DX12 实战 模型导入
前言
本篇是DX12实现篇——使用assimp库导入.obj模型,先仅仅显示简单的模型,等后面再向其中添加纹理等等
github我的实现
Assimp
assimp的安装配置网上有很多教程这里就不谈了,但这有一个坑可能对你有所帮助——不知道为什么笔者用assimp最新的库没法导入.obj模型,系统会提示ReadFile()的返回值为nullptr,但用3.3.1的库就没问题,这坑耗了我两天时间真是受不了!
assimp总体的框架
有几点需要注意:
- Scene对象包含所有的场景和模型数据
- Scene中的mMeshes存储了真正的模型数据,而左边node仅仅存储的是数据数组的索引
- 顶点索引数据来自mFaces
- Mesh存储材质索引
Model class
在这里使用一个Model class来实现模型的导入
struct Vertex
{
Vertex() = default;
Vertex(const Vertex& rhs)
{
this->position = rhs.position;
this->normal = rhs.normal;
this->tangent = rhs.tangent;
this->texCoord = rhs.texCoord;
}
Vertex& operator= (Vertex& rhs)
{
return rhs;
}
Vertex(Vertex&& rhs) = default;
XMFLOAT3 position;
XMFLOAT3 normal;
XMFLOAT3 tangent;
XMFLOAT2 texCoord;
};
class Model
{
public:
struct Mesh
{
Mesh() = default;
std::vector<Vertex> vertices;
std::vector<uint32_t> indices;
Mesh(std::vector<Vertex>& vertices, std::vector<UINT>& indices)
{
this->vertices = vertices;
this->indices = indices;
}
};
// load scene for assimp
Model(const std::string& path);
// Traverse and process the nodes in assimp in turn
void TraverseNode(const aiScene* scene, aiNode* node);
// load mesh, which includes vertex, index, normal, tangent, texture, material information
Mesh LoadMesh(const aiScene* scene, aiMesh* mesh);
std::vector<Vertex> GetVertices();
std::vector<uint32_t> GetIndices();
private:
std::string directory;
std::vector<Mesh> m_meshs;
};
Model::Model(const std::string& path)
{
Assimp::Importer localImporter;
const aiScene* pLocalScene = localImporter.ReadFile(
path,
// Triangulates all faces of all meshes
aiProcess_Triangulate |
// Supersedes the aiProcess_MakeLeftHanded and aiProcess_FlipUVs and aiProcess_FlipWindingOrder flags
aiProcess_ConvertToLeftHanded |
// This preset enables almost every optimization step to achieve perfectly optimized data. In D3D, need combine with aiProcess_ConvertToLeftHanded
aiProcessPreset_TargetRealtime_MaxQuality |
// Calculates the tangents and bitangents for the imported meshes
aiProcess_CalcTangentSpace |
// Splits large meshes into smaller sub-meshes
// This is quite useful for real-time rendering,
// where the number of triangles which can be maximally processed in a single draw - call is limited by the video driver / hardware
aiProcess_SplitLargeMeshes |
// A postprocessing step to reduce the number of meshes
aiProcess_OptimizeMeshes |
// A postprocessing step to optimize the scene hierarchy
aiProcess_OptimizeGraph);
// "localScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE" is used to check whether value data returned is incomplete
if (pLocalScene == nullptr || pLocalScene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || pLocalScene->mRootNode == nullptr)
{
std::cout << "ERROR::ASSIMP::" << localImporter.GetErrorString() << std::endl;
}
directory = path.substr(0, path.find_last_of('/'));
TraverseNode(pLocalScene, pLocalScene->mRootNode);
}
void Model::TraverseNode(const aiScene* scene, aiNode* node)
{
// load mesh
for (UINT i = 0; i < node->mNumMeshes; ++i)
{
aiMesh* pLocalMesh = scene->mMeshes[node->mMeshes[i]];
m_meshs.push_back(LoadMesh(scene, pLocalMesh));
}
// traverse child node
for (UINT i = 0; i < node->mNumChildren; ++i)
{
TraverseNode(scene, node->mChildren[i]);
}
}
Model::Mesh Model::LoadMesh(const aiScene* scene, aiMesh* mesh)
{
std::vector<Vertex> localVertices;
std::vector<uint32_t> localIndices;
// process vertex position, normal, tangent, texture coordinates
for (UINT i = 0; i < mesh->mNumVertices; ++i)
{
Vertex localVertex;
localVertex.position.x = mesh->mVertices[i].x;
localVertex.position.y = mesh->mVertices[i].y;
localVertex.position.z = mesh->mVertices[i].z;
localVertex.normal.x = mesh->mNormals[i].x;
localVertex.normal.y = mesh->mNormals[i].y;
localVertex.normal.z = mesh->mNormals[i].z;
localVertex.tangent.x = mesh->mTangents[i].x;
localVertex.tangent.y = mesh->mTangents[i].y;
localVertex.tangent.z = mesh->mTangents[i].z;
// assimp allow one model have 8 different texture coordinates in one vertex, but we just care first texture coordinates because we will not use so many
if (mesh->mTextureCoords[0])
{
localVertex.texCoord.x = mesh->mTextureCoords[0][i].x;
localVertex.texCoord.y = mesh->mTextureCoords[0][i].y;
}
else
{
localVertex.texCoord = XMFLOAT2(0.0f, 0.0f);
}
localVertices.push_back(localVertex);
}
for (UINT i = 0; i < mesh->mNumFaces; ++i)
{
aiFace localFace = mesh->mFaces[i];
for (UINT j = 0; j < localFace.mNumIndices; ++j)
{
localIndices.push_back(localFace.mIndices[j]);
}
}
return Mesh(localVertices, localIndices);
}
std::vector<Vertex> Model::GetVertices()
{
std::vector<Vertex> localVertices;
for (auto& m : m_meshs)
{
for (auto& v : m.vertices)
{
localVertices.push_back(v);
}
}
return localVertices;
}
std::vector<uint32_t> Model::GetIndices()
{
std::vector<uint32_t> localIndices;
for (auto& m : m_meshs)
{
for (auto& i : m.indices)
{
localIndices.push_back(i);
}
}
return localIndices;
}
输入元素布局
m_inputLayout =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 24, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 36, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}
};
build model
Model ElysiaBody("ModelFile/elysia/body.obj");
Model ElysiaSword("ModelFile/lisushang/body.obj");
// 记录模型数据用于后续的渲染
auto elysiaBodyVertices = ElysiaBody.GetVertices();
auto elysiaBodyIndices = ElysiaBody.GetIndices();
assert(elysiaBodyVertices.size() > 0);
assert(elysiaBodyIndices.size() > 0);
auto elysiaSwordVertices = ElysiaSword.GetVertices();
auto elysiaSwordIndices = ElysiaSword.GetIndices();
assert(elysiaSwordVertices.size() > 0);
assert(elysiaBodyIndices.size() > 0);
uint32_t elysiaBodyVertexOffset = 0;
uint32_t elysiaSwordVertexOffset = elysiaBodyVertices.size();
uint32_t elysiaBodyIndexOffset = 0;
uint32_t elysiaSwordIndexOffset = elysiaBodyIndices.size();
auto pElysiaBodyDraw = std::make_unique<Geometrie::Draw>();
pElysiaBodyDraw->baseVertex = elysiaBodyVertexOffset;
pElysiaBodyDraw->indexCount = (UINT)elysiaBodyIndices.size();
pElysiaBodyDraw->startIndex = elysiaBodyIndexOffset;
auto pElysiaSwordDraw = std::make_unique<Geometrie::Draw>();
pElysiaSwordDraw->baseVertex = elysiaSwordVertexOffset;
pElysiaSwordDraw->indexCount = (UINT)elysiaSwordIndices.size();
pElysiaSwordDraw->startIndex = elysiaSwordIndexOffset;
auto totalVertexCount = elysiaBodyVertices.size() + elysiaSwordVertices.size();
std::vector<Vertex> localVertices(totalVertexCount);
uint32_t k = 0;
for (size_t i = 0; i < elysiaBodyVertices.size(); ++i, ++k)
{
localVertices[k].position = elysiaBodyVertices[i].position;
localVertices[k].normal = elysiaBodyVertices[i].normal;
localVertices[k].tangent = elysiaBodyVertices[i].tangent;
localVertices[k].texCoord = elysiaBodyVertices[i].texCoord;
}
for (size_t i = 0; i < elysiaSwordVertices.size(); ++i, ++k)
{
localVertices[k].position = elysiaSwordVertices[i].position;
localVertices[k].normal = elysiaSwordVertices[i].normal;
localVertices[k].tangent = elysiaSwordVertices[i].tangent;
localVertices[k].texCoord = elysiaSwordVertices[i].texCoord;
}
std::vector<uint32_t> localIndices;
localIndices.insert(localIndices.end(), std::begin(elysiaBodyIndices), std::end(elysiaBodyIndices));
localIndices.insert(localIndices.end(), std::begin(elysiaSwordIndices), std::end(elysiaSwordIndices));
const uint32_t vbSize = (uint32_t)localVertices.size() * sizeof(Vertex);
const uint32_t ibSize = (uint32_t)localIndices.size() * sizeof(uint32_t);
auto pGeo = std::make_unique<Geometrie>();
pGeo->name = "character";
pGeo->vbSize = vbSize;
pGeo->ibSize = ibSize;
pGeo->vbStride = sizeof(Vertex);
pGeo->ibFormat = DXGI_FORMAT_R32_UINT;
pGeo->ibOffset = 0;
pGeo->vbOffset = 0;
ThrowIfFailed(D3DCreateBlob(vbSize, &pGeo->vertexBufferCPU));
CopyMemory(pGeo->vertexBufferCPU->GetBufferPointer(), localVertices.data(), vbSize);
ThrowIfFailed(D3DCreateBlob(ibSize, &pGeo->indexBufferCPU));
CopyMemory(pGeo->indexBufferCPU->GetBufferPointer(), localIndices.data(), ibSize);
pGeo->vertexBufferGPU = CreateDefaultBuffer(m_device.Get(), m_commandList.Get(), localVertices.data(), vbSize, pGeo->vertexUploadBuffer);
pGeo->indexBufferGPU = CreateDefaultBuffer(m_device.Get(), m_commandList.Get(), localIndices.data(), ibSize, pGeo->indexUploadBuffer);
m_draws["ElysiaBody"] = std::move(pElysiaBodyDraw);
m_draws["LiSuShangBody"] = std::move(pElysiaSwordDraw);
m_geometries[pGeo->name] = std::move(pGeo);
build renderer
auto pElysiaRenderer = std::make_unique<Renderer>();
//这里将模型进行平移和旋转,使其看向摄像机
auto elysiaWorld = DirectX::XMMatrixTranslation(-10.f, 0.f, 0.f) * DirectX::XMMatrixRotationY(MathHelper::Pi);
XMStoreFloat4x4(&pElysiaRenderer->world, elysiaWorld);
pElysiaRenderer->objectIndex = 0;
pElysiaRenderer->numFramesDirty = FrameCount;
pElysiaRenderer->pGeo = m_geometries["character"].get();
pElysiaRenderer->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
pElysiaRenderer->indexCount = m_draws["ElysiaBody"]->indexCount;
pElysiaRenderer->startIndex = m_draws["ElysiaBody"]->startIndex;
pElysiaRenderer->baseVertex = m_draws["ElysiaBody"]->baseVertex;
m_allRenderers.push_back(std::move(pElysiaRenderer));
auto pLiSuShangRenderer = std::make_unique<Renderer>();
auto liSuShangWorld = DirectX::XMMatrixTranslation(10.f, 0.f, 0.f) * DirectX::XMMatrixRotationY(MathHelper::Pi);
XMStoreFloat4x4(&pLiSuShangRenderer->world, liSuShangWorld);
pLiSuShangRenderer->objectIndex = 1;
pLiSuShangRenderer->numFramesDirty = FrameCount;
pLiSuShangRenderer->pGeo = m_geometries["character"].get();
pLiSuShangRenderer->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
pLiSuShangRenderer->indexCount = m_draws["LiSuShangBody"]->indexCount;
pLiSuShangRenderer->startIndex = m_draws["LiSuShangBody"]->startIndex;
pLiSuShangRenderer->baseVertex = m_draws["LiSuShangBody"]->baseVertex;
m_allRenderers.push_back(std::move(pLiSuShangRenderer));
for (auto& r : m_allRenderers)
{
m_opaqueRenderers.push_back(r.get());
}
Build Constant Buffer View
// create a const buffer view(CBV) descriptor heap
// pass constant buffer locates behind object constant buffer
m_passCBVOffset = (UINT)m_opaqueRenderers.size() * FrameCount;
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc = {};
// number of pass constant and object constant
cbvHeapDesc.NumDescriptors = (uint32_t)(m_opaqueRenderers.size() + 1) * FrameCount;
cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(m_device->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(&m_cbvHeap)));
NAME_D3D12_OBJECT(m_cbvHeap);
// create object constant buffer view descriptor
{
auto objectCBSize = CalcConstantBufferByteSize(sizeof(ObjectConstantBuffer));
for (size_t frameIndex = 0; frameIndex < FrameCount; ++frameIndex)
{
auto currObjectCB = m_frameResources[frameIndex]->objectUploadCB->Resource();
for (size_t i = 0; i < m_opaqueRenderers.size(); ++i)
{
D3D12_GPU_VIRTUAL_ADDRESS currObjectCBAdress = currObjectCB->GetGPUVirtualAddress();
currObjectCBAdress += i * objectCBSize;
int heapIndex = frameIndex * m_opaqueRenderers.size() + i;
auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(m_cbvHeap->GetCPUDescriptorHandleForHeapStart(), heapIndex, m_cbvDescriptorSize);
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = currObjectCBAdress;
cbvDesc.SizeInBytes = objectCBSize;
m_device->CreateConstantBufferView(&cbvDesc, handle);
}
}
}
// create pass constant buffer view descriptor
{
auto passCBSize = CalcConstantBufferByteSize(sizeof(PassConstantBuffer));
for (size_t frameIndex = 0; frameIndex < FrameCount; ++frameIndex)
{
auto currPassCB = m_frameResources[frameIndex]->passUploadCB->Resource();
D3D12_GPU_VIRTUAL_ADDRESS currPassCBAdress = currPassCB->GetGPUVirtualAddress();
int heapIndex = m_passCBVOffset + frameIndex;
auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(m_cbvHeap->GetCPUDescriptorHandleForHeapStart(), heapIndex, m_cbvDescriptorSize);
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
cbvDesc.BufferLocation = currPassCBAdress;
cbvDesc.SizeInBytes = passCBSize;
m_device->CreateConstantBufferView(&cbvDesc, handle);
}
}
Populate command list
// populate pass constant buffer
{
int heapIndex = passCBVOffset + frameResourceIndex;
auto handle = CD3DX12_GPU_DESCRIPTOR_HANDLE(pCBVDescriptorHeap->GetGPUDescriptorHandleForHeapStart(), heapIndex, CBVDescriptorSize);
pCommandList->SetGraphicsRootDescriptorTable(0, handle);
}
// populate object constant buffer and DrawIndexedInstanced
{
auto objectCBSize = CalcConstantBufferByteSize(sizeof(ObjectConstantBuffer));
auto currObjectUploadCB = this->objectUploadCB->Resource();
for (size_t i = 0; i < renderers.size(); ++i)
{
auto currRenderer = renderers[i];
pCommandList->IASetPrimitiveTopology(currRenderer->PrimitiveType);
pCommandList->IASetVertexBuffers(0, 1, &currRenderer->pGeo->VertexBufferView());
pCommandList->IASetIndexBuffer(&currRenderer->pGeo->IndexBufferView());
int heapIndex = frameResourceIndex * renderers.size() + currRenderer->objectIndex;
auto handle = CD3DX12_GPU_DESCRIPTOR_HANDLE(pCBVDescriptorHeap->GetGPUDescriptorHandleForHeapStart(), heapIndex, CBVDescriptorSize);
pCommandList->SetGraphicsRootDescriptorTable(1, handle);
pCommandList->DrawIndexedInstanced(currRenderer->indexCount, 1, currRenderer->startIndex, currRenderer->baseVertex, 0);
}
}
输出
下一篇
下一篇会实现添加纹理
reference
https://learnopengl-cn.github.io/
Directx12 3D 游戏开发实战