상세 컨텐츠

본문 제목

OpenGL 오픈지엘 투영변환 해상도 크기에 그리기,2D, glOrtho, glViewport, gluLookAt 관련

프로그래밍 관련/3D,2D DRAW 관련

by AlrepondTech 2020. 9. 15. 20:05

본문

반응형

 

 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://www.androidpub.com/2163296

그냥 SurfaceView 쓸 때는 픽셀단위로 계산해서 움직였는데

OpenGL은 제가 정해놓은 무한좌표계? 인가 그걸로 한다면서

 

1 -1 -0.5 뭐 이렇게만 되니

아직 적응이 안되서 그런지 무지 감잡기 어렵네요 ㅠㅠ

이 게시물을...

 

엮인글 주소 : http://www.androidpub.com/index.php?document_srl=2163296&act=trackback&key=3c1

 

2012.05.24 09:44:12

위슈

glOrtho 범위를 다르게 주면 안되나요?

아마 지금은 glOrtho(-1, 1, -1, 1, -1, 1) 이런 식으로 쓰실거 같은데

ortho값을 스크린 사이즈로 하시면 픽셀단위로 움직일 수 있을 거 같습니다. 

 

다만 viewport가 스크린 사이즈와 다를 경우에는 조금 생각을 해보셔야할 거 같은데,, 저도 다시 공부해야하는 처지라 정확히 생각이 안나네요-_-;;

어쨌든 현재 선후님께서 겪고 있는 문제는 glOrtho문제인 것 같네요.ㅎ

 

2012.05.24 10:04:58

건방진프로그래머

glOrtho 는 작업공간.. 즉 본인좌표계가 480 x 800 기준으로 레이아웃을 잡았다면

 

viewPort에서는 해당단말기의 좌표계.. 즉 갤럭시노트라면 1280 x 800으로 설정해주시면 

 

알아서 viewPort에 맞게 설정을 해주더군요.. 모든 보이는것을요(아마도 카메라개념이 아닌가 싶습니다만)

2012.05.24 10:05:27

tomblade

뷰포트의 크기는 관계없이 직교좌표계로 정해놓은 크기대로 움직여집니다.

예를들어 -100, 100 -50, 50  으로 직교좌표계를 정하면 2d 좌표계는 화면 정중앙이 0 , 0 으로 맞춰지고 크기는 가로 200, 세로 100의

화면이 만들어집니다. 어떤 단말, 디바이스도 맞춰지죠. 스크린 사이즈가 아닌 선후님 자신이 원하는 크기를 넣으면 됩니다.

즉, 800, 480 으로 맞추고 싶으시다면 -400, 400, -240, 240 으로 화면 정중앙이 원점이 됩니다.

단, openGL의 좌표계는 좌상단이 기준으로 +y 축이 아래로 내려오는 일반적인 좌표계가 아닌 좌하단이 기준으로 윗쪽이 +y축이됩니다.

고로 디바이스의 터치 좌표와는 y축이 바뀌는점과 터치좌표의 경우 디바이스의 해상도를 계승받는다는 점을 가만하시고 작업해야 합니다.

개념상 뷰포트와 관련이 있는건 직교좌표계가 아닌 빌보드라고 봐야 할껍니다.

3D가 아닌 2d를 하실꺼라면 뷰포트는 신경 안쓰셔도 됩니다.

2012.05.24 13:33:21

위슈

2d에서 뷰포트를 신경안써도 된다는건 무슨 말인가요??

2012.05.24 13:56:54

Godwish

좌표 2D 좌표로 사용할 수 있습니다.

 

gl.glViewport(0, 0, 넓이,높이);

gl.glMatrixMode(GL11.GL_PROJECTION);

gl.glLoadIdentity();

gl.glOrthof(-0.0f, (float)넓이,(float)높이, -0.0f , -32768.0f, 32767.0f);

gl.glViewport(0,0,넓이,높이);

 

이렇게 해보세요.

2012.05.24 23:56:11

선후

음.... 모든 분들이 다 고수이시군요 ㅠㅠ

 

아 GL 이해하는게 어렵네요 ㅠㅠ

 

댓글을 보면서 감을 잡으려고 노력하고 있습니다. ㅠㅠ 흑 전 멍청한듯 ㅠㅠ

2012.05.25 21:20:21

redred

익숙해지면 편합니다.

전체 크기를 개발자 맘대로 잡고 그걸 늘렸다 줄였다 하기 편한 구조거든요.

현실 세계의 cm 나 m 를 기준으로 아이템 상자나 캐릭터 크기를 대충 예측하고 그대로 코딩해도 되요.

이렇게 하면 점프 높이나 가속도 등의 물리 공식을 적용할 때 좀 더 현실적인 값을 넣을 수 있게 됩니다.

2012.07.05 22:06:35

ALee

무한좌표계에 겁먹지 마시고 GL 입장에서 원하는 세계를 맘껏 표현하시고 

Perspective Matrix 모드 에서  

glOrtho 혹은 glPerspective 함수로 보시고 싶은 부분의 영역(Volume)크기를 잡으시고

ModelView 모드에서 시점위치를 정하시면 

정하신 시점에서 정한 영역만큼만 보이는걸 GL 이 알아서 viewport 에 담아줍니다. 

이것은 GL 이 최종적으로 화면에 렌더링 하기 이전에 모든 좌표계를 정규화 하여 -1.0 ~ 1.0 으로 만들기 때문에 여기에 뷰포트 해상도만 곱해주면 제대로 viewport로 매핑이 됩니다.   

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://nobilitycat.tistory.com/entry/glOrtho-%ED%95%A8%EC%88%98

glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top,                                                              GLdouble nearVal, GLdouble farVal);

종횡비를 맞추어주는 함수.

 

가로 500 세로 500 이던 화면에서 가로를 늘려서

가로 1000 세로 500 인 화면일때

만약 정사각형 하나가 그려졌다면 정사각형은 가로로 2배 늘어난 정사각형이 되었을 것이다.

-))) vertex는 -1.0~1.0의 좌표를 사용하는데 변형 전 가로 0.1이 25이던것이

                                                         변형 후 가로 0.1이 50이 되기때문이다.

따라서 glOrtho 함수로 종횡비를 맞추어 주어야 한다.

glOrtho(-1,1,-1,1,1,-1) 이었다면

가로2배가 늘어났으므로

glOrtho(-2,2,-1,1,1,-1) 로 하면 된다.



출처: http://nobilitycat.tistory.com/entry/glOrtho-함수 [고귀양이의 노트.]

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://egloos.zum.com/dodary/v/1172107

[OpenGL] glOrtho, glViewport

 

glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal); : 
Ortho 
함수는 viewport를 변경하게 되었을 때의 오류를 막는데 사용할 수 있으며 보는 사람의 좌표 체계를 바꿀 수 있다. left, right, bottom, top, near, far 총 여섯 개의 파라미터(공간정보)를 가진다.

만약 사용자가 화면의 가로를 2배로 늘려 viewport를 변경하게 되었을 때화면안의 물체는 세로로 긴 모양이 될 것이다이럴 때 glOrtho 함수를 사용하여 늘려줄 수 있다

라고 해봤자 절대 이해가 안갔으나,

 


이걸 본 순간 어느 정도 이해가 가기 시작....
아니면

 



이 정도???

맨위 그림이 정말 대박인거 같다.

glViewport(GLint x, GLint y, GLsizei width, GLsizei height) : 
사물이 그려지는 영역 설정.
아 어렵다. 

사용자가 보는 viewport, 말그대로 보여지는 부분의 크기를 설정한다.
이 얘기도 
맞다. 
그림으로 봐야하는데

0, 0, 25, 25 하면 사물이 이에 맞게 쪼그라듬. 창 크기는 그대로 있고 원래 풀로 보여지는 부분만 저렇게... 근데 만약 앞 두자리에 마이너스 값 주면? 옆으로 가서 짤림. 내가 말하고도 무슨말인지 모름^^...

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://wgkang80.tistory.com/281

glOrtho() 함수

 

6개의 파라미터는 left,  right, bottom, top, near, far 

여기서 설정한 공간에 그림을 그릴것이다~ 

아래그림을 참고하세요

 

 

glOrtho() 함수는

viewport를 변경하게 되었을 때 일어날 수 있는 왜곡현상을

막는데 사용할 수 있습니다.

 

가로 300 세로 300의 viewport를

가로 600 세로 300으로 변경하게 되면

그려진 물체가 정사각형이라면 가로길이가 2배로 커져서 가로가 긴 사각형이 됩니다.

( 이유 -> vertex는 -1.0~1.0사이의 정규좌표를 사용합니다. 가로 300일때 가로 vertex 0.1이 15만큼의 가로 길이를 의미했다면

 가로 600일때는 가로 vertex 0.1이 30만큼의 가로 길이를 의미하게 되므로 물체가 늘어납니다.)

 

이런 왜곡현상을 막고

종횡비를 유지하기 위해서

glOrtho()를 이용할수있습니다.

 

방법은 늘어난 viewport비율만큼 가로세로, 또는 상하를 늘려주면됩니다.

 

glOrtho(-1,1,-1,1,1,10) 이었고

가로가 2배로 늘어났다면

glOrtho(-2,2,-1,1,1,10)로 해주면 됩니다.

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: https://skyfe79.gitbooks.io/opengl-tutorial/content/chapter8.html

투영 변환

  • GL_PROJECTION
  • glOrtho()
  • glFrustum()
  • gluPerspective()

OpenGL 의 렌더링 파이프 안에서 변환이 일어나는 것 중에 아핀 변환과 투영 변환이 있다. 아핀 변환(affine transformation)은 정점의 이동, 회전, 스케일 등의 변환를 말하며 이 변환의 특징은 변환 후에도 변환 전의 평행성과 비율을 보존해 준다는 것이다. 자세한 설명은 'OpenGL 을 통한 3차원 그래픽스 프로그래밍 기초편 - 임인성[도서출판그린]' 을 읽어보길 바란다. 투영 변환(projection transformation)은 n > m 이라 할 때 n 차원 공간의 점을 m 차원 공간의 점으로 바꾸어주는 변환을 말한다. 3차원의 한 점 p(x, y, z) 를 2차원의 한 점 p'(x, y) 로 변환하는 것을 예로 들 수 있다. OpenGL 의 렌더링 파이프 라인에서는 아핀 변환 후에 투영 변환이 일어난다. OpenGL 에서 아핀 변환에 쓰이는 함수는 glTranslatef(), glRotatef(), glScalef() 등이 있으며 투영 변환에 쓰이는 함수는 glOrtho() 함수와 glFrustum() 함수가 있고 glFrustum() 함수를 쓰기 쉽게 만들어 놓은 gluPerspective() 함수가 있다. OpenGL 에서의 투영 변환에는 2 가지의 변환이 있으며 하나는 직교 투영이고 다른 하나는 원근 투영이다. 이 두가지 외의 투영을 하려면 커스텀 투영 행렬을 만들어 현재 투영 행렬 스택에 glMultMatrix() 라는 함수를 이용해서 투영 행렬 스택의 최상단에 올려 놓으면 된다.

직교투영을 만드는 함수 glOrtho() 의 원형은 다음과 같다.

void glOrtho( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far )

이 함수에서 인자들의 의미는 아래 그림에 자세하게 설명되어 있다.

 

위의 그림에서 (left, bottom) - (right, top) 에 의해서 정의 되는 면은 클립핑된 영역이며 이 면에 2D 의 그림이 그려지게 된다. 즉 뒤의 육면체에서 우리가 생각하는 3D 의 폴리곤이 그려지고 아핀변환이 일어난 후에 클리핑된 면으로 투영 변환(여기서는 직교 변환, 3D 에서 2D 로 변환)이 일어나게 된다. 이 직교 투영의 변환 행렬은 아래와 같다.

 

원근투영을 만드는 함수 glFrustum() 의 원형은 다음과 같다.

void glFrustum( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far )

이 함수에서 인자들의 의미는 아래 그림에 자세하게 설명되어 있다.

 

위의 그림에서 (left, bottom) - (right, top) 에 의해서 정의 되는 면은 클립핑된 영역이며 이 면에 2D 의 그림이 그려지게 된다. 즉 육면체(절두체)에서 우리가 생각하는 3D 의 폴리곤이 그려지고 아핀변환이 일어난 후에 클리핑된 면으로 투영 변환(여기서는 원근변환, 3D 에서 2D 로 변환)이 일어나게 된다. 이 원근 투영의 변환 행렬은 아래와 같다.

 

glFrustum() 함수를 좀 더 쉽게 쓰기 위해서 gluPerspective() 함수가 있는데 이 함수에 의해서 생성되는 원근 투영 행렬은 위의 행렬과는 다르다. 이는 직접 책을 찾아 보는 것이 좋을 것 같다. ;]

위의 두 행렬을 비교해 보면 아핀 변환을 할 수 있는 아핀 공간의 점은 p(x, y, z, w) 에서 w = 1 이다. 직교 투영 행렬은 w = 1 인 반면에 원근 투영 행렬은 w 가 1 이 아니다. 이 때문에 직교 투영은 아핀 변환의 특징인 평행성과 비율이 보존 되지만 원근 투영은 보존되지 않는다. 그렇다면 원근 투영에서 아핀 변환은 어떻게 할 수 있을까? 이에 대한 해답은 위에서 언급한 서적을 읽어보길 바란다.

아래의 그림은 아주 간단하게 만들어 본 직교 투영의 예제 프로그램 그림과 소스 코드이다. 원근감이 느껴지지 않는다.

 

void RenderWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
    GLsizei width = LOWORD(lParam);
    GLsizei height = HIWORD(lParam);

    if (height == 0)
        height = 1;

    glViewport( 0, 0, width, height ); 

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glOrtho(-3.0f, 3.0f, -3.0f, 3.0, 1.0f, 100.0f); //!

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

