How to use dynamic vertex coloring with a 3D mesh?

Godot Version

Godot version 4.6.1 mono using C#

Question

I’m building procedural meshes using SurfaceTool and am using vertex colors to color the mesh instead of using textures. I have a ColorPalette and as I build the mesh I index into the Color array and assign those values to the vertices using SetColor. But I would like to be able to update the ColorPalette at runtime to either animate individual colors changing over time or in response to specific in game events, or even to be able to perform entire color palette swaps at runtime, all without having to rebuild the mesh. But unfortunately using vertex coloring with SurfaceTool SetColor bakes the colors directly into the vertices so changing the ColorPalette at runtime has no effect. Is there a way to instead assign a ‘color index’ number to each vertex and have them access the corresponding color from the color palette, so that when the color palette is changed at runtime the mesh immediately reflects that change without having to rebuild it? I know this can be done with shaders, but I’m very inexperienced with shaders and all the example shaders I find online are for 2D canvas items and not 3D meshes. Thanks!

You’ll have to use a shader but it’s a relatively simple one.

Treat RGB components you assign to the vertices as a palette color index. Then in shader use those values as UV coordinates to read a pixel from a palette texture and assign the read color as the vertex color. This is basically how indexed colors are emulated on today’s graphics hardware.

2 Likes

Thanks for the answer. Okay I figured out how to do a very crude prototype of this just to dip my toes into shaders and got it working, but for some reason the colors are all coming out darker than with the StandardMaterial even though they should in theory be the exact same. I converted the StandardMaterial into a ShaderMaterial using the right click menu, and then all I did was add this line
uniform vec4 color_palette_array[20]: source_color;
and convert this line
albedo_tex *= COLOR;
to
albedo_tex *= color_palette_array[int(round(COLOR.r * 20.0))];
And now in my SurfaceTool code I’m assigning the index of the color I want (divided by 20) instead of assigning the real color.
var color = new Color(surfaceType.Color.Index / 20f, 0, 0, 0);
In future I plan to expand this to use a more flexibly sized palette of colors, maybe even a texture atlas as a palette, but I just wanted to prove out the concept here. But as stated up top, for some reason all the colors are coming out darker now even though the original Vertex color version and the new shader based version are using the exact same color palette. Any idea what might be going on here?
Original Vertex Coloring:

New Shader based approach:

What happens if you remove source_color?

1 Like

Huh, that fixed it. So does giving the engine a type hint in the shader code cause it to apply some kind of automatic range correction? Because removing the type hint caused the Inspector UI to switch from displaying the Color Palette Array input as an array of Color values (with convenient color picker support) to an array of Vector4 (x, y, z, w values with no color picker support). So the inspector UI now is way less convenient to work with now, but the colors actually show up correctly in engine. Would be nice to have the best of both worlds here if that’s possible.

Your first material might have in fact not been displaying it correctly. Try toggling srgb conversion for vertex colors there (and bring back source_color hint in the shader)

1 Like

Yup you’re absolutely right. I had turned on Vertex Colors while ignoring sRGB because I did not understand its purpose at the time, and had wrongly assumed it was rendering the colors correctly because they looked very similar to the raw color values in the palette, whereas the new shader with source_color type hint applied makes all the colors look darker than the palette, but turns out they should look that way because my scene is only well lit from a certain angle and is otherwise has a dim ambient light. Thanks for helping me to clear that misconception up. You’ve been an enormous help in getting me started on writing shaders.