使用 Three.js 的 3D 制作動畫場景

推薦:將NSDT場景編輯器加入你的3D開發(fā)工具鏈滥崩。

由于 GSL 語法的復雜性岖圈,對于許多開發(fā)人員來說 WebGL 是一個未知的領域。但是有了 Three.js钙皮,在瀏覽器中 3D 的實現(xiàn)變得簡單蜂科。下面將講述一下如何使用 Three.js 創(chuàng)建一個簡單的 3D 飛機飛行的動畫場景顽决。

譯注:WebGL 是一項利用 JavaScriptAPI 渲染交互式 3D 電腦圖形和 2D 圖形的技術,可兼容任何的網(wǎng)頁瀏覽器导匣,無需加裝插件才菠。通過 WebGL 的技術,只需要編寫網(wǎng)頁代碼即可實現(xiàn) 3D 圖像的展示贡定。GLSL-OpenGL Shading Language 也稱作 GLslang 赋访,是一個以 C 語言為基礎的高階著色語言。它是由 OpenGL ARB 所建立缓待,提供開發(fā)者對繪圖管線更多的直接控制蚓耽,而無需使用匯編語言或硬件規(guī)格語言。詳細麻煩谷歌或百度一下~

在本教程中旋炒,我們將創(chuàng)建一個簡單的 3D 場景, 在兩個主要的部分會有一些交互步悠。在第一部分,我們會講解 Three.js 的基礎和如何創(chuàng)建一個簡單的場景瘫镇。第二部分會詳細講述如何優(yōu)化模型鼎兽,如何為場景中的不同元素增添氣氛以及更流暢的運動效果。

由于完整的游戲超出了本教程的范圍汇四,但是你可以下載或 checkout 源碼接奈。它包含了許多額外有趣的部分如:碰撞,抓硬幣和增加得分通孽。

在本教程中既绩,我們將重點學習 Three.js 中的一些基礎概念茧妒。這些基礎概念將帶你走進 WebGL 這新領域!

事不宜遲,我們馬上開始~

HTML & CSS

本教程主要采用 Three.js 類庫旬盯,Three.js 讓 WebGL 變得易于使用虽另。從官網(wǎng)GitHub repocheckout 獲取關于 Three.js 更多的信息航徙。

第一樣要做的事情就是在 HTML <header> 標簽中引入 Three.js:

<script?type="text/javascript"?src="js/three.js">

然后在 HTML 中需要添加一個元素作為容器牍蜂。

<div?id="world">

你可以像下面那樣寫一些簡單的樣式,讓它填滿整個 viewport:

#world?{

???position:?absolute;

???width:?100%;

???height:?100%;

???overflow:?hidden;

???background:?linear-gradient(#e4e0ba,?#f7d9aa);

}

正如你所見的一樣厚宰,背景有些漸變的效果腌巾,就像天空。

以上是標簽和樣式铲觉!

JavaScript

如果你已經(jīng)掌握了一些 JavaScript 的基礎知識澈蝙,使用 Three.js 會變得相當簡單。來~我們看看實現(xiàn)不同部分的代碼撵幽。

The Color Palette

在開始場景編碼之前灯荧,我覺得定義一個調(diào)色板是很有用的。因為在整個項目中會經(jīng)常使用到盐杂。在這個項目中逗载,我們會選擇以下這些顏色:

var?Colors?=?{

???red:0xf25346,??

???white:0xd8d0d1,??

???brown:0x59332e,??

???pink:0xF5986E,

???brownDark:0x23190f,??

???blue:0x68c3c0

};

代碼結構

雖然 JavaScript 代碼十分冗長哆窿,但是它的結構很簡單。我們需要創(chuàng)建所有主要的函數(shù)并放入初始函數(shù)中:

window.addEventListener('load',?init,?false);

function?init()?{

???//?創(chuàng)建場景厉斟,相機和渲染器

???createScene();

???//?添加光源

???createLights();

???//?添加對象

???createPlane();

???createSea();

???createSky();

???//?調(diào)用循環(huán)函數(shù)挚躯,在每幀更新對象的位置和渲染場景

???loop();

}

創(chuàng)建場景

創(chuàng)建一個 Three.js 的項目,我們至少需要以下這些:

場景: 把這看作一個舞臺擦秽,將需要呈現(xiàn)的對象都添加進去秧均;

相機: 在這情況下,我們將使用透視相機号涯,但它也可能是正投影相機;

渲染器: 使用 WebGL 渲染器顯示所有的場景锯七;

渲染一個或多個對象: 在我們的例子中链快,我們會創(chuàng)建飛機,大海眉尸,天空(一些云)域蜗;

光源: 有不同類型可用的光源。在我們的項目中噪猾,我們主要用到營造氛圍的半球光和制造陰影的方向光霉祸。

createScene函數(shù)中創(chuàng)建場景,相機以及渲染器袱蜡。

有了這三樣東西丝蹭,才能使用相機將對象渲染到頁面中。

var?scene,?camera,?fieldOfView,?aspectRatio,?nearPlane,

