[PhysX] 1화 Primary Shape #1

Grphics 2009. 4. 28. 20:13
지난주부터 PhysX를 공부하면서 정리하려고 했는데, NVIDIA에서 제공하는 튜토리얼이 있긴 하지만 정리하려고 했더니 만만치 않네요. 특히 PhysX는 물리 엔진이기 때문에 렌더링과 관련 라이브러리가 없답니다. 그래서 초반에 셋팅해야 하는 것들이 만만치 않게 많아요.

이번 포스트는 nVidia에서 제공하는 튜토리얼을 기반으로 하고 있습니다. 다만, 단순 번역이 아니라 소스 코드의 순서나, 설명을 제 나름대로 재구성해보았습니다.

만만치 않은 양이지만, "시작이 반이다"라는 말이 있듯이, 한번 달려보죠. 아자!


아시다시피 PhysX는 물리엔진입니다. 그렇기 때문에, 화면에 출력하는 그래픽과 관련된 API가 전무합니다. 사실 그래야 물리엔진이겠죠. 그래서 PhysX를 사용하기 위해서는 렌더링을 할 수 있는 API를 사용해야 합니다. 저는 OpenGL을 사용하도록 하겠습니다. DX도 가능하지만, DX 보다는 OpenGL이 간단하게 프로그래밍하기 좋기 때문에, OpenGL을 사용하도록 하겠습니다.

OpenGL에는 GLUT라는 라이브러리가 있습니다. 이 라이브러리는 DXUT와 마찬가지로 손쉽게 OpenGL을 코딩할 수 있도록 해주는 프레임워크 라이브러리입니다. 이 라이브러리가 없으신 분은 다운 받아 설치합시다.

혹시나 PhysX를 설치하지 않으신 분들은 여기에 강좌가 있으니 설치를 먼저 하세요.

강좌에 사용된 소스코드입니다. 같이 보시면서 공부하시면 도움이 될 것 같습니다. nVidia에 있는 소스와 약간 다릅니다.


이 소스는 기본적으로 PhysX와 관련된 폴더가 환경설정에 추가가 되어 있어야 합니다. 포함파일(include)폴더들과 라이브러리파일(lib) 폴더 경로가 설정되어 있어야 합니다. 또 "PhysX공용"이라고 되어 있는 곳의 소스는
  • (NVIDIA PhysX)\v2.8.1\TrainingPrograms\Programs\Shared_Source
  • (NVIDIA PhysX)\v2.8.1\TrainingPrograms\Programs\Shared_Source\User_Reports
에 있는 소스입니다. 참고하시기 바랍니다.


자 이제 시작하겠습니다. 먼저 메인 함수부터 만들어봅시다.

callback.h에는 주로 렌더링에 필요한 기반 코드와 카메라 제어를 위한 마우스, 키보드 코드가 있니다. physx.h에는 PhysX 라이브러리를 사용하는 물리 엔진 함수와 객체를 렌더링하는 코드, 객체를 제어하기 위한 키보드 입력을 위한 코드가 있습니다.

먼저, InitGLUT함수를 이용해서 렌더링을 위한 윈도우 생성과 OpenGL 초기화를 할 예정입니다. InitNx함수에서는 PhysX의 초기화를 진행하겠습니다. RunGLUT함수가 실행되면 루프를 돌면서 이벤트 처리, 물리 연산, 렌더링이 진행됩니다. 프로그램이 종료되면 ReleaseNx함수가 호출되면서 PhysX에서 사용하던 메모리 해제가 진행됩니다.

1. InitGLUT함수에서는 GLUT에서 필요한 콜백함수를 등록합니다. glutInit함수를 통해 GLUT 라이브러리를 초기화합니다.
2. glutInit*()함수들을 이용해서 우리가 생성하고자 하는 윈도우의 렌더링모드를 설정하고 장의 위치 및 크기를 설정합니다.  마지막으로 glutCreateWindow함수를 이용해서 창을 생성합니다.
3. GLUT에서 필요한 콜백함수를 등록하여 우리가 구현한 함수가 해당하는 이벤트가 발생할 때 실행될 수 있도록 합니다.

먼저 glutReshapeFunc 함수에 등록한 ReshapeCallback 함수부터 구현해봅니다. WM_SIZE가 발생할 때 실행되는 함수 입니다.  먼저 전역변수로 gWindowWidth, gWindowHeight를 만듭니다.  이 변수들은 나중에 카메라 설정때 Aspect Ratio를 구하기 위해서 사용됩니다. 이 함수에서는 창의 크기가 바뀔 때 마다 창에 꽉 차게 렌더링을 하기 위해서 Viewport를 설정합니다.  Viewport를 생성하고 카메라와 조명을 셋팅합니다.


ApplyLight함수는 조명을 설정합니다. 조명은 방향성 조명으로 설정했습니다. glColorMaterial 함수를 이용해서 메쉬에 적용된 색이 Ambient, Diffuse Light에 반응하도록 했습니다. ApplyCamera함수는 카메라을 설정합니다. 전역변수로 설정되어 있는 gCamera* 변수를 이용해서 pos, look, up 벡터를 설정합니다.

이제 좀 더 어려운 부분인 카메라 제어 부분을 살펴보겠습니다. 카메라 제어를 위해서 키보드 관련 콜백함수와 마우스 콜백 함수를 사용합니다.

