응애맘마조
230626 강의 본문
저번 금요일에 했던 로드에 이어서 노멀 매핑에 대해서 강의했습니다. 구글 검색만 해도 obj 다운로드할 수 있는 파일은 굉장히 많이 나오지만 막상 로드할 수 있는 파일은 그렇게 많지 않고 코드 상에서도 많이 터지는 경우도 있습니다. 뿐만 아니라 만드는 사람마다 방식이 전부 다르기 때문에 정점이 안 맞는 경우도 있고 따로 되어있는 경우도 있습니다. 그래서 확실하게 검증된 파일을 사용하는 것이 좋은데 유니티 엔진에서 원활하게 움직이는 파일이라면 DirectX에서도 문제없이 사용할 수 있다고 했습니다.
#include "Common.hlsl"
struct VertexInput
{
float4 Position : POSITION0;
float2 Uv : UV0;
float3 Normal : NORMAL0;
float3 Tangent : TANGENT0;
float4 Indices : INDICES0;
float4 Weights : WEIGHTS0;
};
struct PixelInput
{
float4 Position : SV_POSITION;
float3 wPosition : POSITION0;
float2 Uv : UV0;
float3 Normal : NORMAL;
float3 Tangent : TANGENT;
float3 Binormal : BINORMAL;
};
PixelInput VS(VertexInput input)
{
PixelInput output;
output.Uv = input.Uv;
output.Position = mul(input.Position, World);
output.wPosition = output.Position.xyz;
output.Position = mul(output.Position, ViewProj);
output.Normal = mul(input.Normal, (float3x3) World);
output.Tangent = mul(input.Tangent, (float3x3) World);
output.Binormal = cross(output.Normal.xyz, output.Tangent.xyz);
return output;
}
float4 PS(PixelInput input) : SV_TARGET
{
float4 BaseColor = DiffuseMapping(input.Uv);
float3 ariel = normalize(-float3(1, -1, 1));
float3 normal = NormalMapping(input.Normal, input.Tangent, input.Binormal, input.Uv);
float diffuse = saturate(dot(ariel, normal));
float ambient = Ka.rgb;
float3 lambert = (diffuse + ambient) * Kd.rgb * BaseColor.rgb;
float3 Reflect = reflect(normalize(float3(1, -1, 1)), normal);
float3 viewDir = normalize(ViewPos.xyz - input.wPosition);
float3 specular = pow(saturate(dot(Reflect, viewDir)), Shininess) * Ks.rgb * SpecularMapping(input.Uv);
float alpha = (BaseColor.a < Opacity) ? BaseColor.a : Opacity;
return float4(lambert + specular, alpha);
}
이번 노멀 매핑에 사용한 hlsl파일입니다. 이전에 노멀 매핑 전에 사용했던 파일과 비슷하지만 Tangent, Binormal, Weight가 추가되었습니다.
#pragma once
class Main : public Scene
{
private:
Camera* Cam;
Grid* grid;
Actor* temp;
string file;
Assimp::Importer importer;
const aiScene* scene;
public:
Main();
~Main();
virtual void Init() override;
virtual void Release() override;
virtual void Update() override;
virtual void LateUpdate() override;
virtual void Render() override;
virtual void PreRender() override;
virtual void ResizeScreen() override;
void MakeHierarchy(aiNode* node, GameObject* node2);
void MakeMesh(aiNode* node, GameObject* node2);
void MakeMaterial();
};
#include "stdafx.h"
#include "Main.h"
Main::Main()
{
}
Main::~Main()
{
}
void Main::Init()
{
Cam = Camera::Create();
Cam->LoadFile("Cam.xml");
Camera::main = Cam;
Cam->width = App.GetWidth();
Cam->height = App.GetHeight();
Cam->viewport.width = App.GetWidth();
Cam->viewport.height = App.GetHeight();
grid = Grid::Create();
temp = Actor::Create();
}
void Main::Release()
{
}
void Main::Update()
{
ImGui::Begin("Hierarchy");
Cam->RenderHierarchy();
temp->RenderHierarchy();
grid->RenderHierarchy();
ImGui::End();
if (GUI->FileImGui("ModelImporter", "ModelImporter",
".fbx,.obj,.x", "../Assets"))
{
file = ImGuiFileDialog::Instance()->GetCurrentFileName();
string path = "../Assets/" + file;
importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false);
scene = importer.ReadFile
(
path,
aiProcess_ConvertToLeftHanded
| aiProcess_Triangulate
| aiProcess_GenUVCoords
| aiProcess_GenNormals
| aiProcess_CalcTangentSpace
);
assert(scene != NULL and "Import Error");
temp->ReleaseMember();
MakeMaterial();
GameObject* empty = GameObject::Create("empty");
temp->AddChild(empty);
MakeHierarchy(scene->mRootNode, empty);
importer.FreeScene();
}
Camera::ControlMainCam();
Cam->Update();
grid->Update();
temp->Update();
}
void Main::LateUpdate()
{
}
void Main::PreRender()
{
}
void Main::Render()
{
Cam->Set();
grid->Render();
temp->Render();
}
void Main::ResizeScreen()
{
}
void Main::MakeHierarchy(aiNode* node, GameObject* node2)
{
MakeMesh(node, node2);
for (int i = 0; i < node->mNumChildren; i++)
{
GameObject* child = GameObject::Create(node->mChildren[i]->mName.C_Str());
node2->AddChild(child);
MakeHierarchy(node->mChildren[i], child);
}
}
void Main::MakeMesh(aiNode* node, GameObject* node2)
{
//루트 노드에 담겨있는 메쉬 갯수만큼 반복
for (int i = 0; i < node->mNumMeshes; i++)
{
int index = node->mMeshes[i];
aiMesh* mesh = scene->mMeshes[index];
aiMaterial* mtl = scene->mMaterials[mesh->mMaterialIndex];
string mtlFile = mtl->GetName().C_Str();
int tok = file.find_last_of(".");
string filePath = file.substr(0, tok) + "/";
mtlFile = filePath + mtlFile + ".mtl";
GameObject* Current = node2;
//메쉬가 두개 이상일때
if (i != 0)
{
Current = GameObject::Create(node2->name + "meshObject" + to_string(i));
node2->AddChild(Current);
}
Current->shader = RESOURCE->shaders.Load("4.Cube.hlsl");
Current->material =new Material();
Current->material->LoadFile(mtlFile);
Current->mesh = make_shared<Mesh>();
Current->mesh->byteWidth = sizeof(VertexModel);
Current->mesh->primitiveTopology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
Current->mesh->vertexType = VertexType::MODEL;
Current->mesh->vertexCount = mesh->mNumVertices;
Current->mesh->vertices = new VertexModel[mesh->mNumVertices];
//Current->mesh->indexCount =
vector<UINT> indexList;
for (int j = 0; j < mesh->mNumVertices; j++)
{
VertexModel* vertex = (VertexModel*)Current->mesh->vertices;
//aiVector3D
//텍스쳐 좌표가 있다면
if (mesh->HasTextureCoords(0))
{
vertex[j].uv.x =mesh->mTextureCoords[0][j].x;
vertex[j].uv.y =mesh->mTextureCoords[0][j].y;
}
if (mesh->HasNormals())
{
vertex[j].normal.x = mesh->mNormals[j].x;
vertex[j].normal.y = mesh->mNormals[j].y;
vertex[j].normal.z = mesh->mNormals[j].z;
}
if (mesh->HasPositions())
{
vertex[j].position.x = mesh->mVertices[j].x;
vertex[j].position.y = mesh->mVertices[j].y;
vertex[j].position.z = mesh->mVertices[j].z;
}
if (mesh->HasTangentsAndBitangents())
{
vertex[j].tangent.x = mesh->mTangents[j].x;
vertex[j].tangent.y = mesh->mTangents[j].y;
vertex[j].tangent.z = mesh->mTangents[j].z;
}
}
for (int j = 0; j < mesh->mNumFaces; j++)
{
for (int k = 0; k < mesh->mFaces[j].mNumIndices; k++)
{
indexList.push_back(mesh->mFaces[j].mIndices[k]);
}
}
Current->mesh->indexCount = indexList.size();
Current->mesh->indices = new UINT[indexList.size()];
copy(indexList.begin(), indexList.end(),
stdext::checked_array_iterator<UINT*>
(Current->mesh->indices, indexList.size()));
Current->mesh->Reset();
{
int tok = file.find_last_of(".");
string checkPath = "../Contents/Mesh/" + file.substr(0, tok);
if (!PathFileExistsA(checkPath.c_str()))
{
CreateDirectoryA(checkPath.c_str(), NULL);
}
string filePath = file.substr(0, tok) + "/";
string meshFile = mesh->mName.C_Str();
Current->mesh->file = filePath + meshFile + ".mesh";
Current->mesh->SaveFile(Current->mesh->file);
}
}
}
void Main::MakeMaterial()
{
for (int i = 0; i < scene->mNumMaterials; i++)
{
aiMaterial* srcMtl = scene->mMaterials[i];
Material* destMtl = new Material();
aiColor3D tempColor;
destMtl->file = srcMtl->GetName().C_Str();
//ambient
srcMtl->Get(AI_MATKEY_COLOR_AMBIENT, tempColor);
destMtl->ambient.x = tempColor.r;
destMtl->ambient.y = tempColor.g;
destMtl->ambient.z = tempColor.b;
//diffuse
srcMtl->Get(AI_MATKEY_COLOR_DIFFUSE, tempColor);
destMtl->diffuse.x = tempColor.r;
destMtl->diffuse.y = tempColor.g;
destMtl->diffuse.z = tempColor.b;
//specular
srcMtl->Get(AI_MATKEY_COLOR_SPECULAR, tempColor);
destMtl->specular.x = tempColor.r;
destMtl->specular.y = tempColor.g;
destMtl->specular.z = tempColor.b;
//emissive
srcMtl->Get(AI_MATKEY_COLOR_EMISSIVE, tempColor);
destMtl->emissive.x = tempColor.r;
destMtl->emissive.y = tempColor.g;
destMtl->emissive.z = tempColor.b;
//Shininess
srcMtl->Get(AI_MATKEY_SHININESS, destMtl->shininess);
//opacity
srcMtl->Get(AI_MATKEY_OPACITY, destMtl->opacity);
//Normal
{
aiString aifile;
string TextureFile;
aiReturn texFound;
texFound = srcMtl->GetTexture(aiTextureType_NORMALS, 0, &aifile);
TextureFile = aifile.C_Str();
size_t index = TextureFile.find_last_of('/');
TextureFile = TextureFile.substr(index + 1, TextureFile.length());
//텍스쳐가 있다.
if (texFound == AI_SUCCESS && file != "")
{
destMtl->ambient.w = 1.0f;
destMtl->normalMap = make_shared<Texture>();
size_t tok = file.find_last_of(".");
string checkPath = "../Contents/Texture/" + file.substr(0, tok);
if (!PathFileExistsA(checkPath.c_str()))
{
CreateDirectoryA(checkPath.c_str(), NULL);
}
string orgin = "../Assets/" + TextureFile;
string copy = "../Contents/Texture/" + file.substr(0, tok) + "/" + TextureFile;
bool isCheck = true;
CopyFileA(orgin.c_str(), copy.c_str(), isCheck);
destMtl->normalMap->LoadFile(file.substr(0, tok) + "/" + TextureFile);
}
}
//Diffuse
{
aiString aifile;
string TextureFile;
aiReturn texFound;
texFound = srcMtl->GetTexture(aiTextureType_DIFFUSE, 0, &aifile);
TextureFile = aifile.C_Str();
size_t index = TextureFile.find_last_of('/');
TextureFile = TextureFile.substr(index + 1, TextureFile.length());
//텍스쳐가 있다.
if (texFound == AI_SUCCESS && file != "")
{
destMtl->diffuse.w = 1.0f;
destMtl->diffuseMap = make_shared<Texture>();
size_t tok = file.find_last_of(".");
string checkPath = "../Contents/Texture/" + file.substr(0, tok);
if (!PathFileExistsA(checkPath.c_str()))
{
CreateDirectoryA(checkPath.c_str(), NULL);
}
string orgin = "../Assets/" + TextureFile;
string copy = "../Contents/Texture/" + file.substr(0, tok) + "/" + TextureFile;
bool isCheck = true;
CopyFileA(orgin.c_str(), copy.c_str(), isCheck);
destMtl->diffuseMap->LoadFile(file.substr(0, tok) + "/" + TextureFile);
}
}
//specular
{
aiString aifile;
string TextureFile;
aiReturn texFound;
texFound = srcMtl->GetTexture(aiTextureType_SPECULAR, 0, &aifile);
TextureFile = aifile.C_Str();
size_t index = TextureFile.find_last_of('/');
TextureFile = TextureFile.substr(index + 1, TextureFile.length());
//텍스쳐가 있다.
if (texFound == AI_SUCCESS && file != "")
{
destMtl->specular.w = 1.0f;
destMtl->specularMap = make_shared<Texture>();
size_t tok = file.find_last_of(".");
string checkPath = "../Contents/Texture/" + file.substr(0, tok);
if (!PathFileExistsA(checkPath.c_str()))
{
CreateDirectoryA(checkPath.c_str(), NULL);
}
string orgin = "../Assets/" + TextureFile;
string copy = "../Contents/Texture/" + file.substr(0, tok) + "/" + TextureFile;
bool isCheck = true;
CopyFileA(orgin.c_str(), copy.c_str(), isCheck);
destMtl->specularMap->LoadFile(file.substr(0, tok) + "/" + TextureFile);
}
}
//emissive
{
aiString aifile;
string TextureFile;
aiReturn texFound;
texFound = srcMtl->GetTexture(aiTextureType_EMISSIVE, 0, &aifile);
TextureFile = aifile.C_Str();
size_t index = TextureFile.find_last_of('/');
TextureFile = TextureFile.substr(index + 1, TextureFile.length());
if (texFound == AI_SUCCESS && file != "")
{
destMtl->emissive.w = 1.0f;
destMtl->emissiveMap = make_shared<Texture>();
size_t tok = file.find_last_of(".");
string checkPath = "../Contents/Texture/" + file.substr(0, tok);
if (!PathFileExistsA(checkPath.c_str()))
{
CreateDirectoryA(checkPath.c_str(), NULL);
}
string orgin = "../Assets/" + TextureFile;
string copy = "../Contents/Texture/" + file.substr(0, tok) + "/" + TextureFile;
bool isCheck = true;
CopyFileA(orgin.c_str(), copy.c_str(), isCheck);
destMtl->emissiveMap->LoadFile(file.substr(0, tok) + "/" + TextureFile);
}
}
size_t tok = file.find_last_of(".");
string checkPath = "../Contents/Material/" + file.substr(0, tok);
if (!PathFileExistsA(checkPath.c_str()))
{
CreateDirectoryA(checkPath.c_str(), NULL);
}
string filePath = file.substr(0, tok) + "/";
destMtl->file = filePath + destMtl->file + ".mtl";
destMtl->SaveFile(destMtl->file);
}
}
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prevInstance, LPWSTR param, int command)
{
App.SetAppName(L"ObjLoader");
App.SetInstance(instance);
WIN->Create();
D3D->Create();
Main * main = new Main();
main->Init();
int wParam = (int)WIN->Run(main);
main->Release();
SafeDelete(main);
D3D->DeleteSingleton();
WIN->DeleteSingleton();
return wParam;
}
Main의 헤더와 cpp 파일입니다. 노멀매핑에 맞춰서 코드가 변경되었습니다. 그럼 노멀 매핑 전과 후의 모습을 비교해 보겠습니다.
카메라의 위치에는 차이가 있지만 확연한 차이를 느낄 수 있는 모습입니다.
읽어주셔서 감사합니다.