Shader 基础概念

Makepad Shader 是自定义的 MPSL 着色语言。 MPSL 可以生成为 glsl 等着色语言。

推荐前置知识学习:GLSL 着色器入门教程 https://thebookofshaders.com/?lan=ch

什么是Shader

让我们先从一个简单的比喻开始理解什么是 shader。想象你是一个画家,面前有一块巨大的画布,这个画布被分成了数百万个小格子(像素)。

现在你有两个任务:

  1. 决定每个形状的位置和大小(顶点着色器)
  2. 决定每个格子应该填充什么颜色(像素/片元着色器)
1// 一个最基础的 Makepad shader
2MyFirstShader = {{MyFirstShader}} {
3    // 1. 顶点着色器 - 处理位置
4    fn vertex(self) -> vec4 {
5        // 将形状放在正确的位置
6        let position = self.geom_pos * self.rect_size + self.rect_pos;
7        return self.camera_projection * vec4(position, 0.0, 1.0);
8    }
9
10    // 2. 片元着色器 - 处理颜色
11    fn pixel(self) -> vec4 {
12        // 给每个像素上色
13        return vec4(1.0, 0.0, 0.0, 1.0); // 红色
14    }
15};

让我们可视化一下这个过程:

1顶点着色阶段:                像素着色阶段:
2
3   P1●─────●P2                 ░░░░░░░░
4     │     │                   ░██████░
5     │     │         =>        ░██████░
6     │     │                   ░██████░
7   P3●─────●P4                 ░░░░░░░░
8
9 (确定形状位置)              (填充每个像素)

GPU vs CPU 渲染的区别

为什么要用GPU渲染?让我们通过一个实例来理解。

假设要渲染一个 1000x1000 像素的区域:

1CPU 渲染:
2- 依次处理每个像素
3- 100万个像素需要依次处理
4- 类似于用一个画笔一点点填充
5
6GPU 渲染:
7- 大量像素并行处理
8- 100万个像素可以同时处理
9- 类似于用喷枪快速覆盖

视觉化表示:

1CPU 渲染进度:              GPU 渲染进度:
2█░░░░░░░░░  10%          ▒▒▒▒▒▒▒▒▒▒  100%
3██░░░░░░░░  20%          (同时处理)
4███░░░░░░░  30%
5...
6██████████  100%

本质上是为了提升性能,GPU 是一个专门用于图形渲染的硬件,它可以同时处理大量像素,因此比 CPU 更适合渲染图形。

坐标系统详解

在 Makepad 中,我们需要处理三种主要的坐标系统:

归一化设备坐标(NDC)

[-1,1]范围,提供设备无关的标准化空间,确保渲染结果在不同分辨率下保持一致。

  • 使用场景:当你需要进行设备无关的渲染或者处理3D变换时。
  • 比如:3D渲染、视图裁剪、透视投影、跨设备一致性渲染等。

像素坐标

实际屏幕像素,用于精确定位屏幕上的实际位置,直接对应物理显示设备。

  • 使用场景:当你需要精确控制渲染目标在屏幕上的确切位置时。
  • 比如:UI元素的精确定位、像素边框的绘制、文本渲染的对齐等。

UV 坐标

[0,1]范围的纹理坐标,用于纹理映射和参数化表面, 提供独立于分辨率的相对位置。

  • 使用场景:当你需要处理纹理映射或者创建参数化的视觉效果时。
  • 比如:纹理映射、渐变效果、UV动画、程序化纹理生成、参数化形状等。

代码和图示来理解坐标转换

1让我们通过代码和图示来理解坐标转换:
2fn vertex(self) -> vec4 {
3    // 1. 几何坐标到像素坐标的映射
4    let pixel_pos = self.geom_pos * self.rect_size + self.rect_pos;
5
6    // 2. 像素坐标到 UV 坐标的映射
7    self.uv = (pixel_pos - self.rect_pos) / self.rect_size;
8
9    // 3. 像素坐标到 NDC 的映射
10    let ndc = self.camera_projection * vec4(pixel_pos, 0.0, 1.0);
11
12    return ndc;
13}

坐标系统的可视化:

1归一化设备坐标(NDC)      像素坐标            UV坐标
2     (-1,1)            (0,0)              (0,0)
3        ┃               ┃                   ┃
4        ┃               ┃                   ┃
5  ━━━━━━●━━━━━━   ━━━━━●━━━━━   ━━━━━━━●━━━━━━━
6        ┃               ┃                   ┃
7        ┃               ┃                   ┃
8     (1,-1)         (width,            (1,1)
9                    height)
10
11坐标映射流程图:
12
13像素坐标 (100, 100)            UV坐标 (0.5, 0.5)           NDC (0.0, 0.0)
14    ┌──────────┐               ┌──────────┐               ┌──────────┐
15    │(0,0)     │     ÷size     │(0,0)     │    *2-1       │(-1,1)    │
16    │          │ ────────────► │          │ ────────────► │          │
17    │   ●      │               │   ●      │               │   ●      │
18    │          │               │          │               │          │
19    │     (w,h)│               │     (1,1)│               │    (1,-1)│
20    └──────────┘               └──────────┘               └──────────┘
21
22
23需求判断:
24┌────────────────────┐
25│ 是否需要像素精度?    │
26└─────────┬──────────┘
2728    ┌─────┴─────┐
29    │   Yes     │     No
30    ▼           ▼
31像素坐标     ┌──────────────┐
32            | 是否需要纹理   |
33            │   或渐变?    │
34            └──────┬───────┘
3536              ┌────┴────┐
37              │  Yes    │     No
38              ▼         ▼
39           UV坐标    NDC坐标

颜色与像素

在 Makepad 中,颜色使用 vec4 表示,包含 RGBA 四个通道:

1fn pixel(self) -> vec4 {
2    //---------- 红   绿   蓝   透明度
3    return vec4(1.0, 0.0, 0.0, 1.0);
4}

颜色混合示意:

1基础颜色:           透明度混合:
2Red   (1,0,0)      ██ 不透明 (alpha = 1.0)
3Green (0,1,0)  +   ▒▒ 半透明 (alpha = 0.5)
4Blue  (0,0,1)      ░░ 透明   (alpha = 0.0)

预乘Alpha的概念:

1// 普通 RGBA
2vec4(1.0, 0.0, 0.0, 0.5) // 半透明红色
3
4// 预乘 Alpha (推荐)
5vec4(0.5, 0.0, 0.0, 0.5) // RGB 通道已乘以 alpha