【帶著canvas去流浪(12)】用Three.js製作簡易的MARVEL片頭動畫(上)

大史不說話發表於2019-07-20

示例程式碼託管在:http://www.github.com/dashnowords/blogs

部落格園地址:《大史住在大前端》原創博文目錄

華為雲社群地址:【你要的前端打怪升級指南】

【帶著canvas去流浪(12)】用Three.js製作簡易的MARVEL片頭動畫(上)

一. 大作業說明

通讀完上一篇博文中提及的教程,覺得應該搞個大作業鞏固一下所學的知識,想起剛上映的漫威宇宙第三階段收官之作《蜘蛛俠·英雄遠征》,於是決定仿一個MARVEL的片頭動畫作為three.js的課後練習,使用的版本是R104版本。本節先來解決視訊貼圖的問題。

二.基本思路

簡易片頭動畫的實現思路如下,除了正常的舞臺元素外,需要背景音樂,使用THREE.AudioLoader就可以從後後臺載入音樂,舞臺中主要的實體元素是MARVEL這幾個字母的立體模型,可以使用THREE.TextGeometry來進行建模(【Three.js繪製字型模型】),它要求先載入字型檔案,然後才能例項化,參考官方文件的實現就可以了。有了字型模型以後,還需要一些影片素材貼在字型模型上,THREE.VideoTexture可以解決這個問題(【Three.js使用VideoTexture實現視訊Video更新紋理】),它可以將HTML中的<video>標籤引入的資源作為表面紋理通過材料例項的map引數與之關聯在一起,然後貼在幾何體表面,最後要解決的問題就是鏡頭的變化了,看過漫威電影的同學都知道,片頭動畫最後一部分的畫面先是鏡頭後退,然後MARVEL幾個字母逐漸翻轉過來,這個效果的實現方式很多,可以調整相機引數,也可以調整物體引數,建議自己動手時各種方法都嘗試一下。為了熟悉更多特性,筆者自己在實現中使用正交相機,通過調整正交相機的視場寬度來模擬鏡頭後退動畫(在透視相機下可以直接調整相機的Z軸座標實現類似的效果),然後通過設定幾何體的位移和旋轉來模擬鏡頭的移動。

三.視訊紋理表面修復——UV對映

3.1 問題描述

【帶著canvas去流浪(12)】用Three.js製作簡易的MARVEL片頭動畫(上)

整個大作業中最難處理的就是視訊紋理貼圖的部分,所以本篇先來搞定這個知識點。如果使用THREE.js提供的Geometry基本不會遇到什麼問題,例如上圖中的示例,就將視訊素材貼在了立方體的各個面上,然而當你使用其他帶有一些自定義性質的幾何體例項,比如自己畫了一個shape然後拉伸成為拉伸體,或者本次大作業中需要使用的TextGeometry字型模型時。視訊貼圖就直接失效了。同樣尺寸的立方體,如果用THREE.BoxGeometry來生成例項,表面就可以直接貼視訊,如果使用shape畫一個矩形再拉伸成同樣尺寸的實體,視訊就無法正常覆蓋在模型表面,如下圖所示:

【帶著canvas去流浪(12)】用Three.js製作簡易的MARVEL片頭動畫(上)

仔細看你會發現圖片邊界的地方有發光的條,將細節放大後可以看到下面的場景:

【帶著canvas去流浪(12)】用Three.js製作簡易的MARVEL片頭動畫(上)

可以看到,視訊實際上的確是覆蓋在立方體表面了,但只是佔了很小的一塊,所以需要針對這種情況進行模型紋理修復,使視訊可以覆蓋幾何體的單個表面。

3.2 紋理貼圖的基本原理-UV對映

Three.js中,幾何體是通過的特徵構建起來的,如果將一個幾何體例項物件在控制檯列印出來,就可以看到儲存端點座標資訊的vertexs和儲存面資訊的faces陣列。當你構建一個立方體時,會發現它的faces屬性陣列中有12個面的資訊,因為Three.js中預設使用三角面片來構建幾何體,一個矩形表面需要用兩個三角面片來構建,(你可以將立方體材料material中傳入 wireframe:true來看到立方體的線框圖),faces陣列中每一個面中儲存的是構建這個三角面的3個點的位置資訊。

紋理貼圖座標也稱為UV座標,它的貼圖原理是這樣的,首先將貼圖素材x軸和y軸的長度以0-1來標記,那麼使用3個座標範圍在[0~1,0~1]的點就可以在圖形素材中以三角形剪裁出需要的部分,同理使用4個座標範圍在[0~1,0~1]的點,就可以在圖形素材中以四邊形剪裁出需要的部分,以此類推,如下圖所示:

【帶著canvas去流浪(12)】用Three.js製作簡易的MARVEL片頭動畫(上)

右圖中白色三角形的三個頂點在歸一化座標系中的座標值已經列出,將[0.2,0.2],[0.2,0.8],[0.7,0.2]這三個座標點資訊填充到對應的UV對映陣列中後,Three.js就會用這個三角形區域來對一個三角面進行貼圖。由於預設面是三角面,所以我們通過例項化3個THREE.Vector2(x,y)物件來表示從素材中擷取的三角形區域,得到了素材後要如何將它與三角面的頂點座標對應起來呢?這就引出了本節中的關鍵概念——UV對映矩陣

大部分高大上的概念都離不開一個土掉渣的實現,UV對映矩陣也不例外。

由於貼圖素材是三個點,幾何體某個三角面也是有三個頂點,如果不限制索引,那麼就可能存在很多種貼圖結果:

【帶著canvas去流浪(12)】用Three.js製作簡易的MARVEL片頭動畫(上)

為了保證貼圖素材的方向,它們之間就有存在一個對應關係,否則最後渲染的紋理可能就是倒著的或者旋轉90°的影象,所以UV對映矩陣中儲存的依然是上例中右圖的三個點,但預設索引和構成幾何體指定面的三個頂點的索引相對應,這就唯一限定了擷取表面到幾何體三角面的貼圖樣式。

3.3 關鍵示例程式碼

完整的示例可以從附件或開頭處的github程式碼倉中獲取,示例是一個express工程,npm install裝一下依賴,跑起來之後訪問localhost:3333就可以看到。

//重構UV Mapping
function rebuildUVMapping() {    
    //在紋理素材上標記關鍵點
    let pos = [
        new THREE.Vector2(0,0.1),
        new THREE.Vector2(1,0.1),
        new THREE.Vector2(1,0.9),
        new THREE.Vector2(0,0.9),
    ]

    //uv對映的紋理存放在幾何體例項的下面這個屬性中
    let uvs = geometry.faceVertexUvs[0];

    //背面
    //生成網格時材料可以傳陣列,materialIndex可以為不同面指定不同的材質,本例中對應不同的視訊片段
    geometry.faces[0].materialIndex = 4; 
    geometry.faces[1].materialIndex = 4;
    //重構UV對映關係矩陣
    uvs[0] = [pos[1], pos[0], pos[3]];
    uvs[1] = [pos[3], pos[2], pos[1]];

    //正面
    geometry.faces[2].materialIndex = 0;
    geometry.faces[3].materialIndex = 0;
    uvs[2] = [pos[3], pos[0], pos[1]];
    uvs[3] = [pos[1], pos[2], pos[3]];

    //標記uv對映是可更新的
    geometry.uvsNeedUpdate = true;
}

四.小結

視訊紋理是本例中最難的部分了,下一篇中筆者將構建字型模型,並加入鏡頭轉換,完成整個預期的動畫,敬請關注,也希望感興趣的小夥伴一起交流。

相關文章