Creating sharp transition between vertex colors

Godot Version

4.2.1 Stable

Question

Hello everyone!

I’m currently creating a shader that stores the vertex positions in the red and green channels of the color attribute of each vertex (so I can save the changes to disc), and the material that is used depends on the value of the blue channel (from 0-255). This is all functional and works correctly. However, when rendering the material the vertex color smoothly transitions, which makes the shader display textures in between two values.

For example, I have two vertices, where one has a blue value of 0, and the other has a blue value of 2. I intend for the two vertices to show materials 0 and 2 exclusively, but due to vertex color blending, it also has a blue value of 1 in between them because of interpolation between the two colors. This causes another material that shouldn’t be displayed, be displayed.

Is there a way to disable this in Godot or a workaround that you could do within a shader program?

Thanks for all your help!

(P.S. I have heard about flat shading in openGL, but it doesn’t seem to do anything when I turn it on in compatibility rendering mode or forward+)

Here’s a visual representation on what I have and what I want (made in blender):


Note that this is exaggerated between a blue value of 0 and 255, but I want this to work for any two values of blue.

Question about this part, usually vert color data is normalized between 0 and 1, are you seeing values above that coming from blender into godot, or are you converting the values here?

It’s a value from 0 to 1, but it’s stored as an 8-bit int to save on space. Also the colours are generated within Godot with a procedural mesh (from 0-1) so there’s no mismatch there.

If you want any gradient to have a threshold like you want, the easiest way is to just offset the gradient value and floor the value, for example with red channel vert colors:

First the mesh showing the red vert colors, it’s a 5x5 mesh and the transition starts at vert column 2 as 0 red, and then vert column 3 as 1 red.

This is a pure mix using the red vert color as the mix value between red to green.

Then I added an offset and then a floor to make a threshold cutoff, that offset value can be anything if you want to shift the cutoff to any other part of the gradient.

This really is dependent on the mesh resolution and the winding of the triangles.


In this setup I am using that value as a mix mask, so you would make a parameter for each ‘layer’ of material info you want, textures, colors, etc, and you’d use this to determine which layer is on top of the others, etc.

What is the idea for having the vert color range (0 verses 1 verses 2 . .) determine the material layer, what are you trying to achieve with that kind of system?

2 Likes

So, this is heading in the right direction, but I’m not using a “masking” system.

I have a texture array that has 256 textures, and the blue channel selects which index to use as the albedo. But if I have one vert that is of blue value 0 and next to it is another vert that has a blue value of 255, it will have multiple textures in between those two verts when all I want is textures 0 and 255, with no other textures in-between.

What this system is aiming to achieve is to have multiple textures within the same mesh for a terrain system that is made up of “chunks”, without having to use surface indexes within the mesh, since that messes with trimesh collision. Then all I need to do is change the blue value of the vertex to change its texture.

Ah ok, here is my first attempt :slight_smile:

I am converting my imported 0 to 1 red channel vert color to integers for 8 slices of a texture array seen in the 2nd shot.

The sawtooth masking pattern makes sense, I think, as each triangle is needing to make a choice, though I don’t fully understand, below is taking the vert coloring and passing it to the fragment pass as a float with the same range, and then flooring it there, but I get this:

The vert colors there are basically .4 → 0.0 → 1.0, and it still wants to hit the other ranges even though it’s stepping, must be some math pass to get it to jump without stepping through the others.

1 Like

An alternate possible route:

If you are building your own mesh procedurally, one way you could do this is to not use the same vertices at the edges that change value, so then you can store the value you want on each section’s border verts.

I am not sure what your geometry looks like as far as topology or how you are building it, as this would add more vertices to your meshes, it would also force your value changes were the verts and edges are for that transition, and also limit any blends beyond just a pixel to pixel change.

I think you want to use the step glsl function.

step(0.5, color.r);
// returns 0 if color.r is less than 0.5
// returns 1 otherwise

Awesome, so this is the exact problem i’m getting (the image at the bottom of your post). It’s the next morning so I can show you exactly what everything looks like in my game.

So this is what the terrain looks like around the player (it’s from a 2D side-on perspective). Each mesh is a different color, but all the border verts are “connected” via code (so when a border of one mech gets modified, so does the other)


When a vertex gets removed, it re-winds the indexes of the mesh so that it never has sharp edges in the corners, as shown in the corners around the player in the picture above.

