AppendStructuredBuffer as output of compute buffer, how to transfer to Geometry Shader?

I have a compute shader with a simple task of finding visible voxels in StructuredBuffer and outputing their position in a VoxelVertex struct to an AppendStructuredBuffer. I want to then later use this APB in my geometry shader as a set of points to create quads out of.

The compute buffer works as intended, and have confirmed the output is correct by fetching the append buffer to my CPU and just instantiating boxes at the positions.

4451563--408184--upload_2019-4-20_16-53-44.png

The problem seems to be that the Vertex Shader doesn't want to use APB and i'm not exactly sure how to make it work.

From my searching on the internet there isn't a lot of information on this specific topic, some people are suggesting using : register(u1) and to enable random writes, but none of those methods provided any success for me.

C# Handler

[spoiler]

using System.Runtime.InteropServices;
using UnityEngine;

namespace Assets
{
    public sealed class World : MonoBehaviour
    {
        public GameObject Cube;
        public Material Material;

        public int VoxelCount;
        public VoxelVertex[] VoxelVertices;

        public ComputeShader ComputeShader;
        public ComputeBuffer VoxelComputeBuffer;
        public ComputeBuffer VertexComputeBuffer;
        public ComputeBuffer ArgumentComputeBuffer;

        public void Start()
        {
            var chunk = new Chunk(Vector3.zero, new Vector3(16, 256, 15));

            ConstructTerrain(chunk);
            ConstructMeshGpu(chunk);
            ConstructMeshImplicit();
        }

        public void ConstructTerrain(Chunk chunk)
        {
            for (var x = 0; x < chunk.Dimensions.x; x++)
            {
                for (var z = 0; z < chunk.Dimensions.z; z++)
                {
                    var height = (int)(Mathf.PerlinNoise(x / 16.0f, z / 16.0f) * chunk.Dimensions.z);

                    for (var y = 0; y < height; y++)
                    {
                        var offset = new Vector3(x, y, z);
                        ref var voxel = ref chunk.GetVoxel(offset);
                        voxel.Type = 1;
                    }
                }
            }
        }

        public void ConstructMeshGpu(Chunk chunk)
        {
            var terrainCreatorIndex = ComputeShader.FindKernel("TerrainCreator");

            VoxelComputeBuffer = new ComputeBuffer(chunk.Voxels.Length, Marshal.SizeOf(typeof(Voxel)));
            VoxelComputeBuffer.SetData(chunk.Voxels);

            VertexComputeBuffer = new ComputeBuffer(chunk.Voxels.Length, Marshal.SizeOf(typeof(VoxelVertex)), ComputeBufferType.Append);
            VertexComputeBuffer.SetCounterValue(0);

            ComputeShader.SetVector("_Dimensions", chunk.Dimensions);
            ComputeShader.SetBuffer(terrainCreatorIndex, "_Voxels", VoxelComputeBuffer);
            ComputeShader.SetBuffer(terrainCreatorIndex, "_Vertices", VertexComputeBuffer);

            Material.SetVector("_Dimensions", chunk.Dimensions);
            Material.SetBuffer("_Vertices", VertexComputeBuffer);

            ComputeShader.Dispatch(terrainCreatorIndex, (int)chunk.Dimensions.x, (int)chunk.Dimensions.y, (int)chunk.Dimensions.z);

            ArgumentComputeBuffer = new ComputeBuffer(4, sizeof(int), ComputeBufferType.IndirectArguments);
            var args = new int[] { 0, 1, 0, 0 };
            ArgumentComputeBuffer.SetData(args);

            ComputeBuffer.CopyCount(VertexComputeBuffer, ArgumentComputeBuffer, 0);
            ArgumentComputeBuffer.GetData(args);

            //Was used on CPU in ConstructMeshImplicit() to see if the data was correctly made, was OK
            VoxelCount = args[0];
            VoxelVertices = new VoxelVertex[VoxelCount];
            VertexComputeBuffer.GetData(VoxelVertices);
        }

        void OnPostRender()
        {
            Material.SetPass(0);



            Graphics.DrawProceduralIndirectNow(MeshTopology.Points, ArgumentComputeBuffer );
            //Graphics.DrawProceduralNow(MeshTopology.Points, VoxelCount, 1 );
        }


