前言
根据WMS系统基础仓库数据以及RCS调度坐标系统,生成3D可视化仓库地图,能够实时监控仓库库位坐标、调度任务状态、车辆位置等信息。
社区对于threejs的实战案例太少,于是,花了一个月的时间,手撕了这个需求。此篇重点不会对threejs做深入讲解,毕竟我也是刚上车不到一个月的菜鸟。
但是,我会将重点放在项目搭建的思路、设计技巧上,轻松带大家快速的玩转three.js。
后期如果有比较多感兴趣的朋友,再出专文做具体拆解讲解 ✿✿ヽ(°▽°)ノ✿
项目介绍
先看效果吧~ gif图大小有限,长视频可访问: 3D数字孪生-仓库
除了小车模型,所有可见的场景,都是基于代码实现,这也为新手带来更多探索和实战案例,后期再考虑使用blander创建场景和模型。
那么,进入正题吧!
功能介绍
以下是根据项目目标拆解的关键动作,目的是解释我如何进行组件拆解,以便于进行功能组件的详细拆分和设计。
-
场景渲染
灯光渲染、相机渲染、天空盒、建筑物、树…
-
仓库渲染
仓库模型渲染
仓库基础元件:
货架、区域、巷道、货位…、
车辆模型渲染、
任务动画。
技术准备
在开始之前,你得有threejs基本api的了解,特别是场景、灯光、相机、控制器、几何体、材质和纹理。基本上你在准备设计一个东西的时候,自然而然都会去看和了解,所以,没必要刻意的去刷它的api。 在你不知道自己要干什么之前,你去看它的文档,是很劝退的。因为里面涉及到很多与你以往工作毫无相关的知识(对于新手)。
技术架构
接下来才是发力点,希望你能跟得上 😄,如果对于技术层不是特别感兴趣的朋友,可以跳过这个章节。
关于技术架构,不会在这个篇幅展开,粗略的说一下吧。
项目基于 turborepo
与 pnpm
搭建,采用nomorepo
管理策略的一套技术方案,感兴趣的小伙伴可以参考:基于 pnpm + changesets 的 monorepo 最佳实践
重点*: 接下来,只针对组件包中,three
子包做讲解
three子包设计
在设计3D场景中,除了threejs
本身的依赖包外,还使用到的依赖包有: react-three/filber
、react-three/drei
。
这些额外的依赖包有什么好处呢?它是利用了React的虚拟DOM和组件化开发模式,将Three.js的渲染过程封装为React组件,它可以像react-echart一样,申明式的来构建我们的3D组件结构。
这样,我们创建一个3D的场景实例,就可以是这样:
import { Canvas } from '@react-three/fiber';
import BaseSence from './components/BaseSence';
import Factory from './components/Factory';
import Gizmo from './components/Help';
// 创建Canva组件
const Canva = (props) => {
return (
<>
{/* <Tools /> */}
<Canvas
shadows
gl={{
logarithmicDepthBuffer: true,
}}
>
{/* 场景类 */}
<BaseSence />
{/* 工厂类 */}
<Factory />
{props.children}
{/* 帮助类 */}
<Gizmo />
</Canvas>
</>
);
};
export default Canva;
是不是简单极了~,当然,更简单的还在后面。
three包中的目录结构
├─three
| ├─.eslintrc.js
| ├─index.ts
| ├─package.json
| ├─Canva
| | ├─index.tsx
| | ├─components
| | | ├─Help.tsx
| | | ├─index.ts
| | | ├─Tools
| | | | └index.tsx
| | | ├─Factory
| | | | ├─index.tsx
| | | | ├─data
| | | | | ├─animation.ts
| | | | | ├─area.ts
| | | | | ├─conveyerBelt.ts
| | | | | ├─goods.ts
| | | | | ├─shelf.ts
| | | | | ├─stereoscopicShelf.ts
| | | | | └tunnel.ts
| | | | ├─components
| | | | | ├─Ground.tsx
| | | | | ├─WarehouseMap.tsx
| | | | | ├─area.tsx
| | | | | ├─conveyerBelt.tsx
| | | | | ├─forkTruck.tsx
| | | | | ├─fourWayCar.tsx
| | | | | ├─goods.tsx
| | | | | ├─house.tsx
| | | | | ├─lifter.tsx
| | | | | ├─mxwCar.tsx
| | | | | ├─shelf.tsx
| | | | | ├─stereoscopicShelf.tsx
| | | | | ├─stereoscopicTrack.tsx
| | | | | ├─text.tsx
| | | | | ├─tunnel.tsx
| | | | | ├─annotation
| | | | | | ├─index.less
| | | | | | └index.tsx
| | | ├─BaseSence
| | | | ├─index.tsx
| | | | ├─components
| | | | | ├─Buidings.tsx
| | | | | ├─Camera.tsx
| | | | | ├─CtrlPointerLock.tsx
| | | | | ├─Grid.tsx
| | | | | ├─Ground.tsx
| | | | | ├─Lights.tsx
| | | | | ├─Sky.tsx
| | | | | ├─others
| | | | | | ├─Tree.tsx
| | | | | | ├─dance.tsx
| | | | | | └treeGroup.tsx
组件分层比较多,下面我用思维导图介绍下它的结构:
那么接下来,我将细化的展示,每个核心组件,它的作用。
如果Camera=>index.tsx组件中,我们将场景中所有组件去除,它是这样o( ̄︶ ̄)o
import { Canvas } from '@react-three/fiber';
import React, { useState, useContext } from 'react';
import BaseSence from './components/BaseSence';
import Factory from './components/Factory';
import Gizmo from './components/Help';
import { ThreeMobx, ThreeStoreContext, observer } from 'mobx-threejs-store';
import Tools from './components/Tools';
// 创建Canva组件
const Canva = (props) => {
return (
<>
{/* <Tools /> */}
<Canvas
shadows
gl={{
logarithmicDepthBuffer: true,
}}
>
{/* 场景类 */}
{/* <BaseSence /> */}
{/* 工厂类 */}
{/* <Factory /> */}
{props.children}
{/* 帮助类 */}
{/* <Gizmo /> */}
</Canvas>
</>
);
};
export default Canva;
是的没错,你看到的只是一块黑屏,因为你只是声明了一个空间,里面什么都没有。
接下来,我们尝试吧 Gizmo 组件打开,会发生什么呢?
{/* 帮助类 */}
- {/* <Gizmo /> */}
+ <Gizmo />
这时候,在我们的页面右下方,就会出现一个坐标转换器啦。
到此,说明我们对3D的整个场景初始化是成功的,接下来,我们试着在场景中添加灯光与辅助线。
同样,我们在Canvas=>index.tsx中打开如下代码:
{/* 场景类 */}
- {/* <BaseSence /> */}
+ <BaseSence />
在BaseSence=>index.tsx文件中,我们打开灯光与辅助线。
// 基础场景
import React, {useContext} from 'react';
import Camera from './components/Camera';
import GridModule from './components/Grid';
import Lights from './components/Lights';
import SkyBox from './components/Sky';
import Man from './components/others/dance';
import TreeGroup from './components/others/treeGroup';
import Buildings from './components/Buidings';
import { ThreeStoreContext, observer } from 'mobx-threejs-store';
// 创建Canva组件
const BaseSence = () => {
const threeStore = useContext(ThreeStoreContext);
return (
<group>
<Lights />
{/* <SkyBox /> */}
{/* <Camera /> */}
<GridModule />
{/* 其他 */}
{/* <Man /> */}
{/* 建筑 */}
{/* <Buildings /> */}
{/* 树 */}
{/* <TreeGroup /> */}
</group>
);
};
export default observer(BaseSence);
到此为止,我们在场景里面,添加了坐标转换器、辅助线、与灯光。 当然,跟原生 THREEJS
写法不同的是,不用再显现的初始化我们的WebGLRenderer``和PerspectiveCamera
,这些,在我们引用Canvas组件时,就默认帮我们创建了。
当然,在filber
的Canvas
组件中,我们也可以自己扩展覆盖它的默认属性。比如,接下来,我就会自己创建一个相机以及相机控制器,来切换我们的场景控制。
上面的场景,并没有办法自由的切换场景相机,因为默认是没有帮我们初始化相机的控制器的。
我们打开BaseSence=>index.tsx文件中,Camera组件:
- {/* <Camera /> */}
+ <Camera />
这时候,我们的场景就可以动起来啦 ✿✿ヽ(°▽°)ノ✿
看下camera组件中的代码:
// 添加场景相机
import {
CameraControls,
FirstPersonControls,
MapControls,
} from '@react-three/drei';
import React, { useContext, useEffect } from 'react';
import { observer, ThreeStoreContext } from 'mobx-threejs-store';
const Camera = (props: any) => {
const mobxStore = useContext(ThreeStoreContext);
const flyCtrl = () => {
return (
<group>
<FirstPersonControls
far={100000}
movementSpeed={100}
activeLook={false}
lookVertical={true}
></FirstPersonControls>
{/* <OrbitControls /> */}
<MapControls zoomSpeed={0.1} />
</group>
);
};
const ctrl = () => {
return (
<group>
{/* 相机控制器 */}
<CameraControls />
</group>
);
};
return <>{mobxStore.threeStore.cameraCtrls.choiceCtrls === '1' ? ctrl() : flyCtrl()}</>;
};
export default observer(Camera);
是不是比你预想的还要简单~
里面做了个切换的控制,因为在外层,我们设置了一个开关,控制不同的相机控制效果。用于切换CameraControls
与FirstPersonControls
接下来,我们在场景中添加天空盒。 与上面的步骤相同,打开 BaseSence=>index.tsx
- {/* <SkyBox /> */}
+ <SkyBox />
// BaseSence => components => Sky.tsx
import { Sky } from '@react-three/drei';
import React from 'react';
const SkyBox = (props) => {
return (
<Sky distance={450000} sunPosition={[0, 1, 0]} inclination={0} azimuth={0.25} {...props} />
);
};
export default SkyBox;
(▽),一切,似乎都没你想象的那么难,对吧!
其它的都是同理,想要什么,就添加什么,最重要的是,在设计初期,得很清晰的划分好自己的思路和结构,在玩threejs
的过程中,框架与思路搭建,就显现的更明显,有利于我们更好的管理模块的职责和控制。
我的建议是,搭好框架,生产零件,组装,美化再加工,像极了在工地建的框架楼!!!
至此,我们已经搭建好了基础的仓库所需的环境,在建造工厂的过程中,已经迈出了一大步。
后续规划
-
3D数字孪生 - Three.js 项目介绍与基础环境搭建(一)
-
3D数字孪生 - Three.js 项目实战之场景光源(二)
-
3D数字孪生 - Three.js 项目实战之场景材质(三)
-
3D数字孪生 - Three.js 项目实战之相机(四)
-
3D数字孪生 - Three.js 项目实战之相机控制器(五)文章来源:https://www.toymoban.com/news/detail-848135.html
-
3D数字孪生 - 智能仓储管理平台-开源啦💐文章来源地址https://www.toymoban.com/news/detail-848135.html
到了这里,关于3D数字孪生 - Three.js 项目介绍与基础环境搭建(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!