LayaBox2D使用自定义Shader的方法

本文详细介绍了如何在Laya2D框架下自定义Shader,实现个性化滤镜和高级图形效果。从理解Laya框架运行规则,到创建自定义Value2D对象,再到修改渲染逻辑,最后实现Shader与Image的连接,提供了完整的实现步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

-前言-

之前也写过一篇关于Laya2D自定义Shader的博客。不过那篇博客局限性太大,是完全独立于Laya框架下独自更新的。不能通过Laya的添加层级关系,设置坐标等。所以这次Shader方案是基于Laya的运行框架下的。

-正文-

Laya框架简述

在开始说具体方案时,还是先来了解下Laya框架的运行规则。
Laya框架下分为3块:

  • 用户逻辑层,这一层逻辑是我们自己写游戏的逻辑层,基于Timer帧更新去循环跑
  • 框架逻辑层,这一层逻辑是确定每个图元的渲染方式,是否重绘等逻辑。是根据用户层所设置的各种数据所确定
  • CPU到GPU的渲染层,这一层逻辑是Laya根据框架逻辑层所设值的Sumit渲染函数依次向显卡提交渲染申请。其中每一次的调用WebGL接口为一次着色器调用请求。
//上面三层都存在于Stage.render函数
updateTimers();//用户逻辑层
//框架逻辑层
if (this.renderingEnabled) {
    for (var i: number = 0, n: number = this._scene3Ds.length; i < n; i++)//更新3D场景,必须提出来,否则在脚本中移除节点会导致BUG
        this._scene3Ds[i]._update();
    context.clear();
    super.render(context, x, y);
    Stat._StatRender.renderNotCanvas(context, x, y);
}
//渲染层
if (this.renderingEnabled) {
    Stage.clear(this._bgColor);
    context.flush();
    VectorGraphManager.instance && VectorGraphManager.getInstance().endDispose();
}

自定义Shader

在明确了Laya的框架之后,我们可以确定,我们的Shader完整流程也是需要从用户逻辑层——>框架逻辑层——>渲染层一步一步的透传的。

实现目标

我们可过下面的伪代码来感受下实现效果

//step0 创建图片对象
let shaderImage:Laya.Image = new Laya.Image(图片路径);
//step1 创建控制Image的Shader对象
let shader:Laya.Shader2X = new Laya.Shader2X(顶点着色器、片元着色器、其余参数)
//step2 连接shader与shaderImage
//关键步骤
//step3 添加图片到Image
Laya.stage.addChild(shaderImage);

//以后shaderImage就由shader着色器程序控制

通过上面的伪代码,0,1,3步都是在框架之下执行的,关键步骤就2步,如何连接Image对象与Shader程序。不过在讲如何连接之前,我们首先来讲讲如何创建Shader程序。

创建Shader及自定义Value2D对象

创建Value2D对象

Laya调用WebGL API是通过Value2D对象进行调用,Value2D会根据自己的属性调用不同Shader程序来渲染自己所持有的texture对象。

//以下是Value2D的提交函数upload
upload(): void {
    var renderstate2d: any = RenderState2D;

    // 如果有矩阵的话,就设置 WORLDMAT 宏
    RenderState2D.worldMatrix4 === RenderState2D.TEMPMAT4_ARRAY || this.defines.addInt(ShaderDefines2D.WORLDMAT);
    this.mmat = renderstate2d.worldMatrix4;

    if (RenderState2D.matWVP) {
        this.defines.addInt(ShaderDefines2D.MVP3D);
        this.u_MvpMatrix = RenderState2D.matWVP.elements;
    }

    var sd: Shader2X = Shader.sharders[this.mainID | this.defines._value] || this._ShaderWithCompile();

    if (sd._shaderValueWidth !== renderstate2d.width || sd._shaderValueHeight !== renderstate2d.height) {
        this.size[0] = renderstate2d.width;
        this.size[1] = renderstate2d.height;
        sd._shaderValueWidth = renderstate2d.width;
        sd._shaderValueHeight = renderstate2d.height;
        sd.upload((<ShaderValue>this), null);
    }
    else {
        sd.upload((<ShaderValue>this), sd._params2dQuick2 || sd._make2dQuick2());
    }
}

//mainID定义于ShaderDefines2D,this.deines._value是subID,是在ShaderDefines2D下进一步区分Shader的标志位。
由此看出我们需要控制一个Image使用就需要修改this.deines._value区别于其他ID。幸运是的每一个Image都有一个唯一ID( G I D ) 来 区 分 不 同 纹 理 。 因 此 我 们 可 以 使 用 _GID)来区分不同纹理。因此我们可以使用 GID)使_GID来告诉Laya遇到这个ID的纹理就使用我们自定义的Shader。
Value2D只定义的默认的需要传入Shader程序的uniform变量。因此我们需要自定义Shader时需要新建自己的Value2D扩展类。

