第 1 课:Hello Shader

返回教程目录 · 打开本课练习

从一个三角形开始

日常开发中,材质可以决定物体“看起来是什么样”。MeshBasicMaterial 已能画纯色,但当颜色需要按时间、鼠标或位置逐像素变化时,我们才需要直接写 Shader。本课从纯红三角形走到动画和交互。先运行纯红三角形;它是后面六题的起点。

浏览器打开 exercises/ 目录中的 index.html 不能使用 file://:ES module 和 fetch 读取 .vert / .frag 文件需要 HTTP。进入 doc/shader-tutorial/exercises/ 后执行 http-server -p 1000,然后打开 http://localhost:1000/01-hello-shader/。本教程本地提供 Three.js 模块,无需 CDN。

GPU 为什么分两个阶段

一个三角形有三个顶点。顶点着色器分别处理每个顶点,输出 gl_Position,即裁剪空间中的四维位置。光栅化阶段根据三角形覆盖的屏幕像素生成片元,并对顶点输出的值做插值。片元着色器对每个片元输出颜色。一个 100 万像素的三角形仍只有三个顶点着色器调用,却可能有约 100 万次片元着色器调用。因此放在片元阶段的昂贵计算会被重复执行很多次。

本课的最短路径是:

JavaScript 建立 BufferGeometry(position, uv)
  → Three.js 将 attribute 与 ShaderMaterial 上传到 GPU
  → 顶点着色器把 position 变换成 gl_Position,并传出 vUv
  → 光栅化为三角形内部的每个片元插值 vUv
  → 片元着色器写 gl_FragColor
  → Three.js 进行输出颜色空间转换,显示到画布

position 和 uv 是 attribute:每个顶点有一个值。uTime、uColor、uMouse 是 uniform:一次 draw call 内所有顶点和片元共享同一个值。vUv 是 varying:顶点传出三个值,GPU 在三角形内部插值,片元收到的是当前所在位置的值。第 1 题虽然暂时不使用 vUv,仍保留传递代码,便于第 2–3 课接着使用。

第一段可运行 Shader

main.js 创建三角形,并把两段源码交给 ShaderMaterial。顶点着色器的核心如下:

gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);

position 是模型局部坐标。modelViewMatrix 先把它移到观察空间;projectionMatrix 再映射到裁剪空间。GPU 最后做透视除法,决定它出现在画布哪里。片元着色器的最小颜色语句是:

vec3 color = vec3(1.0, 0.0, 0.0);
gl_FragColor = vec4(color, 1.0);

三个通道分别是 R、G、B,范围通常为 0–1;第四个数是 alpha。这里输出线性颜色,示例尾部的 tonemapping_fragment 和 colorspace_fragment 由 Three.js 处理输出。vec3(1,0,0) 与 vec3(1.0,0.0,0.0) 在语法和类型上不等价;GLSL 中应明确写浮点常量。

uniform、时间和交互

第 2 题在 JS 中写 uColor: { value: new THREE.Vector3(0, 1, 0) },片元着色器写 uniform vec3 uColor;,再用 vec3 color = uColor;。改颜色只改 JS 数值,Shader 不动。它适合主题色、用户选色和状态色。若只需整块物体纯色而无需自定义算法,内建材质更简单,也更容易获得 Three.js 的光照等功能。

第 3 题的混合权重是 0.5 + 0.5 * sin(uTime * 2.0)。sin 原本在 -1 到 1 之间,乘 0.5 再加 0.5 后变为 0 到 1,正好供 mix(red, blue, weight) 使用。uTime 在 JS 中按秒累加;乘数 2.0 是角速度,单位约为弧度每秒,增大它会加快循环。第 4 题让 RGB 三通道的相位错开 2π/3,得到连续的彩色循环。

第 5 题把指针位置转换成归一化画布坐标。DOM 的 Y 轴向下,而 Shader 中的演示坐标约定 Y 轴向上,所以 JS 用 1 - (...) 翻转。指针在左下角时 uMouse≈(0,0),右上角时 uMouse≈(1,1)。这也是把触摸、点击传入 Shader 的常用步骤;不要直接把以 CSS 像素为单位的 clientX 当作 0–1 的 uniform。

第 6 题在顶点着色器中旋转 position.xy。二维旋转公式为 x' = x cosθ - y sinθ,y' = x sinθ + y cosθ。在局部坐标中旋转,三角形绕自身原点转;若把原点移走,旋转中心也会变。第 7 题把同一个 uTime 波形用于顶点缩放和片元亮度:这两个阶段不能互相调用,但能读取相同的 uniform,因此尺寸与颜色同步“呼吸”。

常规实践和故障排查

练习

难度 题目 可见验收
⭐ 纯红三角形 深色背景中央出现红色三角形
⭐ 用 uniform 传入绿色 只改 JS 数值即可变色
⭐⭐ 红蓝之间平滑循环 两色之间连续往返
⭐⭐ RGB 三通道正弦循环 三通道错峰变化
⭐⭐⭐ 鼠标控制红绿通道 向右变红、向上变绿
⭐⭐⭐ 顶点着色器旋转三角形 绕 Z 轴连续旋转
⭐⭐⭐⭐ 颜色与缩放同步呼吸 放大时变亮,缩小时变暗