글에 사용된 모든 그림과 내용은 직접 작성한 것입니다.
[유튜브 영상]
[깃허브 보러가기]
https://github.com/Chang-Jin-Lee/D3D11-AliceTutorial/tree/main/17_fbx_pmx_obj_WithPhong
[풀리퀘 보러가기]
https://github.com/Chang-Jin-Lee/D3D11-AliceTutorial/pull/27
D3D11에서 assimp로 FBX를 로드해 각 버퍼를 생성해 값을 할당하는 흐름을 정리하기 위함
https://velog.io/@whoamicj/DX11-Ready-for-Rendering-fbx-pmx
bool FbxManager::Load(ID3D11Device* device, const std::wstring& pathW)
{
Release();
Assimp::Importer importer;
std::string pathA(pathW.begin(), pathW.end());
const aiScene* scene = importer.ReadFile(pathA,
aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_ImproveCacheLocality |
aiProcess_GenSmoothNormals | aiProcess_CalcTangentSpace | aiProcess_ConvertToLeftHanded);
if (!scene || !scene->HasMeshes()) return false;
std::wstring baseDir = pathW;
size_t slash = baseDir.find_last_of(L"/\\");
baseDir = (slash == std::wstring::npos) ? L"" : baseDir.substr(0, slash + 1);
if (!LoadMaterials(device, scene, baseDir)) return false;
if (!BuildMeshBuffers(device, scene)) return false;
return true;
}


bool FbxManager::LoadMaterials(ID3D11Device* device, const aiScene* scene, const std::wstring& baseDir)
{
if (!m_pWhite)
{
UINT white = 0xFFFFFFFF;
D3D11_TEXTURE2D_DESC td{}; td.Width = 1; td.Height = 1; td.MipLevels = 1; td.ArraySize = 1;
td.Format = DXGI_FORMAT_R8G8B8A8_UNORM; td.SampleDesc.Count = 1; td.Usage = D3D11_USAGE_IMMUTABLE; td.BindFlags = D3D11_BIND_SHADER_RESOURCE;
D3D11_SUBRESOURCE_DATA sd{}; sd.pSysMem = &white; sd.SysMemPitch = sizeof(UINT);
ComPtr<ID3D11Texture2D> tex; HR_T(device->CreateTexture2D(&td, &sd, tex.GetAddressOf()));
D3D11_SHADER_RESOURCE_VIEW_DESC srvd{}; srvd.Format = td.Format; srvd.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvd.Texture2D.MipLevels = 1; srvd.Texture2D.MostDetailedMip = 0;
HR_T(device->CreateShaderResourceView(tex.Get(), &srvd, &m_pWhite));
}
m_MaterialSRVs.assign(scene->mNumMaterials, nullptr);
auto findCached = [&](const std::wstring& key){ auto it = m_TexCache.find(key); return it==m_TexCache.end()? (ID3D11ShaderResourceView*)nullptr : it->second; };
auto addCache = [&](const std::wstring& key, ID3D11ShaderResourceView* v){ if (v) { m_TexCache[key] = v; v->AddRef(); } };
for (unsigned m = 0; m < scene->mNumMaterials; ++m)
{
aiMaterial* mat = scene->mMaterials[m];
aiString texPath;
if (mat->GetTexture(aiTextureType_DIFFUSE, 0, &texPath) == AI_SUCCESS)
{
std::string t = texPath.C_Str();
// fbx 파일에 임베디드된 텍스처 로딩을 시도함 이름 또는 *인덱스 표기를 모두 처리함 (Assimp 헬퍼를 사용해서)
if (!t.empty())
{
const aiTexture* at = scene->GetEmbeddedTexture(t.c_str());
if (at)
{
ComPtr<ID3D11Resource> res; ID3D11ShaderResourceView* srv = nullptr;
if (at->mHeight == 0)
{
// 이미지를 읽어옴. 압축 버퍼를 사용함 (PNG/JPG 등)
if (SUCCEEDED(CreateWICTextureFromMemory(device, reinterpret_cast<const uint8_t*>(at->pcData), at->mWidth, res.GetAddressOf(), &srv)))
m_MaterialSRVs[m] = srv;
}
else
{
// RAW BGRA8 픽셀 데이터
D3D11_TEXTURE2D_DESC td{}; td.Width = at->mWidth; td.Height = at->mHeight; td.MipLevels = 1; td.ArraySize = 1;
td.Format = DXGI_FORMAT_B8G8R8A8_UNORM; td.SampleDesc.Count = 1; td.Usage = D3D11_USAGE_IMMUTABLE; td.BindFlags = D3D11_BIND_SHADER_RESOURCE;
D3D11_SUBRESOURCE_DATA sd{}; sd.pSysMem = at->pcData; sd.SysMemPitch = at->mWidth * sizeof(aiTexel);
ComPtr<ID3D11Texture2D> tex;
if (SUCCEEDED(device->CreateTexture2D(&td, &sd, tex.GetAddressOf())))
{
D3D11_SHADER_RESOURCE_VIEW_DESC srvd{}; srvd.Format = td.Format; srvd.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvd.Texture2D.MipLevels = 1; srvd.Texture2D.MostDetailedMip = 0;
if (SUCCEEDED(device->CreateShaderResourceView(tex.Get(), &srvd, &srv))) m_MaterialSRVs[m] = srv;
}
}
}
}
// 임베디드된 텍스쳐가 없거나 실패 시, 구형 *인덱스 방식 처리
if (!m_MaterialSRVs[m] && !t.empty() && t[0] == '*')
{
int idx = atoi(t.c_str() + 1);
if (idx >= 0 && (unsigned)idx < scene->mNumTextures)
{
const aiTexture* at = scene->mTextures[idx];
if (at)
{
ComPtr<ID3D11Resource> res; ID3D11ShaderResourceView* srv = nullptr;
if (at->mHeight == 0)
{
if (SUCCEEDED(CreateWICTextureFromMemory(device, reinterpret_cast<const uint8_t*>(at->pcData), at->mWidth, res.GetAddressOf(), &srv)))
m_MaterialSRVs[m] = srv;
}
else
{
D3D11_TEXTURE2D_DESC td{}; td.Width = at->mWidth; td.Height = at->mHeight; td.MipLevels = 1; td.ArraySize = 1;
td.Format = DXGI_FORMAT_B8G8R8A8_UNORM; td.SampleDesc.Count = 1; td.Usage = D3D11_USAGE_IMMUTABLE; td.BindFlags = D3D11_BIND_SHADER_RESOURCE;
D3D11_SUBRESOURCE_DATA sd{}; sd.pSysMem = at->pcData; sd.SysMemPitch = at->mWidth * sizeof(aiTexel);
ComPtr<ID3D11Texture2D> tex;
if (SUCCEEDED(device->CreateTexture2D(&td, &sd, tex.GetAddressOf())))
{
D3D11_SHADER_RESOURCE_VIEW_DESC srvd{}; srvd.Format = td.Format; srvd.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvd.Texture2D.MipLevels = 1; srvd.Texture2D.MostDetailedMip = 0;
if (SUCCEEDED(device->CreateShaderResourceView(tex.Get(), &srvd, &srv))) m_MaterialSRVs[m] = srv;
}
}
}
}
}
// 위 방법 둘다 안될경우에 외부 파일 경로 시도
if (!m_MaterialSRVs[m])
{
std::wstring wtex = WStringFromUtf8(t);
bool isAbs = (!wtex.empty() && (wtex.find(L":") != std::wstring::npos || wtex[0] == L'/' || wtex[0] == L'\\'));
std::wstring full = isAbs ? wtex : (baseDir + wtex);
if (auto* cached = findCached(full)) { m_MaterialSRVs[m] = cached; cached->AddRef(); }
else
{
ComPtr<ID3D11Resource> res; ID3D11ShaderResourceView* srv = nullptr;
if (SUCCEEDED(CreateWICTextureFromFile(device, full.c_str(), res.GetAddressOf(), &srv))) { m_MaterialSRVs[m] = srv; addCache(full, srv); }
}
}
}
if (!m_MaterialSRVs[m]) { m_MaterialSRVs[m] = m_pWhite; if (m_pWhite) m_pWhite->AddRef(); }
}
return true;
}