????farPlane,?HEIGHT,?WIDTH,?renderer,?container;

function?createScene()?{

????//?獲得屏幕的寬和高坪蚁,

????//?用它們設置相機的縱橫比

????//?還有渲染器的大小

????HEIGHT?=?window.innerHeight;??

????WIDTH?=?window.innerWidth;

????//?創(chuàng)建場景

????scene?=?new?THREE.Scene();???????

????//?在場景中添加霧的效果奔穿;樣式上使用和背景一樣的顏色

????scene.fog?=?new?THREE.Fog(0xf7d9aa,?100,?950);

????//?創(chuàng)建相機

????aspectRatio?=?WIDTH?/?HEIGHT;

????fieldOfView?=?60;

????nearPlane?=?1;??

????farPlane?=?10000;

????/**

?????*?PerspectiveCamera?透視相機

?????*?@param?fieldOfView?視角

?????*?@param?aspectRatio?縱橫比

?????*?@param?nearPlane?近平面

?????*?@param?farPlane?遠平面

?????*/

????camera?=?new?THREE.PerspectiveCamera(???

??????fieldOfView,

??????aspectRatio,

??????nearPlane,

??????farPlane

??????);

????//?設置相機的位置

????camera.position.x?=?0;??

????camera.position.z?=?200;??

????camera.position.y?=?100;

????//?創(chuàng)建渲染器

????renderer?=?new?THREE.WebGLRenderer({

????//?在?css?中設置背景色透明顯示漸變色

??????alpha:?true,

????//?開啟抗鋸齒,但這樣會降低性能敏晤。

????//?不過贱田,由于我們的項目基于低多邊形的,那還好?:)

??????antialias:?true

????});

????//?定義渲染器的尺寸嘴脾;在這里它會填滿整個屏幕

????renderer.setSize(WIDTH,?HEIGHT);

????//?打開渲染器的陰影地圖

????renderer.shadowMap.enabled?=?true;

????//?在?HTML?創(chuàng)建的容器中添加渲染器的?DOM?元素

????container?=?document.getElementById('world');

????container.appendChild(renderer.domElement);

????//?監(jiān)聽屏幕男摧,縮放屏幕更新相機和渲染器的尺寸

????window.addEventListener('resize',?handleWindowResize,?false);

}

由于屏幕的尺寸改變,我們需要更新渲染器的尺寸和相機的縱橫比译打。

function?handleWindowResize()?{

???//?更新渲染器的高度和寬度以及相機的縱橫比

???HEIGHT?=?window.innerHeight;

???WIDTH?=?window.innerWidth;?????????

???renderer.setSize(WIDTH,?HEIGHT);

???camera.aspect?=?WIDTH?/?HEIGHT;????????

???camera.updateProjectionMatrix();

}

光源

當創(chuàng)建一個場景時耗拓,光源是最棘手的一部分。光源可以奠定整個場景的基調(diào)扶平,所以要適當?shù)剡x取帆离。在這部分我們要盡量制造足以讓對象可見的光源。

var?hemisphereLight,?shadowLight;

function?createLights()?{

??//?半球光就是漸變的光结澄;

??//?第一個參數(shù)是天空的顏色哥谷,第二個參數(shù)是地上的顏色岸夯,第三個參數(shù)是光源的強度

??hemisphereLight?=?new?THREE.HemisphereLight(0xaaaaaa,0x000000,?.9);

???//?方向光是從一個特定的方向的照射

???//?類似太陽,即所有光源是平行的

???//?第一個參數(shù)是關系顏色们妥,第二個參數(shù)是光源強度

??shadowLight?=?new?THREE.DirectionalLight(0xffffff,?.9);

??//?設置光源的方向猜扮。??

???//?位置不同,方向光作用于物體的面也不同监婶,看到的顏色也不同

???shadowLight.position.set(150,?350,?350);

???//?開啟光源投影

??shadowLight.castShadow?=?true;

??//?定義可見域的投射陰影

??shadowLight.shadow.camera.left?=?-400;

??shadowLight.shadow.camera.right?=?400;

??shadowLight.shadow.camera.top?=?400;

??shadowLight.shadow.camera.bottom?=?-400;

??shadowLight.shadow.camera.near?=?1;

??shadowLight.shadow.camera.far?=?1000;

??//?定義陰影的分辨率旅赢;雖然分辨率越高越好,但是需要付出更加昂貴的代價維持高性能的表現(xiàn)惑惶。

??shadowLight.shadow.mapSize.width?=?2048;

??shadowLight.shadow.mapSize.height?=?2048;

??//?為了使這些光源呈現(xiàn)效果煮盼,只需要將它們添加到場景中

??scene.add(hemisphereLight);??

??scene.add(shadowLight);

}

正如你所見,創(chuàng)建光源用到許多參數(shù)带污。不要再猶豫僵控,大膽嘗試用不同的顏色,強度的光源鱼冀。你發(fā)現(xiàn)不同的光源在場景中能夠營造有趣的氛圍和環(huán)境报破。而且你會找到感覺:如何按照你的需求優(yōu)化它們。

