vue中使用threejs

lishihuan大约 8 分钟

vue中使用threejs

参考网站 : http://www.webgl3d.cn/Three.js/open in new window

自建网站 three官网 (搭建参考:搭建本地tree官网open in new window

官网open in new window

中文官网open in new window

官网源码-用于自建官网:https://gitcode.net/mirrors/mrdoob/three.jsopen in new window

在vue中使用threejs-demoopen in new window

demo1open in new window

demo2open in new window

开发参考思路,组件化开发 https://blog.csdn.net/weixin_43931376/article/details/125180625open in new window

模型添加文本-demoopen in new window --待整理

加载进度条open in new window

http://code.js-code.com/public/api.php?app=search&q=threejsopen in new window

https://www.bilibili.com/video/BV1Gg411X7FY/?p=3&spm_id_from=pageDriver&vd_source=ed6b0987ce5a685a1e2f4bd022be1d52open in new window

1.安装

可能需要的插件

"three": "^0.146.0", "@tweenjs/tween.js": "^18.6.4", "dat.gui": "^0.7.9",

npm install three --save

2. 引入

需要引入模块儿不知道路径可以在node_modules去找到three -> examples -> jsm中去寻找。js目录下放的是非模块化的js文件,直接用script src去引入的。jsm目录下放的是模块化的文件。按需去引入即可

 import * as THREE from 'three'
// 下面更具需要 按需导入
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js'
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js'
import Stats from 'three/examples/jsm/libs/stats.module.js'
import TWEEN from '@tweenjs/tween.js'
import { GUI } from 'dat.gui'

属性说明

1.

9. 相机对象

正投影相机OrthographicCameraopen in new window和透视投影相机PerspectiveCameraopen in new window

image-20221209143734342
image-20221209143734342

9.1 正投影相机对象OrthographicCameraopen in new window

/**
 * 正投影相机设置
 */
var width = window.innerWidth; //窗口宽度
var height = window.innerHeight; //窗口高度
var k = width / height; //窗口宽高比
var s = 150; //三维场景显示范围控制系数,系数越大,显示的范围越大
//创建相机对象
var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
camera.position.set(200, 300, 200); //设置相机位置
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
// 构造函数格式
OrthographicCamera( left, right, top, bottom, near, far )
参数(属性)含义
left渲染空间的左边界
right渲染空间的右边界
top渲染空间的上边界
bottom渲染空间的下边界
nearnear属性表示的是从距离相机多远的位置开始渲染,一般情况会设置一个很小的值。 默认值0.1
farfar属性表示的是距离相机多远的位置截止渲染,如果设置的值偏小小,会有部分场景看不到。 默认值1000
img
img

9.2 透视投影相机PerspectiveCameraopen in new window

/**
 * 透视投影相机设置
 */
var width = window.innerWidth; //窗口宽度
var height = window.innerHeight; //窗口高度
/**透视投影相机对象*/
var camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000);
camera.position.set(200, 300, 200); //设置相机位置
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)

构造函数PerspectiveCamera格式

PerspectiveCamera( fov, aspect, near, far )
参数含义默认值
fovfov表示视场,所谓视场就是能够看到的角度范围,人的眼睛大约能够看到180度的视场,视角大小设置要根据具体应用,一般游戏会设置60~90度45
aspectaspect表示渲染窗口的长宽比,如果一个网页上只有一个全屏的canvas画布且画布上只有一个窗口,那么aspect的值就是网页窗口客户区的宽高比window.innerWidth/window.innerHeight
nearnear属性表示的是从距离相机多远的位置开始渲染,一般情况会设置一个很小的值。0.1
farfar属性表示的是距离相机多远的位置截止渲染,如果设置的值偏小,会有部分场景看不到1000
img
img

9.3 相机位置.posiiotn.lookAt(相机拍摄目标位置)

img
img

相机Cameraopen in new window的基类是Object3Dopen in new window,相机对象Camera具有位置属性.posiiotn,通过位置属性.posiiotn可以设置相机的位置。

Three.js相关的开源库。

