GLSL shader: Interpolate between more than two textures
Asked Answered
R

3

9

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?

Relique answered 22/6, 2014 at 21:51 Comment(0)
N
31

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;
Nibelung answered 23/6, 2014 at 4:2 Comment(2)
Hi, I know this an old post but still can you please mention how to calculate weights for N values?Cygnus
You say "meeting the constraint w1 + w2 = 1.0", but the GLSL v4.6 spec (§8.3, p148) doesn't mention that as a requirement. I don't mean to be pedantic though, it's an excellent explanation of the function.Intoxicated
V
3

The other answers have already provided solutions for the genralized mix() function you asked for. But I'd recommend using a different approach, since you explicitely wrote about an "interpolation order (ice, stone, grass)". In that case, you don't need arbitrary weights for each element, you only mix neighboring ones, like ice+stone or stone+grass, but never ice+grass or ice+stone+grass. If that is the case, you can simply use 3D textures and use (tri)linear filtering. Just use each of your 2D texture as a slice in the 3D texture. The first two texcoords can stay as they are, and the third can be directly used to select an arbitrary blending between two neighboring slices. Since texcoords are always in the range [0,1], you just have to map your range to that interval. The "center" of the i-th slice will lie at

p=i/num_layers + 1/(2*num_layers)

Say you have those 3 slices for ice, stone and grass. So you get

0/3+1/6 = 0.16667       100% ice  
1/3+1/6 = 0.5           100% stone
2/3+1/6 = 0.83333       100% grass

and arbirtrary linear blends between neighboring layers just inbetween, like

1/3 = 0.3333            50% ice + 50% stone  
      0.6               70% stone  + 30% grass
...
Vermiculite answered 23/6, 2014 at 16:42 Comment(2)
huh, i dont know how to implement or use 3d textures... to lazy to googel, couldt you post a bit example code? ^^ It does'nt have to contain much explanation but.. yeah.. :DRelique
@Relique You're never going to get answers or help of any kind from the Stack Overflow community if you're “to lazy to googel”. The SO community expects that you search Google first & foremost, and second search SO for existing answers, and finally — after you've done the first two — ask on SO. Diligence is a virtue around here (and in programming as a whole), and laziness will not get you very far with the experts on SO or in your programming career as a whole.Vanhoose
S
2

No, according to the GLSL documentation for mix() there are only overloads for interpolation between two parameters.

Would it be acceptable to you to just interpolate "ice" and "stone" then mix the result with the "grass" texture?

vec4 ice_color   = texture2D(ice_layer_tex,   texcoord);
vec4 stone_color = texture2D(stone_layer_tex, texcoord);
vec4 grass_color = texture2D(grass_layer_tex, texcoord);

vec4 tmp = mix(ice_color, stone_color, pct);
vec4 final_color = mix(tmp, grass_color, pct);
Spent answered 23/6, 2014 at 0:31 Comment(1)
i tried this, but that isnt really the thing i want. See Reto's answer, thats perfect :) thank you anyway ^^Relique

© 2022 - 2024 — McMap. All rights reserved.