用 Three.js 創(chuàng)建對象

Three.js 中已經(jīng)有大量的現(xiàn)成幾何體如:立方體千绪,球體充易,圓環(huán)面,圓柱體以及飛機原型荸型。

對于我們的項目盹靴,所有的對象只需要通過這些幾何體組合而成。這非常適合低多邊形的風格瑞妇,而且我們可以不必在 3D 建模軟件中創(chuàng)建對象鹉究。

用一個圓柱體代表大海

我們開始創(chuàng)建大海模型,因為它是我們實現(xiàn)中最簡單的對象踪宠。為了簡單起見自赔,我們將大海看作一個簡單的圓柱體放置在屏幕的底部柳琢。之后我們再深入研究如何改善大海的外觀绍妨。

接著,讓我們使大杭砹常看起來更具吸引力他去,海浪更加逼真。

//首先定義一個大海對象

Sea?=?function(){

??//?創(chuàng)建一個圓柱幾何體

??//?參數(shù)為:頂面半徑倒堕,底面半徑灾测,高度,半徑分段垦巴,高度分段

??var?geom?=?new?THREE.CylinderGeometry(600,600,800,40,10);

??//?在?x?軸旋轉(zhuǎn)幾何體

??geom.applyMatrix(new?THREE.Matrix4().makeRotationX(-Math.PI/2));

??//?創(chuàng)建材質(zhì)

??var?mat?=?new?THREE.MeshPhongMaterial({

????color:Colors.blue,

????transparent:true,

????opacity:.6,

????shading:THREE.FlatShading

??});

??//?為了在?Three.js?創(chuàng)建一個物體媳搪,我們必須創(chuàng)建網(wǎng)格用來組合幾何體和一些材質(zhì)

??this.mesh?=?new?THREE.Mesh(geom,?mat);

??//?允許大海對象接收陰影

??this.mesh.receiveShadow?=?true;

}

//實例化大海對象铭段,并添加至場景

var?sea;

function?createSea(){

?sea?=?new?Sea();

?//?在場景底部,稍微推擠一下

?sea.mesh.position.y?=?-600;

?//?添加大海的網(wǎng)格至場景

?scene.add(sea.mesh);

}

總結一下創(chuàng)建對象秦爆,需要什么東西序愚。

我們需要:

創(chuàng)建幾何體

創(chuàng)建材質(zhì)

將它們傳入網(wǎng)格

將網(wǎng)格添加至場景

通過這些步驟,我們可以創(chuàng)建許多不同種類的幾何體〉认蓿現(xiàn)在爸吮,如果我們把它們組合起來,就可以創(chuàng)建更多復雜的形狀望门。

在以下步驟中形娇,我們將精確地學習如何創(chuàng)建復雜的形狀。

把簡單的正方體組合成復雜的形狀

云的制作會有一點點復雜筹误,因為他們是由若干個正方體組合而成的一個隨機形狀埂软。

Cloud?=?function(){

?//?創(chuàng)建一個空的容器放置不同形狀的云

?this.mesh?=?new?THREE.Object3D();

?//?創(chuàng)建一個正方體

?//?這個形狀會被復制創(chuàng)建云

?var?geom?=?new?THREE.BoxGeometry(20,20,20);

?//?創(chuàng)建材質(zhì);一個簡單的白色材質(zhì)就可以達到效果

?var?mat?=?new?THREE.MeshPhongMaterial({

???color:Colors.white,??

?});

?//?隨機多次復制幾何體

?var?nBlocs?=?3+Math.floor(Math.random()*3);

?for?(var?i=0;?i<nBlocs;?i++?){

???//?通過復制幾何體創(chuàng)建網(wǎng)格

???var?m?=?new?THREE.Mesh(geom,?mat);

???//?隨機設置每個正方體的位置和旋轉(zhuǎn)角度

???m.position.x?=?i*15;

???m.position.y?=?Math.random()*10;

???m.position.z?=?Math.random()*10;

???m.rotation.z?=?Math.random()*Math.PI*2;

???m.rotation.y?=?Math.random()*Math.PI*2;

???//?隨機設置正方體的大小

???var?s?=?.1?+?Math.random()*.9;

???m.scale.set(s,s,s);

???//?允許每個正方體生成投影和接收陰影

???m.castShadow?=?true;

???m.receiveShadow?=?true;

???//?將正方體添加至開始時我們創(chuàng)建的容器中

???this.mesh.add(m);

?}

}

現(xiàn)在纫事,我們已經(jīng)創(chuàng)建一朵云,我們通過復制它來創(chuàng)建天空所灸,而且將其放置在 z 軸任意位置丽惶。

//?定義一個天空對象Sky?=?function(){???//?創(chuàng)建一個空的容器???this.mesh?=?new?THREE.Object3D();???//?選取若干朵云散布在天空中???this.nClouds?=?20;???//?把云均勻地散布???//?我們需要根據(jù)統(tǒng)一的角度放置它們???var?stepAngle?=?Math.PI*2?/?this.nClouds;???//?創(chuàng)建云對象???for(var?i=0;?i三角函數(shù)

