shader Instancing

Aus DGL Wiki
Version vom 13. August 2009, 14:46 Uhr von Coolcat (Diskussion | Beiträge) ((unfertig, erstmal speichern....später mehr...))

(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche

Shadername

Zurück zur Shadersammlung

Beschreibung Autor Version
Benutzung von GL_ARB_draw_instanced Coolcat 1.0

Bilder

Terrain mit per Instancing gerenderten Pflanzen.

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