[OpenGL Tutorial] Lighting

사용자 삽입 이미지빛, 이 세상에 빛이 없다면 과연 아름다움이란 존재할까. 아마도 존재하지 않을 것이다. 이처럼 보이는 것에는 빛이란 것으로 하여금 아름다움이나 더 무었다운 것으로 보여지게 한다. 이처럼 중요한 빛의 사용을 OpenGL에서 알아보자. 빛에 대해 설명할 것들은 참으로 많다. 하나 하나 짚어보도록 하자.

색에는 빨강, 초록, 파랑이 기본 요소이듯 빛에도 3가지의 기본 요소가 있다. 그것은 주변광(Ambient), 확산광(Diffuse), 반사광(Specular)이다. 이 세가지에 대한 설명은 OpenGL Super Bible(page 268)에 나오는 내용을 그대로 옮기니 잘 읽어두길 바란다.

Ambient Light
특정한 방향이에서 오는 빛이 아니다. 빛의 근원은 있지만 방 혹은 장면 주위에서 반사되어 들어오기 때문에 방향이 없는 광선이다. Ambient light에 의해서 조명되는 물체는 전체적으로 모든 방향으로부터 빛이 비추어진다. 물체를 회전시켜도 Ambient Light에 의해서 색이 변하지 않는다. 즉, 방향을 갖고 있지 않은 Ambient Light가 그 물체를 비추고 있었기 때문에, 어느 각도에서도 같은 색으로 보여지는 것이다.

Diffuse Light
특정한 방향으로 비춰지지만, 그 빛의 반사는 여러 방향으로 이루어진다. 빛이 골고루 반사된다 하더라도, 물체를 비스듬히 비출 때보다 바로 위에서 비출 때가 물체 표면을 더 밝게 보인다. 예로써 형광등의 빛이나 오후에 창을 통해 들어오는 햇빛을 생각하면 된다.

Specular Light
역시 방향을 가지는 빛이다. 하지만 사방으로 밫을 반사시키는 Diffuse Light와는 달리 특정 방향으로만 뚜렷하게 빛을 반사시킨다. 강한 Specular Light이 물체에 닿으면 그 표면에 Spot가 생기는데 이것을 Specular Highlight라고 한다.

자신이 원하는 빛의 값을 얻는데는 정확한 공식이 없다. 많은 예를 접해봄으로써 느낌으로 익혀야 한다. 과학적 기술에 예술적 감각이 필요한 것이다. 빛을 사용하려면 몇가지 준비해야할 것들이 많다. 첫째로 빛을 비추는 조명이 필요하다. 둘째로 물체가 필요한다. 셋째로 빛이 물체에 비칠때 반사각을 결정한 법선벡터가 필요하다. 넷째로 물체의 재질이 필요한데 같은 빛이라도 어떤 재질의 물체가 빛을 받느냐에 따라 다르기 때문이다. 이 네가지를 하나 하나 익혀보도록하자.

1. 빛을 비추는 조명(Lighting Model Setting)

OpenGL에는 최대 9개의 독립적이 광원을 설정하여 사용할 수 있으며 기본적으로 디폴트 광원이 하나 준비되어있다. 하지만 일반적으로 디폴트 광원은 건드리지 않고 독립적인 광원을 설정하여 사용하는데 그 이유는 독립적인 광원은 보다 더 많은 설정 사항이 있어 프로그래머의 입맛에 맛는 광원을 얻을 수 있기 때문이다. 독립적인 광원의 이름은 GL_LIGHT0, GL_LIGHT1, GL_LIGHT2, … 이다. 그럼 이 독립적인 광원들이 가지는 속성 값들에는 어떤 것들이 있을까? 먼저 위에서 말한 세가지 Ambient, Diffuse, Specular Light값이 있다. 그리고 빛의 위치. 빛이 스포트 라이트이라면 그에 해당하는 스포트 라이트 값 등이다. 여그서는 빛에 대한 모든 것을 다루지 못하고(현재 필자의 지식의 한계) Ambient, Diffuse, Specular Light와 빛의 위치들 만으로 빛을 생성하여 보겠다. 특별한 조명 장치가 아닌 실세계의 모든 빛은 이 장에서 언급하는 것들만으로 충분이 가능하다고 생각한다. 필자의 능력이 확장되는대로 소개할 것을 약속한다. 사실 그 외의 빛의 속성값에 대한 것을 실제로 사용해 보았으나 눈으로 분명하게 느끼지 못해 소개하기가 껄끄럽다. 자! 그럼 본격적인 빛의 속성값의 설정과 빛의 사용을 지시하는 예를 들어보자.

