OpenGL/공부

[OpenGL] Normal Transformation (Normal Matrix)

ciel45 2024. 6. 15. 22:29

학교 컴퓨터 그래픽스 수업에서는 적당히 언급만 하고 넘어갔던 부분이라서, 개인적으로 공부해보았다.

 

 

Phong Reflection Model을 통한 쉐이딩을 위해서는 물체의 표면의 normal을 구해야 한다.

이 때의 normal은 World Space(월드 좌표계) 기준이어야 한다. 쉐이딩은 World Space에서 처리할 것이기 때문.

 

위치나 벡터를 Object Space에서 World Space로 변환하는 방법은 Model Matrix를 곱하는 것이고, 

normal 역시 uniform scaling(x, y, z 축으로 같은 비율로 scaling)일 경우에는 그냥 Model Matrix를 곱하면 된다.

 

 

 

다만, non-uniform scaling을 수행하는 Model Matrix의 경우 그냥 곱하면 normal에 왜곡이 발생한다.

출처: learnwebgl

 

위 이미지의 경우처럼 x축으로 늘리고 y축으로 찌그러뜨리는 scaling을 한다면,

normal 역시 그 선형 변환을 따라 x축으로 늘어나고 y축으로 찌그러져 파란 화살표처럼 왜곡되는 것이다.

 

하지만 우리가 실제 원하는 normal은 오렌지색 화살표이다.

 

 

따라서 non-uniform scaling을 하는 Model Matrix의 경우,

World Space에서의 normal을 구하기 위해서는 그냥 Model Matrix를 곱하는게 아니고 별도의 Normal Matrix를 만들어 곱해주어야 한다.

 

 

결론부터 보자면, Normal Matrix를 구하는 방법은 다음과 같다.

 

Model Matrix를 M이라고 하고, 구하고 싶은 Normal Matrix를 S라고 하면

S = (M-1)T

 

코드로 나타내자면

Normal Matrix = mat3(transpose(inverse(model)))

 

 

 

Model Matrix의 역행렬을 전치시키면 된다는건데,

왜 이렇게 되는지 차근차근 살펴보자면, 

출처: learnwebgl

 

벡터 p는 오브젝트의 표면에 놓여져 있고, 벡터 n은 그 표면의 법선 벡터라고 하자.

당연히 두 벡터는 직교하므로, 내적하면 0이 된다. 

dot(p, n) = 0

 

non-uniform scaling을 하는 Model Matrix를 M이라 하자. 

그러면

p' = M * p 

 

올바르게 변환된 normal을 n'이라고 하면,

dot(p', n') = 0

 

그리고 우리가 구하고 싶은, n을 n'(올바른 normal)으로 만들어주는 Normal Matrix를 S라고 하자.

그러면 p' = M · p , n' = S · n 로 표현되어, 위의 식을 이렇게 표현할 수 있다.

dot(M *  p , S * n) = 0

 

 

자 여기서, 계속 dot dot 하던 것을 곱셈으로 풀어쓰자면,

두 열벡터 a, b가 있을 때 dot(a, b) = aT * b 다.

같은 방식으로 저 위의 식을 다시 써보자면,

(M * p)T * S * n = 0

 

transpose를 분배해서 다시 식을 써보면,

pT * MT * S * n = 0

 

다시 처음의 dot(p, n) = 0을 상기해보자.

이는 pT * n = 0 이라는 의미와 같다.

이걸 위의 식 pT * MT * S * n = 0 와 비교해보면? 어어? 중간에 MT * S가 딱 들어가있는 것과 같다.

 

즉, 두 식이 모두 성립하기 위해서는, MT * S = I(Identity Matrix)여야 한다.

그래서 이걸 쭉 정리하면,

 

MT * S = I
(MT) -1 * MT * S = (MT) -1 * I
S = (MT) -1
S = (M-1) T

 

이렇게 증명이 완료된다.

 

참고로, 역행렬을 구하는 연산은 비용이 들기 때문에, 쉐이더에서 Nomal Matrix를 구하기 보다는 CPU에서 미리 계산한 뒤 GPU로 쏴주는 게 더 바람직하다.