GLSL shader: Interpolate between more than two textures

T_01 picture T_01 · Jun 22, 2014 · Viewed 12.9k times · Source

I've implemented a heightmap in OpenGL. For now it is just a sine/cosine curved terrain. At the moment I am interpolating between the white "ice" and the darker "stone" texture. This is done like this:

color = mix(texture2D(ice_layer_tex, texcoord), texture2D(stone_layer_tex, texcoord), (vertex.y + amplitude) / (amplitude * 2))

The result:

from top

from bottom

It works fine, but what could I do if I want to add more textures, for example a grass texture, so that the interpolation order is "ice, stone, grass"? I think, there isn't a function like mix(sampler2D[], percentages[])? How could I write a GLSL method following this logic?

Answer

Reto Koradi picture Reto Koradi · Jun 23, 2014

mix() is really just a convenience function for something you can easily write yourself. The definition is:

mix(v1, v2, a) = v1 * (1 - a) + v2 * a

Or putting it differently, it calculates a weighted average of v1 and v2, with two weights w1 and w2 that are float values between 0.0 and 1.0 meeting the constraint w1 + w2 = 1.0:

v1 * w1 + v2 * w2

You can directly generalize this to calculate a weighted average of more than 2 inputs. For example, for 3 inputs v1, v2 and v3, you would use 3 weights w1, w2 and v3 meeting the constraint w1 + w2 + w3 = 1.0, and calculate the weighted average as:

v1 * w1 + v2 * w2 + v3 * w3

For your example, determine the weights you want to use for each of the 3 textures, and then use something like:

weightIce = ...;
weightStone = ...;
weightGrass = 1.0 - weightIce - weightStone;
color = texture2D(ice_layer_tex, texcoord) * weightIce +
        texture2D(stone_layer_tex, texcoord) * weightStone +
        texture2D(grass_layer_tex, texcoord) * weightGrass;