BOOL RenderWindow::InitGL(void)
{
    Window::InitGL();

    glEnable(GL_LIGHTING);

    GLfloat lightPos[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    glEnable(GL_LIGHT0);

    return TRUE;
}

void RenderWindow::RenderGLScene(void)
{
    Window::RenderGLScene();

    glPushMatrix();
        glTranslatef(0.0f, 0.0f, -2.0f);
        glRotatef(33.0f, 1.0f, 0.0f, 0.0f);
        glRotatef(33.0f, 0.0f, 1.0f, 0.0f);
        glColor3f(0.5f, 0.5f, 0.5f);
        glutSolidTeapot(0.4f);
    glPopMatrix();

    glPushMatrix();
        glTranslatef(1.0f, 0.0f, -4.0f);
        glRotatef(33.0f, 1.0f, 0.0f, 0.0f);
        glRotatef(33.0f, 0.0f, 1.0f, 0.0f);
        glColor3f(0.3f, 0.3f, 0.3f);
        glutSolidTeapot(0.4f);
    glPopMatrix();

    glPushMatrix();
        glTranslatef(-1.0f, 0.0f, -6.0f);
        glRotatef(33.0f, 1.0f, 0.0f, 0.0f);
        glRotatef(33.0f, 0.0f, 1.0f, 0.0f);
        glColor3f(0.1f, 0.1f, 0.1f);
        glutSolidTeapot(0.4f);
    glPopMatrix();
}

아래의 그림은 아주 간단하게 만들어 본 원근 투영의 예제 프로그램 그림과 소스 코드이다. 원근감이 느껴진다.

 

void RenderWindow::OnSize(WPARAM wParam, LPARAM lParam)
{
    GLsizei width = LOWORD(lParam);
    GLsizei height = HIWORD(lParam);

    if (height == 0)
        height = 1;

    glViewport( 0, 0, width, height );

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glFrustum(-1.0f, 1.0f, -1.0f, 1.0, 1.0f, 10.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

BOOL RenderWindow::InitGL(void)
{
    Window::InitGL();

    glEnable(GL_LIGHTING);

    GLfloat lightPos[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    glEnable(GL_LIGHT0);

    return TRUE;
}

void RenderWindow::RenderGLScene(void)
{
    Window::RenderGLScene();

    glPushMatrix();
        glTranslatef(0.0f, 0.0f, -2.0f);
        glRotatef(33.0f, 1.0f, 0.0f, 0.0f);
        glRotatef(33.0f, 0.0f, 1.0f, 0.0f);
        glColor3f(0.5f, 0.5f, 0.5f);
        glutSolidTeapot(0.4f);
    glPopMatrix();

    glPushMatrix();
        glTranslatef(1.0f, 0.0f, -4.0f);
        glRotatef(33.0f, 1.0f, 0.0f, 0.0f);
        glRotatef(33.0f, 0.0f, 1.0f, 0.0f);
        glColor3f(0.3f, 0.3f, 0.3f);
        glutSolidTeapot(0.4f);
    glPopMatrix();

    glPushMatrix();
        glTranslatef(-1.0f, 0.0f, -6.0f);
        glRotatef(33.0f, 1.0f, 0.0f, 0.0f);
        glRotatef(33.0f, 0.0f, 1.0f, 0.0f);
        glColor3f(0.1f, 0.1f, 0.1f);
        glutSolidTeapot(0.4f);
    glPopMatrix();
}

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://m.blog.naver.com/utez/90115780711

 

오늘 포스팅 할 내용은 투영입니다.

투영을 잘 공부하시면 FPS 인터페이스에 대한 구상이 어느정도 떠오르실 겁니다.

OpenGL 포스팅이 끝나면 FPS 인터페이스 하나를 만들수 있을 정도로만 어렵지 않고 뺄내용은 다 빼고 쉽게 가도록 하겠습니다.

 

투영에는 직교 투영과 원근 투영이 있습니다.

직교 투영은 물체와 관측자와의 거리가 달라져도 크기가 변하지 않는 2D 그리기에 적합하다고 생각하시면 됩니다.

주로 CAD나 텍스트, 설계도와 같은 2D 이미지에 자주 쓰입니다.

이번 포스팅에서는 직교투영은 간단하게 설명하고 넘어가겠습니다.

원근투영은 물체와 관측자의 거리에 따라 물체의 크기를 조정합니다.

실제로 우리가 보는 것처럼 멀리 있는 것은 작게, 가까이 있는 것은 크게 보입니다.

 

 

 

왼쪽이 원근 투영이고 오른쪽이 직교투영입니다.

오늘은 원근 투영에 대해서 집중적으로 포스팅 하도록 하겠습니다.

 

제공된 예제는 지난 코드에서 크게 변경되지 않았습니다.

 

먼저 reshape함수를 보겠습니다.

void reshape(int w, int h) {
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glViewport(0, 0, w, h);
    window_width = w;
    window_height = h;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if(w<=h) {
        gluPerspective(60.0, (float)h/w, 1.0, 2000.0);
    }
    else {
        gluPerspective(60.0, (float)w/h, 1.0, 2000.0);
    }
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
    glutPostRedisplay();
}

reshape 함수의 역할이 뭔지는 다 아시죠?

다시한번 집고 넘어가자면 생성된 윈도우창이 움직이거나 변환 되었을때 호출되는 함수로써 인자로는 바뀐 윈도우 창의 넓이(w)와 높이(h)를 받아 실행되는 함수입니다.

 

glViewport(int x, int y, int 넓이, int 높이)

예전에도 설명한적이 있는데 창 내에서 OpenGL의 렌더링이 진행될 영역을 설정할 수 있습니다.

x와 y는 시작되는 위치이고 넓이와 높이는 시작되는 위치에서 넓이와 높이를 의미 합니다.

 

glMatrixMode(GL_PROJECTION)

glMatrixMode는 투영행렬과 모델행렬을 불러오는 함수로써 인자로는 GL_PROJECTION과 GL_MODELVIEW 그리고 GL_TEXTURE가 올 수 있습니다.

지난 포스팅에서 설명한것처럼 OpenGL은 행렬을 통해서 모델을 표현한다고 설명했었습니다.

GL_MODELVIEW를 이용해서 모델뷰 행렬을 불러와 이 행렬에 모델들의 버텍스들이 곱해져서 최종적인 모델의 위치가 생성됩니다. 이렇게 생성된 모델들은 GL_PROJECTION에 의해 생성된 투영 행렬에 곱해져서 최종적으로 화면에 출력되는 것입니다. 

모델뷰 행렬은 모델의 위치를 잡아주고 투영 행렬은 그렇게 생성된 모델들을 최종적으로 출력하는데 사용된다고 보시면 됩니다.

 

glLoadIdentity()

현재 행렬을 초기화(단위행렬)시킵니다.

투영행렬과 모델뷰 행렬에 서로 영향을 주지 않고 현재 선언된 행렬에만 영향을 미칩니다.

위 코드에는 투영행렬을 단위행렬로 초기화 시키고 있습니다.

 

gluPerspective(시야각, 종횡비, 앞면, 뒷면);

위 함수는 원근 투영을 만들어 냅니다.

시야각은 fps게이머라면 잘 아는 FOV로써 각을 나타내며 종횡비는 높이와 넓이의 비율을 의미합니다.

앞면은 밑의 그림의 near를 의미하며 뒷면은 far를 의미합니다.

 

 

 

 

즉 위 그림에 보이는 네모난 공간(절두체라고 합니다.)안에 렌더링 되는 모든 그림들을 표현하게 됩니다.

같은 역할을 하는 함수로 glFrustum이 있지만 투영 설정에 애매한 부분이 있어서 gluPerspective를 많이 사용합니다.

 

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glGetFloatv(GL_MODELVIEW_MATRIX,matrix);

모델뷰 행렬을 불러 온뒤 (glMatrixMode(GL_MODELVIEW))

단위행렬로 초기화 하고(glLoadIdentity())

현재 행렬값을 matrix에 저장합니다(glGetFloatv(GL_MODELVIEW_MATRIX,matrix)).

float eye[3], at[3], up[3];
void init() {
    glEnable(GL_DEPTH_TEST);
    eye[0] = 0; eye[1] = 0, eye[2] = 10;
    at[0] = 0; at[1] = 0; at[2] = 0;
    up[0] = 0; up[1] = 1; up[2] = 0;
}

예제의 위쪽에 보시면 전역변수로 eye, at, up이 선언되어있고 init함수에서 이들을 초기화 해주고 있습니다.

모두 0으로 초기화 되었는데 eye[2] = 10, up[1] = 1로 초기화 된것을 볼 수 있습니다.

이것들은 전부 카메라를 위해 사용되는 변수들 입니다.

 

display함수입니다.

void display() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glPushMatrix();
    glRotatef((float)mouse_dx * 0.3f, 0, 1, 0);
    glRotatef((float)mouse_dy * 0.3f, 1, 0, 0);
    glMultMatrixf(matrix);
    glGetFloatv(GL_MODELVIEW_MATRIX, matrix);
    glPopMatrix();
    glLoadIdentity();
    
    gluLookAt(eye[0], eye[1], eye[2], at[0], at[1], at[2], up[0], up[1], up[2]);
   
    glMultMatrixf(matrix);
    glPushMatrix();
    glPushMatrix();
    glColor3f(1, 0, 0);
    glutSolidTeapot(1);
    glPopMatrix();
    glPushMatrix();
    glColor3f(0, 1, 0);
    glTranslatef(-3, 0, -5);
    glutSolidTeapot(1);
    glPopMatrix();
    glPushMatrix();
    glColor3f(0, 0, 1);
    glTranslatef(5, 0, -4);
    glutSolidTeapot(1);
    glPopMatrix();
    Draw_Axis();
    glPopMatrix();
    glutSwapBuffers();
}

다른것은 지난 예제와 거의 차이가 없습니다.

단지 이상한 함수 하나가 추가되었는데 바로 OpenGL에서 카메라 변환을 적용시켜 주는 함수인 gluLookAt 함수 입니다.

 

gluLookAt(eye[3],at[3],up[3])

gluLookAt은 가장 간단하게 카메라라고 생각하시면 됩니다.

eye는 카메라의 위치로 x,y,z 좌표가 들어갑니다.

at은 카메라의 초점으로써 x,y,z가 들어갑니다.

up은 카메라의 윗 방향으로 x가 1이면 카메라는 x축으로 눞혀있고 y가 1이면 y축을 중심으로 세워져 있게 됩니다. z축도 마찬가지 입니다.

 

 

 

 

위 코드를 보시면 카메라는 (0,0,10)에 위치하고 있고 (0,0,0)을 바라보고 있도록 설정되어있습니다.

eye, at, up값을 여러가지로 변경해 보시면 금방 이해하실 수 있으실 겁니다.

 

정리해 보자면 투영에는 원근과 직교가 있고 각각 3D와 2D에 적합합니다.

glMatrixMode()를 통해서 모델뷰행렬과 투영행렬을 설정할 수 있습니다.

원근 투영에는 gluPerspective가 사용됩니다.

gluLookAt을 이용해서 카메라의 위치를 쉽고 직관적으로 설정할 수 있습니다.

다음에는 FPS 카메라 및 짐벌락과 쿼터니언에 대해서 포스팅 하도록 하겠습니다.

질문과 지적을 환영합니다~(--)(__)/~

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://cluster1.cafe.daum.net/_c21_/bbs_search_read?grpid=dpVc&fldid=1CWa&contentval=0000Lzzzzzzzzzzzzzzzzzzzzzzzzz&nenc=&fenc=&q=OpenGL&nil_profile=cafetop&nil_menu=sch_updw

오늘은 관측에 관한 문제를 이야기해봅시다.
예제소스는 흥미로운걸 몇개 만들어 봤었는데 다음에 천천히 하기루 하고
오늘은 이론적인 부분에 대해서 이야기 해봅시다.

오픈지엘에서는 우리가 볼수 있는 카메라를 어떻게 배치할까요?
지금까지 우리는 정해진 틀에 맞춰놓고 물체를 관측하고 있었습니다.
트랜슬레이트 시키거나 로테이트 시켜서 위치를 움직인 것에 불과하죠..
그렇다면 위치를 움직이지 않고 관측자가, 즉 카메라가 움직여서 오브젝트를
관찰할수 잇다면? 조금은 더 재밌는 화면도 연출할수 있을 것입니다.

이제부터 알아볼 두개의 함수는 관측에 대한 새로운 시각을 갖게 할것입니다.




1. glOrtho(xmin,xmax,ymin,ymax,zmin,zmax);
그리 낯설지만은 않은 함수입니다.
지금까지 그냥 무작정 써왔을수도 있고 아니면 처음 볼수도 있는 함수죠
우리가 관측을 할때 무한정인 공간에서 딱히 어떻게 지정을 하기가 힘듭니다.
그래서 우리가 관측할 범위를 정해주는 것이죠, x,y,z축의 범위를 정해버리면
그에 상응하는 육면체의 공간이 나옵니다. 그 공간을 우리가 관측하는 것이라고
생각했을때 이루어지는 방법을 쉽게 해주기 위해서 glOrtho라는 함수를 이용합니다.
이정도면 이해가 되실겁니다. 사용법은 다들 아시겠죠?





2. gluLookAt(lookx,looky,lookz,atx,aty,atz,upx,upy,upz);
이함수야 말로 3차원 프로그래밍의 핵심입니다.
이 함수만 잘써도 아주 엄청난 차이를 보여줄수 있습니다.
인자가 9개나 되지만 실상으론 그리 어렵지 않습니다.
룩엣 함수는 말그대로 카메라를 어디에 배치하느냐와 같습니다.
쉽게 생각해서 우리가 사진을찍으려고 카메라를 사용할때
카메라의 위치가 있을것이고, 우리가 찍으려고 하는 대상의 위치가 있을 것이고
가로로 찍을까,새로로 찍을까 하는 것들.. 이런것들이 바로 룩엣함수입니다.
앞의 세 인자는 카메라의 위치를 나타냅니다. 어디든지 가져다 놓을수 있겠죠?
그리고 그다음 세개의 인자는 카메라가 바라보는 곳입니다. 카메라가 엉뚱한 
곳을 보고있다면 물체가 보이지 않겠죠?
그리고 마지막 세인자는 업벡터라고 하는데 쉽게 이야기해서 카메라를 가로로 찍을건가
세로로 찍을건가와 같은 말입니다. 즉 카메라의 머리가 어느방향으로 향하는지를
결정하는 것이죠.. 이해가 되시죠?





룩엣함수만 잘써도 우리가 흔히 접하는 3차원게임의 멋진영상같은것을 만들어 낼수
있습니다. 카메라만 잘 움직이면서 보여주면 되니까여.. 
일단은 예제소스는 없습니다. 이다음에 바로 매트릭스에 관한 강좌를
올릴건데 그 소스를 참고하세요.ㅎㅎ



다음강좌는 PushMatrix()? 입니다.

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://alleysark.tistory.com/222

OpenGL 에서 View transformation을 하는 방법은 크게 두가지로 소개된다. 하나는 glRotatef와 glTranslatef를 섞어 사용하는것이고 다른 하나는 gluLookAt을 사용하는것이다.

처음 OpenGL을 시작할땐 이 두가지 방법의 차이점에 대해 굉장히 의문을 가졌었다. 답부터 말하자면 View transformation 결과에는 두 방법 모두 차이가 없다. 단지 차이라면 퍼포먼스에 있을뿐이다.

View transform 관점에서 두 방법을 살펴보자.

* glRotatef + glTranslatef

나는 세상의 중심에 위치하고, 원하는 시점을 만들기 위해 세상을 움직여버리는 비범한 방법이다. 예를들어 45도 위를 바라보고 싶으면 세상을 -45만큼 내려버리면 되는것과 같다.

* gluLookAt

세상은 변함이 없다. 카메라맨이 원하는 장면을 만들기 위해 동분서주 움직이듯, 카메라의 위치(eye)와 바라보는 사물(lookat), 카메라의 위(up) 방향에 대한 정보를 바탕으로 view matrix를 만든다. 그 후 매트릭스연산을 통해 world coordinate => camera coordinate로의 변환을 해주는 방식이다.

즉, 세상을 상대적인 공간으로 보느냐, 절대적인 공간으로 보느냐의 차이이며, gluLookAt의 마지막 매트릭스 연산을 고려한다면 결국 세상을 움직여 시점에 변화를 준다는데는 차이가 없는것이다.

그렇다면 어째서 두 가지 방법이 공존하는것이냐! 일반적으로 두 방법중 gluLookAt이 더 나은 선호도를 보인다. 이유로는,

1. 편하다 - 함수의 prefix에서 알 수 있든 gluLookAt은 GLU계열의 보조함수이다. 이 함수를 써보면 glRotatef+glTranslatef의 조합으로 시점을 조작할때보다 더 적은 코드로 원하는 결과를 만들 수 있다.

2. 빠르다 - 논란의 소지가 있지만, 개인적인 평가로는 gluLookAt은 glRotatef+glTranslatef의 조합보다 빠르다. 짐작가는 이유로는 gluLookAt의 경우 회전이 어찌되든 연산되는 방법은 세 가지 벡터로부터 이루어진다. 반면 변환함수의 조합으로 회전을 위해선 x-axis, y-axis, z-axis로의 세 번 회전이 필요하다. 아무래도 산술연산이 api콜보단 빠르다는게 내 의견이다.

즉, gl계열의 함수로 번거로운 작업을 gluLookAt이란 보조함수를 두어 해결했다는 결론이 나며 심각하게 고민할 거리가 되지 못한다는 얘기가 된다. 이 문제를 가지고 포럼을 뒤지던 때 답변자 중 한 분의 '나는 왜 이 주제가 그래픽스 초보자들에게 의문을 주는지가 의문이다' 라는 말이 이해가된다. View transformation에 대한 확실한 이해가 있다면 헷갈릴 이유가 없다.

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://kslash.tistory.com/entry/OpenGL-ES-13-%E2%80%93-Moving-in-3D

Moving in 3D

 우리가 할 일은 "genuine( 순수한 )" 3D world 밑바닥부터 전부를 만들 것이다. 그러나, 그러기 전에 3D world에서의 이동을 소개하겠다ㅏ.

 오늘 우리가 할 일은 "밑바닥"에서 돌아다니기 위해서 touch를 처리하기 위한 새로운 코드를 추가하는 것이다. Touch를 사용해서 왼쪽, 오른쪽으로 회전할 수 있고, 앞 뒤로 움직일 수 있다.

이번 프로젝트의 시작을 위해서 아래의 파일을 사용한다.

 OpenGL ES 13 - Starter

 The Mythical Camera

 3D world를 카메라를 통해서 들여다 본다는 개념으로 생각하지만, 실제로 OpenGL에 카메라가 있는 것은 아니다. 따라서 카메라가 있는 것처럼 scene에서 움직이고자 한다면, 모든 object들을 이동시켜야 한다. 카메라가 움직이는 것과 같은 느낌은 카메라의 이동으로 인한 것이 아니라 원점( 0, 0, 0 )을 기준으로 한 world 자체의 이동에 의한 것이다.

 뭔가 해야 할 것이 많은 것처럼 들리지만, 실제로 그렇지는 않다. 우리의 어플리케이션에 따라서 이를 구현하는 방법은 많고, 정말 큰 world에서 사용하기 위한 최적화 방법은 더욱 많다. 이에 대한 것은 나중에 다시 언급할 것이다.

 일을 쉽게 하기 위해서 이번 튜토리얼의 프로젝트에는 OpenGL ES의 큰형 격인 GLU library에서 gluLookAt() 함수를 가져왔다. 
 

Using gluLookAt()

프로토 타입 :

void gluLookAt( GLfloat eyex,

GLfloat eyey,

GLfloat eyez,

GLfloat centerx,

GLfloat centery,

GLfloat centerz,

GLfloat upx,

GLfloat upy,

GLfloat upz)

 

eyex, eyey, eyez 는 눈(카메라)의 위치,

centerx, centery, centerz는 눈(카메라)가 바라보는 곳의 위치( 카메라로부터 뻗어나온 방향을 특정하기 위한 것으로 center의 위치는 방향 벡터 위의 점이라면 어디라도 무방 )

upx, upy, upz는 카메라의 up 벡터 카메라가 바라보는 방향벡터와 직각, 위쪽 방향, 왼쪽 오른쪽 벡터를 찾는데 유용하게 쓰인다.

 위 세가지의 좌표는 모두 world 좌표계이다.

 위에 링크해둔 base project에 이미 translation을 제외한 기본 설정은 적용해 두었다.

 기본 상태에서 "Build and Go"를 누르면 위의 화면을 볼 수 있다. 


 

 먼저, glLookAt() 함수를 사용해 보자.

drawView 메소드로 가서 다음의 코드를 glLoadIdentity() 바로 다음에 추가한다. 

glLoadIdentity(); <br>
gluLookAt(5.0, 1.5, 2.0,                     // Eye location, look "from"
-5.0, 1.5, -10.0,                  // Target location, look "to"
0.0, 1.0, 0.0);                    // Ignore for now

 "Build and Go"를 눌러본다. 아래와 같은 화면을 볼 수 있을 것이다. 

 



gluLookAt() 
함수에 이것저것 넣고 테스트 해보자.

 

Movement in 3D    

 이제는 gluLookAt() 함수를 이용해서 바닥 위를 걸어 다니는 것을 시뮬레이션 해보자. X, Y 축 위에서 좌우, 앞뒤로 움직이고, 방향을 바꾸기 위한 회전을 해본다.

 gluLookAt() 함수에서 위의 일을 하기 위해서 필요한 것은 "eye"의 위치와, 바라보는 방향인 "centre"이다.

 우선, interface 로 가서 다음의 두 변수를 추가해준다. 

1.GLfloat eye[3];// Where we are viewing from

2.GLfloat center[3];// Where we are looking towards

위의 두 변수들은 각각 x, y, z값을 갖는다.

initWithCoder 메소드로 가서, 위 두 변수들을 초기화 해 준다.

1.eye[0] = 5.0;

2.eye[1] = 1.5;

3.eye[2] = 2.0;

4. 

5.center[0] = -5.0;

6.center[1] = 1.5;

7.center[2] = -10.0;

이제, drawView 메소드로가서, gluLookAt() 함수호출을 약간 수정해준다.

 

1.gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2], 0.0, 1.0, 0.0); 

"Build and Go"를 눌러서 확인해보자.

 

Getting Ready for Movement

 World에서 돌아다니기 위한 touch 이벤트를 처리하기 전에, header 파일에서 약간의 설정을 해 줘야 한다. Header 파일로 이동해서 몇가지 default를 설정해주고, 새로운 enum 타입을 생성해준다.

먼저, 걷는 속도와 회전 속도를 설정해준다.

1.#define WALK_SPEED 0.005

2.#define TURN_SPEED 0.01


다음으로, 각 동작을 지정할 enum 타입 변수를 하나 만들어준다. 

1.typedef enum __MOVMENT_TYPE {

2.MTNone = 0,

3.MTWalkForward,

4.MTWAlkBackward,

5.MTTurnLeft,

6.MTTurnRight

7.} MovementType;

마지막으로, 현재 동작상태를 저장할 변수를 추가한다.( 위에서 만들어준 MovementType 형 ) 

1.MovementType currentMovement;

 

initWithCoder 메소드로 가서 currentMovement 변수의 초기값을 설정해주는 것을 잊으면 안된다. 

1.currentMovement = MTNone;



Get Touchy Feely

이제 실제로 touch를 처리할 차례다. 저번 튜토리얼에서 봤던 4개의 touch handling 메소드들을 기억하자. 이번 튜토리얼에서는 touchesBegan 과 touchesEnded 단 두개의 메소드만 사용할 것이다.

어떤 행동을 할지를 결정하기 위해서, 화면을 아래와 같이 4 구획으로 나눠줄 것이다.

기본적으로 화면은 가로 320 pixel, 세로로 480 pixel 크기다. 따라서, 세로를 3으로 나눠서 각각의 높이는 160 pixel이다.

- 0 ~ 159 pixel : forward

- 160 ~ 319 pixel: 다시 좌우로 나눠서 왼쪽은 왼쪽회전, 오른쪽은 오른쪽회전으로 한다.

- 320 ~ 480 pixel : backward

 

아래는 이를 적용한 touchesBegan 메소드이다.

-(void) touchesBegan: (NSSet * ) touches withEvent: (UIEvent * ) event {

    UITouch * t = [
        [touches allObjects] objectAtIndex: 0
    ];

    CGPoint touchPos = [t locationInView: t.view];

    // Determine the location on the screen. We are interested in iPhone

    // Screen co-ordinates only, not the world co-ordinates

    // because we are just trying to handle movement.

    //

    // (0, 0)

    // + - - - - - - - - - - - +

    // |                       |

    // |          160          |

    // | - - - - - - - - - - - | 160

    // |           |           |

    // |           |           |

    // | - - - - - - - - - - - | 320

    // |                       |

    // |                       |

    // + - - - - - - - - - - - + (320, 480)

    if (touchPos.y < 160) {

        // We are moving forward

        currentMovement = MTWalkForward;

    } else if (touchPos.y > 320) {

        // We are moving backward

        currentMovement = MTWAlkBackward;

    } else if (touchPos.x < 160) {

        // Turn left

        currentMovement = MTTurnLeft;

    } else {

        // Turn Right

        currentMovement = MTTurnRight;

    }

}


사용자가 touch하기 시작할때 우리가 해야할 일은 단지 구획을 기록하고, currentMovement 변수를 설정해주는 것 뿐이다. 

이제 touchesEnded 메소드를 다음과 같이 만들어준다.

-(void) touchesEnded: (NSSet * ) touches withEvent: (UIEvent * ) event {

    currentMovement = MTNone;

}

단지, currentMovement를 default 값은 MTNone으로 되돌려준다.

마지막으로 위의 touch 이벤트들을 처리할 메소드가 필요하다. 이 새로운 메소드는 상속받는 메소드가 아니므로 interface에 따로 선언을 해 줘야 한다. Header 파일로 가서 아래와 같은 메소드 선언을 추가한다.

- (void)handleTouches;

다시 구현부로 되돌아와서 위 메소드에 3D world에서의 동작을 위한 코드를 추가한다.

The Theory of Moving in 3D

 

먼저 걷기부터 보자. 사용자가 앞으로 가기를 원한다면, eye 의 위치뿐만 아니라 바로보는 위치인 center도 함께 고려해야 한다. Eye의 위치는 곳 우리가 있는 위치를 의미한다. 그리고 center의 위치는 우리가 바라보는 앞 방향이다. ( 만약 카메라가 앞으로 움직일때 eye의 위치만 계속 앞으로 이동하도록 고쳐주고 center의 위치는 고쳐주지 않는다면, 어느순간 eye의 위치가 center를 넘어가게 되고 그 때 부터는 center를 바라보기 위해 뒤를 돌아보는 것처럼 될 것이다. )

 아래 그림을 확인해보자.

 
eye와 centre 두 point 간의 거리는 x 좌표와 z 좌표의 차이에 의해 구할 수 있다. 새로운 x, z값을 구하기 위해 우리가 해야 할 일은 현재의 좌표에 "speed"값을 곱해주는 것이다. 아래와 같이 한다.

위 그림에서 빨간 점이 구하고자 하는 새로운 좌표이다.

Delta X와 Delta Z부터 구해보자.


deltaX = 1.5 - 1.0 = 0.5

deltaZ = -10 - (- 5.0) = -5.0


여기에 walk speed를 곱해준다.


xDisplacement = deltaX * WALK_SPEED

              = 0.5 * 0.01

              = 0.005

 

zDisplacement = deltaZ * WALK_SPEED

              = -5.0 * 0.01

              = 0.05


새로운
 좌표는

(eyex + xDisplacement, eyey, eyez + zDisplacement)

= (0.005+1.0, eyey,(-10)+ 0.05)

= (1.005, eyey, -9.95)

 

이다.

 

위의 방법은 eye 와 center간의 사이가 멀 수록 걷는 속도가 빨라지는 단점이 있지만, CPU 집중도가 낮다는 장점이 있다.

다음으로 왼쪽, 오른쪽 방향으로의 회전을 살펴본다.

두 point를 알고 있으므로, angle을 알 수 있다.

방향 회전을 위해서, 다른 일은 필요없고, 단지 center의 위치만 이동시켜주면 된다. 단 이동될 위치는 eye를 중심으로 하는 원 위에 있어야 한다. 앞에서 우리가 정의해준 TURN_SPEED는 실제로는 angle이다.

회전에 있어서 포인트는 우리의 eye 좌표는 변경하지 않고, 바라보는 곳의 위치를 왼쪽 또는 오른쪽방향으로 회전함으로써 고개를 돌리는 것과 같은 효과를 내는 것이다.

 
newX = eyeX + radius * cos(TURN_SPEED)*deltaX -

                sin(TURN_SPEED)*deltaZ

 newZ = eyeZ + radius * sin(TURN_SPEED)* deltaX +

                cos(TURN_SPEED)*deltaZ

 

Handling Touches and Converting these into Movements

위의 작업을 합쳐본다.

-(void) handleTouches {

    if (currentMovement == MTNone) {

        // We're going nowhere, nothing to do here

        return;

    }

터치하고 있지 않을 경우 아무것도 하지 않기 위한 코드이다.

아래부터는 각 터치에 대한 동작을 실제 적용하기 위한 코드로서, 
위에서 본 deltaX와 deltaZ를 변수로 유지한다.( vector[3] )

GLfloat vector[3];

vector[0] = center[0] - eye[0];

vector[1] = center[1] - eye[1];

vector[2] = center[2] - eye[2];


deltaY도 갖고있긴 하지만 실제로 사용되진 않는다.

switch (currentMovement) {

    case MTWalkForward:

        eye[0] += vector[0] * WALK_SPEED;

        eye[2] += vector[2] * WALK_SPEED;

        center[0] += vector[0] * WALK_SPEED;

        center[2] += vector[2] * WALK_SPEED;

        break;

    case MTWAlkBackward:

        eye[0] -= vector[0] * WALK_SPEED;

        eye[2] -= vector[2] * WALK_SPEED;

        center[0] -= vector[0] * WALK_SPEED;

        center[2] -= vector[2] * WALK_SPEED;

        break;

    case MTTurnLeft:

        center[0] = eye[0] + cos(-TURN_SPEED) * vector[0] - sin(-TURN_SPEED) * vector[2];

        center[2] = eye[2] + sin(-TURN_SPEED) * vector[0] + cos(-TURN_SPEED) * vector[2];

        break;

    case MTTurnRight:

        center[0] = eye[0] + cos(TURN_SPEED) * vector[0] - sin(TURN_SPEED) * vector[2];

        center[2] = eye[2] + sin(TURN_SPEED) * vector[0] + cos(TURN_SPEED) * vector[2];

        break;

}

}


OK

 Bringing it All Together

drawView 메소드로 돌아가서 gluLookAt() 함수 호출 앞에 아래 코드를 추가한다. 

[self handleTouches];
이제 끝!~~

OpenGLES13 Project.zip

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://incblue.tistory.com/2264

OpenGL ES는 3차원 공간입니다. 하지만, 2D 좌표계처럼 사용하고 싶을 때가 있습니다.
그럴 때는 당연히 x, y, z 좌표중 z 좌표를 0으로 세팅하고 사용을 하겠죠.

하지만, 좌표만 바꾼다고 단말기 스크린(Screen)의 픽셀 좌표 1개가 OpenGL 상의 원하는 좌표가
되지 않을 경우가 많습니다. 

이 때는 ViewPort를 적절하게 잘 조절을 해줘야 합니다.
그러기 위해서는 다음과 같이 세팅하면 됩니다.

gl.glOrthof(0, 480, 0, 800, 1, -1);


이 때 값들은 480x800 해상도를 가진 단말기 기준입니다.

그런데, 이 경우는  y좌표가 단말기 위쪽으로 갈수록 커지고 단말기 아래쪽이 0이 됩니다.
우리가 흔히 사용하는 GUI 좌표에서는 y좌표가 위쪽이 0이고, 아래로 갈수록 커지죠.

즉, 이 때는 이렇게 할 수 있습니다.

gl.glOrthof(0, 480, 800, 0, 1, -1);



자, 그러면 이 픽셀 좌표에 맞게 삼각형을 그리는 전체 소스를 한 번 보겠습니다.

SnowOpenGLESActivity.java

view sourceprint?

package snowdeer.opengles.test;

import android.app.Activity;

import android.opengl.GLSurfaceView;

import android.os.Bundle;

import android.view.Window;

import android.view.WindowManager;

public class SnowOpenGLESActivity extends Activity

{

    private GLSurfaceView m_gsView = null;

    @Override

    public void onCreate(Bundle savedInstanceState)

    {

        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);

        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        m_gsView = new GLSurfaceView(this);

        m_gsView.setRenderer(new SnowRenderer());

        setContentView(m_gsView);

    }

    @Override

    protected void onPause()

    {

        super.onPause();

        m_gsView.onPause();

    }

    @Override

    protected void onResume()

    {

        super.onResume();

        m_gsView.onResume();

    }

}

