Cocos Creator Shader 入门 ⒀ —— UBO 内存布局策略

💡 本系列文章收录于个人专栏 ShaderMyHead

💡 本章是对《Cocos Creator 官方文档 ------ UBO 内存布局策略》的详细解释,也是对《着色器语法》一文的知识点扩展。

一、奇怪的报错

在之前的文章中我们了解到,Cocos Creator 着色器里,所有非 sampler 类型的 uniform(如 float, vec4)都必须放到一个 UBO(Uniform Block Object)块中声明,而不是一个个地零散声明。

示例:

js 复制代码
  uniform sampler2D renderTexture;  // 纹理变量无需走 UBO 形式定义

  uniform Constants {
    vec4 glowColor;     // 发光颜色
    vec2 textureAspect; // 纹理宽高比
    float glowSpread;   // 发光最大半径(基于 UV 坐标体系)
  };

上述的代码来自于《描边和发光效果的实现》案例,第 3 行代码开始就是一个标准的 UBO 定义(名为 Constants),它包含了三个不同类型的参数。

此时如果我们在 UBO 中把 vec2 textureAspect 放置到 vec4 glowColor 的声明上方:

js 复制代码
  uniform Constants {
    vec2 textureAspect;   // 将 textureAspect 的声明放到第一行
    vec4 glowColor;
    float glowSpread;
  };

Cocos Creator 编辑器会直接报错:

或者当我们新增了一个 vec3 类型的 uniform 变量:

js 复制代码
CCEffect %{
  techniques:
  - name: glow
    passes:
    - vert: vs:vert
      frag: fs:frag
      # 略...
      properties:
        colorRGB: { value: [1.0, 0.8, 0.3] }      # 新增 vec3 变量
        # 略...
}%


CCProgram fs %{
  // 略...
  
  uniform Constants {
    vec4 glowColor; 
    vec3 colorRGB;      // 新增 vec3 变量
    vec2 textureAspect;
    float glowSpread; 
  };
  
  // 略...
}%

也会出现类似的 implicit padding 报错信息:

这些都是为了兼容 OpenGL std140 规则导致的报错。

二、std140 规则

2.1 规则和示例

OpenGL 的 std140 是一种内存布局标准,用于定义 UBO 中各成员在内存中的排列方式。

它通过 对齐规则(alignment)填充策略(padding),实现显存访问的结构化和对齐,使 GPU 能更快地读取数据,避免跨字节读取,提升访问效率。

std140 的核心规则(对齐 + 填充)如下:

数据类型 对齐要求(字节) 占用大小(字节) 说明
float 4 4 可单独对齐
vec2 8 8 必须 8 字节对齐
vec3 / vec4 16 16 vec3 实际会补齐为 16 字节
mat4 16 64 被看作 4 个 vec4
数组元素 元素对齐 = 16 每个元素占 16 字节 不管你放 float/vec2,都会被扩充到 16 字节
结构体成员 每个成员必须按它自己的对齐来对齐;结构体总大小也必须是最大对齐值的倍数

💡「对齐」是指该类型的数据在显存中的地址偏移量(offset)必须是指定对齐要求的倍数,比如:

  • float 要求 4 字节对齐 → 地址必须是 4 的倍数(0x00、0x04、0x08...);
  • vec2 要求 8 字节对齐 → 地址必须是 8 的倍数(0x00、0x08、0x10...);
  • vec4 要求 16 字节对齐 → 地址必须是 16 的倍数(0x00、0x10、0x20...)。

示例一:vec3 会被补齐成 vec4(占 16 字节)

原本一个非 UBO 的 vec3 类型在显存中是占用 12 字节的,但在 UBO 声明时,会被填充到 16 字节(即跟 vec4 占用的长度一致):

js 复制代码
uniform Example {
    vec3 v3; // 实际占 16 字节(从 12 字节填充到 16 字节)
};

示例二:float 数组里的每个元素都对齐成 vec4

js 复制代码
uniform Example {
    float a[4]; // 每个 float 元素实际占 16 字节,数组总共占 16 x 4 = 64 字节!
};

示例三:错误的成员顺序将触发 padding

