Paint a cubes faces depending on direction

I generate a cube procedurally through C#, now I want to paint to top of that cube green, and the sides brown. As far as I understand it I have 2 options, either:

  • Paint it using vertex colors through script.
  • Paint it based on normals direction via shader

I am fine using either method, but know very little about both and can’t find anything to get me started. I would very much prefer doing it via vertex color, since I want to paint individual faces of a larger mesh in the future.

I have managed to set a single color using vertex colors via script, but that is about as far as I get:

void SetColors()
    {
        Color[] colors = new Color[mesh.vertices.Length];

        for (int i = 0; i < mesh.vertices.Length; i++)
        {
            Debug.Log(mesh.vertices[i]);
            colors[i] = Color.green;
        }

        mesh.colors = colors;
    }

if I print a list of my vertices I have 24 verts, all at different comcinations of (0.5, 0.5, 0.5) - (-0.5, -0.5, -0.5). This makes sence since there are 6 faces, and each face has 4 verts. But how do I know which verts are part of the top face?

Need some help painting a cube 2 different colors based on face direction (horizontal = grass, vertical = dirt).

EDIT full code:

Mesh generation of landscape of cubes:

using UnityEngine;
using System.Collections.Generic;

public class VoxelMeshGenerator : MonoBehaviour
{
    const int VERT_LIMIT = 60000;
    const float SCALE = .5f;

    Mesh mesh;
    List<Vector3> vertices;
    List<int> triangles;

    List<int> test = new List<int>();

    public GameObject chunkPrefab;
    private GameObject activeChunk;

    public void GenerateVoxelMesh(VoxelMapGenerator mapGen)
    {
        InitChunk();

        for (int z = 0; z < mapGen.GetWidth() * mapGen.scale; z += mapGen.scale)
        {
            for (int x = 0; x < mapGen.GetDepth() * mapGen.scale; x+=mapGen.scale)
            {
                if (mapGen.GetCell(x, z) == 1)
                    continue;

                MakeCube(mapGen.scale, new Vector3(x, 0, z), x ,z, mapGen);

                if(vertices.Count >= VERT_LIMIT)
                {
                    UpdateMesh();
                    InitChunk();
                }
            }
        }
       
        UpdateMesh();
    }

    void SetColors()
    {
        Color[] colors = new Color[mesh.vertices.Length];

        for (int i = 0; i < mesh.vertices.Length; i++)
        {
            Debug.Log(mesh.vertices[i]);
            colors[i] = Color.green;
        }

        mesh.colors = colors;
    }

    void InitChunk()
    {
        vertices = new List<Vector3>();
        triangles = new List<int>();
        activeChunk = Instantiate(chunkPrefab, transform);
        mesh = activeChunk.GetComponent<MeshFilter>().mesh;
    }

    void MakeCube(float cubeScale, Vector3 cubePos, int x, int z, VoxelMapGenerator mapGen)
    {
        for (int i = 0; i < 6; i++)
        {
            if (mapGen.GetNeighbor(x,z, (Direction)i) == 1)
                MakeFace((Direction)i, cubeScale, cubePos);
        }
    }

    void MakeFace(Direction dir, float faceScale, Vector3 facePos)
    {
        vertices.AddRange(CubeMeshData.FaceVertices(dir, faceScale, facePos));
        int vCount = vertices.Count;

        triangles.Add(vCount - 4);
        triangles.Add(vCount - 4 + 1);
        triangles.Add(vCount - 4 + 2);

        triangles.Add(vCount - 4);
        triangles.Add(vCount - 4 + 2);
        triangles.Add(vCount - 4 + 3);
    }

    void UpdateMesh()
    {
        mesh.Clear();

        mesh.vertices = vertices.ToArray();
        mesh.triangles = triangles.ToArray();
        mesh.RecalculateNormals();

        activeChunk.GetComponent<MeshCollider>().sharedMesh = mesh;
    }
}

CubeMeshData:

using UnityEngine;

public static class CubeMeshData
{
    public static Vector3[] vertices = {
        new Vector3( 1,  1,  1),
        new Vector3(-1,  1,  1),
        new Vector3(-1, -1,  1),
        new Vector3( 1, -1,  1),
        new Vector3(-1,  1, -1),
        new Vector3( 1,  1, -1),
        new Vector3( 1, -1, -1),
        new Vector3(-1, -1, -1)
    };

    public static int[][] faceTriangles = {
        new int[] { 0, 1, 2, 3 },
        new int[] { 5, 0, 3, 6 },
        new int[] { 4, 5, 6, 7 },
        new int[] { 1, 4, 7, 2 },
        new int[] { 5, 4, 1, 0 },
        new int[] { 3, 2, 7, 6 }
    };

    public static Vector3[] FaceVertices(int dir, float scale, Vector3 pos)
    {
        Vector3[] fv = new Vector3[4];
        for (int i = 0; i < fv.Length; i++)
        {
            fv[i] = (new Vector3(vertices[faceTriangles[dir][i]].x * scale, vertices[faceTriangles[dir][i]].y, vertices[faceTriangles[dir][i]].z * scale)) + pos;
        }
        return fv;
    }

    public static Vector3[] FaceVertices(Direction dir, float scale, Vector3 pos)
    {
        return FaceVertices((int)dir, scale, pos);
    }
}

Well you said you generate a cube procedurally? Surely you then know which side is which and which vertices belong to where? Couldn’t you just create vertices for a face and set color for those vertices, then for the next face, and so on.

I currently keep a List of vertices that I dont set to the mesh mesh.vertices = vertices.ToArray(); until the entire mesh is finished, to help with performance, since I generate a landscape and sometimes have > 200 000 verts. So setting the color each time I create a face seems a bit heavy? But maybe Im wrong, I am by no means an expert on this.

Oh ok, I see. I thought it might something else than a terrain… But I think it’s also somewhat heavy if you have to figure out afterwards which vertices belong to which face etc.? Unless you fill a terrain with full blocks everywhere (6 faces) it’s pretty much impossible to quickly know which vertices belong to which face without processing them somehow. I’m no expert either but I’ve dabbled with mesh generation quite often.

That shader option might be easier then. But I’d test first how fast the mesh can be generated without much optimization.

(I didn’t check your code yet.)

Thanks, I mean I don’t render any Verts that can’t be seen, so only the top and the edges, nothing underneath that, purely for optimization purposes.