Practice - texture mapping Hanyang University Jungsik Park
Texture mapping 텍스처 매핑은 다음과 같은 과정으로 수행된다. glEnable()로 텍스처 매핑 활성화 2차원의 경우 GL_TEXTURE_2D 텍스처 객체 생성 및 바인딩 영상 데이터를 텍스처 메모리로 로드 영상 파일을 이용할 경우 영상 파일로부터 영상 데이터를 로드해야 한다. 텍스처 환경 설정 텍스처 색상의 적용 방식 텍스처 파라미터 설정 보간 필터, 밉맵 등 렌더링시 사용할 텍스처 객체 바인딩 버텍스에 텍스처 좌표 지정 및 렌더링
영상 데이터가 메모리에 저장되는 방식 영상 파일의 로드 영상은 2차원 데이터지만, 메모리에 저장될 때는 일반적으로 1차원 배열로 로드되어 사용된다. 영상 파일의 로드 GLAUX 라이브러리 함수를 이용(24bit BMP 파일) 가장 간단하고 편리한 방법 그밖의 영상 관련 라이브러리(OpenCV 등)를 이용 영상을 다루는 라이브러리의 경우 영상의 한 열의 데이터의 바이트 수가 4의 배수가 되도록 열의 마지막이 zero-padding되어 조정되는 경우가 있는데, 이 때는 zero-padding되는 부분이 없도록 메모리를 재복사하거나 영상의 폭이 4의 배수가 되도록 영상을 편집하여 사용한다. w I(0,0) I(0,1) I(h-1,w-2) I(h-1,w-1) image I b g r … h
GLAUX GLAUX 라이브러리의 영상 데이터 구조체 영상 파일 로드 함수 AUX_RGBImageRec * auxDIBImageLoadA(LPCSTR) 파일명을 인자로 받아 24bit bmp 파일을 로드. BGR로 저장된 파일을 RGB로 변환하며 읽어들인다. AUX_RGBImageRec 포인터와 멤버 변수 data는 함수 내에서 malloc()으로 동적할당된 공간을 가리키므로, 사용이 끝나면 각각 free()로 해제해주어야 한다. typedef struct _AUX_RGBImageRec { GLint sizeX, sizeY; unsigned char *data; } AUX_RGBImageRec; AUX_RGBImageRec *imgRec = auxDIBImageLoad("image.bmp"); … free(imgRec->data); free(imgRec);
Texture object 렌더링할 때는 현재의 텍스처와 상태변수가 적용된다. 여러 개의 텍스처를 사용할 경우 텍스처를 바꿀 때마다 현재 텍스처로 영상 데이터를 텍스처 메모리로 로드하는 것은 비효율적이다. 텍스처 객체 미리 사용할 텍스처와 상태변수들을 텍스처 메모리에 로드해 놓는다(각 텍스처는 텍스처 객체로 구분된다). 렌더링할 때는 사용할 텍스처를 텍스처 객체를 통해 현재 텍스처로 바인딩하여 적용한다.
Texture object 텍스처 객체 생성 텍스처 객체를 현재 텍스처로 바인딩 텍스처 객체 제거 void glGenTextures (GLsizei n, GLuint *textures); n : 생성할 텍스처 객체의 개수 textures : 텍스처 객체의 이름이 저장될 배열 텍스처 객체를 현재 텍스처로 바인딩 void glBindTexture (GLenum target, GLuint texture); target : 어떤 텍스처에 바인딩할 것인지 지정 GL_TEXTURE_1D, GL_TEXTURE_2D 등 texture : 사용할 텍스처 객체의 이름 텍스처 객체 제거 void glDeleteTextures (GLsizei n, const GLuint *textures); textures : 텍스처 객체의 이름이 저장되어 있는 배열
영상 데이터를 현재 텍스처로 로드 void glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); target : 어떤 텍스처에 바인딩할 것인지 지정 GL_TEXTURE_2D, 큐브맵 텍스처의 경우 각 면에 대한 매크로 상수 level : 밉맵 레벨(밉맵을 사용하지 않거나 원영상인 경우 0을 지정) internalformat : 그래픽 하드웨어의 텍스처 메모리에 텍스처가 저장되는 형식 width : 영상의 폭 height : 영상의 높이 border : 영상의 가장자리 경계의 두께 format : 영상 데이터의 형식 type : 영상 데이터의 자료형 pixels : 영상 데이터의 시작 주소
Internal format Format 그래픽 하드웨어의 텍스처 메모리에서 텍스처가 저장되는 형식. 자주 사용되는 형식은 다음과 같다. Format 그래픽 하드웨어로 전송하기 위한 텍스처 영상의 포맷
Data type 영상의 각 채널이 저장된 타입 GL_UNSIGNED_BYTE는 채널당 1byte로 저장되며 0~255의 값을 가진다. GL_FLOAT은 채널당 4byte 실수로 저장되며, 0.0~1.0의 값을 가진다. Packed type의 경우 숫자는 각 채널당 bit수를 나타낸다. GL_UNSIGNED_SHORT_5_6_5의 경우 R채널에 5bit, G채널에 6bit, B 채널에 5bit가 각각 할당됨을 의미한다.
GLAUX함수로 영상 파일 로드 -> 텍스처 객체 생성 -> 텍스처 영상 로드 과정의 예 // 텍스처 객체 변수 GLuint texObj; // 영상 데이터 구조체 포인터 AUX_RGBImageRec *imgRec; // 영상 파일 로드 imgRec = auxDIBImageLoad("image.bmp"); // 텍스처 객체 생성 및 바인딩 glGenTextures(1, &texObj); glBindTexture(GL_TEXTURE_2D, texObj); // 텍스처 로드 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imgRec->sizeX , imgRec->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, imgRec->data); ... // 사용이 끝난 객체의 파괴 및 메모리 해제 glDeleteTextures(1, &texObj); free(imgRec->data); free(imgRec);
Texture parameters 확대/축소 필터, 범위 밖의 텍스처 좌표 처리 방식, 밉맵 등의 파라미터 지정 void glTexParameteri(GLenum target, GLenum pname, GLint param); target : GL_TEXTURE_1D, GL_TEXTURE_2D 등 glTexParameter*에 사용되는 파라미터는 종류가 많고, 그에 따른 파라미터 값도 여러 가지이다.
Texture parameters 텍스처 영상 축소/확대에 사용되는 필터 지정 pname : GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER param GL_NEAREST : 최근접 이웃. 가장 가까운 픽셀의 값을 참조 GL_LINEAR : 주변 픽셀값을 선형 보간
Example #include <gl/glew.h> #include <gl/glaux.h> #include <gl/glut.h> GLfloat xRot = 0.0f, yRot = 0.0f, zoom = -300.0f; AUX_RGBImageRec *imgRec; GLuint texObj; void ChangeSize(GLsizei width, GLsizei height) { GLfloat fAspect = (GLfloat)width/(GLfloat)height; GLfloat nRange = height/4.0; if(height == 0) height = 1; glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60.0f, fAspect, 1.0, 4000.0); glMatrixMode(GL_MODELVIEW); } void SpecialKeys(int key, int x, int y) if(key == GLUT_KEY_UP)xRot-= 5.0f; if(key == GLUT_KEY_DOWN)xRot += 5.0f; if(key == GLUT_KEY_LEFT)yRot -= 5.0f; if(key == GLUT_KEY_RIGHT)yRot += 5.0f; if(key == GLUT_KEY_PAGE_UP)zoom += 10.0f; if(key == GLUT_KEY_PAGE_DOWN)zoom -= 10.0f; xRot = (GLfloat)((const int)xRot % 360); yRot = (GLfloat)((const int)yRot % 360); glutPostRedisplay(); BOOL SetupRC() { glEnable(GL_TEXTURE_2D); imgRec = auxDIBImageLoadA("bricks.bmp"); if (imgRec == 0) return FALSE; glGenTextures(1, &texObj); glBindTexture(GL_TEXTURE_2D, texObj); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imgRec->sizeX, imgRec->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, imgRec->data); return TRUE; } void CleanUp() if (texObj != 0) glDeleteTextures(1, &texObj); if (imgRec != 0) free(imgRec->data); free(imgRec);
Example 버텍스마다 [0.0, 1.0] 범위의 텍스처 좌표를 지정해 준다. void RenderScene(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glTranslatef(0.0f, 0.0f, zoom); glRotatef(xRot, 1.0f, 0.0f, 0.0f); glRotatef(yRot, 0.0f, 1.0f, 0.0f); glBindTexture(GL_TEXTURE_2D, texObj); glBegin(GL_QUADS); glTexCoord2d(0, 0); glVertex3d(-100, -100, 0); glTexCoord2d(1, 0); glVertex3d(100, -100, 0); glTexCoord2d(1, 1); glVertex3d(100, 100, 0); glTexCoord2d(0, 1); glVertex3d(-100, 100, 0); glEnd(); glPopMatrix(); glutSwapBuffers(); } int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(600, 600); glutCreateWindow("texture mapping"); glutReshapeFunc(ChangeSize); glutSpecialFunc(SpecialKeys); glutDisplayFunc(RenderScene); if (SetupRC()) glutMainLoop(); CleanUp(); return 0; } 버텍스마다 [0.0, 1.0] 범위의 텍스처 좌표를 지정해 준다.
Result GL_NEAREST GL_LINEAR
Texture parameters 범위 밖의 텍스처 좌표 처리 방식 텍스처 좌표는 [0.0, 1.0]의 범위를 가질 때 텍스처 영상의 실제 픽셀 좌표에 매핑된다. pname : GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T pname GL_CLAMP GL_REPEAT GL_CLAMP_TO_EDGE GL_CLAMP_TO_BORDER GL_MIRRORED_REPEAT 확장 기능 : GLEW를 이용
Example 다음과 같이 [0.0, 1.0] 범위 밖의 텍스처 좌표를 설정하고, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T 의 값을 변경해 본다. glBegin(GL_QUADS); glTexCoord2d(0, 0); glVertex3d(-100, -100, 0); glTexCoord2d(1, 0); glVertex3d(100, -100, 0); glTexCoord2d(1, 1); glVertex3d(100, 100, 0); glTexCoord2d(0, 1); glVertex3d(-100, 100, 0); glEnd(); glBegin(GL_QUADS); glTexCoord2d(-1, -1); glVertex3d(-100, -100, 0); glTexCoord2d(2, -1); glVertex3d(100, -100, 0); glTexCoord2d(2, 2); glVertex3d(100, 100, 0); glTexCoord2d(-1, 2); glVertex3d(-100, 100, 0); glEnd();
Result GL_REPEAT GL_CLAMP GL_MIRRORED_REPEAT
Texture environment 폴리곤 색상에 텍스처를 어떤 방법으로 반영할 것인지를 지정 void glTexEnvi(GLenum target, GLenum pname, GLint param); target : GL_TEXTURE_ENV pname GL_TEXTURE_ENV_MODE GL_TEXTURE_ENV_COLOR : 블렌딩 모드일 경우 색상을 지정 param GL_MODULATE GL_DECAL GL_BLEND
Texture environment GL_TEXTURE_ENV_MODE에 따른 색상 결정 방법 L : luminance C : color A : alpha 아랫첨자 v : 텍스처 함수에 의해 생성되는 value 아랫첨자 t : texture 아랫첨자 f : fragment 아랫첨자 c : 블랜드 모드일 경우 지정되는 색상
Example SetupRC()에서 텍스처 파라미터 지정하는 부분에 glTexEnvi() 함수를 추가한다. RenderScene()에서 glColor*() 함수로 버텍스 색상을 지정한다. glGenTextures(1, &texObj); glBindTexture(GL_TEXTURE_2D, texObj); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imgRec->sizeX, imgRec->sizeY , 0, GL_RGB, GL_UNSIGNED_BYTE, imgRec->data); glGenTextures(1, &texObj); glBindTexture(GL_TEXTURE_2D, texObj); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imgRec->sizeX, imgRec->sizeY , 0, GL_RGB, GL_UNSIGNED_BYTE, imgRec->data);
Result GL_DECAL GL_MODULATE glColor3f(0.0, 1.0, 0.0)을 적용하여 버텍스 색상을 녹색으로 지정한 경우. GL_DECAL은 버텍스 색상이 무시되나, GL_MODULATE는 버텍스 색상과 텍스처 색상이 곱해져서 나타난다.
Mipmap 하나의 영상만을 텍스처로 로딩하는 것이 아니라, 큰 것부터 작은 것까지 다양한 크기의 영상을 밉맵핑된 텍스처로 로딩하는 것. 텍스처가 입혀진 물체가 작게 렌더링될 경우 성능과 질이 떨어지는 것을 해결하는 방법 여러 개의 텍스처 영상들로 구성되며, 각 영상은 이전 단계 영상의 가로세로 각각 절반 크기를 갖는다. 밉맵 텍스처를 사용할 경우 일반 텍스처에 비해 약 1/3만큼의 추가적인 메모리가 필요하다.
Mipmap 밉맵 파라미터 렌더링시 밉맵 레벨을 선택하는 방법을 지정해 주어야 한다. 텍스처 영상 축소/확대에 사용되는 필터 GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER를 밉맵 텍스처용 파라미터로 지정 glTexParameteri() 함수로 밉맵 레벨 범위를 지정(지정하지 않는 경우 1x1 크기가 될 때까지 서브샘플링 영상을 생성한다.) pname GL_TEXTURE_BASE_LEVEL GL_TEXTURE_MAX_LEVEL param 정수로 기본 레벨 및 최고 레벨 지정(원 영상은 0) GL_NEAREST_MIPMAP_NEAREST 가장 근접한 밉맵 레벨을 선택하고, 최근접 이웃 필터링 적용 GL_LINEAR_MIPMAP_NEAREST 가장 근접한 밉맵 레벨을 선택하고, 선형 필터링 적용 GL_NEAREST_MIPMAP_LINEAR 밉맵 레벨간의 선형 보간을 수행하고, 최근접 이웃 필터링 적용 GL_LINEAR_MIPMAP_LINEAR 밉맵 레벨간의 선형 보간을 수행하고, 선형 필터링 적용
Mipmap 밉맵 텍스처 영상 로드 직접 영상을 축소시켜 glTexImage2D()의 level인자를 지정하여 로드하는 방법 GLU 라이브러리 함수를 이용하는 방법 int gluBuild2DMipmaps(GLenum target, GLint components, Glint width, GLint height, GLenum format, GLenum type, const void *data) 내부적으로 영상을 축소시켜서 각단계의 영상을 자동으로 로드해주는 함수 GLEW 라이브러리의 확장 함수를 이용하는 방법 glGenerateMipmapEXT(GLenum target) target : GL_TEXTURE_1D, GL_TEXTURE_2D 등 glTexImage*D()로 영상을 로드한 뒤, glGenerateMipmapEXT()를 호출하면 하드웨어 가속을 적용하여 자동으로 밉맵 텍스처를 생성해 준다. 확장 함수를 사용하는 방법은 앞의 두 방법과 달리 하드웨어 가속을 사용하므로 빠르게 수행된다.
Environment mapping 큐브맵 텍스처를 이용하여 환경 매핑을 하기 위해서는 입방체의 각 면에 해당하는 6장의 텍스처와 3차원 텍스처 좌표가 필요 GL_TEXTURE_CUBE 텍스처 객체 생성 및 바인딩 각 면에 대해 glTexImage2D 함수로 다음을 타겟으로 지정하여 텍스처를 로드 GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z glTexGeni() 함수로 3차원 텍스처 좌표를 자동으로 생성시킨다.
Texture coordinate generation glEnable(GL_TEXTURE_GEN_S), glEnable(GL_TEXTURE_GEN_T), glEnable(GL_TEXTURE_GEN_R)로 좌표 자동 생성 옵션을 활성화시킨다. void glTexGeni (GLenum coord, GLenum pname, GLint param) 텍스처 좌표를 자동으로 생성하는 모드를 설정. Coord 좌표 성분 지정. GL_S, GL_T, GL_R pname GL_TEXTURE_GEN_MODE param 좌표 생성 방법 지정. GL_OBJECT_LINEAR, GL_EYE_LINEAR, GL_SPHERE_MAP, GL_REFLECTION_MAP 큐브맵을 이용한 환경 매핑에는 GL_REFLECTION_MAP이, 구면맵을 이용한 환경 매핑에는 GL_SPHERE_MAP이 사용된다. GL_OBJECT_LINEAR, GL_EYE_LINEAR의 경우 glTexGenfv를 이용하여 다음과 같이 프로젝션 평면을 별도로 지정해 준다. GLfloat zPlane[] = { 0.0f, 0.0f, 1.0f, 0.0f }; . . . glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGenfv(GL_S, GL_EYE_PLANE, zPlane); glTexGenfv(GL_T, GL_EYE_PLANE, zPlane);
Example #include <gl/glew.h> #include <gl/glaux.h> #include <gl/glut.h> GLfloat xRot = 0.0f; GLfloat yRot = 0.0f; GLfloat zoom = -300.0f; AUX_RGBImageRec *imgRec; const char *szCubeFaces[6] = { "right.bmp", "left.bmp", "up.bmp", "down.bmp", "backward.bmp", "forward.bmp" }; GLuint texObj; GLenum cube[6] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; void ChangeSize(GLsizei width, GLsizei height) { GLfloat fAspect = (GLfloat)width/(GLfloat)height; GLfloat nRange = height/4.0; if(height == 0) height = 1; glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60.0f, fAspect, 1.0, 4000.0); glMatrixMode(GL_MODELVIEW); } void SpecialKeys(int key, int x, int y) { if(key == GLUT_KEY_UP) xRot-= 5.0f; if(key == GLUT_KEY_DOWN) xRot += 5.0f; if(key == GLUT_KEY_LEFT) yRot -= 5.0f; if(key == GLUT_KEY_RIGHT) yRot += 5.0f; if(key == GLUT_KEY_PAGE_UP) zoom -= 10.0f; if(key == GLUT_KEY_PAGE_DOWN) zoom += 10.0f; xRot = (GLfloat)((const int)xRot % 360); yRot = (GLfloat)((const int)yRot % 360); glutPostRedisplay(); } void RenderScene(void) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glTranslatef(0.0f, 0.0f, zoom); glRotatef(xRot, 1.0f, 0.0f, 0.0f); glRotatef(yRot, 0.0f, 1.0f, 0.0f); glBindTexture(GL_TEXTURE_CUBE_MAP, texObj); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glEnable(GL_TEXTURE_GEN_R); glutSolidTorus(50.0, 100.0, 50, 50); glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glDisable(GL_TEXTURE_GEN_R); glPopMatrix(); glutSwapBuffers();
Example BOOL SetupRC() { glCullFace(GL_BACK); glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glEnable(GL_TEXTURE_CUBE_MAP); glGenTextures(1, & texObj); glBindTexture(GL_TEXTURE_CUBE_MAP, texObj); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_REPEAT); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); for(int i = 0; i < 6; i++) imgRec = auxDIBImageLoadA(szCubeFaces[i]); if (imgRec == 0) return FALSE; glTexImage2D(cube[i], 0, GL_RGB , imgRec->sizeX , imgRec->sizeY, 0, GL_RGB , GL_UNSIGNED_BYTE, imgRec->data); free(imgRec->data); free(imgRec); imgRec = 0; } glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP); return TRUE; void CleanUp() { if (texObj != 0) glDeleteTextures(1, &texObj); if (imgRec != 0) free(imgRec->data); free(imgRec); } int main(int argc, char *argv[]) glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(600, 600); glutCreateWindow("texture mapping"); glutReshapeFunc(ChangeSize); glutSpecialFunc(SpecialKeys); glutDisplayFunc(RenderScene); if (SetupRC()) glutMainLoop(); CleanUp(); return 0;
Result