I am creating voxel planets utilizing Marching Cubes (MC). To deal with close by (high-resolution) and distant (low-resolution) blocks, I take advantage of an Octree knowledge construction.
NOTE: The implementation makes use of compute shaders.
Downside:
When two adjoining blocks have totally different LODs, holes seem alongside the perimeters.
(The high-resolution block is on the left, and the decrease one is on the fitting. As you possibly can see, there are holes between them; this can be a crack
.)
I thought of utilizing skirts to cowl the cracks, however extending the skirts towards the middle of the planet solely works on open terrain (plains, mountains, hills, and many others.). In caves or tunnels, particularly from above, extending the skirts towards the middle of the planet creates triangles that trigger very unpleasant protrusions.
Skirts ought to all the time lengthen in the wrong way to the vertex regular, calculated from the normalized sum of the normals of the triangles containing the given vertex. Nonetheless, with the traditional MC implementation:
Every triangle has impartial vertices, so vertex normals can’t be shared between adjoining triangles.
This makes it troublesome to accurately calculate the place of skirts on chunk edges.
So, how can I resolve this drawback?
I must share vertices between triangles, however I do not know the way…
Presently, my chunks are 8x8x8 voxels, processed by a single set of threads.
How the code works: Within the CalculateNoiseValues kernel the compute shader calculate the density area for every level, because the voxels are 888 then the variety of factors will probably be 99*9, after that it shops the values within the noiseValuesBuffer after which they’re used within the ComputeMarchingCubes kernel which create the vertices and triangles.
This code is quiet easy for now, it’s for take a look at, however the idea is that this.*
#embody "Property/Shader Helpers/MCTriangulation.hlsl"
#outline NOISE_VALUES_STRIDE_PER_CHUNK 729
struct Chunk
{
float3 place;
uint celestialBodyIndex;
};
#pragma kernel CalculateNoiseValues
#pragma kernel ComputeMarchingCubes
RWStructuredBuffer noiseValuesBuffer;
RWStructuredBuffer chunksInfoBuffer;
[numthreads(9,9,9)]
void CalculateNoiseValues (uint3 id : SV_GroupThreadID, uint linearID : SV_GroupIndex, uint3 chunkIndex : SV_GroupID)
{
Chunk chunk = chunksInfoBuffer[chunkIndex.x];
float3 pointPos = (float3)float3(id.x + chunk.place.x, id.y + chunk.place.z, id.z + chunk.place.y) - 4;
float noise = SimplexNoise(pointPos * 0.01);
int storeIndex = linearID + chunkIndex.x * NOISE_VALUES_STRIDE_PER_CHUNK;
noiseValuesBuffer[storeIndex] = noise;
}
RWStructuredBuffer verticesBuffer;
RWStructuredBuffer verticesPerChunk;
void GetNoiseValues(uint id, out float values[8])
{
values[0] = noiseValuesBufferhttps://gamedev.stackexchange.com/q/214755;
values[1] = noiseValuesBuffer[id + 1];
values[3] = noiseValuesBuffer[id + 9];
values[2] = noiseValuesBuffer[id + 10];
values[4] = noiseValuesBuffer[id + 81];
values[5] = noiseValuesBuffer[id + 82];
values[7] = noiseValuesBuffer[id + 90];
values[6] = noiseValuesBuffer[id + 91];
}
void CalculateVertexPos(float values[8])
{
}
#outline BLOCK_SIZE 512
#outline CHUNK_STRIDE BLOCK_SIZE * 15
groupshared int sharedData[BLOCK_SIZE];
[numthreads(8,8,8)]
void ComputeMarchingCubes (uint3 id : SV_GroupThreadID, uint linearID : SV_GroupIndex, uint3 chunkIndex : SV_GroupID)
{
//For every voxel
Chunk chunk = chunksInfoBuffer[chunkIndex.x];
float values[8];
uint baseID = id.y * 81 + id.z * 9 + id.x + ( chunkIndex.x * NOISE_VALUES_STRIDE_PER_CHUNK );
GetNoiseValues(baseID, values);
int mixture = 0;
for (int i = 0; i < 8; i++)
{
if (values[i] > 0)
(1 << i);
}
int combinationOffset = mixture * 16;
int numberOfTriangles = triangleTable[combinationOffset + 15];
#pragma area Prefix Sum
//Prefix-sum
sharedData[linearID] = numberOfTriangles;
GroupMemoryBarrierWithGroupSync();
float offset = 1;
//Up-Sweep
for (int threadNeeded = BLOCK_SIZE / 2; threadNeeded > 0; threadNeeded /= 2)
{
if (linearID < threadNeeded)
{
uint indexA = offset * (2 * linearID + 1) - 1;
uint indexB = offset * (2 * linearID + 2) - 1;
sharedData[indexB] += sharedData[indexA];
}
offset *= 2;
GroupMemoryBarrierWithGroupSync();
}
if(linearID == 0)
{
verticesPerChunk[chunkIndex.x] = sharedData[511];
sharedData[511] = 0;
}
offset = BLOCK_SIZE / 2;
GroupMemoryBarrierWithGroupSync();
//Down-Sweep
for (int threadNeeded = 1; threadNeeded <= BLOCK_SIZE / 2; threadNeeded *= 2)
{
if (linearID < threadNeeded)
{
uint indexA = offset * (2 * linearID + 1) - 1;
uint indexB = offset * (2 * linearID + 2) - 1;
int temp = sharedData[indexA];
sharedData[indexA] = sharedData[indexB];
sharedData[indexB] += temp;
}
offset /= 2;
GroupMemoryBarrierWithGroupSync();
}
#pragma endregion
int storeOffset = sharedData[linearID] + chunkIndex.x * CHUNK_STRIDE;
for (int i = 0; i < numberOfTriangles; i+=3)
{
int edge1 = triangleTable[combinationOffset + i];
int edge2 = triangleTable[combinationOffset + i + 1];
int edge3 = triangleTable[combinationOffset + i + 2];
int2 cornerIndices1 = edgeToVertex[edge1];
int2 cornerIndices2 = edgeToVertex[edge2];
int2 cornerIndices3 = edgeToVertex[edge3];
float3 cornerA1 = cornerPositions[cornerIndices1.x];
float3 cornerA2 = cornerPositions[cornerIndices1.y];
float3 cornerB1 = cornerPositions[cornerIndices2.x];
float3 cornerB2 = cornerPositions[cornerIndices2.y];
float3 cornerC1 = cornerPositions[cornerIndices3.x];
float3 cornerC2 = cornerPositions[cornerIndices3.y];
float3 pos1 = (cornerA1 + cornerA2) * 0.5f;
float3 pos2 = (cornerB1 + cornerB2) * 0.5f;
float3 pos3 = (cornerC1 + cornerC2) * 0.5f;
verticesBuffer[storeOffset + i] = pos1 + ((float3)id + chunk.place);
verticesBuffer[storeOffset + i + 1] = pos2 + ((float3)id + chunk.place);
verticesBuffer[storeOffset + i + 2] = pos3 + ((float3)id + chunk.place);
}
}