이 부분이 조금 헷깔릴 수 있습니다. gKeys 배열은 physx.cpp에 정의되어 있는 전역변수입니다. 이를 callback.h에서 사용하기 위해서 physx.h에서 extern으로 전역변수 선언을 한 후, callback.cpp에서 사용한 것입니다. 사실 한 파일에 몽땅 코딩해 버리면 이렇게 할 필요가 없지만, 좀 정리하면서 코딩을 하기 위해서 분리해 놓은 것입니다. 여러분은 extern을 이해할 수 있을 거라 보고, 설명은 넘어가겠습니다. 
KeyboardCallback함수에서는 키보드를 눌렀을 때 호출되는 함수입니다. 이 함수가 호출되면 해당하는 키가 눌렸다는 것을 gKeys배열에 표시합니다. 만약 esc키를 누르면, 프로그램을 종료합니다. KeyDown함수는 physx..h/cpp에 선언된 함수로 PhysX에서 키를 눌렀을때, 하고 싶은 이벤트를 구현한 함수입니다.
KeyboardUpCallback함수는 키를 땠을 때 호출되는 함수입니다. 키를 땠을 경우, gKeys 배열에 이를 표시합니다.

ProcessCameraKey함수에서는 gKeys 배열에 기록된 내용을 토대로 카메라의 위치를 이동합니다. 여기서 gCameraSpeed는 카메라의 움직임이 얼마나 빠르게 동작하는지를 의미합니다. deltaTime은 이전 프레임과 현재 프레임의 시간 차를 의미합니다. 정확하게 코딩하려면 시간 차를 구해서 적용해야 하지만, 이번 강좌에서는 약식으로 넘어가도록 하겠습니다.

카메라 제어 코드의 도식(w를 눌렀을 경우)

그림 1. 카메라 변수들 간의 관계

위의 그림은 ProcessCameraKey 함수에서 사용된 공식을 그림으로 나타낸 것입니다. 카메라의 위치는 gCameraPos이고, w를 눌렀을 경우, gCameaForward 방향으로 deltaTime*gCameraSpeed만큼 이동하게 됩니다. s를 누르게 되면 그 반대로 이동하게 하면 될 것입니다. a와 d는 gCameraForward대신 gCameraRight를 적용하면 좌우로 움직일 수 입니다.

다음은 마우스 쪽의 코드를 살펴보겠습니다.

MouseCallback함수는 마우스를 클릭한 지점을 전역변수인 mx, my에 저장합니다. MotionCallback함수는 드래깅 할 때 호출됩니다. 드래깅할 때 mx, my에서 x, y를 빼서, 클릭한 지점으로 부터 얼마만큼 떨어져있는지 dx, dy를 구합니다. 다음 계산을 위해서 gCameraForward를 정규화하고, gCameraRight를 gCameraForward와 y축을 외적하여 구합니다. 그리고 dx, dy를 각으로 변환하여 y축으로 dx만큼 회전하는 쿼터니온과, gCameraRight를 축으로 dy만큼 회전하는 쿼터니온을 생성합니다. 이를 이용해서 gCameraForward를 회전변환합니다. 쿼터니온은 그래픽스에서 자주 나오는 개념이니까 꼭 공부해보시기 바랍니다.

이제 callback.cpp는 거의 다 끝났습니다. 휴.. 남아 있는 함수를 보도록 하겠습니다.

void RenderCallback함수는 렌더링을 해야 할때 호출되는 함수이며, IdleCallback 함수는 렌더링이 끝난 후에 호출되는 함수 입니다. 사실 GLUT에서는 게으른 방식(??)으로 렌더링을 합니다. 즉, 렌더링되는 윈도우가 움직인다거나, 다시 그려야하는 경우, WM_PAINT가 호출되는 시점과 같은 시기에 렌더링이 됩니다. 즉, 매번 렌더링이 되는게 아니랍니다. 그래서 IdleCallback 함수에서 glutPostRedisplay()함수를 호출하여 다시 렌더링을 하도록 합니다. 이 함수는 마치 Invalidate()함수와 비슷합니다.

RenderCallback 함수에서는 glClear함수를 이용해 그래픽 버퍼를 초기화하고, 렌더링 하기 전, 카메라 제어에 대한 처리를 수행합니다(ProcessCameraKey(), ApplyCamera()).
그리고, PhysX의 객체 렌더링을 수행하는 함수인 RenderScene(physx.h/cpp)를 호출하고, glFlush함수로 큐에 대기중인 모든 OpenGL 명령을 그래픽카드로 보내고, glutSwapBuffers함수가 그래픽 버퍼를 교체합니다(더블 버퍼링, glutInitDisplayMode함수의 GLUT_DOUBLE인자가 더블버퍼링을 위한 인자입니다.).

휴... GLUT와 관련된 함수를 살펴보았습니다. 아마 당분간은 이 부분은 다시 살펴보지 않아도 될 것입니다. 초기화가 대부분이고 PhysX에 의해 바뀔 가능성이 있는 부분은 모두 함수로 빼서 physx.h/cpp에 정의 했으니까 callback.h/cpp는 가끔 살피면 될 것 같습니다.



포스트가 너무 길어졌네요. 다음 physX와 관련된 함수는 다음 포스트에서 이어가겠습니다.

"[PhysX] 1화 Primary Shape #2" 보기
posted by 스펜서.