OpenGL -正确的方式来加载多面

OpenGL - Correct way to load multi faces

本文关键字:加载 方式 OpenGL      更新时间:2023-10-16

如果我有一个立方体模型(立方体有6个面)。我怎样用vbo来画每张脸呢?我需要调用glDrawElements 6次吗?还是有别的函数可以一次画出来?通常我是这样画的:

for (int i = 0; i < facesNum; i++)
    glDrawElements(GL_TRIANGLE_FAN, 4 + i*4, GL_UNSIGNED_INT, (GLvoid*)(i*4));

这是最好的方法吗?

你可以使用原始重启(OpenGL 3.1+)来重新启动一个原语,如三角形风扇,而渲染,就好像你启动了另一个glDraw*命令。

使用glEnable(GL_PRIMITIVE_RESTART)使能,然后使用glPrimitiveRestartIndex(restartIndex)设置索引(如0xFFFF)用于重启。然后,当OpenGL遇到重启索引时,它将停止当前绘制的原语并开始另一个原语。

这允许你用一个索引缓冲区和绘制命令绘制多个三角形带、扇形带、线环或带。只需在每个原语的索引数据之间添加restart索引。

一般来说,你要做的是将你的对象绘制为GL_TRIANGLES而不是GL_TRIANGLE_FAN,这允许你只绘制所有12个三角形(6个面*每个面2个三角形)通过一次调用glDrawElements

要做到这一点,您当然必须重新安排索引缓冲区,以包含每个三角形顶点的信息。这意味着你必须复制一些索引,但这应该不是一个问题,因为索引缓冲区的点正是能够做到这一点,而不是复制顶点。

假设你的顶面由顶点索引0、1、2、3按逆时针顺序组成,例如,您可以将索引缓冲区的那一部分从0,1,2,3更改为0,1,2,0,2,3

修改了索引缓冲区的设置后,只需调用
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, NULL);

(36,因为我们为立方体绘制了12个三角形,每个三角形有3个顶点)

你应该只用一个VBO调用DrawElements一次。有一些替代方案,比如IBO,但我将提供一个VBO的示例,因为这是您所要求的。理想情况下,您应该将所有顶点存储在保存顶点对象的数组或std::vector中。将所有立方体面顶点放入这个数组或std::vector中。您不需要每个面都有一个数组/向量。您将希望保持数据紧密紧凑,并避免使用virtual关键字,因为它创建了一个非暴露的指针(vtptr),这会增加类的内存占用。你想要保持数据紧凑的原因是当你把它发送给OpenGL时,它会期望步长。如果您对类的内存占用有疑问,那么使用sizeof(ClassName)函数进行快速输出是没有害处的。

你的顶点类看起来像这样:

class Vertex {
public:
    ~Vertex() {}
    Vertex() {} 
    Vector3<float>          vertexPosition;
    Vector4<float>          vertexColor;
    Vector2<float>          vertexTextureCoords;
    Vector3<float>          vertexNormal;
    Vector3<float>          vertexTangent;
    Vector3<float>          vertexBitangent;
    Vector4<int>            vertexBoneIndexes;
    Vector4<float>          vertexBoneWeights;
};

其中所有的vector类都是非虚的,唯一的数据成员是vector组件。

以下是我遵循的步骤:
  • 创建顶点对象的std::向量(或数组)并填充顶点数据
  • 用适当的OpenGL调用生成VBO(这是我的引擎的一个例子)。我使用一个具有包装函数的单例,以便与图形API无关。参数的顺序与OpenGL期望的VBO生成函数的顺序完全相同。

    cbengine::CBRenderer * sharedRenderer = cbengine::CBRenderer::sharedCBRenderer();
    sharedRenderer->generateVBOBuffers( 1, &entityToCreate->m_VBOBufferID );
    sharedRenderer->bindVBOBuffer( entityToCreate->m_VBOBufferID );
    sharedRenderer->bufferDataForVBO( ( sizeof( cbengine::Vertex ) * entityToCreate-   >m_verts.size() ), &entityToCreate->m_verts.front() );
    
  • 在你的渲染函数中进行以下OpenGL调用。这就是为什么我建议保持数据紧凑的原因。OpenGL需要知道预期的数据类型、数据类型的大小以及从数据开始的偏移量。

    glEnableVertexAttribArray( m_vertexLocation );
    glVertexAttribPointer( m_vertexLocation, 3, GL_FLOAT, false, sizeof( cbengine::Vertex ), (float*) offsetof( cbengine::Vertex, vertexPosition ) ); 
    glEnableVertexAttribArray( m_colorLocation );
    glVertexAttribPointer( m_colorLocation, 4, GL_FLOAT, false, sizeof( cbengine::Vertex ), (float*) offsetof( cbengine::Vertex, vertexColor ) );
    glEnableVertexAttribArray( m_diffuseTextureCoordLocation );
    glVertexAttribPointer( m_diffuseTextureCoordLocation, 2, GL_FLOAT, false, sizeof( cbengine::Vertex ), (float*) offsetof( cbengine::Vertex, vertexTextureCoords ) );
    glEnableVertexAttribArray( m_normalCoordLocation );
    glVertexAttribPointer( m_normalCoordLocation, 3, GL_FLOAT, false, sizeof( cbengine::Vertex ), (float*) offsetof( cbengine::Vertex, vertexNormal ) );
    glEnableVertexAttribArray( m_tangentLocation );
    glVertexAttribPointer( m_tangentLocation, 3, GL_FLOAT, false, sizeof( cbengine::Vertex ), (float*) offsetof( cbengine::Vertex, vertexTangent ) );
    glEnableVertexAttribArray( m_bitangentLocation );
    glVertexAttribPointer( m_bitangentLocation, 3, GL_FLOAT, false, sizeof( cbengine::Vertex ), (float*) offsetof( cbengine::Vertex, vertexBitangent ) );
    glEnableVertexAttribArray( m_boneIndexesLocation ); // Apparently GL_INT causes issues
    glVertexAttribPointer( m_boneIndexesLocation, 4, GL_FLOAT, false, sizeof( cbengine::Vertex ), (float*) offsetof( cbengine::Vertex, vertexBoneIndexes ) );
    glEnableVertexAttribArray( m_boneWeightsLocation );
    glVertexAttribPointer( m_boneWeightsLocation, 4, GL_FLOAT, false, sizeof( cbengine::Vertex ), (float*) offsetof( cbengine::Vertex, vertexBoneWeights ) );
    
  • 最后,调用DrawArrays

    sharedRenderer->drawArrays( drawPrim, 0, verts.size() );
    

这是如何完成你想要的一个非常快速的例子。其他要记住的事情是选择的缠绕顺序,启用/禁用纹理,以及您可能想要发送给着色器的自定义顶点属性数据。如果你的引擎不支持骨骼动画,你可能不希望包含骨骼权重和骨骼索引向量。