출처: http://soen.kr/lecture/library/opengl/opengl-10.htm

34. 텍스처: 비트맵

비트맵은 미리 만들어진 이미지를 의미하며 다양한 크기와 색상을 가질 수 있다. 그러나 OpenGL에서는 흑백 이미지만을 비트맵이라고 칭하며 컬러 이미지는 픽셀맵(Pixelmap)이라는 별도의 용어를 사용한다. bit가 용어가 원래 0과 1만을 의미하는 흑백적인 뜻이기 때문이라고 한다.

OpenGL은 파일 입출력 기능을 제공하지 않으므로 파일이나 리소스로부터 비트맵을 생성하기 어렵다. 간단한 이미지라면 메모리에서 래스터 데이터를 직접 정의하여 만들어 써야 한다. 모눈 종이에 다음과 같이 이미지를 디자인해 보자.

각 칸의 흰색은 이진수 0에 대응시키고 검정색은 1에 대응시킨다. 이 수를 나열하면 2진수가 나오는데 자리 수가 너무 많으므로 보통 16진수로 압축한다. 이렇게 만든 16진수를 배열에 순서대로 기록하면 비트맵이 된다. OpenGL의 비트맵은 아래에서 위로 정의되므로 제일 아래줄부터 배열에 기록한다.

비트맵의 래스터 데이터는 처리의 효율성을 위해 4바이트 단위로 정렬된다.  행이 4의 배수가 안되더라도 뒷 부분에 더미를 추가해서라도 4의 배수로 맞추는 것이 유리하다. 그러나 위 이미지는 한 행이 2바이트 밖에 안되어 더미를 넣으면 무려 2배나 커진다. 정렬 단위를 바꾸고 싶다면 다음 함수로 비트맵의 저장 방식을 변경해야 한다.

void glPixelStore[f, i](GLenum pname, GLfloat param);

이 함수로 바이트 순서(GL_PACK_SWAP_BYTES)나 바이트 정렬(GL_PACK_ALIGNMENT) 등의 옵션을 지정한다. 메모리에서 비트맵을 읽을 때의 정렬은 GL_UNPACK_ALIGNMENT 옵션으로 지정하며 이 값의 디폴트가 4이므로 4바이트 정렬된다. 2나 1로 바꾸면 더 작은 단위로 정렬할 수 있되 값을 일일이 잘라서 읽어야 하므로 속도에는 불리하다.

비트맵의 출력 위치는 다음 함수로 지정한다.

void glRasterPos[2,3,4][s,i,f,d][v](x, y,z,w);

이 함수가 지정한 좌표가 현재 위치가 되며 이 위치에서부터 비트맵이 출력되고 비트맵 출력 후 현재 위치는 자동으로 다음 위치로 갱신된다.

이 함수가 지정한 좌표는 변환의 영향을 받는다. 만약 변환에 상관없이 윈도우의 특정 좌표에 출력하려면 glWindowPos 함수를 대신 사용한다. 그러나 이 함수는 OpenGL 1.4 이후부터만 지원되므로 윈도우즈의 기본 구현에서는 당장 사용할 수 없다.

비트맵을 출력할 때는 다음 함수를 호출한다.

void glBitmap(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte * bitmap);

bitmap은 비트맵의 모양을 정의하는 래스터 데이터 배열이다. width, height는 비트맵의 폭과 높이를 픽셀 단위로 알려 주는데 일차원 배열의 래스터만으로는 비트맵의 모양을 알 수 없기 때문에 크기 정보를 별도로 제공해야 한다. 위 비트맵은 16 * 16 크기이므로 폭과 높이가 모두 16이다.

xorig, yorig 인수는 비트맵의 어느 부분이 현재 위치에 출력될 것인지 원점을 지정한다. 보통은 좌하단인 (0,0)을 지정하여 비트맵의 아래쪽이 현재 위치에 출력되지만 모양이 특이한 비트맵은 원점을 변경할 수도 있다. xmove, ymove는 비트맵을 그린 후에 이동할 양을 거리를 지정한다.

비트맵은 꼭 흑백으로만 출력해야 하는 것은 아니다. glColor 함수로 색상을 지정하면 비트맵의 1에 대응되는 부분에 색상이 출력된다. 그래서 단색 비트맵이지만 노란색이나 빨간색으로도 출력할 수 있다.

다음 예제는 앞에서 디자인한 16 * 16 크기의 비트맵을 출력한다.

#include <windows.h>
#include <gl/glut.h>

void DoDisplay();

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
       ,LPSTR lpszCmdParam,int nCmdShow)
{
     glutCreateWindow("OpenGL");
     glutDisplayFunc(DoDisplay);
     glutMainLoop();
     return 0;
}

