第 3 课:坐标系与 UV

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

同一个位置有几种表示法

做圆形遮罩、棋盘格、雷达扇形或图片贴图时,最容易混淆的是“位置”到底在哪个坐标系。本课先用正方形预览理解 UV,再故意改成宽画布,看圆形为什么被拉长。运行方式:在 doc/shader-tutorial/exercises/ 执行 http-server -p 1000,打开 http://localhost:1000/03-coordinates-uv/。

变量 所在空间 范围与单位 常见用途
position 几何体局部空间 模型自定义单位 顶点形变与物体轮廓
uv / vUv 几何体 UV 空间 通常 0–1,起点由几何体定义 贴图、重复图案、随物体移动的遮罩
gl_FragCoord.xy 当前渲染目标的窗口空间 像素,左下为原点 屏幕空间图案、像素定位、分辨率相关效果
uResolution JS 传入的画布尺寸 物理像素 把 gl_FragCoord 归一化、宽高比修正

vUv 与 gl_FragCoord 不是同一个东西。平面的 UV 即使在屏幕上只占很小一块,仍可从 0 到 1;gl_FragCoord 始终是该片元在渲染目标中的像素坐标。CSS 像素与画布物理像素还受设备像素比影响。本系列的运行时限制像素比到 2;在 JS 中用 renderer.getDrawingBufferSize() 取得实际绘制尺寸,与 gl_FragCoord 保持一致。

从 UV 到中心距离

平面左下角的 vUv≈(0,0),右上角是 (1,1)。把 vUv.x 写入红通道、vUv.y 写入绿通道,会看到左下黑、右上黄。中心化用 p = vUv - vec2(0.5),此时中心为 (0,0),左右边界的 x 是 -0.5 和 0.5。length(p) 是到中心的欧氏距离:

vec2 p = vUv - vec2(0.5);
float d = length(p); // sqrt(p.x*p.x + p.y*p.y)

半径为 r 的实心圆可写 1.0 - step(r,d)。边界会在一个像素内直接跳变,放大时容易锯齿。柔边版用 1.0 - smoothstep(r-w,r+w,d),w 越大,边缘越宽。要做圆环,可用“外圆遮罩 − 内圆遮罩”;也可用 abs(d-r) 表示到目标半径的距离。遮罩组合是之后 SDF、选区和描边的基础。

注意:UV 中的数学圆不必然是屏幕上的圆。 如果平面在屏幕上宽高不同,同样的 UV 距离会对应不同数量的像素。前七题使用正方形预览,让 UV 的 x/y 单位在画面中等长。第八题刻意用宽画布,暴露拉伸问题。

宽高比修正的适用条件

第八题的平面覆盖整个画布,所以 vUv 与画布比例一一对应。设画布宽 W、高 H,则像素中的等距要求 p.x 乘以 W/H:

vec2 p = vUv - vec2(0.5);
p.x *= uResolution.x / uResolution.y;
float d = length(p);

页面开关在修正前后切换;调整浏览器宽度时圆应继续保持正圆。uResolution 来源是画布 drawing buffer 的物理像素,不是 window.innerWidth。这条公式只适用于当前全屏平面。如果材质贴在一个场景里的长方形物体上,画布宽高比和物体表面宽高比是两件事:要按物体的物理宽高或局部坐标修正,而不能盲目用画布 W/H。若使用 gl_FragCoord.xy 画屏幕空间圆,先除以 uResolution.y 来统一像素单位也很方便。

重复图案与极坐标

棋盘格把 vUv 乘以 8 拆成 8×8 个格子,再由 floor(vUv*8.0) 得到格子索引。fract(cellIndex*0.5) 在偶数索引时为 0、奇数索引时为 0.5;step(0.5, ...) 把它变成 0/1,横纵结果不同的格子涂白。这里用 fract + step 判断奇偶,而不是对每格内部再次分段,因此严格得到 8×8 棋盘。

极坐标用 r=length(p) 表示半径,用 θ=atan(p.y,p.x) 表示相对 X 正方向的角度,范围为 -π 到 π。扇形需同时满足 r < radius 和 abs(θ) < halfAngle。JS 滑块显示的是整段开口角度(度),传给 Shader 时先换成弧度,再除以二。把 90° 改成 180° 后,扇形应由四分之一圆变成半圆。

数据流、常规实践与排查

JS 建立平面与 ShaderMaterial;平面顶点的 uv attribute 经过顶点着色器成为 vUv,GPU 光栅化插值后,片元着色器逐片元计算半径、格子或角度。第八题额外从 JS 传入 uResolution,第九题传入 uHalfAngle。顶点 Shader 管位置,片元 Shader 管当前片元的颜色;以后让图案随物体变形时,UV 和局部坐标会跟随物体,而 gl_FragCoord 仍固定在屏幕。

屏幕上的圆变椭圆时,先检查预览区域是否正方形,再检查使用的是 UV、局部位置还是屏幕像素;不要先随意调半径。棋盘格数量不对时,区分“周期数”和“每周期内再次分段的数量”。扇形角度不对时,检查度与弧度转换、atan 参数顺序,以及传入的是整角还是半角。宽度或设备像素比变化后若形状跳动,检查 uResolution 是否来自当前 drawing buffer。

练习

难度 题目 可见验收
⭐ 把 UV 映射为红绿 左下黑、右上黄
⭐ UV 中心化与距离 中心黑、四周亮
⭐⭐ Length 圆形渐变 中心亮、向外变暗
⭐⭐ Step 实心圆 中心白色实心圆
⭐⭐ Smoothstep 柔和圆边 白圆边缘柔和
⭐⭐⭐ 两条边界组合成圆环 白色空心圆环
⭐⭐⭐ Fract + Step 棋盘格 严格 8×8 黑白棋盘
⭐⭐⭐ 宽画布圆形拉伸与修复 关闭时椭圆、开启时正圆
⭐⭐⭐⭐ Atan + Length 可调扇形 滑块控制开口角度