在vue-cli中使用threejs茸塞,并實(shí)現(xiàn)鼠標(biāo)控制移動(dòng)躲庄,以及點(diǎn)擊交互效果

這里假設(shè)已經(jīng)了解threejs中基本的三要素等基礎(chǔ)知識(shí)

如題,前戲不多钾虐,直接提槍上陣
第一步: 創(chuàng)建一個(gè)vue-cli項(xiàng)目
按照vue-cli官網(wǎng)方式創(chuàng)建 , 這里話不多說噪窘,不需要過多設(shè)置,能運(yùn)行起來就歐克(當(dāng)然是vue2.x)效扫。

第二步: 安裝threejs插件
完全可以按照threejs官網(wǎng)的教程安裝

注意:如果打開為英文狀態(tài)倔监,可以在左上角那個(gè)地方點(diǎn)擊en,切換成中文

image.png

第三步: 初始項(xiàng)目展示頁面

安裝完成之后菌仁,運(yùn)行項(xiàng)目浩习, 當(dāng)然沒什么屌變化,還是初始界面掘托。

所以現(xiàn)在以組件的形式繼續(xù)實(shí)現(xiàn)標(biāo)題內(nèi)容瘦锹。

這里只在vue-cli中運(yùn)行threejs,所以不安裝其他插件闪盔,干擾視線

image.png

上圖是目錄結(jié)構(gòu)弯院,下面的代碼內(nèi)容主要是在ThreeJs組件中實(shí)現(xiàn)。所以泪掀,目前主要是先把測試ThreeJs是否能正常引入并展示听绳。(PS: 按照HelloWorld組件的方式,基本上不會(huì)出錯(cuò))

App.vue中的代碼:


image.png

ThreeJs中的代碼:


image.png

到目前為止异赫,基本上頁面上會(huì)展示 hello-threejs


image.png

第四步:開始在ThreeJS組件中椅挣,實(shí)現(xiàn)threejs的舞臺(tái)

  1. 初始化舞臺(tái)头岔,ThreeJs組件
<template>
    <div>
        <canvas class="c" ref="ThreeJS"></canvas>
    </div>
</template>

<script>
    import * as THREE from 'three'
    export default{
        data(){
            return{
                scene: null,
                camera: null,
                cameraPole: null,
                renderer: null,
                canvas: null,
                canvasW: 0,
                canvasH: 0,
                cameraParam: {
                    fov: 30,
                    aspect: 2,
                    near: .1,
                    far: 200
                },
            }
        },
        created() {
            this.canvasW = window.innerWidth;
            this.canvasH = window.innerHeight;
            // 初始化設(shè)置寬高比
            this.cameraParam.aspect = this.canvasW / this.canvasH
        },
        mounted(){
            this.start()
        },
        methods: {
            start(){
                // 初始化三要素
                this.initMain()
                // 啟用渲染
                this.render()
            },
            initMain(){
                // 初始化三要素
                this.initScene()
                this.initCamera()
                this.initRenderer()
                // 添加環(huán)境光
                this.addLight()
            },
            initScene(){
                // 創(chuàng)建場景
                this.scene = new THREE.Scene();
                this.scene.background = new THREE.Color('white');
            },
            initCamera(){
                // 創(chuàng)建透視攝像頭
                const cP = this.cameraParam;
                this.camera = new THREE.PerspectiveCamera(cP.fov, cP.aspect, cP.near, cP.far);
                this.camera.position.z = 30;
                this.scene.add(this.camera)
            },
            initRenderer(){
                // 渲染器
                this.canvas = this.$refs.ThreeJS;
                this.renderer = new THREE.WebGLRenderer({
                    canvas: this.canvas,
                    antialias: true,//是否開啟反鋸齒,設(shè)置為true開啟反鋸齒鼠证。
                    alpha: true,//是否可以設(shè)置背景色透明峡竣。
                    logarithmicDepthBuffer: true//模型的重疊部位便不停的閃爍起來。這便是Z-Fighting問題量九,為解決這個(gè)問題适掰,我們可以采用該種方法
                })
            },
            addLight(){
                // 環(huán)境光
                const color = 0xFFFFFF;
                const intensity = 1;
                const light = new THREE.AmbientLight(color, intensity);
                this.scene.add(light)
            },
            render(){
                // 啟動(dòng)動(dòng)畫
                this.renderer.render(this.scene, this.camera);
                // 動(dòng)態(tài)監(jiān)聽窗口尺寸變化
                if (this.resizeRendererToDisplaySize(this.renderer)) {
                    const canvas = this.renderer.domElement;
                    this.camera.aspect = canvas.clientWidth / canvas.clientHeight;
                    this.camera.updateProjectionMatrix();
                }
                requestAnimationFrame(this.render.bind(this))
            },
            resizeRendererToDisplaySize(renderer){
                const canvas = renderer.domElement;
                this.canvasW = window.innerWidth;
                this.canvasH = window.innerHeight;
                const needResize = canvas.width !== this.canvasW || canvas.height !== this.canvasH;
                if(needResize){
                    this.renderer.setSize(this.canvasW, this.canvasH, false);
                }
                return needResize;
            },
        }
    }
