首個(gè)threejs項(xiàng)目前端填坑指南
發(fā)布日期:2023/1/20 10:07:51 瀏覽量:
轉(zhuǎn)自博客園 原文鏈接:https://www.cnblogs.com/mazhenyu/p/8692835.html 如有侵權(quán)請(qǐng)聯(lián)系我們刪除
http://www.cnblogs.com/pursues/p/5226807.html
第一次使用threejs到實(shí)際項(xiàng)目中,開始的時(shí)候心情有點(diǎn)小激動(dòng),畢竟是第一次嘛,然而做著做著就感受到這玩意水好深,滿滿的都是坑,填都填不過(guò)來(lái)。經(jīng)過(guò)老板20天慘無(wú)人道的摧殘,終于小有成就。
因?yàn)榈谝淮胃氵@玩意,相對(duì)的遇到的問(wèn)題也是大把的,讓我來(lái)一一訴說(shuō)一路上遇到的各種問(wèn)題。
開發(fā)使用: C4D、Blender2.75、[threejs-r72](http://threejs.org/)
萬(wàn)事開頭難,第一個(gè)問(wèn)題就是怎么才能把3d軟件中做好的模型顯示在瀏覽器中。
一、模型在軟件中的導(dǎo)入與導(dǎo)出。
這個(gè)項(xiàng)目中涉及到單個(gè)模型和動(dòng)畫模型,而不同模型的導(dǎo)入導(dǎo)出有差異,下面就告訴大家我是如何將坑填平的。
1、單個(gè)模型:
因?yàn)樽约翰粫?huì)使用3D軟件建模,只能求助公司大神設(shè)計(jì)師來(lái)一起搞。剛開始的想法是直接用3d軟件建模然后直接導(dǎo)出obj格式來(lái)用,然后設(shè)計(jì)師用C4D做好了一個(gè)測(cè)試模型,發(fā)現(xiàn)模型數(shù)量少的話網(wǎng)頁(yè)的大小還可以接受,但是由于項(xiàng)目的模型數(shù)量比較多,然后粗算了一下模型總的大小,發(fā)現(xiàn)超出了預(yù)想,所以得另尋它法。
接著在網(wǎng)上搜索發(fā)現(xiàn)Blender這玩意,由于設(shè)計(jì)師對(duì)C4D情有獨(dú)鐘不會(huì)Blender軟件,所以決定用C4D做好模型然后導(dǎo)出obj格式接著再導(dǎo)入到Blender里面,再經(jīng)由Blender導(dǎo)出需要的格式。
因?yàn)槭堑谝淮蔚跪v這個(gè)軟件,所以并不會(huì)導(dǎo)出。然后就在網(wǎng)上搜素了怎么用Blender導(dǎo)出的json、js文件。經(jīng)過(guò)測(cè)試js文件導(dǎo)出比較大,最后果斷選擇json。
在軟件中如何導(dǎo)入導(dǎo)出如圖所示:
2、動(dòng)畫模型:
由于設(shè)計(jì)師出一個(gè)動(dòng)畫模型也沒(méi)有這么快,就沒(méi)法進(jìn)行導(dǎo)出測(cè)試。于是看到threejs官網(wǎng)里有demo中使用的動(dòng)畫模型,我就拿過(guò)來(lái)進(jìn)行測(cè)試,發(fā)現(xiàn)動(dòng)畫模型跟單個(gè)模型導(dǎo)出選擇有差異然后發(fā)現(xiàn)更單個(gè)模型的導(dǎo)出有出入,經(jīng)過(guò)反復(fù)的測(cè)試,得到導(dǎo)動(dòng)畫模型需要注意的幾點(diǎn),
(1)選擇好動(dòng)畫的幀數(shù),如果沒(méi)選擇,導(dǎo)出的json文件會(huì)有空幀。并且文件也會(huì)相對(duì)增大。
(2)選擇好導(dǎo)出選項(xiàng)中Animation,一般就選擇Morph Animation、Embed Animation選項(xiàng)。
單個(gè)模型以及動(dòng)畫模型導(dǎo)出選項(xiàng)如下圖所示:

圖二 (左邊為導(dǎo)出單個(gè)模型,右邊導(dǎo)出動(dòng)畫模型)
注:導(dǎo)出單個(gè)帶材質(zhì)模型需要在導(dǎo)出選項(xiàng)的時(shí)候需要在shading選項(xiàng)中選擇Face Materials。
拿著設(shè)計(jì)師做好的動(dòng)畫模型導(dǎo)出json格式后碰到了一些問(wèn)題,雖然json格式的大小相比obj格式的要小一點(diǎn),不過(guò)項(xiàng)目中有人物的動(dòng)畫模型導(dǎo)出的json格式大小還是太大。然后為了解決這個(gè)問(wèn)題,跟設(shè)計(jì)師進(jìn)行討論,然后得到以下解決方案:
(1)將模型的面和頂點(diǎn)在不影響正常顯示的情況下進(jìn)行刪減
(2)對(duì)動(dòng)畫模型的幀數(shù)、面、頂點(diǎn)也進(jìn)行刪減
經(jīng)過(guò)反復(fù)的修改和測(cè)試終于將動(dòng)畫模型控制在500-1000KB左右,單個(gè)模型控制在100K左右。
模型的導(dǎo)出問(wèn)題解決了然后是對(duì)模型進(jìn)行導(dǎo)入到頁(yè)面中去。
二、模型加載到頁(yè)面
在threejs官網(wǎng)上看到利用obj格式加載的demo比較多,所以就直接使用的是obj的格式模型進(jìn)行加載,根據(jù)demo利用THREE.OBJLoader()、THREE.OBJMTLLoader()進(jìn)行加載。然后設(shè)計(jì)大神給了我一個(gè)帶材質(zhì)的模型讓我進(jìn)行測(cè)試,發(fā)現(xiàn)兩問(wèn)題:
(1)模型材質(zhì)丟失
(2)模型的大小太大(模型量少大小還可以接受,考慮到此次項(xiàng)目中的模型量多,估算了一下大概有70-80M左右)
由于模型大小太大所以放棄。所以改選用THREE.JSONLoader()進(jìn)行加載。
在這一步由于是直接導(dǎo)出帶材質(zhì)的json格式,材質(zhì)對(duì)應(yīng)到模型的各個(gè)面是一個(gè)問(wèn)題。然后在官網(wǎng)的demo上看到THREE.MeshFaceMaterial()方法,查看了一下文檔,然后迅速解決這個(gè)了這個(gè)問(wèn)題。
另外threejs提供了各種模型的加載方法具體可去threejs.org查詢。
雖然解決了模型加載和面的問(wèn)題,但是模型在網(wǎng)頁(yè)的表現(xiàn)與軟件中渲染的差別太大。剛開始以為是模型方面以及導(dǎo)入導(dǎo)出的方式不對(duì),于是和設(shè)計(jì)師進(jìn)行各種修改然后反復(fù)的測(cè)試,發(fā)現(xiàn)沒(méi)什么變化。然后求助stackoverflow找到了答案。是由于模型的shading的原因造成的,然后根據(jù)網(wǎng)上提供的解決方案在材質(zhì)中加上materials.shading = THREE.FlatShading來(lái)解決。不過(guò)有些Android手機(jī)上會(huì)出現(xiàn)材質(zhì)無(wú)法解析的錯(cuò)誤。而且在r72的版本中MeshLambertMaterial已經(jīng)移除了shading這個(gè)屬性。
下圖就是在網(wǎng)頁(yè)中渲染的結(jié)果對(duì)比。

