[OpenGL Tutorial] Bill-board(빌보드) 기법

사용자 삽입 이미지빌보드 기법이란 사각형이 항상 시점을 바라보도록 만드는 것을 말한다. 결과적으로 관찰자가 어느 위치로 이동하더라도 사각형은 항상 같은 면을 관찰자에게 보여주게 된다. 응용예로써 넓은 대지위에 나무를 나타내고자 할때, 나무를 메쉬(Mesh)를 사용하여 모델링하지 않고 나무 이미지만으로 구현이 가능하다.

구현에 대한 핵심은, 항상 시점을 바라보도록 사각형을 이루는 네 좌표를 조정하여야 하는데, 이를 위해 Model View 행렬을 이용한다. Model-View 행렬에는 관찰자의 시점에 대한 정수직벡터와 정우측벡터에 대한 정보가 담겨있다.

일단 OpenGL에서 Model View 행렬을 얻는 코드를 알아보자.

ViewMatrix에 저장된 Model View 행렬이 다음 그림과 같다고 할 때 벡터(m1, m5, m9)은 시선 벡터와 수직인 위쪽 벡터이고, 벡터(m0, m4, m8)는 시선 벡터와 수직인 오른쪽 벡터이다.사용자 삽입 이미지시선과 위쪽으로 수직인 벡터를 UpVector라 하고 오른쪽 방향으로 수직인 벡터를 RightVector라 하자. 이 두 벡터를 이용하여 사각형을 이루는 4개의 좌표를 조정하기 위해, 사각형의 중심 벡터를 centerVector라고 하면, 사각형을 이루는 4개의 좌표는 다음 그림과 같다. 사용자 삽입 이미지위의 네 좌표를 스칼라값 Size를 적용하면 다음과 같다. Size는 사각형의 크기로 생각할 수 있다.

CenterPoint + (RightVector + UpVector) * (-Size))
CenterPoint + (RightVector -UpVector) * Size
CenterPoint + (RightVector + UpVector) * Size
CenterPoint + (UpVector -RightVector) * Size

실제로 구현 코드를 살펴보자.

위의 소스에서 이미 제작된 vector 클래스를 사용하였다. vector 클래스의 사용은 크게 어렵지 않으므로 10여분정도 투자하여 소스를 분석해보면 쉽게 이해할 수 있을 것이다.

빌보드 기법을 적용하지 않은 경우에 대한 소스 코드를 주석처리하였 놓았다. 주석을 제거하여 실행해 보면서 비교해보기 바란다.

[OpenGL Tutorial] Display List

사용자 삽입 이미지이번장에서는 오랬동안 길게 끌어온 OpenGL의 렌더링 성능을 향상 시켜주는 Display List에 대해 알아보고자 한다. 사실 이 부분을 16장에 위치시키기에는 그 내용이 기본적이긴 하지만 필자 스스로의 혼란을 줄이기 위해 그냥 순서대로 번호를 매겼다. 이 강좌는 1장에 이여 두번째로 NeHe의 Display List를 그대로 번역한다. 이제 시작해 보자.

이 강좌에서는 Display List에 대한 설명이다. 간단한 장면을 연출하고자 할때 Display List를 사용함으로써 속도의 개선 뿐만이 아니라 코드 줄수 또한 줄일 수 있다.

예를 들어서, 소행성 게임을 만들고 있다고 해 보자. 각 레벨은 최소한 2개 이상의 소행성으로 시작된다. 그래서 당신은 3차원의 소행성을 어떻게 만들 것인지 계산한다. 일단 계산이 끝난후 폴리곤이나 Quad를 이용하여 소행성을 만든다. 이 소행성이 여덜개의 면으로 이루어졌다고 해보자. 이렇게 만든 하나의 소행성을 여러개 만들기 위해, 간결하게 반복문을 이용하여 여러개의 소행성들을 그리게 될 것이다. 소행성을 만들기 위해 18줄 이상의 코드를 쳐 넣을 것이다. 소행성 하나 하나를 시스템 상에서 계산하여 매번 소행성을 그려내는 것은 비효율적이다. 소행성이 복잡하면 복잡할 수록 그 효율은 극적으로 떨어지게 된다.