void DoDisplay()
{
     static GLubyte bitmap[] = {
          0x07, 0xe0, 0x18, 0x18, 0x20, 0x04, 0x43, 0xc2,
          0x44, 0x22, 0x88, 0x11, 0x81, 0x81, 0x81, 0x81,
          0x80, 0x01, 0x80, 0x01, 0x92, 0x49, 0x4c, 0x32,
          0x40, 0x02, 0x20, 0x04, 0x18, 0x18, 0x07, 0xe0,
     };

     glClear(GL_COLOR_BUFFER_BIT);

     // 2바이트 단위로 비트맵을 읽는다.
     glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
     glColor3f(1,1,0); // 노란색
     glRasterPos2f(0.5,0.5);
     glBitmap(16,16,0,0,20,0,bitmap);
     glBitmap(16,16,0,0,20,0,bitmap);
     glBitmap(16,16,0,10,20,0,bitmap);

     glFlush();
}

bitmap 배열에 래스터 데이터를 16진수로 저장했다. 2바이트 정렬로 지정했으므로 별도의 더미 데이터를 삽입할 필요는 없으며 아래행부터 순서대로 죽 나열하면 된다. 만약 4바이트 정렬이라면 한 행당 최소 4바이트를 써야 하므로 두 바이트마다 0x00, 0x00을 삽입해야 한다.

비트맵의 색상은 glBitmap을 호출할 때 적용되는 것이 아니라 glRasterPos를 호출할 때 결정되므로 glRasterPos 함수를 호출하기 전에 먼저 지정해야 한다. 출력 위치는 0.5, 0.5로 지정했으므로 오른쪽 위 중간쯤에에서부터 비트맵이 나타날 것이다.

xmove를 모두 20으로 지정했으므로 비트맵 하나를 출력한 후 오른쪽으로 20픽셀만큼 이동한다. 비트맵 폭인 16보다 4만큼 더 주어 약간씩 여백을 띄웠다. 만약 ymove를 20으로 지정하면 비트맵은 세로로 연이어 출력될 것이다. xmove, ymove는 비트맵을 연속 출력할 때 방향과 거리를 지정하는 인수이다.

앞쪽 두 비트맵은 원점을 (0,0)으로 지정하여 비트맵의 좌하단이 현재 위치에 대응된다. 세번째 비트맵은 y 원점을 10으로 주어 비트맵의 위쪽 10픽셀 지점이 현재 위치에 출력되며 그래서 비트맵이 조금 아래쪽으로 내려간다. 원점을 조정하는 기능은 폰트처럼 베이스가 각각 다른 이미지를 출력할 때 기준선에 맞추어 나란히 출력하기 위해서이다.

 

35. 텍스처: 픽셀맵

컬러를 표현할 수 있는 픽셀맵은 비트맵에 비해서는 이미지를 만들거나 출력하기 훨씬 더 복잡하다. 출력 함수는 다음과 같다.

void glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid * data);

width, height는 이미지의 폭과 높이이다. format은 픽셀의 색상 구성이며 GL_RGB, GL_BGA 등을 지정한다. 이미지 파일에 따라 색상 정보가 RGB 순으로 되어 있는 것이 있고 BGA 순으로 되어 있는 것이 있다. type은 data 배열의 요소 타입을 지정하며 data가 실제 이미지 정보인 래스터 데이터이다.

다음 예제는 간단한 픽셀맵을 배열로 정의하고 출력한다.

#include <stdlib.h>
#include <time.h>

(...)

void DoDisplay()
{
     GLubyte data[32*32*3];

     srand(time(NULL));

     for (int y=0;y<32;y++) {
          for (int x=0;x<32;x++) {
              // 픽셀 한 개에 들어갈 RGB 색상정보 입력
              data[y*32*3 + x*3+0] = rand() % 200;
              data[y*32*3 + x*3+1] = rand() % 200;
              data[y*32*3 + x*3+2] = rand() % 50;
          }
     }

     glClear(GL_COLOR_BUFFER_BIT);
     glRasterPos2f(0.0,0.0);
     glDrawPixels(32, 32, GL_RGB, GL_UNSIGNED_BYTE, data);

     glFlush();
}

32*32크기의 픽셀맵은 각 픽셀당 색상 정보 3개씩이 필요하므로 총 필요한 바이트수는 3072바이트(=32*32*3)이다. 픽셀맵은 비트맵과는 달리 원점을 지정하는 기능이 없으므로 무조건 이미지의 좌하단이 현재 위치에 출력된다. 그래서 중앙에서 약간 오른쪽 위에 이미지가 출력되었다.

제대로 모양을 갖춘 이미지를 사용하려면 파일에서 읽어오는 것이 원칙적이다. OpenGL은 이미지 파일을 읽는 기능을 제공하지 않으므로 운영체제별로 적당한 이미지 읽기 함수를 만들어 사용해야 한다. 이 강좌는 윈도우즈 환경에서 실습을 진행하고 있으므로 윈도우즈 API로 읽기 용이한 BMP 파일을 출력했다.

#include <windows.h>
#include <gl/glut.h>

