OpenGL VAO Best Practices

78

Ich stehe vor einem Problem, von dem ich glaube, dass es VAO-abhängig ist, aber ich bin mir nicht sicher.

Ich bin mir nicht sicher über die korrekte Verwendung eines VAO. Was ich während der GL-Initialisierung getan habe, war einfach

glGenVertexArrays(1,&vao)

gefolgt von einem

glBindVertexArray(vao)

und später habe ich in meiner Zeichnungspipeline nur glBindBuffer (), glVertexAttribPointer (), glEnableVertexAttribArray () usw. aufgerufen, ohne mich um das ursprünglich gebundene VAO zu kümmern

Ist das eine richtige Praxis?

user815129
quelle
31
Ich bin nicht sicher, warum diese Frage eine -1 bekam .. es zeigt keine Anstrengung und.oder ist unklar? Ich habe 3 Tage mit den Spezifikationen, Wikis und Foren gekämpft, bevor ich dies gepostet habe, und habe keinen wirklichen Grund bekommen, warum ich VAO verwenden sollte .. meh ..
user815129
18
Weil für die coolen Brüder alles offensichtlich ist, sobald sie ihre Cut-and-Pasten zur Hand haben, warum sich die Mühe machen, Dinge zu verstehen?
mlvljr

Antworten:

92

VAOs verhalten sich in Bezug auf ihre Bindung ähnlich wie VBOs und Texturen. Wenn ein einzelner VAO über die gesamte Länge Ihres Programms gebunden ist, ergeben sich keine Leistungsvorteile, da Sie möglicherweise auch nur ohne VAOs rendern. Tatsächlich kann es langsamer sein, je nachdem, wie die Implementierung die Einstellungen der Scheitelpunktattribute beim Zeichnen abfängt.

Der Zweck eines VAO besteht darin, alle Methoden auszuführen, die zum einmaligen Zeichnen eines Objekts während der Initialisierung erforderlich sind, und den gesamten zusätzlichen Aufwand für Methodenaufrufe während der Hauptschleife zu reduzieren. Es geht darum, mehrere VAOs zu haben und beim Zeichnen zwischen ihnen zu wechseln.

In Bezug auf bewährte Methoden sollten Sie Ihren Code folgendermaßen organisieren:

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

Dies vermeidet das Durcheinander von Bindungs- / Entbindungspuffern und das Übergeben aller Einstellungen für jedes Scheitelpunktattribut und ersetzt es durch nur einen einzigen Methodenaufruf, der eine VAO bindet.

Robert Rouhani
quelle
1
Ist es wirklich notwendig, die VAO zu lösen? Ich habe verschiedene Praktiken gefunden
user815129
3
Es ist nicht notwendig, aber ich mache es normalerweise für den Fall, dass ein anderes Objekt ohne VAOs rendern möchte (Debug-Zeichnung / Font-Rendering-Bibliothek / etc.), Da dies (soweit ich weiß) die Attributeinstellungen für das aktuell gebundene VAO ersetzen würde .
Robert Rouhani
2
Warum tritt das Aufheben der Bindung nur einmal und außerhalb der Schleife auf? Sollte das Aufheben der Bindung nicht pro Charge erfolgen?
Batbrat
12
Das Binden eines neuen VAO ersetzt das alte VAO. Das Aufheben der Bindung innerhalb der Schleife wäre nur zusätzliche Arbeit.
Robert Rouhani
1
@RobertRouhani Gibt es dann einen Vorteil für diese letzte ungebundene VAO? (Beeinflusst nichts die
Bindung
27

Nein, so verwenden Sie VAO nicht. Sie sollten VAO genauso verwenden wie VBO, Texturen oder Shader. Richten Sie es zuerst ein. Und beim Rendern binden Sie sie nur, ohne sie zu ändern.

Mit VAO machen Sie also Folgendes:

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(..)
}

Siehe auch diese Links:

Mārtiņš Možeiko
quelle
1
Diese Antwort sollte höher sein. Es wurde speziell ein schwer zu findender Fehler von mir behoben.
Abarax
3
glEnableVertexAttribArray(...)sollte vorher angerufen werden glVertexAttribPointer(...). Einige Fahrer (einschließlich meiner) mögen es nicht wirklich umgekehrt.
RecursiveExceptionException
2
Upvoted, da in den meisten Beispielen nicht klar ist, dass Sie während der VAO-Bindung glEnableVertexAttribArray aufrufen MÜSSEN.
Rupert Rawnsley
10

Ist das eine richtige Praxis?

Ja, das ist völlig legal und gültig. Ist es gut? Gut...

Es gab einige informelle Leistungstests für diese Art von Dingen. Und es scheint, zumindest auf NVIDIA-Hardware, auf der dies getestet wurde, dass die "richtige" Verwendung von VAOs (dh: was alle anderen befürworteten) in vielen Fällen tatsächlich langsamer ist. Dies gilt insbesondere dann, wenn durch das Ändern von VAOs nicht geändert wird, welche Puffer gebunden sind.

Meines Wissens nach wurden auf AMD-Hardware keine ähnlichen Leistungstests durchgeführt. Im Allgemeinen ist dies eine akzeptable Verwendung von VAOs, es sei denn, etwas ändert sich mit ihnen.

Nicol Bolas
quelle
2
Sicherlich, sobald jedes VAO genug verschiedene Zustände hat, die es verfolgt, würde das manuelle Umschalten der Puffer und Attribut-Ptrs während der Render-Schleife viele Aufrufe erfordern, und wir würden anfangen, eine verbesserte Leistung durch die Verwendung des VAO zu sehen?
Steven Lu
Leider ist der Link tot.
Nick Caplinger
3

Roberts Antwort oben hat für mich funktioniert, als ich es versuchte. Was es hier wert ist, ist der Code in Go, mehrere Vertex-Attributobjekte zu verwenden:

// 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()

Dann können Sie sie in Ihrer Hauptschleife als solche verwenden:

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)
    }
}

Wenn Sie die vollständige Quelle anzeigen möchten, ist sie als Kern verfügbar und aus den Beispielen in go-gl abgeleitet:

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

Vielen Dank an alle für die ursprünglichen Antworten. Ich hatte dieselbe Frage wie ECrownofFire.

Marek
quelle
3
Sie müssen vao1 nicht lösen, bevor Sie vao2 binden. Es reicht aus, nur vao2 zu binden.
Chaos