선 그리기

지난 시간에 우리는 OpenGL에서 어떻게 다각형을 이루는 기본 요소들을 살펴보았다. 실제로 OpenGL은 점,직선,다각형, 조그만 삼각형 내지 사각형들의 집합으로 이루어진 면등 지원하는 기본 형상객체가 몇개되지 않는다.

이런 OpenGL의 단순함에 내포된 의미는 결국 이러한 간단한 객체들을 이용하여 복잡한 기하학적 모델을 만드는 것은 개발자에 달려있다는 것이다. OpenGL에는 점,선,다각형들을 자세히 다룰수 있는 많은 명령어들이 존재한다.

만약 점의 크기를 정해주고 싶다면 glPointSize명령을 이용하면 된다.:


void glPointSize(GLfloat size)

점의 크기는 기본적으로 1.0으로 주어져 있으며, 위 함수에서 size는 반드시 0보다 커야 한다. 점의 크기를 실수형으로 설정한다는 점을 기억하자. 점과 선의 크기를 분수꼴로 나타내는 것 역시 가능하다. OpenGL은 렌더링 컨텍스트에 따라 분수형으로 주어진 점의 크기를 해석해서 실행한다. 만약 안티-앨리어싱(Anti-aliasing)모드를 켜두었다면 OpenGL은 분수형 폭을 나타내기위하여 약간의 수정이 필요한 선이라 의심되는 부분을 찾아 인근의 픽셀을 보고 수정을 한다.안티앨리어싱은 저해상도의 모니터에서 직선을 보여줄때 발생하는 끊긴 계단모양을 제거해 주기 위하여 사용되어지기도 하는 기법이다. 만약 안티앨리어싱모드가 꺼져있다면 glPointSize에서 준 점/선의 크기(size)는 반올림되어 가까운 정수로 바뀐다.

점의 실제 크기는 장치에 따라 다르다. 따라서 낮은 해상도의 모니터에서 보면 점이 더 커보이게된다. 그러나 플로터와 같이 매우 높은 해상도를 가진 장치에서 기본값 1로 설정된 선을 그린다면 거의 볼 수 없을정도의 두께를 가진 선이 그려진다. 실제로 그려지는 선의 두깨를 알고싶다면 여러분은 먼저 출력장치의 정확한 사양과 해상도를 알고 있어야 한다.

선의 굵기는 glLineWidth함수에서 설정할 수 있는데, 이 함수는 선을 그리는 부분이 들어있는 glBegin() - glEnd() 이전에 호출되어야 새로운 굵기가 적용되어 그려진다. 사용법은 이렇다.:


void glLineWidth(GLfloat width)

OpenGL의 실행에서 안티앨리어싱되지 않는 선의 두께로 정할 수 있는 범위는 최대 안티앨리어싱선의 두께까지로 제한되어 있기때문에 반올림되어 비슷한 정수값이 사용된다. 선의 두께는 직선의 수직방향폭을 나타내는 것이 아니다. 만약 기울기가 1보다 작다면 y축방향이 크기가 되며 반대로 1보다 크다면 x방향의 기울기의 절대값이 선의 크기가 된다는 것을 명심하자.

이번달에는 간단하지만 유용한 2차원 애니메이션프로그램을 작성해보자. 이 프로그램은 여러분이 작성하는 OpenGL프로그램에서 다양한 두께를 가진 선을 어떻게 사용하는지 이해하는데 도움이 될 것이다. (example2.c, Makefile). 나는 먼저 양자물리학책에서 예제를 찾았다. 물리학책을 보면 "a quantum particle trapped in a double well potential."라는 말이 나온다. 왜 그렇게 되냐면...음 나도 잊어버렸다. 어쨌든 물리학을 전공하는 학생들에게 슈뢰딩거 방정식에서 시간이 증가할때마타 어떻게 값이 변하는가를 보여주는데 유용한 그림을 그릴 수 있다. 이분야를 전공하지않는 사람들은 그냥 쉽게이해되지 않는 양자역학의 속성을 살펴보면 된다. 양자역학에서 입자는 위치와 속도로 표현되는 것이 아니라 양자파라는 파형으로 나타낸다.우리가 작성한 애니메이션에서 보라색 실선으로 나타낸 부분이 양자파를 나타낸다. 이것은 주어진위치(하얀 점선)에서 입자를 관측할 수 있는 확률값의 제곱값을 의미한다.:

[Click here to see the image]
그림 1. 양자 시뮬레이션 스냅샷

상미분방정식을 풀때 이루어지는 몇몇 과정들은 파동방정식이 고속 퓨리에변환 스플릿연산자 방법을 사용하여 적분된다. 이 방법은 다른 무한미분법에 비해 빠르고 정확한 장점이 있다. 이 방법은 비선형 파동 확산에도 이용할 수 있다;시간증가를 초단위로 하도록 하고(또는 더 높은 단위로 할 수도 있다) 위치나 모멘텀(주파수)에만 의존하게 한다면 파동함수는 위치와 모멘텀(주파수)공간사이를 왔다갔다하면서 바뀌는 이 연산자를 적용하여 시간에 연속적인 것을 볼 수 있을 것이다.

소스코드의 주요부분은 다른 많은 응용프로그램에서도 사용될 수 있다. 여러분은 필자가 작성한 시간의존함수에 따라 이루어지는 양자시뮬레이션을 지워버리면 더 나은 애니메이션을 여러분의 컴퓨터에서 볼 수 있을 것이다. 또한 그래프를 그리는 함수나 데이터파일을 읽어서 그래프를 그려주는 간단한 OpenGL기반의 gnuplot도 작성할 수 있을 것이다.

여러분이 계속 GLUT와 OpenGL에 관한 연재기사를 읽어왔다면 이 소스코드는 매우 간단하고 이해하기 쉬울것이다.(물론 양자역학부분은 제외한다.) 소스에 특별한 함수나 특이사항은 눈에 띄지 않는다.main()에서 우리는 이중버퍼모드로 단일윈도우를 연 다음 파동함수를 그리고 각각의 파동방정식을 통합하는 작업을 담당하는 display()idle()함수를 전달한다. 다시한번 idel()함수에 어떤 기능이 들어갈지 생각해보자. 이 글의 내용을 이해하기위해 다른 모든것(양자역학적 지식등등)을 알고있어야 하는 것은 아니다. 정말 새로운 OpenGL의 기능은 display 콜백함수이다:


맨처음 한 작업은 컬러버퍼비트를 지우는 작업이다. 이렇게 하면 깨끗한(검은) 그림판을 우리는 얻게 된다. 그 다음 glRasterPosglutBitmapCharacter함수를 이용하여 각주를 단다. (drawstring은 glut유틸리티를 위한 포장지(wrapper)에 불과하다) 다음 강좌에서 다시 자세히 살펴볼 glRasterPos함수는 텍스쳐 렌더링을 위한 부가함수이다. OpenGL이나 GLUT는 그래픽 윈도우에 글자를 렌더링하는 간단하면서 강력한 방법을 제공해 주지 않는다. The glutBitmapCharacter함수는 글꼴 비트맵에서 컬러 버퍼로 기본적인 래스팅작업을 수행한다.

주석을 달고난 다음 외각사각형, 배경화면 격자, 좌표축등 많은 줄을 그리는 부분이 온다. 물론 현재 곡선은 psiDrawpotentialDraw에서 그려진다. 직선을 그리기 전에 그리고자 하는 선의 두께를 픽셀단위로 정해주는 glLineWidth함수를 사용하였다. 그림 1은 리눅스알파머신의 X윈도우 시스템에서 출력된 화면이다. 이 프로그램을 윈도우 95에서 컴파일해서 실행시켰었는데 많은 시간이 걸렸다. 왜 그런지 필자가 도무지 알아낼수 없었다. 아마도 SGI OpenGL드라이버에서 안티앨리어싱기능을 지원해주지 않기때문이 아닐까싶다. 원리적으로 서로 다른 폭으로 선을 구분하는 것은 매우 힘들다. 그리고 배경의 격자선은 같이 그려져야 한다. 이런 결함들은 높은 해상도에서 그릴때 나타나는데 낮은 해상도를 가지는 모니터때문에 생긴것은 아니다. 어쨌든 윈95/NT에서 보다 리눅스의 X윈도우시스템에서 성능이 더 뛰어났었다는 점을 다시 한번 말해둔다.

display() 함수에서 선을 그리는 두가지 방식이 있다. GL_LINES모드는 선을 이루는 두 정점을 계속 입력하는 방식이며 GL_LINE_LOOP모드는 마지막에 폐루프을 형성한다.

안티앨리어싱기능

reshape() 콜백함수에 선을 그릴때 안티앨리어스모드도 가능하도록 하였다.

