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 세월의돌