'3D그래픽'에 해당되는 글 32건

  1. 2011.07.12 OpenGL ES 2.0 Q&A by myself 5
  2. 2011.06.29 OpenGL ES 2.0 on Android 2.2
3D그래픽2011. 7. 12. 19:28
언제 한번 쪼개서 정리를 해야 겠군(글 개수 늘이기ㅋㅋㅋ)

glDrawElement 를 이용해 index 모드로 primitive를 출력 할 때, 공유되는 vertex에 여러개의 texture 좌표를 어떻게 지정할 수 있을까? 예를 들면, 정육면체를 그릴 때, index를 이용하여 출력할 경우 vertex가 8개만 있으면 된다. 그런데, texture를 사용할 경우 texture 좌표는 8개만 가지고는 해결되지 않는다. 이러한 경우, 하나의 vertex에 두 개 이상의 texture 좌표를 지정할 수 있는 방법은 무엇일까?

 Texture 좌표나 normal 이 다른경우, 다른 점으로 표현해야 한다. (괜히 index 모드로 한다고 한참 삽질 함)


 Shader source에 구조체를 선언하고, 선언한 구조체에 대한 uniform을 선언하였는데, 이 uniform에 값을 어떻게 설정해야 할까? 책에는 shader source에 대한 sample code는 있는데, 이것을 이용하는 client code가 없어서 도대체 알 수가 없음-_-;

 구조체에 대한 uniform 변수 자체는 location을 갖지 않는다. 대신 구조체의 각 멤버(?)에 대해서 location이 존재 함.
즉, 아래와 같은 구조체와 uniform 변수가 있을 때,

struct light {
   vec3 direction;
   vec3 halfplane;
   vec4 ambient;
   vec4 diffuse;
   vec4 specular;
};
uniform light u_Light;


실제 client code에서 이 uniform 구조체 변수의 각 멤버(?)의 location은 다음과 같이 얻을 수 있다.

int lightDirectionUniformLocation = glGetUniformLocation(program, "u_Light.direction");


[2011.10.01 추가] 구조체를 uniform array로 선언하는 경우가 있는데, 그런 경우에는 배열의 각 index 별로 각각의 uniform이 존재하고, 접근 가능하다. 즉, 아래같이 shader code에 추가되어 있다면,

uniform light u_Light[10];


client code에서는 아래와 같이 접근이 가능하다.

int lightDirectionUniformLocation = glGetUniformLocation(program, "u_Light[0].direction");


참~~~ 쉽죠 잉?-_-; 이것 땜에 엄청 검색 했다;;

(2014.03.14 추가) OpenGL ES spec.에 보면, 2.11.3 Program Object(여기서는 OpenGL ES 3.0.3 spec.)에서 다음과 같이, 위 의 내용이 설명되어 있다.

...

The entries of active resource lists are generated as follows:

...

  • For an active variable declared as a structure, a separate entry will be generated for each active structure member. The name of each entry is formed by concatenating the name of the structure, the "." character, and the name of the structure member. If a structure member to enumerate is itself a structure or array, these enumeration rules are applied recursively.


 uniform 값을 glUniform*()을 이용해서 지정하는데 glGetError()가 계속 GL_INVALID_OPERATION(0x502, 1282)을 뱉어 낸다. 왜 그런가?

 처음에는 구조체의 멤버를 지정해서 발생하는 에러라고 생각 했으나 (당연히) 전혀 아님. uniform은 상수라서 onSurfaceCreated에서 location을 얻어오고, 값까지 설정해도 된다고 생각했는데 onDrawFrame에서 glUniform*()를 이용해서 설정하면 된다. (Vertex Shader에 대해서 제대로 이해할 수 있도록 공부!)


 Vertex Shader에 조명(Lighting) 함수를 구현하고 정육면체(Cube)에 적용하였는데, 결과물을 보니 면 단위로 빛이 적용 되어 실망스럽다. 왜 그럴까?

 Per-vertex lighting 이기 때문에 그렇다. Per-fragment lighting을 적용해야 제대로 된 조명 효과를 확인할 수 있을 듯.