그렇다면 그 해결책은 무엇인가? 바로 Display Lists이다. Display List를 사용함으로써, 소행성을 단 한번만 만들기만 하면 된다. 여기에 텍스쳐 맵핑을 할 수도 있고 색을 입힌다거나 원하는 모든 것들을 적용할 수 있다. 이 Display List에 이름을 부여할 수 있는데, 예를 들어 소행성의 Display List로 생성을 하고 이름을 ‘Asteroid’라고 한후에 나중에 소행성이 필요할때 마다 단지 이미 정해둔 이름인 ‘Asteroid’로써 Display List를 호출하기만 하면 된다. 즉, 앞으로 소행성을 그리고자 한다면 단지 단 한줄의 코드인 glCallList(asteroid)를 호출하기만 하면 된다. 이미 만들어진 소행성은 즉시 화면에 나타나게 된다. 소행성은 이미 Display List에서 모든 계산이 이루어져 생성되었기 때문에 OpenGL은 다시 소행성을 그리기 위해 계산할 필요가 없어진다. 이것은 메모리 상에 이미 계산의 결과를 저장해 놓았기 때문이다. 이것이 바로 왜 Dislpay List를 사용함으로써 속도가 극적으로 향상되는지하는지에 대한 해답니다.

이제 Display List에 대해 배워볼 마음이 생기는가? 🙂 우리는 앞으로 Q-Bert Display List 데모 프로그램을 만들어 볼 것이다. 화면상에 15개의 큐브를 올려 놓아 보는 데모이다. 각각의 큐브는 BOX와 TOP으로 구성되져 있다. TOP은 별도의 Display List로서 만들어질 것인데 이것은 이 것에 좀더 어두운 색으로 연출하고자 함이다. BOX는 윗면이 없는 큐브이다.

이 코드는 강좌 6의 것을 사용한다. 무엇을 전달하고자 하는지를 쉽게 하기 위해 프로그램을 거의 전체적으로 다시 작성할 것이다. 다음의 코드 라인은 대부분의 강좌에서의 표준 코드들이다. (역자주: 강좌 6의 소스 코드를 일단 다운 로드 받아 살펴보기 바란다. 단지 텍스쳐 맵핑에 대한 내용을 다룬 것이다.)

이제, 앞으로 사용할 변수를 선언해 보자. 먼저 하나의 텍스쳐를 저장하기 위한 변수를 선언하고 앞에서 언급한 2개의 Display List를 위한 변수 2개를 선언하자. 이러한 변수는 Display List가 램에서 어디에 저장되어 있는가 하는 지시자로써의 행동한다. 이 2개의 Display List를 BOX와 TOP이라 부를 것이다.

2개의 변수를 선언한 후에 xloop와 yloop라는 변수를 선언하는데 이것은 화면상의 큐브를 위치시키는데 사용된다. 또 xrot와 yrot라는 2개의 변수를 선언하는데 이것은 x와 y축으로 큐브들을 회전시키는데 사용되는 변수들이다.

다음에 우리는 2개의 색상 배열을 생성한다. 첫번째의 boxcol은 밝은 계열의 색상들로 빨간색, 오랜지색, 초록색 그리고 파란색의 색상값을 저장한다. {}안에 지정된 각각의 값은 Red, Green Blue 값을 나타낸다. 각각의 {}의 구룹은 지정된 색이다.
두번째 색상의 배열은 어두운 계열의 색상들로 빨간색, 오랜지색, 초록색 그리고 파란색을 위한 것이다. 어두운 색들은 박스의 윗면을 나타내는데 사용된다. 우리는 박스의 나머지 부분보다 박스의 뚜껑을 좀더 어둡게 하고자 한다.


