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

Cocos Creator Shader入门实战(四):预处理宏定义和Chunk

引擎: 3.8.5

您好,我是鹤九日!



回顾


学习Shader,前期是让人烦躁无味的,后期可能就是各种的逻辑让人抓耳挠腮。

一成不变的内容:遵循引擎设定的规则,理解引擎要求的规范。

这里,简单回顾下前面所说的大概内容:

一、Shader的实现,需要Material材质和Effect Asset资源的配合

二、Effect Asset资源主要通过CCEffect配置渲染参数和属性, 通过CCProgram编写着色器片段代码

三、CCEffect的配置使用的是YAML,而CCProgram使用的则是GLSL



开篇


还记得,我们刚开始通过编译器创建的 传统无光照着色器(Effect) 的资源吗?

请添加图片描述

属性结构如下:

请添加图片描述

我当时说过:即使有着官方的文档加持,也是一脸的茫然,不知道这些代表着什么,以及如何使用。

今天的主题便是对EffectAsset资源的说明,看着很繁琐,其实没有那么的难。

简单的理解: 引擎对EffectAsset资源的预编译,其实就是借助了主要两点:

一、预处理宏定义

二、着色器片段Chunk



默认资源


正式开始之前,先做一个小小的延伸,也是自己偶然中的学习发现。

以我们创建的着色器资源,内容是从哪里来呢?

注:从严格意义来说,它不属于Shader的范畴,而是编译器的范畴。

请添加图片描述

标注的部分便是引擎的配置,甚至来说 …/default_file_content/scene 下的这个资源是否会很眼熟呢。

请添加图片描述

这些了解下就可以,万一有机会使用呢!



预处理宏定义


预处理宏定义,其实就是#define声明的的一些开关字段,使用的均为大写。

在任意EffectAsset的资源中,我们看到的Precompile Combinations属性展示开关,就是它。

请添加图片描述

在EffectAsset资源的CCProgram片段中,顶点着色器和片段着色器使用的一些宏,也是宏定义。

//  顶点着色器
CCProgram sprite-vs %{
  precision highp float;
  #include <builtin/uniforms/cc-global>
  #if USE_LOCAL
    #include <builtin/uniforms/cc-local>
  #endif
  #if SAMPLE_FROM_RT
    #include <common/common-define>
  #endif
  // ...
}
// 片段着色器
CCProgram sprite-fs %{
  // ...
  #if USE_TEXTURE
    in vec2 uv0;
    #pragma builtin(local)
    layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture;
  #endif
}

甚至,后面要讲到的material材质的属性里面,也包含着宏定义开关。

请添加图片描述

这里注意:宏定义的显示与否,最终还是受着EffectAsset下的CCEffect属性参数和CCProgram着色器代码控制的。

理由很简单:Effect资源负责编写渲染参数和着色器实现,而Material材质负责对Effect的数据包装和可视化。


优点

引擎提供预处理宏定义的支持,它的几个特性主要有:

一、更好的管理代码内容,根据不宏生成不同组合的代码

二、更好的可视化及控制,方便调试

三、避免代码冗余,执行高效

这里注意:一般情况下,使用到的宏定义都会显示在属性面板上,但以CC_开头的不会显示。


内置宏定义

官方内置了很多的宏定义,这里简单罗列下常见的:

定义说明
USE_INSTANCING是否启用几何体实例化
USE_VERTEX_COLOR是否叠加顶点颜色和 Alpha 值
USE_TEXTURE是否使用主纹理(mainTexture)
USE_ALPHA_TEST是否进行半透明测试(AlphaTest)
SAMPLE_FROM_RT是否是从 RenderTexture 中采样

注:更多的内容可参考官方文档:

无光照宏

卡通渲染宏定义

PBR宏定义

这里注意,我们使用到的预处理定义宏,不仅包含着布尔类型,也会包含着常量、函数等。

在后面说到的Chunk中,它有一个通用的文件叫做:common-define.chunk

路径: ../internal/chunks/common/common-define

请添加图片描述

