three.js MirrorShader 详解:用一行像素镜像实现画面水平/垂直翻转变换
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
本篇文章基于 three.js 官方文档的 MirrorShader 页面(docs/pages/module-MirrorShader.html.md),结合仓库内的 着色器源码 与后处理管线源码,系统讲解如何用MirrorShader将画面的一半镜像复制到另一半,生成左右对称或上下对称的视觉效果。读完本文,你将掌握该 addon 的导入方式、side参数的四种语义、GLSL 实现原理,以及如何在EffectComposer后处理链中通过ShaderPass直接落地使用。
MirrorShader 是什么
MirrorShader是 three.js 官方提供的一个内置后处理着色器对象(addon),其核心功能正如文档所述:将输入的图像(通常是一整帧渲染结果)按指定方向做半幅镜像复制——把画面的一侧(左/右/上/下)作为源,经镜像翻转后覆盖另一侧,最终得到一幅"左右对称"或"上下对称"的输出。
它属于ShaderMaterial~Shader类型的常量对象,本身不携带任何 scene / mesh 逻辑,只描述一份可直接喂给ShaderMaterial的着色器定义(uniforms + vertexShader + fragmentShader)。因此最常见的用法是挂载到后处理链路上,对整帧颜色缓冲做全屏后处理。
导入方式:addon 必须显式引入
与 three.js 核心库不同,MirrorShader位于examples/jsm目录,属于非默认导出的官方附加模块,需要显式导入:
import { MirrorShader } from 'three/addons/shaders/MirrorShader.js';之所以能通过three/addons/...这一路径访问,是因为仓库中 examples/jsm/Addons.js 集中重新导出了所有附加模块:
export * from './shaders/MirrorShader.js';因此只要构建/打包环境正确解析three/addons别名,即可与其它 addon(如ShaderPass、EffectComposer)一起引用。在仓库自带的示例 HTML 中(例如 webgl_postprocessing.html),这一别名通过 importmap 映射到 addons 目录实现,效果等价。
模块成员:.MirrorShader(inner, constant)
模块对外暴露的成员只有一个:
.MirrorShader : ShaderMaterial~Shader(内部常量)
一个类型为ShaderMaterial~Shader的着色器定义对象。所谓 inner/constant 指它是该模块私有的、只读的常量结构,包含三部分:
name:'MirrorShader'uniforms:tDiffuse(采样输入贴图)与side(镜像方向)- 着色器代码:
vertexShader与fragmentShader
实际源码见 examples/jsm/shaders/MirrorShader.js,其完整结构如下:
const MirrorShader = { name: 'MirrorShader', uniforms: { 'tDiffuse': { value: null }, 'side': { value: 1 } }, vertexShader: /* glsl */` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }`, fragmentShader: /* glsl */` uniform sampler2D tDiffuse; uniform int side; varying vec2 vUv; void main() { vec2 p = vUv; if (side == 0){ if (p.x > 0.5) p.x = 1.0 - p.x; }else if (side == 1){ if (p.x < 0.5) p.x = 1.0 - p.x; }else if (side == 2){ if (p.y < 0.5) p.y = 1.0 - p.y; }else if (side == 3){ if (p.y > 0.5) p.y = 1.0 - p.y; } vec4 color = texture2D(tDiffuse, p); gl_FragColor = color; }` };uniforms 参数详解
源码定义了两个 uniform:
| uniform | 类型 | 默认值 | 说明 |
|---|---|---|---|
tDiffuse | sampler2D | null | 输入颜色贴图。用于后处理时通常指向上一个 pass 的readBuffer纹理,由ShaderPass自动注入 |
side | int | 1 | 指定"取哪一半作为镜像源",取值范围0~3,对应左/右/上/下 |
注意side在 GLSL 中声明为uniform int,因此设置时务必赋整数:
material.uniforms.side.value = 0; // 而不是 0.0side 语义速查表
结合文档说明与 fragmentShader 判断分支,side四种取值的语义如下:
| side | 含义 | 实际采样逻辑 | 效果 |
|---|---|---|---|
0 | left | p.x > 0.5时令p.x = 1.0 - p.x | 取左侧半图,水平镜像后复制到右侧半区,画面水平对称 |
1 | right(默认) | p.x < 0.5时令p.x = 1.0 - p.x | 取右侧半图,水平镜像后复制到左侧半区 |
2 | top | p.y < 0.5时令p.y = 1.0 - p.y | 取上方半图,垂直镜像后复制到下方半区,画面垂直对称 |
3 | bottom | p.y > 0.5时令p.y = 1.0 - p.y | 取下方半图,垂直镜像后复制到上方半区 |
一句话记忆:哪个 side 代表取哪一半的源数据;被保留的另一半是原样输出,源半幅则被翻转后覆盖到对侧。这个"复制一半到另一半"的行为正是文档中对.MirrorShader的那句描述——Copies half the input to the other half。
着色器实现原理逐行解析
顶点点着色器非常简单,仅仅把模型顶点的uv原样传递给片元:
varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }片元着色器才是"镜像"发生的核心位置,其思路是:先改写采样坐标,再做一次普通贴图采样。
uniform sampler2D tDiffuse; uniform int side; varying vec2 vUv; void main() { vec2 p = vUv; if (side == 0){ if (p.x > 0.5) p.x = 1.0 - p.x; // 右半区采样点映射到左半区 }else if (side == 1){ if (p.x < 0.5) p.x = 1.0 - p.x; // 左半区采样点映射到右半区 }else if (side == 2){ if (p.y < 0.5) p.y = 1.0 - p.y; // 下半区采样点映射到上半区 }else if (side == 3){ if (p.y > 0.5) p.y = 1.0 - p.y; // 上半区采样点映射到下半区 } vec4 color = texture2D(tDiffuse, p); gl_FragColor = color; }关键点在于坐标变换p = 1.0 - p:UV 中0和1互为镜像位置。以side = 0为例,对于右半区(p.x > 0.5)的任意像素,其采样横坐标会被换算为1.0 - p.x(落在0 ~ 0.5),等价于以垂直中线为轴水平翻转后采样——于是右半屏展示的是左半屏的镜像拷贝,画面整体呈左右对称。side = 1/2/3只是把同样的几何思想换到另一侧或 Y 轴方向。
由于它采样自tDiffuse,本质上是一个逐像素的"坐标重映射"型全屏效果,不引入额外的几何体、相机或渲染目标,成本极低。
实战:接入 EffectComposer 后处理链
MirrorShader最常见的正确打开方式是通过ShaderPass加入后处理管线。这里给出一个可直接套用的完整示例(参照仓库 webgl_postprocessing.html 的组织方式,把其中的RGBShiftShader换成MirrorShader即可):
import * as THREE from 'three'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; import { OutputPass } from 'three/addons/postprocessing/OutputPass.js'; import { MirrorShader } from 'three/addons/shaders/MirrorShader.js'; // ...场景与渲染器初始化省略,例如: // const renderer = new THREE.WebGLRenderer(); // const scene = ..., camera = ..., composer = new EffectComposer( renderer ); // 1. 先渲染主场景到 composer composer.addPass( new RenderPass( scene, camera ) ); // 2. 追加 MirrorShader 后处理 pass(默认 textID = 'tDiffuse',与着色器 uniform 匹配) const mirrorPass = new ShaderPass( MirrorShader ); // 3. 动态切换镜像方向:0/1/2/3 mirrorPass.uniforms.side.value = 0; // 左侧镜像复制到右侧 composer.addPass( mirrorPass ); // 4. 末尾接 OutputPass 完成色调空间与颜色编码输出 composer.addPass( new OutputPass() ); // ...render 循环中调用 composer.render() 而非 renderer.render()为什么能直接用?
ShaderPass 的构造函数会把传入的 shader 对象复制 uniforms 并包装为ShaderMaterial;其构造参数textureID默认值为'tDiffuse'(见 examples/jsm/postprocessing/ShaderPass.js)。随后在每次render()时自动执行:
this.uniforms[ this.textureID ].value = readBuffer.texture;把上一 pass 的渲染结果写入tDiffuse,从而满足MirrorShader的输入需求。这就是为什么着色器对象上必须声明名为tDiffuse的sampler2Duniform。若你的目标纹理不同,也可改用它法:直接实例化ShaderMaterial并手动管理tDiffuse,再自行渲染一个全屏四边形,MirrorShader的对象结构(uniforms + 两个 shader 字符串)完全兼容ShaderMaterial的常规构造方式。
注意区分:与 objects/Water.js 中同名 shader 不是一回事
在仓库中搜索MirrorShader还会命中 examples/jsm/objects/Water.js,其中同样出现了一个name: 'MirrorShader'的内部 shader 对象。但这只是名字巧合,二者完全无关:
- examples/jsm/shaders/MirrorShader.js 是本文讲解的独立后处理工具,只含
tDiffuse/side两个 uniform,用于生成对称画面; - Water.js 中的同名 shader 服务于水面物体,其 uniforms 包括
mirrorSampler、normalSampler、textureMatrix、time等一整套水面反射/折射参数,属于Water类的内部实现细节,并不会从MirrorShader.js模块导入。
阅读代码或搜索 API 时留意这一命名重合,可避免误判调用链。
小结与扩展方向
围绕文档中的一句话——Copies half the input to the other half——本文把MirrorShader的能力边界讲透了:这是一个用于后处理阶段、对整帧或任意tDiffuse纹理做四方向半幅镜像复制的轻量着色器对象。其side参数(0=左、1=右、2=上、3=下,默认 1)直接决定源半幅与镜像方向,实现上等价于对 UV 坐标的简单映射,性能开销可忽略。
基于它你可以继续扩展:
- 叠加时间变量或噪声 uniform,让对称的接缝处产生"万花筒"式动态扰动;
- 将
tDiffuse从帧缓冲替换为任意纹理贴图,实现 UV 对称平铺的采样工具; - 参照仓库内其它 addon 的写法(例如 CopyShader,同样仅含
tDiffuse),把它改写为带自定义 mixing 权重的对称混合效果。
更多示例与 API 组织可参考 Addons.js、ShaderPass 以及仓库 examples 目录下的后处理演示页,结合 源码 自行调试side的四种取值即可直观看到对称效果的变化。
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考