js 复制代码
uniform WrongOrder {
    float a;   // offset 0
    vec2 b;    // offset 8(vec2 要 8 字节对齐,因此在前面 padding 了 4 字节)
    float c;   // offset 16
};

上方代码段中,假设 float a 在显存中的地址偏移量为 0,则紧接着的 vec2 b 在显存中的地址偏移数将是 4 字节,这是不符合 vec2 类型的对齐要求的(UBO 中 vec2 类型需要对齐 8 个字节),因此会触发 padding,在 float a 的后面填充 4 个字节:

即这段代码会让显存浪费掉 4 字节的空间。但如果我们修改下声明顺序,把 float c 放到 vec2 b 上方,则不会触发 padding:

js 复制代码
uniform WrongOrder {
    float a;   // offset 0
    float c;   // offset 4
    vec2 b;    // offset 8
};

因为此时 float cvec2 b 的地址偏移字节数,都刚好满足它们的对齐要求:

2.2 为什么 std140 会更高效?

  • 首先内存对齐使 GPU 更易于读取:

    GPU 的内存控制器通常以 16 字节(128 位)为单位读取数据,如果你的数据跨越了这个边界:

    • 会导致需要两次读取 + 拼接,增加负担;
    • 无法使用 SIMD(单指令多数据)或向量化加速。

    std140 强制所有复杂类型都对齐到 16 字节,保证了每次读取都在对齐边界,GPU 可以快速、整块地读取。

  • 其次数据结构统一,可以避免平台差异

    不同厂商/设备/驱动实现可能有不同的默认布局,但 std140 是强制标准:

    • 着色器编译器可以预测每个变量偏移位置;
    • 应用层(CPU 端)可以一次性上传 UBO,不用担心对齐出错;
    • GPU 不需要解析复杂的偏移关系,直接按固定偏移读取。

std140 确实牺牲了一些 GPU 内存空间,也导致我们在 Cocos Creator 着色器的开发中容易出错,但这些代价是值得的,尤其在实时渲染中,带宽和读取性能更关键

三、Cocos Creator 中的限制

为了应对 std140 的统一规范,Cocos Creator 着色器中有如下三条规则:

  • 规则一、不应出现 vec3 成员

    std140 规则下,vec3 类型在 UBO 中实际占用空间会被自动填充至与 vec4 相同的 16 字节,会导致显存空间的浪费,因此 Cocos Creator 干脆直接禁止你在 UBO 中使用 vec3

    这也是文章开头声明 vec3 类型会报错的原因。

  • 规则二、对数组类型成员,每个元素的 size 不能小于 vec4

    这是为了避免数组中的每个元素被强行补齐(且你却不知情):

    js 复制代码
      uniform BadArray {
        float values[4]; // 每个 float 元素实际会被填充为 vec4 大小,即每项占 16 字节!
      };

    这段声明看似只用了 4 × 4 = 16 字节,实际却用了 4 × 16 = 64 字节,Cocos Creator 会要求你必须在明确了解这块浪费的前提下来声明数组。

  • 规则三、不允许任何会触发 padding 的成员声明顺序

    在前文我们了解到,std140 规定错误的成员顺序将触发 padding,会造成 GPU 内存浪费。Cocos Creator 干脆在编译阶段帮你兜底检查,发现错误的声明顺序直接报错。

    这也是文章开头修改 UBO 成员声明顺序后会报错的原因。

相关推荐
成长ing121384 天前
cocos creator塔防路线 运动路线的编辑和录制
前端·cocos creator
VaJoy4 天前
Cocos Creator Shader 入门 ⒃ —— 有向距离场 SDF
cocos creator
VaJoy11 天前
Cocos Creator Shader 入门 ⒂ —— 自定义后处理管线
前端·cocos creator
Thomas游戏开发15 天前
Cocos Creator 面试技巧分享
面试·微信小程序·cocos creator
IkeShyZz15 天前
cocos creator android项目接入deeplink问题总结
cocos creator
成长ing121381 个月前
cocos creator 3.x shader 流光
前端·cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 ⑾ —— 光照跟随
cocos creator
成长ing121381 个月前
闪白效果
前端·cocos creator
冷水金枪鱼1 个月前
Light2D光照系统(基于CocosCreater引擎3.x/2.x)
cocos creator