ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Three.js 官方接入 Gaussian Splatting,Web 端实时渲染更简单

Three.js 官方接入 Gaussian Splatting,Web 端实时渲染更简单 在网页里还原一个真实场景过去既昂贵又麻烦现在Three.js 正在把这条路的门槛再降一截。3D Gaussian Splatting3DGS是这两年在三维重建和渲染领域讨论度最高的方向之一。它把真实场景的训练结果变成一堆带颜色、透明度和形状的高斯点在运行时用类似“高效光栅化”的方式渲染成照片级别的画面。相比 NeRF它训练更快、渲染能到实时相比传统倾斜摄影建模它绕开了网格重建和贴图烘焙的复杂流程。但 Web 端落地一直有个现实问题谁来维护 loader、谁来渲染这堆点。Three.js 的官方仓库近期加入了GaussianSplattingLoader和相关示例意味着主流的 Web 3D 引擎开始官方承接这项技术。这篇文章会先把 3DGS 和 Three.js 的原生支持讲清楚说明它为什么值得关注然后给出一套可复制的环境准备和最小示例再扩展到多场景切换、WebGPU 方向、常见问题与工程实践。读完以后你可以用官方 loader 把一个.splat场景跑起来再逐步接入自己的业务项目。1. 为什么这条消息值得关注先做一个判断Three.js 官方支持 Gaussian Splatting真正降低的是 Web 端接入 3DGS 的工程成本而不是算法成本。3DGS 的核心训练算法在 SIGGRAPH 2023 提出后社区很快就有了 Web 端渲染方案。比如 Antimatter15 的 gaussian-splatting 仓库比如 gsplat.js再比如各种依赖 Three.js 的社区 hack。这些方案能跑但你一旦想在真实项目里长期使用就要面对几个问题.ply、.splat、.ksplat格式谁来解析深度排序在 WebGL 里怎么做近似在 WebGPU 里怎么做精确官方修复管线升级时第三方库还在不在维护出了渲染 Bugissue 应该提给谁过去这些问题都悬在“社区造轮子”上空。Three.js 把GaussianSplattingLoader放进官方 examples 目录后情况发生变化格式解析、基础渲染、WebGPU 示例都有了官方维护的参考实现。对业务开发者的直接收益是不需要从零理解 3DGS 的底层数据格式也能加载渲染可以和现有 Three.js 场景、OrbitControls、交互逻辑无缝整合官方示例同时覆盖 WebGL 与 WebGPU方便评估不同渲染路径后续升级 Three.js 版本时至少有一条平滑迁移路径。所以如果你在做 Web 端数字孪生、云展厅、在线设计工具或者想把实景建模结果放进 Cesium 这类地理平台这条消息值得停下来看。2. 3D Gaussian Splatting 是什么从“带形状的点云”说起2.1 一句话理解 3DGS你可以把 3DGS 想象成一种“带形状、带颜色的点云”。普通点云是一堆没有体积的坐标点渲染出来就像星空很难表达光滑表面。3DGS 的每个基本单元不是一个点而是一个三维高斯分布说人话就是一个小椭球。每个小椭球有自己的空间位置三个轴方向的尺度旋转方向不透明度RGB 颜色。渲染时摄像机把分布在场景里的这成千上万个半透明小椭球投影到屏幕上再做一次从近到远的排序和颜色叠加最终得到接近照片的画面。这也是它和“光场”“体素”这些概念不一样的地方最终画面不是插值出来的网格而是大量高斯核累加出来的像素结果。2.2 与点云、NeRF 的对比维度点云NeRF3D Gaussian Splatting基本单元无体积的点隐式神经场有形状的高斯点训练速度不需要训练慢小时级起步较快渲染速度快但效果单薄慢逐像素采样光栅化可实时场景编辑直接改点难度高显式点集相对方便Web 端接入简单复杂官方 loader 出现后明显简化从开发者的角度看3DGS 的最大优势是“显式”和“实时”。显式意味着拿到点集就能编辑、裁剪、分层实时意味着可以放进交互页面让用户转动视角而不是播放一段预渲染视频。3. Three.js“原生支持”到底支持了什么3.1 官方 addons 中的 Loader 与示例从 Three.js 官方仓库看这套支持主要体现在 examples 目录下的几个文件里loaders/GaussianSplattingLoader.js负责加载和解析 3DGS 数据配套的 WebGL 示例、WebGPU 示例一些底层辅助类和材质逻辑。GaussianSplattingLoader目前的主用输入是.splat文件。这是一种为 Web 端准备的二进制格式比原生 PLY 更轻加载速度也更快。你不需要知道它每一字节的含义也可以直接使用但理解基础结构对排查问题有帮助每个高斯点会记录位置、缩放、旋转、颜色和不透明度数据按二进制紧凑排列加载后由 Three.js 侧的材质和渲染逻辑解释。3.2 “原生”的边界别误会成核心 API这里要明确边界目前它是examples / addons 级别不是THREE.GaussianSplatting这种在核心库里到处可用的 API。这意味着官方会维护它加入它更新它但它仍然有 example 代码的“试运行”属性API 可能随版本调整生产环境使用前需要锁版本、做回归不能认为“官方支持”就等于“绝对稳定”。这个判断非常重要。很多项目拿到“原生支持”的消息后会直接升级依赖如果遇到 API 变化反而容易被坑。稳妥的做法是用一个固定版本先跑通再评估是否升级。4. 环境准备与前置条件这一节给出一套通用环境要求。不同 Three.js 版本的具体路径可能有差异本文以0.170.0为例重点展示原理和可复制的步骤。4.1 浏览器要求WebGL 路径需要支持 WebGL2 的现代浏览器Chrome、Edge、Firefox、Safari 均可WebGPU 路径建议使用较新的 Chrome 或 Edge并确认浏览器已启用 WebGPU移动端不建议在低端手机上直接加载大场景后面会讲原因。4.2 Three.js 版本与模块方式建议直接使用支持GaussianSplattingLoader的近期版本。本文代码使用 npm 生态中的three0.170.0并通过importmap从 CDN 加载。如果你已经在项目里使用 npm 管理依赖也可以先安装npm install three0.170.0然后根据构建工具配置调整 import 路径。核心代码逻辑是通用的。4.3 本地静态服务器.splat文件加载本质上是 fetch 文件所以不要用file://直接打开 HTML否则会遇到跨域和模块加载问题。推荐方式VS Code 的 Live Server 插件Pythonpython -m http.server 8080Nodenpx serve .4.4 数据文件准备一个.splat文件放到assets目录。获取方式一般有使用 Three.js 官方示例中引用的数据文件使用社区开源 3DGS 项目提供的.splat样例使用自己的 3DGS 重建结果通过社区转换工具从 PLY 转成.splat。社区转换工具形态各异有的提供 Python 脚本有的提供在线转换服务命令请以你选择的仓库 README 为准本文不限定具体工具。5. 最小可运行示例用 WebGL 渲染 .splat 场景5.1 项目目录先规划一个最小目录gaussian-splatting-demo/ ├── assets/ │ └── scene.splat └── index.htmlasset这个名字可以随意关键是路径要和代码里的loader.load保持一致。5.2 完整 HTML 代码!DOCTYPE html html langzh-CN head meta charsetUTF-8 / titleThree.js Gaussian Splatting 最小示例/title meta nameviewport contentwidthdevice-width, initial-scale1 / style html, body { margin: 0; padding: 0; overflow: hidden; background: #111; } canvas { display: block; } #info { position: fixed; top: 12px; left: 12px; color: #eee; font: 14px/1.8 system-ui, sans-serif; background: rgba(0, 0, 0, 0.45); padding: 8px 12px; border-radius: 6px; pointer-events: none; } /style /head body div idinfo拖动鼠标旋转视角滚轮缩放/div script typeimportmap { imports: { three: https://unpkg.com/three0.170.0/build/three.module.js, three/addons/: https://unpkg.com/three0.170.0/examples/jsm/ } } /script script typemodule import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js; import { GaussianSplattingLoader } from three/addons/loaders/GaussianSplattingLoader.js; // 场景、相机、渲染器 const scene new THREE.Scene(); scene.background new THREE.Color(0x111111); const camera new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.01, 100 ); camera.position.set(2, 1.5, 2); const renderer new THREE.WebGLRenderer({ antialias: true, alpha: false }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); document.body.appendChild(renderer.domElement); // 轨道控制器 const controls new OrbitControls(camera, renderer.domElement); controls.target.set(0, 0, 0); controls.update(); // 加载 Gaussian Splatting 场景 const loader new GaussianSplattingLoader(); loader.load( assets/scene.splat, (splat) { scene.add(splat); }, (progress) { console.log( 加载进度, ((progress.loaded / progress.total) * 100).toFixed(1) % ); }, (error) { console.error(加载失败, error); } ); // 渲染循环 function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } animate(); // 窗口缩放处理 window.addEventListener(resize, () { camera.aspect window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); /script /body /html5.3 代码拆解这里真正容易踩坑的点是importmap。Three.js 从很早开始就推荐通过 ES module 引入而importmap能让浏览器把three和three/addons/映射到 CDN 地址。这样你在业务代码里写import { OrbitControls } from three/addons/controls/OrbitControls.js实际加载的是远程模块。如果 CDN 不稳定可以把文件下载到本地后改映射路径。GaussianSplattingLoader的用法和 Three.js 其他 Loader 基本一致第一个参数是.splat文件地址第二个参数是加载成功回调拿到一个splat节点后加入场景第三个参数是进度回调第四个参数是错误回调。加载成功后splat是一个可以被相机渲染的对象。不需要再手动加灯因为 Gaussian Splatting 场景本身已经包含训练时的光照信息。这一点和传统网格加载完全不同。5.4 运行方式在项目目录启动静态服务器python -m http.server 8080然后打开http://localhost:8080预期现象是页面先显示深色背景然后.splat场景逐渐出现按住鼠标左键拖动可以旋转视角滚动滚轮可以缩放。如果加载成功后画面上空无一物先看控制台有没有报错再确认相机的near/far和position是否落在场景数据范围内。.splat文件可能来自不同尺度单位不一定是“米”所以相机位置要调到数据附近。6. 扩展加载多个场景与相机动效实际业务里往往不是一个场景加载完就结束。云展厅可能要切换房间家装效果可能要切换户型对比展示要切换多套重建结果。这里需要一个简单的“场景管理”思路。核心做法是维护一个Map把每个加载完的splat节点存下来再通过控制visible属性切换显示。这样做不会重复请求资源也不会因为重复scene.add导致渲染隐患。6.1 代码示例多场景切换import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js; import { GaussianSplattingLoader } from three/addons/loaders/GaussianSplattingLoader.js; const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 100); camera.position.set(2, 1.5, 2); const renderer new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); document.body.appendChild(renderer.domElement); const controls new OrbitControls(camera, renderer.domElement); // 管理多个 splat 节点 const splatScenes new Map(); const loader new GaussianSplattingLoader(); function loadSplat(url, key) { loader.load( url, (splat) { splat.visible false; splatScenes.set(key, splat); scene.add(splat); console.log([${key}] 加载完成); }, (progress) { console.log([${key}] 加载中, progress.loaded, /, progress.total); }, (err) { console.error([${key}] 加载失败, err); } ); } function showSplat(key) { splatScenes.forEach((splat, k) { splat.visible k key; }); } loadSplat(assets/scene_a.splat, room); loadSplat(assets/scene_b.splat, garden); // 3 秒后切换到第二个场景仅用于演示 setTimeout(() { showSplat(garden); }, 3000); function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } animate();这个例子没有引入 UI 组件而是用setTimeout模拟切换动作。你在真实项目里可以把它接到按钮、下拉框或者路由事件里。需要注意visible false只是不渲染内存仍被占用。如果场景很多且内存吃紧应该考虑按需加载、卸载而不是一股脑全部加载。6.2 相机动效如果想做“自动漫游”最简单的方案是每帧修改相机位置和目标点让 OrbitControls 跟随function updateCameraByTime(time) { const radius 3; const angle time * 0.2; camera.position.x Math.cos(angle) * radius; camera.position.z Math.sin(angle) * radius; camera.position.y 1.2; controls.target.set(0, 0.5, 0); controls.update(); } function animate() { requestAnimationFrame(animate); updateCameraByTime(performance.now() * 0.001); renderer.render(scene, camera); } animate();这里要注意用户交互和自动漫游的优先级。更稳妥的做法是用户按下鼠标或触摸屏幕时暂停自动漫游等交互结束后再决定是否恢复。7. WebGPU 方向性能潜力和注意事项7.1 为什么 3DGS 需要 WebGPU3DGS 渲染的关键步骤之一是每一帧都要对高斯点做从近到远排序。只有排序正确半透明高斯点叠加出来的画面才不会闪烁、穿帮。在 WebGL 里排序通常需要 CPU 参与或者借助不太优雅的近似方案。高斯点数量一旦上百万CPU 排序会成为性能瓶颈。WebGPU 有 compute shader可以在 GPU 上并行完成排序和坐标变换这也是 Three.js 官方提供webgpu_gaussian_splatting示例的原因。从 Web 渲染技术栈的演进看3DGS 是一个很适合把 WebGPU 推向真实应用的场景因为数据量大、计算密集、效果又直观。7.2 用 WebGPU 渲染器替换 WebGL在官方支持 WebGPU 的 Three.js 版本中你可以把渲染器从WebGLRenderer换成WebGPURenderer。最小改动示例import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js; import { WebGPURenderer } from three/addons/renderers/webgpu/WebGPURenderer.js; import { GaussianSplattingLoader } from three/addons/loaders/GaussianSplattingLoader.js; async function init() { const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 100); camera.position.set(2, 1.5, 2); // WebGPU 渲染器注意新版需要先初始化 const renderer new WebGPURenderer(); await renderer.init(); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); document.body.appendChild(renderer.domElement); const controls new OrbitControls(camera, renderer.domElement); const loader new GaussianSplattingLoader(); loader.load(assets/scene.splat, (splat) { scene.add(splat); }); function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } animate(); } init();这段代码最大的区别是await renderer.init()。WebGPU 渲染器需要异步获取 GPU device所以初始化变成了 async。不同 Three.js 版本的 API 可能调整请以官方示例为准。需要提醒的是不要以为切到 WebGPU 就万事大吉。如果浏览器不支持 WebGPU页面会直接黑屏或报错所以要加能力检测并准备回退到 WebGL 的逻辑。性能优化的优先级依然是“减少高斯点数量、控制像素比例、避免同时加载多套场景”。8. 常见问题与排查思路问题现象可能原因排查方式解决方案页面黑屏控制台没有报错相机位置不在场景数据范围打印 splat 节点的包围盒或 center调整相机位置和 target加载请求跨域失败数据文件在不同域名且未配置 CORS查看 Network 面板的 fetch 状态配置服务端 CORS或使用同源静态服务器file://打开时报模块加载错误ES Module 不允许文件协议改用http://localhost访问使用 Live Server / http.server加载很慢或页面卡顿.splat文件过大、像素比过高查看文件大小和 GPU 占用降采样、压缩数据、限制 pixelRatio 为 2画面闪烁或局部出现错误颜色高斯点排序不正确或数据损坏改用 WebGPU 示例对比检查.splat来源使用转换工具重建某些浏览器不渲染不支持 WebGL2 或 WebGPU在chrome://gpu查看特性升级浏览器或提供 WebGL 回退多个场景切换后内存居高不下visiblefalse不释放 GPU 资源用 Memory 面板观察增长主动移除节点并释放 geometry/material这几点每一条都是实际接入时容易遇到的情况。排查时不要先怀疑 Three.js 本身第一步应该看数据文件能否正常加载第二步看相机视野是否覆盖场景第三步再看渲染管线差异。9. 工程实践建议9.1 数据准备3DGS 的数据质量直接决定体验。使用训练质量可靠的重建结果不要拿未收敛的点集直接上线.splat文件体积通常不小一个场景几十 MB 很正常要设计合理的加载策略如果场景过大需要做降采样减少高斯点数量对数据做统一坐标处理否则在数字孪生、Cesium 叠加场景里会出现偏移。后面这一点需要格外注意Gaussian Splatting 的坐标单位可能和你的业务坐标单位不同。埋进 Cesium 或 GIS 平台之前必须做坐标对齐否则会出现“模型在但位置不对”的诡异问题。9.2 服务端配置确保静态服务器支持.splat文件访问必要时设置正确的 MIME 类型为跨域加载配置 CORS大文件建议走 CDN并开启 HTTP 缓存避免每次进入页面都重新下载如果文件超过 50MB需要考虑按需加载、渐近加载或 LOD 方案。9.3 渲染与性能设置renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))防止高分屏把 GPU 压力放大好几倍在 SPA 中页面切走时要暂停渲染循环监控 FPS 和 draw call生产环境不要用开发版 CDN脚本路径要锁版本。9.4 兼容性策略WebGPU 是很好的方向但用户不一定能用。稳妥做法是先检测 WebGPU 是否可用可用则使用 WebGPU 渲染器不可用则回退到 WebGL 渲染器两者都不支持时显示友好提示。这种渐进增强策略适合大多数面向真实用户的 Web 3D 项目。10. 总结与后续学习方向Three.js 将 Gaussian Splatting 纳入官方维护范围是一个信号3DGS 正在从研究圈的演示项目变成 Web 前端可以认真对待的渲染数据形态。它不像传统网格那样需要繁琐的建模和烘焙也不像 NeRF 那样对 Web 端渲染要求苛刻而是在“真实感”和“实时性”之间取了很实用的平衡。你现在可以做的下一步很明确把官方示例或本文的index.html跑起来确认.splat场景能正常加载准备自己的数据通过社区工具从 PLY 转到.splat把 loader 接入现有 Three.js 业务处理加载状态、错误回退和性能监控如果场景大、用户设备好再研究 WebGPU 渲染路径和 LOD 方案。学习 3DGS 时不要只停留在“能加载”这一步。建议画出渲染管线里的关键节点数据解析、坐标变换、高斯点排序、光栅化、颜色合成。每一步都直接影响画面质量和帧率也是后续做性能优化时才真正用得上的知识。另外如果你正在做 Cesium 与 Three.js 共享 GL 上下文这类深度集成项目3DGS 可以作为真实场景图层的关键补充。社区后续大概率会围绕“3DGS 与 GIS 坐标对齐”“3DGS 在低端设备上的 LOD”这些方向继续产出方案值得保持关注。先让官方示例跑起来再考虑好不好用。动手永远比争论技术路线更快得到答案。
返回列表