[App 개발] NeHe Lesson 22
본문
*************************************
이번 강의는 Jens Schneider 가 만들었습니다. Lesson 06 에서부터 만들긴 했지만 상당히 많은 변화가 있습니다. 이번 강의에서 배우실 것은 다음과 같습니다.
* 그래픽 카드의 멀티텍스쳐 기능 사용법
* 엠보스 범프 매핑을 흉내내는 법
* 블렌딩 기능을 이용해서 멋있는 로고를 화면에서 움직이게 하는 법
* 멀티 패스 랜더링의 기초
* 효과적인 매트릭스
적어도 위의 것 중 세 가지는 심화 랜더링 기법에서 다루어질 수 있는 내용이고, 여러분은 적어도 OpenGL 의 랜더링 파이프라인의 기본 개념은 알고 있어야 합니다. 지금까지 강의에서 사용된 명령들은 대부분 알고 계셔야 하고, 벡터 계산에 대해서 익숙해야 합니다. 이제부터 여러분은 앞부분에 이론 시작 (...) 그리고 마지막에 이론 끝 (..) 으로 이루어진 블럭들을 보게 될 것입니다. 이 부분은 괄호에서 언급된 문제들에 대한 이론을 설명하려는 부분입니다. 여러분이 만일 그 문제를 알고 계신다면 쉽게 넘어가셔도 됩니다. 만일 여러분이 코드를 분석하는 도중에 문제를 만났을 때에는 이론 부분으로 넘어가서 확인해 보시기 바랍니다.
마지막으로 중요한 점. 이 강의는 1,200 줄 이상 되는 코드로 구성되었고, 많은 부분이 지난 강의에서 언급되었던 따분한 부분이 많이 포함되어 있습니다. 따라서 저는 각 줄마다 주석을 달지 않고, 중요한 부분만 하겠습니다. 따라서 여러분이 ; 로 끝나는 문장을 만나시면, 그 부분은 주석을 달지 않은 것입니다.
이제 시작하겠습니다.
GLfloat MAX_EMBOSS 는 범프 매핑 효과의 강도를 나타냅니다. 큰 값은 매우 큰 효과를 가져오지만 표면 주의에 나타나는 부작용으로 인하여 가시적인 품질이 떨어집니다.
#define MAX_EMBOSS (GLfloat)0.01f
이제 GL_ARB_multitexture 기능을 준비할 차례입니다. 이것은 매우 간단합니다.
요즘의 대부분의 그래픽 카드들은 하나 이상의 텍스쳐 텍스쳐 유닛을 갖고 있습니다. 이것을 활용하기 위해서는 GL_ARB_multitexture 지원이 가능한지를 살펴보아야 합니다. 이것은 두 개 이상의 텍스쳐를 한 번에 OpenGL 의 물체에 매핑할 수 있게 합니다. 별로 대단해 보이지 않겠지만, 사실 대단한 것입니다. 여러분이 프로그램을 만드는 거의 대부분, 다른 텍스쳐를 한 물체에 입히게 되면 가시적인 효과가 높아집니다. 인터리빙 텍스쳐 선택과 구현에는 여러 가지 절차가 필요하기 때문에 이러한 과정은 상당히 힘이 들게 됩니다. 그러나 이러한 문제들이 해결될 것입니다.
이제 코드로 돌아와서, __ARB_ENABLE 은 멀티텍스쳐를 구현하기 위한 특수한 컴파일과 수행을 지정하는 데 쓰입니다. 만약에 여러분의 OpenGL-extensions 을 보려면 #define EXT_INFO 의 주석 처리를 없애주십시오. 그 다음, 우리 프로그램의 휴대성을 위하여 실행 시간 중에 extension 을 확인해야 할 때가 있습니다. 따라서 문자열을 저장할 공간이 필요합니다. 다음 두 줄이 있습니다. 이제 우리는 멀티텍스쳐를 가능하게 하는 것과 실제 사용하는 것을 구분하기 위한 플래그가 두 개 필요합니다. 마지막으로 얼마나 많은 텍스쳐 유닛이 존재하는지를 알아야 합니다. (물론 우리는 그 중에 두 개만 사용합니다) 적어도 OpenGL 기능이 있는 카드에는 한 개의 유닛이 있으므로 일단 maxTexelUnits 를 1로 초기화합니다.
#define __ARB_ENABLE true
// #define EXT_INFO
#define MAX_EXTENSION_SPACE 10240
#define MAX_EXTENSION_LENGTH 256
bool multitextureSupported=false;
bool useMultitexture=true;
GLint maxTexelUnits=1;
다음은 extension 과 C++ 함수를 연결합니다. PFN-어쩌구 는 함수 호출을 기술하기 위한 이미 선언된 데이터 형입니다. 아직 우리가 함수로 만들지 않았으므로 NULL 로 놓습니다. glMultiTexCoordifARB 명령은 우리가 잘 아는 텍스쳐 좌표 지정 glTexCoordif 함수를 를 연결합니다. 이 명령은 glTexCoordif 명령을 완전히 대체하게 됩니다. 우리가 GLfloat 용 함수만을 사용하기 때문에 f 로 끝나는 함수의 프로토타입만 필요합니다. 다른 함수들 (fv 나 i 등) 도 가능합니다. 마지막 두 프로토타입은 최근에 받은 텍스쳐의 활성화된 텍스쳐 유닛을 설정하고 (glActiveTextureARB()) 어떤 텍스쳐 유닛이 사용되는지를 배열포인터 형으로 지정합니다. (클라이언트 서브셋, 따라서 glClientActiveTextureARB) ARB 는 “Architectural Review Board” 의 약자입니다. 이름에 있는 ARB 의 익스텐션은 OpenGL 용 구현에 필요하지 않지만 다양하게 지원되기를 기대합니다. 현재 멀티텍스쳐 익스텐션만이 ARB-status 로 만들어져 있습니다. 빠른 멀티텍스쳐링이 가지는 엄청난 효과는 향상된 랜더링 기술에 이용될 것입니다.
PFNGLMULTITEXCOORD1FARBPROC glMultiTexCoord1fARB = NULL;
PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB = NULL;
PFNGLMULTITEXCOORD3FARBPROC glMultiTexCoord3fARB = NULL;
PFNGLMULTITEXCOORD4FARBPROC glMultiTexCoord4fARB = NULL;
PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL;
PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB= NULL;
광역변수들이 필요합니다. 변수 filter 는 어떤 필터가 사용되는지를 가리킵니다. 강의 6 번을 참조하십시오. 우리는 GL_LINEAR 를 취할 것이므로 1 로 초기화합니다. 변수 texture 는 베이스 텍스쳐를 갖고 있고 필터당 한 개씩 모두 세 개가 있습니다.
변수 bump 는 범프 맵을 갖고 있습니다. 변수 invbump 는 반전된 범프 맵입니다. 이것에 대해서는 이론 부분에서 설명하겠습니다. Logo 변수들은 마지막 부분에서 랜더링 출력에 더해질 간판 텍스쳐를 담고 있습니다. Light 변수들은 OpenGL 의 광원 데이터를 담고 있습니다.
GLuint filter=1;
GLuint texture[3];
GLuint bump[3];
GLuint invbump[3];
GLuint glLogo;
GLuint multiLogo;
GLfloat LightAmbient[] = { 0.2f, 0.2f, 0.2f};
GLfloat LightDiffuse[] = { 1.0f, 1.0f, 1.0f};
GLfloat LightPosition[] = { 0.0f, 0.0f, 2.0f};
GLfloat Gray[] = { 0.5f, 0.5f, 0.5f, 1.0f};
다음 코드는 GL_QUADS 로 만들어질 텍스쳐 육면체의 수치적 표현을 담고 있습니다. 각각의 다섯 숫자들은 2D 텍스쳐 좌표와 3D 꼭지점 좌표를 의미합니다. 육면체가 여러 번 필요하기 때문에 이것을 for 루프로 만들기 위한 것입니다.
GLfloat data[]= {
// 앞면
0.0f, 0.0f, -1.0f, -1.0f, +1.0f,
1.0f, 0.0f, +1.0f, -1.0f, +1.0f,
1.0f, 1.0f, +1.0f, +1.0f, +1.0f,
0.0f, 1.0f, -1.0f, +1.0f, +1.0f,
// 뒷면
1.0f, 0.0f, -1.0f, -1.0f, -1.0f,
1.0f, 1.0f, -1.0f, +1.0f, -1.0f,
0.0f, 1.0f, +1.0f, +1.0f, -1.0f,
0.0f, 0.0f, +1.0f, -1.0f, -1.0f,
// 윗면
0.0f, 1.0f, -1.0f, +1.0f, -1.0f,
0.0f, 0.0f, -1.0f, +1.0f, +1.0f,
1.0f, 0.0f, +1.0f, +1.0f, +1.0f,
1.0f, 1.0f, +1.0f, +1.0f, -1.0f,
// 아래면
1.0f, 1.0f, -1.0f, -1.0f, -1.0f,
0.0f, 1.0f, +1.0f, -1.0f, -1.0f,
0.0f, 0.0f, +1.0f, -1.0f, +1.0f,
1.0f, 0.0f, -1.0f, -1.0f, +1.0f,
// 오른쪽면
1.0f, 0.0f, +1.0f, -1.0f, -1.0f,
1.0f, 1.0f, +1.0f, +1.0f, -1.0f,
0.0f, 1.0f, +1.0f, +1.0f, +1.0f,
0.0f, 0.0f, +1.0f, -1.0f, +1.0f,
// 왼쪽면
0.0f, 0.0f, -1.0f, -1.0f, -1.0f,
1.0f, 0.0f, -1.0f, -1.0f, +1.0f,
1.0f, 1.0f, -1.0f, +1.0f, +1.0f,
0.0f, 1.0f, -1.0f, +1.0f, -1.0f
};
다음은 실행중에 익스텐션 지원이 되는지를 확인합니다.
먼저 우리는 긴 문자열에 모든 가능한 익스텐션들이 \\n 으로 나뉘어진 서브 문자열로 저장되어 있는 것으로 생각할 수 있습니다. 따라서 해야 할 일은 \\n 을 찾아서 문자열을 검색하기를 다른 \\n 을 만나거나 맞는 문자열이 없을 때까지 반복합니다. 첫 번째 경우는 문자열을 찾았을 때 true 를 리턴하고, 두 번째 경우는 end of string 을 만날 때까지 다음번 서브 문자열을 취하여 비교합니다. 문자열을 시작할 때 조금 주의해야 할 것은, 문자열이 newline 문자로 시작하지 않기 때문입니다.
어쨌든, 어떤 특정한 익스텐션이 가능한지를 실행할 때에 반드시 확인해야 한다는 점입니다.
bool isInString(char *string, const char *search) {
int pos=0;
int maxpos=strlen(search)-1;
int len=strlen(string);
char *other;
for (int i=0; i<len; i++) {
if ((i==0) || ((i>1) && string[i-1]=='\\n')) {
other=&string[i];
pos=0;
while (string[i]!='\\n') {
if (string[i]==search[pos]) pos++;
if ((pos>maxpos) && string[i+1]=='\\n') return true;
i++;
}
}
}
return false;
}
이제 익스텐션 문자열을 받아서 원하는 문자열인지를 검색하기 위하여 \\n 문자열로 변환합니다. GL_ARB_multitexture 이 들어 있는 서브 문자열을 찾게 되면 이 기능이 지원되는 것입니다. 그러나 __ARB_ENABLE 이 true 이어야만 이 기능을 사용할 수 있습니다. 그리고 GL_EXT_feature_env_combine 역시 지원되어야 합니다. 이 기능에서는 텍스쳐와 유닛이 작용하는 새로운 방법이 적용됩니다. GL_ARB_multitexture 가 어떤 텍스쳐 유닛에서 그 다음 높은 값만을 넣을 수 있기 때문에 이 기능이 필요합니다. 따라서 다른 복잡한 블렌딩 공식을 (동일한 효과를 가져오지 않을 것입니다) 사용하는 대신 이 익스텐션을 확인하는 것이 좋습니다. 모든 익스텐션이 지원되고 우리가 덮어쓰지 않았다면 얼마나 많은 텍스쳐 유닛이 사용 가능한지를 확인하여 maxTexelUnits 에 저장합니다. 그 다음 함수를 우리 이름으로 걸어줍니다. wglGetProcAdress() 인수에 호출할 함수의 이름을 문자열과 프로토타입 함수가 정확한 함수 형태로 변형시켜 줍니다.
bool initMultitexture(void) {
char *extensions;
extensions=strdup((char *) glGetString(GL_EXTENSIONS));
int len=strlen(extensions);
for (int i=0; i<len; i++)
if (extensions[i]==' ') extensions[i]='\\n';
#ifdef EXT_INFO
MessageBox(hWnd,extensions,"supported GL extensions",MB_OK | MB_ICONINFORMATION);
#endif
if (isInString(extensions,"GL_ARB_multitexture")
&& __ARB_ENABLE
&& isInString(extensions,"GL_EXT_texture_env_combine"))
glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&maxTexelUnits);
glMultiTexCoord1fARB = (PFNGLMULTITEXCOORD1FARBPROC)
wglGetProcAddress("glMultiTexCoord1fARB");
glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC)
wglGetProcAddress("glMultiTexCoord2fARB");
glMultiTexCoord3fARB = (PFNGLMULTITEXCOORD3FARBPROC)
wglGetProcAddress("glMultiTexCoord3fARB");
glMultiTexCoord4fARB = (PFNGLMULTITEXCOORD4FARBPROC)
wglGetProcAddress("glMultiTexCoord4fARB");
glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)
wglGetProcAddress("glActiveTextureARB");
glClientActiveTextureARB= (PFNGLCLIENTACTIVETEXTUREARBPROC)
wglGetProcAddress("glClientActiveTextureARB");
#ifdef EXT_INFO
MessageBox(hWnd,"The GL_ARB_multitexture extension will be used.","feature supported!",MB_OK | MB_ICONINFORMATION);
#endif
return true;
}
useMultitexture=false;
return false;
}
InitLights() 함수는 OpenGL 의 광원을 설정하는 함수로서 이후에 InitGL() 에서 호출됩니다.
void initLights(void) {
glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);
glEnable(GL_LIGHT1);
}
이제 많은 텍스쳐를 읽어들입니다. auxDIBImageLoad()함수에는 에러 핸들러가 없고, LoadBMP() 함수는 try-catch 블럭이 없으면 문제를 발견하기 어렵기 때문에 일단 접어두겠습니다. 이제 텍스쳐 읽기 루틴입니다. 먼저 기본 비트맵을 읽고 세 개의 필터링된 텍스쳐를 만듭니다. (GL_NEAREST, GL_LINEAR, GL_LINEAR_MIPMAP_NEAREST) 비트맵을 저장하기 위해서 하나의 데이터 구조만을 사용한 점을 잘 보십시오. 비트맵을 열기 위해서 한 번에 하나씩만 사용했기 때문입니다. 그리고 여기서 새로운 데이터 구조 alpha 를 사용했습니다. 이것은 텍스쳐의 알파 층을 저장하여 RGBA 이미지를 두 개의 비트맵(24비트 RGB와 8비트 알파채널)으로 저장합니다. 상태 지시기가 정확히 움직이게 하기 위해서 이미지를 읽어들인 다음에 이미지 블럭을 지우고 NULL 로 초기화해줍니다.
그리고 여기서는 텍스쳐 타입을 지정할 때 GL_RGB8 을 3 대신 사용합니다.
int LoadGLTextures() {
bool status=true;
AUX_RGBImageRec *Image=NULL;
char *alpha=NULL;
if (Image=auxDIBImageLoad("Data/Base.bmp")) {
glGenTextures(3, texture);
glBindTexture(GL_TEXTURE_2D, texture[0]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, Image->sizeX, Image->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image->data);
glBindTexture(GL_TEXTURE_2D, texture[1]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, Image->sizeX, Image->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image->data);
glBindTexture(GL_TEXTURE_2D, texture[2]);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB8, Image->sizeX, Image->sizeY, GL_RGB, GL_UNSIGNED_BYTE, Image->data);
}
else status=false;
if (Image) {
if (Image->data) delete Image->data;
delete Image;
Image=NULL;
}
이제 범프 맵을 읽어들입니다. 자세한 것은 나중에 다루고, 이것은 50% 밝기를 가지기 때문에 두 가지 방법으로 스케일을 해야 합니다. 저는 glPixelTransferf() 함수를 이용하기로 했는데, 이것ㅇ느 픽셀 단위로 비트맵이 텍스쳐로 변환되는지를 지정하는 것입니다. 저는 이것으로 비트맵의 RGB 요소들을 50%로 만들었습니다. 여러분들이 이 함수들을 써보신 적이 없으시다면 glPixelTransfer() 명령어군을 살펴보시기를 권합니다. 이 함수들은 아주 유용합니다.
다른 문제는 우리 비트맵이 텍스쳐 위에서 계속 반복되기를 원하지 않는다는 점입니다. 우리는 (s,t)=(0.0f, 0.0f) 에서부터 (s,t)=(1.0f, 1.0f) 까지 한 번 텍스쳐 좌표로 매핑되기를 원합니다. 모든 다른 텍스쳐 좌표들은 검은색으로 매핑되어야 합니다. 이것은 두 개의 glTexParameteri() 함수의 호출로서 가능합니다.
if (Image=auxDIBImageLoad("Data/Bump.bmp")) {
glPixelTransferf(GL_RED_SCALE,0.5f);
glPixelTransferf(GL_GREEN_SCALE,0.5f);
glPixelTransferf(GL_BLUE_SCALE,0.5f);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
glGenTextures(3, bump);
다음의 문장은 다 아실 것입니다. 나중에 설명드리겠습니다만, 반전된 범프 맵을 밝기 50%로 다시 만들어 줍니다. 범프 맵을 완전 흰 색, 정수 표현으로 (255, 255, 255) 으로부터 빼서 구합니다. 우리가 RGB 스케일을 100%로 되돌리지 않았기 때문에 (제 첫 번째 판 프로그램의 가장 큰 문제가 이것이었고, 이것을 찾는데 세 시간이 걸렸습니다) 반전된 범프 맵 역시 50% 밝기로 스케일될 것입니다.
for (int i=0; i<3*Image->sizeX*Image->sizeY; i++)
Image->data[i]=255-Image->data[i];
glGenTextures(3, invbump);
}
else status=false;
if (Image) {
if (Image->data) delete Image->data;
delete Image;
Image=NULL;
}
로고 비트맵을 읽는 것은 RGB와 A 의 조합인 것만 제외하고는 아주 평이해서 살펴보시면 뜻을 알 수 있을 것입니다. 텍스쳐가 이미지 메모리 블럭이 아니라 알파 메모리 블럭에서 만들어졌다는 것을 주의하십시오. 한 가지 필터만 사용되고 있습니다.
if (Image=auxDIBImageLoad("Data/OpenGL_ALPHA.bmp")) {
alpha=new char[4*Image->sizeX*Image->sizeY];
for (int a=0; a<Image->sizeX*Image->sizeY; a++)
alpha[4*a+3]=Image->data[a*3];
if (!(Image=auxDIBImageLoad("Data/OpenGL.bmp"))) status=false;
for (a=0; a<Image->sizeX*Image->sizeY; a++) {
alpha[4*a]=Image->data[a*3];
alpha[4*a+1]=Image->data[a*3+1];
alpha[4*a+2]=Image->data[a*3+2];
}
glGenTextures(1, &glLogo);
glBindTexture(GL_TEXTURE_2D, glLogo);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Image->sizeX, Image->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, alpha);
delete alpha;
}
else status=false;
if (Image) {
if (Image->data) delete Image->data;
delete Image;
Image=NULL;
}
if (Image=auxDIBImageLoad("Data/multi_on_alpha.bmp")) {
alpha=new char[4*Image->sizeX*Image->sizeY];
glGenTextures(1, &multiLogo);
delete alpha;
}
else status=false;
if (Image) {
if (Image->data) delete Image->data;
delete Image;
Image=NULL;
}
return status;
}
doCube() 함수는 완전하고 정규화된 육면체를 만드는 코드입니다. 이 함수에서는 텍스쳐 유닛 #0 을 넣는데, glTexCoord2f(s,t) 는 glMultiTexCoord2f(GL_TEXTURE0_ARB,s,t) 와 동일하기 때문입니다. 그리고 육면체는 중첩된 배열로 만들 수 있습니다만 지금의 논의와는 다른 주제가 됩니다. 그리고 이 육면체는 출력 리스트로는 만들 수가 없는데 왜냐하면 출력 리스트는 GLfloat 와는 다른 내부적인 부동소숫점의 정확성을 이용하기 때문인 것 같습니다. 따라서 데칼 문제와 같은 여러 가지 복잡한 문제들이 야기됩니다. 멀티패스 알고리즘의 일반적인 법칙은 출력리스트를 모두 다 쓰거나 아주 쓰지 않게 됩니다. 두 가지를 혼합하는 것은 어떤 기계에서도 동작하지 않으니 절대로 사용하지 마십시오.
void doCube (void) {
int i;
glBegin(GL_QUADS);
glNormal3f( 0.0f, 0.0f, +1.0f);
for (i=0; i<4; i++) {
glTexCoord2f(data[5*i],data[5*i+1]);
glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
}
glNormal3f( 0.0f, 0.0f,-1.0f);
for (i=4; i<8; i++) {
glTexCoord2f(data[5*i],data[5*i+1]);
glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
}
glNormal3f( 0.0f, 1.0f, 0.0f);
for (i=8; i<12; i++) {
glTexCoord2f(data[5*i],data[5*i+1]);
glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
}
glNormal3f( 0.0f,-1.0f, 0.0f);
for (i=12; i<16; i++) {
glTexCoord2f(data[5*i],data[5*i+1]);
glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
}
glNormal3f( 1.0f, 0.0f, 0.0f);
for (i=16; i<20; i++) {
glTexCoord2f(data[5*i],data[5*i+1]);
glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
}
glNormal3f(-1.0f, 0.0f, 0.0f);
for (i=20; i<24; i++) {
glTexCoord2f(data[5*i],data[5*i+1]);
glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
}
glEnd();
}
이제 OpenGL을 초기화합니다. 강의 6번에서 한 것과 모두 동일한데, 한 가지, 여기서 광원을 설정하지 않고 initLights() 함수를 호출합니다. 그리고 여기서 멀티텍스쳐 셋업도 이루어집니다.
int InitGL(GLvoid)
{
multitextureSupported=initMultitexture();
if (!LoadGLTextures()) return false;
glEnable(GL_TEXTURE_2D);
glShadeModel(GL_SMOOTH);
glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
glClearDepth(1.0f);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
initLights();
return true
}
이제 전체 작업의 95%에 해당하는 부분입니다. “나중에 설명하겠습니다”라고 말씀드렸던 모든 문제들이 여기에 있는 이론 부분에서 설명됩니다.
이론 설명 (엠보스 범프 매핑)
여러분 컴퓨터에 파워포인트 뷰어가 있으시면 한 번 이 프리젠테이션을 다운받아서 보시기 바랍니다.
Emboss Bump Mapping by Michael I. Gold, nVidia Corp. [.ppt, 309K]
우리가 여기서 하는 것은 TNT 의 구현과는 약간 다르게 하여 모든 비디오 카드에서 동작하게 하였습니다. 두세 가지를 여기서 배울 수 있는데, 첫 번째, 범프 매핑은 대부분 그래픽 카드에서 다중 패스 알고르즘이라는 점입니다. (TNT 계엘 제품들은 2텍스쳐를 이용한 1패스 구현) 여러분은 이제 멀티텍스쳐링이 얼마나 대단한 기능인가를 이해하실 것입니다. 우리는 이제 2패스 멀티텍스쳐 알고리즘을 개량한 3패스 비 멀티텍스쳐 알고리즘을 구현할 것입니다.
이제부터 여러분이 신경쓰셔야 할 것은 우리는 매트릭스-매트릭스 곱셈을 해야 합니다. (매트릭스-벡터 곱셈 포함) 그러나 걱정하실 필요는 없습니다. OpenGL 에서 매트릭스-매트릭스 곱셈을 수행할 수 있습니다. 그리고 매트릭스-벡터 곱셈은 아주 쉽습니다. VMatMult(M,v) 함수는 매트릭스 M 과 벡터 v 를 곱하여 그 결과를 v 에 저장합니다. 전달되는 모든 매트릭스와 벡터는 동일한 좌표계를 사용해야 하며, 따라서 4x4 매트릭스와 4차원 벡터를 사용합니다.
void VMatMult(GLfloat *M, GLfloat *v) {
GLfloat res[3];
res[0]=M[ 0]*v[0]+M[ 1]*v[1]+M[ 2]*v[2]+M[ 3]*v[3];
res[1]=M[ 4]*v[0]+M[ 5]*v[1]+M[ 6]*v[2]+M[ 7]*v[3];
res[2]=M[ 8]*v[0]+M[ 9]*v[1]+M[10]*v[2]+M[11]*v[3];
v[0]=res[0];
v[1]=res[1];
v[2]=res[2];
v[3]=M[15];
}
이론 설명 (엠보스 범프 매핑 알고리즘)
여기서는 두 가지 알고리즘을 다룹니다. 첫 번째 알고리즘은 여기에서 ( http://www.nvidia.com/marketing/Developer/DevRel.nsf/TechnicalDemosFrame?OpenPage ) 발견했습니다.
이 프로그램은 GL_BUMP 라고 하며 Diego T’tara 가 1999년에 만든 것입니다. 멋있는 범프 매핑을 구현하였는데, 약간의 부작용도 있습니다. 먼저 T’tara의 알고리즘을 살펴보겠습니다.
모든 벡터들은 물체나 계 공간에 놓는다.
현재 꼭지점에서 빛의 위치로의 벡터 v 를 구한다.
벡터 v 를 정규화한다.
벡터 v 를 법선 계로 투영한다. (이것은 현재 꼭지점에서 표면에 닿는 평면이 된다) 일반적으로 평면에서 작업중이라면 이것은 평면 자체가 된다.
옵셋 (s,t) 투영된 벡터 v 의 x, y 좌표이다.
나쁘지 않은 방법입니다. 이 알고리즘은 기본적으로 M
최신글이 없습니다.
최신글이 없습니다.
댓글목록 0