工厂3D可视化系统

175 阅读2分钟

效果展示

image.png

代码展示

1.html

<template>
  <div>
    <div class="title">工厂3D可视化系统</div>
    <div class="import_model"></div>
  </div>
</template>

2.js 常见搭建以及效果的实现

<script setup>
import { reactive, ref, nextTick, onMounted, watch, onUnmounted } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
import TWEEN from "@/common/js/libs/Tween.min.js";
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";

let scene, mesh, camera, renderer, axesHelper, grid, controls;
var width = window.innerWidth; //窗口宽度
var height = window.innerHeight; //窗口高度
const publicPath = process.env.BASE_URL;
// 该对象用于跟踪时间
let clock = new THREE.Clock();
let gui = new GUI();
let guiCtrl = {};
let weatherFolder;
let rainy_sw = 1; //1雨 2雪 3晴 4阴
let weather = ["rainy", "snowy", "sunny", "cloudy"];
let flow_sw = true;
let group,
  speed = 0.5,
  isFire = false,
  fireColor = "#eb402d";
let urls, urls1;
// 初始化一个空容器,装载粒子
let krq = new THREE.Object3D();
// 加载材质
let textureLoader = new THREE.TextureLoader();


onMounted(() => {
  init();
});

</script>

2.1 初始化

function init() {
  // 创建场景
  createScene();

  // 创建光源
  createLight();
  // 创建相机
  createCamera(width, height);
  // 设置背景
  createBackground();
  // 创建渲染器
  createRender();

  // 创建控制器
  createControls();
  // createGrid();
  // 辅助坐标系
  createAxesHelper();
  // 将平面添加到场景中
  createPlaneGeometryBasicMaterial();
  // 创建并复制房屋
  createBoxGeometryBasicMaterial();
  // 侧边的路
  creatRoadSurface();
  // 创建圆仓大
  createRoundGeometryBasicMaterialMax();
  // 创建圆仓小
  createRoundGeometryBasicMaterialMin();
  // 创建围栏
  crateWall();
  initDataGUI();
  // 创建粒子
  if (rainy_sw < 3) {
    createPointRainy();
  }
  // 渲染
  render();
}

创建场景

function createScene() {
  //创建一个三维场景
  scene = new THREE.Scene();
}

创建光源

function createLight() {
  //点光源
  var point = new THREE.PointLight(0xffffff);
  point.position.set(400, 200, 300); //点光源位置
  scene.add(point); //点光源添加到场景中
  //环境光
  // var ambient = new THREE.AmbientLight(0x444444);
  var ambient = new THREE.AmbientLight("#ffffff", 1);
  scene.add(ambient);
}

创建相机

function createCamera(width, height) {
  var k = width / height; //窗口宽高比
  var s = 200; //三维场景显示范围控制系数,系数越大,显示的范围越大
  // //创建相机对象
  // 正交相机
  // camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
  // 透视相机(此处需要全景和透视)
  camera = new THREE.PerspectiveCamera(
    45,
    window.innerWidth / window.innerHeight,
    0.1,
    20000
  );
  camera.position.set(200, 300, 200); //设置相机位置
  camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
}

设置背景

function createBackground() {
  // 晴空万里
  urls = [
    "model/textures/posx.jpg",
    "model/textures/negx.jpg",
    "model/textures/posy.jpg",
    "model/textures/negy.jpg",
    "model/textures/posz.jpg",
    "model/textures/negz.jpg",
  ];
  // 雾茫茫
  urls1 = [
    "model/textures/posx_gray.jpg",
    "model/textures/negx_gray.jpg",
    "model/textures/posy_gray.jpg",
    "model/textures/negy_gray.jpg",
    "model/textures/posz_gray.jpg",
    "model/textures/negz_gray.jpg",
  ];
  // rainy_sw  1雨 2雪 3晴 4阴
  // console.log("rainy_sw",rainy_sw)
  let url = rainy_sw == 3 ? urls : urls1;
  // 注意要使用透视相机(PerspectiveCamera),而非正交相机(OrthographicCamera)
  scene.background = new THREE.CubeTextureLoader().load(url);

  // 方法二
  /* scene.background = new THREE.CubeTextureLoader()
    .setPath(`${publicPath}model/textures/`)
    .load([
      "posx.jpg",
      "negx.jpg",
      "posy.jpg",
      "negy.jpg",
      "posz.jpg",
      "negz.jpg",
    ]); */

  /*  const loader = new THREE.TextureLoader();
  const bgTexture = loader.load("/model/textures/negz.jpg");
  scene.background = bgTexture; */
}

