shader Terrain GPU4

Aus DGL Wiki
Wechseln zu: Navigation, Suche

Heightmap-Terrain

Zurück zur Shadersammlung

Beschreibung Autor Version
Shader für ein Heightmap-Terrain, benutzt GL_EXT_gpu_shader4 Coolcat 1.0

Bilder

Terrain mit 3 Texturlayern
Terrain aus Heightmap mit 2048x2048 Auflösung und 16bit Graustufen

Beschreibung

Ein Terrain-Shader. Das Terrain wird in Blöcken von 64x64 gerendert, da man ja sinnvollerweise sowieso einen Quadtree oder ähnliches einsetzt. Als Geometrie wird daher nur ein VBO mit 65x65 minimalen 2D-Vertices (keine Höhe) und ein entsprechender Indexbuffer benötigt. Die 3D-Vertices, Normalen und Texturkoordinaten werden im Vertexshader mit Hilfe der Heightmap-Textur erzeugt. Im Fragmentshader werden drei Texturlayer mit Hilfe einer Alphamap interpoliert.

Ein solcher Shader ist besonders für hochauflösende/große Terrains sinnvoll, da keine redundanten Daten im Speicher der Grafikkarte liegen müssen.

Besondere Voraussetzungen

Ich verwende hier einige Features des ShaderModel 4.0 zwecks Performance-Optimierung, es wird also GL_EXT_gpu_shader4 benötigt. Ich verwende Integer-Arithmetik und die Funktion texelFetch2DOffset für beschleunigten Texturzugriff. texelFetch2D ermöglicht den Texturzugriff über exakte Integer-Koordinaten, was intern die Multiplikation mit der Texturgröße sowie die Interpolation zwischen verschiedenen Texeln unnötig macht. Durch texelFetch2DOffset kann der Compiler multiple Texturzugriffe auf die selbe Textur mittels Cache beschleunigen, sofern der Offset klein und konstant ist.

Es sollte aber möglich sein den Shader auch so umzuschreiben, dass er auch auf älterer Hardware funktioniert.

Info DGL.png Die Aussagen zur Funktionsweise von texelFetch2DOffset basieren auf Vermutungen. Die Spezifikation von GL_EXT_gpu_shader4 macht leider keine Angaben was intern genau passiert. Die Tatsache das der Offset konstant sein muss und begrenzt ist legt dies aber Nahe.

Code

Vertexshader:

#extension GL_EXT_gpu_shader4 : enable

uniform sampler2D heightmap; // heightmap texture
uniform ivec2 leafmin; // minimum position of current block

const int tmin = 1; // minimum border for clamping
const int tmax = 2047; // maximum border for clamping
const float leafsize = 64.0; // size of terrain block
const float terrainsize = 2048.0; // size of complete terrain (heightmap size)
const float terrainscale = 100.0; // terrain height scale

varying vec3 normal;

void main()
{
	float height;
	ivec2 coords = leafmin + ivec2(gl_Vertex.xy);
	if (coords.x < tmin || coords.y < tmin || coords.x > tmax || coords.y > tmax) {
		// clamping borders
		height = 0.0;
		normal = vec3(0,-1,0);
	}
	else {
		//retrieve height
		height = terrainscale * texelFetch2DOffset(heightmap, coords, 0, ivec2(0,0)).a;
	
		//compute normal
		vec3 vector[6];
		vector[0] = vec3( 0.0, texelFetch2DOffset(heightmap, coords, 0, ivec2( 0, -1)).a, -1.0);
		vector[1] = vec3(-1.0, texelFetch2DOffset(heightmap, coords, 0, ivec2(-1, -1)).a, -1.0);
		vector[2] = vec3(-1.0, texelFetch2DOffset(heightmap, coords, 0, ivec2(-1,  0)).a,  0.0);
		vector[3] = vec3( 0.0, texelFetch2DOffset(heightmap, coords, 0, ivec2( 0,  1)).a,  1.0);
		vector[4] = vec3( 1.0, texelFetch2DOffset(heightmap, coords, 0, ivec2( 1,  1)).a,  1.0);
		vector[5] = vec3( 1.0, texelFetch2DOffset(heightmap, coords, 0, ivec2( 1,  0)).a,  0.0);
		for (int i=0; i<6; ++i) {
			vector[i].y = terrainscale * vector[i].y - height;
		}
		normal = cross(vector[5], vector[0]);
		for (int i=1; i<6; ++i) {
			normal += cross(vector[i-1], vector[i]);
		}
		normal = normalize(normal);
	}
	
	// transform position
	vec4 position = vec4(coords.x, height, coords.y, 1.0);
	gl_Position = gl_ModelViewProjectionMatrix * position;

	// generate texture coords
	gl_TexCoord[0].xy = gl_Vertex.xy / leafsize;     // coords for layer texture
	gl_TexCoord[1].xy = vec2(coords) / terrainsize;  // coords for alphamap
}

Fragmentshader:

uniform sampler2D layer0;
uniform sampler2D layer1;
uniform sampler2D layer2;
uniform sampler2D alpha;

varying vec3 normal;

void main()
{
	// compute terrain color
	vec3 tex0 = texture2D(layer0, gl_TexCoord[0].xy).rgb;
	vec3 tex1 = texture2D(layer1, gl_TexCoord[0].xy).rgb;
	vec3 tex2 = texture2D(layer2, gl_TexCoord[0].xy).rgb;
	vec3 a = texture2D(alpha, gl_TexCoord[1].xy).rgb;
	vec3 color = a.r*tex0 + a.g*tex1 + a.b*tex2;

	// compute diffuse lighting (directional lightsource only)
	float diffuse = max(dot(gl_LightSource[0].position.xyz, normal), 0.0);
 	gl_FragColor = vec4(diffuse * color + gl_LightSource[0].ambient.xyz, 1.0);
}