之前是一枚每天快快乐乐的原画,偶然一天在项目中接触到了shader这个东西,从此便走上了一条不归路。
其实做的项目基本也不怎么需要shader这种东西,但是感觉还挺好玩的,现在比较喜欢在blender里面连一些效果,然后看能不能移植到cocos中实现。
然后想记录一下自己做的一些阴间的东西,万一以后会用上也方便查找找寻思路。
其实从一个什么代码都不懂的美术开始学shader这种东西,刚开始的阶段是很痛苦的,自学的过程中也踩了很多坑,但是只要把基本的东西学清楚了,后面就是一个好玩的创造的过程了
然后也欢迎美术同学和我一起学 
翻出了自己N年前画的一个披风图标,给它+点阴间特效。

加完shader

这个shader非常简单,算法也很基础,尽量写的详细,方便一些新手能看懂
第一步,在PS给图标绘制特效遮罩图 红色的部分准备做流光,然后绿色的部分衣摆就是一点飘火的效果,背景的蓝色准备做火焰,这里使用通道也可以使用图层用线性减淡模式也可以
PS线性减淡的原理是将图层之间的RGB值 简单粗暴的相加
和cocos中的粒子默认的混合模式
是一样的效果
然后把背景去掉
最后得到两张这样的图。
此外还用了两张特效贴图
是这种比较轻的纹理,适合做一些uv的偏移,
对比度比较高的这种波纹图,适合做火焰或者流光一类的东西。
通道图和流光图加起来不到100Kb可以说是非常省资源了,关键是还可以复用啊 
第二步制作流光的效果
新建一个shader,制作流光的效果,需要一个
沿着我画出来的金属遮罩走
这里因为uv的x分量,是从左往右从0-1,所以使用一个这样的函数,让他产生0-1-0的效果
再调用我们遮罩图的r通道,相乘 就得到了一个流光的效果
void main () {
vec4 o = vec4(1, 1, 1, 1);
vec4 NoiseCol = texture(noiseTex, v_uv0);
float light = NoiseCol.r * pow(abs((fract(v_uv0.x + cc_time.x + 0.5) - 0.5) * 2.0), 2.0);
vec4 test = vec4(light,light,light,1.0);
gl_FragColor = test;
}
结果
然后流光的大小范围(用pow函数控制)和颜色通过property在外部进行调整
第二步是制作衣摆那里的火焰流光
然后这里详细解释一下uv采样的原理。
然后知道了这个原理,我们来做火焰流光的效果
首先有这样一张流光贴图

我们现在要让他产生躁动感。
使用给的uv偏移的纹理,乘以一个比较小的值,再与引擎本身的uv相加
vec2 NewUV = v_uv0 + UvDisCol.xy * 0.3;

得到了这样一个新的UV,使用这个uv对原来的流光图采样
vec2 NewUV = v_uv0 + UvDisCol.xy * 0.3;
vec4 WaveCol = texture(_WaveTex, NewUV);
会采样出一些具有扭曲感的效果

然后如果我们让那个uv偏移的纹理随着时间动起来采样的结果是不是就有一种动态的扭曲感觉