        public void ConstructMeshImplicit()
        {
            for (var x = 0; x < VoxelVertices.Length ; x++)
            {
                Instantiate(Cube, VoxelVertices[x].Position, Quaternion.identity);
            }
        }
        public void ConstructMesh(Chunk chunk)
        {
            for (var x = 0; x < chunk.Dimensions.x; x++)
            {
                for (var y = 0; y < chunk.Dimensions.y; y++)
                {
                    for (var z = 0; z < chunk.Dimensions.z; z++)
                    {
                        var offset = new Vector3(x, y, z);
                        var voxel = chunk.GetVoxel(offset);
                        if (voxel.Type == 1)
                        {
                            Instantiate(Cube, chunk.Position + offset, Quaternion.identity);
                        }
                    }
                }
            }
        }
    }
}

[/spoiler]

Shader

[spoiler]

SplatUnlit.shader

Shader "Splatting/Unlit"
{
   SubShader
   {
       Tags { "RenderType" = "Opaque" }

       Pass
       {

           CGPROGRAM
           #pragma vertex Vertex
           #pragma geometry Geometry
           #pragma fragment Fragment
           #pragma enable_d3d11_debug_symbols
           #pragma target 4.5
           #include "SplatUnlit.cginc"
           ENDCG
       }
   }
}

SplatShader.cginc

#include "UnityCG.cginc"
#include "UnityGBuffer.cginc"
#include "UnityStandardUtils.cginc"




struct v2g
{
   float4 vertex : SV_POSITION;
   fixed4 color : COLOR;
   float4 up : TEXCOORD0;
   float4 right : TEXCOORD1;
};

struct g2f
{
   float4 vertex : SV_POSITION;
   fixed4 color : COLOR;
};

struct VoxelVertex
{
   float3 Position : POSITION;
};

uniform float3 _Dimensions;
uniform RWStructuredBuffer<VoxelVertex> _Vertices : register(u1);

int CalculateIndex(int3 position)
{
   return (int)(position.x +
   position.z * _Dimensions.x +
   position.y * _Dimensions.x * _Dimensions.z);
}

v2g Vertex(VoxelVertex input, uint index : SV_VertexID )
{

   v2g output;
   output.vertex = float4(input.Position,1);
   output.color = float4(1,input.Position.y/256,1,1);

   float3 view = normalize(UNITY_MATRIX_IT_MV[2].xyz);
   float3 upvec = normalize(UNITY_MATRIX_IT_MV[1].xyz);
   float3 R = normalize(cross(view, upvec));
   output.up = float4(upvec*1, 0);
   output.right = float4(R * 1, 0);
   return output;
}

[maxvertexcount(4)]
void Geometry(point v2g input[1], inout TriangleStream<g2f>outputStream) {
   g2f output;

   output.vertex = UnityObjectToClipPos(input[0].vertex + (-input[0].up + input[0].right));
   output.color = input[0].color;
   outputStream.Append(output);

   output.vertex = UnityObjectToClipPos(input[0].vertex + (-input[0].up - input[0].right));
   outputStream.Append(output);

   output.vertex = UnityObjectToClipPos(input[0].vertex + (+input[0].up + input[0].right));
   outputStream.Append(output);

   output.vertex = UnityObjectToClipPos(input[0].vertex + (+input[0].up - input[0].right));
   outputStream.Append(output);

   outputStream.RestartStrip();
}

fixed4 Fragment(g2f input) : SV_Target
{
   return input.color;
}

[/spoiler]

Compute Shader

[spoiler]

#pragma kernel TerrainCreator

struct Voxel
{
   int Solid;   
};

struct VoxelVertex
{
   float3 Position : POSITION;
};

float3 _Dimensions;

StructuredBuffer<Voxel> _Voxels;
AppendStructuredBuffer<VoxelVertex> _Vertices;

int CalculateIndex(int3 position)
{
   return (int)(position.x +
   position.z * _Dimensions.x +
   position.y * _Dimensions.x * _Dimensions.z);
}

[numthreads(1,1,1)]
void TerrainCreator (uint3 threadPosition : SV_DispatchThreadID)
{
    Voxel voxel = _Voxels[CalculateIndex(threadPosition)];

   if( voxel.Solid == 1 && _Voxels[CalculateIndex(float3(threadPosition.x,threadPosition.y+1,threadPosition.z))].Solid == 0 ) {
       VoxelVertex voxelVertex;
       voxelVertex.Position = float3(threadPosition);

       _Vertices.Append(voxelVertex);
   }
}

[/spoiler]

2 Likes

Example:

using UnityEngine;

