All ComputeShader GPU

DirectCompute tutorial for Unity 4: Buffers

In DirectCompute there are two types of data structures you will be using, textures and buffers. Last tutorial covered textures and today I will be covering buffers. While textures are good for data that needs filtering or mipmaping like color information, buffers or more suited to representing information for vertices like position or normals. Buffers can also easily send or retrieve data from the GPU which is a feature that’s rather lacking in Unity.



There are 5 types of buffers you can have, structured, append, consume, counter and raw. Today I will only be covering structured buffers because this is the most commonly used type. I will try and cover the others in separate tutorials as they are a little more advanced and I need to cover some of the basics first.

(有五种类型的buffer你可以用:structured, append, consume, counter and raw,这一篇只讲structured buffer)


So let’s start of by creating a C# script, name it BufferExample and paste in the following code.

t003 - 01


We will also need a material to draw our buffer with so create a normal Cg shader, paste in the follow code and create a material out of it.


t003 - 02


Now to run the scene attach the script to the main camera node and bind the material to the material attribute on the script. This script has to be attached to a camera because to render a buffer we need to use the “OnPostRender” function which will only be called if the script is on a camera (EDIT – turns out you can use “OnRenderObject” if you don’t want the script attached to a camera). Run the scene and you should see a number of random red points.


t003 - 03



Now notice the creation of the buffer from this line.


buffer = new ComputeBuffer(count, sizeof(float)*3, ComputeBufferType.Default);

The three parameters passed to the constructor are the count, stride and type. The count is simply the number of elements in the buffer. In this case 1024 points. The stride is the size in bytes of each element in the buffer. Since each element in the buffer represents a position in 3D space we need a 3 component vector of floats. A float is 4 bytes so we need a stride of 4 * 3. I like to use the sizeof operator as I think it makes the code more clear. The last parameter is the buffer type and it is optional. If left out it creates a buffer of default type which is a structured buffer.

(三个参数:elements数量,每个elements大小,类型默认就是structured buffer)


Now we need to pass the positions we have made to the buffer like so.




This passes the data to the GPU. Just note that passing data to and from the GPU can be a slow processes and in general it is faster to send data than retrieve data.


Now we need to actually draw the data. Drawing buffers has to be done in the “OnPostRender” function which will only be called if the script is attached to the camera. The draw call is made using the “DrawProcedural” function like so…




material.SetBuffer("buffer", buffer);

Graphics.DrawProcedural(MeshTopology.Points, count, 1);

There are a few key points here.

The first is that the materials pass must be set before the DrawProcedural call. Fail to do this and nothing will be drawn. You must also bind the buffer to the material but you only have to do this once, not every frame like I am here. Now have a look at the “DrawProcedural” function. The first parameter is the topology type and in this case I am just rendering points but you can render the data as lines, line strips, quads or triangles. You must however order them to match the topology. For example if you render lines every two points in the buffer will make a line segment and for triangles every three points will make a triangle. The next two parameters are the vertex count and the instance count. The vertex count is just the number vertices you will be drawing, in this case the number of elements in the buffer. The instance count is how many times you want to draw the same data. Here we are just rendering the points once but you could render them many times and have each instance in a different location.

(材质设置必须在call DrawProcedural 函数之前,不然绘不出来。)

(DrawProcedural函数参数:拓扑结构类型;vertex count;instance count)



t003 - 04

Now for the material. This is pretty straight forward. You just need to declare your buffer as a uniform like so…



uniform StructuredBuffer<float3> buffer;

Since buffers are only in DirectCompute you must also set the shader target to SM5 like so…



#pragma target 5.0

The vertex shader must also have the argument “uint id : SV_VertexID“. This allows you to access the correct element in the buffer like so…

(vertex shader存在SV_VertexID这个参数可以让你很容易的进入目标element)


float4 pos = float4(buffer[id], 1);

Buffers are a generic data structure and don’t have to be floats like in this example. We could use integers instead. Change the scripts start function to this…



buffer = new ComputeBuffer(count, sizeof(int)*3, ComputeBufferType.Default);

int[] points = new int[count*3];

Random.seed = 0;

for(int i = 0; i < count; i++)


    points[i*3+0] = (int)Random.Range(-size,size);

    points[i*3+1] = (int)Random.Range(-size,size);

    points[i*3+2] = 0;




and the shaders uniform declaration to this…

uniform StructuredBuffer<int3> buffer;

You could even use a double but just be aware that double precision in shaders is still not widely supported on GPU’s although its common on newer cards.



You are not limited to using primitives like float or int. You can also use Unity’s Vectors like so…

(你还可以使用unity vectors数据结构!!!)


buffer = new ComputeBuffer (count, sizeof(float) * 3, ComputeBufferType.Default);

Vector3[] points = new Vector3[count];

Random.seed = 0;

for (int i = 0; i < count; i++)


    points[i] = new Vector3();

    points[i].x = Random.Range (-size, size);

    points[i].y = Random.Range (-size, size);

    points[i].z = 0;


buffer.SetData (points);


With the uniform as…

uniform StructuredBuffer<float3> buffer;

You can also create you own structs to use.  Change your scripts start function to this with the struct declaration above…



struct Vert


    public Vector3 position;

    public Vector3 color;


void Start ()


    buffer = new ComputeBuffer (count, sizeof(float) * 6, ComputeBufferType.Default);

    Vert[] points = new Vert[count];

    Random.seed = 0;

    for (int i = 0; i < count; i++)


        points[i] = new Vert();

        points[i].position = new Vector3();

        points[i].position.x = Random.Range (-size, size);

        points[i].position.y = Random.Range (-size, size);

        points[i].position.z = 0;

        points[i].color = new Vector3();

        points[i].color.x = Random.value > 0.5f ? 0.0f : 1.0f;

        points[i].color.y = Random.value > 0.5f ? 0.0f : 1.0f;

        points[i].color.z = Random.value > 0.5f ? 0.0f : 1.0f;


    buffer.SetData (points);



and the shader to this…

Shader "Custom/BufferShader"






            ZTest Always Cull Off ZWrite Off

            Fog { Mode off }


            #include "UnityCG.cginc"

            #pragma target 5.0

            #pragma vertex vert

            #pragma fragment frag

            struct Vert


                float3 position;

                float3 color;


            uniform StructuredBuffer<Vert> buffer;

            struct v2f


                float4  pos : SV_POSITION;

                float3 col : COLOR;


            v2f vert(uint id : SV_VertexID)


                Vert vert = buffer[id];

                v2f OUT;

                OUT.pos = mul(UNITY_MATRIX_MVP, float4(vert.position, 1));

                OUT.col = vert.color;

                return OUT;


            float4 frag(v2f IN) : COLOR


                return float4(IN.col,1);







This will draw each point like before but now they will also be random colors. Just be aware that you need to use a struct not a class.



When using buffers you will often find you need to copy one into another. This can be easily done with a compute shader like so…



#pragma kernel CSMain

StructuredBuffer<float> buffer1;

RWStructuredBuffer<float> buffer2;


void CSMain (uint id : SV_DispatchThreadID)


    buffer2[id] = buffer1[id];



You can see that buffer1 is copied into buffer2. Since buffers are always 1 dimensional it is best done from a 1 dimension thread group.



Just like textures are objects so to are buffers and they have some functions that can be called from them. You can use the load function to access a buffers element just like with the subscript operator.



buffer2[id] = buffer1.Load(id);

Buffers also have a “GetDimension” function. This returns the number of elements in the buffer and their stride.



uint count, stride;

buffer2.GetDimensions(count, stride);