【免费下载链接】thebookofshaders
Step-by-step guide through the abstract and complex universe of Fragment Shaders.
导读
本文是开源着色器教材《The Book of Shaders》第一章《入门》(Getting started)的技术解读,面向已经具备编程经验、想进入 GPU 渲染世界的开发者。你将理解"片元着色器(Fragment Shader)到底是什么、为什么它能以惊人的速度处理像素"这两个核心问题,并掌握并行计算(parallel processing)、线程(thread)、GLSL 标准等关键概念,为后续章节亲手编写第一个着色器程序打下理论基础。读完本文,你不仅能解释 GPU 为何"快",还能理解为什么 GLSL 代码的写法与传统 CPU 程序如此不同。
什么是 Fragment Shader(片元着色器)
从"逐笔绘画"到"一次成像"
如果你有使用计算机绘图工具(例如Paint)的经验,就会知道传统流程是这样的:先画一个圆,再画一个矩形、一条线、几个三角形……直到最终拼出想要的图像。这个过程非常像用手逐字写信或写书——它是一系列"一条接一条执行"的指令,属于串行的创作方式。
Shader(着色器)同样是一组指令,但区别在于:这些指令是对屏幕上的每一个像素"同时"执行的。这意味着你写的代码必须根据像素在屏幕上的位置产生不同的行为。在第一章之前的引言(00/README.md)中,作者把着色器比喻为"图形界的古腾堡印刷机":印刷术让文字从逐字手抄变成整页批量复制,而 Fragment Shader 让图像从逐笔绘制变成全屏像素并行计算。
与印刷机类似,你的着色器程序本质上就是一个数学函数:它接收一个像素的**位置(position)作为输入,返回这个像素的颜色(color)**作为输出。一旦编译完成,它就会以极快的速度运行——这就是 Fragment Shader 的基本心智模型:位置 → 颜色的纯函数映射。
与 CPU 程序的本质差异
理解这一点非常重要:传统 CPU 程序是"一次处理一个数据、按顺序完成任务",而 Fragment Shader 是"同时处理全部数据(每个像素)、每个像素独立执行同一段代码"。因此编写着色器时,你思考的不是"流程步骤",而是"给定任意坐标,这个点应该呈现什么颜色"这一通用规则——这也就是后续章节中作者反复强调的"写一个能根据像素位置改变结果的通用函数"所需的抽象能力。
为什么 Shader 这么快?——并行计算的奇迹
CPU 的串行流水线隐喻
想象计算机的 CPU 是一条巨大的工业管道,每个任务就像通过管道的工件,类似工厂流水线:有些任务更大,需要更多时间和能量,即需要更多"算力"(processing power)。由于计算机的架构,这些任务被迫串行执行——每个任务必须等前一个任务完成后才能开始。现代计算机通常拥有多个处理器核心(例如 2、4 或 8 核),它们像多条小管道一样并行工作。每一条这样的"管道"就是一个线程(thread)。
像素计算量:规模惊人的数学题
游戏和其他图形应用需要的算力远超普通程序,因为它们必须逐像素做海量运算:屏幕上每一个像素都要被计算;而在 3D 游戏中,还要额外计算几何与透视。让我们用文档中的数字来直观感受这个规模:
- 老式800×600屏幕:每帧需处理480,000个像素,即每秒14,400,000次计算;
- 现代2560×1440屏幕、60 FPS:每秒达到221,356,800次计算。
这个量级足以压垮任何单个微处理器。图形工程师如何解决这个问题?
GPU:用大量小处理器替代少数大处理器
答案就是并行处理。与其拥有几个又大又强的微处理器(管道),不如让大量微型处理器同时并行工作——这正是显卡上 GPU(图形处理单元)的架构。
文档中用一个非常形象的比喻:把这些微型处理器想象成一张"管道桌",把每个像素的数据想象成乒乓球。每秒 14,400,000 个乒乓球几乎能堵死任何一条单独的管道;但一张由 800×600 条小管道组成的桌子,每秒接收 30 波、每波 480,000 个球,却能轻松消化。分辨率越高同理——并行硬件越多,能处理的数据流就越大。
GPU 的另一项"超能力":硬件级数学加速
除了并行之外,GPU 还有一项隐藏优势:复杂的数学函数直接在硬件芯片层面完成,而不是通过软件模拟。这意味着三角函数、矩阵运算等可以以接近"电的速度"执行。这一点将在后续章节(例如 03/README.md 中提到的sin()、cos()、pow()等硬件加速函数)被反复使用——你在 GLSL 里写的sin()、atan()并不是普通的软件函数调用,而是显卡芯片直接算出来的。
什么是 GLSL?
GLSL 是OpenGLShadingLanguage(OpenGL 着色语言)的缩写,是本书后续章节中你将看到的着色器程序的标准写法。根据硬件和操作系统的不同,还存在其他类型的着色器;本书聚焦于由Khronos Group制定的 OpenGL 规范。理解 OpenGL 的历史有助于理解它许多"奇怪"的约定。
GLSL 着色器之所以值得学习,是因为它跨平台编译运行:同一个 GLSL 程序可以在 OpenGL、OpenGL ES 以及 WebGL 环境中运行,因此你学会的知识可以直接迁移到 Processing、openFrameworks、Three.js 网站以及 iOS/Android 游戏等各类项目中(见 00/README.md 对全书覆盖范围的说明)。
从仓库源码看 GLSL 的样子
在 02/hello_world.frag 中,可以看到最基础的 GLSL 程序结构:
#ifdef GL_ES precision mediump float; #endif uniform float u_time; void main() { gl_FragColor = vec4(1.0,0.0,1.0,1.0); }从这段代码可以提炼出几个关键事实(详见 02/README.md):
- GLSL 与 C 类似,程序有一个唯一的
main函数,最终返回一个颜色; - 最终像素颜色被赋值给保留全局变量
gl_FragColor; - 语言内置
vec4(四维浮点向量)等类型,对应 RGBA 四个通道,且值是归一化的(范围0.0~1.0); #ifdef/#endif属于预处理宏,GL_ES通常在移动设备和浏览器上编译时被定义;- **精度(precision)**至关重要:
precision mediump float;将所有浮点设为中等精度。低精度渲染更快但质量下降,可改为lowp或highp; - GLSL 规范不保证自动类型转换——
vec4(1,0,0,1)这类整数写法可能编译失败。养成良好的习惯:浮点字面量务必写小数点(1.0而不是1)。
为什么 Shader 让初学者望而生畏?
正如"能力越大,责任越大",GPU 强大的并行架构也伴随着严格的限制。文档明确指出两个核心约束:
约束一:线程是"盲目"的(blind)
要让线程并行运行,每个线程(管道)必须与其他线程完全独立。也就是说,线程对"其他线程在做什么"一无所知。这个限制意味着所有数据只能单向流动:
- 无法查看另一个线程的输出结果;
- 无法修改另一个线程的输入数据;
- 无法把一个线程的输出传递给另一个线程作为输入。
如果允许线程间通信,数据的完整性就会受到威胁。这是着色器编程与 CPU 编程最本质的思维差异:你永远不能依赖"邻居像素"的计算结果(除非使用后续章节介绍的特殊技巧)。
约束二:线程是"无记忆"的(memoryless)
GPU 会让每个并行微处理器(管道)保持持续忙碌:一旦空闲就立即接收新任务。因此一个线程不可能知道自己上一秒在做什么——它可能上一刻在绘制操作系统的 UI 按钮,下一刻在渲染游戏里的一片天空,再下一刻在显示邮件正文。每个线程不仅是"盲"的,而且是"无记忆"的。
这两条约束,加上"编写一个能逐像素改变结果的通用函数"所需的抽象思维,正是着色器不受初学者欢迎的根本原因。不过好消息是:这正是本书存在的意义——接下来我们会一步步从简单到高级地学习各种着色计算。
实践路径:如何在浏览器中验证这些概念
文档特别指出:如果你正用现代浏览器阅读本书,你将能直接玩转交互式示例。在仓库中,交互式示例通过嵌入的 Canvas 渲染:例如 01/index.php 会读取对应章节的README-*.md文件并用 Parsedown 渲染,同时配合 00/README.md 中的<canvas>赞
【免费下载链接】thebookofshaders
Step-by-step guide through the abstract and complex universe of Fragment Shaders.
相关推荐
The Book of Shaders 第 02 章详解:GLSL Fragment Shader 的 Hello World——gl_FragColor、vec4 与精度声明
The Book of Shaders 第 02 章详解:GLSL Fragment Shader 的 Hello World——gl_FragColor、ve
The Book of Shaders 实战指南:在 Three.js、Processing 与 openFrameworks 中运行 Fragment Shader
The Book of Shaders 实战指南:在 Three.js、Processing 与 openFrameworks 中运行 Fragment Sha
Ant Design Pagination「更多分页」深度解析:省略号折叠与前后跳转按钮的源码实现
Ant Design Pagination「更多分页」深度解析:省略号折叠与前后跳转按钮的源码实现 导读 本文围绕 Ant Design 官方演示 更多分页说明