Three.js Demo源碼分析-1.MorphTargets與BufferGeometry


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的用法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM