OpenGL이란?
오픈GL은 3D 그래픽인 3차원 그래픽 응용프로그램을 만들기 위한 API입니다.
그렇다면 오픈GL로 무엇을 할 수 있는가? 점, 선, 면 등과 같은 3차원 요소와 비트맵 등의 2차원 요소의 표현, 변환 행렬을 통한 이들 요소의 변형, RGBA모델과 INDEXED COLOR모델에 의한 색상의 지원, 다양한 조명과 쉐이딩의 설정, 텍스처 매핑 등 오픈GL은 3차원 그래픽 라이브러리에 기대할 수 있는 거의 모든 것을 지원합니다.
뿐만 아니라 앤티앨리어싱(Antialiasing), 안개효과, 블렌딩(Blending), 더블 버퍼링, 모션블러(Motion Blur), NURBS (Non-Uniform Rational B-Spline) 등의 고급기능도 구현되어 있습니다. 정리해 보면, 오픈GL은 3차원 그래픽 애플리케이션을 제작하기 위한 거의 모든 기능을 망라하고 있는 고속의 3차원 그래픽 라이브러리라 말할 수 있습니다.
OpenGL의 특징
오픈GL의 여러 가지 특징 중 프로그래밍을 위해 알아두어야 할 중요한 것을 열거해 보면 다음과 같습니다.
오픈GL은 윈도 제어와 관련된 기능을 포함하지 않습니다.
오픈GL의 장점으로 플랫폼에 독립적이라는 것을 강조했었는데 이것은 오픈GL이 윈도 시스템에 따라 각기 다른 윈도 제어 관련기능을 제공하지 않기 때문에 가능합니다.
즉 오픈GL은 장면을 기술하고 렌더링하기 위한 함수만을 제공할 뿐이지 윈도를 생성하고 윈도 크기를 조절하는 등의 기능은 없습니다.
그렇다면 오픈GL로 윈도를 사용하는 애플리케이션을 개발할 수 없다는 말인가? 윈도 생성/파괴, 위치/크기조정, 사용자 입력처리 등의 윈도를 제어하는 함수는 오픈GL 자체에 정의되어 있지 않지만 각 윈도 시스템마다 존재하는 오픈GL 확장 함수가 그것을 가능하게 해줍니다.
오픈GL은 객체지향적이지 않습니다.
오픈GL은 요즘에 나오고 있는 다른 라이브러리와는 달리 객체지향적이지 않습니다.
비슷한 기능을 하는 마이크로소프트 사의 다이렉트 3D가 COM(Component Object Model)에 기반을 둔 전형적인 객체지향 API인 것을 생각해 보면 오픈GL이 다소 뒤떨어진 것으로 보일지도 모릅니다.
하지만 비객체지향적 특징은 호환성이나 이식성을 높이고 C++같은 언어를 모르는 사람도 쉽게 이해할 수 있도록 해주기 때문에 한편으로는 장점일 수도 있습니다.
오픈GL은 클라이언트/서버 구조입니다.
오픈GL은 클라이언트/서버 구조로 설계되어있습니다.
즉 3차원 렌더링을 원하는 프로그램(클라이언트)이 오픈GL(서버)에게 어떤 요청을 하면 서버가 그 요청에 대한 응답을 하는 방식으로 오픈GL이 작동한다는 것이지요.
이 같은 특성을 잘 이용하면 CPU사용이 많이 필요한 3차원 그래픽 애플리케이션을 분산처리하여 성능을 향상시키는 것이 가능할 수 있습니다.
오픈GL은 State Machine 구조입니다.
오픈GL는 State Machine 구조로 이루어져 있습니다.
우리가 내리는 명령 중 많은 부분은 오픈GL의 상태를 변경하거나 읽어들이는 명령입니다.
이러한 명령들이 수행될 때마다 오픈GL은 내부적으로 자신의 상태를 변경, 저장하게 되고, 이러한 상태는 오픈GL이 렌더링된 장면을 만들어낼때 사용되게 됩니다.
오픈GL의 상태에는 여러 가지 종류가 있지만, 일단 Enabled/Disabled의 두 가지 값만을 가지는 상태들에 대해 알아보면 이들 값을 변경하는 기본적인 함수는 glEnable과 glDisable이고 현재의 상태를 읽어들이는 기능을 하는 기본함수는 glIsEnabled입니다.
void glEnable( GLenum cap );
void glDisable( GLenum cap );
GLboolean glIsEnabled( GLenum cap );
함수의 프로토타입에 나오는 cap은 오픈GL capabil ity를 나타내는 상징으로, 오픈GL 헤더 파일에 매크로로 정의되어 있는데 그 중 많이 쓰이는 몇 가지를 나열해 보면 표 1과 같습니다.
===============================================================
매크로 기능
—————————————————————
GL_COLOR_MATERIAL 현재의 색을 물체의 색으로 지정
GL_LIGHTING 조명을 사용
GL_LIGHT? ?번째의 조명을 작동시킴
GL_NORMALIZE 계산 전에 모든 벡터를 단위벡터(크기가 1인벡터)로 바꿈
GL_TEXTURE_1D 1차원 텍스처를 사용
GL_TEXTURE_2D 2차원 텍스처를 사용
GL_DEPTH_TEST 깊이 버퍼를 사용(은선/은면 제거)
GL_CULL_FACE 속도를 위해 뒷면을 렌더링하지 않도록 함
GL_LINE_SMOOTH 선을 앤티앨리어싱함
GL_LINE_STIPPLE 비트패턴으로 선을 그림
GL_POLYGON_SMOOTH 폴리곤을 그릴 때 앤티앨리어싱을 사용
GL_POLYGON_STIPPL 폴리곤을 비트패턴으로 채움
===============================================================
표 1 : 오픈GL의 상태관련 매크로
프로그래머가 오픈GL 렌더링에 조명을 사용하기 원한다면 glEnable(GL_LIGHTING)을 코드에 써넣으면 되고, 반대로 glDisable(GL_LIGHTING)을 명령하면 조명에 관련된 다른 설정과 무관하게 조명은 꺼지게 됩니다.
glEnable(GL_LINE_SMOOTH) 선을 앤티앨리어싱되게 만들고, glEnable(GL_TEXTURE_2D) 2차원 텍스처 매핑이 작동되도록 하는 명령입니다.
또 선을 점선으로 만들고 싶다면, glLineStipple로 점선의 비트 패턴을 지정한 후 glEnable(GL_LINE_STIPPLE)을 명령하면 됩니다.
표 1에 열거된 상태 중 재미있는 것은 GL_CULL_FACE라는 상태인데, GL_CULL_FACE가 enable되면 오픈GL은 면의 한쪽만을 렌더링하게 합니다.
예를 들어 상자밖에서 상자를 바라보면 상자가 보이지만, 상자 안에서 밖을 바라볼 때는 마치 상자가 없는 것처럼 보이게 된다는 것입니다.
오픈GL 상태의 종류는 이처럼 매우 다양하여 상태를 통해 하게 되는 일은 매우 많습니다.
오픈GL은 절차적입니다.
오픈GL을 사용하는 프로그래머는 장면을 절차적으로 기술해야 합니다.
예를 들어 “노란 상자 위에 파란 철사로 된 주전자가 놓여있다”라는 장면을 오픈GL로 렌더링하기 위해서는 다음과 같은 절차를 거쳐야 합니다.
단, 이 기술은 조명이나 쉐이딩 등의 기타 요소는 고려하지 않은 것이며 대괄호 안은 각단계를 오픈GL함수에 맞춰 의사코드(Pseudo-code)로 적은 것입니다.
현재 색상을 노랑색으로 바꾼다. [ glColor3f(1.0f,1.0f,0.0f);]
상자를 그린다. [auxSolidBox(100.0f,100.0f,10.0f);]
현재 위치를 정육면체 위로 이동한다. [glTranslatef(0.0f,0.0f,25.0f);]
현재 색상을 파랑색으로 바꾼다. [glColor3f(0.0f,0.0f,1.0f);]
주전자를 그린다. [auxWireTeapot(20.0f);]
이 코드를 유심히 살펴본다면, 오픈GL의 절차적 장면기술이 위에서 이야기했던 오픈GL상태와 밀접한 관계를 가지고 있음을 알 수 있을 것입니다.
프로그래머가 장면을 기술하기 위해서는 상태를 설정하고 어떤 동작을 하고 또 다시 상태를 변경하는 연속적인 절차를 반복하게 됩니다.
오픈GL로 프로그래밍할 때, 명령을 내리는 순서가 중요한 것은 이런 오픈GL의 절차적 특성 때문입니다.
OpenGL의 구성
오픈GL은 크게 세개의 라이브러리로 구성됩니다.
Gl, Glu, Glaux가 바로 그것인데, 각 라이브러리의 기능과 특징은 표 2와 같습니다.
===============================================================
이 름 함수 접두어 헤더파일 DLL이름 기 능
—————————————————————
OpenGL(Gl) gl gl.h opengl32.dll 오픈GL의 메인라이브러리.
Utility(Glu) glu glu.h glu32.dll 오픈GL 사용을 간편하게 해주는 유틸리티.
Aux(Glaux) aux glaux.h DLL없음 간단한 오픈GL 애플리케이션을 작성하기 위한 함수를 모아놓은 툴킷 라이브러리.
===============================================================
표 2 : 오픈GL의 구성
OpenGL(Gl)
OpenGL(혹은 Gl)라이브러리는 오픈GL의 핵심이라고 할 수 있는 것으로 오픈 GL을 제어하는 기본적인 함수들의 집합체입니다.
Gl 라이브러리 함수들은 주로 오픈GL의 렌더링과 관련된 기능들을 제공합니다.
Gl의 헤더 파일인 gl.h에는 오픈GL의 기본적인 타입정의와 매크로정의 그리고 각 함수들의 프로토타입이 나열되어 있습니다.
Utility(Glu)
오픈GL의 핵심 라이브러리인 Gl이 렌더링과 관련된 기초 기능만을 위주로 하기 때문에 실제 응용프로그램을 만들기 위해서는 많은 반복작업을 해야만 합니다.
이러한 작업을 단순화시켜 주고 그 밖의 유용한 기능을 제공해 주는 라이브러리가 바로 Glu입니다다.
Glu의 중요한 기능을 살펴보면,
투사 행렬 설정 기능
3차원 좌표에 그려진 물체들을 2차원 윈도(뷰포트) 위에 그리기 위해서는 적절한 변환과정을 거쳐야만 하는데, 오픈GL에서는 3차원 좌표값을 프로젝션 매트릭스(투사행렬)라는 4×4크기의 행렬에 곱하는 방법으로 변환을 합니다.
그렇다면 우리가 원하는 시선에 맞는 프로젝션 매트릭스를 어떻게 구할 것인가가 문제가 되는데, 변환과 행렬 계산에 아주 능통한 사람이라면 직접 행렬을 계산해서 glLoadMatrix를 이용해 행렬을 설정할 수도 있겠지만 이 방법은 별로 바람직하지 않습니다.
Gl은 프로젝션 매트릭스를 설정하기 위한 두개의 함수를 제공합니다.
void glOrtho(GLdouble left, GLdouble right, GLdouble bottom,GLdouble top, GLdouble near, GLdouble far);
void glFrustum(GLdouble left, GLdouble right,GLdouble bottom, GLdouble top, GLdouble znear, GLdouble zfar);
glOrtho함수로 프로젝션 매트릭스를 설정하면 멀리있는 물체나 가까이 있는 물체나 같은 크기로 보이는 정사영(한 점에서 한 직선 또는 한 평면 위에 그은 수선의 발)이 화면에 그려지고, glFrustum함수로 행렬을 설정하면 원근감을 가지는 장면이 연출됩니다.
Gl함수로도 원하는 장면을 위한 투사 행렬을 얻을 수 있지만 Glu에 포함된 함수들을 이용하면 훨씬 더 쉽게 다양한 투사행렬을 구할 수 있습니다.
gluOrtho2D함수는 glOrtho와 거의 비슷한 함수인데, glOrtho의 near와 far를 각각 -1.0, 1.0으로 정한 것입니다.
glu Perspective는 glFrustum과 비슷하지만 상하좌우의 값을 설정하는 대신에 y방향의 시선각도인 fovy(FOV는 Field Of View의 약자)와 종횡비인 aspect를 이용한다는 점이 다릅니다.
void gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top );
void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar );
void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery,
GLdouble centerz,GLdouble upx, GLdouble upy, GLdouble upz);
gluLookAt은 3차원 그래픽 도구들에서 볼 수 있는 카메라와 매우 흡사한 것인데 관찰자의 위치(eye)와 바라보고자 하는 위치(center) 그리고 관찰자의 윗쪽 방향(up)을 지정하여 시선을 설정하는 기능을 합니다.
텍스처 매핑을 위한 비트맵의 관리
텍스처를 폴리곤에 매핑하는 것은 메인라이브러리인 Gl의 몫이지만 매핑 소스로 쓰이는 이미지를 관리하는 것은 프로그래머의 몫입니다.
따라서 프로그래머는 매핑 소스로 쓰일 비트맵 파일을 읽어들이고, 적당히 변형을 하고 사용이 끝난 비트맵을 정리하는 등의 과정을 직접 처리해야 합니다.
이러한 일을 처리하는 것은 상당히 귀찮은 작업인데 Glu라이브러리 함수를 사용하면 단순화될 수 있습니다.
텍스처 매핑과 관련된 중요한 함수는 다음과 같은 것이 있습니다.
int gluBuild1DMipmaps(….);
int gluBuild2DMipmaps(….);
int gluScaleImage(….);
이 중 gluScaleImage함수는 이미지를 적절한 크기로 바꾸어주는 함수이고 gluBuild1/2DMipmaps함수는 glu Scale Image함수를 이용해 Mipmaps를 만들어주는 함수입니다.
Mipmaps란 하나의 면을 상황에 따라서 각기 다른 텍스처로 사용하는 것을 말하는데 즉, 면이 관찰자로부터 멀리있을 때는 저해상도의 텍스처를 사용하여 렌더링 속도를 높이고, 가까이 있을 때는 고해상도의 텍스처를 사용하여 렌더링질을 높이는 것이 Mipmapping 입니다.
gluBuild1/2D Mipmaps함수는 Mipmapping에 쓰이는 여러 해상도의 매핑소스를 자동으로 만들어주는 함수입니다.
Polygon Tessellation
Polygon Tessellation이란 오픈GL이 렌더링할 수 없는 폴리곤을 렌더링이 가능한 폴리곤들로 바꾸는 작업입니다.
렌더링이 불가능한 폴리곤이란 오목하거나 속이 빈 폴리곤 혹은 선분이 교차하는 폴리곤을 말합니다.
이와 같은 폴리곤을 장면에 그리기 위해서는 Tessellation을 통해 하나의 폴리곤을 여러 개로 나누어야만 하는데, 즉 오목 사각형을 표현하기 위해서 두개의 볼록 삼각형을 이용하는 것이 바로 Tessellation의 한예라고 할 수 있겠습니다.
프로그래머는 렌더링이 불가능한 폴리곤이 나올 것 같은 장면 이전에 미리 Tessellation과 관련된 속성을 설정해 놓습니다.
그리고 꼭지점을 지정할 때 glVertex함수 대신에 gluTessVertex를 이용하면 자동으로 렌더링 가능한 폴리곤으로 바뀌면서 렌더링이 수행되게 됩니다.
구, 실린더, 디스크 등의 오브직트 생성
Gl 라이브러리만을 이용해서 구, 실린더, 디스크 등의 오브직트를 생성하는 것은 매우 귀찮은 작업입니다.
프로그래머는 이들 오브직트들의 꼭지점을 일일이 계산하여 각 점을 glVertex함수로 하나하나 찍어주어야 되나 Glu함수들을 이용하면 이러한 단순작업을 간단히 할 수 있습니다.
Glu에 정의되어 있는 GLUquadricObj라는 구조체의 포인터를 만든 후 실제 렌더링을 하는 부분에서 포인터를 이용하여 원하는 오브직트를 생성하는 함수를 호출하면 꼭지점을 하나도 찍지 않고 원하는 오브직트를 만들 수 있습니다.
리스트 1을 보면 객체를 만들고 지우는 과정이, C++의 new/delete 연산과 비슷하다는 것을 알 수 있을 것입니다.
리스트 1 : Glu로 구만들기 예제
// 초기화 부분
GLUquadricObj *pObj;
// Quadric Object를 만들기 위한 구조체 포인터
pObj = gluNewQuadric();
// Object를 생성(아직 오브직트가 그려지지 않음)
// 장면 기술부분
gluSphere(pObj , 30.0 , 10 , 10);
// 반지름 30, 위선과 경선 수가 10인 구를 생성
// 정리 부분
gluDeleteQuadric(pObj); // 객체를 삭제
리스트 1에서 gluSphere만 다른 함수로 바꾸면 얼마든지 다른 객체를 만드는 것이 가능하다.
.void gluDisk(GLUquadricObj *qobj,
GLdouble innerRadius,GLdouble outerRadius,
GLint slices,GLint loops); <그림 5>
void gluCylinder(GLUquadricObj * qobj,
GLdouble baseRadius, GLdouble topRadius,
GLdouble height, GLint slices, GLint stacks);<그림 6>
void gluSphere(GLUquadricObj * qobj,
GLdouble radius, GLint slices,
GLint stacks); <그림 7>
기타 기능
오픈GL 렌더링중 발생하는 에러에 대한 정보는 glGet Error함수를 통해 얻을 수 있지만 이 정보는 정수값이기 때문에 사용자에게 에러의 원인을 알리기 위해서는 프로그래머가 직접 에러메시지를 출력해야 합니다.
gluErrorString함수는 glGetError에서 얻은 에러코드를 에러문자열로 바꾸어주는 기능을 하는 함수로 예외처리에서 프로그래머의 노력을 덜어줍니다.
뿐만 아니라 gluGetString을 이용하면 오픈GL의 버전이나 작동가능한 Glu 확장기능 등을 얻어내는 일도 할 수 있습니다.
Aux(Glaux)
OpenGL과 wgl함수를 이용해 선을 하나 렌더링하는 프로그램을 작성하는 것은 API로 HelloWin을 출력하는 프로그램을 만드는 것보다도 훨씬 긴 코드를 필요로 합니다.
Aux라이브러리는 플랫폼에 독립적인 윈도 제어기능과 Glu를 능가하는 물체 생성기능을 가지는 툴킷라이브러리입니다.
Aux라이브러리를 이용하여 프로그램을 만들면 wgl이나 glx 등의 윈도 제어함수들을 모르더라도 쉽게 오픈GL프로그램을 작성할 수 있기 때문에 OpenGL프로그램을 처음 작성하는 사람도 쉽게 OpenGL을 배울 수 있게 해줄 수 있습니다.
뿐만 아니라 플랫폼마다 다른 윈도 제어함수를 사용하지 않기 때문에 한줄도 고치지 않은 상태에서 다른 환경으로 이식하는 것이 가능합니다.
Aux 라이브러리의 중요기능은 윈도생성, 입력제어, 3차원 물체 생성, 더블버퍼링, 텍스처 매핑용 이미지의 로딩 등이 있습니다.
윈도생성
void APIENTRY auxInitDisplayMode(GLenum);
void APIENTRY auxInitPosition(int, int, int, int);
GLenum APIENTRY auxInitWindow(LPCTSTR);
먼저 auxInitDisplayMode로 적당한 윈도 모드를 설정한 후, auxInitPosition으로 윈도의 초기 위치와 크기를 지정하고, auxInitWindow로 윈도 이름을 지정하기만 하면 오픈GL로 렌더링이 가능한 3차원 윈도가 생성됩니다.
후에 wgl함수로 윈도를 띄워보면 알겠지만, PFD(Pixel Format Descriptor)를 작성하고 렌더링 컨텍스트(RC)를 관리하여 wgl윈도를 띄우는 것은 상당히 귀찮은 작업입니다.
입력제어
Aux라이브러리로 만든 프로그램에서 입력을 처리하기 위해서는 미리 입력을 처리할 CALLBACK함수를 만들어야 합니다.
그리고 auxKeyFunc나 auxMouseFunc로 이벤트 처리 함수를 등록하면 키입력이 있을 때마다 등록된 함수가 자동으로 호출됩니다.
윈도 프로그래밍의 경험이 있는 사람이라면 Aux라이브러리가 이벤트 처리함수를 호출하는 것이 윈도 프로그램에서 윈도가 윈도 프로시저를 호출하는 것과 비슷하다는 것을 느낄 수 있을 것입니다.
3차원 물체의 생성
Aux라이브러리는 상자모양, 원뿔모양은 물론, 정사면체, 정육면체, 정팔면체, 정십이면체, 정이십면체의 5가지 정다면체 전부와, 토러스, 구, 실린더, 주전자 등을 그리는 기능을 제공합니다.
void APIENTRY auxSolidSphere(GLdouble); // 구
void APIENTRY auxSolidCube(GLdouble); // 정6면체
void APIENTRY auxSolidBox(GLdouble, GLdouble, GLdouble); // 상자
void APIENTRY auxSolidTorus(GLdouble, GLdouble); // 토러스(도넛모양)
void APIENTRY auxSolidCylinder(GLdouble, GLdouble); // 실린더(원통)
void APIENTRY auxSolidIcosahedron(GLdouble); // 정20면체
void APIENTRY auxSolidOctahedron(GLdouble); // 정8면체
void APIENTRY auxSolidTetrahedron(GLdouble); // 정4면체
void APIENTRY auxSolidDodecahedron(GLdouble); // 정12면체
void APIENTRY auxSolidCone(GLdouble, GLdouble); // 원뿔
void APIENTRY auxSolidTeapot(GLdouble); // 주전자
……….
더블 버퍼링과 매핑용 이미지로딩
더블 버퍼링이란 애니메이션의 다음 장면을 화면에 보이지 않는 버퍼에 렌더링하고 완전히 그리기가 끝난 후 버퍼를 한 순간에 화면으로 옮기는 애니메이션기법을 말합니다.
OpenGL은 명령이 내려지면 곧바로 화면에 그림이 그려지는 Immediate 렌더링 모드로 작동하기 때문에 자연스러운 애니메이션을 위해 더블버퍼링은 꼭 필요한 기능입니다.
더블 버퍼링을 wgl로 구현하기 위해서는 디바이스 컨텍스트(DC)와 렌더링 컨텍스트(RC)를 동시에 관리하면서 여러 가지를 처리해 주어야 하지만 Aux라이브러리에서는 매우 단순합니다.
먼저 auxInitDisplayMode함수를 호출할 때 AUX_DOU B LE을 논리합(OR)을 추가하고 실제 버퍼링이 필요할 때 단순히 auxSwapBuffers함수를 호출하기만 하면 됩니다.
텍스처 매핑용 이미지를 읽어들이는 기능도 Aux라이브러리의 중요한 기능입니다.
auxRGBImageLoad함수와 auxDIB ImageLoad함수를 이용하면 파일의 핸들이나 헤더를 전혀 신경쓰지 않고도 ‘.RGB’포맷이나 ‘.DIB(혹은 BMP)’포맷을 쉽게 읽어들일 수 있습니다.
리스트 2 : GLUT 프로그램의 예
#include “GL/glut.h”
static void display()
{
glClear(GL_COLOR_BUFFER_BIT); // 이곳에서 장면을 묘사한다.
glutSwapBuffers();
}
void main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB );
glutCreateWindow(“Glut Test”);
glutDisplayFunc(display);
glutMainLoop();
}
리스트 3 : AUX프로그램의 예
#include “GL/glaux.h”
void CALLBACK display()
{
glClear(GL_COLOR_BUFFER_BIT);
// 이곳에서 장면을 묘사한다.
auxSwapBuffers();
}
void main(void)
{
auxInitDisplayMode(AUX_DOUBLE | AUX_RGB);
auxInitPosition(0,0,400,300);
auxInitWindow(“Aux Test”);
auxMainLoop(display)
}