이제 실제 Display List를 만들어 본다. 이미 짐작했겠지만, 박스를 만들기 위한 모든 코드는 첫번째 List로 하고 뚜껑으로 사용할 박스의 Top은 다른 List에 들어가게 된다. 다음 절에서 더 자세한 내용을 설명하도록 하겠다.

우리는 OpenGL에게 우리가 2개의 List를 필요로 한다고 알리는 것으로 시작한다. glGenLists(2)는 두개의 List를 위한 공간을 생성하고 첫번재 List를 가르키는 포인터를 반환한다. box가 첫번째 List의 위치를 가지게 된다. 언제든지 box를 호출한다면 첫번째 list에 담긴 물체가 그려지게 된다.

List를 위한 공간을 마련했으므로 실제 List를 생성해 보자. 우리는 이미 두개의 List를 위한 메모리 공간을 마련했고 box가 첫번째 list를 저장하는 공간을 가르킨다는 것을 알고 있다. 이제 OpenGL에게 알려할 모든 것은 리스드가 어디로 가야하며 만들어질 리스트가 어떤 타입인가 하는 것들이다.

작업을 하기 위해 glNewList() 명령을 사용한다. 우리는 첫번째 인자가 box라는 것을 알게 될 것이다. 이것은 box가 가르키고 있는 메모리의 위치에 리스트가 저장되어져 있다고 openGL에게 알리는 것이다. 두번째 인자인 GL_COMPILE는 OpenGL에게 우리가 메모리 상에 모든 계산을 미리 해 놓으라는 지시인데 이렇게 함으로써 나중에 다시 불피요한 재계산을 하지 않아도 되도록 하는 것이다.


GL_COMPILE은 프로그래밍과 유사하다. 프로그램을 작성한다면 컴파일러에 적재해야 한다. 코드를 실행하기 위해서 매번 컴파일을 해야 한다. 코드가 이미 실행파일 형태로 컴파일 되어있다면 이후부터 컴파일 할 필요없이 실행파일만을 클릭하여 실행하기만 하면 된다. 컴파일이 필요없는 것이다. 일단 GL이 display list를 컴파일했다면 이미 렌더링 준비가 되어진 것이고 나중에 또 컴파일이 요구되지 않는다. 이것이 바로 display list를 사용함으로써 속도를 향상시키는 이유이다.


코드의 다음부는 뚜껑(top)이 없는 박스를 그리는 것이다. 이것은 화면상에 나타나지 않고 단지 display list상에 저장된다.

glNewList()와 glEndList()사이에 당신이 원하는 코드들을 추가할 수 있다. 색상을 설정하거나 텍스쳐 맵핑을 바꾼다거나 하는 것등. 추가할 수 없는 유일한 코드는 list에 지정된 객체의 형태를 변경시키는 코드이다. 일단 list로 만들어지면 변경할 수 없다.

만약 glColor3ub(rand()%255, rand()%255, rand()%255)의 코드를 아래에 제시한 코드에 추가했다면, 아마도 당신은 화면상에 물체가 그려지는 때마다 이것의 색이 바뀐다고 생각할 것이다. 그러나 이미 리스트를 생성할때 색상이 지정되졌기 때문에 색은 변경되지 않는다. (역자주: 만약 변경하고자 한다면 list를 생성할때 색상을 지정하는 코드를 사용해서는 않된다)

우리는 List를 다 만들었다는 것을 OpenGL에게 알리기 위해 glEndList() 명령을 내린다. glNewList()와 glEndList() 사이의 부분은 Display list의 부분이다. glNewList() 앞의 부분이나 glEndList()의 뒷부분은 현재 display list의 부분과는 상관없는 부분이다.

