three.js AnaglyphEffect 红绿分色3D渲染实战:API 参数、完整示例与离轴立体投影原理
2026/9/7 3:35:55 网站建设 项目流程

three.js AnaglyphEffect 红绿分色3D渲染实战:API 参数、完整示例与离轴立体投影原理

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

本文围绕 three.js 官方文档 AnaglyphEffect 展开,讲清楚这个红绿分色(Anaglyph)3D 效果类的全部 API——构造函数、eyeSep/planeDistance参数、render/setSize/dispose方法——并给出官方示例 webgl_effects_anaglyph.html 的完整可运行代码。读完并对照源码 examples/jsm/effects/AnaglyphEffect.js 后,你能在不依赖 XR 设备的情况下,让普通显示器呈现具有真实深度感的立体画面,并理解其背后"物理正确的离轴立体投影"(off-axis stereo projection)是如何通过frameCorners()实现的。

一、AnaglyphEffect 是什么,适用什么渲染器

Anaglyph(红绿分色)是一种经典立体视觉方案:用红色滤过左眼图像、青色滤过右眼图像,两张图叠加后,佩戴红青 3D 眼镜的人眼会分别只看到一侧的画面,大脑融合后产生深度感。

three.js 的AnaglyphEffect类文档定义为:"A class that creates an anaglyph effect using physically-correct off-axis stereo projection"(使用物理正确的离轴立体投影创建红绿分色效果的类)。它有两个关键特性:

  • 离轴投影而非平行投影:双眼相机各自偏转、对准同一个虚拟屏幕平面,避免传统平行投影(criss-cross/parallel)导致的梯形失真与视差不适;
  • 零视差平面(Zero Parallax Plane):在指定距离处放置虚拟屏幕,位于该距离的物体看起来恰好"贴在屏幕表面",更近的物体凸出屏幕,更远的物体凹入屏幕。

渲染器限制(文档明确要求)AnaglyphEffect只能与WebGLRenderer配合使用。若项目使用WebGPURenderer,请改用节点式实现 AnaglyphPassNode(下文第七节给出对照说明)。

二、导入与构造

AnaglyphEffect是一个 addon(附加组件),必须显式导入,不能通过import * as THREE from 'three'直接获得:

import { AnaglyphEffect } from 'three/addons/effects/AnaglyphEffect.js';

构造函数

new AnaglyphEffect( renderer, width = 512, height = 512 )
参数类型说明默认值
rendererWebGLRenderer渲染器实例,效果内部所有双通道渲染都通过它完成必填
widthnumber效果的宽度,物理像素(physical pixels)512
heightnumber效果的高度,物理像素(physical pixels)512

注意构造函数接收的是物理像素,而setSize()接收的是逻辑像素(源码内部会乘以renderer.getPixelRatio()),两者单位不要混淆。默认 512×512 只是一个保底值,实际项目中几乎总是紧接着调用setSize()覆盖。

构造时还会初始化三组 GPU 资源,可以从源码 AnaglyphEffect.js#L51-L157 看到:

  • 两张离屏渲染目标_renderTargetL/_renderTargetRWebGLRenderTargetRGBAFormat,minFilter 为LinearFilter、magFilter 为NearestFilter),分别缓存左眼、右眼渲染结果;
  • 一组 Dubois 红青色度矩阵(见第五节);
  • 一个FullScreenQuad合成四边形,挂载负责左右眼画面红青混合的ShaderMaterial

三、核心属性:eyeSep 与 planeDistance

这两个属性决定了立体效果的"生理参数"和"深度基准",是调参的主要入口。

