第 6 课:纹理采样与混合
图片如何变成片元颜色
纹理是 GPU 中可按坐标读取的二维数据。日常开发会用它贴照片、做 UI 遮罩、控制溶解顺序、驱动 UV 位移或存储法线。本课使用本地原创纹理,练习可从 doc/shader-tutorial/exercises/ 运行 http-server -p 1000,不需要 CDN 或额外下载。
main.js 通过 TextureLoader.loadAsync() 读取 PNG,设置 texture.wrapS/wrapT 和颜色空间,然后把对象放到 ShaderMaterial.uniforms。Three.js 把图片上传成 GPU texture,并把 sampler uniform 绑定到对应纹理单元。平面每顶点的 uv attribute 经顶点 Shader 送入 vUv,在三角形内部插值;片元 Shader 通过 texture2D(uTexA,vUv) 取出当前坐标附近的像素,再输出颜色。当前练习使用 ShaderMaterial 的 GLSL ES 1.00 风格写法,因此采样函数是 texture2D;第 12 课再比较 GLSL ES 3.00 的 texture 写法。
纹理坐标、平铺与采样
vUv 在本课正方形预览里从 0 到 1。把它乘以 2,就会访问 0–2:若纹理设置为 RepeatWrapping,超出 1 的部分会回到纹理开头,因此得到 2×2 四个图案。若是默认 ClampToEdgeWrapping,边缘颜色会被拉长,而不是重复。示例的暖色纹理按周期公式制作,两侧颜色相接,平移时才没有明显接缝;随便换一张照片,即使设为 RepeatWrapping,图片左右边缘也未必匹配。
双图渐变取 a=texture2D(uTexA,vUv).rgb 与 b=texture2D(uTexB,vUv).rgb,再 mix(a,b,vUv.x)。左边权重 0,右边权重 1。黑白遮罩把第三张纹理的 .r 当权重:黑处显示 A,白处显示 B,灰处线性混合。遮罩、噪声和位移图保存的是数据,不应把灰度值当 sRGB 照片处理。
颜色空间是数据含义的一部分
教程的 art-a.png、art-b.png 是供人看的颜色纹理,JS 将其标记为 THREE.SRGBColorSpace;WebGL 采样时会得到适合线性计算的颜色。遮罩、噪声和 RG 位移图保持默认无颜色空间,因为 0.5 等数据值必须按原值读取。若误把遮罩标记成 sRGB,灰色 0.5 在 Shader 中会被解码为更低的线性值,混合区域会偏向 A。
片元 Shader 在线性空间混合颜色,最后由 Three.js 的 colorspace_fragment 转换到画布输出。若颜色看起来过亮、过暗或偏色,先核对纹理的 colorSpace、Shader 是否重复手动做了 gamma 变换,再检查浏览器和显示设置。纹理题使用两个原创抽象图案来减少许可证与素材路径问题;替换为项目图片时要确认来源与使用许可。
遮罩、溶解、位移和多次采样
溶解题将噪声值 n 与进度 uProgress 比较:n < progress 的片元透明,n >= progress 的片元保留。阈值附近再用 smoothstep 形成暖色亮边。这里选择透明度以便看见场景背景;也可以用 discard,但它会改变深度、混合及某些 GPU 的性能特征。进度题使用滑块,便于停在边界处观察。
位移图的红绿通道分别表示 X/Y 偏移。图片的 RG 原始范围是 0–1,所以要先减去 0.5 变成正负方向,再乘强度并加到 vUv 上。强度过大时,采样会跑到原图外;当前练习启用了 RepeatWrapping,因此会从对侧重复。若做单张照片的折射,更常用 clamp(sampleUv,0.0,1.0) 或边界淡出。
双图切换由按钮更新 JS 中的目标值,渲染循环按 dt 推进 uProgress;Shader 只做 mix。RGB 故障题对同一纹理采样三次,分别拿 R/G/B,制造通道错位;这比一次采样成本更高,应在目标设备测量。万花筒题用 atan、length 转成极坐标,按扇区取模并镜像折叠,再回到 UV 采样。正方形预览避免扇区在屏幕上被拉成椭圆。
资源与性能实践
- 纹理加载属于异步操作。必须等 PNG 加载成功再创建材质;否则黑屏时很难区分网络错误和 Shader 错误。练习页面会显示启动失败信息,控制台保留原错误。
- 纹理尺寸影响显存与上传时间。示例统一用 256×256 PNG;真实项目按实际显示尺寸、压缩格式和 mipmap 需求选择,不盲目放大。远处或缩小显示时,mipmap 可以降低闪烁。
- 一个片元采样几次,成本通常比写了几行 GLSL 更重要。混合两张图至少两次采样,RGB 故障三次采样;需要大面积全屏运行时,应在低端设备和目标分辨率测量。
- 不要在每帧创建
TextureLoader或新纹理。公共运行时在页面退出时释放挂在材质 uniform 上的纹理;若多个材质共享纹理,真实项目应有统一所有权,避免过早释放。
排查顺序
黑屏先看 Network:PNG 和两个 Shader 文件是否为 200;再看 Shader 编译日志和 sampler uniform 名称。四格重复没出现时检查 RepeatWrapping 与 vUv*2.0;滚动有接缝时检查图片边缘内容,而不只是 wrapping 设置;遮罩反相时输出 vec3(mask) 看灰度;位移过强时先把 uStrength 设为 0 验证原图能显示。
练习
| 难度 | 题目 | 可见验收 |
|---|---|---|
| ⭐ | 采样并显示本地纹理 | 暖色图案完整显示 |
| ⭐ | UV 放大两倍重复四格 | 2×2 重复 |
| ⭐⭐ | 两张纹理的水平渐变混合 | 左 A 右 B |
| ⭐⭐ | 黑白纹理遮罩混合 | 遮罩决定混合 |
| ⭐⭐ | UV 滚动的无缝背景 | 连续循环滚动 |
| ⭐⭐⭐ | 噪声纹理溶解与亮边 | 进度可调,有暖色边 |
| ⭐⭐⭐ | 位移纹理扰动采样 UV | 水面般扭曲 |
| ⭐⭐⭐ | 点击切换双层纹理 | A/B 平滑往返 |
| ⭐⭐⭐⭐ | RGB 通道错位故障艺术 | 随机彩边横带 |
| ⭐⭐⭐⭐ | 极坐标镜像万花筒 | 扇区数可调的镜像图案 |