3D数字孪生 – 项目介绍与基础环境搭建(一)

前言

根据WMS系统基础仓库数据以及RCS调度坐标系统,生成3D可视化仓库地图,能够实时监控仓库库位坐标、调度任务状态、车辆位置等信息。

社区对于threejs的实战案例太少,于是,花了一个月的时间,手撕了这个需求。此篇重点不会对threejs做深入讲解,毕竟我也是刚上车不到一个月的菜鸟。

image.png

但是,我会将重点放在项目搭建的思路、设计技巧上,轻松带大家快速的玩转three.js。

后期如果有比较多感兴趣的朋友,再出专文做具体拆解讲解 ✿✿ヽ(°▽°)ノ✿

项目介绍

先看效果吧~ gif图大小有限,长视频可访问: 3D数字孪生-仓库

20230616110946_rec_.gif

除了小车模型,所有可见的场景,都是基于代码实现,这也为新手带来更多探索和实战案例,后期再考虑使用blander创建场景和模型。

那么,进入正题吧!

功能介绍

以下是根据项目目标拆解的关键动作,目的是解释我如何进行组件拆解,以便于进行功能组件的详细拆分和设计。

  1. 场景渲染

    灯光渲染、相机渲染、天空盒、建筑物、树…

  2. 仓库渲染

    仓库模型渲染

    仓库基础元件:
    货架、区域、巷道、货位…、
    车辆模型渲染、
    任务动画。

技术准备

在开始之前,你得有threejs基本api的了解,特别是场景、灯光、相机、控制器、几何体、材质和纹理。基本上你在准备设计一个东西的时候,自然而然都会去看和了解,所以,没必要刻意的去刷它的api。 在你不知道自己要干什么之前,你去看它的文档,是很劝退的。因为里面涉及到很多与你以往工作毫无相关的知识(对于新手)。

技术架构

接下来才是发力点,希望你能跟得上 ?,如果对于技术层不是特别感兴趣的朋友,可以跳过这个章节。

image.png

关于技术架构,不会在这个篇幅展开,粗略的说一下吧。

项目基于 turborepopnpm 搭建,采用nomorepo管理策略的一套技术方案,感兴趣的小伙伴可以参考:基于 pnpm + changesets 的 monorepo 最佳实践


重点*: 接下来,只针对组件包中,three子包做讲解


three子包设计

在设计3D场景中,除了threejs本身的依赖包外,还使用到的依赖包有: react-three/filberreact-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

组件分层比较多,下面我用思维导图介绍下它的结构:

image.png

那么接下来,我将细化的展示,每个核心组件,它的作用。

如果Camera=>index.tsx组件中,我们将场景中所有组件去除,它是这样o( ̄︶ ̄)o

image.png

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 组件打开,会发生什么呢?

image.png

{/* 帮助类 */}
- {/* <Gizmo /> */}
+ <Gizmo />

这时候,在我们的页面右下方,就会出现一个坐标转换器啦。

到此,说明我们对3D的整个场景初始化是成功的,接下来,我们试着在场景中添加灯光与辅助线。

image.png

同样,我们在Camera=>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组件时,就默认帮我们创建了。

当然,在filberCanvas组件中,我们也可以自己扩展覆盖它的默认属性。比如,接下来,我就会自己创建一个相机以及相机控制器,来切换我们的场景控制。

上面的场景,并没有办法自由的切换场景相机,因为默认是没有帮我们初始化相机的控制器的。

我们打开BaseSence=>index.tsx文件中,Camera组件:

    - {/* <Camera /> */}
    + <Camera />

20230616103608_rec_.gif

这时候,我们的场景就可以动起来啦 ✿✿ヽ(°▽°)ノ✿

看下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);

是不是比你预想的还要简单~

里面做了个切换的控制,因为在外层,我们设置了一个开关,控制不同的相机控制效果。用于切换CameraControlsFirstPersonControls

接下来,我们在场景中添加天空盒。 与上面的步骤相同,打开 BaseSence=>index.tsx

- {/* <SkyBox /> */}
+ <SkyBox />

20230616104847_rec_.gif

// 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的过程中,框架与思路搭建,就显现的更明显,有利于我们更好的管理模块的职责和控制。

我的建议是,搭好框架,生产零件,组装,美化再加工,像极了在工地建的框架楼!!!


至此,我们已经搭建好了基础的仓库所需的环境,在建造工厂的过程中,已经迈出了一大步。

水了一个晚上文,代码中有些文中并没有讲解的代码,可能会造成一些误解,在接下来的文章都会涉及到。

喜欢的朋友可以一键三连啦~ 鼓励我继续接下来的创作吧 (#^.^#)。

image.png

后续规划

持续补充…

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYAk12Tq' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片