http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix/
---------------------------------------------------------------
gl_NormalMatrix는 많은 vertex shader에서 존재한다. 여기에서 몇몇 빛은 이 matrix가 무엇인지 알려주고 무엇을 위한 것인지 알려준다. 이 섹션은 Eric Lengyel의 "Mathematics for 3D Game Programming and Computer Graphics" 훌륭한 책에 의해 영감을 받았다.
많은 연산이 eye space에서 행해진다. 이것은 조명이 흔히 이 공간에서 수행된다는 사실과 관련이 있다. 그렇지 않다면 specular light같은 eye position은 구현하기에 더 여려울 것이다.
그러므로 우리는 normal를 eye space로 바꿀 방법이 필요하다. vertex를 eye space로 바꾸기 위해서 우리는 이렇게 쓸 수 있다.
vertexEyeSpace = gl_ModelViewMatrix * gl_Vertex;
그래서 우리는 normal vector에도 왜 같은 것을 할 수 없을까? normal은 3개의 floats으로 이루어진 벡터이고 modelview matrix는 4x4이다. 둘 째로, normal가 한 벡터이기 때문에, 우리는 오직 그것의 방향만을 바꾸길 원한다. 방향을 포함하는 그 modelview matrix의 영역은 왼쪽 상단의 3x3 부분행렬이다. 그래서 이 부분행렬로 normal을 곱하면 안되나?
이것은 쉽게 다음의 코드로 이루어질 수 있다:
normalEyeSpace = vec3(gl_ModelViewMatrix * vec4(gl_Normal, 0.0));
그래서 gl_NormalMatrix는 그것을 최적화하거나 또는 코드 쓰는것을 간단화 해주는 지름길인가? 아니다. 정말 아니다. 위 코드는 어떤 환경에서 작동할 것이지만 모든 것에서는 그렇지 않다.
가능한 문제를 봐보자.
위의 그림에서, 우리는 normal(법선)과 tangent(접선) 벡터가 있는 삼각형을 볼 수 있다. 다음의 그림은 modelview matrix가 non-uniform scale을 (동일하지 않은 스케일) 포함할 때 일어나는 것을 보여준다.
Note: 만약 scale이 균일하다면, 법선의 방향은 보존되어질 것이다. 그 길이는 영향받지만, 이것은 normalization(표준화)로 쉽게 고쳐질 수 있다.
위의 그림에서, Modelview matrix는 모든 정점뿐만 아니라 법선에도 적용되었다. 그리고 그 결과는 명백히 잘못되었다. 변환된 법선은 더 이상 표면에 수직이 아니다.
우리는 벡터가 두 점 사이의 차로 표현될 수 있다는 것을 안다. 접선 벡터를 고려하여, 그것은 삼각형의 edge의 두 정점 사이의 차이로서 계산될 수 있다. 만약 P1과 P2가 edge를 정의하는 정저미라면, 우리는 안다
T = P2 - P1
벡터가 마지막 요소 집합이 0으로 설정된 네 개의 component tuple로서 쓰여질 수 있다는 것을 고려하여, 우리는 Modelview 행렬로 두 항등식의 변에 곱할 수 있다.
T * Modelview = (P2 - P1) * Modelview
이것은 다음의 결과를 만든다.
T * Modelview = P2 * Modelview - P1 * Modelview
T' = P2' - P1'
P1'과 P2'는 변환된 삼각형의 정점이기 때문에, T'는 삼각형의 edge의 접선으로 남게 된다. 그러므로, Modelview는 접선을 보존하지만, 그것은 normal를 보존하지 않는다.
(간단히 말해서, Modelview를 적용한 삼각형의 빗변은 어쨋든 빗변이게 된다는 것이다. non-uniform scale로 방향이 바뀌더라도, 회전에의해 방향이 바뀌더라도 그 접선 벡터가 그 삼각형의 빗변이고 그게 기준이 된다는 것이다. 하지만 normal은 아니게 되니, 이 바뀐 기준으로 normal를 찾아야 한다.)
vector T에 사용된 같은 접근법을 고려하여, 우리는 Q1, Q2 두 점을 발견할 수 있는데,
N = Q2 - Q1
주된 문제는 변환된 점을 통해 정의된 벡터인 Q2' - Q1'가 필수적으로 위의 그림에서 보여지듯이 normal로 남아있지 않게 된다는 것이다. normal vector는 두 점 사이의 차이로 정의되지 않는다. 그것은 표면에 수직한 벡터로 정의된다.
그래서 이제 우리는 normal vector를 바꾸는 모든 경우에 Modelview를 적용할 수 없다는 것을 안다. 그 질문은 그러면, 무슨 matrix를 적용해야 하는가?
3x3 matrix G를 고려해보고, 이 matrix가 normal vector들을 적절히 벽환하기 위해서 어떻게 연산될 수 있는지를 봐보자.
우리는 matrix 변환 이전에, 벡터들이 수직하다는 정의에의해, TN = 0 이라는 것을 안다. 우리는 또한 변환 이후에, N'T'가 zero로 동일해야만 하는 것을 안다. 그것들은 서로에게 수직해야만 하기 때문이다. T는 modelview의 왼쪽 상단의 3x3 부분행렬로 안전하게 곱해질 수 있다. (T는 벡터이고, 그러므로 w component는 zero이다.) 이 submatrix를 M이라고 부르자.
matrix G를 normal vector를 변환하는 정확한 행렬이라고 가정하자.
dot(N', T') = dot( (GN) , (MT) ) = 0
내적은 벡터들의 곱으로 바뀔 수 있다. 그러므로:
dot(GN, MT) = (GN)^T * (MT)
위의 내적이 저렇게 바뀌는 것을 알려면
http://lucifer246.tistory.com/entry/%EC%A0%84%EC%B9%98%ED%96%89%EB%A0%AC
을 참고하면 된다. 즉 같은 크기의 벡터의 내적에 대해서, dot(u,v) = u^T * v (행렬곱)으로 바뀐다. 그러면 내적과 똑같은 연산을 하게 된다.
그러므로 dot(GN, MT)에서 GN과 MT를 계산하면 두 벡터가 되는데 두 벡터의 내적은 위에서 알았듯이, (GN)^T * MT (행렬곱)으로 바꿀 수 있게 된다.
다시 번역으로 돌아가서..
그 첫 번째 벡터의 전치행렬은 벡터들을 곱하기위해 요구되기 때문에, 고려되어야한다는 것을 주목해라. 우리는 또한 곱의 전치행렬은 전치행렬의 곱이다라는 것을,
우리는 N과 T사이의 내적이 0이라는 것을 명시하여 시작했고, 그래서 만약
이라면, 우리는 다음의 것을 갖게 된다.
dot(N', T') = dot(N,T) = 0
정확히 우리가 원하는 것이다. 그래서 우리는 G를 M을 기반으로 연산할 수 있다.
그러므로 normal를 변환하는 올바른 행렬은 M matrix의 역행렬의 전치행렬이다. OpenGL은 우리를 위해 gl_NormalMatrix에서 이것을 계산한다.
이 섹션의 초반부에, Modelview matrix를 사용하여 어떤 경우에 작업하는 것이 명시되었었다. Modelview의 좌측 상단 3x3 부분 행렬이 orthogonal 할 때 마다, 우리는 다음의 식을 갖는다.
M^-1 = M^T ==> G = M
이것은 orthogonal matrix와 함께, 전치행렬은 역행렬과 같기 때문이다. 그래서 orthogonal matrix가 무엇인가? orthogonal matrix는 모든 열/행이 단위 길이이고, 서로 수직이다. 이것은 두 벡터가 그러한 행렬로 곱해졌을 때, 그것들 사이의 각도가 orthogonal matrix로의 변환 후에 그 변환 전의 것과 같다는 것이다. 간단히, 변환을 하는 것이 벡터사이의 각을 보존한다. 그러므로, 변환된 normals들은 접선에 수직하게 된다. 게다가 그것은 벡터의 길이 또한 보존한다.
그래서 우리는 M이 언제 orthogonal 하다고 확신할 수 있을까? 우리가 회전화 평행이동에 대한 기하학 연산을 제한할 때, 예를들어, OpenGL 프로그램에서, 우리가 오직 glRotate와 glTranslate를 사용할 떼 이다. glScale이 아닌. 이러한 연산들은 M이 orthogonal하는 것을 보장한다. 주의 : gluLookat은 또한 orthogonal matrix를 만들어낸다.
---------------------------------------------------
위의 자료를 좀 더 간단하게 요약하자.
1. Normal vector는 non-uniform scale이 적용되어 transform할 때 표면에 수직이 안되는 경우가 생긴다.
2. 하지만 어떤 삼각형을 가정하고, 그 삼각형의 빗변(normal과 수직인)은 non-uniform scale이 적용된 transform을 하더라도 그것이 바로 빗변이 되고 표면이되므로, 기준이 된다.
3. 이 성질을 이용하여, M는 3x3 upper left of transform matrix라고 하고, T는 기존 삼각형의 빗변 벡터라고 하자. 그러면 T' 는 MT하여 transform한 결과라고 하자.
4. 우리가 올바른 N'을 알고 싶은데 이것을 바로 못구하니, 가정을 해보자.
5. 올바른 N'을 만드는 matrix를 3x3 matrix를 G라고 해보자. 그러면 기존의 normal vector N과 함께, GN = N' 를 한 것으로 하자.
5. 두 벡터가 수직일 때의 내적은 0이므로, dot(N', G') = 0이 되어야 한다.
6. dot(N', G') == dot(GN, MT) == (GN)^T * MT (행렬곱) 이 된다.
7. N^T * G^T * M * T = 0 행렬이 되어야 하므로, G^T * M = I (전치행렬) 이라고 한다면
8. N^T * T = 0 로 식이 바뀌어 올바르게 된다.
9. 그러므로 그 올바른 Normal를 구하는 matrix는 G^T * M = I 식을 이용하여,
10. G = (M^-1)^T 가 된다. 즉 transform 행렬의 역행렬의 전치행렬이다.
댓글 없음:
댓글 쓰기