WebGL three.js學習筆記 載入外部模型以及Tween.js動畫

nsytsqdtn發表於2019-05-11

WebGL three.js學習筆記 載入外部模型以及Tween.js動畫

本文的程式實現了載入外部stl格式的模型,以及學習瞭如何把載入的模型變為一個粒子系統,並使用Tween.js對該粒子系統進行動畫設定

模型動畫demo演示(網頁載入速度可能會比較慢)

demo地址:https://nsytsqdtn.github.io/demo/naval_craft/naval_craft

demo截圖如下:

naval_craft
naval_craft
naval_craft
naval_craft

原模型截圖:

原模型
原模型

在我們寫three.js的網頁的時候,大多時候並不需要我們去手動建立模型,一些複雜的模型都是通過建模軟體去完成,所以在這裡去學習如何去將外部的模型載入到我們的網頁中來。

three.js支援匯入的模型有很多,包括我們常見的OBJ、FBX、STL、PLY、JSON等等格式,在這個程式中,我選擇了使用STL模型來進行學習。

.stl 檔案是在計算機圖形應用系統中,用於表示三角形網格的一種檔案格式,常用於3d列印技術使用,因為STL格式的檔案在網上可以免費不用註冊的下載,比較方便。這裡推薦一個還不錯的網站,http://www.3dhoo.com/model ,裡面有很多免費直接下載STL格式的模型。

載入外部模型

在three.js中,我們要載入外部模型,就需要引入相應的js檔案。比如我需要引入STL格式的檔案,我就引入“three.js\examples\js\loaders\STLLoader.js”,其他格式的js檔案在loaders資料夾也都能找到,如果是three.js沒有支援匯入的模型格式,就需要自己寫一個載入器,網上也有許多的教程。

引入相應js檔案以後,我們首先要做的事建立一個載入器。

1let loader = new THREE.STLLoader();//建立stl的載入器,用載入器來載入stl模型
複製程式碼

我們要使用該載入器載入模型,就需要呼叫loader .load(filename,onSuccess(bufferGeometry),onProgress(xhr),onError(error))這個方法
其中:
filename是模型的路徑 onSuccess(bufferGeometry)是載入成功後回撥處理(引數為生成的模型的幾何體),

注意:這裡的幾何體不是我們常用的geometry,而是bufferGeometry,它和geometry還是有一些的區別,但是也都可以作為THREE.Mesh()的第一個引數穿進去。具體可以進行百度。

onProgress(xhr)是載入過程中回撥處理(xhr物件屬性可計算出已完成載入百分比) onError(error)是失敗回撥處理方法
一般我們只需要使用前兩個引數就可以完成工作。