void DoDisplay();

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
       ,LPSTR lpszCmdParam,int nCmdShow)
{
     glutCreateWindow("OpenGL");
     glutDisplayFunc(DoDisplay);
     glutMainLoop();
     return 0;
}

GLubyte *LoadBmp(const char *Path, int *Width, int *Height)
{
     HANDLE hFile;
     DWORD FileSize, dwRead;
     BITMAPFILEHEADER *fh=NULL;
     BITMAPINFOHEADER *ih;
     BYTE *pRaster;

     hFile=CreateFileA(Path,GENERIC_READ,0,NULL,
          OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

     if (hFile==INVALID_HANDLE_VALUE) {
          return NULL;
     }

     FileSize=GetFileSize(hFile,NULL);
     fh=(BITMAPFILEHEADER *)malloc(FileSize);
     ReadFile(hFile,fh,FileSize,&dwRead,NULL);
     CloseHandle(hFile);

     int len = FileSize - fh -> bfOffBits;
     pRaster=(GLubyte *)malloc(len);
     memcpy(pRaster, (BYTE *)fh+fh->bfOffBits, len);

     // RGB로 순서를 바꾼다.
     for (BYTE *p=pRaster;p < pRaster + len - 3;p+=3) {
          BYTE b = *p;
          *p = *(p+2);
          *(p+2) = b;
     }

     ih=(BITMAPINFOHEADER *)((PBYTE)fh+sizeof(BITMAPFILEHEADER));
     *Width=ih->biWidth;
     *Height=ih->biHeight;

     free(fh);
     return pRaster;
}



void DoDisplay()
{
     GLubyte *data;
     int Width, Height;

     glClear(GL_COLOR_BUFFER_BIT);

     data = LoadBmp("c:\\fedup.bmp", &Width, &Height);

     if (data != NULL) {
          glRasterPos2f(-0.5,-0.5);
          glDrawPixels(Width, Height, GL_RGB, GL_UNSIGNED_BYTE, data);
          free(data);
     }

     glFlush();
}

이 예제의 LoadBmp 함수는 인수로 전달받은 이미지 파일로부터 래스터 데이터를 배열로 읽어들인다. LoadBmp 함수에 대한 자세한 분석이나 설명은 생략한다. 관심있는 사람은 Win32 관련 서적을 참고하기 바란다.

파일로부터 이미지를 구하는 방법 외에 이미 출력된 화면의 이미지를 재사용할 수도 있다. 색상 버퍼의 픽셀값을 버퍼로 읽어들이거나 색상 버퍼의 일정 영역을 다른 곳으로 복사하면 된다. 다음 함수들은 화면의 일부를 버퍼로 읽어 들이고 화면끼리 복사한다. glRasterPos로 복사할 위치를 지정한 후 이 함수로 복사한다.

void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,  GLvoid * data);
void glCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type);

복사할 영역과 복사할 정보의 종류를 밝힌다. typeGL_COLOR를 지정하면 색상 데이터를 복사하는 것이고 GL_DEPTHGL_STENCIL을 지정하면 깊이 정보와 스텐실 정보까지 복사한다. 다음 예제는 밤 이미지의 일부를 왼쪽 아래로 복사한다.

void DoDisplay()
{
     GLubyte *data;
     int Width, Height;

     glClear(GL_COLOR_BUFFER_BIT);

     data = LoadBmp("c:\\fedup.bmp", &Width, &Height);

     if (data != NULL) {
          glRasterPos2f(-0.5,-0.5);
          glDrawPixels(Width, Height, GL_RGB, GL_UNSIGNED_BYTE, data);
          free(data);
     }

     // 색상 정보 복사
     glRasterPos2f(-1.0,-1.0);
     glCopyPixels(100,100,80,50,GL_COLOR);

     glFlush();
}

3차원 그래픽으로 그린 이미지의 일부를 잘라 텍스처로 사용할 때는 이런 기법이 편리하다. 화면에 그려진 래스터 데이터를 활용하는 것이므로 대용량의 배열을 선언할 필요도 없고 파일 입출력을 할 필요도 없다.

비트맵 작업은 싱글 버퍼 환경일 때는 프론트 버퍼를 대상으로 하지만 더블 버퍼링 환경에서는 백 버퍼를 대상으로 수행된다. 대상 버퍼를 강제로 바꾸고 싶다면 다음 두 함수를 사용한다.

void glDrawBuffer(GLenum mode);
void glReadBuffer(GLenum mode);

백 버퍼에 그려 놓고 스왑하면 깜박거림이 제거된다.

 

36. 폴리곤 스티플

폴리곤 스티플링은 다각형의 내부를 채우는 아주 간단한 방법이다. 32*32 크기의 흑백 비트맵을 정의하고 다음 두 함수를 호출하면 된다.

glEnable(GL_POLYGON_STIPPLE);
void glPolygonStipple(const GLubyte * pattern);

pattern은 32 * 32의 비트맵 패턴이므로 총 128 바이트로 구성된 배열이어야 한다.

