[DX] 노멀 벡터(Normal Vector)
🔀 노멀 벡터(Normal Vector)
조명/음영을 구현하기 위해선 노멀 벡터라는 개념을 잘 알아야한다.
💡 Normal 이란?
그리드가 있다면 1칸만 가져다 빼보자.
그럼 하나의 사각형은 두개의 삼각형으로 그려져 있다.
이때 (V0→V2)로 가는 벡터 하나와 (V0→V1)으로 가는 벡터 하나를 두고, 두 벡터에 수직하는 벡터가 바로 노멀 벡터이다.
두 벡터에 수직하는 벡터를 만드는 방법은 "외적"을 이용하면 된다.
(과거에 이미 카메라에서 위 벡터와 전방 벡터을 외적해서 두 벡터에 수직하는 오른쪽 벡터를 만든적이 있다)
즉, 노말 벡터를 구하려면 A (V0→V1), B(V0→V2) 두 A,B벡터에서 수직하는 벡터를 외적으로 구하면 된다.
A 벡터와 B 벡터를 만드는 방법 아래와 같다.
벡터 A = V1 - V0 = V0 → V1
벡터 B = V2 - V0 = V0 → V2
A cross B 의 결과는 Normal Vector이고, 이렇게 나온 노멀벡터는 방향벡터이다.
Normal Vector = Normalize(cross (A,B))
노멀 벡터는 인접한 다른 정점들에게도 다 영향을 받기 때문에 인접한 정점들의 Normal 벡터를 다 더해서 누적한 후, Normal에 대한 평균을 구해야 한다.
그러나 정점 하나하나가 몇개의 인접한 정점을 갖는지 개수를 각각 구하는것은 어렵기 때문에 Normalize를 사용한다.
Normalize 단위벡터는 자신의 크기를 자신으로 나누기 때문에 평균과 완전 똑같진 않지만 유사한 값이 나온다.
평균이라는 의미가 누적시킨 뒤, 누적시킨 횟수만큼을 나누는 값인데, 그 횟수를 크기라고 보면 엇비슷하게 자신의 크기로 나눈결과와 비슷하게 사용할 수 있다.
📄 Terrain.cpp
아래 소스코드는 노멀벡터를 생성하고 출력하는 부분만 발췌한 부분이다.
void Terrain::CreateNormalData()
{
for (UINT i = 0; i < indexCount / 3; i++)
{
UINT index0 = indices[i * 3 + 0];
UINT index1 = indices[i * 3 + 1];
UINT index2 = indices[i * 3 + 2];
TerrainVertex v0 = vertices[index0];
TerrainVertex v1 = vertices[index1];
TerrainVertex v2 = vertices[index2];
Vector3 a = v1.Position - v0.Position;
Vector3 b = v2.Position - v0.Position;
Vector3 normal;
D3DXVec3Cross(&normal, &a, &b);
// Normal을 누적
vertices[index0].Normal += normal;
vertices[index1].Normal += normal;
vertices[index2].Normal += normal;
}
// 평균을 낼 겸 Normal 벡터를 Normalize 한다
for (UINT i = 0; i < vertexCount; i++)
D3DXVec3Normalize(&vertices[i].Normal, &vertices[i].Normal);
}
void Terrain::Render()
{
// Normal Vector Render
for (int i = 0; i < vertexCount; i++)
{
Vector3 start = vertices[i].Position;
Vector3 end = vertices[i].Position + vertices[i].Normal * 2;
DebugLine::Get()->RenderLine(start, end, Color(0, 1, 0, 1));
}
UINT stride = sizeof(TerrainVertex);
UINT offset = 0;
D3D::GetDC()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
D3D::GetDC()->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
D3D::GetDC()->IASetIndexBuffer(indexBuffer, DXGI_FORMAT_R32_UINT, 0);
shader->DrawIndexed(0, pass, indexCount);
}
(참고: 외적에서 순서는 중요하다. 순서가 바뀌면 수직축이 뒤집힘)