shader Instancing
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. Bei Beispiel ist hier sicherlich das rendern von Vegetation, es gibt aber auch viele weitere Anwendungen. 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.
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.
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);
// Daten schreiben.
data[pos++] = position.x;
data[pos++] = position.y;
data[pos++] = position.z;
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;
}