export default class MyValue2D extends Laya.Value2D{

    //-----定义自定义的uniform变量----

    //-----定义自定义的uniform变量----
    constructor(subID:number=0){
         super(Laya.ShaderDefines2D.TEXTURE2D,subID);      
    }
    /**调用完成后清除数据**/
    public clear():void{
        super.clear();
    }

    public setValue():void{
        super.setValue();
    }
    /**提交函数**/
    public upload():void{
        //渲染之前可用更新自定义的变量
        super.upload();
    }
}

上面我们就自定义了一个Value2D对象,值得注意的是,我们在逻辑层是取不到这个对象的,因此需要在逻辑层更新这里的参数要么是从逻辑层的数据传到这里,要么直接暴力的使用静态变量。我认为直接使用静态变量更为简单直接,不过也要看具体应用场景。

注册自定义Value2D

创建好自定义的Value2D对象后,我们需要将这个对象注册到Laya框架之中,以备需要用到的时候可以实例化这个自定义对象。

Laya.Value2D._initone(Laya.ShaderDefines2D.TEXTURE2D | (Image对象的$_GID), MyValue2D);
//ex 示例
let img:Laya.Image = new Laya.Image("xxx.png");
let id:number = img['_bitmap'].$_GID;
Laya.Value2D._initone(Laya.ShaderDefines2D.TEXTURE2D | id, MyValue2D);
修改创建Value2D实例化逻辑

完成上面几步我们还是没法让我们想要的Image使用自定义的Value2D对象,默认情况下Context对象在绘制纹理时创建的Value2D默认是TextureSV对象。我们需要修改Context._inner_drawTexture函数来告诉Laya遇到哪个Image对象要使用自定义Shader

_inner_drawTexture(tex: Texture, imgid: number, x: number, y: number, width: number, height: number, m: Matrix, uv: ArrayLike<number>, alpha: number, lastRender: boolean): boolean {
    //省略部分代码..
    //修改之前:
    this._submits[this._submits._length++] = this._curSubmit = submit = SubmitTexture.create(this, mesh, Value2D.create(ShaderDefines2D.TEXTURE2D, 0));
    //修改之后:
    this._submits[this._submits._length++] = this._curSubmit = submit = SubmitTexture.create(this, mesh, Config.customRenderID.indexOf(imgid) != -1 ? Value2D.create(ShaderDefines2D.TEXTURE2D, imgid) : Value2D.create(ShaderDefines2D.TEXTURE2D, 0));
}

这里我将需要使用的自定义纹理ID存储到了Config新建的customRenderID数组中,不过任何可以访问到的对象存储都可以。

完成的创建过程
let img = new Laya.Image("shader/noise.png");
let id: number = img['_bitmap'].$_GID;
Config.customRenderID.push(id);
Laya.Value2D._initone(Laya.ShaderDefines2D.TEXTURE2D | id, CustomShader);
//顶点着色器 片元着色器
this._shader = new Laya.Shader2X(vs,fs,Laya.ShaderDefines2D.TEXTURE2D | id);
this._image.pos(100,100);
this._image.scale(1.2,1.2);
Laya.stage.addChild(this._image);

注意事项

要想使用Laya框架下添加节点,设置坐标,旋转纹理这些都需要在顶点着色器匹配Laya默认的纹理类型的顶点着色器。

precision highp float; 
attribute vec4 posuv;
varying vec2 v_texUV;
#ifdef WORLDMAT
uniform mat4 mmat;
#endif
#ifdef MVP3D
uniform mat4 u_MvpMatrix;
#endif
void main(){
    vec4 pos = vec4(posuv.xy,0.,1.);
    //其余顶点着色器逻辑

    //...
    //其余顶点着色器逻辑

    //匹配Laya默认情况下的顶点着色器
    #ifdef WORLDMAT
        pos=mmat*pos;
    #endif
        vec4 pos1=vec4((pos.x/size.x-0.5)*2.0,(0.5-pos.y/size.y)*2.0,0.,1.0);
    #ifdef MVP3D
        gl_Position=u_MvpMatrix*pos1;
    #else
        gl_Position=pos1;
    #endif
}

-结语-

这种使用Shader的方式适用于,我们既想使用Laya控制纹理的方式,又想实现不同的效果的方式。包括个性化的滤镜,高级的图形效果都可以使用该方式。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值