shader Terrain GPU4
Inhaltsverzeichnis
Heightmap-Terrain
Zurück zur Shadersammlung
Beschreibung | Autor | Version |
---|---|---|
Shader für ein Heightmap-Terrain, benutzt GL_EXT_gpu_shader4 | Coolcat | 1.0 |
Bilder
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.
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);
}