VUE使用Three.js实现模型,点击交互,相机旋转视角跟随移动(Threejs中使用Tweenjs,含demo源码)

目录

一、Three.js是什么?

二、VUE简单使用Three.js步骤

1.npm安装

2.template模板

3.引入库

4.定义全局变量

5.初始化场景

6.初始化相机

7.初始化灯光

8.初始化渲染器

9.创建模型(这里我搭建的模型是一个简单双面货架模型)

10.根据浏览器窗口自适应

11.初始化函数,页面加载完成时调用(mounted()中调用)

12.Style样式

三、VUE进阶使用Three.js步骤(完成各种事件和效果)

在简单使用Three.js的基础上,添加以下控件和代码

1.引入库及需要使用的组件

2.template模板

3.定义全局变量

4.使用OrbitControls控制给模型添加缩放,旋转,平移和拖拽等效果

5.点击模型后的模型样式效果

6.相机跟随点击事件移动动画效果

7.返回主视角按钮事件

8.添加鼠标点击模型事件,并调用相机移动方法(第6点)和点击后样式方法(第5点)

9.运行动画

10.初始化函数,页面加载完成时调用(mounted()中调用)

11.Style样式

四、源码地址


一、Three.js是什么?

Three.js是一款基于原生WebGL封装通用Web 3D引擎,在小游戏、产品展示、物联网、数字孪生、智慧城市园区、机械、建筑、全景看房、GIS等各个领域基本上都有three.js的身影

二、VUE简单使用Three.js步骤

1.npm安装

npm install three
npm install @tweenjs/tween.js

2.template模板

<template>
  <div class="container">
    <div id="model">
    </div>
  </div>
</template>

3.引入库

import * as THREE from "three";

4.定义全局变量

data() {
    return {
        isDisabled: true,
        scene: null,
        camera: null,
        renderer: null,
        light: null,
        light2: null,
        //定义模型架子的长度
        long: 100,
        //定义模型架子的宽度
        tall: 24,
        //定义模型架子横纵板的宽度
        thickness: 0.4,
    };
}

5.初始化场景

//初始化场景
initScene() {
   this.scene = new THREE.Scene();
},

6.初始化相机

//初始化相机
initCamera() {
    const { long, tall } = this;
    this.camera = new THREE.PerspectiveCamera(
        45,
        window.innerWidth / window.innerHeight,
        0.1,
        50000
    );
    this.camera.position.set(0, -(5 * tall) / 12, (6 * long) / 5);
}

7.初始化灯光

//初始化灯光
initLight() {
    let ambientLight = new THREE.AmbientLight(0x404040);
    this.scene.add(ambientLight);
    //定义灯,并设置位置
    this.light = new THREE.DirectionalLight(0x333333);
    this.light.position.set(60, 30, 40);
    this.light2 = new THREE.DirectionalLight(0xdddddd);
    this.light2.position.set(-20, 20, -20);
    this.scene.add(this.light);
    this.scene.add(this.light2);
},

8.初始化渲染器

//初始化渲染器
initRender() {
  //dom元素渲染器
  this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  this.renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染区域尺寸
  this.renderer.setClearColor(0x000000, 0); // 设置背景颜色
  //window.devicePixelRatio 当前设备的物理分辨率与css分辨率之比
  this.renderer.setPixelRatio(window.devicePixelRatio);
  //按层级先后渲染
  this.renderer.sortObjects = true;
  document.getElementById("model").appendChild(this.renderer.domElement);
},

9.创建模型(这里我搭建的模型是一个简单双面货架模型)

