• 북마크
  • 추가메뉴
어디로 앱에서 쉽고 간편하게!
애플 중고 거래 전문 플랫폼
오늘 하루 보지 않기
KMUG 케이머그

소프트웨어

[App 개발] NeHe Lesson 22

본문

22번은 작문도 상당히 엉터리고, 그래서 번역도 엉터리고, 소스도 엉터리고... 이것을 붙잡고 계속 늘어지고 있는 것보다는 어서 앞으로 나아가야겠다는 생각에 예제도 만들지 못하고 건너뛰게 되었습니다. 대단히 송구스럽습니다. 별로 도움 안 되는 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 0
로그인 후 추천 또는 비추천하실 수 있습니다.
포인트 228,692
가입일 :
2003-02-18 14:12:30
서명 :
미입력
자기소개 :
미입력

최신글이 없습니다.

최신글이 없습니다.

댓글목록 0

등록된 댓글이 없습니다.
전체 529 건 - 10 페이지
2005.03
07

[App 개발] oracle 10g를 페더에 설치가 가능한가요??

이번 학기에 데이터 베이스 수업과.. JAVA수업을 듣게 되었는데.. JAVA는 아이북에서 설치가 가능하다고 하신거 같은데.. oracle 10g는 맥 서버용으로 나온건 알고 있습니다.. 그런데 그걸 그냥 일반 페더에도 깔수가 있…

2005.02
18

[App 개발] 한글 파일이름 open 이 원래 안되나요?

python 에서 한글 파일 이름으로 write 하려고 open 했는데 에러가 나는군요. 다른 프로그램으로 open 해도 화일이름이 한글이면 에러가 나는 것 같습니다. 한글이름이면 open 시스템콜이 안되나 봅니다 예) >>> f = ope…

2005.02
12

[App 개발] gnu c library

mac os x 에 설치된 c library 는 gnu c library 가 아니군요. BSD 계열이니 당연한 것이겠지만서도..... 수치계산할 때 complex 형 변수를 선언할 수 없어 불편하군요. 그런데 c library를 손…

2005.02
10

[App 개발] hdf5설치 성공하신 분 있나요?

라이브러리 설치 문제가 계속 걸려서 fink 를 이용해 봤습니다만, 안되는군요. :-( libpng, libjpeg 등을 모두 설치해도 h5utils 를 configure 하는 도중에 이 라이브러리들이 없다고 하는군요. hdf5 역…

2005.02
01

[App 개발] hdf5 라이브러리는 어디에...

리눅스에서 제가 작성했던 코드를 가져와서 컴파일 하려 하니 hdf5 라이브러리가 없다며 실패하는군요. hdf5 를 포트에서 설치하면 될 줄 알았는데 그게 아니었나 봅니다. h5cc 등의 툴만 있지 실제적으로 C 코드에서 호출해서 …

2005.01
28

[App 개발] [질문] 개발자에게 있어서 맥이란 플랫폼이란...?

안녕하세요. 컴퓨터 전공하는 학생입니다. 4년전에 대학교 입학할 때 컴퓨터를 장만하면서 맥도 고려했었습니다. 큐브가 어찌나 이뻐 보이던지 ^^; 하지만 PC에 비해 너무 고가의 장비였고... 무엇보다 맥으로 코딩 작업이 …

2005.01
06

[App 개발] apple developer 사이트에서 샘플 긁어오기 스크립트

본격적으로 osx 공부를 해볼까해서 apple developer 사이트를 보면서 샘플을 하나하나 다운로드하니 노가다성이 심하다 생각되서 파이손 스크립트 하나 만들었습니다. :) 페이지에 있는 *.sit 파일들만 쭉 다 다운로드 해줍니다. 저는 터…

2005.01
02

[App 개발] 프로그래밍 공부하는 방법

-------------------------------------------------------------------------------- 그냥 나름대로 정리해봄.. ----------------------------------------…

2004.12
28

[App 개발] MPC 7448 그리고 MPC 8641

프리스케일 웹사이트에 드디어 MPC 7448 과 MPC 8641 이 정식으로 명명되어 대략 설명이 올라왔습니다. 정말 내년 초 파워북 G4 의 업그레이드가 헛소문만은 아닌가봅니다. MPC7448 은 200MHz 의 MPX 버스를 가지게 되었습니다…