圖三 (左邊為未加shading,右邊加了shading)
靜態(tài)模型的導(dǎo)入沒(méi)有問(wèn)題了,然后是動(dòng)畫模型的導(dǎo)入,參考官網(wǎng)demo,直接套用基本的動(dòng)畫沒(méi)有太大問(wèn)題,只是項(xiàng)目中有一個(gè)人物運(yùn)球的動(dòng)畫模型比較難折騰,剛開始的時(shí)候直接是按照動(dòng)畫模型的導(dǎo)出直接導(dǎo),然后同步到頁(yè)面中發(fā)現(xiàn)只能導(dǎo)出一個(gè)動(dòng)畫,另外一個(gè)丟失了,定位了一下問(wèn)題好像是Blender不能同時(shí)導(dǎo)出多個(gè)動(dòng)畫(具體是不是待研究)。最后想了一個(gè)辦法就是采取分開導(dǎo)出再創(chuàng)建一個(gè)obj包裹兩個(gè)動(dòng)畫,操作這個(gè)obj。來(lái)解決多個(gè)動(dòng)畫問(wèn)題(如有更好的辦法求大神指導(dǎo))。
至此將模型放到頁(yè)面中的準(zhǔn)備工作都做完了。接著就是模型上的事件與動(dòng)畫,模型上的各種事件整的頭都大了,然而到現(xiàn)在我還是有一些東西沒(méi)有弄清楚原理還得繼續(xù)研究。
三、模型的圓周運(yùn)動(dòng)
剛開始項(xiàng)目中有個(gè)需求就是進(jìn)入頁(yè)面中模型需要做一個(gè)圓周運(yùn)動(dòng),圓周運(yùn)動(dòng)以前在數(shù)學(xué)中學(xué)過(guò),但是一直沒(méi)用所以就忘了,然后就在網(wǎng)上找有關(guān)圓周運(yùn)動(dòng)計(jì)算的方法。這里不做過(guò)多的解釋,用下面一張圖來(lái)完整解釋怎么來(lái)計(jì)算圓周運(yùn)動(dòng)。
圖四 (圓周運(yùn)動(dòng)計(jì)算)
在頁(yè)面中測(cè)試的結(jié)果如下圖所示:

圖五 (圓周運(yùn)動(dòng)測(cè)試結(jié)果)
功能代碼如下:
老板看了一下,然后腦補(bǔ)一下整個(gè)頁(yè)面的效果說(shuō)還是不要這樣子,很多模型都這樣運(yùn)動(dòng)的話畫面太亂了,最后決定的是簡(jiǎn)單點(diǎn)直接把所有模型擺放成一個(gè)球體形狀,然后模型不單獨(dú)運(yùn)動(dòng),而是整體繞中心轉(zhuǎn),這個(gè)實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單思路是直接設(shè)置外層模型y軸旋轉(zhuǎn)就可以了。
四、所有模型在空間里的位置
整體的運(yùn)動(dòng)效果描繪出來(lái)了,接著就是開始實(shí)施了。接著遇到了一個(gè)算是比較坑的問(wèn)題。那就是模型在空間位置的確認(rèn)了,由于對(duì)3D場(chǎng)景的不熟悉,將所有模型擺出一個(gè)球體就有點(diǎn)困難了。只能求助設(shè)計(jì)大神了,然后他在C4D中將所有模型擺成一個(gè)球體之后,然后像操作模型一樣導(dǎo)了一份obj給我。然后我利用Blender打開(如下圖六所示),然后我看了下,每個(gè)模型在軟件中都存在一個(gè)x,y,z值,我抱著僥幸的心里把所有模型的x,y,z記錄下來(lái)然后填到頁(yè)面中。最后發(fā)現(xiàn)球體的形狀出來(lái)了,只是距離有點(diǎn)差異,接著想了個(gè)投機(jī)取巧的辦法把所有的x,y,z進(jìn)行等比的放大縮小,改完效果還不錯(cuò)。最后就是拉著設(shè)計(jì)師瘋狂調(diào)整細(xì)節(jié)方面的問(wèn)題。效果如下圖七所示。