里面就包含了很多,常用的宏常量、函数等等。

注:如果仔细观察,引擎对EffectAsset资源预编译后

属性检查器中GLSL 300ES Output的输出就有它的影子。


用法

一般来说,使用#define常用于常量、布尔类型开关。比如:

#define USE_INSTANCING 0
#define USE_TWOSIDE 0
#define USE_ALBEDO_MAP 0

较为特殊的用法,便是引擎支持的设定指定的范围宏定义,主要有:

// 通过range([min, max])设定连续数字的宏定义
#pragma define-meta FACTOR range([-5, 5])
// 通过options([...]) 设定指定数字的宏定义
#pragma define-meta FACTOR options([-3, -2, 5])

这里注意:

一、#pragma 是通用的预处理命令,可指定编译选项、定义元数据等

二、#define 用于修饰静态的,比如替换文本,定义常量。

三、#pragma define-meta 是Cocos Creator提供的特有指令,可用于定义动态参数。

这样的好处便是:我们不需要重新编译,并且能够在属性检查器中动态调整。

以EffectAsset的着色器片段为例,主要代码如下

CCProgram sprite-fs %{
  // ...
  #pragma define-mate FILTER_TYPE range([0, 4]);

  vec4 frag () {
    vec4 o = vec4(1, 1, 1, 1);
    o *= CCSampleWithAlphaSeparated(cc_spriteTexture, v_uv0);

    #if FILTER_TYPE == 1
      // gray
      float gray = (o.r + o.g + o.b)/3.0;
      o = vec4(gray, gray, gray, o.a);
    #elif FILTER_TYPE == 2 
       // ...
    #elif FILTER_TYPE == 3
      // ...
    #elif FILTER_TYPE == 4
      // ...
    #endif
    
    return o;
  }
}%

在Effect的属性检查器中,便可以看到它的属性:

请添加图片描述

而在对应的材质属性检查器中看到的属性,通过调整便可预览效果。

请添加图片描述



Chunk


Chunk听着好像是一个很高大上的名词儿,其实简单的理解就是#include

CCProgram sprite-vs %{
  precision highp float;
  #include <builtin/uniforms/cc-global>
  #if USE_LOCAL
    #include <builtin/uniforms/cc-local>
  #endif
}

在日常开发中,我们会对一些通用的常量、方法等,进行分文件存放,使用的时候引用即可。

这样的方式,可以最大程度的复用代码逻辑。

引擎同样也做了这样的事情,所有的封装文件都在:internal/chunks 中。

请添加图片描述

这些跨文件代码的chunk封装,即跨文件代码引用机制,对我们编写Shader是很有帮助的。

上面的例子中提到的一个common-define.chunk 便是一个很好的例子。

官方内置的Chunk有很多,这里不在一一列举,可参考官方文档:

内置全局Uniform

公共函数库

注:Chunk的使用,仅针对于GL SL 300ES


使用

除了内置的Chunk外,官方支持开发者自定义创建Chunk片段,如下图所示:

请添加图片描述

在我们学习参考的示例中,还记得创建无光照Effect资源时候的这段配置吗?

CCEffect %{
  techniques:
  - name: opaque
    passes:
    - vert: legacy/main-functions/general-vs:vert # builtin header
      frag: unlit-fs:frag
}%

如果学习使用,我们可以参考者将顶点着色器的逻辑,作为通用的chunk来使用。

比如,创建sprite-vs.chunk,只需要做到将CCProgram下的着色器代码拷贝过来,放进去就可以了。

precision highp float;
#include <builtin/uniforms/cc-global>
#if USE_LOCAL 
  #include <builtin/uniforms/cc-local>
#endif

in vec3 a_position; // 顶点的位置坐标(XYZ)
in vec2 a_texCoord; // 顶点的纹理坐标(UV),用于映射纹理
in vec4 a_color;    // 顶点的颜色值(RGBA)

out vec4 v_color;   // 用于向片元着色器传递顶点颜色,片元着色器中会插值处理
out vec2 v_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 

  v_uv0 = a_texCoord;
  v_color = a_color;

  return pos;
}