2004.12
18

[App 개발] GUI 인터페이스의 vim

새로 iMac 을 장만하고, 프로그래밍을 해 보겠다는 꿈을 품고 Xcode 를 무작정 실행해 보니 확실히 잘 모를것들이 많더군요. 앞으로 어찌 공부해야 할지, 갈길이 멀군요. 일단 고지식하게 Vim 을 찾았습니다. 터미널에 vi 는 있었지만, …

2004.12
06

[App 개발] 유용한 MacOS X external commands

요즘 타 맥관련 사이트에서도 열심히 글타래를 정독하면서 OS X의 근본적인 unix나 이래저래 공부를 하고 있습니다. 그중에서 unix 커맨드중에서 유용한 것들에 대한 설명이 되어있는 글을 보고 이렇게 올려드려요~ 공부하시는 분들에겐 도…

2004.11
29

[App 개발] MOD Player 를 만들어보자 2

지난 4월이었던가요 유닉스용 소스에 퀵타임 루틴을 첨가하여 간단하게 MOD 플레이어를 만들어본 적이 있는 것을 기억하실 것입니다. 그 때에는 첫 번째 시도이기도 하고, 괜찮은 소스를 입수하는 것도 여의치 않아서, 오리지날 4채널 MOD 포맷만을 지원했…

2004.11
17

[App 개발] MACOSX용 BBS WebStation 1.0 공개합니다.

MACOS용 BBS WebStation 1.0 공개합니다. 패키지로 구성된 파일을 실행하시면 자신의 MACOSX에서 BBS를 운영하실 수 있습니다. Perl 기반으로 작성되어있으며 FileDB를 사용하므로 별다른 작업없이 커뮤니티 구성이 가능합…

2004.11
11

[App 개발] 슈퍼컴퓨터 컨퍼런스 2004

제가 살고있는 피츠버그에서 슈퍼컴퓨터 컨퍼런스가 열리고 있습니다. 저는 오늘 하루 전시회 입장권만 구입해서 잠시 구경을 했습니다. 원래는 차기에 구매할 number crunching 기계 중에서 물망에 오르고 있는 4-way Opteron 제품을 직접…

2004.11
07

[App 개발] eclipse 사용해보시지 않으실래요..

안녕하세요.. 일본머그지기 시니입니다.. 저도.. 오픈소스를 넘 좋아해서리.. ㅋㅋㅋ 그나저나.. 개발하시는분들중엔 사용하고 계실지 모르지만.. 전 자바를 좋아해서리..ㅋㅋㅋ 새로나온.. 머 좀 지났지만 이클립스 3.0.1 함 사용해보세요.…

2004.11
03

[App 개발] 신형 아이북, 파워맥 벤치마크 결과

아이북, 파워맥 1.8GHz 벤치마크 느린 버스속도가 싱글 파워맥 성능저하의 요인 By James Galbraith 최근 애플에서 신형 아이북과 저가형 파워맥 G5가 발표되었습니다. 전체 리뷰를 진행하면서 동시에 Speedmark 테스트 …

2004.10
31

[App 개발] 게임엔진 NeoEngine 의 컴파일

SourceForge.net 에서 제일 인기있는 공개 소스 버젼 게임엔진 NeoEngine 이 있습니다. 모질라 라이센스를 따르고 있어서 소스의 변경 수정이 자유롭다는 특징이 있고, 공개 버젼 중에서는 가장 완성도가 높은 라이브러리가 아닌가 생각됩니다…

2004.10
18

[App 개발] [ 급질문 ] 맥용 웹하드 정도 만들어 볼려면..

음... 맥용 웹하드 정도 만들어 볼려면 어느정도 지식이 필요 할까요 아직 맥을 구경도 못해봤습니다..-_-;;; 맥이 어떻게 켜지는지도 모르고.... 맥에 관심이 어느날 부터 가기 시작하던데... 그런데 너무 맥에 관한 자료가 없더군요.. fi…

2004.10
13

[App 개발] X Code에서 include 포함 파일이나 라이브러리 파일의 경로는 어떻게 지정하나요 ?