圖六 (球體模型)
圖七 (頁(yè)面中渲染效果)
頁(yè)面的基本樣子出來(lái)了,剩下的就是頁(yè)面的交互了,整個(gè)難點(diǎn)基本都在這里了。
項(xiàng)目需求:在頁(yè)面中選中模型,然后選擇模型現(xiàn)在在屏幕中間,然后用手指進(jìn)行360度滑動(dòng),點(diǎn)擊關(guān)閉按鈕回到原型原有的位置。
思路:選中模型-->移動(dòng)某個(gè)東西-->綁定旋轉(zhuǎn)事件-->回位。
可以說(shuō)項(xiàng)目大部分的時(shí)間都花在實(shí)現(xiàn)這個(gè)操作過(guò)程中。下面就簡(jiǎn)單說(shuō)一下我是怎么去填這些坑的。
五、在頁(yè)面中選中模型
之后的所有操作都要基于這個(gè)模型去做,所有第一步就需要選中這個(gè)模型。這個(gè)跟以前做的完全不同,然后在官網(wǎng)demo和stackoverflow游蕩,因?yàn)樯婕暗狡聊蛔鴺?biāo)和世界坐標(biāo)這個(gè)概念有種完全懵逼的感覺(jué)。還好官網(wǎng)上有demo的支持,參考了demo之后發(fā)現(xiàn),首先獲取屏幕坐標(biāo)的x,y然后想辦法轉(zhuǎn)換成向量,接著標(biāo)準(zhǔn)化向量,通過(guò)raycaster.intersectObjects的檢測(cè)來(lái)獲取選中的模型。功能代碼如下:
參考: http://threejs.org/examples/webgl_octree_raycasting.html
模型選中之后然后開始下一步。
六、相機(jī)的移動(dòng)
因?yàn)橐屵x中的模型顯示在屏幕中間,于是想了兩種方案:
(1)改變選中物體的x,y,z值
經(jīng)過(guò)反復(fù)的測(cè)試發(fā)現(xiàn)改變x,y,z值,模型會(huì)在空間中亂竄,把握不好位置。于是就思考其它的方案。
(2)移動(dòng)相機(jī)
移動(dòng)相機(jī)這一塊被坑了無(wú)數(shù)次,因?yàn)閯傞_始對(duì)這個(gè)相機(jī)的原理不是很清楚,就隨意試了幾個(gè)值(可以利用threejs提供的相機(jī)輔助線來(lái)操作具體參考:http://threejs.org/docs/index.html#Reference/Extras.Helpers/CameraHelper),來(lái)證明自己的方向是正確的,發(fā)現(xiàn)模型確實(shí)出現(xiàn)在不同的位置了,然后就繼續(xù)往下深挖。首先在google中尋找有關(guān)threejs中相機(jī)的原理,具體參考:http://www.flowers1225.com/lessons/2015/12/08/1,對(duì)原理有一定了解之后,然后就想怎么讓相機(jī)出現(xiàn)在自己想要的位置上。
首先獲取到選中模型的世界坐標(biāo),然后再根據(jù)當(dāng)前的坐標(biāo)值改變相機(jī)的x,y,z讓相機(jī)直接照在當(dāng)前模型上。測(cè)試了一下發(fā)現(xiàn)選中模型出現(xiàn)在屏幕中間,不過(guò)根據(jù)模型的不同位置上有偏差,這個(gè)后來(lái)設(shè)置了默認(rèn)值來(lái)修正偏差。效果下圖所示:

圖八 (相機(jī)移動(dòng))
功能實(shí)現(xiàn)了,然后發(fā)現(xiàn)選擇之后出現(xiàn)的太突然了,沒(méi)有體現(xiàn)相機(jī)移動(dòng)的效果,然后就想到使用TweenMax這個(gè)動(dòng)畫庫(kù)來(lái)實(shí)現(xiàn)平滑過(guò)渡的動(dòng)畫。代碼如下:
加上之后效果如下圖所示:

圖九 (相機(jī)平滑移動(dòng))
模型已經(jīng)放大顯示在屏幕中,然后點(diǎn)擊關(guān)閉按鈕讓模型回到原位這個(gè)就直接用TweenMax將相機(jī)的position值設(shè)置為初始值就可以了。
接下來(lái)就是開發(fā)用手指操作模型旋轉(zhuǎn)的功能了。
七、操作模型旋轉(zhuǎn)
這個(gè)功能我卡了好久,里面涉及到數(shù)學(xué)中的矩陣、四元數(shù)、歐拉角、向量乘積、軸-角啥的,完全都忘了。在二維中旋轉(zhuǎn)可以通過(guò)角度來(lái)控制,而在三維空間中需要通過(guò)四元數(shù)或者矩陣來(lái)實(shí)現(xiàn),萬(wàn)般無(wú)奈只能求助萬(wàn)能的google來(lái)了解怎么在三維空間中對(duì)物體進(jìn)行旋轉(zhuǎn)操作。 通過(guò)了解在3D中表現(xiàn)旋轉(zhuǎn)有三種方法,矩陣、歐拉角、四元組。 最后選用四元組來(lái)實(shí)現(xiàn)旋轉(zhuǎn)的方法,簡(jiǎn)單點(diǎn)說(shuō)原因就是四元組的是圍繞一個(gè)軸來(lái)做旋轉(zhuǎn),而且在threejs中也提供了THREE.Quaternion()方法,然后在threejs的包中找到了一些寫好的鼠標(biāo)控制的類(TrackballControls.js、OrbitControls.js等),然后參考著源碼,將方法雖然寫出來(lái)了,但是有時(shí)候操作起來(lái)會(huì)有意向不到的bug,所以里面的細(xì)節(jié)還有待深挖。下面簡(jiǎn)述一下旋轉(zhuǎn)的思路。
首先四元數(shù)控制旋轉(zhuǎn)需要的是一個(gè)旋轉(zhuǎn)軸和一個(gè)旋轉(zhuǎn)弧度,直接上圖清楚明了

圖十
然后就想辦法得到這個(gè)兩個(gè)東西,接著開始想怎么弄到旋轉(zhuǎn)弧度,首先獲取到點(diǎn)擊開始和結(jié)束的x,y值,然后得到兩個(gè)向量之間的夾角,得到一個(gè)弧度,然后在設(shè)置一個(gè)默認(rèn)的旋轉(zhuǎn)系數(shù),兩者相乘得到弧度。接著通過(guò)開始和結(jié)束的向量乘積得到旋轉(zhuǎn)軸,最后通過(guò)setFromAxisAngle(axis, angle)得到旋轉(zhuǎn)四元數(shù)。
核心代碼如下:
在這里有個(gè)小坑,就是所有模型的外觀大小不同,當(dāng)旋轉(zhuǎn)的時(shí)候,可能會(huì)出現(xiàn)誤操作,然后用了一個(gè)小技巧就是用一個(gè)透明的方體包裹模型,這樣做就相當(dāng)于旋轉(zhuǎn)一個(gè)cube了,而且設(shè)置方體有網(wǎng)格時(shí)對(duì)排除bug有幫助。如下圖所示:

圖十一 (外層包裹框)
雖然旋轉(zhuǎn)的效果做出來(lái)了,但是旋轉(zhuǎn)里面涉及的東西還有一些理解的不是很清楚,還需要繼續(xù)深入研究,等我研究透徹了再重新整理一下(還望大神指點(diǎn)一下)。
參考:四元數(shù)旋轉(zhuǎn)、cube旋轉(zhuǎn)、TrackballControls.js源碼
八、 燈光
最后就是燈光的控制,因?yàn)镹BA的主色調(diào)是紅藍(lán)色,設(shè)計(jì)大神就想模型在頁(yè)面中有紅藍(lán)光打在模型上的感覺(jué),于是照著這個(gè)方向,他開始在軟件中調(diào)試燈光,調(diào)整好之后我按照設(shè)計(jì)師在軟件中調(diào)整好的位置擺放燈光,發(fā)現(xiàn)跟預(yù)想的有點(diǎn)差異,頁(yè)面中的顏色顯的太深了,于是在整個(gè)空間中加上了一個(gè)白色的全局光來(lái)提亮整體的亮度,然后對(duì)燈光進(jìn)行反復(fù)的調(diào)整,就到了現(xiàn)在頁(yè)面中呈現(xiàn)的樣子了。燈光調(diào)整的過(guò)程如圖所示:

圖十二 (燈光調(diào)整過(guò)程)
注: threejs提供很多種燈光具體可以在threejs文檔中查看http://threejs.org/docs/index.html#Reference/Lights,而燈光調(diào)整可以借助threejs提供的輔助線來(lái)調(diào)整,THREE.PointLightHelper()、THREE.SpotLightHelper()等。這樣有助于迅速定位到問(wèn)題。
最后就是手機(jī)兼容性的測(cè)試了(因?yàn)槭俏⑿诺幕顒?dòng)頁(yè),所以其它瀏覽器未測(cè)試)。在iPhone下整體體驗(yàn)較好,在Android下使用r71版本發(fā)現(xiàn)模型會(huì)出現(xiàn)菱角不分明的情況如圖三所示,之后改用r72版本,用高版本的Android測(cè)試發(fā)現(xiàn)問(wèn)題解決了,然后拿我自己的mx2測(cè)試時(shí)出現(xiàn)另外一個(gè)問(wèn)題,直接卡在加載頁(yè)面進(jìn)不去頁(yè)面,然后通過(guò)調(diào)試工具發(fā)現(xiàn)在控制臺(tái)中有方法報(bào)錯(cuò)(小米2也是同等情況),倒騰了好久,然而并沒(méi)有什么卵用,因?yàn)槭莟hreejs內(nèi)部報(bào)錯(cuò),無(wú)奈只能放大招,做一個(gè)Android版本來(lái)解決這個(gè)問(wèn)題。
最后附上體驗(yàn)地址(頁(yè)面中意想不到的bug肯定是還有的):