Each vertex has a blue value of either 0 or 2 for the following test;
(So, there should only be two stone textures, and no dirt)

And these are the textures used in order from index 0 to index 2

So what is happening is that openGL is transitioning the blue channel from 0 to 2 in between vertices, which just so happens to contain index 1 as well, which is causing the dirt texture to show up as well, even when the two individual vertices don’t have that texture assigned to them. What I want is a hard border in between blue values so that other textures don’t show up in-between vertices.

EDIT:
I’ve figured out how to somewhat get rid of the in-between values using a varying int that gets set in the vertex shader and passed over to the fragment shader, but it’s not quite what i’m looking for… It comes up as “blocky” for some reason, and it creates an issue where the player doesn’t know what material they’re mining, since it shows two materials around the same vert.
image

So yeah, each vert would need to know information about it’s neighboring vert to really have any control over the way they blend, as all it knows right now is that to smoothly gradient from one value to another, and to do that it has to cross every value in between.

I think the only thing you can possibly do is save more data into the mesh, via vert colors or even extra UV sets, to bake in some neighboring vert information, like what tile index it is, etc, to allow the shader to do some math to determine where and when along the gradient to do the cutoff between the neighboring verts.

What I would look into is baking your mesh with the idea that you have your verts and edges where the cutoff between the two tile types are, then you don’t have those two sides share the same verts along that transitional edge. So you’d end up, on each of your chunk, a single mesh, but each layer type would have it’s own mesh shell that is not welded to any of the others, and each shell would have it’s baked in tile index value.

You’d have to rethink the way the mesh is built as now what is considered a ‘gameplay’ tile the player interacts with, for instance right now it seems like the vert is the determinant for what the interactive item is in the game?

The more I think about it, the trickier it gets as I think you’d have to consider at least 4 unique vert neighbors to really understand the neighbors information to do a successful gradient cutoff :o

There are many other possible solutions, but I think it would depend on how many tile types you are planning to have and how many each tile type can have as a neighbor, how many of the vert coloring data can you use on the visual element, etc.

1 Like

Okay, I have found a solution to the problem, and it doesn’t create sharp transitions, but it does allow for multiple materials on the same mesh (with limitations, of course).

So how I went about this is using the “masking” method explained by @roc4u1000 (thanks for all your help btw, you’re a legend!), and kind of forcing the chunks to have a maximum of 4 textures each, and blending between them using the RGBA channels of each vertex, where a value of 255 (or 1 in the case of floats) for a certain channel corresponds to a certain material being active on that vertex. Each vertex can only have one of the 4 materials at a time, so the possible range of values is (0, 0, 0, 0) for air, (255, 0, 0, 0) for material 1, (0, 255, 0, 0) for material 2, (0, 0, 255, 0) for material 3 and (0, 0, 0, 255) for material 4. What this allows is the blending of each material without going through multiple other textures, which was causing the transitioning issue earlier, and has the side-benefit of creating a smooth transition.

To go more in-depth on how it works, when the “material” of a vertex within a chunk is changed, it adds it to an array the code can index later on, and sets one of the textures within the shader to that “material” (which is, just to re-iterate, a Texture2D). If all material slots are taken up and another one wants to be added, it will just be thrown away and won’t make any changes. (note that this could be changed with another custom Vector4 parameter, which could allow up to 8 or even 16, but I feel it’s a bit overkill and unneeded)

The “master” material that each chunk gets is duplicated, since you can’t have instance-based Texture2D parameters, which may cause performance issues if you have too much on screen (maybe?) but i’m not sure. I haven’t had any issues with it yet.

So, here’s the shader code if anyone is looking for it:

And sorta just re-iterate the principle behind the multi-material thing

Also thanks for reading, and thank you to everyone above who helped!

:smiley:

The final product with 2 materials:

and with 3 materials:

2 Likes

Very nice, looks really good too, like the naturalistic warbling you are getting with your blends between substances.

I don’t want to open up another rabbit hole, but actually the multi sampler approach does open back up the idea of using your texture array for the substance types (one texture array fetch, four reads) with pretty much the same setup you have now with the 4 channel vert colors, etc.

But this is really cool looking!

Yeah, I thought about leaving it as a texture array, but that limits the amount of different textures I have, and if I wanted to add modding in the future, I want to allow for that wiggle room to have more textures than 256, but yeah!

1 Like