//创建载入模型
initModel() {
    const { long, tall, thickness } = this;
    //坐标系
    // let axes = new THREE.AxesHelper(4000);
    // this.scene.add(axes);

    //所有横板
    for (let index = 1; index <= tall / 4 - 1; index++) {
        let geometry = new THREE.BoxGeometry(long, thickness, 12);
        let material = new THREE.MeshLambertMaterial({
            color: 0x808080,
            opacity: 0.7,
            transparent: true,
        }); //材质对象Material
        let mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
        mesh.position.set(0, index * 4, 0); //设置mesh3模型对象的xyz坐标为120,0,0
        this.scene.add(mesh); //网格模型添加到场景中
    }
    //所有纵板
    for (let index = 1; index <= long / 4 + 1; index++) {
        let geometry = new THREE.BoxGeometry(thickness, tall, 12);
        let material = new THREE.MeshLambertMaterial({
            color: 0x808080,
            opacity: 0.7,
            transparent: true,
        }); //材质对象Material
        let mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
        mesh.position.set(-long / 2 + (index - 1) * 4, tall / 2, 0); //设置mesh3模型对象的xyz坐标为120,0,0
        this.scene.add(mesh); //网格模型添加到场景中
    }
    //正面所有箱子
    let list1 = new Array(tall / 4);
    for (let i = 0; i < list1.length; i++) {
        list1[i] = new Array(long / 4);
        //不一定写for循环赋值,还可以直接赋值,在数量有限的情况下
        for (let j = 0; j < long / 4; j++) {
            // a[i][j] = i + j;
            let geometry = new THREE.BoxGeometry(3, 3, 5);
            let material = new THREE.MeshLambertMaterial({
                color: 0x00b1f7,
                opacity: 0.4,
                transparent: true,
            });
            let mesh = new THREE.Mesh(geometry, material);
            //定义每个箱子的名称(或者编号)
            mesh.name = i + 1 + "-" + (j + 1);
            //设置每个箱子的位置
            mesh.position.set(-long / 2 + j * 4 + 2, i * 4 + 2, 3);
            this.scene.add(mesh);
        }
    }
    //背面所有箱子
    let list2 = new Array(tall / 4);
    for (let i = 0; i < list2.length; i++) {
        list2[i] = new Array(long / 4);
        //不一定写for循环赋值,还可以直接赋值,在数量有限的情况下
        for (let j = 0; j < long / 4; j++) {
            // a[i][j] = i + j;
            let geometry = new THREE.BoxGeometry(3, 3, 5);
            let material = new THREE.MeshLambertMaterial({
                color: 0x00b1f7,
                opacity: 0.4,
                transparent: true,
            });
            let mesh = new THREE.Mesh(geometry, material);
            //定义每个箱子的名称(或者编号)
            mesh.name = "-" + (i + 1) + "-" + (j + 1);
            //设置每个箱子的位置
            mesh.position.set(-long / 2 + j * 4 + 2, i * 4 + 2, -3);
            this.scene.add(mesh);
        }
    }
}

10.根据浏览器窗口自适应

//根据浏览器窗口自适应
onWindowResize() {
    this.renderer.setSize(window.innerWidth, window.innerHeight);
    this.camera.aspect = window.innerWidth / window.innerHeight;
    this.camera.updateProjectionMatrix();
}

11.初始化函数,页面加载完成时调用(mounted()中调用)

//初始化函数,页面加载完成时调用
init() {
    this.initScene();
    this.initCamera();
    this.initLight();
    this.initRender();
    this.initModel();
    this.renderer.render(this.scene, this.camera);
    window.onresize = this.onWindowResize;
}

12.Style样式

<style scoped>
.container {
  width: 100%;
  height: 100%;
  position: relative;
}
/*模型样式*/
#model {
  width: 100%;
  height: 100%;
  position: relative;
  overflow: hidden;
}
</style>

呈现效果,如下图。

但是这样仅仅只是将自制的模型创建并渲染了出来,无法满足我们大多数情况的使用场景,例如旋转,点击,拖拽平移,相机视角跟随移动等,接下来就实现这些效果。

三、VUE进阶使用Three.js步骤(完成各种事件和效果)

在简单使用Three.js的基础上,添加以下控件和代码

1.引入库及需要使用的组件

import TWEEN from "@tweenjs/tween.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";

2.template模板

<template>
  <div class="container">
    <div id="model"></div>
    <button
      id="btns"
      :style="{ cursor: isDisabled ? '' : 'pointer' }"
      :disabled="isDisabled"
      type="isDisabled:"
      @click="toHomeView(1)"
    >
      主视角
    </button>
  </div>
</template>

3.定义全局变量

这里就是所有的全局变量了,直接复制替换就行了

data() {
    return {
        isDisabled: true,
        scene: null,
        camera: null,
        renderer: null,
        controls: null,
        light: null,
        light2: null,
        group: new THREE.Group(),
        composer: null, // 控制发光
        outlinePass: null,
        renderPass: null,
        // 选中的模型
        selectedObjects: [],
        mouse: new THREE.Vector2(),
        raycaster: new THREE.Raycaster(),
        tween: null,
        //定义模型架子的长度
        long: 100,
        //定义模型架子的宽度
        tall: 24,
        //定义模型架子横纵板的宽度
        thickness: 0.4,
        positionObj: null,
    };
}