[2011-09-08 추가] 위의 얘기가 맞긴 한데, per-vertex lighting의 결과가 그렇게 실망스러웠던 이유는 따로 있었다.
바로 normal transformation! Lighting equation의 연산을 수행하는데 잘못된 normal을 사용하고 있었기 때문이었다.
Normal을 eye-space가 아닌 normalized device coordinate(정규화된 장치 좌표계)로 변환시켰던 것이다.
즉, normal matrix(MV matrix의 inverse transpose)가 아닌, MVP matrix를 변환 matrix로 사용하였기 때문!
뭔가 새로운 세상을 경험한 기분... ㅜ.ㅜ


 Shader source가 제대로 동작을 하는 코드인지 아닌지를 실제 client code에서는 확인하기가 너무 어렵다. 왜냐하면 shader source 이외에도 다른 문제가 있을 수 있으므로... 어떻게 검증할 수 있을까?

 gDEBuggerRenderMonkey 같은 "공짜" 도구들이 있어, 이를 활용하면 확인이 가능.
처음에는 gDEBugger를 설치하여 실행 했는데, 도대체 어떻게 사용하는지 감이 오질 않았다. 뭔가 할 수 있는 기능이 되게 많은 것 같은데 너무 복잡하다는 생각이 듬. 이건 나중에 뭔가 높은 수준(?)의 경지에 도달했을 때 사용해 봐야겠다고 다짐하고 패스.
그래서 다시 RenderMonkey를 설치. 화면 구성도 훨씬 간단하고, 지금 내가 적합한 도구라는 판단이 들었지만, 여전히 어떻게 사용해야 할지는 잘 몰랐다. 그래서 메뉴얼을 다 훑어봤지만 사용자 인터페이스에 대한 설명이 전부-_-; 구글링을 통해 어찌어찌하여 내가 작성한 shader source의 테스트에 성공! -0-


 육면체를 출력할 때, culling을 활성화 시킨 경우(정면을 반시계방향으로 정의 Counter Clock Wise, CCW), 밑 면이 제대로 보이지 않았다. 왜 이럴까? 어떻게 보이게 할 것인가?

 우선, 밑 면을 보이게 하려면 다른 면들과 반대의 순서로 삼각형을 출력 해야 한다. 이유는 잘 모르겠다.

왜 그럴까? 누가 좀 알려 주세요. (__) 유레카! 처음에 vertex를 나열 할 때, 이미 내 스스로가 뒷 면은 뒷면이니 반대로 그려야 할꺼야 라고 생각하고, 알아서 앞 면과 반대 삼각형을 그렸다. 왜 그런지 이해하지 못하고 말이지... 그리고 방금 깨닫게 되었다!

OpenGL은 흔히 오른손 좌표계를 사용한다고 되어 있다.
즉 각 축의 양(+)의 방향이, X축은 오른쪽(엄지), Y축은 윗 방향(검지), Z축은 정면(중지).

cartesian.png
여기에 해답이 있다. 반시계방향(CCW)을 front-face라고 정의하면, 양의 방향에서 바라 봤을 때, 시점에 가까운 면은 반시계방향, 시점에서 먼 면은 시계방향으로 그려야 한다.


 NeHe Lesson 8 에서는 blending을 위해 간단하게 culling과 depth test를 비활성화 시켰다. 그런데 실제로는 이렇게 하면 안될 거 같은데... 각각을 활성화 시키는 경우 나타나는 현상은 다음과 같다.
Culling (GL_BACK, GL_CCW) Depth Test Blending 동작 여부 및 현상
X X 제대로 동작 함
X O (1) 일부(?) 동작 함
O X 동작하지 않음
O O 동작하지 않음
위 표에서 보면, 우선 culling을 활성화 하면 무조건 blending이 동작하지 않는다. 아마도 보이지 않는 면(CW)에 대해서는 그리지 않도록 하는 설정이기 때문인듯 하다. 특이하게 (1)의 경우에는 일부만 동작을 한다. 그 이유는 vertex들이 그려지는 순서에 따라 출력 여부가 결정되기 때문인듯 하다.
그렇다면, culling과 depth test를 모두 활성화 시킨 상태에서 제대로 blending이 동작되도록 하는 방법은 무엇을까?

 아직 정확한 정답은 얻지 못했다. 아마도 엄청 복잡한 과정을 거쳐야 할 거 같다. 게임엔진이 해 주는 역할 일듯.
