因为工作需要,要在游戏中自定义Shader,去翻论坛的时候发现2.0已经没有官方支持自定义Shader了。
只好研究研究自定义Shader如何实现。这个方法会涉及修改laya.core.js中源代码。如果对Laya2D渲染感兴趣的也可以去看看具体实现。
之前关于阅读Laya源代码的文章提到过,Laya核心帧循环逻辑在Render中的render函数。我们的自定义Shader就要跟laya底层生成的Shader一起渲染。
注意:这个方法目前只适合Shader在舞台最底层的情况,如果需要在舞台中间插入Shader逻辑,对于引擎改动比较大。如果你又闲心,可以去研究研究。需要自定义一个图元提交函数
Laya底层对于Shader的实现做了一部分封装,要实现自己的Shader也要去遵循这个规则。
Value2D:这个对象中存储Shader对象Texture,对应ID等信息,这里面有个很干的ShaderDefine2D存在,我感觉特别坑,后面会讲到具体是拿来干嘛的。
Mesh2D:这个对象就是我们用来存储buffer的东西了,别人是在注释中说明了,只具备数据存储功能,不具备渲染功能。
Context:这个是渲染对象,我们需要将我们的Shader渲染到什么地方的一个上下文,我们所有的渲染都是到Laya.Render._context,所以这里不会变化。
有了这三个对象,我们就凑齐了,可以初始化我们的核心对象SubmitTexture,我们需要拿着这个对象去申请重绘。
初始化Shader程序
Laya中Shader都是通过预编译实现的,实现逻辑对应在Shader2D.__init__中
不过我们可以直接new一个Shader对象
//vs:顶点着色器 fs:片元着色器
var shader:Laya.Shader = new Laya.Shader(vs,fs,0x900);
//上面这个0x900是shader的一个id,到时从Shader提交会从数组中取Shader的时候会用到
//这个0x900就跟之前说的Value2D里面很坑的ShaderDefine2D相关
创建纹理
这一步就比较简单,直接暴力的方式就是直接创建一个Image对象,但是我们只需要其中的WebGLTexture对象
var img:Laya.Image = new Laya.Image("url");
创建自定义Shader渲染对象
这一步就是核心逻辑了,我们需要创建一个SubmitTexture对象,其中存储之前所说的几个对象。
//1.创建mesh
var mesh:Laya.Mesh2D = new Laya.Mesh2D(5 * Float32Array.BYTES_PER_ELEMENT,4,0);
var vertexBuffer:Laya.VertexBuffer2D = mesh.getVBR();
vertexBuffer.append(new Float32Array(顶点数据数组));
//相当于gl.vertexAttribPointer
mesh.setAttributes([
WebGLRenderingContext.FLOAT, 3, 0,
WebGLRenderingContext.FLOAT, 2, Float32Array.BYTES_PER_ELEMENT * 3
]);
var indexBuffer:Laya.IndexBuffer2D = mesh.getIBR();
//
indexBuffer.append(顶点序列集合);
//2.创建value2d
//这里创建了一个自定义的Value2D
var value2d:Laya.CustomValue2D = new Laya.CustomValue2D(0);
//将着色器uniform属性都存在这里
value2d.uSampler = image.source.bitmap['_glTexture']//之前创建的image对象
....
....
//3.实例一个SubmitTexture对象
this._renderSubmit = Laya.SubmitTexture.create(Laya.Render._context,mesh,value2d);
this._renderSubmit.shaderValue.textureHost = image.source;
this._renderSubmit._key.other = image.source.bitmap.id;
this._renderSubmit._numEle += 顶点数量;
更新着色器
在说更新着色器之前,先说下Laya中存在一个批提交概念,一个提交就是我们之前定义SubmitTexture的提交函数执行一次。
Laya底层中的Submit都是存储在一个数组中的,我们这种做法是绕过了底层的逻辑去清除重绘,所以我们需要稍微修改下Render帧循环的代码,这也是因为为什么这种做法只适合Shader应用于Stage最底层的情况。
我们在Laya的Stage对象中,添加一个数组存储我们之前创建的Program
/**自定义shader */
this._customShader = [];
//修改render函数
RunDriver.clear(this._bgColor);//清空舞台
//下面添加我们的自定义shader更新逻辑
for(var i=0,n=this._customShader.length;i < n;++i)
this._customShader[i]._update();
//laya自己的渲染函数
context.flush();
CustomValue2D里面有什么
var CustomValue2D=(function(_super){
function CustomValue2D(subID){
(subID===void 0)&& (subID=0);
this.uSampler = null;
this.u_Stage_Size = null;
this.a_Time = null;
CustomValue2D.__super.call(this,0x900,subID);//设置mainID为0x900
}
__class(CustomValue2D,'laya.webgl.shader.d2.value.CustomValue2D',_super);
var __proto=CustomValue2D.prototype;
__proto.clear=function(){
this.texture=null;
this.shader=null;
this.defines._value=this.subID+(WebGL.shaderHighPrecision? 0x900:1);
}
return CustomValue2D;
})(Value2D)
为什么需要设置0x900
设置这个标志位主要是因为,存储在Value2D中的mainID会拿来取Shader集合里面的Shader,我们想取到我们自己的自定义Shader,就必须设置一个区别于普通的标志位id。