UnityShader——初探Compute Shader

来源:互联网 发布:linux c执行shell命令 编辑:程序博客网 时间:2024/05/18 04:45

Compute Shader是基于DX11(SM4.5+)的在GPU上运行的程序,通过Compute Shader我们可以将大量可以并行的计算放到GPU中计算从而节省CPU资源,Unity 5.6版本提供的 Graphics.DrawMeshInstancedIndirect 接口可以非常方便的配合ComputeShader做大规模渲染。

先将一些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);    }

最终运行结果如下:


原创粉丝点击