compute shader

computer shader是在显卡上运行的程序,在正常的渲染管道之外。被用于大量并行的gpu算法,或加速部分游戏渲染。想要高效利用他们,最好深入的理解cpu机制和并行算法。还有DirectCompute,OpenGL Compute,CUDA或openCL。
unity 的compute shader很像DX11 DirectCompute技术。能工作的平台有:
1. windows,有Dx11或Dx12显卡Api,shader model 4.5 gpu;
2. 用metal显卡api的macOS和ios;
3. Android linux和windows有Vulkan Api;
4. 现代openGL平台(openGL 4.3在linux和windows;gl es 3.1在安卓)。注意mac os x不支持opengl 4.3;
5. 现代控制台(sony ps4和微软xbox one)
运行时判断是否支持compute shader可以用SystemInfo.supportsComputeShaders。

compute shader资源

类似于普通的shader,compute shader在工程里也是资源文件,.compute扩展名。他们是用Dx11风格的hlsl语言缩写。用#pragma 编译指令指定哪些很少被当成compute shader核心编译,如下:

#pragma kernel KMain
[numthreads(8, 8, 1)]
void KMain(uint2 groupId : SV_GroupID, uint2 groupThreadId : SV_GroupThreadID, uint2 dispatchThreadId : SV_DispatchThreadID)
{
    // Upper-left pixel coordinate of quad that this thread will read
    int2 threadUL = (groupThreadId << 1) + (groupId << 3) - 4;

    // Downsample the block
    float2 offset = float2(threadUL);
    float4 p00 = _Source.SampleLevel(sampler_LinearClamp, (offset                    + 0.5) * _Size.zw, 0.0);
    float4 p10 = _Source.SampleLevel(sampler_LinearClamp, (offset + float2(1.0, 0.0) + 0.5) * _Size.zw, 0.0);
    float4 p01 = _Source.SampleLevel(sampler_LinearClamp, (offset + float2(0.0, 1.0) + 0.5) * _Size.zw, 0.0);
    float4 p11 = _Source.SampleLevel(sampler_LinearClamp, (offset + float2(1.0, 1.0) + 0.5) * _Size.zw, 0.0);

    // Store the 4 downsampled pixels in LDS
    uint destIdx = groupThreadId.x + (groupThreadId.y << 4u);
    Store2Pixels(destIdx     , p00, p10);
    Store2Pixels(destIdx + 8u, p01, p11);

    GroupMemoryBarrierWithGroupSync();

    // Horizontally blur the pixels in LDS
    uint row = groupThreadId.y << 4u;
    BlurHorizontally(row + (groupThreadId.x << 1u), row + groupThreadId.x + (groupThreadId.x & 4u));

    GroupMemoryBarrierWithGroupSync();

    // Vertically blur the pixels in LDS and write the result to memory
    BlurVertically(dispatchThreadId, (groupThreadId.y << 3u) + groupThreadId.x);
}

表示kMain函数被当作compute shader编译,以及:

// test.compute

#pragma kernel FillWithRed

RWTexture2D<float4> res;

[numthreads(1,1,1)]
void FillWithRed (uint3 dtid : SV_DispatchThreadID)
{
    res[dtid.xy] = float4(1,0,0,1);
}

语言是标准dx 11 hlsl语言。一个compute shader资源文件必须包含至少一个会被调用的compute kernel。可以有多个,写多行#pragma语句即可。
当用#pragma时,注意同一行加“// 。。”这种注释会产生编译错误。
#pragma后面可以跟这个shader编译需要的宏

#pragma kernel KernelOne SOME_DEFINE DEFINE_WITH_VALUE=1337
#pragma kernel KernelTwo OTHER_DEFINE

调用compute shader

  1. 在你的脚本中,定义一个ComputeShader类型的变量,给这个资源一个引用。
    如下在resource类里定义ComputeShader,如下:
public sealed class ComputeShaders
        {
            public ComputeShader exposureHistogram;
            public ComputeShader lut3DBaker;
            public ComputeShader texture3dLerp;
            public ComputeShader gammaHistogram;
            public ComputeShader waveform;
            public ComputeShader vectorscope;
            public ComputeShader multiScaleAODownsample1;
            public ComputeShader multiScaleAODownsample2;
            public ComputeShader multiScaleAORender;
            public ComputeShader multiScaleAOUpsample;
            public ComputeShader gaussianDownsample;
        }
  1. 在resource界面,将compute shader赋过来:
    这里写图片描述
  2. 调用代码如下:先设置参数,再用ComputeShader.Dispatch方法调用。从unity脚本文档里查ComputeShader类的使用。如下CommandBuffer用

public void DispatchCompute(ComputeShader computeShader, int kernelIndex, int threadGroupsX, int threadGroupsY, int threadGroupsZ);
调用computeShader:

void PushUpsampleCommands(CommandBuffer cmd, int lowResDepth, int interleavedAO, int highResDepth, int? highResAO, RenderTargetIdentifier dest, Vector3 lowResDepthSize, Vector2 highResDepthSize, bool invert = false)
        {
            var cs = m_Resources.computeShaders.multiScaleAOUpsample;
            int kernel = cs.FindKernel(highResAO == null
                ? invert
                    ? "main_invert"
                    : "main"
                : "main_blendout");

            float stepSize = 1920f / lowResDepthSize.x;
            float bTolerance = 1f - Mathf.Pow(10f, m_Settings.blurTolerance.value) * stepSize;
            bTolerance *= bTolerance;
            float uTolerance = Mathf.Pow(10f, m_Settings.upsampleTolerance.value);
            float noiseFilterWeight = 1f / (Mathf.Pow(10f, m_Settings.noiseFilterTolerance.value) + uTolerance);

            cmd.SetComputeVectorParam(cs, "InvLowResolution", new Vector2(1f / lowResDepthSize.x, 1f / lowResDepthSize.y));
            cmd.SetComputeVectorParam(cs, "InvHighResolution", new Vector2(1f / highResDepthSize.x, 1f / highResDepthSize.y));
            cmd.SetComputeVectorParam(cs, "AdditionalParams", new Vector4(noiseFilterWeight, stepSize, bTolerance, uTolerance));

            cmd.SetComputeTextureParam(cs, kernel, "LoResDB", lowResDepth);
            cmd.SetComputeTextureParam(cs, kernel, "HiResDB", highResDepth);
            cmd.SetComputeTextureParam(cs, kernel, "LoResAO1", interleavedAO);

            if (highResAO != null)
                cmd.SetComputeTextureParam(cs, kernel, "HiResAO", highResAO.Value);

            cmd.SetComputeTextureParam(cs, kernel, "AoResult", dest);

            int xcount = ((int)highResDepthSize.x + 17) / 16;
            int ycount = ((int)highResDepthSize.y + 17) / 16;
            cmd.DispatchCompute(cs, kernel, xcount, ycount, 1);
        }

和compute shader联系紧密的是compute buffer,用法如下

if (m_Data == null)
                m_Data = new ComputeBuffer(m_NumBins, sizeof(uint));

            var compute = context.resources.computeShaders.gammaHistogram;
            var cmd = context.command;
            cmd.BeginSample("GammaHistogram");

            // Clear the buffer on every frame as we use it to accumulate values on every frame
            int kernel = compute.FindKernel("KHistogramClear");
            cmd.SetComputeBufferParam(compute, kernel, "_HistogramBuffer", m_Data);
            cmd.DispatchCompute(compute, kernel, Mathf.CeilToInt(m_NumBins / (float)m_ThreadGroupSizeX), 1, 1);

另外,其他用法请搜索文档。
RenderTextures也可以从compute shader写入,如果设置随机访问权限的话。查询RenderTexture.enableRandomWrite。

compute shader中的texture 采样

在unity中,贴图和采样器不是分开的事物。所以要在compute shader中使用采样器的话,需要遵循下面的unity专门的规则:
1. 和贴图名称用同样的名称,如贴图名为Texture2D MyTex,则SamplerState samplerMyTex。这样,sampler会被初始化为该贴图的过滤模式,wrap模式,异向模式。
2. 用预定于的采样器,名称必须带有Linear或Point(过滤模式)和Clamp或Repeat(wrap模式)。比如SamplerState MyLinearClampSamplers创建一个linear过滤模式和Clamp wrap模式的采样器。更多的内容查询SamplerState

跨平台支持

像正常shader一样,unity能把compute shader从hlsl转换成其他shader语言。所以,为了最简单的跨平台编译,你可以用hlsl写compute shader。然而有几个因素需要考虑。

跨平台最好的实践

Dx 11支持许多其他平台不支持的行为(比如metal 或OpenGL ES)。所以,你需要保证你的shader在其他支持能力低的平台上工作正常。下面几个问题要注意:
1. 内存溢出访问。Dx11 读的时候会返回0,不会出问题。但很少支持的平台可能会gpu崩溃。另外,dx11的一些技巧,比如buffer大小和你的线程组数量是无关的,[ 试图从缓冲区的开始或结束读取相邻的数据元素,以及类似的不兼容性。
2. 初始化你的资源,新的缓冲区和贴图内容是未定义的。一些平台可能全是0,其他的可能是任意东西,包括为空。
3. 绑定你的compute shader需要的所有资源。即使你确定地知道这个shader在他的当前状态下由于分支原因不会用到这个资源,你仍然需要确定这个资源绑定到shader上。

特定平台的差异

  1. Metal(ios或tvOS平台)不支持贴图上的原子操作。Metal也不支持buffer上的GetDimensions查询。将buffer大小当成常量传给shader如果必要的话。
  2. openGL ES3.1(安卓、ios或txOS平台)一次只支持4个compute buffer。实际实现可能支持更多,但是如果开发openGL ES,你应该将相关的数据分组放到一个结构里,而不是每种数据放到自己的buffer里。

hlsl-only和glsl-only compute shader

一般,compute shader文件是用hlsl写的,被自动编译或翻译到所有必要的平台。然而,可以阻止将它翻译到其他语言,或者手动写glsl compute shader。
下面的内容只应用于hlsl-only和glsl-only compute shader,而不是跨平台编译。因为这些内容会导致compute shader从一些平台排除。
1.被CGPROGRAM和ENDCG包围的 compute shader不能被非hlsl平台处理;
2. 被GLSLPROGRAM和ENDGLSL包围的compute shader被当作glsl处理,逐字排除。你要注意,自动翻译的shader的buffer遵循hlsl数据布局,手动写的glsl shader遵循glsl布局规则。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值