cocos版本:2.4.4
参考:
Cocos3D文档: 材质 、 常用 shader 内置 Uniform
基础知识: The Book of Shader 中文版
水友文章: 学习shader的入门笔记
Cocos Creator Shader Effect 系列
从被攻击闪白shader到相关原创整理,以及相关学习资料整理
目录
一 Shader
二 Cocos中的Shader
三 学习TheBookOfShader,并在cocos中实现书中效果
一 Shader
着色器(Shader)是用来实现图像渲染的,用来替代固定渲染管线的可编辑程序。其中Vertex Shader(顶点着色器)主要负责顶点的几何关系等的运算,Pixel Shader(像素着色器)主要负责片源颜色等的计算。
着色器替代了传统的固定渲染管线,可以实现3D图形学计算中的相关计算,由于其可编辑性,可以实现各种各样的图像效果而不用受显卡的固定渲染管线限制。
理解上Shader是一段代码,通过编程告诉GPU如何绘制顶点和颜色,从而实现各种各样的图像效果,例如彩色字体、按钮置灰、图片马赛克、描边、动态让图片扭动起来等等。
二 Cocos中的Shader
每张图片cc.Sprite或文本cc.Label都有一个默认Materials材质。
材质资源可以用来控制渲染组件在场景中的视觉效果。简单来说材质就是用来指定物体表面的特性,如颜色、光亮程度、自发光度以及不透明度等。
材质对应的Effect文件,里面就是shader代码
Shader语言有3种:
1.基于OpenGL的OpenGL Shading Language,简称GLSL。
2.基于DirectX的High Level Shading Language,简称HLSL。
3. NVIDIA公司的C for Graphic,简称Cg语言。
Cocos采用的是YAML和GLSL,YAML声明控制流程清单,GLSL声明实际的shader片段。具体查看Effect语法。
builtin-2d-sprite.effect完整代码如下:
CCEffect编写声明
其中properties可以自定义外部变量,例如自定义颜色变量,定义后可在属性面板选择颜色。
CCPrograms vs
获取顶点数据,向下一个渲染管道传递数据。
这段代码之后基本不用动,主要理解是两个变量:
out vec4 v_color; //当前node节点颜色。
out vec2 v_uv0; //坐标,原点在左上角,xy轴坐标分别通过v_uv0.x 和v_uv0.y获取。
CCProgram fs
大部分自定义shader逻辑代码写在这里面。下面的代码对cocos的图片进行采样,然后和node节点颜色混合后输出,实现普通builtin-2d-sprite效果。
CCTexture(texture,v_uv0,o); //对图片进行采样,颜色存储在o里
o *= v_color; // o和node节点颜色v_color进行混合
gl_FragColor = i; //输出颜色
那么问题来了,里面的其它变量都是什么意思...都有哪些内置函数和变量,cocosAPI搜索根本查不到这些....哪里看这些API的文档...
Cocos Effect语法:Effect语法
Cocos常用 shader 内置 Uniform:内置Uniform
GLSL语法:OpenGL shader GLSL 中文手册
三 学习TheBookOfShader,并在cocos中实现书中效果
打开 The Book of Shader 中文版 ,开始学习。
边看教程边写例子。在cocos中新建测试用的Effect和Material,分别命名为TestMaterial和TestEffect。
选择TestMaterial,设置Effect属性为TestEffect
选择任意一张图片,赋值Materials属性为TestMaterial
在资源管理器,选择这个图片,将packable的勾去掉。如果这个打包的勾被勾选,则shader在发布后出问题。
双击TestEffect,则可以在vs code打开并编辑TestEffect文件,但是文件都是白字,没有语法高亮。需要安装一个插件来支持代码高亮。
vscode中安装Cocos Effect插件
这个插件可以高亮effect文件,方便阅读代码。选择查看-扩展
搜索Cocos Effect并安装
安装后代码有了颜色
第一个例子,将图片变成红色
修改TestEffect的CCProgram fs的最后一行gl_FragColor=o改为gl_FragColor = vec4(1.0,0,1.0,1.0)
vec4的4个参数分别代表颜色通道(red, green,blue,alpha),颜色值是范围0-1,注意不要写整数1,要写浮点数1.0。
CCProgram fs %{ precision highp float; #include <alpha-test> #include <texture> #include <cc-global> #include <cc-local> in vec4 v_color; #if USE_TEXTURE in vec2 v_uv0; uniform sampler2D texture; #endif uniform color{ vec4 imgColor; }; void main () { vec4 o = vec4(1, 1, 1, 1); #if USE_TEXTURE CCTexture(texture, v_uv0, o); #endif o *= v_color; ALPHA_TEST(o); gl_FragColor = vec4(1.0,0,1.0,1.0)*o; } }%
关于颜色值vec4的访问,下图中访问方式是等效的。
第二个例子,图片红色闪烁
cc_time.x就是书里的u_time,表示游戏的运行时间。o.r就是红色通道值, abs(sin(cc_time.x))就是利用余弦函数,随着游戏时间增加,红色通道值一直在0-1之间变化,从而形成了闪烁效果。
void main () { vec4 o = vec4(1, 1, 1, 1); #if USE_TEXTURE CCTexture(texture, v_uv0, o); #endif o *= v_color; ALPHA_TEST(o); o.r = abs(sin(cc_time.x)); gl_FragColor = o; }
第三个例子,渐变色
v_uv0就是书中的gl_FragCoord.xy/u_resolution,表示坐标。
v_uv0.x和v_uv0.y值是从0-1变化的,gl_FragColor = vec4(v_uv0.x,v_uv0.y,1.0,1.0)表示红色和绿色通道值从左上角到右下角由0-1变化。
void main () { vec4 o = vec4(1, 1, 1, 1); #if USE_TEXTURE CCTexture(texture, v_uv0, o); #endif o *= v_color; ALPHA_TEST(o); gl_FragColor = vec4(v_uv0.x,v_uv0.y,1.0,1.0); }
因为没有计算cocos图片颜色o,所以这里是单纯的颜色值。
gl_FragColor = vec4(v_uv0.x,v_uv0.y,1.0,1.0)理解起来很抽象,我们代入几个值到公式里看看就知道规律了。
x坐标 y坐标 颜色值 结果
v_uv0.x=0 v_uv0.y=0 vec4(0,0,1.0,1.0) (0,0)表示左上角,颜色值蓝色
v_uv0.x = 1.0 v_uv0.y=1.0 vec4(1.0,1.0,1.0,1.0) (1,1)表示右下角,颜色值白色
v_uv0.x = 0.5 v_uv0.y=0.5 vec4(0.5,0.5,1.0,1.0) (0.5,0.5)表示中间,颜色值淡紫色
渐变色带图,这里计算了图片本身的颜色o
void main () { vec4 o = vec4(1, 1, 1, 1); #if USE_TEXTURE CCTexture(texture, v_uv0, o); #endif o *= v_color; ALPHA_TEST(o); gl_FragColor = vec4(v_uv0.x*o.r,v_uv0.y*o.g,o.b,o.a); }
第四个例子,画一条绿线
smoothstep(起始值A,结束值B,插值t) ,参考cocos里的cc.Vec3.lerp函数,大致smoothstep(A,B,t)返回值应该是A + (B-A)*t
float plot(vec2 st) { return smoothstep(0.02, 0.0, abs(st.y - st.x)); } void main () { float y = v_uv0.x; vec3 color = vec3(y); // Plot a line float pct = plot(v_uv0); color = (1.0-pct)*color+pct*vec3(0.0,1.0,0.0); gl_FragColor = vec4(color,1.0); }
第五个例子,圆
distance计算距离, distance(v_uv0, vec2(0.5))得到坐标离图片中心点的距离,距离越远值越大,越接近白色;距离越近值越小,越接近黑色。
void main () { float pct = 0.0; pct = distance(v_uv0,vec2(0.5)); vec3 color = vec3(pct); gl_FragColor = vec4( color, 1.0 ); }
画圆形,dot(x,y)返回x,y的点积。
float circle(in vec2 _st, in float _radius){ vec2 dist = _st-vec2(0.5); return 1.-smoothstep(_radius-(_radius*0.01), _radius+(_radius*0.01), dot(dist,dist)*4.0); } void main () { vec3 color = vec3(circle(v_uv0,0.25)); gl_FragColor = vec4(color,1.0); }
第六个例子 画长方形
step(阙值A,检测值B) B<A返回0.0,B>=A返回1.0。
下面只画了左和上,left在x<0.1的地方是0,其它地方1;bottom在y<0.1的地方是0,其它地方1。left*bottom有&&的作用,只有x和y都=1结果才是1。
所以只有满足x>=0.1&&y>=0.1的地方才会是1白色值。
void main () { vec3 color = vec3(0.0); float left = step(0.1,v_uv0.x); float top = step(0.1,v_uv0.y); color = vec3( left * top ); gl_FragColor = vec4(color,1.0); }
下面画右和下,只有x<=0.9 && y<=0.9的地方是1白色。
void main () { vec3 color2 = vec3(0.0); float right = step(0.1,1.0-v_uv0.x); float bottom = step(0.1,1.0-v_uv0.y); color2 = vec3( right * bottom ); gl_FragColor = vec4(color2,1.0); }
代码合起来
void main () { vec3 color = vec3(0.0); float left = step(0.1,v_uv0.x); float top = step(0.1,v_uv0.y); color = vec3( left * top ); vec3 color2 = vec3(0.0); float right = step(0.1,1.0-v_uv0.x); float bottom = step(0.1,1.0-v_uv0.y); color2 = vec3( right * bottom ); gl_FragColor = vec4(color*color2,1.0); }
第7个例子 从上到下四色渐变
void main () { vec4 o = vec4(1, 1, 1, 1); #if USE_TEXTURE CCTexture(texture, v_uv0, o); #endif o *= v_color; ALPHA_TEST(o); vec4 color1 = vec4(1,0,0,1); vec4 color2 = vec4(0,1,0,1); vec4 color3 = vec4(0,0,1,1); vec4 color4 = vec4(1,1,1,1); vec4 resultColor = vec4(1,1,1,1); if(v_uv0.y < 0.33){ resultColor = vec4(color1.x + (color2.x - color1.x)*v_uv0.y*3.0, color1.y + (color2.y - color1.y)*v_uv0.y*3.0, color1.z + (color2.z - color1.z)*v_uv0.y*3.0, 1.0); }else if(v_uv0.y < 0.66){ resultColor = vec4(color2.x + (color3.x - color2.x)*(v_uv0.y-0.33)*3.0, color2.y + (color3.y - color2.y)*(v_uv0.y-0.33)*3.0, color2.z + (color3.z - color2.z)*(v_uv0.y-0.33)*3.0, 1.0); }else if(v_uv0.y <= 1.0){ resultColor = vec4(color3.x + (color4.x - color3.x)*(v_uv0.y-0.66)*3.0, color3.y + (color4.y - color3.y)*(v_uv0.y-0.66)*3.0, color3.z + (color4.z - color3.z)*(v_uv0.y-0.66)*3.0, 1.0); } gl_FragColor = resultColor*o; }
第8个例子 设置effect属性
定义个imgColor属性
定义变量
在TestMaterial属性检查器中看一看到属性,并可以选择颜色
将imgColor赋予图片
void main () { vec4 o = vec4(1, 1, 1, 1); #if USE_TEXTURE CCTexture(texture, v_uv0, o); #endif o *= v_color; ALPHA_TEST(o); gl_FragColor = o*imgColor; }
第9个例子 攻击闪白效果
复制cocos的sprite和spine的普通effect
修改复制的sprite effect,高亮图片的颜色
修改复制的spine effect,高亮图片颜色
将修改后的高亮effect赋予图片,效果是这样的
点击一个图片或spine、db时,切换高亮effect持续0.1秒,然后恢复正常的effect,就可以做出攻击闪白的效果
const { ccclass, property } = cc._decorator; @ccclass export default class AttackFlash extends cc.Component { @property(dragonBones.ArmatureDisplay) //龙骨怪物 monster_db: dragonBones.ArmatureDisplay = null; @property(sp.Skeleton) //spine怪物 monster_spine: sp.Skeleton = null; @property(cc.Sprite) //普通图片怪物 monster_img: cc.Sprite = null; @property(cc.Material) //sprite被攻击闪白材质 mat_attacked_sprite: cc.Material = null; @property(cc.Material) //spine被攻击闪白材质 mat_attacked_spine: cc.Material = null; @property(cc.Material) //普通材质 mat_normal: cc.Material = null; @property(cc.Material) //普通spine材质 mat_normal_spine: cc.Material = null; onLoad() { this.monster_db.node.on(cc.Node.EventType.TOUCH_END, this.onMonsterDbTap, this); this.monster_spine.node.on(cc.Node.EventType.TOUCH_END, this.onMonsterSpineTap, this); this.monster_img.node.on(cc.Node.EventType.TOUCH_END, this.onMonsterImgTap, this); } //点击dragonBones,切换高亮material onMonsterDbTap() { this.monster_db.setMaterial(0, this.mat_attacked_sprite); this.unschedule(this.flashDb); this.schedule(this.flashDb, 0.1); } //计时结束,切换普通material flashDb() { this.monster_db.setMaterial(0, this.mat_normal); } //点击spine,切换高亮material onMonsterSpineTap() { this.monster_spine.setMaterial(0, this.mat_attacked_spine); this.unschedule(this.flashSpine); this.schedule(this.flashSpine, 0.1); } //计时结束,切换普通material flashSpine() { this.monster_spine.setMaterial(0, this.mat_normal_spine); } //点击图片,切换高亮material onMonsterImgTap() { this.monster_img.setMaterial(0, this.mat_attacked_sprite); this.unschedule(this.flashImg); this.schedule(this.flashImg, 0.1); } //计时结束,切换普通material flashImg() { this.monster_img.setMaterial(0, this.mat_normal); } }