4장 기하학적 객체와 변환 – OpenGL 변환 학습목표 OpenGL 에서 어떻게 변환을 수행하는지 OpenGL 행렬모드 회전 이동 크기변환 OpenGL 행렬모드 모델-관측 행렬 투영행렬
OpenGL 행렬 OpenGL에서 행렬은 상태의 한 부분이다 세 가지 타입 조작을 위한 한 벌의 함수들 모델-관측 (GL_MODEL_VIEW) 투영 (GL_PROJECTION) 텍스처 (GL_TEXTURE) (지금은 다루지 않음) 조작을 위한 한 벌의 함수들 다음의 함수로 조작될 행렬을 선택 glMatrixMode(GL_MODEL_VIEW); glMatrixMode(GL_PROJECTION);
현재 변환행렬-Current Transformation Matrix (CTM) 정점들 p p’=Cp C
CTM 연산들 CTM은 새로운 CTM을 적재하든지, 뒤에 곱하기를 통해서 바뀐다 단위행렬을 적재: C I 임의의 행렬을 적재: C M 이동행렬을 적재: C T 회전행렬을 적재: C R 크기변환행렬을 적재: C S 임의의 행렬을 뒤에 곱하기: C CM 이동행렬을 뒤에 곱하기: C CT 회전행렬을 뒤에 곱하기: C C R 크기변환행렬을 뒤에 곱하기: C C S
고정점을 중심으로한 회전 단위행렬로 시작: C I 고정점을 원래대로 복귀: C CT 회전: C CR 고정점을 원점으로 이동: C CT -1 결과: C = TRT-1 각 연산은 프로그램에서 하나의 함수호출에 대응된다 마지막으로 지정된 연산이 프로그램상에서 처음으로 실행되는 연산임을 주의하라
OpenGL에서의 CTM OpenGL은 모델-관측 행렬과 투영행렬을 파이프라인에 포함하고 있으며 이 두 행렬이 연결되어 CTM을 구성한다 행렬모드를 올바로 설정한 후에 각각을 조작할 수 있다
회전, 이동, 크기변환 단위행렬의 적재: 변환행렬을 CTM의 오른쪽에 곱하기: glLoadIdentity() glRotatef(theta, vx, vy, vz) theta 는 각도(degree), (vx, vy, vz) 는 회전축을 정의 glTranslatef(dx, dy, dz) glScalef(sx, sy, sz)
예제 (1.0, 2.0, 3.0)을 고정점으로 하고 z 축을 중심으로 30도 회전 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(1.0, 2.0, 3.0); glRotatef(30.0, 0.0, 0.0, 1.0); glTranslatef(-1.0, -2.0, -3.0); 프로그램에서 마지막으로 지정된 행렬이 처음으로 적용되는 행렬
예제 Ex. 크기조절 이후 회전? 회전 이후 크기조절? glMatrixMode(GL_MODELVIEW); glLoadIdentity( ); glScalef(sx, sy, sz); glRotatef(theta, vx, vy, vz); glBegin(GL_POINTS); glVertex3f(px, py, pz); glEnd( ); 크기조절 이후 회전? 회전 이후 크기조절?
객체 변환에 의한 모델링 코드 세계좌표 기준의 객체변환 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(45, 0.0, 0.0, 1.0); 객체 변환의 역순 glTranslatef(10.0, 0.0, 0.0); glVertex3f(Px, Py, Pz); 세계좌표 기준의 객체변환 P' = T•P P'' = R•P' = R•T•P
좌표계 변환에 의한 모델링 코드 모델 좌표계를 변환 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(45, 0.0, 0.0, 1.0); 좌표계 변환과 동일 순서 glTranslatef(10.0, 0.0, 0.0); glVertex3f(Px, Py, Pz); 모델 좌표계를 변환 직전의 모델 좌표계를 기준으로 변환 CTM = I•R CTM = CTM•T = I•R•T P'' = CTM•P = I•R•T•P
함수호출 순서 행렬연산 순서 함수호출 순서
많은 경우, 나중에 사용하기 위해서 변환행렬을 저장하기를 원한다 행렬 스택 많은 경우, 나중에 사용하기 위해서 변환행렬을 저장하기를 원한다 계층 자료구조의 순회 (9장) 디스플레이 리스트를 실행할 때 상태 변화를 피함 OpenGL은 각 타입의 행렬을 위해서 행렬 스택을 유지 다음 명령으로 현재 타입(glMatrixMode에 의해 설정된)에 접근 glPushMatrix() glPopMatrix()
행렬 스택 오렌지 매달기 오렌지 매달기 프로그램 좌표계를 (d)로 되돌리려면 어떻게 하는가 -> 스택
행렬 스택 푸시, 팝 지엘의 푸시, 팝
행렬 스택 오렌지 매달기 프로그램의 스택 변화
행렬 스택 일반적 형태 glPushMatrix( ); glPopMatrix( ); glTranslatef( ); glRotatef( ); glScalef( ); ... Draw_TransformedObject( ); glPopMatrix( ); 일반적인 스택함수 호출
계층구조 모델링 void drawArm( ){ glMatrixMode(GL_MODELVIEW); glLoadIdentity( ); 전역 좌표계 = 모델 좌표계 Draw_Body( ); 몸체 그리기 glPushMatrix( ); 전역 좌표계 저장 GoToShoulderCoordinates( ); 어깨 기준 모델 좌표계 Draw_UpperArm( ); 위 팔 그리기 glPushMatrix( ); 어깨 기준 모델 좌표계 저장 GoToElbowCoordinates( ); 팔꿈치 기준 모델 좌표계 Draw_LowerArm( ); 아래팔 그리기 glPushMatrix( ); 팔꿈치 기준 모델 좌표계 저장 GoToWristCoordinates( ); 손목 기준 모델 좌표계 Draw_Hand( ); 손 그리기 glPopMatrix( ); 팔꿈치 좌표계 복원 glPopMatrix( ); 어깨 좌표계 복원 glPopMatrix( ); 몸체 좌표계 복원 }
임의의 행렬 응용프로그램에서 정의된 행렬로 적재하거나 곱할 수 있다 행렬 m은 16 원소의 1차원 배열이고 원하는 4 x 4 행렬을 열(column) 순서로 저장한 것이다 glMultMatrixf에서, m은 기존의 행렬을 오른쪽에서 곱한다 glLoadMatrixf(m) glMultMatrixf(m)
행렬을 읽어들임 질의함수를 통해서 행렬들에(상태의 다른 부분도 마찬가지)접근할 수 있다 행렬에 대해서 다음과 같이 사용한다 glGetIntegerv glGetFloatv glGetBooleanv glGetDoublev glIsEnabled double m[16]; glGetFloatv(GL_MODELVIEW, m);
예제: idle 함수를 사용해서 입방체를 회전시키고 마우스 함수로 회전의 방향을 바꿈 변환의 사용 예제: idle 함수를 사용해서 입방체를 회전시키고 마우스 함수로 회전의 방향을 바꿈 입방체 그리기 프로그램으로 부터 시작 (colorcube.c) 중심이 원점에 있음 면들이 축에 평행
회전입방체 void main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB GLUT_DEPTH); glutInitWindowSize(500, 500); glutCreateWindow("colorcube"); glutReshapeFunc(myReshape); glutDisplayFunc(display); glutIdleFunc(spinCube); glutMouseFunc(mouse); glEnable(GL_DEPTH_TEST); glutMainLoop(); }
회전입방체 void spinCube() { theta[axis] += 2.0; if( theta[axis] > 360.0 ) theta[axis] -= 360.0; glutPostRedisplay(); } void mouse(int btn, int state, int x, int y) if(btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN) axis = 0; if(btn==GLUT_MIDDLE_BUTTON && state == GLUT_DOWN) axis = 1; if(btn==GLUT_RIGHT_BUTTON && state == GLUT_DOWN) axis = 2;
회전입방체 void display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glRotatef(theta[0], 1.0, 0.0, 0.0); glRotatef(theta[1], 0.0, 1.0, 0.0); glRotatef(theta[2], 0.0, 0.0, 1.0); colorcube(); glutSwapBuffers(); } 답신의 형태가 고정되어 있으므로, theta와 axis 같은 변수들은 전역변수로 정의되어야 함을 주의하라 카메라 정보는 표준적 재구성 답신에 들어간다
볼과 롤러 사이에 마찰이 작으면 볼을 밀어서 계속 돌게 함으로써 연속적인 변화를 일으킬 수 있다 두 가지 가능한 작동모드 물리적 트랙볼 트랙볼은 볼 마우스를 “뒤집어 놓은 것” 볼과 롤러 사이에 마찰이 작으면 볼을 밀어서 계속 돌게 함으로써 연속적인 변화를 일으킬 수 있다 두 가지 가능한 작동모드 연속적 밀기 혹은 손동작을 따름 회전
문제: 하나의 마우스에서 두 가지 동작모드를 원한다 또한 마우스가 마찰이 없는 이상적인 트랙볼을 흉내내기 원한다 마우스를 이용한 트랙볼 문제: 하나의 마우스에서 두 가지 동작모드를 원한다 또한 마우스가 마찰이 없는 이상적인 트랙볼을 흉내내기 원한다 두 단계로 해결 트랙볼 위치를 마우스 위치로 사상 GLUT 를 사용해서 적절한 모드를 얻음
트랙볼 프래임 원점이 볼의 중심
트랙볼 위치의 투영 트랙볼의 위치를 마우스 패드 위로 직교투영 함으로써 둘 사이의 관계를 얻음
y = 역투영 패드와 볼의 상반구는 2차원 면이므로 투영의 역을 구할 수 있다 마우스 패드 상의 점 (x,z) 는 볼의 상반구 상의 점 (x,y,z) 와 대응된다. 여기서, y = if r |x| 0, r |z| 0
회전의 계산 마우스로부터 두 개의 점을 얻었다고 해 보자 두 점을 반구로 투영함으로써 반구 상의 두 점 p1과 p2 를 얻는다 이 점들은 구 상의 큰 원을 결정한다 적절한 회전축과 점들 사이의 회전각을 얻음으로써 점p1 으로부터 점 p2 로 회전 할 수 있다
외적의 사용 회전축은 원점과 점 p1 과 p2 로 이루어지는 평면의 법선에 의해서 주어진다 n = p1 p2
| sin q| = sin q q 각도의 계산 p1 과 p2 사이의 각도는
idle, motion, mouse 답신을 사용하여 가상 트랙볼을 구현한다 다음의 세 개의 불리언을 사용해서 동작을 정의한다 GLUT를 이용한 구현 idle, motion, mouse 답신을 사용하여 가상 트랙볼을 구현한다 다음의 세 개의 불리언을 사용해서 동작을 정의한다 trackingMouse: true 이면, 트랙볼의 위치를 갱신 redrawContinue: true 이면, idle 함수가 다시 그리기를 포스트(glutPostRedisplay) trackballMove: true 이면, 회전 행렬을 갱신
가상트랙볼 이 예제에서는, 가상 트랙볼을 이용해서 앞에서 모델링한 컬러 입방체를 회전시킨다 colorcube 함수는 앞의 예제와 변함이 없으므로 생략한다
가상트랙볼 bool type */ #define false 0 #define true 1 #define bool int /* if system does not support bool type */ #define false 0 #define true 1 #define M_PI 3.14159 /* if not in math.h */ int winWidth, winHeight; float angle = 0.0, axis[3], trans[3]; bool trackingMouse = false; bool redrawContinue = false; bool trackballMove = false; float lastPos[3] = {0.0, 0.0, 0.0}; int curx, cury; int startX, startY;
가상트랙볼 void trackball_ptov(int x, int y, int width, int height, float v[3]) { float d, a; /* project x,y onto a hemisphere centered within width, height , note z is up here*/ v[0] = (2.0*x - width) / width; v[1] = (height - 2.0F*y) / height; d = sqrt(v[0]*v[0] + v[1]*v[1]); v[2] = cos((M_PI/2.0) * ((d < 1.0) ? d : 1.0)); a = 1.0 / sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); v[0] *= a; v[1] *= a; v[2] *= a; }
가상트랙볼 void mouseMotion(int x, int y) { float curPos[3], dx, dy, dz; /* compute position on hemisphere */ trackball_ptov(x, y, winWidth, winHeight, curPos); if(trackingMouse) /* compute the change in position on the hemisphere */ dx = curPos[0] - lastPos[0]; dy = curPos[1] - lastPos[1]; dz = curPos[2] - lastPos[2];
가상트랙볼 if (dx || dy || dz) { /* compute theta and cross product */ angle = 90.0 * sqrt(dx*dx + dy*dy + dz*dz); axis[0] = lastPos[1]*curPos[2]–lastPos[2]*curPos[1]; axis[1] = lastPos[2]*curPos[0]–lastPos[0]*curPos[2]; axis[2] = lastPos[0]*curPos[1]–lastPos[1]*curPos[0]; /* update position */ lastPos[0] = curPos[0]; lastPos[1] = curPos[1]; lastPos[2] = curPos[2]; } glutPostRedisplay();
가상트랙볼 void spinCube() { if (redrawContinue) glutPostRedisplay(); } void display() glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); if (trackballMove) glRotatef(angle, axis[0], axis[1], axis[2]); colorcube(); glutSwapBuffers();
가상트랙볼 void mouseButton(int button, int state, int x, int y) { if(button==GLUT_RIGHT_BUTTON) exit(0); /* holding down left button allows user to rotate cube */ if(button==GLUT_LEFT_BUTTON) switch(state) case GLUT_DOWN: y=winHeight-y; startMotion( x,y); break; case GLUT_UP: stopMotion( x,y); }
가상트랙볼 void startMotion(int x, int y) { trackingMouse = true; redrawContinue = false; startX = x; startY = y; curx = x; cury = y; trackball_ptov(x, y, winWidth, winHeight, lastPos); trackballMove=true; }
가상트랙볼 void stopMotion(int x, int y) { trackingMouse = false; /* check if position has changed */ if (startX != x || startY != y) redrawContinue = true; else angle = 0.0; redrawContinue = false; trackballMove = false; }