이제, 두번째 Display list를 만들 것이다. 두번째 display list가 메모리에 저장될 곳을 찾기 위해 우리는 예전의 display list(box)에 1을 추가한다. 아래의 코드는 두번째 display list의 위치와 같은 ‘top’을 만들 것이다.

이제 두번째 display list을 저장할 곳을 알았으므로 뚜껑에 대한 list를 만들 수 있다. 첫번재 list를 만드는 같은 방법을 사용하지만 이번에는 ‘box’대신 ‘top’에 list를 저장한다고 OpenGL에게 알린다.

다음 코드는 단지 박스의 꼭대기를 그려주는 것이다. Z 평면 상의 간단한 사각형이다.

다시 우리는 OpenGL에게 두번째 list를 생성했다고 glEndList() 명령을 이용해 알린다. 바로 아래처럼. 우리는 성공적으로 2개의 display list를 생성했다.

비트맵/텍스쳐를 생성하는 코드는 이전에 강좌의 비트맵을 로딩하고 텍스쳐를 생성하는 코드와 같다. (역자주: Dip2K의 튜터리얼도 NeHe의 텍스쳐 맵핑 방법과 동일하다. 우리는 각각의 큐브의 모든 6면 상에 맵핑할 텍스쳐를 원한다.좀더 부드러운 텍스쳐 맵핑을 위해 Mipmapping을 사용하고자 한다. 필자는 픽셀을 보는 것을 무척 싫어한다. (역자주: 픽셀을 본다는 것은 알리아싱 현상을 의미한다) ‘cube.bmp’라는 비트맵을 로딩한다. data 디렉토리에 이 비트맵 파일이 위치한다. LoadBMP 함수를 찾아서 다음과 같이 수정한다.

폼의 크기가 재조정될때 발생하는 이벤트의 코드는 강좌 6의 코드와 동일하다.

init 코드는 약간의 수정만이 필요하다. BuildList() 코드를 추가한다. 이것은 앞에서 display list를 생성하는 코드를 담고 있다. LoadGLTextures()함수 이후에 BuildList() 를 호출한다는 것에 주의해야 한다. 이것은 BuildList() 함수에서 텍스처를 필요로 하므로 먼저 텍스쳐를 생성해야 한다는 것이다.

다음의 세줄의 코드는 quick & dirty 빛을 활성화 하는 것이다. (역자주: quick & dirty 광원은 비디오 카드에 기본적으로 설정된 광원이다) Light0는 대부분의 비디오 카드에 미리 정의된 것이다. light0을 활성화 함으로써 빛을 사용한다. 만약 독자의 비디오 카드에서 light0가 작동하지 않는다면(화면에 마냥 검다면) 그냥 광원을 꺼라.마지막 라인인 GL_COLOR_MATRIAL은 텍스쳐 맵핑에 색을 추가하도록 해 준다. 이 재질의 컬러링을 가능하지 않게 한다면, 텍스쳐는 항상 원래의 색상으로 나타난다. 즉 glColor3f(r,g,b)가 아무런 효과를 내지 못한다. 그래서 이것을 가능하게 해주는 것이 중요하다.

최종적으로 멋지게 보이도록 하기 위해서 투영에 대한 힌트를 GL에게 제공하고 초기화가 잘되었음을 알리기 위해 TRUE를 반환한다.


이제 그려주는 코드에 대한 것이다. 일단 화면과 Depth Buffer를 청소하는 일부터 시작한다.

그 다음에 큐브에 텍스쳐 맵핑을 바인드한다. Display list 안에 이 텍스쳐 맵핑을 추가할 수 있었으나 그 밖으로 빼 놓았다. 이것으로써 원한다면 언제든 텍스쳐 맵핑을 변경할 수 있는 것이다. (역자주: 이미 앞에서 언급한대로 List를 만들때 색상이라든지 텍스쳐 맵핑을 지정하면 나중에 그 List의 객체의 색상, 텍스쳐 맵핑을 변경할 수 없다)

이제 재미있는 것에 대해 살펴보자. yloop라 불리는 반복문이 있다. 이 반복문은 큐브들의 Y 축상(위에서 아래)에 위치시키는데 사용된다. 위에서 아래로 5개의 큐브를 원하므로 6보다 하나 작은 반복을 한다(즉, 다섯번)

xloop라는 다른 반복문이 있다. 이것은 X 축상(왼쪽에서 오른쪽)에 큐브들을 위치시키는데 사용된다. 왼쪽에서 오른쪽으로 그리고자 하는 큐브는 정해져 있지 않다. 만약 가장 위쪽의 줄이라면 xloop는 0에서 1까지의 xloop 반복이면 된다. 그리고 그 다음 줄의 xloop는 0에서 2가 될것이다(즉, 2개의 큐브가 그려져야 한다) (역자주: 계속 하나씩 증가한다, 실제 결과 프로그램을 실행하여 살펴보면 확실히 알수있다)

glLoadIdentity()를 호출함으로써 View를 초기화한다.

다음은 화면상의 지정된 지점으로 이동시키는 코드이다. 이것은 다소 헤깔리지만 실제로는 그렇지 않다. X축 상에서, 다음의 내용이 이뤄진다 :

큐브들이 모여 이뤄진 피라미드가 화면상의 중심에 놓여지도록 오른쪽으로 1.4 유닛만큼 이동한다. 그 다음에 xloop에 2.8을 곱한후 다시 여기에 1.4를 더한다. 마지막으로 yloop*1.4를 뺀다. 이것은 큐브들을 그들이 위치한 줄에 따라 왼쪽으로 이동하게 된다.

Y축상에서 6에서 yloop를 뺌으로써 아래 방향으로 피라미드가 만들어진다. 다음에 그 결과에 2.4를 곱한다. 큐브들은 y축 상의 각각의 꼭대기 상에 놓여진다. 다음에 7을 빼는데 이것은 피라미드가 화면의 아래쪽에서 시작하고 윈쪽으로 만들어지게 한다.

최종적으로 Z축 상에서 화면 안쪽으로 20 유닛 이동한다. 이것은 피라미드를 멋지게 만든다.

이제 x축 상으로 회전시켜 보자. 우리는 큐브를 뷰쪽으로 45도에서 yloop*2를 뺀 상태로 기울인다.. 퍼스펙티브 모드는 자동으로 큐브들을 기울이므로 이 기울어짐을 제거 했다. 이것은 최선은 아니지만 원하는대로 작동한다. 🙂

최종적으로 xrot를 더한다. 이것은 키보드를 통해 각을 조정할 수있도록 한다.

x축으로 회전한후에 y축상으로 45도 회전하고 yrot을 더하는데 이것은 키보드를 통해서 yrot값을 조정할 수 있다.

다음에 큐브들이 위치한 줄에 따라 달리 색상을 지정한다.

이제 박스에 대한 List를 호출한다.

큐브의 꼭대기에 놓여질 뚜껑의 색상을 지정한다.

뚜껑에 대한 List를 호출하고 모든것이 잘되었으므로 TRUE를 호출한다.

이제 남은 코드들은 WinMain 함수에서 이뤄지는데 다음은 키보드에 대해 xrot와 yrot의 값을 변경해 주는 키보드 입력 처리 코드이다. SwapBuffer(hDC) 코드 다음에 놓여진다.

<변역 끝>

변역을 여기서 끝마친다. 사실 이 강좌는 Display List에 대한 장이다. Display List에 대한 사용법이 쉽다는 것을 알 수 있다. 필자는 이 강좌를 번역할때 List를 제작하고 사용하는 방법에 대해서는 최대한 모든내용을 번역했지만 후반부의 큐브를 화면상에 피라미드 모양으로 위치시키는 세세한 내용은 그냥 술~ 술~ 넘기며 번역했음을 고백한다.