然后,在CCEffect的片段中,修改下顶点着色器的路径即可:

CCEffect %{
  techniques:
  - passes:
    - vert: ../res/effect/rgb/sprite-vs:vert
      frag: sprite-fs:frag
}%

延伸:GLSL 300ES

了解Chunk后,EffectAsset资源的着色器中关于output的预览代码就容易理解了。

由于Chunk基于GLSL 300 ES,那便以它为例,文件是builtin-sprite.effect的顶点着色器为例:

注:内容过多,只显示部分

// 着色器中调用的 #include <builtin/uniforms/cc-global>
// 引擎便将 ../chunks/builtin/unfiroms/cc-global的内容引用过来
layout(std140) uniform CCGlobal {
 highp   vec4 cc_time;
 mediump vec4 cc_screenSize;
 mediump vec4 cc_nativeSize;
 mediump vec4 cc_probeInfo;
 mediump vec4 cc_debug_view_mode;
};
// ...

// 着色器调用的#include <builtin/uniforms/cc-local>
// 引擎便将 ../chunks/builtin/unfiroms/cc-local的内容引用过来
#if USE_LOCALlayout(std140) uniform CCLocal {
   highp mat4 cc_matWorld;
   highp mat4 cc_matWorldIT;
   highp vec4 cc_lightingMapUVParam;
   highp vec4 cc_localShadowBias;
   highp vec4 cc_reflectionProbeData1;
   highp vec4 cc_reflectionProbeData2;
   highp vec4 cc_reflectionProbeBlendData1;
   highp vec4 cc_reflectionProbeBlendData2;
 };
#endif
// 引擎引用下的 ../common/common-define.chunk的通用宏定义相关
#if SAMPLE_FROM_RT
    #define QUATER_PI         0.78539816340
    #define HALF_PI           1.57079632679
    #define PI                3.14159265359
    // ...'
#endif 

// 着色器的片段实现相关
in vec3 a_position;
in vec2 a_texCoord;
in vec4 a_color;
out vec4 color;
out vec2 uv0;
vec4 vert () {
 // ...
}

// gl_Postion是GLSL语言中顶点着色器的最终输出
// main()接口的使用,是引擎在编译时自动添加的
// 这也是我们自定义着色器入口时不能使用main的原因
void main() { gl_Position = vert(); }


结尾

今天的文章到这里就算是结束了,如果理解有误,编写不当,希望您能指正一二。

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


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

相关文章:

  • Docker 》》Docker Compose 》》network 网络 compose
  • 前端UI编程基础知识:基础三要素(结构→表现→行为)
  • 【设计模式】探索状态模式在现代软件开发中的应用
  • 程序化广告行业(18/89):交易模式与关键概念解析
  • Python教程(三):类对象、闭包、装饰器、类型注解、MRO
  • 几款电工仿真软件
  • JDBC 核心 API 全面解析与高效数据库操作
  • 初探 Threejs 物理引擎CANNON,解锁 3D 动态魅力
  • Flutter Dart 流程控制语句详解
  • GetCurrentTime
  • 25年教师资格认定材料及认定详细流程‼
  • centos Supported Java versions are: [17, 21]
  • 【数据分析大屏】基于Django+Vue汽车销售数据分析可视化大屏(完整系统源码+数据库+开发笔记+详细部署教程+虚拟机分布式启动教程)✅
  • 《灵珠觉醒:从零到算法金仙的C++修炼》卷三·天劫试炼(55)聚宝盆装区间 - 合并区间(排序贪心)
  • 【Erdas实验教程】015:哨兵二号卫星数据简介及下载方法
  • PyTorch系列教程:基于LSTM构建情感分析模型
  • Spring Boot应用首次请求性能优化实战:从数据库连接池到JVM调优
  • OSPF | LSDB 链路状态数据库 / SPF 算法 / 实验
  • 爬虫逆向:Hook 技术原理与实战
  • Java 学习记录:基础到进阶之路(二)