TBN 은 노말맵 할 때 주로 쓰는데 Tangent, Bi-Tangent, Normal 의 줄임말이다.
벡터가 각각 수직으로, 이것이 모여서 하나의 공간인 Tangent 공간을 만든다.
하지만 또 다른 뜻도 있다.
Tangent 공간은 Texture Mapping 과 밀접히 연관이 있다.
Normal Map 은 uv 로 각 픽셀의 노말값을 들고 오는데 이때의 값이 TBN 공간에 있다.
Normal Map 의 값은 Z 가 우리가 텍스쳐를 보는 방향, x, y 는 각각 uv 축을 의미한다.
이런 값을 그냥 사용할 순 없다.
우리는 이러한 Tangent 공간에서 Local, World 공간으로 변환해 사용해야 한다.
왜냐하면 world 에서 빛, 카메라, 사물 등의 위치가 공유되기 때문이다.
그러면 T, B, N 은 각각 무엇이어야 하겠는가? 당연히 Local 기준의 Tangent 공간 축이다.
Normal Map 의 x 축, y 축, z 축이 Local 의 x, y, z 축으로 매핑되어야 하기 때문이다.
우리는 노말맵 내의 (0,0,1) 이 표면과 수직이란 약속이란 것을 배우지 않았나.
우리는 서로 수직이고 Unit Vector 인 공간 축만 알면 공간 변환, 역변환 모두 할 수 있다.
이때의 축이 T,B,N 인 것이다.
그런데 Normal 은 vertex 에 필수요소인데, Tangent, Bi-Tangent 는 종종 없다.
이를 어떻게 구할까?
Polygon 은 삼각형으로 구성되어 있다. 이를 이용해서 구하면 된다.
사실 이렇게 구하면 엄밀하겐 삼각형 하나를 기준으로 만든 Tangent 공간이 되는데,
점 하나는 여러 삼각형과의 교점이라서 오차가 심할 수도 있다.
하지만 TBN 안주는데 어쩌겠나. 어차피 그렇게 오차는 안심하다.
하는 법은 다음과 같다.
삼각형 정점, p0, p1, p2 가 있으면 p2 - p0, p1 - p0 의 공간좌표와
그 점에서의 uv0, uv1, uv2 가 있어서 uv2 - uv0, uv1 - uv0 이 있을 것이다.
그럼 후자 * TBN = 전자. 이렇게 될 것이다.
이는 TBN = 후자역행렬 * 전자 이렇게 될 것이다.
그럼 위처럼 식을 만들 수 있다.
u0 은 p1 - p0 의 uv 값 중 u, u1 은 p2 - p0 의 값 중 u 이다. e, v 도 비슷하다.
p1 이 먼전지 p2 가 먼저인지 순서는 별로 중요하지 않다.
하지만 만약 p1 - p0, p2 - p0 으로 새로 normal 을 계산한다면 callback 이 시계방향인지 아닌지 꼭 살펴보자.
하지만 프로그래밍은 부동소수점 오류가 많아서 저대론 쓰지 않는다.
우리는 위 식을 이용해 T 만 구한다.
그리고 이미 주어진 값인 Normal 을 이용해 T 와 Normal 이 수직하게 만든다.
마지막으로 Tangent 를 Normalize 를 시킨다.
Binormal 은 Normal 과 위에서 구한 Tangent 를 Cross 시키면 자연스럽게 구할 수 있다.
물론 위의 역행렬을 써도 비슷하겐 얻을 수 있는데 필요한 조건을 맞추려면,
즉 서로 수직 + 유닛벡터 를 만족하려면 이게 젤 낫다.
Tangent = (Tangent - dot(Normal, Tangent) * Normal );
Tangent = Normalize(Tangent);
Binormal = Normalize(Cross(Normal, Tangent));
위처럼 말이다.
참고로 Cross 순서는 저게 맞다.
Cross(x,y)=>z, Cross(y,z) =>x, Cross(z,x)=>y 이렇고 순서바꾸면 음수로 나온다.
이렇게 구한 TBN을 이용해 Tangent Space 내의 값인 Normal Map 의 값을
Local Space로 그리고 World 를 곱해서 Specular, Diffuse 등에 쓰일 수 있다.
for (uint i = 0; i < indices.size() / 3; i++) { uint index0 = indices[i * 3 + 0]; uint index1 = indices[i * 3 + 1]; uint index2 = indices[i * 3 + 2]; Vector3 p0 = vertices[index0].pos; Vector3 p1 = vertices[index1].pos; Vector3 p2 = vertices[index2].pos; Vector3 e0 = p1 - p0; Vector3 e1 = p2 - p0; Vector2 uv0 = vertices[index0].uv; Vector2 uv1 = vertices[index1].uv; Vector2 uv2 = vertices[index2].uv; float u0 = uv1.x - uv0.x; float u1 = uv2.x - uv0.x; float v0 = uv1.y - uv0.y; float v1 = uv2.y - uv0.y; float r = 1.0f / (u0 * v1 - v0 * u1); // 역행렬용 Vector3 normal = vertices[index0].normal; normal = Vector3::Cross(e0, e1).Normalize(); Vector3 tangent; tangent.x = r * (v1 * e0.x - v0 * e1.x); tangent.y = r * (v1 * e0.y - v0 * e1.y); tangent.z = r * (v1 * e0.z - v0 * e1.z); if (isinf(r)) { tangent = normal; tangent.x += 0.01f; } tangent = (tangent - normal * Vector3::Dot(tangent, normal)).Normalize(); Vector3 binormal = Vector3::Cross(normal, tangent).Normalize();; vertices[index0].tangent = tangent; vertices[index1].tangent = tangent; vertices[index2].tangent = tangent; vertices[index0].binormal = binormal; vertices[index1].binormal = binormal; vertices[index2].binormal = binormal; vertices[index0].normal = normal; vertices[index1].normal = normal; vertices[index2].normal = normal; }
댓글 없음:
댓글 쓰기