</script>

<style>
    html, body{
        height: 100%;
        margin: 0;
        background: #0033CC;
    }
    .c{
        width: 100%;
        height: 100%;
        display: block;
    }
</style>

到這里,基本上你會(huì)看到一個(gè)白色的地的空白頁面荠列,因?yàn)檫€沒有往畫布里面添加內(nèi)容

  1. 添加形狀以及文字
    data中添加
data(){
  return{
    ...,
    planArr: [
      { x: -3, y: 3, name: '第一塊區(qū)域' },
      { x: 3, y: 3, name: '第二塊區(qū)域'},
      { x: -3, y: -3, name: '第三塊區(qū)域' },
      { x: 3, y: -3, name: '第四塊區(qū)域' }
    ]
  }
}

methods方法中添加

methods: {
  ...,
  initMain(){
    // 這個(gè)是已經(jīng)有的方法类浪,
    // 只是需要在這里調(diào)用一下創(chuàng)建幾何體的方法
    ..., 
    // 添加形狀
    this.createCube()
  },
  geometry(width, height, depth){
    return new THREE.BoxGeometry(width, height, depth)
  },
  createCube(){
    for(let i = 0; i < this.planArr.length; i++){
        // 添加幾何體
        const material = new THREE.MeshPhongMaterial({
            color: 0x8aff58
        })
                    
        const cube = new THREE.Mesh(this.geometry(5, 5, .001), material);
        this.scene.add(cube);
        // 添加名稱
        cube.name = this.planArr[i].name
        // 設(shè)置幾何模型形變
        cube.position.set(this.planArr[i].x, this.planArr[i].y, 0)
    }
  },
}

到這里,你會(huì)看到下面的效果圖:


image.png
  1. 添加鼠標(biāo)劃過方塊以及點(diǎn)擊方塊時(shí)的事件

同樣首先在data中添加數(shù)據(jù)

data(){
  return{
    ...,
    events: {
      raycaster: new THREE.Raycaster(),
      pickedObject: null,
      pickedObjectSavedColor: 0,
      pickPosition: { x: 0, y: 0 }
    },
  }
}

methods中添加方法

