Applying Vertex Rotations Per Face in Voxel Chunk System

Godot Version

4.7

Question

I have a voxel chunk system created by SurfaceTool, and I have a custom shader that renders the texture of each voxel face. I am trying to rotate the vertices of the left side of each voxel as shown in the image below:

However, I want the rotation pivots to be local to each voxel, instead of the chunk’s whole left side mesh at one time. It should be aligned to the mesh like the right side is, but with the rotation applied as well. I tried using custom data to save the centers of each voxel face as shown below:

var leftSurfaceTool = new SurfaceTool();
var leftMaterial = new ShaderMaterial
{
     Shader = ResourceLoader.Load<Shader>("res://block resources/left_shader.gdshader")
};
leftMaterial.SetShaderParameter("albedo_texture", _textureAtlas);
leftMaterial.SetShaderParameter("atlas_size_ratio", _atlasSizeRatio);

leftSurfaceTool.Begin(Mesh.PrimitiveType.Triangles);
leftSurfaceTool.SetCustomFormat(0, SurfaceTool.CustomFormat.RgbFloat);

for (int x = 0; x < SideLength; x++)
{
    for (int y = 0; y < MaxHeight; y++)
    {
        for (int z = 0; z < SideLength; z++)
        {
            if (_blocks[x, y, z] == 0)
            {
                continue;
            }

            if (LeftFaceVisible(x, y, z))
            {
                var a = new Vector3(x + 0.5f, y + 0.5f, z + 0.5f);
                var b = new Vector3(x + 0.5f, y - 0.5f, z + 0.5f);
                var c = new Vector3(x - 0.5f, y - 0.5f, z + 0.5f);
                var d = new Vector3(x - 0.5f, y + 0.5f, z + 0.5f);
                var faceTexture = CalculateAtlasCoordinates(_grassBlock.LeftFace);
                var faceCenter = (a + b + c + d) / 4f;
                
                leftSurfaceTool.SetCustom(0, new Color(faceCenter.X, faceCenter.Y, faceCenter.Z));
                leftSurfaceTool.SetUV(faceTexture[0]);
                leftSurfaceTool.AddVertex(a);
            
                leftSurfaceTool.SetCustom(0, new Color(faceCenter.X, faceCenter.Y, faceCenter.Z));
                leftSurfaceTool.SetUV(faceTexture[1]);
                leftSurfaceTool.AddVertex(b);
                
                leftSurfaceTool.SetCustom(0, new Color(faceCenter.X, faceCenter.Y, faceCenter.Z));
                leftSurfaceTool.SetUV(faceTexture[2]);
                leftSurfaceTool.AddVertex(c);
                
                leftSurfaceTool.SetCustom(0, new Color(faceCenter.X, faceCenter.Y, faceCenter.Z));
                leftSurfaceTool.SetUV(faceTexture[0]);
                leftSurfaceTool.AddVertex(a);
                
                leftSurfaceTool.SetCustom(0, new Color(faceCenter.X, faceCenter.Y, faceCenter.Z));
                leftSurfaceTool.SetUV(faceTexture[2]);
                leftSurfaceTool.AddVertex(c);
                
                leftSurfaceTool.SetCustom(0, new Color(faceCenter.X, faceCenter.Y, faceCenter.Z));
                leftSurfaceTool.SetUV(faceTexture[3]);
                leftSurfaceTool.AddVertex(d);
            }

...

leftSurfaceTool.GenerateNormals();
var leftMeshArray = leftSurfaceTool.CommitToArrays();
var combinedMeshArray = new ArrayMesh();
combinedMeshArray.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, leftMeshArray);
combinedMeshArray.SurfaceSetMaterial(0, leftMaterial);

// mesh assignment to collision shape

But all I get is this error:

 ERROR: servers/rendering/rendering_server.cpp:789 - Condition "p_arrays[ai].get_type() != Variant::PACKED_BYTE_ARRAY" is true. Returning: ERR_INVALID_PARAMETER
 ERROR: Invalid array format for surface.

I also tried overriding the shader’s COLOR built-in, but it results in pretty much the same thing as the first image.

Here’s how I implemented the vertex shader:

void vertex() {
	vec3 pivot = CUSTOM0.rgb;
	VERTEX -= pivot;
	float test_rot = -PI / 4.0;
	mat3 test_matrix = mat3(
		vec3(1, 0, 0),
		vec3(0, cos(test_rot), -sin(test_rot)),
		vec3(0, sin(test_rot), cos(test_rot))
	);

	VERTEX *= test_matrix;
	VERTEX += pivot;
}

Does anyone know why the custom value approach won’t work? Is there another way I can go about this?

You need to set the flags for your CustomFormat when calling AddSurfaceFromArrays().

This worked, for those wondering what I did, here is the code:

combinedMeshArray.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, leftMeshArray, flags: (Mesh.ArrayFormat)((int)SurfaceTool.CustomFormat.RgbFloat << (int)Mesh.ArrayFormat.FormatCustom0Shift));

From what I understand this will shift the bits of the RGB float format by the correct CUSTOM0 shift amount.