[toc]本篇讨论UnityGraphicsPrograming第一册第二章内容 ComputeShader(CS)语法 参考资料: https://docs.unity3d.com/Manual/class-ComputeShader.html https://docs.unity3d.com/Manual/SL-SamplerStates.html 在Manul左侧的目录中也存在很多值的参考的消息 ▲Thread相关常数 参考资料:微软DX官方文档 Kernel:一个GPU任务,需要在CS代码中声明;computeShader.FindKernel(“KernelName”); Thread Group:Kernel的执行单元;computeShader.Dispatch(kernelID, a, b, c); 同时执行的Thread Group数为a * b * c,左上角为(0,0,0),右为X轴正方形,下为Y轴正方向,Z表示阵列厚度; SV_GroupID:类似的宏表示CS代码中的常数;组坐标;Thread所在的Thread Group的坐标; 根据情况,如果只指定x和y值不为1,有助于提升效率。 Thread:Thread Group的执行单元;[numthreads(x, y, z)]; 单个Thread Group中的Thread数为x * y * z,左上角为(0,0,0),右为X轴正方形,下为Y轴正方向,Z表示阵列厚度; SV_GroupThreadID:组内线程坐标;Thread在自己的Thread Group中的坐标; SV_DispatchThreadID:Thread在所有Thread中的坐标;可以理解为将一个长方体切成a * b * c份,每一份再切成x * y * z份,某个Thread在这个类似于坐标系的长方体中也可以用三维坐标表示:SV_DispatchThreadID = (x,y,z) * SV_GroupID + SV_GroupThreadID。 SV_GroupIndex:Thread所在的Thread Group的Index;排序方式为从每一页的左上角开始,右数至末端后从下一排左端开始,右数至最后一排时从下一页开始。 ▲CS语法 CS语法和Shader语法有很多类似之处,读者应熟练掌握Shader语法后再尝试CS语法。 Kernel声明: #pragma kernel KernelFunction 类似于Shader的Pass中定义vertex、geometry、fragment,这里的kernel声明了一个GPU任务(Kernel)的名称并指定了执行方法,在C#脚本中对Kernel的Dispatch会直接执行这个函数。 Kernel的ID:根据定义先后顺序,0,1…进行赋值。 包含声明: #include “SimplexNoise3D.hlsl” 这个完全和Shader语法一致。 变量声明: RWStructuredBuffer intBuffer; RWStructuredBuffer _OutPositions; RWTexture2D textureBuffer; StructuredBuffer _InPositions; int intValue; float floatValue; 在CS代码中的计算可能涉及到标量、向量、矩阵、纹理、数组、常量、float、bool等,这些变量都有读取(赋值)和保存(取值)需求。 RW类型的Buffer支持读/写,而StructuredBuffer只支持读; 函数声明: float4 taylorInvSqrt(float4 r) { return 1.79284291400159 - r * 0.85373472095314; } 几乎都有retrun。 Kernel函数声明: [numthreads(4, 1, 1)] void KernelFunction_B(uint3 groupThreadID : SV_GroupThreadID) { intBuffer[groupThreadID.x] += 1; } 没有return,函数主要功能为向Buffer写入数据。 numthreads标签:声明单个Thread Group的Tread阵列; SV_GroupThreadID:访问Thread的恒量,每个语法需参考其定义,只能从有限的语法中选择。 Texture读写: RWTexture2D textureBuffer; //Texture2D声明 float width, height; textureBuffer.GetDimensions(width, height); //获取Texture2D实例的像素宽高 textureBuffer[dispatchThreadID.xy] = float4(0,0,0,1); //Texture2D的写入 ▲Compute Shader实例用法 CS代码需要配套的C#代码进行驱动,让本来应该由CPU计算的活传递给GPU负责。 指定Compute Shader实例: public ComputeShader computeShader; ComputeShader这个类不能直接实例化,必须在编辑模式下指定好。 获取Kernel的ID: int kernelID = computeShader.FindKernel(“Kernel_A”); 一个CS文件中可以定义多个Kernel,我们通过字符串标记来区别。 为Buffer赋值: 在CS文件中声明的Buffer并不会被Unity直接声明,我们需要主动开辟这块内存并向里面赋值; ComputeBuffer intComputeBuffer = new ComputeBuffer(4, sizeof(int)); Buffer中可以存储任意结构体,单个数据的长度和数据数量必须指定。 public struct someData {} //声明一个值类型 NativeArray data = new NativeArray(4, Allocator.Temp); //创建值类型数组 for (int i = 0; i < 4; ++i) { data[i] = i; //为数组赋值 } intComputeBuffer.SetData(data); //填充Buffer cmd.SetGlobalBuffer(m_intComputeBufferId, intComputeBuffer); 如果一个Buffer是全局变量,可以在不同shader、computeshader中使用。 创建ColorBuffer: RenderTexture rt = new RenderTexture(512, 512, 0, RenderTextureFormat.ARGB32); rt.enableRandomWrite = true; //SM5.0 或者CS中必须开启 rt.Create(); computeShader.SetTexture(kernelID, “textureBuffer”, rt); //关联CS变量 有图片专用缓存 指定Kernel执行所需的参数: computeShader.SetBuffer(kernelID, “intBuffer”, intComputeBuffer); computeShader.SetTexture(kernelID, “textureBuffer”, renderTexture); 为Buffer填充数据时需要指定Kernel、Buffer名、Buffer实例; computeShader.SetInt(“intValue”, 1); 不同于Buffer类型变量,我们在填充float和float数组等变量时无需指定Kernel。 执行Kernel: computeShader.Dispatch(kernelID, 1, 1, 1); 获取Buffer中的数据: int[] result = new int[4]; intComputeBuffer.GetData(result); 释放Buffer: intComputeBuffer.Release(); 既然new了,记得release;如果Buffer尺寸固定的,设置为静态的也可以。 ▲GPU结构与变量存储 SP(streaming processor):最基本的处理单元,可执行一个Thread;图中绿色框框(Core);一个SM中根据不同构架包含100个左右的SP,硬件上分为多个warp。 warp:调度和运行的基本单元,比如图中32个SP组成一个区块,所有Thread执行相同指令,可以空闲。 SM(streaming multiprocessor):GPU大核,比如一个GPU有16个SM。 Register:与SP相对的存储区域,存储函数内引用的本地变量,离SP最近,访问速度最快,以4byte为单位构成。 Shared Memory:与SM相对的存储区域,与L1缓冲一同被管理 Global Memory on DRAM:在DRAM中,容量很大,访问速度相对慢。 local memory:在DRAM中,存储Register中装不下的数据。 texture memory:将Global Memory作为texture的专用存储区域。 constant memory:读取专用存储区域,预先保存Kernel的引数和定数。 registers(Read-write per-thread) local memory(Read-write per-thread) shared memory(Read-write per-block) global memory(Read-write per-grid) constant memory(Read-only per-grid) texture memory(Read-only per-grid) ▲HLSL编程指南 很多细节是没法在书中展开的,需要仔细阅读微软的HLSL编程指南。 ▲Variable Syntax变量声明语法 HLSL变量类似于C语言,变量有一些命名限制,根据变量声明的位置决定了变量的作用域属性,可以将user metadata附加到变量。HLSL中有几种标准数据类型,也定义了C语言中没有的额外类型,以帮助优化4元矩阵计算。 [Storage_Class] [Type_Modifier] Type Name[Index] [: Semantic] [: Packoffset] [: Register]; Storage_Class 提示编译器 extern: 将全局变量标记为着色器的外部输入;这是所有全局变量的默认标记。 nointerpolation: 将vertex shader的outputs传递给pixel shader时,不进行差值。 precise: shared: groupshared: 变量存储于thread-group-shared memory,DX10中最多16kb,DX11中最多32kb。 在对groupshared变量进行写入时,多Threads之间没有同步,所以这意味着每个Thread只有对groupshared数组变量的部分区域的写入权限。使用SV_GroupIndex以保证多Threads之间不会发生冲突。在读取方面,所有Threads都有对数组的读取权限。 static: 标记一个local变量为static,初始化一次,在函数调用之间保持值不变。如果声明中没有初始化则值设置为0. uniform: 标记一个变量,在shader执行过程中其数据为恒量。 global变量默认为uniform。 volatile: 标记一个变量,这个变量频繁变更,提示编辑器将变量作为local变量。 Type_Modifier const row_major column_major:默认值 Type 变量类型 参考HLSL Data Types。 float/float4 name = {0,0,1,1} int/int name[3] = {1,2,3} Name[Index] 使用字符串作为shader变量的唯一标识。 定义数组类型变量时,Index表示数组长度。 Semantic 可选,参数的用法信息,用于连接到shader的输入/输出。 有几种预定义的Semantic供vertex/pixel shader使用。 Packoffset 可选关键字,Packing Rules for Constant VariablesRegister 可选关键字,将shader变量手动分配到指定RegisterInitial_Value 可选的初始值,值的数量应与声明匹配。 extern类型global变量必须初始化为a literal value(字面意思值?)。 static类型变量必须初始化为常量。 全局变量没标记为static或extern时,不会被编译到shader。 编译器不会自动设置全局变量的默认值,不会在优化中使用全局变量。