1let loader = new THREE.STLLoader();//建立stl的載入器,用載入器來載入stl模型
2let loader.load("../../../asset/ship.stl"function (bufferGeometry{//載入模型的方法,
3//第一個引數是模型的路徑,第二個引數時候我們定義的回撥函式,一旦模型載入成功,回撥函式就會被呼叫
4let material = new THREE.MeshBasicMaterial();
5let mesh = new THREE.Mesh(bufferGeometry,material);
6scene.add(mesh);
7}
複製程式碼

一般只需要這樣寫回撥函式,模型就可以成功載入。
但我在這裡想根據該模型去建立一個粒子系統,像本文開頭的那樣,所以我們需要改一下程式碼。

 1        let loader = new THREE.STLLoader();//建立stl的載入器,用載入器來載入stl模型
2        group = new THREE.Object3D();
3        loader.load("../../../asset/ship.stl"function (bufferGeometry{//載入模型的方法,第一個引數是模型的路徑,第二個引數時候我們定義的回撥函式,一旦模型載入成功,回撥函式就會被呼叫
4            let geometry = new THREE.Geometry().fromBufferGeometry(bufferGeometry);//stl模型載入到js裡就會變成bufferGeometry型別,我們先用一個方法把它變成Geometry型別
5            loadGeometry = geometry.clone();//建立該geometry的克隆體,後面會用到
6            let material = new THREE.PointsMaterial({//點雲的材質
7                color: 0xffffff,
8                transparenttrue,
9                opacity1,
10                size0.5,//可自由修改看看效果
11                blending: THREE.AdditiveBlending,
12                map: generateSprite()//自定義畫布圖案來充當每一個粒子的材質
13            });
14            //建立點雲,以及設定它的位置及旋轉角度,調整到最好看的地方
15            group = new THREE.Points(geometry, material);
16            group.sortParticles = true;
17            group.position.set(0,0,0);
18            group.position.x -=70;
19            group.rotation.x = Math.PI*3/2;
複製程式碼

其中: 我們使用THREE.Geometry().fromBufferGeometry(bufferGeometry)函式把bufferGeometry型別改為geometry型別,因為該型別我們更加熟悉,後面使用起來也比較方便。

generateSprite()函式是在之前的文章也介紹過的,建立一個顏色漸變的畫布,來充當粒子系統紋理,這裡就不再贅述了。具體程式碼如下:

 1//自定義漸變顏色的畫布,前面的文章有介紹,這個方法在寫three.js程式很常用
2    function generateSprite({
3
4        var canvas = document.createElement('canvas');
5        canvas.width = 16;
6        canvas.height = 16;
7
8        var context = canvas.getContext('2d');
9        var gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 20, canvas.width / 2, canvas.height / 2, canvas.width / 2);
10        gradient.addColorStop(0'rgba(255,255,255,1)');
11        gradient.addColorStop(0.2'rgba(0,255,255,1)');
12        gradient.addColorStop(0.4'rgba(0,0,255,1)');
13        gradient.addColorStop(1'rgba(0,0,0,1)');
14
15        context.fillStyle = gradient;
16        context.fillRect(00, canvas.width, canvas.height);
17
18        var texture = new THREE.Texture(canvas);
19        texture.needsUpdate = true;
20        return texture;
21    }
複製程式碼

Tween.js動畫

tweenjs 是使用 JavaScript 中的一個簡單的補間動畫庫,支援數字、物件的屬性和 CSS 樣式屬性的賦值。
tweenjs 以平滑的方式修改元素的屬性值,需要傳遞給 tween 要修改的值、動畫結束時的最終值和動畫花費時間,之後 tween 引擎就可以計算從開始動畫點到結束動畫點之間值,從而產生平滑的動畫效果。

我們首先需要引入tween.js檔案,該檔案的路徑是“three.js\examples\js\libs\tween.min.js”,也可以直接百度搜尋tween.js去下載。
具體的用法是:

1let posSrc = {pos0};//建立一個posSrc的物件,該物件裡面有pos的屬性,並初始化該屬性為0
2let tween = new TWEEN.Tween(posSrc).to({pos1}, 5000);//建立tween的補間動畫,使posSrc中的pos屬性的值在5000ms內從0到1變化
3tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果
複製程式碼

我們建立了TWEEN.Tween物件,這個物件會確保x屬性值在5000毫秒內從0變化到1。通過Tweenjs,你還可以指定屬性值是如何變化的,是線性的、指數性的,還是其他任何可能的方式。屬性值在指定時間內的變化被稱為easing(緩動),在Tween.js中你可以使用easing()方法來配置緩動效果。我們還可以建立更多的TWEEN.Tween物件,並使用chain(TWEEN.Tween)函式連結多個補間動畫。

我們還需要一個update的函式,在每次更新補間的時候,都可以去更新每個粒子的位置,來實現的動畫效果。

 1            let posSrc = {pos0};//建立一個posSrc的物件,該物件裡面有pos的屬性
2            //並初始化該屬性為0
3            let tween = new TWEEN.Tween(posSrc).to({pos1}, 5000);//建立tween的補間動畫
4            //使posSrc中的pos屬性的值在5000ms內從0到1變化
5            tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果
6            let tweenStand = new TWEEN.Tween(posSrc).to({pos1}, 2000);//讓動畫在pos的值
7            //變為1後停止一段時間,方便我們觀察,所以再建立一個tween,讓pos從1到1(即不變)
8            tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果
9            let tweenBack = new TWEEN.Tween(posSrc).to({pos0}, 5000);//建立tweenBack的
10            //補間動畫,和初始相反,使posSrc中的pos屬性的值在5000ms內從1到0變化
11            tweenBack.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果
12            //每一個補間動畫之間使用chain()連線起來
13            tween.chain(tweenStand);
14            tweenStand.chain(tweenBack);
15            tweenBack.chain(tween);
16
17            //在補間的過程中,讓所有的粒子開始移動
18            let onUpdate = function ({
19                let pos = posSrc.pos;//定義一個pos,賦值為posSrc物件的pos屬性
20                let count = 0;
21                loadGeometry.vertices.forEach(function (e{//遍歷每個頂點
22                //這裡需要遍歷剛剛克隆的geometry
23                //(暫時不是很明白這點,反正如果遍歷group.geometry.vertices
24                //動畫系統會讓整個物體一起移動,沒有伸展開來的效果)。
25                    var newZ = e.z * pos;//得到新的Z值,根據當前的pos值去改變
26                    group.geometry.vertices[count++].set(e.x, e.y, newZ);//設定每個頂點的位置
27                    //group.geometry.vertices是陣列型別,所以用count作為索引
28                    group.geometry.verticesNeedUpdate = true;//重要,不然會沒有動畫效果
29                });
30                group.sortParticles = true;
31            };
32            //tween在每次更新後會執行tween.onUpdate()函式
33            //裡面的引數就是我們自定義要讓它如果去運動的函式,即上面寫的onUpdate
34            tween.onUpdate(onUpdate);
35            tweenStand.onUpdate(onUpdate);
36            tweenBack.onUpdate(onUpdate);
37
38            tween.start();//開啟tween
複製程式碼

在這段程式碼中,我們建立了三個個補間: tween、tweenStand、tweenBack。第一個補間定義了position屬性如何從1過渡到0,第三個剛好相反,第二個是讓動畫暫時停下。通過chain(方法可以將這三個補間銜接起來,這樣當動畫啟動之後,程式就會在這三個補間迴圈。程式碼最後定義的是onUpdate()方法,這個方法遍歷粒子系統中的所有頂點,並使用補間(this.pos)提供的位置更新頂點的位置。

補間動畫需要在模型載入完成後就啟動,所以我們在下面的函式末尾呼叫tween.start()方法:

如果之前沒有把bufferGeometry轉化為Geometry型別,要去更改每個頂點的位置會變得比較麻煩。

最後還需要告知three.js什麼時候重新整理所有的補間動畫,所以在render()函式里加上TWEEN.update();

1 function render({
2        TWEEN.update();//通知TWEEN在什麼時候去重新整理補間動畫,重要,否則會沒有動畫
3        //效能監控器的更新
4        stats.update();
5        renderer.clear();
6        requestAnimationFrame(render);
7        renderer.render(scene, camera);
8    }
複製程式碼

到了這裡,程式的大體就已經完成,剩下的就是建立場景,攝像機,渲染器等等東西以及調整模型的位置。這裡不再贅述。

完整的程式碼如下:

  1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <title>Naval Craft Sprite</title>
6    <script src="../../import/three.js"></script>
7    <script src="../../import/stats.js"></script>
8    <script src="../../import/Setting.js"></script>
9    <script src="../../import/OrbitControls.js"></script>
10    <script src="../../import/tween.min.js"></script>
11    <script src="../../import/STLLoader.js"></script>
12    <style type="text/css">
13        div#WebGL-output {
14            border: none;
15            cursor: pointer;
16            width100%;
17            height850px;
18            background-color#333333;
19        }
20    
</style>
21</head>
22<body onload="threeStart()">
23<div id="WebGL-output"></div>
24<script>
25    let camera, renderer, scene,controller;
26    function initThree({
27        //渲染器初始化
28        renderer = new THREE.WebGLRenderer({
29            antialiastrue//抗鋸齒開啟
30        });
31        //設定渲染的大小
32        renderer.setSize(window.innerWidth, window.innerHeight);
33        //設定渲染的顏色
34        renderer.setClearColor(0x333333);
35        renderer.shadowMapEnabled = true;//開啟陰影的渲染
36        renderer.shadowMapType = THREE.PCFSoftShadowMap;//設定陰影型別為柔和
37        document.getElementById("WebGL-output").appendChild(renderer.domElement);//將渲染新增到div中
38
39        //初始化攝像機,這裡使用透視投影攝像機
40        camera = new THREE.PerspectiveCamera(50window.innerWidth / window.innerHeight, 0.0110000);
41        camera.position.set(353575);//相機的位置,自由調整
42        camera.up.x = 0;//設定攝像機的上方向為哪個方向,這裡定義攝像的上方為Y軸正方向
43        camera.up.y = 1;
44        camera.up.z = 0;
45        //攝像機對準的地方
46        camera.lookAt(000);
47
48        //初始化場景
49        scene = new THREE.Scene();
50
51        //相機的移動
52        controller = new THREE.OrbitControls(camera, renderer.domElement);
53        //相機圍繞旋轉的目標,設定為原點
54        controller.target = new THREE.Vector3(000);
55
56    }
57
58    let loadGeometry;
59    let group;
60
61    function initObject({
62        let loader = new THREE.STLLoader();//建立stl的載入器,用載入器來載入stl模型
63        group = new THREE.Object3D();
64        loader.load("../../asset/naval_craft.stl"function (bufferGeometry{//載入模型的方法,第一個引數是模型的路徑,第二個引數時候我們定義的回撥函式,一旦模型載入成功,回撥函式就會被呼叫
65            let geometry = new THREE.Geometry().fromBufferGeometry(bufferGeometry);//stl模型載入到js裡就會變成bufferGeometry型別,我們先用一個方法把它變成Geometry型別
66            loadGeometry = geometry.clone();//建立該geometry的克隆體,後面會用到
67            let material = new THREE.PointsMaterial({//點雲的材質
68                color: 0xffffff,
69                transparenttrue,
70                opacity1,
71                size0.5,//可自由修改看看效果
72                blending: THREE.AdditiveBlending,
73                map: generateSprite()//自定義畫布圖案來充當每一個粒子的材質
74            });
75            //建立點雲,以及設定它的位置及旋轉角度,調整到最好看的地方
76            group = new THREE.Points(geometry, material);
77            group.sortParticles = true;
78            group.position.set(0,0,0);
79            group.position.x -=70;
80            group.rotation.x = Math.PI*3/2;
81
82            let posSrc = {pos0};//建立一個posSrc的物件,該物件裡面有pos的屬性,並初始化該屬性為0
83            let tween = new TWEEN.Tween(posSrc).to({pos1}, 5000);//建立tween的補間動畫,使posSrc中的pos屬性的值在5000ms內從0到1變化
84            tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果
85            let tweenStand = new TWEEN.Tween(posSrc).to({pos1}, 2000);//讓動畫在pos的值變為1後停止一段時間,方便我們觀察,所以再建立一個tween,讓pos從1到1(即不變)
86            tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果
87            let tweenBack = new TWEEN.Tween(posSrc).to({pos0}, 5000);//建立tweenBack的補間動畫,和初始相反,使posSrc中的pos屬性的值在5000ms內從1到0變化
88            tweenBack.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果
89            //每一個補間動畫之間使用chain()連線起來
90            tween.chain(tweenStand);
91            tweenStand.chain(tweenBack);
92            tweenBack.chain(tween);
93
94            //在補間的過程中,讓所有的粒子開始移動
95            let onUpdate = function ({
96                let pos = posSrc.pos;//定義一個pos,賦值為posSrc物件的pos屬性
97                let count = 0;
98                loadGeometry.vertices.forEach(function (e{//遍歷每個頂點,這裡需要遍歷剛剛克隆的geometry
99                    var newZ = e.z * pos;//得到新的Z值,根據當前的pos值去改變
100                    group.geometry.vertices[count++].set(e.x, e.y, newZ);//設定每個頂點的位置,group.geometry.vertices是陣列型別,所以用count作為索引
101                    group.geometry.verticesNeedUpdate = true;//重要,不然會沒有動畫效果
102                });
103                group.sortParticles = true;
104            };
105            //tween在每次更新後會執行tween.onUpdate()函式,裡面的引數就是我們自定義要讓它如果去運動的函式,即上面寫的onUpdate
106            tween.onUpdate(onUpdate);
107            tweenStand.onUpdate(onUpdate);
108            tweenBack.onUpdate(onUpdate);
109
110            tween.start();//開啟tween
111            scene.add(group);
112        });
113    }
114
115    //自定義漸變顏色的畫布,前面的文章有介紹,這個方法在寫three.js程式很常用
116    function generateSprite({
117
118        var canvas = document.createElement('canvas');
119        canvas.width = 16;
120        canvas.height = 16;
121
122        var context = canvas.getContext('2d');
123        var gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 20, canvas.width / 2, canvas.height / 2, canvas.width / 2);
124        gradient.addColorStop(0'rgba(255,255,255,1)');
125        gradient.addColorStop(0.2'rgba(0,255,255,1)');
126        gradient.addColorStop(0.4'rgba(0,0,255,1)');
127        gradient.addColorStop(1'rgba(0,0,0,1)');
128
129        context.fillStyle = gradient;
130        context.fillRect(00, canvas.width, canvas.height);
131
132        var texture = new THREE.Texture(canvas);
133        texture.needsUpdate = true;
134        return texture;
135
136    }
137
138    //渲染函式
139    function render({
140        TWEEN.update();//通知TWEEN在什麼時候去重新整理補間動畫,重要,否則會沒有動畫
141        //效能監控器的更新
142        stats.update();
143        renderer.clear();
144        requestAnimationFrame(render);
145        renderer.render(scene, camera);
146    }
147
148    //功能函式
149    function setting({
150        loadFullScreen();
151        loadAutoScreen(camera, renderer);
152        loadStats();
153    }
154
155    //執行主函式
156    function threeStart({
157        initThree();
158        initObject();
159        setting();
160        render();
161    }
162
</script>
163</body>
164</html>
複製程式碼

相關文章