???var?a?=?stepAngle*i;?//這是云的最終角度

???var?h?=?750?+?Math.random()*200;?//?這是軸的中心和云本身之間的距離

???//?三角函數(shù)!E懒ⅰ钾唬!希望你還記得數(shù)學學過的東西?:)

???//?假如你不記得:

???//?我們簡單地把極坐標轉(zhuǎn)換成笛卡坐標

???c.mesh.position.y?=?Math.sin(a)*h;

???c.mesh.position.x?=?Math.cos(a)*h;

???//?根據(jù)云的位置旋轉(zhuǎn)它

???c.mesh.rotation.z?=?a?+?Math.PI/2;

???//?為了有更好的效果侠驯,我們把云放置在場景中的隨機深度位置

???c.mesh.position.z?=?-400-Math.random()*400;

???//?而且我們?yōu)槊慷湓圃O置一個隨機大小

???var?s?=?1+Math.random()*2;

???c.mesh.scale.set(s,s,s);

???//?不要忘記將每朵云的網(wǎng)格添加到場景中

???this.mesh.add(c.mesh);

???}??

}

//?現(xiàn)在我們實例化天空對象抡秆,而且將它放置在屏幕中間稍微偏下的位置。

var?sky;

function?createSky(){

???sky?=?new?Sky();

???sky.mesh.position.y?=?-600;

???scene.add(sky.mesh);

}

更加復雜的形狀:創(chuàng)建飛機模型

壞消息是:創(chuàng)建飛機模型的代碼有點復雜有點長吟策。但是好消息是:為了創(chuàng)建它我們已經(jīng)學習了所有應該知道的儒士。這里所有都是關于組合和封裝形狀的代碼。

var?AirPlane?=?function()?{

???this.mesh?=?new?THREE.Object3D();

???//?創(chuàng)建機艙

???var?geomCockpit?=?new?THREE.BoxGeometry(60,?50,?50,?1,?1,?1);

???var?matCockpit?=?new?THREE.MeshPhongMaterial({

???????color:?Colors.red,

???????shading:?THREE.FlatShading

???});

???var?cockpit?=?new?THREE.Mesh(geomCockpit,?matCockpit);

???cockpit.castShadow?=?true;

???cockpit.receiveShadow?=?true;

???this.mesh.add(cockpit);

???//?創(chuàng)建引擎

???var?geomEngine?=?new?THREE.BoxGeometry(20,?50,?50,?1,?1,?1);

???var?matEngine?=?new?THREE.MeshPhongMaterial({

?????????color:?Colors.white,

?????????shading:?THREE.FlatShading

???});

???var?engine?=?new?THREE.Mesh(geomEngine,?matEngine);

???engine.position.x?=?40;

???engine.castShadow?=?true;

???engine.receiveShadow?=?true;

???this.mesh.add(engine);

???//?創(chuàng)建機尾

???var?geomTailPlane?=?new?THREE.BoxGeometry(15,?20,?5,?1,?1,?1);

???var?matTailPlane?=?new?THREE.MeshPhongMaterial({

???????color:?Colors.red,

???????shading:?THREE.FlatShading

???});

???var?tailPlane?=?new?THREE.Mesh(geomTailPlane,?matTailPlane);

???tailPlane.position.set(-35,?25,?0);

???tailPlane.castShadow?=?true;

???tailPlane.receiveShadow?=?true;

???this.mesh.add(tailPlane);

????//?創(chuàng)建機翼

???var?geomSideWing?=?new?THREE.BoxGeometry(40,?8,?150,?1,?1,?1);

???var?matSideWing?=?new?THREE.MeshPhongMaterial({

???????color:?Colors.red,

???????shading:?THREE.FlatShading

???});

???var?sideWing?=?new?THREE.Mesh(geomSideWing,?matSideWing);

???sideWing.castShadow?=?true;

???sideWing.receiveShadow?=?true;

???this.mesh.add(sideWing);

???//?創(chuàng)建螺旋槳

???var?geomPropeller?=?new?THREE.BoxGeometry(20,?10,?10,?1,?1,?1);

???var?matPropeller?=?new?THREE.MeshPhongMaterial({

???????color:?Colors.brown,

???????shading:?THREE.FlatShading

???});

???this.propeller?=?new?THREE.Mesh(geomPropeller,?matPropeller);

???this.propeller.castShadow?=?true;

???this.propeller.receiveShadow?=?true;

???//?創(chuàng)建螺旋槳的槳葉

???var?geomBlade?=?new?THREE.BoxGeometry(1,?100,?20,?1,?1,?1);

???var?matBlade?=?new?THREE.MeshPhongMaterial({

???????color:?Colors.brownDark,

???????shading:?THREE.FlatShading

???});

???var?blade?=?new?THREE.Mesh(geomBlade,?matBlade);

???blade.position.set(8,?0,?0);

???blade.castShadow?=?true;

???blade.receiveShadow?=?true;

???this.propeller.add(blade);

???this.propeller.position.set(50,?0,?0);

???this.mesh.add(this.propeller);

};