创建渲染器

function createRender() {
  renderer = new THREE.WebGLRenderer();
  renderer.setSize(width, height); //设置渲染区域尺寸
  // renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色
  // renderer.setClearColor(0xffffff, 1); //设置背景颜色
  renderer.outputEncoding = THREE.sRGBEncoding; // 模型颜色偏差
  renderer.shadowMap.enabled = true; //开启渲染气阴影效果。
  nextTick(() => {
    document.querySelector(".import_model").appendChild(renderer.domElement);
  });
}

创建控制器


function createControls() {
  //创建控件对象  相机对象camera作为参数   控件可以监听鼠标的变化,改变相机对象的属性
  controls = new OrbitControls(camera, renderer.domElement);
  //监听鼠标事件,触发渲染函数,更新canvas画布渲染效果
  controls.addEventListener("change", () => {
    renderer.render(scene, camera);
  });
}
function createAxesHelper() {
  axesHelper = new THREE.AxesHelper(250);
  scene.add(axesHelper);
}
function createPlaneGeometryBasicMaterial() {
  // 创建材质
  var material = new THREE.MeshBasicMaterial({
    map: textureLoader.load("/model/textures/floor3.png"),
    transparent: true, //多余的透明隐藏
    side: THREE.DoubleSide,
  });
  // 创建地面
  var geometry = new THREE.PlaneGeometry(270, 260);
  var plane = new THREE.Mesh(geometry, material);
  // 设置平面位置并旋转
  plane.rotation.x = -0.5 * Math.PI;
  plane.position.y = -0.1;
  scene.add(plane);
}

辅助坐标系

function createAxesHelper() {
  axesHelper = new THREE.AxesHelper(250);
  scene.add(axesHelper);
}

将平面添加到场景中

function createPlaneGeometryBasicMaterial() {
  // 创建材质
  var material = new THREE.MeshBasicMaterial({
    map: textureLoader.load("/model/textures/floor3.png"),
    transparent: true, //多余的透明隐藏
    side: THREE.DoubleSide,
  });
  // 创建地面
  var geometry = new THREE.PlaneGeometry(270, 260);
  var plane = new THREE.Mesh(geometry, material);
  // 设置平面位置并旋转
  plane.rotation.x = -0.5 * Math.PI;
  plane.position.y = -0.1;
  scene.add(plane);
}

image.png

创建并复制房屋

function createBoxGeometryBasicMaterial() {
  const loader = new OBJLoader();
  loader.load(`/model/textures/003.obj`, function (obj) {
    // console.log("obj", obj);
    var mesh = obj.children[0];
    // 网格材质
    mesh.material = new THREE.MeshBasicMaterial({
      map: textureLoader.load("/model/textures/003.png"),
    });
    mesh.scale.set(1.3, 1.4, 1.5);
    mesh.position.set(11, 0, -85);
    scene.add(mesh);
    for (let i = 0; i < 2; i++) {
      for (let j = 0; j < 3; j++) {
        var mc = mesh.clone();
        scene.add(mc);
        mc.translateX(i * 52);
        mc.translateZ(j * 83);
      }
    }
  });
}

image.png

添加侧边的路


function creatRoadSurface() {
  // 平面几何体
  var geometry = new THREE.PlaneGeometry(24, 280);
  var texture = textureLoader.load("/model/textures/road2.png");
  var material = new THREE.MeshBasicMaterial({
    map: texture,
    side: THREE.DoubleSide,
  });
  texture.wrapS = THREE.RepeatWrapping;
  texture.wrapT = THREE.RepeatWrapping;
  texture.repeat.set(1, 10);
  var mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);
  mesh.rotateX(-Math.PI / 2);
  mesh.position.x = 100.5;
  // console.log("material", material);
}

image.png

创建圆仓大

function createRoundGeometryBasicMaterialMax() {
  const loader = new OBJLoader();
  loader.load(`/model/textures/gong001.obj`, function (obj) {
    // console.log("obj", obj);
    var mesh = obj.children[0];
    // 网格材质
    mesh.material = new THREE.MeshBasicMaterial({
      map: textureLoader.load("/model/textures/d001.png"),
      transparent: true,
      side: THREE.DoubleSide,
      clipIntersection: true,
    });
    mesh.rotateZ(Math.PI);
    mesh.position.set(-40, 36, -105);
    scene.add(mesh);
    for (let i = 0; i < 2; i++) {
      for (let j = 0; j < 3; j++) {
        var mc = mesh.clone();
        scene.add(mc);
        mc.translateX(i * 28);
        mc.translateZ(j * 20);
      }
    }
  });
}

image.png

创建圆仓小

function createRoundGeometryBasicMaterialMin() {
  const loader = new OBJLoader();
  loader.load(`/model/textures/002.obj`, function (obj) {
    var mesh = obj.children[0];
    mesh.material = new THREE.MeshBasicMaterial({
      map: new THREE.TextureLoader().load("/model/textures/002.png"),
      transparent: true,
      side: THREE.DoubleSide,
      clipIntersection: true,
    });
    mesh.rotateZ(Math.PI);
    mesh.position.set(-40, 20, -19);
    for (let i = 0; i < 2; i++) {
      for (let j = 0; j < 6; j++) {
        var mc = mesh.clone();
        mc.translateX(i * 28);
        mc.translateZ(j * 24);
        scene.add(mc);
      }
    }
  });
}

image.png

创建围栏