methods: {
  ...,
  // 點(diǎn)擊當(dāng)前坐標(biāo)
            clickPickPosition(){
                this.pickEvents(this.events.pickPosition, this.scene, this.camera, obj=>{
                    obj.userData.checked = !obj.userData.checked;
                    // alert(`您選中了--${obj.name}`)
                    if(!obj.userData.checked){
                        obj.material.color.setHex(0x8aff58)
                        alert(`您已經(jīng)取消選中--${obj.name}`)
                    }else{
                        obj.material.color.setHex(0xFFFF00)
                        alert(`您選中了--${obj.name}`)
                    }
                })
            },
            // 獲取當(dāng)前焦點(diǎn)坐標(biāo)
            setPickPosition(event){
                const pos = this.getCanvasRelativePosition(event);
                this.events.pickPosition.x = (pos.x / this.canvas.width) * 2 - 1;
                this.events.pickPosition.y = (pos.y / this.canvas.height) * -2 + 1;
                
                this.pickEvents(this.events.pickPosition, this.scene, this.camera)
            },
            // 獲取當(dāng)前事件焦點(diǎn)坐標(biāo)所在位置
            getCanvasRelativePosition(event){
                const rect = this.canvas.getBoundingClientRect();
                return {
                    x: (event.clientX - rect.left) * this.canvas.width / rect.width,
                    y: (event.clientY - rect.top) * this.canvas.height / rect.height
                }
            },
            // 添加鼠標(biāo)劃過以及點(diǎn)擊事件
            clickEvents(){
                window.addEventListener('click', this.clickPickPosition);   
                window.addEventListener('mousemove', this.setPickPosition);
            },
            // 創(chuàng)建點(diǎn)擊事件(默認(rèn)是離攝像頭最近的相交)
            pickEvents(normalizedPosition, scene, camera, callback){
                // 如果存在拾取的對(duì)象肌似,則恢復(fù)顏色
                if(this.events.pickedObject){
                    this.events.pickedObject.material.emissive.setHex(this.events.pickedObjectSavedColor);
                    this.events.pickedObject = undefined;
                }
                // 沿著攝像頭的方向投射射線
                this.events.raycaster.setFromCamera(normalizedPosition, camera)
                // 獲取與射線光線相交的對(duì)象列表
                const intersectedObjects = this.events.raycaster.intersectObjects(this.scene.children);
                if(intersectedObjects.length){
                    // 獲取與射線光纖相交的第一個(gè)對(duì)象费就。也是最近的一個(gè)
                    this.events.pickedObject = intersectedObjects[0].object;
                    // 保存當(dāng)前對(duì)象的顏色
                    this.events.pickedObjectSavedColor = this.events.pickedObject.material.emissive.getHex();
                    // 將其發(fā)射顏色設(shè)置為閃爍的紅色/黃色
                    this.events.pickedObject.material.emissive.setHex(0xFFFF00)
                    
                    if(callback){
                        callback(this.events.pickedObject)
                    }
                }
            },
}

在methods的initMain方法中調(diào)用一下鼠標(biāo)點(diǎn)擊事件

methods: {
  initMain(){
    ...,
    this.clickEvents();
  }
}

至此,基本上已經(jīng)完全實(shí)現(xiàn)效果: 鼠標(biāo)滑過幾何體變成黃色川队,點(diǎn)擊幾何體變成黃色
完整版代碼中添加了文字

ThreeJS.vue完整版代碼:

<template>
    <div>
        <canvas class="c" ref="ThreeJS"></canvas>
    </div>
</template>

