Lazarus - OpenGL 3.3 Tutorial - Instancing - Instance mit VertexAttribut
Inhaltsverzeichnis
Instancing - Instance mit VertexAttribut
Einleitung
Hier sind sogar 10'000'000 Instancen möglich, gegenüber der Uniform-Variante die bei gut 800 schon Schluss machte.
Bei noch höheren Werten macht der FPC-Compiler Schluss, wieviel das die Grafikkarte vertägt, kann ich nicht sagen.
Das es eine Diashow ist, das ist was anderes.
Die Anzahl Instance
const
InstanceCount = 10000;
Für die Instancen werden VBOs gebraucht.
type
TVB = record
VAO: GLuint;
VBO: record
Vertex,
I_Size, I_Matrix, I_Color: GLuint;
end;
end;
Die Deklaration, der Arrays ist gleich wie bei der Uniform-Übergaben.
var
VBQuad: TVB;
Data: record
Scale: array[0..InstanceCount - 1] of GLfloat;
Matrix: array[0..InstanceCount - 1] of TMatrix;
Color: array[0..InstanceCount - 1] of TVector3f;
end;
VBO-Puffer für Instancen anlegen. Uniformen werden keine gebraucht.
procedure TForm1.CreateScene;
var
i: integer;
begin
Shader := TShader.Create([FileToStr('Vertexshader.glsl'), FileToStr('Fragmentshader.glsl')]);
Shader.UseProgram;
glGenVertexArrays(1, @VBQuad.VAO);
glGenBuffers(4, @VBQuad.VBO);
for i := 0 to Length(Data.Matrix) - 1 do begin
Data.Scale[i] := Random * 2 + 1.0;
Data.Matrix[i].Identity;
Data.Matrix[i].Translate(1.5 - Random * 3.0, 1.5 - Random * 3.0, 0.0);
Data.Color[i] := vec3(Random, Random, Random);
end;
end;
Für die Instancen werden die Puffer gefüllt.
Da für die Puffer nur Vektoren mit 1-4 Elemeten erlaubt sind, muss man die Matrix in 4 Vektoren unterteilen.
Dabei werden auch 4 Attribut-Indexe gebraucht.
Eine glVertexAttribPointer(2, 16,... geht leider nicht. Im Shader kann man es direkt als Matrix deklarieren.
So was geht leider nicht:
glVertexAttribPointer(2, 16, GL_FLOAT, False, 0, nil);
Mit glVertexAttribDivisor(... teilt man mit das es sich um ein Instance-Attribut handelt.
Der erste Parameter bestimmt, um welches Vertex-Attribut es sich handelt.
Der Zweite sagt, das der Zeiger im Vertex-Attribut bei jedem Durchgang um 1 erhöt wird.
Setzt man dort 0 ein, handelt es sich um ein gewöhnliches Attribut.
Was Werte >1 bedeuten ist bei VertexAttribDivisor beschrieben.
procedure TForm1.InitScene;
var
i: integer;
begin
glClearColor(0.6, 0.6, 0.4, 1.0); // Hintergrundfarbe
glBindVertexArray(VBQuad.VAO);
// --- Normale Vektordaten
// Daten für Vektoren
glBindBuffer(GL_ARRAY_BUFFER, VBQuad.VBO.Vertex);
glBufferData(GL_ARRAY_BUFFER, sizeof(Quad), @Quad, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, False, 0, nil);
// --- Instancen
// Instance Size
glBindBuffer(GL_ARRAY_BUFFER, VBQuad.VBO.I_Size);
glBufferData(GL_ARRAY_BUFFER, SizeOf(Data.Scale), @Data.Scale, GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 1, GL_FLOAT, False, 0, nil);
glVertexAttribDivisor(1, 1);
// Instance Matrix
glBindBuffer(GL_ARRAY_BUFFER, VBQuad.VBO.I_Matrix);
glBufferData(GL_ARRAY_BUFFER, SizeOf(Data.Matrix), nil, GL_STATIC_DRAW); // Nur Speicher reservieren
for i := 0 to 3 do begin
glEnableVertexAttribArray(i + 2);
glVertexAttribPointer(i + 2, 4, GL_FLOAT, False, SizeOf(TMatrix), Pointer(i * 16));
glVertexAttribDivisor(i + 2, 1);
end;
// Instance Color
glBindBuffer(GL_ARRAY_BUFFER, VBQuad.VBO.I_Color);
glBufferData(GL_ARRAY_BUFFER, SizeOf(Data.Color), @Data.Color, GL_STATIC_DRAW);
glEnableVertexAttribArray(6);
glVertexAttribPointer(6, 3, GL_FLOAT, False, 0, nil);
glVertexAttribDivisor(6, 1);
end;
Die Instance Parameter werden einfache mit glBufferSubData(.... übergeben.
Es werden nur die Matrizen aktualisiert, die anderen Werte bleiben gleich.
Will man eine andere Anzahl von Instance, dann muss man mit glBufferData(... mehr oder weniger Speicher reservieren.
Dafür braucht man keine Uniformen.
procedure TForm1.ogcDrawScene(Sender: TObject);
begin
glClear(GL_COLOR_BUFFER_BIT);
Shader.UseProgram;
glBindBuffer(GL_ARRAY_BUFFER, VBQuad.VBO.I_Matrix);
glBufferSubData(GL_ARRAY_BUFFER, 0, SizeOf(Data.Matrix), @Data.Matrix);
glBindVertexArray(VBQuad.VAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, Length(Quad) * 3, InstanceCount);
ogc.SwapBuffers;
end;
Matrizen neu berechnen.
procedure TForm1.Timer1Timer(Sender: TObject);
var
i: integer;
begin
for i := 0 to Length(Data.Matrix) - 1 do begin
Data.Matrix[i].RotateC(0.02);
end;
glBindVertexArray(VBQuad.VAO);
ogcDrawScene(Sender); // Neu zeichnen
end;
Vertex-Shader:
Der Shader sieht sehr einfach aus.
#version 330
#define Instance_Count 200
// Vektor-Daten
layout (location = 0) in vec2 inPos;
// Instancen
layout (location = 1) in float Size;
layout (location = 2) in mat4 mat;
layout (location = 6) in vec3 Color;
out vec3 col;
void main(void)
{
gl_Position = mat * vec4((inPos * Size), 0.0, 1.0);
col = Color;
}
Fragment-Shader:
#version 330
out vec4 outColor; // ausgegebene Farbe
in vec3 col;
void main(void)
{
outColor = vec4(col, 1.0);
}
Autor: Mathias
Siehe auch
- Übersichtseite Lazarus - OpenGL 3.3 Tutorial