diff --git a/manual/list.json b/manual/list.json index 6bcd490504e223..5a24759f8584bd 100644 --- a/manual/list.json +++ b/manual/list.json @@ -339,6 +339,15 @@ "相关资源": "zh/useful-links", "WebGL兼容性检查": "zh/webgl-compatibility-check" }, + "进阶": { + "动画系统": "zh/animation-system", + "颜色管理": "zh/color-management", + "如何创建VR内容": "zh/how-to-create-vr-content", + "如何释放对象": "zh/how-to-dispose-of-objects", + "如何更新对象": "zh/how-to-update-things", + "如何使用后处理": "zh/how-to-use-post-processing", + "矩阵变换": "zh/matrix-transformations" + }, "---": {}, "基本": { "基础": "zh/fundamentals", @@ -356,7 +365,8 @@ "阴影": "zh/shadows", "雾": "zh/fog", "渲染目标": "zh/rendertargets", - "自定义缓冲几何体": "zh/custom-buffergeometry" + "自定义缓冲几何体": "zh/custom-buffergeometry", + "物理": "zh/physics" }, "技巧": { "按需渲染": "zh/rendering-on-demand", @@ -390,6 +400,10 @@ "体素几何体 (Minecraft)": "zh/voxel-geometry", "来试试做一个游戏吧": "zh/game" }, + "WebGPU": { + "WebGPU渲染器": "zh/webgpurenderer", + "后处理": "zh/webgpu-postprocessing" + }, "WebXR": { "VR - 基础": "zh/webxr-basics", "VR - 用目光进行选择": "zh/webxr-look-to-select", diff --git a/manual/zh/animation-system.html b/manual/zh/animation-system.html new file mode 100644 index 00000000000000..7cf9bb4555da6a --- /dev/null +++ b/manual/zh/animation-system.html @@ -0,0 +1,169 @@ + + + 动画系统 + + + + + + + + + + + + + + +
+
+

动画系统

+
+
+
+ +

概述

+ +

+ 在 three.js 的动画系统中,你可以为模型的多种属性制作动画: + 例如蒙皮绑定模型的骨骼、形态目标(morph targets)、不同材质属性 + (颜色、不透明度、布尔值)、可见性与变换。动画属性可以淡入、 + 淡出、交叉淡化(crossfade)和变速。即使是同一对象上的多个动画, + 或不同对象上的多个动画,也可以独立调整权重和时间缩放, + 并进行同步。

+ + 为了在一个统一系统中实现这些功能,three.js 动画系统在 + 2015 年[link:https://github.com/mrdoob/three.js/issues/6881 发生了彻底重构] + (注意甄别过时资料)。当前架构与 Unity / Unreal Engine 4 + 更接近。本页将简要介绍该系统的核心组件,以及它们如何协同工作。 + +

+ +

动画片段(Animation Clips)

+ +

+ + 当你成功导入一个带动画的 3D 对象后(无论它使用骨骼、形态目标,或两者兼有), + 比如通过 [link:https://github.com/KhronosGroup/glTF-Blender-IO glTF Blender 导出器] + 从 Blender 导出,再使用 `GLTFLoader` 加载到 three.js 场景中, + 返回结果中通常会有一个名为 "animations" 的数组字段, + 其中包含该模型的动画片段(下文会列出支持此能力的加载器)。

+ + 每个 `AnimationClip` 一般表示对象的一种动作数据。以角色模型为例, + 可以有一个片段表示走路,第二个表示跳跃,第三个表示侧移,等等。 + +

+ +

关键帧轨道(Keyframe Tracks)

+ +

+ + 在 `AnimationClip` 内部,每个被动画驱动的属性都会存储在独立的 + `KeyframeTrack` 中。假设角色有骨架,那么一条轨道可以记录前臂骨骼 + 随时间变化的位置数据,另一条记录同一骨骼的旋转变化,第三条记录 + 其他骨骼的位置、旋转或缩放,依此类推。也就是说, + 一个 AnimationClip 通常由大量此类轨道组成。

+ + 如果模型有形态目标(比如一个表示微笑,另一个表示愤怒), + 每条相关轨道会描述某个形态目标在该片段播放过程中, + 其影响权重如何随时间变化。 + +

+ +

动画混合器(Animation Mixer)

+ +

+ + 这些存储的数据只是动画基础,真正的播放控制由 `AnimationMixer` 完成。 + 你可以把它理解成不只是一个“播放器”,更像一台真实的混音台: + 能够同时控制多个动画,并对它们进行混合与融合。 + +

+ +

动画动作(Animation Actions)

+ +

+ + `AnimationMixer` 本身只有少量通用属性和方法, + 因为它主要通过动画动作来驱动。通过配置 `AnimationAction`, + 你可以决定某个 `AnimationClip` 在某个 mixer 上何时播放、暂停或停止, + 是否循环、循环次数、是否淡入淡出、是否进行时间缩放, + 以及更多高级控制(如交叉淡化与同步)。 + +

+ +

动画对象组(Animation Object Groups)

+ +

+ + 如果你希望一组对象共享同一套动画状态, + 可以使用 `AnimationObjectGroup`。 + +

+ +

支持的格式与加载器

+ +

+ 请注意,并非所有模型格式都包含动画(例如 OBJ 就不包含), + 而且只有部分 three.js 加载器支持 `AnimationClip` 序列。 + 下面这些加载器支持这种动画数据: +

+ +
    +
  • THREE.ObjectLoader
  • +
  • THREE.BVHLoader
  • +
  • THREE.ColladaLoader
  • +
  • THREE.FBXLoader
  • +
  • THREE.GLTFLoader
  • +
+ +

+ 另外,3ds Max 和 Maya 目前还不能直接将多个动画 + (即不在同一时间轴上的动画)导出到同一个文件中。 +

+ +

示例

+ +
+let mesh;
+
+// 创建 AnimationMixer,并获取 AnimationClip 列表
+const mixer = new THREE.AnimationMixer( mesh );
+const clips = mesh.animations;
+
+// 每帧更新 mixer
+function update () {
+  mixer.update( deltaSeconds );
+}
+
+// 播放指定动画
+const clip = THREE.AnimationClip.findByName( clips, 'dance' );
+const action = mixer.clipAction( clip );
+action.play();
+
+// 播放全部动画
+clips.forEach( function ( clip ) {
+  mixer.clipAction( clip ).play();
+} );
+
+ +
+
+
+ + + + + + + + + + + diff --git a/manual/zh/color-management.html b/manual/zh/color-management.html new file mode 100644 index 00000000000000..76e2471d37cee7 --- /dev/null +++ b/manual/zh/color-management.html @@ -0,0 +1,351 @@ + + + 颜色管理 + + + + + + + + + + + + + + + +
+
+

颜色管理

+
+
+
+ +

什么是色彩空间?

+ +

+ 每一种色彩空间,都是一组经过权衡的设计选择。它们共同目标是: + 在满足精度和显示技术限制的前提下,覆盖尽可能大的颜色范围。 + 在创建 3D 资源或把多个 3D 资源组装进同一场景时, + 理解这些属性及其在不同色彩空间之间的关系非常重要。 +

+ +
+ +
+ 参考 CIE 1931 色度图中的 sRGB 颜色与白点(D65)。 + 彩色区域是 sRGB 色域(三维体积)在二维中的投影。 + 来源:Wikipedia +
+
+ +
    +
  • + 色彩原色(Color primaries):原色(如红、绿、蓝)并非绝对值; + 它们是在有限精度与显示设备能力约束下,从可见光谱中选定的。 + 颜色由各原色的比例来表达。 +
  • +
  • + 白点(White point):大多数色彩空间都会定义: + 当原色满足 R = G = B 时呈现“无色(中性)”。 + 白、灰等中性色的视觉效果依赖人眼感知,而感知又与观察环境相关。 + 因此色彩空间会指定一个“白点”以统一基准。 + sRGB 的白点是 [link:https://en.wikipedia.org/wiki/Illuminant_D65 D65]。 +
  • +
  • + 传递函数(Transfer functions):在确定色域和颜色模型后, + 还要定义数值与颜色空间之间的映射(传递函数)。 + r = 0.5 是表示物理光照比 r = 1.0 少 50%, + 还是表示人眼感知亮度少 50%?两者并不等价, + 差异由数学函数来描述。根据目标不同,传递函数可以是 + 线性非线性。sRGB 使用非线性传递函数。 + 这些函数有时会近似为伽马函数,但“gamma”一词在这里含义模糊, + 应尽量避免混用。 +
  • +
+ + 以上三个参数(原色、白点、传递函数)共同定义了一个色彩空间。 + 在此基础上,再补充几个术语会更清晰: + +
    +
  • + 颜色模型(Color model):在既定色域中用数字描述颜色的方式, + 可理解为颜色坐标系。在 three.js 里我们主要使用 RGB 模型, + 坐标为 r, g, b ∈ [0,1](闭区间)或 + r, g, b ∈ [0,+∞)(开域),每一项都表示某个原色所占比例。 + 其他模型(HSL、Lab、LCH)常用于美术调色。 +
  • +
  • + 色域(Color gamut):当原色与白点确定后,就确定了可见光谱中的一个体积范围, + 这就是“色域”。不在这个体积内的颜色(超出色域)无法用闭区间 [0,1] 的 RGB 表达。 + 在开域 [0,+∞) 中,色域在数学上可以视为无限。 +
  • +
+ +

+ 来看两个最常见的色彩空间:`SRGBColorSpace`(sRGB)与 + `LinearSRGBColorSpace`(Linear-sRGB)。两者原色和白点相同, + 因此色域一致,也都使用 RGB 模型。它们只在传递函数上不同: + Linear-sRGB 相对于物理光强是线性的;sRGB 使用非线性传递函数, + 更接近人眼感知与常见显示设备的响应特性。 +

+ +

+ 这个差异非常关键。光照计算和大多数渲染运算通常必须在线性色彩空间中完成。 + 但线性颜色在图像或帧缓冲中的存储效率较低,且直接显示给人眼时观感不正确。 + 因此,输入纹理与最终输出图像通常会使用非线性的 sRGB 色彩空间。 +

+ +
+

+ ℹ️ 注意:虽然部分现代显示器支持 Display-P3 等更宽色域, + 但 Web 平台图形 API 仍主要基于 sRGB。 + 当前 three.js 应用通常只会使用 sRGB 与 Linear-sRGB。 +

+
+ +

色彩空间在流程中的角色

+ +

+ 现代渲染所需的线性工作流通常会涉及不止一种色彩空间, + 每种色彩空间承担不同职责。线性与非线性色彩空间适用于不同环节, + 如下所示。 +

+ +

输入色彩空间

+ +

+ 传入 three.js 的颜色(来自取色器、纹理、3D 模型等)都带有各自色彩空间。 + 凡是不在 Linear-sRGB 工作色彩空间中的输入,都需要转换; + 纹理也必须正确设置 texture.colorSpace。 + 如果在初始化颜色前启用 THREE.ColorManagement, + 某些转换(如十六进制颜色与 CSS sRGB 颜色)会自动处理: +

+ + +THREE.ColorManagement.enabled = true; + + +

+ THREE.ColorManagement 默认已启用。 +

+ +
    +
  • + 材质、灯光与着色器:其颜色数据中的 RGB 分量存储在线性的 + Linear-sRGB 工作空间中。 +
  • +
  • + 顶点颜色:`BufferAttribute` 中的 RGB 分量也存储在 + Linear-sRGB 工作空间中。 +
  • +
  • + 颜色纹理:包含颜色信息的 PNG/JPEG `Texture` + (如 `.map`、`.emissiveMap`)应使用闭区间 sRGB, + 并标注 texture.colorSpace = SRGBColorSpace。 + OpenEXR 等格式(常用于 `.envMap`、`.lightMap`)则使用 Linear-sRGB, + 标注为 texture.colorSpace = LinearSRGBColorSpace, + 并且可能包含开域 [0,+∞) 的值。 +
  • +
  • + 非颜色纹理:不存储颜色信息的纹理(如 `.normalMap`、`.roughnessMap`) + 没有对应色彩空间,一般使用默认标注 + texture.colorSpace = NoColorSpace。 + 在少数场景下,非颜色数据可能因技术原因采用其他非线性编码。 +
  • +
+ +
+

+ ⚠️ 警告:许多 3D 模型格式并未正确或一致地定义色彩空间信息。 + three.js 虽会尽量处理常见情况,但旧格式仍常出现问题。 + 为获得最佳结果,请优先使用 glTF 2.0(`GLTFLoader`), + 并尽早在在线查看器中验证资源本身是否正确。 +

+
+ +

工作色彩空间

+ +

+ 渲染、插值及许多其他计算,必须在开域的线性工作色彩空间中进行, + 此时 RGB 分量与物理光照强度成比例。在 three.js 中, + 工作色彩空间是 Linear-sRGB。 +

+ +

输出色彩空间

+ +

+ 输出到显示设备、图片或视频时,通常需要将开域 Linear-sRGB + 工作空间转换到目标色彩空间。该转换由 + `WebGLRenderer.outputColorSpace` 定义。 + 使用后处理时,需要 `OutputPass`。 +

+ +
    +
  • + 显示:写入 WebGL 画布并显示的颜色应使用 sRGB。 +
  • +
  • + 图像:写入图像时应使用与格式和用途匹配的色彩空间。 + 完整渲染后保存为 PNG/JPEG 的图片通常使用 sRGB。 + 若图像包含自发光、光照贴图或其他不受 [0,1] 限制的数据, + 通常使用开域 Linear-sRGB,并配合 OpenEXR 等兼容格式。 +
  • +
+ +
+

+ ⚠️ 警告:渲染目标可使用 sRGB 或 Linear-sRGB。 + sRGB 在有限精度下利用率更高:在闭区间内,sRGB 常用 8-bit 即可, + 而 Linear-sRGB 可能需要至少 16-bit(half float)。 + 若后续管线阶段还要求 Linear-sRGB 输入,额外转换会带来一定性能开销。 +

+
+ +

+ 基于 `ShaderMaterial` 和 `RawShaderMaterial` 的自定义材质需要自行实现输出色彩空间转换。 + 对于 `ShaderMaterial`,通常在片元着色器 `main()` 中加入 + `colorspace_fragment` shader chunk 即可。 +

+ +

使用 THREE.Color 实例

+ +

+ 读取或修改 `Color` 的方法默认假设数据已经在 three.js 的工作色彩空间 + (Linear-sRGB)中。RGB 与 HSL 分量都直接对应 `Color` 实例内部数据, + 不会被隐式转换。你可以显式调用 + .convertLinearToSRGB().convertSRGBToLinear() 进行转换。 +

+ +
+// RGB 分量(不发生转换)。
+color.r = color.g = color.b = 0.5;
+console.log( color.r ); // → 0.5
+
+// 手动转换。
+color.r = 0.5;
+color.convertSRGBToLinear();
+console.log( color.r ); // → 0.214041140
+
+ +

+ 当设置 ColorManagement.enabled = true(推荐,且默认开启)后, + 某些转换会自动执行。由于十六进制与 CSS 颜色通常属于 sRGB, + `Color` 在 setter 中会把它们从 sRGB 转为 Linear-sRGB; + 在 getter 返回十六进制或 CSS 值时,则会从 Linear-sRGB 转回 sRGB。 +

+ +
+// 十六进制转换。
+color.setHex( 0x808080 );
+console.log( color.r ); // → 0.214041140
+console.log( color.getHex() ); // → 0x808080
+
+// CSS 颜色转换。
+color.setStyle( 'rgb( 0.5, 0.5, 0.5 )' );
+console.log( color.r ); // → 0.214041140
+
+// 通过 'colorSpace' 参数覆盖默认转换。
+color.setHex( 0x808080, LinearSRGBColorSpace );
+console.log( color.r ); // → 0.5
+console.log( color.getHex( LinearSRGBColorSpace ) ); // → 0x808080
+console.log( color.getHex( SRGBColorSpace ) ); // → 0xBCBCBC
+
+ +

常见错误

+ +

+ 当某个颜色或纹理配置错误时,它看起来会比预期更亮或更暗。 + 当渲染器输出色彩空间配置错误时,整张场景都可能偏暗 + (例如遗漏了到 sRGB 的转换)或偏亮(例如后处理中重复转换到 sRGB)。 + 这类问题通常并非全局线性偏差,单纯增减光照并不能真正解决。 +

+ +

+ 更隐蔽的问题是:当输入和输出色彩空间设置错误时, + 整体亮度看似正常,但颜色会在不同光照下异常变化, + 或明暗层次变得过曝、生硬。两个错误不会相互抵消。 + 务必确保工作色彩空间是线性的(scene referred), + 输出色彩空间是非线性的(display referred)。 +

+ +

延伸阅读

+ + + +
+
+
+ + + + + + + + + + + diff --git a/manual/zh/how-to-create-vr-content.html b/manual/zh/how-to-create-vr-content.html new file mode 100644 index 00000000000000..f7fda8ea363be8 --- /dev/null +++ b/manual/zh/how-to-create-vr-content.html @@ -0,0 +1,109 @@ + + + 如何创建 VR 内容 + + + + + + + + + + + + + + +
+
+

如何创建 VR 内容

+
+
+
+ +

+ 本指南简要介绍如何使用 three.js 构建一个基于 Web 的 VR 应用, + 以及其中最基础的组成部分。 +

+ +

工作流程

+ +

+ 首先,在项目中引入 + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/VRButton.js VRButton.js]。 +

+ +
+import { VRButton } from 'three/addons/webxr/VRButton.js';
+
+ +

+ *VRButton.createButton()* 会做两件重要的事:它会创建一个用于指示 + VR 兼容性的按钮;此外,如果用户点击该按钮,它会发起 VR 会话。 + 你只需要在应用中添加下面这行代码。 +

+ +
+document.body.appendChild( VRButton.createButton( renderer ) );
+
+ +

+ 接下来,需要在 `WebGLRenderer` 实例上启用 XR 渲染。 +

+ +
+renderer.xr.enabled = true;
+
+ +

+ 最后,要调整动画循环。VR 场景中不再使用常见的 + *window.requestAnimationFrame()*,而是使用 `renderer.setAnimationLoop()`。 + 最小示例代码如下: +

+ +
+renderer.setAnimationLoop( function () {
+
+  renderer.render( scene, camera );
+
+} );
+
+ +

下一步

+ +

+ 可以查看官方 WebXR 示例,了解上述流程在实际项目中的用法。

+ + [example:webxr_xr_ballshooter WebXR / XR / ballshooter]
+ [example:webxr_xr_cubes WebXR / XR / cubes]
+ [example:webxr_xr_dragging WebXR / XR / dragging]
+ [example:webxr_xr_paint WebXR / XR / paint]
+ [example:webxr_xr_sculpt WebXR / XR / sculpt]
+ [example:webxr_vr_panorama_depth WebXR / VR / panorama_depth]
+ [example:webxr_vr_panorama WebXR / VR / panorama]
+ [example:webxr_vr_rollercoaster WebXR / VR / rollercoaster]
+ [example:webxr_vr_sandbox WebXR / VR / sandbox]
+ [example:webxr_vr_video WebXR / VR / video] +

+ +
+
+
+ + + + + + + + + + + diff --git a/manual/zh/how-to-dispose-of-objects.html b/manual/zh/how-to-dispose-of-objects.html new file mode 100644 index 00000000000000..24bab4c56c16a3 --- /dev/null +++ b/manual/zh/how-to-dispose-of-objects.html @@ -0,0 +1,184 @@ + + + 如何释放对象 + + + + + + + + + + + + + + +
+
+

如何释放对象

+
+
+
+ +

+ 为了提升性能并避免内存泄漏,及时释放不再使用的对象非常关键。 + 每当你创建一个 *three.js* 实例,都会分配一定内存。同时,*three.js* + 还会为几何体、材质等对象创建渲染所需的 WebGL 资源(如缓冲区和着色器程序)。 + 这些资源不会自动释放,应用必须通过专用 API 主动清理。 + 本文简要说明这些 API 的使用方式,以及哪些对象需要关注。 +

+ +

几何体

+ +

+ 几何体通常由一组顶点属性组成。*three.js* 会为每个属性在内部创建 + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer]。 + 这些资源只有在调用 `BufferGeometry.dispose()` 后才会被删除。 + 当几何体不再使用时,应执行该方法释放相关资源。 +

+ +

材质

+ +

+ 材质决定对象如何被渲染。*three.js* 会根据材质信息构建着色器程序。 + 着色器程序只有在对应材质被释放后才可能删除。出于性能考虑, + *three.js* 会尽量复用已存在的着色器程序,因此只有当相关材质都释放后, + 着色器程序才会真正销毁。释放材质请调用 `Material.dispose()`。 +

+ +

纹理

+ +

+ 释放材质不会影响纹理。纹理需要单独管理,因为一个纹理可能被多个材质共享。 + 每当创建 `Texture`,three.js 会在内部创建 + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]。 + 与缓冲区一样,它只能通过 `Texture.dispose()` 删除。 +

+ +

+ 如果纹理数据源是 `ImageBitmap`,你还需要在应用层调用 + [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap/close ImageBitmap.close]() + 来释放 CPU 侧资源。`Texture.dispose()` 无法自动调用该方法, + 因为 `close()` 后图像位图将不可再用,而引擎无法判断它是否还被其他地方使用。 +

+ +

渲染目标

+ +

+ `WebGLRenderTarget` 不仅会分配 + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture], + 还会分配 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer] + 和 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer] + 来支持自定义渲染输出。这些资源只能通过 `WebGLRenderTarget.dispose()` 释放。 +

+ +

蒙皮网格

+ +

+ 蒙皮网格通过骨架(skeleton)表示骨骼层级。若不再需要某个蒙皮网格, + 可以对其骨架调用 `Skeleton.dispose()` 释放内部资源。 + 注意骨架可能被多个蒙皮网格共享,只有在确认未被其他活动对象使用时再释放。 +

+ +

其他

+ +

+ examples 目录中的其他类(如 controls、后处理 pass)也可能提供 `dispose()`, + 用于移除内部事件监听器或渲染目标。通常建议查看类的 API / 文档, + 只要有 `dispose()`,在清理阶段就应调用。 +

+ +

常见问题

+ +

为什么 *three.js* 不能自动释放对象?

+ +

+ 这是社区经常提出的问题。核心原因是:*three.js* 无法知道用户创建对象 + (如几何体、材质)的生命周期与作用域,这属于应用层职责。 + 例如某材质当前帧没被使用,下一帧仍可能需要。 + 因此当应用确认对象可删除时,必须调用对应 `dispose()` 通知引擎。 +

+ +

把 mesh 从场景移除后,几何体和材质会自动释放吗?

+ +

+ 不会。你需要显式调用 *dispose()* 释放几何体和材质。 + 同时要注意它们可能被多个 3D 对象共享。 +

+ +

*three.js* 能查看缓存对象数量吗?

+ +

+ 可以。查看渲染器的 `renderer.info` 属性即可,它包含显存与渲染流程的统计信息, + 包括当前内部缓存了多少纹理、几何体、着色器程序等。 + 如果应用出现性能问题,调试该属性有助于快速定位内存泄漏。 +

+ +

纹理图像尚未加载完成时调用 `dispose()` 会怎样?

+ +

+ 纹理内部资源只有在图像完全加载后才会分配。 + 若在加载前就调用 `dispose()`,通常不会发生任何事; + 因为资源尚未创建,也就无需清理。 +

+ +

调用 `dispose()` 后又再次使用该对象,会怎样?

+ +

+ 取决于对象类型。对于几何体、材质、纹理、渲染目标和后处理 pass, + 被删除的内部资源通常可以由引擎重新创建,因此不会直接报运行时错误; + 但当前帧可能有性能损耗,尤其在需要重新编译着色器时。 + + controls 和 renderer 属于例外:调用 `dispose()` 后实例不可继续使用, + 需要重新创建。 +

+ +

应用里该如何管理 *three.js* 对象?什么时候该释放?

+ +

+ 没有唯一标准答案,取决于具体业务场景。需要强调的是,并非任何时候都必须立即释放。 + 例如多关卡游戏,切关通常是做统一清理的好时机:遍历旧场景并释放失效的材质、 + 几何体和纹理。就像上文所说,即使误释放了仍会被使用的对象, + 一般也不会立刻报错,最坏情况通常是某一帧性能下降。 +

