当前位置: 首页 > article >正文

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

引擎:3.8.5

您好,我是鹤九日!



回顾


前面的几篇文章,讲述的主要是Cocos引擎对Shader使用的一些固定规则,这里汇总下:

一、Shader实现基础是OpenGL ES可编程渲染管线,开发者只需关注顶点着色器片段着色器

二、Cocos引擎对Shader实现进行了多层封装,用于简化Shader的难度,以及更灵活管理,主要有:

  1. 封装了EffectAsset资源,主要包含CCEffectCCProgram两部分
  2. CCEffect部分, 采用YAML 1.2标准,与JSON兼容,并封装了渲染技术Technique、渲染过程Pass,以及Properties等多种属性配置
  3. CCProgram部分,采用GLSL语言,引擎封装了宏定义常量、函数以及Chunk等多种文件

三、Shader效果的实现,需要借助Effect Asset资源的属性配置和Material材质的数据包装。



简介


此篇文章,主要讲解关于材质Material部分,主要讲述内容:

一、材质的基本使用

二、使用iMaterialInfo动态初始化材质

三、材质的属性设置

四、材质的动态加载

正式开始之前,有两点需要注意:

  1. 材质是依托EffectAsset资源的,没有Effect资源,渲染材质无从谈起。
  2. 材质的作用,可以理解为两个:一、属性检查器的可视化调整; 二、通过脚本进行属性的动态调整

借助Cocos引擎实现Shader效果,简要的说,三个步骤:

一、创建EffectAsset资源,配置CCEffect渲染参数和编写CCProgram着色器片段

二、创建Material材质,设置属性检查器着色器资源,渲染技术、宏定义开关

三、渲染组件设置自定义材质属性



内置Effect

使用之前,首先我们需要有EffectAsset资源,可以通过编译器创建,也可以使用引擎内置的Effect资源。

内置Effect目录:../internal/effects
请添加图片描述

按照官方文档所言:

  • advanced 基于PBR着色器制作的一些高级效果,比如水面、皮肤、头发、玉石等,引擎会持续迭代
  • for2d 2D渲染相关着色器,比如:sprite、spine等
  • internal 引擎内置功能相关着色器,用户无需关注
  • particles 粒子相关特效
  • pipeline 管线着色器,比如延迟光照、后效和抗锯齿等

更多信息,可参考:内置着色器

本篇文章,便以内置的buitin-sprite.effect为例,主要原因有二:

  1. 精灵组件的Grayscale灰度渲染效果是它实现的
  2. CCEffect的属性配置和顶点着色器的片段代码,算是2D渲染中较为通用的。

内容如下:

  • CCEffect部分:
// Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd.
CCEffect %{
  # 渲染技术
  techniques:
  # 渲染过程
  - passes:
    # 顶点、片段着色器的名字和入口,必须参数
    - vert: sprite-vs:vert
      frag: sprite-fs:frag
      # 深度和模板测试
      depthStencilState:
        depthTest: false
        depthWrite: false
      # 混合模式状态
      blendState:
        targets:
        - blend: true
          blendSrc: src_alpha
          blendDst: one_minus_src_alpha
          blendDstAlpha: one_minus_src_alpha
      # 光栅化状态,禁用面剔除,常见参数有:front, back, none
      rasterizerState:
        cullMode: none
      # 属性参数配置,可用于属性检查器的可视化调整或代码的传参
      properties:
        alphaThreshold: { value: 0.5 }
}%
  • CCProgram着色器部分:
// 顶点着色器
CCProgram sprite-vs %{
  // 精度的设置
  precision highp float;
  // 引擎的Chunk和宏定义开关
  #include <builtin/uniforms/cc-global>
  #if USE_LOCAL
    #include <builtin/uniforms/cc-local>
  #endif
  #if SAMPLE_FROM_RT
    #include <common/common-define>
  #endif
  // 应用程序传入的顶点参数,分别是:位置(xyZ), 纹理坐标(uv), 颜色(rgba)
  in vec3 a_position;
  in vec2 a_texCoord;
  in vec4 a_color;
  // 顶点着色器传给片段着色器的数据,颜色会经过光栅化采样,纹理坐标用于纹理采样
  out vec4 color;
  out vec2 uv0;

  vec4 vert () {
    vec4 pos = vec4(a_position, 1);
    // 顶点坐标的坐标转换
    #if USE_LOCAL
      // 从局部坐标系转换到世界坐标系
      pos = cc_matWorld * pos;
    #endif

    #if USE_PIXEL_ALIGNMENT
      // cc_matView 视图矩阵, 将世界坐标系转换到视图坐标系
      pos = cc_matView * pos;
      pos.xyz = floor(pos.xyz);
      // cc_matProj 投影矩阵,将视图坐标转换为裁剪坐标
      pos = cc_matProj * pos;
    #else
      // cc_matViewProj 视图投影矩阵,将世界坐标转换为裁剪坐标
      pos = cc_matViewProj * pos;
    #endif

    uv0 = a_texCoord;
    #if SAMPLE_FROM_RT
      CC_HANDLE_RT_SAMPLE_FLIP(uv0);
    #endif
    color = a_color;

    return pos;
  }
}%

// 片段着色器
CCProgram sprite-fs %{
  precision highp float;
  #include <builtin/internal/embedded-alpha>
  #include <builtin/internal/alpha-test>

  in vec4 color;

  // 纹理采样相关
  #if USE_TEXTURE
    in vec2 uv0;
    #pragma builtin(local)
    layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture;
  #endif

  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
    #endif

    o *= color;
    ALPHA_TEST(o);
    return o;
  }
}%