하나의 가정을 하지 저기 높은 어느 특정한 곳에 전체적으로 노란빛을 띠는 빛이 있다고 해보자. 그렇다면 하얀색의 물체는 그 노란색의 빛을 받아 노란색으로 보일 것이다. 그렇다면 노란빛을 띠는 빛의 속성값은 어떻게 정의하는가? 정의하는 코드는 다음과 같다.

GLfloat ambientLight[] = { 0.25f, 0.25f, 0.0f, 1.0f };     // <1>
GLfloat diffuseLight[] = { 0.9f, 0.9f, 0.0f, 1.0f };       // <2>
GLfloat lightPos[] = { -100.0f, 130.0f, 150.0f, 1.0f };    // <3>
   
glEnable(GL_LIGHTING);                                     // <4>
glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);            // <5>
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);            // <6>
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);               // <7>
glEnable(GL_LIGHT0);                                       // <8>

1번 코드는 ambient light값을 설정하기 위한 변수이고 2번 코드는 diffuse light값을 설정하기 위한 변수이다. 노란색을 띠는 빛이므로 Ambient은 약한 노란색이고 Diffuse는 조금 강한 노란색의 속성값을 저장하기 위한 코드이다. 3번 코드는 빛의 위치값을 저장하기 위한 변수이다. 4번 코드는 우리가 OpenGL에서 빛을 사용하겠다는 것이다. 5번과 6번, 7번은 빛의 각각의 속성값을 실제로 설정하기 위한 것인데 모두 GL_LIGHT0라는 빛의 속성을 지정한 것이다. 결국 8번 코드에서 앞에서 GL_LIGHT0의 빛을 사용하겠다고 지정함으로써 기본적인 빛의 값은 설정되고 빛이 켜지는 것이다. 그런데 한가지 우리가 알아보지 못한 빛의 속성이 있다. 그것은 바로 Specular Light 속성인데 빛을 정면에서 받을 경우 정면에서 받은 물체의 면이 띠게될 색을 저정해 주는 것이다. 일반적으로 빛을 정면에서 받으면 순백색을 띠게 되는데 대부분 하얀색을 이 속성값으로 지정하게 된다. 즉 다음과 같다.

GLfloat specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
glLightfv(GL_LIGHT0, GL_SPECULAR, specular);

이것으로 빛의 속성값을 설정하는 것에 대해서 마치도록 하고 이제 물체를 만들어 보자.

2. 빛을 받는 물체 그리고 면

빛과 물체에서 중요한 것은 바로 면에 있다. 즉 빛이 물체의 면을 비출때 어떻게 되는가 하느냐에 따라 다른 장면을 만들어 낸다. 우리가 알고 있듯 면에 빛이 비칠때 수직으로 비치는 경우가 아닌라 조금 경사지게 비친다고 해보자. 면에 빛이 들어올때의 각을 입사각이라고 하고 나갈때의 빛의 각도를 반사각이라고 하면 면을 기준으로 서로 입사각과 반사각은 같다. 그렇다면 OpenGL은 그 입사각과 반사각을 어떤식으로 알수있을까? 그것은 법선을 이용하는 것이다. 즉 면에 법선을 정의해줌으로써 빛이 어느 각도로 입사하고 또 반사할지를 결정하는 것이다. 사실 법선은 면에 할당되는것이 아니라 하나의 점에 하나 이상 정의될수있다. 그러나 면을 이루는 각 점이 같은 법선백터를 가진다면 그 면은 하나의 법선백터를 가진다고 이해할 수가 있다. 즉 우리가 물체를 정의해줄때 각점에 대해서 법선 백터를 정의줘야 우리가 원하는 빛의 효과를 기대할 수 있다. 글로써 이해가 되지 않는 독자를 위해 다음 그림을 제시한다.

사용자 삽입 이미지

다음으로 면에는 앞과 뒤가 있다. 빛을 고려한다면 면의 앞면과 뒷면을 분명하게 지정해 줘야 한다. 앞면과 뒷면은 어떤식으로 결정하는가? 면은 폴리곤이다. 폴리곤은 각 점들로 이루어진 폐각형인데 이 점들을 지정하는 방향으로 앞면과 뒷면을 결정하게 된다. OpenGL은 기본적으로 반시계방향으로 면을 지정할 경우 앞면이고 그 면의 다른 방향의 면이 뒷면이 된다. 그러면 왜 OpenGL은 앞면과 뒷면을 구분하는가? 그냥 면이면 충분하지 않았을까? 그것은 꼭 필요한 연산만을 위한 것이다. 예를 들어 정육면체에서 안쪽의 면들은 연산에서 고려할 필요가 없다. 왜냐하면 볼 필요가 없기 때문이다. 즉 불필요한 계산을 피하고자 앞면과 뒷면을 고려한 것이다. 또 하나의 이유는 앞면과 뒷면이 서로 다른 재질로 이루어졌을 경우의 구현을 위해서이다.

