Advertisement

Interpolating between 3 textures in GLSL using height

Started by November 23, 2023 09:03 PM
4 comments, last by snoken 1 year, 1 month ago

I'm working on some simple terrain texturing, and I am quite confused on how you go about interpolating between 3 different textures for a terrain, where you'd interpolate based on the height value of the terrain. I'm also confused about normals for these textures. Each texture has a normal map, so you have 3 normal maps read into the shader. How do you decide which normal to use for the lighting, do you interpolate between the 3 normals as well?

This is what im currently doing. I am interpolating between 2 textures and I am unsure how to interpolate between three and also have lighting work based on each textures normals.

vec3 grasNormalmap = texture(grassNormalMap, texcoords).rgb;
vec3 rocksNormalmap = texture(rocksNormalMap, texcoords).rgb;
vec3 snowNormalmap = texture(snowNormalMap, texcoords).rgb;

//surface normal being used currently but would like to use normal for each texture
// when doing lighting
vec3 N = normalize(normal); 

// Ambient lighting
vec3 ambient = vec3(0.2, 0.2, 0.2) * rockTextureColor;

// Diffuse lighting
vec3 diffuseAmount = vec3(0.5, 0.5, 0.5);
float diff_dot_product = max(dot(N, LightDirection), 0.0);
vec3 diffuse = diffuseAmount * diff_dot_product * rockTextureColor;

float grass_height = 1.f;
float rocky_height = 2.0f;
float mountain_snow_height = 3.0f;
// heightValue is from height map
float t = (heightValue - rocky_height) / (grass_height - rocky_height);
color = mix(rockTextureColor, grassTextureColor, t);

The first thing you should do is sample the normal maps properly so that the resulting values are in the [-1,1] range, instead of [0,1]:

vec3 sampleNormal( in sampler2D map, in vec2 uv )
{
    return texture( map, uv ).rgb * 2.0 - 1.0;
}

The second thing you need is to determine the TBN matrix that rotates from tangent space to shading space. You will need to calculate tangent and binormal vectors in addition to normals.

// lerpTangent, lerpBinormal, lerpNormal come from vertex shader
mat3 tbn = mat3( normalize( lerpTangent ), normalize( lerpBinormal ), lerpNormal );

Then you can use those things to sample and interpolate the maps:

// Determine blending weights based on height
// These should sum to 1.
vec3 blendWeights = vec3( 0.0f );
if ( height < grassHeight )
	blendWeights.x = 1.0f;
else if ( height < rockHeight )
{
	blendWeights.y = (height - grassHeight)/(rockHeight - grassHeight);
	blendWeights.x = 1.0f - blendWeights.y;
}
else if ( height < snowHeight )
{
	blendWeights.z = (height - rockHeight)/(snowHeight - rockHeight);
	blendWeights.y = 1.0f - blendWeights.z;
}
else
{
	blendWeights.z = 1.0f;
}

// Sample normal maps.
vec3 n0 = sampleNormal( normalMap0, uv );
vec3 n1 = sampleNormal( normalMap1, uv );
vec3 n2 = sampleNormal( normalMap2, uv );

// Blend tangent space normals (naive blending).
vec3 blendedNormal = normalize( blendWeights.x * n0 + blendWeights.y * n1 + blendWeights.z * n2 );

// Rotate from local to shading space.
vec3 shadingNormal = normalize( normalMatrix * (tbn * blendedNormal) );

// Sample color maps.
vec3 c0 = texture( colorMap0, uv ).rgb;
vec3 c1 = texture( colorMap1, uv ).rgb;
vec3 c2 = texture( colorMap2, uv ).rgb;

vec3 blendedColor = blendWeights.x * c0 + blendWeights.y * c1 + blendWeights.z * c2;

// Do lighting

The “normalMatrix” there is the transpose of the inverse of the upper left 3x3 of the model matrix. I blended the normals using a basic method, which works OK most of the time. There are better methods for blending between just 2 normals:

// "whiteout" normal blending
vec3 blendNormals( in vec3 n1, in vec3 n2, in float lerp )
{
    float n1z = mix( n1.z, 1.0, 2.0*max( lerp - 0.5, 0.0 ) );
    float n2z = mix( 1.0, n2.z, min( 2.0*lerp, 1.0 ) );
    return normalize( vec3( mix( n1.xy, n2.xy, lerp ), n1z*n2z ) );
}
Advertisement

snoken said:
This is what im currently doing. I am interpolating between 2 textures and I am unsure how to interpolate between three and also have lighting work based on each textures normals.

Your code makes no sense because you mix one lit with other unlit textures. In general there are two options:
1. Mix all materials together, and then calculate lighting for the mixed result. (Aresseras post)
2. Apply lighting to each material, and mix the lit results from that. (Likely more expensive for no visual win, but there can be reasons)

snoken said:
How do you decide which normal to use for the lighting, do you interpolate between the 3 normals as well?

Usually yes. So you was thinking in the right direction. You could have tried it. If something makes sense, it usually works. ; )

But if you look for some ‘correct’ method, or you want to find some common standard, look for blogs from expert devs.
They share knowledge about all those devils in the details:

https://blog.selfshadow.com/publications/blending-in-detail/

Edit: hehe, seems i'm a bit late : )

Thanks for this detailed post. I will give this a go!

Thank you for the reply and giving an helpful and insightful explanation!

This topic is closed to new replies.

Advertisement