+ +

为什么遍历场景并释放可达资源后,`renderer.info.memory` 仍显示几何体和纹理?

+ +

+ 某些情况下,Three.js 会创建一些内部使用的纹理和几何体, + 它们无法通过遍历场景图直接访问到,因此也无法在遍历时释放。 + 所以即便做了完整场景清理,`renderer.info.memory` 仍可能显示这些对象。 + 这通常不代表泄漏,它们会在后续“清理-重建”循环中被复用。 + + 常见相关场景包括使用 `material.envMap`、`scene.background`、 + `scene.environment` 等,会触发引擎创建内部资源。 +

+ +

`dispose()` 用法示例

+ +

+ [example:webgl_test_memory WebGL / test / memory]
+ [example:webgl_test_memory2 WebGL / test / memory2]
+

+ +
+
+
+ + + + + + + + + + diff --git a/manual/zh/how-to-update-things.html b/manual/zh/how-to-update-things.html new file mode 100644 index 00000000000000..a12157df5a21e7 --- /dev/null +++ b/manual/zh/how-to-update-things.html @@ -0,0 +1,281 @@ + + + 如何更新对象 + + + + + + + + + + + + + + +
+
+

如何更新对象

+
+
+
+ +
+

默认情况下,只要对象被添加到场景中,就会自动更新其矩阵:

+
+const object = new THREE.Object3D();
+scene.add( object );
+
+ 或者,它是某个已加入场景对象的子对象: +
+const object1 = new THREE.Object3D();
+const object2 = new THREE.Object3D();
+
+object1.add( object2 );
+scene.add( object1 ); // object1 和 object2 都会自动更新它们的矩阵
+
+
+ +

但如果你确定对象是静态的,可以关闭自动更新,仅在需要时手动更新变换矩阵。

+ +
+object.matrixAutoUpdate = false;
+object.updateMatrix();
+
+ +

BufferGeometry

+
+

+ BufferGeometry 把顶点位置、面索引、法线、颜色、UV 以及自定义属性等信息 + 存在属性缓冲中,也就是 + [link:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Typed_arrays 类型化数组]。 + 这种结构通常比旧版 Geometry 更快,但使用起来通常更复杂。 +

+

+ 更新 BufferGeometry 时最重要的一点是:不能调整缓冲区大小 + (开销很大,基本等同于新建几何体),但可以更新缓冲区已有内容。 +

+

+ 这意味着如果你知道某个属性会增长(如顶点数增加), + 必须预先分配足够大的缓冲区来容纳新增数据。 + 同时也意味着 BufferGeometry 必然存在最大容量, + 无法做到无限高效扩展。 +

+

+ 下面以“运行时不断延长的线段”为例: + 先为 500 个顶点分配空间,但起初只绘制 2 个点, + 通过 `BufferGeometry.drawRange` 控制绘制范围。 +

+
+const MAX_POINTS = 500;
+
+// 几何体
+const geometry = new THREE.BufferGeometry();
+
+// 属性
+const positions = new Float32Array( MAX_POINTS * 3 ); // 每个点使用 3 个浮点数(x、y、z)
+geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
+
+// 绘制范围
+const drawCount = 2; // 仅绘制前 2 个点
+geometry.setDrawRange( 0, drawCount );
+
+// 材质
+const material = new THREE.LineBasicMaterial( { color: 0xff0000 } );
+
+// 线段
+const line = new THREE.Line( geometry, material );
+scene.add( line );
+
+

+ 接着按如下方式随机写入线段点位: +

+
+const positionAttribute = line.geometry.getAttribute( 'position' );
+
+let x = 0, y = 0, z = 0;
+
+for ( let i = 0; i < positionAttribute.count; i ++ ) {
+
+    positionAttribute.setXYZ( i, x, y, z );
+
+    x += ( Math.random() - 0.5 ) * 30;
+    y += ( Math.random() - 0.5 ) * 30;
+    z += ( Math.random() - 0.5 ) * 30;
+
+}
+
+

+ 如果要在首次渲染后改变绘制点数量,这样做: +

+
+line.geometry.setDrawRange( 0, newValue );
+
+

+ 如果要在首次渲染后修改位置数据,需要设置 `needsUpdate`: +

+
+positionAttribute.needsUpdate = true; // 首次渲染后修改数据时必须设置
+
+ +

+ 首次渲染后修改位置数据时,通常还需要重新计算包围体, + 以保证视锥体裁剪、辅助器等功能正常工作。 +

+
+line.geometry.computeBoundingBox();
+line.geometry.computeBoundingSphere();
+
+ +

+ [link:https://jsfiddle.net/t4m85pLr/1/ 这个 fiddle 示例] + 展示了一个动画线段,你可以按需改造。 +

+ +

示例

+ +

+ [example:webgl_custom_attributes WebGL / custom / attributes]
+ [example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / custom / attributes / particles] +

+ +
+ +

材质

+
+

所有 uniforms 都可以自由修改(如颜色、纹理、不透明度等),并会在每帧传入着色器。

+ +

GL 状态相关参数也可随时修改(如 depthTest、blending、polygonOffset 等)。

+ +

以下属性在运行时不易修改(尤其材质至少渲染过一次后):

+
    +
  • uniform 的数量与类型
  • +
  • 是否启用以下特性 +
      +
    • texture(纹理)
    • +
    • fog(雾)
    • +
    • vertex colors(顶点色)
    • +
    • morphing(变形)
    • +
    • shadow map(阴影贴图)
    • +
    • alpha test(Alpha 测试)
    • +
    • transparent(透明)
    • +
    +
  • +
+ +

这些变化会触发重建着色器程序,你需要设置:

+ material.needsUpdate = true + +

注意这一步可能较慢并导致帧率抖动或卡顿(尤其在 Windows 上,DirectX 下编译 shader 往往比 OpenGL 更慢)。

+ +

为获得更平滑体验,可以通过“占位值”模拟部分开关效果,比如强度为 0 的灯光、纯白纹理或密度为 0 的雾。

+ +

你可以替换几何体分块所用材质,但无法在运行时改变对象按面材质划分分块的方式。

+ +

如果你需要在运行时切换多套材质配置:

+

若材质/分块数量较少,可提前分块(例如人物:头发/脸/身体/上衣/裤子;汽车:前/侧/顶/玻璃/轮胎/内饰)。

+ +

若数量很大(例如每个面都可能不同),建议改用属性或纹理驱动每面外观,而非大量分块材质。

+ +

示例

+

+ [example:webgl_materials_car WebGL / materials / car]
+ [example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof] +

+
+ + +

纹理

+
+

图像、Canvas、视频和数据纹理若内容有变更,需要设置:

+ + texture.needsUpdate = true; + +

渲染目标会自动更新。

+ +

示例

+

+ [example:webgl_materials_video WebGL / materials / video]
+ [example:webgl_rtt WebGL / rtt] +

+ +
+ +

相机

+
+

相机位置和目标会自动更新。如果你要修改以下参数:

+
    +
  • + fov(视野范围) +
  • +
  • + aspect(宽高比) +
  • +
  • + near(近裁剪面) +
  • +
  • + far(远裁剪面) +
  • +
+

+ 则需要重新计算投影矩阵: +

+
+camera.aspect = window.innerWidth / window.innerHeight;
+camera.updateProjectionMatrix();
+
+
+ +

InstancedMesh

+
+

+ `InstancedMesh` 用于在 `three.js` 中便捷地进行实例化渲染。 + 视锥体裁剪、射线检测等功能依赖最新包围体(包围盒和包围球)。 + 由于 `InstancedMesh` 的工作方式,它具有自己的 `boundingBox` 与 `boundingSphere`, + 会覆盖几何体级别的包围体。 +

+

+ 与几何体类似,只要底层数据变化就应重算包围体。 + 对 `InstancedMesh` 来说,常见场景是通过 `setMatrixAt()` 修改实例变换矩阵后, + 再重算包围体。处理方式与几何体相同。 +

+
+instancedMesh.computeBoundingBox();
+instancedMesh.computeBoundingSphere();
+
+ +
+ +

SkinnedMesh

+
+

+ 在包围体机制上,`SkinnedMesh` 与 `InstancedMesh` 原则相同: + 它拥有自己的 `boundingBox` 与 `boundingSphere`,用于正确包裹动画中的网格。 + 当调用 `computeBoundingBox()` 与 `computeBoundingSphere()` 时, + 会基于当前骨骼变换(即当前动画状态)计算对应包围体。 +

+
+ +
+
+
+ + + + + + + + + + + diff --git a/manual/zh/how-to-use-post-processing.html b/manual/zh/how-to-use-post-processing.html new file mode 100644 index 00000000000000..eef2ed827d5663 --- /dev/null +++ b/manual/zh/how-to-use-post-processing.html @@ -0,0 +1,148 @@ + + + 如何使用后处理 + + + + + + + + + + + + + + +
+
+

如何使用后处理

+
+
+
+ +

+ 许多 three.js 应用会把 3D 对象直接渲染到屏幕上。但在一些场景中, + 你会希望叠加景深(DOF)、Bloom、胶片颗粒、抗锯齿等视觉效果。后处理(Post Processing) + 是实现这些效果的常见方案:先把场景渲染到一个渲染目标(显存中的图像缓冲区), + 再通过一个或多个后处理 pass 对该缓冲区应用滤镜与效果,最后输出到屏幕。 +

+

+ three.js 通过 `EffectComposer` 提供了完整的后处理工作流支持。 +

+ +

工作流程

+ +

+ 第一步是从 examples 目录导入所需模块。本文默认你使用 three.js 官方 + [link:https://www.npmjs.com/package/three npm 包]。在本教程的基础示例中,我们需要以下文件。 +

+ +
+import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
+import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
+import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';
+import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
+
+ +

+ 导入完成后,把 `WebGLRenderer` 实例传入,创建 composer。 +

+ +
+const composer = new EffectComposer( renderer );
+
+ +

+ 使用 composer 后,需要调整动画循环:不再调用 `WebGLRenderer.render()`, + 而改为调用 `EffectComposer.render()`。 +

+ +
+function animate() {
+
+  requestAnimationFrame( animate );
+
+  composer.render();
+
+}
+
+ +

+ 到这里 composer 已准备就绪,可以配置后处理 pass 链。各 pass 按添加顺序依次执行, + 共同决定最终输出结果。在本例中先执行 `RenderPass`,再执行 `GlitchPass`, + 最后执行 `OutputPass`。链路中最后一个启用的 pass 会自动渲染到屏幕。 + 配置如下: +

+ +
+const renderPass = new RenderPass( scene, camera );
+composer.addPass( renderPass );
+
+const glitchPass = new GlitchPass();
+composer.addPass( glitchPass );
+
+const outputPass = new OutputPass();
+composer.addPass( outputPass );
+
+ +

+ `RenderPass` 通常放在链路开头,用于提供场景渲染结果给后续步骤。 + 本例中 `GlitchPass` 会基于图像数据施加故障效果。 + `OutputPass` 一般放在最后,负责 sRGB 色彩空间转换与色调映射(tone mapping)。 + 可参考这个 [link:https://threejs.org/examples/webgl_postprocessing_glitch 在线示例]。 +

+ +

内置 Pass

+ +

+ 引擎提供了大量预置后处理 pass,可在 + [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing postprocessing] + 目录中找到。 +

+ +

自定义 Pass

+ +

+ 如果你要编写自定义后处理着色器并接入 pass 链,可以使用 `ShaderPass`。 + 在导入相关模块和自定义 shader 后,按下述方式添加 pass: +

+ +
+import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
+import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js';
+
+// 稍后在你的初始化流程中
+
+const luminosityPass = new ShaderPass( LuminosityShader );
+composer.addPass( luminosityPass );
+
+ +

+ 仓库中提供的 + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/shaders/CopyShader.js CopyShader] + 是编写自定义 shader 的良好起点。`CopyShader` 仅将 `EffectComposer` 的读缓冲内容 + 复制到写缓冲,不附加任何效果。 +

+ +
+
+
+ + + + + + + + + + + diff --git a/manual/zh/matrix-transformations.html b/manual/zh/matrix-transformations.html new file mode 100644 index 00000000000000..5b9a443ee1efde --- /dev/null +++ b/manual/zh/matrix-transformations.html @@ -0,0 +1,116 @@ + + + 矩阵变换 + + + + + + + + + + + + + + +
+
+

矩阵变换

+
+
+
+ +

+ Three.js 使用 `matrices`(矩阵)来表示 3D 变换:平移(position)、旋转和缩放。 + 每个 `Object3D` 实例都包含一个 `matrix`,用于存储该对象的位置、旋转与缩放。 + 本页介绍如何更新对象的变换。 +

+ +

便捷属性与 `matrixAutoUpdate`

+ +

+ 更新对象变换有两种方式: +

+
    +
  1. + 修改对象的 `position`、`quaternion`、`scale` 属性, + 由 three.js 根据这些属性重新计算矩阵: +
    +object.position.copy( start_position );
    +object.quaternion.copy( quaternion );
    +
    + 默认情况下 `matrixAutoUpdate = true`,矩阵会自动重算。 + 如果对象是静态的,或你希望手动控制重算时机,可将其设为 `false` 以获得更好性能: +
    +object.matrixAutoUpdate = false;
    +
    + 修改属性后,再手动更新矩阵: +
    +object.updateMatrix();
    +
    +
  2. +
  3. + 直接修改对象矩阵。`Matrix4` 提供了多种矩阵修改方法: +
    +object.matrix.setRotationFromQuaternion( quaternion );
    +object.matrix.setPosition( start_position );
    +object.matrixAutoUpdate = false;
    +
    + 注意这种方式下 `matrixAutoUpdate` 必须设为 `false`, + 并且要确保不要调用 `updateMatrix`。 + 调用 `updateMatrix` 会根据 `position`、`scale` 等重新计算矩阵, + 从而覆盖你手动写入的矩阵内容。 +
  4. +
+ +

对象矩阵与世界矩阵

+

+ 对象的 `matrix` 存储的是相对于父对象的局部变换; + 若要获取对象在世界坐标中的变换,需要访问对象的世界矩阵。 +

+

+ 当父对象或子对象变换发生变化时,可以调用 `object.updateMatrixWorld()`, + 触发子对象世界矩阵更新。 +

+

+ 也可以通过 `applyMatrix4()` 对对象施加变换。注意:该方法底层依赖 + `Matrix4.decompose()`,并非所有矩阵都能这样分解。 + 例如父对象存在非均匀缩放时,子对象世界矩阵可能无法正确分解, + 此时该方法并不适用。 +

+ +

旋转与四元数

+

+ Three.js 提供两种 3D 旋转表示:欧拉角与四元数,并支持相互转换。 + 欧拉角会受到“万向节锁(gimbal lock)”问题影响, + 在某些姿态下会丢失一个自由度(导致无法绕某一轴旋转)。 + 因此,对象旋转在内部始终存储为四元数。 +

+

+ 旧版本库曾有 `useQuaternion` 属性,设为 false 时会用欧拉角计算对象矩阵。 + 这种做法已废弃。现在应使用 `object.setRotationFromEuler()`, + 它会同步更新四元数。 +

+ +
+
+
+ + + + + + + + + + + diff --git a/manual/zh/physics.html b/manual/zh/physics.html new file mode 100644 index 00000000000000..a9a4e53eeb1bff --- /dev/null +++ b/manual/zh/physics.html @@ -0,0 +1,176 @@ + + + + + + 物理 + + + + + + + + + + + + + + + +
+
+

物理

+
+
+
+ +

+ 物理引擎可以在 3D 场景中模拟重力、碰撞、受力等物理现象。 + 在常规 three.js 场景中,我们通常直接修改对象位置和旋转; + 而在使用物理引擎时,会额外维护一个并行的“物理世界”, + 刚体在其中响应力和碰撞。然后每帧把 three.js 网格与物理刚体状态同步, + 从而呈现出“真实物理”效果。 +

+ +

+ 需要注意的是,物理引擎不一定要每帧更新。为了保持模拟稳定, + 通常会采用固定时间步。比如游戏循环运行于 60fps, + 物理更新运行于 30fps(1/30 秒), + 同时每帧使用物理引擎的最新状态更新 three.js 网格变换(位置、旋转等)。 +

+ +

+ 物理模拟尤其适用于游戏、交互可视化,以及任何需要真实对象行为的应用, + 例如下落、弹跳、滑动等效果。 +

+ +

集成方式

+ +

+ 在 three.js 项目中集成物理引擎,主要有三种方式: +

+ +

1. 使用 three.js 物理插件

+ +

+ Three.js 在 examples/jsm/physics 目录中为多个常见物理引擎提供了封装类。 + 这些插件可简化接入流程,完成物理世界初始化与网格同步。 +

+ +

+ 可用插件包括: +

+ +
    +
  • AmmoPhysics:Ammo.js(Bullet 物理)的封装。
  • +
  • JoltPhysics:Jolt Physics 的封装。
  • +
  • RapierPhysics:Rapier 的封装。
  • +
+ +

+ 这些插件屏蔽了大量底层复杂性。对于常规需求,它们是最快的入门路径之一。 +

+ +

+ 示例 +

+ + +

2. 使用第三方 JS/TS 物理库

+ +

+ 许多物理引擎直接由 JavaScript / TypeScript 编写, + 与 Web 生态集成较容易。像 cannon-es 这类库因轻量且接入简单而常被采用。 +

+ +

+ 使用这类库时,你需要自己创建物理世界和刚体, + 并在动画循环中手动把刚体的位置、四元数同步到 three.js 网格。 +

+ + +

+ 项目 +

+
    +
  • cannon-es:纯 JS/TS 的轻量 3D 物理引擎,MIT 协议。看起来维护已不活跃(最近提交距今较久)。
  • +
  • cannon.js:纯 JavaScript 的轻量 3D 物理引擎,MIT 协议。已基本停止维护。建议优先使用其较新的分支 cannon-es。
  • +
  • phy:面向 three.js 的纯 JavaScript 物理引擎,MIT 协议。当前仍在维护。
  • +
  • Oimo.js:纯 JavaScript 轻量 3D 物理引擎,已不再维护。作者建议改用 phy。
  • +
+

+ 另外还有一类“看似 JS/TS、实则调用外部引擎”的方案,例如: +

+
    +
  • Physijs:底层调用 ammo.js,并借助 Web Worker 在独立线程处理物理,MIT 协议。维护不活跃(最近提交距今多年)。
  • +
  • enable3d:基于 ammo.js 的 three.js 3D 物理框架,LGPL-3.0 协议。看起来仍在维护。
  • +
+ +

3. 引入基于 WASM 的引擎

+ +

+ 如果你需要更高性能、稳定性和精度(尤其复杂模拟), + 可以选择 C++/Rust 等语言编写并编译为 WebAssembly(WASM)的物理引擎。 + 例如 Ammo.js(Bullet 的移植版)和 Rapier 都属于这一类。 +

+ +

+ 这种方式通常功能最完整、性能最好,但接入成本更高, + 需要处理 WASM 内存管理及与物理 API 的直接交互。 +

+ +

+ 示例 +

+ + +

+ 项目 +

+
    +
  • JoltPhysics:面向多核的刚体物理与碰撞检测库,C++ 编写,MIT 协议,活跃维护。已在《Horizon Forbidden West》《Death Stranding 2》等知名作品中得到验证,并获得 Godot 游戏引擎官方支持。
  • +
  • PhysX:NVIDIA 提供的工业级实时 3D 物理引擎,BSD-3-Clause 协议,稳定且持续维护。
  • +
  • Rapier:注重性能的 2D/3D 物理引擎,Rust 编写,MIT 协议,活跃维护。
  • +
  • Bullet: + 用于 VR、游戏、视觉特效、机器人、机器学习等场景的实时碰撞检测与多物理模拟库,C++ 编写,ZLIB 协议。维护状态可能不活跃。
  • +
+

+ 其中一些跨平台 3D 物理引擎已有可直接使用的 WASM 版本,例如: +

+
    +
  • JoltPhysics.js:使用 Emscripten 将 JoltPhysics 移植到 JavaScript,MIT 协议,当前维护中。
  • +
  • physx-js-webidl:NVIDIA PhysX 的 JavaScript WASM 绑定,MIT 协议,当前维护中。
  • +
  • Rapier.js:Rapier 的官方 JavaScript 绑定,Apache-2.0 协议,活跃维护。
  • +
  • Ammo.js:使用 Emscripten 将 Bullet 直接移植到 JavaScript,维护不活跃(最近提交距今多年),采用类似 MIT 的宽松自定义协议。
  • +
+ +
+
+
+ + + + + + + + + diff --git a/manual/zh/webgpu-postprocessing.html b/manual/zh/webgpu-postprocessing.html new file mode 100644 index 00000000000000..1f4a4976c01321 --- /dev/null +++ b/manual/zh/webgpu-postprocessing.html @@ -0,0 +1,248 @@ + + + WebGPURenderer 后处理 + + + + + + + + + + + + + + +
+
+

WebGPURenderer 后处理

+
+
+
+ +

+ `WebGPURenderer` 提供了全新的后处理组件。本文介绍其工作方式和基础用法。 +

+ +

概述

+ +

+ 旧版 `WebGLRenderer` 的后处理在设计上存在一些限制。由于渲染器支持不足, + 使用 MRT(多渲染目标)较繁琐,同时缺少自动 pass/effect 合并机制, + 难以优化整体性能。 +

+ +

+ 新版 `WebGPURenderer` 后处理栈从一开始就面向这些需求设计。 +

+
    +
  • + 内置完整 MRT 支持。 +
  • +
  • + 系统会在可能时自动合并效果,减少总渲染 pass 数量。 +
  • +
  • + 效果链通过节点组合表达,可更灵活地搭建后处理流程。 +
  • +
+

+ 下面来看如何在 three.js 应用中接入这套后处理系统。 +

+ +

基础用法

+ +

+ 请先阅读 WebGPURenderer 指南并正确配置导入。 + 然后按如下方式创建渲染管线模块实例: +

+ +
+const renderPipeline = new THREE.RenderPipeline( renderer );
+
+ +

+ `RenderPipeline` 用于替代旧的 `EffectComposer`。 + 要确保最终输出来自该模块,需要把动画循环改成如下形式: +

+
+-  renderer.render( scene, camera );
++  renderPipeline.render();
+
+ +

+ 大多数后处理流程都会先创建一个所谓的场景 pass(scene pass),也称 beauty pass, + 作为原始渲染图像,然后再叠加 Bloom、景深、SSR 等效果。 + 先从 TSL 命名空间导入 `pass()` 并创建该 pass。 +

+
+import { pass } from 'three/tsl';
+
+// 在你的初始化流程中
+
+const scenePass = pass( scene, camera );
+
+

+ 节点系统的核心思想是:把材质或后处理效果表示为节点组合。 + 例如要实现 DotScreen 与 RGB Shift,只需创建对应效果节点并串联。 +

+ +
+import { pass } from 'three/tsl';
++  import { dotScreen } from 'three/addons/tsl/display/DotScreenNode.js';
++  import { rgbShift } from 'three/addons/tsl/display/RGBShiftNode.js';
+
+// 在你的初始化流程中
+
+const scenePass = pass( scene, camera );
+
++  const dotScreenPass = dotScreen( scenePass );
++  const rgbShiftPass = rgbShift( dotScreenPass );
+
+ +

+ 完成后,把最终节点赋给 `RenderPipeline` 即可。 +

+
+renderPipeline.outputNode = rgbShiftPass;
+
+ +

色调映射与色彩空间

+ +

+ 使用后处理时,色调映射与色彩空间转换会在效果链末尾自动执行。 + 但某些场景你可能希望完全控制执行时机与顺序。 + 例如使用 `FXAANode` 做 FXAA,或用 `Lut3DNode` 做调色时, + 可以关闭自动处理,并通过 `renderOutput()` 手动应用。 +

+ +
+import { pass, renderOutput } from 'three/tsl';
+import { fxaa } from 'three/addons/tsl/display/FXAANode.js';
+
+// 在你的初始化流程中
+
+const renderPipeline = new THREE.RenderPipeline( renderer );
+renderPipeline.outputColorTransform = false; // 禁用默认输出色彩变换
+
+const scenePass = pass( scene, camera );
+const outputPass = renderOutput( scenePass ); // 在这里应用色调映射和色彩空间转换
+
+// FXAA 必须在 sRGB 色彩空间中计算
+
+const fxaaPass = fxaa( outputPass );
+renderPipeline.outputNode = fxaaPass;
+
+ +

+ `renderOutput()` 不是强制的,你也可以按需求自行实现色调映射与色彩空间转换。 +

+ +

MRT(多渲染目标)

+ +

+ 新后处理栈内置 MRT,对高级效果非常关键。MRT 允许你在一次渲染 pass 中产生多个输出。 + 例如使用 TRAA 时,你可以按下述配置准备抗锯齿输入。 +

+ +
+import { pass, mrt, output, velocity } from 'three/tsl';
+
+// 在你的初始化流程中
+
+const scenePass = pass( scene, camera );
+scenePass.setMRT( mrt( {
+  output: output,
+  velocity: velocity
+} ) );
+
+

+ 传给 `mrt()` 的配置对象用于描述该 pass 的各个输出。 + 本例中我们保存默认输出(场景主图)和速度信息,用于 TRAA。 + 如果还需要深度,一般无需额外作为 MRT 输出配置; + 在默认输出 pass 中按需请求即可获取。 + 若后续效果需要这些结果,可把它们作为纹理节点读取。 +

+ +
+import { traa } from 'three/addons/tsl/display/TRAANode.js';
+
+// 在你的初始化流程中
+
+const scenePassColor = scenePass.getTextureNode( 'output' );
+const scenePassDepth = scenePass.getTextureNode( 'depth' );
+const scenePassVelocity = scenePass.getTextureNode( 'velocity' );
+
+const traaPass = traa( scenePassColor, scenePassDepth, scenePassVelocity, camera );
+renderPipeline.outputNode = traaPass;
+
+ +

+ MRT 配置取决于你的具体方案。你可以使用 `output`、`velocity`、`normalView`、 + `emissive` 等 TSL 对象,把片元数据写入不同 attachment。 + 在复杂 MRT 流程中,为提升性能并避免显存压力,必须做好数据打包与格式优化。 + 默认 attachment 精度是 RGBA16(Half-Float),并非所有数据都需要这么高。 + 例如下方把 `diffuseColor` 改为 RGBA8,可将带宽和内存占用减半。 +

+ +
+const diffuseTexture = scenePass.getTexture( 'diffuseColor' );
+diffuseTexture.type = THREE.UnsignedByteType;
+
+ +

+ 下方 SSR(屏幕空间反射)示例把默认 FP16 法线转换为 RGBA8 颜色, + 并将金属度/粗糙度打包到单个 attachment。配合 `sample()` TSL 函数可实现自定义解包, + 本例中会把颜色还原为归一化方向向量。 +

+ +
+scenePass.setMRT( mrt( {
+  output: output,
+  normal: directionToColor( normalView ),
+  metalrough: vec2( metalness, roughness )
+} ) );
+
+// 使用 RGBA8 替代 RGBA16
+
+const normalTexture = scenePass.getTexture( 'normal' );
+normalTexture.type = THREE.UnsignedByteType;
+
+const metalRoughTexture = scenePass.getTexture( 'metalrough' );
+metalRoughTexture.type = THREE.UnsignedByteType;
+
+// 自定义解包。后续效果里请使用得到的 "sceneNormal"
+// 来替代 "scenePassNormal"
+
+const sceneNormal = sample( ( uv ) => {
+
+  return colorToDirection( scenePassNormal.sample( uv ) );
+
+} );
+
+ +

+ 后续还会继续增强打包/解包能力,提供更多 MRT 数据组织方式。 + 目前建议参考 + 官方示例, + 了解现有效果和配置模式。 +

+
+
+
+ + + + + + + + diff --git a/manual/zh/webgpurenderer.html b/manual/zh/webgpurenderer.html new file mode 100644 index 00000000000000..ccfe3dcf0dbb7a --- /dev/null +++ b/manual/zh/webgpurenderer.html @@ -0,0 +1,182 @@ + + + WebGPU 渲染器 + + + + + + + + + + + + + + +
+
+

WebGPU 渲染器

+
+
+
+ +

+ `WebGPURenderer` 是 three.js 的下一代渲染器。本文会简要介绍它的能力和基本使用方式。 +

+ +

概述

+ +

+ `WebGPURenderer` 被设计为 `WebGLRenderer` 的现代替代方案。 + 它优先使用 WebGPU(现代高性能图形与计算 API), + 同时也被设计成通用渲染器:若设备/浏览器不支持 WebGPU, + 会自动回退到 WebGL 2 后端。 +

+

+ 这个回退机制非常关键:应用可以在支持 WebGPU 的平台获得新能力, + 同时不牺牲仅支持 WebGL 2 设备的兼容性。 +

+ +

+ 除了接入 WebGPU,`WebGPURenderer` 还提供了以下特性: +

+ +

    +
  • + 内置全新的节点材质系统,开发自定义材质更灵活、更稳健。 +
  • +
  • + 支持 three.js 着色语言 TSL。你可以用 JavaScript 以跨平台方式编写 shader, + 并根据后端自动转译为 WGSL 或 GLSL。 +
  • +
  • + 内置全新后处理栈,支持 MRT(多渲染目标)并可借助节点系统自动合并 pass。 +
  • +
+ + 下面看看如何在 three.js 应用中集成 `WebGPURenderer`。 + +

使用方式

+ +

+ `WebGPURenderer` 使用不同构建入口,因此导入方式需要调整: +

+ +
+-  import * as THREE from 'three';
++  import * as THREE from 'three/webgpu';
+
+ +

+ 如果你使用 import map,建议改成如下形式(路径按你的工程结构调整): +

+
+  <script type="importmap">
+    {
+      "imports": {
+        "three": "../build/three.webgpu.js",
+        "three/webgpu": "../build/three.webgpu.js",
+        "three/tsl": "../build/three.tsl.js",
+        "three/addons/": "./jsm/"
+      }
+    }
+  </script>
+
+ +

+ 创建渲染器实例的方式和 `WebGLRenderer` 类似: +

+ +
+const renderer = new THREE.WebGPURenderer( { antialias: true } );
+renderer.setPixelRatio( window.devicePixelRatio );
+renderer.setSize( window.innerWidth, window.innerHeight );
+renderer.setAnimationLoop( render );
+document.body.appendChild( renderer.domElement );
+
+

+ 需要注意,WebGPU 初始化是异步的。因此推荐使用 `setAnimationLoop()`, + 它能确保首次渲染前完成初始化。 + 如果你坚持使用 `window.requestAnimationFrame()` 或需要在初始化阶段直接使用渲染器, + 则要额外调用一行初始化代码。 +

+
+const renderer = new THREE.WebGPURenderer( { antialias: true } );
+renderer.setPixelRatio( window.devicePixelRatio );
+renderer.setSize( window.innerWidth, window.innerHeight );
+renderer.setAnimationLoop( render );
+document.body.appendChild( renderer.domElement );
+
++  await renderer.init();
+
+

+ `WebGLRenderer` 中常见的方法(如 `clear()`、`setRenderTarget()`、`dispose()`) + 在 `WebGPURenderer` 中同样可用。完整接口请参考 + API 文档。 +

+ +

+ 正如前文所述,`WebGPURenderer` 默认使用 WebGPU,必要时回退 WebGL 2。 + 如果你想在测试中强制 WebGL 2,或出于某些原因禁用 WebGPU, + 可以使用 `forceWebGL` 参数。 +

+
+-  const renderer = new THREE.WebGPURenderer( { antialias: true } );
++  const renderer = new THREE.WebGPURenderer( { antialias: true, forceWebGL: true } );
+
+ +

迁移说明

+ +

+ 准备迁移到 `WebGPURenderer` 时,需要注意以下几点: +

+ +
    +
  • + `ShaderMaterial`、`RawShaderMaterial` 以及通过 `onBeforeCompile()` 改造内置材质, + 在 `WebGPURenderer` 中不受支持。相关逻辑需要迁移到节点材质与 TSL。 +
  • +
  • + `EffectComposer` 及其传统 pass 在这里不支持, + 因为 `WebGPURenderer` 提供了新一代后处理栈。 + 类似材质迁移,后处理效果也使用 TSL 编写,并以节点组合表达。 + 常用效果已迁移并提供了性能更好的节点版本,同时新增了 SSGI、SSS、 + 更好的 DoF 等新效果。可查看 + 官方示例。 +
  • +
  • + 渲染器整体仍属于实验阶段,尽管成熟度近年已明显提升。 + 依据你的应用和场景,仍可能遇到缺失特性,或在某些场景下 `WebGLRenderer` 更快。 + 如遇问题建议在 GitHub 提 issue。`WebGPURenderer` 会持续迭代, + 建议尽量使用最新版本。 +
  • +
+ +

WebGLRenderer 的现状

+ +

虽然当前研发重点在 `WebGPURenderer`、节点材质和 TSL, + `WebGLRenderer` 仍在维护,且依然是纯 WebGL 2 应用的推荐选择。 + 但请注意,项目已不计划为 `WebGLRenderer` 增加大型新特性,这一点从最近的版本说明(release notes)中也可以明显看出。 + 同时我们也在评估为其加入有限的节点材质支持, + 以便某些项目更平滑地迁移到 `WebGPURenderer`。 +

+ +
+
+
+ + + + + + + + diff --git a/src/audio/AudioListener.js b/src/audio/AudioListener.js index f9478d06ccc864..1d3081cf594cd1 100644 --- a/src/audio/AudioListener.js +++ b/src/audio/AudioListener.js @@ -1,6 +1,6 @@ import { Vector3 } from '../math/Vector3.js'; import { Quaternion } from '../math/Quaternion.js'; -import { Clock } from '../core/Clock.js'; +import { Timer } from '../core/Timer.js'; import { Object3D } from '../core/Object3D.js'; import { AudioContext } from './AudioContext.js'; @@ -71,7 +71,7 @@ class AudioListener extends Object3D { // private - this._clock = new Clock(); + this._timer = new Timer(); } @@ -176,9 +176,11 @@ class AudioListener extends Object3D { super.updateMatrixWorld( force ); + this._timer.update(); + const listener = this.context.listener; - this.timeDelta = this._clock.getDelta(); + this.timeDelta = this._timer.getDelta(); this.matrixWorld.decompose( _position, _quaternion, _scale ); diff --git a/src/materials/nodes/manager/NodeMaterialObserver.js b/src/materials/nodes/manager/NodeMaterialObserver.js index eb94197e935aeb..bdb0d242a8e824 100644 --- a/src/materials/nodes/manager/NodeMaterialObserver.js +++ b/src/materials/nodes/manager/NodeMaterialObserver.js @@ -438,14 +438,14 @@ class NodeMaterialObserver { // check index const index = geometry.index; - const storedIndexId = storedGeometryData.id; + const storedIndexId = storedGeometryData.indexId; const storedIndexVersion = storedGeometryData.indexVersion; const currentIndexId = index ? index.id : null; const currentIndexVersion = index ? index.version : null; if ( storedIndexId !== currentIndexId || storedIndexVersion !== currentIndexVersion ) { - storedGeometryData.id = currentIndexId; + storedGeometryData.indexId = currentIndexId; storedGeometryData.indexVersion = currentIndexVersion; return false;