ComputerShader是一种特殊的shader,得利于GPU强大的并行计算力,computershader可以轻松完成一些重复且量大的计算任务。
ComputerShader用法
ComputerShader部分:
ComputerShader是以.comCputer为后缀的文件。可以在Project面板中右键-Creat-Shader-ComputerShader来创建它。打开之后可以看到文件中自带的几行示例:
#pragma kernal CSMain :预编译命令,和我们熟悉的#pragma vertex vert类似,用于指定要编译的核心函数名,这里函数名就是CSMain,可以在c#中通过这个名称指定调用。
RWTexture2D<float4> Result :声明一张存储float4类型的纹理,RW代表是可读可写的;可写意味着我们会将它作为输出纹理来使用,与之对应的还有可读不可写的输入纹理Texture2D<>。除纹理之外,还可以声明其他类型的值如float、int、struct、RWStructuredBuffer<>等用于在c#中传递数据。
[numthreads(8,8,1)] :用于指定一个三维的线程组大小,x,y,z分量分别指定了三个维度的长度。关于线程、线程组、组内线程索引、线程组索引等易混淆的概念在下文中详细解释。
void CSMain(uint3 id : SV_DispatchTreadID){...} :ComputerShader的核心-函数主体部分,一个computershader可以有多个这样的函数,只需要在开头处加上相应的预编译命令#pragma kernal functionName即可。这个函数是没有返回值的,因为我们会把要输出的数据存储在可写纹理中,只需要在c#中申请rt并指定给computershader即可。
函数的参数unit3 id :SV_DispatchThreadID是unit3类型的线程id,关于线程编号如何来算下文详述。
c#部分:
要使用computershader,就需要在脚本中传递参数,然后创建commendBuffer来调用其中的函数。(在urp管线下)通过Project面板中右键-Creat-Rendering-URP Renderer Feature来创建一个脚本。关于RendererFeature的用法不是本文重点,不再详述。
通过CommandBuffer.SetComputeFloatParam()、CommandBuffer.SetComputeTextureParam()等函数可以向指定的ComputerShader传参数:
//首先在commandbuffer池中申请一个对象
CommandBuffer cmd = CommandBufferPool.Get("ComputerExample");
//根据colorBuffer的大小申请一个临时rt
int temptex = Shader.PropertToID("tempTex");
var camData = renderingData.cameraData;
var tempDesc = camData.cameraTargetDescriptor;
//打开随机写入
tempDesc.enableRandomWrite = true;
cmd.GetTemporaryRT(temptex,tempDesc);
//向computerShader传值
cmd.SetComputeFloatParam(myCS,"csFloatParam",1.52f);
cmd.SetComputeTextureParam(myCS,"Result",temptex); //将temptex作为输出rt传入
//执行函数
cmd.DispatchCompute(myCS,0,(int)tempDesc.width/10,(int)tempDesc.height/10,1);
//myCS为指定的computershader名,0为kernal索引(类似shader中的pass索引),后边三个参数为线程组参数
//还可以:cmd.Dispatch(kernalid,(int)tempDesc.width,(int)tempDesc.height,1);
//将结果绘制到colorBuffer中
cmd.Blit(tempDesc,Sour);
//释放commandBuffer
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
线程、线程组与调度
Thread(线程) :shader中最小的调度单元,一个执行中的函数即一个线程 。
ThreadGroup(线程组):由线程组成的三维数组,存储几十到几百个线程。上面的[numthreads(8,8,1)]便表示一个线程组,其中有64=8*8*1个线程。
Dispatch(调度) :调度的单位是线程组,一次调度会包含多个线程组,即调度是由线程组组成的三维数组,存储了几百到几千个线程。如c#脚本中的cmd.Dispatch(Kernelid,3,2,1),表示调度6=3*2*1个由[numthreads(8,8,1)]构成的线程组。
DispatchThreadID(调度线程ID):kernel函数默认的传入参数,是一个三个分量组成的数组,表示线程在所有线程调度中的绝对位置,即第x个线程组的第y号线程。具体算法很简单,用组号乘上每组内的线程数,再加上线程在组内的相对位置即可:
DispatchThreadID = GroupThreadID+GroupID*numthreads
以上图这个位置为例,它在组内的相对位置为(2,1,0),它所在的线程组的位置为(0,1,0),因此它的调度线程ID为:(2,5,0)=(2,1,0)+(0,1,0)*(4,4,3)。
GroupThreadID(组内线程ID)和GroupID(线程组ID):kernel函数接受传入参数时可以选择SV_GroupThreadID和SV_GroupID来接受组内线程id和线程组id作为参数。它们也是一个三分量的数组,分别表示线程在组内的相对位置和线程所在线程组组在调度中的位置。
ComputerShader是一个灵活且强大的工具,可以用它来实现自定义的后处理效果,还可以利用它快速并行运算的特点来构建一个粒子系统,用来实现粒子流体、粒子特效等。