Cocos Creator Shader入门实战(六):使用setProperty动态设置材质属性,以及材质常用接口

发布于:2025-03-25 ⋅ 阅读:(39) ⋅ 点赞:(0)

引擎:3.8.5

您好,我是鹤九日!



回顾


上篇文章,我们主要讲解了关于材质的使用,主要有这么几点:

一、没有Effect资源,材质无从说起。

二、材质的构建,支持编译器和代码的动态构建

三、材质的动态初始化通过ImaterialInfo,所需参数同属性检查器的配置类似。

四、材质继承于Asset,支持resources.load的动态加载

更多详情内容可参考:

Cocos Creator Shader入门实战(五):材质的了解、使用和动态构建



简介


正式开始之前,先回顾下前面关于Shader的一些知识点。

着色器片段代码是有GLSL语言编写的,它有三个很重要的变量:

一、 varying 用于顶点着色器传递给片段着色器的插值

二、attribute 用于应用程序和顶点着色器的通信

三、uniform 用于应用程序和顶点、片段着色器的通信

在程序开发的过程中,使用最多的是uniform,它多用于属性参数的传递。

Cocos引擎中,设置uniform的参数传递,我们一般做三件事:

一、在EffectAsset资源的CCEffect中的properties中,设置属性参数

二、在EffectAsset资源的CCProgram中着色器部分,编写属性参数的逻辑部分

三、通过代码动态获取材质material,传递属性参数

这里使用到的Effect资源,依然以bulitin-sprite.effect为准。

在灰度渲染的基础上进行少许的拓展,实现渐变效果,用于我们理解动态参数配置。

注:这里就不再复制所有的配置代码,仅粘贴关键部分。



属性配置


CCEffect下的properties配置,属于指定渲染技术(Technique)下的某个渲染过程(pass)下的可选参数。

看上去有些绕口,一环套着一环。这是引擎设定的规则,主要原因:

一、引擎支持多个渲染技术的配置,用于实现不同渲染模式,比如不透明、半透明等物体。

二、每个渲染技术可包含多个渲染过程,用于分段实现渲染效果。

三、每个渲染过程除了着色器名字、入口的配置以外,其他都是可选参数。

这样的设定,便可满足不同平台、不同场景,不同物体的渲染要求。

注:日常的开发中,除非特殊需要,一般都是单技术、单过程的,无须想的太复杂!

这里,我们在原有灰度渲染的的基础配置上,新增两个字段,用于实现精灵的变色效果。配置如下:

properties:
    alphaThreshold: { value: 0.5 }
    startColor: { value: [1.0, 0.0, 0.0, 1.0], editor: {
        type: color, tooltip: "起始颜色"} } 
    endColor: { value: [1.0, 1.0, 1.0, 1.0], editor: {
        type: color, tooltip: "结束颜色"} }

然后在CCProgram的片段着色器部分添加Unform参数,如下:

CCProgram sprite-fs %{
  precision highp float;
  #include <builtin/internal/embedded-alpha>
  #include <builtin/internal/alpha-test>
  in vec4 color;
  
  // 引擎规定,不允许离散使用Uniform
  // ARGS为任意命名
  // 设定以后,引擎会自动将ARGS内的参数与properties的属性相关联
  uniform ARGS {
    vec4 startColor;
    vec4 endColor;
  };
  // ...
}%

注意:倘若仅设定了properties参数,着色器代码部分未设定,则编译器会报错:

type error: Error EFX3302: illegal property declaration for ‘startColor’: no matching uniform

即属性声明与实际的Uniform变量不匹配

这里做个小延伸,为什么要使用{} 结构体的样式来声明呢?

我们将 uniform 的配置修改为如下:

uniform vec4 startColor;
uniform vec4 endColor;

编译器会报错提示:Error EFX2201: vector uniforms must be declared in blocks…

原因:声明uniform必须以块的方式声明。

注:了解即可,知道uniform设定参数的规则就可以了。


回归到正题,通过属性配置和uniform {} 设定并保存参数后,我们便可在材质的属性检查器中看到对应的设置了。

请添加图片描述

注:如果属性参数的设置,没有value,则会走引擎的默认参数配置,都为0



代码构建


颜色渐变效果的实现,可以理解为:在X或Y轴中,随着坐标变换,颜色发生变化。

这样我们在CCEffect的片段着色器处,添加如下代码:

CCProgram sprite-fs %{
  // unifrom变量的块声明
  uniform ARGS {
    vec4 startColor;
    vec4 endColor;
  };

  vec4 frag () {
    vec4 o = vec4(1, 1, 1, 1);

    #if USE_TEXTURE
      o *= CCSampleWithAlphaSeparated(cc_spriteTexture, uv0);
      #if IS_GRAY
        float gray  = 0.2126 * o.r + 0.7152 * o.g + 0.0722 * o.b;
        o.r = o.g = o.b = gray;
      #endif

      // 通过纹理坐标UV的变化来调整X轴的渐变色
      o.rgb *= mix(startColor, endColor, vec4(uv0.)).rgb;
    #endif
  }
}%

