- Home /
Calling Graphics.DrawProcedural multiple times for chunked procedural terrain
In my project, I'm creating chunks of 3D procedural terrain (voxels) using a series of compute shaders and then passing the vertex data of each chunk from a ComputeBuffer to the Graphics.DrawProcedural method to be rendered in a surface shader.
void OnPostRender()
{
foreach(GPUMeshData gMeshData in gpuMeshData) {
m_drawBuffer.SetBuffer("_Buffer", gMeshData._Vert);
m_drawBuffer.SetBuffer("_ColorBuffer", gMeshData._Color);
m_drawBuffer.SetPass(0);
Graphics.DrawProcedural(MeshTopology.Triangles, SIZE);
}
}
...
...
public struct GPUMeshData {
public int meshNum;
public ComputeBuffer _Vert;
public ComputeBuffer _Color;
public GPUMeshData(int meshNumber, ComputeBuffer vert, ComputeBuffer color) {
meshNum = meshNumber;
_Vert = vert;
_Color = color;
}
}
It works OK, but the problem is that it appears the buffer data seems to get jumbled up intermittently. Newer buffer vertex data is somehow getting merged with older vertex data that was in previous frames but should no longer be present in my GPUMeshData list. As a result, old meshes at different LODs are overlapping my newly rendered chunks. It starts to get ugly quick.
Through debugging, I know for certain that I'm not making calls to re-render the "bad" chunks/buffers after I remove them, yet somehow the old data gets mixed into one of my new buffers. When I remove objects from the GPUMeshData list, I am doing a Release() on the ComputeBuffers as well:
public void removeChunkGPU(int meshNum) {
if(gpuMeshData.Exists(x => x.meshNum == meshNum)) {
GPUMeshData gMeshData = gpuMeshData.Find(x => x.meshNum == meshNum);
if(gMeshData._Vert != null) gMeshData._Vert.Release();
if(gMeshData._Color != null) gMeshData._Color.Release();
gpuMeshData.RemoveAll(x => x.meshNum == meshNum);
}
}
I'm just trying to find out... am I doing a big "no-no" here by making multiple DrawProcedural calls for different buffers per frame? I can't understand how the older data is getting "stuck" in the Graphics pipeline. I also found a very similar question asked here:
https://forum.unity3d.com/threads/compute-shaders-and-drawprocedural-drawproceduralindirect.413196/
In my case though, I only need to render a maximum of ~350 chunks in the worst case. But as that poster mentioned, merging all chunks into a single buffer just seems counter-intuitive to me.
Any thoughts are appreciated!
EDIT: So I discovered something that seems to fix the issue, but I'm not sure why exactly. Essentially if I pre-initialize all the values in my mesh ComputeBuffers using a SetData() call before generating data in them, the problem no longer occurs.
public void generateChunkGPU(OctreeNode node) {
...
...
gpuMeshData.Add(new GPUMeshData(meshNum,
new ComputeBuffer(SIZE, sizeof(float)*7),
new ComputeBuffer(SIZE, sizeof(float)*4)));
GPUMeshData gMeshData = gpuMeshData[gpuMeshData.Count-1];
// Initialize all verts to -1
float[] val = new float[SIZE*7];
for(int k = 0; k < SIZE*7; k++)
val[k] = -1.0f;
gMeshData._Vert.SetData(val);
...
perlinNoise.SetFloat("_ChunkWidth", chunkWidth);
...
perlinNoise.SetBuffer(0, "_Result", noiseBuffer);
perlinNoise.Dispatch(0, 4, 4, 4);
marchingCubes.SetFloat("_ChunkWidth", chunkWidth);
...
marchingCubes.SetBuffer(0, "_Voxels", noiseBuffer);
marchingCubes.SetBuffer(0, "_Buffer", gMeshData._Vert);
marchingCubes.Dispatch(0, 4, 4, 4);
}
Obviously though SetData() is very expensive and stalls on the CPU, so I'd like to avoid using it. But this seems to suggest that whenever I create a new ComputeBuffer, there is some "left over" data sitting in memory where it's allocating to create the new buffer.
I also tried writing another ComputeShader to just "clear" the buffer in my remove function:
public void removeChunkGPU(int meshNum) {
if(gpuMeshData.Exists(x => x.meshNum == meshNum)) {
GPUMeshData gMeshData = gpuMeshData.Find(x => x.meshNum == meshNum);
// Clear buffer before releasing
initializeMeshBuffer.SetInt("SIZE", SIZE);
initializeMeshBuffer.SetBuffer(0, "_Buffer", gMeshData._Vert);
initializeMeshBuffer.Dispatch(0, 0, 0, 1);
if(gMeshData._Vert != null) gMeshData._Vert.Release();
if(gMeshData._Color != null) gMeshData._Color.Release();
gpuMeshData.RemoveAll(x => x.meshNum == meshNum);
}
}
But that didn't seem to help at all.. Here's the shader code for it anyway:
#pragma kernel CSMain
struct Vert
{
float4 position;
float3 normal;
};
int SIZE;
RWStructuredBuffer<Vert> _Buffer;
[numthreads(1,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
Vert vert;
vert.position = float4(-1.0, -1.0, -1.0, -1.0);
vert.normal = float3(-1.0, -1.0, -1.0);
for(int i = 0; i < SIZE; i++)
_Buffer[i] = vert;
}
Anybody have any thoughts on how I can avoid using SetData() and truly get a clear ComputeBuffer each time I create a new one?
Answer by onehand · May 14, 2017 at 07:30 PM
I managed to fix it using the aforementioned initializeMeshBuffer compute shader. I just had the thread groups set wrong in my dispatch call! Changed it to:
initializeMeshBuffer.Dispatch(0, 1, 1, 1);
And it now clears the buffer data successfully! :)
Answer by Meffjuw · May 21, 2017 at 02:47 AM
Hi @onehand,
I've experimented a bit with procedural voxel generation, but that has always been on the CPU side so far. I am now attempting to learn how to carry out the same generation, but on GPUs, due to their parallel architecture. Would you mind sharing your source code with me? If not, can you at least point me to some tutorials/articles you have used which helped you?
Thanks :)
@$$anonymous$$effjuw I don't want to provide my source as I plan to use it in a commercial game eventually, but I can provide you a great sample project.
Unity project https://www.dropbox.com/s/8aao2q6lvxu70am/$$anonymous$$archingCubesOnTheGPU.zip?dl=0
This is an amazing asset that helped me learn a lot. This blog has since been taken down, so hopefully he doesn't $$anonymous$$d me posting it. But @scrawk please let me know if you want me to remove it. I personally think it's a great one to have out there for the community interested in procedural generation.
Also, you can find his new blog here: https://www.digital-dust.com/code
I dont $$anonymous$$d.
A improved version of this project will be on the new blog at some point.
Your answer
Follow this Question
Related Questions
More than one Kernel in Compute Shader 1 Answer
Compute Shader not work on older GPU with DX11 0 Answers
Compute Shader crashes when buffer is too big 0 Answers
Use contents of RWStructuredBuffer written by shader in another shader 0 Answers
Texture sampling will move when sampled same position. 1 Answer