안녕하세요.. 비주얼 스튜디오 같은 곳에서는 포함 파일이나 라이브러리 파일을 옵션에서 설정해 줄 수 있었습니다. X Code 에서는 이를 어떻게 설정해 주어야 하나요 예를 들어 /Volumes/Data/SDKLib 라는 곳에…

2004.09
10

[App 개발] 아이맥 G5 의 특이한 점 - 개발자 문서에서 살펴본 결과

아이맥 G5 의 개발자 문서가 드뎌 공개되었군요. 메모리 버스가 어떻게 구성되어 있는지 상세히 밝혀져 있습니다. 구형 파워맥 G5 의 구성과 비교해 보겠습니다. 신형 파워맥 G5 에서는 변경된 사항이 있을지도 모르겠습니다. 1. …

2004.08
31

[App 개발] 아이맥 G5 의 특이한 점???

개발실에 쓸 글은 아닙니다만... 딱히 좋은 장소가 없어서 여기에 글을 올립니다. 일단 오늘(8월 31일) 발표된 새 아이맥은 날씬하고 보기 좋고, 게다가 가격도 무척 저렴해서 크게 인기를 끌 수 있는 가능성이 풍부한 제품이라고 생각합니다. …

2004.07
05

[App 개발] 질문하나 있습니다.

아.. 소스를 카피해온다는 것이 못해왔네요. 다름이 아니라, Xcode에서 Interface Builder로 Custom View를 하나 만들었고, 이 클래스로 파일을 생성(.h, .m파일)하여 Xcode에서 .m파일에 이 Custom View에…

2004.05
24

[App 개발] 여기 아무두 안오세용? 도와주세요.

이제 막 코코아 개발을 시작하려고 하는 초보입니다. C에 대해선 어렴풋이나마 알고 있고 잘 알지 못하는 중에 코코아로 바로 뛰어들었는데요. 많이 부족함을 느낍니다. 천천히 하고있는데... 열심히 하고 싶지만 시간이 없네요. 지금 OR…

2004.05
15

[App 개발] 맥에서 Qt 사용 어떤가요?

사운드신세시스 프로그래밍을 공부하는데요 아무래도 GUI 가 필요해서 GUI 프로그래밍을 보려하는데, 리눅스환경에서 만들고 있어서 Qt 를 사용해서 GUI 를 만들어보려고요. 그런데 Qt 소개를 자세히 보니까 멀티플랫폼 API 이어…

2004.04
21

[App 개발] NeHe Lesson 23

무척 오랫만입니다... ㅎㅎㅎ 먹고 사는 것이 바쁘다보니... 일단 샘플 코드의 구현은 흥미로운 것 위주로 하도록 하고, 나머지는 텍스트를 익히는 것에 중점을 두려고 합니다. (제가 게을러졌다는 것을 우회적으로 표현하는 것입니다. ㅡㅡ;;;) …

2004.04
02

[App 개발] 국제컴퓨터, 소프트웨어, 통신전시회에 참석 하고자 분들 (리플부탁)

- 컴퓨터:휴대형단말기, PC(Desktop, Notebook, Laptop,Palmtop, Etc), 매킨토시, 네트웍컴퓨터, 서버, 워크스테이션, 메인프레임, 프리젠테이션장비, 기타 - 소프트웨어:WINDOWS/NT/UNIX기반 시스템ɨ…

2004.03
11

[App 개발] ModPlayer 를 만들어보자

MOD 는 Amiga 의 음악 파일 포맷입니다. 사실 아미가에서 쓰였기 때문에 유명해졌다기보다는 많은 도스 게임들이 MOD 배경음악을 지원하면서 탄탄한 지원군을 많이 얻음으로 인하여 공개 소스들이 많이 돌아다니게 되고, 유저의 손에서 더욱 더 확장되고…

2004.03
10

[App 개발] 맥마메 분석 - 5 사운드 출력

맥마메 분석 – 5 사운드 출력 개인적으로 가장 관심있는 부분으로 접어들었습니다. 오랫동안 피씨 프로그래밍만 해오다 보니 매킨토시의 사운드 출력이 어떻게 이루어지는지를 궁금해 해 왔습니다. 최근에는 맥과 피씨가 유사한 칩셋들을 많이 공…

