Cosos Creator 2.2.X shader 教程(入门篇)

前言

自己之前要做shader的一些效果,发现cocos相关文档很少,后面在论坛看帖子慢慢摸索了几周,也踩了几个坑耽误了一些功夫
现在把自己总结的一些笔记和大家分享讨论一下,里面的效果都是参考网上和论坛的,有什么错误或者不足的欢迎提建议,自己也还是个刚入门的,希望可以帮到一些想着手的童鞋,也算是回馈社区

几个简单的效果

(ps:压缩的有点厉害!)

流程:

新建 shader 资源

编辑器中:

在 Creator 中新建所需的 matrial、effect, 并且在 material 中设置对应的 effct 资源

代码中:

你需要在creator.d.ts 中添加几个接口来防止 ts 报错(不加只是爆红,不影响使用)

export class Material extends Asset {	
	effectAsset: Asset;		//材质对应的effect资源
	define(name: string, val: any): void;		//设置宏定义
	setProperty(name:string, val: any);			//设置变量
	static getBuiltinMaterial(materialUrl: string): Material	//获取系统的材质
	{
	}
}
export class EffectAsset extends Asset
{
}

设置材质的接口

/** !#en
Base class for components which supports rendering features.
!#zh
所有支持渲染的组件的基类 */
export class RenderComponent extends Component
{
	/** !#en The materials used by this render component.
	!#zh 渲染组件使用的材质。 */
	sharedMaterials: Material[];
	/**
	!#en Get the material by index.
	!#zh 根据指定索引获取材质
	@param index index 
	*/
	getMaterial(index: number): Material;
	/**
	!#en Set the material by index.
	!#zh 根据指定索引设置材质
	@param index index
	@param material material 
	*/
	setMaterial(index: number,material: Material): void;
}

你可以用上面的几个接口来加载对应 effect 和 material,设置属性,设置对应的材质到对应的组件上
只要是继承了 RenderComponent 的组件,比如是Sprite, Label, Spine等,都可以设置和获取材质

Effect 资源和 Matrial 资源

EffectAsset 就是保存我们自己编写的 shader 程序, 在引擎中对应着 EffectAsset 资源, 引擎读取渲染组件中的 effect 配置,并设置 对应渲染数据后调用WebGL的API进行渲染。
Creator 2.2 版本已经更新了 effect 文件的格式,关于 Matrial 和 Effect 的可以参考 Cocos Creator 3D文档
这里用一个最简单的栗子来介绍一下

CCEffect %{
  techniques:
  - passes:
    - vert: vs		//指向vert shader
      frag: fs		//指向frag shader
      blendState:	//渲染参数
        targets:
        - blend: true
      rasterizerState:
        cullMode: none
      properties:	//变量,会显示在 material 面板 上
        texture: { value: white }
        u_time: { value: 1.0 }
}%

CCProgram vs %{		//顶点着色器(GLSL 300 es格式)
	#include <cc-global>	//引用头文件,cc_matViewProj 变换矩阵就是在里面的变量
	precision highp float;	//定义精度
    in vec3 a_position;		//顶点位置
    in vec2 a_uv0;			//uv 坐标
    out vec2 uv0;			//插值输出到片元的uv 坐标
    void main () {
        gl_Position = cc_matViewProj * vec4(a_position, 1);
        uv0 = a_uv0;
    }
}%

CCProgram fs %{		//片元着色器
	precision highp float;		//定义精度
	uniform sampler2D texture;	//纹理
	uniform ARGS {				//除了系统的uniform ,其他uniform 变量都要定义在UBO(统一变量块)内
		//时间 根据时间计算需要丢弃的像素颜色值范围,也就是溶解的范围
		float u_time;
	}
	in vec2 uv0;

	void main()
	{
		float time = u_time;
		vec4 c = texture2D(texture,uv0);	//用纹理和uv坐标采样到对应片元的颜色
		float height = c.g;
		if(height < time)
		{
			//丢弃像素,相当于溶解效果
			discard;
		}
		if(height < time + 0.1) {
			//这里可以对溶解边缘进行一些处理,比如透明度减少等
			c.a = c.a-0.1;
		}
		//给片元(像素)赋值
		gl_FragColor = c;
	}
}%

Material 只需要在编辑器或者代码中设置对应的effect。在初始化和运行的时候设置对应的变量

运行

像溶解和流光等效果都需要在运行的代码中更新对应的时间参数,下面也举个小栗子

const {ccclass,property} = cc._decorator;

@ccclass
export default class ShaderTime extends cc.Component
{
    /**记录时间 */
    private time: number;
    /**精灵上的材质 */
    private material: any;
    private IsAdd: boolean;

    /**时间参数 */
    @property(cc.Float)
    speed: number = 1.0;

    start()
    {
        this.time = 0;
        this.IsAdd = true;
        this.material = this.node.getComponent(cc.Sprite).getMaterial(0);   //获取材质 
    }

    update(dt)
    {
        this.material.setProperty("u_time",this.time);          //设置材质对应的属性
        this.IsAdd ? this.time += dt * this.speed : this.time -= dt * this.speed;
        if(this.time > 1.5)
        {
            this.IsAdd = false;
        }
        else if(this.time < -0.5)
        {
            this.IsAdd = true;
        }
    }
}

关于合图导致web和模拟器显示不一致的BUG

现象:
正确的效果:

UV错误的效果:

原因: Cocos 会把小于512*512的碎图自动合图以减少DrawCall,而effect中接收到的uv 坐标是整个合图的uv, 导致需要用到uv坐标的effect在自动合图下显示不正确。
解决办法:

  • 关闭自动合图
    ps: 在Cocos Creator 2.1.3和2.2.0 以上都支持单独取消某个纹理的合图
  • 手动获取当前sprite 的纹理uv坐标传入到effect(无需取消自动合图)
//获取UV位置到Effect
let frame = sprite.spriteFrame as any;
let l = 0,r = 0,b = 1,t = 1;
l = frame.uv[0];
t = frame.uv[5];
r = frame.uv[6];
b = frame.uv[3];
let u_UVoffset = new cc.Vec4(l,t,r,b);
let u_rotated = frame.isRotated() ? 1.0 : 0.0;
this._material.setProperty("u_UVoffset",u_UVoffset);
this._material.setProperty("u_rotated",u_rotated);
//在Effect 中接受u_UVoffset u_rotated 后重新设置UV
vec2 UVnormalize;
UVnormalize.x = (uv0.x-u_UVoffset.x)/(u_UVoffset.z-u_UVoffset.x);
UVnormalize.y = (uv0.y-u_UVoffset.y)/(u_UVoffset.w-u_UVoffset.y);
if(u_rotated > 0.5)
{
	float temp = UVnormalize.x;
	UVnormalize.x = UVnormalize.y;
	UVnormalize.y = 1.0 - temp;
}

总结

有了材质和Effect以后,cocos使用shader 更加直观了。
移植一个需要shader 效果其实也只是在effect文件中设置好变量,修改一下shader 的代码片段语法,最后在代码或面板中设置参数即可

学习Shader

70赞

感谢!!mark

感谢!!

mark

请问性能如何?

mark

mark

mark

支持,插眼

MARK

mark

马住马住

我们用的几个简单的效果,暂时还没有碰到性能的问题

mark

mark

mark

可以的

这是demo 里面的报错吗,改了什么代码,版本是啥

2.2.1没改 直接跑就报错了 但是能跑
放到预制体 里面报这个错 只能直接放场景里面吗