bool FbxManager::BuildMeshBuffers(ID3D11Device* device, const aiScene* scene)
{
......
std::vector<VertexTBN> vertices; vertices.reserve(8192);
std::vector<uint32_t> indices; indices.reserve(16384);
m_Subsets.clear();
std::function<void(const aiNode*, const aiMatrix4x4&)> traverse;
traverse = [&](const aiNode* node, const aiMatrix4x4& parent){
aiMatrix4x4 global = parent * node->mTransformation;
for (unsigned mi = 0; mi < node->mNumMeshes; ++mi)
{
const aiMesh* mesh = scene->mMeshes[node->mMeshes[mi]];
size_t base = vertices.size();
for (unsigned i = 0; i < mesh->mNumVertices; ++i)
{
aiVector3D p = mesh->mVertices[i];
aiVector3D n = mesh->HasNormals() ? mesh->mNormals[i] : aiVector3D(0,1,0);
aiVector3D uv = mesh->HasTextureCoords(0) ? mesh->mTextureCoords[0][i] : aiVector3D(0,0,0);
aiVector3D tg = mesh->HasTangentsAndBitangents() ? mesh->mTangents[i] : aiVector3D(1,0,0);
aiVector3D bt = mesh->HasTangentsAndBitangents() ? mesh->mBitangents[i] : aiVector3D(0,1,0);
vertices.push_back({ {p.x,p.y,p.z}, {n.x,n.y,n.z}, {tg.x,tg.y,tg.z}, {bt.x,bt.y,bt.z}, {1,1,1,1}, {uv.x,uv.y} });
}
uint32_t start = (uint32_t)indices.size();
for (unsigned f = 0; f < mesh->mNumFaces; ++f)
{
const aiFace& face = mesh->mFaces[f];
if (face.mNumIndices == 3)
{
// Reverse winding to correct back/front after Y-flip
indices.push_back((uint32_t)(base + face.mIndices[0]));
indices.push_back((uint32_t)(base + face.mIndices[1]));
indices.push_back((uint32_t)(base + face.mIndices[2]));
}
}
uint32_t count = (uint32_t)indices.size() - start;
m_Subsets.push_back({ start, count, mesh->mMaterialIndex });
}
for (unsigned ci = 0; ci < node->mNumChildren; ++ci) traverse(node->mChildren[ci], global);
};
traverse(scene->mRootNode, aiMatrix4x4());
......
}





