当前位置: 首页 > news >正文

【Web】使用Vue3开发3D游戏(十一)渲染3D高斯泼溅效果

文章目录

  • 一、效果
  • 二、简介
    • 2.1、本文介绍
    • 2.2、什么是GaussianSplats3D
  • 三、技术选型与核心依赖
  • 四、核心组件实现(PlyCanvasViewer.vue)
    • 4.1、组件结构设计
    • 4.2、核心功能解析
  • 五、核心代码

一、效果

二、简介

2.1、本文介绍

高斯泼溅(Gaussian Splatting)是当前 3D 可视化领域的热门技术,凭借其超高的渲染精度和视觉表现力,成为 3D 重建、数字孪生等场景的优选方案。本文将基于 Vue3 + @mkkellogg/gaussian-splats-3d 库实现 3D 高斯泼溅效果的渲染,并整合交互、相机控制、自适应布局等核心能力,延续 Vue3+PlayCanvas 系列的开发风格,完成高斯泼溅模型的可视化落地。

2.2、什么是GaussianSplats3D

GaussianSplats3D 是由开发者 Matthew Kellogg 开发的一个轻量级 JavaScript 库,专为在 Web 浏览器 中渲染 3D Gaussian Splatting 场景 而设计。它基于 WebGL 构建,支持 .ply 格式的高斯点云数据(通常由 3D Gaussian Splatting 论文 的官方实现导出),并提供了:

  • 实时渲染
  • 相机控制(OrbitControls)
  • 渐进式加载(Progressive Loading)
  • 点选与悬停交互
  • 响应式布局支持
    该库封装了底层复杂的着色器逻辑和数据解析流程,让前端开发者可以像使用普通 3D 查看器一样轻松集成高斯点云。

GitHub 仓库:https://github.com/mkkellogg/gaussian-splats-3d

三、技术选型与核心依赖

  1. 核心依赖
    Vue3 (Setup 语法糖):作为前端框架,负责组件化开发、生命周期管理、Props 传参;
    @mkkellogg/gaussian-splats-3d:专门用于解析和渲染高斯泼溅模型(.ply 格式)的高性能库,内置 WebGL 渲染管线和相机控制;
    ResizeObserver:监听容器尺寸变化,保证 Canvas 自适应渲染;
    自定义交互封装:实现高斯泼溅模型的 hover/select 事件监听,输出空间坐标等交互信息。
  2. 前置准备
    确保项目已安装依赖:
npminstall@mkkellogg/gaussian-splats-3d--save

四、核心组件实现(PlyCanvasViewer.vue)

4.1、组件结构设计

组件分为 3 个核心区域:

  • 渲染容器:承载高斯泼溅模型的 Canvas 画布;
  • 状态 Overlay:展示加载状态、错误信息、调试日志;
  • 交互 Overlay:展示鼠标悬停 / 选中的 3D 空间坐标信息。

4.2、核心功能解析

  1. 高斯泼溅 Viewer 初始化
    通过GaussianSplats3D.Viewer创建渲染实例,核心配置项说明:

rootElement:渲染容器 DOM 节点;
selfDrivenMode:自驱动渲染模式(自动维护渲染循环);
cameraUp:相机向上方向(设置为[0,0,1]适配高斯泼溅模型坐标系);
useBuiltInControls:是否启用内置相机控制(通过lockCameraProps 控制);
initialCameraPosition/initialCameraLookAt:相机初始位置和看向目标。

  1. 相机控制优化
    默认情况下,高斯泼溅 Viewer 的相机旋转存在角度限制(垂直方向 ±85°),通过修改控制器参数解除限制:
// 解除垂直旋转角度限制viewer.controls.maxPolarAngle=Math.PIviewer.controls.minPolarAngle=0// 解除水平旋转角度限制viewer.controls.maxAzimuthAngle=Infinityviewer.controls.minAzimuthAngle=-Infinity// 调整旋转速度viewer.controls.rotateSpeed=1.2
  1. 模型加载兼容处理
    适配不同版本的@mkkellogg/gaussian-splats-3d API,提供 3 种加载方式:
  • addSplatScene:基础加载方式;
  • downloadAndBuildSingleSplatSceneProgressiveLoad:渐进式加载(适合大模型);
  • downloadAndBuildSingleSplatSceneStandardLoad:标准加载。
  1. 交互能力整合
    通过setupSplatInteractions封装高斯泼溅的 hover/select 交互:
  • onHover:鼠标悬停时输出 3D 空间坐标;
  • onSelect:鼠标点击时输出坐标 + 距离信息;
  • 交互信息通过 Overlay 展示在画布底部,不遮挡渲染内容。
  1. 自适应渲染
    使用ResizeObserver监听容器尺寸变化,触发forceRenderNextFrame保证 Canvas 自适应:
resizeObserver=newResizeObserver(()=>{try{viewer?.forceRenderNextFrame?.()}catch{/* ignore */}})resizeObserver.observe(containerRef.value)
  1. 资源销毁
    在组件卸载前,通过disposeViewer方法释放所有资源:
  • 断开ResizeObserver监听;
  • 销毁交互实例;
  • 停止渲染循环并销毁 Viewer 实例,避免内存泄漏。

五、核心代码

PlyCanvasViewer.vue

<template><divstyle="width:100%;height:100vh;background:#000"><divref="containerRef"class="viewer"><!-- 加载/错误/调试信息 overlay --><divv-if="isLoading || errorText || debugText"class="overlay"><divv-if="isLoading"class="overlayLine">加载中: {{ loadedCount }} / 1</div><divv-if="errorText"class="overlayLine">{{ errorText }}</div><divv-if="debugText"class="overlayLine debug">{{ debugText }}</div></div><!-- 悬停/选中信息 overlay --><divv-if="hoverText || selectText"class="overlay overlay2"><divv-if="hoverText"class="overlayLine">{{ hoverText }}</div><divv-if="selectText"class="overlayLine">{{ selectText }}</div></div><!-- 原容器节点 --><divid="container"></div></div></div></template><scriptsetup>import{onBeforeUnmount,onMounted,ref,watch}from'vue'import*asGaussianSplats3Dfrom'@mkkellogg/gaussian-splats-3d'import{setupSplatInteractions}from'../comment/splatInteraction'// 定义 props,允许通过 props 或路由参数传入 URLconstprops=defineProps({url:{type:String,default:'',// 默认为空,将从路由参数获取},lockCamera:{type:Boolean,default:false,},cameraPosition:{type:Array,default:()=>[0,-10,5],},cameraLookAt:{type:Array,default:()=>[0,0,0],},progressive:{type:Boolean,default:true,},})constcontainerRef=ref(null)constisLoading=ref(false)consterrorText=ref('')constdebugText=ref('')consthoverText=ref('')constselectText=ref('')constloadedCount=ref(0)constmodelUrl='/ply/room.ply'letviewerletresizeObserverletinteractionHandlefunctiondisposeViewer(){try{resizeObserver?.disconnect?.()}catch{// ignore}try{interactionHandle?.dispose?.()}catch{// ignore}try{viewer?.stop?.()}catch{// ignore}try{viewer?.dispose?.()}catch{// ignore}viewer=undefinedresizeObserver=undefinedinteractionHandle=undefined}asyncfunctioninitViewerAndLoad(){errorText.value=''isLoading.value=trueloadedCount.value=0if(!containerRef.value)returntry{viewer=newGaussianSplats3D.Viewer({rootElement:containerRef.value,selfDrivenMode:true,cameraUp:[0,0,1],useBuiltInControls:!props.lockCamera,initialCameraPosition:props.cameraPosition,initialCameraLookAt:props.cameraLookAt,})// ========== 解除控制器角度限制 ==========if(viewer.controls&&!props.lockCamera){// 解除垂直旋转角度限制(默认是{ min: -85, max: 85 })viewer.controls.maxPolarAngle=Math.PI// 允许旋转到任意垂直角度viewer.controls.minPolarAngle=0// 可选:如果需要完全无限制,设为 0 或 -Math.PI// 解除水平旋转角度限制(默认无限制,如需确认可显式设置)viewer.controls.maxAzimuthAngle=Infinityviewer.controls.minAzimuthAngle=-Infinity// 可选:调整旋转速度(如果需要)viewer.controls.rotateSpeed=1.2// 数值越大旋转越快}// ===========================================resizeObserver=newResizeObserver(()=>{try{viewer?.forceRenderNextFrame?.()}catch{// ignore}})resizeObserver.observe(containerRef.value)debugText.value=`renderMode=${String(viewer?.renderMode??'')}\nLoading:${modelUrl}`if(viewer?.addSplatScene){awaitviewer.addSplatScene(modelUrl)loadedCount.value=1}elseif(props.progressive&&viewer?.downloadAndBuildSingleSplatSceneProgressiveLoad){awaitviewer.downloadAndBuildSingleSplatSceneProgressiveLoad(modelUrl)loadedCount.value=1}elseif(viewer?.downloadAndBuildSingleSplatSceneStandardLoad){awaitviewer.downloadAndBuildSingleSplatSceneStandardLoad(modelUrl)loadedCount.value=1}else{thrownewError('GaussianSplats3D Viewer API not compatible: cannot find load method')}applyFullSizeCanvas()applyLockedCamera()interactionHandle=setupSplatInteractions(viewer,{onHover:(hit)=>{if(!hit?.point){hoverText.value=''return}constp=hit.point hoverText.value=`悬停: [${p.x.toFixed(3)},${p.y.toFixed(3)},${p.z.toFixed(3)}]`},onSelect:(hit)=>{if(!hit?.point){selectText.value='选中: (无)'return}constp=hit.point selectText.value=`选中: [${p.x.toFixed(3)},${p.y.toFixed(3)},${p.z.toFixed(3)}]`+(hit.distance!=null?`距离=${Number(hit.distance).toFixed(3)}`:'')},onError:(err)=>{console.warn('高斯交互错误:',err)},debug:true,})viewer?.start?.()}catch(e){console.error(e)errorText.value=e?.message?String(e.message):String(e)}finally{isLoading.value=false}}functionapplyFullSizeCanvas(){constcanvas=containerRef.value?.querySelector('canvas')if(!canvas)returncanvas.style.width='100%'canvas.style.height='100%'canvas.style.display='block'}functionapplyLockedCamera(){if(!props.lockCamera)returnconstcam=viewer?.cameraif(!cam)returnconstp=Array.isArray(props.cameraPosition)?props.cameraPosition:[0,-10,5]constt=Array.isArray(props.cameraLookAt)?props.cameraLookAt:[0,0,0]cam.position.set(Number(p[0]??0),Number(p[1]??-10),Number(p[2]??5))cam.lookAt(Number(t[0]??0),Number(t[1]??0),Number(t[2]??0))cam.updateMatrixWorld?.()// 禁用交互,让相机保持固定if(viewer?.controls)viewer.controls.enabled=false}onMounted(async()=>{awaitinitViewerAndLoad()})watch(()=>modelUrl,async(next,prev)=>{if(String(next||'')===String(prev||''))returndisposeViewer()awaitinitViewerAndLoad()})onBeforeUnmount(()=>{disposeViewer()})</script><stylescoped>.viewer{position:relative;width:100%;height:100%;overflow:hidden;background:#0b0f19;}.overlay{position:absolute;top:12px;left:12px;z-index:10;padding:8px 10px;background:rgba(0,0,0,0.55);color:#fff;font-size:12px;border-radius:6px;pointer-events:none;}.overlayLine{line-height:1.4;}.overlay2{top:auto;bottom:12px;}.debug{white-space:pre-wrap;max-width:70vw;}</style>
http://www.jsqmd.com/news/808055/

