【分享】Cocos Creator 2.x Shader组件与材质系统

ShaderHelper组件演示视频(1)

http://v.youku.com/v_show/id_XMzkxNTg5MTUzNg==.html

一、ShaderHelper组件

今天把ShaderHelper组件重新整理,代码已经上传到github,地址:https://github.com/ShawnZhang2015/ShaderHelper

说实话楼主对Shader也是刚刚起步,目前只会依葫芦画瓢,根据论坛中panda、Colin、小叔叔等大佬们的教程和源码结合自己的使用体验,编写了ShaderHelper组件,请先看使用方法:
http://v.youku.com/v_show/id_XMzkxNTg5NTk2MA==.html

1. ShaderHelper使用

通过上面视频,你会发现使用ShaderHelper组件基本上没有任何负担,通过ShaderHelper的属性Program下拉菜单选择需要的Shader效果即可,看下图:

同时注意,ShaderHelper组件目前必须配合cc.Sprite组件一起使用,就是说节点上必须有一个Sprite组件。

2. Shader模板对象

有人可能会问,Program下拉菜单中Shader效果都不是我想要的,要自己添加Shader怎么做呢?
我这里设计了一个Shader程序的模板结构,下面以Wave动态效果的Shader为例说明一下Shader程序的使用规则:

/**
 * shader模版对象
 * */

const renderEngine = cc.renderer.renderEngine;
const renderer = renderEngine.renderer;

//定义一个shader对象
const shader = {
    //名字必须字段 
    name: "xxx",
    //着色器代码中需要与js交互的参数名字与数据类型
    params: [
        {name: 'yyy', type: renderer.PARAM_FLOAT},
        {name: 'zzz', type: renderer.PARAM_FLOAT2}
    ],

    //着色器中使用到的define定义,非必要字段
    defines: [],
   
    //start回调,此可以初始化着色器中的参数
    start(sprite, material) { ... },

    //update每帧回调,如果是动态效果,可以在此设置Shader参数
    update(sprite, material) { ... },
    
    //vert顶点着色器代码,它是一个字符串
    vert: '...',

    //frag片元着色器
    frag: `...`
};

//注意这里,通过CustomMaterial.addShader添加shader对象,
//这样就可以被ShaderHelper组件所显示
let CustomMaterial = require('CustomMaterial');
CustomMaterial.addShader(shader);

如果要自定义Shader并能被ShaderHelper所显示且正常加载运行,需要实现上面的一些字段和方法。其中比较重要的几点说明一下:

  1. 定义一个最简单的shader对像只要需要有name、vert、frag三个属性
  2. 如果着色器代码中有需要初始化的变量,需要定义params字段,描述参数的名字和数据类型,如下所示:
params: [
        {name: 'iCenter', type: renderer.PARAM_FLOAT2},
        {name: 'iResolution', type: renderer.PARAM_FLOAT3}
]
  1. 如果着色器中的变量需要初值,可以通过start回调函数中进行初始化
  2. 如果需要每帧修改着色器中的变量值,实现动态特效,可以在update中进行设置
  3. 如果着色器中用到了define,需要在js代码中控制,可以定义define字段 ,同样他是一个数组,描述各个define是否开启或关闭,如下所示:
defines: [
        { name: 'HAS_HEART', value: true },
        { name: 'USE_POST_PROCESSING', value: false },
],
  1. 最后需要让shader被ShaderHelper组件加载,还需要将shader添加到CustomMaterial自定义材质对象中。
let CustomMaterial = require('CustomMaterial');
CustomMaterial.addShader(shader);

至此你就可以在ShaderHelper中看到新增的shader了。

#二、CustomMaterial.js源码分析
ShaderHelper组件只是对论坛Colin大神提供的CustomMaterial的调用,CustomMaterial又是对Cocos Creator引擎中的渲染引擎、材质系统API的运用,其中四个重要的对象:

Material(材质)、Effect(表现)、Technique(技术)、Pass(过程)

