Link

加载 3D 模型

渲染三角形和参数化网格很好,但是引擎加载在专用程序中制作的 3D 模型。为此,我们将实现基本的 OBJ 格式加载。

OBJ 格式是一种非常简单的格式,几乎所有处理 3D 模型的软件都能理解。我们将使用库 tiny_obj_loader 加载 Blender 猴子网格(在 assets 文件夹中)并渲染它。

我们现在拥有的代码可以渲染任何任意网格,只要顶点数组被填充,并且我们可以使用推送常量矩阵在 3D 空间中移动该网格。

我们将首先向 VulkanEngine 类添加一个新的 Mesh 对象,以保存新加载的猴子网格。

class VulkanEngine {
public:
//other code ....
Mesh _monkeyMesh;
}

接下来,我们将向 Mesh 对象添加一个函数,以从 obj 文件初始化它。

struct Mesh {
	// other code .....

	bool load_from_obj(const char* filename);
};
//make sure that you are including the library
#include <tiny_obj_loader.h>
#include <iostream>
bool Mesh::load_from_obj(const char* filename)
{
    return false;
}

OBJ 格式

在 OBJ 文件中,顶点不是存储在一起的。相反,它保存了位置、法线、UV 和颜色的单独数组,然后是一个指向这些数组的面数组。给定的 obj 文件也有多个形状,因为它可以容纳多个对象,每个对象都有单独的材质。在本教程中,我们将单个 obj 文件加载到单个网格中,并且所有 obj 形状都将被合并。

让我们继续填充加载函数

bool Mesh::load_from_obj(const char* filename)
{
    //attrib will contain the vertex arrays of the file
	tinyobj::attrib_t attrib;
    //shapes contains the info for each separate object in the file
	std::vector<tinyobj::shape_t> shapes;
    //materials contains the information about the material of each shape, but we won't use it.
    std::vector<tinyobj::material_t> materials;

    //error and warning output from the load function
	std::string warn;
	std::string err;

    //load the OBJ file
	tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filename, nullptr);
    //make sure to output the warnings to the console, in case there are issues with the file
	if (!warn.empty()) {
		std::cout << "WARN: " << warn << std::endl;
	}
    //if we have any error, print it to the console, and break the mesh loading.
    //This happens if the file can't be found or is malformed
	if (!err.empty()) {
		std::cerr << err << std::endl;
		return false;
	}
}

通过这段代码,我们使用库将 obj 文件加载到我们可以用来转换为网格格式的结构中。我们需要声明 LoadObj 函数使用的一些结构,然后我们进行错误检查。

继续加载函数,将文件中的网格放入我们的顶点缓冲区

    // Loop over shapes
	for (size_t s = 0; s < shapes.size(); s++) {
		// Loop over faces(polygon)
		size_t index_offset = 0;
		for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {

            //hardcode loading to triangles
			int fv = 3;

			// Loop over vertices in the face.
			for (size_t v = 0; v < fv; v++) {
				// access to vertex
				tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];

                //vertex position
				tinyobj::real_t vx = attrib.vertices[3 * idx.vertex_index + 0];
				tinyobj::real_t vy = attrib.vertices[3 * idx.vertex_index + 1];
				tinyobj::real_t vz = attrib.vertices[3 * idx.vertex_index + 2];
                //vertex normal
            	tinyobj::real_t nx = attrib.normals[3 * idx.normal_index + 0];
				tinyobj::real_t ny = attrib.normals[3 * idx.normal_index + 1];
				tinyobj::real_t nz = attrib.normals[3 * idx.normal_index + 2];

                //copy it into our vertex
				Vertex new_vert;
				new_vert.position.x = vx;
				new_vert.position.y = vy;
				new_vert.position.z = vz;

				new_vert.normal.x = nx;
				new_vert.normal.y = ny;
                new_vert.normal.z = nz;

                //we are setting the vertex color as the vertex normal. This is just for display purposes
                new_vert.color = new_vert.normal;


				_vertices.push_back(new_vert);
			}
			index_offset += fv;
		}
	}

    return true;

TinyOBJ 转换循环可能很难正确实现。这个是从他们的示例代码派生出来的,并进行了一些简化。您可以在以下位置查看原始代码:https://github.com/tinyobjloader/tinyobjloader README 页面。在这里,我们将每个面的顶点数硬编码为 3。如果您将此代码与尚未三角化的模型一起使用,则会出现问题。加载具有 4 个或更多顶点的面的模型会更复杂,因此我们将其留到以后再说。

添加代码后,我们现在可以将 obj 加载到我们的 Mesh 结构中,所以让我们将猴子网格加载到我们的三角形网格中,看看会发生什么。

加载网格

在 VulkanEngine 的 load_meshes 函数中,我们将加载猴子网格以及三角形

void VulkanEngine::load_meshes()
{
	_triangleMesh._vertices.resize(3);

	_triangleMesh._vertices[0].position = { 1.f,1.f, 0.5f };
	_triangleMesh._vertices[1].position = { -1.f,1.f, 0.5f };
	_triangleMesh._vertices[2].position = { 0.f,-1.f, 0.5f };

	_triangleMesh._vertices[0].color = { 0.f,1.f, 0.0f }; //pure green
	_triangleMesh._vertices[1].color = { 0.f,1.f, 0.0f }; //pure green
	_triangleMesh._vertices[2].color = { 0.f,1.f, 0.0f }; //pure green

    //load the monkey
	_monkeyMesh.load_from_obj("../../assets/monkey_smooth.obj");

    //make sure both meshes are sent to the GPU
    upload_mesh(_triangleMesh);
	upload_mesh(_monkeyMesh);
}

猴子网格现在已加载,所以我们可以在我们的绘制循环中使用它来显示它。它与三角形相同,但我们现在使用猴子而不是 triangleMesh


 //bind the mesh vertex buffer with offset 0
	VkDeviceSize offset = 0;
    vkCmdBindVertexBuffers(cmd, 0, 1, &_monkeyMesh._vertexBuffer._buffer, &offset);

    //we can now draw the mesh
    vkCmdDraw(cmd, _monkeyMesh._vertices.size(), 1, 0, 0);

您应该看到一个旋转的猴子头。但是有一个小故障,有些面会互相重叠绘制。这是由于我们现在缺少深度缓冲区造成的,所以让我们在下一篇文章中修复它。

triangle

下一步:设置深度缓冲区