Three.js 是一款運行在瀏覽器中的 3D 引擎男窟,你可以用它在 web 中創(chuàng)建各種三維場景,包括了攝影機阻塑、光影、材質(zhì)等各種對象果复。使用它可以讓我們更加直觀的了解 webgl 的世界陈莽。
3D 場景前置知識
1.場景(Scene):是物體、光源等元素的容器虽抄,可以配合 chrome 插件使用走搁,拋出 window.scene即可實時調(diào)整 obj 的信息和材質(zhì)信息。
2.相機(Camera):場景中的相機迈窟,代替人眼去觀察私植,場景中只能添加一個,一般常用的是透視相機(PerspectiveCamera)
3.物體對象(Mesh):包括二維物體(點菠隆、線兵琳、面)、三維物體骇径,模型等等
4.光源(Light):場景中的光照,如果不添加光照場景將會是一片漆黑者春,包括全局光破衔、平行光、點光源等
5.渲染器(Renderer):場景的渲染方式钱烟,如webGL\canvas2D\Css3D晰筛。
6.控制器(Control): 可通過鍵盤、鼠標控制相機的移動
下面我們依次詳細學習以上的細分知識點拴袭。
相機
Three.js中我們常用的有兩種類型的相機:正交(orthographic)相機读第、透視(perspective)相機。一般情況下為了模擬人眼我們都是使用透視相機拥刻; 正交鏡頭的特點是怜瞒,物品的渲染尺寸與它距離鏡頭的遠近無關。也就是說在場景中移動一個物體,其大小不會變化吴汪。正交鏡頭適合2D游戲惠窄。 透視鏡頭則是模擬人眼的視覺特點,距離遠的物體顯得更小漾橙。透視鏡頭通常更適合3D渲染杆融。
THREE.PerspectiveCamera(fov,aspect,near,far)
參數(shù) | 描述 |
---|---|
fov | 視野角度,從鏡頭可以看到的場景的部分霜运。通常3D游戲的FOV取值在60-90度之間較好的默認值為60 |
aspect | 渲染區(qū)域的縱橫比脾歇。較好的默認值為window.innerWidth/window.innerHeight |
near | 最近離鏡頭的距離 |
far | 遠離鏡頭的距離 |
透視相機示意圖:
創(chuàng)建攝像機以后還要對其進行移動、然后對準物體積聚的場景中心位置淘捡,分別是設置其 position和調(diào)用 lookAt 方法藕各,參數(shù)均是一個 xyz向量(new THREE.Vector3(x,y,z))
camera.position:控制相機在整個3D環(huán)境中的位置(取值為3維坐標對象-THREE.Vector3(x,y,z))
camera.lookAt:控制相機的焦點位置,決定相機的朝向(取值為3維坐標對象-THREE.Vector3(x,y,z))
燈光
在Three.js中光源是必須的案淋,如果一個場景你不設置燈光那么世界將會是一片漆黑座韵。Three.js內(nèi)置了多種光源以滿足特定場景的需要。大家可以根據(jù)自己的項目需要來選擇何種燈光
光源分類
關于光源的詳細 API 大家可以參考 threejs 官網(wǎng)踢京,很詳細誉碴,demo 也很完整 傳送門
Mesh
在計算機的世界里,一條弧線是由有限個點構成的有限條線段連接得到的瓣距。當線段數(shù)量越多黔帕,長度就越短,當達到你無法察覺這是線段時蹈丸,一條平滑的弧線就出現(xiàn)了成黄。 計算機的三維模型也是類似的。只不過線段變成了平面逻杖,普遍用三角形組成的網(wǎng)格來描述奋岁。我們把這種模型稱之為 Mesh 模型。 在 threeJs 的世界中荸百,材質(zhì)(Material)+幾何體(Geometry)就是一個 mesh闻伶。設置其name屬性可以通過scene.getObjectByName(name)獲取該物體對象;Geometry就好像是骨架,材質(zhì)則類似于皮膚够话,對于材質(zhì)和幾何體的分類見下表格
材質(zhì)分類
材質(zhì) | 說明 |
---|---|
MeshBasicMaterial | 基本的材質(zhì)蓝翰,顯示為簡單的顏色或者顯示為線框。不考慮光線的影響 |
MeshDepthMaterial | 使用簡單的顏色女嘲,但是顏色深度和距離相機的遠近有關 |
MeshNormalMaterial | 基于面Geometry的法線(normals)數(shù)組來給面著色 |
MeshFacematerial | 容器畜份,允許為Geometry的每一個面指定一個材質(zhì) |
MeshLambertMaterial | 考慮光線的影響,啞光材質(zhì) |
MeshPhongMaterial | 考慮光線的影響欣尼,光澤材質(zhì) |
ShaderMaterial | 允許使用自己的著色器來控制頂點如何被放置爆雹、像素如何被著色 |
LineBasicMaterial | 用于THREE.Line對象,創(chuàng)建彩色線條 |
LineDashMaterial | 用于THREE.Line對象,創(chuàng)建虛線條 |
RawShaderMaterial | 僅和THREE.BufferedGeometry聯(lián)用顶别,優(yōu)化靜態(tài)Geometry(頂點谷徙、面不變)的渲染 |
SpriteCanvasMaterial | 在針對單獨的點進行渲染時用到 |
SpriteMaterial | 在針對單獨的點進行渲染時用到 |
PointCloudMaterial | 在針對單獨的點進行渲染時用到 |
幾何圖形
2D
3D
加載外部模型
一般來講我們的場景中不可能都是一些奇奇怪怪的形狀,或多或少項目中都會用到一些外部的模型資源驯绎,不如動物啊完慧,裝飾物啊什么的,再加上一些動畫剩失,這樣整個場景更加顯得生動屈尼,那么 threejs 中我們可以通過哪些方式來加載外部的模型資源呢?
加載外部模型拴孤,是通過Three.js加載器(Loader)實現(xiàn)的脾歧。加載器把文本/二進制的模型文件轉化為Three.js對象結構。 每個加載器理解某種特定的文件格式演熟。
需要注意的是鞭执,由于貼圖的尺寸必須是(2的冪數(shù))X (2的冪數(shù)),如:1024X512芒粹,所以為了防止貼圖變形兄纺,平面的寬度比例需要與貼圖的比例一致。
支持的格式
格式 | 說明 |
---|---|
JSON | Three.js自定義的化漆、基于JSON的格式估脆。可以聲明式的定義一個Geometry或者Scene.利用該格式座云,你可以方便的重用復雜的Geometry或Scene |
OBJ / MTL | OBJ是Wavefront開發(fā)的一種簡單3D格式疙赠,此格式被廣泛的支持,用于定義Geometry朦拖,MTL用于配合OBJ圃阳,它指定OBJ使用的材質(zhì) |
Collada(dae) | 基于XML的格式,被大量3D應用程序璧帝、渲染引擎支持 |
STL | STereoLithography的簡寫限佩,在快速原型領域被廣泛使用。3D打印模型通常使用該格式定義Three.js提供了STLExporter.js裸弦,使用它可以把Three.js模型導出為STL格式 |
CTM | openCTM定義的格式,以緊湊的格式存儲基于三角形的Mesh |
VTK | Visualization Toolkit定義的格式作喘,用于聲明頂點和面理疙。此格式有二進制/ASCII兩種變體,Three.js僅支持ASCII變體 |
AWD | 3D場景的二進制格式泞坦,主要被away3d引擎使用窖贤,Three.js不支持AWD壓縮格式 |
Assimp | 開放資產(chǎn)導入庫(Open asset import library)是導入多種3D模型的標準方式。使用該Loader你可以導入多種多樣的3D模型格式 |
VRML | 虛擬現(xiàn)實建模語言(Virtual Reality Modeling Language)是一種基于文本的格式,現(xiàn)已經(jīng)被X3D格式取代盡管Three.js不直接支持X3D赃梧,但是后者很容易被轉換為其它格式 |
Babylon | 游戲引擎Babylon的私有格式 |
PLY | 常用于存儲來自3D掃描儀的信息 |
在項目一開始嘗試是使用 dae 文件滤蝠,后面發(fā)現(xiàn) json 文件更加方便一點,所以最終使用的是 jsonloader 導入 json 文件授嘀。json文件可以通過 blender 或者3DsMax 導出物咳,他們都有各自的 export json的插件。在軟件中處理好模型貼圖和動畫以后蹄皱,導出 json 文件和相應的貼圖文件給到前端即可览闰。
var jsonLoader = new THREE.JSONLoader();
jsonLoader.load('model.json', function (geometry, materials) {
materials.forEach(function (mat) {
//這里面可以設置材質(zhì)的各種信息
mat.skinning = true;
mat.color = new THREE.Color("rgb(233,203,113)"); //模型顏色
mat.emissive = new THREE.Color("rgb(110,110,110)");//自發(fā)光顏色
});
var model = new THREE.SkinnedMesh(geometry, new THREE.MeshFaceMaterial(materials));
model.name = “model name”;
scene.add(model);
//下面是播放模型中的動畫內(nèi)容
var sceneAnimationClip = model.geometry.animations[0]
var mixer = new THREE.AnimationMixer(model);
mixers.push(mixer);
var sceneAnimation = mixer.clipAction(sceneAnimationClip);
sceneAnimation.play();
});
同理其他類型的文件也可以使用相應的 loader 導入文件,控制其材質(zhì)信息和動畫播放巷折,具體的可以查看官網(wǎng)的 demo压鉴。
粒子
THREE.Sprite
在WebGlRenderer渲染器中使用THREE.Sprite創(chuàng)建的粒子可以直接添加到scene中。創(chuàng)建出來的精靈總是面向鏡頭的锻拘。即不會有傾斜變形之類透視變化油吭,只有近大遠小的變化。
比如一個紋理為花瓣的粒子示例:
//花瓣的貼圖
var textureList = [
__uri("../../img/flower-1.png"),
__uri("../../img/flower-2.png"),
__uri("../../img/flower-3.png"),
__uri("../../img/flower-4.png"),
__uri("../../img/flower-5.png"),
__uri("../../img/flower-6.png"),
__uri("../../img/flower-7.png"),
__uri("../../img/flower-8.png"),
__uri("../../img/flower-9.png"),
__uri("../../img/flower-10.png")]
var particles = []; //存儲生成的粒子
//粒子從Z軸產(chǎn)生區(qū)間在-20到20
for (var zpos = -20; zpos < 20; zpos += 0.5) {
var texturerain = textLoader.load(textureList[Math.floor(Math.random() * 10)])
var material = new THREE.SpriteMaterial({
transparent: true,
opacity: util.getRandomInt(0.7, 1),
map: texturerain
}
);
//生成粒子
particle = new THREE.Sprite(material);
particle.name = "particle"
//隨即產(chǎn)生x軸,y軸
particle.position.x = Math.random() * 100
particle.position.y = Math.random() * 100;
//設置z軸
particle.position.z = zpos;
//將產(chǎn)生的粒子添加到場景
scene.add(particle);
//將粒子位置的值保存到數(shù)組
particles.push(particle);
}
//移動粒子的函數(shù)
function updateParticles() {
//遍歷每個粒子
for (var i = 0; i < particles.length; i++) {
particle = particles[i];
//設置粒子向前移動的速度依賴于鼠標在平面Y軸上的距離
particle.position.y -= i / particles.length / 50;
particle.position.x -= i / particles.length / 80;
if (particle.position.y < -7) { //溢出視野以后設置回原位置
particle.position.x = Math.random() * 100 - 50;
particle.position.y = 7;
}
}
場景交互
Three.js中并沒有直接提供“點擊”功能署拟,一開始使用的時候我也覺得一臉懵逼婉宰,后來才發(fā)現(xiàn)我們可以基于THREE.Raycaster來判斷鼠標當前對應到哪個物體,用來進行碰撞檢測.
//核心代碼
var clickObjects = []; //存儲哪些 obj 需要交互
var _raycaster = new THREE.Raycaster();//射線拾取器
var raycAsix = new THREE.Vector2();//屏幕點擊點二維坐標
var container = null;
function onMouseMove(event) {
event.preventDefault();
container = document.getElementById("Canvas1");
raycAsix.x = ( (event.pageX - $(container).offset().left) / container.offsetWidth ) * 2 - 1;
raycAsix.y = -( (event.pageY - $(container).offset().top) / container.offsetHeight ) * 2 + 1;
_raycaster.setFromCamera(raycAsix, Camera);
var intersects = _raycaster.intersectObjects(clickObjects);//獲取射線上與存儲的可被點擊物體的集合的交集,集合的第一個物體為距離相機最近的物體芯丧,最后一個則為離相機最遠的芍阎。
if (intersects.length > 0) {
document.body.style.cursor = 'pointer';
console.log(intersects[0].object.name) //打印導入模型時設置的model name
} else {
document.body.style.cursor = 'default';
}
}
其他的交互比如點擊事件都是基于此。
動畫
場景中如果我們添加了各種 mesh 和模型并給他加入了一些 tweend動畫會發(fā)現(xiàn)他并不會運動缨恒,因為你的場景并沒有實時渲染谴咸,所以要讓場景真的動起來,我們需要用到requestAnimationFrame骗露;關于它的詳細使用請大家自行 google,核心代碼如下
var requestAnimationFrame = window.requestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.msRequestAnimationFrame;
function animate() {
var delta = clock.getDelta();
if (mixers.length > 0) {
for (var i = 0; i < mixers.length; i++) {
mixers[i].update(delta);
}
}
//Renderer即我們實例化的 webglRender 對象岭佳;
updateParticles()
Renderer.clear();
Renderer.render(scene, Camera);
requestAnimationFrame(animate);
//如果有使用 Tween做一些補間動畫,也需要在此調(diào)用 TWEEN.update();
TWEEN.update();
}
另外我們?nèi)绻胱约?K 動畫也是可以的萧锉,不過我覺得應該沒有人這么無聊
jsonLoader.load('../resource/hudie.json', function ( geometry, materials ) {
materials.forEach(function (mat){
mat.skinning = true;
mat.color = new THREE.Color("rgb(0,255, 0)");
mat.emissive =new THREE.Color("rgb(255, 0, 255)");
});
var tracks = [];
//NumberKeyframeTrack(name珊随,times,values)柿隙,依次去K模型的位置叶洞,縮放和旋轉
tracks.push( new THREE.NumberKeyframeTrack( '.position', [ 0, 1, 2 ], [ -15,-5,-7, 0, 0, 0, 5,0.3,-9 ] ) );
tracks.push( new THREE.NumberKeyframeTrack( '.scale', [ 0, 1, 2 ], [ 0.04, 0.04, 0.04, 0.04, 0.04, 0.04,
0.04, 0.04, 0.04] ) );
tracks.push( new THREE.NumberKeyframeTrack( '.rotation', [ 0, 1, 2 ], [ 0, 0.2, 0, 0, 0, 0, 0, 0.2, 0.2 ] ) );
var model = new THREE.SkinnedMesh(geometry, new THREE.MeshFaceMaterial(materials));
model.name="蝴蝶";
var clip = new THREE.AnimationClip( 'Action', -1, tracks );
var mixer = new THREE.AnimationMixer( model );
mixers.push(mixer);
mixer.clipAction( clip ).play();
scene.add( model );
} );
總結
因為時間關系以及項目原因,學完以上的知識點差不多可以完成項目的需要了禀崖,但是關于 threejs 中其他的知識點衩辟,比如骨骼動畫啊,第一人稱控制等等由于時間關系還沒有深入的學習波附,后續(xù)打算進一步的學習這一部分內(nèi)容并且整理成文檔艺晴。另外由于 webgl 的自身原因昼钻,如果只是一張全景圖還好,但如果是加載了大量的模型以及粒子動畫等等封寞,那么對電腦的性能也有一定的要求然评,比如在 mac 下每次一打開相應的網(wǎng)站就能明顯的聽到風扇呼呼的轉。這些瓶頸的問題是 webgl 開發(fā)無法避免的問題狈究,畢竟網(wǎng)頁的承載和游戲的引擎不是一個級別碗淌。最后希望更多的人喜歡 webgl,喜歡 threejs 這個強大的庫谦炒,感謝Threejs作者帶給我們更好的3D開發(fā)體驗贯莺。