Three.js主頁上有很多絢麗的Demo,是學習的極佳素材。我正利用閑暇時間學習這些Demo,並將心得體會記錄下來。
老調重彈
空間中旋轉的立方體板條箱,恐怕是主頁上最簡單的一個例子了。在WebGL原生API教程中就有這個例子,用Three.js實現起來更加方便了。但是,作為開始的開始,還是再重彈一遍老調吧。
較簡單的例子多采用這種一目了然的結構:
// 在<body>尾部: // 一些全局變量 var camera, scene, renderer; var mesh; // 初始化函數,僅運行一次 function init(){ // 初始化這些全局變量 } // 動畫函數,每一幀運行一次 function animate(){ requestAnimationFrame(animate); // 動畫和繪圖 } init(); animate();
在init()中初始化相機,場景,渲染器,輕車熟路。
renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(70, window.innerWidth/window.innerHeight, 1, 1000); camera.position.z = 400; scene = new THREE.Scene();
創建一個立方體mesh,貼上紋理,並加入到場景中。
var geometry = new THREE.CubeGeometry(200, 200, 200); var texture = THREE.ImageUtils.loadTexture('code.JPG'); texture.anisotropy = renderer.getMaxAnisotropy(); var material = new THREE.MeshBasicMaterial({map:texture}); mesh = new THREE.Mesh(geometry, material); scene.add(mesh);
在animate()中渲染,結束。
function animate(){ requestAnimationFrame(animate); mesh.rotation.x += 0.005; mesh.rotation.y += 0.01; renderer.render(scene, camera); }
這個例子就不多說了,代碼放在這里只是作為最簡單的開始。如果你之前使用過Three.js,一段時間不用后有所生疏,這個例子可以迅速將你喚醒。
MorphTargets
MorphTargets允許物體發生變形。如果該物體的geometry有 $n$ 個頂點,那么MorphTargets允許你再指定 $n$ 個, $2n$ 個, $3n$ 個甚至更多個頂點(比如,$ p\cdot n$ 個),同時mesh對象提供一個數組morphTargetInfluences(公式中$ f_{j} $表示morphTargetInfluences[j]),具有 $p$ 個元素,每個元素取值在0-1之間。渲染這個物體的時候,某個頂點 $V_{i}$ 的位置其實變了,成了:
$$V_{i}=V_{i}+\sum_{j=0}^{p}f_{j}\cdot (V_{j,i}-V_{i})$$
舉個簡單的例子,一個立方體有8個頂點,MorphTargets又指定了8個頂點,立方體的一個頂點為(1,1,1),而在MorphTargets中與之對應的頂點為(2,2,2),那么當morphTargetInfluences[0]為0.5的時候,實際渲染的時候該頂點的位置就成了(1.5,1.5,1.5)。這樣做的好處是顯而易見的,你可以通過簡單地調整morphTargetInfluences數組來使物體形變,只要之前你設置好了。
向物體加入morphTargets的方法很簡單:
var geometry = new THREE.CubeGeometry(100,100,100); var material = new THREE.MeshLambertMaterial({color:0xffffff, morphTargets:true}); var vertices = []; for(var i=0; i<geometry.vertices.length; i++) { var f = 2; vertices.push(geometry.vertices[i].clone()); vertices[i].x *= f; vertices[i].y *= f; vertices[i].z *= f; } geometry.morphTargets.push({name:'target0', vertices:vertices});
在其他什么地方(比如animate()或render()方法中)改變morphTargetInfluences,實在方便
var s = 0; function render() { s += 0.03; mesh.morphTargetInfluences[0] = Math.abs(Math.sin(s)); ... }
最關鍵的問題是,我相信,這個功能是通過着色器來完成的。我閱讀過一些簡單的着色器,因此我發現在着色器中完成這件事實在太合適了。如果某個geometry有幾千甚至上萬個頂點,使用JavaScript逐個計算變形后頂點的位置會造成很大壓力,而顯卡大規模並行計算的能力很適合處理這個任務(畢竟每個頂點是獨立地)。
BufferGeometry
BufferGeometry是自由度最高的geometry類型了,你可以自由指定每個頂點的位置、顏色、法線(影響光照)。
所謂Buffer,緩沖區,就是指頂點位置數組、頂點顏色數組等JavaScript二進制數組。這樣定義一個BufferGeometry:
var geometry = new THREE.BufferGeometry(); geometry.attributes = { index:{ itemSize:1, array:new Uint16Array(triangles*3), numItems:triangles*3 }, position:{ itemSize:3, array:new Float32Array(triangles*3*3), numItems:triangles*3*3 }, normal:{ itemSize:3, array:new Float32Array(triangles*3*3), numItems:triangles*3*3 }, color:{ itemSize:3, array:new Float32Array(triangles*3*3), numItems:triangles*3*3 } };
這個例子通過好幾個循環,在一個800寬度的立方體范圍內隨機構造了16萬個尺寸1-12的小三角形,計算了法向,並按照位置賦值了顏色,指定了材質。最后的效果相當壯觀。
接着創建一個旁氏反射類型的材質和mesh。
var material = new THREE.MeshPhongMaterial({ color: 0xaaaaaa, ambient: 0xaaaaaa, specular: 0xffffff, shininess: 250, side: THREE.DoubleSide, vertexColors: THREE.VertexColors }); mesh = new THREE.Mesh(geometry, material);
如果頂點的個數過多,超過了整型數index表示的范圍(65536),則可以使用offset來處理。在這個例子中有16萬個三角形,48萬個頂點,使用了8個offset,每個offset相當於重置了index值。
geometry.offsets = []; for(var i=0; i<offsets; i++){ var offset = { start:i*chunkSize*3, index:i*chunkSize*3, count:Math.min(triangles-(i*chunkSize), chunkSize)*3 }; geometry.offsets.push(offset); }
如果看過WebGL原生API教程,就會發現這與其中構建幾何體的過程實在是太相似了。
另兩個類似的例子,是線類型和點類型的。
這兩種類型聲明geometry時只需要position屬性和color屬性,不需要normal(沒有面自然沒有法向)屬性。
geometry.attributes = { position:{ itemSize:3, array:new Float32Array(segments*3), numItems:segments*3 }, color:{ itemSize:3, array:new Float32Array(segments*3), numItems:segments*3 } };
用自己的邏輯填充完數組后,創建材質及相應的line對象或particleSystem對象,而不是mesh對象。
對線類型:
var material = new THREE.LineBasicMaterial({vertexColors:true}); line = new THREE.Line(geometry, material);
對點類型
var material = new THREE.ParticleBasicMaterial({ size:15, vertexColors:true }); particleSystem = new THREE.ParticleSystem(geometry, material);
這一篇先以一個最簡單的例子“熱身”,之后記錄了MorphTargets和BufferGeometry的用法。