引擎:3.8.5
您好,我是鹤九日!
回顾
前面的几篇文章,讲述的主要是Cocos引擎对Shader使用的一些固定规则,这里汇总下:
一、Shader实现基础是OpenGL ES可编程渲染管线,开发者只需关注顶点着色器和片段着色器。
二、Cocos引擎对Shader实现进行了多层封装,用于简化Shader的难度,以及更灵活管理,主要有:
- 封装了EffectAsset资源,主要包含
CCEffect
和CCProgram
两部分 CCEffect
部分, 采用YAML 1.2标准,与JSON兼容,并封装了渲染技术Technique、渲染过程Pass,以及Properties等多种属性配置CCProgram
部分,采用GLSL语言,引擎封装了宏定义常量、函数以及Chunk等多种文件
三、Shader效果的实现,需要借助Effect Asset资源的属性配置和Material材质的数据包装。
简介
此篇文章,主要讲解关于材质Material部分,主要讲述内容:
一、材质的基本使用
二、使用iMaterialInfo
动态初始化材质
三、材质的属性设置
四、材质的动态加载
正式开始之前,有两点需要注意:
- 材质是依托EffectAsset资源的,没有Effect资源,渲染材质无从谈起。
- 材质的作用,可以理解为两个:一、属性检查器的可视化调整; 二、通过脚本进行属性的动态调整
借助Cocos引擎实现Shader效果,简要的说,三个步骤:
一、创建EffectAsset资源,配置CCEffect
渲染参数和编写CCProgram
着色器片段
二、创建Material材质,设置属性检查器着色器资源,渲染技术、宏定义开关
三、渲染组件设置自定义材质属性
内置Effect
使用之前,首先我们需要有EffectAsset资源,可以通过编译器创建,也可以使用引擎内置的Effect资源。
内置Effect目录:../internal/effects
按照官方文档所言:
- advanced 基于PBR着色器制作的一些高级效果,比如水面、皮肤、头发、玉石等,引擎会持续迭代
- for2d 2D渲染相关着色器,比如:sprite、spine等
- internal 引擎内置功能相关着色器,用户无需关注
- particles 粒子相关特效
- pipeline 管线着色器,比如延迟光照、后效和抗锯齿等
更多信息,可参考:内置着色器
本篇文章,便以内置的buitin-sprite.effect为例,主要原因有二:
- 精灵组件的Grayscale灰度渲染效果是它实现的
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,主要定义物体在物理模拟中的摩擦力、弹性等。
它们的继承结构如下:
既然为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;
});
}
}
总结
今天的文章到这里就结束了!
可能理解有误,欢迎您的指出,如果觉得文章不错,期待您的点赞和留言,感谢!
我是鹤九日,祝你生活快乐!