首页 > 解决方案 > 如何读取 Blender 的默认立方体?

问题描述

我正在学习 DirectX 11,在查看 Blender 的默认立方体后我感到非常困惑,我已经将其导出为 Wavefront OBJ 格式,这是我得到的文件:

# Blender v2.92.0 OBJ File: ''
# www.blender.org
mtllib untitled.mtl
o Cube
v 1.000000 1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 1.000000 -1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 1.000000
vt 0.625000 0.500000
vt 0.875000 0.500000
vt 0.875000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.750000
vt 0.625000 1.000000
vt 0.375000 1.000000
vt 0.375000 0.000000
vt 0.625000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.250000
vt 0.125000 0.500000
vt 0.375000 0.500000
vt 0.125000 0.750000
vn 0.0000 1.0000 0.0000
vn 0.0000 0.0000 1.0000
vn -1.0000 0.0000 0.0000
vn 0.0000 -1.0000 0.0000
vn 1.0000 0.0000 0.0000
vn 0.0000 0.0000 -1.0000
usemtl Material
s off
f 1/1/1 5/2/1 7/3/1 3/4/1
f 4/5/2 3/4/2 7/6/2 8/7/2
f 8/8/3 7/9/3 5/10/3 6/11/3
f 6/12/4 2/13/4 4/5/4 8/14/4
f 2/13/5 1/1/5 3/4/5 4/5/5
f 6/11/6 5/10/6 1/1/6 2/13/6

我知道它渲染的秘诀是使用提供的索引,但我不确定我应该如何为这些数据实现缓冲区,如果顶点、uv 和法线的数量相同,我可以将它们组织成一个数组结构,但如果它们的数量不同,我不知道我怎么能这样做并创建一个缓冲区。

标签: c++directx-11

解决方案


Wavefront OBJ 格式非常老派,但它已经存在很长时间了,所以它很容易理解并且很容易解析。也就是说,像 Blender 这样的 3D 渲染器包不必以您通常用于运行时渲染的相同格式存储它们的数据,因此希望做一些工作来将每种数据的单个索引的 Wavefront OBJ 方法更改为单流渲染的顶点格式。

另一个挑战是 Wavefront OBJ 将面存储为“n-gons”,您需要将其转换为三角形以使用 Direct3D 11 进行渲染。您还可以遇到不同数组的相对索引(即负数)。然后是处理mtl文件。

可以在 GitHub 上找到使用 C++ 进行所有文本文件解析和转换的示例:WavefrontReader.h此处记录。

WaveFrontReader<uint16_t> wfReader;
Microsoft::WRL::ComPtr<ID3D11Buffer> vertexBuffer;
Microsoft::WRL::ComPtr<ID3D11Buffer> indexBuffer;

HRESULT hr = wfReader.Load(L"cube.obj");
if (FAILED(hr))
    // Error

D3D11_BUFFER_DESC bufferDesc = {};
bufferDesc.ByteWidth = static_cast<UINT>(wfReader.vertices.size() * sizeof(WaveFrontReader::Vertex));
bufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bufferDesc.Usage = D3D11_USAGE_DEFAULT;

D3D11_SUBRESOURCE_DATA initData = { wfReader.vertices.data(), 0, 0 };

hr = device->CreateBuffer(&bufferDesc, &initData, &vertexBuffer);
if (FAILED(hr))
    // Error

bufferDesc.ByteWidth = static_cast<UINT>(wfReader.indices.size() * sizeof(uint16_t));
bufferDesc.bindFlags = D3D11_BIND_INDEX_BUFFER;
initData.pSysMem = wfReader.indices.data();
hr = device->CreateBuffer(&bufferDesc, &initData, &indexBuffer);
if (FAILED(hr))
    // Error
UINT vertexStride = sizeof(WaveFrontReader::Vertex);
uint vertexOffset = 0;
deviceContext->IASetVertexBuffers(0, 1, vertexBuffer.GetAddressOf(), &vertexStride, &vertexOffset);

deviceContext->IASetIndexBuffer(indexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);

一个常见的策略——至少是游戏——是离线进行所有解析并写出一个很好的、GPU 友好的运行时文件格式,您可以将其读入内存并进行渲染。对于 DirectX,有许多“示例”格式,例如VBOSDKMESH或 Visual Studio 的CMO. 使用这些工具通常会使在运行时读取顶点和索引缓冲区变得微不足道。请参阅DirectXMeshmeshconvertDirectX SDK 示例内容导出器Visual Studio 内容管道。加载和渲染所有这些格式的代码可以在DirectX 工具包中找到。

当然,对于像立方体这样简单的常见形状,您可以直接在内存中创建它,而不必首先经历解析模型的所有麻烦。这就是DirectX 工具包GeometricPrimitive的作用。

注意:即使您使用多流渲染,您仍然必须复制数据,因为所有流都必须为每个顶点使用来自 IB 的相同索引。由于您已经在 CPU 上进行了一些数据重新排列,因此您不妨创建一个单流打包的 Vertex,它比 2 或 3 个流更有效。


推荐阅读