<script>
    import * as THREE from 'three'
    export default{
        data(){
            return{
                scene: null,
                camera: null,
                cameraPole: null,
                renderer: null,
                canvas: null,
                canvasW: 0,
                canvasH: 0,
                cameraParam: {
                    fov: 30,
                    aspect: 2,
                    near: .1,
                    far: 200
                },
                planArr: [
                    { x: -3, y: 3, name: '第一塊區(qū)域' },
                    { x: 3, y: 3, name: '第二塊區(qū)域'},
                    { x: -3, y: -3, name: '第三塊區(qū)域' },
                    { x: 3, y: -3, name: '第四塊區(qū)域' }
                ],
                events: {
                    raycaster: new THREE.Raycaster(),
                    pickedObject: null,
                    pickedObjectSavedColor: 0,
                    pickPosition: { x: 0, y: 0 }
                }
            }
        },
        created() {
            this.canvasW = window.innerWidth;
            this.canvasH = window.innerHeight;
            // 初始化設(shè)置寬高比
            this.cameraParam.aspect = this.canvasW / this.canvasH
        },
        mounted(){
            this.start()
        },
        methods: {
            start(){
                // 初始化三要素
                this.initMain()
                // 啟用渲染
                this.render()
            },
            initMain(){
                // 初始化三要素
                this.initScene()
                this.initCamera()
                this.initRenderer()
                
                // 添加環(huán)境光
                this.addLight()
                
                // 添加形狀
                this.createCube()
                
                // 調(diào)用點(diǎn)擊事件
                this.clickEvents()
            },
            initScene(){
                // 創(chuàng)建場景
                this.scene = new THREE.Scene();
                this.scene.background = new THREE.Color('white');
            },
            initCamera(){
                // 創(chuàng)建透視攝像頭
                const cP = this.cameraParam;
                this.camera = new THREE.PerspectiveCamera(cP.fov, cP.aspect, cP.near, cP.far);
                this.camera.position.z = 30;
                this.scene.add(this.camera)
            },
            initRenderer(){
                // 渲染器
                this.canvas = this.$refs.ThreeJS;
                this.renderer = new THREE.WebGLRenderer({
                    canvas: this.canvas,
                    antialias: true,//是否開啟反鋸齒力细,設(shè)置為true開啟反鋸齒。
                    alpha: true,//是否可以設(shè)置背景色透明呼寸。
                    logarithmicDepthBuffer: true//模型的重疊部位便不停的閃爍起來艳汽。這便是Z-Fighting問題,為解決這個(gè)問題对雪,我們可以采用該種方法
                })
            },
            addLight(){
                // 環(huán)境光
                const color = 0xFFFFFF;
                const intensity = 1;
                const light = new THREE.AmbientLight(color, intensity);
                this.scene.add(light)
            },
            render(){
                // 啟動(dòng)動(dòng)畫
                this.renderer.render(this.scene, this.camera);
                // 動(dòng)態(tài)監(jiān)聽窗口尺寸變化
                if (this.resizeRendererToDisplaySize(this.renderer)) {
                    const canvas = this.renderer.domElement;
                    this.camera.aspect = canvas.clientWidth / canvas.clientHeight;
                    this.camera.updateProjectionMatrix();
                }
                requestAnimationFrame(this.render.bind(this))
            },
            resizeRendererToDisplaySize(renderer){
                const canvas = renderer.domElement;
                this.canvasW = window.innerWidth;
                this.canvasH = window.innerHeight;
                const needResize = canvas.width !== this.canvasW || canvas.height !== this.canvasH;
                if(needResize){
                    this.renderer.setSize(this.canvasW, this.canvasH, false);
                }
                return needResize;
            },
            
            // ====================================基礎(chǔ)設(shè)置完成=======================
            // ====================================創(chuàng)建立方體========================
            geometry(width, height, depth){
                return new THREE.BoxGeometry(width, height, depth)
            },
            createCube(){
                for(let i = 0; i < this.planArr.length; i++){
                    // 添加幾何體
                    const material = new THREE.MeshPhongMaterial({
                        color: 0x8aff58
                    })
                    
                    const cube = new THREE.Mesh(this.geometry(5, 5, .001), material);
                    this.scene.add(cube);
                    // 添加名稱
                    cube.name = this.planArr[i].name
                    // 設(shè)置幾何模型形變
                    cube.position.set(this.planArr[i].x, this.planArr[i].y, 0)
                    
                    // 添加文字
                    let texture = new THREE.Texture(this.getTextCanvas(this.planArr[i].name));
                    texture.needsUpdate = true;
                    let spriteMaterial = new THREE.PointsMaterial({
                            map: texture,
                            size: 12,
                            transparent: true,
                            opacity: 1,
                    });
                    //創(chuàng)建坐標(biāo)點(diǎn)河狐,并將材質(zhì)給坐標(biāo)
                    let geometry = new THREE.BufferGeometry();
                    let vertices = [0, 0, 0];
                    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
                    let sprite = new THREE.Points(geometry, spriteMaterial);
                    sprite.position.set(0, 0, .002);
                    
                    cube.add(sprite);
                }
            },
            // 創(chuàng)建文字canvas
            getTextCanvas(text){
                var width=100, height=100; 
                const canvas = document.createElement('canvas');
                canvas.width = width;
                canvas.height = height;
                const ctx = canvas.getContext('2d');
                ctx.fillStyle = 'transparent';
                ctx.fillRect(0, 0, width, height);
                ctx.font = '6px';
                ctx.fillStyle = '#2891FF';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText(text, width/2,height/2); 
                return canvas;
            },
            // 點(diǎn)擊當(dāng)前坐標(biāo)
            clickPickPosition(){
                this.pickEvents(this.events.pickPosition, this.scene, this.camera, obj=>{
                    obj.userData.checked = !obj.userData.checked;
                    // alert(`您選中了--${obj.name}`)
                    if(!obj.userData.checked){
                        obj.material.color.setHex(0x8aff58)
                        alert(`您已經(jīng)取消選中--${obj.name}`)
                    }else{
                        obj.material.color.setHex(0xFFFF00)
                        alert(`您選中了--${obj.name}`)
                    }
                })
            },
            // 獲取當(dāng)前焦點(diǎn)坐標(biāo)
            setPickPosition(event){
                const pos = this.getCanvasRelativePosition(event);
                this.events.pickPosition.x = (pos.x / this.canvas.width) * 2 - 1;
                this.events.pickPosition.y = (pos.y / this.canvas.height) * -2 + 1;
                
                this.pickEvents(this.events.pickPosition, this.scene, this.camera)
            },
            // 獲取當(dāng)前事件焦點(diǎn)坐標(biāo)所在位置
            getCanvasRelativePosition(event){
                const rect = this.canvas.getBoundingClientRect();
                return {
                    x: (event.clientX - rect.left) * this.canvas.width / rect.width,
                    y: (event.clientY - rect.top) * this.canvas.height / rect.height
                }
            },
            // 添加鼠標(biāo)劃過以及點(diǎn)擊事件
            clickEvents(){
                window.addEventListener('click', this.clickPickPosition);   
                window.addEventListener('mousemove', this.setPickPosition);
            },
            // 創(chuàng)建點(diǎn)擊事件(默認(rèn)是離攝像頭最近的相交)
            pickEvents(normalizedPosition, scene, camera, callback){
                // 如果存在拾取的對(duì)象,則恢復(fù)顏色
                if(this.events.pickedObject){
                    this.events.pickedObject.material.emissive.setHex(this.events.pickedObjectSavedColor);
                    this.events.pickedObject = undefined;
                }
                // 沿著攝像頭的方向投射射線
                this.events.raycaster.setFromCamera(normalizedPosition, camera)
                // 獲取與射線光線相交的對(duì)象列表
                const intersectedObjects = this.events.raycaster.intersectObjects(this.scene.children);
                if(intersectedObjects.length){
                    // 獲取與射線光纖相交的第一個(gè)對(duì)象瑟捣。也是最近的一個(gè)
                    this.events.pickedObject = intersectedObjects[0].object;
                    // 保存當(dāng)前對(duì)象的顏色
                    this.events.pickedObjectSavedColor = this.events.pickedObject.material.emissive.getHex();
                    // 將其發(fā)射顏色設(shè)置為閃爍的紅色/黃色
                    this.events.pickedObject.material.emissive.setHex(0xFFFF00)
                    
                    if(callback){
                        callback(this.events.pickedObject)
                    }
                }
            },
        }
    }