void DoDisplay()
{
     static GLubyte bitmap[] = {
          0x07, 0xe0, 0x18, 0x18, 0x20, 0x04, 0x43, 0xc2,
          0x44, 0x22, 0x88, 0x11, 0x81, 0x81, 0x81, 0x81,
          0x80, 0x01, 0x80, 0x01, 0x92, 0x49, 0x4c, 0x32,
          0x40, 0x02, 0x20, 0x04, 0x18, 0x18, 0x07, 0xe0,
          0x07, 0xe0, 0x18, 0x18, 0x20, 0x04, 0x43, 0xc2,
          0x44, 0x22, 0x88, 0x11, 0x81, 0x81, 0x81, 0x81,
          0x80, 0x01, 0x80, 0x01, 0x92, 0x49, 0x4c, 0x32,
          0x40, 0x02, 0x20, 0x04, 0x18, 0x18, 0x07, 0xe0,
          0x07, 0xe0, 0x18, 0x18, 0x20, 0x04, 0x43, 0xc2,
          0x44, 0x22, 0x88, 0x11, 0x81, 0x81, 0x81, 0x81,
          0x80, 0x01, 0x80, 0x01, 0x92, 0x49, 0x4c, 0x32,
          0x40, 0x02, 0x20, 0x04, 0x18, 0x18, 0x07, 0xe0,
          0x07, 0xe0, 0x18, 0x18, 0x20, 0x04, 0x43, 0xc2,
          0x44, 0x22, 0x88, 0x11, 0x81, 0x81, 0x81, 0x81,
          0x80, 0x01, 0x80, 0x01, 0x92, 0x49, 0x4c, 0x32,
          0x40, 0x02, 0x20, 0x04, 0x18, 0x18, 0x07, 0xe0,
     };

     glClear(GL_COLOR_BUFFER_BIT);

     glEnable(GL_POLYGON_STIPPLE);
     glPolygonStipple(bitmap);

     glBegin(GL_TRIANGLES);
     glVertex2f(0.0, 0.5);
     glVertex2f(-0.5, -0.5);
     glVertex2f(0.5, -0.5);

     glEnd();
     glFlush();
}

스티플링은 래스터 데이터만으로 도형의 내부를 채울 수 있다는 면에서 아주 간편한 방법이기다. 그러나 간편한만큼 제약이 많아 실용성은 떨어진다. 비트맵은 무조건 32 * 32 크기여야 하며 그나마도 단색 무늬만 가능해서 표현력의 제약이 심하다. 게다가 회전이나 확대, 축소도 적용되지 않아 현실적으로 거의 쓸모가 없다. 도형의 내부를 원하는 무늬로 채우는 정석은 텍스처 맵핑이다.

 

37. 텍스처 매핑 개요

텍스처 맵핑(Texture Mapping)은 3차원 물체의 표면에 이미지를 입히는 기법이다. 3차원 표면에 울퉁불퉁한 모양이나 나무 무늬 등을 그려 넣으려면 조명이나 물체의 재질을 지정하는 방법으로는 한계가 많다. 대량의 정점이 필요하고 정점이 많아지면 속도도 느려진다. 텍스처 맵핑은 미리 만들어져 있는 이미지를 다각형의 표면에 그려 넣음으로써 임의의 무늬를 빠른 속도로 그려내는 획기적인 방법이다.

텍스처 맵핑 기능을 사용하려면 먼저 해당 기능을 켜야 한다.

glEnable(GL_TEXTURE_2D);

주로 2차원 평면 이미지로 텍스처를 입히지만 1차원이나 3차원의 이미지를 사용할 수도 있다. 사용할 텍스처의 차원에 따라 개별적으로 활성화시켜야 하며 한번에 하나만 활성화할 수 있다. 통상은 2차원 텍스처를 가장 많이 사용하며 ES 버전은 2차원밖에 지원하지 않는다.

다음은 텍스처로 사용할 이미지를 메모리 버퍼로 로드한다. 여러 가지 방법으로 이미지를 준비할 수 있지만 가장 일반적인 방법은 이미지 파일에서 읽어오는 것이다. 앞에서 준비해둔 함수로 래스터 데이터를 배열에 읽어 놓는다. 이미지를 바로 사용할 수는 없으며 텍스처 맵핑에 적당한 형태로 가공해야 하는데 이때는 다음 함수를 호출한다.

void glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid * data);

target은 이미지로부터 어떤 텍스처를 만들 것인가를 지정한다. 평범한 텍스처를 만들 수도 있고 프록시나 큐브맵을 만들 수도 있다. level은 밉맵(mipmap) 레벨이되 밉맵을 사용하지 않을 때는 0으로 지정한다.