정리를 해보자. 빛을 받는 물체에 있어서 우리가 고려해야할 것은 바로 법선백터와 앞면과 뒷면을 정확히 구분하여야 한다는 점이다. 다음의 코드는 그 두가지를 분명하게 고려한 것이다.

 glBegin(GL_QUADS);
 glNormal3f(0.0f, 0.0f, 1.0f);   // <1>
 glVertex3f(1.0f, 1.0f, 1.0f);   // <2>
 glVertex3f(-1.0f, 1.0f, 1.0f);  // <2>
 glVertex3f(-1.0f, -1.0f, 1.0f); // <2>
 glVertex3f(1.0f, -1.0f, 1.0f);  // <2>
   
 glNormal3f(1.0f, 0.0f, 0.0f);
 glVertex3f(1.0f, 1.0f, 1.0f);
 glVertex3f(1.0f, -1.0f, 1.0f);
 glVertex3f(1.0f, -1.0f, -1.0f);
 glVertex3f(1.0f, 1.0f, -1.0f);
   
 glNormal3f(0.0f, 0.0f, -1.0f);
 glVertex3f(1.0f, 1.0f, -1.0f);
 glVertex3f(1.0f, -1.0f, -1.0f);
 glVertex3f(-1.0f, -1.0f, -1.0f);
 glVertex3f(-1.0f, 1.0f, -1.0f);
   
 glNormal3f(-1.0f, 0.0f, 0.0f);
 glVertex3f(-1.0f, 1.0f, -1.0f);
 glVertex3f(-1.0f, -1.0f, -1.0f);
 glVertex3f(-1.0f, -1.0f, 1.0f);
 glVertex3f(-1.0f, 1.0f, 1.0f);
   
 glNormal3f(0.0f, -1.0f, 0.0f);
 glVertex3f(1.0f, -1.0f, -1.0f);
 glVertex3f(1.0f, -1.0f, 1.0f);
 glVertex3f(-1.0f, -1.0f, 1.0f);
 glVertex3f(-1.0f, -1.0f, -1.0f);
   
 glNormal3f(0.0f, 1.0f, 0.0f);
 glVertex3f(-1.0f, 1.0f, -1.0f);
 glVertex3f(-1.0f, 1.0f, 1.0f);
 glVertex3f(1.0f, 1.0f, 1.0f);
 glVertex3f(1.0f, 1.0f, -1.0f);
 glEnd();

<1>코드가 바로 법선 벡터를 지정한 것인데 시작점은 항상 원점이다. 즉 <1>은 (0,0,0)에서 (0,0,1)까지의 백터이다. OpenGL의 성능향상을 위해 법선백터는 항상 크기가 1인 단위벡터를 사용한다. 한번 법선 백터를 지정하면 다음으로 지정되는 좌표들은 그 법선벡터를 갖게 된다. <2>코드들은 면을 구성하는 것인데 앞면이라는 것을 나타내기 위해 그 지정순서가 반시계방향이다. 나머지 면들에 대해서도 모두 마찬가지이다.

3. 물체의 재질의 설정(Material Setting)

물체가 준비되었으나 아직 빛을 받아들이기에는 부족하다. 왜냐하면 같은 모양의 물체라도 나무냐 금속이냐에 따라서 빛이 비치는 모습이 다르기 때문이다. 즉 물체의 재질이 따라 다르다는 말이다. 물체의 재질을 설정하는 방법에 대해 알아도록하자.
물체가 재질로는 어떤 속성들이 있을까? Ambient, Diffuse, Specualr, Emission, Shiness가 있다. 쉽고 익숙한 말로 풀어보면 Ambient는 물체가 은은하게 나타내는 색이고 Diffuse는 물체의 주된 색상이다. Specular은 물체가 빛에 정면으로 비칠때 나타내는 색이고 Emisson은 조금 다른 것으로 발광색이다. Shiness는 빛나는 정도를 의미한다. 물체의 대부분의 경우 Ambient와 Diffuse는 같은 값을 갖으며 빛을 정면으로 받을때의 색은 순백색이다. Shiness는 Spot Highlight의 크기를 나타내는데 그 값은 0~128사이로 커질수록 그 Spot Highlight의 크기가 작아지면서 더욱 진하게 된다. 그럼 예를 들어보자. Shiness가 10정도이고 조금 밝은 회색의 물체인데 빛을 정면으로 받으면 하얀색인 물체의 재질을 설정하는 것을 코드로 예를 들어보자.

