Vue3 + Three.js + gltf-pipeline大型园区场景渲染与3D业务

news/2024/11/16 19:13:02/

在非使用unity作为3D渲染方案的前提下,对与目前web开发者比较友好的除了canvas场景需要的2D babylon.jsfabric.js, Three.js是目前针对于jsWeb用户最直接且比较友好的3D引擎方案了。
准备工作:
1.明确需要用的场景方案都有那些,模型需要的加载器是什么
2.模型的场景大小已经相关的交互业务
3.场景的工作环境(浏览器及硬件要求)
step1:
以.glb模型为例

import * as THREE from "three";
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader";
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";

以上就是一个场景绘制需要的基本3个要素
模型压缩
由于建模工程师因为场景规模的原因模型在建立时过多了使用了面,导致整个模型的体积很大,一个校区或者大园区为例,建筑加环境要素及周边地形都整体的模型体积已经达到了100M+,这个时候就需要我们在开发前就考虑模型的压缩问题了
DRACO压缩算法

npm install -g gltf-pipeline
--input, -i                   Path to the glTF or glb file.[string] [required]
--output, -o                  Output path of the glTF or glb file. Separate   resources will be saved to the same directory.  [string]
--binary, -b                  Convert the input glTF to glb.                                      //将输入的glTF转换为glb[boolean] [default: false]
--json, -j                    Convert the input glb to glTF.                                      //将输入的glb转换为glTF[boolean] [default: false]
--separate, -s                Write separate buffers, shaders, and textures                       //编写单独的缓冲区、着色器和纹理而不是把它们嵌入到glTF中instead of embedding them in the glTF.          [boolean] [default: false]
--separateTextures, -t        Write out separate textures only.                                   //只写出单独的纹理[boolean] [default: false]
--stats                       Print statistics to console for output glTF                         //将统计信息打印到控制台以输出glTF文件file.                 [boolean] [default: false]     
--keepUnusedElements          Keep unused materials, nodes and meshes.                            //保留未使用的材质、节点和网格[boolean] [default: false]
--draco.compressMeshes, -d    Compress the meshes using Draco. Adds the                            //使用Draco压缩网格。添加KHR_draco_mesh_压缩扩展KHR_draco_mesh_compression extension.[boolean] [default: false]
--draco.compressionLevel      Draco compression level [0-10], most is 10,                           //Draco压缩级别[0-10],大多数是10,最小值为0。值为0将会连续应用 编码并保留face顺序。least is 0. A value of 0 will apply sequentialencoding and preserve face order.[number] [default: 7]   
--draco.quantizePositionBits  Quantization bits for position attribute when                        //位置坐标属性的量化位使用Draco压缩。using Draco compression.  [number] [default: 11]--draco.quantizeNormalBits    Quantization bits for normal attribute when                           //法线属性的量化位使用Draco压缩using Draco compression.   [number] [default: 8]--draco.quantizeTexcoordBits  Quantization bits for texture coordinate                               //纹理坐标的量化位属性。attribute when using Draco compression.[number] [default: 10]--draco.quantizeColorBits     Quantization bits for color attribute when using                        //使用时颜色属性的量化位德拉科压缩Draco compression.         [number] [default: 8]--draco.quantizeGenericBits   Quantization bits for skinning attribute (joint                        //蒙皮属性(关节的量化位索引和关节权重)ad自定义属性使用Draco压缩时。indices and joint weights) ad custom attributeswhen using Draco compression. [number] [default: 8]--draco.uncompressedFallback  Adds uncompressed fallback versions of the                            //添加未压缩的回退版本压缩网格compressed meshes.    [boolean] [default: false]--draco.unifiedQuantization   Quantize positions of all primitives using the            //统一定义的量化网格所有基本体的边界框。 如果这个选项未设置,对每个应用量化原始的可能会导致差距出现在不同图元之间。same quantization grid defined by the unifiedbounding box of all primitives. If this optionis not set, quantization is applied on eachprimitive separately which can result in gapsappearing between different primitives.[boolean] [default: false]

