2021SC@SDUSC
回顾一下前几篇文章,我们讲到了有关图形学三维空间的变换与各种数据存储对象的信息,接下来就是关于三维空间中物体的网格表示。
IMesh.h与Mesh.h
与网格相关的类有两个其中IMesh是一个基类,定义了4个纯虚函数用于子类继承。
namespace OvRendering::Resources
{
/**
* Interface for any mesh
*/
class IMesh
{
public:
virtual void Bind() = 0;
virtual void Unbind() = 0;
virtual uint32_t GetVertexCount() = 0;
virtual uint32_t GetIndexCount() = 0;
};
}
而重点则是以下这个Mesh类,它对IMesh进行公有继承,用于实现网格的相关操作。
class Mesh : public IMesh
{
public:
Mesh(const std::vector<Geometry::Vertex>& p_vertices, const std::vector<uint32_t>& p_indices, uint32_t p_materialIndex);
virtual void Bind() override;
virtual void Unbind() override;
virtual uint32_t GetVertexCount() override;
virtual uint32_t GetIndexCount() override;
uint32_t GetMaterialIndex() const;
const OvRendering::Geometry::BoundingSphere& GetBoundingSphere() const;
private:
void CreateBuffers(const std::vector<Geometry::Vertex>& p_vertices, const std::vector<uint32_t>& p_indices);
void ComputeBoundingSphere(const std::vector<Geometry::Vertex>& p_vertices);
private:
const uint32_t m_vertexCount;
const uint32_t m_indicesCount;
const uint32_t m_materialIndex;
Buffers::VertexArray m_vertexArray;
std::unique_ptr<Buffers::VertexBuffer<float>> m_vertexBuffer;
std::unique_ptr<Buffers::IndexBuffer> m_indexBuffer;
Geometry::BoundingSphere m_boundingSphere;
};
Mesh类中包含了以下几个成员变量:
m_vertexCount:网格中的顶点个数;
m_indicesCount:顶点索引个数;
m_materialIndex:材质索引个数;?
m_vertexArray:顶点数组对象;
m_vertexBuffer:顶点缓冲对象;
m_indexBuffer:索引缓冲对象;
m_boundingSphere:当前网格的碰撞球对象。
这里我们看到一个新的数据类型unique_ptr,它是 C++ 11 提供的用于防止内存泄漏的智能指针中的一种实现,独享被管理对象指针所有权的智能指针。unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。 unique_ptr具有->和*运算符重载符,因此它可以像普通指针一样使用。
总的来说就是比起从前的new指针,unique_ptr对象更为安全,这里作了解就好。
这些数据类型在前文都有提及,此处就不再赘述,紧接着我们来看Mesh类的两个重要的私有函数:
1.CreateBuffers
首先我们来看CreateBuffers函数,它的作用在于为传入的网格顶点数据创建一个缓冲。我们看一看到,CreateBuffers函数的两个参数分别是一个顶点向量(相当于一个2维矩阵),以及一个索引向量。
在函数开头,创建了一个浮点向量用于存储顶点数据,然后是一个无符号整型向量用于存储索引数据。
void OvRendering::Resources::Mesh::CreateBuffers(const std::vector<Geometry::Vertex>& p_vertices, const std::vector<uint32_t>& p_indices)
{
std::vector<float> vertexData;
std::vector<unsigned int> rawIndices;
然后函数开始了一个遍历过程,在这个地方有一个语句for(auto &a:b),这个语句可以利用a容器来遍历b中的内容,同时可以利用a对b的值做出改变。
for (const auto& vertex : p_vertices)
{
vertexData.push_back(vertex.position[0]);
vertexData.push_back(vertex.position[1]);
vertexData.push_back(vertex.position[2]);
vertexData.push_back(vertex.texCoords[0]);
vertexData.push_back(vertex.texCoords[1]);
vertexData.push_back(vertex.normals[0]);
vertexData.push_back(vertex.normals[1]);
vertexData.push_back(vertex.normals[2]);
vertexData.push_back(vertex.tangent[0]);
vertexData.push_back(vertex.tangent[1]);
vertexData.push_back(vertex.tangent[2]);
vertexData.push_back(vertex.bitangent[0]);
vertexData.push_back(vertex.bitangent[1]);
vertexData.push_back(vertex.bitangent[2]);
}
在以下for循环中,函数利用变量vertex来存储p_vertices的数据,然后将vertex中的位置信息(position)、纹理坐标(texCoords)、法向量(normals)、切向量(tangent)、侧切向量(bitangent)等信息依次压入vertexData中。利用这个过程将多个变量转为一个变量,方便后期读取。
之后,为vertexData创建一个VBO顶点缓冲对下,并利用make_unique函数(一个创建unique_ptr智能指针的函数)创建一个指向该VBO的指针。
同样的也为索引向量创建上述的索引缓冲指针。
m_vertexBuffer = std::make_unique<Buffers::VertexBuffer<float>>(vertexData);
m_indexBuffer = std::make_unique<Buffers::IndexBuffer>(const_cast<uint32_t*>(p_indices.data()), p_indices.size());
最后,将VBO中的顶点数据与VAO(即变量m_vertexArray)绑定,并配置顶点信息读取格式。
在这里我们简单来看看,VBO的第一组信息是位置坐标position,所以读取信息的指针为0号,顶点数据来源为VBO _mvertexBuffer,OpenGL特定数据类型为GL_Float,每组数据的长度为3个单位数据,步长为一个vertex结构体所占的字节数。
uint64_t vertexSize = sizeof(Geometry::Vertex);
m_vertexArray.BindAttribute(0, *m_vertexBuffer, Buffers::EType::FLOAT, 3, vertexSize, 0);
m_vertexArray.BindAttribute(1, *m_vertexBuffer, Buffers::EType::FLOAT, 2, vertexSize, sizeof(float) * 3);
m_vertexArray.BindAttribute(2, *m_vertexBuffer, Buffers::EType::FLOAT, 3, vertexSize, sizeof(float) * 5);
m_vertexArray.BindAttribute(3, *m_vertexBuffer, Buffers::EType::FLOAT, 3, vertexSize, sizeof(float) * 8);
m_vertexArray.BindAttribute(4, *m_vertexBuffer, Buffers::EType::FLOAT, 3, vertexSize, sizeof(float) * 11);
后面的以此类推。
2.ComputeBoundingSphere
接下来是ComputeBoundingSphere函数,该函数的作用是对于一组网格顶点数据计算其碰撞球的中心点与半径。
具体的思路很简单,首先,为碰撞球赋一个初值,坐标为原点,半径为0。
void OvRendering::Resources::Mesh::ComputeBoundingSphere(const std::vector<Geometry::Vertex>& p_vertices)
{
m_boundingSphere.position = OvMaths::FVector3::Zero;
m_boundingSphere.radius = 0.0f;
然后,将C++标准模板库的float型数据最大值分别赋值给初始最小坐标minX、minY、minZ;同样将最小值赋值给初始最大坐标maxX、maxY、maxZ。
接着利用上文提到的for语句对p_vertices的数据进行遍历,在访问每一个顶点数据时,将最小坐标的3的分量与当前顶点坐标的3个分量进行比较,选择更小的一方更新最小坐标值;同样最大坐标值进行类似处理,只不过是利用更大的值更新变量。
if (!p_vertices.empty())
{
float minX = std::numeric_limits<float>::max();
float minY = std::numeric_limits<float>::max();
float minZ = std::numeric_limits<float>::max();
float maxX = std::numeric_limits<float>::min();
float maxY = std::numeric_limits<float>::min();
float maxZ = std::numeric_limits<float>::min();
for (const auto& vertex : p_vertices)
{
minX = std::min(minX, vertex.position[0]);
minY = std::min(minY, vertex.position[1]);
minZ = std::min(minZ, vertex.position[2]);
maxX = std::max(maxX, vertex.position[0]);
maxY = std::max(maxY, vertex.position[1]);
maxZ = std::max(maxZ, vertex.position[2]);
}
完成上述过程后,就会得到一个网格所能接触的最大与最小的坐标值分量。?
最后,将最大值与最小值求平均,得到的3个坐标分量就是当前网格的碰撞球的中心点;同时再度遍历所有顶点,将该顶点到碰撞球中心的距离,与当前碰撞球半径作比较,去最大值更新碰撞球半径。
m_boundingSphere.position = OvMaths::FVector3{ minX + maxX, minY + maxY, minZ + maxZ } / 2.0f;
for (const auto& vertex : p_vertices)
{
const auto& position = reinterpret_cast<const OvMaths::FVector3&>(vertex.position);
m_boundingSphere.radius = std::max(m_boundingSphere.radius, OvMaths::FVector3::Distance(m_boundingSphere.position, position));
}
}
}
如此,便完成了网格碰撞球的计算。
3.其他函数
剩下的函数与前文提到的一些函数大同小异,都是对一些具体函数进行二次封装,在这里,就不做太多介绍。有一点需要注意的是,其中的一些函数是对IMesh基类中的纯虚函数的重定义,如果Mesh类中没有完成该操作,就会产生错误。
OvRendering::Resources::Mesh::Mesh(const std::vector<Geometry::Vertex>& p_vertices, const std::vector<uint32_t>& p_indices, uint32_t p_materialIndex) :
m_vertexCount(static_cast<uint32_t>(p_vertices.size())),
m_indicesCount(static_cast<uint32_t>(p_indices.size())),
m_materialIndex(p_materialIndex)
{
CreateBuffers(p_vertices, p_indices);
ComputeBoundingSphere(p_vertices);
}
void OvRendering::Resources::Mesh::Bind()
{
m_vertexArray.Bind();
}
void OvRendering::Resources::Mesh::Unbind()
{
m_vertexArray.Unbind();
}
uint32_t OvRendering::Resources::Mesh::GetVertexCount()
{
return m_vertexCount;
}
uint32_t OvRendering::Resources::Mesh::GetIndexCount()
{
return m_indicesCount;
}
uint32_t OvRendering::Resources::Mesh::GetMaterialIndex() const
{
return m_materialIndex;
}
const OvRendering::Geometry::BoundingSphere& OvRendering::Resources::Mesh::GetBoundingSphere() const
{
return m_boundingSphere;
}
|