这是一篇基于CocosCreator2.x版本的科普
一、前言
CocosCreator在2.0版本重写了底层渲染器,并逐步在后续版本中加入了对材质系统的支持。我们现在可以很方便的通过编写组合不同的 Effect 和材质,并关联到对应的渲染组件上,实现丰富的渲染效果表现;
本文基于CocosCreator2.x版本,主要以讲解 CocosCreator Shader Effect 知识为主,相关涉及到的知识点包括:WebGL、GLSL ES、YAML、ShaderToy 等等;由于涉及到的知识点非常多,所以只会介绍编写 Creator Effect 时所必须了解的内容,其它相关的知识只做简单说明不会展开太多,需要了解更详细的相关资料请点击参考部分链接;
因为TypeScript可以很方便的写出WebGL渲染示例,同时也是Creator引擎采用的标准开发脚本,所以本文所有示例代码都是基于WebGL且使用TypeScript编码,并在Creator2.3.3版本的Web环境下调试通过;
在学习Creator Effect 之前,我们先来了解一下Creator渲染基础WebGL部分的相关知识,这样能更好地理解本文后续的内容;
二、WebGL 渲染简介
- OpenGL、OpenGL ES 和 WebGL
OpenGL 是一套跨平台的底层图形渲染API,几乎所有的游戏引擎都支持它;
OpenGL ES 是OpenGL为嵌入式和移动系统派生的轻量化子集;
WebGL 是基于OpenGL ES的跨平台Web标准API,并通过HTML5 Canvas元素公开给ECMAScript;