내부 포맷은 텍셀의 포맷을 지정하는데 GL_RGB, GL_BGR 등등 여러 가지 포맷으로 만들 수 있다. width, height는 텍스처의 높이이되 반드시 2의 거듭제곱 크기여야 하며 여기에 경계선의 두께를 더해야 한다. 경계선이 없을 경우 32, 64, 128, 256 정도의 크기면 된다. border는 경계선의 두께를 지정하는데 없으면 0을 준다. format, type, data는 래스터 데이터의 포맷과 픽셀의 타입, 그리고 래스터 데이터 배열이다.

텍스처가 준비된 다음은 도형의 각 정점과 텍스처의 좌표를 대응시킨다. 도형의 어느 부분에 텍스처의 어디쯤을 출력할 것인가를 지정하는 것이다.

텍스처 이미지는 0 ~ 1 사이의 실수 좌표로 비율을 지정한다. 정점의 좌표 표현시 보통 xyzw 명칭을 축으로 사용하지만 텍스처는 strq라는 좌표축 명칭을 대신 사용한다.

이미지의 크기에 상관없이 텍스처의 좌하단은 (0,0)이고 우상단은 (1,1)이다. 정중앙은 물론 (0.5, 0.5)가 될 것이다. 각 정점에 텍스처의 좌표를 지정할 때는 다음 함수를 사용한다.

void glTexCoord[1,2,3,4][s,i,f,d][v](s, t, r, q);

정점에 텍스처 좌표를 대응시켜 놓으면 OpenGL이 정점의 위치에 텍스처의 대응되는 픽셀을 읽어 출력할 것이다. 정점들의 중간에는 정점과 텍스처의 거리 비율에 맞게 적당히 늘리거나 줄여서 픽셀들을 대응시켜 다각형 전체에 텍스처를 입힌다. 텍스처와 다각형의 크기가 보통 일치하지 않으므로 스트레칭은 거의 항상 발생한다.

다음 예제는 피라미드에 대리석 모양의 텍스처를 입힌다.

#include <windows.h>
#include <gl/glut.h>

void DoDisplay();
void DoKeyboard(unsigned char key, int x, int y);
void DoMenu(int value);

GLfloat xAngle, yAngle, zAngle;
GLint EnvMode = GL_REPLACE;
GLint TexFilter = GL_LINEAR;

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
       ,LPSTR lpszCmdParam,int nCmdShow)
{
     glutCreateWindow("OpenGL");
     glutDisplayFunc(DoDisplay);
     glutKeyboardFunc(DoKeyboard);
     glutCreateMenu(DoMenu);
     glutAddMenuEntry("Replace",1);
     glutAddMenuEntry("Modulate",2);
     glutAddMenuEntry("Add",3);
     glutAddMenuEntry("Nearest Filter",4);
     glutAddMenuEntry("Linear Filter",5);
     glutAttachMenu(GLUT_RIGHT_BUTTON);
     glutMainLoop();
     return 0;
}

void DoKeyboard(unsigned char key, int x, int y)
{
     switch(key) {
     case 'a':yAngle += 2;break;
     case 'd':yAngle -= 2;break;
     case 'w':xAngle += 2;break;
     case 's':xAngle -= 2;break;
     case 'q':zAngle += 2;break;
     case 'e':zAngle -= 2;break;
     case 'z':xAngle = yAngle = zAngle = 0.0;break;
     }

     char info[128];
     sprintf(info, "x=%.1f, y=%.1f, z=%.1f", xAngle, yAngle, zAngle);
     glutSetWindowTitle(info);
     glutPostRedisplay();
}

void DoMenu(int value)
{
     switch(value) {
     case 1:
          EnvMode = GL_REPLACE;
          break;
     case 2:
          EnvMode = GL_MODULATE;
          break;
     case 3:
          EnvMode = GL_ADD;
          break;
     case 4:
          TexFilter = GL_NEAREST;
          break;
     case 5:
          TexFilter = GL_LINEAR;
          break;
     }

     glutPostRedisplay();
}

