本文的程序實現了加載外部stl格式的模型,以及學習瞭如何把加載的模型變爲一個粒子系統,並使用Tween.js對該粒子系統進行動畫設置javascript
demo地址:https://nsytsqdtn.github.io/demo/naval_craft/naval_craftcss
demo截圖以下:
html
原模型截圖:
java
在咱們寫three.js的網頁的時候,大多時候並不須要咱們去手動創建模型,一些複雜的模型都是經過建模軟件去完成,因此在這裏去學習如何去將外部的模型加載到咱們的網頁中來。git
three.js支持導入的模型有不少,包括咱們常見的OBJ、FBX、STL、PLY、JSON等等格式,在這個程序中,我選擇了使用STL模型來進行學習。github
.stl 文件是在計算機圖形應用系統中,用於表示三角形網格的一種文件格式,經常使用於3d打印技術使用,由於STL格式的文件在網上能夠免費不用註冊的下載,比較方便。這裏推薦一個還不錯的網站,http://www.3dhoo.com/model ,裏面有不少免費直接下載STL格式的模型。web
在three.js中,咱們要加載外部模型,就須要引入相應的js文件。好比我須要引入STL格式的文件,我就引入「three.js\examples\js\loaders\STLLoader.js」,其餘格式的js文件在loaders文件夾也都能找到,若是是three.js沒有支持導入的模型格式,就須要本身寫一個加載器,網上也有許多的教程。canvas
引入相應js文件之後,咱們首先要作的事建立一個加載器。數組
let loader = new THREE.STLLoader();//建立stl的加載器,用加載器來加載stl模型
咱們要使用該加載器加載模型,就須要調用loader .load(filename,onSuccess(bufferGeometry),onProgress(xhr),onError(error))這個方法
其中:
filename是模型的路徑
onSuccess(bufferGeometry)是加載成功後回調處理(參數爲生成的模型的幾何體),app
注意:這裏的幾何體不是咱們經常使用的geometry,而是bufferGeometry,它和geometry仍是有一些的區別,可是也均可以做爲THREE.Mesh()的第一個參數穿進去。具體能夠進行百度。
onProgress(xhr)是加載過程當中回調處理(xhr對象屬性可計算出已完成加載百分比)
onError(error)是失敗回調處理方法
通常咱們只須要使用前兩個參數就能夠完成工做。
let loader = new THREE.STLLoader();//建立stl的加載器,用加載器來加載stl模型 let loader.load("../../../asset/ship.stl", function (bufferGeometry) {//加載模型的方法, //第一個參數是模型的路徑,第二個參數時候咱們定義的回調函數,一旦模型加載成功,回調函數就會被調用 let material = new THREE.MeshBasicMaterial(); let mesh = new THREE.Mesh(bufferGeometry,material); scene.add(mesh); }
通常只須要這樣寫回調函數,模型就能夠成功加載。
但我在這裏想根據該模型去建立一個粒子系統,像本文開頭的那樣,因此咱們須要改一下代碼。
let loader = new THREE.STLLoader();//建立stl的加載器,用加載器來加載stl模型 group = new THREE.Object3D(); loader.load("../../../asset/ship.stl", function (bufferGeometry) {//加載模型的方法,第一個參數是模型的路徑,第二個參數時候咱們定義的回調函數,一旦模型加載成功,回調函數就會被調用 let geometry = new THREE.Geometry().fromBufferGeometry(bufferGeometry);//stl模型加載到js裏就會變成bufferGeometry類型,咱們先用一個方法把它變成Geometry類型 loadGeometry = geometry.clone();//建立該geometry的克隆體,後面會用到 let material = new THREE.PointsMaterial({//點雲的材質 color: 0xffffff, transparent: true, opacity: 1, size: 0.5,//可自由修改看看效果 blending: THREE.AdditiveBlending, map: generateSprite()//自定義畫布圖案來充當每個粒子的材質 }); //建立點雲,以及設置它的位置及旋轉角度,調整到最好看的地方 group = new THREE.Points(geometry, material); group.sortParticles = true; group.position.set(0,0,0); group.position.x -=70; group.rotation.x = Math.PI*3/2;
其中:
咱們使用THREE.Geometry().fromBufferGeometry(bufferGeometry)函數把bufferGeometry類型改成geometry類型,由於該類型咱們更加熟悉,後面使用起來也比較方便。
generateSprite()函數是在以前的文章也介紹過的,建立一個顏色漸變的畫布,來充當粒子系統紋理,這裏就再也不贅述了。具體代碼以下:
//自定義漸變顏色的畫布,前面的文章有介紹,這個方法在寫three.js程序很經常使用 function generateSprite() { var canvas = document.createElement('canvas'); canvas.width = 16; canvas.height = 16; var context = canvas.getContext('2d'); var gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2); gradient.addColorStop(0, 'rgba(255,255,255,1)'); gradient.addColorStop(0.2, 'rgba(0,255,255,1)'); gradient.addColorStop(0.4, 'rgba(0,0,255,1)'); gradient.addColorStop(1, 'rgba(0,0,0,1)'); context.fillStyle = gradient; context.fillRect(0, 0, canvas.width, canvas.height); var texture = new THREE.Texture(canvas); texture.needsUpdate = true; return texture; }
tweenjs 是使用 JavaScript 中的一個簡單的補間動畫庫,支持數字、對象的屬性和 CSS 樣式屬性的賦值。
tweenjs 以平滑的方式修改元素的屬性值,須要傳遞給 tween 要修改的值、動畫結束時的最終值和動畫花費時間,以後 tween 引擎就能夠計算從開始動畫點到結束動畫點之間值,從而產平生滑的動畫效果。
咱們首先須要引入tween.js文件,該文件的路徑是「three.js\examples\js\libs\tween.min.js」,也能夠直接百度搜索tween.js去下載。
具體的用法是:
let posSrc = {pos: 0};//建立一個posSrc的對象,該對象裏面有pos的屬性,並初始化該屬性爲0 let tween = new TWEEN.Tween(posSrc).to({pos: 1}, 5000);//建立tween的補間動畫,使posSrc中的pos屬性的值在5000ms內從0到1變化 tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果
咱們建立了TWEEN.Tween對象,這個對象會確保x屬性值在5000毫秒內從0變化到1。經過Tweenjs,你還能夠指定屬性值是如何變化的,是線性的、指數性的,仍是其餘任何可能的方式。屬性值在指定時間內的變化被稱爲easing(緩動),在Tween.js中你可使用easing()方法來配置緩動效果。咱們還能夠建立更多的TWEEN.Tween對象,並使用chain(TWEEN.Tween)函數連接多個補間動畫。
咱們還須要一個update的函數,在每次更新補間的時候,均可以去更新每一個粒子的位置,來實現的動畫效果。
let posSrc = {pos: 0};//建立一個posSrc的對象,該對象裏面有pos的屬性 //並初始化該屬性爲0 let tween = new TWEEN.Tween(posSrc).to({pos: 1}, 5000);//建立tween的補間動畫 //使posSrc中的pos屬性的值在5000ms內從0到1變化 tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果 let tweenStand = new TWEEN.Tween(posSrc).to({pos: 1}, 2000);//讓動畫在pos的值 //變爲1後中止一段時間,方便咱們觀察,因此再建立一個tween,讓pos從1到1(即不變) tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果 let tweenBack = new TWEEN.Tween(posSrc).to({pos: 0}, 5000);//建立tweenBack的 //補間動畫,和初始相反,使posSrc中的pos屬性的值在5000ms內從1到0變化 tweenBack.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果 //每個補間動畫之間使用chain()鏈接起來 tween.chain(tweenStand); tweenStand.chain(tweenBack); tweenBack.chain(tween); //在補間的過程當中,讓全部的粒子開始移動 let onUpdate = function () { let pos = posSrc.pos;//定義一個pos,賦值爲posSrc對象的pos屬性 let count = 0; loadGeometry.vertices.forEach(function (e) {//遍歷每一個頂點 //這裏須要遍歷剛剛克隆的geometry //(暫時不是很明白這點,反正若是遍歷group.geometry.vertices //動畫系統會讓整個物體一塊兒移動,沒有伸展開來的效果)。 var newZ = e.z * pos;//獲得新的Z值,根據當前的pos值去改變 group.geometry.vertices[count++].set(e.x, e.y, newZ);//設置每一個頂點的位置 //group.geometry.vertices是數組類型,因此用count做爲索引 group.geometry.verticesNeedUpdate = true;//重要,否則會沒有動畫效果 }); group.sortParticles = true; }; //tween在每次更新後會執行tween.onUpdate()函數 //裏面的參數就是咱們自定義要讓它若是去運動的函數,即上面寫的onUpdate tween.onUpdate(onUpdate); tweenStand.onUpdate(onUpdate); tweenBack.onUpdate(onUpdate); tween.start();//開啓tween
在這段代碼中,咱們建立了三個個補間: tween、tweenStand、tweenBack。第一個補間定義了position屬性如何從1過渡到0,第三個恰好相反,第二個是讓動畫暫時停下。經過chain(方法能夠將這三個補間銜接起來,這樣當動畫啓動以後,程序就會在這三個補間循環。代碼最後定義的是onUpdate()方法,這個方法遍歷粒子系統中的全部頂點,並使用補間(this.pos)提供的位置更新頂點的位置。
補間動畫須要在模型加載完成後就啓動,因此咱們在下面的函數末尾調用tween.start()方法:
若是以前沒有把bufferGeometry轉化爲Geometry類型,要去更改每一個頂點的位置會變得比較麻煩。
最後還須要告知three.js何時刷新全部的補間動畫,因此在render()函數里加上TWEEN.update();
function render() { TWEEN.update();//通知TWEEN在何時去刷新補間動畫,重要,不然會沒有動畫 //性能監控器的更新 stats.update(); renderer.clear(); requestAnimationFrame(render); renderer.render(scene, camera); }
到了這裏,程序的大致就已經完成,剩下的就是建立場景,攝像機,渲染器等等東西以及調整模型的位置。這裏再也不贅述。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Naval Craft Sprite</title> <script src="../../import/three.js"></script> <script src="../../import/stats.js"></script> <script src="../../import/Setting.js"></script> <script src="../../import/OrbitControls.js"></script> <script src="../../import/tween.min.js"></script> <script src="../../import/STLLoader.js"></script> <style type="text/css"> div#WebGL-output { border: none; cursor: pointer; width: 100%; height: 850px; background-color: #333333; } </style> </head> <body onload="threeStart()"> <div id="WebGL-output"></div> <script> let camera, renderer, scene,controller; function initThree() { //渲染器初始化 renderer = new THREE.WebGLRenderer({ antialias: true//抗鋸齒開啓 }); //設置渲染的大小 renderer.setSize(window.innerWidth, window.innerHeight); //設置渲染的顏色 renderer.setClearColor(0x333333); renderer.shadowMapEnabled = true;//開啓陰影的渲染 renderer.shadowMapType = THREE.PCFSoftShadowMap;//設置陰影類型爲柔和 document.getElementById("WebGL-output").appendChild(renderer.domElement);//將渲染添加到div中 //初始化攝像機,這裏使用透視投影攝像機 camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10000); camera.position.set(35, 35, 75);//相機的位置,自由調整 camera.up.x = 0;//設置攝像機的上方向爲哪一個方向,這裏定義攝像的上方爲Y軸正方向 camera.up.y = 1; camera.up.z = 0; //攝像機對準的地方 camera.lookAt(0, 0, 0); //初始化場景 scene = new THREE.Scene(); //相機的移動 controller = new THREE.OrbitControls(camera, renderer.domElement); //相機圍繞旋轉的目標,設置爲原點 controller.target = new THREE.Vector3(0, 0, 0); } let loadGeometry; let group; function initObject() { let loader = new THREE.STLLoader();//建立stl的加載器,用加載器來加載stl模型 group = new THREE.Object3D(); loader.load("../../asset/naval_craft.stl", function (bufferGeometry) {//加載模型的方法,第一個參數是模型的路徑,第二個參數時候咱們定義的回調函數,一旦模型加載成功,回調函數就會被調用 let geometry = new THREE.Geometry().fromBufferGeometry(bufferGeometry);//stl模型加載到js裏就會變成bufferGeometry類型,咱們先用一個方法把它變成Geometry類型 loadGeometry = geometry.clone();//建立該geometry的克隆體,後面會用到 let material = new THREE.PointsMaterial({//點雲的材質 color: 0xffffff, transparent: true, opacity: 1, size: 0.5,//可自由修改看看效果 blending: THREE.AdditiveBlending, map: generateSprite()//自定義畫布圖案來充當每個粒子的材質 }); //建立點雲,以及設置它的位置及旋轉角度,調整到最好看的地方 group = new THREE.Points(geometry, material); group.sortParticles = true; group.position.set(0,0,0); group.position.x -=70; group.rotation.x = Math.PI*3/2; let posSrc = {pos: 0};//建立一個posSrc的對象,該對象裏面有pos的屬性,並初始化該屬性爲0 let tween = new TWEEN.Tween(posSrc).to({pos: 1}, 5000);//建立tween的補間動畫,使posSrc中的pos屬性的值在5000ms內從0到1變化 tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果 let tweenStand = new TWEEN.Tween(posSrc).to({pos: 1}, 2000);//讓動畫在pos的值變爲1後中止一段時間,方便咱們觀察,因此再建立一個tween,讓pos從1到1(即不變) tween.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果 let tweenBack = new TWEEN.Tween(posSrc).to({pos: 0}, 5000);//建立tweenBack的補間動畫,和初始相反,使posSrc中的pos屬性的值在5000ms內從1到0變化 tweenBack.easing(TWEEN.Easing.Sinusoidal.InOut);//配置緩動效果 //每個補間動畫之間使用chain()鏈接起來 tween.chain(tweenStand); tweenStand.chain(tweenBack); tweenBack.chain(tween); //在補間的過程當中,讓全部的粒子開始移動 let onUpdate = function () { let pos = posSrc.pos;//定義一個pos,賦值爲posSrc對象的pos屬性 let count = 0; loadGeometry.vertices.forEach(function (e) {//遍歷每一個頂點,這裏須要遍歷剛剛克隆的geometry var newZ = e.z * pos;//獲得新的Z值,根據當前的pos值去改變 group.geometry.vertices[count++].set(e.x, e.y, newZ);//設置每一個頂點的位置,group.geometry.vertices是數組類型,因此用count做爲索引 group.geometry.verticesNeedUpdate = true;//重要,否則會沒有動畫效果 }); group.sortParticles = true; }; //tween在每次更新後會執行tween.onUpdate()函數,裏面的參數就是咱們自定義要讓它若是去運動的函數,即上面寫的onUpdate tween.onUpdate(onUpdate); tweenStand.onUpdate(onUpdate); tweenBack.onUpdate(onUpdate); tween.start();//開啓tween scene.add(group); }); } //自定義漸變顏色的畫布,前面的文章有介紹,這個方法在寫three.js程序很經常使用 function generateSprite() { var canvas = document.createElement('canvas'); canvas.width = 16; canvas.height = 16; var context = canvas.getContext('2d'); var gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2); gradient.addColorStop(0, 'rgba(255,255,255,1)'); gradient.addColorStop(0.2, 'rgba(0,255,255,1)'); gradient.addColorStop(0.4, 'rgba(0,0,255,1)'); gradient.addColorStop(1, 'rgba(0,0,0,1)'); context.fillStyle = gradient; context.fillRect(0, 0, canvas.width, canvas.height); var texture = new THREE.Texture(canvas); texture.needsUpdate = true; return texture; } //渲染函數 function render() { TWEEN.update();//通知TWEEN在何時去刷新補間動畫,重要,不然會沒有動畫 //性能監控器的更新 stats.update(); renderer.clear(); requestAnimationFrame(render); renderer.render(scene, camera); } //功能函數 function setting() { loadFullScreen(); loadAutoScreen(camera, renderer); loadStats(); } //運行主函數 function threeStart() { initThree(); initObject(); setting(); render(); } </script> </body> </html>