1. 程式人生 > >11 Animation動畫

11 Animation動畫

這些天有些忙,導致一個多星期沒有更新文章,群裡的小夥伴也天天催我更。為了表示歉意,我決定在現在的基礎上面增加一節Three.js核心類的相關介紹,來讓讓小夥伴們能更清楚的瞭解相關的基礎內容。下面我們開始這一節的內容,動畫。 動畫一般可以定義兩種:一種是變形動畫,另一種是骨骼動畫。下面,我們先介紹一下變形動畫。

變形動畫

變形動畫的實現就是通過修改當前模型的頂點位置來實現動畫。就比如,一個動畫需要變動十次才可以實現,那麼我們就需要為當前模型的每一個頂點定義每一次所在的位置,Three.js通過每一次修改實現最後的一個動畫的整個流程。 為了更好的理解變形動畫,我們建立了一個案例,檢視地址為:點選這裡

這個案例是為了讓我們更好的瞭解變形動畫的實現以及使用。在右上角,我們能發現兩個可切換的拖拽條,這兩個拖拽條對應的是兩個變形目標陣列,拖拽範圍是0-1,就是當前的變形目標對本體的影響程度。我們進行拖拽就可以發現,介面中的立方體也會跟隨著變動,影響當前的立方體。接下來我講解一下,當前案例是如何實現的:

  • 首先,在我們建立模型的幾何體時,給幾何體morphTargets賦值了兩個變形目標,morphTargets是一個數組,這意味著我們可以增加很多的變形目標,在給morphTargets新增的陣列,我們需要自己定義一個名稱和相關的頂點,這個頂點資料必須和預設的模型的頂點資料保持一致,設定完後,我們需要呼叫geometry
    computeMorphNormals()來更新:
var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);

// 建立兩個影響立方體的變形目標
var cubeTarget1 = new THREE.BoxGeometry(2, 10, 2);
var cubeTarget2 = new THREE.BoxGeometry(8, 2, 8);

// 將兩個geometry的頂點放入到立方體的morphTargets裡面
cubeGeometry.morphTargets[0] = {name: 'target1', vertices: cubeTarget1.vertices};
cubeGeometry.morphTargets[1] = {name: 'target2', vertices: cubeTarget2.vertices};
cubeGeometry.computeMorphNormals();
  • 然後,當前模型使用的材質必須設定可以使用變形目標變形:
var cubeMaterial = new THREE.MeshLambertMaterial({morphTargets: true, color: 0x00ffff});
  • 當我們建立好網格模型新增到場景內後,可以在mesh物件上面找到morphTargetInfluences這一個配置項,這裡面也是一個數組,是和geometrymorphTargets相對應的,主要就是用於設定當前的變形目標對本體的影響度,預設值為0-1,0為不影響本體,1為完全影響本體:
gui = {
    influence1:0.01,
    influence2:0.01,
    update : function () {
        cube.morphTargetInfluences[0] = gui.influence1;
        cube.morphTargetInfluences[1] = gui.influence2;
    }
};

通過上面我們手動實現了一個變形動畫,就會發現,其實變形動畫是一直在修改變形目標對本體的影響尺度。我們可以通過這個原理實現一些變形動畫。 當前案例程式碼檢視地址:點選這裡

骨骼動畫

骨骼動畫是需要生成一個與模型相關的骨架,骨架中的骨骼也會存在對應關係,模型的每一個需要動畫的頂點需要設定影響它的骨骼以及骨骼影響頂點的程度。骨骼動畫和變形動畫相比會比較複雜一些,但是它又有更多的靈活性。我們可以想象一下人體的骨骼,如果使用變形動畫,需要把所有的每一次的變動都存一個頂點陣列,而骨骼動畫,只需要設定骨骼的相關資訊,就可以實現更多的動畫。下面我們看一下骨骼動畫的簡單案例:點選這裡 skeleton 這是一個官方提供的案例,我經過一些簡單的修改,也將當前一個柱形圖形的骨骼顯示的出來,這個實現比較複雜,我們需要做的就是先理解它是怎麼實現的:

  • 首先, 我們建立了一個圓柱幾何體,然後通過圓柱的幾何體每一個頂點的y軸座標來設定需要繫結的骨骼的下標和影響的程度:
//遍歷幾何體所有的頂點
for (var i = 0; i < geometry.vertices.length; i++) {

    //根據頂點的位置計算出骨骼影響下標和權重

    var vertex = geometry.vertices[i];
    var y = (vertex.y + sizing.halfHeight);

    var skinIndex = Math.floor(y / sizing.segmentHeight);
    var skinWeight = (y % sizing.segmentHeight) / sizing.segmentHeight;

    geometry.skinIndices.push(new THREE.Vector4(skinIndex, skinIndex + 1, 0, 0));
    geometry.skinWeights.push(new THREE.Vector4(1 - skinWeight, skinWeight, 0, 0));

}

幾何體的skinIndices屬性和skinWeights屬性就是來設定相關的繫結下標和權重(骨骼影響程度)。

  • 相應的,我們需要設定一組相關的骨骼,骨骼具有巢狀關係,這樣才能實現一個骨架,由於圓柱體比較簡單,我們就建立一條骨骼垂直巢狀的骨骼:
bones = [];

var prevBone = new THREE.Bone();
bones.push(prevBone);
prevBone.position.y = -sizing.halfHeight;

for (var i = 0; i < sizing.segmentCount; i++) {

    var bone = new THREE.Bone();
    bone.position.y = sizing.segmentHeight;
    bones.push(bone); //新增到骨骼陣列
    prevBone.add(bone); //上一個骨骼定義為父級
    prevBone = bone;

}
  • 建立紋理時,我們還需要設定當前紋理需要受到骨骼的影響,將材質的skinning屬性設定為true
var lineMaterial = new THREE.MeshBasicMaterial({
    skinning: true,
    wireframe: true
});
  • 最後,我們需要建立骨骼材質,並將模型繫結骨骼:
mesh = new THREE.SkinnedMesh(geometry, [material, lineMaterial]);
var skeleton = new THREE.Skeleton(bones); //建立骨架
mesh.add(bones[0]); //將骨骼新增到模型裡面
mesh.bind(skeleton); //模型繫結骨架

這樣,我們就實現了使用Three.js建立一個簡單的骨骼動畫。使用dat.gui我們能夠修改每一個骨骼的poisitionrotationscale並檢視對當前模型的影響。案例的原始碼地址:點選這裡

兩種動畫的區別

變形動畫主要用於精度要求高的動畫,比如人物的面部表情。優點是動畫表達會很到位,缺點就是擴充套件性不強,只能執行設定好的相關動畫。 骨骼動畫主要用於那種精度要求低,而且需要豐富多樣的動畫,就比如人物的走動,攻擊防禦等動畫,我們可以通過一套骨骼,修改相應骨骼的位置的資訊直接實現相應的效果。確定是沒有變形動畫的精度高,但是可以實現多種多樣的效果。 總結:我們可以根據專案的需求來設定不同的動畫,就比如一個人物模型,說話我們使用變形動畫去實現,而肢體動作使用骨骼動畫去實現。

匯入模型動畫

Three.js的動畫系統中,你可以為模型的各種屬性設定動畫:骨骼動畫,變形動畫,材質的相關屬性(顏色,透明度, 是否可見)。動畫屬性可以設定淡入淡出效果以及各種扭曲特效。也可以單獨的改變一個物件或者多個物件上的動畫的影響程度和動畫時間。 為了實現這些,Three.js動畫系統在2015年修改為了一個類似於Unity和虛幻引擎4的架構。接下來我們瞭解一下這套動畫系統的主要元件以及它們時如何協同工作。

動畫片段(Animation Clips)

