Triplanar terrain texturing with GLSL

By , last updated November 25, 2019

Stretched textures on vertical triangles is one of a million possible bugs when texturing terrain with shaders. To fix it we need to recalculate texture coordinates in 3 projections: x, y and z.

The main algorithm to apply triplanar texturing of a 3D terrain with shaders is as following:

Compute the vertex’s normal vector and check what the larger component of the normal is, out of x, y and z. If x is the larger component, we use the geometry z coordinate as the texture coordinate s, and the geometry y coordinate as the texture coordinate t. If z is the larger component, we use the geometry x coordinate as the texture coordinate s, and the geometry y coordinate as the texture coordinate t.

Here’s how terrain looked like before we used shaders:

Here’s a simple example of GLSL vertex and fragment shaders for texturing terrain in 3D:

Vertex shader

varying float v;
varying float xcoord,ycoord,zcoord;

void main ()
{
	//Texture Coordinates
	xcoord = gl_Vertex.x;
	zcoord = gl_Vertex.z;
	ycoord = gl_Vertex.y ;
	
	// projection1. y is largest normal component
	// so use x and z to sample texture
	gl_TexCoord[0] = vec4(xcoord,zcoord,0,0); //first projection
	// projection2. x is largest normal component
	// so use z and y to sample texture
	gl_TexCoord[1] = vec4(zcoord,ycoord,0,0); //second projection
	// projection3. z is largest normal component
	// so use x and y to sample texture
	gl_TexCoord[2] = vec4(xcoord,ycoord,0,0); //third projection
	
   //gl_Normal is the vertex's normal vector. 
   //We compare it's absolute values with each other to find which projection to use
	float x = abs(gl_Normal.x);
	float y = abs(gl_Normal.y);
	float z = abs(gl_Normal.z);
	if(x > y && x > z)
	{
       //v is a variable used in fragment shader to distinguish textures
		v = 1; 
	}
	if(y > x && y > z)
	{
		v = 0;
	}
	if(z > y && z > x)
	{
		v = 2;
	} 
		
	gl_Position = ftransform();
}

Fragment shader

varying float v;
uniform sampler2D myTexture0;
uniform sampler2D myTexture1;
uniform sampler2D myTexture2;
void main (void)
{
	if ( 0 == v )
	{
		gl_FragColor = texture2D( myTexture0, gl_TexCoord[0].st);
	}
	if ( 1 == v )
	{
		gl_FragColor = texture2D( myTexture1, gl_TexCoord[1].st);
	}
	if ( 2 == v )
	{
		gl_FragColor = texture2D( myTexture2, gl_TexCoord[2].st);
	}
}

Here’s the result (we used 3 textures in shaders to visualize the result):

Senior Software Engineer developing all kinds of stuff.