Cocos Creator Shader入门实战(二):GLSL语法和OpenGL流程

发布于:2025-03-03 ⋅ 阅读:(126) ⋅ 点赞:(0)

引擎版本:3.8.5

您好,我是鹤九日!



回顾


稍微的回顾下,上篇文章的主要内容概况:

一、Shader效果的实现,需要材质MaterialEffectAsset资源的互相支持,它们都属于资源的范畴。

二、Effect的创建有两种:无光照着色器(Effect)和基于PBR表面着色器(Surface Effect)。

三、材质的创建有两种:普通材质(Material)和物理材质(Pythsics Material)

四、针对于引擎的封装,我们只需要关注:CCEffect属性的配置和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)

首先说:纹理和图像是两码事,应用程序中常用的JPGPNG属于图像格式,渲染只认纹理。

由于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变量等。



结尾


讲解的某些东西可能让人云里雾里,甚至不够的深入,这里请谅解下!

提供一些学习的参考资料,期望对您有用!

结尾之前,希望您能明白:渲染流程的核心研究并吃透,的确善莫大焉!

然而,对于新人而言:兴趣是坚持的动力,倘若一直被各种打击,怎么学习并坚持呢?

所以,不懂从来不是坏事,存在问题也不是坏事,就怕因恐惧而不敢再面对…

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


网站公告

今日签到

点亮在社区的每一天
去签到