功能
Physijsopen in new windowPhysijs是一款物理引擎,可以协助基于原生WebGL或使用three.js创建模拟物理现象,比如重力下落、物体碰撞等物理现
stats.jsopen in new windowJavaScript性能监控器,同样也可以测试webgl的渲染性能
dat.guiopen in new window轻量级的icon形用户界面框架,可以用来控制Javascript的变量,比如WebGL中一个物体的尺寸、颜色
TWEEN.jsopen in new window借助TWEEN.js快速创建补间动画,可以非常方便的控制机械、游戏角色运动
ThreeBSPopen in new window可以作为three.js的插件,完成几何模型的布尔,各类三维建模软件基本都有布尔的概念

1. tween.js

https://github.com/tweenjs/tween.js/open in new window

https://blog.csdn.net/weixin_43081805/article/details/89399031open in new window

tween.js允许以平滑的方式修改元素的属性值。只需要告诉tween想修改什么值,以及动画结束时它的最终值是什么,动画花费多少时间等信息,tween引擎就可以计算从开始动画点到结束动画点之间值,来产生平滑的动画效果

其他记录

const pointLabel = new CSS2DObject(label)

scene.remove(pointLabel) ;// 会将当前的dom元素删除

案例

工大大屏

webgl_loader_pdb

image-20221210160322631
image-20221210160322631

CSS2DObject标签绑定点击事件

https://wow.techbrood.com/fiddle/62482open in new window

const el = document.createElement('div')
el.textContent = '123'
el.style.pointerEvents = 'auto'
el.addEventListener('click', function (event) { //如果 click 不行 用 pointerdown
    console.log(event)
})
new CSS2DObject(el)

// 给 el 加上 el.style.pointerEvents = 'auto'
// 版本 0.125.2