GLubyte *LoadBmp(const char *Path, int *Width, int *Height)
{
     HANDLE hFile;
     DWORD FileSize, dwRead;
     BITMAPFILEHEADER *fh=NULL;
     BITMAPINFOHEADER *ih;
     BYTE *pRaster;

     hFile=CreateFileA(Path,GENERIC_READ,0,NULL,
          OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

     if (hFile==INVALID_HANDLE_VALUE) {
          return NULL;
     }

     FileSize=GetFileSize(hFile,NULL);
     fh=(BITMAPFILEHEADER *)malloc(FileSize);
     ReadFile(hFile,fh,FileSize,&dwRead,NULL);
     CloseHandle(hFile);

     int len = FileSize - fh -> bfOffBits;
     pRaster=(GLubyte *)malloc(len);
     memcpy(pRaster, (BYTE *)fh+fh->bfOffBits, len);

     // RGB로 순서를 바꾼다.
     for (BYTE *p=pRaster;p < pRaster + len - 3;p+=3) {
          BYTE b = *p;
          *p = *(p+2);
          *(p+2) = b;
     }

     ih=(BITMAPINFOHEADER *)((PBYTE)fh+sizeof(BITMAPFILEHEADER));
     *Width=ih->biWidth;
     *Height=ih->biHeight;

     free(fh);
     return pRaster;
}

void DoDisplay()
{
     GLubyte *data;
     int Width, Height;

     // 텍스처 이미지 준비
     glEnable(GL_TEXTURE_2D);
     data = LoadBmp("c:\\block.bmp", &Width, &Height);
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Width, Height,
          0, GL_RGB,  GL_UNSIGNED_BYTE, data);
     free(data);

     // 텍스처 환경 설정
     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, EnvMode);

     // 텍스처 필터 설정
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, TexFilter);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, TexFilter);

     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     glShadeModel(GL_FLAT);
     glEnable(GL_DEPTH_TEST);

     glPushMatrix();
     glRotatef(xAngle, 1.0f, 0.0f, 0.0f);
     glRotatef(yAngle, 0.0f, 1.0f, 0.0f);
     glRotatef(zAngle, 0.0f, 0.0f, 1.0f);

     // 아랫면 흰 바닥
     glBegin(GL_QUADS);
     glColor3f(1,1,1);
     glTexCoord2f(0.0, 1.0);
     glVertex2f(-0.5, 0.5);
     glTexCoord2f(1.0, 1.0);
     glVertex2f(0.5, 0.5);
     glTexCoord2f(1.0, 0.0);
     glVertex2f(0.5, -0.5);
     glTexCoord2f(0.0, 0.0);
     glVertex2f(-0.5, -0.5);
     glEnd();

     // 위쪽 빨간 변
     glBegin(GL_TRIANGLE_FAN);
     glColor3f(1,0,0);
     glTexCoord2f(0.5, 0.5);
     glVertex3f(0.0, 0.0, -0.8);
     glTexCoord2f(1.0, 1.0);
     glVertex2f(0.5, 0.5);
     glTexCoord2f(0.0, 1.0);
     glVertex2f(-0.5, 0.5);

     // 왼쪽 노란 변
     glColor3f(1,1,0);
     glTexCoord2f(0.0, 0.0);
     glVertex2f(-0.5, -0.5);

     // 아래쪽 초록 변
     glColor3f(0,1,0);
     glTexCoord2f(1.0, 0.0);
     glVertex2f(0.5, -0.5);

     // 오른쪽 파란 변
     glColor3f(0,0,1);
     glTexCoord2f(1.0, 1.0);
     glVertex2f(0.5, 0.5);
     glEnd();

     glPopMatrix();
     glFlush();
}
#include <windows.h>
#include <gl/glut.h>

void DoDisplay();
void DoKeyboard(unsigned char key, int x, int y);
void DoMenu(int value);

GLfloat xAngle, yAngle, zAngle;
GLint EnvMode = GL_REPLACE;
GLint TexFilter = GL_LINEAR;

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
       ,LPSTR lpszCmdParam,int nCmdShow)
{
     glutCreateWindow("OpenGL");
     glutDisplayFunc(DoDisplay);
     glutKeyboardFunc(DoKeyboard);
     glutCreateMenu(DoMenu);
     glutAddMenuEntry("Replace",1);
     glutAddMenuEntry("Modulate",2);
     glutAddMenuEntry("Add",3);
     glutAddMenuEntry("Nearest Filter",4);
     glutAddMenuEntry("Linear Filter",5);
     glutAttachMenu(GLUT_RIGHT_BUTTON);
     glutMainLoop();
     return 0;
}

void DoKeyboard(unsigned char key, int x, int y)
{
     switch(key) {
     case 'a':yAngle += 2;break;
     case 'd':yAngle -= 2;break;
     case 'w':xAngle += 2;break;
     case 's':xAngle -= 2;break;
     case 'q':zAngle += 2;break;
     case 'e':zAngle -= 2;break;
     case 'z':xAngle = yAngle = zAngle = 0.0;break;
     }

     char info[128];
     sprintf(info, "x=%.1f, y=%.1f, z=%.1f", xAngle, yAngle, zAngle);
     glutSetWindowTitle(info);
     glutPostRedisplay();
}

void DoMenu(int value)
{
     switch(value) {
     case 1:
          EnvMode = GL_REPLACE;
          break;
     case 2:
          EnvMode = GL_MODULATE;
          break;
     case 3:
          EnvMode = GL_ADD;
          break;
     case 4:
          TexFilter = GL_NEAREST;
          break;
     case 5:
          TexFilter = GL_LINEAR;
          break;
     }

     glutPostRedisplay();
}