4.使用OrbitControls控制给模型添加缩放,旋转,平移和拖拽等效果

//使用OrbitControls控制三维场景缩放和旋转等功能
initControls() {
    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    //动态阻尼系数 即鼠标拖拽旋转的灵敏度
    this.controls.dampingFactor = 0.25;
    // this.controls.target.set(0, 900, 0)
    // //上下旋转范围
    this.controls.minPolarAngle = 0;
    this.controls.maxPolarAngle = 1.5;
    this.controls.autoRotate = true;
    //惯性滑动,滑动大小默认0.25
    this.controls.dampingFactor = 0.25;
    //滚轮是否可控制zoom,zoom速度默认1
    //缩放倍数
    this.controls.zoomSpeed = 1.0;
    //最大最小相机移动距离(景深相机)
    this.controls.minDistance = 1;
    this.controls.maxDistance = Infinity;
    //水平方向视角限制
    this.minAzimuthAngle = -Math.PI * 2;
    this.maxAzimuthAngle = Math.PI * 2;
    this.controls.enabledPan = true;
    this.keyPanSpeed = 7.0;
}

5.点击模型后的模型样式效果

//高亮显示模型(呼吸灯)
outlineObj(selectedObjects) {
    // 创建一个EffectComposer(效果组合器)对象,然后在该对象上添加后期处理通道。
    this.composer = new EffectComposer(this.renderer);
    // 新建一个场景通道  为了覆盖到原理来的场景上
    this.renderPass = new RenderPass(this.scene, this.camera);
    this.composer.addPass(this.renderPass);
    // 物体边缘发光通道
    this.outlinePass = new OutlinePass(
        new THREE.Vector2(window.innerWidth, window.innerHeight),
        this.scene,
        this.camera,
        selectedObjects
    );
    this.outlinePass.edgeStrength = 8.0; // 高光边缘边框的亮度
    this.outlinePass.edgeGlow = 1; // 光晕[0,1]  边缘微光强度
    this.outlinePass.usePatternTexture = false; // 是否使用父级的材质,纹理覆盖
    this.outlinePass.edgeThickness = 3; // 边框宽度,高光厚度
    this.outlinePass.downSampleRatio = 1; // 边框弯曲度
    this.outlinePass.pulsePeriod = 2; // 呼吸闪烁的速度,数值越大,律动越慢
    this.outlinePass.visibleEdgeColor.set(parseInt(0x00f6ff)); // 呼吸显示的颜色
    this.outlinePass.hiddenEdgeColor = new THREE.Color(0, 0, 0); // 呼吸消失的颜色
    // this.outlinePass.clear = true
    this.composer.addPass(this.outlinePass); // 加入高光特效
    this.outlinePass.selectedObjects = selectedObjects; // 需要高光的模型
}

6.相机跟随点击事件移动动画效果

// 相机移动动画
initTween(targetX, targetY, targetZ) {
    // 获取当前相机位置
    let initPosition = {
        x: this.camera.position.x,
        y: this.camera.position.y,
        z: this.camera.position.z,
    };
    //定义相机移动方法
    let tween = new TWEEN.Tween(initPosition)
        .to({ x: targetX, y: targetY, z: targetZ }, 2000)
        .easing(TWEEN.Easing.Sinusoidal.InOut);
    //格子位置参数
    let onUpdate = (pos) => {
        let x = pos.x;
        let y = pos.y;
        let z = pos.z;
        //z<0为背面格子,z>0为正面格子,并设置相机的位置
        if (pos.z < 0) {
            this.camera.position.set(x, y, z - 12);
        } else {
            this.camera.position.set(x, y, z + 12);
        }
    };
    //调用相机方法并传入格子位置参数
    tween.onUpdate(onUpdate);
    tween.start();
    //设置相机target位置(相机看向格子的位置)
    if (this.positionObj.z < 0) {
        this.controls.target.set(
            this.positionObj.x,
            this.positionObj.y - 0.4,
            -12
        );
    } else {
        this.controls.target.set(
            this.positionObj.x,
            this.positionObj.y - 0.4,
            12
        );
    }
}

7.返回主视角按钮事件

