'OpenGL'에 해당되는 글 2건

  1. 2009.04.24 OpenGL Viewing
  2. 2009.04.22 메시지 크래커(Message Cracker)
프로그래밍/OpenGL2009. 4. 24. 14:03

뷰잉

I. 개요

A. 뷰잉 변환: 카메라가 Scene을 향하도록 설정한다

B. 모델링 변환: 원하는 장면을 화면에 담도록 모델을 구성한다

C. 투영 변환: 사용할 카메라 렌즈를 선택하거나 줌을 조절한다

D. 뷰포트 변환: 사진의 크기를 결정한다

E. 연산들의 순서: 뷰잉 변환이 모델링 변환보다 먼저 수행되도록 작성해야 하며, 투영 변환이나 뷰포트 변환은 드로잉 작업이 수행되기 전에 어느 곳에 나와도 상관없다.

1. 오브젝트 좌표 -> 모델뷰행렬 (눈 좌표) -> 투영행렬 (클립 좌표) -> 투시분할 -> 정규화 장치 좌표 -> 뷰포트 전환(윈도우 좌표)

II. 변환 행렬

A. 컴퓨터 그래픽을 이해하기 위해서는 가장 핵심적이고 기본이 되는 4x4 행렬 형식의 변환을 설명하는 것으로부터 시작된다.

B. 기본변환

1. 이 절에서는 평행 이동, 회전, 크기 같은 기본변환들을 알아본다

C. 평행이동(Translation)

1. 한 위치를 다른 위치로 변경하는 것은 평행이동 행렬 T로 표현된다. 이 행렬은 하나의 벡터 t=(tx,ty,tz)를 이용하여 개체를 이동시킨다.

2. T(t) = T(tx,ty,tz) = ( 1 0 0 tx )

( 0 1 0 ty)

( 0 0 1 tz)

( 0 0 0 1)

  



D. 회전(rotation)

1. 회전 행렬은 Rx(ɸ), Ry(ɸ), Rz(ɸ)에 의해 표시된다.

2. 이 행렬들은 하나의 개체를 x, y, z축으로 ɸ라디안 만큼 회전시킴을 의미한다.

Rx(a)= 

  Ry(a) =
 
Rz(a) =

E. 크기 조정(scaling)

1. 크기 조정 행렬 S(s) = S(x,y,z)는 한 개체를 x,y,z방향으로 각각 x배, y배, z배만큼 확대 축소한다.

2. 조정행렬

  


3. 만약 x = y = z 이면 크기 조정 연산은 균등(uniform)하다고 하고 그렇지 않은 경우는 비균등(nonuniform)하다고 한다.

F. 예제

1. 지금까지 각각의 변환 행렬들에 대해서 살펴보았다. 그렇다면 어떤 한 점을 어느 지점으로 이동 시킨 후에 다시 어떤 축을 중심으로 회전하고 크기를 변경시키고자 할 때 행렬이 어떤식으로 계산되어 지는지를 알아보도록 한다

2. p = (3,2,1)의 점을 X축으로 -1만큼 이동 후 z축으로 90 회전시킬때의 행렬

   =>  
 

3. 위 행렬은 아래와 같이 계산되어 질 수도 있음


G. 본 절에서 설명된 변환 행렬 이외에도 쉬어변환 강체 변환, 법선 벡터 변환등 다양한 행렬 표현이 존재한다

III. 뷰잉 및 모델링 변환

A. 고정 좌표계

1. 모델의 위치, 방향, 크기 등에 영향을 미치는 행렬 곱셈을 고정 좌표계 관점에서 생각하는 경우에는 코드에 나온 순서와 반대로 곱셈이 수행된다는 것을 명심해야 한다.

B. 로컬 좌표계 이동하기

1. 행렬 곱셈을 바라볼때 변환할 오브젝트가 고정 좌표계에 있지않고, 오브젝트에 로컬 좌표계가 달려 있는 것처럼 생각할 수 있다. 모든 연산들은 이러한 좌표계에 상대적으로 수행된다.

2. 모델링 변환

a) glTranslate{fd}(TYPE x, TYPE y, TYPE z);

(1) 인자로 지정된 x,y,z 값 만큼 오브젝트(또는 로컬 좌표계)를 평행 이동 시키도록 지정된 행렬과 현재 행렬을 곱한다.