단, Culling을 활성화 하고, depth test를 비활성화 한 경우에 정상적으로 보이게 하는 방법은 찾았다(사실 친구에게 일장 강의를 듣고나서 이해 했음). 각 면을 CW 순서로 한번 더 그려주면 된다. 단순하면서도 복잡한 방법. (이렇게 하면서 culling과 depth test를 모두 비활성화 하면, 과도한 blending(?)으로 texture가 거의 흰색에 가까워진다)


 Target device에서 OpenGL ES 2.0를 연습하면서, 코드를 수정하고 실행하고를 반복하다 보면, 어느순간 프로그램은 시작이 되지만 그리고자 하는 object가 표시되지 않는 문제가 발생한다. LogCat 에서는 GLSurfaceView에 대한 메모리를 할당하지 못했다고 나오는데... 그렇다면 이것은 내부적으로 계속 graphic memory에 대한 leak이 발생한다는 말인가? 만약 그렇다면 어떻게 해결해야 할 것인가?

 (2011.10.22) 우선 glSurfaceView에 대한 onPause()의 호출이 상당히 중요한 의미라는 것을 깨닫게 되었다. 일단 onPause가 불리면 지금까지 사용하던 gl context는 없다고 간주해야 한다. 즉 내부적으로 사용하는 texture memory 라던가, shader program 등의 자원들이 모두 초기화 된다고 생각하고 구현해야 한다.


 여러개의 shader program을 사용해야 할 경우, attribute 및 uniform의 location을 어떻게 관리해야 하는지 궁금했는데, 한 가지 방법을 알게 되었다.

 각 location의 integer 값을 보관하지 말고 string 값을 저장하여, 매 frame마다 program에서 각 attribute 및 uniform의 location을 query하여 사용하는 것이다. 이렇게 하면 각 program마다 동일한 용도의 attribute나 uniform의 이름은 동일하게 만들어 간편하게 사용할 수 있다. 다만, 이러한 방법이 성능에 어떤 영향을 줄지는 잘 모르겠다.

 (2011.10.22) 이 당시 구현방법에서, 성능에 대해서는 확인을 안해봤지만, memory 측면에서의 문제점을 찾았고 수정했다. 자세한 내용은 "onDrawFrame에서의 고려사항 Android OpenGL ES 2.0"을 참고.


 (2011.10.22) OpenGL ES 2.0 Programming Guide(Aftab Munshi, et al., Addison Wesley)에 보면 vertex skinning과 관련하여 아래와같은 문장이 있다.

We assume 32 matrices in the matrix palette, and that the matrices are stored in row-major order. The matrices are also assumed to be orthonormal (i.e., the same matrix can be used to transform position and normal) and up to four matrices are used to transform each vertex.


 책을 보면서, 위 문장이 무슨 내용인지 이해하지 못하고 있었다. 그런데, (역시나) REAL-TIME RENDERING(Tomas Akenine-Moller & Eric Haines, A K PETERS, 2nd edition)을 보고나서 무슨 말인지 알게 되었다.

보통 normal을 변환하기 위해서는 inverse transpose matrix가 필요하지만, 만약 변환 행렬이 rotation과 translation, uniform scaling만 포함하고 있다면(즉, scaling non-uniform scaling이나 shearing이 없는 경우) 그 행렬을 vertex 및 normal 변환에 동일하게 사용할 수 있다.

M-1 = M이므로 (MT)-1 = M




'3D그래픽' 카테고리의 다른 글

Transforms (변환 행렬)  (0) 2011.09.06
Goraud shading vs. Phong shading  (0) 2011.09.02
Normal Transform(법선벡터 변환)  (0) 2011.09.02
COLLADA (3D Asset Exchange Schema)  (0) 2011.08.31
OpenGL ES 2.0 on Android 2.2  (0) 2011.06.29
Posted by 세월의돌
3D그래픽2011. 6. 29. 23:13
Android 2.2 ApiDemo에는 OpenGL ES 2.0을 이용하여 Textured Triangle을 회전시키는 예제가 들어있다.

그냥 막연하게 OpenGL ES 1.1을 가지고 뭔가를 해 보려고 했었는데, GLSurfaceView.Renderer interface method 들의 argument가 GL10으로 되어 있어, 그걸 GL11로 casting 해서 사용해 보려고도 했었는데 (당연히) 실패했다(하하^^;). 그래서, "아... OpenGL ES 2.0은 커녕 OpenGL ES 1.1도 사용하기 어렵구나..." 라고만 생각하고 있었는데, 바보 같은 생각 이었다. (역시 사람은 모르면 찾아보고 물어봐야 한다! -_-;)

GLSurfaceView는 하나의 Activity에서 하나의 instance만 사용이 가능하기 때문에, (아마도 OpenGL이 state machine 기반이기 때문이 아닐까 짐작) GLES11 또는 GLES20 class의 static method로 이용이 가능했던 것이다. GLES11 or GLES20 reference document만 차분히 확인 했어도 짐작할 수 있었던 내용인데, GLSurfaceView.Renderer interface method 들의 argument로 넘겨주는 GL10 instance에 낚여 버렸다. (javax.microedition.khronos.opengles.GLnx와 android.opengl.GLESnx의 차이는 또 다시 확인 해 봐야 할 사항이다. License 때문에 Java Micro Edition은 Android에서 빠졌다고 알고 있는데, 이건 왜 넣어 놓은걸까? 궁금!)

