ThreeJs 基礎入門

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 遠離鏡頭的距離

透視相機示意圖:

image

創(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ù)自己的項目需要來選擇何種燈光

光源分類

image.png

關于光源的詳細 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

image.png

3D

image.png

加載外部模型

一般來講我們的場景中不可能都是一些奇奇怪怪的形狀,或多或少項目中都會用到一些外部的模型資源驯绎,不如動物啊完慧,裝飾物啊什么的,再加上一些動畫剩失,這樣整個場景更加顯得生動屈尼,那么 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ā)體驗贯莺。

參考:
https://sq.163yun.com/blog/article/203590884053413888

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市宁改,隨后出現(xiàn)的幾起案子缕探,更是在濱河造成了極大的恐慌,老刑警劉巖还蹲,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爹耗,死亡現(xiàn)場離奇詭異,居然都是意外死亡谜喊,警方通過查閱死者的電腦和手機潭兽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斗遏,“玉大人山卦,你說我怎么就攤上這事∷写危” “怎么了账蓉?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長逾一。 經(jīng)常有香客問我铸本,道長,這世上最難降的妖魔是什么遵堵? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任箱玷,我火速辦了婚禮,結果婚禮上陌宿,老公的妹妹穿的比我還像新娘锡足。我一直安慰自己,他們只是感情好壳坪,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布舱污。 她就那樣靜靜地躺著,像睡著了一般弥虐。 火紅的嫁衣襯著肌膚如雪扩灯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天霜瘪,我揣著相機與錄音珠插,去河邊找鬼。 笑死颖对,一個胖子當著我的面吹牛捻撑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缤底,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼顾患,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了个唧?” 一聲冷哼從身側響起江解,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎徙歼,沒想到半個月后犁河,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡魄梯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年桨螺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酿秸。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡灭翔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辣苏,到底是詐尸還是另有隱情肝箱,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布考润,位于F島的核電站狭园,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏糊治。R本人自食惡果不足惜唱矛,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望井辜。 院中可真熱鬧绎谦,春花似錦、人聲如沸粥脚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刷允。三九已至冤留,卻和暖如春碧囊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背纤怒。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工糯而, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人泊窘。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓熄驼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親烘豹。 傳聞我的和親對象是個殘疾皇子瓜贾,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

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