2004.03
09

[App 개발] 맥마메 분석 - 4 HID 루틴

맥마메 분석 – 4 조이스틱 입력 네 번째 시간이 되었습니다. 솔직히 저는 조이스틱 지원에 대해서는 별로 관심은 없습니다. (집에 조이스틱이 없거든요. 하하…) 애플 II 에서는 조이스틱이 없으면 완전 바보였지만, 이제는 입력장치로 마…

2004.03
06

[App 개발] 맥마메 분석 - 3 마우스 입력

맥마메 분석 – 3 마우스 입력 마우스 입력도 키보드 입력과 거의 비슷한 방식으로 구현되어 있습니다. 메인 윈도우 이벤트 핸들러로 마우스 입력 핸들러를 등록하는 것, 그리고 이벤트 핸들러에서 입력된 값을 마우스 상태 배열에 …

2004.03
05

[App 개발] 맥마메 분석 - 2 키보드 입력

맥마메 분석 사실 마메의 진짜 핵심은 프로세서 에뮬레이션, 비디오 프로세서, 사운드 프로세서 에뮬레이션 루틴이죠. 한 번 마메 소스를 열어보신 분들이라면 실로 방대한 에뮬레이션 루틴들을 보면서 놀라셨을 것입니다. 그런데, 에뮬레이터를 제…

2004.03
04

[App 개발] 수박 겉핥기 MacMAME 분석 - 1. 디스플레이

수박 겉핥기 MacMAME 분석 Multiple Arcade Machine Emulator 의 약자인 MAME 는 게임 센터의 PCB 게임들의 에뮬레이터로서 고전 게임 매니아들에게 사랑받는 프로그램입니다. 대부분 기종에 이식되어 있고, 매킨토시에…

2004.02
18

열람중 [App 개발] NeHe Lesson 22

22번은 작문도 상당히 엉터리고, 그래서 번역도 엉터리고, 소스도 엉터리고... 이것을 붙잡고 계속 늘어지고 있는 것보다는 어서 앞으로 나아가야겠다는 생각에 예제도 만들지 못하고 건너뛰게 되었습니다. 대단히 송구스럽습니다. 별로 도움 안 되는 22번이…

2004.02
10

[App 개발] 10 bytes extended 형 데이터 처리.

안녕하세요 계속해서 Aiff 파일을 공부하고 있는데요. 헤더에 들어가있는 short, long, char 등의 데이터형의 데이터는 쉽게 읽고 쓸수가 있는데, Wave 화일과는 달리 Aiff 파일은 Sample Rate 를 기록하는 데이…

2004.02
08

[App 개발] NeHe Lesson 21

늦었습니다. ^^ 이번에는 내용도 많고, 간단하긴 하지만 그래도 게임 제작이고, 원래가 윈도우용으로 제작된 소스를 이식해야 하는 것이라 빼먹은 부분도 많고, 그래도 대충 돌아가게끔 맞춰놓긴 했습니다만... 어쨌든 즐겨 주십시오. ^^ ------…

2004.02
03

[App 개발] NeHe Lesson 20

강의 20번째입니다. 비트맵 이미지 포맷은 거의 모든 컴퓨터, 모든 운영체제에서 지원합니다. 사용하기 쉽고 텍스쳐로 읽고 만들기에 간단합니다. 지금까지 우리는 텍스트나 이미지를 출력할 때 배경을 지우지 않고 물체와 섞이도록 했습니다. 효과적이긴 하지만…

2004.02
01

[App 개발] Cocoa Design Patterns (7) 마지막

Facades 클래스 클러스터와 프록시를 설명할 때 이미 소개했습니다만, Facade(겉면 - 여기서는 포장지(Wrapper Facade)로 번역하겠습니다)는 복잡한 객체 집단을 간단히 단일화하여 보여주는 객체입니다. 포장지 패턴은 코코아에서 다른 …

2004.02
01

[App 개발] c++ file io 에서 기초적인 질문입니다.

