推薦:將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)了什么功能:
幾乎完成橄仆!
正如你所看見的剩膘,使用 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)造力;我希望看到你做出什么來~