Skip to main content

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 主要是处理图形的,而图形就是向量。

类型列表

类型描述用途示例
vec22 个浮点数纹理坐标 (UV), 2D 屏幕坐标
vec33 个浮点数3D 位置 (XYZ), RGB 颜色
vec44 个浮点数齐次坐标 (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 变量使用(不能是 attributevarying
  • 只能在片段着色器中使用(顶点着色器中不能采样纹理)
  • 使用 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;
}

其他采样器类型

类型描述用途示例
sampler2D2D 纹理普通图片纹理
samplerCube立方体贴图天空盒、环境贴图
sampler3D3D 纹理体积数据

纹理采样基础

// 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 的类型系统。

具体要求

  1. 使用 vec2:将 UV 坐标的 xy 分量作为 RGB 的前两个通道
  2. 使用 vec3 和 vec4:构造一个基础颜色,然后转换为 RGBA
  3. 向量构造:展示至少 3 种不同的向量构造方式
  4. 类型转换:正确使用 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.01.0
  • V 轴:垂直方向,从下到上,范围 0.01.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 变量用于从顶点着色器传递数据到片段着色器。工作流程:

  1. 顶点着色器中声明并赋值
  2. 片段着色器中声明并接收(会自动插值)
// ========== 顶点着色器 ==========
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):混合两个颜色,t0.01.0 之间
    • t = 0.0 → 完全使用 a
    • t = 1.0 → 完全使用 b
    • t = 0.5 → 两者各占一半
  • 相乘 *:颜色相乘会让结果变暗,适合创建叠加效果
  • 条件判断 if:可以根据 UV 位置创建分区效果
  • Swizzling:可以交换、重复或重新排列向量分量,创建变化

检查清单

  • 所有浮点数都使用了 .0 后缀
  • 没有混用 intfloat
  • 展示了至少 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 和类型转换创建图案(如棋盘格)

参考资源