数字孪生指的是采用虚拟仿真技术,将物理实体的全部或部分信息数字化并复制到虚拟世界中,使得物理实体对象的一切运动、行为及系统属性在虚拟空间中得以表现和模拟的技术。其以数据为驱动,构建孪生体模型,继而在数字空间模拟仿真物理系统的演绎过程,进一步在数字空间对孪生系统进行推演和预测。它主要基于遥感、传感器等技术实现,并运用大数据、人工智能、云计算等技术提高其精确度,为智能决策和高效操作提供支撑。本文以物流方向出发,主要从业务角度和应用角度,针对ThreeJS 和 ThingJS 做出技术调研。
近些年来,我国密集出台相关政策以支持数字孪生技术的应用发展,相关政策颁布时间及内容如下:
对于物流行业来说,中转分拣是其普遍面临的复杂场景之一,顺丰科技建立了高度逼真的物流自动化设备孪生体及数字孪生平台,为物流中转场提供了端到端的高度逼真的孪生验证环境,通过优化运营策略,提高了产能,降低了成本,解决了业务中的实际问题,实现了物流中转场的智能运维。通过构建高度真实的虚拟数字孪生体对场地的真实分拣情况进行仿真模拟,平台依靠数据与算法输出了更优的分拣计划,大大提升了中转场地的分拣验证效率。基于数字孪生的全新优化方法,可以在逼真度达99%以上的虚拟孪生体中每天验证数千次,既高效可靠又突破了优化的瓶颈,分拣效果更优。在现实中转场小件分拣机的多个作业班次上验证,可增加超8%的半圈落格的件量,且在相同件量下可缩短超10%的分拣时长,在固定分拣时长下可实际提升超8%的平均产能。
2022年9月顺丰科技还正式聘请了香港中文大学(深圳)的黄川教授作为数字孪生科学家,联合产学研各方的优势资源与专业能力,推动数字孪生技术能力建设往纵深方向发展,在仿真效率、模型逼真度、大规模仿真与优化联合求解等复杂课题上开展先行研究,致力于构建数字孪生优化物流与供应链行业运营效率的新范式。
数字孪生在物流领域的应用尚处在初期阶段,随着政策环境优化、技术演进以及行业标准和体系逐步成熟,数字孪生应用场景广度和深度将进一步拓展,推动行业规模迅速增长。根据IDC数据,预计2025年,全球数字孪生市场规模将增至264.6亿美元,2020-2025年CAGR高达38.35%。数字孪生是智慧物流的必经之路。
供应链管理涉及供应商、制造商、经销商和零售商之间的物流活动,目的是确保产品和服务能够按时提供给消费者。通过数字孪生技术,物流从业者可以建立一个准确的模型,用于监控和管理整个供应链的运作。通过实时数据的监测和分析,可以及时发现和解决供应链中的问题,提高整体运营效率和灵活性。例如,当某个环节出现问题时,数字孪生系统可以快速定位问题所在,并提供相应的解决方案,从而减少供应链中断的时间和风险。
物流行业需要管理大量的运输活动。通过数字孪生技术,物流从业者可以通过虚拟模型来模拟不同运输的效果,从而优化物流运作。例如,可以模拟不同运输路线的成本和效益,选择最优的路线来降低运输成本;
此外,数字孪生技术还可以帮助企业建立一套仓库实时监控系统,监测采用热成像仪、光学传感器、气体传感器等多种传感器装置,可以实时采集仓库内的温度、湿度、气体、光线等信息,并通过数字孪生技术实时地将这些信息反映到数字模型中。这个模型可以反映出仓库内的各种情况,帮助管理者快速了解仓库的运作状态,并采取相应措施。比如,当仓库库存接近上限时,系统会给管理者发送警报,以便及时调整物流计划,或可监测仓库是否有长时间滞留的货物,以便管理者定期清理滞留货物,避免造成仓存空间浪费。
通过对模型仓库的数据分析,可以预测出物流的需求趋势与变化。这样,管理者可以提前调整物流计划,合理安排仓储与运输资源,避免库存积压或库存闲置浪费的情况。同时,通过数字孪生仓库模型的模拟实验,还可以测试不同物流策略的效果,进而制定出最优的物流规划。。
通过虚拟模型,物流从业者可以结合历史数据和实时监测数据进行预测,以便及时调整运输、仓储、人力等各资源安排的计划。此外,数字孪生还可以结合物流大数据和人工智能技术,通过对数据的分析和挖掘,提供更准确的预测和决策支持。预测和优化能力的提升,可以帮助物流企业提前做好准备,降低库存和运输成本,提高客户满意度和市场竞争力。
数字孪生是一个综合的概念,涉及到多个技术领域,以下是数字孪生中常用的十个技术:
Three.js是目前最流行的,基于WebGL的开源3D框架,它可以让开发者轻松地实现复杂的 3D 场景、动画和交互效果。Three.js广泛应用于游戏开发、虚拟现实(VR)和增强现实(AR)项目以及数据可视化等领域。
Three.js 的特点和优势如下:
Three.js 的缺点如下:
Thing.js 的特点和优势如下:
Thing.js 的缺点如下:
https://finemaxdemo.fanruan.com/
https://store.thingjs.com/zeroCode/getProjects
数字孪生在未来物流领域有较大的潜力,物理实体的虚拟数字模型的前端技术实现,根据Three.js 和 Thing.js 的特性:Three.js 更适合创建一个复杂的3D场景或游戏,Thing.js 更适合构建一个适用于物联网设备的可交互3D模型和界面;且根据开发团队的人员配置,中小企业使用Thing.js或许更合适。
ThingJS 3D 可视化开发平台提供在线开发、离线开发两种开发方式。其中离线开发又分离线开发 SDK 版(坐席版)和离线开发网络版。
在线开发VIP,每个VIP支持多名(上限为9人)开发人员共同进行项目开发
ThingJS 离线开发 SDK 版需要一个U盘形式的Key,每个Key仅供一台计算机使用,如果企业有多位 ThingJS 开发人员要投入开发,如果购买多个离线开发 SDK 版授权来支持,这在成本、便捷度两方面都不是特别合适。
ThingJS 离线开发网络版是一个可在局域网环境部署的开发服务器。它通过和开发版本管理工具 Git 结合,理论上可支持不限人数的 ThingJS 开发人员共同进行项目开发。离线开发网络版所支持的本机开发 IDE(VSCode)+Git 方式,在开发习惯跟大部分开发人员的日常工作习惯相同,可让开发人员在开发方式上完成无缝切换。
现因条件限制,下面用在线开发工具进行示例讲解。
官网:https://www.thingjs.com/guide/
点击“创建3D项目“进入如下界面,界面会有官方场景,可以选择官方场景进行修改使用,也可新建自己的项目。
下面以官方案例为基础,在上面进行修改,最终渲染出的结果如下图:
可以通过点击界面上的按钮操作场景中的物体,代码中控制对象时通过id来获取的对象(也可通过其它属性来获取),在界面如下操作中可获取对象的id:
数据对接的方式有四种:
① Ajax:在 ThingJS 在线开发环境中,内置了 JQuery 库,可以直接使用 JQurey 封装的 Ajax 方法进行数据对接;
② JSONP:由于 JQuery 的 Ajax 请求对 JSONP 进行了封装,因此可以直接使用相关方法请求 JSONP 数据,使用 JQuery 的 Ajax 方法 发起多个 jsonp 请求时,回调函数名不要重复(即 jsonpCallback 的设置不要重复),否则可能会导致回调函数 undefined ;
③ WebSocket:WebSocket 的优点在于服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,真正实现了数据的实时双向通信。并且 WebSocket 通信不受同源策略的限制,即不存在跨域问题;
④ MQTT:MQTT是一个轻量级协议,使用MQTT协议的中心是broker(服务器/代理),客户端通过订阅消息和发布消息进行数据交互。使用MQTT方式的步骤如下:
1.直接连接MQTT服务器(需支持websocket访问,Mosquitto支持websocket的配置可自行百度)
2.引用第三方 mqtt库
3.MQTT数据对接
浏览器端的示例如下 (在客户端可调用 end() 方法主动关闭 Mosquitto 连接):
// 引用第三方 mqtt 库,详见 https://github.com/mqttjs/MQTT.jsTHING.Utils.dynamicLoad(['https://www.thingjs.com/static/lib/mqtt.js'], function () {
init();
});
var client = null;
// 创建一个Mosquitto连接
client = mqtt.connect("wss:www.3dmmd.cn:8086");
client.subscribe("/public/TEST/dev1");
// 连接成功后发送数据
client.on("message", function (topic, payload) {
console.log('data:' + payload);
obj.setAttribute("monitorData/温度", payload);
changeColor(obj);
});
// 关闭连接
client.end();
下面我们用Ajax对接方式为例,实现当接口返回temper信息时,将场景中的旗帜变成红色,代码及预览效果如下图:
上面讲了线上开发界面、控制对象、数据对接,完整代码如下:
// 第一步:加载场景代码----------如果这一步漏掉了, 场景预览界面会提示“脚本加载失败”
var app = new THING.App({
url: 'https://www.thingjs.com/static/models/factory', // 场景地址
background: '#000000',
env: 'Seaside',
});
// 创建Thing。可创建多个Thing,创建的Thing都会在场景中展示
var obj = app.create({
name: '烟囱_02',
url: '/api/models/9121e5476dd1496c9896f649853f042c/0/gltf/',
position: [20, 0, 0],
angle: 0,
complete: function () { // 创建Thing完成后的回调
}
});
/**
* on事件用于通知App初始化完成或场景、物体加载完成
*/
app.on('load',function(){
// 1.旗帜的显示与隐藏
var flag = app.query('#6555')[0] //通过id获取对象数组,取第一个为旗帜
new THING.widget.Button('隐藏旗帜',function(){
flag.visible = false
})
new THING.widget.Button('显示旗帜',function(){
flag.visible = true
})
// 2.汽车的移动
var car = app.query('#2271')[0]
new THING.widget.Button('汽车移动',function(){
var path = [[10,0,-10],[10,0,10]]
car.movePath({
orientToPath:true,
path:path,
time:1000
})
})
// 3.对接数据
$.ajax({
type: "get",
url: "https://3dmmd.cn/getMonitorDataById",
data: { "id":1605 },
dataType: "json",
success: function (d) {
if(d.data.temper){
// 将旗帜的颜色变成红色
flag.style.color='red'
}
}
});
})
// 天空效果
var effect = {
showHelper:true, // 显示光源位置
turbidity:13.4, // 光源扩散大小
rayleigh:1.84, // 大气散射
time:16.093, // 时间 [0~24]
beta:299, // 水平角度
};
app.skyEffect = effect;
部署步骤:
2. 选择年度在线部署或季度在线部署后,选择下一步,即可进入支付页面,支付完成部署成功后显示如下:
在线部署项目更新:
为方便开发人员对已部署的项目内容进行更新,ThingJS在线开发平台提供“项目更新”的功能。
点击项目-项目更新-在线部署项目更新-确定即可完成项目更新。
开发人员可将所完成开发的项目离线部署到自己的私有服务器上。这时候就会使用到该项目的“ThingJS项目离线部署包”。
“ThingJS 项目离线部署包”可由已开通“VIP 商业开发者”或购买“离线部署永久授权”的账号从“在线开发”环境进行下载;或由ThingJS离线开发环境生成。
硬件配置
安装服务器推荐配置
(注: (1)若需在CPU为ARM的Linux服务器上进行部署,请先咨询平台客服;
(2)不支持在Docker中进行部署。)
浏览器客户端推荐配置
具体操作参考:https://support.thingjs.com/book/thingjs-lowcode10/63842fcfddc46115804148b9
threejs是一个js库,在项目的开发环境引入threejs,直接通过npm命令行安装就行,为了方便,下面的示例讲解中直接在.html文件中直接引入threejs。
three.js 依赖于ES module,因此任何引用它的script标签必须使用type=“module”。
下面我们创建一个简单的场景,里面是一个绿色的立方体,具体操作步骤及说明均在代码里有注释,代码如下:
DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My first three.js apptitle>
<style>
body { margin: 0; }
style>
head>
<body>
<script type="module">
// 引入three.js
import * as THREE from 'https://unpkg.com/three/build/three.module.js';
// 1. 创建一个场景
const scene = new THREE.Scene();
/**
* 2. 创建一个透视摄像机
* 第一个参数是视野角度(FOV)。视野角度就是无论在什么时候,你所能在显示器上看到的场景的范围,它的单位是角度(与弧度区分开)
* 第二个参数是长宽比(aspect ratio)。 也就是你用一个物体的宽除以它的高的值。
* 第三个参数和第四个参数分别是近截面(near)和远截面(far)。 当物体某些部分比摄像机的远截面远或者比近截面近的时候,该这些部分将不会被渲染到场景中。
*
*/
const camera = new THREE.PerspectiveCamera( 90, window.innerWidth / window.innerHeight, 0.1, 1000 );
// 3. 创建一个渲染器
const renderer = new THREE.WebGLRenderer();
// 4. 设置渲染器的尺寸
renderer.setSize( window.innerWidth, window.innerHeight );
// 5. 这一步非常重要:将renderer的dom元素添加到HTML文档的body中。这就是渲染器用来显示场景给我们看的
document.body.appendChild( renderer.domElement );
// 6. 到这里canvas元素已经准备好了,我们可以往canvas中添加我们想要的模型了
const geometry = new THREE.BoxGeometry( 1, 1, 1 ); // 创建一个立方体的形状,注意这里只是描述了形状,这里还没有物体
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); // 创建一个MeshBasicMaterial材质
const cube = new THREE.Mesh( geometry, material ); // 创建一个Mesh表示一个虚拟的物体,参数就是上面创建的geometry立方体和material材质,这里才有了物体
// 7. 把物体添加到场景中,调用scene.add()的时候,物体将会被添加到(0,0,0)坐标,此时摄像机和立方体重叠在一起的
scene.add( cube );
// 8. 调整摄像机的坐标,不然摄像机和物体重叠,看不见物体
camera.position.set(3,-1,5) // x,y,z
// 9. 渲染场景
renderer.render( scene, camera );
script>
body>
html>
通过three.js的控制器对物体模型进行操控,下面以拖放控制器(DragControls)作为例子来操作一下物体。
因为我这里下载three.js文件包太慢了,之前有个vue项目安装了three.js,所以下面改用vue项目中使用three.js做为例子。
DragControls 是一个附加组件,必须显式导入。
// 引入 DragControls
import { DragControls } from "three/addons/controls/DragControls.js";
/** 1.创建一个拖拽控制器
* 第一个参数就是我们上面创建的立方体,该参数是一个数组;
* 第二个参数就是我们上面创建的相机;
* 第三个参数就是我们上面创建的渲染器的dom元素
*/
const controls = new DragControls([cube], camera, renderer.domElement);
// 2.添加事件侦听器
controls.addEventListener("drag", function () {
renderer.render(scene, camera);
});
如上就实现了一个非常简单的拖拽物体效果。
如果想要实现常规的通过点击物体来操作物体,那么我们就要需要先了解一下三维空间点击事件的原理:
在三维空间内判断鼠标点击的是哪个模型,核心的原理是射线碰撞,即从相机(camera)的中心点到屏幕上鼠标点组成一条射线,计算三维场景内哪些模型被射线穿过。主要是涉及了三个坐标系的转换:
<script>
import * as THREE from "three";
import { DragControls } from "three/addons/controls/DragControls.js";
export default {
name: "threejs-test",
data() {
return {
scene: null,
camera: null,
};
},
mounted() {
// ---------------------- 第一部分内容:创建场景 --------------------------
// 1. 创建一个场景
const scene = new THREE.Scene();
this.scene = scene;
/**
* 2. 创建一个透视摄像机
* 第一个参数是视野角度(FOV)。视野角度就是无论在什么时候,你所能在显示器上看到的场景的范围,它的单位是角度(与弧度区分开)
* 第二个参数是长宽比(aspect ratio)。 也就是你用一个物体的宽除以它的高的值。
* 第三个参数和第四个参数分别是近截面(near)和远截面(far)。 当物体某些部分比摄像机的远截面远或者比近截面近的时候,该这些部分将不会被渲染到场景中。
*
*/
const camera = new THREE.PerspectiveCamera(
90,
window.innerWidth / window.innerHeight,
0.1,
1000
);
this.camera = camera;
// 3. 创建一个渲染器
const renderer = new THREE.WebGLRenderer();
// 4. 设置渲染器的尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
// 5. 这一步非常重要:将renderer的dom元素添加到HTML文档的body中。这就是渲染器用来显示场景给我们看的
document.body.appendChild(renderer.domElement);
// 6. 到这里canvas元素已经准备好了,我们可以往canvas中添加我们想要的模型了
const geometry = new THREE.BoxGeometry(1, 1, 1); // 创建一个立方体的形状,注意这里只是描述了形状,这里还没有创建物体
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // 创建一个MeshBasicMaterial材质
const cube = new THREE.Mesh(geometry, material); // 创建一个Mesh表示一个虚拟的物体,参数就是上面创建的geometry立方体和material材质,这里才有了物体
// 7. 把物体添加到场景中,调用scene.add()的时候,物体将会被添加到(0,0,0)坐标,此时摄像机和立方体彼此在一起
scene.add(cube);
// 8. 调整摄像机的坐标,不然摄像机和物体重叠,看不见物体
camera.position.set(3, -1, 5); // x,y,z
// 9. 渲染场景
// renderer.render(scene, camera);
(function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
})();
// ---------------------- 第二部分内容: 控制对象 --------------------------
// 1.利用控制器操控对象
const controls = new DragControls([cube], camera, renderer.domElement); // 创建一个拖拽控制器
// 添加事件侦听器
controls.addEventListener("drag", function () {
renderer.render(scene, camera);
});
// 2.通过点击操作对象
const _this = this;
document
.getElementsByTagName("body")[0]
.addEventListener("click", function (event) {
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
//将鼠标点击位置的屏幕坐标转换成threejs中的标准坐标
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = 1 - (event.clientY / window.innerHeight) * 2;
// 通过鼠标点的位置和当前相机的矩阵计算出raycaster
raycaster.setFromCamera(mouse, _this.camera);
// 获取raycaster直线和所有模型相交的数组集合
var intersects = raycaster.intersectObjects(_this.scene.children);
//将所有的相交的模型(被点击的模型)的颜色设置为红色
for (var i = 0; i < intersects.length; i++) {
intersects[i].object.material.color.set(0xff0000);
}
});
},
};
</script>
注意第49行——52行渲染场景的代码有所调整,应用了requestAnimationFrame()方法,它可以保证合适的时间触发renderer.render()方法,一般为 60次/秒,如果不应用requestAnimationFrame(),你可能会在点击物体后看不到任何变化。
因上面仅是示例代码,故所有功能都写到mounted生命周期中的,平时项目开发中,还是要进行函数的封装。
由于three.js是一个js库,所以它的数据对接及项目部署同一般项目一样,无需特殊操作。