Texture blending with multiple textures is dealt with in many tutorials, principally in Caitlin Zima’s series of guides here. An issue when using this technique is that is truly does blend two dissimilar textures together, essentially blurring one into the other as shown below.
This is unrealistic. A better approach is documented here . This approach does not blend the values of the pixels by combining their values and dividing by a blend factor, but uses the blend factor to select a pixel from one or the other underlying texture. This means for any one pixel the result comes from one or the other texture but not a blend of the two.
The result can be seen here
This gives a more realistic looking result close up, with either stones or grass showing. In the link to Andrey Mishkinis’ example he provides the renderer with a height map alongside the visible texture and samples the height map of the two textures – the highest pixel wins and gets painted. This requires some complexity (and artistry) in terms of providing extra textures just for determining height. In my example above I side-step the issue to get a slightly less accurate effect but one which does not require an extra texture sample or further artwork.
To get the effect of depth on the texture I simply convert them to grayscale and sample the result – the darkest pixel is deemed to be the deeper, and does not get rendered. Because the conversion to grayscale is arbitrary (I could have chosen the reddest pixel as an alternative) the pixel painted is a bit arbitrary but consistent.
The HLSL to do this is (where groundCoverage and appliqueTexture are the result of a tex2D sample of my two textures.)
float groundCoverageGrayscale = dot(groundCoverage.rgb, float3(0.30, 0.59, 0.11));
float appliqueGrayscale = dot(appliqueTexture.rgb, float3(0.30, 0.59, 0.11));
// Apply applique in preference to ground cover
if (appliqueGrayscale > groundCoverageGrayscale)
textureColor = normalize(appliqueTexture);
bump = appliqueBump;
textureColor = normalize(groundCoverage);
bump = groundCoverageBump;