1. CustomMaterial自定义材质系统

我们先看一下CustomMaterial类的整体框架,首先CustomMaterial继承了引擎的Material类,同时实现对Effect的实例化,看下面代码:

...
var CustomMaterial = (function (Material$$1) {
    ...
    //继承Material
    cc.js.extend(CustomMaterial, Material$$1);
    
    //定义了三个属性effect、texture、color
    var prototypeAccessors = { 
        effect:  { configurable: true }, 
        texture: { configurable: true }, 
        color:   { configurable: true } 
    };
    ...
    Object.defineProperties(CustomMaterial.prototype, prototypeAccessors);
    ...
}(Material));

上面我将干扰代码移除,基中最为关键的是effect属性的定义,我们看Effect是怎么实例化出来的:

//实现化Effect对象
this._effect = new renderer.Effect(
    [ mainTech ],
    {},
    defines, //第三个参数defines就是我们之前定义的shader.dfines字段
);
this._texture = null;
this._color = { r: 1, g: 1, b: 1, a: 1 };
...
//定义effect\texutre\color的get方法
prototypeAccessors.effect.get = function () {
	return this._effect;
};
prototypeAccessors.texture.get = function () {
	return this._texture;
};
prototypeAccessors.color.get = function () {
	return this._color;
};

上面代码为_effect、_texture、_color三个对象实现get方法,使其可以被外部访问。

2. Effect的实例化

_texture与_color的初始化比较简,但Eeffect实例化需要三个参数,看下引擎源码:

//--------------CustomMaterail.js-----------------
this._effect = new renderer.Effect(
    [ mainTech ], 
    {},  
    defines, //defines就是我们之前定义的shader.dfines字段
);

//-----------------render-engine.js-----------------
//看下Effect类构建函数参数
var Effect = function Effect(techniques, properties, defines) {
   ...
};

Effect中三个数组分别是:techniques, properties, defines,其中关键在techniques,接下来看CustomMaterial中是怎么创建的。

3. Technique的实例化

Effect类的第一个参数需要Technique的数组,再看Technique的创建

//--------------------CustomMaterial.js-----------------------
//定义了两个默认的参数:texture、color
var techParams = [
    { name: 'texture', type: renderer.PARAM_TEXTURE_2D },
    { name: 'color', type: renderer.PARAM_COLOR4 }
];
//params就是之前定义的shader.params
if (params) {
    techParams = techParams.concat(params);
}
//实例化Technique,我们之前定义的params成了Technique的参数
var mainTech = new renderer.Technique(
    ['transparent'], //stages参数,暂时也没搞懂具体意思
    techParams,     
    [pass]
);

//--------------------render-engine.js-----------------------
//Technique类的构建函数
var Technique = function Technique(stages, parameters, passes, layer) {
...
}

Technique的构建函数需要4个参数,上面代码中给了前三个,其中techParams就是我们前面shader对象中定义的params字段,stages参数这里给的是[‘transparent’]我暂时也没搞懂是什么意思,先暂时不管它。passes又是什么鬼呢?我看再看pass的创建过程。

4. Pass的实例化

创建Technique又需要一个passes数组,再看代码pass的实例化过程:

//我们之前定义的shader.name成了Pass的构造参数
var pass = new renderer.Pass(shaderName);
//下面的函数调用Shawn也不太了解,这里就不解释了,等弄明白了再回来
pass.setDepth(false, false);
pass.setCullMode(gfx.CULL_NONE);
pass.setBlend(
    gfx.BLEND_FUNC_ADD,
    gfx.BLEND_SRC_ALPHA, gfx.BLEND_ONE_MINUS_SRC_ALPHA,
    gfx.BLEND_FUNC_ADD,
    gfx.BLEND_SRC_ALPHA, gfx.BLEND_ONE_MINUS_SRC_ALPHA
);

说话实Pass的实例化我也不太了解,通过字面意思猜测是设置材质相关的参数,参考了论坛中Panda提供的heartfelt工程,也是同样的写法。

