shader Instancing: Unterschied zwischen den Versionen
(→Was ist Instancing?) |
K (→Was ist Instancing?) |
||
(2 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 17: | Zeile 17: | ||
== Was ist Instancing? == | == Was ist Instancing? == | ||
− | Dieses Tutorial befasst sich mit dem Thema Instancing und der Extension [[GL_ARB_draw_instanced]]. Ein häufiger Anwendungsfall ist das rendern vieler gleichartiger Objekte. Hier im Beispiel geht es um das rendern von Vegetation, es gibt aber auch viele weitere Anwendungen. Man könnte die Technik beispielsweise auch für Billboards einsetzen. Die Voraussetzung für Instancing ist, dass sich die zu renderden Objekte die selben Vertex-Daten teilen. Die Primitiv-Anzahl und der Primitiv-Typ müssen identisch sein. Die Extension beschleunigt derartige Anwendungen, in dem die Anzahl der API-Aufrufe und die Menge redundanter Daten reduziert wird. | + | Dieses Tutorial befasst sich mit dem Thema [[Instancing]] und der Extension [[GL_ARB_draw_instanced]]. Ein häufiger Anwendungsfall ist das rendern vieler gleichartiger Objekte. Hier im Beispiel geht es um das rendern von Vegetation, es gibt aber auch viele weitere Anwendungen. Man könnte die Technik beispielsweise auch für Billboards einsetzen. Die Voraussetzung für Instancing ist, dass sich die zu renderden Objekte die selben Vertex-Daten teilen. Die Primitiv-Anzahl und der Primitiv-Typ müssen identisch sein. Die Extension beschleunigt derartige Anwendungen, in dem die Anzahl der API-Aufrufe und die Menge redundanter Daten reduziert wird. |
== Voraussetzungen == | == Voraussetzungen == | ||
Zeile 29: | Zeile 29: | ||
== Funktionsweise == | == Funktionsweise == | ||
− | Im Beispiel werden insgesamt 1600 über das Terrain verteilte Büsche gerendert. Der Busch liegt als [[VBO]] mit [[Indices]] vor. Statt nun den Busch mit einer Schleife und | + | Im Beispiel werden insgesamt 1600 über das Terrain verteilte Büsche gerendert. Der Busch liegt als [[VBO]] mit [[Indices]] vor. Statt nun den Busch mit einer Schleife und [[glDrawElements]] zu zeichnen, wird Instancing verwendet. Statt 1600 Aufrufen von <tt>glDrawElements</tt>, wird nur ein einziger Aufruf von [[glDrawElementsInstancedARB]] benötigt. |
Jedes Objekt benötigt eine eigene ModelView-Matrix. Diese Matrix wird im Vertexshader erzeugt. Ein Texturbuffer-Objekt (TBO) liefert mit 4 Floats pro Objekt Daten zur Position und Skalierung. Natürlich kann man hier auch eine Rotation etc. einbauen, alles nur eine Frage der Komplexität. | Jedes Objekt benötigt eine eigene ModelView-Matrix. Diese Matrix wird im Vertexshader erzeugt. Ein Texturbuffer-Objekt (TBO) liefert mit 4 Floats pro Objekt Daten zur Position und Skalierung. Natürlich kann man hier auch eine Rotation etc. einbauen, alles nur eine Frage der Komplexität. | ||
Zeile 68: | Zeile 68: | ||
position.y = getTerrainHeight(position.x, position.z); | position.y = getTerrainHeight(position.x, position.z); | ||
− | // | + | // Positionsdaten schreiben |
data[pos++] = position.x; | data[pos++] = position.x; | ||
data[pos++] = position.y; | data[pos++] = position.y; | ||
data[pos++] = position.z; | data[pos++] = position.z; | ||
− | data[pos++] = 1.3f + ((color - 100) / 100.0f) * (rnd() + 2.0f); | + | |
+ | // Skalierung abhängig von Steigung des Terrains und etwas Zufall. | ||
+ | data[pos++] = 1.3f + ((color - 100) / 100.0f) * (rnd() + 2.0f); | ||
+ | |||
++i; | ++i; | ||
} | } |
Aktuelle Version vom 9. Januar 2010, 17:54 Uhr
Inhaltsverzeichnis
Shadername
Zurück zur Shadersammlung
Beschreibung | Autor | Version |
---|---|---|
Benutzung von GL_ARB_draw_instanced | Coolcat | 1.0 |
Bilder
Was ist Instancing?
Dieses Tutorial befasst sich mit dem Thema Instancing und der Extension GL_ARB_draw_instanced. Ein häufiger Anwendungsfall ist das rendern vieler gleichartiger Objekte. Hier im Beispiel geht es um das rendern von Vegetation, es gibt aber auch viele weitere Anwendungen. Man könnte die Technik beispielsweise auch für Billboards einsetzen. Die Voraussetzung für Instancing ist, dass sich die zu renderden Objekte die selben Vertex-Daten teilen. Die Primitiv-Anzahl und der Primitiv-Typ müssen identisch sein. Die Extension beschleunigt derartige Anwendungen, in dem die Anzahl der API-Aufrufe und die Menge redundanter Daten reduziert wird.
Voraussetzungen
Instancing erfordert relativ neue Grafikhardware (ca. ab Geforce 8), man muss sich also überlegen, ob man diese Technik wirklich einsetzen möchte. Insbesondere beim rendern von hunderten identischer Objekte mit nur wenigen Polygonen bringt Instancing jedoch einen massiven Performance-Schub.
Für dieses Beispiel werden die folgenden Extensions benötigt:
- GL_ARB_draw_instanced (klar...)
- GL_EXT_gpu_shader4 (für TBOs)
- GL_EXT_texture_buffer_object (für TBOs)
- GL_ARB_texture_float (32bit Float Texturen)
Funktionsweise
Im Beispiel werden insgesamt 1600 über das Terrain verteilte Büsche gerendert. Der Busch liegt als VBO mit Indices vor. Statt nun den Busch mit einer Schleife und glDrawElements zu zeichnen, wird Instancing verwendet. Statt 1600 Aufrufen von glDrawElements, wird nur ein einziger Aufruf von glDrawElementsInstancedARB benötigt.
Jedes Objekt benötigt eine eigene ModelView-Matrix. Diese Matrix wird im Vertexshader erzeugt. Ein Texturbuffer-Objekt (TBO) liefert mit 4 Floats pro Objekt Daten zur Position und Skalierung. Natürlich kann man hier auch eine Rotation etc. einbauen, alles nur eine Frage der Komplexität.
Statt einem TBO kann man auch eine gewöhnliche Textur verwenden. Ein TBO eignet sich aber besser, da die Grafikkarte keine Texel-Interpolation vornimmt, sondern einfach nur einen Array-Zugriff mit Integer-Koordinaten.
Code
Initialisierung
Hier wird ein Vertexbuffer-Objekt erzeugt, welches Daten zur Position und Skalierung einer jeden Pflanze enthält. Auf dieses VBO wird später im Shader als Texturbuffer-Objekt (TBO) zugegriffen.
Ich verwende hier ein Graustufen-Bild welches angibt wo Pflanzen gesetzt werden dürfen. Dieses Bild hat an den Stellen eine helle Farbe an denen das Terrain flach ist. Steile Stellen sind dunkel.
Dieses Vertexbuffer-Objekt könnte man auch in jedem Frame neu mit Daten füllen. Es wäre also auch möglich einen Sichtbarkeitstest durchzuführen. Natürlich muss man dabei insbesondere bei sehr kleinen Objekten abwägen, ob es nicht schneller ist einfach alles zu rendern.
plantCount = 1600; // insgesamt werden 1600 Pflanzen erzeugt
// create buffer object
glGenTextures(1, &m_tboTransform);
glGenBuffers(1, &m_vboTransform);
unsigned int data_size = plantCount*4*sizeof(float);
glBindBuffer(GL_ARRAY_BUFFER, m_vboTransform);
glBufferData(GL_ARRAY_BUFFER, data_size, NULL, GL_STATIC_DRAW);
float* data = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
int pos=0;
unsigned int i = 0;
while (i<plantCount) {
// zufällige Position ausdenken
CVector3f position = rndVec() * 204.0f;
// Darf hier eine Pflanze hingesetzt werden?
CVector2f lookup;
lookup.x = (position.x - TERRAIN_XSHIFT) / TERRAIN_SIZE;
lookup.y = (position.z - TERRAIN_ZSHIFT) / TERRAIN_SIZE;
int color = getColor(image, lookup.x, lookup.y);
if (color < 100) { continue; }
// Terrainhöhe bzw. y-Koordinate ermitteln
position.y = getTerrainHeight(position.x, position.z);
// Positionsdaten schreiben
data[pos++] = position.x;
data[pos++] = position.y;
data[pos++] = position.z;
// Skalierung abhängig von Steigung des Terrains und etwas Zufall.
data[pos++] = 1.3f + ((color - 100) / 100.0f) * (rnd() + 2.0f);
++i;
}
glUnmapBuffer(GL_ARRAY_BUFFER);
m_prgInstancing = 0;
loadShaders(); // <--- sollte ja hoffentlich klar sein...
glUseProgram(m_prgInstancing);
glUniform1i(glGetUniformLocation(m_prgInstancing, "texLayer0"), 0);
glUniform1i(glGetUniformLocation(m_prgInstancing, "tboTransform"), 1);
Rendern
Der folgende Code rendert alle Objekte mit einem einzigen Aufruf von glDrawElementsInstancedARB. Diverse Einstellungen bezüglich Z-Buffer und Blending habe ich hier ausgelassen, da sie nichts mit dem eigentlichen Instancing zu tun haben.
glUseProgram(m_prgInstancing);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_pMesh->getIBO());
glBindBuffer(GL_ARRAY_BUFFER, m_pMesh->getVBO());
glInterleavedArrays(GL_T2F_N3F_V3F, 0, 0);
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_pMesh->getTex());
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_BUFFER_EXT, m_tboTransform);
glTexBufferEXT(GL_TEXTURE_BUFFER_EXT,GL_RGBA32F_ARB, m_vboTransform);
glDrawElementsInstancedARB(GL_TRIANGLES, m_pMesh->getFaceCount()*3, GL_UNSIGNED_INT, 0, plantCount);
Vertexshader
Hier versteckt sich der Trick. Der Shader liest mit Hilfe von gl_InstanceID Informationen zur Position und Skalierung des aktuell gerenderten Objektes aus dem Textur-Buffer-Objekt tboTransform und wendet diese auf gl_Vertex an.
#extension GL_EXT_gpu_shader4 : require
#extension GL_ARB_draw_instanced : require
uniform samplerBuffer tboTransform;
varying vec3 normal;
varying vec3 position;
void main()
{
vec4 transform = texelFetchBuffer(tboTransform, gl_InstanceID);
vec4 vertex = vec4((gl_Vertex.xyz * transform.w + transform.xyz),gl_Vertex.w);
normal = normalize(gl_NormalMatrix * gl_Normal);
position = vec3(gl_ModelViewMatrix * vertex);
gl_Position = gl_ModelViewProjectionMatrix * vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
}
Fragmentshader
Der Fragmentshader ist relativ langweilig. Es handelt sich um einen einfachen Per-Pixel-Lighting Shader.
uniform sampler2D texLayer0;
varying vec3 normal;
varying vec3 position;
void main()
{
gl_FragColor = gl_FrontLightProduct[0].ambient;
float diffuse = max(dot(gl_LightSource[0].position.xyz, normal), 0.0);
gl_FragColor += vec4(diffuse,diffuse,diffuse,1);
vec4 texcolor = texture2D(texLayer0, gl_TexCoord[0].xy);
gl_FragColor *= texcolor;
if (diffuse > 0.0) {
vec3 r = normalize(
(2.0 * dot(normal, gl_LightSource[0].position.xyz) * normal)
- gl_LightSource[0].position.xyz
);
float f = dot(r, normalize(-position));
float specular = pow(max(f, 0.0), gl_FrontMaterial.shininess);
gl_FragColor += gl_FrontLightProduct[0].specular * specular;
}
gl_FragColor.a = texcolor.a;
}