在我們成功匯入模型以後,如果模型擁有相關的動畫屬性,會在返回的模型資料中產生一個名為animations的陣列,陣列的每一個子項都是一個AnimationClips物件。 每一個單獨AnimationClips物件通常儲存的都是模型的一個動畫的資料,假如,如果模型網格是一個人物角色,第一個AnimationClips物件有可能儲存的是人物走動的動畫,第二個AnimationClips物件用於跳躍,第三個用於攻擊動畫等等。

關鍵幀軌跡(Keyframe Tracks)

AnimationClips物件內部,一般會有四個屬性:

  • name:當前的動畫的一個名稱
  • uuid:一個不會重複的uuid
  • duration:當前動畫一個迴圈所需要的時間
  • tracks:軌跡當前動畫每一次切換動作所需要的資料

假設當前的動畫是骨骼動畫,在關鍵幀軌跡中儲存的資料是在每一幀骨骼隨著時間變動的資料(位置,旋轉和縮放等)。 如果當前動畫是一個變形動畫,在關鍵幀軌跡中將會把頂點資料的變動儲存在其中(比如實現人臉的笑以及哭等動作)。

動畫混合器(Animation Mixer)

在動畫片段中儲存的資料僅僅構成了動畫實現的基礎,實際的播放權力在動畫混合器的手中。你可以想象動畫混合器其實不僅僅只是作為動畫的播放器,它還可以同時控制幾個動畫,混合它們或者合併它們。

動畫播放器(Animation Actions)

這個英文我更樂意將它翻譯成動畫播放器,因為我們最終需要將資料生成一個動畫播放器來操作當前的動畫執行,暫停或者停止,是否使用淡入淡出效果或者將動畫加快或減慢。

動畫物件組(Animation Object Groups)

如果你希望一組模型物件共享當前的動畫,我們可以使用動畫物件組來實現。

通過匯入模型顯示動畫

變形動畫

變形動畫

我們首先檢視一個官方的模型案例,這個案例效果是一匹馬奔跑的動畫,我們也可以通過下面地址檢視:點選這裡 接下來我們看一下這匹馬是如何實現的:

  • 在模型載入成功以後,我們首先將模型創建出來,並將材質的morphTargets設定為ture,可以使用變形動畫:
mesh = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({
    vertexColors: THREE.FaceColors,
    morphTargets: true
}));
mesh.castShadow = true;
mesh.scale.set(0.1, 0.1, 0.1);
scene.add(mesh);
  • 然後我們建立了一個針對於該模型的混合器:
mixer = new THREE.AnimationMixer(mesh);
  • 接著使用變形目標資料建立一個動畫片段:
var clip = THREE.AnimationClip.CreateFromMorphTargetSequence('gallop', geometry.morphTargets, 30);
  • 使用混合器和動畫片段建立一個動畫播放器來播放:
var action = mixer.clipAction(clip); //建立動畫播放器
action.setDuration(1); //設定當前動畫一秒為一個週期
action.play(); //設定當前動畫播放
  • 最後,我們還需要在重新繪製迴圈中更新混合器,進行動作更新:
function render() {

    control.update();

    var time = clock.getDelta();
	//由於模型匯入是非同步的,所以我們再模型沒有載入完之前是獲取不到混合器的
    if (mixer) {
        mixer.update(time);
    }

    renderer.render(scene, camera);
}

骨骼動畫

骨骼動畫

骨骼動畫模型我們使用的是gltf格式,這個模型是在Sketchfab網站下載,案例是一個小姐姐跳舞的一個片段,檢視地址:點選這裡 gltf格式的模型匯入進來以後,我們可以直接通過animations陣列建立播放器:

mixer = new THREE.AnimationMixer(obj); //通過當前模型建立混合器
action = mixer.clipAction(gltf.animations[0]); //通過動畫資料建立播放器

直接呼叫播放器的播放事件讓動畫播放:

action.play();

最後,我們還是需要在迴圈渲染中更新混合器,並將每一幀渲染的間隔時間傳入

function render() {
    control.update();
    var time = clock.getDelta();
    if (mixer) {
        mixer.update(time);
    }
    renderer.render(scene, camera);
}