(사실 이해했는지 모르겠다. 어렴풋이 느껴질 뿐... 그래도 그 느낌을 정리한다.)
1. Normal Mapping
- Vertex에서만 존재하는 Normal 값을, 텍스쳐로 받아서 더 디테일하게 표면을 그려보려고 한다.
- Normal 정보를 가지고 있는 Normal Map(Texture)와 그것을 적절히 모델에 적용(Mapping)시키는 것이 목적이다.
- 노멀맵 이미지를 보면, 파란색 베이스에 초록색이나 붉은색이 약간씩 섞여있는... 아무튼 그런 모양새다.
- RGB에 각각 Normal의 X, Y, Z 값을 넣어서 그런 것이다. Z가 접면 좌표계에서는 수직을 나타내니깐, 그것을 담은 나타내는 B값이 제일 클 것이고, 그래서 전반적으로 푸른빛을 내는 것이다.
- bit - format은 각 성분의 구간이 8-bit, 16-bit, [0, 255] 정수, [0, 1] [-1, 1] 부동소수점 등등 있다.
- 그래서 쉐이더에서 Normal Map에서 값을 가져와 사용할 때 조심해야 한다.
- 교재에서 든 예를 살펴보면, 쉐이더에서 사용할 때는 diffuse 텍스쳐와 똑같이 hlsl의 sample 함수를 이용해서 그 값을 가져온다.
- 성분별 8 - bit 정수값을 가지고 있는 [0, 255] 노멀맵을 sample로 가져오면, 내부적으로 [0, 1]의 float 구간으로 바꿔준다.
- 그리고 이것을 다시 작업 중인 좌표계에서 사용할 수 있도록 [-1, 1] 구간으로 mapping(사상)하는 것을 보여준다.
Norm = 2 * Norm - float3(1.f, 1.f, 1.f);
2. Texture Coordinates(접공간, Tangent Space)과 TBN
- 삼각형의 Vertex가 가지고 있는 UV좌표로 Normal 값을 Sampling 해서 가져온다.
- 이 값을 가지고 lighting이나 뭐... 그런 상호작용을 통해 여러 효과들을 표현하고 싶은데
- 조명 계산은 월드 좌표계에서 이뤄진다. 삼각형 위에 냅다 매핑된 노멀 값을 그대로 사용할 수 없다.
- 삼각형 위에 UV 만으로 냅다 매핑된, 제멋대로 자기만의 값을 가지고 있는 좌표계를 접공간이라고 하고,
- 이 좌표계의 축을 보통 TBN으로 나타낸다. UV에서 파생되어서,
- u 방향 (x축) - T(Tangent)
- v 방향 (y축) - B(Bitangent),
- uv와 동시에 수직인 (z축) - N(Normal)
(이제 이것을 월드로 바꿔야 한다.)
3. TBN을 World Coordinates로 변환
- 기존 Vertex 구조체에 float3 TangentU 값을 추가해 준다. (Bitangent 값은 Normal과의 외적으로 구한다. N x T)
- 그리고 메쉬를 생성할 때도 적절한(의도한) TangentU 값을 구해줘야 한다.
(간단한 거는 대충 구하고... 아니면 유한차분 뭐 그런 걸로 구하지 않을까?)
- 원래 쉐이더 내부에서는 (로컬 -> 월드)를 WorldMat으로 변환을 한다.
- Vertex 마다 생기는 TBN값을 basis로 사용하여 기저변환을 한다. 대충 아래와 같은 개념으로 (ㄹㅇㅋㅋ)
- 샘플링 한 Normal 값을 TBN에서 로컬로 바꾸고, 다시 로컬에서 월드로 바꿔서 사용하는 개념이다.
(쉐이더 코드에서는 좀 정리가 들어간 방법을 사용한다.)
4. App에서 해줘야 하는 것
- TangentU 값이 넘어가도록 Vertex Buffer를 생성해 준다.
- Normal Texture도 머... diffuse랑 똑같은 방법으로 넘겨주고..
- 그에 맞는 Root Signature나 Descriptor Heap을 생성한다.
5. 언급할 만한 예제에서 사용한 방법(구조)
-
6. 연습 문제
(클릭하면 커집니다.)
연습 문제 1
- NVIDIA Texture Tools Exporter, 이거 가지고 노멀맵을 만들어서 써보라는 건데.... 해보자
Texture Tools Exporter | NVIDIA Developer
- 이미지를 넣으면, 이런저런 옵션 값으로 Normal을 생성해 준다.
- 예제에서 제공하는 이 친구를 넣어보면
- 이렇게 높낮이를 설정할 성분을 뭘로 할 것인지 (높낮이로 hill을 만들어서 Normal을 만드는 과정인 것 같다.)
- 필터링은 어떻게 할 것인지, 최소 Z, 스케일링, 알파채널 등등 설정할 수 있다.
- 다른 옵션은 뭔지 잘 모르겠고, 일단 대충 만들어서 써보겠다.
- 이것을 skull에다가 대충 입혀보면
- 요런 느낌이 나온다.
연습 문제 2
- CrazyBump라는 프로그램을 사용하는 예제인데... 유료다 (설치하는 순간, trial 30 days 스타트라는데... ㅎㄷㄷ)
- 대충 체커보드를 해보겠다.
- 온갖 디테일을 최대로 하고, intensity 만 적당히 해주기로 했다.
- DirectXTK12으로 노멀맵을 가져오고.
- 요런 느낌쓰 ... 왼쪽이 노멀맵 적용한 거(오른쪽이 안 한 거)
- displacement map은 연습문제 5번에서 같이 해보겠다.
연습 문제 3
- TexTransform 이 Rotation 한다면, Tangent space도 그에 맞게 Rotation을 해야 하는데 그 이유가 뭔지 물어보는 연습문제다.
- (해답은 아닌 것 같고) 느낌을 말하면, Normal 값을 TexC 좌표로 가져오고, 그것을 Vertex struct에 있는 N과 T로 계산을 하는데... 아무래도 TexC로 샘플링이 되는 Normal은 (Diffuse도 마찬가지다.) Texture를 따르는 게 맞지 않나...
- 편의상 Vertex가 N과 T를 가지고 있고, 또 그것을 이용해서 TBN -> World로 바꾸는 Bumping이라는 걸 하는데, 이것도 접면 좌표계가 생기는 것도, Texture Transform에 기초하는 게 프로그래머가 의도하는 결과가 나올 것이라고 생각한다.
(내가 써놓은 거 읽는데도... 뭐라는 건지..)
- 무튼 이럴 때는 1) 월드 공간에서 T 축을 N을 기준으로 Rotation을 시키던가, 2) 접면공간에서 T에 Texture Transform을 적용시키면 된다.
연습 문제 4
- 조명 계산을 카메라 위치와, 빛의 위치를 접면공간으로 바꾼 다음에 조명 계산을 하는 연습이다.
(Texture Normal을 월드로 바꾼 다음에 하는 게 아니라)
- 픽셀 쉐이더에서 조명 계산을 할 때, 냅다 basis transform을 걸어버리기로 했다.
- LookUp Vector를 TBN으로 변환 시 의도하는 방향을 참조하지 않는가 보다... (나중에 고치자 ㄹㅇㅋㅋ)
연습 문제 5
- 일단 미뤄놨던, 연습 문제 2번 displacement map 결과이다.
- 테셀레이션을 한 결과에 변위를 적용시키는 것이다.
- Texture Scaling을 하지 않으면, 의도했던 대로 굴곡이 느껴진다.
- 하지만 Texture Tile을 좀 빡세게 하면, 삼각형 개수가 부족해서 예쁘게 displacment가 나오지 않는다.
- 찐으로 예쁘게 하고 싶으면, 타일 하나하나를 Patch로 넣어주는 것이 좋을 것이다.
- 쉐이더 코드
(클릭하면 커집니다.)
(Common.hlsl에 MaterialData에 uint DispMapIndex 멤버를 추가한다.)
(픽셀 쉐이더는 원래 예제와 똑같다.)
- App 코드
- 좀더 개선하면 요런 느낌스이다. ( + Patch를 생성하는 코드추가)
( + 그리고 displacement strength의 scale을 0.1로 줄였다. 타일이 작아진 만큼 그 변위도 작아져야 한다.)
- 이제 원래 5번 문제 테셀레이션과 + heightmap 2개를 이용해서, 파도를 표현하는 문제를 해결해 보자.
- rgb에 Normal을 a에 Displacement 값을 가지고 있는 2개의 텍스쳐 파일이다.
- 진짜 예쁘게 하려면, Tiling도 다르게 하고, Material Animation도 다르게 하고, Scaling도 다르게 하고, Diffuse도 다르게 하고...( 무튼 이런저런 수치들을 잘 만져가면서 진짜 파도처럼 만들 수 있겠지만)
- 구조를 싹 바꾸기는 귀찮아서, Material Animation만 방향을 다르게 해 줬다.
- 쉐이더 코드
- Material 구조체에 2번째, Texture Transform이 들어가도록 추가해 줬다.
- Normal과 Displace가 하나의 텍스쳐에 들어있으니, 채널만 잘 만져가면서 해주면 된다.
- 도메인 쉐이더 수정한 부분
- 픽셀 쉐이더 수정한 부분
- App에서는 Material Animation과 그 정보를 넘겨주기 위해서, Matrix 하나를 그냥 멤버로 두었고, MaterialData 구조체에 Float4x4 하나 추가해 줬다.
- 이전과 비슷한 방식으로 wave Texture View 만들고... 쉐이더 컴파일하고, PSO 만들고, Material 만들고/업데이트하고, RenderItem 만들고... 그냥 그렇게 했다.
- 결과는 요로콤 나온다.
7. 메모용
- Tutorial: Types of Normal Maps & Common Problems (80.lv)
- Releases · kmkolasinski/AwesomeBump (github.com)
- AwesomeBump (Crazy Bump 공짜 버전이다.)
- 연습 문제 5번을 풀 때, Tessellation으로 생긴 점들이 height map에 의해 고저차가 생기고, 그러면 삼각형이 바뀌고, TBN 공간도 바뀌는데... 이걸 계산하기가 힘들어서 Normal Map과 함께 사용하는 거겠지만... 그래도 궁금하다, Tessellation 단계에서 생성된 점들의 TBN 공간을 계산하는 방법이....
책 : DirectX 12를 이용한 3D 게임 프로그래밍 입문
'Programming > D3D12' 카테고리의 다른 글
[책공부] Ambient Occlusion + chap 21 연습 문제 (0) | 2024.03.08 |
---|---|
[책공부] Shadow Mapping + chap 20 연습 문제 (0) | 2024.03.04 |
[책공부] Cube Mapping + chap 18 연습 문제 (0) | 2024.02.26 |
[책공부] Picking + chap 17 연습 문제 (0) | 2024.02.22 |
[책공부] Instancing / Frustum Culling + chap 16 연습 문제 (0) | 2024.02.21 |