注: 注释相关在原有的文章中提及过,这里就当是加深印象吧!



基本使用


创建任意Effect资源,将builtin-sprite.effect内容复制粘贴进去。

然后创建Material材质,这里使用编译器直接构建普通材质Material即可。

请添加图片描述

其属性如下:

请添加图片描述

  • Effect :材质使用到的着色器资源EffectAsset,可通过下拉条进行选择。
  • Technique :Effect资源在CCEffect中所包含的渲染技术,可通过下拉条选择,用于设置不同的渲染效果。
  • Pass… :Effect资源在CCEffect中针对于每一个渲染技术所包含的渲染过程
  • USE… :预处理宏定义开关组合,不同宏定义的组合生成不同的代码,进而实现不同的效果。

这里我们只需关注Effect即可,选择我们添加的effect文件,保存。

请添加图片描述

构建任一页面,页面中包含一个Sprite组件,如下图所示:

请添加图片描述

将创建的demo.mtl材质拖拽进去,保存后,打开demo.mtl材质的属性检查器,如下图:

请添加图片描述

标记部分的勾选与否,便可直接预览灰度渲染效果。



自定义材质


Cocos引擎对Shader为开发者默默做了很多的事情。

上图中,我们借助的是Sprite渲染组件下的customMaterial ,即自定义材质。

我们可以这样认为:

一、对于任何持有CustomMaterial属性的UI和2D组件,我们都可以认为设置自定义材质

二、自定义材质属性即使为空,引擎也会进行默认配置

学习shader,customMateiral属性是一个很重要的入口。

注意:2D渲染对象不支持多材质,自定义材质数量最多为一个。

参考:2D渲染对象自定义材质



imaterialInfo初始化


材质的构建,编译器是最为直观、简洁的方式。那么它支持代码初始化吗?答案可以。

代码的动态初始化,与编译器的构建有相似之处。

引擎对于材质构建所需要的参数进行了封装叫做:IMaterialInfo,它的主要结构如下:

// 初始化材质的基本信息
export interface IMaterialInfo {
    // effectAsset资源引用,和effectName至少要指定一个
    effectAsset?: EffectAsset | null;
    // effect资源名,和effectAsset至少要指定一个
    effectName?: string;
    // 使用到的渲染技术,默认0
    technique?: number;
    // 使用到的预处理宏定义组合,默认全为0
    defines?: renderer.MacroRecord | renderer.MacroRecord[];
    // 自定义管线状态
    states?: renderer.PassOverrides | renderer.PassOverrides[];
}

使用的接口是Material下的initialize,代码的构建实例:

@ccclass('demo')
export class demo extends Component {
    @property(Sprite)
    sprite: Sprite = null!;
    @property(EffectAsset)
    effect: EffectAsset = null!;        // Effect资源引用          

    protected onLoad(): void {
        this.initMaterial();
    }
    // 初始化材质
    private initMaterial() {
        const material = new Material();
        const info: IMaterialInfo = {
            effectAsset: this.effect,
            technique: 0,
            defines: {
                USE_TEXTURE: true,      // 宏定义开关,使用纹理开启
                IS_GRAY: true,          // 宏定义开关,置灰开启
            }
        };
        material.initialize(info);
        // 设置精灵的自定义材质
        this.sprite.customMaterial = material;
    };
}

效果如下:

请添加图片描述



动态加载

不管是普通材质Material,还是物理材质Physics Material,它们都属于Asset资源的范畴。

  • 普通材质Material,主要用于渲染
  • 物理材质Physics Material,主要定义物体在物理模拟中的摩擦力、弹性等。

它们的继承结构如下:

Material
Asset
PhysicsMaterial

既然为Asset资源,那便支持动态加载,大概示例如下:

@ccclass('demo')
export class demo extends Component {
    @property(Sprite)
    sprite: Sprite = null!;       

    protected onLoad(): void {
        this.loadMateiral();
    }

    private loadMateiral() {
        // 资源路径,不需要后缀
        const url = "demo/demo";
        resources.load(url, Material, (err: Error, material: Material) => {
            if (err) {
                return console.error(err.message);
            }
            this.sprite.customMaterial = material;
        });
    }
}


总结


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

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

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


http://www.kler.cn/a/598500.html

相关文章:

  • C++和标准库速成(十二)——练习
  • Flutter Dart 泛型详解
  • MATLAB+Arduino控制小车直行+转向
  • JVM(基础篇)
  • 深入解析大文件切片上传:Vue3 实现全流程指南
  • 低配电脑畅玩《怪物猎人:荒野》,ToDesk云电脑优化从30帧到144帧?
  • 多无人车协同探索开源包启动文件介绍(下)
  • MyBatis 学习经验分享
  • 使用LLama-Factory的简易教程(Llama3微调案例+详细步骤)
  • JavaScript案例0323
  • 【Git】用Git命令克隆一个远程仓库、修改仓库中的文件,并将更改推送到远程仓库
  • 图论 | 岛屿数量(深搜,广搜)
  • 虾皮(Shopee)商品ID获取商品详情请求示例
  • Android Compose 约束布局(ConstraintLayout、Modifier.constrainAs)源码深度剖析(十二)
  • 【完整版】DeepSeek-R1大模型学习笔记(架构、训练、Infra、复现代码)
  • SQL的DCL,DDL,DML和DQL分别是什么
  • 2025-03-21 Unity 序列化 —— 自定义2进制序列化
  • 面试常问系列(一)-神经网络参数初始化
  • NLP高频面试题(十一)——RLHF的流程有哪些
  • ModuleNotFoundError: No module named ‘flask‘ 错误