<script type="module">

    import * as THREE from 'three';

    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
    import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';

    import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

    let gui;

    let camera, scene, renderer, labelRenderer;

    const layers = {

        'Toggle Name': function () {

            camera.layers.toggle( 0 );

        },
        'Toggle Mass': function () {

            camera.layers.toggle( 1 );

        },
        'Enable All': function () {

            camera.layers.enableAll();

        },

        'Disable All': function () {

            camera.layers.disableAll();

        }

    };

    const clock = new THREE.Clock();
    const textureLoader = new THREE.TextureLoader();

    let moon;

    init();
    animate();

    function init() {

        const EARTH_RADIUS = 1;
        const MOON_RADIUS = 0.27;

        camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200 );
        camera.position.set( 10, 5, 20 );
        camera.layers.enableAll();
        camera.layers.toggle( 1 );

        scene = new THREE.Scene();

        const dirLight = new THREE.DirectionalLight( 0xffffff );
        dirLight.position.set( 0, 0, 1 );
        dirLight.layers.enableAll();
        scene.add( dirLight );

        const axesHelper = new THREE.AxesHelper( 5 );
        axesHelper.layers.enableAll();
        scene.add( axesHelper );

        //

        const earthGeometry = new THREE.SphereGeometry( EARTH_RADIUS, 16, 16 );
        const earthMaterial = new THREE.MeshPhongMaterial( {
            specular: 0x333333,
            shininess: 5,
            map: textureLoader.load( 'textures/planets/earth_atmos_2048.jpg' ),
            specularMap: textureLoader.load( 'textures/planets/earth_specular_2048.jpg' ),
            normalMap: textureLoader.load( 'textures/planets/earth_normal_2048.jpg' ),
            normalScale: new THREE.Vector2( 0.85, 0.85 )
        } );
        const earth = new THREE.Mesh( earthGeometry, earthMaterial );
        scene.add( earth );

        const moonGeometry = new THREE.SphereGeometry( MOON_RADIUS, 16, 16 );
        const moonMaterial = new THREE.MeshPhongMaterial( {
            shininess: 5,
            map: textureLoader.load( 'textures/planets/moon_1024.jpg' )
        } );
        moon = new THREE.Mesh( moonGeometry, moonMaterial );
        scene.add( moon );

        //

        earth.layers.enableAll();
        moon.layers.enableAll();

        const earthDiv = document.createElement( 'div' );
        earthDiv.className = 'label';
        earthDiv.textContent = 'Earth';
        earthDiv.style.marginTop = '-1em';
        earthDiv.style.pointerEvents = 'auto'
        earthDiv.addEventListener('pointerdown', function (event) {
            console.log(event)
        })
        const earthLabel = new CSS2DObject( earthDiv );
        earthLabel.position.set( 0, EARTH_RADIUS, 0 );
        earth.add( earthLabel );
        earthLabel.layers.set( 0 );
        earthLabel.element.addEventListener('click', ()=>{
            alert(12121)
        })
        const earthMassDiv = document.createElement( 'div' );
        earthMassDiv.className = 'label';
        earthMassDiv.textContent = '5.97237e24 kg';
        earthMassDiv.style.marginTop = '-1em';
        const earthMassLabel = new CSS2DObject( earthMassDiv );
        earthMassLabel.position.set( 0, - 2 * EARTH_RADIUS, 0 );
        earth.add( earthMassLabel );
        earthMassLabel.layers.set( 1 );

        const moonDiv = document.createElement( 'div' );
        moonDiv.className = 'label';
        moonDiv.textContent = 'Moon';
        moonDiv.style.marginTop = '-1em';
        const moonLabel = new CSS2DObject( moonDiv );
        moonLabel.position.set( 0, MOON_RADIUS, 0 );
        moon.add( moonLabel );
        moonLabel.layers.set( 0 );

        const moonMassDiv = document.createElement( 'div' );
        moonMassDiv.className = 'label';
        moonMassDiv.textContent = '7.342e22 kg';
        moonMassDiv.style.marginTop = '-1em';
        const moonMassLabel = new CSS2DObject( moonMassDiv );
        moonMassLabel.position.set( 0, - 2 * MOON_RADIUS, 0 );
        moon.add( moonMassLabel );
        moonMassLabel.layers.set( 1 );

        //

        renderer = new THREE.WebGLRenderer();
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize( window.innerWidth, window.innerHeight );
        document.body.appendChild( renderer.domElement );

        labelRenderer = new CSS2DRenderer();
        labelRenderer.setSize( window.innerWidth, window.innerHeight );
        labelRenderer.domElement.style.position = 'absolute';
        labelRenderer.domElement.style.top = '0px';

        document.body.appendChild( labelRenderer.domElement );

        const controls = new OrbitControls( camera, labelRenderer.domElement );
        controls.minDistance = 5;
        controls.maxDistance = 100;

        //

        window.addEventListener( 'resize', onWindowResize );

        initGui();

    }

    function onWindowResize() {

        camera.aspect = window.innerWidth / window.innerHeight;

        camera.updateProjectionMatrix();

        renderer.setSize( window.innerWidth, window.innerHeight );

        labelRenderer.setSize( window.innerWidth, window.innerHeight );

    }


    function animate() {

        requestAnimationFrame( animate );

        const elapsed = clock.getElapsedTime();

        moon.position.set( Math.sin( elapsed ) * 5, 0, Math.cos( elapsed ) * 5 );

        renderer.render( scene, camera );
        labelRenderer.render( scene, camera );

    }

    //

    function initGui() {

        gui = new GUI();

        gui.add( layers, 'Toggle Name' );
        gui.add( layers, 'Toggle Mass' );
        gui.add( layers, 'Enable All' );
        gui.add( layers, 'Disable All' );

    }

</script>

threejs点击获取三维坐标(Three.js获取鼠标点击的三维坐标)

addClick();
function addClick () {
    var raycaster = new THREE.Raycaster();//光线投射,用于确定鼠标点击位置
    var mouse = new THREE.Vector2();//创建二维平面
    window.addEventListener("mousedown",mousedown);//页面绑定鼠标点击事件
    //点击方法
    function mousedown(e){
        //将html坐标系转化为webgl坐标系,并确定鼠标点击位置
        mouse.x =  e.clientX / renderer.domElement.clientWidth*2-1;
        mouse.y =  -(e.clientY / renderer.domElement.clientHeight*2)+1;
        //以camera为z坐标,确定所点击物体的3D空间位置
        raycaster.setFromCamera(mouse,camera);
        //确定所点击位置上的物体数量
        var intersects = raycaster.intersectObjects(scene.children);
        //选中后进行的操作
        if(intersects.length){
            var selected = intersects[0];//取第一个物体
            console.log("x坐标:"+selected.point.x);
            console.log("y坐标:"+selected.point.y);
            console.log("z坐标:"+selected.point.z);
        }
    }
}

GLTFLoader 模型下加载 CSS2DObject(作为文字标记点)

场景说明:GLTF 模型需要添加标记点,标记点利用 CSS2DObject 创建

并且需要 能点击标记点