相关文章:

  • 羽毛球每天必练的基本功:拉吊四方球战术、吊杀结合战术
  • 2026年常州高分子材料管业深度选购指南:编织网管与电池防护配件源头工厂直供全景对标 - 优质企业观察收录
  • 人机生殖隔离
  • 具身智能(Embodied AI):当Agent拥有了物理身体
  • 2026年4月冬令营推荐,恩格贝沙漠穿越/儿童生存训练营/少年游骑兵生存训练营/恩格贝沙漠夏令营/夏令营,冬令营选哪家 - 品牌推荐师
  • CSDN 创作同步插件与 AI 标注功能实测大纲
  • 神兽街靠谱吗? - 工业品牌热点
  • 2026年饲料颗粒机厂家怎么选?这三点关键别错过 - 天涯视角
  • 【Excel提效 No.074】一句话搞定周数星期自动计算
  • 2026 常州名表专业变现指南|高价参考 + 避坑技巧,一文掌握 - 奢侈品回收测评
  • 从 whisper.cpp 到 PDF 导出:拆解一套离线语音工具中 Vulkan 的“统一加速”与 sherpa-onnx 的“唯一短板”
  • 果树学考研辅导班推荐:专门针对性培训机构评测 - michalwang
  • 半导体制造中OPC技术与蚀刻偏差的挑战与创新
  • 那些转行做DBA的人,后来都怎么样了
  • NGINX服务(六)
  • 2026年收藏5款免费降AI工具:高效去AI痕迹,降低论文AIGC率 - 降AI实验室
  • PowerToys[edge把豆包网页设置为应用程序][使用Win11只有功能为快捷方式分配快捷键][使用PowerToys为快捷方式分配快捷键]
  • Cursor Pro破解工具完整指南:如何免费使用AI编程助手的终极方案
  • 2026年城市更新公司推荐,博涛科技怎么收费 - 工业品牌热点
  • TVA与传统视觉技术的本质区别——以工业视觉检测为例(10)
  • 电磁明渠流量计产品详情,测量原理与性能优势解析 - 陈工日常
  • 保姆级教程:用TensorFlow 2.0从零复现YOLOv5(附完整代码与数据集处理)
  • 终极指南:Windows键盘记录工具 - 从零开始快速掌握
  • 2026年常州高分子材料管业定制化解决方案深度横评:源头工厂直供vs行业竞品全对标 - 优质企业观察收录
  • OpenAI新一代生图模型GPT Image 2 功能解析、使用方法
  • 当半监督学习遇上标签噪声:DivideMix如何巧妙‘变废为宝’?一个生动的比喻解读
  • 门店业绩上报表格模板落地全攻略:7 步打造高效业绩上报体系
  • 每天多出30分钟:让taojinbi自动完成淘宝淘金币和农场任务
  • C语言(8) 函数
  • Java基础全套教程