void
reshape (int w, int h)
{
  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
  glViewport (0, 0, w, h);
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  gluOrtho2D (-1.2, 1.2, -1.2, 1.2);
  glEnable (GL_LINE_SMOOTH);     /* 안티앨리어싱 된 직선을 그릴수 있게 한다.*/
  glEnable (GL_LINE_STIPPLE);
};

GL_LINE_STIPPLE은 무엇인가? OpenGL은 직선의 두께뿐만 아니라 모양까지도 결정할 수 있게 해준다. GL_LINE_STIPPLE 을 가능하게 하여 우리는 점선이나 다른 모양의 직선을 그릴 수 있다. 애니메이션에는 psiDraw()함수에서만 점선을 사용하였다.:

  glLineWidth (1);
  glPushAttrib (GL_LINE_BIT);
  glLineStipple (3, 0xAAAA);
  glBegin (GL_LINE_STRIP);
  for (i = 0; i < nx; i++)
    {
      xs = ratio1 * (x[i] - XMIN) - 1.0;
      ys = ratio2 * (psi[2 * i] - YMIN) - 1.0;
      glVertex2d (xs, ys);
    };
  glEnd ();
  glPopAttrib ();

선 모양바꾸기

glLineStipple은 점선모양을 결정해주는 함수인데, 이 글에 사용된 예제에서는 0xAAAA패턴을 사용하였다. 이 값을 이진수로 변환하면 0000100010001000이 되는데 OpenGL은 이를 3비트는 꺼져있고, 한비트 켜지고, 3비트 꺼져있고, 1비트켜지고, 3비트 꺼지고 1비트켜지고, 4비트꺼진 식의 점선을 그린다. 그렇다. 이 선모양은 뒤에서부터 읽는다. 뒤에서 부터 읽는 이유는 낮은차의 비트를 먼저 사용하기때문이다. 이제 glLineStipple은 두개의 파라메터가 설정되었다. 하나는 모양을 알려주는 16진수값이고 하나는 이러한 패턴을 그려줄때 사용되는 비율을 알려주는 정수가 주어진다. 즉 예제에서는 3이라 주었으므로 패턴이 다시 변화되어 9칸꺼지고, 3칸 켜지고, 9칸 꺼지고, 3칸 꺼지고, 9칸 꺼지고, 3칸 켜지고 마지막에 12칸 꺼지는 그런 점선을 그리게 된다. 이 값과 이진수로 표현되는 패턴을 가지고 여러분은 모든종류의 점선을 그릴 수 있을 것이다.

조금더 자세히 말해둘것이 있다.: 필자는 점선렌더링을 푸시(push)와 팝(pop)속성행사이에 두었다. 여러분은 이 글을 처음 연재할때 OpenGL은 상태머신이라는 말을 본 기억이 있는가? 아마 다음호에 우리는 이 푸시와 팝조작에 관하여 더 자세히 살펴볼 것이다. 그러나 간단하게 말하자면 처음에 glPushAttrib (GL_LINE_BIT)에서 하는 작업은 GL_LINE_BIT의 현재값(이 값이 점선모양을 결정한다)을 스택에 쌓는다. 그리고 나서 glLineStipple 함수를 이용하여 GL_LINE_BIT의 값을 바꾼다. 그 다음 우리가 실제 하고자 하는 작업을 수행한 다음 glPopAttrib 함수를 실행히켜 이전의 GL_LINE_BIT변수값을 다시 가져온다. 이러한 방식은 OpenGL머신의 상태변수값을 부분적으로 바꾸고자 할때 효율적이게 된다. 만약 이렇게 하지 않는다면 선을 그리고 나서 다시 이전의 선모양으로 바꾸어주기 위하여 매번 이전과 동일한 선모양으로 glLineStipple 함수를 호출해주어야 한다. 푸시와 팝기능은 이런 번거로운 작업을 덜어준다.

다음번에는....

OpenGL은 환상적인 3차원 API인터페이스로 알려져있다. 지금까지 우리는 OpenGL을 가지고 2차원 렌더링의 기본적인 기능을 살펴보았다. 다음번에는 3차원 OpenGL프로그램을 만들어 보면서 관조법은 어떻게 하고 시스템의 좌표와 클리핑영역과 투사행렬을 설정하는지 살펴보기로 하겠다.

다음연재까지 OpenGL을 가지고 재미있게 놀기 바란다.......

Posted by 코딩하는 야구쟁이
,