一文帶你了解threejs在vue項目中的基本使用
Three.js 是一個跨瀏覽器的腳本,使用 JavaScript 函數(shù)庫或 API 來在網(wǎng)頁瀏覽器中創(chuàng)建和展示動畫的三維計算機圖形。為啥突然想寫這么一篇文章的主要原因其實是前幾天有個人需要我?guī)兔懸粋€簡單的 demo,花了幾個小時之后覺得基本上 threejs 基本的使用效果都實現(xiàn)了,之前就看過 threejs 的東西,但是一直沒有時間靜下心來整理匯總一下,所以說呢,今天時間比較充足,就稍微的記錄一下。當然了,我也沒有深入的學習使用,學習的時間很短,所以說也談不上經(jīng)驗的分享,就算是一個簡單的學習記錄吧,淺看則以,切勿盡信。
其實相對來說 threejs 的學習成本比較高的,需要掌握的知識相對來說會稍微雜一些,但是簡單的入門倒是很簡單,現(xiàn)在網(wǎng)上的資料還是很多的,無論是博客還是視頻都是比較充足的,然后接下來的博文內(nèi)容,就簡單的介紹一些在 vue2 項目中 threejs 的基本使用。
threejs 介紹
threejs 是運行在瀏覽器中的 3D 引擎,是JavaScript編寫的WebGL第三方庫。提供了非常多的3D顯示功能。開發(fā)者可以用它創(chuàng)建各種三維場景,包括了攝影機、光影、材質(zhì)等各種對象??梢栽谒闹黜撋峡吹皆S多精彩的演示。不過,這款引擎還處在比較不成熟的開發(fā)階段,其不夠豐富的 API 以及匱乏的文檔增加了初學者的學習難度(尤其是文檔的匱乏)。
前言
在講解 threejs 的時候,我們通過一個基本的簡單的案例,來實現(xiàn)一個小的效果,然后把常用的 API、工具、功能稍微說一下哈!
這個案例我是使用 vue2 + 腳手架工具創(chuàng)建的項目,采用 javascript 開發(fā)。再次之前需要先準備一個 vue 的空項目,好在我們不需要使用網(wǎng)絡請求,直接默認創(chuàng)建一個 vue2 的項目即可,不需要過多的配置。
安裝 threejs
安裝 threejs 的方式也很簡單,直接使用 npm 工具就可以安裝到項目里面使用:
npm install --save three
在終端輸入命令然后回車等待執(zhí)行完成就可以了!
安裝完成之后,就可以看到 package.json 文件中已經(jīng)包含了我們剛剛安裝的 three 依賴。
同時,在 node_modules 文件夾下,也出現(xiàn)了 three 相關的包依賴。
這樣,我們就成功將 threejs 相關的依賴添加到我們的項目,就可以繼續(xù)進行后續(xù) threejs 相關功能的開發(fā)了。
初始化項目
這個步驟就不多說了,直接使用 cli3 以上的版本創(chuàng)建一個 vue2 的項目,然年修改一下組件內(nèi)容,創(chuàng)建一個 div 標簽鋪滿整個瀏覽器頁面就可以了。
<template> <div class="three-canvas" ref="threeTarget"></div> </template> <script> export default { name: 'HelloWorld', } </script> <style scoped> .three-canvas { width: 100%; height: 100%; overflow: hidden; background-color: #d6eaff; } </style>
大體效果就是下面的樣子,當然了這個無所謂了。
然后我們在這個組件中實現(xiàn) threejs 效果,效果呢,掛載到我們創(chuàng)建的 <div class="three-canvas" ref="threeTarget"></div>
標簽上面渲染。
為了保證項目代碼稍微的有點規(guī)范性,我們創(chuàng)建一個 TEngine.js
文件,在當前組件引入,然后呢,所有與 threejs 初始化、操作等代碼都是 TEngine.js
文件中實現(xiàn)。
創(chuàng)建渲染器 WebGLRenderer
接下來我們在 TEngine.js 文件中初始化一個 threejs ,首先第一步,我們需要有一個 dom 掛載我們創(chuàng)建的 threejs ,啥叫掛載呢,簡單點說就是我創(chuàng)建的 3D 模型顯示在哪里,我們之前初始化項目不是創(chuàng)建一個全屏的 div 嗎?然后我們就把 3D 模型放在這個 div 上面顯示。
第一步,我們現(xiàn)在 TEngine.js 中創(chuàng)建并交出一個 ThreeEngine 類,然后這個類,在組件中實例化就可以了,前面說了,需要一個 dom 節(jié)點掛載模型,那么我們首先得接收一個 dom 節(jié)點吧?所以說在構(gòu)造器函數(shù)里面獲取到傳入的 dom 節(jié)點,然后掛載。
export class ThreeEngine { dom = null; // 掛載的 DOM // 構(gòu)造器函數(shù) constructor(dom) { this.dom = dom } }
然后我們就可以在組件中實例化這個類了。注意,需要在 mounted 生命周期鉤子中實例化吧?不能在 created 生命周期鉤子中,為啥,因為 mounted 才是 dom 都渲染完成吧,好:
<script> import { ThreeEngine } from './js/TEngine' export default { name: 'HelloWorld', data() { return { ThreeEngine: null, }; }, mounted() { this.ThreeEngine = new ThreeEngine(this.$refs.threeTarget) } } </script>
OK,這樣子第一步就完成了,但是呢頁面沒效果,因為我們剛剛開始,完全沒有任何的 threejs 的操作。
接下來,就是 threejs 相關的操作了哈,都在 ThreeEngine 類的構(gòu)造器函數(shù)中實現(xiàn)。
【引導】首先你想,我們想在一個 div 上面展示 3D 模型的東西,是不是首先得有一個東西把這個 3D 模型轉(zhuǎn)換成我們?yōu)g覽器可以展示的畫面放在我們傳遞進來 div 上展示啊,這個幫助我們把 模型 展示到 div 上的東西就可以簡單的理解成渲染器
。舉一個例子:老師說我們準備換一個新教室,老師想看一下新教室的布局,但是自己有事過不去,怎么辦?找個同學小明幫忙過去看一下就可以了吧,怎么讓老師親眼看到?對,視頻通話,小明拿手機拍攝,然后老師在手機上就可以看到這個新教室的布局了吧,那這個小明就是渲染器。所以第一步,找一個小明。
老師的渲染器是小明,而 threejs 的渲染器就是 WebGLRenderer。WebGLRenderer是 three 中提供的一個工具類,我們在使用之前需要先引入他,使用也很簡單。
import {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> WebGLRenderer } from 'three'
首先創(chuàng)建一個渲染器:
let renderer = new WebGLRenderer() // 創(chuàng)建渲染器
創(chuàng)建完成之后,我們需要把這個渲染器掛載到 dom 上面,這樣,渲染器渲染的效果就可以展示在 div 上面,就是學生和老師打視頻電話,才可以讓老師在自己的手機看到新教室布局。
dom.appendChild(renderer.domElement) // 將渲染器掛載到dom
問題來了,我們告訴渲染器說:你把 threejs 的效果展示在 div 上面。可以渲染器有點蒙蔽還,就是我要渲染多大???這個 div 有高寬,我是渲染在這個 div 的那個部分呢?所以說還需要設置一下渲染器的大小吧?我們一般設置的和 dom 節(jié)點一樣大小就可以。
renderer.setSize(dom.offsetWidth, dom.offsetHeight, true)
這樣我們的渲染器初始化的全部代碼就完成了!
import { WebGLRenderer } from 'three' export class ThreeEngine { dom = null; // 掛載的 DOM constructor(dom) { // 創(chuàng)建渲染器 let renderer = new WebGLRenderer({ antialias: true, // 開啟抗鋸齒 }) dom.appendChild(renderer.domElement) // 將渲染器掛載到dom renderer.setSize(dom.offsetWidth, dom.offsetHeight, true) this.dom = dom } }
我們看一下頁面效果。
非常好,和沒有初始化之前一模一樣,為啥。
【引導】還是老師想看新教室,渲染器小明有了,但是小明拿手機拍啥啊?新教室對吧?但是我們只是找到了小明,交代給小明說你去渲染給我看,但是并沒有告訴小明去看啥,這里讓小明看的東西叫做場景
,我們需要告訴小明看什么場景才可以。所以說下一步,找一個場景。
創(chuàng)建場景 Scene
threejs 中的場景是 Scene,同樣這個也是 threejs 提供的工具類,使用的話也需要引入,創(chuàng)建一樣簡單。
import { WebGLRenderer, Scene } from 'three'
創(chuàng)建場景直接 new 就可以。
let scene = new Scene() // 實例化場景 this.scene = scene
就這兩行代碼初始完場景了,然后到此為止,所有的代碼就是下面這樣的。
import { WebGLRenderer, Scene } from 'three' export class ThreeEngine { dom = null; // 掛載的 DOM scene = null; // 場景 constructor(dom) { // 創(chuàng)建渲染器 let renderer = new WebGLRenderer({ antialias: true, // 開啟抗鋸齒 }) dom.appendChild(renderer.domElement) // 將渲染器掛載到dom renderer.setSize(dom.offsetWidth, dom.offsetHeight, true) let scene = new Scene() // 實例化場景 this.dom = dom this.scene = scene } }
我們看一下效果:
我勒個去!還是怎么東西沒有,我之前一模一樣。這又是為啥!
【引導】還是老師讓小明看新教室,渲染器小明有了,場景也有了。但是小明拿著手機懵了,為啥懵了,小明到了新教室,他不知道怎么給老師看新教室,我們想法是啥,小明拿手機打視頻給老師看,但是小明不知道?。∥覀兊媒o小明一個有攝像機的手機才可以。繼續(xù),小明有相機了,但是小明比較笨,他不知道從那個角度拍給老師看(盡管小明笨,但不許換掉小明),所以說我們還得告訴小明拍攝的位置,也就是說從哪個角度拍攝吧。
創(chuàng)建相機并設置位置 PerspectiveCamera
threejs 中的相機是 PerspectiveCamera,他同樣是 three 提供的工具類,我們需要引入,然后在實例化。
import { WebGLRenderer, Scene, PerspectiveCamera } from 'three'
怎么創(chuàng)建相機有幾個步驟,首先實例化一個相機;然后需要設置相機的位置,就是從哪里拍;再然后設置相機拍攝的位置,就是拍具體哪里;最后可以設置相機角度,就是歪著拍還是豎著拍;
首先是初始化相機
// 實例化相機 let camera = new PerspectiveCamera(45, dom.offsetWidth / dom.offsetHeight, 1, 1000)
這里傳了幾個參數(shù),分別是啥意思稍微說一下。
- 第一個參數(shù) 45 是 攝像機視錐體垂直視野角度,人眼看東西就差不多60度左右嘛,不可能看到頭后面的東西,這里也是這個意思,一般就設置 45。
- 第二個參數(shù) dom.offsetWidth / dom.offsetHeight 是攝像機視錐體長寬比,我們就設置是我們 div 容器的長寬比就可以,如果不這樣設置,可能會變形。因為我們看到的要和相機看到的一樣大小,不然會被拉伸。
- 第三個參數(shù) 1 是攝像機視錐體近端面
- 第四個參數(shù) 1000 攝像機視錐體遠端面
然后是設置相機位置,就是相機都放在哪里。
camera.position.set(50, 50, 50) // 設置相機位置
我們把相機放在 three 坐標 50 50 50 的位置。
然后是設置相機看向哪里,這里我們讓相機看向原點。
camera.lookAt(new Vector3(0, 0, 0)) // 設置相機看先中心點
我們還可以設置相機自身的方向。
camera.up = new Vector3(0, 1, 0) // 設置相機自身的方向
這里我們稍微補充一點知識點,因為沒有圖形學基礎的話可能不好理解,首先說一點,threejs 坐標系是向右為 x 軸正方向,垂直屏幕向外為 z 軸的正方向,向上為 y 軸正方向。
所以說設置相機的位置和看向原點就理解了哈,然后渲染器默認加載完成后他的中心就是(0,0,0)原點,分別對應 (x,y,z)。
camera.up 是用來設置相機自身的方向設置 y = 1 表示 y 軸的正方向為相機向上的方向,可能沒說明白,就是相機向上移動就是向 three 坐標系 y 軸的正方向移動。
到這里,我們初始化相機的部分就完成了,然后我們到此位置所有代碼:
import { WebGLRenderer, Scene, PerspectiveCamera, Vector3 } from 'three' export class ThreeEngine { dom = null; // 掛載的 DOM scene = null; // 場景 constructor(dom) { // 創(chuàng)建渲染器 let renderer = new WebGLRenderer({ antialias: true, // 開啟抗鋸齒 }) dom.appendChild(renderer.domElement) // 將渲染器掛載到dom renderer.setSize(dom.offsetWidth, dom.offsetHeight, true) let scene = new Scene() // 實例化場景 // 實例化相機 let camera = new PerspectiveCamera(45, dom.offsetWidth / dom.offsetHeight, 1, 1000) camera.position.set(50, 50, 50) // 設置相機位置 camera.lookAt(new Vector3(0, 0, 0)) // 設置相機看先中心點 camera.up = new Vector3(0, 1, 0) // 設置相機自身方向 this.dom = dom this.scene = scene } }
然后我們保存代碼,看一下頁面效果。
非常好,還是那個樣子,啥都沒有。
為啥呢?再來引導一波!
【引導】我們初始化了渲染器,找到小明了;初始化了場景,讓小明去了新教室;相機準備好了,小明掏出手機對準了目標。但是沒有視頻??!老師啥也看不到。所以我們接下來需要把這個相機和場景綁定到渲染器里面。
綁定很簡單,只需要在初始化相機之后呢,把場景和相機綁進渲染器,讓渲染器渲染就可以了:
renderer.render(scene, camera) // 渲染器渲染場景和相機
OK,現(xiàn)在在看一下效果。
全部變黑了是吧?這就是成功了,為啥是黑的呢,因為現(xiàn)在這個場景沒有東西,如果有東西的話就可以展示出來了吧。
添加模型 Mesh
現(xiàn)在我們創(chuàng)建一個立方體放進場景里面去,我們就可以看到一個模型了吧?好的,現(xiàn)在開始!
為了保證我們項目代碼的結(jié)構(gòu),我們創(chuàng)建一個 TBaseObject.js 文件,用來存放基礎的模型,然后這個文件中我們創(chuàng)建一個立方體模型,并返回出來。
我們就簡單點,先聲明一個數(shù)組拋出,然后數(shù)組里面是創(chuàng)建的模型,這樣外面使用這個文件的時候,導入就可以獲取模型的列表了。
export const allBaseObject = [] // 返回所有基礎模型
然后創(chuàng)建一個立方體模型,當然也可以拋出去,也可以往數(shù)組里面添加一下,這樣的話我們既可以單獨使用這個立方體,也可以獲取全部模型。
創(chuàng)建一個簡單的立方體很簡單,Mesh 是 three 提供的基于以三角形為polygon mesh(多邊形網(wǎng)格)的物體的類,我們可以通過他創(chuàng)建一個立方體。
// 創(chuàng)建立方體 export const box = new Mesh( new BoxGeometry(20, 20, 20), // 設置立方體的大小 (x 長度, y 高度 ,z 長度) new MeshStandardMaterial({ // 設置材質(zhì) color: 'rgb(36, 172, 242)', // 設置材質(zhì)的顏色 }) ) allBaseObject.push(stage) // 添加到模型數(shù)組
小地方說一下哈,設置模型大小肯定需要的,這個模型多寬、多高、多長。那材質(zhì)是啥意思,就是我們這個立方體的樣式,比如顏色,光澤等屬性,當然如果是實際模型可能還有貼圖之類的。簡單理解就是什么樣子的。
當然,中間使用的類也需要引入一下。
import {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> BoxGeometry, Mesh, MeshStandardMaterial } from "three"
好,創(chuàng)建完成做一個事情,就是我們需要在 three 中把這個立方體添加進三維場景中,我們在 TEngine.js 文件中創(chuàng)建一個方法,用來向場景中添加模型。
/** * 向場景中添加模型 * @param {...any} object 模型列表 */ addObject(...object) { object.forEach(elem => { this.scene.add(elem) // 場景添加模型 }) }
然后我們在組件中把獲取模型列表,然后呢,把模型添加到場景中。
import {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> allBaseObject } from './js/TBaseObject'
再 threejs 初始化完成后,調(diào)用我們寫的方法,把模型列表添加到場景。
this.ThreeEngine.addObject(...allBaseObject) // 添加基礎模型
代碼我最后會全部提交到 gitee,到時候如果需要可以看一下。
這樣我們在看一下效果:
哇偶,還是黑色的。為啥呢,在引導一波!
【引導】小明開視頻了,但是老師眼前一黑,為啥?沒開燈唄!其實 threejs 還是很真實的,他里面集成了光線的設置,如果沒有光線,就和實際生活一樣,完全就是漆黑的一篇,真棒!那么接下來,我們給場景添加一個“自然光”。
光線添加
嗯,現(xiàn)實生活中光線有很多了,比如說房間一盞燈,點亮之后就是一個點光源向四周發(fā)散光,在比如聚光,各大晚會的聚光燈照在一個人身上這種。threejs 中也存在這種光源,我們先編寫一個最簡單的光線,叫 “自然光”。
注意一點,我們創(chuàng)建的很多東西如果想展示出來都需要添加到場景才可以,比如我們創(chuàng)建的立方體、現(xiàn)在要創(chuàng)建的自然光,以及后邊說的光線輔助啥的都需要添加進場景才可以看到,那么我們寫這個光線的時候和立方體一樣,創(chuàng)建一個 TLights.js 文件,把光源創(chuàng)建出來,然后引入到組件然后添加進場景進行展示。
創(chuàng)建光線其實很簡單:
import { AmbientLight } from "three" /** * 光線 */ export const allLights = [] // 添加環(huán)境光(自然光),設置自然光的顏色,設置自然光的強度(0 最暗, 1 最強) export const ambientLight = new AmbientLight('rgb(255,255,255)', 0.8) allLights.push(ambientLight)
threejs 中的自然光是 AmbientLight ,使用之前需要引入,引入完成實例化的時候需要傳遞兩個參數(shù):
- 第一個參數(shù)是光線的顏色。
- 第二個參數(shù)是光線的強度。0最暗,1最亮。
然后我們同樣也是在 組件 中引入光線,然后將光線添加到場景。
this.ThreeEngine.addObject(...allLights) // 添加光線
這樣,光線就被我們添加到場景了,我們再來看一下效果。
啊? 還是黑色的!這又是怎么回事?。。。。?!
【說明】我們知道,頁面是有刷新率的,比如 60hz 表示屏幕一秒鐘渲染60個頁面,我們的眼睛有延時,頁面切換的太快,所以說我們看到的就是一個視頻效果,但是 threejs 的渲染器,在初始化渲染器完成之后就只渲染了一次就不管了,所以說后邊我們再修改場景修改模型的時候,并沒有給我們渲染,所以說我們需要自己寫代碼然他渲染,怎么寫呢,官網(wǎng)其實說的也很明白,一段代碼加上就 OK 了。
接下來,我們在 構(gòu)造器函數(shù) 最后加上這段代碼,threejs 就會一直幫我們逐幀渲染頁面效果。
// 逐幀渲染threejs let animate = () => { renderer.render(scene, camera) // 渲染器渲染場景和相機 requestAnimationFrame(animate); } animate()
我們現(xiàn)在再來看效果:
終于,我們的立方體加載出來了。如果我們不設置正方體的位置,默認模型初始化加載在原點位置。
我們看到渲染器背景是黑色的,這是因為我們沒有設置,他默認就是黑色的,我們可以給渲染器設置其他的顏色,在渲染器綁定完相機和場景之后:
renderer.setClearColor('rgb(239, 70, 1)') // 設置渲染器的顏色
他就可以被設置成我們想設置的任意顏色。
好了,這就是最基本的使用。
軌道控制器 OrbitControls
上面我們說完了基本的初始化渲染器、相機、場景、添加模型、設置光線之后,我們發(fā)現(xiàn)一個問題啊,就是這個頁面是靜態(tài)的,我們之前看百度地圖或者是其他 cesium 創(chuàng)建場景之后,鼠標可以拖動,放大縮小,但是現(xiàn)在我們編寫的案例還不可以,接下來我們實現(xiàn)這個功能。
要想實現(xiàn)鼠標操控,需要使用 threejs 的另一個工具類,那就是 OrbitControls,它叫做軌道控制器。
怎么使用呢?首先需要引入進項目,主要,這個工具類不是 three 中提供的,而是在它提供的案例里面,我們需要單獨引入。
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
引入完成,需要初始化軌道控制器。
let orbitControls = new OrbitControls(camera, renderer.domElement)
OK,初始化完成再去看效果,我們的案例就可以鼠標旋轉(zhuǎn)縮放了。
這就是 軌道控制器 的基本使用。使用方式也很簡單:
- 鼠標左鍵按下拖拽:圍繞視圖中心點旋轉(zhuǎn)。
- 鼠標中鍵滾動:縮小放大,實際是相機靠近和遠離。
- 鼠標右鍵按下拖拽:移動場景。
【拓展】
再稍微拓展一個軌道控制器的地方,就是我們的軌道控制器鼠標按鍵功能,是可以設置的,因為我們后面可能介紹鼠標點擊事件,所以說鼠標左鍵按下事件可能有沖突,所以說我們重新設置一下,中鍵功能不變,旋轉(zhuǎn)改為右鍵操作,左鍵什么功能都沒有。
let orbitControls = new OrbitControls(camera, renderer.domElement) orbitControls.mouseButtons = { // 設置鼠標功能鍵(軌道控制器) LEFT: null, // 左鍵無功能 MIDDLE: MOUSE.DOLLY, // 中鍵縮放 RIGHT: MOUSE.ROTATE // 右鍵旋轉(zhuǎn) }
里面使用了 MOUSE,這是 three 提供的,我們得引入一下:
import { WebGLRenderer, Scene, PerspectiveCamera, Vector3, MOUSE } from 'three'
這樣設置之后,我們鼠標按鍵的功能就發(fā)生了變化,可以試一下。
OK,發(fā)現(xiàn)鼠標功能確實實現(xiàn)了。但是有沒有發(fā)現(xiàn)一個很大的問題?。烤褪歉究床怀隽⒎襟w的感覺來,你說他是立方體,我還就說他是一個多邊形不停的變換呢!
確實是這樣哈!正經(jīng)的立方體他是有輪廓顯示的,類似于下面:
但是現(xiàn)在沒有為啥。稍微解釋一下,為了看見這個立方體,我們使用了環(huán)境光,環(huán)境光有一個特點,啥特點呢,就是說,他在模型的每一個面上光照強度都是一樣的,不會衰減,所以說我們看到的模型,他每個面放光是一樣的,根本看不出立體感。如果想要立體感怎么辦?很簡單哈,換一種光線,不使用環(huán)境光了,我們使用一個點光源,從一個點射出一束光向四周擴散,這樣的話,照在模型上,因為距離不一樣,光照強度就不一樣,立體感就出來了。
添加點光源 PointLight
我們之前在 TLights.js 文件創(chuàng)建了一個環(huán)境光,現(xiàn)在我們再創(chuàng)建一個點光源 PointLight,添加到場景中去。因為之前封裝好了,我們只需要創(chuàng)建完點光源,然后把點光源放進光源數(shù)組就可以了吧。
創(chuàng)建點光源使用的是 PointLight,這個工具類同樣是 three 中提供的,我們需要引入一下子。
import { AmbientLight, PointLight } from "three"
然后就是創(chuàng)建點光源,創(chuàng)建點光源和創(chuàng)建環(huán)境光有點不一樣,因為他就像一個燈泡,需要有顏色、強度、能照射多遠、光照衰減值,最后還有位置:
// 點光源 export const pointLight = new PointLight( 'rgb(255,255,255)', 0.5, 600, 0.2 ) pointLight.position.set(0, 100, 200) // 設置點光源位置 (x,y,z) allLights.push(pointLight) // 將點光源添加到光源列表拋出
PointLight 有四個參數(shù):
- color - (可選參數(shù))) 十六進制光照顏色。默認 0xffffff (白色)。
- intensity - (可選參數(shù)) 光照強度。 缺省值 1。
- distance - 這個距離表示從光源到光照強度為0的位置。 當設置為0時,光永遠不會消失,默認0。
- decay - 沿著光照距離的衰退量。默認 1。
OK,現(xiàn)在我們再來看一下添加完點光源之后,模型效果:
非常好,模型的立體感已經(jīng)出來了。
模型部分拓展
我們既然說完了光線,其實還有很多中光線,可以去官網(wǎng)查看相關使用。
接下來我們稍微拓展一點兒東西哈,就是我們之前創(chuàng)建模型是使用的下面的代碼:
// 創(chuàng)建立方體 export const box = new Mesh( new BoxGeometry(20, 20, 20), // 設置立方體的大小 new MeshStandardMaterial({ // 設置材質(zhì) color: 'rgb(36, 172, 242)', // 設置材質(zhì)的顏色 }) )
我們可以向這個模型添加數(shù)據(jù)的,比如我們設置個 name,我這個立方體叫做 “box” 可以吧。只需要這樣寫就可以配置他的 name 屬性。
export const box = new Mesh( new BoxGeometry(20, 20, 20), // 設置立方體的大小 new MeshStandardMaterial({ // 設置材質(zhì) color: 'rgb(36, 172, 242)', // 設置材質(zhì)的顏色 }) ) box.name = 'box'
除了 name 之外還可以設置他的位置。
box.position.set(5, 5, 5) // 設置模型位置 (x,y,z)
當然,位置信息也可以單獨設置。
box.position.x = 5 box.position.y = 5 box.position.z = 5
單獨設置每個坐標軸的位置也是可以的。
在實際開發(fā)的時候,比如我們有一個模型,我們需要給這個模型綁定一些數(shù)據(jù),點擊彈窗顯示或者是鼠標懸浮顯示的時候獲取到這些數(shù)據(jù),怎么綁定數(shù)據(jù)呢?其實我們可以直接設置,比如:
box.sheshimoxingshuju = { name: 'box', user: '我是ed.' }
當然,threejs 提供了一個參數(shù) userData
用來存放用戶數(shù)據(jù),建議放到那里面,默認我們都放到 uerData 里面,這樣是為了以后多人開發(fā),不至于每個人創(chuàng)建一個屬性最后亂套了都。
export const box = new Mesh( new BoxGeometry(20, 20, 20), // 設置立方體的大小 new MeshStandardMaterial({ // 設置材質(zhì) color: 'rgb(36, 172, 242)', // 設置材質(zhì)的顏色 }) ) box.name = 'box' // 設置模型 name box.position.set(5, 5, 5) // 設置模型位置 box.position.x = 5 box.position.y = 5 box.position.z = 5 box.sheshimoxingshuju = { name: 'box', user: '我是ed.' } box.userData = { name: '我是ed.' }
怎么確定我們都設置成功了?我們打印一下就可以了。
我們直接打印一下 box 就可以看到我們配置的都是生效了的,都存進去了的。為啥突然想說這個,主要是想說一下 name 設置的,因為后邊可能要根據(jù)模型的 name 從場景中獲取模型,所以說一下模型的 name 怎么設置。我們?nèi)サ魷y試多余的代碼哈。
然后再說一下模型的材質(zhì)問題
還是這段代碼:
export const box = new Mesh( new BoxGeometry(20, 20, 20), // 設置立方體的大小 new MeshStandardMaterial({ // 設置材質(zhì) color: 'rgb(36, 172, 242)', // 設置材質(zhì)的顏色 }) )
關于材質(zhì),我們只設置了一個顏色對吧!頁面效果也顯示出來了,然后是藍色的很精致的小盒子,他除了顏色還可以設置其他的屬性,比如:粗糙度 roughness。
roughness 粗糙度是啥意思,就比如說我們生活里面,木頭的粗糙度就很高,玻璃的粗糙度就很低。
roughness 上怎么提現(xiàn)粗糙度呢,roughness 的取值范圍是 0 到 1。當 roughness 為 0 時,表示粗糙度最低,就越光滑;當 roughness 為 1 時,表示粗糙度最高,越粗糙。
比如我們給這個正方體設置一個粗糙度為 0 ,也就是最光滑。
export const box = new Mesh( new BoxGeometry(20, 20, 20), // 設置立方體的大小 new MeshStandardMaterial({ // 設置材質(zhì) color: 'rgb(36, 172, 242)', // 設置材質(zhì)的顏色\ roughness: 0 // 粗糙度(0 最光滑,1 最粗糙) }) )
我們怎么看效果,這也是為啥我在這里說粗糙度而不是在添加模型說的原因,我們在場景添加了一個點光源,可以理解成就是一個燈吧!如果一個物體,他表面光滑到一個程度之后他會反光的!我就把正方體的粗糙度調(diào)到最低,也就是最光滑的時候,他肯定會反光吧,那我們調(diào)節(jié)模型,看他有沒有反光的時候??葱Ч?/p>
找到反光的點了,是吧!但是如果我們粗糙度調(diào)到最高,是絕對不可能反光的,這里我們就不看了,有興趣的可以自己看一下。
除了粗糙度,在說一個吧,就是 金屬度 metalness。
我們在生活中見過鐵吧!見過不銹鋼吧!見過鋁合金吧!那種金屬質(zhì)感很酷吧?就算是相同的顏色,塑料和金屬你一眼就分個大概吧!
metalness 就是用來設置模型金屬質(zhì)感的,他的取值也是從 0 到 1,當 metalness 為 0 表示金屬質(zhì)感最少,最不像金屬;metalness 為 1 表示金屬質(zhì)感最強,最像金屬。
我們在給模型添加一個金屬質(zhì)感。
export const box = new Mesh( new BoxGeometry(20, 20, 20), // 設置立方體的大小 new MeshStandardMaterial({ // 設置材質(zhì) color: 'rgb(36, 172, 242)', // 設置材質(zhì)的顏色 metalness: 0.5, // 金屬度 (1 最像金屬,0 最不想金屬) roughness: 0 // 粗糙度(0 最光滑,1 最粗糙) }) )
我這里金屬質(zhì)感設置的 0.5,為啥,現(xiàn)實生活中有沒有一個感覺,就是一個金屬塊,表面越光滑,金屬感越強,他的顏色就越暗,暗的發(fā)黑。
看,我設置完金屬度之后,模型不如之前亮了,但是沒有看出金屬質(zhì)感???別急,我移動一下,照樣讓他返回看一下,金屬質(zhì)感立馬就出來了。
怎么樣!厲害吧!啊哈哈哈哈!
好了,關于這個小的拓展部分就到這里吧!完成!
添加輔助線
這一部分說一些輔助工具,我們添加模型啥的,包括模型的定位,都是憑感覺,不知道各個軸的方向,也不知道原點位置,所以說能不能讓原點位置和坐標軸可視化?
答案是肯定的, threejs 為我們提供了輔助線,用來可是畫坐標軸。接下來就實現(xiàn)一下坐標軸的可視化操作。
首先我們還是和模型、光線一樣,創(chuàng)建一個輔助文件 THelper.js ,在這個 js 文件中創(chuàng)建輔助線,然后拋出,在組件中接受,最后添加在場景里面,我們就可以看到坐標軸輔助線了。
首先坐標軸輔助線是 AxesHelper,這個工具類是 three 提供的,所以說我們需要單獨引入一下。
import { AxesHelper } from 'three'
引入完成就可以使用來了。接下來創(chuàng)建輔助:
import { AxesHelper, GridHelper } from 'three' export const allHelper = [] // 坐標輔助 export const axesHelper = new AxesHelper(500) // 創(chuàng)建坐標輔助 (500 為輔助線的長度) allHelper.push(axesHelper) // 添加到輔助列表
還是,在組件中引入,然后就可以添加到場景里面去了。
import { allHelper } from './js/THelper'
添加到場景:
this.ThreeEngine.addObject(...allHelper) // 添加輔助
這樣輔助線就添加到場景中去了,我們可以看一下效果。
這里的 紅色線就是 x 軸,藍色線就是 z 軸,綠色線就是 y 軸。
除了坐標輔助線,我們還可以添加地面網(wǎng)格線 GridHelper。
import { AxesHelper, GridHelper } from 'three' export const allHelper = [] // 坐標輔助 export const axesHelper = new AxesHelper(500) // 創(chuàng)建坐標輔助 // 創(chuàng)建地面網(wǎng)格輔助 export const gridHelper = new GridHelper(100, 10, 'red', 'rgb(222, 225, 230)') allHelper.push(axesHelper, gridHelper)
網(wǎng)格輔助線一共需要配置四個參數(shù):
- size – 坐標格尺寸. 默認為 10. 這就是網(wǎng)格組成的大正方形最大是多少
- divisions – 坐標格細分次數(shù). 默認為 10. 組成的最大的正方向平均分多少份
- colorCenterLine – 中線顏色. 值可以為 Color 類型, 16進制 和 CSS 顏色名. 默認為 0x444444。這個是指網(wǎng)格和坐標的 x軸 z 軸重合線的顏色。
- colorGrid – 坐標格網(wǎng)格線顏色. 值可以為 Color 類型, 16進制 和 CSS 顏色名. 默認為 0x888888
我們看一下效果:
這樣,地面網(wǎng)格線也出來了。其實光線輔助也是有的,但是呢,我不想寫了,如果需要的話去官網(wǎng)看一下怎么使用自己加進去就可以了。得接著往下說其他的功能了。
模型編輯邏輯梳理
到這里的話,模型加載展示基礎就基本上完成了,如果是我們自己從網(wǎng)上下載的模型不是這樣添加,我看看有時間開一篇新的博客說一下,但是這篇博客因為是基礎嘛,就不說加載第三方模型的東西了。
現(xiàn)在這一部分說一下模型的編輯。
我們通過上面的步驟,成功的把立方體模型添加到場景了,但是我們發(fā)現(xiàn),添加的位置是原點嘛,因為沒有設置初始位置,所以說就是默認原點,那我現(xiàn)在想鼠標拖動這個模型,改變模型的位置,甚至是旋轉(zhuǎn)這個模型,也可能拉伸這個模型讓他進行形變怎么辦?可以實現(xiàn)嗎?
答案是可以的,threejs 幫我們提供了這樣一個操作類,接下來我們就說一下這部分的使用。
【分析】
我們先來分析一波。我們想讓模型通過鼠標拖拽的方式移動位置,他有幾種移動的方向?這個和二維的不一樣吧?二維的只有長寬,所以說移動一個二維的東西,只有 x 軸 和 y 軸移動吧?但是 threejs 是三維的,他除了 x軸,y軸 還有一個 z 軸,三條軸立體移動。同樣如果旋轉(zhuǎn)、形變也都是和二維是不一樣的,不是拖拖鼠標在一個平面移動一下就可以的。
所以說,threejs 為我們提供了一個工具類叫做 變換控制器 TransformControls 。他可以提供一種類似于在數(shù)字內(nèi)容創(chuàng)建工具(例如Blender)中對模型進行交互的方式,來在3D空間中變換物體。
他類似于這個樣子:
我們要做的是什么操作,就是我們點擊要移動模型,針對這個被點擊的模型綁定一個 變換控制器,變換控制器有三根軸,分別對應的就是 threejs 坐標系的 x 軸 、y 軸 、z 軸,我們拖動 變換控制器 的軸,就可以實現(xiàn)對應模型的移動。
OK,所以說,首先要實現(xiàn)的一件事情是啥?不是初始化變換控制器,而是點擊事件。
【分析】 再分析一波!二維里面我們看到一個正方形,我們想要點擊正方形怎么做?鼠標移動上去直接點擊就可以吧?但是我們 threejs 是三維的,是一個立體的空間,我問一下,看下面的圖片,我把視圖范圍放大,讓立方體離相機遠一點,所以顯得立方體變小了。
這個時候,我把鼠標移動到藍色立方體那個位置,點擊左鍵,有沒有點擊到立方體身上?答案肯定是沒有!因為鼠標的位置也就是屏幕是二維的,但是正方體在三維場景里面,他是在屏幕里面,渲染器渲染出空間來了,鼠標和小方塊直接是有空間的,有距離的,放到三維里面,鼠標沒有點擊到小方塊,而是在空氣上面點擊了一下吧!??!一定要搞清楚哈,鼠標沒有放到小方塊上面,只是鼠標擋在了相機前面,把小方塊擋住了而已,所以說你點擊的不是小方塊,是 threejs 的相機鏡頭??!
所以,我們想要點擊小方塊怎么辦呢?點擊不了。
那我們想一下,我們想要點擊小方塊的目的是啥?是不是想給我們想要拖拽的小方塊綁定一個變換控制器。那所以說,我們一定要點擊到小方塊嗎?好像也不需要,只要讓我們在點擊鼠標的時候獲取到小方塊這個模型對象就可以了吧?
在生活當中,如果我們想讓旁邊的人關注遠處的一個人怎么辦?是不是你直接拿手指一下遠處的人就可以了,別人通過你手指的方向,結(jié)合你看的方向,結(jié)合當時的場景就大體知道你指的是哪個人了吧?threejs 中,也提供了類似的功能,叫做 射線發(fā)射器 Raycaster。
射線發(fā)射器 Raycaster 會根據(jù)鼠標在二維屏幕中點擊的位置,結(jié)合當前相機的一些狀態(tài),比如位置、角度、方向等,從屏幕向鼠標點擊的方向發(fā)出一條射線,把被射線穿過模型返回成一個列表回來,列表的順序就是穿過的先后順序,所以我們照著小方塊點過去,射線一定會穿過小方塊,當然可能還有其他的,但是第一個肯定是最先被射線穿過的小方塊吧!畢竟我們沒必要隔山打牛。
好的,邏輯捋清楚了,接下來就可以開始編寫代碼了。
初始化射線發(fā)射器 Raycaster
初始化射線發(fā)射器其實是很簡單的事情,threejs 官方也為我們提供了 方式,只需要一行代碼就可以實現(xiàn)了。
// 初始化射線發(fā)射器 let raycaster = new Raycaster()
根據(jù)上面一部分分析,我們知道鼠標要觸發(fā)點擊事件,然后把射線從屏幕打出去,看看打穿了哪些模型吧?好的,那么分析一個事情,我們點擊鼠標,從點擊的地方發(fā)出射線吧?OK,我們首先得知道鼠標的位置是吧,我們可以寫一個鼠標移動的事件來獲取鼠標實時位置吧?OK,插一句,其實這個獲取鼠標位置可以在點擊的時候獲取到,但是我想特別的添加一個鼠標移動事件,為了后邊一個案例做準備吧算是,我們添加鼠標移動事件其實就是在渲染器上面添加吧,因為他充滿整個屏幕。
// 鼠標移動事件 renderer.domElement.addEventListener("mousemove", event => { let x = event.offsetX let y = event.offsetY console.log(x, y) })
好的,我們鼠標移動事件寫好了。其中 x , y 就是鼠標在屏幕的坐標。截取了一個圖片,當鼠標在渲染器渲染的時候,可以看到鼠標在控制臺的實時位置。
我們看到控制臺已經(jīng)在實時打印我們鼠標的位置了,但是呢,現(xiàn)在思考一個問題哈。就是我們獲取到鼠標的位置,是相對于屏幕的。但是呢,我們一會配置射線發(fā)生器需要兩個參數(shù),分別是相機,他會獲取相機角度,位置,方向,結(jié)合傳遞的第二個參數(shù),鼠標點擊位置,計算實際射線在 threejs 中射線穿過的模型。所以說,第二個參數(shù)的鼠標位置,應該是 threejs 視角的鼠標位置。這個位置和我們計算出來的相對于屏幕的鼠標位置是不一樣的。
看下面一張圖:
對于電腦屏幕來說,也就是我們上一步拿到的鼠標坐標,它是以左上角為 0,0 點,向左,向下逐漸變大,最大就是電腦視圖的高度和寬度。
但是對于 threejs 視圖來說呢,它是以視圖的中心點為 0,0,向左變大,向下變大,且最大是 1。
所以說我們獲取到了鼠標在電腦視圖的坐標,需要計算得到在 threejs 視圖的鼠標坐標啊,所以,我們獲取到的鼠標坐標 x,y 通過計算獲得 threejs 的坐標是下面這個算法:
對于 threejs 而言,他的原點就是屏幕寬度的一半和屏幕高度的一半。所以:
橫軸: (x - width / 2) / (width / 2)
縱軸: (height / 2 - y) / (height / 2)化簡一下就是
x / width * 2 - 1
-y * 2 / height + 1
OK,這樣我們就獲取到了 threejs 中鼠標的位置。
因為我們后邊需要射線發(fā)射器傳遞兩個參數(shù),一個是相機,一個是鼠標,我們的第二個參數(shù)鼠標是一個二維的對象,我們先提前聲明一下。
// 初始化鼠標位置 let mouse = new Vector2() // 屏幕鼠標x,屏幕鼠標y 視圖寬度,視圖高度 let x = 0; let y = 0; let width = 0; let height = 0
然后在鼠標移動事件里面給鼠標對象設置他的 x 和 y:
renderer.domElement.addEventListener("mousemove", event => { x = event.offsetX y = event.offsetY width = renderer.domElement.offsetWidth height = renderer.domElement.offsetHeight mouse.x = x / width * 2 - 1 mouse.y = -y * 2 / height + 1 })
這樣子我們就成功的獲取到了鼠標在 threejs 中的位置信息。
然后接下來就可以一編寫點擊事件了,點擊事件要做的事情就是當我們按下鼠標之后,射出一個射線,被射線穿過的模型列表,都會給我們返回回來:
// 鼠標點擊事件 renderer.domElement.addEventListener("click", event => { raycaster.setFromCamera(mouse, camera) // 配置射線發(fā)射器,傳遞鼠標和相機對象 const intersection = raycaster.intersectObjects(scene.children) // 獲取射線發(fā)射器捕獲的模型列表,傳進去場景中所以模型,穿透的會返回我們 console.log(intersection) })
我們通過射線發(fā)射器捕獲到了我們點擊的模型,然后打印一下所有的數(shù)據(jù)看一下:
當我點擊了立方體之后,控制臺打印出來一個模型的列表,其實這個模型,就是點擊的立方體。
我們展開看到 object 模型下面的 name 就是我們設置的 box 名字吧!
好的,有個問題說一下,拋開做的這個demo, 有時候我們點擊一個位置,他打印出來的不是一個對象, 而是好幾個,因為射線能穿過了好幾個模型的,但是列表的第一個模型,肯定是我們點擊的,因為這個列表是按照穿過的先后順序返回的。還有一個,我們的輔助線,甚至是一會要使用的變換控制器,都會被射線穿過,都會被返回。
使用變換控制器 TransformControls
首先我們需要引入,這個引入和之前不一樣,是單獨的,在案例里面:
import { TransformControls } from 'three/examples/jsm/controls/TransformControls'
引入完成,我們需要初始化我們的 變換控制器。
// 初始化變換控制器 let transformControls = new TransformControls(camera, renderer.domElement) scene.add(transformControls) // 將變換控制器添加至場景
這個要初始化在點擊事件之前哈!然后,在點擊事件中,我們得首先判斷一下,點擊下去有沒有射穿模型,如果沒有的話就沒有必要給第一個模型添加變換控制器了吧。如果有,就給第一個模型添加變換控制器。
// 點擊事件 renderer.domElement.addEventListener("click", event => { raycaster.setFromCamera(mouse, camera) // 配置射線發(fā)射器,傳遞鼠標和相機對象 const intersection = raycaster.intersectObjects(scene.children) // 獲取射線發(fā)射器捕獲的模型列表,傳進去場景中所以模型,穿透的會返回我們 if (intersection.length) { const object = intersection[0].object // 獲取第一個模型 transformControls.attach(object) } })
完成到這里之后,我們就可以對模型進行編輯操作了。
好的,但是多操作幾次發(fā)現(xiàn)是有問題的。比如我們先拖動,然后松開鼠標,在拖動的話,就發(fā)現(xiàn) 變化控制器添加到別的地方去了,就不再小正方體上面了。
這是什么原因造成的呢,因為我們所有的場景里面添加的東西,都是模型,所以說呢,變換控制器本身也是模型,當然,這不是主要原因,他會造成其他的問題,這個地方的原因是什么,我們可以在點擊事件里面打印一句話:
renderer.domElement.addEventListener("click", event => { console.log("click") raycaster.setFromCamera(mouse, camera) // 配置射線發(fā)射器,傳遞鼠標和相機對象 const intersection = raycaster.intersectObjects(scene.children) // 獲取射線發(fā)射器捕獲的模型列表,傳進去場景中所以模型,穿透的會返回我們 if (intersection.length) { const object = intersection[0].object // 獲取第一個模型 transformControls.attach(object) } })
然后我們看一下打印的效果:
我們注意到,我們點擊鼠標左鍵之后,打印出來了 click,但是我們拖拽完 變換控制器之后,又打印了一遍,為啥呢,第一次打印其實是點擊小方塊的,這個我們可以理解,第二次是因為我們點擊變換控制器時候觸發(fā)的呀。
所以需要解決一個問題,就是我們要處理一下:我們給變換控制器一個鼠標按下的事件,然后我們定義一個 變量記錄是否是 變換控制器 按下的事件。
let transing = false transformControls.addEventListener("mouseDown", event => { transing = true })
然后,在之前的點擊事件中判斷一下,如果是變化控制器按下的話,就不處理就可以了吧
// 點擊事件 renderer.domElement.addEventListener("click", event => { if (transing) { transing = false return } raycaster.setFromCamera(mouse, camera) // 配置射線發(fā)射器,傳遞鼠標和相機對象 const intersection = raycaster.intersectObjects(scene.children) // 獲取射線發(fā)射器捕獲的模型列表,傳進去場景中所以模型,穿透的會返回我們 if (intersection.length) { const object = intersection[0].object // 獲取第一個模型 transformControls.attach(object) } })
這樣我們就成功修改掉那個 bug 了。
其實呢,還有一點小問題,就是之前說過的,我們所有場景里面添加的東西,都是模型,所以說呢,變換控制器本身也是模型。我問為了防止我們按下獲取到的組件是變換控制器本身,所以說,我們按下鼠標獲取點擊模型之前,先把變換控制器移除,然后獲取到模型之后再把變換控制器整出來。
// 點擊事件 renderer.domElement.addEventListener("click", event => { if (transing) { transing = false return } scene.remove(transformControls) // 移除變換控制器 transformControls.enabled = false // 停用變換控制器 raycaster.setFromCamera(mouse, camera) // 配置射線發(fā)射器,傳遞鼠標和相機對象 const intersection = raycaster.intersectObjects(scene.children) // 獲取射線發(fā)射器捕獲的模型列表,傳進去場景中所以模型,穿透的會返回我們 if (intersection.length) { const object = intersection[0].object // 獲取第一個模型 scene.add(transformControls) // 添加變換控制器 transformControls.enabled = true // 啟用變換控制器 transformControls.attach(object) } })
這樣就不用擔心我們按下鼠標之后,點擊到變換控制器本身了。
當然效果和之前是完全一樣的。
然后到現(xiàn)在為止呢,變換控制器移動模型就可以了。
但是變換控制器遠遠不值這么點功能。除了移動位置之外,還可以實現(xiàn)形變和旋轉(zhuǎn)。
比如說我們添加一個小功能,當我們:
- 按下鍵盤 E 之后,可以對模型進行縮放。
- 按下鍵盤 R 之后,可以對模型進行旋轉(zhuǎn)。
- 按下鍵盤 T 之后,改為對模型進行移動。
只需要監(jiān)聽一下鍵盤按下事件,改變變換控制器的類型就可以了。
// 監(jiān)聽變換控制器模式更改 document.addEventListener("keyup", event => { if (transformControls.enabled) { // 變換控制器為啟用狀態(tài)執(zhí)行 if (event.key === 'e') { // 鼠標按下e鍵,模式改為縮放 transformControls.mode = 'scale' return false } if (event.key === 'r') { // 鼠標按下r鍵,模式改為旋轉(zhuǎn) transformControls.mode = 'rotate' return false } if (event.key === 't') { // 鼠標按下t鍵,模式改為平移 transformControls.mode = 'translate' return false } } })
然后我們就按下按鍵實現(xiàn)效果了!
好的,這樣的話效果就都實現(xiàn)了。
鼠標移動到模型變色
好了,接下來我們實現(xiàn)一個稍微簡單的功能,記得之前點擊按鈕使用鼠標 x ,y 坐標的時候,我沒有在點擊事件中獲取,而是特意寫了一個鼠標移動監(jiān)聽事件嗎?就是為了演示這個地方做準備的。
要實現(xiàn)鼠標移動上去模型變色,所以說呢,我們首先得知道模型有沒有被鼠標移動上去,然后模型觸發(fā)移入或者是移除的事件,時間里面就是改變模型的顏色吧!
我們首先創(chuàng)建一個變量,用來存儲我們鼠標移入之后獲取到的這個模型:
cacheObject = null // 鼠標移入緩存效果
然后我們在鼠標移動的事件里面修改成下面的代碼:
renderer.domElement.addEventListener("mousemove", event => { x = event.offsetX y = event.offsetY width = renderer.domElement.offsetWidth height = renderer.domElement.offsetHeight mouse.x = x / width * 2 - 1 mouse.y = -y * 2 / height + 1 raycaster.setFromCamera(mouse, camera) // 配置射線發(fā)射器 scene.remove(transformControls) // 移除變換控制器 const intersection = raycaster.intersectObjects(scene.children) if (intersection.length) { const object = intersection[0].object if (object !== this.cacheObject) { // 如果當前物體不等于緩存的物體 if (this.cacheObject) { // 如果有緩存物體先執(zhí)行之前物體的離開事件 this.cacheObject.dispatchEvent({ type: 'mouseleave' }) } object.dispatchEvent({ // 添加當前物體進入事件 type: 'mouseenter' }) } else if (object === this.cacheObject) { // 如果當前物體等于緩存的物體 object.dispatchEvent({ // 執(zhí)行移動事件 type: 'mousemove' }) } this.cacheObject = object } else { if (this.cacheObject) { // 如果有緩存物體就先執(zhí)行離開事件 this.cacheObject.dispatchEvent({ type: 'mouseleave' }) } this.cacheObject = null } })
同時,我們得給 box 模型添加兩個事件,分別是鼠標移入和鼠標移出的吧?
import { BoxGeometry, Color, Mesh, MeshStandardMaterial } from "three" export const allBaseObject = [] // 返回所有基礎模型 // 創(chuàng)建地面 export const box = new Mesh( new BoxGeometry(20, 20, 20), // 設置立方體的大小 new MeshStandardMaterial({ // 設置材質(zhì) color: 'rgb(36, 172, 242)', // 設置材質(zhì)的顏色 metalness: 0.5, // 金屬度 (1 最像金屬,0 最不想金屬) roughness: 0 // 粗糙度(0 最光滑,1 最粗糙) }) ) box.name = 'box' // 設置模型 name // 給模型添加鼠標移入事件 box.addEventListener("mouseenter", () => { box.material.color = new Color("#ff3366") // 修改材質(zhì)顏色為紅色 }) // 給模型添加鼠標移除事件 box.addEventListener("mouseleave", () => { box.material.color = new Color("rgb(36, 172, 242)") // 恢復模型的材質(zhì) }) allBaseObject.push(box) // 添加到模型數(shù)組
好的,接下來我們的效果就實現(xiàn)了。
好,我們看到我們鼠標移入移除就實現(xiàn)了模型材質(zhì)顏色的切換。
但是發(fā)現(xiàn)一個問題,為啥鼠標在模型上,但是他有一段變成了最開始的顏色???
之前說過,場景里面所以的東西都是模型,射線發(fā)射器都會根據(jù)穿過順序返回。也就是說,網(wǎng)格輔助線也是會被穿透的!網(wǎng)格輔助線其實也是有一定的寬高的,所以那時候射線發(fā)射器第一個穿過的是網(wǎng)格輔助線,但是輔助線沒有實現(xiàn)鼠標移入移出時間,當輔助線移入的時候,就是小方塊鼠標的移出吧!所以他恢復了之前的顏色。
結(jié)束語
好了,關于 threejs 的基本操作就這些,后期可能還會寫一篇關于加載第三方模型的博文以及實現(xiàn)鼠標移動到模型上面顯示 tip 標簽的功能
到此這篇關于threejs在vue項目中的基本使用的文章就介紹到這了,更多相關threejs在vue的基本使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
代碼資料
相關文章
ant-design-vue table分頁onShowSizeChange后的pageNo解決
這篇文章主要介紹了ant-design-vue table分頁onShowSizeChange后的pageNo的問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04vue中element 的upload組件發(fā)送請求給后端操作
這篇文章主要介紹了vue中element 的upload組件發(fā)送請求給后端操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09