function crateWall() {
  const loader = new OBJLoader();
  loader.load(`/model/textures/wall.obj`, function (obj) {
    // console.log(obj);
    obj.scale.set(0.98, 0.6, 1);
    var texLan = textureLoader.load("/model/textures/lan2.png");
    // 纹理重复
    texLan.wrapS = THREE.RepeatWrapping;
    texLan.wrapT = THREE.RepeatWrapping;
    obj.children[0].material = new THREE.MeshBasicMaterial({
      map: texLan,
      side: THREE.DoubleSide,
      transparent: true,
    });
    obj.children[1].material = new THREE.MeshBasicMaterial({
      map: new THREE.TextureLoader().load("/model/textures/door.png"),
      side: THREE.DoubleSide,
      transparent: true,
    });
    // 纹理重复
    scene.add(obj);
  });

image.png

创建GUI

function initDataGUI() {
  weatherFolder = gui.addFolder("Weather");
  // 添加进度条
  guiCtrl.speed = speed;
  gui.add(guiCtrl, "speed", 0, 10).onChange((e) => {
    // console.log(e);
    speed = e;
  });
  // 添加颜色
  // guiCtrl.backgroundColor = "#ffffff";
  guiCtrl.backgroundColor = fireColor;
  gui.addColor(guiCtrl, "backgroundColor").onChange((e) => {
    console.log(e);
    fireColor = e;
    // renderer.setClearColor(e);
  });
  // 是否选中
  guiCtrl.isFire = isFire;
  gui.add(guiCtrl, "isFire").onChange((e) => {
    addFire();
    group.traverse(function(obj) {
      console.log(obj.name)
    })

    isFire = e;
    // if (isFire) {
    //   addFire();
    // } else {
    //   scene.remove(scene.getObjectByName("particles_flame"));
    // }
    // console.log(e);
  });

  // 添加天气
  guiCtrl.weather = weather[rainy_sw - 1];
  gui.add(guiCtrl, "weather", weather).onChange((e) => {
    let indexNum = weather.indexOf(e);
    rainy_sw = indexNum + 1;
    // console.log("rainy_sw", rainy_sw);
    // scene.traverse((obj) => {
    //   console.log(obj.name);
    // });
    scene.remove(scene.getObjectByName("particles_rainy"));
    scene.remove(scene.getObjectByName("particles_snowy"));
    let url = rainy_sw == 3 ? urls : urls1;
    // 注意要使用透视相机(PerspectiveCamera),而非正交相机(OrthographicCamera)
    scene.background = new THREE.CubeTextureLoader().load(url);
    if (rainy_sw < 3) {
      createPointRainy();
    }
  });
  // 输入框
  guiCtrl.name = "工厂3D可视化系统";
  gui.add(guiCtrl, "name").onChange((e) => {
    console.log(e);
  });
  //
  guiCtrl.weather2 = weather3;
  guiCtrl.weather3 = weather4;
  weatherFolder.add(guiCtrl, "weather2").onChange((e) => {
    console.log(e);
  });
  weatherFolder.add(guiCtrl, "weather3").onChange((e) => {
    console.log(e);
  });
  /* for (let i = 0; i < weather.length; i++) {
    console.log("name", weather[i]);
    weatherItem(weather[i])
    // createEmoteCallback
  } */

  weatherFolder.open();
}

image.png

添加天气

function createPointRainy() {
  // rainy_sw  1雨 2雪 3晴 4阴
  // console.log("rainy_sw==", rainy_sw);
  var img =
    rainy_sw == 1 ? "raindrop.png" : rainy_sw == 2 ? "snowflake.png" : "";
  var name =
    rainy_sw == 1 ? "particles_rainy" : rainy_sw == 2 ? "particles_snowy" : "";
  // console.log(img, name);
  let num = 400;
  // 载入点材质
  var texture = new THREE.TextureLoader().load("model/textures/" + img);
  // 创建一个组表示所有的雨滴
  group = new THREE.Group();
  for (let i = 0; i < num; i++) {
    var spriteMaterial = new THREE.SpriteMaterial({
      color: 0xffffff, //设置精灵矩形区域颜色
      // rotation:Math.PI/4,//旋转精灵对象45度,弧度值
      map: texture, //设置精灵纹理贴图
      transparent: true, // 是否设置透明度
      opacity: 1, // 透明
      blending: THREE.AdditiveBlending, //去掉背景色
      sizeAttenuation: true, // 是否相同尺寸
    });
    var sprite = new THREE.Sprite(spriteMaterial);
    group.add(sprite);
    // 控制精灵大小,
    sprite.scale.set(4, 5, 1); //// 只需要设置x、y两个分量就可以
    var k1 = Math.random() - 0.5;
    var k2 = Math.random() - 0.5;
    sprite.position.set(1000 * k1, 300 * Math.random(), 1000 * k2);
    group.name = name;
    scene.add(group);
  }
}

设计雨滴速度

function rainAnimate() {
  // 每次渲染遍历雨滴群组,刷新频率30~60FPS,两帧时间间隔16.67ms~33.33ms
  // 每次渲染都会更新雨滴的位置,进而产生动画效果
  group.children.forEach((sprite) => {
    // 雨滴的y坐标每次减1
    sprite.position.y -= speed;
    if (sprite.position.y < 0) {
      // 如果雨滴落到地面,重置y,从新下落
      sprite.position.y = 200;
    }
  });
}

开火

function addFire() {
  var texture = new THREE.TextureLoader().load("model/textures/flamex.png");
  var material = new THREE.SpriteMaterial({
    //以canvas作为纹理
    map: texture,
    color: fireColor,
    //混合度 加法混合
    blending: THREE.AdditiveBlending,
  });
  //循环1000  添加粒子
  for (var i = 0; i < 2000; i++) {
    var particle = new THREE.Sprite(material);
    initParticle(particle, i);
    group.name = "particles_flame";
    scene.add(particle);
  }
  // particle.scale.set(30, 40, 1);
  // particle.position.set(-2, 20, 0);
}

粒子加载

function initParticle(particle, delay) {
  particle.position.set(0, Math.random() + 20, 0);
  particle.scale.x = particle.scale.y = Math.random() * 13;
  //下面是一系列的动画
  var xx = Math.random() * 40 - 20;
  var yy = Math.cos((Math.PI / 100) * xx) * 80;
  //位移
  new TWEEN.Tween(particle.position)
    .delay(delay)
    .to(
      {
        x: xx,
        y: yy,
        z: Math.random() * 40 - 20,
      },
      2000
    )
    .onComplete(function () {
      initParticle(particle, delay);
    })
    .start();
  // 大小
  new TWEEN.Tween(particle.scale)
    .delay(delay)
    .to(
      {
        x: 0.01,
        y: 0.01,
      },
      1000
    )
    .start();
}