Three.js二三事

填坑中。。。。

=====2018-03-21更=====搶不到JJ的票好捉急====


開個坑。。。。

本人研一下就和幾個朋友開始創業,叫auto3d, 做自動化三維重建,作為當時唯一正兒八經寫代碼的,那時開始接觸WebGL, Three.js, 這是我們的案例。

創業的人都知道,忙成狗,也沒心思去寫文章做總結。去年9月,忙活了兩年多我司被鏈家網收購了,在那待了半年現在來到一家大廠,終於可以靜下心來總結一點之前的經驗,Three.js算是從頭至尾接觸最多的,也趟了許多坑,那就從他開始吧。


Three.js的介紹就不多說了,網上都有,本篇主要在於基於個人的經驗和踩過的坑,提供一個比較高效的學習進階路線以及對於各個模塊的源碼分析(如果有毅力持續寫下去的話哈哈= =)

前提

在學習Three之前,建議先從webgl開始看起,本質上three是對webgl的封裝,把複雜的向量、矩陣運算抽象成可以理解的對象和模塊。我當年看的是這本書, 裡面對於概念有比較清晰的解釋,每章的task也很好,跟著做完能對WebGL有比較清晰的認識

入門

對於Three.js的入門,個人建議直接從官網doc+example 學起來,上面的內容對於入門已經足夠了。並且example中就已經有很多作者提供的js封裝庫,比如各種loader, control, shader等等,對於新手做一個demo很友好。一開始就看書反而效率會比較低,畢竟在有一定經驗後你才能分辨出書的好壞以及抱著目的看書收穫能更大

Three.js源碼里有兩個核心模塊, Math和Core,

Math模塊

這個模塊里平時用的最多的應該就是Vector3類: 三維向量對象及其運算的封裝, 加加減減乘乘,算個距離、投影啥的,構造函數接收三個number (x,y,z),帶順序的, 學過線性代數的應該都了解。

Matrix4: 4x4矩陣對象的封裝,有了他各種旋轉縮(tiao)放(yue)隨手就來。

需要注意的是Three中這類基礎對象都是類,因此在做拷貝操作時需要使用clone或者直接new 一個新對象然後把原對象copy進去,切忌直接"a=b", 這也是新手通常會不經意間犯的錯誤(錯誤雖小,但debug起來好累的= =)

Box3

box3對象主要是為了表示一個mesh的minimum bounding box, 接收兩個參數:min 和max, 皆為Vector3

function Box3( min, max ) { this.min = ( min !== undefined ) ? min : new Vector3( + Infinity, + Infinity, + Infinity); this.max = ( max !== undefined ) ? max : new Vector3( - Infinity, - Infinity, - Infinity);}

這邊一開始會比較奇怪為什麼要用infinity來做初始化的值,後來在看到isEmpty方法以及這個issue後就比較清楚了,這邊算是一個注意點吧,Three的世界裡 Vector3(0,0,0)也是有含義的。

isEmpty: function () { // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z );}

Box3大部分方法主要是各種新增物體拓展自身邊界以及交互相關,比如expandByVector expandByObject。這裡主要挑expandByObject說下

expandByObject: function () { var scope, i, l; var v1 = new Vector3(); function traverse( node ) { ... if ( geometry !== undefined ) { if ( geometry.isGeometry ) { ... v1.copy( vertices[ i ] ); v1.applyMatrix4( node.matrixWorld ); scope.expandByPoint( v1 ); } } else if ( geometry.isBufferGeometry ) { ... v1.fromBufferAttribute( attribute, i ).applyMatrix4( node.matrixWorld ); scope.expandByPoint( v1 ); } } } return function expandByObject( object ) { scope = this; object.updateMatrixWorld( true ); object.traverse( traverse ); return this; };}()

這邊主要是先對object對象做個update matrix操作,然後再遞歸的添加Obejct3D及其chidlren(children是[Object3D])。

對於Three內部的matrix操作,官網doc里的這篇文章已經說的比較明白,所謂的position, rotation, quaternion其實算是語義上做了層封裝,內部還是做的Matrix4,你可以用Three提供的介面去做位置移動、旋轉等操作,進階一點也可以直接操作Matrix4,看每個人需求了。對於初學者而言,能有這個意識,平時操作操作position, quaternion啥的已經足夠了。

除了各種expand,還有若干個intersect方法,是用來做碰撞檢測的,從這裡可以看出Three從基本對象開始已經做好了各種能想好的介面。Physijs這個庫是基於Three做物理碰撞效果的,算是github上star比較多的了,之前給公司實習生做過,效果還不錯,不過項目已經很久沒更新了,感興趣的小夥伴可以去把玩下(話題岔遠了哈哈 = =)

intersect: function ( box ) { this.min.max( box.min ); this.max.min( box.max ); // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values. if ( this.isEmpty() ) this.makeEmpty(); return this;}

要注意的是這裡的intersect方法是改變的調用對象本身,並不是返回新對象。


Core模塊

Object3D

這是Three中大部分類的父類, 包括mesh, material, camera等等,一切都基於此。

主要屬性有

uuid: 用做對象標識

type:用於判斷對象的類別

children:用於維護子對象列表

position(Vector3):三維空間的位置

rotation(Euler): 用於維護自身旋轉

quaternion(Quaternion): 基於某個向量的旋轉(這邊的坑以後在開)

scale(Vector3): 縮放比例

需要注意的是在源碼中position, rotation, quaternion和scale使用了Object.defineProperties的方法保證該屬性是可枚舉但是不可寫,個人覺得主要是因為這三個屬性指向的都是其他three對象的實例,這樣可以保證不內存泄露。

Object.defineProperties( this, { position: { enumerable: true, value: position }, rotation: { enumerable: true, value: rotation }, quaternion: { enumerable: true, value: quaternion }, scale: { enumerable: true, value: scale }, modelViewMatrix: { value: new Matrix4() }, normalMatrix: { value: new Matrix3() } } );

同時從scale屬性也使用Vector3類可以看出,Vector3類不僅僅可以用來三維空間中的一個位置,也可以用來表示一個向量或者僅僅是維護三個數據,方便內部計算。

先開個坑,之後再更。


推薦閱讀:

QQX計劃推廣H5——這可能是地球上最美的H5,是怎麼做出來的?

TAG:前端開發 | threejs | WebGL |