5. 小结

我们暂且不纠结细节,从整体上理清楚Cocos Creator 2.x材质系统的框架结构,请看下图:

其实楼主只是做了一个搬运工,整合了一下Cocos官网论坛中大佬们分享的项目源码,目前ShaderHelper还是有不少问题,比如:组件上不能设置shader参数;ShaderHelper只能配全精灵组件使用;切换program时曾经的shader效果没有清除等。

虽然组件还不是很完善,但提供一个思路和测试Shader的方法,一点一滴地去改进相信能把它做的更好,也期待大家的参与!

项目源码:

https://github.com/ShawnZhang2015/ShaderHelper

参考资源:
http://forum.cocos.com/t/creator2-0/64727
http://forum.cocos.com/t/shader/59401
https://github.com/pandamicro/heartfelt


欢迎关注「奎特尔星球」微信公众号,愿我们一起成长!

33赞

告诉大家一个好消息,「奎特尔星球」微信公众号马上突破800关注,感谢大家的关注与支持!「奎特尔800勇士」即将开启,活动有好礼相送哦!

1赞

感谢分享,很早就关注了公众号,教程真的不错。

前排点赞:2:

教程很好,好多干货

感谢支持:blush:

幫上個連結
https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/03%20Blending/

pass.setBlend(
        // 對rgb作混色
        gfx.BLEND_FUNC_ADD,
        gfx.BLEND_SRC_ALPHA, gfx.BLEND_ONE_MINUS_SRC_ALPHA,

        // 對alpha作混色
        gfx.BLEND_FUNC_ADD,
        gfx.BLEND_SRC_ALPHA, gfx.BLEND_ONE_MINUS_SRC_ALPHA
    );

感谢你的帮助:+1:

mark~~~

mark

微信下游戏上会报错

1赞

不好意思,之前没在微信上做测试。 感谢你的提示,找到问题,引擎未初始化完成就在调用

if (cc.renderer._forward) {
        cc.renderer._forward._programLib.define(shader.name, shader.vert, shader.frag, shader.defines || []);
        g_shaders[shader.name] = shader;
    } else {
        //在微信上初始时cc.renderer._forward不存在,需要等引擎初始化完毕才能使用
        cc.game.once(cc.game.EVENT_ENGINE_INITED, function () {
            cc.renderer._forward._programLib.define(shader.name, shader.vert, shader.frag, shader.defines || []);
            g_shaders[shader.name] = shader;
        });
    }

代码已经上传github。

1赞

##「奎特尔800+勇士」已经开始

#传送门

uniform float progress[4];
uniform vec2 centres[4];

问下,这两种应该传入什么对象 ?
下面这样写不报错也没有任何效果,看不到东西。
mat.setParamValue(“progress”, [-1.0,-1.0,-1.0,-1.0] );
mat.setParamValue(“centres”, [
new cc.Vec2(0,0),
new cc.Vec2(0,0),
new cc.Vec2(0,0),
new cc.Vec2(0,0),
] );


已解决,看了render-engine.js源码,根据参数读取方式做修改
比如一个float数组给shader

let $createVec4 = (array)=>{
    if (array.length != 4){
        cc.warn("$createVec4 长度不一致!",array)
    }
    const ks = "xyzw";
    let vec4={
        set(index,value){
            this[ ks[index]] = value;
        },
        get(index){
            return this[ ks[index]]
        }
    };
    for (let index = 0;index<4;index++){
        vec4[ ks[index] ] = array[index];
    }
    return vec4;
}

 mat.setParamValue("progress", $createVec4([-1.0,-1.0,-1.0,-1.0]) );



及时雨 Colin大神的也看了 无奈基础太差看不懂 感谢补充

请问如何代码里动态创建sprite,然后代码里给sprite指定shader?

我在代码里放了一个动态创建Sprite 并设置Shader的测试场景,你可以参考一下。

1赞

终于没叫ShaderKiller