在 HLSL 着色器中任意限制值时避免“如果”

Posted

技术标签:

【中文标题】在 HLSL 着色器中任意限制值时避免“如果”【英文标题】:Avoiding an "if" when arbitrarily clamping a value in a HLSL Shader 【发布时间】:2021-09-24 17:50:03 【问题描述】:

我没有编写着色器的经验,我已经组装了一个小片段着色器,它可以进行色度键控(在播放视频时使某种颜色和与其相似的颜色透明):

Shader "Equinox/ChromaKeyShader5" 
    Properties 
        _MainTex ("Base (RGB)", 2D) = "white" 
        _MaskCol ("Mask Color", Color)  = (1.0, 0.0, 0.0, 1.0)
        _Threshold1 ("Threshold 1", Range(0,1)) = 0.8
        _Threshold2 ("Threshold 2", Range(0,1)) = 0.6
    

    SubShader 
        CGINCLUDE
            #include "UnityCG.cginc"

        ENDCG

        Pass 
            ZTest Less
            Cull Off
            ZWrite Off
            Lighting Off
            Fog  Mode off 
            Blend SrcAlpha OneMinusSrcAlpha


            CGPROGRAM
                #pragma vertex vert_img
                #pragma fragment frag
                #pragma fragmentoption ARB_precision_hint_fastest
                #pragma alpha

                uniform sampler2D _MainTex;
                uniform float4 _MaskCol;
                uniform float _Threshold1;
                uniform float _Threshold2;

                half4 frag (v2f_img i) : COLOR 
                    half4 c = tex2D(_MainTex, i.uv);
                    half d = distance(c.rgb, _MaskCol.rgb);

                    d = clamp(d, 0, 1); // Do I need it?

                    // TODO: remove if
                    if (d > _Threshold1) 
                        d = 1;
                     else if (d < _Threshold2) 
                        d = 0;
                    

                    return half4(c.rgb, d);
                
            ENDCG
        
    
    Fallback off

我担心这个if 块在GPU 并行性方面的性能。是否有一个原生功能可以进行这种钳位,或者没有条件操作的其他方式来编写它?

【问题讨论】:

【参考方案1】:

您的夹紧方式更像是切断而不是夹紧。正在做

d = clamp(d, _Threshold2, _Threshold1);

将是正常的夹紧方式 - 但是,该方法的作用更类似于

if(d > _Threshold1)
    d = _Threshold1;
else if(d < _Threshold2)
    d = _Threshold2;

如果你真的想保持你的截止行为(如果它低于或高于这些阈值,将 d 设置为 0 或 1,而不是将其设置为阈值),则没有内在功能.

此外,虽然着色器中的if看起来很吓人,但着色器编译器实际上有两种不同的方式来处理这种分支:扁平化和动态分支。动态分支是一种可怕的分支,它确实会影响性能,因为它不适用于并行 GPU 使用的 SIMD 风格。另一方面,扁平化分支在大多数情况下要便宜得多:这样,GPU 将实际执行 if 的所有分支,然后根据分支条件在结果之间执行 lerp

对于您的if,着色器编译器很可能会选择展平分支,这会将您的if 变成类似

d = lerp(lerp(d, 0, d < _Threshold2), 1, d > _Threshold1);

请注意,这种重写是由着色器编译器自动完成的,因此为了便于阅读,最好保留 if 并将优化留给编译器。

【讨论】:

以上是关于在 HLSL 着色器中任意限制值时避免“如果”的主要内容,如果未能解决你的问题,请参考以下文章

在着色器中解释纹理数据

在CG/HLSL中访问着色器的内容

HLSL 分支规避

WPF 像素着色器入门:使用 Shazzam Shader Editor 编写 HLSL 像素着色器代码

HLSL像素着色器

(转载)(官方)UE4--图像编程----着色器开发----HLSL 交叉编译器