GLfloat gray[] = {0.75f, 0.75f, 0.75f };
GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f}

glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gray);
glMaterialfv(GL_FRONT, GL_SPECULAR, specref);
glMateriali(GL_FRONT, GL_SHININESS, 10);

이렇게 재질이 지정되면 다음에 만들어지는 모든 물체는 앞에서 설정된 재질로 지정이 된게 된다. 또다른 간단한 재질 설정법이 있는데 그것은 우리가 많이 사용하는 glColor3f 함수를 이용하는 것이다. 이것으로 간단하게 Ambient와 Diffuse를 설정할 수 있다. 위의 코드와 동일한 일을 하는 코드를 다시 예로 들어보면 다음과 같다.

GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f}

glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
glColor4f(0.75f, 0.75f, 0.75f, 1.0f);
glMaterialfv(GL_FRONT, GL_SPECULAR, specref);
glMateriali(GL_FRONT, GL_SHININESS, 10);

한가지 주목할만한 것으로 GL_FRONT라는 인자인데 상반되는 인자로 GL_BACK이다. 이것을 이용하면 면의 앞면과 뒷면의 재질을 서로 다르게 설정할 수 있다. 두면을 동시에 같게 설정할 수 있는 인자로 GL_FRONT_AND_BACK이다.

이제 최종적으로 전체적인 소스 코드로 이 장을 정리하도록 하겠다. 사용하는 코드는 1장의 소스 코드이다.

먼저 물체를 회전시키는데 필요한 변수이다 전역 변수를 선언하는 곳에 선언하자.

GLfloat rot = 0.0f;

빛을 설정하고 재질을 설정하는 코드가 들어갈 함수는 initGL 정도가 알맞겠다.

int InitGL(GLvoid) // All Setup For OpenGL Goes Here
{
    GLfloat ambientLight[] = { 0.25f, 0.25f, 0.0f, 1.0f };
    GLfloat diffuseLight[] = { 0.9f, 0.9f, 0.0f, 1.0f };
    GLfloat lightPos[] = { -100.0f, 130.0f, 150.0f, 1.0f };
    GLfloat specular[] = { 1.0f, 1.0f, 1.0f, 1.0f };
    GLfloat specref[] = { 1.0f, 1.0f, 1.0f, 1.0f };
   
    glShadeModel(GL_SMOOTH); // Enable Smooth Shading
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Black Background
    glClearDepth(1.0f); // Depth Buffer Setup
    glEnable(GL_DEPTH_TEST); // Enables Depth Testing
    glEnable(GL_CULL_FACE); // 뒷면에 대해서는 계산하지 말라
    glFrontFace(GL_CCW);   // 시계방향이 앞면이다.
    glEnable(GL_LIGHTING); // 빛을 사용한다.
   
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
    glEnable(GL_LIGHT0); // 0번빛을 사용한다.
   
    glEnable(GL_COLOR_MATERIAL); 
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glMaterialfv(GL_FRONT, GL_SPECULAR, specref);
    glMateriali(GL_FRONT, GL_SHININESS, 10);
   
    glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
    // Really Nice Perspective    Calculations
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); 
   
    return TRUE; // Initialization Went OK
} 

실제로 물체를 그려주는 코드는 DrawScean 정도 알맞겠다.