b) glRotate{fd}(TYPE angle, TYPE x, TYPE y, TYPE z);

(1) 원점에서 x,y,z에 이르는 선을 기준으로 오브젝트(또는 로컬 좌표계)를 반시계 방향으로 회전시키는 행렬과 현재 행렬을 곱한다. angle 매개변수에는 회전할 각도를 지정한다

c) glScale{fd}(TYPE x, TYPE y, TYPE z);

(1) 오브젝트를 각 축을 따라 늘이거나 , 대칭이동 행렬과 현재 행렬을 곱한다. 오브젝트를 구성하는 점의 x,y,z 좌표는 인자로 주어진 x,y,z 값과 곱해진다.

d) glMultiMatrix

C. 뷰잉 변환

1. 뷰잉 변환을 사용하면 시점의 위치와 방향을 변경할 수 있다.

2. glTranslate와 glRotate 사용하기

a) 카메라는 초기에 z축의 음의 방향을 향하고 있다는 점에 주의

b) 오브젝트를 볼 수 있게 하기 위해서는 몇가지 변환을 수행해야 한다.

c) 시점을 오브젝트 뒤로 이동시킬 수 있다.

3. gluLookAt 유틸 사용하기

a) void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery,GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz);

b) 이 행렬은 뷰잉 행렬을 정의한 뒤 이를 현재 행렬의 오른쪽에 곱한다. 원하는 시점은 eyex, eyey, eyez로 지정한다. centerx, centery, centerz인자로 원하는 시선에 존재하는 임의의점을 지정할 수 있지만, 일반적으로 바라보는 장면의 가운데 지점을 지정할 때 사용한다. upx, upy, upz 인자는 위쪽에 해당하는 방향을 가리킨다

IV. 투영 변환

A. 기본형식

1. glMatrixMode(GL_PROJECTION)

2. glLoadIdenty();

B. 원근투영

1. 원근 투영의 가장 큰 특징은 바로 포쇼트닝(foreshortening, 단축법, 오브젝트가 카메라로부터 멀리 떨어질수록 작게 그리는 기법)이다. 이러한 효과는 관측 공간을 피라미드의 절두체로 정의할 때 나타난다.

2. 절두체를 정의하는 glFrustum() 커맨드는 먼저 원근 투영을 나타내는 행렬을 계산한 다음, 그 결과를 현재 투영 행렬과 곱한다.

3. glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);

a) 원근 형태의 절두체를 나타내는 행렬을 생성하고, 이를 형재 행렬과 곱한다. left, bottom, -near과 right, top, -near은 각각 카메라와 가까운곳에 위치한 클리핑 평면의 좌측 하단 모서리와 우측 상단 모서리 좌표를 나타낸다. near와 far인자는 각각 시점과 가까이 있는 클리핑 평면과 멀리 떨어진 클리핑 평면까지의 거리를 나타낸다.


b) gluPerspective(GLdouble 래표, GLdouble aspect, GLdouble near, GLdouble far);

(1) 원근 대칭 절두체를 나타내는 행렬을 생성하고 이를 현재 행렬과 곱한다. fovy 인자는 yz 평면상의 FOV각도를 나타내며, 이 값은 [0.0, 180.0]을 벗어날 수 없다. aspect 인자는 절두체의 종횡비를 나타낸다. near과 far는 각각 시점과 두 클리핑 평면 사이의 거리를 나타내며 반드시 양수로 지정되어야 한다.

C. 직교 투영(orthogonal projection)

1. 평행 육면체 모양의 관측 공간을 사용한다

2. 원근 투영과 달리 관측 공간의 양 끝면의 크기가 일정하기 때문에 카메라 거리가 오브젝트의 모양에 영향을 미치지 않는다

3. 건축 설계도나 CAD 설계등과 같이 오브젝트의 크기와 각도를 정확히 유지해야 하는 애플리케이션에서 주로 사용된다.

4. glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);

a) 직교 평행 관측 공간에 대한 행렬을 생성하고 이를 현재 행렬과 곱한다.


V. 뷰포트 변환

A. 뷰포트는 이미지가 그려질 윈도우의 직사각형 모양의 영역을 뜻한다

