GLSL shader: Interpolate between more than two textures

本秂侑毒 提交于 2019-12-20 10:56:03

问题


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:

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?


回答1:


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;



回答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);



回答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
...


来源:https://stackoverflow.com/questions/24356075/glsl-shader-interpolate-between-more-than-two-textures

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!