어쨌든, Android 2.2 이상에서 OpenGL ES 2.0을 이용하는 방법(순서?)를 ApiDemo를 기준으로 정리하면 다음과 같다.

[[[ Activity ]]]
1. GLSurfaceView instance를 생성
2. ActivityManager service에서 device configuration info(android.content.pm.ConfigurationInfo)를 획득하여 OpenGL ES 버전을 확인
3. (1)에서 생성한 GLSurfaceView가 사용할 EGL의 Version을 2(아마도 2.0?)로 설정
4. OpenGL ES 2.0으로 작성 된 Renderer instance를 (1)에서 생성한 GLSurfaceView의 renderer로 설정
5. 선택적으로 rendering overhead를 줄이기 위해, (1)에서 생성한 GLSurfaceView의 setRenderMode method를 호출하여 렌더링 모드를 RENDERMODE_WHEN_DIRTY로 설정할 수 있다. (기본은 RENDERMODE_CONTINUOSLY)

[[[ GLSurfaceView.Renderer ]]]
(onSurfaceCreated)
1. Vertex Shader를 생성하고, Vertext Shader 코드를 로드 & 컴파일
    glCreateShader(), glShaderSource(), glCompileShader()
2. Fragment Shader를 생성하고, Fragment Shader  코드를 로드 & 컴파일
    glCreateShader()glShaderSource()glCompileShader()
3. Program을 생성 (Vertext Shader 및 Fragment Shader를 묶는 단위)
3-1. Vertext Shader를 Program에 attach glCreateProgram()
3-2. Fragment Shader를 Program에 attach glAttachShader()
3-3. Vertext Shader와 Fragment Shader가 추가된 Program을 링크 glLinkProgram()
4. Program으로부터 attribute와 uniform을 참조할 수 있는 handle을 획득
    glGetAttribLocation(), glGetUniformLocation()
5. Rendering에 사용 될 Texture를 준비
5-1. Texture object 생성
5-2. 생성된 Texture의 ID를 GL_TEXTURE_2D로 바인딩
5-3. GL_TEXTURE_2D의 옵션(속성)을 설정
5-4. Bitmap data를 GL_TEXTURE_2D로 전송
6. Matrix class를 이용해 View Matrix를 초기화 (Camera 설정이라고 생각하면 될 듯)
    Matrix.setLootAtM()

(onSurfaceChanged)

7. View 영역(View port)를 설정 glViewport()
8. Matrix class를 이용해 Projection Matrix를 초기화 Matrix.frustumM()

(onDrawFrame)
9. Clear 색상 설정 glClearColor()
10. Depth buffer와 Color buffer를 초기화 glClear()
11. 사용할 Program을 지정 glUseProgram()
12. Texture를 활성화 하고, (5-4)에서 전송 해 둔 Texture ID와 연결
13. Vertex 좌표가 저장된 buffer의 포인터를 attribute로 설정 glVertexAttribPointer()
     - glVertexAttribPointer() 사용 시 주의할 점은, 5th argument인 stride는 byte 기준이라는 것!
     - 즉, float 형 x, y, z 세 개씩 건너 뛰어야 할 경우, stride 값은 3 * float 변수의 byte 크기(4) = 12
        (stride 값을 3으로 주고, 안보여서 한참 삽질 했네-_-;)
14. Vertex attribute array를 활성화 glEnableVertexAttribArray()
15. Texture 좌표가 저장된 buffer의 포인터를 attribute로 설정
16. Model Matrix 설정(여기서는 회전) Matrix.setRotateM()
17. MVP Matrix를 계산(MVPMatrix = mProjMatrix * VMatrix * MMatrix) Matrix.multiplyMM()
18. MVP Matrix를 uniform으로 설정 glUniformMatrix4fv()
19. 삼각형을 출력 glDrawArrays()

'3D그래픽' 카테고리의 다른 글

Transforms (변환 행렬)  (0) 2011.09.06
Goraud shading vs. Phong shading  (0) 2011.09.02
Normal Transform(법선벡터 변환)  (0) 2011.09.02
COLLADA (3D Asset Exchange Schema)  (0) 2011.08.31
OpenGL ES 2.0 Q&A by myself  (5) 2011.07.12
Posted by 세월의돌