[ShaderFXEditor Tutorials - 01] Atlas UV

最近一直在更新 Shader 编辑器 的功能:

在继续推进新功能的同时,打算利用编辑器,发一点简单的教程,用可视化的方式,解释一些原理或是实现一些特效。

今天正好半夜在写合图方面的功能,就顺便写了篇文章,用可视化的方式,来解释下 UV 合图后存在问题的原因和简单的解决方法。

Packable / 贴图打包

Creator 中使用 Shader 时常会碰到一个问题,就是 编辑器中调试好的 Shader 效果,往往实际已运行,效果就和编辑器中的看到的不一样了

这样的问题论坛很多,当然解决方案也有:

  1. 关闭使用 Shader 的对象纹理的 Packable 属性

  2. Shader 涉及到使用 UV 值的计算,特别是绝对值进行计算的时候,要进行坐标的转换

这么做的原因,官方文档也有写明:

packable

如果引擎开启了 动态合图 功能,动态合图会自动将合适的贴图在开始场景时动态合并到一张大图上来减少 drawcall。但是将贴图合并到大图中会修改原始贴图的 uv 坐标,如果在自定义 effect 中使用了贴图的 uv 坐标,这时 effect 中的 uv 计算将会出错,需要将贴图的 packable 属性设置为 false 来避免贴图被打包到动态合图中。

下面就来配合 SSRShaderEdit ,通过可视化的方式,来看看对上面这段代码的解释。

Initialize / 初始化

首先是在编辑中,新建一个项目,连接出一个默认的 Shader 效果,就是将纹理输出到最终结果:

这里用到的 OriginUV 组件,使用的就是 Creator 中默认的 v_uv0 数值。

Color / 上色

接着简单修改一下,给这个 Shader 做个上色的效果;

原理非常简单,就是将原来输出的 Color 乘以另外一个 Color 的值。

Mix / 混合

接着继续修改,略微复杂一点,让上色的结果,在两个颜色中混合:

这里用了一个 Mix 组件,可以通过调节一个 float 数值的属性,来控制对输入的两个颜色的混合比例:

数值为 0 的时候,全部为 A 的值,就是红色。

数值为 1 的时候,全部为 B 的值,就是绿色

数值为 0.5 的时候,就是 A/B 数值各取一半混合。

它的源码就像是这样的:

vec4 main(vec4 A, vec4 B, float Factor) {
	return mix(A, B, Factor);
}

Half Color / 半边上色

接着继续我们加一个 Step 组件,配合 Mix 组件的使用,来实现一个 if / else 的效果:

Step 组件接收两个参数 ( n / edge ) ,进行比较,当 n < edge 的时候返回 0 ,反之返回 1。配合上前面的 Mix 组件,这好就实现了类似下面代码的效果:

if (v_uv0.x < 0.5) {
    color = RED;
}
else {
    color = GREEN;
}

简单说,就是 左半边为红色,右半边为绿色

Issue / 问题

就像前提出的问题一样,有时当你写好这样一个 Shader 之后,在 Creator 编辑器中一切正常,但是已运行起来,就怎么都不对了,这就是因为,你的 Shader 作用对象的纹理,默认是开启 Packable 的:

可以看到,纹理开启 Pakcable 之后,再次改变 Step 组件输入的数值,得到的结果怎么都不是想要的了。

Dynamic Atlas UV / 动态合图 UV

官方文档也提到了,因为开启 Packble 属性后,运行时就会尝试去动态合图了,也就是说,Shader 代码里的 0.5 是错误的。

下面通过编辑,实际来下原因:

可以看到,开启 Packable 以后,实际的纹理被合并到了一张大图中。

gif 可能不清楚再看下截图,开启打包前:

纹理的 UV 数值自然是 0, 0, 1, 1 / x, y, width, height

开启打包后:

纹理的 UV 数值变化了,变成了 0, 0, 1, 1 中的一小部分 0.701, 0.591, 0.250, 0.250

纹理长这样:

Verify / 验证

知道了原因,那就来验证一下:

根据合图后的 UV 和容易算出,之前的 0.5 应该改为多少了,修改以后可以看到结果就是正确的一半上色了。

Atlas UV Convert / UV 转换

对于编辑器来说,解决方案是,当 Shader 中要用到 UV 绝对值进行计算,而且还希望开启 Packable 属性的话,就不要直接使用 OriginUV 而是将其做一下转换,使用一个 AtlasUV 组件:

可看到,添加一个 AtlasUV 组件,然后将 OriginUV 作为参数传入,再增加一个 UVec4 类型的 Uniform ,然后在实际使用时,通过设置 UVec4 的值,就可以确保特效在使用 0.5 这个数值的时候,对 pacakble 是否开启的纹理,都是一样的效果了。

UVRect 可以在运行时,通过 Creator 提供的 cc.SpriteFrame / uv 属性方便的获取到:

// js
let UVRect = cc.v4(
  spriteFrame.uv[0], 
  spriteFrame.uv[7], 
  spriteFrame.uv[6] - spriteFrame.uv[0], 
  spriteFrame.uv[1] - spriteFrame.uv[7]
);

最后在 Shader 中将 UV 值映射到 0 ~ 1 区间即可,比如像这样:

float sfx_func_linear(float x1, float y1, float x2, float y2, float val) {
	return (y2 - x2) * (val - x1) / (y1 - x1) + x2;
}

vec2 sfx_func_uv_linear_to_01(vec4 uvrect, vec2 uv) {
	return vec2(
			sfx_func_linear(uvrect[0], uvrect[0] + uvrect[2], 0.0, 1.0, uv.x),
			sfx_func_linear(uvrect[1], uvrect[1] + uvrect[3], 0.0, 1.0, uv.y)
	);
}

vec2 atlasUV = sfx_func_uv_linear_to_01(UVRect, v_uv0);

当然,对于非 UV 依赖的 Shader 特效而言,就没有必要在意是否开启 Packable 的问题了。


6赞

深不可测,深不可测呀

楼主牛逼。

不知道是否能做到全自动,就是一个节点运行时兼容是否合图,或者兼容2D也行(也许这是引擎区分SpriteFrame和Texture的目的之一?)

就是进入引擎看看引擎如何适配图片合图与否的。引擎至少对那些内建2D Effect是兼容的吧。

楼主图丢了,请问最后加的那个atlasUV是什么