DX12 实战 模型导入

前言

本篇是DX12实现篇——使用assimp库导入.obj模型,先仅仅显示简单的模型,等后面再向其中添加纹理等等

github我的实现

Assimp

assimp的安装配置网上有很多教程这里就不谈了,但这有一个坑可能对你有所帮助——不知道为什么笔者用assimp最新的库没法导入.obj模型,系统会提示ReadFile()的返回值为nullptr,但用3.3.1的库就没问题,这坑耗了我两天时间真是受不了!

assimp总体的框架
img

有几点需要注意:

  1. Scene对象包含所有的场景和模型数据
  2. Scene中的mMeshes存储了真正的模型数据,而左边node仅仅存储的是数据数组的索引
  3. 顶点索引数据来自mFaces
  4. 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);
    }
}

输出

image-20230420232152420

下一篇

下一篇会实现添加纹理

reference

https://learnopengl-cn.github.io/

Directx12 3D 游戏开发实战

posted @ 2023-04-21 09:01  爱莉希雅  阅读(336)  评论(0)    收藏  举报