引擎版本:3.8.5
您好,我是鹤九日!
回顾
稍微的回顾下,上篇文章的主要内容概况:
一、Shader效果的实现,需要材质Material和EffectAsset资源的互相支持,它们都属于资源的范畴。
二、Effect的创建有两种:无光照着色器(Effect)和基于PBR表面着色器(Surface Effect)。
三、材质的创建有两种:普通材质(Material)和物理材质(Pythsics Material)
四、针对于引擎的封装,我们只需要关注:CCEffec
t属性的配置和CCProgram
片段的编写。
简介
从上篇文章了解到:渲染效果的展示,需要材质和Effect的相互配合。
按照引擎提供的规则,我们便可以实现对着色器属性参数的配置、可视化调整。
今天的内容主要讲解下shader的语言和渲染的基本流程,这样对于后续的Shader片段化代码理解会很有帮助。
注:理解可能存在错误,期待您的谅解,并欢迎您的指出。
GLSL语法
官方引擎的渲染使用的是OpenGL ES, 它是OpenGL图形库的子集。
主要应用于嵌入式和手持设备,比如手机,平板电脑等。
它采用的语法是GLSL,一种类C的语言。
因渲染需要很庞大的工作量,对性能极度的敏感,因此该语言不会支持太复杂的结构。
官方的文档对GLSL语法有着详细的说明和解释:着色器语法。
这里简要说明下:
精度
精度在着色器片段代码的头部声明,限定符主要有:
限定符 | 描述 |
---|---|
highp | 高精度 |
mediump | 中精度 |
lowp | 低精度 |
CCProgram sprite-vs %{
// GLSL不支持double双精度
precision highp float;
#include <builtin/uniforms/cc-global>
}
变量类型
类型 | 说明 | 可选项或默认值 |
---|---|---|
bool | 布尔类型 | true/false |
int | 整数类型 | 0 |
float | 单精度浮点型类型 | 0 |
ivec2/ivec3/ivec4 | 包含 2/3/4 个整型向量 | [0,0]/[0,0,0]/[0,0,0,0] |
vec2/vec3/vec4 | 包含 2/3/4 个浮点型向量 | [0.0,0.0]/… |
sampler2D | 2D 纹理 | black、grey、white、normal、default |
samplerCube | 立方体纹理 | black-cube、white-cube、default-cube |
mat2/mat3/mat4 | 2x2/3x3/4x4 矩阵 | |
struct | 结构体 |
这里注意:
一、针对于整数和浮点数,GLSL语言不支持隐式转换
二、变量的使用,必须有特定的类型和定义
int value = 10;
float num = 1.0; // 一定要挂小数点
修饰符
限定符 | 说明 |
---|---|
const | 表示变量为只读或常量 |
attribute | 应用程序发送给顶点着色器的数据 |
uniform | 应用程序发送给顶点和片段着色器的数据。在顶点和片段着色器中数据保持一致 |
varying | 顶点着色器发送给片元着色器的数据,数据经过光栅化的插值计算是可变的。 |
in | 修饰输入参数 |
out | 修饰输出参数 |
inout | 修饰输入输出参数 |
宏定义
GLSL 允许定义和 C 语言类似的宏定义。
预处理宏定义允许着色器定义多样化的动态分支,确定最终的渲染效果。
在 GLSL 中使用预处理宏定义的代码示例如下:
#define
#undef
#if
#ifdef
#ifndef
#else
#elif
#endif
在Shader的片段编写中,甚至引擎对Effect编译后生成GL300 ES/GL 100中,就用着很多的宏定义。
OpenGL流程
以人的眼睛去观察世界,看到的是五颜六色的的事物。
但倘若将这些应用到计算机中处理,便成为了复杂的计算机行为和逻辑。
渲染的实现是一个很复杂的过程,但它同时代一样是进步的。
最早的渲染管线,人们是不可自定义操控的,名为固定编程渲染管线。
而现阶段,允许开发者通过顶点着色器和片段着色器,定义可控、炫彩的效果,名为可编程渲染管线。
无论怎样,渲染流程终究要做的事情都是:将3D的物品最终显示在2D的屏幕中。
大致的渲染效果如下:
大致的流程如下:
一、顶点数据 主要包括位置坐标,颜色,纹理等信息,这个是渲染的基础数据
二、顶点着色器 主要执行坐标转换和向片段着色器传递可变变量
三、图元装配 顾名思义生成图形,比如线段、三角形等,并执行裁切。
四、光栅化 将图源转换为一系列的片段(像素点), 并进行采样计算插值
五、片段着色器 将光栅化计算的插值数据对片段进行着色
六、测试和混合 会对片段执行透明度,深度,裁切,抖动等测试
七、帧缓冲 前后缓冲,用于屏幕的绘制刷新。
这里对流程进行一些补充和拓展,方便能够理解后面的着色器片段代码。
顶点数据
顶点数据的基本要素:坐标位置(XYZ)、纹理坐标(UV)、颜色(RGBA),它们分别对应渲染对象的外观、形状和视觉效果。
坐标位置
坐标位置会被顶点着色器执行坐标转换,这是一系列性的。
主要坐标转换有:局部坐标、世界坐标、视图坐标、裁剪坐标和屏幕坐标。
对应的专业术语有:
- 模型矩阵:将局部坐标(Local/Model Coordinates)转换为世界坐标(World Coordinates)
- 视图矩阵:将世界坐标转换为视图坐标(View Coordinates,也称为相机坐标)
- 投影矩阵:将视图坐标转换为裁剪坐标(Clip Coordinates)
转换的示意图:
更多详情可参考:[LeranOpenGL 坐标系统](https://learnopengl-cn.github.io/01 Getting started/08 Coordinate Systems/)
纹理坐标
顶点数据传入的纹理数据,就是纹理坐标(UV)。
首先说:纹理和图像是两码事,应用程序中常用的JPG
、PNG
属于图像格式,渲染只认纹理。
由于PNG模式的是RGBA8888格式,而JPG会被引擎强制转换,故此PNG比JPG的性能要快。
为了让GPU更快的获取纹理数据,因而衍生了不同平台不同格式的压缩纹理,比如ETC、PVR等。
对了,纹理数据的最终使用,是需要向片段着色器传递数据,然后根据纹理坐标从纹理中获取采样数据,才能渲染。
更多详情内容可参考:Cocos 纹理格式和压缩纹理
颜色
颜色是最终效果实现的基础,无论怎样的计算和改变都是基础的色块:R、G、B、A。
这里注意:顶点着色器向片段着色器传递的数据是可变的。
原因在于:经过光栅化阶段的时候都会经过单一采样和多重采样的插值计算。
Sprite置灰效果
Spirte是渲染组件中最基础的,它有一个特性:
Grayscale灰度模式,开启后 Sprite 会使用灰度模式渲染。
这里不再追踪它的内部实现逻辑,它使用的主要文件路径:
材质文件:internal/default_materials/ui-sprite-gray-material.mtl
Effect文件:internal/effects/for2d/builtin-sprite.effect
这里看下Effect文件关于着色器主要代码:
顶点着色器
CCProgram sprite-vs %{
precision highp float;
// 输入参数,顶点的位置坐标(XYZ)
in vec3 a_position;
// 输入参数,顶点的纹理坐标(UV),用于映射纹理
in vec2 a_texCoord;
// 输入参数,顶点的颜色值(RGBA)
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;
}
}%
通过着色器的语法,我们便很容易理解了。
一、通过in
传入参数:位置坐标(a_position)、纹理坐标(a_texCoord)和颜色(a_color)
二、顶点着色器会执行坐标转换
三、顶点着色器会将顶点颜色(color)和纹理(uv0)传递给片段着色器。
片元着色器
CCProgram sprite-fs %{
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
// 通过CCSampleWithAlphaSeparated函数从纹理中采样颜色
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;
}
这里简单粘贴下片段着色器的代码,理解下uniform
变量等。
结尾
讲解的某些东西可能让人云里雾里,甚至不够的深入,这里请谅解下!
提供一些学习的参考资料,期望对您有用!
- LearnOpenGL:一个非常全面且系统的 OpenGL 学习平台,适合从零开始学习 OpenGL 的开发者。
- 20分钟让你了解OpenGL——OpenGL全流程详细解读 知乎大佬对于渲染流程的深入讲解,里面的第一张图就是使用的大佬的,这里表示感谢!
- Cocos Shader学习资源推荐:Cocos官方论坛整理的一些学习资料。
结尾之前,希望您能明白:渲染流程的核心研究并吃透,的确善莫大焉!
然而,对于新人而言:兴趣是坚持的动力,倘若一直被各种打击,怎么学习并坚持呢?
所以,不懂从来不是坏事,存在问题也不是坏事,就怕因恐惧而不敢再面对…
我是鹤九日,祝您生活愉快!