在Shader中处理Atlas的uv以及一点小优化

Posted by GT on August 5, 2020

背景

接上一篇
https://forum.cocos.org/t/topic/95986
如何在最小改动shader的前提下,让shader支持atlas?

方法

一个相对通用的方法,是将子纹理uv射到[0, 1]区间,其余部分保留shader原来的逻辑。
普通的映射函数如下, Remap01 将子纹理uv映射到[0, 1]区间, Remap 则支持任意两个区间的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 将[a, b]区间映射到[0, 1]区间
// t是[a, b]区间内的值
// 函数返回t被映射后的值
float Remap01(float a, float b, float t) {
	return (t-a) / (b-a);
}

// 将[a, b]区间映射到[c, d]区间
// t是[a, b]区间内的值
// 函数返回t被映射后的值
float Remap(float a, float b, float c, float d, float t) {
    return Remap01(a, b, t) * (d - c) + c;
}

代码中的a、b参数,分别代表原区间的起始、结束,即子纹理在atlas中uv的坐标区间。这两个值需要通过顶点属性传入shader。

在shader中通过以下代码实现映射

1
2
3
// v_xrange、v_yrange分别表示子纹理在x轴、y轴上的uv区间
uv.x = Remap01(v_xrange.x, v_xrange.y, uv.x);
uv.y = Remap01(v_yrange.x, v_yrange.y, uv.y);

优化Remap01方法

参数预计算

$(t-a) / (b-a) = t\frac{1}{b-a}+\frac{a}{a-b}$
可以在CPU直接计算好$p=\frac{1}{b-a},q=\frac{a}{a-b}$代替v_xrange、v_yrange传入shader
Remap01 函数也可以简化为$tp+q$,一条MAD指令即可处理完。

合并x、y轴运算

GPU被设计成可以高效处理四元数,一条指令里可以同时处理向量里的xyzw分量。
将x、y轴的p放在一个向量,x、y轴的q放在一个向量, Remap01 可以简化成如下形式

1
2
3
4
5
// 正向映射
uv = uv * v_p + v_q;

// 反向映射也很方便
uv = (uv - v_q) / v_p


优化Remap方法

Remap01 的优化方法同样适用于任意区间映射的 Remap 函数,同时这个优化方法可以将原本a, b, c, d 4个参数简化为p, q 2个参数,在shader里并不用关心uv究竟被映射到了哪个区间。

参考

https://forum.unity.com/threads/how-expensive-is-smooth-step-in-shaders-for-mobile.501809/