SnowRenderer.java

view sourceprint?

package snowdeer.opengles.test;

import javax.microedition.khronos.egl.EGLConfig;

import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;

public class SnowRenderer implements Renderer

{

    private SnowTriangle m_SnowTriangle = null;

    public SnowRenderer()

    {

        m_SnowTriangle = new SnowTriangle();

    }

    public void onDrawFrame(GL10 gl)

    {

        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

        gl.glLoadIdentity(); // Matrix 리셋

        gl.glTranslatef(0.0 f, 0.0 f, 0.0 f);

        m_SnowTriangle.draw(gl);

    }

    public void onSurfaceChanged(GL10 gl, int width, int height)

    {

        if (height == 0)

        {

            height = 1; // 0으로 나누는 것을 방지하기 위해서

        }

        gl.glViewport(0, 0, width, height); // ViewPort 리셋

        gl.glMatrixMode(GL10.GL_PROJECTION); // MatrixMode를 Project Mode로

        gl.glLoadIdentity(); // Matrix 리셋

        // 윈도우의 Aspect Ratio 설정

        gl.glOrthof(0, 480, 800, 0, 1, -1);

        gl.glMatrixMode(GL10.GL_MODELVIEW); // Matrix를 ModelView Mode로 변환

        gl.glLoadIdentity(); // Matrix 리셋

    }

    public void onSurfaceCreated(GL10 gl, EGLConfig config)

    {

        gl.glShadeModel(GL10.GL_SMOOTH); // Smooth Shading이 가능하도록 설정

        gl.glClearColor(0.0 f, 0.0 f, 0.0 f, 1.0 f); // 하얀 바탕 그리기

        gl.glClearDepthf(1.0 f); // Depth Buffer 세팅

        gl.glEnable(GL10.GL_DEPTH_TEST); // Depth Test 가능하도록 설정

        gl.glDepthFunc(GL10.GL_LEQUAL); // The Type Of Depth Testing

        // glHint 설정

        gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);

    }

}

 

SnowTriangle.java

view sourceprint?

package snowdeer.opengles.test;

import java.nio.ByteBuffer;

import java.nio.ByteOrder;

import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

public class SnowTriangle

{

    private FloatBuffer m_vertexBuffer;

    // 삼각형 좌표 입력

    private float vertices[] = {
        0.0 f,
        0.0 f,
        0.0 f,

        240.0 f,
        800.0 f,
        0.0 f,

        480.0 f,
        0.0 f,
        0.0 f,
    };

    public SnowTriangle()

    {

        ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);

        byteBuf.order(ByteOrder.nativeOrder());

        m_vertexBuffer = byteBuf.asFloatBuffer();

        m_vertexBuffer.put(vertices);

        m_vertexBuffer.position(0);

    }

    public void draw(GL10 gl)

    {

        gl.glFrontFace(GL10.GL_CW); // 시계방향 그리기 설정

        // VertexPointer 설정

        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, m_vertexBuffer);

        // Vertex Array 사용 가능하도록 설정

        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

        // 삼각형 Strip 그리기

        gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);

        // Vertex Array 사용 상태를 다시 불가능하도록 설정

        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

    }

}

 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

 

출처: http://gogorchg.tistory.com/entry/Android-Opengl-es-20-LoadTexture


1.0에서는 Texture에 Bitmap 파일만 넣어주면, Android에서 알아서 바꿔주었었다. 

아주 간편하게 Texture를 적용 시킬 수가 있었습니다.
(안에서 어떻게 돌아가든 관계 없어..)

하지만, 2.0에서는 모든 것을 개발자에게 맡기게 되어있죠.

구글링을 해본 결과 , 안드로이드의 Bitmap 값은 ARGB로 32bit 픽셀로 되어 있다고 합니다.
하지만, Opengl은  RGBA로 되어 있어서 컨버팅 할 필요가 생긴거죠.
그래서 2.0에서는 glTexture2D 의 매개변수가 Buffer로 되어 있는 겁니다. 


결과적으로 Texture에 Bitmap을 로드 시킬 때 다음과 같은 코드  형식으로 하면 되겠네요.

private static int loadTexture(InputStream is) {
    int[] textureId = new int[1];
    Bitmap bitmap;
    bitmap = BitmapFactory.decodeStream(is);

    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bitmap.getWidth() * bitmap.getHeight() * 4);
    byteBuffer.order(ByteOrder.BIG_ENDIAN);
    IntBuffer ib = byteBuffer.asIntBuffer();

    int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
    bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
    for (int i = 0; i < pixels.length; i++) {
        ib.put(pixels[i] << 8 | pixels[i] >>> 24);
    }

    bitmap.recycle();

    byteBuffer.position(0);
    GLES20.glGenTextures(1, textureId, 0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0]);

    GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, bitmap.getWidth(), bitmap.getHeight(), 0,
        GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, byteBuffer);

    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

    return textureId[0];
}

 

 

 

=======================

=======================

=======================

 

 

 

 

 

 

출처: http://gogorchg.tistory.com/entry/Android-Opengl-es-20-Texture-Setting

[Android Opengl es 2.0 ] Texture Setting.

///

//  Load texture from resource

//
private int loadTexture(InputStream is, int texId)

{

    /*

         * Create our texture. This has to be done each time the

         * surface is created.

         */

    int[] textures = new int[1];

    if (texId == 0)

        GLES20.glGenTextures(1, textures, 0);

    else

        textures[0] = texId;

    int mTextureID = textures[0];

    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID);

    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,

        GLES20.GL_NEAREST);

    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,

        GLES20.GL_TEXTURE_MAG_FILTER,

        GLES20.GL_LINEAR);

    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,

        GLES20.GL_REPEAT);

    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,

        GLES20.GL_REPEAT);

    Bitmap bitmap;

    try {

        bitmap = BitmapFactory.decodeStream(is);

        bitmap = Bitmap.createScaledBitmap(bitmap, getMinPowerByTwo(bitmap.getWidth()), getMinPowerByTwo(bitmap.getHeight()), false);

    } finally {

        try {

            is.close();

        } catch (IOException e) {

            // Ignore.

        }

    }

    GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

    bitmap.recycle();

    return mTextureID;

}



전,, Buffer로 꼭 넣어야만 Opengl es 2.0 에서 Bitmap을 Texture에 넣을 수 있는줄 알았습니다.
http://gogorchg.tistory.com/entry/Android-Opengl-es-20-LiveWallpaper-%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9-%EC%8B%9C-onSurfaceCreated-%EB%AC%B8%EC%A0%9C


하지만, 위 소스 같이 예전 opengl es 1.1에서 했던 형태로 해도 문제가 없습니다.