在这里插入图片描述
gltf-pipeline的参数有很多这里我们只需要提炼出一个满足我们需要的就够了

gltf-pipeline -i .\public\tep\23.glb -o .\public\tep\23-main.glb  -d --draco.compressionLevel 9 --draco.quantizePositionBits 10 --draco.quantizeColorBits 10
-i .\public\cascl\caa4.glb   //输入路径-o .\public\cascl\caa4-main.glb  //输出路径及名称-d --draco.compressionLevel 10 //压缩等级--draco.quantizePositionBits 20  //量化  0 标识无损压缩 

需要注意的是安装好之后是不可以直接运行的我们需要一个three.js为我们提供的基本依赖draco_decoder.js 这个文件一般就放在node_module/three/examples/js/libs/draco目录下cpoy出来与模型文件一起放在public文件夹下即可

完成上述这些准备工作之后我们开始渲染我们的第一个大园区场景,因为我们使用了压缩算法所以我们需要额外再引入一个解压加载器,并将我们copy出来的draco_decoder.js文件与我们压缩好的模型都放在public下

import {DRACOLoader} from "three/examples/jsm/loaders/DRACOLoader"

step2
初始一个加载模型的方法

export const initMod=(id,filePath,fun)=>{container=document.getElementById(id);scene = new THREE.Scene();camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);renderer = new THREE.WebGLRenderer({ antialias: true,alpha: true });renderer.setSize(container.clientWidth, container.clientHeight);container.appendChild(renderer.domElement);renderer.setClearColor('#6fc0ec', 1.0);renderer.outputEncoding = THREE.sRGBEncoding;const loader = new GLTFLoader();let dracoLoader = new DRACOLoader();dracoLoader.setDecoderPath("/cascl/"); // 设置public下的解码路径,注意最后面的/dracoLoader.setDecoderConfig({ type: "js" });dracoLoader.preload();loader.setDRACOLoader(dracoLoader);loader.load(filePath,gltf => {// 将模型放到中间const box = new THREE.Box3().setFromObject(gltf.scene);const size = box.getSize(new THREE.Vector3()).length();const center = box.getCenter(new THREE.Vector3());gltf.scene.position.x -= center.x;gltf.scene.position.y -= center.y;gltf.scene.position.z -= center.z;camera.near = size / 100;camera.far = size * 100;camera.updateProjectionMatrix();camera.position.copy(center);camera.position.x += size / 2;camera.position.y += size / 2;camera.position.z += size / 2;camera.lookAt(center);scene.add(gltf.scene);console.log('---加载的模型',gltf.scene)const ambient = new THREE.AmbientLight(0xffffff, 0.4);scene.add(ambient);//添加在模型的右上角高三倍设置一个光源 太阳const light = new THREE.DirectionalLight(0xffffff, 1);// 模型宽度const width = box.max.x - box.min.x;// 模型高度const height = box.max.y - box.min.y;// 模型深度const depth = box.max.z - box.min.z;light.position.set(width * 3, height * 3, depth * 3);scene.add(light);
// 点光源let point = new THREE.PointLight('#74beee',1);point.position.set(-width * 3, -height * 3, depth * 3); // 点光源位置scene.add(point); // 点光源添加到场景中//多设置几个光源const light3 = new THREE.DirectionalLight('#8dccee', 1);light3.position.set(-width * 3, -height * 3, depth * 3);scene.add(light3);const light4 = new THREE.HemisphereLight('#8dccee', 0.3);scene.add(light4);//包含关键帧动画的模型作为参数创建一个播放器mixer = new THREE.AnimationMixer(gltf.scene);//  获取gltf.animations[0]的第一个clip动画对象clipAction = mixer.clipAction(gltf.animations[0]); //创建动画clipAction对象clipAction.play(); //播放动画
//不循环播放clipAction.loop = THREE.LoopOnce;
// 物体状态停留在动画结束的时候clipAction.clampWhenFinished = true// 如果想播放动画,需要周期性执行`mixer.update()`更新AnimationMixer时间数据clock = new THREE.Clock();},undefined,error => {console.error(error);});camera.position.z = 5;// 添加OrbitControls控制器controls = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true;controls.dampingFactor = 0.05;controls.screenSpacePanning = false;controls.minDistance = 1;controls.maxDistance = 1000;const tag = labelTag({x: -580,y:50,z: -705});tagList.push(tag)scene.add(tag);//添加到指定的场景里renderLabel()animate();runLoop()renderer.domElement.addEventListener('click', handleModClick, false)
}

