Unity Shader:Compute Shader详解
发表于2018-03-20
Compute Shaders是在GPU运行却又在普通渲染管线之外的程序,通过Compute Shader我们可以将大量可以并行的计算放到GPU中计算从而节省CPU资源,Unity 5.6版本提供的 Graphics.DrawMeshInstancedIndirect 接口可以非常方便的配合ComputeShader做大规模渲染。下面就和大家介绍下Compute Shader。
首先将一些Compute Shader中不同于普通Shader的概念梳理下:
numthreads(MSDN)
个人理解:
numthreads 定义了一个三维的线程结构
如果我们在程序的Dispatch接口发送了(5,3,2)这样的结构,就会生成5x3x2个线程组,其中每个组的线程结构由ComputeShader中的numthreads定义,图中numthreads定义了10x8x3的三维结构,由此,我们可以分析4个HLSL关键词的定义。
- SV_GroupThreadID 表示该线程在该组内的位置
- SV_GroupID 表示整个组所分配的位置
- SV_DispatchThreadID 表示该线程在所有组的线程中的位置
- SV_GroupIndex 表示该线程在该组内的索引
通过这些关键词,我们可以在并行计算时获取其他线程的输入数据
如果是计算4X4的矩阵加法,可以定义为4X4X1的numthreads结构,这样线程的索引会自动匹配输入的矩阵,同样,我们可以定义16X1X1的结构,但这样只能基于当前线程数去计算输入矩阵(原文是 however it would then have to calculate the current matrix entry based on the current thread number. 没太理解)
- SM4.5 允许numthreads最多768条线程
- SM5.0 允许numthreads最多1024条线程
Sampler
sampler在ComputeShader中的定义与普通Shader略有不同,常用的DX9的声明方法在ComputeShader中不再适用,贴图采样需使用DX10/11中的方法
又因为贴图的Mip level在compute shader中没有定义,因此无法将线程数匹配到具体像素,必须自己定义Mip level,所以使用Texture.SampleLevel 或者 Texture.Load 来采样,几何着色器和顶点着色器同理。
Example
我们首先在C#脚本中和Shader中定义同样的结构体
public struct MyInstance{ public Vector3 color; public Vector3 position; public Vector3 velocity; public Vector3 scale; }
struct _myIns{ float3 color; float3 position; float3 velocity; float3 scale; };
在C#脚本中初始化ComputeBuffer并赋值到Compute Shader和渲染用的普通Shader中
void InitBuffer() { argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments); uint numIndices = meshInstance.GetIndexCount(0); args[0] = numIndices; args[1] = (uint)num; argsBuffer.SetData(args); instanceBuffer = new ComputeBuffer(num, MySize.SizeOfFloat3*4); _instance = new MyInstance[num]; for (int i = 0; i < num; i++) { MyInstance mi = new MyInstance(); mi.color = new Vector3(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f)); mi.position = Random.insideUnitSphere * Radius; mi.velocity = Random.insideUnitSphere; mi.scale = Vector3.one; _instance[i] = mi; } instanceBuffer.SetData(_instance); matinstance.SetBuffer("positionBuffer", instanceBuffer); //compute shader init _kernel = insCompute.FindKernel("CSMain"); if (_kernel == -1) { Debug.LogError("Failed to find kernel"); return; } insCompute.SetBuffer(_kernel, "inss", instanceBuffer); insCompute.SetFloat("deltaTime", Time.fixedDeltaTime); insCompute.SetFloat("radiu", Radius); insCompute.SetTexture(_kernel, "noiseTex", noiseTex); }
ComputeShader我们简单的使用了128x1x1的线程结构
float deltaTime; float radiu; RWStructuredBuffer<_myIns> inss; Texture3D<float4> noiseTex; SamplerState samplernoiseTex { Filter = MIN_MAG_MIP_LINEAR; AddressU = Wrap; AddressV = Wrap; }; [numthreads(BLOCKSIZE,1,1)] void CSMain (uint3 id : SV_DispatchThreadID) { // TODO: insert actual code here! uint i = id.x; uint num, stride; inss.GetDimensions(num, stride); float3 position = inss[i].position; float3 velocity = inss[i].velocity; float3 ns = inss[i].scale; float3 uv = float3(abs(position.x),abs(position.y),abs(position.z))/radiu; ns = noiseTex.SampleLevel(samplernoiseTex,uv,0); //caculate position += 5 * velocity * deltaTime; if(i < num) { inss[i].position = position; inss[i].velocity = velocity; inss[i].scale = ns*ns; } }
普通Shader中通过SV_InstanceID获取GPU Instance索引
v2f vert (appdata_full v, uint instanceID : SV_InstanceID) { #if SHADER_TARGET >= 45 _myIns data = positionBuffer[instanceID]; #else _myIns data = 0; #endif float3 localPosition = v.vertex.xyz * data.scale; float3 worldPosition = data.position + localPosition; float3 worldNormal = v.normal; float3 ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz)); float3 ambient = ShadeSH9(float4(worldNormal, 1.0f)); float3 diffuse = (ndotl * _LightColor0.rgb); float3 color = data.color; v2f o; o.pos = mul(UNITY_MATRIX_VP, float4(worldPosition, 1.0f)); o.uv_MainTex = v.texcoord; o.ambient = ambient; o.diffuse = diffuse; o.color = color; TRANSFER_SHADOW(o) return o; }
最后在Update中通过DrawMeshInstancedIndirect进行绘制
private void Update() { var numOfGroups = Mathf.CeilToInt((float)num / GroupSize); insCompute.Dispatch(_kernel, numOfGroups, 1, 1); Bounds bs = new Bounds(transform.position, Vector3.one * Radius); Graphics.DrawMeshInstancedIndirect(meshInstance, 0, matinstance, bs, argsBuffer); }
最终运行结果如下:
来自:http://blog.csdn.net/notmz/article/details/75547759