int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
    // Clear Screen And Depth    Buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
    glLoadIdentity(); // Reset The Current Modelview Matrix
    glTranslatef(0.0f, 0.0f, -7.0f);
   
    glColor3f(0.95f, 0.95f, 0.95f);
   
    glRotatef(rot, 1.0f, 1.0f, 1.0f);
    glBegin(GL_QUADS);
    glNormal3f(0.0f, 0.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f); 
   
    glNormal3f(1.0f, 0.0f, 0.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
   
    glNormal3f(0.0f, 0.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
  
    glNormal3f(-1.0f, 0.0f, 0.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
   
    glNormal3f(0.0f, -1.0f, 0.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
  
    glNormal3f(0.0f, 1.0f, 0.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glEnd();
   
    rot+=1.0f;
    if(rot>359.0f) rot=1.0f;
   
    return TRUE;
}

실제 실행되는 화면은 다음과 같다.

사용자 삽입 이미지

어떤가? 밝은 회색의 물체가 노란 불빛때문에 전체적으로 노란색을 띠게 되는데 물체가 회전할때마다 빛의 영향으로 현실감있게 색이 변한다.

[OpenGL Tutorial] Color

사용자 삽입 이미지이번 장에서는 색에 대해서 알아보도록하자. 색을 지정하는 윈도우즈 API로 RGB함수가 있다. 인자를 3개 받는데 각각 빨간색, 녹색, 파란색을 갖으며 0~255사이의 값을 갖는다. OpenGL도 마찬가지인데 조금 다른 것은 이 3개의 인자로 실수형, 정수형, 배열형 등 다양하게 취한다는 것이다. 이때 주목해야 할것은 실수형일때와 정수형일때 각 값이 갖을수있는 범위이다. 실수형에 있어서는 0~1.0사이의 값을 갖으며 정수형은 우리가 알고 있는 0~255 사이의 값을 갖는다. 또한 배열을 인자로 받는 경우 그 배열의 각 셀은 빨간색, 녹색, 파란색의 값을 갖는다. OpenGL은 이 3개의 색상값 말고도 하나의 값을 더 갖는 경우가 있는데 이것은 바로 알파(Alpha) 값이다. Alpha값은 투명도라는 특성값이다. 이장에서 Alpha에 대한 설명은 하지 않겠다. 지금까지의 설명이 색에 대한 것들의 대부분이다. 이제 실제 코딩을 통해 익혀보자. 우리는 정육면체를 생성하고 각면에 서로 다른 색을 지정해 보는 것으로 이장을 마칠까한다.

우리가 사용할 코드는 1장의 소스코드에서 시작한다.

먼저 전역 변수를 하나 선언해야 한다. 물체를 지속적으로 회전시키는데 사용하는 변수이다. 소스 코드의 전역변수를 선언하는 곳에 아래의 코드를 추가하자.

GLfloat rot = 0.0f;

이제 물체를 그리고 각면에 서로 다른 색을 입히는 코드를 만들어보자. 물체를 그려주는 코드이므로 DrawGLScene 함수서 코딩을 해주면 된다. 다음은 그 내용이다.

int DrawGLScene(GLvoid)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(0.0f, 0.0f, -5.0f);
    glRotatef(rot, 1.0f, 1.0f, 0.0f);
   
    glBegin(GL_QUADS);
    glColor3f(1.0f, 0.0f, 0.0f); // 1면을 Red으로 칠한다.
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
   
    glColor3f(1.0f, 1.0f, 0.5f); // 2면을 밝은 노란색으로 칠한다.
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
   
    glColor3f(0.0f, 0.0f, 1.0f); // 3면을 Blue로 칠한다.
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
   
    glColor3f(1.0f, 1.0f, 0.0f); // 4면을 노란색으로 칠한다.
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
   
    glColor3f(0.0f, 1.0f, 1.0f); // 5면을 Cyan으로 칠한다.
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    
    glColor3f(1.0f, 1.0f, 1.0f); // 6면의 한쪽 꼭지점의 색 지정
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glColor3f(1.0f, 0.0f, 1.0f); // 6면의 두번째 꼭지점의 색지정
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glColor3f(0.0f, 1.0f, 0.0f); // 6면의 세번째 꼭지점의 색지정
    glVertex3f(1.0f, 1.0f, 1.0f);
    glColor3f(1.0f, 1.0f, 0.0f); // 6면의 네번째 꼭지점의 색지정
    glVertex3f(1.0f, 1.0f, -1.0f);
    glEnd();    rot+=1.0f;
    if(rot>359) rot=1.0f;   

    return TRUE;
}

위 코드에서 중요한 것은 색을 지정하는 방법이다. 일단 색이 한번 지정되면 그 이후로 만들어지는 모든 물체에 대해서 그 색이 반영이 된다. GL_QUADS으로써 면을 생성하는데 면의 생성에 앞서 색을 지정해주면 그 면에 지정된 색이 칠해지게 된다. 그런데 약간 다른 것이 하나 있는데 그것은 6번째 면에 대한 색의 지정이다. 색의 지정을 각 점을 지정할때 마다 해주었는데 이것의 효과는 한 점에서 지정된 색과 다른 한점에서 지정된 색으로 두 점 사이로 부드러운 색변화로써 색을 지정하는 것이다. 즉 우리가 많이 보는 그라이디언트로 색이 지정되는 것이다.

이제 실행해보자. 아래는 그 실행 결과이다.

사용자 삽입 이미지

실제로 6면에 대해서 다른 면과는 다른 모습으로 색이 칠해졌음을 알수있다.

이로써 색에 대한 것들을 마친다.

[OpenGL Tutorial] Translate, Rotate and Scale Objects

사용자 삽입 이미지이번에는 화면상의 물체(점, 선, 폴리곤, 그리고 이들의 조합으로 이루어진 것들)을 움직여 보고 회전 시켜 보며 크기를 변경하는 것에 대해서 살펴보자. 이 장은 1장의 소스에서 출발을 한다.

먼저 이동과 회전 그리고 크기를 조절할 대상이 되는 간단한 물체를 생성해 보자. 뭐가 좋을까? 정육면체로 하자. 정육면체는 면이 6개이며 꼭지점이 8개인 물건(?)이다. 꼭지점이 8개 이므로 우리는 8개의 좌표를 알고 있어야 한다. 다음의 코드는 화면상에 정육면체를 그려주는 glDrawGLScene 함수의 코드이다.

int DrawGLScene(GLvoid)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(0.0f,0.0f,-10.0f);
    glColor3f(1.0f, 1.0f, 1.0f);
    glBegin(GL_LINE_LOOP);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glEnd(); 
   
    glBegin(GL_LINE_LOOP);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glEnd();
   
    glBegin(GL_LINES);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glEnd();
    return TRUE;
} 	

2장을 충분이 이해하고 있다면 전혀 어려운 부분이 없다. 이해가 가지 않는 독자가 있다면 2장을 보기 바란다. 코딩을 끝마친후 실행해보자. 다음은 그 실행 결과이다.사용자 삽입 이미지

혹시 이런 질문을 던지는 독자가 있을지도 모르겠다. 정육면체라고 했는데 앞면과 뒷면의 크기가 다르지 않는가?라고… 답부터 말하자면 앞면과 뒷면의 크기는 분명이 동일하다. 달라보이는 이유는 좀더 사실적인 연출을 위해 원근감이 적용되었기 때문이다.

자, 이제 물체의 이동, 회전, 크기조절(스케일)을 할 물체가 준비되었다.

먼저 독자에게 주지 시켜야 할것이 있다. 그것은 필자가 말한 물체의 이동, 물체의 회전, 물체의 크기조절이라는 말에서 풍기는 의미전달의 오류를 바로 잡고자 한다. OpenGL에서는 이러한 작업을 좌표체계를 변경함으로써 이루어진다는 것이다. 즉 물체의 이동에 있어서 실제로 물체를 이동하는 것이 아니고 좌표축을 이동시킨후에 물체를 그려주는 것이다. 회전도 마찬가지이다. 먼저 좌표축을 지정된 각으로 회전시킨후에 그 회전 좌표축 상에 물체를 그려주는 것이다. 결국 우리가 원하는 상태의 물체를 얻을 수 있다는 것이다. 자 지금까지의 모든 것을을 이해했다면 이제 실제 우리가 원하는 것들을 해보자!!

먼저 물체를 이동시켜보자. 왼쪽으로 1.0만큼, 위쪽으로 1.0만큼 좌표체계를 이동시키는 것은 어떻게 될까? 바로 다음 한줄의 코드가 그러한 일을 한다.

glTranslatef(1.0f, 1.0f, 0.0f); 

glTranslatef 함수의 첫번째 인자는 X축으로 이동값, 두번째와 세번째는 각각 Y축과 Z축으로의 이동값이다. 이 함수를 어디에 위치시켜야 하는가? 바로 물체를 그려주기 바로 전에 사용하면 되겠다. 다음 코드의 <*> 코드가 그 주인공이다.

int DrawGLScene(GLvoid)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(0.0f,0.0f,-10.0f);
    glColor3f(1.0f, 1.0f, 1.0f);
    
    glTranslatef(1.0f, 1.0f, 0.0f); // <*>
   
    glBegin(GL_LINE_LOOP);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glEnd(); 
   
       .
       .
       .
   
    return TRUE;
}

아래는 실행 결과이다. 우리가 원하는 대로 물체가 이동되었음을 알수있다.

사용자 삽입 이미지

이제 물체를 회전시켜보는 것을 알아보자. Z축으로 45도 회전시켜보는 것으로 예를 들어보자. 다음은 좌표축을 Z축으로 45도 회전시키는 코드이다.

glRotatef(45.0f, 0.0f, 0.0f, 1.0f)

첫번째 인자는 회전각(60도)이고 나머지 인자들은 X,Y,Z축상의 값들이다. (0.0, 0.0, 1.0)이 Z축을 기준으로 회전한다는 의미데 회전을 할때는 반드시 회전축이 필요하다. 바로 (0.0, 0.0, 1.0)이 회전축을 지정해 주는 것이다. 원점에서 (0.0, 0.0, 1.0)을 잇는 선, 바로 이 선이 회전축인 것이다. 그렇다면 이 코드의 위치는 어디인가? 마찬가지로 물체를 그려주기 바로 전에 위치하면 되겠다.

int DrawGLScene(GLvoid)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(0.0f,0.0f,-10.0f);
    glColor3f(1.0f, 1.0f, 1.0f);
    
    glRotatef(45.0f, 0.0f, 0.0f, 1.0f); // <*>
    
    glBegin(GL_LINE_LOOP);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glEnd(); 
    
         .
         .
         .
    
    return TRUE;
} 

