이전에는 간단한 삼각형만 렌더링 했었는데
이번엔 외부 라이브러리로 파일을 로드시켜서 화면에 띄워볼 것임
모델 파일을 불러오기 위해 일단 확장자가 다양하게 있음
obj, fbx, blend 등이 대표적으로 많이 사용됨
이런 파일들을 읽어오는 라이브러리가 몇가지가 있는데
Assimp 라는 라이브러리는 위에 적어놓은 확장자를 포함하여
다양한 확장자들을 읽을 수 있다.
The Asset-Importer Library Home (assimp.org)
하지만 사용법이 안보이고 샘플 프로그램을
직접 분석해야 하기 때문에 공부하기 까다롭다
다른 방법으로 FbxSDK를 사용하면 Fbx 파일만 읽을 수는 있지만
이 또한 사용법은 제대로 안알려주니 똑같이 샘플 코드보고 따라하던가 해야함
Blender Sdk 는 안써봐서 잘모르겠음
아무튼 근성으로 FbxSDK 이것저것 뜯어보고 실험 해본 결과
대충 쓸 수는 있게 됐으므로 정리해보기로 하였음
결과
이전에 빨간 삼각형 출력하는 프로그램에 추가로 작업
일단 정점 쉐이더
cbuffer ConstantBuffer : register(b0)
{
matrix World;
matrix View;
matrix Projection;
}
struct Vin {
float4 pos : POSITION;
float2 texcoord : TEXCOORD;
};
struct VOut {
float4 pos : SV_POSITION;
float2 texcoord : TEXCOORD;
};
VOut main(Vin input)
{
VOut output;
output.pos = mul(input.pos, World);
output.pos = mul(output.pos, View);
output.pos = mul(output.pos, Projection);
output.texcoord = input.texcoord;
return output;
}
Vin은 쉐이더로 들어오는 데이터이고 VOut은 나가는 데이터
texcoord 텍스처 좌표는 들어오자마자 건들지말고 고대로 픽셀 쉐이더로 넘겨버리고
3 매트릭스는 정점 위치랑 순서대로 곱하여 최종 위치를 계산하여 픽셀로 넘겨준다
픽셀 쉐이더
Texture2D diffuseTexture;
SamplerState SampleType;
struct Pin
{
float4 position : SV_POSITION;
float2 tex : TEXCOORD;
};
float4 main(Pin input) : SV_TARGET
{
float4 textureColor = diffuseTexture.Sample(SampleType, input.tex);
return textureColor;
}
정점 쉐이더로 부터 전달받은 데이터로 Texture2D랑 샘플링 하여
float4의 픽셀 컬러로 반환
Mesh 클래스
#include <vector>
#include <string>
#include <fbxsdk.h>
#include <d3dx11.h>
#include <DirectXMath.h>
struct Vertex
{
DirectX::XMFLOAT3 position;
DirectX::XMFLOAT2 textureCoord;
};
class Mesh
{
public:
Mesh();
bool InitializeBuffer(ID3D11Device* pDevice);
void Render(ID3D11DeviceContext* pDeviceContext);
void Shutdown();
void SetResource(ID3D11Device* pDevice, std::string filePath);
public:
std::vector<Vertex> vertices;
std::vector<UINT> indices;
ID3D11ShaderResourceView* diffuseTexture;
private:
ID3D11Buffer* m_vertexBuffer;
ID3D11Buffer* m_indexBuffer;
UINT stride = sizeof(Vertex);
UINT offset = 0;
};
메쉬 하나마다 정점의 위치와 정점을 그리는 순서(Index), 텍스처를 저장해두고
렌더링할 때 마다 Render함수를 호출하면 미리 초기화 둔 정점, 인덱스 버퍼를 통해
정점 쉐이더로 전달하는 클래스임
FbxLoader 클래스
#ifndef _FBXLOADER_H_
#define _FBXLOADER_H_
#pragma comment(lib, "libfbxsdk-mt.lib")
#include "Mesh.h"
class FbxLoader
{
public:
FbxLoader();
bool LoadFile(ID3D11Device*, HWND, std::string FileDirectoryPath);
void Shutdown();
void Render(ID3D11DeviceContext* pDeviceContext);
private:
void processNode(FbxNode*, ID3D11Device* pDevice);
void processMesh(FbxNode*, ID3D11Device* pDevice);
bool GetTextureFromMaterial(FbxSurfaceMaterial*, ID3D11Device* pDevice, Mesh& mesh);
private:
std::vector<Mesh> m_meshes;
};
#endif
Fbx 파일로부터 메쉬 데이터를 추출하는 클래스
FbxSDK 2018 버전을 설치하고 폴더 위치를 포함 디렉터리에 추가하면됨
다중 스레드 디버그를 선택하고 링커 추가 종속성에
libfbxsdk-mt.lib 를 추가하면 됨
#include "FbxLoader.h"
FbxLoader::FbxLoader()
{
}
bool FbxLoader::LoadFile(ID3D11Device* pDevice, HWND hwnd, std::string fileDirectory)
{
bool result;
FbxIOSettings* pIos;
FbxImporter* pImporter;
FbxScene* pScene;
//FBX 매니저 생성
FbxManager* pManager = FbxManager::Create();
//파일 IO 입출력 설정(IOSROOT 기본 설정)
pIos = FbxIOSettings::Create(pManager, IOSROOT);
//설정 적용
pManager->SetIOSettings(pIos);
//FileName을 두번째 매개변수에 넣어 씬 이름을 저장하는데 딱히 쓸데는 없다
//Scene 생성
pScene = FbxScene::Create(pManager, "");
//불러온 데이터에 접근 할 수 있는 인터페이스를 제공
pImporter = FbxImporter::Create(pManager, "");
//파일이 제대로 로드가 됐는지 확인
result = pImporter->Initialize(fileDirectory.c_str(), -1, pManager->GetIOSettings());
if (!result)
{
MessageBox(hwnd, L"FbxImport Error", L"Error", MB_OK);
std::string str = pImporter->GetStatus().GetErrorString();
std::wstring w_trans = std::wstring(str.begin(), str.end());
MessageBox(hwnd, w_trans.c_str(), L"Error", MB_OK);
return false;
}
//로드된 파일의 데이터를 Scene에 담고 Importer 자원 반환
pImporter->Import(pScene);
pImporter->Destroy();
pImporter = nullptr;
//보통 폴리곤 형태가 사각형으로 되어 있기도 하여 일단 삼각형 폴리곤으로 변환함
FbxGeometryConverter converter(pManager);
converter.Triangulate(pScene, true);//true시 원본 데이터 유지
//모델 파일의 노드 탐색
processNode(pScene->GetRootNode(), pDevice);
//메쉬마다 정점과 인덱스 버퍼를 생성
for (Mesh& m : m_meshes)
{
result = m.InitializeBuffer(pDevice);
if (!result)
{
MessageBox(hwnd, L"Could not initialize Vertex Buffer", L"Error", MB_OK);
return false;
}
}
return true;
}
void FbxLoader::processNode(FbxNode* pNode, ID3D11Device* pDevice)
{
//노드의 속성을 가져옴
FbxNodeAttribute* nodeAttribute = pNode->GetNodeAttribute();
if (nodeAttribute != nullptr)
{
//노드 타입이 메쉬나 스켈레톤일 경우에만 작업
switch (nodeAttribute->GetAttributeType())
{
case FbxNodeAttribute::eMesh:
processMesh(pNode, pDevice);
break;
case FbxNodeAttribute::eSkeleton:
break;
}
}
//자식 노드가 있는지 확인하며 탐색
if (pNode->GetChildCount() > 0)
{
for (int i = 0; i < pNode->GetChildCount(); i++)
{
processNode(pNode->GetChild(i), pDevice);
}
}
return;
}
void FbxLoader::processMesh(FbxNode* pNode, ID3D11Device* pDevice)
{
FbxMesh* pMesh = pNode->GetMesh();
Mesh lMesh;
if (pMesh->IsTriangleMesh())
{
//메쉬내 모든 정점 정보를 받아옴
FbxVector4* positions = pMesh->GetControlPoints();
//UVSet 이름들을 불러옴
FbxStringList uvNames;
pMesh->GetUVSetNames(uvNames);
//모든 텍스처 좌표를 배열에 저장
int polygonCount = pMesh->GetPolygonCount();
for (int j = 0; j < polygonCount; j++)
{
for (int i = 0; i < 3; i++)//삼각형 폴리곤 기준 정점 그리는 순서에 따라 데이터를 배열에 밀어넣는다.
{
bool unmappedUV;//매핑 여부 (주의*) false를 리턴받게 되면 매핑이 되어있음을 의미
Vertex vt;
//정점 그리는 순서를 받아옴
UINT index = pMesh->GetPolygonVertex(j, i);
//정점 좌표를 저장
vt.position = DirectX::XMFLOAT3(
static_cast<float>(positions[index].mData[0]),
static_cast<float>(positions[index].mData[1]),
static_cast<float>(positions[index].mData[2]));
//텍스처 좌표를 저장
FbxVector2 fv2;
pMesh->GetPolygonVertexUV(j, i, uvNames[0], fv2, unmappedUV);
vt.textureCoord = DirectX::XMFLOAT2(
static_cast<float>(fv2.mData[0]),
1.0f - static_cast<float>(fv2.mData[1])
);
//배열에 추가
lMesh.vertices.emplace_back(vt);
}
}
//인덱스 순서대로 나열
int vertexCount = pMesh->GetPolygonVertexCount();
for (int i = 0; i < vertexCount; i++)
{
lMesh.indices.emplace_back(i);
}
//텍스처가 있는 경우 저장
int materialCount = pNode->GetMaterialCount();
if (materialCount > 0)
{
FbxSurfaceMaterial* pMaterial = pNode->GetMaterial(0);
if (!pMaterial)
{
return;
}
GetTextureFromMaterial(pMaterial, pDevice, lMesh);
}
m_meshes.emplace_back(lMesh);
pMesh->Destroy();
}
return;
}
//Material을 통해 텍스처 파일 경로를 알아내고 해당 경로를 통해 텍스처를 로드하는 함수
bool FbxLoader::GetTextureFromMaterial(FbxSurfaceMaterial* pMaterial, ID3D11Device* pDevice, Mesh& mesh)
{
HRESULT result;
//Diffuse 속성을 가져옴
FbxProperty prop = pMaterial->FindProperty(FbxSurfaceMaterial::sDiffuse);
if (!prop.IsValid())
{
return false;
}
//텍스처 보유 확인
int textureCount = prop.GetSrcObjectCount<FbxTexture>();
if (!textureCount)
{
return false;
}
//텍스처 파일 정보를 가져옴
FbxString tempstr;
FbxFileTexture* pTexture = prop.GetSrcObject<FbxFileTexture>();
if (!pTexture) {
return false;
}
//파일 경로를 저장한다.
//저장한 FbxString을 string으로 변환
tempstr = pTexture->GetFileName();
mesh.SetResource(pDevice, tempstr.Buffer());
pTexture->Destroy();
return true;
}
void FbxLoader::Render(ID3D11DeviceContext* pDeviceContext)
{
//모델의 메쉬 하나씩 렌더링 함
for (Mesh& m : m_meshes)
{
m.Render(pDeviceContext);
}
return;
}
void FbxLoader::Shutdown()
{
//구조체 배열안의 포인터는 직접 해제
for (Mesh& m : m_meshes)
{
m.Shutdown();
}
m_meshes.clear();
}
나머지 전체 코드는 깃 링크에
h117562/Tutorial_FbxSDK: Tutorial FbxSDK (github.com)
사용한 모델 파일들은 Mixamo 홈페이지에서 구할 수 있음
AppicationClass에서 모델파일의 위치를 수정하여 렌더링하면
자동으로 파일에 내장되어있는 텍스처가 fbm 폴더로 추출됨
내장되어있는 텍스처를 그대로 사용하는 방법은 알 수 없었음