Aiff 화일 헤더분석하는 프로그램을 만들어보려고 하는데요, 그냥 콘솔형으로 하려고, gcc 를 사용해서 하고 있습니다. 파일 입출력을 위해 ifstream 을 사용하는데요, aiff 화일로 부터 1바이트 char 형 데이터를 읽기위해…

2004.01
31

[App 개발] NeHe Lesson 19

강의 19번째입니다. 지금까지 여러 가지를 배워 오셨는데, 이제부터는 갖고 놀고 싶으실 겁니다. 저는 이번 강의에서 새로운 명령을 하나 소개하려고 합니다. 삼각 막대입니다. 아주 간단하고, 많은 수의 삼각형을 그리는 프로그램을 빠르게 만드는데 도움이 …

2004.01
30

[App 개발] Cocoa Design Patterns (6)

Notifications 코코아는 통지(notifications)기능을 이용해서 응용프로그램 내의 객체간에 중요한 일들이 발생했음을 알립니다. 어떤 통지 메세지들은 수신자가 여러 개일수도 있습니다. 물론 여러 개의 객체가 한 메세지를 보내거나 공시할…

2004.01
29

[App 개발] java2 network (chat server/client)

-- TCP기반의 에코서버/클라이언트 //EchoClientTest.java   import java.io.*; import java.net.*; public class &nbs…

2004.01
29

[App 개발] 진짜 간단한 AWT 계산기입니다..

import java.awt.*; import java.awt.event.*; public class Calcurator_ex extends Frame implements ActionListener{ // 인스턴스 변수 선언 Butt…

2004.01
29

[App 개발] AWT java text 에디터입니다.

import java.awt.*; import java.awt.event.*; import java.io.*; class EditorDemo extends Frame implements ActionListener{ TextAr…

2004.01
29

[App 개발] NeHe Lesson 18

Quadrics Quadrics는 for 루프와 삼각함수를 이용하여 복잡한 물체를 그리는 방법을 뜻합니다. 강의 7 에서 사용한 코드를 이용할 것입니다. 일곱 개의 변수를 추가하고 텍스쳐를 바꾸어서 변화를 주었습니다. bool sp;…

2004.01
28

[App 개발] NeHe Lesson 17

이번 강의는 NeHe와 Giuseppe D’Agata에 의해 만들어졌습니다. 폰트때문에 힘드신 것 다 압니다. 지금까지 제가 만든 강의에서는 단순히 글자를 출력하는 것뿐만 아니라 3D 글자, 텍스쳐 매핑된 글자를 출력할 수 있고, 변수를 처리할 …

2004.01
25

[App 개발] CoreGraphics의 풀스크린 화면 만들기

박진철님께서 올리셨던 질문 중에 코코아에서 퀵드로우의 CopyBits대신 사용할 수 있는 방법으로서 아시는 분들은 다 아실만한 CGDirectDisplay 를 이용한 방법을 한 번 시도해 보았습니다. 프로그램은 심히 완성도가 떨어지는 관계로 감히…

2004.01
23

[App 개발] Cocoa Design Patterns (5)

Commands GOF의 패턴에 익숙하신 분들은 아마 타겟/액션이 마치 커맨드 패턴의 구현이라고 생각될 것입니다. 그러나 커맨드 패턴같은 것이 타겟/액션과 같은 구현이 이용된다 하더라도 Objective-C에서는 그것이 필요가 없습니다. 커맨드 패턴…

2004.01
22

[App 개발] Cocoa Design Patterns (4)

Enumeration 코코아의 모든 콜랙션 클래스는 계수기를 제공합니다. 이 패턴은 GOF의 반복기 패턴과 비슷합니다. 계수기는 객체의 집합을 훑어내고 각 객체에 어떤 일을 수행하는 방법을 제공합니다. 특정한 집합에 대하여 특수한 루프를 짜 주는 대…

2004.01
22

[App 개발] NeHe Lesson 16

이번 강의는 Chris Aliotta가 제작하였습니다. 당신의 OpenGL 프로그램에 안개를 넣고 싶으십니까 이번 강의에서 바로 그 방법을 보여드리려고 합니다. 저는 강의를 쓰는 것은 처음이고, OpenGL/C++ 프로그래밍은 근래에 시작한 것이…