다음은 실행 결과의 화면이다.

사용자 삽입 이미지

어떤가 생각했던 대로 회전이 이루어 졌는가?

이제 다음으로 물체의 크기조절에 대한 것에 대해 알아보도록 하겠다. 물체를 Y축으로 2배 늘려보자. 다른 좌표축보다 Y축으로 2배의 값으로 증가하는 좌표축을 구성하면 물체도 그에 따라 Y축으로 2배 늘어날 것이다. 바로 아래가 그러한 일을 하는 코드이다.

glScalef(1.0, 2.0f, 1.0f);

첫번째 인자는 X축 좌표값의 증가배수값이고 두번째와 세번째가 각각 Y, Z축의 좌표값의 증가 배수이다. 두번째 값을 2.0으로 잡아줌으로써 Y축의 좌표축 값을 다른 축에 비해 2배로 증가하게 되는 것이다. 이 코드의 위치해야할 곳은 또 어디인가? 마찬가지로 물체를 그리기 바로 전에 위치하면 된다.

int DrawGLScene(GLvoid)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(0.0f,0.0f,-10.0f);
    glColor3f(1.0f, 1.0f, 1.0f);
   
    glScalef(1.0, 2.0f, 1.0f);
   
    glBegin(GL_LINE_LOOP);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glEnd(); 
   
       .
       .
       .
   
    return TRUE;
} 