OpenGL 从2.0开始支持可编程渲染管线。OpenGL ES 则是使用OpenGL着色器语言(GLSL)基于Shader的API。WebGL 在语义上与基础OpenGL ES API相似,非常接近OpenGL ES规范,并为JavaScript之类的内存管理语言做了进一步的兼容;WebGL 1.0公开了OpenGL ES 2.0的API;WebGL 2.0公开了OpenGL ES 3.0的API;
WebGL的渲染流程简单概括如下图:
- 一个简单的WebGL示例
/**
* 使用 WebGL 着色器代码在屏幕中心画一个红点
*/
// 顶点着色器代码
let VS =
"attribute vec4 a_Position; \n" + // 外部传入坐标信息
"void main() { \n" +
" gl_Position = a_Position; \n" + // 赋值给内置变量,控制每一个点
" gl_PointSize = 10.0; \n" + // 赋值给内置变量,控制所有的点
"} \n"
// 片元着色器代码
let FS =
"precision mediump float; \n" + // 指定变量精度
"uniform vec4 u_FargColor; \n" + // 外部传入颜色信息
"void main() { \n" +
" gl_FragColor = u_FargColor; \n" + // 赋值给内置变量,控制每个片元的颜色
"} \n"
// 主函数
function main() {
// 获取Canvas
let canvas = document.getElementById("webgl")
// 获取WebGL上下文
let gl = getWebGLContext(canvas)
// 初始化定义的着色器代码
if (initShaders(gl, VS, FS)) {
// 绑定着色器开放的变量地址
let a_Position = gl.getAttribLocation(gl.program, "a_Position")
let u_FargColor = gl.getUniformLocation(gl.program, "u_FargColor")
// 指定颜色清屏
gl.clearColor(0.0, 0.0, 0.3, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
// 将值传递给着色器变量
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0)
gl.uniform4f(u_FargColor, 1.0, 0.0, 0.0, 1.0)
// 绘制一个点
gl.drawArrays(gl.POINTS, 0, 1)
}
}
示例代码分成两部分,通过调用WebGL的API代码部分(main函数部分)和GLSL的着色器代码部分(VS、FS变量定义部分)。API调用部分完成设备、缓冲初始化,参数传递和具体绘制操作;着色器部分则完成渲染过程中具体的表现效果实现;完整代码请查看本文参考部分,限于篇幅原因我们将主要介绍着色器代码;
着色器(Shader)代码分为两部分,都是基于GLSL ES着色器语言编写的代码段;
- 顶点着色器:处理渲染过程中顶点的特性,计算顶点的位置和大小;
- 片元着色器:处理渲染过程中每个片元所需要呈现的表现效果,比如颜色;(片元可以理解为像素)
- GLSL ES 常用语法简介
本文只介绍部分在编写Creator Effect 时会涉及到的常用的GLSL ES 语法、变量和函数,更详细的相关资料请点击参考部分链接;
- 大小写敏感
Test1 // 两个不同的变量
test1
2. 每条语句都以分号;结束
float ftest = 1.0; // 定义一个浮点数
3. 每个着色器程序都要有main()入口函数,并且必须是void类型;
void main () {...}
4. 单行注释使用 //,多行注释则是/* 注释 */
// 单行注释
/* 多行注释*/
5. 支持基本数据类型,但不支持String
// 支持 float、int、bool 等基本类型
float fnumber = 1.1; // 浮点数
int inumber = 1; // 整型
bool blogic = true; // 布尔型
// 强制类型转换
int fresult = int(fnumber); // 去掉小数部分,例如:3.14转换为3
bool bresult = int(blogic); // true转为1,false转为0
int iresult = float(inumber); // int转换为float,例如:3转换为3.0
bresult = float(blogic); // bool转换为float,true转为1.0,false转为0.0
iresult = bool(inumber); // int转换为bool,0转为false,其余非0数转为true
fresult = bool(fnumber); // int转换为bool,0.0转为false,其余非0.0数转为true
6. 支持矢量类型和矩阵类型;
// ------------------矢量
// 定义构造具有2、3、4个浮点数元素的矢量类型
vec2 v2 = vec2(1.0, 1.0);
vec3 v3 = vec3(1.0, 1.0, 1.0);
vec4 v4 = vec4(1.0, 1.0, 1.0, 1.0);
// 还可以使用:ivec2/ivec3/ivec4,bvec2/bvec3/bvec4 定义int和bool矢量类型
// 按矢量类型定义不同(vec2/vec3/vec4),访问矢量元素分量值
v4.x = 1.0; // 可以通过 x/y/z/w 方式获取矢量的顶点坐标分量
v4.r = 1.0; // 可以通过 r/g/b/a 方式获取矢量的颜色分量
v4.s = 1.0; // 可以通过 s/t/p/q 方式获取矢量的纹理坐标分量
vec2 = v4.xy; // 混合访问矢量的顶点x和y坐标分量
vec3 = v4.rgb; // 混合访问矢量的颜色r,g,b色值分量
// 矢量运算
vec2 v2_1 = vec2(2.0, 3.0);
v2 = v2 + v2_1; // (v2.x + v2_1.x, v2.y + v2_1.y)
v2 = v2 + 2.0; // (v2.x + 2.0, v2.y + 2.0)
// ------------------矩阵
// 定义构造具有 2x2、3x3、4x4的浮点数元素的矩阵
mat2 m2 = mat2(1.0, 1.0,
1.0, 1.0);
mat3 m3 = mat3(1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, 1.0);
mat4 m4 = mat4(1.0, 2.0, 3.0, 4.0,
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0);
// 矩阵取值
vec4 v4 = m4[0];
float mx_1 = m4[0].x;
float mx_2 = m4[0][1];
// 矩阵运算
mat2 m2_1, m2_2;
vec2 v2;
float f;
// 加法
m2_1 + f; // m2_1[0].x + f; m2_1[0].y + f;
// m2_1[1].x + f; m2_1[1].y + f;
// 矩阵右乘矢量
vec2 v2_r = m2_1 * v2; // v2_r.x = m2_1[0].x * v2.x + m2_1[1].x * v2.y;
// v2_r.y = m2_1[0].y * v2.x + m2_1[1].y * v2.y;
// 矩阵左乘矢量
v2_r = v2 * m2_1; // v2_r.x = v2.x * m2_1[0].x + v2.y * m2_1[0].y;
// v2_r.y = v2.x * m2_1[1].x + v2.y * m2_1[1].y;
// 矩阵相乘
mat2 m2_r = m2_1 * m2_2; // m2_r[0].x = m2_1[0].x * m2_2[0].x + m2_1[1].x * m2_2[0].y;
// m2_r[1].x = m2_1[0].x * m2_2[1].x + m2_1[1].x * m2_2[1].y;
// m2_r[0].y = m2_1[0].y * m2_2[0].x + m2_1[1].y * m2_2[0].y;
// m2_r[1].y = m2_1[0].y * m2_2[1].x + m2_1[1].y * m2_2[1].y;
7. 支持结构体定义
// 定义结构体
struct light {
vec4 color;
vec3 position;
}
// 声明light类型变量
light light_1;
8. 变量限定符:限定符赋给变量特殊的含义
-
const 用于声明非可写的编译时常量变量
-
attribute 用于经常更改的信息,只可以在顶点着色器中使用
-
uniform 用于不经常更改的信息,可同时用于顶点着色器和片元着色器
-
varying 用于顶点着色器传递到片元着色器的插值信息
- 支持取样器
/* GLSL ES有一种内置类型称作取样器(sampler),我们必须通过此类型变量访问纹理,
* 并且取样器变量只能是uniform变量。取样器有两种基本类型:sampler2D和samplerCube
*/
uniform sampler2D u_Sampler;
sampler1D 访问一个一维纹理
sampler2D 访问一个二维纹理
sampler3D 访问一个三维纹理
samplerCube 访问一个立方体纹理
sampler1DShadow 访问一个带对比的一维深度纹理
sampler2DShadow 访问一个带对比的二维深度纹理
10. 精度定义
//包括 lowp highp mediump 三种精度,例如:
// 在数据定义前指定变量精度
highp vec4 position;
// 在 Shader 代码的开始处指定默认精度
precision highp float;
/* 在顶点 Shader 中,如果没有默认的精度,则 float 和 int 精度都为 highp
* 在片元 Shader 中,float 没有默认的精度,所以必须在片元 Shader中为 float
* 指定一个默认精度或为每个 float 变量指定精度。
*/
-
GLSL ES 常用内置的变量与函数
- 常用内置变量:
gl_Position // 用于vertex shader, 写顶点位置;被图元收集、裁剪等固定操作功能所使用;
gl_PointSize // 用于vertex shader, 写光栅化后的点大小,像素个数;
gl_FragColor // 用于Fragment shader,写fragment color;被后续的固定管线使用;
gl_FragData // 用于Fragment shader,是个数组,写gl_FragData[n] 为data n;被后续的固定管线使用;
gl_FragCoord // 用于Fragment shader,只读, Fragment相对于窗口的坐标位置 x,y,z,1/w; 这个是固定管线图元差值后产生的;z 是深度值;
gl_FrontFacing // 用于判断 fragment是否属于 front-facing primitive;只读;
gl_PointCoord // 仅用于 point primitive;
2. 常用内置函数:
// 弧度计算
radians(degree) : 角度变弧度;
degrees(radian) : 弧度变角度;
sin(angle), cos(angle), tan(angle)
asin(x): arc sine, 返回弧度 [-PI/2, PI/2];
acos(x): arc cosine,返回弧度 [0, PI];
atan(y, x): arc tangent, 返回弧度 [-PI, PI];
atan(y/x): arc tangent, 返回弧度 [-PI/2, PI/2];
// 数学计算
pow(x, y): x的y次方;
exp(x): 指数, log(x):
exp2(x): 2的x次方, log2(x):
sqrt(x): x的根号; inversesqrt(x): x根号的倒数
abs(x): 绝对值
sign(x): 符号, 1, 0 或 -1
// 取值
floor(x): 底部取整
ceil(x): 顶部取整
fract(x): 取小数部分
mod(x, y): 取模, x - y*floor(x/y)
min(x, y): 取最小值
max(x, y): 取最大值
clamp(x, min, max): min(max(x, min), max);
mix(x, y, a): x, y的线性混叠, x(1-a) + y*a;
step(edge, x): 如 x
// 向量计算
length(x): 向量长度
distance(p0, p1): 两点距离, length(p0-p1);
dot(x, y): 点积,各分量分别相乘 后 相加
cross(x, y): 差积: x[1]*y[2] - y[1]*x[2],
x[2]*y[0] - y[2]*x[0],
x[0]*y[1] - y[0]*x[1]
normalize(x): 归一化, length(x)=1;
faceforward(N, I, Nref): 如 dot(Nref, I)< 0则N, 否则 -N
reflect(I, N): I的反射方向, I -2*dot(N, I)*N, N必须先归一化
refract(I, N, eta): 折射,k=1.0-eta*eta*(1.0 - dot(N, I) * dot(N, I));
如k<0.0 则0.0,否则 eta*I - (eta*dot(N, I)+sqrt(k))*N
matrixCompMult(matX, matY): 矩阵相乘, 每个分量 自行相乘;
即 r[i][j] = x[i][j]*y[i][j];矩阵线性相乘,直接用 *
lessThan(vecX, vecY): 向量 每个分量比较 x < y
lessThanEqual(vecX, vecY): 向量 每个分量比较 x<=y
greaterThan(vecX, vecY): 向量 每个分量比较 x>y
greaterThanEqual(vecX, vecY): 向量 每个分量比较 x>=y
equal(vecX, vecY): 向量 每个分量比较 x==y
notEqual(vecX, vexY): 向量 每个分量比较 x!=y
any(bvecX): 只要有一个分量是true, 则true
all(bvecX): 所有分量是true, 则true
not(bvecX): 所有分量取反
- GLSL2.0 ES 与 GLSL3.0 ES 的区别
CocosCreator 支持 GLSL3.0 ES 标准,和2.0相比差异为:
- attribute 和 varying 使用 in 和 out 来代替
- 头文件多了个 #version 300 es
- 纹理 texture2D 和 texture3D 统统改为 texture
- 内置函数gl_FragColor和gl_FragData删除,如果片段着色器要输出用 out 声明字段输出。不过保留了 gl_Position
- layout,可直接指定位置
三、CocosCreator 渲染简介
- 纹理、材质、Effect
在Creator编辑器中,创建一个Sprite精灵,然后拖一个贴图到精灵的SpriteFrame属性中,纹理和材质的协同工作表现如下图:
纹理是所需要渲染的内容,材质是渲染所必须的配置,Effect则是渲染要呈现的效果所采用的方法;
项目中所有可用的Effect都会显示在材质面板的Effect属性下拉列表中,包括内置Effect。编辑器内置的 Effect 资源全部位于 Internal DB 的 assets/effects 目录下;
Effect定义代码块中 properties 部分会被解析显示到材质面板上,包括:宏定义、out 变量、UBO结构等等;
- CocosCreator 渲染组件
Creator2.0版本以后,所有支持渲染的组件都从RenderComponent继承,
我们以cc.Sprite举例说明:
/* 该组件用于在场景中渲染精灵。 */
export class Sprite extends RenderComponent implements BlendFunc {
在RenderComponent组件定义中,定义了一个材质数组
/**所有支持渲染的组件的基类 */
export class RenderComponent extends Component {
/**
!#en The materials used by this render component.
!#zh 渲染组件使用的材质。 */
sharedMaterials: Material[];
/**
!#en Get the material by index.
!#zh 根据指定索引获取材质
@param index index
*/
getMaterial(index: number): MaterialVariant;
/**
!#en Gets all the materials.
!#zh 获取所有材质。
*/
getMaterials(): MaterialVariant[];
/**
!#en Set the material by index.
!#zh 根据指定索引设置材质
@param index index
@param material material
*/
setMaterial(index: number, material: Material): Material;
}
当组件设置指定材质后(setMaterial方法),最终会通过 RenderFlow.visitRootNode 方法,调用到 RenderComponent._checkBacth 方法
RenderFlow.visitRootNode = function (rootNode) {
RenderFlow.validateRenderers();
_cullingMask = 1 << rootNode.groupIndex;
if (rootNode._renderFlag & WORLD_TRANSFORM) {
_batcher.worldMatDirty ++;
rootNode._calculWorldMatrix();
rootNode._renderFlag &= ~WORLD_TRANSFORM;
flows[rootNode._renderFlag]._func(rootNode);
_batcher.worldMatDirty --;
}
else {
flows[rootNode._renderFlag]._func(rootNode);
}
};
_checkBacth 方法中,会将材质数组的第0个材质配置提交到渲染主流程中;
_checkBacth (renderer, cullingMask) {
let material = this._materials[0];
if ((material && material.getHash() !== renderer.material.getHash()) ||
renderer.cullingMask !== cullingMask) {
renderer._flush();
renderer.node = material.getDefine('CC_USE_MODEL') ? this.node : renderer._dummyNode;
renderer.material = material;
renderer.cullingMask = cullingMask;
}
}
在介绍完渲染基础和渲染组件后,我们终于可以正式的开始学习 Creator Effect 的语法和结构了;
四、Creator Effect 语法
官网有一个详细的 Effect 结构描述图,为了便于理解,我们可以简单的把Effect结构分成如下图展示的几部分;

- CCEffect部分结构
CCEffect部分是基于YAML语言编写,用于声明特效的流程控制清单;YAML 是一种数据序列化语言,语法简洁直观,和Python类似使用缩进来表达语法的层次结构,更多应用于编写配置文件;
Creator官方表示支持符合 YAML 1.2 标准的解析器,意味着CCEffect部分可直接使用JSON格式编写;但是官方仍推荐使用YAML编写,因为更简单直观;
YAML语法简介:
- 大小写敏感;
Test: 1 // 两个不同的变量
test: 2
2. 用缩进表示层级关系,同级必须对齐;缩进只能使用空格,不能使用TAB字符;
# YAML
one:
two: 2
three:
four: 4
five: 5
// 以上的内容转成 JSON 后
"one": {
"two": 2,
"three": {
"four": 4,
"five": 5
}
}
3. 使用#表示注释
# 我是注释
4. 支持以键值对方式(key : value)的对象定义,使用“冒号+空格”来区分键与值;
# YAML
key:
child-key1: value1
child-key2: value2
// JSON
"key": {
"child-key1": "value1",
"child-key2": "value2",
}
# 另一种对象定义方法
# YAML
key: { child-key1: value1, child-key2: value2 }
// JSON
"key": { "child-key1": "value1", "child-key2": "value2" }
5. 支持一维或多 维数组;
# 一维数组定义
# YAML
values:
- value1
- value2
- value3
// JSON
"values": [ "value1", "value2", "value3" ]
# 另一种一维数组定义
# YAML
values: [value1, value2, value3]
// JSON
"values": [ "value1", "value2", "value3" ]
# ■■数组定义(使用缩进表示层级关系)
# YAML
values:
-
- value1
- value2
-
- value3
- value4
// JSON
"values": [ [ "value1", "value2"], ["value3", "value4"] ]
6. 支持:String、Boolean、Integer、Float、Null 等数据类型;
# -------------字符串定义可支持使用单双引号包裹-------------
# YAML
strings:
- Hello without quote # 不用引号包裹
- 'He said: "Hello!"' # 单双引号支持嵌套"
// JSON
"strings":
[ "Hello without quote", "He said: 'Hello!'" ]
# -------------Boolean 定义-------------
# YAML
boolean:
- true # True、TRUE
- yes # Yes、YES
- false # False、FALSE
- no # No、NO
// JSON
"boolean": [ true, true, false, false ]
# -------------Integer 定义-------------
# YAML
int:
- 666
- 0001_0000 # 二进制表示
// JSON
"int": [ 666, 4096 ]
# -------------Float 定义-------------
# YAML
float:
- 3.14
- 6.8523015e+5 # 使用科学计数法
// JSON
"float": [ 3.14, 685230.15 ]
# -------------Null 定义-------------
# YAML
nulls:
- null
- Null
- ~
-
// JSON
"nulls": [ null, null, null, null ]
本文仅介绍Creator Effect 编写过程中部分经常使用到的YAML语法,更详细的相关资料请点击参考部分链接;
CCEffect示例:
CCEffect %{ # 该部分定义体现在属性检查器上,以YAML格式编写
techniques: # 支持多段定义,在属性面板上通过选择框选择,参见下图1
- name: test1 # 每段 technique 的名称
passes: # 每个 passes 部分都是完整的渲染管线定义,一个 technique 支持多个 passes
- vert: vs:main1 # 顶点 Shader 的名称,格式:"Shader名称:入口函数名称",如果入口函数省略,则默认为 main
frag: fs:main1 # 片元 Shader 的名称,格式定义与上面相同
blendState: # 混合模式设置,该部分参数详见官方文档 Pass Params 部分定义
targets:
- blend: true
rasterizerState:
cullMode: none
properties: # 属性定义,可以在属性检查器面板上体现并编辑的属性,一般用于配置uniform
texture: { value: white }
alphaThreshold: { value: 0.5 }
# inspector 用于属性检查器面板的显示定义,包括显示名称、提示、类型、设定区间;
# 编辑器会根据不同类型显示并使用不同的输入设置
outlineSize: { value: 1, inspector: { displayName: "描边大小", tooltip: "描边大小", range: [1.0, 10.0] } }
outlineColor: { value: [0.5, 0.5, 0.5, 0.5], inspector: { displayName: "描边大小", tooltip: "描边颜色", type: color } }
testCheck: { value: true, inspector: { displayName: "选择框", tooltip: "测试选择框", type: boolean }}
testPos: { value: [1.0, 2.0], inspector: { displayName: "坐标", tooltip: "测试坐标" }}
- name: test2 # 第二段 technique
passes:
- vert: vs_2 # 该段顶点和片元使用默认的 main 入口函数
frag: fs_2
# .... 以下省略
passes中顶点片元的片段名可以是当前文件中的片段名称,也可以是引擎提供的标准头文件,但是片段名不能是main,因为main已经被指定为片段名的默认函数入口名称;
passes 部分可选配置的参数有很多,但只能和官方文档的材质->Pass Params 部分列表定义一一对应;更详细的相关内容请查看参考部分链接;
- CCProgram部分结构
CCProgram部分是符合GLSL3 ES语法格式的Shader片段,一个Effect文件中可包含多段CCProgram定义;CCProgram包含顶点Shader和片元Shader两种,在CCEffect的Pass部分会定义当前引用的顶点和片元Shader ;
基础说明:
在CCProgram中可以使用类似 C/C++ 的头文件 include 机制引入其它的代码片段;
// 注:CCEffect 使用 # 注释,CCProgram 中的注释则是使用 //
// 引擎代码片段一般存放在路径: resources\engine\cocos2d\renderer\build\chunks
#include <cc-global> // 引入引擎的cc-global代码
#include "../test.chunk" // 引入自定义的代码
Creator预先定义了一系列的:预处理宏、工具函数和内置 Uniform 变量;Uniform 变量定义的明细可查看官方文档,或在引擎工程下全局查找变量名称,可以获得详细定义;
所有的宏定义会默认在属性编辑器上体现,以虚线框标注,方便使用调整;
多次引用的头文件、宏和工具函数,只会在编译后的Shader文件中展开一次,在Creator编辑器中选中Effect文件后,属性检查器会显示Effect源代码,以及展开后的顶点和片元代码;
例如:Effect中使用的 texture() 函数,在展开后变成了 texture2D();
Creator 在加载 Effect 时,会默认填充传递顶点和纹理数据到相关的 Shader 片段中;所以我们不用手动定义和传递纹理相关的参数;
下图为顶点 Shader 中的:a_position、a_color、a_uv0 参数的定义和展开
下图为顶点Shader中 cc_matViewProj 参数的定义和展开
外部参数赋值过程,请查看引擎源码
UBO内存布局:
UBO (Uniform Block Object)是OpenGL 3.1 Core中引入的概念。基于UBO的技术实现,可以多个Shader共享一组缓存的 Uniform 变量,从而简化着色器中变量的传递和共享,减少参数传递的开销以达到节省性能的目的。
Creator规定在 Shader 中所有非 sampler 的 Uniform 都应遵循UBO并以 block 形式声明;如下图所示,CCEffect 部分 properties 中定义的 Uniform 外部参数,在片元Shader 中以UBO 形式申明后才能在片段代码中获得并使用;
同时,对于所有 UBO的定义有以下要求:
- 不应出现 vec3 成员;
- 对数组类型成员,每个元素 size 不能小于 vec4;
- 不允许任何会引入 padding 的成员声明顺序。
对于不符合要求的定义,任意长度小于 vec4 类型的数组和结构体,都会逐元素补齐至 vec4,并且UBO成员偏移会按自身所占字节数进行对齐;
// 未对齐定义
uniform IncorrectUBOOrder {
float f1_1; // 偏移量0,长度4(对齐到4字节)
vec2 v2; // 偏移量8,长度8(对齐到8字节) [成员 f1_1 隐式填充至8字节]
float f1_2; // 偏移量16,长度4(对齐到4字节)
}; // 合计32个字节长度(与平台相关)
// 已对齐定义
uniform CorrectUBOOrder {
float f1_1; // 偏移量0,长度4(对齐到4字节)
float f1_2; // 偏移量4,长度4(对齐到4字节)
vec2 v2; // 偏移量8,长度8(对齐到8字节)
}; // 合计16个字节长度
代码示例:
// Creator自带的置灰的 Shader
// 顶点 Shader
CCProgram vs %{
// 设置精度
precision highp float;
// 引入引擎公共 Shader 代码片段
#include <cc-global>
// 引擎传入的顶点信息
in vec3 a_position;
in mediump vec2 a_uv0; // 顶点uv
out mediump vec2 v_uv0; // uv数据需要传递到片元 Shader,所以要以out 定义;
// 该定义必须与片元 Shader 中定义一一对应;
// 引擎传入的顶点颜色信息
in vec4 a_color; // 顶点颜色
out vec4 v_color; // 与 v_uv0 一样,传递到片元的数据
// 顶点 Shader 入口
void main () {
// 转换每个点的纹理坐标到GL
gl_Position = cc_matViewProj * vec4(a_position, 1);
// 将外部每个点的UV和颜色保存传递给片元 Shader
v_uv0 = a_uv0;
v_color = a_color;
}
}%
// 片元 Shader,获得光栅化后的片元(像素)信息
CCProgram fs %{
// 设置精度
precision highp float;
// 引入引擎纹理 Shader 代码片段
#include <texture>
// 外部传入的纹理数据
uniform sampler2D texture;
// 从顶点 Shader 传入的数据,该定义必须与顶点 Shader 中一一对应
// 这里获得的是经过光栅化后片元(像素)的uv和Color插值数据
in mediump vec2 v_uv0;
in vec4 v_color;
void main () {
vec4 color = v_color;
CCTexture(texture, v_uv0, color);
// 将色值转换成灰度
float gray = 0.2126*color.r + 0.7152*color.g + 0.0722*color.b;
gl_FragColor = vec4(gray, gray, gray, color.a);
}
}%
五、简单的示例
- 描边 Shader 示例
实现效果
代码片段
// 实现描边效果
CCEffect %{
techniques:
- passes:
properties:
# 描边开放参数,其它参数定义暂略
outlineSize: { value: 2, inspector: { tooltip: "描边大小", range: [1.0, 10.0] } }
outlineColor: { value: [0.5, 0.5, 0.5, 0.5], inspector: { tooltip: "描边颜色", type: color } }
}%
CCProgram vs %{
// 主要基于片元Shader实现效果,顶点Shader代码略
}%
// 片元 Shader
CCProgram fs %{
precision highp float;
#include <alpha-test>
#include <texture>
#include <cc-global>
#include <common>
in vec4 v_color;
#if USE_TEXTURE
in vec2 v_uv0;
uniform sampler2D texture;
#endif
uniform Constant { // 开放参数
vec4 outlineColor; // 描边颜色
float outlineSize; // 描边宽度,以像素为单位
};
// 纹理大小(宽和高),为了计算周围各点的纹理坐标,必须传入它,
// 因为纹理坐标范围是0~1,测试代码,省略从外部参数传入过程;
const vec2 textureSize = vec2(200, 200);
// 判断在这个角度上距离为outlineSize那一点是不是透明
int getIsStrokeWithAngel(float angel)
{
int stroke = 0;
// 这个浮点数是 pi / 180,角度转弧度
float rad = angel * 0.01745329252;
// outlineSize * cos(rad)可以理解为在x轴上投影,或取得当前距离uv坐标outlineSize远的一个点
// 除以textureSize.x是因为texture2D接收的是一个0~1的纹理坐标,而不是像素坐标
float sx = v_uv0.x + outlineSize * cos(rad) / textureSize.x;
float sy = v_uv0.y + outlineSize * sin(rad) / textureSize.y;
// 把alpha值大于0.5都视为不透明,小于0.5都视为透明
float a = texture(texture, vec2(sx, sy)).a;
if (a >= 0.5)
{
stroke = 1;
}
return stroke;
}
void main () {
vec4 texel = vec4(1);
#if USE_TEXTURE
CCTexture(texture, v_uv0, texel);
#endif
#if USE_TEXTURE
// 小于0.5都视为透明
if (texel.a < 0.5) {
// for循环在安卓某些机型(如小米4)上会直接崩溃,
// 查找资料发现OpenGL es并不是很支持循环,低端机型 while和for 都不要用
int strokeCount = 0;
strokeCount += getIsStrokeWithAngel(0.0);
strokeCount += getIsStrokeWithAngel(30.0);
strokeCount += getIsStrokeWithAngel(60.0);
strokeCount += getIsStrokeWithAngel(90.0);
strokeCount += getIsStrokeWithAngel(120.0);
strokeCount += getIsStrokeWithAngel(150.0);
strokeCount += getIsStrokeWithAngel(180.0);
strokeCount += getIsStrokeWithAngel(210.0);
strokeCount += getIsStrokeWithAngel(240.0);
strokeCount += getIsStrokeWithAngel(270.0);
strokeCount += getIsStrokeWithAngel(300.0);
strokeCount += getIsStrokeWithAngel(330.0);
// 四周围至少有一个点是不透明的,这个点要设成描边颜色
if (strokeCount > 0)
{
texel = outlineColor;
}
}
#endif
texel *= v_color;
gl_FragColor = texel;
}
}%
六、ShaderToy 学习简介
ShaderToy 是一个基于WEBGL使用片元 Shader 编程、展示、分享的网站,ShaderToy 按字面意直接翻译过来就是着色器玩具。网站地址:
如下图所示,代码入口函数为:mainImage,红色框中为 Shader 所需要依赖的参数:
常用参数常量定义:
uniform vec4 fragColor; // 计算出的像素颜色
uniform vec4 fragCoord; // 像素坐标
uniform vec3 iResolution; // 窗口分辨率,单位像素
uniform float iTime; // 程序运行的时间,单位秒
uniform float iTimeDelta; // 渲染时间,单位秒
uniform float iFrame; // 帧率
uniform vec4 iMouse; // 鼠标位置
uniform vec4 iDate; // 日期(年,月,日,时)
基本上,只要做一些很小的改动,就能够在 Creator 里使用这些Shader,主要就是将上面介绍的 ShaderToy 定义的常量参数替换成 Creator 中的参数;
vec2 uv = fragCoord.xy / iResolution.xy; // 将坐标转换成UV坐标
vec2 uv = v_uv0; // Creator 中直接使用 v_uv0 代替
vec2 size = iResolution;
vec2 size = vec2(1080.0, 960.0); // 使用外部传入的屏幕大小或使写死代替
vec2 size = cc_screenSize.xy; // 也可以使用 Creator 中定义的常量代替
finalCol = texture(iChannel0, v_uv0); // 获取iChannel0 纹理中的uv点颜色
CCTexture(texture, v_uv0, finalCol); // 使用 Creator 中定义的函数代替
vec2 fc = fragCoord.xy;
vec2 fc = gl_FragCoord.xy; // 使用 Creator 中定义的 gl_FragCoord 代替
vec4 ff = fragColor;
vec4 ff = gl_FragColor; // 使用 Creator 中定义的 gl_FragColor 代替
七、示例和参考
- 示例DEMO
欢迎扫码关注我的公众号,获取webGL和Shader示例DEMO,以及更多Cocos干货:

- 参考资料
LearnOpenGL CN
https://learnopengl-cn.github.io/
WebGL 理论基础
https://webglfundamentals.org/webgl/lessons/zh_cn/
WebGL GLSL ES语法基础
https://zhuanlan.zhihu.com/p/149853457
官网材质系统介绍,包括(YAML、Effect语法、Pass参数、内置 Uniform 等等)
https://docs.cocos.com/creator3d/manual/zh/material-system/overview.html
详细的YAML学习资料
ShaderToy系列:零