這飛機看起來很簡單吧檩坚?不要擔心它現(xiàn)在的樣子着撩,接著我們將看到如何改進形狀,讓飛機更加好看!

現(xiàn)在匾委,我們可以實例化這飛機并添加到場景中:

var?airplane;

function?createPlane(){

???airplane?=?new?AirPlane();

???airplane.mesh.scale.set(.25,.25,.25);

???airplane.mesh.position.y?=?100;

???scene.add(airplane.mesh);

}

渲染

我們已經(jīng)創(chuàng)建了幾個對象并把它們添加到我們的場景中了拖叙,但是為啥運行游戲的時候什么都看不到呢?那是因為我們需要渲染場景赂乐,添加一下這句簡單的代碼:

renderer.render(scene,?camera);

動畫

通過使螺旋槳旋轉(zhuǎn)并轉(zhuǎn)動大海和云讓我們的場景更具生命力薯鳍。因此我們需要一個無限循環(huán)函數(shù)。

function?loop(){

??//?使螺旋槳旋轉(zhuǎn)并轉(zhuǎn)動大海和云

??airplane.propeller.rotation.x?+=?0.3;

??sea.mesh.rotation.z?+=?.005;

??sky.mesh.rotation.z?+=?.01;

??//?渲染場景

??renderer.render(scene,?camera);

??//?重新調(diào)用?render()?函數(shù)

??requestAnimationFrame(loop);

}

正如你看到的一樣挨措,我們將渲染器的 render() 函數(shù)移動到 loop() 函數(shù)中挖滤。因為每次修改物體的位置或顏色之類的屬性就需要重新調(diào)用一次 render() 函數(shù)崩溪。

隨著鼠標的移動,添加交互

在這刻壶辜,我們已經(jīng)看見飛機在場景在中間悯舟,接下來我們還需要實現(xiàn)什么呢?就是監(jiān)聽鼠標的移動實現(xiàn)交互砸民。

當文檔加載完成抵怎,我們就需要為文檔添加監(jiān)聽器,檢測鼠標是否有移動岭参。因此反惕,我們需要對初始化函數(shù)作出以下的修改。

function?init(event){

???createScene();

???createLights();

???createPlane();

???createSea();

???createSky();

???//添加監(jiān)聽器

???document.addEventListener('mousemove',?handleMouseMove,?false);

???loop();

}

另外演侯,我們創(chuàng)建一個mousemove事件的事件處理函數(shù)姿染。

var?mousePos={x:0,?y:0};

//?mousemove?事件處理函數(shù)

function?handleMouseMove(event)?{

???//?這里我把接收到的鼠標位置的值轉(zhuǎn)換成歸一化值,在-1與1之間變化

???//?這是x軸的公式:

???var?tx?=?-1?+?(event.clientX?/?WIDTH)*2;

???//?對于?y?軸秒际,我們需要一個逆公式

???//?因為?2D?的?y?軸與?3D?的?y?軸方向相反

???var?ty?=?1?-?(event.clientY?/?HEIGHT)*2;

???mousePos?=?{x:tx,?y:ty};

}

現(xiàn)在獲得鼠標的?x?,y坐標值悬赏,我們可以適當?shù)匾苿语w機。

我們需要修改循環(huán)函數(shù)并添加一個新功能去更新飛機的位置娄徊。

function?loop(){

???sea.mesh.rotation.z?+=?.005;

???sky.mesh.rotation.z?+=?.01;

???//?更新每幀的飛機

???updatePlane();

???renderer.render(scene,?camera);

???requestAnimationFrame(loop);

}

function?updatePlane(){

???//?讓我們在x軸上-100至100之間和y軸25至175之間移動飛機

???//?根據(jù)鼠標的位置在-1與1之間的范圍闽颇,我們使用的?normalize?函數(shù)實現(xiàn)(如下)

???var?targetX?=?normalize(mousePos.x,?-1,?1,?-100,?100);

???var?targetY?=?normalize(mousePos.y,?-1,?1,?25,?175);

???//?更新飛機的位置

???airplane.mesh.position.y?=?targetY;

???airplane.mesh.position.x?=?targetX;

???airplane.propeller.rotation.x?+=?0.3;

}

function?normalize(v,vmin,vmax,tmin,?tmax){

???var?nv?=?Math.max(Math.min(v,vmax),?vmin);

???var?dv?=?vmax-vmin;

???var?pc?=?(nv-vmin)/dv;

???var?dt?=?tmax-tmin;

???var?tv?=?tmin?+?(pc*dt);

???return?tv;

}

恭喜你!到這里寄锐,已經(jīng)實現(xiàn)了飛機隨著鼠標的移動而移動兵多。到目前為止,看看我們已經(jīng)實現(xiàn)了什么功能:

第一部分的 Demo

幾乎完成橄仆!