또 다음은 그 실행 결과이다.

사용자 삽입 이미지

어떤가. 우리가 원하는 대로 물체가 Y축으로 2배 길어졌다.

이로써 물체의 이동, 회전, 스케이링에 대한 장을 마치겠다.

[OpenGL Tutorial] Displaying Points, Lines, Polygons

사용자 삽입 이미지OpenGL에서 화면상에 점과 선, 그리고 면을 그리는 것은 아주 쉽다. 그려주는 핵심적인 코드를 살펴보자. 먼저 코드를 살펴보기 전에 알고 있어야 할 것은 OpenGL의 좌표체계는 3차원 직각좌표를 기본으로 하고 있으며 왼쪽에서 오른쪽으로 그 X좌표의 값이 증가하며 아래에서 위쪽으로 Y좌표의 값이 증가한다. 그리고 모니터 화면의 바깓쪽으로 Z값이 증가한다는 점이다. 또한 폴리곤은 한마디로 안이 채워진 도형이며 면(Face)이라는 것이다


화면에 점을 그려주는 예

glBegin(GL_POINTS);
glVertex3f(0.0f, 0.0f, 0.0f);
glEnd();

화면에 선을 2개 그려주는 예

glBegin(GL_LINES);
glVertex3f(-1.0f, -1.0f, 0.0f);
glVertex3f(1.0f, -1.0f, 0.0f);
glVertex3f(1.0f, 1.0f, 0.0f);
glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd();

폴리곤을 그려주는 예

glBegin(GL_POLYGON);
glVertex3f(-1.0f, -1.0f, 0.0f);
glVertex3f(1.0f, -1.0f, 0.0f);
glVertex3f(1.0f, 1.0f, 0.0f);
glVertex3f(-1.0f, 1.0f, 0.0f);
glEnd();

눈치가 빠른 독자라면 위에서 보인 세가지의 코드가 공통점이 있다는 것을 파악했을 것이다. 바로 glBegin과 glEnd라는 구문 안에 각 점들을 기술해줬다는 것이다. 점들을 기술하는 방법은 우리가 이미 알고 있는 3차원 직교 자표계로 쉽게 생각할 수 있다. 세번째인 폴리곤을 그려주는 예에서 4개의 점 좌표를 기술해 줬는데 종이를 준비해서 각 점을 종이에 순서대로 찍어보고 그 점을 순서 이어보자. 사각형이 그려질 것이다. 폴리곤이므로 처음 좌표와 마지막 좌표가 이어지게 되고 안은 지정된 색으로 채워지게 된다. 바로 이것이 좌표를 기술해 주는 방식이다. 간단하다.