image-20221210224139612
image-20221210224139612
// css2d渲染器-用作 文本渲染器
_createLabelRenderer () {
    // 新建CSS2DRenderer
    this.labelRenderer = new CSS2DRenderer()
    this.labelRenderer.setSize(window.innerWidth, window.innerHeight)
    this.labelRenderer.domElement.style.position = 'absolute'
    this.labelRenderer.domElement.style.top = '0'
    this.labelRenderer.domElement.style.pointerEvents = 'none'; // 这步很重要,设置.pointerEvents=none,以免模型标签HTML元素遮挡鼠标选择场景模型-然后添加的 CSS2DObject 元素 设置 label.style.pointerEvents = 'auto'
    this.container.appendChild(this.labelRenderer.domElement); // 将渲染器创建在 容器中,会导致 该图层和画布冲突,导致无法操作
    // document.body.appendChild(this.labelRenderer.domElement)
}


// 创建文本标记点,并定义点击事件
createLableObj (item, vector) {
    const itemObj = JSON.stringify(item)
    // 创建一个 div 标签
    const labelTemp = document.createElement('div')
    labelTemp.className = '3d-tag'
    const id = `tag${item.id}`
    labelTemp.id = id
    document.getElementById('bimbox').appendChild(labelTemp)
    const label = document.getElementById(id)
    const img = document.createElement('img')
    img.className = 'tag_img'
    // type:工程是否告警 1-告警 0 不告警
    img.src = item.type === 1 ? this.tagIconMap.default : this.tagIconMap.alarm
    label.appendChild(img)
    label.style.pointerEvents = 'auto' // 这步很重要,否则会 无法点击标记点

    // 解决传参问题
    label.addEventListener('click', (e) => this.labelClick(e, itemObj))

    const pointLabel = new CSS2DObject(label)
    pointLabel.position.set(vector.x, vector.y, vector.z)
    this.group.add(pointLabel)
}

// 点击标记点-弹窗,转向
labelClick (e, item) {
    console.log(e.target)
    console.log(item)
}

释放内存

https://blog.csdn.net/u014361280/article/details/124309410open in new window

// 删除场景对象中Scene一个子对象Group,并释放组对象Group中所有网格模型几何体的顶点缓冲区占用内存
deleteObject(group) {
    // 递归遍历组对象group释放所有后代网格模型绑定几何体占用内存
    group.traverse(function(obj) {
        if (obj.type === 'Mesh') {
        obj.geometry.dispose();
        obj.material.dispose();
      }
    })
 
    // 删除场景对象scene的子对象group
    scene.remove(group);
}
 // 删除组
 clearGroup(group) {
    // 释放 几何体 和 材质
    const clearCache = (item) => {
      item.geometry.dispose();
      item.material.dispose();
    };
    
    // 递归释放物体下的 几何体 和 材质
    const removeObj = (obj) => {
      let arr = obj.children.filter((x) => x);
      arr.forEach((item) => {
        if (item.children.length) {
          removeObj(item);
        } else {
          clearCache(item);
          item.clear();
        }
      });
      obj.clear();
      arr = null;
    };
 
    // 移除 group
    removeObj(group);
  }
 

// 1)如果是场景中
scene.remove(group); // 删除组
 
// 2)如果是 组中
groups.remove(group);// 删除模型

模型没有啥纹理材质

img
/**
     *场景环境
     *  解决 部分模型 没有 纹理材质,导致模型变成一坨,不好看
     * @private
     */
_createEnvironment() {
    this.renderer.physicallyCorrectLights = true;
    this.renderer.outputEncoding = THREE.sRGBEncoding;
    this.renderer.setClearColor(0xcccccc);
    this.renderer.setPixelRatio(window.devicePixelRatio);
    var pmremGenerator = new THREE.PMREMGenerator(this.renderer);
    pmremGenerator.compileEquirectangularShader();
    var neutralEnvironment =  pmremGenerator.fromScene(new RoomEnvironment()).texture;
    this.scene.environment = neutralEnvironment;
}

地图开发

https://zhuanlan.zhihu.com/p/109555689open in new window

https://juejin.cn/post/7065289935099527198open in new window

https://github.com/nie-ny/blogopen in new window

geojson-3d-map-master

image-20221208160447411
image-20221208160447411

3d-china-map-master

image-20221208161321280
image-20221208161321280