掃碼體驗(yàn)
第一次在實(shí)戰(zhàn)中使用threejs開發(fā),還有很多的不足需要彌補(bǔ),希望下一次能做的更好。自己也繼續(xù)的在webgl的坑中掙扎,掙扎,掙扎(重要的要說(shuō)三遍),還望有大神指點(diǎn)迷津?。?br />
附上公司大神設(shè)計(jì)師的站酷:http://www.zcool.com.cn/u/738186
另外附上一些自己在填坑中找到的對(duì)3D學(xué)習(xí)有幫助的網(wǎng)站、博客、書籍:
各種計(jì)算 http://www.euclideanspace.com/maths/index.htm
許多有趣的東西 http://www.natural-science.or.jp/article/laboratory/cat467/
大叔很厲害的 http://learningthreejs.com/
threejs源碼注釋 https://github.com/omni360/three.js.sourcecode
關(guān)于threejs的書籍只有英文原版的:
《Three.Js Essentials》、《threejs-cookbook》、《Learning Three.js》
馬上咨詢: 如果您有業(yè)務(wù)方面的問(wèn)題或者需求,歡迎您咨詢!我們帶來(lái)的不僅僅是技術(shù),還有行業(yè)經(jīng)驗(yàn)積累。
QQ: 39764417/308460098 Phone: 13 9800 1 9844 / 135 6887 9550 聯(lián)系人:石先生/雷先生
