필요한 개념
일반 이미지를 이용해서 이미지의 굴곡을 더 강하게 해주는것(입체감이 느껴지도록 눈속임)
정점을 늘리면 렌더링 시간이 길어지니까, Pixel shader를 이용해서 입체감을 입히는 기법
테셀레이션을 이용한 실제 굴곡 넣는 게임도 있지만,
현재 NormalMapping을 안쓰는 게임이 없다.
3D 디자인 쪽에서는 입체감을 주는 것을 BUMP Mapping이라고 한다.
프로그래머들은 NormalMapping이라고 부른다.
Parallax Occlusion이라는 것도 있는데 따로 다루지 않음(복잡해서)
Tessellation도 있는데 이거는 실제로 정점을 굴곡있게 만든다.
NormalMapping : 어떤 텍스처를 주고 정점을 늘리지 않고 입체감이 느끼도록 해주는 기법
Microsoft DirectX SDK (June 2010)\Samples\SampleBrowser\SampleBrowser.exe를 실행시키면 Sample 프로젝트와 실행해서 테스트해볼 수 있다.
여기서는 DetailTessellation11을 보았다.
사람이 입체감(굴곡)을 느끼게 되는 것은 음영이 있기 때문이다.
노멀 맵은 픽셀의 값을 방향으로 변환하여 노멀 벡터로 간주하여 라이팅과의 내적으로 음영을 만든다.
3DMax에서 이 맵을 만들기 위해 BumpMap 텍스처를 만들 수 있게 지원
음영을 만들기 위해
NormalMap 텍스처 하나하나 픽셀값을 읽어들이고 그 픽셀값을 Normal(-1 ~ +1)로 바꿔준다. 빛의 반대 방향과 내적함.(그림 7-1)
문제는 접선공간이 있다.
접선 공간? 외곽선에 접하는 공간(그림 7-2)
모델을 예를 들자면 각 정점이 있고 uv대로 텍스쳐가 발라질 것이고, 정점 사이에는 픽셀들이 있다.
그렇게 되면 접하는 면이하나 생긴다.(그거를 수학에서는 tangent라 부름)
접선공간에 수직이 Normal이 된다.
평면상의 노멀 벡터는 Y축 방향
모델 공간에서는 Z축 방향이 된다.
(ex) 노멀벡터가 Y축인 땅바닥에 있던 판을 캐릭터에 배에 대었다면 노멀이 Z축이 된다.)
Z - Normal
X : U - Tangent
Y : V - BiNoraml or BiTangent
Z - Normal
X : U - Tangent(가로 방향을 탄젠트라고함)
Y : V - BiNoraml(바이노멀) or BiTangent(바이탄젠트)
바이노멀 혹은 바이탄젠트는 외적을 어느방향으로 하느냐에 따라 달라짐
Tangent를 만들때 정규직교화가 쓰인다. 여기서는 안쓴다. 왜?
Assimp 모델을 로딩할때 Tangent 정보를 로딩할 수 있어서
평면이나 이런거 만들때는 방향을 만들어서 간단히 방향값을 넣을 거임
지형이나 실시간 이런거 할때는 정규직교화를 써서 축을 만든다. 그때는 방향을 회전시켜서 만듬
지면 같은거 편집할때 하면 Normal이 실시간적으로 바뀐다. 정규직교화를 다루게 된다.(나중에 배움)
정규직교화(그람슈미트) -> 방향을 비트는것(찾아볼것)
그래서 지금은 Tangent방향을 임의로 매핑시키는 것으로 만든다.
이미지에서 가져온 벡터의 값(방향)이 우리가 만든 축을 가지고 만든 접선공간으로 변환되어야함 그래야 우리가 원하는 대로 음영을 만들 수 있다.
이미지(텍스쳐)의 좌표는 U,V이다. U -> X -> Tangent에 매핑, V -> Y - BiNormal에 매핑, Z -> Normal을 가지고 함
이미지 RGB에서 가져온 값은 단순 2D이다. 이 2D를 실제 쓰는 3D공간으로 바꾸기 위해 탄젠트공간이 필요
탄젠트공간은
Z - Normal
X : U - Tangent
Y : V - BiNoraml or BiTangent(정점 Normal(z), 정점 Tangent(x) 두개를 외적해서 BiNormal(y))
이 x,y,z 축으로 만들어진 공간으로 정의됨(TBN 공간)
이미지에서 나온 RGB 값을 방향으로 바꾼 방향을 우리가 만든 탄젠트 공간으로 변환한다.
NormalMap이미지를 보았을때 푸른색 방향이 많다. 이유는?(뱡향을 픽셀로 저장해온것을 우리가 역으로 가져오는것)
밝은 뱡향이 많다. B가 Z에 매핑이 되어서 Normal 값이다. 표면상으로 다르다.(일반적인 평면이라면 Y가 Normal인 반면,
모델은 Z가 Normal값이다), 앞으로 나가는 값(법선벡터)이다.
Max같은 경우는 Y를 Z로 쓰는 경우가 있다. 그럴때는 녹색 계열의 NormalMap이미지가 나오는 것이다.
Tangent가 어려우면 Normal과 수직하는 x방향이라고 생각하면 된다.
Tangent는 Model, Animation 같은 경우는 모델 불러올때 Tangent 정보도 불러온다.
그런데 Mesh들은 그 작업이 안되어있다. Mesh는 만들어야함
Mesh 탄젠트 만들때
탄젠트(u) -> X방향, 우측(정확히는 U방향이다. U도 우측방향이다.)
탄젠트 벡터가 각 면의 방향으로 회전된다고 생각하고 계산하면 된다.
Front(1, 0, 0)
// 오른쪽 방향에 매핑되는데 뒤집어 지니까
Back(-1, 0, 0)
이런식으로 다 오른쪽 방향으로 매핑시켜주면 된다.
원래는 정확히 탄젠트 벡터를 계산하려면 U,V로 X,Y 방향 만들어서 복잡한 모델은 없어서 그렇게 할필요 없다.
-> 그래서 오른쪽 방향에만 매핑시킴(정확히는 U방향(이미지를 샘플링할 공간이어서))
저번에 설명했던 https://cpetry.github.io/NormalMap-Online/ 에서 만들면 된다.
요약
정점의 탄젠트 벡터는 오른쪽(X,U) 방향으로 생성
TBN 공간 생성시 Tangent 벡터는 정규 직교(그람슈미트) 사용
Mesh.h
class Mesh
{
public:
// 탄젠트 추가
typedef VertexTextureNormalTangent MeshVertex;
...
}
MeshCube, MeshGrid, MeshQuad.cpp 수정(탄젠트 추가)
void MeshCube::Create()
{
float w = 0.5f; float h = 0.5f; float d = 0.5f;
// z가 +1이면 모니터 안쪽 방향이다. -1이면 모니터 바깥쪽 방향
// 깔끔하게 하기 위해 뒤집는 부분이 있다...
// Normal은 바깥쪽으로 받아야 빛을 정상적으로 받을 수 있다.
// Normal은 항상 겉 방향이라고 생각하면 된다.
vector<MeshVertex> v;
//Mesh 탄젠트 만들때
//탄젠트(u)->X방향, 우측(정확히는 U방향이다.U도 우측방향이다.)
//탄젠트 벡터가 각 면의 방향으로 회전된다고 생각하고 계산하면 된다.
//이런식으로 다 오른쪽 방향으로 매핑시켜주면 된다.
//이런식으로 다 오른쪽 방향으로 매핑시켜주면 된다.
//원래는 정확히 탄젠트 벡터를 계산하려면 U, V로 X, Y 방향 만들어서 복잡한 모델은 없어서 그렇게 할필요 없다.
//->그래서 오른쪽 방향에만 매핑시킴(정확히는 U방향(이미지를 샘플링할 공간이어서))
//Front
v.push_back(MeshVertex(-w, -h, -d, 0, 1, 0, 0, -1, 1, 0, 0));
v.push_back(MeshVertex(-w, +h, -d, 0, 0, 0, 0, -1, 1, 0, 0));
// 우상으로 잡아야 깔끔히 나옴
v.push_back(MeshVertex(+w, +h, -d, 1, 0, 0, 0, -1, 1, 0, 0));
v.push_back(MeshVertex(+w, -h, -d, 1, 1, 0, 0, -1, 1, 0, 0));
//Back
v.push_back(MeshVertex(-w, -h, +d, 1, 1, 0, 0, 1, -1, 0, 0));
v.push_back(MeshVertex(+w, -h, +d, 0, 1, 0, 0, 1, -1, 0, 0));
v.push_back(MeshVertex(+w, +h, +d, 0, 0, 0, 0, 1, -1, 0, 0));
v.push_back(MeshVertex(-w, +h, +d, 1, 0, 0, 0, 1, -1, 0, 0));
//Top
v.push_back(MeshVertex(-w, +h, -d, 0, 1, 0, 1, 0, 1, 0, 0));
v.push_back(MeshVertex(-w, +h, +d, 0, 0, 0, 1, 0, 1, 0, 0));
v.push_back(MeshVertex(+w, +h, +d, 1, 0, 0, 1, 0, 1, 0, 0));
v.push_back(MeshVertex(+w, +h, -d, 1, 1, 0, 1, 0, 1, 0, 0));
//Bottom
v.push_back(MeshVertex(-w, -h, -d, 1, 1, 0, -1, 0, -1, 0, 0));
v.push_back(MeshVertex(+w, -h, -d, 0, 1, 0, -1, 0, -1, 0, 0));
v.push_back(MeshVertex(+w, -h, +d, 0, 0, 0, -1, 0, -1, 0, 0));
v.push_back(MeshVertex(-w, -h, +d, 1, 0, 0, -1, 0, -1, 0, 0));
//Left
v.push_back(MeshVertex(-w, -h, +d, 0, 1, -1, 0, 0, 0, 0, -1));
v.push_back(MeshVertex(-w, +h, +d, 0, 0, -1, 0, 0, 0, 0, -1));
v.push_back(MeshVertex(-w, +h, -d, 1, 0, -1, 0, 0, 0, 0, -1));
v.push_back(MeshVertex(-w, -h, -d, 1, 1, -1, 0, 0, 0, 0, -1));
//Right
v.push_back(MeshVertex(+w, -h, -d, 0, 1, 1, 0, 0, 0, 0, 1));
v.push_back(MeshVertex(+w, +h, -d, 0, 0, 1, 0, 0, 0, 0, 1));
v.push_back(MeshVertex(+w, +h, +d, 1, 0, 1, 0, 0, 0, 0, 1));
v.push_back(MeshVertex(+w, -h, +d, 1, 1, 1, 0, 0, 0, 0, 1));
...
}
void MeshGrid::Create()
{
...
vector<MeshVertex> v;
for (UINT z = 0; z < countZ; z++)
{
for (UINT x = 0; x < countX; x++)
{
MeshVertex vertex;
// 위치를 가운데로 잡기위해서 빼줌
vertex.Position = Vector3(float(x - w), 0, (float)z - h);
// 얘는 굴곡없는 판이라서 1방향만 준다.
vertex.Normal = Vector3(0, 1, 0);
vertex.Tangent = Vector3(1, 0, 0);
vertex.Uv.x = (float)x / (float)(countX - 1) * offsetU;
vertex.Uv.y = (float)z / (float)(countZ - 1) * offsetV;
v.push_back(vertex);
}
}
...
}
void MeshQuad::Create()
{
...
v.push_back(MeshVertex(-w, -h, 0, 0, 1, 0, 0, -1, 1, 0, 0));
v.push_back(MeshVertex(-w, +h, 0, 0, 0, 0, 0, -1, 1, 0, 0));
v.push_back(MeshVertex(+w, -h, 0, 1, 1, 0, 0, -1, 1, 0, 0));
v.push_back(MeshVertex(+w, +h, 0, 1, 0, 0, 0, -1, 1, 0, 0));
...
}
MeshCylinder.cpp 수정(탄젠트 추가)
void MeshCylinder::Create()
{
vector<MeshVertex> v;
float stackHeight = height / (float)stack_count;
float radiusStep = (topRadius - bottomRadius) / (float)stack_count;
UINT ringCount = stack_count + 1;
for (UINT i = 0; i < ringCount; i++)
{
float y = -0.5f * height + i * stackHeight;
float r = bottomRadius + i * radiusStep;
float theta = 2.0f * Math::PI / (float)slice_count;
for (UINT k = 0; k <= slice_count; k++)
{
float c = cosf(k * theta);
float s = sinf(k * theta);
MeshVertex vertex;
vertex.Position = Vector3(r * c, y, r * s);
vertex.Uv = Vector2((float)k / (float)slice_count, 1.0f - (float)i / (float)stack_count);
// 뒤에 가면 노멀매핑때 사용(이거는 뒤에 가서 따로 설명)
// 뒤집어서 구함
Vector3 tangent = Vector3(-s, 0.0f, c);
float dr = bottomRadius - topRadius;
Vector3 biTangent = Vector3(dr * c, -height, dr * s);
// tangent와 biTangent을 역순으로 계산해보면 회전각을 뒤집어서 오른쪽으로 만든 것을 알 수 있음
D3DXVec3Cross(&vertex.Normal, &tangent, &biTangent);
D3DXVec3Normalize(&vertex.Normal, &vertex.Normal);
vertex.Tangent = tangent;
v.push_back(vertex);
}
}
vector<UINT> i;
UINT ringVertexCount = slice_count + 1;
for (UINT y = 0; y < stack_count; y++)
{
for (UINT x = 0; x < slice_count; x++)
{
i.push_back(y * ringVertexCount + x);
i.push_back((y + 1) * ringVertexCount + x);
i.push_back((y + 1) * ringVertexCount + (x + 1));
i.push_back(y * ringVertexCount + x);
i.push_back((y + 1) * ringVertexCount + x + 1);
i.push_back(y * ringVertexCount + x + 1);
}
}
BuildTopCap(v, i);
BuildBottomCap(v, i);
vertices = new MeshVertex[v.size()];
vertexCount = v.size();
copy(v.begin(), v.end(), stdext::checked_array_iterator<MeshVertex*>(vertices, vertexCount));
indices = new UINT[i.size()];
indexCount = i.size();
copy(i.begin(), i.end(), stdext::checked_array_iterator<UINT*>(indices, indexCount));
}
void MeshCylinder::BuildTopCap(vector<MeshVertex>& vertices, vector<UINT>& indices)
{
float y = 0.5f * height;
float theta = 2.0f * Math::PI / (float)slice_count;
for (UINT i = 0; i <= slice_count; i++)
{
float x = topRadius * cosf(i * theta);
float z = topRadius * sinf(i * theta);
float u = x / height + 0.5f;
float v = z / height + 0.5f;
vertices.push_back(MeshVertex(x, y, z, u, v, 0, 1, 0, 1, 0, 0));
}
vertices.push_back(MeshVertex(0, y, 0, 0.5f, 0.5f, 0, 1, 0, 1, 0, 0));
UINT baseIndex = vertices.size() - slice_count - 2;
UINT centerIndex = vertices.size() - 1;
for (UINT i = 0; i < slice_count; i++)
{
indices.push_back(centerIndex);
indices.push_back(baseIndex + i + 1);
indices.push_back(baseIndex + i);
}
}
void MeshCylinder::BuildBottomCap(vector<MeshVertex>& vertices, vector<UINT>& indices)
{
float y = -0.5f * height;
float theta = 2.0f * Math::PI / (float)slice_count;
for (UINT i = 0; i <= slice_count; i++)
{
float x = bottomRadius * cosf(i * theta);
float z = bottomRadius * sinf(i * theta);
float u = x / height + 0.5f;
float v = z / height + 0.5f;
vertices.push_back(MeshVertex(x, y, z, u, v, 0, -1, 0, -1, 0, 0));
}
vertices.push_back(MeshVertex(0, y, 0, 0.5f, 0.5f, 0, -1, 0, -1, 0, 0));
UINT baseIndex = vertices.size() - slice_count - 2;
UINT centerIndex = vertices.size() - 1;
for (UINT i = 0; i < slice_count; i++)
{
indices.push_back(centerIndex);
indices.push_back(baseIndex + i);
indices.push_back(baseIndex + i + 1);
}
}
MeshSphere.cpp 수정(탄젠트 추가)
void MeshSphere::Create()
{
vector<MeshVertex> v;
// 위에서 부터 점을 찍어서 그려나간다.
// 맨위니까 Normal(0, 1, 0)
// 윗점
// 위꼭지점이니까 Tangent도 오른쪽
v.push_back(MeshVertex(0, radius, 0, 0, 0, 0, 1, 0, 1, 0, 0));
// 파이
float phiStep = Math::PI / stack_count;
// 세타
float thetaStep = Math::PI * 2.0f / slice_count;
// 밑지점은 따로 찍을거여서 stack_count - 1까지
for (UINT i = 1; i <= stack_count - 1; i++)
{
float phi = i * phiStep;
for (UINT k = 0; k <= slice_count; ++k)
{
float theta = k * thetaStep;
// 구면좌표계로 그려나감
Vector3 p = Vector3
(
(radius * sinf(phi) * cosf(theta)),
(radius * cos(phi)),
(radius * sinf(phi) * sinf(theta))
);
Vector3 n;
// 위치를 방향으로 쓸수있다.(x의 1, 0, 0지점이 있다면(위치)
// Normal로 쓰면 x방향이된다. 그거를 위치를 Normalize하면 방향크기를 1로씀
// 위치를 방향으로 씀
D3DXVec3Normalize(&n, &p);
// tangent는 u방향으로 매핑됨
Vector3 t = Vector3
(
-(radius * sinf(phi) * sinf(theta)),
0.0f,
(radius * sinf(phi) * cosf(theta))
);
D3DXVec3Normalize(&t, &t);
Vector2 uv = Vector2(theta / (Math::PI * 2), phi / Math::PI);
v.push_back(MeshVertex(p.x, p.y, p.z, uv.x, uv.y, n.x, n.y, n.z, t.x, t.y, t.z));
}
}
// 밑점
v.push_back(MeshVertex(0, -radius, 0, 0, 0, 0, -1, 0, -1, 0, 0));
vertices = new MeshVertex[v.size()];
vertexCount = v.size();
copy(v.begin(), v.end(), stdext::checked_array_iterator<MeshVertex*>(vertices, vertexCount));
// Indices
vector<UINT> i;
for (UINT k = 1; k <= slice_count; k++)
{
i.push_back(0);
i.push_back(k + 1);
i.push_back(k);
}
UINT baseIndex = 1;
UINT ringVertexCount = slice_count + 1;
for (UINT k = 0; k < slice_count - 2; k++)
{
for (UINT j = 0; j < slice_count; j++)
{
i.push_back(baseIndex + k * ringVertexCount + j);
i.push_back(baseIndex + k * ringVertexCount + j + 1);
i.push_back(baseIndex + (k + 1) * ringVertexCount + j);
i.push_back(baseIndex + (k + 1) * ringVertexCount + j);
i.push_back(baseIndex + k * ringVertexCount + j + 1);
i.push_back(baseIndex + (k + 1) * ringVertexCount + j + 1);
}
}
UINT southPoleIndex = v.size() - 1;
baseIndex = southPoleIndex - ringVertexCount;
for (UINT k = 0; k < slice_count; k++)
{
i.push_back(southPoleIndex);
i.push_back(baseIndex + k);
i.push_back(baseIndex + k + 1);
}
indices = new UINT[i.size()];
indexCount = i.size();
copy(i.begin(), i.end(), stdext::checked_array_iterator<UINT*>(indices, indexCount));
}
Converter.cpp 수정(모델, 애니메이션 탄젠트 정보 읽어오기)
void Converter::ReadMeshData(aiNode* node, int bone)
{
...
for (UINT i = 0; i < node->mNumMeshes; ++i)
{
UINT index = node->mMeshes[i];
aiMesh* srcMesh = scene->mMeshes[index];
aiMaterial* material = scene->mMaterials[srcMesh->mMaterialIndex];
mesh->MaterialName = material->GetName().C_Str();
UINT startVertex = mesh->Vertices.size();
// 계속 반복면서 정점이 쌓인다.
for (UINT v = 0; v < srcMesh->mNumVertices; v++)
{
Model::ModelVertex vertex;
memcpy(&vertex.Position, &srcMesh->mVertices[v], sizeof(Vector3));
if (srcMesh->HasTextureCoords(0))
memcpy(&vertex.Uv, &srcMesh->mTextureCoords[0][v], sizeof(Vector2));
if (srcMesh->HasNormals())
memcpy(&vertex.Normal, &srcMesh->mNormals[v], sizeof(Vector3));
// aiProcess_CalcTangentSpace을 Scene 생성시에 주었어서, 다 Tangent 정보를 가지고 있다.
// 우리는 BiNormal을 쓸거니까 Tangent만 저장한다.
if(srcMesh->HasTangentsAndBitangents())
memcpy(&vertex.Tangent, &srcMesh->mTangents[v], sizeof(Vector3));
mesh->Vertices.push_back(vertex);
}
...
}
}
NormalMappingDemo.h
#pragma once
#include "Systems/IExecute.h"
class NormalMappingDemo : public IExecute
{
public:
virtual void Initialize() override;
virtual void Ready() override {}
virtual void Destroy() override {}
virtual void Update() override;
virtual void PreRender() override {}
virtual void Render() override;
virtual void PostRender() override {}
virtual void ResizeScreen() override {}
private:
void Mesh();
void Airplane();
void Kachujin();
void KachujinCollider();
void KachujinWeapon();
void PointLighting();
void SpotLighting();
void Pass(UINT mesh, UINT model, UINT anim);
private:
Shader* shader;
CubeSky* sky;
Material* floor;
Material* stone;
Material* brick;
Material* wall;
MeshRender* cube;
MeshRender* cylinder;
MeshRender* sphere;
MeshRender* grid;
ModelRender* airplane = NULL;
ModelAnimator* kachujin = NULL;
Transform* colliderInitTransforms;
ColliderObject** colliders;
ModelRender* weapon = NULL;
Transform* weaponInitTransform;
vector<MeshRender*> meshes;
vector<ModelRender*> models;
vector<ModelAnimator*> animators;
};
NormalMappingDemo.cpp
#include "stdafx.h"
#include "NormalMappingDemo.h"
void NormalMappingDemo::Initialize()
{
Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
Context::Get()->GetCamera()->Position(1, 36, -85);
shader = new Shader(L"82_NormalMapping.fxo");
sky = new CubeSky(L"Environment/GrassCube1024.dds");
Mesh();
Airplane();
Kachujin();
KachujinCollider();
KachujinWeapon();
PointLighting();
SpotLighting();
}
void NormalMappingDemo::Update()
{
static UINT selected = 0;
ImGui::InputInt("NormalMap Selected", (int*)&selected);
selected %= 4;
shader->AsScalar("Selected")->SetInt(selected);
sky->Update();
cube->Update();
grid->Update();
cylinder->Update();
sphere->Update();
airplane->Update();
kachujin->Update();
Matrix worlds[MAX_MODEL_TRANSFORMS];
for (UINT i = 0; i < kachujin->GetTransformCount(); i++)
{
kachujin->GetAttachTransform(i, worlds);
weapon->GetTransform(i)->World(weaponInitTransform->World() * worlds[40]);
}
weapon->UpdateTransforms();
weapon->Update();
}
void NormalMappingDemo::Render()
{
sky->Render();
Pass(0, 1, 2);
wall->Render();
sphere->Render();
brick->Render();
cylinder->Render();
stone->Render();
cube->Render();
floor->Render();
grid->Render();
airplane->Render();
kachujin->Render();
weapon->Render();
}
void NormalMappingDemo::Mesh()
{
//Create Material
{
floor = new Material(shader);
floor->DiffuseMap("Floor.png");
floor->Specular(1, 1, 1, 20);
floor->SpecularMap("Floor_Specular.png");
floor->NormalMap("Floor_Normal.png");
stone = new Material(shader);
stone->DiffuseMap("Stones.png");
stone->Specular(1, 1, 1, 20);
stone->SpecularMap("Stones_Specular.png");
stone->Emissive(1.0f, 1.0f, 1.0f, 0.3f);
stone->NormalMap("Stones_Normal.png");
brick = new Material(shader);
brick->DiffuseMap("Bricks.png");
brick->Specular(1, 0.3f, 0.3f, 20);
brick->SpecularMap("Bricks_Specular.png");
brick->Emissive(1.0f, 1.0f, 1.0f, 0.3f);
brick->NormalMap("Bricks_Normal.png");
wall = new Material(shader);
wall->DiffuseMap("Wall.png");
wall->Specular(1, 1, 1, 20);
wall->SpecularMap("Wall_Specular.png");
wall->Emissive(1.0f, 1.0f, 1.0f, 0.3f);
wall->NormalMap("Wall_Normal.png");
}
//Create Mesh
{
Transform* transform = NULL;
cube = new MeshRender(shader, new MeshCube());
transform = cube->AddTransform();
transform->Position(0, 5, 0);
transform->Scale(20, 10, 20);
grid = new MeshRender(shader, new MeshGrid(5, 5));
transform = grid->AddTransform();
transform->Position(0, 0, 0);
transform->Scale(12, 1, 12);
cylinder = new MeshRender(shader, new MeshCylinder(0.5f, 3.0f, 20, 20));
sphere = new MeshRender(shader, new MeshSphere(0.5f, 20, 20));
for (UINT i = 0; i < 5; i++)
{
transform = cylinder->AddTransform();
transform->Position(-30, 6, -15.0f + (float)i * 15.0f);
transform->Scale(5, 5, 5);
transform = cylinder->AddTransform();
transform->Position(30, 6, -15.0f + (float)i * 15.0f);
transform->Scale(5, 5, 5);
transform = sphere->AddTransform();
transform->Position(-30, 15.5f, -15.0f + (float)i * 15.0f);
transform->Scale(5, 5, 5);
transform = sphere->AddTransform();
transform->Position(30, 15.5f, -15.0f + (float)i * 15.0f);
transform->Scale(5, 5, 5);
}
}
sphere->UpdateTransforms();
cylinder->UpdateTransforms();
cube->UpdateTransforms();
grid->UpdateTransforms();
meshes.push_back(sphere);
meshes.push_back(cylinder);
meshes.push_back(cube);
meshes.push_back(grid);
}
void NormalMappingDemo::Airplane()
{
airplane = new ModelRender(shader);
airplane->ReadMesh(L"B787/Airplane");
airplane->ReadMaterial(L"B787/Airplane");
Transform* transform = airplane->AddTransform();
transform->Position(2.0f, 9.91f, 2.0f);
transform->Scale(0.004f, 0.004f, 0.004f);
airplane->UpdateTransforms();
models.push_back(airplane);
}
void NormalMappingDemo::Kachujin()
{
kachujin = new ModelAnimator(shader);
kachujin->ReadMesh(L"Kachujin/Mesh");
kachujin->ReadMaterial(L"Kachujin/Mesh");
kachujin->ReadClip(L"Kachujin/Sword And Shield Idle");
kachujin->ReadClip(L"Kachujin/Sword And Shield Walk");
kachujin->ReadClip(L"Kachujin/Sword And Shield Run");
kachujin->ReadClip(L"Kachujin/Sword And Shield Slash");
kachujin->ReadClip(L"Kachujin/Salsa Dancing");
Transform* transform = NULL;
transform = kachujin->AddTransform();
transform->Position(0, 0, -30);
transform->Scale(0.075f, 0.075f, 0.075f);
kachujin->PlayTweenMode(0, 0, 1.0f);
transform = kachujin->AddTransform();
transform->Position(-15, 0, -30);
transform->Scale(0.075f, 0.075f, 0.075f);
kachujin->PlayTweenMode(1, 1, 1.0f);
transform = kachujin->AddTransform();
transform->Position(-30, 0, -30);
transform->Scale(0.075f, 0.075f, 0.075f);
kachujin->PlayTweenMode(2, 2, 0.75f);
transform = kachujin->AddTransform();
transform->Position(15, 0, -30);
transform->Scale(0.075f, 0.075f, 0.075f);
kachujin->PlayBlendMode(3, 0, 1, 2);
kachujin->SetBlendAlpha(3, 1.5f);
transform = kachujin->AddTransform();
transform->Position(30, 0, -32.5f);
transform->Scale(0.075f, 0.075f, 0.075f);
kachujin->PlayTweenMode(4, 4, 0.75f);
kachujin->UpdateTransforms();
animators.push_back(kachujin);
}
void NormalMappingDemo::KachujinCollider()
{
UINT count = kachujin->GetTransformCount();
colliders = new ColliderObject * [count];
colliderInitTransforms = new Transform();
colliderInitTransforms->Position(-2.9f, 1.45f, -50.0f);
colliderInitTransforms->Scale(5, 5, 75);
for (UINT i = 0; i < count; i++)
{
colliders[i] = new ColliderObject();
//colliders[i]->Init = new Transform();
//colliders[i]->Init->Position(0, 0, 0);
//colliders[i]->Init->Scale(10, 30, 10);
colliders[i]->Transform = new Transform();
//colliders[i]->Collider = new Collider(colliders[i]->Transform, colliders[i]->Init);
colliders[i]->Collider = new Collider(colliders[i]->Transform, colliderInitTransforms);
}
}
void NormalMappingDemo::KachujinWeapon()
{
weapon = new ModelRender(shader);
weapon->ReadMesh(L"Weapon/Sword");
weapon->ReadMaterial(L"Weapon/Sword");
UINT count = kachujin->GetTransformCount();
for (UINT i = 0; i < count; i++)
weapon->AddTransform();
weapon->UpdateTransforms();
models.push_back(weapon);
weaponInitTransform = new Transform();
weaponInitTransform->Position(-2.9f, 1.45f, -6.45f);
weaponInitTransform->Scale(0.5f, 0.5f, 0.75f);
weaponInitTransform->Rotation(0, 0, 1);
}
void NormalMappingDemo::PointLighting()
{
PointLight light;
light =
{
Color(0.0f, 0.0f, 0.0f, 1.0f), //Ambient
Color(0.0f, 0.0f, 1.0f, 1.0f), //Diffuse
Color(0.0f, 0.0f, 0.7f, 1.0f), //Specular
Color(0.0f, 0.0f, 0.7f, 1.0f), //Emissive
Vector3(-30, 10, -30), 5.0f, 0.9f // 위치, 범위, 강도
};
Lighting::Get()->AddPointLight(light);
light =
{
Color(0.0f, 0.0f, 0.0f, 1.0f),
Color(1.0f, 0.0f, 0.0f, 1.0f),
Color(0.6f, 0.2f, 0.0f, 1.0f),
Color(0.6f, 0.3f, 0.0f, 1.0f),
Vector3(15, 10, -30), 10.0f, 0.3f
};
Lighting::Get()->AddPointLight(light);
light =
{
Color(0.0f, 0.0f, 0.0f, 1.0f), //Ambient
Color(0.0f, 1.0f, 0.0f, 1.0f), //Diffuse
Color(0.0f, 0.7f, 0.0f, 1.0f), //Specular
Color(0.0f, 0.7f, 0.0f, 1.0f), //Emissive
Vector3(-5, 1, -17.5f), 5.0f, 0.9f
};
Lighting::Get()->AddPointLight(light);
light =
{
Color(0.0f, 0.0f, 0.0f, 1.0f),
Color(0.0f, 0.0f, 1.0f, 1.0f),
Color(0.0f, 0.0f, 0.7f, 1.0f),
Color(0.0f, 0.0f, 0.7f, 1.0f),
Vector3(-10, 1, -17.5f), 5.0f, 0.9f
};
Lighting::Get()->AddPointLight(light);
}
void NormalMappingDemo::SpotLighting()
{
SpotLight light;
light =
{
Color(0.3f, 1.0f, 0.0f, 1.0f),
Color(0.7f, 1.0f, 0.0f, 1.0f),
Color(0.3f, 1.0f, 0.0f, 1.0f),
Color(0.3f, 1.0f, 0.0f, 1.0f),
Vector3(-15, 20, -30), 25.0f,
Vector3(0, -1, 0), 30.0f, 0.4f
};
Lighting::Get()->AddSpotLight(light);
light =
{
Color(1.0f, 0.2f, 0.9f, 1.0f),
Color(1.0f, 0.2f, 0.9f, 1.0f),
Color(1.0f, 0.2f, 0.9f, 1.0f),
Color(1.0f, 0.2f, 0.9f, 1.0f),
Vector3(0, 20, -30), 30.0f,
Vector3(0, -1, 0), 40.0f, 0.55f
};
Lighting::Get()->AddSpotLight(light);
}
void NormalMappingDemo::Pass(UINT mesh, UINT model, UINT anim)
{
for (MeshRender* temp : meshes)
temp->Pass(mesh);
for (ModelRender* temp : models)
temp->Pass(model);
for (ModelAnimator* temp : animators)
temp->Pass(anim);
}
00_Light.fx 추가된 내용
////////////////////////////////////////////////////////////////////////////
void NormalMapping(float2 uv, float3 normal, float3 tangent, SamplerState samp)
{
float4 map = NormalMap.Sample(samp, uv);
// any : 모든 변수의 값이 0이면 flase, 하나라도 0보다 크면 true
// rgb모두 0이라면 수행할 필요가 x
[flatten]
if (any(map.rgb) == false)
return;
// 픽셀값을 가져옴
// rgb(0 ~ 1)까지의 값이 -1 ~ +1까지의 방향으로 바뀜
float3 coord = map.rgb * 2.0f - 1.0f;
// 탄젠트 공간 생성
float3 N = normalize(normal); // Z
// Tangent란? 정규직교화 - 그람 슈미트 과정
// 정규직교화의 식이다.
// dot(tangent, N) 에서 * N을 해서 늘려주고 tangent를 빼서 투영시켜준다.
float3 T = normalize(tangent - dot(tangent, N) * N); // X
// BiNoraml = cross(N, T)를 반대로 뒤집으면
// BiTangent = cross(T, N)가 됨
// N, T가 길이 1이하여서 굳이 Normalize안해도 크기가 1이된다.
float3 B = cross(N, T);
float3x3 TBN = float3x3(T, B, N);
// coord는 단지 이미지 공간이므로 현재 우리가 만든 3D 공간(탄젠트 공간)으로 변환해준다.
coord = mul(coord, TBN);
// 자기색
// 음영은 본인이 가진 색이므로 Diffuse에 적용하며 동일하게 Lambert식으로 계산
// 이렇게 하면 해당 픽셀마다 해당 값으로 음영이 드리워짐
Material.Diffuse *= saturate(dot(-GlobalLight.Direction, coord));
}
void NormalMapping(float2 uv, float3 normal, float3 tangent)
{
NormalMapping(uv, normal, tangent, LinearSampler);
}
00_Render.fx 추가된 내용
// 탄젠트 추가
struct VertexMesh
{
float4 Position : Position;
float2 Uv : Uv;
float3 Normal : Normal;
float3 Tangent : Tangent;
matrix Transform : Inst1_Transform;
float4 Color : Inst2_Color;
};
////////////////////////////////////////////////////////////////////////////
#define VS_GENERATE \
output.oPosition = input.Position.xyz;\
\
output.Position = WorldPosition(input.Position);\
output.wPosition = output.Position.xyz;\
output.Position = ViewProjection(output.Position);\
\
output.Normal = WorldNormal(input.Normal);\
output.Tangent = WorldTangent(input.Tangent);\
\
output.Uv = input.Uv;\
output.Color = input.Color;
////////////////////////////////////////////////////////////////////////////
struct VertexModel
{
float4 Position : Position;
float2 Uv : Uv;
float3 Normal : Normal;
float3 Tangent : Tangent;
float4 BlendIndices : BlendIndices;
float4 BlendWeights : BlendWeights;
uint InstanceID : SV_InstanceID;
matrix Transform : Inst1_Transform;
float4 Color : Inst2_Color;
};
00_Global.fx 추가된 내용
// 탄젠트 추가
// world 공간으로 tangent가 회전이됨
float3 WorldTangent(float3 tangent)
{
return mul(tangent, (float3x3) World);
}
/////////////////////////////////////////////////////////////////////////////////////
// 모든 정점 셰이더가 이 방법으로 리턴함
struct MeshOutput
{
float4 Position : SV_Position0;
float3 oPosition : Position1;
float3 wPosition : Position2;
float3 Normal : Normal;
float3 Tangent : Tangent;
float2 Uv : Uv;
float4 Color : Color;
};
82_NormalMapping.fx
#include "00_Global.fx"
#include "00_Light.fx"
#include "00_Render.fx"
// 비교할겸 변수 넣음
uint Selected = 0;
float4 PS(MeshOutput input) : SV_Target
{
// 기본 흰색(음영만 보일 수 있게끔)
Material.Diffuse = float4(1, 1, 1, 1);
// 0번은 흰색만보이게
if (Selected == 0)
{
//Texture(Material.Diffuse, DiffuseMap, input.Uv);
}
// 흰색 + 노멀맵(음영만)
else if (Selected == 1)
{
NormalMapping(input.Uv, input.Normal, input.Tangent);
}
// 텍스쳐만
else if (Selected == 2)
{
Texture(Material.Diffuse, DiffuseMap, input.Uv);
}
// 텍스쳐 + 노멀
else if (Selected == 3)
{
Texture(Material.Diffuse, DiffuseMap, input.Uv);
NormalMapping(input.Uv, input.Normal, input.Tangent);
}
// NormalMapping()에서는 Diffuse를 *해주기 때문에 Texture()함수 먼저해서 Diffuse 먼저 구해도 이후에 구해도 상관없다.
//Texture(Material.Diffuse, DiffuseMap, input.Uv);
Texture(Material.Specular, SpecularMap, input.Uv);
MaterialDesc output = MakeMaterial();
MaterialDesc result = MakeMaterial();
ComputeLight(output, input.Normal, input.wPosition);
AddMaterial(result, output);
//ComputePointLight(output, input.Normal, input.wPosition);
//AddMaterial(result, output);
//ComputeSpotLight(output, input.Normal, input.wPosition);
//AddMaterial(result, output);
return float4(MaterialToColor(result), 1.0f);
}
technique11 T0
{
P_VP(P0, VS_Mesh, PS)
P_VP(P1, VS_Model, PS)
P_VP(P2, VS_Animation, PS)
}
결과
0번일때는 밑밑하고
1번일때는 Noraml으로 한거여서 윤곽(굴곡)이 있음
2번일때는 Texture만 출력
3번일때는 Normal + Texture
'DirectX11 3D > 기본 문법' 카테고리의 다른 글
<DirectX11 3D> 85 - Rain (0) | 2022.03.14 |
---|---|
<DirectX11 3D> 83 - Billboard (0) | 2022.03.14 |
<DirectX3D 11> 80 - SpotLighting (0) | 2022.03.09 |
<DirectX11 3D> 78 - PointLighting & HLSL FlowControl (0) | 2022.03.09 |
<DirectX11 3D> 75 - Lighting(Ambient, Diffuse, Specular, Emissive) (0) | 2022.03.09 |