이 부분은 OpenGL의 전체적인 내용을 정리한 부분입니다.
- 위 그림에서 보여주듯 OpenGL은 윈도우에서 펜,브러쉬,폰트 및 다른 GDI객체를 사용하여 그리는 것과는 다른 방법으로 윈도우와 연결
- GDI가 윈도우에서 디바이스 컨텍스트를 사용하는 것처럼 OpenGL은 렌더링 켄텍스트를 사용.
- 렌더링 컨텍스트는 디바이스 켄텍스트와 연결되어 윈도우와 상호작용을 수행
OpenGL의 사용환경
- OpenGL은 플랫폼 독립적으로 각종 하드웨어 플랫폼에 이식이 가능
- 유닉스, IBM OS/2, MS Window, 리눅스, 오픈스텝, BeOS등을 모두 지원
- OpenGL에는 윈도우에 관련된 일련의 작업이라든가 사용자 입력에 관한 직접적인 윈도우를 제어하는 명령어는 없고 이러한 기능들은 각각의 플랫폼에 속한 시스템에 존재하는 확장함수가 대신.
각종 OS | 확장 라이브러리 |
MS Window | WGL 라이브러리 |
X Window(Unix) | GLX 라이브러리 |
OS/2 (IBM) | PGL 라이브러리 |
OpenGL 개발 언어 | C/C++, 포트란, java, Ada |
표1 : OpenGL의 확장함수와 개발언어
- OpenGL 개발 언어로는 C/C++, 포트란, java, Ada등 각종 언어로써 오픈지엘 라이브러리를 손쉽게 사용할 수
있다.
*** 확장함수(즉 윈도우 제어함수) WGL(MS Windows)이나 GLX(Unix,Xwindows)는 OpenGL에 윈도우 지원을 위해 win32에 추가 된 것으로 직접적인 사용이 어렵다. 그리하여 보통의 유저들은 확장함수를 사용하여 편하게 간소화 시킨 MS Windows 기반의 Aux라이브러리 또는 Glut를 자주 사용한다. OpengGL Utility Toolkit(GLUT) 또는 AUX는 윈도우 시스템의 독립적인 툴킷으로서 윈도우 API들의 복잡성을 숨길수 있다. ***
OpenGL의 관련 파일
Library | Header file | Prefix | 관련기능 |
Opengl32.dll | Gl.h | gl | OpenGL의 라이브러리를 실제로 정의 |
Glu32.dll | Glu.h | glu | OpenGL의 유틸리티로서 폴리곤,원통,곡면생성.행렬연산등의 각종함수 제공 |
Glaux.lib | Glaux.h | aux | 윈도우 제어 |
Glut32.dll | Glut.h | glut | 윈도우 제어나 각종 유틸리티 함수 제공 |
표2 : 라이브러리의 종류
표2에 주어진 바와 같이 GL은 그래픽과 관련된 대부분의 기능이 실제로 정의되어 있다. 그리고 GLU는 GL함수들의 사용을 보다 쉽게 프로그래밍하도록 만든 utility라 할수 있다. 그리고 window제어와 직접 관련된 것으로 WGL과 관련된 Glaux(또는 GLUT)라는 라이브러리를 사용할수 있다.
예시1) AUX라이브러리를 사용한 경우
#include <windows.h>
#include <gl\gl.h>
#include <gl\glaux.h>
......
glVertex3f(25.0f,-25.0f,25.0f);
glVertex3f(-25.0f,-25.0f,25.0f);
.........
void main(void)
{
MessageBeep(0);
auxInitDisplayMode(AUX_DOUBLE | AUX_RGBA); //--->glutInitDisplayMod와 대응
auxInitPosition(100,100,250,250);//-->glutInitWindowSize와glutInitWindowPosition에 대응
auxInitWindow("A 3D Cube, Press Space");//-->glutCreateWindow에대응 auxReshapeFunc(ChangeSize);
auxKeyFunc(AUX_SPACE,KeySpace);
auxMainLoop(RenderScene);
}
** AUX라이브러리를 사용할 경우 우선 위의 예제와 같이 "windows.h", "gl.h", "glaux.h"를 인클루드 시켜야 한다. 그리고 main() 함수에서 윈도우 제어를 위한 구체적인 aux 라이브러리 함수를 코딩한다.
예시2) GLUT를 사용한 경우
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>
...................................
glVertex3f(-3.0,0.0, 0.0);
glVertex3f(3.0,0.0, 0.0);
glVertex3f(-3.0,-3.0, 0.0);
glVertex3f(-3.0,3.0, 0.0);
.............................................
main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize (600, 600);
glutInitWindowPosition (100, 100);
glutCreateWindow ("sampling");
init ();
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
**Glut라이브러리를 사용할 경우 우선 위의 예제와 같이 "windows.h", "gl.h", "glut.h"를 인클루드 시켜야 한다. 그리고 main() 함수에서 윈도우 제어를 위한 구체적인 glut 함수를 코딩한다. 구체적인 함수의 각 기능은 3주차 강의에서 설명한다.
**각 대응 관계를 비교해보기 바람**
OpenGL의 시작
기본적인 라이브러리들은 sgi사의 OpenGL 공식사이트에서 쉽게 구할수 있다.관계 파일들은 다음 표와 같이 적절한 관계 폴더에 삽입한다.
Gl.h glu.h glaux.h glut.h | C:\ProgramFiles\MicrosoftVisualStudio\VC98\include\gl |
Glu32.lib glaux.lib glut32.lib | C:\Program Files\Microsoft Visual Studio\VC98\lib |
Opengl32.dll Glu32.dll glut32.dll | C:\windows\system |
표3 라이브러리의 위치
OpenGL프로그래밍의 순서
1.RC(Rendering Context)생성
2.광원 설정
3.투영 방식(projection) 지정
4.뷰포트 설정.
*****예제 프로그램********
다음은 AUX라이브러리를 사용하여 "주전자"를 그리는 간단한 오픈지엘 프로그램이다.
(여러분도 테스트해보시기 바랍니다.)
#include <windows.h>
#include <gl\gl.h>
#include <gl\glaux.h>
void CALLBACK ChangeSize(GLsizei w, GLsizei h)
{
if(h == 0)
h = 1;
glViewport(0, 0, w, h);
glLoadIdentity();
if (w <= h)
glOrtho (-100.0f, 100.0f, -100.0f, 100.0f*h/w, -100.0, 100.0);
else
glOrtho (-100.0f, 100*w/h, -100.0f, 100.0f, -100.0, 1.00);
}
void CALLBACK RenderScene(void)
{
glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0f, 0.0f, 0.0f);
auxWireTeapot(50.0f);
glFlush();
}
void main(void)
{
auxInitDisplayMode(AUX_SINGLE | AUX_RGBA);
auxInitPosition(100,100,250,250);
auxInitWindow("3D Wire-Frame Teapot");
auxReshapeFunc(ChangeSize);
auxMainLoop(RenderScene);
}
***결과 화면***
다음은 GLUT를 사용하여 "사각형"를 그리는 간단한 오픈지엘 프로그램이다.
(테스트 해보세요)
#include <windows.h>
#include <GL/gl.h>
#include <GL/glut.h>
void display(void)
{
glClear (GL_COLOR_BUFFER_BIT);
glColor3f (1.0, 1.0, 1.0);
glBegin(GL_POLYGON);
glVertex3f(0.25,0.25, 0.0);
glVertex3f(0.75,0.25, 0.0);
glVertex3f(0.75,0.75, 0.0);
glVertex3f(0.25,0.75, 0.0);
glEnd();
glFlush ();
}
void init (void)
{
glClearColor (0.0, 0.0, 0.0, 0.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize (600, 600);
glutInitWindowPosition (100, 100);
glutCreateWindow ("sampling");
init ();
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
결과 화면
3차원 그래픽의 원리 이해
OpenGL에 들어가기에 앞서서 간단하게 3차원 그래픽의 파이프 라인을 이해하고 넘어가는게 좋을 듯 싶어서 간단하게
3D-->2D로 변환이 되는 과정에 대해서 하나씩 알아 보고 OpenGL에 대해서 알아본다.
이 바로 앞의 내용은 OpenGL로 프로그래밍을 하기 위해서 처음에 세팅 하는 부분과 간단한 함수들을 불러 들여서 OpenGL
의 그래픽 처리에 관한 부분을 이해해 본 것이라구 생각을 하면 되고 이 아래 부분은 Direct3D부분에서는 어떻게 이 부분을
이해하고 구성되어 있는지는 모르겠지만 OpenGL에서 사용되는 컴퓨터 그래픽 파이프 라인을 이해해 봄으로써 3D그래픽에
대한 이론들을 이해를 하고 그 이론들을 실제 OpenGL에서는 어떤 함수들을 이용하여 사용을 하는지 알아본다.
래스터화는 모든 부분이 마무리가 되고 나서 모니터에 뿌려지는 부분이다.
이 모든 용어에 대한 설명은 하나씩 하기에는 좀 뭐하구 그냥 이제부터 나오는 부분을 하나씩 집고 넘어 가다 보면 파이프 라
인을 이해를 할 수가 있을 것이다.
우선 3차원의 좌표계가 어떻게 모니터의 2차원 좌표계로 바뀌는지에 대해서 알아 본다.
1.디스플레이 장치로부터 가장 먼 좌표계로부터 시작한다. 그림에서 왼쪽에 보인바와 같이 각각의 3차원 물체는 3차원 모델링 좌표계에서 정의된다. 객체 좌표계(object-coordinate systme) 또는 지역 좌표계(local coordinate system)라고 부르기도 한다.2.정의된 물체는 모델링 변환에 의해 세계 좌표계(world-coordinate system)로 변환 되는데 여기서 하나의 장면이나 완벽한 객체의 표현이 이루어 진다.문제 좌표계(problem coordinate system) 또는 응용 좌표계(application-coordinate system)라 부르기도 한다.
3차원 객체를 디스플레이에 생성하기 위해서는 2차원으로 투영하여 좌표를 생성한다.
3.뷰 볼륨을 정의하기 위한 좌표계로 뷰기준 좌표계(view-reference coordinate system)가 있다. 때때로 왼손 좌표계로 구현되기도 한다.4.뷰 좌표계 다음은 정규 투영 좌표계로서(normalized-projection coordinate system) 또는 3차원 화면 좌표계(3D screen coordinate)로서 평행투영 정규 뷰 볼륨 좌표계이다.
5.마지막으로 3차원에서 2차원으로의 투영은 2차원 장치 좌표계(2D device-coordimate system)을 필요로 한다.정규 장치 좌표계, 이미지 좌표계, 또는 장치 좌표계라 부르기도 한다.
그럼 다음으로 3차원 좌표계의 정점이 어떤 파이프 라인으로 모니터의 좌표계로 변환이 되는지 알아 보자.
1.원래의 정점을 모델 관측 행렬로 곱하여,변환된 시각 좌표계를 생성한다.
2.시각 좌표계는 투영 행렬을 곱해서 클리핑 좌표로 변환된다.
-->이 과정에서 관측공간 밖의 모든 데이터를 효과적으로 제거를 한다.
3.클리핑 좌표는 w좌표로 나누어 정규화 장치 좌표계로 변환이 된다.
-->이때 w값은 사용된 변환에 따라 투영 행렬이나 모델 관측 행렬에 의해 변경 될 수 있다.
4.이 세 값의 좌표는 뷰포트 변환에 의해 2차원 평면으로 매핑된다.
대충 파이프 라인이 이렇게 돌아간다. 그럼 하나하나의 변환이 어떤 함수로 이루어지는지 알아본다.
여기서 미리 알아 두고 갈 것이 high-level,low-level차원으로 Opengl에서는 함수를 제공을 하는데....
쉽게 말해서 high-level은 두 번의 행렬 연산을 해야 하는 경우 함수 하나가 그 기능을 하는 것이구 low-level차원은 행렬을
두 개 만들어서(미리 만들어 져 있겠죠?) 두 번 곱해서 값을 얻는 것이다. 여기서는 이해를 쉽게 하기 위해 low-level표현과
high-level함수를 이용하는 두가지 경우를 들어 보겠다.
1.모델 관측 행렬은 4x4행렬로 물체의 위치와 방향을 설정하는데 사용해 변환된 좌표계를 나타낸다.
가정:각변이 10의 길이를 가진 육면체를 생성하여 y축의 양의 방향으로 10만큼 이동시켜라.
- low-level표현 방법-
glutWireCube(10.0f); //각변이 10의 길이를 가지는 육면체 생성
// y축의 양의 방향으로 10만큼 이동하는 행렬을 생성한다.
//모델 관측 행렬과 위의 행렬을 곱한다.
//육면체를 그린다.
glutWireCube(10.0f);
- high-level함수 사용 -
void glTranslatef(GLfloat x, GLfloat y, GLfloat z);
glTranslatef(0.0f,10.0f,0.0f);
glutWireCube(10.0f);
여기서는 예제를 이동만 다루었는데 회전,크기 변환도 모두 이 부분에서 다루게 된다. 단순하게 행렬을 지정을 해서 사용
을 하고자 한다면 low-level차원으로 하나씩 단위 행렬이면 [1.0 0 0 0][0 1.0 0 0][0 0 1.0 0][0 0 0 1.0]행렬을
배열로 선언을 해서 사용을 하면 된다. 프로그래머가 생각을 해서 이상한 짓을 할려구 하는데 high-level함수가 없다면 모
를까 왜 굳이 이런짓을 해야 하는지는... 근데 low-level차원으로 해주는게 더 속도가 좋은 경우도 있다구 한다.
2.시각 좌표계는 투영 행렬을 곱해서 클리핑 좌표로 변환된다.
-->이 과정에서 관측공간 밖의 모든 데이터를 효과적으로 제거를 한다.
먼저 시각 좌표계를 만드는 방법에 대해서 알아 본다. 이건 조망 변환(viewing transformation)이라구도 한다.
카메라의 위치와 방향(카메라가 바라보는 방향)을 변경 시킨다.
void glulookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz);
(eyex,eyey,eyez)는 현재 주시하고 있는 눈의 위치를 의미하며 (centerx,centery,centerz)는 주시하고 있는 물체의 위치를 표현하며 (upx,upy,upz)는 upvector를 의미한다.
위 그림에서 카메라는 원점에 위치하고 주시점(reference point)은 z축 음의 방향으로 -100이며 up vector는 양의 y축 방향이므로gluLookat(0.0, 0.0, 0.0, 0.0, 0.0, -100.0 ,0.0, 1.0, 0.0); 으로 코딩이 가능하다.
** gluLookAt(4.0, 2.0, 1.0, 2.0, 4.0, -3.0, 2.0, 2.0, -1.0);
위 그림에서 한번 봐 보도록 하자. eyex,y,z는 빨간 색의 선 부분이다. 그럼 eyex,y,z는 해결이 됐다. 무엇인지 다음으로
centerx,y,z는 카메라가 보는 방향을 결정을 해준다. 앞의 eyex,y,z는 단지 카메라의 위치일뿐 어느쪽을 볼건지는 다른 어
떤 점이 있어서 방향이 있는 벡터 값으로 주지 않는 이상 어느쪽을 볼지를 모른다. 그래서 centerx,y,z가 그 방향을 잡아준다.
여기서는 e라는 저 점이 그 기능을 한다. 그럼 하나하나씩 생각을 해 보도록 하자. centerY값을 eY1점으로 이동을 시키면
어떻게 보일까요? 당연히 카메라는 가만히 있는 상태에서 보는 것입니다. 당연히 머리 부분이 보이겠죠!! 사용자에게는 물체
가 아래로 내려간 것처럼 보일거구요..
그럼 Z축을 생각해 볼까요!! eZ1과 eZ2의 차이는 무엇일까요?
아무 차이도 나질 않습니다. 왜냐? 이건 그냥 카메라가 어딜 보는지를 정하는거지? 카메라는 움직이지 않기 때문이죠..
그러면 카메라의 eyeZ보다 왼쪽으로 가면 당연히 검정색만 보이겠죠.....
그럼 마지막의 upx,y,z를 볼까요?
0, 1.0, 0으로 설정을 하면 카메라의 +y축으로 법선 벡터가 나간다구 생각을 하며 된다. 그럼 이 물체는 반드시 서 있는거
처럼 보인다. 0, -1.0, 0은 -y축으로 나가므로 물체가 물구나무 서기를 하고 있다. 다음으로 1.0, 0, 0은 +x축으로 나가므로
머리가 왼쪽 다리가 오른쪽으로 향하게 누워있을 것이다.
그림을 보고 비교해보길 바란다.
** 예시 **
void display(void)
{
glClear (GL_COLOR_BUFFER_BIT);
glColor3f (1.0, 1.0, 1.0);
glLoadIdentity (); /* clear the matrix */
/* viewing transformation */
gluLookAt (0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
glScalef (1.0, 2.0, 1.0); /* modeling transformation */
glutWireCube (1.0);
glFlush ();
}
glulookAt()함수는 카메라의 위치와 카메라가 바라보는 곳의 위치, 그리고 카메라의 위쪽 방향을 지정함으로써 조망 변환을
설정하는데,상당히 직관적이고 명료하다.
또 단점은 GLU라이브러리를 링크해야 한다는 것이다. GLU라이브러리를 사용하기 싫다면 glRotate(),glTranslate()함수를
glTranslate(0.0f, 0.0f, -10.0f)이렇게 하게 되면 물체의 좌표값이 -10만큼 z축으로 멀어지므로 어차피 카메라가 그대로 있기
때문에 카메라를 z축으로 10만큼 옮긴 효과를 발생 시킨다. 그럼 카메라의 이동 말구 방향을 바꾸게 할려면 어떻게 해야 할
까?
void PlaneView(GLfloat planeX,GLfloat planeY,GLfloat planeZ,GLfloat roll,GLfloat pitch,GLfloat yaw)
{
//롤은 z축에 대한 회전각이다.
glRotatef(roll, 0.0f, 0.0f, 1.0f);
//요는 y축에 대한 회전각이다.
glRotatef(yaw, 0.0f, 1.0f, 0.0f);
//피치는 x축에 대한 회전각이다.
glRotatef(pitch, 1.0f, 0.0f, 0.0f);
//비행기를 비행기의 전역 좌표로 이동
glTranslatef(-planeX,-planeY,-planeZ);
}
이건 그냥 있길래 올려 놓은건데 내용은 잘 모르겠다.
투영에 대해서 그럼 알아 보겠습니다. 투영에는 직교투영과 투시투영(glFrustum(),gluPerspective())이 있다.
void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
직교 투영은 모든 면이 사각형이다. 이론적으로는 앞, 뒤, 위, 아래, 왼쪽, 오른쪽 면이 모두 같다. 주로 화면에 정확한 모양과 치수를 표현해야하는 CAD난 건축설계에 많이 사용된다.
glOrtho2D()는 2차원 형태의 직교 투영을 정의한다.
oid reshape (int w, int h)
{
glViewport (0, 0, (GLsizei) w, (GLsizei) h); //뷰포트 영역을 우선 지정을 해 준다 .
glMatrixMode (GL_PROJECTION); //행렬 모드를 투영을 사용을 한다구 지정한다.
glLoadIdentity (); //항상 초기화를 해 주어야 한다.
//현재 변환 행렬을 단위 행렬로 바꾸어 준다. 여기서는 투영 행렬을 단위 행렬로 변형해 준다.
//이건 본질적으로 좌표계를 시각 좌표계로 초기화 하는 것과 같다.
if (w <= h) //아래의 투시투영은 상관은 없지만 직교 투영은 두 개의 경우를 생각해야 한다. 사용을 거의 안하니...
glOrtho (-50.0, 50.0, -50.0*(GLfloat)h/(GLfloat)w,
50.0*(GLfloat)h/(GLfloat)w, -1.0, 1.0);
else
glOrtho (-50.0*(GLfloat)w/(GLfloat)h,
50.0*(GLfloat)w/(GLfloat)h, -50.0, 50.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW); //이 부분을 설정해 주는 이유는 우리가 사용한 GL_PROJECTION즉 투영 행렬
//이 모델에 영향을 미칠 것이다라는걸 알려 주는 것이다.
//GL_MODELVIEW :행렬 연산이 모델 관측 스택에 적용된다.(장면 상에서 물체를 이동할 때 사용)
//GL_PROJECTION :행렬 연산이 투영 행렬 스택에 적용된다.(클리핑 공간을 정의할 때 사용)
//GL_TEXTURE :행렬 연산이 텍스쳐 행렬 스택에 적용된다.(텍스쳐 좌표를 조작)
}
void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
void reshape (int w, int h)
{
glViewport (0, 0, (GLsizei) w, (GLsizei) h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0);
glMatrixMode (GL_MODELVIEW);
}
glFrustum()은 원하는 효과를 얻기 위해 투영 방법을 직관적으로 파악하기 힘들다. 하지만 gluPerspective()는 직관적으로
쉬운 편이다.
void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble near, GLdouble far);
void reshape(int w, int h)
{
glViewport(0, 0, (GLsizei) w, (GLsizei) h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 1.0, 30000.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
그럼 또 나오게 된 클리핑이라는 용어에 대해서 알아 봅니다.
윈도우는 물리적으로 픽셀단위로 모니터에 뿌려줘야 하기 때문에 프로그램에게 클리핑 공간을 가르켜 주어야 한다.
2D와는 다르게 클리핑이라구 하면 추가되는 내용이 뷰포트와의 연관성에 대한 이해가 필요하다.
쉽게 윈도우의 크기를 조절한다구 가정을 하자. 클리핑 공간설정은 우리가 원하는 150x100의 좌표계가 실제 스크린의
높이와 같게 설정을 해주는 것이다. 이 방법에는 위의 투영의 3가지 방법이 사용이 된다. 직교 투영을 사용을 하면 당연히
같은크기의 물체가 항상 보이겠죠..
그러구 뷰포트가 축소 확대입니다. 뷰포트의 크기를 축소를 시키면 윈도우의 스크린의 가로,세로값이 들어가서 축소가 되는
겁니다.
3.클리핑 좌표는 w좌표로 나누어 정규화 장치 좌표계로 변환이 된다.
-->이때 w값은 사용된 변환에 따라 투영 행렬이나 모델 관측 행렬에 의해 변경 될 수 있다.
클리핑 좌표는 다시 w좌표로 나누어 정규화 장치 좌표계로 변환된다. 이때 사용된 w 값은 사용된 변환에 따라 투영행렬이
나 모델 관측 행렬에 의해 변경될 수 있다.
4.이 세 값의 좌표는 뷰포트 변환에 의해 2차원 평면으로 매핑된다.
glViewport함수는 많이 보았을 것이다.
다음으로 알아 보는 내용은 폴리곤의 생성 방법들에 대해서 자세히 알아 보도록 한다.
3차원 공간을 표현을 하는데 가장 기본이 되는 것은 점이라구 하였다. 그리구 선 그리구 한면을 만드는데는 폴리곤이라구
불리는 면을 구성하는 최소 단위인 삼각형으로 이루어져 있다. 그럼 이 말에서두 알 수 있듯이 삼각형 즉 최소의 폴리곤을
만들기 위해서는 정점이 3개는 필요하게 된다. 그럼 이 이론을 바탕으로 라인에서부터 삼각형 그 이상으로 복잡한 폴리곤을
어떻게 구성을 하고 그리구 가장 적은 메모리 사용을 하기 위해서 OpenGL은 어떻게 구현을 하는지 알아 본다.
glBegin(GL_LINE_STRIP);
glVertex3f(0.0f, 0.0f, 0.0f); //V0
glVertex3f(50.0f, 50.0f, 0.0f); //V1
glVertex3f(50.0f, 100.0f, 0.0f); //V2
glEnd();
옆의 선을 구성하는 함수는 GL_LINE_LOOP를 사용하구 끝에 나오는 선은 처음 명시된 정점과 마지막에 명시된 정점 사이를
연결하게 되어 닫혀 있는 형태의 선을 쉽게 그릴 수 있게 해준다.
대충 선을 긋는데는 이런 두가지 방법으로 할 수가 있다. 그러면 이 선들을 가지고 구성되는 삼각형은 어떤 방법들로 그리게
되는지 알아 보도록 한다.
삼각형을 그리는데는 감기(Winding)에 따라서 앞면,뒤면을 구분하게 된다.
glBegin(GL_TRIANGLES);
glVertex2f(0.0f, 0.0f);
glVertex2f(25.0f, 25.0f);
glVertex2f(50.0f, 0.0f); //이 부분은 오른쪽의 삼각형을 구성한다.
glVertex2f(-50.0f, 0.0f);
glVertex2f(-75.0f, 50.0f);
glVertex2f(-25.0f, 0.0f); //지금 이 부분은 보면 왼쪽 삼각형이 아니구 반대로 즉 시계방향으로 이동한다.
//그럼 이 부분도 이 좌표대로만 삼각형을 그린다면 앞면이 된다.
glEnd();
그럼 이렇게 지금 보여주구 있는 폴리곤이 앞면인지 뒷면인지를 구분하는 이유가 무엇일까?
많은 이유가 있다. 앞뒤면이 서로 다른 재질의 폴리곤이 필요할 때두 있구.
뒷면을 그냥 감출 수도 있구,일정한 색을 설정하여 반사 성질을 나타낼 수도 있다.
그리구 랜더링 속도를 결정하는 중요한 요소가 되기도 한다.
glFrontFace(GL_CW) 이 함수는 시계방향 감기를 사용한 폴리곤이 앞면을 향한다는 것을 OpenGL에게 알려주는 것이다.
위와 같이 그냥 시계방향이면 앞면이구 반시계방향 감기이면 뒷면이면 좋은데 그렇지 않는 경우에 알려주는 것이다.
그럼 다른 경우를 알아 본다.
트라이 앵글 스트립(Triangle Strips,삼각띠)라구 하는데 이 방법은 처음 세 정점을 지정하여 첫 번째 삼각형을 나타낸 후,
그 다음에 나오는 삼각형을 그리기 위해서는 단 하나의 점만 있으면 된다. 이러면 상당 부분의 프로그램 코드와 저장 공간을
절약할 수 있다.이건 앞에서 하던식으로 반시계방향 감기를 하고 있습니다.
트라이 앵글 팬(Triangle Fans,부채꼴) 이 방법은 시계방향으로 돌아간다는게 다르다.
이 모든 부분에 대한 소스는 나중에 하나의 예제를 통해 알아 보도록 한다.
다음으로 중요한 용어가 은면제거(Hidden Surface Removal)이다. 쉽게 이런 문제가 발생할 수 있다. 처음 물체를 그렸는데
그리구 나서 다른 물체는 더 앞에 위치한 물체로 표현을 했는데두 여전히 처음 물체가 뒤의 물체를 가린다는 것이다.
이 문제를 보완하기 위해서는 z버퍼(깊이 검사)라는걸 수행을 하여 관측자로부터 물체가 얼마의 z축의 음의 방향을
향하는지 알아 보면 된다.
이걸 수행하는 함수는 glEnable(GL_DEPTH_TEST)이다.
다음으로 나오는 용어가 선별(culling)이다. 쉽게 면을 숨기는 것이다.
사변형(Quad)를 구성하는 함수 GL_QUAD_STRIP()
범용 폴리곤(General Polygon)
지금부터 소스는 윈뿔을 그리는 소스이다. 색깔이 다른 두 부분이 있는데 위에것이 원뿔을 구성하는 옆면을 만들구 아래가
원뿔을 구성하는 밑면을 만들어 준다. 항상 차례가 이렇다. 내가 구현할려구 하는 RC를 미리 설정해 놓은 다음에 진짜 그리는
함수에는 선별,깊이 검사,폴리곤 랜더링 등의 방법등에 대한 모든 부분을 설정을 해준다음에 모델 관측 행렬을 단위행렬로
바꾸어주던지 다른 방법을 쓰던지 해서 시각 좌표계로 변환을 해주어야 한다. 그러구 물체를 움직이던지 하는 방법에 있어서
행렬을 사용한다. 그리구 진짜 물체를 그리게 된다. 이 방법은 전에 무슨 변환을 해 주었는지를 OpenGL이 알고 있다가 다른
문제가 발생하지 않도록 해 주는 것이다.
void SetupRC() //그럼 랜더링 컨텍스트를 초기화 해 보도록 하겠다.
{
glClearColor(0.0f,0.0f,0.0f,1.0f); //지워지구 다시 그려지는 색
glColor3f(0.0f,1.0f,0.0f); //현재색
glShadeModel(GL_FLAT); //마지막으로 명시된 정점에서 사용하는 색을 사용하여 폴리곤을 일정하게 색칠하게
//만든다. 반대로 GL_SMOOTH는 각 정점에 명시된 값을 부드럽게 연결 하도록, 두 정점에 대해 명시된 색을
//보간(interpolate)하게 한다. 쉽게 GL_FLAT는 한색으로 GL_SMOOTH는 각 정점의 색이 번져나가듯이....
glFrontFace(GL_CW);//트라이 앵글 팬을 사용할거닌까 시계방향이구 트라이 앵글스트립이면 반시계방향
}
void RenderScene(void) //장면을 그릴 때 사용을 한다.
{
GLfloat x,y,angle; //좌표값과 각도 저장
int iPivot = 1;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//윈도우와 깊이 버퍼를 CLEAR한다.
if(bCull) //선별을 원하는지 않하는지를 구분.
glEnable(GL_CULL_FACE);
else
glDisable(GL_CULL_FACE);
if(bDepth) //깊이 검사를 테스트
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
if(bOutline) //뒷면의 폴리곤만 그리는지 설정
glPolygonMode(GL_BACK,GL_LINE);
else
glPolygonMode(GL_BACK,GL_FILL);
glPushMatrix(); //현재 행렬을 glLoadIdentity()로 초기화를 하지 않고 스택에 저장하라는 함수이다.
//이 함수를 사용하는 목적은 매번 관측 행렬을 단위 행렬로 초기화하는 것은 바람직하지 않다. 즉 현재 변환
//상태를 저장하고 특정 물체를 지정한후 다시 복구하는 기능이 필요할 때는 초기화를 하게 되면 다시 변환
//된 행렬을 다시 바꿔 줘야 하므로 비 효율적이다.
glRotatef(xRot,1.0f,0.0f,0.0f);
glRotatef(yRot,0.0f,1.0f,0.0f); //여기서 회전을 시킨다.
glBegin(GL_TRIANGLE_FAN); //트라이 앵글 팬을 그리기 시작한다.
glVertex3f(0.0f,0.0f,75.0f); //원뿔의 꼭지점 지정
for(angle=0.0f; angle < (2.0f*GL_PI); angle +=(GL_PI/8.0f)) //원의 짝수번째점을 트라이앵글 팬의 정점으로 설정
{
x = 50.0f*sin(angle); //정점의 X,Y값을 계산
y = 50.0f*cos(angle);
if((iPivot %2) == 0) //적색과 녹색을 번갈아가며 사용한다.
glColor3f(0.0f,1.0f,0.0f);
else
glColor3f(1.0f,0.0f,0.0f);
iPivot++;//값을 증가시켜 다른 색을 칠하게 설정
glVertex2f(x,y);//트라이 앵글 팬에 사용할 정점을 사용하기 위해서 정점 지정
}
glEnd();
glBegin(GL_TRIANGLE_FAN);//바닥 채우기
glVertex2f(0.0f,0.0f); //중심은 원점
for(angle = 0.0f; angle < (2.0f*GL_PI); angle += (GL_PI/8.0f))
{
x=50.0f*sin(angle);
y=50.0f*cos(angle);
if((iPivot %2) == 0)
glColor3f(0.0f,1.0f,0.0f);
else
glColor3f(1.0f,0.0f,0.0f);
iPivot++;
glVertex2f(x,y);
}
glEnd();
glPopMatrix(); //스택에 저장된 행렬을 다시 불러낸다.
glFlush(); //드로잉 명령을 실행한다.
}
다음으로 폴리곤에 패턴을 채우는 방법에 대해서 알아보자.
두가지 방법이 있다. 텍스쳐 매핑과 폴리곤 스티플링 패턴(polygon stippling pattern)이 있다.
텍스쳐 매핑은 제가 아직 모르닌까 뭐라 할말은 없구 스티플링 패턴은 단지,채우기 패턴에 사용되는 32x32 단색 비트맵으로
보면 된다.
glEnable(GL_POLYGON_STIPPLE); //이제부터 폴리곤 스티플링 패턴을 사용한다는걸 알려준다.
glPolygonStipple(pBitmap); //이제 나오는 모든 폴리곤은 pBitmap으로 명시된 패턴을 사용하여 채워질 것이다는걸 알려줌
이 모양을 만들기 위해서 제일 밑에서부터 시작하여 한번에 한줄씩 저장하는 것이다. 최상위 비트가 먼저 읽혀지는 방식으로
구성되어 있어서,데이터를 기본적으로 저장된 순서대로 읽을 수 있다. 그리구 나서 각 바이트를 왼쪽에서부터 오른쪽으로
읽은 뒤,4바이트씩 32행을 담기 충분한 GLubyte형의 배열에 저장한다.
GLubyte fire[] ={0x00,0x00,0x00,0x00,
~~~~~~~~~~~~,
0x00,0x00,0x00,0xc0,
0x00,0x00,0x01,0xf0,
0x00,0x00,0x07,0xf0,
뭐 이런식으로 비트마다에 값을 넘어서 하나의 모양을 구성하게 한다.
void SetupRC()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glColor3f(1.0f, 0.0f, 0.0f);
glEnable(GL_POLYGON_STIPPLE);
glPolygonStipple(fire);
}
다음으로 나오는 내용이 폴리곤의 제약에 대한 부분입니다.
분할(Subdivision) 간선
다음으로 중요한 빛에 대해서 알아본다. 그럼 빛에는 어떤 것들이 있는지 알아보도록 하겠다.
Ambient Light(주변광)특정한 방향이에서 오는 빛이 아니다. 빛의 근원은 있지만 방 혹은 장면 주위에서 반사되어 들어오기 때문에 방향이 없는 광선이다. Ambient light에 의해서 조명되는 물체는 전체적으로 모든 방향으로부터 빛이 비추어진다. 물체를 회전시켜도 Ambient Light에 의해서 색이 변하지 않는다. 즉, 방향을 갖고 있지 않은 Ambient Light가 그 물체를 비추고 있었기 때문에, 어느 각도에서도 같은 색으로 보여지는 것이다.
Diffuse Light(반사광)
특정한 방향으로 비춰지지만, 그 빛의 반사는 여러 방향으로 이루어진다. 빛이 골고루 반사된다 하더라도, 물체를 비스듬히 비출 때보다 바로 위에서 비출 때가 물체 표면을 더 밝게 보인다. 예로써 형광등의 빛이나 오후에 창을 통해 들어오는 햇빛을 생각하면 된다.
Specular Light(확산광)
역시 방향을 가지는 빛이다. 하지만 사방으로 밫을 반사시키는 Diffuse Light와는 달리 특정 방향으로만 뚜렷하게 빛을 반사시킨다. 강한 Specular Light이 물체에 닿으면 그 표면에 Spot가 생기는데 이것을 Specular Highlight라고 한다.
이 부분은 phong의 반사모형이라는 게 있는데 이걸 수학적 공식으로 쓰고 하는건 무자게 머리가 아프다. 굳이 알 필요성도
전혀 없다. 그럼 우리 주변의 빛은 이런 정도로 구성이 되어 있다는걸 외우구 넘어 간다.
조명을 적용하는 단계를 우선알고 생각하면서 넘어가도록 한다.
1.모든 객체의 모든 정점들에 대해 법선들을 계산한다. 그 법선들은 광원에 대한 객체의 상대적인 방향을 결정한다.
2.모든 광원들을 생성,선택,배치한다.
3.조명 모델을 선택한다.조명 모델은 주변광 및 조명 계산을 위한 시점의 위치를 결정한다.
4.장면 안의 객체들에 대한 재질 속성을 정의한다.
진행을 위의 단계순으로 하진 않겠다. 처음부터 법선이 나오구 하면 머리가 아프기 때문이에요...
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //이건 윈도우를 지울 때 다시 말해서 바탕.
glColor3f(1.0f, 0.0f, 0.0f); //이건 폴리곤이나 특정 영역의 색 지정
glColor3ub((GLubyte)0, (GLubyte)0, (GLubyte)255); //이건 window RGB를 사용을 많이한 프로그래머를 위한 함수
다음으로 색을 지정하는 방법이 있는데 이는 쉐이딩이라구 한다.
위에두 나오게 되지만 쉐이딩에는 GL_FLAT와 GL_SMOOTH가 있다. 이 내용은 위에 나오닌까 생략한다.
그리구 GL_POLYGON은 GL_FLAT와 달리 폴리곤 내부의 첫정점에 설정된 색을 칠한다.
다음으로 중요한 개념이 물체가 가지고 있는 고유한 재질에 따라서 색이 달리 보인다는것이다.
쉽게 특정색을 가진 물체는 같은 특정색을 비추게 되면 그 색은 거의 반사를 하고 다른색은 흡수를 한다는 것이다.
가령 가정해 보자.폴리곤이 적색일 경우,폴리곤이 빛의 적색 성분을 대부분 반사하는 재질로 이루어져 있다구 설정한다.
물체의 표면이 적색을 나타내도록 설정하는 것뿐만 아니마 주변광,반사광,확산광에 대한 재질의 반사 속성도 함께 설정해
주어야 한다. 가령 재질이 반짝이는 특성을 가지며 확산광을 주로 반사하는 반면,대부분의 주변광,반사광은 흡수하다고 설정
할 수 있다. 이와 반대로 평평하고, 색을 띠고 있는 물체를 모든 확산광은 흡수하여 주변 환경에 관계없이 반짝이지 않도록
설정할 수도 있다. 이 모든 내용은 OpenGL에서 지원을 하는 함수들이 있으니 한번 알아 본다..
GLfloat ambientLight[] = { 0.25f, 0.25f, 0.0f, 1.0f }; // 주변광에 사용할 색지정 약한 노란색
GLfloat diffuseLight[] = { 0.9f, 0.9f, 0.0f, 1.0f }; // 반사광 조금 강한 노란색
GLfloat specular[] = { 1.0f, 1.0f, 1.0f, 1.0f }; //빛을 정면으로 받을 경우 순백색을 띠는게 보통이므로 하얀색 사용
glEnable(GL_LIGHTING); //조명 효과 연산을 수행은 한다고 설정
glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight); // 실제 사용
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight); // 실제 사용 GL_LIGHT0으로 사용하겠다구 알려줌
glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
glEnable(GL_LIGHT0); //GL_LIGHT0을 사용하겠다구 알려줌
그럼 재질은 지정하는 함수를 알아본다. Ambient는 물체가 은은하게 나타내는 색이고 Diffuse는 물체의 주된 색상이다. Specular은 물체가 빛에 정면으로 비칠때 나타내는 색이고 Emisson은 조금 다른 것으로 발광색이다. Shiness는 빛나는 정도를 의미한다. 여기두 마찬가지로 색을 지정을 하고 함수에 그 색을 지정하고 사용을 하면 된다.
GLfloat gray[] = {0.75f, 0.75f, 0.75f };
GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f}
GLfloat specref[] = { 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);
GL_FRONT라는 인자인데 상반되는 인자로 GL_BACK이다. 이것을 이용하면 면의 앞면과 뒷면의 재질을 서로 다르게 설정할 수 있다. 두면을 동시에 같게 설정할 수 있는 인자로 GL_FRONT_AND_BACK이다.
뭐 이런식으로 재질을 지정한다.
다음으로 광원(Light Source)가 필요하다. 쉽게 빛이 어디서 비추냐에 따라서 물체가 비추는 면이면 빛이 비춰야 하고
아니면 각도에 따라 빛의 양이나 비추는 방법을 달리 해야 한다. 여기서 우리는 법선 벡터라는걸 알아야 한다.
쉽게 법선은 표면으로 들어오는 빛의 각도와 표면으로부터 나가는 빛의 각도(표면이 반사하는 빛의 세기)를 계산하기 위한
수단이다.삼각형을 생각해 보자. 표면에 하나의 법선벡터를 계산해서 색을 칠해 주는 것과 3정점을 계산해서 다루게 색을
칠해주는 것은 매우 다를 것이다. 그럼 문제가 발생한다. 많은 폴리곤을 계산을 하려면 그 많은 정점들을 다 법선벡터를 계산
해야 하는 것이다. 이를 효과적으로 사용하기 위한 OpenGL의 함수들을 알아 보도록 하겠다.
법선벡터를 구할려면 우선 두 벡터의 외적(두 벡터에 수직인 벡터)를 구하면 된다. 그럼 폴리곤을 구성하는 최소 단위의 삼각
형에서 법선 벡터를 구할려면 세 점을 알든지 아니면 두 벡터를 알면 된다. 그러면 low-level과 high-level을 알아 보자.
void CrossProduct(float point1[3], float point2[3],float point3[3], float normal[3])
{
float vector1[3], vector2[3];
//외적을 구하는데 필요한 세 정점으로 두 벡터를 만든다.
vector1[0] = point1[0] point2[0];
vector1[1] = point1[1] point2[2];
vector1[1] = point1[1] point2[2];
vector2[0] = point2[0] point3[0];
vector2[1] = point2[1] point3[2];
vector2[1] = point2[1] point3[2];
//두 벡터의 외적을 구하여 normal[]에 저장
normal[0] = vector1[1]*vector2[2] vector1[2]*vector2[2];
normal[1] = vector1[2]*vector2[0] vector1[0]*vector2[2];
normal[2] = vector1[0]*vector2[1] vector1[1]*vector2[0];
} //세 정점만 아는 경우
void CrossProduct(float vector1[3], float vector2[3], float normal[3])
{
normal[0] = vector1[1]*vector2[2] vector1[2]*vector2[2];
normal[1] = vector1[2]*vector2[0] vector1[0]*vector2[2];
normal[2] = vector1[0]*vector2[1] vector1[1]*vector2[0];
} //두 벡터를 아는 경우
high-level 함수를 알아 보자. glNormal3f()이다.
glNormal3f(1.0f, 0.0f, 0.0f); //이 면은 x축의 양의 방향으로 1의 크기를 갖는 법선 벡터이다.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); //사각형의 폴리곤을 만드는데 +x축 방향으로 해서 시계방향으로 회전할것이다.
OpenGL은 법선을 항상 단위 법선으로 변환해서 계산을 한다. 이 함수는 glEnable(GL_NORMALIZE)가 있는데 성능상
문제를 많이 가지고 있다. 그래서 GL_RESCALE_NORMAL이 있는데 이것은 모델뷰 행렬에 의해 결정된 확대,축소 비율이
법선의 각 성분에 일괄적으로 곱해진다. 아니면 직접 low-level차원으로 함수를 만들어두 된다.
double VectorLength(float vector[3])
{
return sqrt((vector[0]*vector[0]) +(vector[1]*vector[1]) + (vector[2]*vector[2]));
} //벡터의 길이 구하기
void Normalize(float normalVector[3])
{
double length;
length = VectorLength(normalVector);
for ( int idx =0; idx < 3; idx++)
normalVector[idx] /= length;
} //벡터를 정규화한다. 이렇게 low-level로 하면 더 속도가 나올 수도 있다.
그럼 마지막으로 세 정점만 가지고 단위 법선을 만드는 함수를 정의해 보자.
void CalculateNormal (float point1[3], float point2[3], float point3[3], float normal[3])
{
CrossProduct(point1,point2,point3,normal);
Normalize(normal);
} //속도를 높이려면 CrossProduct()와 Normalize()를 직접 안에 집어 넣어주는게 빠르겠죠.
이 정도면 법선벡터를 구하는 방법에 대해서 알 수가 있다.
1.모든 객체의 모든 정점들에 대해 법선들을 계산한다. 그 법선들은 광원에 대한 객체의 상대적인 방향을 결정한다.
2.모든 광원들을 생성,선택,배치한다.
3.조명 모델을 선택한다.조명 모델은 주변광 및 조명 계산을 위한 시점의 위치를 결정한다.
4.장면 안의 객체들에 대한 재질 속성을 정의한다.
그럼 대충 개념적으론 빛에 대한 전반적인 내용을 이해했다구보면 된다. 기본적인 내용으로는 이 정도면 다른 부분을 이해하
는데두 문제가 없다구 보구 그냥 이쯤에서 빛에 대한 전반적인 기초 내용은 마무리를 한다.
다음으로 중요한 개념인 텍스쳐 매핑에 대해서 알아보자.
우선 텍스쳐 매핑을 할려면 이미지파일을 하나 불러 와야 하는데 보통 보면 bmp파일을 많이 사용하고 책에 예제가 나와있기
때문에 bmp를 불러 들이는걸 공부하는건 무의미한거 같으므로 tga파일을 불러 들여서 한번 텍스쳐 매핑에 적용해 본다.
Targa이미지 파일은 알파 채널이 있으므로 텍스쳐로 사용하기가 쉽다. tga파일은 파일헤더부분과 파일 데이터를 메모리에
읽어 들이는 방법을 살펴 본다.TARGAFILEHEADER과 TGAFILE구조체의 구조에 대해서 한번 보구 넘어가도록...
int LoadTGAFile(char *filename, TGAFILE *tgaFile) //tga파일을 읽어서 이미지 데이터를 메모리에 저장하는 함수
{
FILE *filePtr;
unsigned char ucharBad; // 임시용 unsigned char data
short int sintBad; // 임시용 short int data
long imageSize; // size of the TGA image
int colorMode; // 4 면 RGBA , 3 이면 RGB
long imageIdx; // 카운트 변수
unsigned char colorSwap; // B <-->R bmp파일과 마찬가지로 24비트에서는 BGR형태로 데이터가
//들어가야 한다.(?)
// open the TGA file
filePtr = fopen(filename, "rb"); //rb읽기 모드는 read+binary이다. 이진 쓰기 모드
if (!filePtr)
return 0;
// 처음 두 바이트는 필요없으므로 넘어간다.
fread(&ucharBad, sizeof(unsigned char), 1, filePtr);
fread(&ucharBad, sizeof(unsigned char), 1, filePtr);
// 이미지 타입을 읽는다.
fread(&tgaFile->imageTypeCode, sizeof(unsigned char), 1, filePtr);
2 =>압축되지 않는 RGB이미지 3 =>압축되지 않은 흑백 이미지
4 =>RLE(Run-Length Encoding)방식으로 압축된 RGB이미지 11 =>압축된 흑백 이미지
// 2와 3일 때만 읽는다.)
if ((tgaFile->imageTypeCode != 2) && (tgaFile->imageTypeCode != 3))
{
fclose(filePtr);
return 0;
}
// 불필요한 13바이트는 건너 뛴다.
fread(&sintBad, sizeof(short int), 1, filePtr);
fread(&sintBad, sizeof(short int), 1, filePtr);
fread(&ucharBad, sizeof(unsigned char), 1, filePtr);
fread(&sintBad, sizeof(short int), 1, filePtr);
fread(&sintBad, sizeof(short int), 1, filePtr);
// 이미지 너비와 높이를 읽는다.
fread(&tgaFile->imageWidth, sizeof(short int), 1, filePtr);
fread(&tgaFile->imageHeight, sizeof(short int), 1, filePtr);
// 이미지 색상 비트수를 읽는다.
fread(&tgaFile->bitCount, sizeof(unsigned char), 1, filePtr);
// 불필요한 1바이트를 건너 뛴다.
fread(&ucharBad, sizeof(unsigned char), 1, filePtr);
// colorMode -> 3 = BGR, 4 = BGRA
colorMode = tgaFile->bitCount / 8;
imageSize = tgaFile->imageWidth * tgaFile->imageHeight * colorMode;
// 이미지 데이터를 넣을 메모리 할당
tgaFile->imageData = (unsigned char*)malloc(sizeof(unsigned char)*imageSize);
// 이미지 데이터를 읽어 들이기
fread(tgaFile->imageData, sizeof(unsigned char), imageSize, filePtr);
// Opengl에 맞게 BGR을 RGB로 교체한다.
for (imageIdx = 0; imageIdx < imageSize; imageIdx += colorMode)
{
colorSwap = tgaFile->imageData[imageIdx];
tgaFile->imageData[imageIdx] = tgaFile->imageData[imageIdx + 2];
tgaFile->imageData[imageIdx + 2] = colorSwap;
}
// close the file
fclose(filePtr);
return 1;
}
TGAFILE *myTGA; //구조체를 객체를 생성해 주구..
void DrawBitmap(long width, long height, unsigned char* bitmapImage)
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glRasterPos2i(200,200);
glDrawPixels(width, height, GL_RGB, GL_UNSIGNED_BYTE, bitmapImage);
} // 이 정도해주면 된다.
myTGA = (TGAFILE*)malloc(sizeof(TGAFILE)); //메모리 공간을 잡아 준다.
LoadTGAFile("test.tga", myTGA); //파일 이름을 집어 넣어 준다.
그럼 이제 tga이미지도 준비가 되어 있으니 텍스쳐 매핑의 방법들에 대해서 알아보자.
텍스쳐에는 1D,2D,3D가 있다. 1D는 너비만 있는 선이다. 3D텍스쳐는 아직은 거의 사용을 하지 않지만 하드웨어의 빠른 성장
으로 나중에는 사용을 하게 될 것이다.
함수는 glTexImage1D(),glTexImage2D()가 있다. 이건 두가지 방법이 있는데 여기서 다루기에는 너무 많은 부분을 차지할
거 같아서 생략을 한다.
그냥 간단하게 어떤 함수들이 어떤 기능을 해서 어떻게 폴리곤에 이미지가 바인딩되는지에 대해서 알아본다.
glEnable(GL_TEXTURE_2D); // 2D 텍스쳐링을 하겠다구 알려줌
// bmp파일을 읽어 들임
bitmapData = LoadBitmapFile("checker.bmp", &bitmapInfoHeader);
glGenTextures(1, &texture); // 텍스쳐객체를 생성시킨다.
glBindTexture(GL_TEXTURE_2D, texture); // 텍스쳐를 바인딩한다.
//1번째 매개변수 =>텍스쳐링은 2D사용하겠다.
//2번째 매개변수 =>텍스쳐 필터링(텍스쳐가 실제 화면에 어떻게 입혀질지에 결정하는거)에서 확대(하나의 텍셀이 하 //나의 픽셀보다 큰 영역에 입혀지는 것),축소(여러개의 텍셀이 하나의 픽셀에 입혀지는거)가 수행되도록 설정해 준다.
//3번째 매개변수 =>텍스쳐가 입혀질 픽셀들의 중앙과 가장 가까운 텍셀을 사용한다.
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_RGB, bitmapInfoHeader.biWidth,
bitmapInfoHeader.biHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, bitmapData);
이 정도 해준다음에 진짜 그림을 그리는 부분에 glTexCoord2f()함수를 사용하여 모니터 좌표값과 이미지의 좌표값을
맞춰주면 된다.
glTexImage2D()와 glReadPixels()함수가 있는데 glReadPixels()는 잘은 모르겠지만 좌표값에 일일이 맞추어주진 않은거
같은데.....
다음으로 다중 텍스쳐링에 대해서 알아 보자.
말그대로 두 개의 텍스쳐 이미지를 겹치게 하는 것이다. 이 부분은 현재 시스템에서 지원이 되는지에 대해서 먼저 확인을 한
다음에 구현을 해야 한다. 쉽게 그래픽카드가 좋아야 할 수가 있다.
GL_EXTENSIONS인자나 gluCheckExtension()함수를 사용하여 현재의 그래픽 카드가 GL_ARB_multitexture라는 확장
이름이 존재 한다면 시스템이 지원이 된다는걸 알 수가 있다. 순서는 아래와 같다.
1.다중 텍스쳐링의 지원 여부를 확인
2.확장 함수에 대한 포인터를 얻는다.(윈도우즈프로그램의 경우 필수)
3.텍스쳐 단위들을 설정
4.텍스쳐 좌표들을 지정
그럼 1번은 대충 위에서 한거구 다음으로 확장 함수의 포인터를 얻어야 한다 .함수는 wglGetProcAddress()이다.
glMultiTexCoordifARB(다중 텍스쳐링을 위한 텍스쳐 좌표들을 지정),glActiveARB(텍스쳐 단위를 설정)
glClientActiveTextureARB(포인터 배열 명령들을 위한 현재 텍스쳐 단위를 설정
3.텍스쳐 단위들을 설정하는 부분에 대해서 알아 보자. 현재 텍스쳐 단위는 glActiveTextureARB()함수로 선택한다.
구현 방법은 일단 두 개의 텍스쳐를 불러 들이구 두 개의 텍스쳐 객체를 만들구 이 두 텍스쳐 객체들을 다중 텍스쳐링을
위한 두 개의 텍스쳐 단위들로 만든다.
4.텍스쳐 좌표의 지정.
glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0f, 0.0f);
glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1.0f, 0.0f); //이건식으로 두 개의 텍스쳐들을 지정해 주어야 한다.
다음으로 블렌딩과 안개 효과에 대해서 알아 보도록 하겠습니다.
블렌딩이라구 하면 이미 화면상에 그려진 픽셀의 색과 이제 바로 같은 위치에 그려질 픽셀의 색의 조합하는 방식이다. 어떤식으로 색상을 조합하는 지는 알파값과 블랜딩 함수에 의해 정해진다. 지금까지 색상을 지정하는 방식으로 GL_RGB를 사용했었는데 여기에는 알파값이 없다. 알파값의 추가를 위해 GL_RGBA를 사용할수있다. 그리고 알파값을 포함한 색상을 지정하기 위해 glColor3f 대신 glColor4f를 사용할 수 있다.
블랜딩 공식이 있는데 그건 별루 중요한게 아닌거 같다. 그럼 코딩 부분을 한번 따라가 보도록 하겠습니다.
glEnable(GL_CULL_FACE); 거의 모든 부분에서 면의 앞면만그리구 뒷면을 그리지 말라는 이 함수를 사용을 하는데 이러면블랜딩효과를 구현을 할 수가 없다. 그래서 제거를 한다. 쉽게 블렌딩은 앞면에 투명도를 주어서 뒷면이 비추게 하기 위한 방
법이기 때문이다.
glEnable(GL_BLEND); //블렌딩 시작
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); //첫번째 인자인 GL_SRC_ALPHA는 원본 픽셀에 대한 블랜딩 계수를 계산하는 방식인데 원본칼라를 원본 알파값으로 곱하는 것이다. 그리고 두번째 인자는 대상 픽셀에 대한 블랜딩 계수를 계산하는 방식인데 대상 칼라에 (1-원본 알파값)을 곱한다. 최종적으로 이렇게 처리된 두 픽셀값이 합해져서 최종 픽셀값으로 처리되어 화면상에 나타나게 된다.
다음으로 안개에 대해서 알아 보자.
OpenGL에서 제공하는 안개의 종류는 3가지가 있는데 사실 2가지로 구분된다. 하나는 GL_EXP와 GL_EXP2이고 다른 하나는 GL_LINEAR이다. GL_EXP와 GL_EXP2는 밀도라는 값을 이용해서 화면 전체에 지정된 안개의 색으로 마치 자욱한 연기 안에 물체들이 놓여있는 듯한 효과를 낸다. GL_LINEAR은 안개가 이제 막 시작되는 깊이와 안개가 완전하게 들이워져서 더 이상 물체가 보이지 않을 깊이를 지정함으로써 사실 가장 실제적인 안개 효과를 연출할 수 있다.GLfloat fogColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };//먼저 안개 색 지정
glClearColor(1.0f, 1.0f, 1.0f, 1.0f); //배경색도 안개색에 맞춘다.
다음에 initGL에서 추가해야할 것은 안개에 대한 설정값들이다. 다음의 코드를 추가하자.
glFogi(GL_FOG_MODE, GL_LINEAR); //
glFogfv(GL_FOG_COLOR, fogColor); // LINEAR모드의 안개 색 지정
glFogf(GL_FOG_DENSITY, 0.3f); // 안개의 밀도 지정.GL_EXP,GL_EXP2만 사용
glHint(GL_FOG_HINT, GL_NICEST); // 멋있게 해달라구 요구
glFogf(GL_FOG_START, 5.5f); // LINEAR에서만 적용 안개가 보이는 Z값
glFogf(GL_FOG_END, 7.0f); // 안개가 들이워져서 물체가 보이지 않는 Z값
glEnable(GL_FOG); // 안개 사용
다음으로 OpenGL의 버퍼에 대해서 알아 보자.
버퍼라구 하면 보통 데이터를 임시적으로 담아 놓는 저장소이다. 그러면 일반적으로 버퍼라는 개념과 OpenGL에서
사용되는 버퍼의 개념에 대해서 틀린점을 알아 보자.
OpenGL에서 버퍼라구 하면 일반적으로 색상 버퍼를 뜻하는 경우가 많다. 하지만 보통 색상버퍼는 화면에 나타날
픽셀들의 색상들을 담는 저장소이다. OpenGL에서는 색상버퍼는 윈도우의 각 픽셀에 대한 RGBA또는 색상 색인 값
을 담는다. 쉽게 OpenGL에서는 버퍼는 아래의 버퍼들을 더 가지는 함축적인 집합을 의미한다.
프레임 버퍼는 시스템의 모든 버퍼들을 통칭한다. 하지만 OpenGL에서는 프레임 버퍼는 색상버퍼,깊이버퍼(z 버퍼
라고도 한다.),스텐실 버퍼,누적 버퍼로 구성된다. 그러므로 이 모든 버퍼들을 효과적으로 관리를 하고 프로그래밍
해야 랜더링 속도를 향상 시킬 수가 있다 .
앞의 두 버퍼는 그런대로 함수나 일반적인 방법을 위에서 모두 다룬 편이다. 하지만 스텐실 버퍼와 누적 버퍼에 관해
서는 다루지 못한편이다. 그러면 이제부터 이 부분에 대해서 다루어 보도록 하겠다.
우선 스텐실 버퍼는 z버퍼(깊이 버퍼)와 유사한 점이 많이 있지만 z버퍼로는 불가능한 효과를 구현을 한다.
예를 들어서 창문을 통해서 집의 내부를 볼 수 있다는 것과 비행 시뮬레이션 프로그램에서는 스텐실 버퍼를 이용하
여 비행기 조정석의 풍속계나 지평선 표시기와 같은 계기판 내부에 대해 드로잉 연산을 제한할 수 있다.
하지만 가장 흥미로운 예는 그림자이다. 그래픽 카드에 따라 다르겠지만 다중 광원에 의한 짙고 옅은 그림자를 생성
할 수 있다.
PIXELFORMATDESCRIPTOR의 cStencilBits = 2;를 하면 2비트의 스텐실 버퍼가 생기는 것이다.
glEnable(GL_STENCIL_TEST); //스텐실 테스트를 활성화 한다.
glStencilFunc(GL_ALWAYS, 1, 1); //스텐실 테스트는 항상 통과시키구 참조값은 1이구 마스크는 1로 설정
glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP); //스텐실 버퍼에 적용되는 방식지정
//1.스텐실 테스트를 통과하지 못했을 때 적용될 작용을 의미
//2.스텐실 테스트를 통과했으나 깊이 테스트는 통과하지 못했을 때 적용될 작용 의미
//3.스텐실 ,깊이 테스트를 모두 통과했을 때 적용될 작용 의미.
마지막으로 누적 버퍼는 하나의 버퍼에 장면들을 여러번 랜더링한다 그러면 각 랜더링에서 생긴 픽셀들이 누적 버퍼
에 누적된다. 그런 다음 누적 버퍼의 내용을 색상 버퍼로 옮겨서 화면에 나타나도록 하는데,이러한 과정을 통해 모션
블러(움직이는데 모양이 떨리는거 같은 효과 쉽게 호랑이가 달려가는데 다리가 여러개가 약하게 보이는 효과)나
필드의 깊이 효과, 장면 전체의 안티얼라이이싱,부드러운 그림자 등의 효과를 얻을 수 있다.
이 부분은 하드웨어 지원이 일반화 되지 않는 한 게임에서 사용하는 건 힘든일이라구한다. 어쩌면 지금은 그렇지 않
는 줄도 잘 모르지만....
이상으로 그냥 기본적인 방법들에 대한 내용을 마무리 하려구 한다. 다음으로 알아 두어야 하는 개념이 복잡한 폴리
곤을 그리는 방법과 곡선과 곡면을 그리는 방법,Selection인데 이건 어떤 특정 픽셀을 클릭하면 무슨 효과가 나게
하는 것이다. 이 정도면 어떤 복잡한 알고리즘을 가지고 있는 특수효과라두 구현을 하는데는 문제가 없을 것으로 본
다.