GLubyte *LoadBmp(const char *Path, int *Width, int *Height)
{
     HANDLE hFile;
     DWORD FileSize, dwRead;
     BITMAPFILEHEADER *fh=NULL;
     BITMAPINFOHEADER *ih;
     BYTE *pRaster;

     hFile=CreateFileA(Path,GENERIC_READ,0,NULL,
          OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

     if (hFile==INVALID_HANDLE_VALUE) {
          return NULL;
     }

     FileSize=GetFileSize(hFile,NULL);
     fh=(BITMAPFILEHEADER *)malloc(FileSize);
     ReadFile(hFile,fh,FileSize,&dwRead,NULL);
     CloseHandle(hFile);

     int len = FileSize - fh -> bfOffBits;
     pRaster=(GLubyte *)malloc(len);
     memcpy(pRaster, (BYTE *)fh+fh->bfOffBits, len);

     // RGB로 순서를 바꾼다.
     for (BYTE *p=pRaster;p < pRaster + len - 3;p+=3) {
          BYTE b = *p;
          *p = *(p+2);
          *(p+2) = b;
     }

     ih=(BITMAPINFOHEADER *)((PBYTE)fh+sizeof(BITMAPFILEHEADER));
     *Width=ih->biWidth;
     *Height=ih->biHeight;

     free(fh);
     return pRaster;
}

void DoDisplay()
{
     GLubyte *data;
     int Width, Height;

     // 텍스처 이미지 준비
     glEnable(GL_TEXTURE_2D);
     data = LoadBmp("c:\\block.bmp", &Width, &Height);
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Width, Height,
          0, GL_RGB,  GL_UNSIGNED_BYTE, data);
     free(data);

     // 텍스처 환경 설정
     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, EnvMode);

     // 텍스처 필터 설정
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, TexFilter);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, TexFilter);

     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     glShadeModel(GL_FLAT);
     glEnable(GL_DEPTH_TEST);

     glPushMatrix();
     glRotatef(xAngle, 1.0f, 0.0f, 0.0f);
     glRotatef(yAngle, 0.0f, 1.0f, 0.0f);
     glRotatef(zAngle, 0.0f, 0.0f, 1.0f);

     // 아랫면 흰 바닥
     glBegin(GL_QUADS);
     glColor3f(1,1,1);
     glTexCoord2f(0.0, 1.0);
     glVertex2f(-0.5, 0.5);
     glTexCoord2f(1.0, 1.0);
     glVertex2f(0.5, 0.5);
     glTexCoord2f(1.0, 0.0);
     glVertex2f(0.5, -0.5);
     glTexCoord2f(0.0, 0.0);
     glVertex2f(-0.5, -0.5);
     glEnd();

     // 위쪽 빨간 변
     glBegin(GL_TRIANGLE_FAN);
     glColor3f(1,0,0);
     glTexCoord2f(0.5, 0.5);
     glVertex3f(0.0, 0.0, -0.8);
     glTexCoord2f(1.0, 1.0);
     glVertex2f(0.5, 0.5);
     glTexCoord2f(0.0, 1.0);
     glVertex2f(-0.5, 0.5);

     // 왼쪽 노란 변
     glColor3f(1,1,0);
     glTexCoord2f(0.0, 0.0);
     glVertex2f(-0.5, -0.5);

     // 아래쪽 초록 변
     glColor3f(0,1,0);
     glTexCoord2f(1.0, 0.0);
     glVertex2f(0.5, -0.5);

     // 오른쪽 파란 변
     glColor3f(0,0,1);
     glTexCoord2f(1.0, 1.0);
     glVertex2f(0.5, 0.5);
     glEnd();

     glPopMatrix();
     glFlush();
}

    

38. 텍스처 매핑 옵션

텍스처 맵핑은 굉장히 복잡한 연산이어서 선택할 수 있는 옵션들이 아주 많다. 다음 함수는 텍스처 맵핑 환경을 지정한다.

void glTexEnv[f, i](GLenum target, GLenum pname, GLfloat param);

target 인수는 어떤 환경을 조정할 것인가를 지정한다. GL_TEXTURE_ENV, GL_TEXTURE_FILTER_CONTROL, GL_POINT_SPRITE 중 하나이다. pname은 조정하고자 하는 옵션의 이름이고 param은 옵션의 값이다. GL_TEXTURE_ENV_MODE는 텍셀의 색상과 지오메트리의 색상을 결합하는 방식 지정하는데 다음 여섯 가지 방식이 있다.

  • GL_MODULATE: 두 색상을 곱한다.
  • GL_REPLACE: 색상을 무시하고 텍스처로 덮어쓴다.
  • GL_ADD: 두 색상을 더한다.
  • GL_DECAL
  • GL_BLEND: 블랜딩 색상과 텍스처를 혼합한다.
  • GL_COMBINE

피라미드의 각 면에 고유의 색상이 지정되어 있는데 결합 모드에 따라 이 색상과 텍스처 색상의 연산이 결정된다. REPLACE로 덮어 쓰면 텍스처의 무늬만 나타나지만 MODULATEADD로 혼합하면 면의 원래색과 논리적으로 섞인다. 팝업 메뉴로 모드를 바꿔 보자.

    