正如你所看見的剩膘,使用 Three.js 對創(chuàng)建 WebGL 內(nèi)容有非常大的幫助。建立一個場景和渲染一些自定義對象不需要懂太多 WebGL 的知識盆顾。到目前為止怠褐,我們已經(jīng)學會一些基礎概念和你已經(jīng)可以開始通過調(diào)整一些參數(shù)類似光源的強度,霧的顏色和物體的大小掌握了一些基本的訣竅您宪”共或許現(xiàn)在你已經(jīng)很熟悉創(chuàng)建一些新的對象了。

如果你想學習更加深入的技術蚕涤,請繼續(xù)閱讀筐赔。因為你將會學習到如何改進 3D 場景,使飛機飛行得更加平穩(wěn)揖铜,并模仿低多邊形海浪對大海的影響茴丰。

一架更酷的飛機

好了~我們之前創(chuàng)建了非常基礎的飛機。我們現(xiàn)在知道如何創(chuàng)建對象并組合它們贿肩,但是我們?nèi)匀恍枰獙W習如何修改幾何體令其更加符合我們的需求峦椰。

例如正方體,可以移動它的頂點汰规。在我們的案例中汤功,我們需要使它更加像駕駛艙。

讓我們看一下駕駛艙這部分的代碼溜哮,還有看下我們是如何讓他的背部變得更窄的:

//?駕駛艙

var?geomCockpit?=?new?THREE.BoxGeometry(80,50,50,1,1,1);

var?matCockpit?=?new?THREE.MeshPhongMaterial({color:Colors.red,?shading:THREE.FlatShading});

//?我們可以通過訪問形狀中頂點數(shù)組中一組特定的頂點

//?然后移動它的?x,?y,?z?屬性:

geomCockpit.vertices[4].y-=10;

geomCockpit.vertices[4].z+=20;

geomCockpit.vertices[5].y-=10;

geomCockpit.vertices[5].z-=20;

geomCockpit.vertices[6].y+=30;

geomCockpit.vertices[6].z+=20;

geomCockpit.vertices[7].y+=30;

geomCockpit.vertices[7].z-=20;

var?cockpit?=?new?THREE.Mesh(geomCockpit,?matCockpit);

cockpit.castShadow?=?true;

cockpit.receiveShadow?=?true;

this.mesh.add(cockpit);

這就是如何操縱一個形狀以適應我們的需求的一個例子滔金。

如果你看到飛機的完整代碼,你會看到幾個對象:更像窗口的對象和更美觀的螺旋槳茂嗓。沒有什么復雜的東西餐茵,試著調(diào)整相關的值找找感覺,制造屬于你自己的飛機述吸。

但是忿族,是誰在開飛機呢?

為我們的飛機添加一個飛行員蝌矛,就好像添加幾個盒子一樣容易道批。

但是我們只需要一個酷酷的飛行員,頭發(fā)要很飄逸的入撒!感覺它好像很難實現(xiàn)的樣子隆豹,但是由于我們開始的時候是在低多邊形的場景下開始的,所以這就變得簡單多了衅金!嘗試通過幾個盒子模擬創(chuàng)建飄逸的頭發(fā),同時會給予一種獨特的感覺簿煌。

讓我們看看源碼:

var?Pilot?=?function(){???this.mesh?=?new?THREE.Object3D();???this.mesh.name?=?"pilot";???//?angleHairs是用于后面頭發(fā)的動畫的屬性???this.angleHairs=0;???//?飛行員的身體???var?bodyGeom?=?new?THREE.BoxGeometry(15,15,15);???var?bodyMat?=?new?THREE.MeshPhongMaterial({color:Colors.brown,?shading:THREE.FlatShading});???var?body?=?new?THREE.Mesh(bodyGeom,?bodyMat);???body.position.set(2,-12,0);???this.mesh.add(body);???//?飛行員的臉部???var?faceGeom?=?new?THREE.BoxGeometry(10,10,10);???var?faceMat?=?new?THREE.MeshLambertMaterial({color:Colors.pink});???var?face?=?new?THREE.Mesh(faceGeom,?faceMat);???this.mesh.add(face);???//?飛行員的頭發(fā)???var?hairGeom?=?new?THREE.BoxGeometry(4,4,4);???var?hairMat?=?new?THREE.MeshLambertMaterial({color:Colors.brown});???var?hair?=?new?THREE.Mesh(hairGeom,?hairMat);???//?調(diào)整頭發(fā)的形狀至底部的邊界氮唯,這將使它更容易擴展。???hair.geometry.applyMatrix(new?THREE.Matrix4().makeTranslation(0,2,0));???//?創(chuàng)建一個頭發(fā)的容器???var?hairs?=?new?THREE.Object3D();???//?創(chuàng)建一個頭發(fā)頂部的容器(這會有動畫效果)???this.hairsTop?=?new?THREE.Object3D();???//?創(chuàng)建頭頂?shù)念^發(fā)并放置他們在一個3*4的網(wǎng)格中???for?(var?i=0;?i<12;?i++){???????var?h?=?hair.clone();???????var?col?=?i%3;???????var?row?=?Math.floor(i/3);???????var?startPosZ?=?-4;???????var?startPosX?=?-4;???????h.position.set(startPosX?+?row*4,?0,?startPosZ?+?col*4);???????this.hairsTop.add(h);???}???hairs.add(this.hairsTop);???//?創(chuàng)建臉龐的頭發(fā)???var?hairSideGeom?=?new?THREE.BoxGeometry(12,4,2);???hairSideGeom.applyMatrix(new?THREE.Matrix4().makeTranslation(-6,0,0));???var?hairSideR?=?new?THREE.Mesh(hairSideGeom,?hairMat);???var?hairSideL?=?hairSideR.clone();???hairSideR.position.set(8,-2,6);???hairSideL.position.set(8,-2,-6);???hairs.add(hairSideR);???hairs.add(hairSideL);???//?創(chuàng)建后腦勺的頭發(fā)???var?hairBackGeom?=?new?THREE.BoxGeometry(2,8,10);???var?hairBack?=?new?THREE.Mesh(hairBackGeom,?hairMat);???hairBack.position.set(-1,-4,0)???hairs.add(hairBack);???hairs.position.set(-5,5,0);???this.mesh.add(hairs);???var?glassGeom?=?new?THREE.BoxGeometry(5,5,5);???var?glassMat?=?new?THREE.MeshLambertMaterial({color:Colors.brown});???var?glassR?=?new?THREE.Mesh(glassGeom,glassMat);???glassR.position.set(6,0,3);???var?glassL?=?glassR.clone();???glassL.position.z?=?-glassR.position.z;???var?glassAGeom?=?new?THREE.BoxGeometry(11,1,11);???var?glassA?=?new?THREE.Mesh(glassAGeom,?glassMat);???this.mesh.add(glassR);???this.mesh.add(glassL);???this.mesh.add(glassA);???var?earGeom?=?new?THREE.BoxGeometry(2,3,2);???var?earL?=?new?THREE.Mesh(earGeom,faceMat);???earL.position.set(0,0,-6);???var?earR?=?earL.clone();???earR.position.set(0,0,6);???this.mesh.add(earL);???this.mesh.add(earR);?}//?移動頭發(fā)Pilot.prototype.updateHairs?=?function(){???//?獲得頭發(fā)???var?hairs?=?this.hairsTop.children;???//?根據(jù)?angleHairs?的角度更新頭發(fā)???var?l?=?hairs.length;???for?(var?i=0;?i

現(xiàn)在讓頭發(fā)動起來姨伟,只需要在循環(huán)函數(shù)里添加以下這句代碼惩琉。

airplane.pilot.updateHairs();

制作海浪

或許你已經(jīng)注意到這大海不像真的大海那樣,但更像被壓路機壓平的表面夺荒。

它需要一些海浪瞒渠。這需要結合我們之前用到的兩項技術來完成:

操縱幾何體的頂點就像我們處理飛機的駕駛艙那樣

每個頂點執(zhí)行循環(huán)移動就像我們移動飛行員的頭發(fā)一樣

為了制造海浪,我們將圍繞圓柱體的初始位置對每個頂點旋轉(zhuǎn)技扼。通過給它們一個隨機旋轉(zhuǎn)速度和一個隨機距離(旋轉(zhuǎn)半徑)伍玖。很抱歉,這里還是需要用到一些三角函數(shù)剿吻!

讓我們對大海作出一些修改:

Sea?=?function(){???var?geom?=?new?THREE.CylinderGeometry(600,600,800,40,10);???geom.applyMatrix(new?THREE.Matrix4().makeRotationX(-Math.PI/2));???//?重點:通過合并頂點窍箍,我們確保海浪的連續(xù)性???geom.mergeVertices();???//?獲得頂點???var?l?=?geom.vertices.length;???//?創(chuàng)建一個新的數(shù)組存儲與每個頂點關聯(lián)的值:???this.waves?=?[];???for?(var?i=0;?i

就好像我們對飛行員的頭發(fā)做的那樣,我們在循環(huán)函數(shù)中添加以下這句代碼:

sea.moveWaves();

現(xiàn)在好好欣賞海浪吧!

改善場景中的光源

在教程中的第一部分椰棘,我們已經(jīng)創(chuàng)建了一些光源纺棺。但是想為場景添加更好的氣氛,并使陰影更加柔和邪狞。為了實現(xiàn)它祷蝌,我們打算使用環(huán)境光源。

createLight函數(shù)中帆卓,我們添加以下幾行代碼:

//?環(huán)境光源修改場景中的全局顏色和使陰影更加柔和

ambientLight?=?new?THREE.AmbientLight(0xdc8874,?.5);scene.add(ambientLight);

別再猶豫了巨朦!調(diào)節(jié)環(huán)境光源的顏色和強度,它會為你的場景增添獨特的潤色鳞疲。

一次平穩(wěn)的飛行

我們的小小飛機已經(jīng)隨著我們的鼠標移動罪郊。但它總感覺不像真正的飛行。當飛機改變它的飛行高度尚洽,如何改變它的位置和方向時更加流暢就完美了悔橄。在教程的最后一點,我們將實現(xiàn)它腺毫。

一個簡單的方法就是讓它移動到目標位置癣疟,通過添加一點點距離讓它在每一幀與目標位置分離。

基本上潮酒,相關的代碼會這樣(這是一個通用的公式睛挚,不要馬上添加到你的代碼中):

currentPosition?+=?(finalPosition?-?currentPosition)*fraction;

更現(xiàn)實點來說,飛機旋轉(zhuǎn)也可以根據(jù)運動的方向急黎。如果飛機很快的向上移動扎狱,它應該很快地沿著逆時針方向旋轉(zhuǎn);如果飛機慢慢向下移動勃教,它應該慢慢地沿著順時針方向旋轉(zhuǎn)淤击;為了準確地實現(xiàn)它,我們應該把旋轉(zhuǎn)比例值簡單地分配給在目標和飛機位置之間的剩余距離故源。

在我們的代碼里污抬,updatePlane 函數(shù)需要像以下這樣:

function?updatePlane(){

???var?targetY?=?normalize(mousePos.y,-.75,.75,25,?175);

???var?targetX?=?normalize(mousePos.x,-.75,.75,-100,?100);

???//?在每幀通過添加剩余距離的一小部分的值移動飛機

???airplane.mesh.position.y?+=?(targetY-airplane.mesh.position.y)*0.1;

???//?剩余的距離按比例轉(zhuǎn)動飛機

???airplane.mesh.rotation.z?=?(targetY-airplane.mesh.position.y)*0.0128;

???airplane.mesh.rotation.x?=?(airplane.mesh.position.y-targetY)*0.0064;

???airplane.propeller.rotation.x?+=?0.3;

}

現(xiàn)在飛機的移動看起來更加自然和真實。通過修改一下小數(shù)值绳军,你可以使用飛機隨著鼠標的移動響應速度更加快或更加慢印机。

看下我們場景中的最后一個階段:第二部分 Demo

很好!C偶荨射赛!

接著要干嘛呢?

如果你看到這奶是,你已經(jīng)學會 Three.js 中的通用的一些技術了咒劲,能夠讓你創(chuàng)建您的第一個場景∏牦。現(xiàn)在你知道如何通過原始幾何體創(chuàng)建物體,如何激活它們腐魂,以及如何設置一個場景中的光源帐偎,你已經(jīng)知道如何改進你的對象的外觀和運動,還有如何調(diào)整環(huán)境氛圍蛔屹。

下一步已經(jīng)超出本文范圍了削樊,由于它涉及到更多復雜的技術,它是實現(xiàn)一個游戲兔毒,大概思路是碰撞漫贞,收集點數(shù),液位控制育叁。下載源碼迅脐,看看實現(xiàn)的思路;你會看到到目前為止你學到過的概念和一些高階的知識點豪嗽,你可以研究一下和玩一下谴蔑。請注意這游戲已經(jīng)優(yōu)化了以便桌面使用。

但愿龟梦,這篇教程幫助你熟悉Three.js和激發(fā)你實現(xiàn)屬于你自己的項目隐锭。讓我看到你的創(chuàng)造力;我希望看到你做出什么來~

DEMO源碼**

原文鏈接:https://www.mvrlink.com/thressjs/

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末计贰,一起剝皮案震驚了整個濱河市钦睡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌躁倒,老刑警劉巖荞怒,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異秧秉,居然都是意外死亡褐桌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門福贞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撩嚼,“玉大人停士,你說我怎么就攤上這事挖帘。” “怎么了恋技?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵拇舀,是天一觀的道長。 經(jīng)常有香客問我蜻底,道長骄崩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮要拂,結果婚禮上抠璃,老公的妹妹穿的比我還像新娘。我一直安慰自己脱惰,他們只是感情好搏嗡,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拉一,像睡著了一般采盒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蔚润,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天磅氨,我揣著相機與錄音,去河邊找鬼嫡纠。 笑死烦租,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的货徙。 我是一名探鬼主播左权,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼痴颊!你這毒婦竟也來了赏迟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蠢棱,失蹤者是張志新(化名)和其女友劉穎锌杀,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泻仙,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡糕再,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了玉转。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片突想。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖究抓,靈堂內(nèi)的尸體忽然破棺而出猾担,到底是詐尸還是另有隱情,我是刑警寧澤刺下,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布绑嘹,位于F島的核電站,受9級特大地震影響橘茉,放射性物質(zhì)發(fā)生泄漏工腋。R本人自食惡果不足惜姨丈,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望擅腰。 院中可真熱鬧蟋恬,春花似錦、人聲如沸趁冈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽箱歧。三九已至矾飞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呀邢,已是汗流浹背洒沦。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留价淌,地道東北人申眼。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像蝉衣,于是被迫代替她去往敵國和親括尸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容