.eyeSep : number(默认0.064

瞳距(interpupillary distance,IPD),即双眼分开的距离,单位为世界单位。典型人类瞳距约 0.064 米(64mm),源码注释见 AnaglyphEffect.js#L67-L74。

调整方式取决于场景的比例尺:如果你的场景以"米"为单位建模,直接保留0.064;如果场景比例不同(例如 1 个单位 = 1 厘米),应改为6.4。瞳距过大或过小都会改变物体的相对深度强度——瞳距越大,视差越强,立体感越夸张但也越容易视觉疲劳。

.planeDistance : number(默认0.5

观察者到"虚拟屏幕平面"的距离(世界单位),即零视差发生的位置。文档对其行为的描述是:

Objects at this distance appear at the screen surface. Objects closer appear in front of the screen (negative parallax). Objects further appear behind the screen (positive parallax).

The screen dimensions are derived from the camera's FOV and aspect ratio at this distance, ensuring the stereo view matches the camera's field of view.

翻译过来:

  • 位于该距离的物体 → 看起来贴在屏幕表面(零视差);
  • 更近的物体 → 凸出屏幕前方(负视差);
  • 更远的物体 → 凹入屏幕后方(正视差)。

同时,虚拟屏幕的宽高会由相机 FOV 与长宽比在该距离上推导出来(源码中halfHeight = planeDistance * tan(fov/2)halfWidth = halfHeight * aspect,见 AnaglyphEffect.js#L206-L207),保证立体视场的视场角与你原本的相机完全一致,不会因开启立体效果而"放大"或"缩小"画面。

调参经验:官方示例中把planeDistance设为3,正好等于相机到场景中心的距离,让画面中心区域落在屏幕深度上(示例注释:"Match camera distance to origin for zero parallax at scene center",见 webgl_effects_anaglyph.html#L101-L105)。一个实用的做法是:把planeDistance设为你想让画面"看起来平"的那一层内容到相机的距离。

四、完整可运行示例

以下代码整理自官方示例 webgl_effects_anaglyph.html,用 import map 方式引用本地构建产物,可直接放入项目使用:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no"> </head> <body> <script type="importmap"> { "imports": { "three": "../build/three.module.js", "three/addons/": "./jsm/" } } </script> <script type="module"> import * as THREE from 'three'; import { AnaglyphEffect } from 'three/addons/effects/AnaglyphEffect.js'; let container, camera, scene, renderer, effect; const spheres = []; let mouseX = 0, mouseY = 0; let windowHalfX = window.innerWidth / 2; let windowHalfY = window.innerHeight / 2; document.addEventListener( 'mousemove', ( event ) => { mouseX = ( event.clientX - windowHalfX ) / 100; mouseY = ( event.clientY - windowHalfY ) / 100; } ); function init() { container = document.createElement( 'div' ); document.body.appendChild( container ); // 透视相机:FOV 60,近裁剪面 0.01,相机位于 z=3 camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.01, 100 ); camera.position.z = 3; scene = new THREE.Scene(); // 500 个球体用于展示立体深度 const geometry = new THREE.SphereGeometry( 0.1, 32, 16 ); const material = new THREE.MeshBasicMaterial( { color: 0xffffff } ); for ( let i = 0; i < 500; i ++ ) { const mesh = new THREE.Mesh( geometry, material ); mesh.position.set( Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5 ); const s = Math.random() * 3 + 1; mesh.scale.setScalar( s ); scene.add( mesh ); spheres.push( mesh ); } renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setAnimationLoop( animate ); container.appendChild( renderer.domElement ); // 1) 构造效果:只传 renderer,尺寸随后由 setSize 指定 const width = window.innerWidth || 2; const height = window.innerHeight || 2; effect = new AnaglyphEffect( renderer ); effect.setSize( width, height ); // 2) 配置立体参数 effect.eyeSep = 0.064; // 瞳距 64mm(场景以米为单位) effect.planeDistance = 3; // 与相机到场景中心的距离一致 → 场景中心零视差 window.addEventListener( 'resize', onWindowResize ); } function onWindowResize() { windowHalfX = window.innerWidth / 2; windowHalfY = window.innerHeight / 2; camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); // 3) 窗口变化时同步调整效果尺寸 effect.setSize( window.innerWidth, window.innerHeight ); } function animate() { render(); } function render() { const timer = 0.0001 * Date.now(); camera.position.x += ( mouseX - camera.position.x ) * .05; camera.position.y += ( - mouseY - camera.position.y ) * .05; camera.lookAt( scene.position ); for ( let i = 0; i < spheres.length; i ++ ) { spheres[ i ].position.x = 5 * Math.cos( timer + i ); spheres[ i ].position.y = 5 * Math.sin( timer + i * 1.1 ); } // 4) 关键:用 effect.render 替代 renderer.render effect.render( scene, camera ); } init(); </script> </body> </html>

四个要点回顾:

  1. new AnaglyphEffect( renderer )构造;
  2. effect.setSize( width, height )指定逻辑像素尺寸;
  3. 按场景比例设置eyeSepplaneDistance
  4. 渲染循环中调用effect.render( scene, camera )不是renderer.render( scene, camera )——这是文档对render方法的明确定义:"this method should be called instead of the default WebGLRenderer#render"。

五、源码级原理:一次 effect.render 内部发生了什么

render( scene, camera )的完整实现在 AnaglyphEffect.js#L183-L254,可以拆成四步:

1. 从相机世界矩阵提取坐标轴并计算双眼位置

camera.matrixWorld.extractBasis( _right, _up, _forward ); const halfSep = this.eyeSep / 2; _eyeL.copy( camera.position ).addScaledVector( _right, - halfSep ); // 左眼 _eyeR.copy( camera.position ).addScaledVector( _right, halfSep ); // 右眼

注意眼睛的偏移方向来自相机的世界坐标右轴而非全局 X 轴——即使相机翻转、倾斜,左右眼的相对位置依然正确。

2. 推导虚拟屏幕四角,用 frameCorners 构建离轴投影

_screenCenter.copy( camera.position ).addScaledVector( _forward, - this.planeDistance ); const halfHeight = this.planeDistance * Math.tan( MathUtils.DEG2RAD * camera.fov / 2 ); const halfWidth = halfHeight * camera.aspect; // …由 _screenCenter ± halfWidth*_right ± halfHeight*_up 算出屏幕三个角点 frameCorners( _cameraL, _screenBottomLeft, _screenBottomRight, _screenTopLeft, true );

frameCorners来自 CameraUtils.js#L34-L80,它是整套立体方案的核心:

  • 以屏幕三corner点为基准,构造离轴投影矩阵(off-axis frustum)——projectionMatrix中第三、六列(r+l)/(r-l)(t+b)/(t-b)的偏移项使左眼相机的视锥中心指向屏幕、右眼相机的视锥中心指向同一点,两个视锥精确共面(shared plane),这正是"物理正确"的来源:屏幕上任意一点对双眼而言都在同一条视线交点上,从而保证零视差平面上没有视差误差
  • 同时把相机四元数旋转到使焦平面贴合屏幕平面;
  • 最后一个参数estimateViewFrustum = true会为相机估算一个保守的 FOV,"to make frustum tall/wide enough to encompass it"——用于修正离轴视锥下的视锥剔除(frustum culling),避免画面边缘物体被错误剔除。

调用之后,效果内部手动composematrixWorld并求逆matrixWorldInverse(AnaglyphEffect.js#L227-L236),因为离轴矩阵已经手工写入projectionMatrix,绕过了常规的updateProjectionMatrix()流程——frameCorners的注释也明确提醒:"do not call updateProjectionMatrix() after this"。

3. 双通道渲染

左右眼各渲染一次完整场景到独立渲染目标:

renderer.setRenderTarget( _renderTargetL ); renderer.clear(); renderer.render( scene, _cameraL ); renderer.setRenderTarget( _renderTargetR ); renderer.clear(); renderer.render( scene, _cameraR );

因此开启 AnaglyphEffect 后每帧的渲染负载约为原来的 2 倍再加一次全屏合成,这是性能上必须知晓的代价(对大型场景可考虑降低离屏目标分辨率)。

4. 红青色度矩阵合成

合成着色器从两张离屏纹理采样后执行:

vec3 color = clamp( colorMatrixLeft * colorL.rgb + colorMatrixRight * colorR.rgb, 0., 1. ); gl_FragColor = vec4( color, max( colorL.a, colorR.a ) ); #include <tonemapping_fragment> #include <colorspace_fragment>

所用的色度矩阵是 Dubois 最小二乘优化的红青矩阵(源码注释标注了出处,见 AnaglyphEffect.js#L53-L65):

// 左眼:[ 0.4561, -0.0400822, -0.0152161; // 0.500484, -0.0378246, -0.0205971; // 0.176381, -0.0157589, -0.00546856 ] // 右眼:[ -0.0434706, 0.378476, -0.0721527; // -0.0879388, 0.73364, -0.112961; // -0.00155529, -0.0184503, 1.2264 ]

相比最朴素的"左眼只留红、右眼只留青绿蓝"做法,Dubois 矩阵通过跨通道混合在色度上做了全局最小二乘拟合,能保留更多原场景颜色信息、减少鬼影(retinal rivalry)。合成末尾的两个#include保证合成结果同样经过 tone mapping 与色彩空间转换,与主渲染管线保持一致。

六、setSize 与 dispose:生命周期管理

.setSize( width, height )

setSize( width, height ) // 逻辑像素

源码实现(AnaglyphEffect.js#L165-L174)做了三件事:

renderer.setSize( width, height ); const pixelRatio = renderer.getPixelRatio(); _renderTargetL.setSize( width * pixelRatio, height * pixelRatio ); _renderTargetR.setSize( width * pixelRatio, height * pixelRatio );

即:同步调整画布尺寸,并按当前pixelRatio把左右眼离屏目标放大到对应物理分辨率。窗口 resize 时必须调用它(同时记得更新camera.aspectcamera.updateProjectionMatrix()),否则屏幕尺寸与立体视场的长宽比推导会不一致,画面会拉伸或裁剪错误。

.dispose()

effect.dispose();

释放两张WebGLRenderTarget、合成ShaderMaterialFullScreenQuad(AnaglyphEffect.js#L260-L268)。在 SPA 页面切换、场景销毁或热重载时调用,避免 GPU 资源泄漏。

七、WebGPU 对照:AnaglyphPassNode

文档明确指出:使用WebGPURenderer时应改用 AnaglyphPassNode。两者共享同一套frameCorners离轴投影算法(updateStereoCamera中的屏幕四角推导与 WebGL 版逐行对应,见 AnaglyphPassNode.js#L447-L502),但节点版把合成逻辑搬进 TSL(three Shadertoy Language)节点管线,且提供了远比AnaglyphEffect更丰富的色度算法:

import { anaglyphPass, AnaglyphAlgorithm, AnaglyphColorMode } from 'three/addons/tsl/display/AnaglyphPassNode.js'; const pass = anaglyphPass( scene, camera ); pass.algorithm = AnaglyphAlgorithm.DUBOIS; // 默认即 dubois pass.colorMode = AnaglyphColorMode.RED_CYAN; // 默认 redCyan pass.eyeSep = 0.064; pass.planeDistance = 3;

其中AnaglyphAlgorithm提供TRUEGREYCOLOURHALF_COLOURDUBOISOPTIMISEDCOMPROMISE七种分色算法,AnaglyphColorMode提供RED_CYANMAGENTA_CYANMAGENTA_GREEN三种配色(矩阵表完整定义见 AnaglyphPassNode.js#L114-L270,注释中各算法的来源论文也一一标注)。AnaglyphEffect则固定使用 Dubois 红青矩阵,不可切换——如果你的产品需要"灰度模式减少眩晕"或"Magenta/Cyan 配色",WebGPU 管线是更灵活的选择。

八、使用要点小结

要点说明
渲染器仅限WebGLRendererWebGPURendererAnaglyphPassNode
渲染调用effect.render( scene, camera )替代renderer.render(...)
eyeSep世界单位瞳距,默认0.064(对应米制场景 64mm);按场景比例尺换算
planeDistance默认0.5;建议设为"希望呈现为平面"的内容层到相机的距离,官方示例取相机到场景中心的3
尺寸构造参数是物理像素;setSize是逻辑像素,内部按pixelRatio换算;窗口 resize 必须同步更新相机 aspect 与setSize
性能每帧 = 左眼全场景 + 右眼全场景 + 全屏合成,约为 2 倍场景渲染开销
资源场景销毁时调用effect.dispose()释放离屏目标与合成材质

通过AnaglyphEffect,three.js 把一个需要自己处理双相机、离轴矩阵与色度矩阵的问题收敛成了三行代码(构造、setSizerender),而frameCorners带来的零视差平面保证,使它在普通显示器上也能得到几何上正确的红青立体效果;需要更多分色算法或 WebGPU 管线时,切换到AnaglyphPassNode即可无缝复用同一套立体相机算法。

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询