最近一直在更新 Shader
编辑器 的功能:
在继续推进新功能的同时,打算利用编辑器,发一点简单的教程,用可视化的方式,解释一些原理或是实现一些特效。
今天正好半夜在写合图方面的功能,就顺便写了篇文章,用可视化的方式,来解释下 UV
合图后存在问题的原因和简单的解决方法。
Packable / 贴图打包
在 Creator
中使用 Shader
时常会碰到一个问题,就是 编辑器中调试好的 Shader
效果,往往实际已运行,效果就和编辑器中的看到的不一样了。
这样的问题论坛很多,当然解决方案也有:
-
关闭使用
Shader
的对象纹理的Packable
属性 -
在
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
的问题了。