第一次使用threejs到實際項目中,開始的時候心情有點小激動,畢竟是第一次嘛,然而作着作着就感覺到這玩意水好深,滿滿的都是坑,填都填不過來。通過老闆20天慘無人道的摧殘,終於小有成就。
由於第一次搞這玩意,相對的遇到的問題也是大把的,讓我來一一訴說一路上遇到的各類問題。
開發使用: C4D、Blender2.7五、[threejs-r72](http://threejs.org/)
萬事開頭難,第一個問題就是怎麼才能把3d軟件中作好的模型顯示在瀏覽器中。
1、模型在軟件中的導入與導出。
這個項目中涉及到單個模型和動畫模型,而不一樣模型的導入導出有差別,下面就告訴你們我是如何將坑填平的。
一、單個模型:
由於本身不會使用3D軟件建模,只能求助公司大神設計師來一塊兒搞。剛開始的想法是直接用3d軟件建模而後直接導出obj格式來用,而後設計師用C4D作好了一個測試模型,發現模型數量少的話網頁的大小還能夠接受,可是因爲項目的模型數量比較多,而後粗算了一下模型總的大小,發現超出了預想,因此得另尋它法。
接着在網上搜索發現Blender這玩意,因爲設計師對C4D情有獨鍾不會Blender軟件,因此決定用C4D作好模型而後導出obj格式接着再導入到Blender裏面,再經由Blender導出須要的格式。
由於是第一次倒騰這個軟件,因此並不會導出。而後就在網上搜素了怎麼用Blender導出的json、js文件。通過測試js文件導出比較大,最後果斷選擇json。
在軟件中如何導入導出如圖所示:html
二、動畫模型:
因爲設計師出一個動畫模型也沒有這麼快,就無法進行導出測試。因而看到threejs官網裏有demo中使用的動畫模型,我就拿過來進行測試,發現動畫模型跟單個模型導出選擇有差別而後發現更單個模型的導出有出入,通過反覆的測試,獲得導動畫模型須要注意的幾點,
(1)選擇好動畫的幀數,若是沒選擇,導出的json文件會有空幀。而且文件也會相對增大。
(2)選擇好導出選項中Animation,通常就選擇Morph Animation、Embed Animation選項。
單個模型以及動畫模型導出選項以下圖所示:web
圖二 (左邊爲導出單個模型,右邊導出動畫模型)json
注:導出單個帶材質模型須要在導出選項的時候須要在shading選項中選擇Face Materials。
拿着設計師作好的動畫模型導出json格式後碰到了一些問題,雖然json格式的大小相比obj格式的要小一點,不過項目中有人物的動畫模型導出的json格式大小仍是太大。而後爲了解決這個問題,跟設計師進行討論,而後獲得如下解決方案:
(1)將模型的面和頂點在不影響正常顯示的狀況下進行刪減
(2)對動畫模型的幀數、面、頂點也進行刪減
通過反覆的修改和測試終於將動畫模型控制在500-1000KB左右,單個模型控制在100K左右。
模型的導出問題解決了而後是對模型進行導入到頁面中去。
2、模型加載到頁面
在threejs官網上看到利用obj格式加載的demo比較多,因此就直接使用的是obj的格式模型進行加載,根據demo利用THREE.OBJLoader()、THREE.OBJMTLLoader()進行加載。而後設計大神給了我一個帶材質的模型讓我進行測試,發現兩問題:
(1)模型材質丟失
(2)模型的大小太大(模型量少大小還能夠接受,考慮到這次項目中的模型量多,估算了一下大概有70-80M左右)
因爲模型大小太大因此放棄。因此改選用THREE.JSONLoader()進行加載。
在這一步因爲是直接導出帶材質的json格式,材質對應到模型的各個面是一個問題。而後在官網的demo上看到THREE.MeshFaceMaterial()方法,查看了一下文檔,而後迅速解決這個了這個問題。
另外threejs提供了各類模型的加載方法具體可去threejs.org查詢。
雖然解決了模型加載和麪的問題,可是模型在網頁的表現與軟件中渲染的差異太大。剛開始覺得是模型方面以及導入導出的方式不對,因而和設計師進行各類修改而後反覆的測試,發現沒什麼變化。而後求助stackoverflow找到了答案。是因爲模型的shading的緣由形成的,而後根據網上提供的解決方案在材質中加上materials.shading = THREE.FlatShading來解決。不過有些Android手機上會出現材質沒法解析的錯誤。並且在r72的版本中MeshLambertMaterial已經移除了shading這個屬性。
下圖就是在網頁中渲染的結果對比。瀏覽器
圖三 (左邊爲未加shading,右邊加了shading)微信
靜態模型的導入沒有問題了,而後是動畫模型的導入,參考官網demo,直接套用基本的動畫沒有太大問題,只是項目中有一我的物運球的動畫模型比較難折騰,剛開始的時候直接是按照動畫模型的導出直接導,而後同步到頁面中發現只能導出一個動畫,另一個丟失了,定位了一下問題好像是Blender不能同時導出多個動畫(具體是否是待研究)。最後想了一個辦法就是採起分開導出再建立一個obj包裹兩個動畫,操做這個obj。來解決多個動畫問題(若有更好的辦法求大神指導)。
至此將模型放到頁面中的準備工做都作完了。接着就是模型上的事件與動畫,模型上的各類事件整的頭都大了,然而到如今我仍是有一些東西沒有弄清楚原理還得繼續研究。less
3、模型的圓周運動
剛開始項目中有個需求就是進入頁面中模型須要作一個圓周運動,圓周運動之前在數學中學過,可是一直沒用因此就忘了,而後就在網上找有關圓周運動計算的方法。這裏不作過多的解釋,用下面一張圖來完整解釋怎麼來計算圓周運動。ide
圖四 (圓周運動計算)工具
在頁面中測試的結果以下圖所示:測試
圖五 (圓周運動測試結果)動畫
功能代碼以下:
var clock = new THREE.Clock(); //時間跟蹤 //圓周運動 var time = clock.getElapsedTime() * 1; loadMesh.position.x = Math.cos( time ) * 10; loadMesh.position.y = Math.sin( time ) * 10;
老闆看了一下,而後腦補一下整個頁面的效果說仍是不要這樣子,不少模型都這樣運動的話畫面太亂了,最後決定的是簡單點直接把全部模型擺放成一個球體形狀,而後模型不單獨運動,而是總體繞中心轉,這個實現起來比較簡單思路是直接設置外層模型y軸旋轉就能夠了。
4、全部模型在空間裏的位置
總體的運動效果描繪出來了,接着就是開始實施了。接着遇到了一個算是比較坑的問題。那就是模型在空間位置的確認了,因爲對3D場景的不熟悉,將全部模型擺出一個球體就有點困難了。只能求助設計大神了,而後他在C4D中將全部模型擺成一個球體以後,而後像操做模型同樣導了一份obj給我。而後我利用Blender打開(以下圖六所示),而後我看了下,每一個模型在軟件中都存在一個x,y,z值,我抱着僥倖的內心把全部模型的x,y,z記錄下來而後填到頁面中。最後發現球體的形狀出來了,只是距離有點差別,接着想了個投機取巧的辦法把全部的x,y,z進行等比的放大縮小,改完效果還不錯。最後就是拉着設計師瘋狂調整細節方面的問題。效果以下圖七所示。
圖六 (球體模型)
圖七 (頁面中渲染效果)
頁面的基本樣子出來了,剩下的就是頁面的交互了,整個難點基本都在這裏了。
項目需求:在頁面中選中模型,而後選擇模型如今在屏幕中間,而後用手指進行360度滑動,點擊關閉按鈕回到原型原有的位置。
思路:選中模型-->移動某個東西-->綁定旋轉事件-->回位。
能夠說項目大部分的時間都花在實現這個操做過程當中。下面就簡單說一下我是怎麼去填這些坑的。
5、在頁面中選中模型
以後的全部操做都要基於這個模型去作,全部第一步就須要選中這個模型。這個跟之前作的徹底不一樣,而後在官網demo和stackoverflow遊蕩,由於涉及到屏幕座標和世界座標這個概念有種徹底懵逼的感受。還好官網上有demo的支持,參考了demo以後發現,首先獲取屏幕座標的x,y而後想辦法轉換成向量,接着標準化向量,經過raycaster.intersectObjects的檢測來獲取選中的模型。功能代碼以下:
mouse.x = ( e.clientX / window.innerWidth ) * 2 - 1; //鼠標的x到屏幕y軸的距離與屏幕寬的一半的比值 絕對值不超過1 mouse.y = - ( e.clientY / window.innerHeight ) * 2 + 1; //鼠標的y到屏幕x軸的距離與屏幕寬的一半的比值 絕對值不超過1 //新建一個三維變換半單位向量 假設z方向就是0.5,這樣左右移的時候,還會有先後移的效果 //屏幕和場景轉換工具根據照相機,把這個向量從屏幕轉化爲場景中的向量 var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 ).unproject( camera ); //變換事後的向量vector減去相機的位置向量後標準化 var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() ); //新建一條從相機的位置到vector向量的一道光線 var intersects = raycaster.intersectObjects( objects ); if ( intersects.length > 0 ) { //把選中的對象放到全局變量SELECTED中 SELECTED = intersects[ 0 ].object; }
參考: http://threejs.org/examples/webgl_octree_raycasting.html
模型選中以後而後開始下一步。
6、相機的移動
由於要讓選中的模型顯示在屏幕中間,因而想了兩種方案:
(1)改變選中物體的x,y,z值
通過反覆的測試發現改變x,y,z值,模型會在空間中亂竄,把握很差位置。因而就思考其它的方案。
(2)移動相機
移動相機這一塊被坑了無數次,由於剛開始對這個相機的原理不是很清楚,就隨意試了幾個值(能夠利用threejs提供的相機輔助線來操做具體參考:http://threejs.org/docs/index.html#Reference/Extras.Helpers/CameraHelper),來證實本身的方向是正確的,發現模型確實出如今不一樣的位置了,而後就繼續往下深挖。首先在google中尋找有關threejs中相機的原理,具體參考:http://www.flowers1225.com/lessons/2015/12/08/1,對原理有必定了解以後,而後就想怎麼讓相機出如今本身想要的位置上。
首先獲取到選中模型的世界座標,而後再根據當前的座標值改變相機的x,y,z讓相機直接照在當前模型上。測試了一下發現選中模型出如今屏幕中間,不過根據模型的不一樣位置上有誤差,這個後來設置了默認值來修正誤差。效果下圖所示:
圖八 (相機移動)
功能實現了,而後發現選擇以後出現的太忽然了,沒有體現相機移動的效果,而後就想到使用TweenMax這個動畫庫來實現平滑過渡的動畫。代碼以下:
TweenMax.to(camera.position, 1, { x: x, y: y, z: z, ease:Expo.easeInOut, onComplete: function (){} })
加上以後效果以下圖所示:
圖九 (相機平滑移動)
模型已經放大顯示在屏幕中,而後點擊關閉按鈕讓模型回到原位這個就直接用TweenMax將相機的position值設置爲初始值就能夠了。
接下來就是開發用手指操做模型旋轉的功能了。
7、操做模型旋轉
這個功能我卡了很久,裏面涉及到數學中的矩陣、四元數、歐拉角、向量乘積、軸-角啥的,徹底都忘了。在二維中旋轉能夠經過角度來控制,而在三維空間中須要經過四元數或者矩陣來實現,萬般無奈只能求助萬能的google來了解怎麼在三維空間中對物體進行旋轉操做。 經過了解在3D中表現旋轉有三種方法,矩陣、歐拉角、四元組。 最後選用四元組來實現旋轉的方法,簡單點說緣由就是四元組的是圍繞一個軸來作旋轉,並且在threejs中也提供了THREE.Quaternion()方法,而後在threejs的包中找到了一些寫好的鼠標控制的類(TrackballControls.js、OrbitControls.js等),而後參考着源碼,將方法雖然寫出來了,可是有時候操做起來會有意向不到的bug,因此裏面的細節還有待深挖。下面簡述一下旋轉的思路。
首先四元數控制旋轉須要的是一個旋轉軸和一個旋轉弧度,直接上圖清楚明瞭
圖十
而後就想辦法獲得這個兩個東西,接着開始想怎麼弄到旋轉弧度,首先獲取到點擊開始和結束的x,y值,而後獲得兩個向量之間的夾角,獲得一個弧度,而後在設置一個默認的旋轉系數,二者相乘獲得弧度。接着經過開始和結束的向量乘積獲得旋轉軸,最後經過setFromAxisAngle(axis, angle)獲得旋轉四元數。
核心代碼以下:
function rotateMatrix(rotateStart, rotateEnd){ var axis = new THREE.Vector3(), quaternion = new THREE.Quaternion(); //獲得開始和結束向量間的夾角 var angle = Math.acos(rotateStart.dot(rotateEnd) / rotateStart.length() / rotateEnd.length()); if (angle){ //若是夾角等於0, 說明物體沒有旋轉 axis.crossVectors(rotateStart, rotateEnd).normalize(); //rotateStart,rotateEnd向量乘積 標準化 獲得旋轉軸 angle *= _that.rotationSpeed; //rotationSpeed旋轉系數 獲得旋轉弧度 quaternion.setFromAxisAngle(axis, angle); //從一個旋轉軸和旋轉弧度獲得四元組, 若是要讓物體相反方向旋轉 設置angle爲負 } return quaternion; //返回一個旋轉的四元數 } this.handleRotation = function(object){ _that.rotateEndPoint = _that.projectOnTrackball(_that.deltaX, _that.deltaY); var rotateQuaternion = rotateMatrix(_that.rotateStartPoint, _that.rotateEndPoint); var curQuaternion = object.quaternion; curQuaternion.multiplyQuaternions(rotateQuaternion, curQuaternion); //設置四元組 a x b curQuaternion.normalize(); object.setRotationFromQuaternion(curQuaternion); //方法經過規範化的旋轉四元數直接應用旋轉 參數必須normalize() };
在這裏有個小坑,就是全部模型的外觀大小不一樣,當旋轉的時候,可能會出現誤操做,而後用了一個小技巧就是用一個透明的方體包裹模型,這樣作就至關於旋轉一個cube了,並且設置方體有網格時對排除bug有幫助。以下圖所示:
圖十一 (外層包裹框)
雖然旋轉的效果作出來了,可是旋轉裏面涉及的東西還有一些理解的不是很清楚,還須要繼續深刻研究,等我研究透徹了再從新整理一下(還望大神指點一下)。
參考:四元數旋轉、cube旋轉、TrackballControls.js源碼
8、 燈光
最後就是燈光的控制,由於NBA的主色調是紅藍色,設計大神就想模型在頁面中有紅藍光打在模型上的感受,因而照着這個方向,他開始在軟件中調試燈光,調整好以後我按照設計師在軟件中調整好的位置擺放燈光,發現跟預想的有點差別,頁面中的顏色顯的太深了,因而在整個空間中加上了一個白色的全局光來提亮總體的亮度,而後對燈光進行反覆的調整,就到了如今頁面中呈現的樣子了。燈光調整的過程如圖所示:
圖十二 (燈光調整過程)
注: threejs提供不少種燈光具體能夠在threejs文檔中查看http://threejs.org/docs/index.html#Reference/Lights,而燈光調整能夠藉助threejs提供的輔助線來調整,THREE.PointLightHelper()、THREE.SpotLightHelper()等。這樣有助於迅速定位到問題。最後就是手機兼容性的測試了(由於是微信的活動頁,因此其它瀏覽器未測試)。在iPhone下總體體驗較好,在Android下使用r71版本發現模型會出現菱角不分明的狀況如圖三所示,以後改用r72版本,用高版本的Android測試發現問題解決了,而後拿我本身的mx2測試時出現另一個問題,直接卡在加載頁面進不去頁面,而後經過調試工具發如今控制檯中有方法報錯(小米2也是同等狀況),倒騰了很久,然而並無什麼卵用,由於是threejs內部報錯,無奈只能放大招,作一個Android版原本解決這個問題。