效果图如下:

请添加图片描述

重头戏来了,接下来我们通过代码设定下配置的uniform参数。

原理同样很简单: 获取精灵组件的材质,然后调用setProperty接口即可。

@property(Sprite)
sprite: Sprite = null!;

protected onLoad(): void {
    const material = this.sprite.customMaterial;
    if (material) {
        material.setProperty("startColor", new Vec4(1.0, 1.0, 0.0, 1.0));
        material.setProperty("endColor", new Vec4(1.0, 0.0, 0.0, 1.0));
    }
} 

效果如下:

请添加图片描述

注:shader效果的实现,如果不想那么复杂,是不是会很简单呢。



延伸:材质实例和共享材质


这里做下小延伸,因为材质的获取并不是只有一种方式,它有很多的接口。

以Sprite为例,看下它的继承结构:

Sprite组件
UIRenderer渲染精灵组件
Renderer渲染基类
Component

Renderer是渲染的基类,它提供了很多的获取或设置材质的接口:

​​/*
所有可以提交内容到渲染流程的可渲染类的基类,它管理着一组 [[renderer.Model]]
以及它们的可见性、材质和材质实例。

下面是这个组件所管理的各种材质属性的解释,需要正确区分并小心使用:
 * [[sharedMaterials]] 是共享材质,所有使用此材质资源的组件实例都默认使用材质的共享实例对象,
   所有修改都会影响所有使用它的组件实例。
 * [[materials]] 是专为组件对象创建的独立材质实例,所有修改仅会影响当前组件对象。
 * 使用 [[getRenderMaterial]] 获取的渲染材质是用于实际渲染流程的材质对象,
 当存在材质实例的时候,永远使用材质实例。
 * 默认情况下,渲染组件使用共享材质进行渲染,材质实例也不会被创建出来。
 仅在用户通过 [[material]],[[materials]] 和 [[getMaterialInstance]] 接口
 获取材质时才会创建材质实例。
*/
export class Renderer extends Component {
    // 获取默认的共享材质
    get sharedMaterial(): Material | null;
    // 模型的所有共享材质
    get sharedMaterials(): (Material | null)[];
    set sharedMaterials(val: (Material | null)[]);
    
    // 获取默认的材质实例,如果还没有创建,将会根据默认共享材质创建一个新的材质实例
    get material(): Material | renderer.MaterialInstance | null;
    set material(val: Material | renderer.MaterialInstance | null);
    // 所有模型材质
    get materials(): (Material | renderer.MaterialInstance | null)[];
    set materials(val: (Material | renderer.MaterialInstance | null)[]);

    // 获取/设定指定子模型的共享材质资源
    getSharedMaterial(idx: number): Material | null;
    setSharedMaterial(material: Material | null, index: number): void;
    // 获取/设定指定子模型的材质实例
    getMaterialInstance(idx: number): renderer.MaterialInstance | null;
    setMaterialInstance(matInst: Material | renderer.MaterialInstance | null, index: number): void;
}

简单的理解,我们可以认为材质的获取主要有两类:

一、材质实例,用于单个渲染组件,属性的修改也只会影响到该渲染组件。

二、共享材质,多个渲染组件共同使用,属性的修改会影响到所有渲染组件。

鉴于2D渲染组件的特殊性,即不支持多个材质,详情可参考:2D渲染对象注意事项

所以,如下代码的效果都是一样的。

@property(Sprite)
sprite_0: Sprite = null!;
@property(Sprite)
sprite_1: Sprite = null!;
@property(Sprite)
sprite_2: Sprite = null!;

protected onLoad(): void {
    // 材质实例
    const customMaterial = this.sprite_0.customMaterial;
    if (customMaterial) {
        customMaterial.setProperty("startColor", new Vec4(1.0, 1.0, 0.0, 1.0));
        customMaterial.setProperty("endColor", new Vec4(1.0, 0.0, 0.0, 1.0));
    }
    // 共享材质
    const shaderdMaterial = this.sprite_1.getSharedMaterial(0);
    if (shaderdMaterial) {
        shaderdMaterial.setProperty("startColor", new Vec4(1.0, 1.0, 0.0, 1.0));
        shaderdMaterial.setProperty("endColor", new Vec4(1.0, 0.0, 0.0, 1.0));
    }
    // 
    const materialInstance = this.sprite_2.getMaterialInstance(0);
    if (materialInstance) {
        materialInstance.setProperty("startColor", new Vec4(1.0, 1.0, 0.0, 1.0));
        materialInstance.setProperty("endColor", new Vec4(1.0, 0.0, 0.0, 1.0));
    }
} 

请添加图片描述

注:日常的开发中,注意材质实例和共享材质即可
倘若区别不大,推荐使用共享材质,即getShaderMaterial(0)



结尾


今天的文章,到这里就结束了。

可能理解有误,欢迎您的指出,如果觉得文章不错,期待您的点赞和留言,感谢!

我是鹤九日,祝你生活快乐!


网站公告

今日签到

点亮在社区的每一天
去签到