The Book of Shaders 术语表详解:GLSL struct 自定义结构体的声明、自动构造器与成员访问

原创2026-10-15 10:36:171,733 阅读

The Book of Shaders 术语表详解:GLSL struct 自定义结构体的声明、自动构造器与成员访问

本文基于 The Book of Shaders(thebookofshaders)仓库术语表中的 struct 词条,系统讲解 GLSL 中 struct 关键字的声明语法、自动生成的构造器机制,以及变量声明的可选语义。读完本文后,你将能够独立定义自己的着色器数据结构(如光照材料、调色板等),正确使用其构造器完成初始化,并理解它在 GLSL 与 JavaScript 对象之间的对应关系与边界。

1. struct 在术语表中的位置

The Book of Shaders 是一本循序渐进讲解 Fragment Shader 的开源教程,其根目录下的 术语表总索引 按主题(TYPES、QUALIFIERS、BUILT-IN VARIABLES 等)和字母序组织了全部 GLSL 词条,struct 被归类在 TYPES(类型) 主题下。

术语表每个词条都是一个独立目录,其中存放多语言版本说明,struct 词条目录包含三个版本:

词条页面由 glossary/index.php 渲染:脚本根据 URL 参数 ?search=struct 定位到对应子目录下的 README[-语言].md 文件,再通过 Parsedown 将其转换为 HTML 输出。从源码结构看,每个词条因此都可以作为独立页面被检索和引用,这也是本文以该词条为骨架的原因。

2. struct 声明什么:基于标准类型的用户自定义结构体

struct 关键字用于声明一个基于标准类型组合而成的用户自定义数据结构。术语表词条给出的示例是一个经典的着色器"材料"(material)参数打包,把传统 Phong 光照模型中的环境色、漫反射色、镜面反射色与镜面指数收纳进同一个类型:

struct matStruct {
    vec4 ambientColor;
    vec4 diffuseColor;
    vec4 specularColor;
    float specularExponent;
} newMaterialOne, newMaterialTwo; // 同时声明变量 newMaterialOne 与 newMaterialTwo(类型均为 matStruct)

// 变量 newMaterialOne 的声明处已写过类型,此处赋值时不再需要重复写 matStruct
newMaterialOne = matStruct(
    vec4(0.1, 0.1, 0.1, 1.0),
    vec4(1.0, 0.0, 0.0, 1.0),
    vec4(0.7, 0.7, 0.7, 1.0),
    50.0
);

// matStruct 此后作为类型名用于声明同类型的变量
matStruct anotherNewMaterial = matStruct(...);

从这段示例可以读出三层语法事实:

  1. 字段类型必须是已有的标准类型。示例中的字段全部由 vec4 与 float 构成——struct 本身不创造新数据格式,只是把既有类型按固定顺序组合命名。
  2. 构造器调用 matStruct(...) 的实参顺序与字段声明顺序一一对应:前三个 vec4 分别填入 ambientColor、diffuseColor、specularColor,最后的 float 填入 specularExponent。从示例的写法看,实参类型也需与字段类型匹配。
  3. 类型名之后可以紧跟一个或多个变量声明。这与 C 语言的结构体声明风格一致:matStruct 从此同时具有"类型"身份,anotherNewMaterial = matStruct(...) 这类写法就靠它成立。

3. 自动构造器与"变量声明可选"语义

术语表对 struct 的核心描述是两条规则(英文原文见 glossary/struct/README.md):

struct declares a custom data structure based on standard types. A constructor for the structure with the same name is created automatically. The declaration of a variable (in this case "newMaterial") is optional.

  • 同名构造器自动生成:一旦 struct matStruct { ... } 被声明,语言自动提供名为 matStruct 的构造器,无需手写初始化函数。这与附录中讲解向量构造器的思路一致——appendix/04/README.md 在解释 vec4 构造器重载时就说明了"函数按不同签名被自动/显式提供多个版本"这一 GLSL 特性,struct 的构造器是同一机制在自定义类型上的体现。
  • 变量声明是可选的:struct Foo { ... }; 后面直接跟分号也完全合法,此时只引入类型本身;也可以像词条示例那样在声明处就顺带创建变量。

