DirectCompute tutorial for Unity 5: Append buffers | Cheney Shen

Technology blog

DirectCompute tutorial for Unity 5: Append buffers

In today’s tutorial I will be expanding on the topic of buffers covered in the last tutorial by introducing append buffers. These buffers offer greater flexibility over structured buffers by allowing you to dynamically increase their size during run time from a compute or Cg shader.

(append buffer特点是灵活,size可变)

 

The great thing about these buffers is that they can still be used as structured buffers which makes the actually rendering of their contents simpler. Start of by creating a new shader and pasting in this code. This is what we will be using to draw the points we add into the buffer. Notice the buffer is just declared as a structured buffer.

(使用方式和standard一致,下面是shader代码)

 

 

Now lets make a script to create our buffers. Create a new C# script, paste in this code and then drag it onto the main camera in a new scene.

(下面是C#代码)

 

 

Notice this line here…

 

 

This is the creation of a append buffer. It must be of the type “ComputeBufferType.Append” unsurprisingly. Notice we still pass in a size for the buffer (the width * width parameter). Even though append buffers need to have their elements added from a shader they still need to have a predefined size. Think of this as a reserved area of memory that the elements can be added to. This also raises a subtle error that can arise which I will get to later.

(创建append buffer,这里要注意的是还是要设置一个初始大小。)

 

The append buffer starts of empty and we need to add to it from a shader. Notice this line here…

 

Here we are running a compute shader to fill our buffer. Create a new compute shader and paste in this code.

(我们用一个shader来给这个buffer填充数据)

 

 

This will fill our buffer with a position for each thread that runs. Nothing fancy. Notice this line here…

 

 

The “Append(pos)” is the line that actually adds a position to the buffer. Here we are only adding a position if the x and y dispatch id are even numbers. This is why append buffers are so useful. You can add to them on any conditions you wish from the shader.

(这里注意:Append方法就是在buffer里面加一个position。这里就是当x,y超出范围的时候采用这个方法来添加。)

 

The dynamic contents of a append buffer can cause a problem when rendering however. If you remember from last tutorial we rendered a structured buffer using this function…

(这里buffer的动态增长会导致一些问题)

 

Unity’s “DrawProcedual” function needs to know the number of elements that need to be drawn. If our append buffers contents are dynamic how do we know how many elements there are?

(Graphics.DrawProcedural方法需要提前知道buffer size,但是上面的append buffer size是不确定的。)

 

To do that we need to use a argument buffer. Take a look at this line from the script…

(因此我们用到了argument buffer)

 

Notice it has to be of the type “ComputeBufferType.DrawIndirect“, it has 4 elements and they are integers. This buffer will be used to tell Unity how to draw the append buffer using the function “DrawProceduralIndirect“.

(这个buffer会告诉unity buffer的最终大小,绘制方法要改用DrawProceduralIndirect方法)

 

These 4 elements represent the number of vertices, the number of instances, the start vertex and the start instance. The number of instances is simply how many times to draw the buffer in a single shader pass. This would be useful for something like a forest where you have a few trees that need to be drawn many times. The start vertex and instance just allow you to adjust where to draw from.

(DrawProceduralIndirect的参数分别是:instance数量,instance大小,开始顶点,开始instance。这种方式特别适合绘制森林这样的少量对象绘制多次的情况,每次只需要位置变化。)

 

These values can be set from the script. Notice this line here…

(argBuffer数值设置方法一,一般buffer填充四个值再copy给argBuffer)

 

 

Here we are just telling Unity to draw one instance of our buffer starting from the beginning. Its the first number that is the important one however. Its the number of vertices and see its set to 0. This is because we need to get the exact number of elements that are in the append buffer. This is done with the following line…

 

This copies the number of elements in the append buffer into our argument buffer. At this stages its important to make sure everything’s working correctly so we will also get the values in the argument buffer and print out the contents like so…

(第二种方式就是直接赋值,Debug.Log告诉你这四个值啥含义。)

 

Now run the scene, making sure you have bound the shader and material to the script. You should see the vertex count as 256. this is because we ran 1024 threads with the compute shader and we added a position only for even x and y id’s which ends up being the 256 points you see on the screen.

(跑结果,会得到vertex count达到了256.也就是执行了1024线程)

 

Now remember I said there was a subtle error that can arise and it has to do with the buffers size? When the buffer was declared we set its size as 1024 and now we have entered 256 elements into it. What would happen if we added more that 1024 elements? Nothing good. Appending more elements than the buffers size causes a error in the GPU’s driver. This causes all sorts of issues. The best thing that could happen is that the GPU would crash. The worst is a persistent error in the calculation of the number of elements in the buffer that can only be fixed by restarting Unity and means that the buffer will not be draw correctly with no indication as to why. Whats worse is that since this is a driver issue you may not experience the issue the same way on different computers leading to inconstant behavior which is always hard to fix.

(注意append buffer有最大限制,就是看当前支持多少线程并行,像这里大于1024就会挂。)

 

This is why I have printed out the number of elements in the buffer. You should always check to make sure the value is within the expected range.

(使用的时候最好先检查一下GPU支持情况)

 

Once you have copied the vertex count to the argument buffer you can then draw the buffer like so…

(使用了argument buffer后你就可以用下面的函数绘制结果了)

 

Your not limited to filling a compute buffer from a compute shader. You can also fill it from a Cg shader. This is useful if you want to save some pixels from a image for some sort of further post effect. The catch is that since you are now using the normal graphics pipeline you need to play by its rules. You need to output a fragment color into a render texture even if you don’t want to. As such if you find yourself in that situation it means that whatever your doing is probably better done from a compute shader. Other than that the process works much the same but like everything there are a few things to be careful of.

(另一种新的写法如下:)

 

Create a new shader and paste in this code…

 

 

Notice that the fragment shader is much the same as the compute shader used previously. Here we are just adding a position to the buffer for each pixel rendered if the uv (as the id) is a even number on both the x and y axis. Don’t ever try and create a material from this shader in the editor! The reason is that Unity will try and render a preview image for the material and will promptly crash. You need to pass the shader to a script and create the material from there.

(和原来很像,但是增加了每个pixel的buffer的position)

 

Create a new C# script and paste in the following code…

 

 

Again you will see that this is very much like the previous script using the compute shader. There are a few differences however.

(你会发现和之前的代码很像,略有区别)

 

 

Instead of the compute shader dispatch call we need to use Graphics blit and we need to bind and unbind the buffer. We also need to provide a render texture as the source destination for Graphics blit. This makes the process Pro only unfortunately.

(不再采用dispatch方法,而是采用Graphics的方法来替代。Graphics blit就是来生成render texture)

 

Attach this script to the main camera in a new scene and run the scene after binding the material and shader to the script. It should look just like the previous scene (a grid of points) but they will be blue.

(跑这个新代码会得到和原来一样的效果。)

 

 


One Comment

Post a Comment

Your email address will not be published. Required fields are marked *

  • Categories

  • Tags