OpenGL VAO 最佳实践

86

我遇到了一个问题,我认为它是依赖VAO的,但我不确定。

我不太清楚如何正确使用VAO,在GL初始化期间我通常会做简单的以下操作:

glGenVertexArrays(1,&vao)

跟随着一个

glBindVertexArray(vao)

后来,在我的绘图管线中,我只是调用了glBindBuffer()、glVertexAttribPointer()、glEnableVertexAttribArray()等函数...而不关心最初绑定的VAO。

这种做法正确吗?


33
我不确定为什么这个问题会被评为-1分。它似乎没有表现出足够的努力或者不够清晰?在发布这个问题之前,我花了三天时间苦思冥想规格、维基和论坛,但是并没有得到任何真正的理由说明我应该使用VAO..呃.. - user815129
20
因为对于酷兄弟们来说,只要他们有复制和粘贴功能,所有事情都显而易见了,何必费心去理解呢? - mlvljr
4个回答

106

VAO与VBO和纹理类似,它们的绑定方式也相似。在整个程序中仅绑定一个VAO并不会带来任何性能上的好处,因为你可能干脆完全不使用VAO进行渲染。事实上,这样做可能会更慢,具体取决于实现方式拦截顶点属性设置的方式。

使用VAO的目的是在初始化期间运行绘制对象所需的所有方法,并消除主循环中的额外方法调用开销。目的是拥有多个VAO并在绘制时在它们之间切换。

就最佳实践而言,以下是代码组织方式:

initialization:
    for each batch
        generate, store, and bind a VAO
        bind all the buffers needed for a draw call
        unbind the VAO

main loop/whenever you render:
    for each batch
        bind VAO
        glDrawArrays(...); or glDrawElements(...); etc.
    unbind VAO

这样可以避免绑定/解绑缓冲区和传递每个顶点属性的所有设置而带来的混乱,并用单个方法调用替换它,即绑定VAO。


1
有必要解绑VAO吗?我发现不同的做法。 - user815129
3
这并非必须,但我通常这样做,以防某些其他对象在没有VAO(调试绘图/字体渲染库等)的情况下进行渲染,因为据我所知,这会替换当前绑定的VAO的属性设置。 - Robert Rouhani
2
为什么解绑只发生一次并且在循环外?难道不应该按批次进行解绑吗? - batbrat
13
绑定一个新的VAO会替换旧的VAO。在循环内取消绑定只会增加额外的工作量。 - Robert Rouhani
2
@RobertRouhani 那最后一个解绑定 VAO 有什么好处吗?(解绑是否会影响任何东西?) - Mateen Ulhaq
显示剩余3条评论

30
不,那不是你使用VAO的方式。 你应该像使用VBO、纹理或着色器一样使用VAO。首先设置它。在渲染期间只需绑定它们,而无需修改它。
所以使用VAO时,你需要执行以下操作:
void Setup() {
    glGenVertexArrays(..);
    glBindVertexArray(..);
    // now setup all your VertexAttribPointers that will be bound to this VAO
   glBindBuffer(..);
   glVertexAttribPointer(..);
   glEnableVertexAttribArray(..);
}

void Render() {
    glBindVertexArray(vao);
    // that's it, now call one of glDraw... functions
    // no need to set up vertex attrib pointers and buffers!
    glDrawXYZ(..)
}

以下是相关链接:


7
هœ¨è°ƒç”¨glVertexAttribPointer(...)ن¹‹ه‰چه؛”该ه…ˆè°ƒç”¨glEnableVertexAttribArray(...)م€‚ن¸€ن؛›é©±هٹ¨ç¨‹ه؛ڈ(هŒ…و‹¬وˆ‘çڑ„)é‌‍ه¸¸ن¸چه–œو¬¢è؟™ن¸¤ن¸ھه‡½و•°çڑ„调用é،؛ه؛ڈ颠ه€’م€‚ - RecursiveExceptionException
2
点赞,因为大多数示例并不清楚在绑定VAO期间必须调用glEnableVertexAttribArray。 - Rupert Rawnsley

11

这是正确的做法吗?

是的,这是完全合法且有效的。好吗?嗯…

已经有一些非正式的性能测试针对这种情况进行了。至少在测试了NVIDIA硬件的情况下,“正确”使用VAOs(即其他人都提倡的方式)在许多情况下实际上是更慢的。特别是如果更改VAOs不会更改绑定的缓冲区。

据我所知,在AMD硬件上尚未进行类似的性能测试。一般来说,除非有变化,否则这是VAOs的可接受用途。


2
当每个VAO跟踪足够多的不同状态时,手动在渲染循环中切换缓冲区和属性指针将需要许多调用,我们是否会开始从使用VAO中看到性能提升? - Steven Lu
不幸的是,链接已失效。 - Nick Caplinger

3

当我尝试时,Robert上面的答案对我有用。以下是使用多个顶点属性对象的Go代码:

// VAO 1

vao1 := gl.GenVertexArray()
vao1.Bind()

vbo1 := gl.GenBuffer()
vbo1.Bind(gl.ARRAY_BUFFER)

verticies1 := []float32{0, 0, 0, 0, 1, 0, 1, 1, 0}
gl.BufferData(gl.ARRAY_BUFFER, len(verticies1)*4, verticies1, gl.STATIC_DRAW)

pa1 := program.GetAttribLocation("position")
pa1.AttribPointer(3, gl.FLOAT, false, 0, nil)
pa1.EnableArray()
defer pa1.DisableArray()

vao1.Unbind()

// VAO 2

vao2 := gl.GenVertexArray()
vao2.Bind()

vbo2 := gl.GenBuffer()
vbo2.Bind(gl.ARRAY_BUFFER)

verticies2 := []float32{-1, -1, 0, -1, 0, 0, 0, 0, 0}
gl.BufferData(gl.ARRAY_BUFFER, len(verticies2)*4, verticies2, gl.STATIC_DRAW)

pa2 := program.GetAttribLocation("position")
pa2.AttribPointer(3, gl.FLOAT, false, 0, nil)
pa2.EnableArray()
defer pa2.DisableArray()

vao2.Unbind()

然后在你的主循环中,你可以这样使用它们:

for !window.ShouldClose() {
    gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

    vao1.Bind()
    gl.DrawArrays(gl.TRIANGLES, 0, 3)
    vao1.Unbind()

    vao2.Bind()
    gl.DrawArrays(gl.TRIANGLES, 0, 3)
    vao2.Unbind()

    window.SwapBuffers()
    glfw.PollEvents()

    if window.GetKey(glfw.KeyEscape) == glfw.Press {
        window.SetShouldClose(true)
    }
}

如果您想查看完整的源代码,它作为一个Gist可用,并且是从go-gl的示例派生而来的。

https://gist.github.com/mdmarek/0f73890ae2547cdba3a7

感谢大家提供的原始答案,我和ECrownofFire有同样的问题。

3
在绑定vao2之前不需要解绑vao1,只需直接绑定vao2即可。 - Student Loan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接