I am making a voxel based terrain system (Yes, I know, very original...), anyway...
Each voxel should have it's own color so the player can distinguish which type of material it is. For example: dirt would be brown; grass, green and stone would be gray.
I don't really understand marching cubes, so I come to you guys with my issue. I want to be able to color each vertex based on which voxel it would visually make sense to belong to. Edit: To clear things up a little. The voxel contains the color data, I do not wish to generate the colors (like how some systems automatically apply a stone texture based on the height/slope of the face)
How would I go about doing this?
Edit: Marching Cubes Code
Here is the code I am using for marching cubes, as I have said. I don't understand marching cubes so this code wasn't written by me.
Original Source of the Code Here
(Some of the data structures (like the lookup tables) have been omitted from the code)
Also note that the code below has been edited to pass in the color data for each Voxel.
public virtual void Generate(IList<float> voxels, IList<Voxel> voxelData, int width, int height, int depth, IList<Vector3> verts, IList<int> indices)
{
if (Surface > 0.0f)
{
WindingOrder[0] = 0;
WindingOrder[1] = 1;
WindingOrder[2] = 2;
}
else
{
WindingOrder[0] = 2;
WindingOrder[1] = 1;
WindingOrder[2] = 0;
}
int x, y, z, i;
int ix, iy, iz;
for (x = 0; x < width - 1; x++)
{
for (y = 0; y < height - 1; y++)
{
for (z = 0; z < depth - 1; z++)
{
//Get the values in the 8 neighbours which make up a cube
for (i = 0; i < 8; i++)
{
ix = x + VertexOffset[i, 0];
iy = y + VertexOffset[i, 1];
iz = z + VertexOffset[i, 2];
Cube[i] = voxels[ix + iy * width + iz * width * height];
}
//Perform algorithm
March(x, y, z, Cube, verts, indices);
}
}
}
}
protected virtual float GetOffset(float v1, float v2)
{
float delta = v2 - v1;
return (delta == 0.0f) ? Surface : (Surface - v1) / delta;
}
protected override void March(float x, float y, float z, float[] cube, IList<Vector3> vertList, IList<int> indexList)
{
int i, j, vert, idx;
int flagIndex = 0;
float offset = 0.0f;
//Find which vertices are inside of the surface and which are outside
for (i = 0; i < 8; i++) if (cube[i] <= Surface) flagIndex |= 1 << i;
//Find which edges are intersected by the surface
int edgeFlags = CubeEdgeFlags[flagIndex];
//If the cube is entirely inside or outside of the surface, then there will be no intersections
if (edgeFlags == 0) return;
//Find the point of intersection of the surface with each edge
for (i = 0; i < 12; i++)
{
//if there is an intersection on this edge
if ((edgeFlags & (1 << i)) != 0)
{
offset = GetOffset(cube[EdgeConnection[i, 0]], cube[EdgeConnection[i, 1]]);
EdgeVertex[i].x = x + (VertexOffset[EdgeConnection[i, 0], 0] + offset * EdgeDirection[i, 0]);
EdgeVertex[i].y = y + (VertexOffset[EdgeConnection[i, 0], 1] + offset * EdgeDirection[i, 1]);
EdgeVertex[i].z = z + (VertexOffset[EdgeConnection[i, 0], 2] + offset * EdgeDirection[i, 2]);
}
}
//Save the triangles that were found. There can be up to five per cube
for (i = 0; i < 5; i++)
{
if (TriangleConnectionTable[flagIndex, 3 * i] < 0) break;
idx = vertList.Count;
for (j = 0; j < 3; j++)
{
vert = TriangleConnectionTable[flagIndex, 3 * i + j];
indexList.Add(idx + WindingOrder[j]);
vertList.Add(EdgeVertex[vert]);
}
}
}
/// VoxelData
[Serializable]
public struct Voxel
{
public Color32 color;
public long materialId;
}
Example of what I tried
I have already tried assigning the color based on the current cube being processed, it did not give the result I wanted, take a look at the image below, notice that the corner of some voxels are white instead of gray, this is because of the way I implemented colors the first time, those vertices are technically generated from an air block but should visually belong to a stone block.
(Ignore the weird glitches in the terrain, that is something I have already fixed, it was with the way I was generating the terrain)
After a few hours of searching:
I found what seems promising in an answer here
private static final byte[] BLOCK_PICK_BITS_BY_VERT_INDEX = new byte[]{
(byte)0x67,
(byte)0x26,
(byte)0x23,
(byte)0x37,
(byte)0x45,
(byte)0x04,
(byte)0x01,
(byte)0x15,
(byte)0x57,
(byte)0x46,
(byte)0x02,
(byte)0x13
};
private static final int pickBlockIdForVertex(long blockIds, int vertIndex)
{
int bits = BLOCK_PICK_BITS_BY_VERT_INDEX[vertIndex];
int blockIndexA = bits >> 4;
int blockIndexB = bits & 0xf;
int blockIdA = (int)(blockIds >> 8 * blockIndexA) & 0xff;
int blockIdB = (int)(blockIds >> 8 * blockIndexB) & 0xff;
//assert (blockIdA == airBlockId) != (blockIdB == airBlockId);
//return blockIdA - nonZeroAirBlockId + blockIdB;
return blockIdA + blockIdB;
}
However the answer doesn't explain how blockIds
work or how to get vertIndex
.
I am really at a lost on how to solve this problem, I'd really like someones help. I'm sure there are probably other people with the same questions as me, I am honestly surprised there is absolutely no direct documentation on this (at least, to my knowledge there isn't).
I will be reading up on marching cubes and trying to figure out how to maybe implement colors.
I think I'm almost there!
So the answer I linked to in the previous edit, where I said it looked promising, I finally was able to understand some of it. And from what I understood I made the code below:
// Because the Marching Cubes implementation I use has some of the least to greatest significant vertex indices swapped, I modified this below to account for that.
/// Mainly any 7s in the array below were swapped with 6s and the same for 4s and 5s.
private static byte[] MAT_CHOOSE_BITS = new byte[]{
0x76,
0x27,
0x23,
0x36,
0x54,
0x05,
0x01,
0x14,
0x46,
0x57,
0x02,
0x13
};
public Color32 GetMaterialColor(int[] mats, int vert)
{
int bits = MAT_CHOOSE_BITS[vert];
// Because I am indexing an array and not bit-shifting a long I must use `7 - bits`
int indexA = 7 - (bits >> 4);
int indexB = 7 - (bits & 0xf);
return Material.materials[(mats[indexA] + mats[indexB]) - 1].color;
}
The code above ALMOST works, it's very frustrating. When I run the code, it runs through the loop about 8 times before it encounters an Argument Out Of Bounds Exception, I debugged it and apparently in one of the situations the "mats" array doesn't contain any zeros, which from what I understand shouldn't be possible. The theory behind the code above is that one of the indices (either indexA
or indexB
) should point to a zero in the mats
array, because in theory one side of the equation is always zero (or air).
I don't understand what I did wrong... I know at this point it has to be the way I am generating the voxels, it might also still be that code but I need to first solve the issue where none of the ints passed into the mats array is 0.
Here is the code I am using to generate the terrain:
for (int i = 0; i < 16; i++)
{
for(int j = 0; j < 256; j++)
{
for (int k = 0; k < 16; k++)
{
Voxel v;
float d = terrain.noise.Value.GetSimplex(i, j, k);
if (d > 0 || Mathf.Approximately(d, 0f)) v.materialId = UnityEngine.Random.Range(1, 3);
else v.materialId = 0;
v.density = d;
data.voxels[i + j * 16 + k * 16 * 256] = v;
}
}
}
FAILURE
So I wasn't able to fix this issue, and it seems nobody else on this forum is going to as well. So I decided to go with a much simpler solution, basically, I dropped smoothing the terrain to just binary smoothing. Either a cube is off, or on and there are sharp slopes between that. it makes the terrain look blocky but it's the best solution for how I fixed the vertex colors. I fixed the vertex colors by first checking if the current cube is air, if it is it chooses the material of the first neighbor that isn't air.
If anyone has an answer to this question in the future, I am all ears.