02. 基本数据类型与变量
GLSL 是一种强类型语言。这与 JavaScript 非常不同。在 JS 中你可以写 let a = 1; a = 1.5;,但在 GLSL 中,整数和浮点数是完全不同的物种,不能混用。
1. 基础标量类型
float (浮点数) - 最常用的类型
GLSL 中绝大多数计算都是浮点运算。 ⚠️ 重要规则:必须包含小数点!
float a = 1.0; // ✅ 正确
float b = 1.; // ✅ 正确(简写)
float c = 1; // ❌ 错误!这是整数,不能赋值给 float
float d = 1.0 + 2; // ❌ 错误!不能将 float 和 int 相加
float e = 1.0 + 2.0;// ✅ 正确
int (整数)
通常用于循环计数器或数组索引。
int i = 10;
int j = -5;
bool (布尔值)
用于条件判断。
bool isActive = true;
bool isZero = (1.0 > 0.5); // 结果为 true
2. 向量类型 (Vector) - GLSL 的灵魂
GLSL 主要是处理图形的,而图形就是向量。
类型列表
| 类型 | 描述 | 用途示例 |
|---|---|---|
vec2 | 2 个浮点数 | 纹理坐标 (UV), 2D 屏幕坐标 |
vec3 | 3 个浮点数 | 3D 位置 (XYZ), RGB 颜色 |
vec4 | 4 个浮点数 | 齐次坐标 (XYZW), RGBA 颜色 |
构造向量
构造函数非常灵活,可以将不同类型组合起来。
// 基础构造
vec2 v2 = vec2(1.0, 2.0);
vec3 v3 = vec3(1.0, 2.0, 3.0);
// 从标量构造(所有分量相同)
vec3 white = vec3(1.0); // 等同于 vec3(1.0, 1.0, 1.0)
// 混合构造
vec3 v3_mixed = vec3(v2, 3.0); // 使用 vec2 填充前两个分量,3.0 填充第三个
vec4 color = vec4(v3, 1.0); // 常用:将 RGB 转为 RGBA
3. 矩阵类型 (Matrix)
矩阵用于坐标变换(旋转、缩放、平移)。
mat2: 2x2 矩阵mat3: 3x3 矩阵mat4: 4x4 矩阵(最常用,如 MVP 矩阵)
mat4 m = mat4(1.0); // 创建一个单位矩阵(对角线为1,其余为0)
4. 采样器类型 (Sampler) - 纹理的"钥匙"
采样器类型用于访问纹理数据,是 GLSL 中处理图像和纹理的核心类型。
sampler2D - 2D 纹理采样器
sampler2D 是最常用的采样器类型,用于访问 2D 纹理(如图片)。
⚠️ 重要规则:
sampler2D只能作为uniform变量使用(不能是attribute或varying)- 只能在片段着色器中使用(顶点着色器中不能采样纹理)
- 使用
texture2D()函数从纹理中读取颜色
// 片段着色器中
precision mediump float;
uniform sampler2D uTexture; // 声明一个 2D 纹理采样器
varying vec2 vUv; // UV 坐标(用于指定采样位置)
void main() {
// 使用 texture2D() 函数从纹理中采样颜色
// 参数1:采样器变量
// 参数2:UV 坐标(vec2,范围通常是 0.0 到 1.0)
vec4 color = texture2D(uTexture, vUv);
gl_FragColor = color;
}
其他采样器类型
| 类型 | 描述 | 用途示例 |
|---|---|---|
sampler2D | 2D 纹理 | 普通图片纹理 |
samplerCube | 立方体贴图 | 天空盒、环境贴图 |
sampler3D | 3D 纹理 | 体积数据 |
纹理采样基础
// UV 坐标的含义:
// vUv.x = 0.0 表示纹理左边,1.0 表示右边
// vUv.y = 0.0 表示纹理下边,1.0 表示上边
vec4 color = texture2D(uTexture, vec2(0.0, 0.0)); // 左下角
vec4 color = texture2D(uTexture, vec2(1.0, 1.0)); // 右上角
vec4 color = texture2D(uTexture, vec2(0.5, 0.5)); // 中心点
返回值:texture2D() 返回 vec4(RGBA 颜色),即使纹理本身是 RGB 格式,也会自动补上 Alpha 通道。
5. 类型转换
GLSL 不会自动转换类型,你必须显式转换。
int i = 10;
float f = float(i); // int -> float
float x = 2.5;
int y = int(x); // float -> int (结果为 2)
📝 小测验
以下哪行代码是正确的?
void main() {
float a = 1; // A
vec3 b = vec3(1); // B
float c = 1.0 + 2; // C
vec2 d = vec2(1.0); // D
}
点击查看答案
答案:D
- A 错:
1是 int,不能赋给 float。 - B 错:
vec3需要 float 参数(vec3(1.0)),虽然部分驱动允许vec3(1)但不标准。 - C 错:不能把 float 和 int 相加。
- D 对:
vec2(1.0)会创建一个(1.0, 1.0)的向量。
📝 编程作业
难度:⭐ 简单 | 预计时间:30-40 分钟
任务要求
创建一个平面,使用 UV 坐标和向量类型绘制图案,展示 GLSL 的类型系统。
具体要求
- 使用 vec2:将 UV 坐标的
xy分量作为 RGB 的前两个通道 - 使用 vec3 和 vec4:构造一个基础颜色,然后转换为 RGBA
- 向量构造:展示至少 3 种不同的向量构造方式
- 类型转换:正确使用
float()进行类型转换
💡 理解 UV 坐标:把 3D 表面"展开"成 2D 地图
UV 坐标的本质:将 3D 几何体的表面"展开"成 2D 平面,用坐标标记每个点。
类比 1:给地球画地图
想象你要给地球画一张世界地图:
- 地球是 3D 的(有经度、纬度、高度)
- 地图是 2D 的(只有 X、Y 坐标)
- UV 坐标就像地图上的坐标系统,告诉你"这个点在地图上哪个位置"
类比 2:给礼物包包装纸
想象你要给一个立方体礼物包包装纸:
- 立方体有 6 个面(3D 物体)
- 包装纸是平的(2D 图片)
- UV 坐标告诉你:"这个顶点对应包装纸上的哪个位置"
- 左下角顶点 → 包装纸左下角
(0, 0) - 右上角顶点 → 包装纸右上角
(1, 1)
- 左下角顶点 → 包装纸左下角
可视化理解
3D 几何体表面 (PlaneGeometry) 2D UV 坐标空间
┌─────────────────┐ ┌──────────────┐
│ │ │ │
│ (0,1) (1,1) │ │ (0,1) (1,1) │
│ ●────● │ 展开成 2D │ ●────● │
│ │ │ │ ──────────> │ │ │ │
│ │ │ │ │ │ │ │
│ ●────● │ │ ●────● │
│ (0,0) (1,0) │ │ (0,0) (1,0) │
└─────────────────┘ └──────────────┘
关键点:
- U 轴:水平方向,从左到右,范围
0.0→1.0 - V 轴:垂直方向,从下到上,范围
0.0→1.0 - 每个顶点都有一个 UV 坐标,标记它在"展开的地图"上的位置
- 每个像素的 UV 是自动插值计算的(在顶点之间平滑过渡)
实际应用
// 在片段着色器中,vUv 告诉你当前像素在"地图"上的位置
void main() {
// vUv.x = 0.0 表示在左边,1.0 表示在右边
// vUv.y = 0.0 表示在下边,1.0 表示在上边
// 可以直接用 UV 作为颜色,可视化 UV 分布
gl_FragColor = vec4(vUv.x, vUv.y, 0.0, 1.0);
// 红色通道 = U 坐标(水平渐变)
// 绿色通道 = V 坐标(垂直渐变)
}
所以回答你的问题:
- ✅ 是的,UV 代表几何体表面,但更准确地说,它代表"表面上的每个点在展开的 2D 地图上的位置"
- 就像门牌号告诉你房子在地图上的位置一样,UV 坐标告诉你"这个点在地图上的位置"
代码框架
⚠️ 重要:varying 变量必须在两个着色器中都声明!
varying 变量用于从顶点着色器传递数据到片段着色器。工作流程:
- 在顶点着色器中声明并赋值
- 在片段着色器中声明并接收(会自动插值)
// ========== 顶点着色器 ==========
precision mediump float;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
attribute vec3 position;
attribute vec2 uv; // 从几何体接收 UV 坐标
varying vec2 vUv; // 声明要传递给片段着色器的变量
void main() {
vUv = uv; // ⚠️ 关键:将 UV 赋值给 varying 变量
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
// ========== 片段着色器 ==========
precision mediump float;
varying vec2 vUv; // 接收来自顶点着色器的插值后的 UV 坐标
void main() {
// 任务 1:使用 vec2 的 xy 作为颜色
vec3 color1 = vec3(vUv.xy, 0.5);
// 任务 2:使用 vec3 构造颜色,然后用 vec4 添加透明度
vec3 baseColor = vec3(1.0, 0.5, 0.0);
vec4 finalColor = vec4(baseColor, 1.0);
// 任务 3:使用 swizzling 创建新颜色
vec3 white = vec3(1.0);
vec3 swapped = white.bgr; // 交换 RGB 顺序
// TODO: 组合这些效果,创建一个有趣的图案
gl_FragColor = vec4(color1, 1.0);
}
关键点说明:
attribute vec2 uv;:从几何体(如PlaneGeometry)接收 UV 数据varying vec2 vUv;:在两个着色器中都要声明,变量名必须完全一致vUv = uv;:在顶点着色器的main()中赋值,这样片段着色器才能使用- 片段着色器中的
vUv是插值后的值(每个像素的 UV 是自动计算的)
组合技巧说明:
mix(a, b, t):混合两个颜色,t在0.0到1.0之间t = 0.0→ 完全使用at = 1.0→ 完全使用bt = 0.5→ 两者各占一半
- 相乘
*:颜色相乘会让结果变暗,适合创建叠加效果 - 条件判断
if:可以根据 UV 位置创建分区效果 - Swizzling:可以交换、重复或重新排列向量分量,创建变化
检查清单
- 所有浮点数都使用了
.0后缀 - 没有混用
int和float - 展示了至少 3 种向量构造方式
- 代码可以正常运行,没有编译错误
参考答案:
// ========== 组合这些效果,创建一个有趣的图案 ==========
// 以下是几种组合方式的示例,你可以选择一种或自己创新:
// 方式 1:混合两种颜色(根据 UV 位置)
vec3 mixed = mix(color1, baseColor, vUv.x); // 从左到右渐变混合
// 方式 2:使用 swizzling 创建变化
vec3 colorWithSwizzle = vec3(vUv.yx, 0.5); // 交换 UV 的 x 和 y
// 方式 3:根据位置选择不同颜色(创建分区效果)
vec3 finalResult;
if (vUv.x < 0.5) {
finalResult = color1; // 左半边用 UV 颜色
} else {
finalResult = baseColor; // 右半边用基础颜色
}
// 方式 4:相乘叠加(让颜色更丰富)
vec3 multiplied = color1 * baseColor;
// 选择一种方式输出(你可以尝试不同的组合)
gl_FragColor = vec4(mixed, 1.0);
// 或者:gl_FragColor = vec4(finalResult, 1.0);
// 或者:gl_FragColor = vec4(multiplied, 1.0);
扩展挑战
- 🌟 创建一个"类型展示画廊",屏幕分成多个区域展示不同的向量操作
- 🌟 使用
int和类型转换创建图案(如棋盘格)
参考资源
- 完整作业清单:编程作业清单.md