//相机返回主视角动画
toHomeView(id) {
    const { long, tall } = this;
    if (id === 1) {
        // 获取当前相机位置
        let initPosition = {
            x: this.camera.position.x,
            y: this.camera.position.y,
            z: this.camera.position.z,
        };
        //定义相机移动方法
        let tween = new TWEEN.Tween(initPosition)
            .to({ x: 0, y: -(5 * tall) / 12, z: (6 * long) / 5 }, 2000)
            .easing(TWEEN.Easing.Sinusoidal.InOut);
        //主视角相机位置(点击前原位置)
        let onUpdate = (pos) => {
            let x = pos.x;
            let y = pos.y;
            let z = pos.z;
            this.camera.position.set(x, y, z);
        };
        tween.onUpdate(onUpdate);
        tween.start();
        //设置相机target位置(看向坐标轴零点的位置)
        this.controls.target.set(0, 0, 0);
        //相机返回原点后,返回主视角禁用
        this.isDisabled = true;
        //相机返回原点后,开启模型自动旋转
        this.controls.autoRotate = true;
    }
}

8.添加鼠标点击模型事件,并调用相机移动方法(第6点)和点击后样式方法(第5点)

// 鼠标点击模型
onMouseClick(event) {
    //通过鼠标点击的位置计算出raycaster所需要的点的位置,以屏幕中心为原点,值的范围为-1到1
    this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    this.mouse.y = -(event.clientY / (window.innerHeight-50)) * 2 + 1;
    // 通过鼠标点的位置和当前相机的矩阵计算出raycaster
    this.raycaster.setFromCamera(this.mouse, this.camera);
    // 获取raycaster直线和所有模型相交的数组集合
    let intersects = this.raycaster.intersectObjects(this.scene.children);
    if (!intersects[0]) {
        return;
    } else {
        //这样会获取所有模型,因为我们这里只需要小格子
        //所以只需要获取我们之前设置name!=""属性的object即可
        if (!intersects[0].object.name == "") {
            this.selectedObjects = [];
            this.selectedObjects.push(intersects[0].object);
            //调用高亮显示模型(呼吸灯)的方法给点击的格子添加点击后的样式
            this.outlineObj(this.selectedObjects);
            //拿到格子的position坐标
            this.positionObj = {
                x: intersects[0].object.position.x,
                y: intersects[0].object.position.y,
                z: intersects[0].object.position.z,
            };
            //调用机移动动画,将相机移动至被点击的格子处
            this.initTween(
                this.positionObj.x,
                this.positionObj.y,
                this.positionObj.z
            );
            //点击格子后,开放返回主视角的点击权限
            this.isDisabled = false;
            //点击格子后,禁止模型自动旋转
            this.controls.autoRotate = false;
        }
    }
}

9.运行动画

//运行动画
animate() {
    //运行相机旋转动画
    TWEEN.update();
    //渲染场景和相机
    this.renderer.render(this.scene, this.camera);
    this.controls.update();
    if (this.composer) {
        this.composer.render();
    }
    //使用requestAnimationFrame周期性渲染
    requestAnimationFrame(this.animate);
}

10.初始化函数,页面加载完成时调用(mounted()中调用)

//初始化函数,页面加载完成时调用
init() {
    this.initScene();
    this.initCamera();
    this.initLight();
    this.initRender();
    this.initModel();
    this.renderer.render(this.scene, this.camera);
    this.initControls();
    this.animate();
    window.onresize = this.onWindowResize;
    window.onclick = this.onMouseClick;
}

11.Style样式

<style scoped>
.container {
  width: 100%;
  height: 100%;
  position: relative;
}
#btns {
  position: absolute;
  background-color: #031b34;
  bottom: 0;
  left: 50%;
  width: 80px;
  height: 30px;
  line-height: 30px;
  transform: translate(-50%, -50%);
  text-align: center;
  z-index: 9999;
  color: #00eeff;
  font-weight: bold;
  box-shadow: 0px 0px 2px 1px #00f6ff;
  border-radius: 6px;
  border: 1px solid #00f6ff;
  padding: 0;
}
/*模型样式*/
#model {
  width: 100%;
  height: 100%;
  position: relative;
  overflow: hidden;
}
</style>

呈现效果如下图,如下图。

四、源码地址

建议认真看每一部分的代码,实在不清楚可以看源码,毕竟最近才做这方面的项目,写的不好的地方,还请各位嘴下留情。
货架三维模型: 简单的货架三维模型实现点击交互,相机旋转等功能https://gitee.com/halsixsixsix/3d-model-of-shelf.git

猜你喜欢

转载自blog.csdn.net/weixin_43721856/article/details/128290946