unity compute shader学习(一)
unity compute shader学习(一)
入门篇
创建compute shader
unity资产库中,右键创建一个compute shader。
默认的shader:
1 | // Each #kernel tells which function to compile; you can have many kernels |
AI注释:
1 | // 编译指令,指定CSMain为计算着色器的主函数 |
CSMain
函数最终会在GPU执行。
一个ComputeShader中 至少要有一个kernel才能够被唤起 。声明方法即为:
1 |
我们也可用它在一个ComputeShader里声明多个内核,可选择性地在 #pragma kernel
行后面添加要在编译该内核时定义的多个预处理器宏:
1 | #pragma kernel KernelOne SOME_DEFINE DEFINE_WITH_VALUE=1337 |
使用多个 #pragma kernel
行时,请注意在 #pragma kernel
指令的同一行上不允许 // text
样式的注释,如果使用,会导致编译错误。
1 | #pragma kernel functionName // 一些注释 |
定义线程(numthreads(8,8,1),意味着每个线程组包含8x8的线程网格来一起处理图像的一个小区块):
1 | numthreads(X, Y, Z) |
X
, Y
, Z
表示每个线程组在各自维度上的线程数目。这三个数值决定了每个线程组具体包含多少个线程。它们乘积得出的结果是该线程组中总的线程数。
- 这个数量决定了 GPU 上每次能并行运行多少个线程以及它们是如何组织的。
- 计算任务在 Compute Shader 中通常被分割成许多小块来在多个线程中并行执行,每个小块由一个线程组处理。其中,每个线程组又包含了多个线程,这些线程会一起工作完成指定的任务。
这里的 Z
被设置成 1 是因为在大多数二维计算任务中,第三维并不需要。
numthreads
还会影响到内存共享策略,因为同一个 Thread Group 中的线程可以通过共享内存快速交换数据,这比跨 Thread Group 通信要高效得多。
ps:也不是没有缺点,类似fragment shader,用线程块操作的时候无法获取到线程块以外的数据,那么会导致卷积算法出现接缝。
计算缓冲区compute buffer
ComputeShader程序通常需要将任意数据读取和写入内存缓冲区。
可以使用SystemInfo.supportsComputeShaders
来查询是否支持缓冲区(具体与shader model的版本有关),这个函数的返回值是一个bool。
具体使用方法详见[官方文档](Unity - 脚本 API:ComputeBuffer (unity3d.com))。
最常用的是RWStructuredBuffer<任意类型的数据、结构体>,RWTexture2D
BeginWrite | 开始对缓冲区执行写入操作 |
---|---|
EndWrite | 结束对缓冲区的写入操作 |
GetData | 将数据值从缓冲区读取到数组中。数组只能使用可流通类型。 |
GetNativeBufferPtr | 检索指向缓冲区的本机(基础图形 API)指针。 |
IsValid | 如果此计算缓冲区有效,则返回 true,否则返回 false。 |
Release | 释放计算缓冲区。 |
SetCounterValue | 设置 append/consume buffer 的计数器值。 |
SetData | 使用数组中的值设置缓冲区。 |
调用compute shader
可以在c#脚本中,使用Dispatch
来调用compute shader。
1 | public void Dispatch(int kernelIndex, int threadGroupsX, int threadGroupsY, int threadGroupsZ); |
参数解析
参数 | 解析 |
---|---|
内核索引 | 要执行的内核。单个计算着色器资产可以有多个内核入口点。通常是一个数字。 |
线程组X | X 维度中的工作组数。 |
线程组Y | Y 维度中的工作组数。 |
线程组Z | Z 维度中的工作组数。 |
可以使用 GetKernelThreadGroupSizes
函数查询工作组大小,使用FindKernel
查询kernel函数的序号。
1 | int FindKernel(string name); |
1 | void GetKernelThreadGroupSizes(int kernelIndex, out uint x, out uint y, out uint z); |
具体使用
例如:
1 | //class |
在 Compute Shader 中,你可以定义多个缓冲区,并通过名字来引用它们。在 Compute Shader 的 kernel 函数中,你可以使用 ulong
类型的 __global
变量作为缓冲区,并通过名字来引用它们。例如:
1 | RWStructuredBuffer<Particle> particleBuffer; |
在这个例子中,我们定义了一个名为 “particleBuffer” 的缓冲区,它是一个 Particle
类型的 RWStructuredBuffer
,表示一个可读写的结构体缓冲区。然后,在 kernel 函数中,我们通过名字 particleBuffer
来引用这个缓冲区,并使用 [id.x]
来访问第 id.x
个元素。
在 C# 代码中,我们通过 computeShader.SetBuffer(0, "particleBuffer", cBuffer);
将 C# 中的 cBuffer
设置为 Compute Shader 中的 “particleBuffer” 缓冲区。这样,在 Compute Shader 中,我们就可以通过 “particleBuffer” 来访问 C# 中的数据了。
1 |