안녕하세요, OpenGL ES 어플리케이션 Tutorial을 작성해 보려고 합니다.
제가 참고할 교재는 "OpenGL ES 2.0 Programming Guide" 와 API 가 정리된 라이브러리 페이지 정도가 되겠습니다.
구현은 windows 기반의 Emulator인 Imagination Technologys사에서 제공하는 환경을 기반으로 합니다.
(Imagination Technologys는 많은 유틸리티의 제공으로 AMD Emulator 보다 source 구현및 해석이 복잡한 단점이 있으나, 컴파일이 잘되고 다양한 Tutorial 예제가 제공됩니다.)
Traning Course 의 소스를 참고하며 보시기 바랍니다.
OpenGL ES 2.0 어플리케이션은 libGLESv2, libEGL 라이브러가 필요합니다.
현재로선 각각의 Emulator에서 제공되는 라이브러리를 사용하시면 됩니다.
이번 주제는 OpenGL ES의 초기화와 화면 구동 정도입니다.
다음은 include 파일입니다. 이번 tutorial에선 initialize가 목적이므로 다음의 헤더만 추가합니다.
1#include <EGL/egl.h>
2#include <GLES2/gl2.h>
OpenGL ES는 3D 그래픽스를 처리하는 OpenGL ES 함수들과 시스템에 종속적인 부분을 처리하는 EGL 및 시스템 함수들로 구성됩니다.
EGL은 네이티브 플랫폼 인터페이스(Native Platform Interface)로 특정 플랫폼 시스템과 OpenGL ES API 사이의 glue 인터페이스 레이어 함수를 정의합니다.
즉, 특정 위도우잉 시스템과의 인터페이스를 제공하고 디스플레이 연결을 위해 EGLDisplay를 제공합니다.
EGL 값들을 선언해 봅시다.
1 EGLDisplay eglDisplay = 0;
2 EGLConfig eglConfig = 0;
3 EGLSurface eglSurface = 0;
4 EGLContext eglContext = 0;
5 EGLNativeWindowType eglWindow = 0;
EGLConfig는 EGL에 의해 생성되는 평면에 대한 정보를 포함합니다.
평면에 사용할 색의 구성과 수에 관련된 깊이버퍼, 스텐실 버퍼, 평면 타입이나 기타등등의 정보등입니다.
EGLSurface는 eglCreateWindowSurface()를 통해 만들어지는 윈도우를 가리킵니다.
EGLContext는 연산을 위해 필요한 내용들을 포함하는 OpenGL ES 2.0에 대한 데이터 문맥입니다.
이들 문맥은 정점 데이터 배열이나 vertex 및 fragment 쉐이더에 대한 참조를 포함할 수 있습니다.
EGLNativeWindowType은 EGLSurface와 함께 윈도우를 만들때 사용되는 윈도우 핸들을 가리킵니다.
현재 Emulator의 윈도윙 방식이 Windows API를 따르므로 HWND를 얻어와 eglWindow에 연결합니다.
1hWnd = CreateWindow( WINDOW_CLASS, _T("HelloTriangle"), WS_VISIBLE | WS_SYSMENU,
2 0, 0, nWidth, nHeight, NULL, NULL, hInstance, NULL);
3 eglWindow = hWnd;
4hDC = GetDC(hWnd);
5eglDisplay = eglGetDisplay(hDC);
eglDisplay 는 hDC의 Display가 되겠군요.
다음으로, EGLConfig 속성을 정합니다.
1const EGLint pi32ConfigAttribs[] =
2 ...{
3 EGL_LEVEL, 0,
4 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
5 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
6 EGL_NATIVE_RENDERABLE, EGL_FALSE,
7 EGL_DEPTH_SIZE, EGL_DONT_CARE,
8 EGL_NONE
9 };
10int iConfigs;
11eglChooseConfig(eglDisplay, pi32ConfigAttribs, &eglConfig, 1, &iConfigs) ;
pi32ConfigAttribs 는 속성 list를 나타냅니다.
EGLConfig의 속성들은 너무 많아 전부 나열하긴 힘들고..위에서 쓰인 것들만 보도록 하죠.
EGL_LEVEL 은 프레임 버퍼 레벨을 말합니다. 기본 0 입니다.
EGL_SURFACE_TYPE은 지원되는 EGL 평면의 타입을 말합니다. 여기서는 EGL_WINDOW_BIT (4)를 할당했습니다.
EGL_RENDERABLE_TYPE은 config로 지원되는 렌더링 인터페이스를 말합니다. EGL_OPENGL_ES2_BITf (4)를 할당했습니다. EGL_DEPTH_SIZE는 깊이 버퍼의 비트 수 입니다. EGL_DONT_CARE는 -1로 사용하지 않음 입니다.
eglChooseConfig는 pi32ConfigAttrib의 속성과 일치하는 EGLConfig를 선택합니다. maxReturnConfigs는 1이고 numConfig를 iConfigs변수로 확인합니다. (예외처리에 사용될 수 있습니다.)
1eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, eglWindow, NULL);
2EGLint ai32ContextAttribs[] = ...{ EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
3 eglContext = eglCreateContext(eglDisplay, eglConfig, NULL, ai32ContextAttribs);
4eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
eglCreateWindowSurface 는 윈도우 생성을 위한 함수로 EGLSurface 형태의 윈도우를 반환 합니다.
반환 값을 통하여 에러 원인등을 측정할 수도 있으며, 이제 렌더링 평면이 생성된 것 입니다.
이 윈도우를 내부 렌더링 평면이라고 칭합니다.
마지막 NULL 인자는 속성 목록입니다만, 위의 Attribs 변수와는 다른 단일 값이 들어갑니다. (EGL_RENDER_BUFFER)
보통 OpenVG등 다른 렌더와의 병행을 생각하여 전방 버퍼 및 후방버퍼 등 렌더링 버퍼를 지정하는데 사용됩니다.
현재는 기본 값인 거죠.
다음으로 eglCreateContext 는 위에서 설명한 렌더링 문맥 EGLContext를 생성할 때 사용됩니다.
이 또한 디스플레이 연결을 해야하며, 세번째 NULL 인자는 쉐이더 프로그램과 텍스쳐 맵들과 같은 특정 데이터 타입을
공유하기 위한 shareContext로 EGLContext 형입니다. 마지막에 넣어주는 속성은 단일 값으로 (EGL_CONTEXT_CLIENT_VERSION) 1.X 나 2.X등의 버젼 정보입니다.
eglCreateContext 는 성공 시 렌더링 문맥(EGLContext)의 핸들을 반환합니다. 또 반환 값으로 에러코드를 볼 수도 있습니다.
이제 eglMakeCurrent로 EGLContext와 렌더링 평면 EGLSurface를 연결 합니다.
EGL은 여기까지 입니다. 윈도우의 생성과 문맥 형성이 끝났습니다!!
생성된 렌더링 평면 윈도우에 그리는 과정을 넣어 OpenGL ES의 간단한 Render를 완성 시켜 보겠습니다.
IT사의 Trainning Course 02에 나와있는 삼각형 그리기로 해 볼까요,
OpenGL ES 2.0은 쉐이더 기반의 프로그램이며 버텍스 및 프래그먼트 쉐이더를 통해 메소드를 구현합니다.
쉐이더에 대한 내용은 다음 시간에 더 공부해보도록 하고 오늘은 간단한 삼각형을 그리기 위한 쉐이더만 사용해 보겠습니다.
먼저 다음과 같은 Vertex및 Fragment 쉐이더 소스 코드를 준비합니다.
1 char* pszFragShader = "\
2 void main (void)\
3 {\
4 gl_FragColor = vec4(1.0, 1.0, 0.66 ,1.0);\
5 }";
6 char* pszVertShader = "\
7 attribute highp vec4 myVertex;\
8 uniform mediump mat4 myPMVMatrix;\
9 void main(void)\
10 {\
11 gl_Position = myPMVMatrix * myVertex;\
12 }";
다음으로 쉐이더 객체를 생성하고 소스코드를 로드하여 연결 합니다.
1// Create the fragment shader
2GLuint uiFragShader = glCreateShader(GL_FRAGMENT_SHADER);
3glShaderSource(uiFragShader, 1, (const char**)&pszFragShader, NULL);
4glCompileShader(uiFragShader);
5// Create the vertex shader
6GLuint uiVertShader = glCreateShader(GL_VERTEX_SHADER);
7glShaderSource(uiVertShader, 1, (const char**)&pszVertShader, NULL);
8glCompileShader(uiVertShader);
glCreateShader를 사용하여 생성하고 glShaderSource로 소스 적재, glCompileShader함수로 컴파일 합니다.
다음은 fragment Shader의 컴파일 상태를 점검해보는 코드입니다.
1GLuint uiProgramObject; /* Used to hold the program handle (made out of the two previous shaders */
2GLint bShaderCompiled;
3glGetShaderiv(uiFragShader, GL_COMPILE_STATUS, &bShaderCompiled);
glGetShaderiv함수에 GL_COMPILE_STATUS를 확인한 결과인 bShaderCompiled 값이 없다면, 컴파일 오류들이 정보 로그에 기록됩니다.
이 때, glGetShaderiv함수에 다시 GL_INFO_LOG_LENGTH를 넣어 오류를 확인해 보는 것이 대부분의 경우입니다.
Vertex Shader의 경우도 같은 확인을 해 주도록 합니다.
쉐이더의 생성이 끝나면 프로그램 객체를 초기화 하도록 합니다.
프로그램 객체는 쉐이더를 첨부한 최종적인 실행 가능 프로그램을 링크하는 컨테이너 객체입니다.
먼저 프로그램 객체를 생성합니다.
1// Create the shader program
2GLuint uiProgramObject = glCreateProgram();
3
4glAttachShader(uiProgramObject, uiFragShader);
5glAttachShader(uiProgramObject, uiVertShader);
6glBindAttribLocation(uiProgramObject, VERTEX_ARRAY, "myVertex");
7
8// Link the program
9glLinkProgram(uiProgramObject);
glCreateProgram으로 생성, glAttachShader로 프로그램 객체에 쉐이더 첨부(Frag 및 Vertex) , glBindAttribLocation 함수를 통해 myVertex의 속성을 VERTEX_ARRAY로 할당해 주고 있으며, glLinkProgram을 통해 링크합니다.
1glGetProgramiv(uiProgramObject, GL_LINK_STATUS, &bLinked);
2glUseProgram(uiProgramObject);
glGetProgramiv 함수를 통해 glGetShaderiv로 쉐이더를 검사한 것 처럼 프로그램 링크의 성공 여부를 확인합니다.
glUseProgram은 링크된 프로그램 객체를 활성 프로그램으로 사용 하도록 합니다.
이제, 삼각형을 그려볼까요 ㅋㅋ
1GLfloat afVertices[] = ...{ -0.4f,-0.4f,0.0f, // Position
2 0.4f ,-0.4f,0.0f,
3 0.0f ,0.4f ,0.0f};
4GLuint ui32Vbo;
5// Generate and Bind the vertex buffer object (VBO)
6glGenBuffers(1, &ui32Vbo);
7glBindBuffer(GL_ARRAY_BUFFER, ui32Vbo);
8
9unsigned int uiSize = 3 * (sizeof(GLfloat) * 3);
10glBufferData(GL_ARRAY_BUFFER, uiSize, afVertices, GL_STATIC_DRAW);
11
12// Frame 호출로 들어가서
13glClear(GL_COLOR_BUFFER_BIT);
14
15// First gets the location of that variable in the shader using its name
16 int i32Location = glGetUniformLocation(uiProgramObject, "myPMVMatrix");
17glUniformMatrix4fv( i32Location, 1, GL_FALSE, pfIdentity);
18
19glEnableVertexAttribArray(VERTEX_ARRAY);
20glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, 0, 0);
21
22glDrawArrays(GL_TRIANGLES, 0, 3);
23eglSwapBuffers(eglDisplay, eglSurface);
glGenBuffers와 glBindBuffer는 VBO (Vertex Buffer Objects) 설정입니다.
간단히 말해서 Vertex들의 정보를 담아두는 버퍼에 대한 설정입니다.
(VBO를 사용하는 것은 성능과 연관이 있습니다.
저도 DTV 타겟의 렌더러 구현을 도울 때 VBO 설정을 도입하는 것을 본 적이 있습니다.)
glBufferData 를 통하여 삼각형 Vertex를 담아 놓습니다.
다음으로 삼각형을 렌더링 평면에 그려 봅시다.
glClear() 연산으로 색상 버퍼를 비우고,
glGetUniformLocation으로 프로그램 객체의 myPMVMatrix 핸들을 얻어옵니다.
( uniform은 쉐이더에서 전달 받는 읽기 전용 상수 값들을 저장하는 변수입니다.)
여기서는 uiProgramObject의 myPMVMatrix 를 i32Location으로 얻어오는 군요.
i32Location는 핸들, 즉 uniform의 위치라고 이해해 두시기 바랍니다.
이제 glUniformMatrix4fv를 톹하여 uniform 값을 적재합니다.
(pfIdentity는 단위행렬로 저장시킨 프로젝션 행렬 변수입니다.)
glVertexAttribPointer 로 정점 데이터를 적재합니다.
다음으로 glDrawArrays에서 GL_TRIANGLES 형태로 array를 그려 주는군요.
eglSwapBuffers는 전방 버퍼와 후방 버퍼를 바꾸어 주는 연산입니다.
이중 버퍼링 연산을 해주는 것입니다.
이로써 삼각형 그리기가 완료 됩니다.
다음은 할당된 값들을 해지 하는 루틴입니다. ^^
1// Frees the OpenGL handles for the program and the 2 shaders
2glDeleteProgram(uiProgramObject);
3glDeleteShader(uiFragShader);
4glDeleteShader(uiVertShader);
5
6// Delete the VBO as it is no longer needed
7glDeleteBuffers(1, &ui32Vbo);
8
9eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
10eglTerminate(eglDisplay);
11
12// Release the device context
13if (hDC) ReleaseDC(hWnd, hDC);
14
15// Destroy the eglWindow
16if (hWnd) DestroyWindow(hWnd);
간단한 OpenGL ES 의 초기화와 삼각형 그리기 였습니다.
다음 시간엔 더욱 중요한 쉐이딩 언어를 진행하겠습니다.
Fin.
'OpenGL ES' 카테고리의 다른 글
OpenGL ES 2.0 Emulator 개발 환경 구성 (0) | 2010.11.11 |
---|---|
OpenGL ES 에서의 lookAt 구현 (0) | 2010.10.20 |
OpenGL ES 2.0 Reference Page (0) | 2010.10.15 |
[PPT] OpenGL ES Intro (0) | 2010.10.13 |