다음 함수는 텍스처 파라미터를 지정하는데 이 파라미터에 따라 텍스처를 그리는 랜더링 방식이 달라진다.

void glTexParameter[f, i][v](GLenum target, GLenum pname, GLfloat param);

target은 텍스처의 차원을 지정한다. pname은 파라미터의 이름이고 param은 값이다. 텍스처와 물체의 면적이 정확하게 일치하는 경우가 드물므로 텍스처를 늘리거나 줄여서 입혀야 한다. 텍스처 맵으로부터 입힐 색상을 계산하는 과정을 텍스처 필터링이라고 한다. GL_TEXTURE_MIN_FILTER는 축소시의 필터링을 지정하고 GL_TEXTURE_MAG_FILTER는 확대시의 필터링을 지정한다. 주로 다음 두 가지 필터가 사용된다.

  • 최단거리(GL_NEAREST) : 비율상 대응되는 위치의 텍셀값을 그대로 사용한다. 알고리즘은 단순하지만 품질은 떨어진다.
  • 선형(GL_LINEAR) : 대응되는 위치 주변의 텍셀값에 대한 평균을 계산한다. 오버헤더가 많지만 품질은 훨씬 더 좋다.

LINEAR 필터링은 부드럽게 확대되지만 NEAREST 필터링은 품질이 훨씬 더 거칠며 계단 현상도 나타난다. 물론 속도는 반대겠지만 이 정도 도형에서 속도차를 실감하기는 어렵다. 텍스처를 더 큰 것으로 사용하면 확대에 의한 부작용이 감소하므로 품질은 더 좋아진다.

GL_TEXTURE_WRAP_S, T, R 파라미터는 각 축에 대한 텍스처 랩핑 방식을 지정하며 다음값중 하나를 지정한다.

  • GL_CLAMP: 경계 부근을 반복한다.
  • GL_CLAMP_TO_BORDER: 경계 텍셀의 값을 반복한다.
  • GL_CLAMP_TO_EDGE
  • GL_MIRRORED_REPEAT: 반사된 모양으로 반복한다.
  • GL_REPEAT: 반복한다.

텍스처 범위와 텍셀 범위가 일치하지 않을 경우 반복 및 경계 부근의 처리 방식을 지정한다.

 

39. OpenGL ES (축소된 서브셋)

제약이 심한 임베디드 환경을 위해 더 작고 가벼운 별도의 표준을 재정했는데 이것이 바로 OpenGL ES이다. ES는 Embeded System의 약자이다. ES 표준은 크로노스 그룹에서 관리한다. ES는 상위의 표준인 OpenGL에서 잘 사용되지 않거나 대체적인 방법이 있는 것을 제외하고 축소하여 만든 것이다. 즉, OpenGL의 서브셋이다. 

ES는 임베디트 환경을 위해 경량화된 것이어서 주로 OpenGL의 기능에서 일부를 제외하여 작성된 표준이다. ES 스펙 문서는 각 기능에 대한 명세를 밝히는 것보다 OpenGL에서 어떤 기능이 제외되었는지를 설명한다.

  • 지원 타입의 수를 줄였다. 대표적으로 GLdouble 타입을 지원하지 않는데 사실 OpenGL에서도 이 타입은 잘 사용되지 않았다. 라이트 버전에는 GLfloat도 없고 고정 소수점 타입인 GLfixed 타입을 대신 사용한다. 임베디드 시스템에는 부동소수점 연산을 하드웨어가 수행하지 못하므로 소프트웨어로 느리게 수행해야 하기 때문이다. GLbyte, GLubyte, GLshort 타입도 제거되었다.
  • 즉시 모드를 지원하지 않는다. glBegin ~ glEnd 블록안에서 그리기 함수를 호출할 수 없고 정점 배열만 사용할 수 있다. 함수 호출 오버헤드를 줄이기 위해서이다. 여러 정보를 한 배열에 저장하는 인터리브 배열도 지원하지 않는다.
  • 사각형을 그리는 glRect 함수가 제외되었다. 그래서 모든 물체를 삼각형으로만 구성해야 한다. GL_QUADS, GL_POLYGON 모델도 제외되었다. 사실 이 기능들은 OpenGL에서도 잘 사용되지 않는 것이다.
  • 색상 인덱스 모드는 지원하지 않는다. 조명이나 텍스처 맵핑 등에 제약이 많다. OpenGL에서도 팔레트 모드는 원래 제약이 많았다.
  • 그외 폴리곤 모드, 피드백, 선택 기능, 누적 버퍼, 출력 목록, 속성 저장 기능을 지원하지 않는다.
  • 텍스처 맵핑은 2D만 지원된다.
  •  조명은 앞뒷면이 반드시 동일해야 한다. 재질 모드는 GL_AMBIENT_AND_DIFFUSE만 지원된다.

 

문의 | 코멘트 또는 yoonbumtae@gmail.com


카테고리: Media Artetc.


0개의 댓글

답글 남기기

Avatar placeholder

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다