UnityShader——初探Compute Shader

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

先将一些Compute Shader中不同于普通Shader的概念梳理下:


numthreads(MSDN)
个人理解:
numthreads 定义了一个三维的线程结构,


UnityShader——初探Compute Shader_第1张图片

如果我们在程序的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中的方法


UnityShader——初探Compute Shader_第2张图片

又因为贴图的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 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);
    }

最终运行结果如下:


UnityShader——初探Compute Shader_第3张图片

你可能感兴趣的:(U3D)