快速入门 Cocos Creator 3D Shader 上篇
前言
Shader,又名着色器,是控制 GPU 绘制的指令集。从某种意义来说,着色器是一个把输入转换到输出的程序,为图形渲染管线的某个特定部分而运行。图形渲染管线是渲染的核心组件。通过给定虚拟相机、3D 模型以及光源等场景要素来生成图像。整个渲染流程又可以大致分为:应用阶段、几何阶段、光栅化阶段。其中,应用阶段是在 CPU 上进行,主要负责场景渲染数据的准备,比如顶点数据,相机数据等;几何阶段是在 GPU 进行,主要负责和渲染图元打交道,处理需要绘制的几何相关工作,决定图元怎样绘制,在哪里绘制;光栅化阶段也是在 GPU 上处理,主要是将几何图元进行逐像素处理,最终渲染到屏幕上。下面,引用网络上的一个渲染流程的图片让大家基本了解一下整个过程。
在 Cocos Creator 3D 中主要采用的是 YAML 格式搭配 GLSL 语法以及基本着色器(顶点、片段着色器)来书写 Shader。编辑器中可以通过在 资源管理器 面板,右键选择 新建/Effect 来创建 Shader 资源。但是在实际的项目开发中,通常很多美术效果的表现都没法由程序来独立完成,那这时就会涉及到,如何让 Shader 变成更加简便,可操控的资源呢?那就引入了材质的概念。一个材质依赖一个 Shader 资源,可以通过属性开放的方式提供可调配界面来达到不改一行 Shader 代码就能实现不同的效果。同样通过类似创建 Shader 资源的方式来创建 Material 资源。并选择这个 Material 的 Effect 为前面创建的 Shader。
结构介绍
经过上面的操作后创建的默认 Shader 是一个不带光照,只开放贴图和颜色属性的 shader。这样的 Shader 一般可以应用于 UI 以及粒子上。
接着,把默认创建的 Shader 做一个拆分,剔除掉内容后,基本结构大致是这个样子。
CCEffect %{
techniques:
- name: xxx
passes:
- vert: program-name:function-name
frag: program-name:function-name
[properties]
- name: xxx
[passes]
}%
CCProgram vs-name %{}%
CCProgram fs-name %{}%
其中,带 [] 代表的是可以有多个数据。 CCEffect 是一个渲染流程的清单,里面包含了这个 Shader 所包含的所有 pass,顶点、片段选取,属性声明和初始设置以及测试模版配置等等。而 CCProgram 是一个 Shader(顶点/片段着色器) 片段,一个 Shader 文件里又可以包含多个顶点/片段着色器来共 pass 使用。
语法格式
1. CCEffect 格式介绍
在之前的结构介绍里可以看到 CCEffect 是由多个 technique 组成,technique 代表一个实现技术,每一个材质只能选用对应 Shader 的其中一个 technique,每个 technique 可以有多个 pass,pass 就是代表渲染一次模型,多个 pass 之间可以共同作用。例如一个卡渲的 Shader,第一个 pass 进行描边绘制,第二个 pass 进行色彩绘制。
引擎提供了一个 连字符 + 空格 的形式来区分数组,多 pass 书写如下:
passes:
- vert: 片段名:入口函数名 // (入口函数的名字必须非 main)
frag: 片段名:入口函数名
# ...
- vert: 片段名:入口函数名
frag: 片段名:入口函数名
每个 pass 都会优先定义引用顶点/片段着色器以及它们相对应的入口函数,然后再定义属性以及测试缓冲等。
passes:
- vert: test-vs:vert
frag: test-fs:frag
properties:
# ...
blendState:
# ...
# ...
CCProgram test-vs %{ vec4 vert(){ return (vec4 类型值) }}%
CCProgram test-fs %{ vec4 frag(){ return (vec4 类型值) }}%
当然,也有可能会遇到需要数据间的引用与继承来避免书写重复的内容,引用的方式引擎提供了一个 & 配合 * 使用的方式,例如:
object1: &o1 // 开放复用数据 object1
key1: value1
object2:
key2: value2
key3: *o1 // 复用开放数据 object1
// 最后形成样式
{
"object1": {
"key1": "value1"
},
"object2": {
"key2": "value2",
"key3": {
"key1": "value1"
}
}
}
继承的方式引擎提供了一个 & 配合 << 和 * 使用的方式,例如:
object1: &o1
key1: value1
key2: value2
object2:
<<: *o1
key3: value3
// 最后形成样式
{
"object1": {
"key1": "value1",
"key2": "value2"
},
"object2": {
"key1": "value1",
"key2": "value2",
"key3": "value3"
}
}
pass 的更多参数参考这里。
2. CCEffect 属性 properties
属性 properties 是与编辑器交互的核心部分,它的通用格式是:prop-name: { value: init-value, [target], [editor] }。其中,[] 代表可选部分。
prop-name 代表属性的名字。
value 属性初始值。
值类型 | 默认值 | 说明 |
---|---|---|
ivec2/3/4,vec2/3/4, color, array | [0],[0, 0],[0, 0, 0],[0, 0, 0, 0] | 根据声明选择数组长度。例如:ivec3 |
int, float | 0 | 任意数字。例如:value: 0.5 |
sampler2D | default | black, grey, white, normal, default。例如:value: grey |
samplerCube | default-cube | black-cube, white-cube,default-cube。例如:value: white-cube |
可选属性 target ,指向作用的 Uniform 属性。例如:声明 replaceR:{ value: { 0.5 }, target: mainColor.r } uniform vec4 mainColor; 那么,该属性则会修改 mainColor 的 r 通道。
可选编辑器呈现效果属性 editor,是一个可以在编辑器上定制数据的属性。可以通过在 properties 下写入 metadata: { visible: false } 的方式定义基础通用编辑器呈现方式,然后差异组件自身再定义不一样的数据。在下面我会介绍基础配置的说明,更多配置请查看这里。
配置名 | 值 | 说明 |
---|---|---|
displayName | any string | 默认的是属性的名字,如果这里填入新的值,则在编辑器的属性名上显示该值。例如:editor: { displayName: Albedo } |
parent | 宏 | 指定宏启用后才显示该属性。关于宏的介绍,会在接下来 4. CCProgram 继续介绍。例如:editor: { parent: USE_NORMAL_MAP } |
type | vector, color | 当前属性的类型,通常 vec2/3/4,array,number,贴图类型不需要填写,像颜色这样数值是 vec4 显示是色值编辑的,就需要定义类型。例如:editor: { type: color } |
visible | true, false | 是否在编辑器上可视。例如: editor: { visible: false } |
tooltip | any string | 鼠标移到属性名上提示的内容。例如:editor: { tooltip: 显示看这里 } |
range | [min, max, [step]] | 数值可填写范围,step 是数值在编辑器里调整的步长。例如:editor: { range: [0, 1, 0.1] } |
3. CCProgram
主要包含的是 Shader 片段,如果是在 CCEffect 的 pass 里声明需要使用的顶点/片段着色器,入口函数必须返回相对应的数据,比如顶点着色器返回需要绘制的屏幕坐标,片段着色器返回绘制的像素颜色。结构大致如下:
CCProgram unlit-vs %{
precision highp float;
vec4 vert () {
vec4 position = vec4(1, 1, 1, 1);
return position;
}
}%
CCProgram unlit-fs %{
precision highp float;
vec4 frag () {
vec4 color = vec4(1, 1, 1, 1);
return color;
}
}%
整个内容的书写会在下一篇里做一个详细说明,这里就不过多描述。
结合上述属性定义,做一个小的展示如下:
properties:
mainColor: { value: [1, 1, 1, 1], editor: { type: color, displayName: bgColor } }
showRange: { value: 0.5, editor: { range:[0, 1, 0.1] } }
mainTexture: { value: grey, editor: { tooltip: 主贴图, parent: USE_TEXTURE } }
reOrderColor: { value: [1, 1, 1, 1], editor: { type: color, visible: false } }
uniform Constants {
vec4 mainColor;
vec4 reOrderColor;
float showRange;
};
uniform sampler2D mainTexture;
小目标
接下来应用上面的属性内容以及基础框架,尝试开始定义一下自定义 Shader 的基础属性。先定义一个可调节颜色属性,代码如下。
// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
CCEffect %{
techniques:
- name: test
passes:
- vert: test-vs:vert
frag: test-fs:frag
properties:
mainColor: { value: [1, 1, 1, 1], editor: { type: color } }
}%
CCProgram test-vs %{
precision mediump float; // 精度定义,不能去
highp vec4 vert () {
highp vec4 position = vec4(0, 0, 0, 1);
return position;
}
}%
CCProgram test-fs %{
precision mediump float;
vec4 frag () {
vec4 o = mainColor;
return o;
}
}%
这个时候会发现在编辑器的控制台输出如下报错。这个报错的主要原因在于声明了 mainColor 属性,但是却没有实际的 uniform 来与它对应,接下来在 test-fs 里加入这个声明 uniform vec4 mainColor;
这时发现,报错的方式换了,因为除了 sampler 类型,其余的 uniform 声明都必须放在声明块里(这个在下一篇文章里会做一个说明)。因此,还是在 test-fs 里加入 uniform Constants { vec4 mainColor; };
然后,在编辑器中查看,报错没有了,此时,点击 show 材质资源,出现定义的属性 mainColor。各位同学也可以尝试一下用上面提到的方式修改显示的名字,来观察定义的属性的名字是否是你定制的,如果是,那么恭喜你,完成了自定义材质属性的第一步!
接下来,开始应用这个材质。在 层级管理器 上右键 创建/3D 对象/Quad,选中 Quad 节点,在 属性检查器 里的 materials 下方替换成当前创建的材质,观察模型会发现什么内容都没有出现,这个时候就要回到我们在开篇提及到那部分内容了,图形渲染管线接受一组 3D 坐标,然后把它们转变为你屏幕上的有色 2D 像素输出,图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。从上面我们书写的内容就可以看出问题出现在顶点着色器中,顶点着色器顾名思义就是要负责处理顶点数据的,在这里始终输出的都是一个点的信息,这样最终在屏幕上肯定是什么也没有。因此,在这里必须要将模型 Quad 的顶点数据传输上来最终绘制上去。那么,要如何上传顶点数据呢?那就要先告诉大家这个顶点数据怎么来的。顶点数据可以是整个游戏世界里的绘制模型的世界坐标点,也可以是隐藏在其他地方的模型坐标点。最后都会通过一系列的坐标转换(模型坐标 -> (模型矩阵 )-> 世界坐标 -> (视图矩阵)-> 观察坐标 -> (投影矩阵)-> 裁剪坐标 -> (视口变换) -> 屏幕坐标),返回出换算好的屏幕点给下一个阶段使用。在顶点着色器中可以通过一个 in 关键字来接收相对应输入的顶点数据 a_position。
CCProgram test-vs %{
precision mediump float; // 精度定义,不能去
#include <cc-global>
#include <cc-local>
in vec3 a_position;
highp vec4 vert () {
highp vec4 position = vec4(a_position, 1);
// 投影矩阵 <- 视图矩阵 <- 世界矩阵 <- 本地坐标
return cc_matProj * (cc_matView * cc_matWorld) * position;;
}
}%
上面,可以看出 include 出了几个头文件,多了几个 cc_matProj 等都没有被声明量,这几个变量从名字上看,就知道是空间变换的矩阵。cc-local 里包含了本地空间转实际空间的矩阵(cc_matWorld)等,cc-global 里包含了世界空间到屏幕空间的一系列矩阵(cc_matView,cc_matProj)和其他全局数据。最终计算的是本地空间坐标转屏幕坐标的变换。有兴趣的可以看下官方说明,至于 include 的用法,会在下一篇详细说明。
在这里还需要给大家补充的一点是,可能会有一部分人会有疑问,关于如何定义着色器的输入数据,我就拿 Quad 打比方,在 Quad 的模型导入的时候,就定义了自身会传入的数据,可以通过对 Quad 资源右击,在 Library 中显示查看相对应的 json 文件里的 attributes 来知悉,如果定义了 a_position 则会传入 顶点数据,如果定义了 a_normal 则会传入法线数据,以此类推。
最后,经过上述步骤在编辑器下就能看到一个白色的正方形。通过双击材质修改颜色属性,就可以控制屏幕上的正方形方块变色。
结语
看到这里的同学应该大概能知道引擎的 Shader 开发流程是怎样的了,本人也是才这条道路上磕磕绊绊的学习,第一次写教程如果有哪里写错的或者说的不够清晰的地方希望大家能够不吝赐教。接下来会写更多的教程让大家更深入的了解引擎的 Shader 使用,以更好的制作出更加精美的效果,共勉。
推荐阅读
-
用 shader effect 实现雨滴落水效果!Cocos Creator 3D !
-
cocos creator 3D | 拇指投篮 | 3D项目入门实战
-
初探雾效果!shader 源码分析与讲解! Cocos Creator 3D Shader Fog !
-
瞄准器!3D入门实战!拇指射箭!Cocos Creator 3D !
-
蚂蚁庄园运动会登山赛!3d项目入门实战!Cocos Creator 3D!
-
笑容逐渐消失? shader 编程入门实战 ! Cocos Creator!
-
不好意思,我膨胀了!shader 入门精要!Cocos Creator 3D Shader !
-
cocos creator3d如何显示3d物体_mesh_shader_材质
-
快速入门 Cocos Creator 3D Shader 上篇