现在再property里面定义几个参数方便调控(这里我习惯于用一个vec4来调控图片的uv
(xy分量调控贴图的缩放,zw分量调控uv的偏移)
properties:
alphaThreshold: { value: 0.5 }
_AlphaTex: { value: white } # 遮罩图
_UvDisTex: { value: white } # uv偏移图
_UvDisTile: { value: [1.0, 1.0, 1.0, 1.0]} # 控制uv偏移图的缩放和偏移
_WaveTex: { value: white } # 火焰流光图
_WaveTile: { value: [1.0, 1.0, 1.0, 1.0]} # 控制火焰流光图的缩放和偏移
_FireCol: { value: [1.0, 1.0, 1.0, 1.0], editor : { type : color }} # 控制火焰颜色
别忘了在片段着色器里面声明一下
uniform sampler2D _AlphaTex; //uniform规则是图片要单独拎出来声明
uniform sampler2D _UvDisTex;
uniform sampler2D _WaveTex;
uniform suibianqu{ //其他的颜色和浮点要定义在一个结构体中,名字可以随便取
vec4 _UvDisTile; //注意结构体中vec4要在float前面声明,不然会报错
vec4 _WaveTile;
vec4 _FireCol;
};
最后再进行计算
vec4 AlphaCol = texture(_AlphaTex, v_uv0); //采样获得遮罩贴图
vec4 UvDisCol = texture(_UvDisTex, v_uv0 * _UvDisTile.xy + cc_time.x * _UvDisTile.zw); //采样获得uv偏移贴图,并让它随着时间移动
vec2 NewUV = v_uv0 + UvDisCol.xy * 0.3; //根据采样获得uv偏移贴图,生成一个新的uv用于采样流光贴图
vec4 WaveCol = texture(_WaveTex, NewUV * _WaveTile.xy + cc_time.x * _WaveTile.zw); //根据新的uv采样流光贴图,并让它随着时间移动
vec4 FinalFireCol = AlphaCol.g * WaveCol * _FireCol; //用最后的结果乘以遮罩的g通道,把结果限制在披风的区域
然后再外部调整一下流光的缩放值和平移速度 因为只想让他竖方向移动,所以tile的z值就都设置为0

最后把流光和火焰的结果和原图加起来`
gl_FragColor = o + FinalFireCol + Finallight;
就获得了一个基本的流光效果了,颜色也可以在外部进行调整

然后最后背景的火焰我比较懒,就直接用刚刚衣服上的火焰把uv的缩放值和偏移值调整一下
再乘以遮罩的b通道,就可以了,最后衣服上的符文和宝石上的呼吸灯也都是画遮罩做的,这里就不加上去了。
完整代码如下
CCEffect %{
techniques:
- passes:
- vert: vs
frag: fs
blendState:
targets:
- blend: true
rasterizerState:
cullMode: none
properties:
texture: { value: white }
alphaThreshold: { value: 0.5 }
_AlphaTex: { value: white } # 遮罩图
_UvDisTex: { value: white } # uv偏移图
_UvDisTile: { value: [1.0, 1.0, 1.0, 1.0]} # 控制uv偏移图的缩放和偏移
_UvDisBgTile: { value: [1.0, 1.0, 1.0, 1.0]} # 控制uv偏移图的缩放和偏移
_UvDisPow: { value : 0.3 } # 控制uv偏移图的强度
_WaveTex: { value: white } # 火焰流光图
_WaveTile: { value: [1.0, 1.0, 1.0, 1.0]} # 控制火焰流光图的缩放和偏移
_WaveBgTile: { value: [1.0, 1.0, 1.0, 1.0]} # 控制背景流光图的缩放和偏移
_FireCol: { value: [1.0, 1.0, 1.0, 1.0], editor : { type : color }} # 控制火焰颜色
_FireBgCol: { value: [1.0, 1.0, 1.0, 1.0], editor : { type : color }} # 控制背景颜色
_LightCol: { value: [1.0, 1.0, 1.0, 1.0], editor : { type : color }} # 控制流光颜色
}%
CCProgram vs %{
precision highp float;
#include <cc-global>
#include <cc-local>
in vec3 a_position;
in vec4 a_color;
out vec4 v_color;
in vec2 a_uv0;
out vec2 v_uv0;
void main () {
vec4 pos = vec4(a_position, 1);
#if CC_USE_MODEL
pos = cc_matViewProj * cc_matWorld * pos;
#else
pos = cc_matViewProj * pos;
#endif
v_uv0 = a_uv0;
v_color = a_color;
gl_Position = pos;
}
}%
CCProgram fs %{
precision highp float;
#include <alpha-test>
#include <cc-global> //因为后面火焰动画需要时间值,这个头文件里包含了cc_time.x就是shader运行的时间所以要在这里声明出来
#include <texture>
in vec4 v_color;
in vec2 v_uv0;
uniform sampler2D texture;
uniform sampler2D _AlphaTex; //uniform规则是图片要单独拎出来声明
uniform sampler2D _UvDisTex;
uniform sampler2D _WaveTex;
uniform suibianqu{ //其他的颜色和浮点要定义在一个结构体中,名字可以随便取
vec4 _UvDisTile; //注意结构体中vec4要在float前面声明,不然会报错
vec4 _WaveTile;
vec4 _FireCol;
vec4 _FireBgCol;
vec4 _LightCol;
vec4 _UvDisBgTile;
vec4 _WaveBgTile;
float _UvDisPow;
};
void main () {
vec4 o = vec4(1, 1, 1, 1);
o *= v_color;
#if USE_TEXTURE
CCTexture(texture, v_uv0, o);
#endif
vec4 AlphaCol = texture(_AlphaTex, v_uv0); //采样获得遮罩贴图
//衣摆上的火焰
vec4 UvDisCol = texture(_UvDisTex, v_uv0 * _UvDisTile.xy + cc_time.x * _UvDisTile.zw); //采样获得uv偏移贴图,并让它随着时间移动
vec2 NewUV = v_uv0 + UvDisCol.xy * _UvDisPow; //根据采样获得uv偏移贴图,乘以一个强度值,生成一个新的uv用于采样流光贴图
vec4 WaveCol = texture(_WaveTex, NewUV * _WaveTile.xy + cc_time.x * _WaveTile.zw); //根据新的uv采样流光贴图,并让它随着时间移动
vec4 FinalFireCol = AlphaCol.g * WaveCol * _FireCol; //用最后的结果乘以遮罩的g通道,把结果限制在披风的区域
//流光
vec4 Finallight = _LightCol * AlphaCol.r * pow(abs((fract(v_uv0.x - cc_time.x + 0.5) - 0.5) * 2.0), 10.0); //计算金属流光的颜色
//背景上的火焰
vec4 UvDisBgCol = texture(_UvDisTex, v_uv0 * _UvDisBgTile.xy + cc_time.x * _UvDisBgTile.zw); //采样获得uv偏移贴图,并让它随着时间移动
vec2 NewBgUV = v_uv0 + UvDisBgCol.xy * _UvDisPow; //根据采样获得uv偏移贴图,乘以一个强度值,生成一个新的uv用于采样流光贴图
vec4 WaveBgCol = texture(_WaveTex, NewBgUV * _WaveBgTile.xy + cc_time.x * _WaveBgTile.zw); //根据新的uv采样流光贴图,并让它随着时间移动
vec4 FinalFireBgCol = 2.0 * AlphaCol.b * WaveBgCol * _FireBgCol; //用最后的结果乘以遮罩的g通道,把结果限制在披风的区域
gl_FragColor = o + FinalFireCol + Finallight + FinalFireBgCol;
}
}%
再随便加一点阴间的粒子,敷衍一下赶快结束 

啊,我发现自己做的时候是一回事,做这种类似教程的东西又是另一回事 ,也太累了。晕
具体我就不细调了,这个是我之前做的,源文件已经不见了,现在重做一遍也不太好调的跟之前一摸一样 。贴图和代码上面都给了,想试试可以直接复制粘贴大法。自己调整一些tile的参数可以实现一些不同的效果。另外通道图也可以自己擦除一部份区域,比如衣摆被上面肩甲遮住的投影那里应该暗一些。
总体来说是个很基础的东西了,刚学习shader的同学可以试一试。
可以看到最后采样了5次贴图,虽然贴图大小可以压缩到256*256
但是能省还是尽量省,自己做着玩就无所谓了
实际项目中贴图的采样次数还是尽量减少 尤其是1024以上分辨率的贴图。






没有 我现在对shader还停留在入门阶段,对于一些比较复杂的算法还需要继续学习