public class AppendBuffer : MonoBehaviour
{
    public Material material;
    public ComputeShader shader;
    public int size = 8;

    ComputeBuffer buffer, argBuffer;

    void Start()
    {
        buffer = new ComputeBuffer(size * size * size, sizeof(float) * 3, ComputeBufferType.Append);
        argBuffer = new ComputeBuffer(4, sizeof(int), ComputeBufferType.IndirectArguments);
        shader.SetBuffer(0, "buffer", buffer);
        shader.SetFloat("size", size);
        shader.Dispatch(0, size/8, size/8, size/8);
        int[] args = new int[]{ 0, 1, 0, 0 };
        argBuffer.SetData(args);
        ComputeBuffer.CopyCount(buffer, argBuffer, 0);
        argBuffer.GetData(args);
    }

    void OnPostRender ()
    {
        material.SetPass(0);
        material.SetBuffer ("buffer", buffer);
        Graphics.DrawProceduralIndirect(MeshTopology.Points, argBuffer, 0);
    }

    void OnDestroy ()
    {
        buffer.Release();
        argBuffer.Release();
    }
}
#pragma kernel CSMain

AppendStructuredBuffer<float3> buffer;
float size;

[numthreads(8,8,8)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    float3 center = float3(id / size);
    buffer.Append(center);
}
Shader "AppendBuffer"
{
    SubShader
    {
        Cull Off
        Pass
        {
            CGPROGRAM
            #pragma vertex VSMain
            #pragma geometry GSMain
            #pragma fragment PSMain
            #pragma target 5.0

            StructuredBuffer<float3> buffer;

            struct Structure
            {
                float4 position : SV_Position;
                uint id : custom;
            };

            Structure VSMain(uint id : SV_VertexID)
            {
                Structure VS;
                VS.position = float4(buffer[id], 1.0);
                VS.id = id;
                return VS;
            }

            float Sign(int x)
            {
                return (x > 0) ? 1 : -1;
            }

            [maxvertexcount(36)]
            void GSMain( point Structure patch[1], inout TriangleStream<Structure> stream, uint id : SV_PRIMITIVEID )
            {
                Structure GS;
                float2 d = float2 (0.01, -0.01);
                float3 c = float3 (patch[0].position.xyz);
                for (int i=1; i<7; i++)
                {
                    GS.position = UnityObjectToClipPos(float4(c.x+d.y*Sign(i!=6), c.y+d.y*Sign(i!=4), c.z+d.y*Sign(i!=2), 1.0));
                    GS.id = patch[0].id;
                    stream.Append(GS);
                    GS.position = UnityObjectToClipPos(float4(c.x+d.x*Sign(i!=3 && i!=4 && i!=5), c.y+d.y*Sign(i!=4), c.z+d.x*Sign(i!=1), 1.0));
                    GS.id = patch[0].id;
                    stream.Append(GS);
                    GS.position = UnityObjectToClipPos(float4(c.x+d.y*Sign(i!=3 && i!=4 && i!=6), c.y+d.x*Sign(i!=3), c.z+d.y*Sign(i!=2), 1.0));
                    GS.id = patch[0].id;
                    stream.Append(GS);
                    GS.position = UnityObjectToClipPos(float4(c.x+d.x*Sign(i!=5), c.y+d.x*Sign(i!=3), c.z+d.x*Sign(i!=1), 1.0));
                    GS.id = patch[0].id;
                    stream.Append(GS);
                    GS.position = UnityObjectToClipPos(float4(c.x+d.y*Sign(i!=3 && i!=4 && i!=6), c.y+d.x*Sign(i!=3), c.z+d.y*Sign(i!=2), 1.0));
                    GS.id = patch[0].id;
                    stream.Append(GS);
                    GS.position = UnityObjectToClipPos(float4(c.x+d.x*Sign(i!=3 && i!=4 && i!=5), c.y+d.y*Sign(i!=4), c.z+d.x*Sign(i!=1), 1.0));
                    GS.id = patch[0].id;
                    stream.Append(GS);
                    stream.RestartStrip();
                }
            }

            float4 PSMain(Structure PS) : SV_Target
            {
                float k = frac(sin(PS.id)*43758.5453123);
                return float4(k.xxx, 1.0);
            }

            ENDCG
        }
    }
}

You can use AppendBuffer as StructuredBuffer inside HLSL shader. Example - it makes cubes from points inside Geometry Shader.

4453573--408526--upload_2019-4-21_12-48-57.png

6 Likes

Thank you!

1 Like