B. 윈도우 좌표로 측정됨, 이들은 윈도우의 좌측 하단 모서리를 기준으로 나타낸 스크린상의 픽셀 위치로 표현한다.

C. 관측공간을 벗어난 정점들은 모두 클리핑 된다.

D. 뷰포트 정의하기

1. 기본적으로 스크린 상에 윈도우를 생성하는 작업은 윈도우시스템이 담당한다

2. 그러나 기본적으로 윈도우를 처음 생성할 때 전체 윈도우에 해당하는 픽셀 영역을 뷰포트로 설정하도록 되어있다.

E. void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);

1. 최종 이미지가 매핑될 윈도우상의 픽셀 사각형을 정의한다.

2. (x,y)매개변수는 이러한 뷰포트의 좌측 하단 모서리를 나타낸다

3. width와 height는 뷰포트의 크기를 지정한다.

 

※ 다시한번 아래그림을 보면서 행렬과 좌표의 변화를 단계별로 생각해보자!


Reference :http://www.gisdeveloper.co.kr/opengl_tutorials/opengl15/15.htm

Sample Code

void init(void)

{

glClearColor(0.0, 0.0, 0.0, 0.0);

glShadeModel(GL_FLAT);

}

void display(void)

{

glClear(GL_COLOR_BUFFER_BIT);

glColor3f(1.0, 1.0, 1.0);

glLoadIdentity();

gluLookAT(0,0, 0.0, 5, 0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

glScalef(1.0, 2.0, 1.0);

glutWireCube(1.0):

glFlush();

}

  

void reshape(int w, int h)

{

glViewport(0, 0, (GLsizei) w, (GLsizei) h);

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

glFrustum(-1.0, 1.0, -1, 0, 1.0, 1.5, 20.0);

glMatrixMode(GL_MODELVIEW);

}

  

int main(int argc, char** argv)

{

glutInit(&argc, argv);

glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB);

glutInitWindowSize(500,500);

glutInitWindowPosition(100,100);

  

gluitCreateWindow(argv[0]);

init();

glutDisplayFunc(display);

glutReshapeFunc(reshape);

glutMainLoop();

return 0;

}

int DrawGLScene(GLvoid)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glLoadIdentity(); //<1>

glTranslatef(0.0f, 0.0f, -22.0f); //<2>


glPushMatrix(); //<3>

glRotatef(o1_rot, 0.0f, 1.0f, 0.0f); //<4>

gluQuadricDrawStyle(obj, GLU_FILL); //<5>

glColor3f(0.9f, 0.9f, 0.9f); //<5>

gluSphere(obj, 2.0f, 24, 24); //<5>

gluQuadricDrawStyle(obj, GLU_LINE); //<5>

glColor3f(0.6f, 0.6f, 1.0f); //<5>

gluSphere(obj, 2.1f, 24, 24); //<5>

glPopMatrix(); //<6>


glPushMatrix(); //<7>

glRotatef(o2_rot1, 0.0f, 1.0f, 0.0f); //<8>

glTranslatef(distance, 0.0f, 0.0f); //<9>

glRotatef(o2_rot2, 0.0f, 1.0f, 0.0f); //<10>

gluQuadricDrawStyle(obj, GLU_FILL); //<11>

glColor3f(0.9f, 0.9f, 0.9f); //<11>

gluSphere(obj, 0.7f, 12, 12); //<11>

gluQuadricDrawStyle(obj, GLU_LINE); //<11>

glColor3f(1.0f, 0.6f, 0.6f); //<11>

gluSphere(obj, 0.75f, 12, 12); //<11>

glPopMatrix(); //<12>


o1_rot+=0.5f; if(o1_rot>359.5f) o1_rot=0.0f; //<13>

o2_rot1+=0.05f; if(o2_rot1>359.95f) o2_rot1=0.0; //<13>

o2_rot2+=2.0f; if(o2_rot2>358.0f) o2_rot2=0.0; //<13>


return TRUE;

}

i) <1>번 코드는 변환 행렬을 단위 행렬로 초기화 시켜주는 예이다.