잘못된 정보를 알려드려 죄송합니다. ;;;; 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://gogorchg.tistory.com/entry/Android-Opengl-es-20-%EC%97%AC%EB%9F%AC-Object%EB%A5%BC-%ED%95%9C-%EB%B2%88%EC%97%90-%EA%B7%B8%EB%A6%AC%EA%B8%B0

[Android Opengl es 2.0 ] 여러 Object를 한 번에 그리기.

 

 코딩을 하는 도중... 만약에 1000개 이상의 Object들을 한꺼번에 그릴 려고 할 때,

For문으로 1000개를 돌려야 할까요???? 개발 요구사항에 따라 다르겠지만,

만약 전체적인 회전이나  컬러 알파 정도는 수정할 수 있는 방법이 있었습니다. 

바로!!! 1000개의 Object의 Vertex를 지정하고,
한번에 그리는 것입니다.!!! ( 제가 초보라서.. 이제 알게됨..^^;)

Vertex와 Index 배열을 잘 이용하면 쉽게 그릴 수 있습니다.

먼저 테스트 해본 배열은 

 private final float[] mVerticesData = {

    		

            // X, Y, Z, U, V,alpha,colorR,colorG,colorB,colorA

    		

    		 0.0f, 0.5f, 0, 1.0f, 0.0f,1.0f,0.0f,0.0f,1.0f,1.0f,    // 0

    		-0.5f, 0.5f, 0, 0.0f, 0.0f,1.0f,0.0f,0.0f,1.0f,1.0f,    // 1

    		-0.0f, 1.0f, 0, 1.0f, 1.0f,1.0f,0.0f,0.0f,1.0f,1.0f,    // 2

    		-0.5f, 1.0f, 0, 0.0f, 1.0f,1.0f,0.0f,0.0f,1.0f,1.0f,    // 3

    		

    		 0.0f, 0.0f, 0, 1.0f, 0.0f,0.0f,1.0f,1.0f,1.0f,1.0f,    // 4

    		-0.5f, 0.0f, 0, 0.0f, 0.0f,0.0f,1.0f,1.0f,1.0f,1.0f,    // 5

    		-0.0f, 0.5f, 0, 1.0f, 1.0f,0.0f,1.0f,1.0f,1.0f,1.0f,    // 6

    		-0.5f, 0.5f, 0, 0.0f, 1.0f,0.0f,1.0f,1.0f,1.0f,1.0f,    // 7

    		

    		 0.0f,-0.5f, 0, 1.0f, 0.0f,1.0f,1.0f,1.0f,1.0f,1.0f,    // 8

    		-0.5f,-0.5f, 0, 0.0f, 0.0f,1.0f,1.0f,1.0f,1.0f,1.0f,    // 9

    		-0.0f, 0.0f, 0, 1.0f, 1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,    // 10

    		-0.5f, 0.0f, 0, 0.0f, 1.0f,1.0f,1.0f,1.0f,1.0f,1.0f,    // 11

     };

    

    private short[] indices = { 

    		0, 1, 2, 2, 1, 3,                                           // 첫번째 4각형
                4, 5, 6, 6, 5, 7,                                           // 두번째 4각형

    		8, 9, 10, 10, 9, 11,                                      //  세번째 4각형

    		};  


 주석에 적어 놓은 것과 같이, vertex 포인터 위치, 알파값 , 색깔 값으로 지정이 되어 있습니다.

[Shader 코드] : VertexShader에는 위치 값을 FrameShader에는 알파와 색깔 값을 설정

private final String mVertexShader =

    "uniform mat4 uMVPMatrix;\n" +

    "attribute vec4 aPosition;\n" +

    "attribute vec2 aTextureCoord;\n" +

    "varying vec2 vTextureCoord;\n" +

    "attribute float aAlpha;\n" +

    "varying float vAlpha; \n" +

    "attribute vec4 aColor;\n" +

    "varying vec4 vColor; \n" +

    "void main() {\n" +

    "  gl_Position = uMVPMatrix * aPosition;\n" +

    "  vTextureCoord = aTextureCoord;\n" +

    "  vAlpha = aAlpha;\n" +

    "  vColor = aColor;\n" +

    "}\n";

private final String mFragmentShader =

    "precision mediump float;\n" +

    "varying vec2 vTextureCoord;\n" +

    "varying float vAlpha; \n" +

    "varying vec4 vColor; \n" +

    "uniform sampler2D sTexture;\n" +

    "void main() {\n" +

    "  gl_FragColor = texture2D(sTexture, vTextureCoord)*vColor*vAlpha;\n" +

    "}\n";

[ Attribute 적용 코드 ] : 버퍼로 초기화를 해준 후, 각각 Attribute에 할당

mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length

                * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();

        mTriangleVertices.put(mTriangleVerticesData).position(0);

        

        

        mTriangleIndics = ByteBuffer.allocateDirect(indices.length

                * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asShortBuffer();

        mTriangleIndics.put(indices).position(0);

        

        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);

        GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,

                TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);

        checkGlError("glVertexAttribPointer maPosition");

        GLES20.glEnableVertexAttribArray(maPositionHandle);

        checkGlError("glEnableVertexAttribArray maPositionHandle");

        

        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);

        GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false,

                TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);

        checkGlError("glVertexAttribPointer maTextureHandle");

        GLES20.glEnableVertexAttribArray(maTextureHandle);

        checkGlError("glEnableVertexAttribArray maTextureHandle");

        

        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_COLOR_OFFSET);

        GLES20.glVertexAttribPointer(maColorHandle, 4, GLES20.GL_FLOAT, false,

                TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);

        checkGlError("glVertexAttribPointer maColorHandle");

        GLES20.glEnableVertexAttribArray(maColorHandle);

        checkGlError("glEnableVertexAttribArray maColorHandle");

        

        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_ALPHA_OFFSET);

        GLES20.glVertexAttribPointer(maAlphaHandle, 1, GLES20.GL_FLOAT, false,

                TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);

        checkGlError("glVertexAttribPointer maAlphaHandle");

        GLES20.glEnableVertexAttribArray(maAlphaHandle);

        checkGlError("glEnableVertexAttribArray maAlphaHandle");

[ Draw 부분] : 3개의 Object에 한 개당 6개의 index 포인트가 있으므로 18을 설정

GLES20.glDrawElements ( GLES20.GL_TRIANGLES, 18, GLES20.GL_UNSIGNED_SHORT, mTriangleIndics );

 

결과 화면 

 
이 소스는 Apidemo에 있는 opengl2.0 샘플을 변경 한 것입니다.

render 소스만 첨부 할터이니~ 참고하세요^^

 

 

 

=======================

=======================

=======================

 

 

 

 

출처: http://gogorchg.tistory.com/entry/Android-Opengl-es-20-%EC%97%AC%EB%9F%AC-Texture%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%A0-%EB%95%8C

[ Android Opengl es 2.0 ] 여러 Texture를 사용할 때

GLES20을 보면 Texture의 갯수를 30개 까지 설정을 할 수 있는 것 같이..(?) ㅋ 위치 변수가 있다.  

하지만, 제가 경험해 본 일 중에.... 8개 이상의 Texture를 할당할 경우...

// Texture 위치
mObjTexLoc = GLES20.glGetUniformLocation ( mProgramObject, "s_texture" );


// Object Texture 설정
GLES20.glActiveTexture ( GLES20.GL_TEXTURE0 );

GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, mObjTexId0 );


GLES20.glActiveTexture ( GLES20.GL_TEXTURE1 );

GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, mObjTexId1 );

GLES20.glActiveTexture ( GLES20.GL_TEXTURE2 );

GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, mObjTexId2 );

GLES20.glActiveTexture ( GLES20.GL_TEXTURE3 );

GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, mObjTexId3 );

GLES20.glActiveTexture ( GLES20.GL_TEXTURE4 );

GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, mObjTexId4 );

....

GLES20.glActiveTexture ( GLES20.GL_TEXTURE8 );

GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, mObjTexId8 );

// Texture 이미지 셋팅
GLES20.glUniform1i ( mObjTexLoc, 0~8 );


다음과 같이 설정 할 수가 있다.

0번 부터 6번 까지는 아주 ~~ 잘되었다.
하지만.. 7번부터... 인덱스가 엉키는지.. 올바른 Texture 이미지가 나오지 않앗다.

그래서 생각해 본 것이!
6번까지는 무리없이 돌아가니~~~ 반복적으로 이용하자 이다.

Texture를 사용하는 자리에서 Texture를 액티브 시키고 바인딩 시키는 것이다.
Texture의 ID는 가지고 있으니깐.. ㅎㅎ

// Object Texture 설정
GLES20.glActiveTexture ( GLES20.GL_TEXTURE0 );

GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, mObjTexId0 );


GLES20.glActiveTexture ( GLES20.GL_TEXTURE1 );

GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, mObjTexId1 );

 ....

// Object Texture 설정
GLES20.glActiveTexture ( GLES20.GL_TEXTURE0 );

GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, mObjTexId7 );


GLES20.glActiveTexture ( GLES20.GL_TEXTURE1 );

GLES20.glBindTexture ( GLES20.GL_TEXTURE_2D, mObjTexId8 ); 


