Normal Map이 왜 필요한가?
위의 그림처럼
버텍스가 많고 노말이 모두 다른 고품질 모델의 경우는
라이팅 효과가 적절히 되면서 실제처럼 체감을 할 수 있다
그런데
버텍스가 많으면 gpu 병렬 처리 연산이 많아지고 프레임이 저하된다
이를 해결하기위해서 Normal Map을 사용한다
Normal Map이란?
고품질(복잡한) 모델에서
노멀정보만을 추출해서 texture 형태로 저장한 것을 Normal Map이라고 한다
노멀맵은 offline과정으로 모델러가 직접 생성한다
이렇게 저장된 노멀맵과 버택스 4개 만을 사용한 사각형으로
굴곡이 느껴지게끔 만들어준다
NormalMap을 만드는 방법
1) Height map 생성
Height field를 추출해 Height map을 만든다
얼마나 튀어놔와있는지 높이를 저장하는
RGB 값이 모두 동일한 gray scale 텍스쳐로 구성된다
참고로, Height map의 해상도(칸의 크기)는 모델러가 설정한다
2) 픽셀 단위로 노멀 생성
근방 4개의 픽셀에 height 정보를 가지고
서로 가로지르게 2개의 벡터를 만든 다음
cross product(외적)해서 노멀을 생성한다
물론 위의 방법이 아닌
근처의 삼각형 노멀의 평균을 이용하는 방법,
원래 메시로 부터 노멀을 보간해서 만드는 방법들도 있다
3) 노멀맵 -> 이미지
이미지는 0 ~ 1 혹은 0 ~ 255이다
일반적으로는 0 ~ 1이기에
-1, 1(Normal)을 0, 1(RGB)로 바꾸기 위해서
1을 더해서 2로 나누는 작업을 통해 노멀맵을 텍스쳐화한다
Normal Map을 가지고 렌더링 하는 방법
fragment shader에서
기존에 사용하던 n을 scan conversion을 통해 보간한 normal 값이 아닌
normal map의 normal값을 활용한다
참고로, vertex shader의 normal 값은
추후에 어떠한 문제를 해결하기 위해 사용된다
위의 코드에서 하이라이트 된 부분을 보면,
0 ~ 1(RGB)을 -1 ~ 1(Normal)로 변환해야 하니 2를 곱하고 1을 빼는 것을 볼 수 있다
Normal Map 렌더링 문제
vec3 lightDir = normalize(-direction);
float diffuseFactor = max(dot(normal, lightDir), 0.0);
vec3 lightDiffuse = light.lightColor * light.diffuseIntensity * diffuseFactor;
위의 코드는 FragmentShader에서 노멀값을 이용해
광원 효과를 내는 일부 코드이다
연산은 같은 좌표끼리의 연산을 해야 의미가 생기고 오류가 발생하지 않는다
그런데, 위의 코드에서 두 번째 줄을 보면
light는 월드좌표인데 노멀 또한 월드좌표에 있는 것처럼 계산을 해버린다
따라서, 실제 코드를 실행하면 제대로 Normal map이 적용되지 않는 모습을 확인할 수 있다
이를 해결하기 위해서는
노멀맵의 노멀과 연산에 사용되는 light 모두
해당 모델의 표면을 기준으로 한 탄젠트 좌표계에서 연산을 해야 한다
여기서 표면을 얻기 위해서 Vertex Array에 노멀값을 활용한다
Normal Map 렌더링 문제 해결 - Tangent Space
위의 문제를 해결하는 방법을 간단하게 정리하면
노멀맵의 노멀을 탄젠트좌표계로 변환을 하고
월드좌표의 light도 탄젠트 좌표계로 바꿔서 연산해서 해결하는 것이다
- 상세한 이론
노멀맵의 노멀은 탄젠트 좌표계 기준의 데이터이긴 하지만
상대적인 위치이기 때문에
절대적인 탄젠트 좌표계로 변환을 해야하고 방법은 아래와 같다
Vertex Array에 담긴 노멀(표면을 기준으로 up벡터)을 가지고
TBN을 뽑아낸다
그리고 TBN 축으로 생성된 Tangent Space에서
Normal Map의 노멀값을 적용한다
그리고 라이트 벡터, 뷰 벡터도 탄젠트 좌표계로 계산을 한다
- 실제 코드 (Vertex Shader)
아래의 Vertex Shader 코드를 보면
light 벡터와 view 벡터를 탄젠트 좌표계로 미리 변환했다
- 실제 코드 (Fragment Shader)
아래의 Fragment Shader 코드를 보면
Vertex Shader에서 변환된 탄젠트 좌표계의 라이트와
탄젠트 좌표계의 노말맵 노말값을 dot 해서 연산하는 것을 확인할 수 있다
Normal Map의 활용
위의 그림처럼
노말맵과 기존 모델의 겹치는 좌표를 ray 형식으로 보간하고 인코딩하면
아래의 사진처럼 저폴리(적은 버택스)로도 높은 품질인 것처럼 보이게 할 수 있다
'게임 그래픽스 > [전북대] OpenGL' 카테고리의 다른 글
[OpenGL Note] Shadow Map (0) | 2024.11.25 |
---|---|
[OpenGL Note] Quaternion (0) | 2024.11.24 |
[OpenGL Note] Output Merger - Alpha Blending (0) | 2024.04.20 |
[OpenGL Note] Output Merger - Z buffering (0) | 2024.04.20 |
[OpenGL Note] Lighting - Ambient, Emmisive (0) | 2024.04.19 |