ii) <2>번 코드는 이동 변환 행렬을 이용해서 좌표축을 Z축으로 -20만큼 이동한 것이다. 이렇게 이동한 이유는 우리가 OpenGL에서 물체를 바로보는 눈의 위치가 (0,0,0)에 있기 때문이다. 만약 이러한 이동이 없이 물체를 그려준다면 물체의 위치와 눈의 위치가 같게 되어서 물체가 보이지 않으므로 물체를 뒤쪽으로 이동시켜야 그려줌으로써 물체가 잘 보이도록 해주는 것이다.

iii) <3>번 코드에서 <6>번 코드까지 지구를 그려주는 것이다. 지구는 제자리에서 자전만 한다.

iv) <3>번 코드는 지금까지 변환행렬에 의해서 변환된 좌표축을 저장해 놓는 것이다.

v) <4>번 코드는 y축을 기준으로해서 회전을 o1_rot 각 만큼 회전을 시키는 것이다. 결과적으로 <2>번 코드에서의 이동 변환 행렬과 <4>번 코드의 회전 변환 행렬의 연산으로 좌표축이 변경될 것임을 알수있다.

vi) <5>번 코드들은 지구를 그려주는 코드들이다. (자세한 설명은 Quadric 객체를 설명한 장을 참고하기 바란다)

vii) <6>번 코드는 <3>번 코드에서 저장해둔 좌표축을 꺼내는 것인데 이렇게 함으로써 <4>번 코드에 의해 변환되기 이전의 좌표축으로 되돌릴수있다.

viii) <7>부터 <12>번까지는 달을 그려주는 것이다. 달은 지구를 중심으로 공전하며 또 스스로 자전한다.

ix) <7>번 코드 다시 변환행렬을 스택에 저장한다.

x) <8>번 코드는 y축을 기준으로 회전을 o2_rot1 각 만큼 하게 된다.

xi) <9>번 코드는 x축으로 distance만큼 이동을 하게된다. 결과적으로 <8>과 <9>번 코드에 의해서 달은 지구를 중심으로 공전을 하게되는 것이다.

xii) <10>번 코드는 y축을 기준으로 o2_rot2 각 만큼 회전하게 된다. 이 코드로써 달이 자전 하게 된다.

xiii) <8>, <9>, <10>번의 변환행렬의 순서는 중요하다. 순서가 바뀔 경우 우리가 원하는 동작과 위치는 잘못될 것이다.

xiv) <11>번 코드는 달을 그려주는 코드들이다. (자세한 설명은 Quadric 객체를 설명한 장을 참고하기 바란다)

xv) <12>번 코드는 다시 <7>번 코드에 의해서 저장된 변환 행렬을 꺼내는 것이다.

xvi) <13>번 코드들은 지구와 달의 회전각들을 일정하게 증가시켜주는 코드들이다.

xvii) 이렇게 정리를 하면 되겠다. 물체 마다 하나씩의 지역 좌표계를 둔다는 것이다. 즉 PushMatrix와 PopMatrix 사이에서 물체의 변환 행렬을 정의해주면 다른 물체에는 전혀 영향을 받지 않으므로 각 물체마다 독립적으로 생각해줄 수 있다.

 

 

 

 

 

 

'프로그래밍 > OpenGL' 카테고리의 다른 글

Texture Mapping  (0) 2009.04.29
옥트리(octree)  (0) 2009.04.25
OpenGL 기초  (1) 2009.04.23
Picking 사용하기  (0) 2009.04.22
void glutIdleFunc(void (*func)(void))  (0) 2009.04.22
Posted by 마블(이환문)

<고전적인 메시지 처리>
◈ 윈도우 프로시저의 switch-case문
문제점
- 메시지가 많아지면 case가 많아져 코드가 길어진다. 가독성이 떨어지고 자원이 낭비된다
- case에서 사용하는 변수가 많아지면 프로시저가 호출될 때마다 스택의 낭비가 심하고 불필요한 메모리를 소모한다

<개별 메시지 처리 함수>
- 메시지 처리 코드는 처리 함수를 만들고 WndProc안에서는 함수 호출만 한다
- 메시지 처리 함수를 메시지 핸들러라 한다
- 가독성이 높다
- 함수 내부 수정이 용이하며 다중 루프 탈출도 편하고 코드 재사용성이 높다

