数据类型 (Data Types)
OpenGL Shading Language(GLSL)定义了多种数据类型,也定义了用户自定义类型的手段。
基本类型
基本类型是 GLSL 中最基础的类型,非基本类型是这些基础类型的聚合。
INFO
本文档会提及双精度类型,这些仅在 OpenGL 4.0 或 ARB_gpu_shader_fp64 可用时可用。
标量类型
| 类型 | 说明 |
|---|---|
bool | 布尔类型,值为 true 或 false |
int | 有符号 32 位二进制补码整数 |
uint | 无符号 32 位整数 |
float | IEEE-754 单精度浮点数 |
double | IEEE-754 双精度浮点数 |
WARNING
GLSL 1.30 及以上版本才有上述具体的整数和浮点大小和格式规范。
向量类型
每种标量类型都有 2、3、4 分量的向量版本。其中 n 可以是 2、3 或 4:
| 类型 | 说明 |
|---|---|
bvecn | 布尔向量 |
ivecn | 有符号整数向量 |
uvecn | 无符号整数向量 |
vecn | 单精度浮点向量 |
dvecn | 双精度浮点向量 |
向量值可以使用与标量值相同的数学运算符,这些运算符对每个分量执行逐分量操作。
分量访问(Swizzling)
可以使用以下语法访问向量的分量:
vec4 someVec;
someVec.x + someVec.y;这被称为分量访问(swizzling)。可以使用 x、y、z、w,分别引用第一、第二、第三和第四个分量。
vec2 someVec;
vec4 otherVec = someVec.xyxx;
vec3 thirdVec = otherVec.zyy;最多可以使用 4 个字母的任意组合创建该长度的向量(相同基本类型)。
分量访问也适用于左值:
vec4 someVec;
someVec.wzyx = vec4(1.0, 2.0, 3.0, 4.0); // 反转顺序
someVec.zx = vec2(3.0, 5.0);WARNING
使用分量访问设置值时,不能使用相同的分量两次。如 someVec.xx = vec2(4.0, 4.0); 是不允许的。
有三组分量掩码:xyzw、rgba(用于颜色)、stpq(用于纹理坐标)。这三组没有实际区别,只是语法糖。不能在单次分量访问操作中混合使用不同组的名称。
OpenGL 4.2 或 ARB_shading_language_420pack 起,标量也可以分量访问:
float aFloat;
vec4 someVec = aFloat.xxxx;矩阵类型
所有矩阵类型都是浮点类型,可以是单精度或双精度。其中 n 和 m 可以是 2、3 或 4:
| 类型 | 说明 |
|---|---|
matnxm | n 列 m 行的矩阵(注意与数学约定相反!) |
matn | matnxn 的简写,n×n 方阵 |
双精度矩阵使用 dmat 前缀(如 dmat4x4)。
OpenGL 以列主序(column-major)格式存储矩阵——即元素在列中连续存储。
分量访问不适用于矩阵。可以使用数组语法访问矩阵字段:
mat3 theMatrix;
theMatrix[1] = vec3(3.0, 3.0, 3.0); // 设置第二列为全 3.0
theMatrix[2][0] = 16.0; // 设置第三列第一个元素为 16.0
theMatrix[1].yzx = vec3(3.0, 1.0, 2.0); // 第一个数组访问结果是向量,可以分量访问不透明类型
不透明类型(Opaque Types)表示着色器以某种方式引用的外部对象。不透明变量没有与常规类型相同的"值";它们是引用真实数据的标记。
不透明类型变量只能以两种方式之一声明:
- 作为全局作用域的
uniform变量 - 作为
in限定的函数参数
不透明类型不能是左值。
采样器
采样器类型是表示绑定到 OpenGL 上下文的纹理的不透明 GLSL 类型。每种纹理类型都有对应的采样器类型。
详见 采样器。
图像
图像变量引用存储在纹理中的特定类型图像,用于着色器内的任意加载/存储操作。
原子计数器
原子变量表示缓冲区对象中可以进行原子操作的内存位置。
隐式转换
某些值可以隐式转换为某些类型:
- 有符号整数可以隐式转换为无符号整数(反之不行)
- 任一整数类型可以转换为浮点数
- 整数和浮点数可以转换为双精度
如果向量或矩阵包含的基本类型可以隐式转换,则它们也可以隐式转换。
数组
基本类型可以分组为元素序列,称为数组。通常必须用常量表达式初始化的大小来声明数组。
uniform float myValues[12];
myValues.length(); // 返回 12调用 length() 函数产生常量表达式,除非变量是存储块中最后一个成员的无大小数组。
不透明数组
在 GLSL 3.30 中,采样器数组只能通过编译时常量表达式访问。
在 GLSL 4.00 及以上,访问不透明值的数组索引可以是非常量,但必须是动态一致的:
uniform sampler images[10];
uniform int imageCount;
void main()
{
vec4 accum = vec4(0.0);
for (int i = 0; i < imageCount; i++)
{
accum += texture(images[i], ...);
}
}多维数组
OpenGL 4.3 或 ARB_arrays_of_arrays 起,可以声明多维数组:
uniform vec3 multidim[5][2]; // 5 个元素的数组,每个元素是 2 个 vec3 的数组
uniform vec3[5][2] multidim1; // 等价写法WARNING
数组在变量名上从左到右读取,然后类型名上的数组从左到右读取。所以 vec3[5] multidim2[2] 是 2 个元素的数组,每个元素是 5 个 vec3 的数组。
结构体
结构体定义方式与 C++ 类似:
struct Light
{
vec3 eyePosOrDir;
bool isDirectional;
vec3 intensity;
float attenuation;
} variableName; // 可选的变量声明WARNING
GLSL 不支持匿名结构体(没有类型名称的结构体),结构体必须至少有一个成员声明。结构体不能在另一个结构体内定义,但可以使用之前定义的结构体作为成员。
结构体可以包含不透明类型,但此类结构体只能用于不透明类型可用的方式。
结构体字段使用标准的 "." 语法访问。
构造函数和初始化器
几乎所有类型的变量都可以用初始值初始化。例外包括:
- 输入或输出限定变量
- 任何不透明类型的变量
- 接口块中声明的变量
字面值
字面值是写入着色器中的值:
- 布尔字面值:
true、false - 整数字面值默认为
int类型,使用u或U后缀表示uint - 浮点字面值默认为
float类型,使用f或F后缀 - 双精度字面值使用
lf或LF后缀(GLSL 4.00+)
整数字面值可以使用八进制(前缀 0)或十六进制(前缀 0x)表示。
构造函数
复杂类型使用构造函数初始化:
typename(value1, value2, ...)转换构造函数
基本标量类型构造函数可用于类型转换:
| 转换 | 说明 |
|---|---|
从 bool | false → 0/0.0,true → 1/1.0 |
到 bool | 0/0.0 → false,其他 → true |
int ↔ uint | 保证保留位模式(可能改变符号) |
向量构造函数
向量可以从单个值构造(所有分量都设为该值):
vec3(1.0) == vec3(1.0, 1.0, 1.0);也可以从其他向量或标量组合构造:
vec4(vec2(10.0, 11.0), 1.0, 3.5);
vec3(vec4(1.0, 2.0, 3.0, 4.0)); // 截取前三个分量
vec4(vec3(1.0, 2.0, 3.0)); // 错误:分量不足矩阵构造函数
- 单个标量值:对角线初始化为该值,其余为 0。
mat4(1.0)是 4×4 单位矩阵 - 多个值:按列主序填充
mat2(
float, float, // 第一列
float, float); // 第二列
mat4(
vec4, // 第一列
vec4, // 第二列
vec4, // 第三列
vec4); // 第四列矩阵可以从另一个矩阵构造,对应元素被复制,未填充值使用单位矩阵填充。
数组构造函数
const float array[3] = float[3](2.5, 7.0, 1.5);
// 大小可省略
const float array[] = float[](2.5, 7.0, 1.5);结构体构造函数
按定义顺序填充值:
struct Data
{
float first;
vec2 second;
};
Data dataValue = Data(1.4, vec2(16.0, 22.5));初始化列表
GLSL 4.2 或 ARB_shading_language_420pack 起,可以使用初始化列表:
Data dataArray[3] = {
{1.0, {-19.0, 4.5}},
{-3.0, {2.718, 2.0}},
{29.5, {3.142, 3.333}}
};编译器会自动推导类型。初始化列表可以用于初始化任何类型,包括标量值。
接口块
使用 input、output 或 uniform 类型限定符的变量组可以分组为命名块。详见 接口块。