英文原版示例声明了单个变量 newMaterial,而乌克兰文版的示例更进一步,演示了一次声明多个同名结构体变量(newMaterialOne, newMaterialTwo),并逐行注释了"之后对该变量赋值时不必再写类型名"的用法,比原版多覆盖了多变量声明这一分支。

4. 与 JavaScript 对象的对应关系:"没有方法的对象"

术语表词条偏重语法,而 appendix/04/README.md("给 JavaScript 开发者的 GLSL 入门"一节,乌克兰文版见 appendix/04/README-ua.md)从语言对比角度给出了更完整的语义描述:

If you want a more expressive way of declaring a variable, there is also a struct type. These are like objects without methods; they allow to store and access multiple variables inside the same object.

即 struct 相当于没有方法(methods)的对象:只能存储和访问数据,不能挂接行为。该节同时给出了另一个示例——ColorStruct 打包三个 vec3 颜色:

struct ColorStruct {
    vec3 color0;
    vec3 color1;
    vec3 color2;
}

// 初始化:调用与结构同名的构造器,实参按字段顺序传入
ColorStruct sandy = ColorStruct( vec3(0.92,0.83,0.60),
                                vec3(1.,0.94,0.69),
                                vec3(0.95,0.86,0.69) );

// 通过 "变量名.字段名" 访问成员
sandy.color0 // vec3(0.92,0.83,0.60)

这里还隐含了 GLSL 与 JS 的重要差异背景:GLSL 是编译型、强类型语言,结构体的字段类型在编译期即确定(见 appendix/04/README.md 对 strong typing 的论述),因此 struct 的字段集合在着色器编译后是封闭的,不能像 JS 对象那样在运行时增删属性。对 JS 背景的作者来说,把 sandy.color0 读作"取对象属性"是成立的,只是它背后是编译期就布局好的内存字段,而非动态键值表。

5. 进阶用法:结构体数组与 uniform 数组

在 06/notes.md 中,作者记录并验证了 GLSL 中 struct 的一个实战形态——以结构体类型声明 uniform 数组,这展示了结构体在"从 CPU 向 GPU 批量传参"场景下的价值:

precision mediump float;

struct my_struct {
    vec4 color;
};

uniform my_struct u_colors[2];

void main(void) {
    gl_FragColor = u_colors[0].color;
}

这个片段(作者在笔记中标注其源自 Khronos 的 WebGL 一致性测试)证明了两点:

  • 结构体类型可以作为数组元素的类型(uniform my_struct u_colors[2]),适合从 CPU 端传递一组同构数据,例如多套配色方案;
  • 成员访问语法为下标 + 点号链式:u_colors[0].color 先取下标再取字段,这一链式访问与 sandy.color0 属同一套成员访问机制。

这与附录中关于 GLSL 数组的说明相互印证:GLSL 数组大小固定、不能动态增删、需要逐元素赋值(见 appendix/04/README.md),而"结构体数组 + uniform 修饰符"正是在这种限制下组织复杂参数的一种典型做法。

6. 使用要点小结

结合术语表词条与仓库内的示例代码,使用 struct 时需注意:

  1. 字段只能来自已有标准类型(float、vec2~vec4、int、矩阵等),组合顺序即构造器实参顺序;
  2. 构造器同名自动生成,初始化一律写成 类型名( 实参1, 实参2, ... ),实参类型与顺序须与字段匹配;
  3. 结构体声明处可附带变量声明,也可以省略;已声明的变量后续赋值时直接写变量名即可,无需重复类型;
  4. 成员访问统一使用点号,支持 数组下标.字段 的链式形式;
  5. 它是"无方法的对象",只做数据打包,不含行为;对 JS 背景的作者可将其理解为编译期定型的轻量数据容器。

延伸阅读路径(均在本仓库内):术语表总索引 可查看全部 GLSL 词条;glossary/struct/README-ua.md 与 glossary/struct/README.md 为 struct 词条本体;appendix/04/README.md 提供 JS 视角的完整类型系统导读;06/notes.md 收录了结构体 uniform 数组的可运行片段。

登录后查看全文
thebookofshaders