[App 개발] NeHe Lesson 20
본문
블렌딩된 텍스쳐는 대부분 블렌딩이 더 되었거나 덜 되었거나 합니다. 스프라이트를 이용한 게임을 만들 때 주인공 때문에 주인공 뒤의 배경이 빛나게 되는 것은 별로 원하지 않을 것입니다. 여러분은 글씨를 화면에 출력할 때 글자를 또렷하게 해서 읽기 쉽게 만들고 싶으실 것입니다.
이럴 때 마스킹이 유용합니다. 마스킹은 두 가지 단계의 작업입니다. 첫 번째, 텍스쳐의 흑백 이미지를 씬 위에 올려놓습니다. 흰색은 텍스쳐의 투명한 부분이고, 검은 색은 텍스쳐의 불투명한 부분입니다. 우리가 사용하는 블렌딩으로 인하여 화면에는 검은 색 부분만이 드러날 것입니다. 마치 쿠키 커터 효과같습니다. 이제 블렌딩 모드를 변경하고 검은 색을 도려낸 위에 텍스쳐를 입힙니다. 블렌딩 모드에 의하여 화면의 검은 색 마스크 위의 부분에 해당하는 텍스쳐만이 복사될 것입니다.
이 강의에서는 새로 추가된 부분만 게재하지 않고, 프로그램을 몽땅 소개하겠습니다. 새로운 것을 배우실 준비가 되셨습니까? 그러면 시작하죠.
#include <math.h>
#include <stdio.h>
#include <gl\\gl.h>
#include <gl\\glu.h>
#include <gl\\glaux.h>
우리는 일곱 개의 광역 변수를 사용할 것입니다. 변수 masking 은 논리 변수 (TRUE/FALSE) 로서 마스킹 기능이 켜졌는지 꺼졌는지를 기록합니다. 변수 mp 는 키보드 M 이 눌려져 있는지를 기록합니다. 변수 sp 는 스페이스바가 눌려있는지를 기록하고, 변수 scene 은 첫 번째 혹은 두 번째 씬에 그리고 있는지를 기록합니다.
변수 texture[5] 은 우리가 사용할 다섯 개의 텍스쳐를 저장할 공간입니다. 변수 loop 은 일반적인 카운터 변수입니다. 텍스쳐를 설정하는 등에 간간히 사용할 것입니다. 마지막으로 변수 roll 은 화면을 가로질러 텍스쳐가 돌아다니도록 해서 멋있는 효과를 만드는 데에 쓰입니다. 그리고 두 번째 신에서는 물체를 회전하는 데에도 쓰입니다.
bool masking=TRUE;
bool mp;
bool sp;
bool scene;
GLuint texture[5];
GLuint loop;
GLfloat roll;
비트맵 읽기 코드는 강의 6 번 이후로 변한 것이 없습니다.
다음은 다섯 개의 이미지를 읽어들일 장소를 만드는 코드입니다. 먼저 다섯 개의 이미지를 읽을 장소를 초기화하고 루프를 이용해서 각 이미지를 텍스쳐로 변환합니다. 텍스쳐는 texture[0-4]에 저장됩니다.
int LoadGLTextures()
{
int Status=FALSE;
AUX_RGBImageRec *TextureImage[5];
memset(TextureImage,0,sizeof(void *)*5);
if ((TextureImage[0]=LoadBMP("Data/logo.bmp")) &&
(TextureImage[1]=LoadBMP("Data/mask1.bmp")) &&
(TextureImage[2]=LoadBMP("Data/image1.bmp")) &&
(TextureImage[3]=LoadBMP("Data/mask2.bmp")) &&
(TextureImage[4]=LoadBMP("Data/image2.bmp")))
{
Status=TRUE;
glGenTextures(5, &texture[0]);
for (loop=0; loop<5; loop++)
{
glBindTexture(GL_TEXTURE_2D, texture[loop]);
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, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
}
}
for (loop=0; loop<5; loop++)
{
if (TextureImage[loop])
{
if (TextureImage[loop]->data)
{
free(TextureImage[loop]->data);
}
free(TextureImage[loop]);
}
}
return Status;
}
ReSizeGLScene() 도 변경이 없으니 넘어가도록 하겠습니다.
초기화 코드는 완전히 기본 골격입니다. 텍스쳐를 읽고, 화면 지우는 색상 선정하고, depth testing 을 활성화하고, smooth shading 을 켜고, 텍스쳐 매핑을 활성화합니다. 간단한 프로그램에 복잡한 초기화는 필요가 없죠.
int InitGL(GLvoid)
{
if (!LoadGLTextures())
{
return FALSE;
}
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepth(1.0);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_SMOOTH);
glEnable(GL_TEXTURE_2D);
return TRUE;
}
이제 재미있는 그리기 코드입니다. 항상 하던 것으로 시작합니다. 먼저 배경색으로 화면을 지우고 depth buffer 를 초기화합니다. 그다음 모델뷰 매트릭스를 리셋하고 화면 안쪽으로 2유닛 이동하여 씬을 볼 수 있게끔 합니다.
int DrawGLScene(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0.0f,0.0f,-2.0f);
다음은 로고 텍스쳐를 선택합니다. 여기서는 화면에 사각형으로 텍스쳐를 입힙니다. 네 텍스쳐 좌표를 네 꼭지점에 대응시킵니다.
Jonathan Roy 가 다시 설명합니다. OpenGL 은 꼭지점 기반 시스템입니다. 대부분의 인수들은 특정한 꼭지점의 특성으로 기록됩니다. 텍스쳐 좌표는 그러한 특성 중의 하나입니다. 여러분이 다각형의 꼭지점에 대하여 적절한 텍스쳐 좌표를 설정하게 되면 OpenGL은 interpolation 이라는 기법을 이용해서 꼭지점 사이의 평면에 텍스쳐를 자동적으로 입히게 됩니다. Interpolation 이란 OpenGL 이 꼭지점들이 갖고 있는 값을 통해서 꼭지점 사이의 변수 값이 어떻게 변화하는지를 알도록 하는 기본적인 기하학적인 기법입니다.
지난 강의들과 마찬가지로 사각형을 바라보는 입장에서 텍스쳐 좌표를 다음과 같이 (0.0, 0.0)과 왼쪽 아래, (0.0, 1.0)과 오른쪽 위, (1.0, 0.0) 과 오른쪽 아래, (1.0, 1.0)과 오른쪽 위로 부여합니다. 이러한 설정에서, 사각형의 중앙은 텍스쳐 좌표로 어떻게 될까요? 맞습니다. (0.5, 0.5) 입니다. 그러나 코드 어떤 부분에서도 그것을 지정해 준 일은 없습니다. 사각형을 그릴 때 OpenGL이 자동으로 부여합니다. 어떤 위치, 크기, 기준의 다각형이라도 처리할 수 있습니다.
이번 강의에서 우리는 텍스쳐 좌표를 0.0과 1.0으로 주지 않고 약간 변화를 줄 것입니다. 텍스쳐 좌표는 정규화되어 있습니다. 0.0은 텍스쳐의 끝부분이고, 1.0은 반대편 끝부분에 해당하여, 텍스쳐 이미지의 픽셀 크기와는 상관 없이 텍스쳐 이미지의 전체 넓이와 높이가 한 유닛 크기에 펼쳐져 있습니다. (따라서 우리는 텍스쳐 이미지의 크기를 신경쓰지 않아도 되고, 프로그램을 쉽게 짤 수 있습니다) 1.0보다 큰 값은 텍스쳐의 반대편 모서리로 덮인 것처럼 되어 텍스쳐가 반복됩니다. 다시 말해서 텍스쳐 좌표 (0.3, 0.5)를 예로 들면, (1.3, 0.5) 나 (12.3, -2.5)는 모두 텍스쳐 이미지의 동일한 픽셀로 매핑됩니다. 이 강의에서는 1.0 대신 3.0 을 대입하여 사각형 표면 위에 텍스쳐를 아홉 번 (3x3 타일) 반복하는 타일링 효과를 얻을 것입니다.
더불어서, 변수 roll 을 이용하여 사각형 표면 위에서 텍스쳐를 이동 (슬라이딩) 시킬 것입니다. 수직 텍스쳐 좌표에 변수 roll 값 0.0 이면 사각형 밑면의 텍스쳐 매핑이 텍스쳐 이미지의 밑면으로부터 시작된다는 것을 뜻합니다. 변수 roll 이 0.5가 되면 사각형 밑의 매핑이 이미지의 중간부터 시작됩니다. 이동하는 텍스쳐는 움직이는 구름이나 물체에 움직이는 글자를 만드는 등 좋은 효과를 만들 때 쓰일 수 있습니다.
glBindTexture(GL_TEXTURE_2D, texture[0]);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, -roll+0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);
glTexCoord2f(3.0f, -roll+0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);
glTexCoord2f(3.0f, -roll+3.0f); glVertex3f( 1.1f, 1.1f, 0.0f);
glTexCoord2f(0.0f, -roll+3.0f); glVertex3f(-1.1f, 1.1f, 0.0f);
glEnd();
어쨌든, 현실로 돌아와서, 이제 블렌딩을 활성화합니다. 이 효과가 동작하게끔 하기 위해서는 depth testing 을 꺼야 합니다. 이렇게 하는 것이 매우 중요합니다. Depth testing 을 끄지 않으면 전체 이미지는 사라지고 여러분은 아무것도 볼 수 없을 것입니다.
glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
블렌딩과 depth testing 다음에 해야 할 첫 번째 일은 이제 이미지 마스킹을 할 것인지 옛날 식으로 블렌딩을 할 것인지 확인하는 것입니다. 다음은 masking 이 TRUE 인지 확인해서, 그렇다면 블렌딩을 설정해서 우리 마스크가 화면에 제대로 그려질 수 있도록 하는 코드입니다.
if (masking)
{
만약 masking 이 TRUE 라면 다음 코드에서 마스크를 위한 블렌딩을 설정합니다. 마스크는 화면에 그리고자 하는 흑백 텍스쳐에 불과합니다. 마스크의 흰 색 부분은 투명한 부분이 되고, 검은 색 부분은 불투명한 부분이 됩니다.
다음의 블렌딩 명령은 다음과 같은 일을 합니다. 만일 입혀지는 마스크의 색상이 검은 색이라면 목적 색상 (화면색) 을 검은 색으로 만듭니다. 다시 말해서 마스크의 검은 색에 해당하는 화면 영역은 검은 색이 됩니다. 마스크 밑에 있는 화면의 어떤 것이라도 검은 색으로 지워집니다. 흰 색 마스크로 덮여지는 화면의 영역은 변함이 없습니다.
glBlendFunc(GL_DST_COLOR,GL_ZERO);
}
이제 어떤 씬이 그려지는지 검사합니다. 만일 scene 이 TRUE 라면 두 번째 씬을 그리는 것이고, 만일 FALSE 라면 첫 번째 씬을 그리는 것입니다.
if (scene)
{
물체가 너무 큰 것을 원하지 않기 때문에 화면 안쪽으로 한 유닛 더 이동하겠습니다. 이렇게 하면 물체의 크기가 줄어듭니다.
화면 안쪽으로 이동한 다음 roll 값에 따라서 0-360도 회전합니다. 만약 roll 이 0.0 이라면 0도 회전하고, 1.0 이라면 360도 회전합니다. 약간 회전이 빠른 듯 합니다만, 단지 화면 중앙에서 이미지를 회전하기 위해서 또 다른 변수를 만들 필요는 없을 것 같군요.
glTranslatef(0.0f,0.0f,-1.0f);
glRotatef(roll*360,0.0f,0.0f,1.0f);
우리는 이미 화면에 움직이는 로고를 만들었고, 씬을 Z 축을 중심으로 회전하게 해서 그려지는 어떤 물체라도 반시계방향으로 회전하게 되었습니다. 이제 masking 이 켜져 있는지를 확인합니다. 만일 켜져 있으면 마스크를 그리고 물체를 그리게 되고, 꺼져 있으면 단지 물체만 그리게 됩니다.
if (masking)
{
만일 masking 이 TRUE 라면 화면에 마스크를 그립니다. 우리는 마스크를 입히면서 이미 한 번 블렌딩을 설정했기 때문에 블렌딩 모드는 확실히 설정되어 있을 것입니다. 이제 화면에 마스크를 그립니다. 두 번재 씬이기 때문에 마스크 2 를 선택합니다. 마스크 텍스쳐를 선택하 다음 사각형에 텍스쳐를 매핑합니다. 사각형은 왼쪽 오른쪽으로 1.1 유닛으로 설정하여 화면을 조금 더 덮습니다. 텍스쳐 하나만 출력할 것이기 때문에 택스쳐 좌표는 0.0부터 1.0까지입니다.
화면에 마스크를 그리고 나면 텍스쳐의 검은 색 모습이 화면에 남게 됩니다. 최종 결과물은 마치 누군가가 뽑기 틀을 가지고 화면에서 마지막 텍스쳐의 모습같이 잘라낸 듯 검은 공간을 남겨놓게 됩니다.
glBindTexture(GL_TEXTURE_2D, texture[3]);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f, 1.1f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f, 1.1f, 0.0f);
glEnd();
}
이제 마스크를 화면에 그리는 일이 끝났고 블렌딩 모드를 바꿀 차례입니다. 이번에는 OpenGL 에게 화면에 텍스쳐의 검은 색을 제외한 나머지 부분을 복사하게 합니다. 마지막 텍스쳐는 마스크와 동일한 모양에 색상을 가지고 있어서, 텍스쳐에서 화면으로 그려지는 부분은 마스크의 검은 색 위에 있던 화면 영역이 됩니다. 마스크가 검은 색이었기 때문에 화면에서 텍스쳐를 통과해서 드러나는 부분이 없어지게 됩니다. 이렇게 하면 화면 위를 움직이는 아주 두툼한 느낌의 텍스쳐를 만들게 됩니다.
마지막 블렌딩 모드를 선택한 다음에 다음 이미지를 선택한 것을 잘 보십시오. 이렇게 해서 두 번째 마스크에 기초한 색상 이미지를 선택하게 됩니다. 그리고, 동일한 텍스쳐 좌표와 동일한 꼭지점을 사용해서, 마스크의 바로 위에 이미지를 그리게 됩니다.
만약 마스크 위에 정확히 덮지 않으면 이미지가 화면에 복사되긴 하지만 화면에 있던 픽셀과 섞이게 됩니다.
glBlendFunc(GL_ONE, GL_ONE);
glBindTexture(GL_TEXTURE_2D, texture[4]);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f, 1.1f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f, 1.1f, 0.0f);
glEnd();
}
만약 scene 이 FALSE 였다면 첫 번째 씬을 그립니다.
else
{
이제 masking 이 TRUE 인지 FALSE 인지 확인하는 코드입니다.
if (masking)
{
만약 masking 이 TRUE 라면 첫 번째 마스크 (첫 번째 씬의 마스크) 를 화면에 출력합니다. 텍스쳐가 오른쪽에서 왼쪽으로 (roll 변수가 수평 텍스쳐 좌표에 더해지므로) 움직입니다. 이 텍스쳐를 전체 화면에 출력할 것이므로 화면 안쪽으로 이동하지 않을 것입니다.
glBindTexture(GL_TEXTURE_2D, texture[1]);
glBegin(GL_QUADS);
glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);
glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);
glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f, 1.1f, 0.0f);
glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f, 1.1f, 0.0f);
glEnd();
}
다시 블렌딩을 활성화하고 첫 번째 씬을 위한 텍스쳐를 선택합니다. 마스크 위에 텍스쳐를 입히게 됩니다. 이 텍스쳐도 역시 움직이게 해야 합니다. 그렇지 않으면 마스크와 최종 이미지가 맞아 떨어지지 않게 됩니다.
glBlendFunc(GL_ONE, GL_ONE);
glBindTexture(GL_TEXTURE_2D, texture[2]);
glBegin(GL_QUADS);
glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);
glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);
glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f, 1.1f, 0.0f);
glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f, 1.1f, 0.0f);
glEnd();
}
이제 depth testing 을 켜고 블렌딩을 끕니다. 이렇게 해서 이후의 프로그램에 의해서 이상한 일들이 발생하지 않게 합니다.
glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
마지막으로 남은 일은 변수 roll 을 증가시키는 일입니다. 만약 roll 이 1.0 보다 크면 1.0 을 빼서 roll 값이 한게를 넘는 일을 방지합니다.
roll+=0.002f;
if (roll>1.0f)
{
roll-=1.0f;
}
return TRUE;
}
KillGLWindow(), CreateGLWindow() 는 변함이 없으니 넘어갑니다.
이제 키보드 처리 코드입니다. 먼저 스페이스바가 눌렸는지 검사해서 눌렸으면 변수 sp 를 TRUE 로 놓습니다. 만일 sp 가 TRUE 라면 다음 코드는 두 번째부터는 스페이스바가 떨어지기 전까지 실행되지 않습니다. 이렇게 하면 씬이 빨리 뒤바뀌는 것을 방지할 수 있습니다. 변수 sp 를 TRUE 로 놓은 다음에는 scene 의 상태를 바꿉니다. 예전에 TRUE 였다면 이제 FALSE가 되고, 예전에 FALSE 였다면 이제 TRUE 가 됩니다. 위에 있는 그리기 코드에서 scene 이 FALSE 라면 첫 번째 씬이 그려지고, TRUE 라면 두 번째 씬이 그려집니다.
if (keys[' '] && !sp)
{
sp=TRUE;
scene=!scene;
}
다음은 스페이스바가 떨어졌는지를 검사해서 떨어졌으면 sp 를 FALSE 로 놓아서 스페이스바가 계속 눌려있지 않다는 것을 프로그램이 알게 합니다. 변수 sp 가 FALSE 가 되면 위의 코드에서 다시 스페이스바가 눌렸는지를 검사하게 되고, 눌렸으면 위의 작업이 반복됩니다.
if (!keys[' '])
{
sp=FALSE;
}
다음은 키보드 M 이 눌렸는지를 검사해서, 눌렸으면 mp 를 TRUE 로 놓아서 키보드가 떨어지기 전까지 다시 검사하지 않도록 하고, 변수 masking 을 TRUE 에서 FALSE로, FALSE 에서 TRUE 로 변경합니다. 만약 masking 이 TRUE 라면 마스크를 그리게 되고, FALSE 라면 마스크를 그리지 않습니다. 만약 마스크를 그리지 않으면 우리가 지금까지 사용한 옛날 방식의 블렌딩 방식이 사용되고 화면과 물체가 서로 섞이게 됩니다.
if (keys['M'] && !mp)
{
mp=TRUE;
masking=!masking;
}
마지막으로 키보드 M 이 그만 눌렸는지를 검사해서 그렇다면 mp 를 FALSE 로 놓아서 키보드 M 이 더 이상 눌려있지 않음을 프로그램이 알게 합니다. 키보드 M 이 떨어지면 마스킹 모드를 다시 켜거나 끌 수 있게 됩니다.
if (!keys['M'])
{
mp=FALSE;
}
최신글이 없습니다.
최신글이 없습니다.
댓글목록 0