在 HLSL 着色器中任意限制值时避免 "if"
Avoiding an "if" when arbitrarily clamping a value in a HLSL Shader
我没有编写着色器的经验,我已经组装了一个小片段着色器,它可以进行色度键控(播放视频时使某种颜色和与其相似的颜色透明):
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 并行性方面的性能。是否有进行这种钳制的本机功能,或者没有条件操作的另一种编写方式?
你的夹紧方式与其说是夹紧,不如说是切断。正在做
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
并将优化留给编译器。
我没有编写着色器的经验,我已经组装了一个小片段着色器,它可以进行色度键控(播放视频时使某种颜色和与其相似的颜色透明):
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 并行性方面的性能。是否有进行这种钳制的本机功能,或者没有条件操作的另一种编写方式?
你的夹紧方式与其说是夹紧,不如说是切断。正在做
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
并将优化留给编译器。