Three.js建模基础
在Three.js中,一個可見的物體是由幾何體和材料構成的。在這個教程中,我們將學習如何從頭開始創建新的網格幾何體,研究Three.js為處理幾何對象和材質所提供的相關支持。
1、索引面集/Indexed Face Sets
Three.js中的Mesh網格對象是索引面的集合。Three.js網格對象類型為THREE.Geometry,包含一系列的頂點(其類型為THREE.Vector3)。除了頂點,Mesh網格還包含一系列的三角面(其類型為THREE.Face3),每個Face3對象都指定了Mesh幾何體的一個三角面。三角面的三個頂點由三個整數指定,這些整數值都表示該頂點在Mesh對象的頂點數組的索引。例如:
var f = new THREE.Face3( 0, 7, 2 );這三個索引值存儲為Face3面對象的屬性 f.a、f.b和f.c。 例如,讓我們來看看如何直接為這個金字塔創建一個對應的Three.js幾何體:
請注意,金字塔的下部是一個正方形,因此需要拆分為兩個三角形,才能將金字塔表示為Mesh網格對象。假設我們用pyramidGeom表示這個金字塔的幾何對象,那么pyramidGeom.vertices是頂點數組,金pyramidGeom.faces是索引面數組??紤]到這一點,我們可以定義:
var pyramidGeom = new THREE.Geometry();pyramidGeom.vertices = [ // array of Vector3 giving vertex coordinatesnew THREE.Vector3( 1, 0, 1 ), // vertex number 0new THREE.Vector3( 1, 0, -1 ), // vertex number 1new THREE.Vector3( -1, 0, -1 ), // vertex number 2new THREE.Vector3( -1, 0, 1 ), // vertex number 3new THREE.Vector3( 0, 1, 0 ) // vertex number 4];pyramidGeom.faces = [ // array of Face3 giving the triangular facesnew THREE.Face3( 3, 2, 1 ), // first half of the bottom facenew THREE.Face3 3, 1, 0 ), // second half of the bottom facenew THREE.Face3( 3, 0, 4 ), // remaining faces are the four sidesnew THREE.Face3( 0, 1, 4 ),new THREE.Face3( 1, 2, 4 ),new THREE.Face3( 2, 3, 4 )];請注意,三角面的頂點順序并非完全任意:它們應該按從三角面前方查看的逆時針順序排列,即從金字塔外面觀察三角面。
這個金字塔幾何體,當使用MeshBasicMaterial時可以正常工作,但如果要使用MeshLambertMaterial或MeshPhongMaterial,就需要為該幾何體指定法線向量。如果一個Mesh幾何體沒有設置法線向量,那么使用Lambert或Phong材質時該集合體將呈現為黑色??梢允止ぴO置幾何對象的法線向量,但也可以使用Three.js中Geometry類的方法進行計算,例如:
pyramidGeom.computeFaceNormals();此方法計算每個面的法線矢量,其中法線向量垂直于面。如果使用平面著色(flat shading)的材質,這就足夠了,也就是說將材質的flatShading屬性設置為True。
Flat Shading適合金字塔這樣的幾何體的著色,但是當一個物體看起來光滑而不是面片時,它需要每個頂點的法線向量,而不是每個面的法線向量。Face3包含了一個頂點法線數組,我們可以手動設置,three.js也可以通過計算三角面的法線的平均值來得到光滑表面的頂點法線的合理估值。只需調用
geom.computeVertexNormals();?其中geom表示一個幾何對象。請注意,在computeVertexNormals被調用之前,必須已經存在面法線,因此通常在調用geom.computeFaceNormals()后會立即調用geom.computeVertexNormals()方法。具有表面法線但沒有頂點法線的幾何體將無法使使其flatShading屬性為false的材質,要在金字塔的表面使用平滑著色(Smooth Shading),應將每個三角面各頂點法線設置為與該三角面的面法線一致。在這種情況下,即使使用了平滑著色,金字塔的側面看起來還是平坦的。標準的three.js幾何形狀,如BoxGeometry則內置了正確的表面和頂點法線。
一個對象的面法線保存在THREE.Face3對象的normal屬性中,頂點法線則保存在THREE.Face3對象的vertexNormal屬性中,該屬性為Vector3數組。
我們的金字塔幾何體目前包含了完整的法線矢量,可以使用任何mesh材質,但看起來還是有點乏味,因為只有一種顏色。在一個網格上實際可以使用多種顏色。為此,需要向網格對象構造函數傳入一組材質,這使得將不同的材質應用于不同的面成為可能。例如,下面的代碼將六種不同材質應用于立方體的六個面:
var cubeGeom = new THREE.BoxGeometry(10,10,10); var cubeMaterials = [new THREE.MeshPhongMaterial( { color: "red" } ), // for the +x facenew THREE.MeshPhongMaterial( { color: "cyan" } ), // for the -x facenew THREE.MeshPhongMaterial( { color: "green" } ), // for the +y facenew THREE.MeshPhongMaterial( { color: "magenta" } ), // for the -y facenew THREE.MeshPhongMaterial( { color: "blue" } ), // for the +z facenew THREE.MeshPhongMaterial( { color: "yellow" } ) // for the -z face ]; var cube = new THREE.Mesh( cubeGeom, cubeMaterials );要使這一點與幾何對象配合使用,幾何體的每個三角面都需要一個"材質索引"。三角面的材質索引是一個整數,表示所使用的材質在材質數組中的索引。BoxGeometry的面具備正確的索引。請注意,一個Box幾何體有 12 個面,因為每個矩形側面需要被拆分成兩個三角面。構成矩形側面的兩個三角面具有相同的材質索引。
假設我們希望在金字塔的每一個側面使用上面創建的不同材質。要使之發揮作用,每個面都需要一個材質索引,該索引存儲在名為MaterialIndex的屬性中。對于金字塔來說,面數組中的前兩個面組成了金字塔的方形基座。他們可能應該有相同的材質索引。以下代碼將材質索引 0 分配給前兩個面,將材質索引 1、2、3 和 4 分配給其他四個面:
pyramidGeom.faces[0].materialIndex = 0; for (var i = 1; i <= 5; i++) {pyramidGeom.faces[i].materialIndex = i-1; }??上面的代碼摘自示例程序threejs/MeshFaceMaterial.html。該程序使用每個對象上的多個材質顯示一個立方體和一個金字塔。以下是顯示結果:
還有另一種方法可以將不同的顏色分配給Mesh對象的每個面:可以將顏色存儲為幾何中面對象的屬性。然后,就可以在對象上使用普通材質,而不是一系列材質。但你也必須告訴材質使用幾何體的顏色代替材質的color屬性。
有幾種方法可以將顏色分配給網格中的面。一是簡單地將每個面設置為不同的純色。每個面對象都有一個color屬性,可用于實現此想法。color屬性的值是THREE.Color類型的對象,代表整個面的顏色。例如,我們可以設置金字塔的面顏色:
pyramidGeom.faces[0].color = new THREE.Color(0xCCCCCC); pyramidGeom.faces[1].color = new THREE.Color(0xCCCCCC); pyramidGeom.faces[2].color = new THREE.Color("green"); pyramidGeom.faces[3].color = new THREE.Color("blue"); pyramidGeom.faces[4].color = new THREE.Color("yellow"); pyramidGeom.faces[5].color = new THREE.Color("red");要使用這些顏色,材料的vertexColors屬性必須設置為THREE.FaceColors。例如:
material = new THREE.MeshLambertMaterial({vertexColors: THREE.FaceColors,shading: THREE.FlatShading});FaceColors屬性的默認值為THREE.NoColors,它告訴渲染器使用材質的color屬性著色每一個面。
將顏色應用于面的第二種方法是將不同的顏色應用于三角面的每個頂點。然后,WebGL 將插值頂點顏色值以計算面內部各像素的顏色。每個面對象都有一個名為vertexColors的屬性,其值應該是一個THREE.Color對象數組,每個頂點一個。要使用這些顏色,材料的頂點vertexColors屬性必須設置為三THREE.VertexColors。
下圖展示了在球體的二十面體近似表示上使用頂點顏色和面顏色:
2、曲線和表面/Curves and Surfaces
除了支持構建索引三角面集外,Three.js還支持處理數學定義的曲線和表面。演示程序threejs/curves-and-surfaces.htm中提供了一些展示,下面我們討論其中的一些示例。
參數化表面是最容易處理的。參數化表面由數學函數f(u,v)定義,其中 u和v是數字,該函數的每個值都是空間中的一個點。表面由指定范圍內u和v函數值的所有點組成。對于Three.js,該函數就是返回THREE.Vector3類型值的常規 JavaScript 函數。參數化表面幾何形狀是通過在uv點陣中計算函數值而創建的。給出表面上的點陣,然后連接這些點,從而給出表面的多邊形近似。在three.js中,u和v的值始終在 0.0 到 1.0 之間。幾何形狀是由以下構造器創建:
new THREE.ParametricGeometry( func, slices, stacks )其中 func是 JavaScript 函數,slices和stacks決定網格中的點數;slices在u方向上給出了間隔從 0 到 1 的細分數,stacks在v方向上進行細分。一旦有了幾何形狀,就可以用它以通常的方式創建mesh對象。下面是一個示例:
上面的對象使用以下函數?
function surfaceFunction( u, v ) {var x,y,z; // A point on the surface, calculated from u,v.// u and v range from 0 to 1.x = 20 * (u - 0.5); // x and z range from -10 to 10z = 20 * (v - 0.5);y = 2*(Math.sin(x/2) * Math.cos(z));return new THREE.Vector3( x, y, z ); }由以下代碼創建:
var surfaceGeometry = new THREE.ParametricGeometry(surfaceFunction, 64, 64); var surface = new THREE.Mesh( surfaceGeometry, material );曲線(Curve)在three.js中更為復雜(不幸的是,用于處理曲線的 API 不是很一致)。THREE.Curve代表二維或三維的參數化曲線的抽象,它不是three.js幾何形狀。參數化曲線由包含一個數字變量t的函數定義。該函數返回的值為THREE.Vector2或THREE.Vector3,分別用于2D曲線和3D曲線。對于THREE.Curve對象,其getPoint(t)方法應返回與參數t值相對應的曲線上的點。但是,在Curve類中并未定義此方法。因此要獲得實際曲線,你需要自己進行定義。例如:
var helix = new THREE.Curve(); helix.getPoint = function(t) {var s = (t - 0.5) * 12*Math.PI;// As t ranges from 0 to 1, s ranges from -6*PI to 6*PIreturn new THREE.Vector3(5*Math.cos(s),s,5*Math.sin(s)); }定義getPoint后,就將獲得可用的曲線。你可以用它做的一件事是創建一個管狀幾何體,它定義了一個由管沿著曲線中心掃過運動掃過的幾何體。示例程序使用上述定義的helix曲線創建兩個管裝幾何體:
幾何形狀使用如下代碼創建:
tubeGeometry1 = new THREE.TubeGeometry( helix, 128, 2.5, 32 );構造器的第二個參數是沿曲線長度的表面細分數,第三個是管狀橫截面的半徑,第四個是橫截面周長周圍的細分數。
要制作管狀幾何體,需要 3D 曲線。也有幾種方法可以從2D曲線上制作表面。一種方法是圍繞一個軸線旋轉曲線,產生一個旋轉的表面。表面由曲線旋轉時通過的所有點組成。這叫做lathing。此示例程序中的圖像顯示了lathing一個余弦曲線產生的表面,曲線本身顯示在表面之上:
??表面用three.js的THREE.LatheGeometry創建。LatheGeometry不是從曲線上構建的,而是從曲線上的一系列點構建的。點是Vector2型的對象,曲線位于xy平面中。表面是通過圍繞y軸旋轉曲線生成的。LatheGeometry構造器形式如下:
new THREE.LatheGeometry( points, slices )第一個參數是Vector2數組。第二個是當一個點圍繞軸旋轉時沿圓產生的表面細分的數量。在示例程序中,通過調用cosine.getPoints(128) 從余弦類型的曲線對象創建點陣列。此功能使用范圍從 0.0 到 1.0 的參數值在曲線上創建 128 點的數組。
你可以用 2D 曲線完成的另一件事就是簡單地填充曲線內部,從而提供 2D 填充形狀。要使用three.js做到這一點,你可以使用THREE.Shape類型,這是THREE.Curve的子類。Shape的定義方式與 2D Canvas API 中的路徑相同。THREE.Shape對象有moveTo、lineTo、quadraticCurveTo和bezierCurveTo等方法。例如,我們可以創建淚滴形狀:
var path = new THREE.Shape(); path.moveTo(0,10); path.bezierCurveTo( 0,5, 20,-10, 0,-10 ); path.bezierCurveTo( -20,-10, 0,5, 0,10 );要使用路徑創建一個填充形狀,我們需要一個ShapeGeometry對象?:
var shapeGeom = new THREE.ShapeGeometry( path );下圖左側顯示了上述代碼創建的 2D 形狀:
圖片中的另外兩個對象是通過擠壓(extrude)形狀創建的。在擠壓中,填充的 2D 形狀沿 3D 路徑移動。形狀經過的點構成 3D 實體。在這種情況下,形狀沿著垂直于形狀的線條擠壓,這是最常見的情況?;緮D壓的形狀顯示在上圖的右側。中間的對象則同時進行了圓角處理。
3、紋理/Textures
紋理可用于向對象添加視覺興趣和細節。在three.js中,圖像紋理由THREE.Texture對象表示。由于我們談論的是網頁,因此three.js紋理的圖像通常從 Web 地址加載。圖像紋理通常使用THREE.TextureLoader對象中的load方法創建。該方法以 URL(網址,通常是相對地址)為參數,并返回Texture紋理對象:
var loader = new THREE.TextureLoader(); var texture = loader.load( imageURL );three.js的紋理被認為是材質的一部分。要將紋理應用于網格,只需將Texure對象分配給網格材質的map屬性:
material.map = texture;map屬性也可以在材料構造器中設置。所有三種類型的網格材質(Basic、Lamber和 Phong)都可以使用紋理。一般來說,材質基色為白色,因為材質顏色將乘以紋理上的顏色。非白色的材質顏色將為紋理顏色添加"色調"。將圖像映射到網格所需的紋理坐標是網格幾何體的一部分。標準網格幾何形狀,如THREE.SphereGeometry已經定義了紋理坐標。
這就是基本的思路——從圖像URL創建紋理對象,并將其賦值給材質的map屬性。然而,其中也有一些復雜的細節。首先,圖像加載是"異步的"。即調用加載功能僅啟動加載圖像的過程,并且該過程可以在功能返回后的某個時間完成。在圖像完成加載之前在對象上使用紋理不會導致錯誤,但對象將呈現為完全黑色。加載圖像后,必須再次渲染場景以顯示圖像紋理。如果運行了動畫,這一切將自動發生:圖像在完成加載后將顯示在第一幀中。但是,如果沒有啟動動畫,則需要一種方法在圖像加載后渲染場景。事實上,紋理加載器中的load函數幾個可選參數:
loader.load( imageURL, onLoad, undefined, onError );?此處的第三個參數被賦予undefined,因為該參數不再使用。onLoad和onError參數是回調功能。如果定義了onLoad參數,則一旦圖像成功加載該參數函數將被調用。如果加載圖像的嘗試失敗,將調用onError函數。例如,如果存在一個自定義的渲染場景的函數 render(),則render()本身可作為onLoad參數:
var texture = new THREE.TextureLoader().load( "brick.png", render );另一個可能的onLoad用法是將紋理延遲直到圖像完成加載再分配給材質。如果你修改了material.map的值,記得設置:
material.needsUpdate = true;以確保更改在重新繪制對象時生效。
Texture紋理對象具有許多可以設置的屬性,包括為紋理設置最小化和放大過濾器的屬性,以及用于控制mipmaps生成的屬性,這些屬性默認情況下會自動定義,最有可能要更改的屬性是范圍 0 到 1 之外的紋理坐標的包裝模式和紋理轉換。
對于一個紋理對象tex,屬性tex.wrapS和tex.wrapT控制在范圍 0 到 1 之外處理s和t紋理坐標的方式。默認值是"clamp to edge"。你很可能希望通過將屬性值設置為THREE.RepeatWrapping來使紋理在兩個方向上重復,例如:
tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping;重復包裝最適合"無縫"紋理,圖像的上邊緣與下邊緣匹配,左邊緣與右邊緣匹配。three.js還提供了一個有趣的變體稱為"鏡像重復",其中重復圖像的所有其他副本被翻轉。這消除了圖像副本之間的接縫。對于鏡像重復,請使用屬性值三THREE.MirroredRepeatWrapping:
tex.wrapS = THREE.MirroredRepeatWrapping; tex.wrapT = THREE.MirroredRepeatWrapping;紋理對象的屬性repeat和offset控制應用于紋理的縮放和轉換作為紋理轉換(不支持旋轉)。這些屬性的值為THREE.Vector2,每個屬性有x和y成員。對于紋理對象,tex.offset的兩個長遠在水平和垂直方向上提供紋理轉換。要將紋理水平偏移 0.5,可以使用如下代碼:
tex.offset.x = 0.5;或如下等效代碼:
tex.offset.set( 0.5, 0 );請記住,正的水平偏移量會將紋理移動到對象的左側,因為偏移應用于紋理坐標而不是紋理圖像本身。
屬性tex.repeat在水平和垂直方向上提供紋理縮放。例如:
tex.repeat.set(2,3);將橫向和垂直擴展 2 倍和 3 倍的紋理坐標。同樣,對圖像的影響是反向的,因此圖像被水平收縮 2 倍和垂直 3 倍。結果是在水平方向獲得兩個圖像副本,垂直方向三個。這解釋了名稱"重復",但請注意,值不限于整數。
下面的演示允許查看一些設置了紋理的three.js對象。順便說一下,演示中的"Pill"對象是一個由圓柱體和兩個半球組成的復合對象:
假設我們希望在本節開頭創建的金字塔上應用圖像紋理。為了將紋理圖像應用于對象,WebGL 需要該對象的紋理坐標。當我們從頭開始構建網格時,我們必須提供紋理坐標作為網格幾何對象的一部分。
示例中的pyramidGeom等幾何對象具有名為faceVertexUv 的屬性來保存紋理坐標。"UV"是指映射到紋理中的s和t坐標的對象上的坐標。faceVertexUvs的值是一個數組,其中每個元素本身又是一個數組的數組:在大多數情況下,僅使用元素faceVertexUvs[0],但在某些高級應用程序中使用了額外的uv坐標集。faceVertexUvs[0] 的值本身就是一個數組,每個成員對應幾何體的一個面。每個面存儲的數據還是一個數組:faceVertexUVs[0][N] 是一個數組,表示三角面N的三個頂點的坐標。最后,該數組中的每對紋理坐標都是THREE.Vector2類型。
金字塔有六個三角面,每個面需要一個包含三個Vector2對象的數組來表示。必須以合理的方式選擇將紋理坐標映射到三角面上。我們將整個紋理圖像映射到金字塔的地面,它從圖像中切出一塊三角形以便應用于每個側面。需要仔細處理以便得到正確的左邊。我們為此金字塔定義的紋理坐標如下:
pyramidGeometry.faceVertexUvs = [[[ new THREE.Vector2(0,0), new THREE.Vector2(0,1), new THREE.Vector2(1,1) ],[ new THREE.Vector2(0,0), new THREE.Vector2(1,1), new THREE.Vector2(1,0) ],[ new THREE.Vector2(0,0), new THREE.Vector2(1,0), new THREE.Vector2(0.5,1) ],[ new THREE.Vector2(1,0), new THREE.Vector2(0,0), new THREE.Vector2(0.5,1) ],[ new THREE.Vector2(0,0), new THREE.Vector2(1,0), new THREE.Vector2(0.5,1) ],[ new THREE.Vector2(1,0), new THREE.Vector2(0,0), new THREE.Vector2(0.5,1) ], ]];請注意,這是一個三維陣列。
示例程序threejs/textured-pyramid.html顯示具有磚塊紋理的金字塔。以下是來自程序的圖像:
4、變換/Transforms
為了在three.js中有效地處理對象,深入其變換的實現機制是非常有必要的。對于一個Object3D類型的對象obj,其屬性包括obj.position,obj.scale和obj.rotation,指定了在本地坐標系中的模型變換。 但是,在渲染對象時,不會直接使用這些屬性。相反,它們被組合起來計算另一個屬性,obj.matrix,它將對象的變換表示為一個矩陣。默認情況下,每次渲染場景時,都會自動重新計算此矩陣。如果轉換永遠不變,這種做法就是低效的,所以obj有另一個屬性,obj.matrixAutoUpdate控制obj.matrix是否自動計算。如果將obj.matrixAutoUpdate設置為false,則不會自動更新變換矩陣。在這種情況下,如果確實需要更新變換矩陣,可以調用obj.updateMatrix()以利用當前的obj.position、obj.scale和obj.rotation值計算矩陣。
我們已經看到了如何通過直接改變屬性obj.position、obj.scale和obj.rotation的值來更新obj的模型變換。 不過,也可以通過調用函數obj.translate X(dx)、obj.translateY(dy)或obj.translateZ(dz)來改變位置,以便將對象沿指定坐標軸的方向移動。還有一個函數obj.translateOnAxis(axis, amount),其中axis是Vector3類型,amount是一個數字,表示要移動的距離。物體沿axis指定的方向移動,axis矢量必須是歸一化的:即它必須有長度1。例如,沿(1,1,1)方向移動 5 個單位,可以使用如下代碼:
obj.translateOnAxis( new THREE.Vector3(1,1,1).normalize(), 5 );沒有用于縮放變換的方法。但是,你可以使用obj.rotateX(angle)、obj.rotateY(angle)和obj.rotateZ(angle)來圍繞指定坐標軸旋轉對象。請記住角度單位是弧度。調用obj.rotateX(angle)與在obj.rotation.x值上增加角度不同,因為它在其他可能已有旋轉之上應用了關于 x 軸的旋轉。
還有一個函數obj.rotateOnAxis(axis,angle),其中axis是Vector3,此方法繞指定適量旋轉對象一定的角度。axis參數必須是歸一化矢量。
需要強調的是,平移和旋轉功能會修改對象的position和rotation屬性。即它們應用于對象坐標,而不是世界坐標,當對象呈現時,它們作為對象上的第一個模型轉換應用。例如,如果對象不是定位在原點,那么旋轉是世界坐標可以改變物體的位置。但是,更改對象的rotation屬性值永遠不會更改其位置。
有一個更有用的方法來設置旋轉:obj.lookAt(vec),它旋轉對象,使其朝向給定點。參數vec是Vector3類型,必須在對象自己的本地坐標系中表示。對象也旋轉,使其"觀察"方向等于屬性obj.up的值,默認值為 (0,1,0)。此功能可用于任何對象,但它對相機最有用。
原文鏈接:Three.js 3D建?;A - BimAnt
總結
以上是生活随笔為你收集整理的Three.js建模基础的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 您安心的走吧——5.17晚一夜天降小雨祭
- 下一篇: 财务建模完整指南第六讲——第五届CVA估