이렇게 하니 꼬이는 버그가 생긱지 않더군요. ㅎㅎ
이건 어디까지는 저의 경험~ ㅎㅎ
참고하세요~` ㅎ

 

 

 

=======================

=======================

=======================

 

 

 

 

 

반응형

 

 

728x90

 

 

 

 

 

 Nexus S 이상 때부터 Opengl을 사용할 때, 
본래 Bitmap 이미지의 크기를 2의 배수로 지정해주지 않으면,
그 Object를 하얗게 뿌려버리더군요.

그래서 다음과 같은 함수를 만들어서 사용을 합니다.

private int getMinPowerByTwo(int value) {

    int result = 2;

    do {

        result *= 2;

    } while (result < value);

    return result;

}


 위 함수는  계속 2씩 곱해 나가다가 value 값 보다 커졌을 경우,
그 값을 리턴 시켜주는 거지요.

즉, 500x168 이라는 이미지가 있을 경우에는
위 함수를 통하여 우선 512 x256의 크기로 Bitmap을 리사이징 하고
Texture에 저장을 시킨 후,
Texture 크기를 500x168로 맞추면 되는 거죠^^

width = getMinPowerByTwo(bmp.getWidth());

height = getMinPowerByTwo(bmp.getHeight());

Bitmap tmpBmp = Bitmap.createScaledBitmap(bmp, width, height, true);

 
먼가 부분적으로 설명을 하여 이해하기가 힘들실지도 모르지만,
분명 어느정도 기초가 쌓여가면서 예제 소스들 보시고
이걸 보시면 아하 ~ 하면서 이해를 금방 하실 수 있을겁니다.^^

그럼 오늘도 즐코딩요~ㅋ 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://gogorchg.tistory.com/entry/Android-AndEngine-%ED%85%8D%EC%8A%A4%EC%B3%90-%EA%B9%A8%EC%A7%90-%ED%98%84%EC%83%81

[ Android : AndEngine ] 텍스쳐 깨짐 현상.

 

Texture windBright_Tx = new Texture(256,256,TextureOptions.BILINEAR_PREMULTIPLYALPHA);

확대 하거나 회전을 할 때, 이미지가 깨지는 것을 종종 볼 수 있다.

텍스쳐에 위와 같은 옵션을 줄 경우, 어느정도 무마되어진다.

참고하세요^^ 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

 

출처: http://gogorchg.tistory.com/entry/Android-Color-%EA%B0%92%EC%9D%84-RGB%EB%A1%9C

[ Android ] Color 값을 RGB로

public void toRGB(int color) {

    float red = color >> 16 & 0xff;

    float green = color >> 8 & 0xff;

    float blue = color & 0xff;

    Log.d("DEBUG1", red + " / " + green + " / " + blue);
}


간단한 함수이다..

그래도 색깔 값을 이용할려면 자주 쓰일 듯하다. ㅎㅎ 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://gogorchg.tistory.com/entry/Android-Opengl-es-20-LiveWallpaper-%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9-%EC%8B%9C-onSurfaceCreated-%EB%AC%B8%EC%A0%9C

 이 문제는 제가 코딩을 잘못 하거나, 샘플 소스를 잘못 이용한 걸 수도 있지만,
경험의 유추했을 때 결론이 나와. 이렇게 글을 남깁니다.
혹시 잘못된 부분이 있으면 주저 말고 댓글을 달아주시면 감사하겠습니다.^^ 



 Opengl es 2.0을 사용하여 개발하는 중.. 화면을 껏다가 다시 켜면..
뭔가 속도가 느려지다가 결국 Out of memory 오류와 함께 팅겨져 버립니다.

역시나 Out of memory 부분은 Texture 부분이 문제 이죠.

전 onSurfaceCreated 함수 내에 Texture를 셋팅 하는 소스를 코딩하였습니다.

loadTexture ( mContext.getResources().openRawResource( R.raw.background) ,"mBgTexId" );


디버깅을 해본 결과... 예전 Opengl es 1.1에서는 화면을 갱신 해도, onSurfaceCreated 함수가 불러지지 않았었습니다.

하지만, Opengl es 2.0으로 바뀌면서 static 메모리 함수로 바뀌고 나서
GLES20와 관련된 메모리 장치들이 초기화가 되는 것 같습니다.
더 웃긴 건... 초기화가 되면서, 메모리 Texture 버퍼들이 삭제가 되지 않는 점입니다.

결국.. 프로그램 실행 시 , 한번만 실행이 되었던 onSurfaceCreated 함수내에 예외 처리가 필요했습니다.

deleteTexture(texId);

texId = loadTexture ( mContext.getResources().openRawResource( R.raw.background) ,"mBgTexId" );


private void deleteTexture(int texId){
    // 전에 저장되어 있는 texture 가 있는지 확인
    if(texId == 0)

    	return;
 

    int idArr[];

    IntBuffer intbuf;

    idArr = new int[]{texId};

    intbuf = getIntBufferFromIntArray(idArr);

    GLES20.glDeleteTextures(1, intbuf);

 }  

   이렇게 계속 갱신 할때마다 Texture를 불러 올 경우, 매번 이미지를 불러와야 하므로 속도가 느려집니다.
결국, TextureBuffer를 저장 시켜놓고, 갱신 할때마다 TextureBuffer를 이용하는 쪽으로 수정하였습니다.
이럴 경우, Texture 설정 시간이 조금 걸리지만 그리 느려지는 현상이 줄어듭니다.

private int loadTexture(InputStream is, String strKey)

{

    int[] textureId = new int[1];
    // 기존의 존재하는지 확인

    if (texBuffer.get(strKey) == null) {

        TexValue value = new TexValue();

        Bitmap bitmap;

        bitmap = BitmapFactory.decodeStream(is);

        bitmap = Bitmap.createScaledBitmap(bitmap, getMinPowerByTwo(bitmap.getWidth()), getMinPowerByTwo(bitmap.getHeight()), false);

        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bitmap.getWidth() * bitmap.getHeight() * 4);

        byteBuffer.order(ByteOrder.BIG_ENDIAN);

        IntBuffer ib = byteBuffer.asIntBuffer();

        int bitmapWidth = bitmap.getWidth();

        int bitmapHeight = bitmap.getHeight();

        int[] pixels = new int[bitmapWidth * bitmapHeight];

        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());

        bitmap.recycle();

        bitmap = null;

        for (int i = 0; i < pixels.length; i++) {

            ib.put(pixels[i] << 8 | pixels[i] >>> 24);

        }

        value.bmpWidth = bitmapWidth;

        value.bmpHeight = bitmapHeight;

        value.texBuffer = byteBuffer;
        // Buffer를 저장 시켜 놓는다.

        texBuffer.put(strKey, value);

    }

    texBuffer.get(strKey).texBuffer.position(0);

    GLES20.glGenTextures(1, textureId, 0);

    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0]);

    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);

    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);

    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

    GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, texBuffer.get(strKey).bmpWidth, texBuffer.get(strKey).bmpHeight, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, texBuffer.get(strKey).texBuffer);

    return textureId[0];

}

 

지금 이건 저의 경험을 토대로 나온 것들입니다.

뭔가 문제가 있다고 생각되시면 언제든지! 꼭! 댓글 부탁드립니다.

감사합니다.


 

=======================

=======================

=======================

 

 

 

 

 

 

출처: http://gogorchg.tistory.com/entry/Android-Opengl%EC%97%90-%ED%85%8D%EC%8A%A4%EC%B3%90%EB%A5%BC-%EC%9E%85%ED%9E%90-%EC%8B%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80%EA%B0%80-%EC%95%88%EB%B3%B4%EC%9D%B4%EB%8A%94-%ED%98%84%EC%83%81

이미지가 보이지 않을 시에는 무엇보다!

벡터와 버퍼의 사이즈를 확실히 확인 한 후,

gl.glEnable(GL10.GL_TEXTURE_2D); //Enable Texture Mapping 

는 설정 되어 있는지!

loadGLTexture(gl, this.context);

함수를 불러 들이셨는지!

 

이래도 보이지 않으시는 분들은,

혹시 BMP를 제외한 JPG나 PNG이미지 파일을 출력할려고 하신다면

bitmap을 리사이징 해줘야합니다.

 

정사각형 형태로.

64x64 , 128,128, 256x256,512x512 등..

Bitmap bitmap256 = Bitmap.createScaledBitmap(bitmap, 512, 512, true);

 

혹시 저같이 헤매시는 분을 위해 적어놓습니다.

죄송합니다. 꼭 저 위 형태가 아니라 가로 세로 길이를 일치 시키기만 하면 됩니다.

80x80,90x90 모두 표현 되요^^ 

-- 다시 죄송...

핸드폰 기기에 따라 다른 것 같습니다.

HTC 디자이어 HD 같은 경우는 꼭 64x64형태로 맞춰야 하지만,

Sky Vega X 같은 경우는 좌우만 일치하면 보이더라구요.

참조하세요^^ 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://gogorchg.tistory.com/entry/Android-Opengl-%ED%85%8D%EC%8A%A4%EC%B3%90-%EB%B3%80%EA%B2%BD-%EC%8B%9C

[Android] Opengl 텍스쳐 변경 시!!!

 제가 하루 동안 갑자기 발생한 Memory over flow 때문에...
원래 제출 해야하는 날보다 하루 미루게 되었던 대 사건의 원인을 적어볼까 합니다. 

- 테스트 폰 : HTC 디자이어 HD, Sky Vega X
- 문제:
  LiveWallpaper의 설정을 여러 번 바꾼 후에, 갑자기 핸드폰이 멈춰버리는 대 사건이!-0- 

- 원인:
  거의 5시간을 걸쳐 디버깅 하고, 구글리을 해서 알아낸 결과!
  heap memory가 사라지지 않고, 계속 쌓이는 것이었습니다.

  처음 Bitmap의 초기화를 잘 못했나 해서 받아들인 Bitmap에 전부 초기화를 했습니다.

 [Bitmap 초기화 방법]

gl.glGenTextures(textures.length , textures, 0);

gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[i]);

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);

gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);

GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

bitmap.recycle();
bitmap = null;
 => 위와 같이 텍스처에 설정을 해 놓으면 다시 Bitmap을 쓸 일이 없기 때문에 초기화를 꼭! 해주세요.


 하지만!!
heap momory는 줄어들지 않고 계속 차지 하고 있는 것입니다.(참고로, 전 이미 모든 Bitmap에 recycle를 해준 상태였음)
그래서 Texture에 문제다! 라고 생각한 저는 바로 구글링을 다시 시작했습니다.
찾아보면.. 거의 Bitmap에 대한 이야기가 대부분 이더군요.

근데!! 문제가 된 것은 단순합니다.

텍스쳐를 처음 설정할 때(추가할 때)는  gl.glGenTextures 로써 포인터를 지정하지만,
텍스쳐를 다른 Bitmap으로 수정할 때는 gl.glGenTextures를 사용하면 안됩니다.!!!!

 
 정말 중요한 부분 입니다. 저 함수를 지정하면 별도의 텍스쳐 주소를 지정해주므로,
 다시 프로그램이 종료가 되지 않은 상태에서는 계속 쌓이게 되어 버립니다.

꼭 텍스쳐를 변경할 부분이 있으시면
load함수와 set함수 2개를 만들어 두세요.

public void loadGLTexture(GL10 gl, Bitmap bitmap) {

    if (bitmap == null) {
        Log.e("DEBUG", "loadGLTexture : Bitmap is null!!!!!!!!!!");
        return;
    }

    //텍스처 포인터 설정
    gl.glGenTextures(textures.length, textures, 0);

    gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[index]);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
    GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

    bitmap.recycle();
    bitmap[i] = null;

}

public void setGLTexture(GL10 gl, Bitmap bitmap) {

    if (bitmap == null) {
        Log.e("DEBUG", "setGLTexture : Bitmap is null!!!!!!!!!!");
        return;
    }

    gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[index]);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
    GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap[i], 0);

    bitmap.recycle();
    bitmap[i] = null;

}


위 두 함수를 참고하세요^^ 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://gogorchg.tistory.com/entry/Android-%EB%B9%84%ED%8A%B8%EB%A7%B5%EC%97%90-%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%82%98-%EB%AD%94%EA%B0%80%EB%A5%BC-%EA%B7%B8%EB%A6%B4-%EB%95%8C-%EB%9C%A8%EB%8A%94-%EC%97%90%EB%9F%AC

java.lang.IllegalStateException: Immutable bitmap passed to Canvas constructor

Bitmap bitmap = BitmapFactory.decodeFile(..);
Canvas canvas = new Canvas(bitmap);

canvas.save();
canvas.drawbitmap(...);
...

canvas.restore();

위 와 같은 형식에 소스에서 디버깅을 하면 회색 네모상자의 에러가 나옵니다. 


BitmapFactory에서 불러온 이미지는 수정이 불가능 하다고 하네요.

크기나 뭐 옵션 같은 것은 줄 수 있겠지만요^^ 

그래서 똑같은 비트맵을 복사함으로써 해결이 됩니다.

Bitmap bitmap = BitmapFactory.decodeFile(..);
Bitmap copyBitmap = bitmap.copy(Bitmap.Config.ARGB_8888,true); 

 
아니면 새로운 비트맵을 생성 해서 그 속에 이미지랑 다른 것들도 함께 넣어버리는 것이지요.

Bitmap paletBmp = Bitmap.createBitmap(imgWidth, imgHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(paletBmp);

.... 



간단하죠??^^ 

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://gogorchg.tistory.com/entry/Android-%EB%B0%B0%EA%B2%BD%ED%99%94%EB%A9%B4%EC%97%90-%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%A5%BC-%ED%91%9C%ED%98%84%ED%95%A0-%EB%95%8C-%EC%B0%B8%EA%B3%A0%EC%82%AC%ED%95%AD

배경화면에 이미지를 출력할 때, 
offset까지 생각을 해서 화면보다 좌우를 더 크게할 경우가 많으시죠.

그럴 경우,  이미지가 커지고 출력할 때 
과부하가 생기기도 합니다.

즉, 큰 이미지의 딱 화면 크기 정도만 가져와서 
표현을 하게 되면, 과부하가 생길 이유가 없겠죠.


하늘색 부분만 보이도록 하는 것이지요!

소스는 다음과 같습니다.

RectF rect = new RectF(0, 0, screenSizeWidth, screenSizeHeight); // 네모 상자 크기 지정
Matrix matrix = new Matrix();
matrix.mapRect(rect);
matrix.setTranslate(mOffsetX, 0); // offset설정
mainCanvas.drawBitmap(mainBmp,matrix,mainPaint); // 이미지에 적용해서 출력

 

 

 

=======================

=======================

=======================

 

 

 

 

 

출처: http://3d-graphics.org/doku.php?id=opengl:example

개요

OpenGL 3 버전 이상을 쓰고 싶었다. 그런데 버전 지정하는 게 안되서 그냥 했는데 어짜피 확장 이용하는 거라 잘 된다. 이 예제는 VC10으로 작성하였고, C++0x의 문법을 조금 사용하고 있으나 읽는데 그렇게 큰 무리는 없다. 수학 라이브러리는 glm을, 이미지 라이브러리는 DevIL을 사용하였다. 좌표계는 OpenGL 좌표계를 그대로 사용한다.

프로젝트 세팅

glew

http://glew.sourceforge.net/ 여기에서 코드와 바이너리를 얻을 수 있다. OpenGL 확장을 검사해서 함수 포인터를 세팅한다. OpenGL 프로그래밍에 거의 필수이다.

  • Include Path ..\glew\win32\glew-1.5.6\include
  • Library Path ..\glew\win32\glew-1.5.6\lib
  • DLL Copy glew32.dll

glm

http://glm.g-truc.net/ 여기에서 코드를 얻는다. include 디렉토리가 위치하는 경로를 프로젝트 include 경로에 추가하면 된다.

  • Include Path ..\glm-0.9.0.3

DevIL

http://openil.sourceforge.net/ 여기에서 코드 혹은 바이너리를 얻는다. DevIL은 현재 LGPL 라이선스이기 때문에 동적 링크만 허용된다. 논의에 BSD 라이선스로의 전환 얘기가 있는데, 그렇게 된다면 정말 좋을 것이다. 나는 Win32로 빌드 된 것을 받아서 사용하였다.

  • Include Path ..\DevIL-SDK-x86-1.7.8\include
  • Library Path ..\DevIL-SDK-x86-1.7.8\lib\unicode
  • DLL Copy DevIL.dll ILU.dll ILUT.dll

코드와 설명

윈도우 만들기

평범하게 WIN32API를 이용하여 윈도우를 만든다.

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) {
	setlocale(LC_ALL, "");
 
	WNDCLASSEX wc = { 
		sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
		GetModuleHandle(NULL), NULL, LoadCursor(NULL, IDC_ARROW), NULL, NULL,
		CLASS_NAME, NULL
	};
 
	::RegisterClassEx(&wc);
 
	HWND hWnd = ::CreateWindow(CLASS_NAME, APP_NAME,
		/*WS_POPUP*/ WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU, 
		0L, 0L, WIN_WIDTH, WIN_HEIGHT,
		NULL, NULL, wc.hInstance, NULL);

윈도우의 작업 영역이 정확히 캔버스 사이즈와 같도록 조정한다.

// set canvas size to (WIN_WIDTH, WIN_HEIGHT)
RECT canvas;
::GetClientRect(hWnd, &canvas);
::SetWindowPos(hWnd, 0U, 0, 0, WIN_WIDTH + WIN_WIDTH - canvas.right, WIN_HEIGHT + WIN_HEIGHT - canvas.bottom, SWP_NOZORDER | SWP_NOMOVE);

디버깅을 위해 콘솔 윈도우도 띄우도록 하자.

// create the console window
::AllocConsole();
freopen("CONOUT$", "wt", stdout);::SetConsoleTitle(L "Debug Console");::SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
	   FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);
RECT rect;::GetWindowRect(hWnd, & rect);::SetWindowPos(::GetConsoleWindow(), NULL, rect.right, 0, 0, 0, SWP_NOSIZE);

OpenGL 초기화를 한다. 함수의 구현은 아래에서 설명할 것이다.

// init OpenGL
try {
    CreateGLContext(hWnd);
} catch (wchar_t * msg) {
    //------------------------
    DWORD err = ::GetLastError();
    printf("err code: %d (0x%x)\n", err, err);
    //------------------------
    ::MessageBox(hWnd, msg, L "Error", MB_OK | MB_ICONERROR);
    goto exit;
}

윈도우를 화면에 나타내고,

::ShowWindow(hWnd, SW_SHOWDEFAULT); ::SetForegroundWindow(hWnd);

메시지 루프를 돈다. 윈도우 타이틀에 FPS를 출력하도록 했다. 윈도우 메시지 처리 콜백과 FPS스타일의 카메라 이동 함수는 본 튜터리얼에서 중요하지 않으므로 넣지 않았다.

MSG msg;::ZeroMemory( & msg, sizeof(msg));

while (msg.message != WM_QUIT) {
    if (PeekMessage( & msg, NULL, 0 U, 0 U, PM_REMOVE)) {
        TranslateMessage( & msg);
        DispatchMessage( & msg);
    } else {
        //----------------------------------------------------------
        static float last = ::GetTickCount() / 1000.0 f;
        float current = ::GetTickCount() / 1000.0 f;
        float dt = current - last;
        last = current;
        //----------------------------------------------------------
        static float t_acc = 0.0 f;
        static int c_frame = 0;
        t_acc += dt;
        c_frame++;
        if (t_acc >= 1.0 f) {
            wchar_t buf[256];
            swprintf_s(buf, 256, L "%ls FPS: %d", APP_NAME, (int)(c_frame / t_acc));::SetWindowTextW(hWnd, buf);

            t_acc = 0.0 f;
            c_frame = 0;
        }
        //----------------------------------------------------------

        FPSCamera(dt);
        Render(dt);
        //::Sleep(1);
    }
}

메시지 루프가 끝나면 종료한다.

exit:
    // deinit OpenGL
    Clean(hWnd);

// delete the console window
::FreeConsole();

::UnregisterClass(CLASS_NAME, wc.hInstance);
return 0;
}

가능한 픽셀 포맷 얻기

픽셀 포맷을 얻기 위해서는 더미 윈도우를 하나 만들어서 그 DC에서 작업을 하는 게 좋다. 왜냐면 한 DC에 대해 SetPixelFormat을 한 번 해버리면 MSAA나 프레임 버퍼 sRGB 옵션을 바꾸고 다시 SetPixelFormat을 하면 2000번 에러1)가 발생하기 때문이다. 그러니 먼저 더미 창을 만드는 코드를 보도록 하자. 관련 논의

LRESULT CALLBACK MsgProcDummy(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

void CheckPixelFormat(std::vector < int > & samples) {
        // make a dummy window
        const wchar_t * dummyLabel = L "OpenGL_Pixel_Format_Test_Class";
        HINSTANCE hInst = ::GetModuleHandle(NULL);
        WNDCLASS wcDummy = {
            CS_OWNDC,
            & MsgProcDummy,
            0,
            0,
            hInst,
            NULL,
            NULL,
            NULL,
            NULL,
            dummyLabel
        };::RegisterClass( & wcDummy);
        HWND hWndDummy = ::CreateWindow(dummyLabel, dummyLabel, WS_POPUP | WS_CLIPCHILDREN, 0, 0, 32, 32, 0, 0, hInst, 0);

이제 윈도우에서 기본으로 지원하는 픽셀 포맷을 이용해 렌더링 컨텍스트를 만든다. 여기서 주의 할 부분은 컬러 비트는 RGB에만 해당 된다는 점이다. 그래서 24로 쓰고 알파 비트를 추가로 8로 써 주어야 한다. 명세는 이렇다 하는데 그냥 32로 써도 여기서는 잘 되었다. 관련 논의

// choose pixel format
PIXELFORMATDESCRIPTOR pfd;
memset( & pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;
pfd.cAlphaBits = 8;
pfd.cDepthBits = 24;
pfd.cStencilBits = 8;
pfd.iLayerType = PFD_MAIN_PLANE;

이제 DC를 얻고 픽셀 포맷을 고른다.

HDC hDummyDC = ::GetDC(hWndDummy);

int pf = ::ChoosePixelFormat(hDummyDC, & pfd);
if (pf == 0)
    throw L "gl error: ChoosePixelFormat";

BOOL result = ::SetPixelFormat(hDummyDC, pf, & pfd);
if (result == FALSE)
    throw L "gl error: SetPixelFormat";

픽셀 포맷이 골라 졌다면 렌더링 컨텍스트를 만들고 활성화 한다. 이 더미 윈도우는 곧 해제 될 것이므로 예전 DC와 컨텍스트를 얻어 두도록 한다.

HGLRC old_hRC = wglGetCurrentContext();
HDC old_hDC = wglGetCurrentDC();

HGLRC hDummyRC = wglCreateContext(hDummyDC);
wglMakeCurrent(hDummyDC, hDummyRC);

일단 간단하게 하기 위해 여기서 glewInit를 실행 하였다. 이 함수는 렌더링 컨텍스트를 사용 중 일 때에만 호출 할 수 있다.

// after this line, it can use WGL extensions.
GLenum err = glewInit();
if (err != GLEW_OK)
    throw L "gl error: glewInit";

glewInit를 호출하였으니 확장을 쓸 수 있다. 멀티샘플링 레벨을 체크하도록 하자.

// check AA
if (WGLEW_ARB_pixel_format && WGLEW_ARB_multisample) {
    const int attribList[] = {
        WGL_DRAW_TO_WINDOW_ARB,
        GL_TRUE,
        WGL_ACCELERATION_ARB,
        WGL_FULL_ACCELERATION_ARB,
        WGL_SUPPORT_OPENGL_ARB,
        GL_TRUE,
        WGL_DOUBLE_BUFFER_ARB,
        GL_TRUE,
        WGL_SAMPLE_BUFFERS_ARB,
        1, // AA를 켜고,
        WGL_SAMPLES_ARB,
        2, // 일단 제일 낮은 2점 샘플링으로 설정한다.
        /* 여기에선 컬러 비트나 뎁스 버퍼 설정이 필요 없다 */
        0
    };

    int formats[256];
    unsigned int count = 0;

    if (wglChoosePixelFormatARB(g_hDC, attribList, NULL, 256, formats, & count)) { // 이 함수는 주어진 포맷과 비슷한 포맷을 모두 리턴한다.
        for (int i = 0; i < count; i++) { // 리턴된 포맷을 순회하면서,
            int q = WGL_SAMPLES_ARB, v;
            if (wglGetPixelFormatAttribivARB(g_hDC, formats[i], 0, 1, & q, & v)) { // 샘플링 점을 질의 하면 몇 점 샘플링인지 알 수 있다.
                if (std::find(samples.begin(), samples.end(), v) == samples.end()) // 새로운 샘플링 모드가 있다면 추가
                    samples.push_back(v);
            }
        }
    }

    std::sort(samples.begin(), samples.end()); // 순서대로 정렬
}

할 것은 다 했으므로 더미 윈도우를 삭제하고 빠져 나온다.

// destroy the dummy window
wglMakeCurrent(old_hDC, old_hRC);
wglDeleteContext(hDummyRC);::ReleaseDC(hWndDummy, hDummyDC);::DestroyWindow(hWndDummy);::UnregisterClass(dummyLabel, hInst);
}

이제 몇 점 샘플링이 가능한지 알 수 있게 되었다.

실제 렌더링 컨텍스트 만들기

먼저 컨텍스트에 관련된 전역 변수를 선언한다.

HDC g_hDC = NULL; HGLRC g_hRC = NULL;

방금 만든 가능한 AA 샘플을 얻어오도록 하자. 가능한 샘플링 점과 WGL_EXT_framebuffer_sRGB가 지원되는 지 화면에 출력했다. WGL_EXT_framebuffer_sRGB는 나중에 셰이딩 할 때 감마 계산에 필요한 옵션이다. 이번에는 사용하지 않는다.

void CreateGLContext(HWND hWnd) {
        std::vector < int > samples;
        CheckPixelFormat(samples);

        //------------------------
        printf("Available AA Samples: ");
        for (int i = 0; i < samples.size(); i++)
            printf("x%d ", samples[i]);
        printf("\n");
        printf("sRGB Frame Buffer: %s\n", (WGL_EXT_framebuffer_sRGB ? "yes" : "no"));
        //------------------------

렌더링 컨텍스트를 만든다. 먼저 캔버스 윈도우의 DC를 얻는다. 그리고 가능한 픽셀 포맷을 얻었을 때와 마찬가지로 픽셀 포맷을 세팅한다.

BOOL result;
g_hDC = ::GetDC(hWnd);

PIXELFORMATDESCRIPTOR pfd;
memset( & pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24; // only RGB bits
pfd.cAlphaBits = 8;
pfd.cDepthBits = 24;
pfd.cStencilBits = 8;
pfd.iLayerType = PFD_MAIN_PLANE;

ARB 픽셀 포맷이 사용가능하고 멀티 샘플링 혹은 sRGB를 이용할 수 있다면 wglChoosePixelFormatARB를 통해 신버전의 픽셀 포맷을 고른다. 만약 ARB 픽셀 포맷이 사용 불가능 하다면 예전 방식대로 진행한다.

if (WGLEW_ARB_pixel_format && (WGLEW_ARB_multisample || WGL_EXT_framebuffer_sRGB)) {
    int attribList[] = {
        WGL_DRAW_TO_WINDOW_ARB,
        GL_TRUE,
        WGL_ACCELERATION_ARB,
        WGL_FULL_ACCELERATION_ARB,
        WGL_SUPPORT_OPENGL_ARB,
        GL_TRUE,
        WGL_DOUBLE_BUFFER_ARB,
        GL_TRUE,
        WGL_PIXEL_TYPE_ARB,
        WGL_TYPE_RGBA_ARB,
        WGL_COLOR_BITS_ARB,
        pfd.cColorBits,
        WGL_ALPHA_BITS_ARB,
        pfd.cAlphaBits,
        WGL_DEPTH_BITS_ARB,
        24,
        WGL_STENCIL_BITS_ARB,
        8,
        WGL_SAMPLE_BUFFERS_ARB,
        (samples.size() > 0 ? 1 : 0),
        WGL_SAMPLES_ARB,
        (samples.size() > 0 ? samples.back() : 0),
        /*WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT, GL_TRUE,*/
        0
    };

    int pf;
    UINT numFormats;
    result = wglChoosePixelFormatARB(g_hDC, attribList, NULL, 1, & pf, & numFormats);
    if (result == FALSE)
        throw L "gl ARB error: wglChoosePixelFormatARB";
    else if (numFormats == 0)
        throw L "gl ARB error: There is no matched pixel format.";

    result = ::SetPixelFormat(g_hDC, pf, NULL);
    if (result == FALSE)
        throw L "gl ARB error: SetPixelFormat";
} else {
    int pf = ::ChoosePixelFormat(g_hDC, & pfd);
    if (pf == 0)
        throw L "gl error: ChoosePixelFormat";

    BOOL result = ::SetPixelFormat(g_hDC, pf, & pfd);
    if (result == FALSE)
        throw L "gl error: SetPixelFormat";
}

이제 픽셀 포맷을 골랐으니 렌더링 컨텍스트를 만든다. 그리고 버전을 화면에 출력한다.

g_hRC = wglCreateContext(g_hDC);
wglMakeCurrent(g_hDC, g_hRC);

// 숫자로 얻고 싶은 경우 이것으로 사용.
//int version[2];
//glGetIntegerv(GL_MAJOR_VERSION, &version[0]);
//glGetIntegerv(GL_MINOR_VERSION, &version[1]);

printf("%s\n", glGetString(GL_VERSION));

이제 렌더링 컨텍스트가 만들어 졌다.

프로젝션, 뷰 매트릭스 생성

앞으로 사용하게 될 프로젝션, 뷰 매트릭스를 먼저 만들어 두도록 하자. 전역으로 선언할 것이다.

glm::mat4 g_matProj; glm::mat4 g_matView;

이제 내용을 채우도록 한다. 이는 어디에서 행해도 문제가 없는 코드다.

g_matProj = glm::perspective(45.0 f, (float) WIN_WIDTH / WIN_HEIGHT, 1.0 f, 1000.0 f);
g_matView = glm::lookAt(glm::vec3(0.0 f, 25.0 f, 50.0 f), glm::vec3(0.0 f, 15.0 f, 0.0 f), glm::vec3(0.0 f, 1.0 f, 0.0 f));

WIN_WIDTH와 WIN_HEIGHT는 캔버스 크기다.

셰이더 작성

OpenGL에는 Direct3D의 fx같은 건 없다2). 따라서 렌더링 스테이트, 텍스쳐 필터 등을 모두 클라이언트에서 C++코드로 작성해서 컨트롤 한다. 물론 버텍스, 프래그먼트, 지오메트리 셰이더도 각각 다른 파일로 작성하는 게 보통이다. 이게 성능을 떠나서 은근히 불편하기 때문에 파일 하나에 모두 작성하고 전처리기로 처리하도록 한다.

#if defined(VERTEX_SHADER)

    in vec3 in_Position; in vec3 in_Normal; in vec2 in_ST; in vec4 in_Tangent;
out vec2 ex_ST;
centroid out vec4 ex_Depth;
centroid out vec4 ex_Normal;
centroid out vec4 ex_Tangent;
centroid out vec4 ex_Binormal;

layout(std140) uniform BaseParam {
    mat4 proj;
    mat4 view;
    mat4 model;
    mat4 modelInverseTransformed; // for scaling
};

void main() {
    vec4 viewSpacePos = view * model * vec4(in_Position, 1.0 f);
    gl_Position = proj * viewSpacePos;

    ex_ST = in_ST;
    ex_Depth = viewSpacePos; // will be convert to depth in fragment shader
    ex_Normal = vec4(mat3(view) * normalize(mat3(modelInverseTransformed) * in_Normal), 0.0 f);
    ex_Tangent = vec4(mat3(view) * normalize(mat3(modelInverseTransformed) * in_Tangent.xyz), 0.0 f);
    ex_Binormal = vec4(cross(ex_Tangent.xyz, ex_Normal.xyz), 0.0 f) * in_Tangent.w;
}

#elif defined(GEOMETRY_SHADER)

// no geom shader

#elif defined(FRAGMENT_SHADER)

precision highp float;

in vec2 ex_ST;
centroid in vec4 ex_Depth;
centroid in vec4 ex_Normal;
centroid in vec4 ex_Tangent;
centroid in vec4 ex_Binormal;
out vec4 out_Color;

uniform sampler2D diffuse;
uniform sampler2D normal;
uniform sampler2D specular;
uniform sampler2D emissive;

void main() {
    #if defined(NORMAL_MAP)
    // calc normal map
    vec3 normal_ts = texture2D(normal, ex_ST).rgb * 2.0 f - 1.0 f;
    mat3 TBN = mat3(ex_Tangent.xyz, ex_Binormal.xyz, ex_Normal.xyz);
    vec3 normal_vs = TBN * normal_ts;
    out_Color = vec4(normal_vs, 1.0 f);
    #else
    out_Color = vec4(ex_Normal.xyz, 1.0 f);
    #endif
}

#endif

여기서 주의할 점은 #endif 다음에 엔터를 하나만 입력하면 컴파일 에러

가 난다. 지포스, 라데온 동일하며 #endif 다음에 엔터를 하나도 입력하지 않거나 두 개 이상 넣으면 정상적으로 컴파일된다. 예전과 다르게 in, out이라는 게 보인다. HLSL을 쓰던 사람에게는 매우 익숙 한 구조이겠지만 기존 GLSL과는 다르기 때문에 약간 헷갈릴 수 있다. 버텍스 셰이더에서의 in은 기존의 attribute와 같고, out은 varying과 같다. 프래그먼트 셰이더에서의 in은 기존의 varying과 같다. out은 좀 애매한데, 예전에는 gl_FragColor로 출력하던 것을 out 변수로 넘겨주었다고 보면 될 것이다. 그리고 멀티 샘플링을 사용할 것이므로 centroid 옵션도 추가해 주었다.

유니폼 버퍼 오브젝트

GLSL 셰이더 프로그램 간에 공유되는 유니폼 변수가 있다면 Uniform Buffer Object를 이용하여 공유한다. 유니폼 버퍼 오브젝트는 공유 뿐만 아니라 다른 추가적인 레이아웃도 있지만 대부분 공유의 목적으로 사용할 것이다. 여기서는 노멀맵을 사용하는 프로그램과 사용하지 않는 프로그램 간에 BaseParam이라는 유니폼 블럭을 공유하게 될 것이다. 먼저 유니폼 버퍼 오브젝트에 관련된 변수를 선언한다.

struct UNIFORM_BLOCK_BASE_PARAM {
    glm::mat4 proj;
    glm::mat4 view;
    glm::mat4 model;
    glm::mat4 mit;
}
g_uniformBlockBaseParam;

unsigned int g_uniformBlockBaseParamBuffer = 0; // 버퍼 ID가 들어갈 변수
int g_uniformBlockBaseParamBufferSize = sizeof(UNIFORM_BLOCK_BASE_PARAM); // 버퍼의 크기. 여기에선 256bytes.
int g_uniformBlockBaseParamIndex = 0; // 이 유니폼 버퍼는 0번으로 할 것임

여기서 사용할 셰이더 코드에서는 공유되는 유니폼 변수가 4개 있는데, 프로젝션, 뷰, 모델 그리고 노멀을 변형할 때 사용할 모델 인버스 트랜스포즈 매트릭스이다. g_uniformBlockBaseParamIndex라는 특이한 변수가 있는데 이는 아래에서 다시 다룰 것이다. 이제 유니폼 블럭과 연동할 유니폼 버퍼를 만들도록 한다.

// create shared uniform block
g_uniformBlockBaseParam.proj = g_matProj;
g_uniformBlockBaseParam.view = g_matView;
g_uniformBlockBaseParam.model = glm::mat4(1.0 f);
g_uniformBlockBaseParam.mit = glm::mat4(1.0 f);

glGenBuffers(1, & g_uniformBlockBaseParamBuffer);
glBindBuffer(GL_UNIFORM_BUFFER, g_uniformBlockBaseParamBuffer);
glBufferData(GL_UNIFORM_BUFFER, sizeof(UNIFORM_BLOCK_BASE_PARAM), & g_uniformBlockBaseParam, GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);

일반적인 버퍼 생성과 똑같다. 버퍼에 값을 복사하기 전에 미리 프로젝션과 뷰 매트릭스를 넣어주었다. 유니폼 블럭은 거의 매 그리기 함수마다 값이 수정될 것이므로 GL_DYNAMIC_DRAW로 버퍼를 생성하였다. 그리고 위에서 선언한 g_uniformBlockBaseParamIndex라는 특이한 변수를 이용할 것이다.

glBindBufferBase(GL_UNIFORM_BUFFER, g_uniformBlockBaseParamIndex, g_uniformBlockBaseParamBuffer);
// 버퍼의 일부분에만 ID를 매기기 위해서는 glBindBufferRange를 사용하면 된다.

Buffer Object의 페이지를 보면 자세하게 나온다. 간단하게 말해서 GL_UNIFORM_BUFFER 혹은 GL_TRANSFORM_FEEDBACK_BUFFER 타입의 버퍼에 0부터 시작하는 ID를 매길 수 있다. 그리고 이 ID를 나중에 셰이더 프로그램의 유니폼 블럭에 바인딩 하게 될 것이다. 이러한 방식의 공유 변수를 만드는 것은 D3D의 셰이더 풀을 이용한 것 보다 훨씬 복잡하고 귀찮은 작업이다. 하지만 더 직관적인데, 셰이더 프로그램에서 유니폼 블럭의 내용은 GPU 메모리에 있는 버퍼와 연결이 되어있고, 그 버퍼를 수정하여 유니폼 변수를 수정한다는 컨샙이다. 주의할 점은 D3D9는 셰이더 파라메터 수정을 바로 적용하지 않고 CommitChanges() 혹은 드로우 콜 때 적용하지만, 유니폼 블럭을 업데이트 하기 위해 버퍼를 수정하는 작업은 바로 커밋을 유발한다는 것이다. 따라서 수정된 내용을 따로 모아서 한번에 커밋하는 로직을 수작업으로 작성해야 한다. 여기에선 간단하게 하기 위해 그런것은 신경 쓰지 않았다

. 여튼 유니폼 블럭은 버퍼에 직접 연결 되므로 OpenCL 등과 연동이 아주 자연스럽게 된다.

셰이더 컴파일

셰이더는 노멀맵을 사용하는 프로그램과 사용하지 않는 프로그램 두 개를 컴파일 할 것이다. 이는 위에서 보았듯이 셰이더 코드에서 전처리기로 처리되어있다. 먼저 셰이더 프로그램과 유니폼 변수의 인덱스를 저장할 전역 변수를 선언한다.

// 노멀맵을 사용하지 않는 셰이더 프로그램
GLhandleARB g_hBaseProgram = NULL;
GLhandleARB g_hBaseVertShader = NULL;
GLhandleARB g_hBaseFragShader = NULL;

GLint g_hBaseUniformBlockBaseParam = 0;

GLint g_hBaseUniformDiffuse = 0;
GLint g_hBaseUniformNormal = 0;
GLint g_hBaseUniformSpecular = 0;
GLint g_hBaseUniformEmissive = 0;

// 노멀맵을 사용하는 셰이더 프로그램
GLhandleARB g_hNormalmapProgram = NULL;
GLhandleARB g_hNormalmapVertShader = NULL;
GLhandleARB g_hNormalmapFragShader = NULL;

GLint g_hNormalmapUniformBlockBaseParam = 0;

GLint g_hNormalmapUniformDiffuse = 0;
GLint g_hNormalmapUniformNormal = 0;
GLint g_hNormalmapUniformSpecular = 0;
GLint g_hNormalmapUniformEmissive = 0;

셰이더 프로그램 오브젝트를 만들고 glsl 파일을 읽는다. 일반적으로 .vert 파일과 .frag 처럼 셰이더를 각 파일에 나누어 놓는데 나는 그게 불편해서 #if defined(VERTEX_SHADER) 등의 전처리기로 처리했다.

// create the shader program
g_hBaseProgram = glCreateProgramObjectARB();

std::string source = [](const char * filename) - > std::string {
    FILE * fd = fopen(filename, "rt");
    if (fd == NULL)
        throw "Cannot found the shader file";

    std::string context;
    while (!feof(fd)) {
        char buf[256];
        fgets(buf, 256, fd);
        context += buf;
    }

    fclose(fd);

    return context;
}("base.glsl");

셰이더가 제대로 컴파일 되었는지 확인 할 함수를 만든다. ATi 카드에서는 컴파일이 성공하면 성공했다는 메시지를 돌려주고, NVIDIA는 아무런 메시지를 돌려주지 않는다.

// check shaders' compile errors or informations
auto CheckShader = [ & hWnd](GLhandleARB & handle, GLenum type) - > void {
    int stat = GL_FALSE;
    glGetObjectParameterivARB(handle, type, & stat); // 에러가 있는지 얻어 옴

    int len = 0;
    char log[512];
    glGetInfoLogARB(handle, 512, & len, log); // 메시지가 있다면 얻어 옴

    if (len > 0)
        printf("%s\n", log);

    if (stat == GL_FALSE) {
        ::MessageBoxA(hWnd, log, "Error", MB_OK | MB_ICONERROR);
        throw "Shader error occured";
    }
};

버텍스 셰이더와 프래그먼트 셰이더를 컴파일 한다. 여기서는 GLSL 버전 1.5를 사용한다.

// create the vertex shader
std::string vertSource = "#version 150\n#define VERTEX_SHADER\n" + source;
const GLchar * vs = vertSource.c_str();

g_hBaseVertShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
glShaderSourceARB(g_hBaseVertShader, 1, & vs, NULL);
glCompileShaderARB(g_hBaseVertShader);
CheckShader(g_hBaseVertShader, GL_COMPILE_STATUS);

// create the fragment shader
std::string fragSource = "#version 150\n#define FRAGMENT_SHADER\n" + source;
const GLchar * fs = fragSource.c_str();

g_hBaseFragShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
glShaderSourceARB(g_hBaseFragShader, 1, & fs, NULL);
glCompileShaderARB(g_hBaseFragShader);
CheckShader(g_hBaseFragShader, GL_COMPILE_STATUS);

각 셰이더가 컴파일 되었으니 링크한다. 이 때에 버텍스 셰이더의 in 변수의 위치를 직접 지정해 줄 수 있다. 셰이더 코드에 작성한 순서대로 지정하도록 한다.

// link the shader program
glAttachObjectARB(g_hBaseProgram, g_hBaseVertShader);
glAttachObjectARB(g_hBaseProgram, g_hBaseFragShader);

glBindAttribLocation(g_hBaseProgram, 0, "in_Position");
glBindAttribLocation(g_hBaseProgram, 1, "in_Normal");
glBindAttribLocation(g_hBaseProgram, 2, "in_ST");
glBindAttribLocation(g_hBaseProgram, 3, "in_Tangent");

glLinkProgramARB(g_hBaseProgram);
CheckShader(g_hBaseProgram, GL_LINK_STATUS);

각 변수의 위치를 얻어 온다.

// bind shader vars
glUseProgramObjectARB(g_hBaseProgram);

g_hBaseUniformDiffuse = glGetUniformLocationARB(g_hBaseProgram, "diffuse");
g_hBaseUniformNormal = glGetUniformLocationARB(g_hBaseProgram, "normal");
g_hBaseUniformSpecular = glGetUniformLocationARB(g_hBaseProgram, "specular");
g_hBaseUniformEmissive = glGetUniformLocationARB(g_hBaseProgram, "emissive");

마지막으로 유니폼 블럭의 위치를 얻어오고 버퍼를 지정한다.

g_hBaseUniformBlockBaseParam = glGetUniformBlockIndex(g_hBaseProgram, "BaseParam");
glGetActiveUniformBlockiv(g_hBaseProgram, g_hBaseUniformBlockBaseParam, GL_UNIFORM_BLOCK_DATA_SIZE, & g_uniformBlockBaseParamBufferSize);
glUniformBlockBinding(g_hBaseProgram, g_hBaseUniformBlockBaseParam, g_uniformBlockBaseParamIndex);

g_uniformBlockBaseParamIndex가 쓰였음을 확인 할 수 있다. 이제 노멀맵을 사용하는 셰이더를 컴파일 할 차례다.

// create the program using normal map
g_hNormalmapProgram = glCreateProgramObjectARB();

// create the vertex shader
std::string vertSource2 = "#version 150\n#define VERTEX_SHADER\n#define NORMAL_MAP\n" + source;
const GLchar * vs2 = vertSource2.c_str();

g_hNormalmapVertShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
glShaderSourceARB(g_hNormalmapVertShader, 1, & vs2, NULL);
glCompileShaderARB(g_hNormalmapVertShader);
CheckShader(g_hNormalmapVertShader, GL_COMPILE_STATUS);

// create the fragment shader
std::string fragSource2 = "#version 150\n#define FRAGMENT_SHADER\n#define NORMAL_MAP\n" + source;
const GLchar * fs2 = fragSource2.c_str();

g_hNormalmapFragShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
glShaderSourceARB(g_hNormalmapFragShader, 1, & fs2, NULL);
glCompileShaderARB(g_hNormalmapFragShader);
CheckShader(g_hNormalmapFragShader, GL_COMPILE_STATUS);

// link the shader program
glAttachObjectARB(g_hNormalmapProgram, g_hNormalmapVertShader);
glAttachObjectARB(g_hNormalmapProgram, g_hNormalmapFragShader);

glBindAttribLocation(g_hNormalmapProgram, 0, "in_Position");
glBindAttribLocation(g_hNormalmapProgram, 1, "in_Normal");
glBindAttribLocation(g_hNormalmapProgram, 2, "in_ST");
glBindAttribLocation(g_hNormalmapProgram, 3, "in_Tangent");

glLinkProgramARB(g_hNormalmapProgram);
CheckShader(g_hNormalmapProgram, GL_LINK_STATUS);

// uniform block
g_hNormalmapUniformBlockBaseParam = glGetUniformBlockIndex(g_hNormalmapProgram, "BaseParam");
glGetActiveUniformBlockiv(g_hNormalmapProgram, g_hNormalmapUniformBlockBaseParam, GL_UNIFORM_BLOCK_DATA_SIZE, & g_uniformBlockBaseParamBufferSize);
glUniformBlockBinding(g_hNormalmapProgram, g_hNormalmapUniformBlockBaseParam, g_uniformBlockBaseParamIndex);

g_hNormalmapUniformDiffuse = glGetUniformLocationARB(g_hNormalmapProgram, "diffuse");
g_hNormalmapUniformNormal = glGetUniformLocationARB(g_hNormalmapProgram, "normal");
g_hNormalmapUniformSpecular = glGetUniformLocationARB(g_hNormalmapProgram, "specular");
g_hNormalmapUniformEmissive = glGetUniformLocationARB(g_hNormalmapProgram, "emissive");

별반 다를 바는 없고 #define NORMAL_MAP이 추가되었다는 것에만 주목하면 된다.

렌더 스테이트 변경

본격적으로 렌더링에 들어가기 전에 렌더 스테이트를 지정하도록 한다.

glViewport(0, 0, WIN_WIDTH, WIN_HEIGHT); // 뷰포트 세팅

// GL Satatus settings
glEnable(GL_MULTISAMPLE_ARB); // 멀티 샘플링을 사용한다.

glClearColor(0.25 f, 0.25 f, 0.25 f, 0.0 f); // 클리어 컬러를 지정
glClearStencil(0); // 스텐실을 초기화 할 값을 지정
glClearDepth(1.0 f); // 깊이를 초기화 할 값을 지정

glEnable(GL_DEPTH_TEST); // 깊이 테스트를 켠다.
glDepthFunc(GL_LEQUAL); // 깊이가 더 작거나 같을 때 픽셀을 그린다.

glEnable(GL_TEXTURE_2D); // 2D 텍스쳐를 사용한다.

// 텍스쳐 필터를 지정(밉맵 사용)
for (int i = 0; i < 4; i++) {
    glTexParameteri(GL_TEXTURE0 + i, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE0 + i, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}

모델 로드

렌더링 할 준비는 되었으나 렌더링 할 모델이 없다. 렌더링 할 모델을 로드하도록 한다. 3D Studio Max Exporter로 뽑은 모델을 로딩할 것이다.

자료 구조

모델 구조체와 그 안에 들어갈 버텍스, 텍스쳐 정보 등을 작성한다. 버텍스 버퍼는 두가지로 작성한다.

  • Interleaved Buffer
    buffer: [VERTEX0, VERTEX1, VERTEX2, …, VERTEXn]
  • Separated Buffer
    buffer[0]: [POSITION0, POSITION1, POSITION2, …, POSITIONn]
    buffer[1]: [NORMAL0, NORMAL1, NORMAL2, …, NORMALn]
    buffer[2]: [ST0, ST1, ST2, …, STn]

Interleaved Buffer는 D3D에서 쓰는 것과 동일한 방식이다. 버텍스 포맷이 고정된다는 단점이 있지만 캐시 히트율이 높아서 렌더링이 더 빠른 장점이 있다. Separated Buffer는 각 어트리뷰트가 버퍼를 따로 가지고 있다. 따라서 필요한 어트리뷰트만 바인드해서 그릴 수 있으므로 굉장히 유연하다. Interleaved Buffer가 일반적으로 더 빠르다고 알려져 있는데, 내 경험상 NVIDIA에서는 가시적인 차이가 보였지만 ATi에서는 그게 그거 같다. 좀 더 자세히 논의 해 볼 가치가 있는 주제다.

#ifndef __OPENGL_TEST_MODEL_H__
#define __OPENGL_TEST_MODEL_H__

struct VERTEX {
    glm::vec3 position;
    glm::vec3 normal;
    glm::vec2 st;
    glm::vec4 tangent;
};

버텍스 구조다. D3D9의 버텍스와 다른 점은 없다. 이제 모델 구조체를 만든다.

struct MODEL {
    glm::mat4 model_matrix;

    struct {
        std::vector < VERTEX > data;

        unsigned int vbo;
    }
    interleaved;

    struct {
        struct {
            std::vector < glm::vec3 > position;
            std::vector < glm::vec3 > normal;
            std::vector < glm::vec2 > st;
            std::vector < glm::vec4 > tangent;
        }
        data;

        union {
            unsigned int vbos[4];
            struct {
                unsigned int position;
                unsigned int normal;
                unsigned int st;
                unsigned int tangent;
            }
            vbo;
        };
    }
    separated;

    std::vector < unsigned short > indices;
    unsigned int ibo;

    int num_vertices;
    int num_indices;

    union {
        unsigned int textures[4];
        struct TEXTURES {
            unsigned int diffuse;
            unsigned int normal;
            unsigned int specular;
            unsigned int emissive;
        }
        texture;
    };
};
  • model_matrix 4×4 모델 행렬이다.
  • interleaved
    • data 클라이언트에 저장 될 버텍스 배열
    • vbo 버퍼 오브젝트 핸들
  • separated
    • data 각 속성이 따로 배열을 가지고 있다. 따라서 4개의 일차원 배열이다.
    • vbos[4] 4개의 버퍼 핸들 배열이다.
    • vbo 버퍼를 따로 이름으로 참조할 때 사용한다.
  • indices 클라이언트에 저장 될 인덱스 배열
  • ibo 인덱스 버퍼 오브젝트 핸들
  • num_vertices 버텍스 자료 갯수
  • num_indices 인덱싱 된 삼각형 갯수. 즉, 인덱스 버퍼의 길이는 실제로 num_indices x 3이다.
  • textures[4] 텍스쳐 핸들 배열
  • texture 텍스쳐를 따로 이름으로 접근 할 때 사용

아래는 모델 그룹을 로딩 할 때 쓰려고 만든 것인데, 그렇게 중요하지는 않다.

struct GROUP {
    std::vector < MODEL > models;
};

#endif

함수 선언

모델 로드에 사용 할 함수 원형을 선언한다. 전반적으로 C스타일로 작성하였다.

#ifndef __OPENGL_TEST_UTIL_H__
#define __OPENGL_TEST_UTIL_H__

#include "model.h"

int LoadModel(const char * filename, MODEL * pModel);
int UnloadModel(MODEL * pModel);
int CopyModel(MODEL * source, MODEL * target);

int LoadGroup(const char * filename, GROUP * pGroup);
int UnloadGroup(GROUP * pGroup);

#endif

함수 구현

텍스쳐는 이름과 핸들 쌍으로 맵으로 관리해서 중복되는 텍스쳐 로딩을 피하도록 한다.

std::map<std::string, unsigned int> g_textures;

모델을 로드하는 함수를 구현하도록 한다. 파일 이름과 모델 포인터를 인자로 받아 모델 포인터에 파일 내용을 기록할 것이다.

int LoadModel(const char * filename, MODEL * pModel) {
        FILE * fd = fopen(filename, "rt"); // 유니코드 함수를 사용하는 게 더 좋을 것이다. 일단 그냥~
        if (fd == NULL)
            return -1;

위치와 회전을 입력받아 모델 매트릭스를 만든다. 3Ds MAX의 z는 OpenGL의 y축, 3Ds MAX의 y는 OpenGL의 -z 방향이다. 그에 맞게 바꾸어 입력받는다. 오일러 각은 맥스의 x, z축 방향 회전이 반대이므로 -1을 곱한다. 그런데 맥스에서 (-90, 0, -180)같은 값이 들어오면 OpenGL의 y축을 기준으로 뒤집어져 버렸다. 쿼터니언이나 아예 맥스의 4×3짜리 월드 행렬을 가져오도록 하는 게 나을 것이다.

glm::vec3 position;
glm::vec3 rotation;

fscanf(fd, "%f %f %f", & position.x, & position.z, & position.y);
fscanf(fd, "%f %f %f", & rotation.x, & rotation.z, & rotation.y);

position.z = -position.z;
rotation.x = -rotation.x;
rotation.y = -rotation.y;
rotation = glm::radians(rotation);

glm::mat4 rot = glm::eulerAngleYXZ(rotation.y, rotation.x, rotation.z);
glm::vec4 pos = glm::transpose(rot) * glm::vec4(position, 1.0 f); // rot^-1 means inv mat
pModel - > model_matrix = glm::translate(rot, pos.xyz);

텍스쳐 로드를 위해서 모델의 경로를 추출한다.

// get path
int rend = -1;
for (rend = strlen(filename) - 1; rend >= 0; rend--) {
    if (filename[rend] == '\\' || filename[rend] == '/') {
        rend++;
        break;
    }
}

머터리얼 갯수를 입력받는다. 하지만 여기에서는 문제를 간단히 하기 위해 단 하나의 재질만을 지원하기로 했다

	// materials (support only 1 material)
	int num_materials;
	fscanf(fd, "%d\n", & num_materials);

순서대로 디퓨즈, 노멀, 스펙큘러, 에미시브 텍스쳐가 들어온다. 약간 길지만 문자열 처리 코드가 대부분이다.

unsigned int * target[] = {
    &
    pModel - > texture.diffuse,
    &
    pModel - > texture.normal,
    &
    pModel - > texture.specular,
    &
    pModel - > texture.emissive
};

for (int i = 0; i < num_materials; i++) {
    for (int j = 0; j < 4; j++) {
        char buf[512] = {
            '\0',
        };
        if (rend > 0) {
            strncpy(buf, filename, rend);
            fgets(buf + rend, 512 - rend, fd);
        } else
            fgets(buf, 512, fd);

        // rtrim
        for (int j = strlen(buf) - 1; j >= 0; j--) {
            if (buf[j] == '\r' || buf[j] == '\n' || buf[j] == ' ' || buf[j] == '\t')
                buf[j] = '\0';
            else
                break;
        }

        FILE * fd = fopen(buf, "rb");
        if (fd == NULL) {
            // find right '.'
            int pos;
            for (pos = strlen(buf) - 1; pos >= 0; pos--) {
                if (buf[pos] == '.')
                    break;
            }

            strcpy(buf + pos, ".dds");
        } else
            fclose(fd);

        auto itr = g_textures.find(buf);
        if (itr != g_textures.end()) {
            * target[j] = ( * itr).second;
        } else {
            wchar_t wbuf[512];
            mbstowcs(wbuf, buf, 512);
            * target[j] = ilutGLLoadImage(wbuf);

            if ( * target[j] == 0) {
                printf("[x] %s load failed.\n", buf);
            } else {
                // build mipmaps when it has no mipmap
                if (ilActiveMipmap(1) == IL_FALSE) {
                    BOOL builed = ilutGLBuildMipmaps();
                    printf("[%d] %s, mipmap: %s\n", * target[j], buf, builed ? "builed" : "build failed");
                } else
                    printf("[%d] %s, mipmap: already exist\n", * target[j], buf);
            }

            // add this texture to global texture resource map
            g_textures[buf] = * target[j];
        }
    }

    // 1 material support
    break;
}

ilutGLLoadImage 함수가 사용되었다. 이 함수는 DevIL에 있는 함수로 그림을 OpenGL 텍스쳐로 한 방에 올려준다. 이 함수를 사용하기 위해서는 DevIL 세팅을 먼저 해야한다. 아래 코드를 모델 로드하기 전에 한번만 실행하면 된다. 렌더링 컨텍스트 만드는 곳에 하면 될 것이다.

// 이 코드는 모델 데이터 로드 전에 한번만 실행하면 됨!!!!

// Init DevIL
ilInit();
iluInit();
ilutRenderer(ILUT_OPENGL);
// using dds
ilEnable(IL_KEEP_DXTC_DATA);
ilEnable(IL_ORIGIN_SET);
ilSetInteger(IL_ORIGIN_MODE, IL_ORIGIN_UPPER_LEFT);
ilutEnable(ILUT_GL_USE_S3TC);

이제 버텍스 정보를 읽는다.

// vertex buffer
int num_vertices;
fscanf(fd, "%d", & num_vertices);

pModel - > num_vertices = num_vertices;

배열 데이터 영역을 버텍스 갯수에 맞게 미리 잡아둔다.

// interleaved array
pModel - > interleaved.data.reserve(num_vertices);

// separated arrays
pModel - > separated.data.position.reserve(num_vertices);
pModel - > separated.data.normal.reserve(num_vertices);
pModel - > separated.data.st.reserve(num_vertices);
pModel - > separated.data.tangent.reserve(num_vertices);

이제 읽는다. 위와 마찬가지로 y, z를 바꾸고 z에는 -1을 곱한다. 맥스와 OpenGL의 텍스쳐 좌표는 같기 때문에 그대로 쓰면 된다.

// * exported file format *
// position(x, y, z) : float3
// normal(x, y, z) : float3
// tex_n(s, t, 0, n) : (float, float, float, int)
// tangent(x, y, z, dir) : float4
for (int i = 0; i < num_vertices; i++) {
    VERTEX v;
    float temp;
    fscanf(fd, "%f %f %f", & v.position.x, & v.position.z, & v.position.y);
    fscanf(fd, "%f %f %f", & v.normal.x, & v.normal.z, & v.normal.y);
    fscanf(fd, "%f %f %f %f", & v.st.s, & v.st.t, & temp, & temp);
    fscanf(fd, "%f %f %f %f", & v.tangent.x, & v.tangent.z, & v.tangent.y, & v.tangent.w);
    v.position.z = -v.position.z;
    v.normal.z = -v.normal.z;
    v.tangent.z = -v.tangent.z;

    pModel - > interleaved.data.push_back(v);

    pModel - > separated.data.position.push_back(v.position);
    pModel - > separated.data.normal.push_back(v.normal);
    pModel - > separated.data.st.push_back(v.st);
    pModel - > separated.data.tangent.push_back(v.tangent);

버퍼 오브젝트를 생성하고 값을 복사한다. 버텍스 정보를 GPU에 올리고 거의 수정하지 않을 것이므로 GL_STATIC_DRAW로 만든다. Separated Buffer를 만들 때, 아래처럼 아예 4개의 버퍼를 잡아도 되지만, 하나의 버퍼만 길게 잡고 glBufferSubData로 값을 복사해도 상관 없다.

glGenBuffersARB(1, & pModel - > interleaved.vbo);
glBindBufferARB(GL_ARRAY_BUFFER, pModel - > interleaved.vbo);
glBufferDataARB(GL_ARRAY_BUFFER, sizeof(VERTEX) * num_vertices, & pModel - > interleaved.data[0], GL_STATIC_DRAW);

glGenBuffersARB(4, pModel - > separated.vbos);
glBindBufferARB(GL_ARRAY_BUFFER, pModel - > separated.vbo.position);
glBufferDataARB(GL_ARRAY_BUFFER, sizeof(glm::vec3) * num_vertices, & pModel - > separated.data.position[0], GL_STATIC_DRAW);
glBindBufferARB(GL_ARRAY_BUFFER, pModel - > separated.vbo.normal);
glBufferDataARB(GL_ARRAY_BUFFER, sizeof(glm::vec3) * num_vertices, & pModel - > separated.data.normal[0], GL_STATIC_DRAW);
glBindBufferARB(GL_ARRAY_BUFFER, pModel - > separated.vbo.st);
glBufferDataARB(GL_ARRAY_BUFFER, sizeof(glm::vec2) * num_vertices, & pModel - > separated.data.st[0], GL_STATIC_DRAW);
glBindBufferARB(GL_ARRAY_BUFFER, pModel - > separated.vbo.tangent);
glBufferDataARB(GL_ARRAY_BUFFER, sizeof(glm::vec4) * num_vertices, & pModel - > separated.data.tangent[0], GL_STATIC_DRAW);

이제 인덱스 정보를 읽어오자.

// index buffer
int num_indices;
fscanf(fd, "%d", & num_indices);

pModel - > num_indices = num_indices;
pModel - > indices.reserve(num_indices * 3);

for (int i = 0; i < num_indices; i++) {
    unsigned int a, b, c;
    fscanf(fd, "%d %d %d", & a, & b, & c);

    assert(a < 0xFFFF && b < 0xFFFF && c < 0xFFFF);

    pModel - > indices.push_back(a - 1);
    pModel - > indices.push_back(b - 1);
    pModel - > indices.push_back(c - 1);
}

인덱스는 2bytes로 할 것이기 때문에 혹시 이 범위를 벗어나는 인덱스가 있는지 검사한다. 그리고 맥스에서 인덱싱 할 때 1에서 시작하였으므로 1을 빼서 0부터 시작하도록 고친다. 그리고 인덱스 버퍼를 만들고 값을 복사하면 끝이다.

// create a IBO
glGenBuffersARB(1, & pModel - > ibo);
glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, pModel - > ibo);
glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * num_indices * 3, & pModel - > indices[0], GL_STATIC_DRAW);

fclose(fd);
return 0;
}

이제 모델은 언로드하는 함수를 구현한다. 별다를 건 없다. 텍스쳐는 g_textures로 관리하므로 모델 언로드 시에는 건들지 않는다.

int UnloadModel(MODEL * pModel) {
    // delete VBOs
    pModel - > interleaved.data.clear();
    pModel - > separated.data.position.clear();
    pModel - > separated.data.normal.clear();
    pModel - > separated.data.st.clear();
    pModel - > separated.data.tangent.clear();

    glDeleteBuffersARB(1, & pModel - > interleaved.vbo);
    glDeleteBuffersARB(4, pModel - > separated.vbos);

    // delete the IBO
    pModel - > indices.clear();

    glDeleteBuffersARB(1, & pModel - > ibo);

    return 0;
}

모델을 복사하는 함수이다. 버퍼를 따로 갖는다.

int CopyModel(MODEL * source, MODEL * target) {
    target - > model_matrix = source - > model_matrix;

    int num_vertices = source - > interleaved.data.size();
    target - > num_vertices = num_vertices;

    glGenBuffersARB(1, & target - > interleaved.vbo);
    glBindBufferARB(GL_ARRAY_BUFFER, target - > interleaved.vbo);
    glBufferDataARB(GL_ARRAY_BUFFER, sizeof(VERTEX) * num_vertices, & source - > interleaved.data[0], GL_STATIC_DRAW);

    glGenBuffersARB(4, target - > separated.vbos);
    glBindBufferARB(GL_ARRAY_BUFFER, target - > separated.vbo.position);
    glBufferDataARB(GL_ARRAY_BUFFER, sizeof(glm::vec3) * num_vertices, & source - > separated.data.position[0], GL_STATIC_DRAW);
    glBindBufferARB(GL_ARRAY_BUFFER, target - > separated.vbo.normal);
    glBufferDataARB(GL_ARRAY_BUFFER, sizeof(glm::vec3) * num_vertices, & source - > separated.data.normal[0], GL_STATIC_DRAW);
    glBindBufferARB(GL_ARRAY_BUFFER, target - > separated.vbo.st);
    glBufferDataARB(GL_ARRAY_BUFFER, sizeof(glm::vec2) * num_vertices, & source - > separated.data.st[0], GL_STATIC_DRAW);
    glBindBufferARB(GL_ARRAY_BUFFER, target - > separated.vbo.tangent);
    glBufferDataARB(GL_ARRAY_BUFFER, sizeof(glm::vec4) * num_vertices, & source - > separated.data.tangent[0], GL_STATIC_DRAW);

    int num_indices = source - > num_indices;
    target - > num_indices = num_indices;

    glGenBuffersARB(1, & target - > ibo);
    glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, target - > ibo);
    glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short) * num_indices * 3, & source - > indices[0], GL_STATIC_DRAW);

    target - > texture = source - > texture;

    return 0;
}

그룹과 관련된 함수이다. 첫 줄에 모델 갯수가 나오고 둘쨋줄 부터 모델 파일 이름이 나오는 간단한 구조이다.

int LoadGroup(const char * filename, GROUP * pGroup) {
    FILE * fd = fopen(filename, "rt");
    if (fd == NULL)
        return -1;

    int n;
    fscanf(fd, "%d\n", & n);

    pGroup - > models.resize(n);

    int rend = -1;
    for (rend = strlen(filename) - 1; rend >= 0; rend--) {
        if (filename[rend] == '\\' || filename[rend] == '/') {
            rend++;
            break;
        }
    }

    for (int i = 0; i < n; i++) {
        char buf[512] = {
            '\0',
        };
        if (rend > 0) {
            strncpy(buf, filename, rend);
            fgets(buf + rend, 512 - rend, fd);
        } else
            fgets(buf, 512, fd);

        for (int j = strlen(buf) - 1; j >= 0; j--) {
            if (buf[j] == '\n' || buf[j] == '\r' || buf[j] == '\t' || buf[j] == ' ')
                buf[j] = '\0';
            else
                break;
        }

        LoadModel(buf, & pGroup - > models[i]);
    }
}

int UnloadGroup(GROUP * pGroup) {
    for (int i = 0; i < pGroup - > models.size(); i++) {
        UnloadModel( & pGroup - > models[i]);
    }

    return 0;
}

실제로 로드하기

전역으로 모델을 관리할 배열을 하나 만든다.

std::vector<MODEL*> g_models;

이제 모델을 로드한다. g_mon과 g_bong은 MODEL 타입의 전역 변수이다.

LoadModel("../models/objobjobjpolySurface23.idx.txt", & g_mon);
g_models.push_back( & g_mon);
LoadModel("../models/objobjobjImrodLowPoly_3__polySurface7.idx.txt", & g_bong);
g_models.push_back( & g_bong);

렌더 루프

먼저 화면을 클리어 한다.

void Render(float dt) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

뷰 매트릭스가 카메라 이동 등에 의해서 바뀌었을 수 있으므로 업데이트 한다. 유니폼 블럭이므로 버퍼에 데이터를 업로드 해주면 된다.

glBindBuffer(GL_UNIFORM_BUFFER, g_uniformBlockBaseParamBuffer);
glBufferSubData(GL_UNIFORM_BUFFER, (int) & g_uniformBlockBaseParam.view - (int) & g_uniformBlockBaseParam,
    sizeof(glm::mat4), & g_matView);
glBindBuffer(GL_UNIFORM_BUFFER, 0);

로드 된 모델 수 만큼 루프를 돌면서 찍어 주도록 한다. MIT 매트릭스는 혹시 모델 매트릭스가 스케일 되었을 수 있으므로 노멀 벡터 보정을 위해서 필요하다. 모델 매트릭스와 MIT 매트릭스를 한번에 업로드 한다.

for (int i = 0; i < g_models.size(); i++) {
    glm::mat4 matMIT = glm::transpose(glm::inverse(g_models[i] - > model_matrix));
    glm::mat4 mats[2] = {
        g_models[i] - > model_matrix,
        matMIT
    };

    glBindBuffer(GL_UNIFORM_BUFFER, g_uniformBlockBaseParamBuffer);
    glBufferSubData(GL_UNIFORM_BUFFER, (int) & g_uniformBlockBaseParam.model - (int) & g_uniformBlockBaseParam,
        sizeof(glm::mat4) * 2, & mats[0]);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

이제 셰이더 프로그램을 고를 시간이 왔다. 여기서 셰이더 프로그램을 고르는 기준은 모델이 노멀맵을 가지고 있느냐이다.

// select shader program
if (g_models[i] - > texture.normal == 0) { // has no normal map
    glUseProgramObjectARB(g_hBaseProgram);

    glClientActiveTexture(GL_TEXTURE0);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, g_models[i] - > texture.diffuse);

    glUniform1i(g_hBaseUniformDiffuse, 0);
} else { // normal mapping shader
    glUseProgramObjectARB(g_hNormalmapProgram);

    for (int j = 0; j < 4; j++) {
        if (g_models[i] - > textures[j] != 0) {
            glClientActiveTexture(GL_TEXTURE0 + j);
            glActiveTexture(GL_TEXTURE0 + j);
            glBindTexture(GL_TEXTURE_2D, g_models[i] - > textures[j]);
        }
    }

    glUniform1i(g_hNormalmapUniformDiffuse, 0); // 스테이지 번호를 업로드
    glUniform1i(g_hNormalmapUniformNormal, 1);
    glUniform1i(g_hNormalmapUniformSpecular, 2);
    glUniform1i(g_hNormalmapUniformEmissive, 3);
}

노멀맵이 없다면 NORMAL_MAP을 정의하지 않고 컴파일 했던 프로그램을 사용하고, 텍스쳐도 디퓨즈 한 장만 지정 했다. 노멀맵이 있다면 NORMAL_MAP을 정의하고 컴파일 했던 프로그램을 사용하고, 텍스쳐도 4장 모두 지정 한다. 여기서 D3D9와 다른 점은 uniform sampler2D에 지정하는 값이 텍스쳐 핸들이 아니라 스테이지 번호다. 이제 버텍스 버퍼를 지정해야 한다. 우리는 interleaved와 separated 버퍼를 각각 만들었다. 그래서 어떤 모드로 렌더링 할 지 정할 상태 변수가 필요하다. 아래와 같은 코드를 전역에 추가하도록 하자. 이 변수는 키 입력 등에 반응해서 바뀌게 하면 될 것이다.

enum MODE1 { SEPARATED = 0, INTERLEAVED };   MODE1 g_mode1 = SEPARATED;

버텍스 버퍼를 바인드 한다. 먼저 SEPARATED 모드를 보도록 하자.

if (g_mode1 == SEPARATED) {
    glBindBufferARB(GL_ARRAY_BUFFER, g_models[i] - > separated.vbo.position);
    glVertexAttribPointerARB(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArrayARB(0);

    glBindBufferARB(GL_ARRAY_BUFFER, g_models[i] - > separated.vbo.normal);
    glVertexAttribPointerARB(1, 3, GL_FLOAT, GL_TRUE, 0, 0);
    glEnableVertexAttribArrayARB(1);

    glBindBufferARB(GL_ARRAY_BUFFER, g_models[i] - > separated.vbo.st);
    glVertexAttribPointerARB(2, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArrayARB(2);

    glBindBufferARB(GL_ARRAY_BUFFER, g_models[i] - > separated.vbo.tangent);
    glVertexAttribPointerARB(3, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArrayARB(3);

4개의 버퍼를 순서대로 바인드 하는 간단한 형식이다. glEnableVertexAttribArrayARB 함수에 들어가는 0, 1, 2, 3은 셰이더 컴파일 시에 지정했던 in 변수 번호이다.

glBindAttribLocation(g_hBaseProgram, 0, “in_Position”);
glBindAttribLocation(g_hBaseProgram, 1, “in_Normal”);
glBindAttribLocation(g_hBaseProgram, 2, “in_ST”);
glBindAttribLocation(g_hBaseProgram, 3, “in_Tangent”);

이제 INTERLEAVED 모드를 보자.

} else {
			glBindBufferARB(GL_ARRAY_BUFFER, g_models[i]->interleaved.vbo);
			glVertexAttribPointerARB(0, 3, GL_FLOAT, GL_FALSE, sizeof(VERTEX), 0);
			glEnableVertexAttribArrayARB(0);
 
			glBindBufferARB(GL_ARRAY_BUFFER, g_models[i]->interleaved.vbo);
			glVertexAttribPointerARB(1, 3, GL_FLOAT, GL_TRUE, sizeof(VERTEX), (void*)12);
			glEnableVertexAttribArrayARB(1);
 
			glBindBufferARB(GL_ARRAY_BUFFER, g_models[i]->interleaved.vbo);
			glVertexAttribPointerARB(2, 2, GL_FLOAT, GL_FALSE, sizeof(VERTEX), (void*)24);
			glEnableVertexAttribArrayARB(2);
 
			glBindBufferARB(GL_ARRAY_BUFFER, g_models[i]->interleaved.vbo);
			glVertexAttribPointerARB(3, 4, GL_FLOAT, GL_FALSE, sizeof(VERTEX), (void*)32);
			glEnableVertexAttribArrayARB(3);
		}

아까와는 다르게 glVertexAttribPointerARB의 마지막 인자 2개가 0이 아니다. 마지막에서 두번째 인자는 버텍스 하나의 크기로 몇 바이트씩 뛰어 넘어야 하는 지 지정한다. 마지막 인자는 몇 번째 바이트에서 그 어트리뷰트의 데이터가 시작하는 지를 나타낸다. 이제 인덱스 버퍼를 바인딩하고 그리면 된다.

glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, g_models[i] - > ibo);

glDrawRangeElements(GL_TRIANGLES, 0, g_models[i] - > num_indices * 3, g_models[i] - > num_indices * 3, GL_UNSIGNED_SHORT, 0);
// glDrawElements(GL_TRIANGLES, g_models[i]->num_indices * 3, GL_UNSIGNED_SHORT, 0); // 上同

모든 상태 변화 등을 커밋하고 버퍼를 스왑해서 캔버스에 지금 그린 것을 나타나게 한다.

}

glFlush();

::SwapBuffers(g_hDC);
}

정리

텍스쳐 로드한 것과 모델 로드한 것을 모두 해제하고, 셰이더 프로그램도 해제한다. 그리고 최종적으로 렌더링 컨텍스트와 DC까지 해제하면 끝이다.

void Clean(HWND hWnd) {
    for (auto itr = g_textures.begin(); itr != g_textures.end(); itr++) {
        glDeleteTextures(1, & ( * itr).second);
    }

    for (int i = 0; i < g_models.size(); i++)
        UnloadModel(g_models[i]);

    auto SafeDeleteGLObject = [](GLhandleARB & handle) - > void {
        glDeleteObjectARB(handle);
        handle = NULL;
    };

    SafeDeleteGLObject(g_hBaseProgram);
    SafeDeleteGLObject(g_hBaseVertShader);
    SafeDeleteGLObject(g_hBaseFragShader);

    SafeDeleteGLObject(g_hNormalmapProgram);
    SafeDeleteGLObject(g_hNormalmapVertShader);
    SafeDeleteGLObject(g_hNormalmapFragShader);

    if (g_hRC) {
        wglMakeCurrent(NULL, NULL);
        wglDeleteContext(g_hRC);
        g_hRC = NULL;
    }

    if (g_hDC) {
        ::ReleaseDC(hWnd, g_hDC);
        g_hDC = NULL;
    }
}

샘플 스크린샷

노멀맵이 없는 벽면은 그냥 버텍스 노멀이 출력되고, 노멀맵이 있는 모델들은 노멀맵을 계산해서 출력되었다. 노멀은 뷰스페이스 기준이다.

 

 

컴퓨터를 켜고 놀거나 작업을 하면 점점 프레임이 떨어진다. 원래는 1600나오는데 200수준으로 떨어졌다

 

결론

감상

간단한 예제를 만드려고 했는데 D3D9로 구현했던 기능을 대부분 넣으려다 보니 길어졌다. 오래 전에 화면에 박스와 선이 나오기만 하면 되었던 때 OpenGL을 사용했었는데, 그 사이에 버전이 엄청나게 올라가고 기능도 엄청 추가 되었다. 2.x 버전 후에 한동안 업데이트가 없다가 3 버전으로 넘어오면서 D3D API에 있는 많은 것이 코어로 편입이 되어서 게임 렌더링 API로도 손색이 없는 것 같다. 하지만 역시 비디오 카드 벤더와 드라이버 버전을 많이 타는 것 같다. 좀 더 많이 사용해 보면서 새로워진 OpenGL에 익숙해 져야 하겠다. 그런데 솔직하게 말해서 OpenGL을 다시 쓰려고 마음 먹은 이유는 그저 DX10 이상이 윈도우 XP에서 돌아가지 않기 때문이다

ㅋㅋㅋㅋ

성능

컴퓨터를 켜자 마자 측정하면 같은 기능의 D3D9 프로그램과 차이가 없다. 그런데 내컴퓨터가 요새 좀 이상해서 켜두면 점점 컴퓨터가 느려지면서 프레임이 떨어지는데, OpenGL 프로그램은 특히 심하다. 3) interleaved buffer와 separated buffer는 현재 내 하드웨어인 ATi Radeon HD4670에서는 그렇게 프레임 차이가 나지 않는다. 그리고 같은 셰이더를 쓰는 모델끼리 몰아서 상태 변경하거나 이런 최적화가 이루어지지도 않았기 때문에 성능 측정이라고 할 것도 없을 것이다.

API 편의성

OpenGL은 그 특유의 상태 기반 기계 API를 가지고 있다. 그래서 지금 내가 무엇을 수정하고 있는지 정확히 알기가 힘들다. 게다가 멀티 스레드화가 되어가는 현재 대세에는 정말 어울리지 않는 형태라고 할 수 있다. OpenGL에 데이터를 커밋하거나 렌더링 명령을 내리는 스레드를 하나 만들고, 거기서만 처리하게 하는 방식으로 멀티 스레드화를 해야 할 것이다. 이는 D3D9도 마찬가지다. D3D11에서도 어짜피 내부적으로 같은 처리를 하는 것이므로, 잘 캡슐화만 하면 OpenGL API로 인한 약점은 어느정도 극복 할 수 있을 것이다. 하지만 역시 좋다고는 할 수 없다.

1) ERROR_INVALID_PIXEL_FORMAT: The pixel format is invalid.

2) CG를 사용하면 된다.

3) 문제는 기가바이트 보드의 전력 관리 프로그램이 비정상적으로 종료 되면서 클럭이 1초 마다

 올라갔다 내려갔다 하면서 시스템을 엉망으로 만드는 것이었다. ASUS P5W에 인텔 Q9450으로 넘어오니 날아 다닌다!

 

 

 

=======================

=======================

=======================

 

 

 

 

 

링크: 

- http://3d.sangji.ac.kr/ppt/CG/03.pdf

- http://gcland.tistory.com/189

- http://www.dei.isep.ipp.pt/~jpp/sgrai/OpenGL.ppt

 

OpenGL.ppt
2.85MB

 

 

=======================

=======================

=======================

 

 

 

반응형


관련글 더보기

댓글 영역