그렇다면 이제 점을 그릴 것인지 선을 그릴것인지, 아니면 폴리곤을 그릴것인지 어떤 식으로 기술할 수 있는가. 역시 눈치 빠른 독자는 이미 그 방법을 위의 코드를 통해서 알것이다. 그렇다. 바로 glBegin이라는 함수를 통해 그 인자로 지시를 해주는 것이다. GL_POINTS는 점, GL_LINES는 선, GL_POLYGON은 폴리곤을 그려라는 의미이다. 이쯤이라면 위에서 제시한 3가지 코드에 대한 이해를 완벽하게 했으리라 믿어 의심치 않는다. 그래도 모르겠다면 필자에게 왜 이렇게 설명을 못하냐는 멜과 함께 뭐가 이해가 가지 않는지 멜을 가볍게 한통 던져주길 바란다.


여기서 끝마치면 너무 아쉽고 서운하다. 이 코드를 실제적으로 실습해봐야 할것이고 glBegin에 오는 인자가 더 있지나 않나하는 호기심을 품어야함이 옳다. 먼저 실습하는 방법에 대해서 알아보자. 필자는 이미 OpenGL을 초기화하는 과정을 튜어리얼 1번을 통해서 아주 길게 설명했다. 이 2번 튜어리얼은 1번에서 했던 코드를 그대로 사용한다. 먼저 1번 튜어리얼에서제작한 소스 코드를 살펴보면 DrawGLScane라는 함수가 있다. 바로 이 함수 안에서 그려주는 코드를 삽입하면 되겠다. 폴리곤을 그려주는 것으로 예를 들면 다음과 같다.

int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
    // Clear Screen And Depth    Buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
    glLoadIdentity(); // Reset The Current Modelview Matrix
    glTranslatef(0.0f,0.0f,-10.0f);   // 추가된 코드 1
    glColor3f(1.0f, 1.0f, 1.0f);      // 추가된 코드 2
    glBegin(GL_POLYGON);              // 추가된 코드
    glVertex3f(-1.0f, -1.0f, 0.0f);   // 추가된 코드
    glVertex3f(1.0f, -1.0f, 0.0f);    // 추가된 코드
    glVertex3f(1.0f, 1.0f, 0.0f);     // 추가된 코드
    glVertex3f(-1.0f, 1.0f, 0.0f);    // 추가된 코드
    glEnd();                          // 추가된 코드
    return TRUE; // Everything Went OK
} 

추가된 코드만을 코딩한후에 실행해 보자. 썰렁한 하얀 정사각형이 화면에 그려질 것이다. 하지만 우리가 그리고자 했던 것임은 틀림없다. 아닌가??

사용자 삽입 이미지

위의 코드를 더 살펴보자. 추가된 코드1과 2가 있는데 이것을 그냥 넘겨버리자니 이미 황폐해져 버린 필자의 마지막 남은 양심을 지킬 수가 없고 해서 설명해야겠다. 먼저 OpenGL은 처음에 초기화 과정에서 원점(0, 0, 0)을 화면의 정중앙에 맞추게 된다. 이 원점을 중심으로 OpenGL에서 그려지는 모든 것들을 바라보게 된다. 중요한 것이 바로 여기에 있다. 바로 원점에서 우리가 그린 사각형을 바라봐야 하는데 눈과 사각형이 겹치게 된다. 상상해 보라 독자의 눈과 책이 만날정도로 부착시키면 독자는 무었을 보게 되는가… 그렇다! 아무것도 보이지 않는다. 그래서 추가코드1이 필요한 것이다. 즉 물체를 눈의 위치보다 앞이나 뒤로 옮겨야 볼수 있는 것이다. 추가 코드1인 glTranslatef(0.0f, 0.0f, -10.0f)가 바로 그러한 일을 하는데 물체를 Z축으로 10.0f만큼 뒤로(-) 옮겨 그리라는 것이다. 이제 추가 코드2번에 대해서 살펴보도록 하자. 함수 이름에서 직감할 수 있듯이 그려줄 것에 대한 색을 지정하는 것이다. glColor3f는 세개의 인자를 갖는데 각각의 인자는 Red, Green, Blue 값이며 셋모두 하한값과 상한값이 0~1 사이를 갖는다. 이 정도면 나머지 것들도 실습하는데 별 어려움이 없을 것이다. 하지만 그 어떤 아쉬움이 남지 않는가….. 하지만 일단 그 아쉬움에 대해 후일을 기약하자.



이제 glBegin에 올수 있는 인자에 대해서 알아보도록 하자. 아래 그림으로 한방에 해결하고 마친다.