<메시지 크래커>
- WndProc에 들어갈 함수 호출문과 메시지 처리 함수의 원형을 매크로로 잘 정의해 놓은 것

<Windowsx.h>
- 메시지 크래커는 컴파일러가 지원하는 것도 아니고 C언어 명세에 있는 것도 아니다
- Windowsx.h 헤더 파일에 정의되어 있는 단순한 매크로 구문이다
HANDLE_MSG(윈도우, 메시지, 처리함수)
- 윈도우에서 발생하는 메시지와 처리 함수를 짝짓기한다
windowsx.h
#define HANDLE_MSG(hwnd, message, fn) case (message) : return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
- HANDLE_##message 매크로 함수는 메시지별로 정의되어 있다
CF)##은 치환후 주변의 공백과 함께 스스로 제거된다
메시지 크래커 적용
- WndProc에서 처리하고자 하는 메시지에 대해 HANDLE_MSG 매크로 구문을 삽입한다. 처리하고자 하는 메시지와 함수명을 짝짖기 하면 된다
- 함수의 본체를 만든다. 메시지 처리 함수의 원형은 windowsx.h에 주석으로 기록되어 있다
- 핸들러 함수의 원형을 선언한다. windowsx.h를 포함시킨다
 
 
 
=================================================
 
예제 코드
 
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
       HANDLE_MSG(hwnd, WM_SIZE, OnSize);
       HANDLE_MSG(hwnd, WM_PAINT, OnPaint);
       HANDLE_MSG(hwnd,WM_DESTROY, OnDestroy);
       return DefWindowProc(hwnd, message, wParam, lParam);
}
 
void OnSize(HWND hwnd, UINT state, int cx, int cy)
{
       g_cx = cx;
       g_cy = cy;
}
 
void Onpaint(HWND hwnd)
{
       HDC       hdc;
       PAINTSTRUCT   ps;
 
       hdc = BeginPaint(hwnd, &ps);
 
       char  szString[30];
       wsprintf(szString, "너비 : %d, 높이 : %d", g_cx, g_cy );
       TextOut(hdc, 0, 0, szString, lstrlen(szString) );
 
       EndPaint ( hwnd, &ps );
}
 
void OnDestroy(HWND hwnd)
{
       PostQuitMessage(0);
}
 
갈색으로 된 부분을 보면  윈드프록 함수를 간결하게 그리고 메시지 처리를 함수로 빼어 내서
훨씬 알아보기도 쉽고 사용하기 편하게 보입니다.
 
 
HANDLE_MSG 매크로는 windowsx.h 파일에 정의 된 매크로인데
 
#define HANDLE_MSG(hwnd, message, fn) \ case (message)" return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
이렇게 정의 되어 있습니다
 
HANDLE_MSG(hwnd, WM_SIZE, OnSize); 함수가 호출 되면 'message' 에 WM_SIZE가 전달이
되어
 
 case (WM_SIZE) : return HANDLE_##message((hwnd),(wParam), (lParam), (OnSize))
 
가 호출이 되게 됩니다.
##연산자는  #왼쪽과 ## 오른쪽을 연결해서 하나의 문자열로 만드는데 'message'에 WM_SIZE가 호출 되었으므로 결국
 
case (WM_SIZE): return HANDLE_WM_SIZE((hwnd), (wParam), (lParam), (OnSize))
가 되게 됩니다.
 
결국 HANDLE_MSG(hwnd, WM_SIZE, OnSize);  함수가 실행 되면
 
windowsx.h 파일내에 있는
 
#define HANDLE_WM_SIZE(hwnd, wParam, lParam, OnSize) \
             OnSize(hwnd, wParam, LOWORD(lParam), HIWORD(lParam)), 0L
이 호출 됩니다.
 
좀복잡했지만 정리하면
 
 
 
HANDLE_MSG(hwnd, WM_SIZE, OnSize);
이 코드는 바뀌고 바껴서
 
case WM_SIZE:
       OnSize(hwnd, wParam, LOWORD(lParam), HIWORD(lParam));
       return 0;
 
이렇게 바뀌게 되는 겁니다.  결국 저 간단한 코드로 OnSize 함수를 실행 시키게 되는 겁니다.
Posted by 마블(이환문)