음영 효과를 내기 위해서는 폴리곤의 갯수를 많이 늘리면 된다. 하지만 폴리곤 갯수를 무한정 늘리게 될 경우 그 모든 폴리곤을 렌더링 해주면서 부하가 심해진다는 것을 알 수 있다. 그렇기에 이런 문제를 해결하기 위해 Normal Mapping을 사용해준다.


Normal Vector를 저장하고, 이를 활용해서 정밀한 음영효과를 표현Normal Vector가 달라지기에 어디로 기준을 잡아야 하는지가 문제 -> Tangent Space를 활용Tangent Space에서 TBN벡터를 구해준다.Tangent Space가 어느 영역과 만나서 연산이 되는지는 쉐이더를 통해 만들때 결정된다.Space Transformation이 이루어져야 한다.Tangent Space -> Local Space -> World Space or View Space
struct VertexTextureNormalTangentData
{
Vec3 position = { 0, 0, 0 };
Vec2 uv = { 0, 0 };
Vec3 normal = { 0, 0, 0 };
Vec3 tangent = { 0, 0, 0 };
};
VertexTextureNormalTangentData구조체를 베이스로 하는 geometry로 변경shared_ptr<Geometry<VertexTextureNormalTangentData>> _geometry;
VertexTextureNormalTangentData구조체를 베이스로 하는 Create() 추가 static void CreateQuad(shared_ptr<Geometry<VertexTextureNormalTangentData>> geometry);
static void CreateCube(shared_ptr<Geometry<VertexTextureNormalTangentData>> geometry);
static void CreateSphere(shared_ptr<Geometry<VertexTextureNormalTangentData>> geometry);
static void CreateGrid(shared_ptr<Geometry<VertexTextureNormalTangentData>> geometry, int32 sizeX, int32 sizeZ);
void GeometryHelper::CreateQuad(shared_ptr<Geometry<VertexTextureNormalTangentData>> geometry)
{
vector<VertexTextureNormalTangentData> vtx;
vtx.resize(4);
vtx[0].position = Vec3(-0.5f, -0.5f, 0.f);
vtx[0].uv = Vec2(0.f, 1.f);
vtx[0].normal = Vec3(0.f, 0.f, -1.f);
vtx[0].tangent = Vec3(1.0f, 0.0f, 0.0f);
vtx[1].position = Vec3(-0.5f, 0.5f, 0.f);
vtx[1].uv = Vec2(0.f, 0.f);
vtx[1].normal = Vec3(0.f, 0.f, -1.f);
vtx[1].tangent = Vec3(1.0f, 0.0f, 0.0f);
vtx[2].position = Vec3(0.5f, -0.5f, 0.f);
vtx[2].uv = Vec2(1.f, 1.f);
vtx[2].normal = Vec3(0.f, 0.f, -1.f);
vtx[2].tangent = Vec3(1.0f, 0.0f, 0.0f);
vtx[3].position = Vec3(0.5f, 0.5f, 0.f);
vtx[3].uv = Vec2(1.f, 0.f);
vtx[3].normal = Vec3(0.f, 0.f, -1.f);
vtx[3].tangent = Vec3(1.0f, 0.0f, 0.0f);
geometry->SetVertices(vtx);
vector<uint32> idx = { 0, 1, 2, 2, 1, 3 };
geometry->SetIndices(idx);
}
void GeometryHelper::CreateCube(shared_ptr<Geometry<VertexTextureNormalTangentData>> geometry)
{
float w2 = 0.5f;
float h2 = 0.5f;
float d2 = 0.5f;
vector<VertexTextureNormalTangentData> vtx(24);
// 앞면 // normal // tangent
vtx[0] = VertexTextureNormalTangentData(Vec3(-w2, -h2, -d2), Vec2(0.0f, 1.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
vtx[1] = VertexTextureNormalTangentData(Vec3(-w2, +h2, -d2), Vec2(0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
vtx[2] = VertexTextureNormalTangentData(Vec3(+w2, +h2, -d2), Vec2(1.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
vtx[3] = VertexTextureNormalTangentData(Vec3(+w2, -h2, -d2), Vec2(1.0f, 1.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
// 뒷면
vtx[4] = VertexTextureNormalTangentData(Vec3(-w2, -h2, +d2), Vec2(1.0f, 1.0f), Vec3(0.0f, 0.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f));
vtx[5] = VertexTextureNormalTangentData(Vec3(+w2, -h2, +d2), Vec2(0.0f, 1.0f), Vec3(0.0f, 0.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f));
vtx[6] = VertexTextureNormalTangentData(Vec3(+w2, +h2, +d2), Vec2(0.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f));
vtx[7] = VertexTextureNormalTangentData(Vec3(-w2, +h2, +d2), Vec2(1.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f));
// 윗면
vtx[8] = VertexTextureNormalTangentData(Vec3(-w2, +h2, -d2), Vec2(0.0f, 1.0f), Vec3(0.0f, 1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f));
vtx[9] = VertexTextureNormalTangentData(Vec3(-w2, +h2, +d2), Vec2(0.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f));
vtx[10] = VertexTextureNormalTangentData(Vec3(+w2, +h2, +d2), Vec2(1.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f));
vtx[11] = VertexTextureNormalTangentData(Vec3(+w2, +h2, -d2), Vec2(1.0f, 1.0f), Vec3(0.0f, 1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f));
// 아랫면
vtx[12] = VertexTextureNormalTangentData(Vec3(-w2, -h2, -d2), Vec2(1.0f, 1.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f));
vtx[13] = VertexTextureNormalTangentData(Vec3(+w2, -h2, -d2), Vec2(0.0f, 1.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f));
vtx[14] = VertexTextureNormalTangentData(Vec3(+w2, -h2, +d2), Vec2(0.0f, 0.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f));
vtx[15] = VertexTextureNormalTangentData(Vec3(-w2, -h2, +d2), Vec2(1.0f, 0.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f));
// 왼쪽면
vtx[16] = VertexTextureNormalTangentData(Vec3(-w2, -h2, +d2), Vec2(0.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f));
vtx[17] = VertexTextureNormalTangentData(Vec3(-w2, +h2, +d2), Vec2(0.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f));
vtx[18] = VertexTextureNormalTangentData(Vec3(-w2, +h2, -d2), Vec2(1.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f));
vtx[19] = VertexTextureNormalTangentData(Vec3(-w2, -h2, -d2), Vec2(1.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f));
// 오른쪽면
vtx[20] = VertexTextureNormalTangentData(Vec3(+w2, -h2, -d2), Vec2(0.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f));
vtx[21] = VertexTextureNormalTangentData(Vec3(+w2, +h2, -d2), Vec2(0.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f));
vtx[22] = VertexTextureNormalTangentData(Vec3(+w2, +h2, +d2), Vec2(1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f));
vtx[23] = VertexTextureNormalTangentData(Vec3(+w2, -h2, +d2), Vec2(1.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f));
geometry->SetVertices(vtx);
vector<uint32> idx(36);
// 앞면
idx[0] = 0; idx[1] = 1; idx[2] = 2;
idx[3] = 0; idx[4] = 2; idx[5] = 3;
// 뒷면
idx[6] = 4; idx[7] = 5; idx[8] = 6;
idx[9] = 4; idx[10] = 6; idx[11] = 7;
// 윗면
idx[12] = 8; idx[13] = 9; idx[14] = 10;
idx[15] = 8; idx[16] = 10; idx[17] = 11;
// 아랫면
idx[18] = 12; idx[19] = 13; idx[20] = 14;
idx[21] = 12; idx[22] = 14; idx[23] = 15;
// 왼쪽면
idx[24] = 16; idx[25] = 17; idx[26] = 18;
idx[27] = 16; idx[28] = 18; idx[29] = 19;
// 오른쪽면
idx[30] = 20; idx[31] = 21; idx[32] = 22;
idx[33] = 20; idx[34] = 22; idx[35] = 23;
geometry->SetIndices(idx);
}
void GeometryHelper::CreateGrid(shared_ptr<Geometry<VertexTextureNormalTangentData>> geometry, int32 sizeX, int32 sizeZ)
{
vector<VertexTextureNormalTangentData> vtx;
for (int32 z = 0; z < sizeZ + 1; z++)
{
for (int32 x = 0; x < sizeX + 1; x++)
{
VertexTextureNormalTangentData v;
v.position = Vec3(static_cast<float>(x), 0, static_cast<float>(z));
v.uv = Vec2(static_cast<float>(x), static_cast<float>(sizeZ - z));
v.normal = Vec3(0.f, 1.f, 0.f);
v.tangent = Vec3(1.f, 0.f, 0.f);
vtx.push_back(v);
}
}
geometry->SetVertices(vtx);
vector<uint32> idx;
for (int32 z = 0; z < sizeZ; z++)
{
for (int32 x = 0; x < sizeX; x++)
{
// [0]
// | \
// [2] - [1]
idx.push_back((sizeX + 1) * (z + 1) + (x));
idx.push_back((sizeX + 1) * (z)+(x + 1));
idx.push_back((sizeX + 1) * (z)+(x));
// [1] - [2]
// \ |
// [0]
idx.push_back((sizeX + 1) * (z)+(x + 1));
idx.push_back((sizeX + 1) * (z + 1) + (x));
idx.push_back((sizeX + 1) * (z + 1) + (x + 1));
}
}
geometry->SetIndices(idx);
}
void GeometryHelper::CreateSphere(shared_ptr<Geometry<VertexTextureNormalTangentData>> geometry)
{
float radius = 0.5f; // 구의 반지름
uint32 stackCount = 20; // 가로 분할
uint32 sliceCount = 20; // 세로 분할
vector<VertexTextureNormalTangentData> vtx;
VertexTextureNormalTangentData v;
// 북극
v.position = Vec3(0.0f, radius, 0.0f);
v.uv = Vec2(0.5f, 0.0f);
v.normal = v.position;
v.normal.Normalize();
v.tangent = Vec3(1.0f, 0.0f, 0.0f);
v.tangent.Normalize();
vtx.push_back(v);
float stackAngle = XM_PI / stackCount;
float sliceAngle = XM_2PI / sliceCount;
float deltaU = 1.f / static_cast<float>(sliceCount);
float deltaV = 1.f / static_cast<float>(stackCount);
for (uint32 y = 1; y <= stackCount - 1; ++y)
{
float phi = y * stackAngle;
for (uint32 x = 0; x <= sliceCount; ++x)
{
float theta = x * sliceAngle;
v.position.x = radius * sinf(phi) * cosf(theta);
v.position.y = radius * cosf(phi);
v.position.z = radius * sinf(phi) * sinf(theta);
v.uv = Vec2(deltaU * x, deltaV * y);
v.normal = v.position;
v.normal.Normalize();
v.tangent.x = -radius * sinf(phi) * sinf(theta);
v.tangent.y = 0.0f;
v.tangent.z = radius * sinf(phi) * cosf(theta);
v.tangent.Normalize();
vtx.push_back(v);
}
}
// 남극
v.position = Vec3(0.0f, -radius, 0.0f);
v.uv = Vec2(0.5f, 1.0f);
v.normal = v.position;
v.normal.Normalize();
v.tangent = Vec3(1.0f, 0.0f, 0.0f);
v.tangent.Normalize();
vtx.push_back(v);
geometry->SetVertices(vtx);
vector<uint32> idx(36);
// 북극 인덱스
for (uint32 i = 0; i <= sliceCount; ++i)
{
idx.push_back(0);
idx.push_back(i + 2);
idx.push_back(i + 1);
}
// 몸통 인덱스
uint32 ringVertexCount = sliceCount + 1;
for (uint32 y = 0; y < stackCount - 2; ++y)
{
for (uint32 x = 0; x < sliceCount; ++x)
idx.push_back(1 + (y)*ringVertexCount + (x));
idx.push_back(1 + (y)*ringVertexCount + (x + 1));
idx.push_back(1 + (y + 1) * ringVertexCount + (x));
idx.push_back(1 + (y + 1) * ringVertexCount + (x));
idx.push_back(1 + (y)*ringVertexCount + (x + 1));
idx.push_back(1 + (y + 1) * ringVertexCount + (x + 1));
}
}
// 남극 인덱스
uint32 bottomIndex = static_cast<uint32>(vtx.size()) - 1;
uint32 lastRingStartIndex = bottomIndex - ringVertexCount;
for (uint32 i = 0; i < sliceCount; ++i)
{
idx.push_back(bottomIndex);
idx.push_back(lastRingStartIndex + i);
idx.push_back(lastRingStartIndex + i + 1);
}
geometry->SetIndices(idx);
}
struct VertexTetrueNormalTangent
{
float4 position : POSITION;
float2 uv : TEXCOORD;
float3 normal : NOERMAL;
float3 tangent : TANGENT;
};
struct MeshOutput
{
float4 position : SV_POSITION; //SV : System Value
float3 worldPosition : POSITION1;
float2 uv : TEXCOORD;
float3 normal : NORMAL;
float3 tangent : TANGENT;
};
ComputeNormalMapping()를 생성// inout : 포인터 or 참조 느낌
void ComputeNormalMapping(inout float3 normal, float3 tangent, float2 uv)
{
// [0, 255] -> [0, 1] 변환
float4 map = NormalMap.Sample(LinearSampler, uv);
if (any(map.rgb) == false)
return;
float3 N = normalize(normal); // z
float3 T = normalize(tangent); // x
float3 B = normalize(cross(N, T)); // y
// Tan -> World 변환 행렬
float3x3 TBN = float3x3(T, B, N);
// [0,1] -> [-1, 1] 변환
float3 tangentSpaceNormal = (map.rgb * 2.0f - 1.0f);
float3 worldNormal = mul(tangentSpaceNormal, TBN);
normal = worldNormal;
}
VS에서 tangent값 추가PS에서 변환시킨 normal값 적용MeshOutput VS(VertexTetrueNormalTangent input)
{
MeshOutput output;
output.position = mul(input.position, W);
output.worldPosition = input.position.xyz;
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = mul(input.normal, (float3x3) W);
output.tangent = mul(input.tangent, (float3x3) W);
return output;
}
float4 PS(MeshOutput input) : SV_TARGET
{
ComputeNormalMapping(input.normal, input.tangent, input.uv);
float4 color = ComputeLight(input.normal, input.uv, input.worldPosition);
return color;
}
NormalMap추가void NormalMappingDemo::Init()
{
RESOURCES->Init();
_shader = make_shared<Shader>(L"14.NormalMapping.fx");
// Material
{
shared_ptr<Material> material = make_shared<Material>();
{
material->SetShader(_shader);
}
{
auto texture = RESOURCES->Load<Texture>(L"Leather", L"../Resources/Textures/Leather.jpg");
material->SetDiffuseMap(texture);
}
{
auto texture = RESOURCES->Load<Texture>(L"LeatherNormap", L"../Resources/Textures/Leather_Normal.jpg");
material->SetNormalMap(texture);
}
MaterialDesc& desc = material->GetMaterialDesc();
desc.ambient = Vec4(1.f);
desc.diffuse = Vec4(1.f);
desc.specular = Vec4(1.f);
RESOURCES->Add(L"Leather", material);
}
// Camera
_camera = make_shared<GameObject>();
_camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -10.f });
_camera->AddComponent(make_shared<Camera>());
_camera->AddComponent(make_shared<CameraScript>());
// Object
_obj = make_shared<GameObject>();
_obj->GetOrAddTransform();
_obj->AddComponent(make_shared<MeshRenderer>());
{
auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
_obj->GetMeshRenderer()->SetMesh(mesh);
}
{
auto material = RESOURCES->Get<Material>(L"Leather");
_obj->GetMeshRenderer()->SetMaterial(material);
}
// Object2
_obj2 = make_shared<GameObject>();
_obj2->GetOrAddTransform()->SetPosition(Vec3{ 0.5f, 0.f, 2.f });
_obj2->AddComponent(make_shared<MeshRenderer>());
{
auto mesh = RESOURCES->Get<Mesh>(L"Cube");
_obj2->GetMeshRenderer()->SetMesh(mesh);
}
{
auto material = RESOURCES->Get<Material>(L"Leather");
_obj2->GetMeshRenderer()->SetMaterial(material);
}
RENDER->Init(_shader);
}


Tangent Space
- 모델 표면의 로컬(Local) 좌표계
- 즉, 메시의 각 정점마다 그 표면 기준으로 X, Y, Z 방향(TBN벡터)을 정의한 좌표 공간
- T (Tangent) : X축, 표면의 U 텍스처 좌표 방향
- B (Bitangent) : Y축, 표면의 V 텍스처 좌표 방향
- N (Normal) : Z축, 표면에서 수직 방향
- 보통 노멀맵의 RGB는 TangentSpace 기준의 노멀방향으로 저장되지만, 조명 계산은 World Space 나 View Space에서 이루어지기 때문에, 월드나 뷰로 변환해줘야한다.
- 이러한 과정을 통해서 픽셀 단위의 "가짜 법선 방향"을 실제 월드 좌표 기준으로 바꿔 조명을 정확히 계산할 수 있다.