Splat map edge blending issues


I am trying to use texture arrays for terrain texture blending. I have currently trying to do a brush tool so i can directly paint the splatmap/alpha instead of relying on external editors.
Right now i am mixing two materials identified by their ID in an integer RGB texture (only RG are used)
and an alpha map which determines the blends between them.

My issue is that i can get the edges to blend in nicely with texture filtering enabled. This happens because it “automatically” blends between two different materials at the edges.
See images below:

This is a brush stroke:

As you can see there is a blending problem at the edge of the brush (same thing happens if i use a “round” brush.

This is the materials map:

and this is the alpha map:

If i turn off filtering for the alpha map i get the expected results, however its unusable due to the blockiness:

Any suggestions on how to fix the problem?

Right now when the brush is activated i put the material id in the first index, and the previous higher value in the second index.

What are the texture parameters (specifically, GL_TEXTURE_WRAP_S and _T) for the alpha map? That image looks like it’s using GL_CLAMP_TO_BORDER, when it should probably be GL_CLAMP_TO_EDGE (or even GL_REPEAT). Alternatively, setting GL_TEXTURE_BORDER_COLOR to (1,1,1,1) would probably work for this case, but would produce the same error if the situation was reversed (i.e. the material IDs were swapped and each value in the alpha map was replaced by 1-alpha).

Here are the texture parameters (despite the name its just one map):

I dont think that the border options are the issue as the splat map encompasses all of the terrain so the edges would be at the terrain edges.

When i click on the terrain i basically change the values in the alpha map directly and reupload that section into the GPU.

I see.

For this particular case, I suggest swapping the order of the materials and inverting the alpha map (so it’s zero at the edge and one in the central “blob”). But this is going to be an issue wherever you have a boundary in the material map. The material map will change instantly (because you can’t interpolate integers) but the alpha map cannot (the steepest possible gradient is 0->1 over the width of a texel).

You can avoid the issue in the case of two materials meeting by always using replacing the material which has zero weight at that point. But if you have three or more different materials meeting at a point, there’s no way to avoid it. At least, not with a single alpha channel; if you can have N materials meeting at a point, you need a material map with N slots and an alpha map with N-1 slots.

Yes that exactly the issue here! :slight_smile:
Basically the ideal would be to turn off blending at the edges but i dont think that is possible.

I did your suggestion and it works fine when painting the base map:

But when i paint over that one it will ofc break again (because the blend value is now 1.0f) and display the edges.

I dont quite get what you are saying here, if i swap the materials when the alpha map is 0 then i would put the other material at 1.0f as its a weighted map. Can you clarify please?

Suppose that you have three materials (0,1, and 2) and you want to have bands of them in that order, with blending at the boundaries. Assume that the fragment shader is something like:

sampler2DArray textures; // GL_RGBA8, GL_LINEAR
usampler2D materials; // GL_RG8UI, GL_NEAREST
sampler2D alpha; // GL_R8, GL_LINEAR

in vec2 texcoord;
out vec4 color;

void main()
    uvec2 m = texture(materials, texcoord);
    vec4 c1 = texture(textures, vec3(texcoord, m.x));
    vec4 c2 = texture(textures, vec3(texcoord, m.y));
    vec4 t = texture(alpha, texcoord);
    color = mix(c1, c2, t);

Initially, the material map contains [0,1], i.e. 0 in slot 0 and 1 in slot 1, and the alpha map is initially zero (so slot 0 has weight 1.0 and slot 1 has weight 0.0). As you reach the boundary, the alpha value changes from 0 to 1, blending from material 0 to material 1. Once the alpha value is stable at 1, the material map changes to [2,1], i.e. slot 0 changes from material 0 (which is no longer used) to material 2 (which will be the next material used). A slot 0 has zero weight at this point, there is no change in the result. As you reach the next boundary the alpha value changes from 1 to 0, blending from material 1 to material 2.

So if you took a 1-D slice through the maps, you’d see something like:

mat[0]  0   0   0   2   2   2
mat[1]  1   1   1   1   2   2
alpha  0.0 0.5 1.0 1.0 0.5 0.0
result  0  0/1  1   1  1/2  2

Provided that you never have more than two materials meeting at a point, this works with any number of materials. Each material always uses a different slot to its neighbours. Changes in the material map only occur at points where alpha is either 0 or 1, and it’s always the opposite slot which changes (i.e. if the alpha is 0, meaning that material 0 is used at that point, it’s slot 1 which changes; and vice versa).

Suppose that you have the following materials:

mat = (0, 1) -> material ID 0, material ID 1.
alpha = 0.5

and now you want to paint over this zone with a material ID 4, which one to choose?

So you have to get rid of one of the materials to substitute, so if you only change when the alpha is 0/1 you cant actually paint over them.

Would an extra material slot (and an extra alpha value) solve this?

That would result in a point where three materials (0, 1, 4) meet, which cannot be implemented with a single alpha channel.

With a single alpha channel, you’re limited to no more than two materials at any one point. I.e. any “blob” of one material must be surrounded by a single other material. Essentially, you can’t paint a third material over an existing border between two materials.

As I said previously:

I get that this is something of a nuisance for making an intuitive editor, but it’s an inevitable limitation of having a finite limit on the number of alpha channels.

1 Like

Right i think i got it, thanks for your giant help on this. I can increase the materials and alpha channels all the way to 4 channels (RGBA) but i think the mixing math will get a lot more complicated.
Also there is an obvious limitation when there isn’t any channel at 0 or 1, i.e, the alpha map is:
0.25, 0.25, 0.25, 0.25 which would require a fifth material which isn’t possible

Is there any “graceful” way to this with the maximum limitation of 4 components for the materials for this situation?

The mixing math is just a0*c0+a1*c1+a2*c2+a3*c3, where the a* are the alpha values and the c* are the colours from the material textures. The alpha values should always sum to one, so if you have four materials you only need to store three alpha values; the fourth is always one minus the sum of the other three.

You shouldn’t need more than four materials at any point. For “natural” environments, you should only need three; if you have four materials meeting at a point, you can just push two of the boundaries in opposite directions slightly to stagger the intersection. That doesn’t work with man-made environments (where features are intentionally aligned), but four should be enough there.

Right, i was referring to the math of the “brush” and not in the shader, because that one is the one you mentioned.

Now i am trying to figure out which material index to replace when multiple alpha values are 0, because i am still getting issues on the borders on two materials with a third that is not yet inside the materials map.

If i choose the first one avaibable then i get this after painting over with a material not yet in the material map (4):

There should only be 3 materials in there (2,3 and 4)