unity-shader-GPU优化_step函数替代if-else

GPU并不擅长 if-else 判断语句的计算, 使用 unity 优化过函数 step 绝大部分情况下可以替代 if-else 达到相同的效果
参考: Shader中if和for的效率问题以及使用策略 - https://zhuanlan.zhihu.com/p/33260382


前篇

这里 带边缘颜色的 溶解 效果为例

相关函数

函数 功能
sign(x) x > 0, res=1;
x = 0, res = 0;
x < 0; res = -1
约束 值 只能是 -1, 0, 1
saturate(x) x < 0, res = 0
x > 1, res = 1
0 < x < 1, res = x
约束值 只能是 [0, 1]
step(a, b) 如果 a <= b,返回 1 ;否则,返回 0 。
lerp(a, b, f) 计算 或者 的值。即在下限 a 和上限 b 之间进行插值, f 表示权值。注意,如果 a 和 b 是向量,则权值 f 必须是标量或者等长的向量。
展开后等价于 a(1-f) + b*f;

测试代码

简单粗暴上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
Shader "test/Unlit-Texture-Dissolve" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Noise ("Noise (RGB)", 2D) = "white" {}
_MainColor ("Color", Color) = (1, 1, 1, 1)
_BurnAmout ("Burn Amount", Range(0.0, 1.0)) = 0.0
}

SubShader {
Tags { "RenderType"="Opaque" }
LOD 100

Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata_t {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
};

struct v2f {
float4 vertex : SV_POSITION;
half2 texcoord : TEXCOORD0;
float2 uvNoise : TEXCOORD1;

UNITY_FOG_COORDS(1)
};

sampler2D _MainTex;
float4 _MainTex_ST;
float4 _MainColor;
fixed _BurnAmout;

sampler2D _Noise;
float4 _Noise_ST;

v2f vert (appdata_t v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uvNoise = TRANSFORM_TEX(v.texcoord, _Noise);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
fixed val = tex2D(_Noise, i.uvNoise).r;

fixed diff = val - _BurnAmout;
fixed edgeWidth = 0.05; // 这个是边缘宽度, 可以提取乘一个参数暴露给材质动态控制

clip(diff);

fixed4 col = tex2D(_MainTex, i.texcoord) * _MainColor;
fixed4 edgeClr = fixed4(1, 1, 0, 1);
fixed4 finalClr;

// 方式a
// if (diff < edgeWidth) {
// finalClr = edgeClr;
// } else {
// finalClr = col;
// }

// 方式b
// fixed isEdge = saturate(sign(edgeWidth - diff));
fixed isEdge = step(diff, edgeWidth); // 与上一行代码等价
// finalClr = isEdge * edgeClr + (1 - isEdge)*col;
finalClr = lerp(col, edgeClr, isEdge); // 与上一行代码等价

// 方式a 与 方式b 等价, 但 方式b 更优于 gpu 计算
UNITY_APPLY_FOG(i.fogCoord, finalClr);
UNITY_OPAQUE_ALPHA(finalClr.a);
return finalClr;
}
ENDCG
}
}
}

shader graph 中使用

积雪 效果

step 函数使用的频率非常高, 常用于条件的判断 (替代 if-else, 更优于gpu并行计算). 之前也总结过 unity-shader-GPU优化_step函数替代if-else.

step(a, b) : 如果 a <= b,返回 1 ;否则,返回 0

官方示例中的 积雪 效果

  • snow direction : (0, 1, 0), 也就是y轴正方向
  • snow depth : 0, 这一步step也就是判断哪些 面 的 法线 与 (0, 1, 0) 的夹角小于 90 度