上方的初始方案中包含了一个基本的动画加载器由此我们可以完成一个基本的模型加载的场景创建
在这里插入图片描述
这是一个可以执行楼层分层爆炸的模型内置的动画由:

   //包含关键帧动画的模型作为参数创建一个播放器mixer = new THREE.AnimationMixer(gltf.scene);

完成捕捉及后续播放
step3
接下来我们完成园区的业务需求建设
1.场景需要有天空背景
2.场景需要有关键建筑的标注
3.场景的交互具有高亮环绕
4.场景具有漫游功能
基于以上我们开始增加需要的工具

  1. 场景漫游动画处理库
import TWEEN from '@tweenjs/tween.js';

2.场景天空环境即天空盒

    const urls = ['../sky/Above Day B_Cam_3_Right-X.png',//x正方形'../sky/Above Day B_Cam_2_Left+X.png',//x负方向'../sky/Above Day B_Cam_4_Up+Y.png',//y正方形'../sky/Above Day B_Cam_5_Down-Y.png',//y负方向'../sky/Above Day B_Cam_0_Front+Z.png',//z正方形'../sky/Above Day B_Cam_1_Back-Z.png'//z负方形]const textureCube = new THREE.CubeTextureLoader().load(urls)scene.background = textureCube

3.场景后处理器

import {EffectComposer} from 'three/examples/jsm/postprocessing/EffectComposer';
import {RenderPass} from 'three/examples/jsm/postprocessing/RenderPass';
import {OutlinePass} from 'three/examples/jsm/postprocessing/OutlinePass';

在使用了后处理器后因为模型抗拒齿原因我们需要在额外补充一个

import {SMAAPass} from 'three/examples/jsm/postprocessing/SMAAPass';//抗锯齿后处理const smaaPass = new SMAAPass(container.clientWidth * pixelRatio, container.clientHeight * pixelRatio);

4.一个漫游动画控制的方法

export const createCameraTween = (pos2, pos) => {tween = new TWEEN.Tween({// 相机开始坐标x: camera.position.x,y: camera.position.y,z: camera.position.z,// 相机开始指向的目标观察点tx:  current.x,ty:   current.y,tz:   current.z,}).to({// 相机结束坐标x: pos.x,y: pos.y,z: pos.z,// 相机结束指向的目标观察点tx: pos.x,ty: pos.y,tz: pos.z,}, 2000).onUpdate(function (obj) {// 动态改变相机位置camera.position.set(obj.x, obj.y, obj.z);// 动态计算相机视线camera.lookAt(pos.x, pos.y, -pos.z);}).start();animates();
}
const animates = (time) => {TWEEN.update(time);requestAnimationFrame(animates);
}

在使用glb/gltf模型中我们也常常会需要处理模型加载发暗,材质渲染失真的情况这里我们也一并加入到初始化的方案内

import {GammaCorrectionShader} from'three/examples/jsm/shaders/GammaCorrectionShader';
import {ShaderPass} from 'three/examples/jsm/postprocessing/ShaderPass';
import {RoomEnvironment} from 'three/examples/jsm/environments/RoomEnvironment';

由此我们以及基本完成了所有的加载需要的必备条件即要求,我们渲染一个大园区场景
在这里插入图片描述
在这里插入图片描述
场景后处理的效果业务由:

const renderOutline = (mod) => {if (buildIds.includes(mod.name)) {// 创建后处理对象EffectComposer,WebGL渲染器作为参数composer = new EffectComposer(renderer);renderPass = new RenderPass(scene, camera);composer.addPass(renderPass);
// 创建OutlinePass通道container = document.getElementById('mod') ? document.getElementById('mod') : document.getElementById('mod2');const v2 = new THREE.Vector2(container.clientWidth, container.clientHeight);const outlinePass = new OutlinePass(v2, scene, camera);// 创建伽马校正通道const gammaPass = new ShaderPass(GammaCorrectionShader);composer.addPass(gammaPass);const pixelRatio = renderer.getPixelRatio()//抗锯齿后处理const smaaPass = new SMAAPass(container.clientWidth * pixelRatio, container.clientHeight * pixelRatio);composer.addPass(smaaPass);outlinePass.selectedObjects = [mod];outlinePass.visibleEdgeColor.set('#a838ef');outlinePass.edgeThickness = 4;outlinePass.edgeStrength = 15;outlinePass.pulsePeriod = 3;composer.addPass(outlinePass);animateOutline()bus.$emit('showMod', mod)} else {clearOutline();}
}

由于业务延展很多不再过的的赘述,解决方案包含了动态标签切换,标记交互,灯光动态,场景灯光随相机,标签随相机,场景模型过滤,场景模型设备等状态动态更新,单楼层模型,室内模型控制切换即各类物联网设备交互等等。


http://www.ppmy.cn/news/1215752.html

相关文章

微信小程序用户隐私API

用户隐私保护 由于用户隐私保护的政策执行,我们在调用涉及到用户隐私的API时,未更新用户隐私保护协议是无法直接调用的,小程序会默认判断是否更新用户隐私保护 ,并根据用户隐私保护中的协议来判断是否可以调用对应的API&#xff…

Java面向对象(进阶)-- Object类的详细概述

文章目录 一、如何理解根父类二、 Object类的方法(1)引子(2)Object类的说明 三、了解的方法(1)clone( )1、介绍2、举例 (2)finalize( )1、介绍2、举例 (3)get…

2023亚太杯数学建模B题思路解析

文章目录 0 赛题思路1 竞赛信息2 竞赛时间3 建模常见问题类型3.1 分类问题3.2 优化问题3.3 预测问题3.4 评价问题 4 建模资料5 最后 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 1 竞赛信息 2023年第十三…

采集标准Docker容器日志:部署阿里云Logtail容器以及创建Logtail配置,用于采集标准Docker容器日志

文章目录 引言I 预备知识1.1 LogtailII 查询语法2.1 具体查询语法2.2 查询示例2.3 设置token时间(登录过期时间)see also引言 I 预备知识 1.1 Logtail Logtail是日志服务提供的日志采集Agent,用于采集阿里云ECS、自建IDC、其他云厂商等服务器上的日志。本文介绍Logtail的功…

Zigbee智能家居方案设计

背景 目前智能家居物联网中最流行的三种通信协议,Zigbee、WiFi以及BLE(蓝牙)。这三种协议各有各的优势和劣势。本方案基于CC2530芯片来设计,CC2530是TI的Zigbee芯片。 网关使用了ESP8266CC2530。 硬件实物 节点板子上带有继电器…

五、L2TPv2 VPN

L2TPv2 VPN 1、L2TPv2概述1.1.目的1.2.特点 2、L2TP原理2.1.基本概念2.2.工作原理2.2.1.协议架构2.2.2.报文结构2.2.3.报文封装2.2.4.报文传输 3、工作过程4、应用场景4.1、远程拨号用户发起L2TP隧道连接4.2、LAC接入拨号请求发起L2TP隧道连接4.3、LAC接入PPPoE用户发起L2TP隧道…

Qt执行带参sql

//准备执行的sql语句,此为带参的sql语句query.prepare("update employee set Name:Name, Gender:Gender,Height:Height,"" Birthday:Birthday, Mobile:Mobile, Province:Province,"" City:City, Department:Department, Education:Educati…

ubuntu 怎么安装图形界面

ubuntu 安装图形界面的方法,可以通过以下步骤操作来实现: 1、确认版本首先登录一下服务纯缺器ubuntu,查看系统版本。然后用root账号登录,如下图所示: 2、更新apt-get首先要先更新一下apt-get源,输入apt-g…