</script>

<style>
    html, body{
        height: 100%;
        margin: 0;
        background: #0033CC;
    }
    .c{
        width: 100%;
        height: 100%;
        display: block;
    }
</style>

最終展示效果:


鼠標(biāo)劃過
第一次點(diǎn)擊選中幾何體
第二次點(diǎn)擊取消選中幾何體
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末馋艺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子迈套,更是在濱河造成了極大的恐慌捐祠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桑李,死亡現(xiàn)場離奇詭異踱蛀,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)贵白,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門率拒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人禁荒,你說我怎么就攤上這事猬膨。” “怎么了呛伴?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵勃痴,是天一觀的道長谒所。 經(jīng)常有香客問我,道長沛申,這世上最難降的妖魔是什么劣领? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮铁材,結(jié)果婚禮上剖踊,老公的妹妹穿的比我還像新娘。我一直安慰自己衫贬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布歇攻。 她就那樣靜靜地躺著固惯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缴守。 梳的紋絲不亂的頭發(fā)上葬毫,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音屡穗,去河邊找鬼贴捡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛村砂,可吹牛的內(nèi)容都是我干的烂斋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼础废,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼汛骂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起评腺,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤帘瞭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蒿讥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蝶念,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年芋绸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了媒殉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侥钳,死狀恐怖适袜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舷夺,我是刑警寧澤苦酱,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布售貌,位于F島的核電站,受9級(jí)特大地震影響疫萤,放射性物質(zhì)發(fā)生泄漏颂跨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一扯饶、第九天 我趴在偏房一處隱蔽的房頂上張望恒削。 院中可真熱鬧,春花似錦尾序、人聲如沸钓丰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽携丁。三九已至,卻和暖如春兰怠,著一層夾襖步出監(jiān)牢的瞬間梦鉴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工揭保, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肥橙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓秸侣,卻偏偏與公主長得像存筏,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子塔次,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容