#include "17_Shared.fxh"
// 픽셀 셰이더(쉐이더/셰이더)
float4 main(VertexOut pIn) : SV_Target
{
// 디버그 단축 경로들 (g_Pad)
if (abs(g_Pad - 1.0f) < 1e-3) { return float4(1,0,1,1); }
if (abs(g_Pad - 2.0f) < 1e-3) { return float4(1,1,1,1); }
if (abs(g_Pad - 3.0f) < 1e-3) { return pIn.color; }
// 알파 컷아웃 조명 모드에서만 적용
float4 textureColor = g_DiffuseMap.Sample(g_Sam, pIn.tex);
float alphaTex = textureColor.a * g_Material.diffuse.a;
clip(alphaTex - 0.1f);
// 월드 노말 계산(Nw) - 노말맵 토글에 따라 분기
float3 N = normalize(pIn.normalW);
if (g_EnableNormalMap != 0)
{
float3 T = normalize(pIn.tangentW);
float3 N = normalize(pIn.normalW);
float3 B = normalize(pIn.bitanW);
float handed = dot(cross(T, B), N);
if (handed < 0.0f) B = -B;
float3x3 TBN = float3x3(T, B, N);
float3 N_ts = g_NormalMap.Sample(g_Sam, pIn.tex).xyz * 2.0f - 1.0f;
N_ts.y = -N_ts.y; // 그린 채널 반전 보정
N_ts = normalize(N_ts);
N = normalize(mul(N_ts, TBN));
}
// 공통: 라이팅 벡터들
float3 L = normalize(-g_DirLight.direction);
float3 V = normalize(g_EyePosW - pIn.posW);
float NdotL = dot(N, L);
float theta = saturate(NdotL);
float4 ambientTerm = g_Material.ambient * g_DirLight.ambient;
float4 diffuseTerm = theta * g_DirLight.diffuse;
float4 specularTerm = 0;
// 분기별 조명 계산
if (g_ShadingMode == 0)
{
// Phong
float3 R = reflect(-L, N);
float NdotV = saturate(dot(N, V));
float specGate = step(0.0f, NdotL) * step(0.0f, NdotV);
float s = pow(max(dot(R, V), 0.0f), max(g_Material.specular.w, 1.0f)) * specGate;
specularTerm = s * g_Material.specular * g_DirLight.specular;
}
else if (g_ShadingMode == 1)
{
// Blinn-Phong
float3 H = normalize(L + V);
float NdotH = saturate(dot(N, H));
float NdotV = saturate(dot(N, V));
float specGate = step(0.0f, NdotL) * step(0.0f, NdotV);
float s = pow(NdotH, g_Material.specular.w) * specGate;
specularTerm = s * g_Material.specular * g_DirLight.specular;
}
else if (g_ShadingMode == 2)
{
// Lambert
specularTerm = 0;
}
// Unlit (3): 조명 없이 텍스처*diffuse만
else if (g_ShadingMode == 3)
{
ambientTerm = 0;
diffuseTerm = 0;
specularTerm = 0;
}
// TextureOnly (4): 텍스처만 출력
else if (g_ShadingMode == 4)
{
float4 only = textureColor * g_Material.diffuse;
only.a = alphaTex;
return only;
}
// kd = texture * material.diffuse
float4 kd = textureColor * g_Material.diffuse;
float4 litColor = kd * (ambientTerm + diffuseTerm) + specularTerm;
// 환경 반사 (Unlit/Lambert에는 적용하지 않음)
if (g_ShadingMode == 0 || g_ShadingMode == 1)
{
float3 Renv = reflect(-V, N);
float roughness = saturate(g_Material.reflect.a);
const float kMaxMip = 8.0f;
float mipBias = roughness * roughness * kMaxMip;
float4 reflectionColor = g_TexCube.SampleBias(g_Sam, Renv, mipBias);
float reflectGate = theta;
litColor += (g_Material.reflect * reflectGate) * reflectionColor;
}
litColor.a = alphaTex;
return litColor;
}
| pmx - Phong | pmx - Blinn Phong |
|---|---|
| pmx - Lambert | pmx - TextureOnly |
|---|---|
| fbx - Phong | fbx - Blinn Phong |
|---|---|
| fbx - Lambert | fbx - TextureOnly |
|---|---|
| obj - Phong | obj - Blinn Phong |
|---|---|
| obj - Lambert | obj - TextureOnly |
|---|---|
도움이 많이 되었어요