使用WebGL + Three.js製做動畫場景

使用WebGL + Three.js製做動畫場景

3D圖像,技術,打造產品,還有互聯網:這些只是我愛好的一小部分。php

如今,感謝WebGL的出現-一個新的JavaScriptAPI,它能夠在不依賴任何插件的狀況下渲染瀏覽器中的3D圖像-這讓3D渲染操做變得異常簡單。ios

隨着虛擬現實和加強現實應用的發展,大型廠商們開始轉向數字化觸覺體驗,這是使人動心的一項技術。git

或者,至少那些已經投資的人這一年還抱有但願-11億美金流入VR和AR領域.github

Abbey Road Studios的谷歌交互之旅拍攝Deadliest Catch用到的艦隊,經過在非真實世界給觀衆沉浸式的體驗,全部的產品,服務和環境得以實現更好的配合。web

因爲人們能接觸到更多體驗性的技術,2D開始變得有些單調。這是事實。chrome

讓咱們現實點。目前看來,不少致力於創造體驗的應用仍處在技術探索階段,對大多數商業領域而言前景不算明朗。canvas

或者說他們真的創造了使人激動的體驗嗎?後端

走進WebGL:一項實用與靈活的技術,能夠創造更強沉浸式3D內容。不管是Porsche展現一輛新911的細節,仍是NASA重點介紹的火星是什麼樣子,或者是J Dlla備受喜好的甜甜圈專輯慶典,WebGL能應用於不少領域來表現各類類型的故事。瀏覽器

爲讓你熟悉這項強大的技術,我打算作一個關於它是如何工做的簡要歸納,還有使用Three.js(一個基於WebGL API的JavaScript庫)一步步創造簡單3D環境的快速教程。app

首先,什麼是WebGL?

WebGL是一項在瀏覽器中展現基於硬件加速的3D圖像的web技術,不須要安裝額外插件或者下載多餘的軟件。

所以,不少受衆能夠更方便地接觸到WebGL。瀏覽器支持程度也很不錯(目前應用普遍),Chrome,Firefox,IE,Opera和Safari等主流移動端和桌面瀏覽器都提供了很好的支持。

許多計算機和智能手機有先進的圖像渲染單元(GPUS),可直到最近,大多數網頁和移動網站都不能使用GPUS。這致使設備的加載速度緩慢,圖像質量低,而且對3D內容的支持程度也很低。

爲了解決這個問題WebGL花了很多時間。基於著名的OpenGL 3D 圖像標準,WebGL賦予Javascript插件式的自由接入方式,經過HTML5 元素鏈接一個設備的圖像硬件,並在瀏覽器中直接應用3D技術。結果是360度的3D內容變得更容易建立—排除了使用獨立應用或插件的干擾——同時用戶能更容易地在網上擁有高清體驗。

什麼是Three.js?

OpenGL和WebGL的複雜度相差不大。

Three.js是一個開源語法庫,簡化了WebGL工具和環境的建立工做。它支持大部分基於GPU加速的低代碼量3D動畫。

聊得差很少了,讓咱們編寫代碼吧

示例用Three.js庫展現了更復雜的效果。爲了練習須要,我會盡可能寫的簡單,用低複雜度的環境來展現僅靠理解的基礎知識能實現什麼效果。

我打算構建一個咱們已使用過的例子
Christmas-Closure_Header

讓咱們開始用瞭解的基礎知識作點東西吧。

一個渲染器,一個場景,還有一個相機

代碼連接第一步

貢獻者 Matt Agar(@agar)

代碼發佈於CodePen.

點擊並拖動這個例子,作點嘗試

CodePen上的例子至關於入門,如今咱們開始使用Three.js。

Firstly we need a Scene — a group or stage containing all the objects we want to render. Scenes allow you to set up what and where is going to be rendered by Three.js. This is where you place objects, lights, and cameras.
首先咱們須要一個場景 — 一個包含了咱們要渲染的全部對象的羣組。場景容許你設置Three.js要渲染的對象和渲染位置,以及如何進行渲染。這個場景指的就是你放置對象,光線和相機的地方。

`var scene = new THREE.Scene();`

-用一個好方法建立場景

接下來咱們在這個例子中添加一個相機。我添加的是透視相機,但也有其餘可用的選項。頭兩個參數分別指明瞭相機的視野區域和寬高比。後兩個參數表明相機渲染對象的截止距離。

var camera = new THREE.PerspectiveCamera(
    75,                                   // Field of view
    window.innerWidth/window.innerHeight, // Aspect ratio
    0.1,                                  // Near clipping pane
    1000                                  // Far clipping pane
);

// Reposition the camera
camera.position.set(5,5,0);

// Point the camera at a given coordinate
camera.lookAt(new THREE.Vector3(0,0,0));

-添加相機,視場,寬高比和截止距離

最後相當重要的部分是渲染器自己,它掌握着一個來自給定相機視角場景的渲染。Three.js提供了不少種渲染器以供選擇,但我決定在這個練習中使用標準的WebGL渲染器。

var renderer = new THREE.WebGLRenderer({ antialias: true });

// Size should be the same as the window
renderer.setSize( window.innerWidth, window.innerHeight );

// Set a near white clear color (default is black)
renderer.setClearColor( 0xeeeeee );

// Append to the document
document.body.appendChild( renderer.domElement );

// Render the scene/camera combination
renderer.render(scene, camera);

-添加渲染器

這個例子也包括了一些基礎的幾何結構— 在這裏是一個扁平的平面 — 咱們能夠看到一些特徵以深度形式被渲染出來。若是沒有它,咱們只能看到空空的屏幕。我接下來會簡短介紹關於Geometry(幾何結構),Materials(材質)和Meshes(網格)。

// A mesh is created from the geometry and material, then added to the scene
var plane = new THREE.Mesh(
  new THREE.PlaneGeometry( 5, 5, 5, 5 ),
  new THREE.MeshBasicMaterial( { color: 0x222222, wireframe: true } )
);
plane.rotateX(Math.PI/2);
scene.add( plane );

-添加一個扁平的平面

一個關於控制相機的小貼士

你可能已經意識到我在這個例子裏使用了外部模塊。這個是Three.js 的Github repo裏能找到的衆多可用模塊的一個。

在這個例子裏是軌道控制
它容許咱們捕獲canvas元素上的鼠標事件以從新定位圍繞着場景的相機。

var controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.addEventListener( 'change', function() { renderer.render(scene, camera); } );

-實現軌道控制

在CodePen例子中從動做,點擊和拖放或者滾動鼠標輪等方面檢驗軌道控制。在這個示例中,因爲咱們沒有設置動做循環(一旦我開始裝飾個人聖誕樹,我就會介紹動做循環),當控制發生更新時咱們一樣須要從新渲染場景。

準備渲染

好吧,以前的例子如今可能看着有點蠢,但你在沒有硬件基礎的狀況下沒法構建一個更好的屋子或聖誕樹。

是時候給咱們的場景添些東西了,如今有三件事須要咱們去探索:Geometries,Materials,還有Meshes。

代碼連接第二步

貢獻者Matt Agar(@agar)

來自Codepen

-鈴兒叮噹響。是,它們必定會響的

不管是點擊仍是拖拽,快嘗試一下吧。

使用平面陰影來添加一些簡單的多邊形

首先咱們須要一些Geometry。它能夠是包含點和線的任何立方形狀。

Three.js簡化了一系列可實現的構建基礎多邊形操做。這裏有不少種適合3D格式的文件加載器。你也能夠選擇經過指定頂點和表面建立你本身的幾何結構。

如今,咱們將以一個基礎的八面體做爲開始。

`var geometry = new THREE.OctahedronGeometry(10,1);`

-添加Geometry

Materials描繪了對象的外觀。它們的定義不受渲染器影響(大部分狀況下),因此當你決定使用不一樣的渲染器時沒必要重寫它。

這裏是可實現的各類Materials,全部的Materials都使用一個包含各類屬性的對象,屬性會被應用於這些Materials。

下面的例子實現了一個扁平帶陰影的Material,這展現了咱們的多邊形對象,而不是打算對它們進行平滑處理。

var material = new THREE.MeshStandardMaterial( {
    color: 0xff0051,
    shading: THREE.FlatShading, // default is THREE.SmoothShading
    metalness: 0,
    roughness: 1
} );

-用Materials肯定對象的紋理

第三個咱們須要的是Mesh(網格)。一個Mesh就是一個對象,它獲得一個多面體並給它應用Material,咱們能夠把網格插入咱們的場景中並自由移動它。

下面是如何來合併Geometry和Material並放入一個Mesh,而後添加到場景中。須要指明的是,將Mesh添加進場景後,咱們能夠自由地從新定位或者旋轉它。

var shapeOne = new THREE.Mesh(geometry, material);
shapeOne.position.y += 10;
scene.add(shapeOne);

-將Geometry和Material合併進一個Mesh中,並將Mesh加入場景

添加光線

一旦咱們在場景中擁有了對象,咱們須要照亮它們。爲了實現這種效果,咱們會添加兩類不一樣的光線:環境光和點狀光。

環境光的色彩會全局應用到場景中全部的對象。

var ambientLight = new THREE.AmbientLight( 0xffffff, 0.2 );
scene.add( ambientLight );

-給場景添加環境光

點狀光在場景中某特定位置建立光。光在任何方向都會閃爍,大概和燈泡的效果相似。

var pointLight = new THREE.PointLight( 0xffffff, 1 );
pointLight.position.set( 25, 50, 25 );
scene.add( pointLight );

-爲場景添加點狀光

若是這些不能知足你的需求,還有其餘種類的光能夠選擇,包括定向光和斑點光。查看Three.js 光線手冊來得到更多信息。

製造並接收陰影

陰影默認是不能使用的,但對建立視覺上的深度頗有幫助 — 因此咱們須要在渲染器上啓用它們。

renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;

-在渲染器中啓用陰影

下一步是指定哪些光線能夠造成陰影,還有要渲染的陰影範圍有多大。

pointLight.castShadow = true;
pointLight.shadow.mapSize.width = 1024;
pointLight.shadow.mapSize.height = 1024;

-啓用光線。相應地,陰影也會出現

最終咱們指定哪些網格應該接收陰影。須要指明的是任何網格在不依賴場景的狀況下都能製造和接收陰影。

shapeOne.castShadow = true;
shapeOne.receiveShadow = true;

-利用陰影來突出Mesh

在這個場景裏,咱們使用了一個特殊的陰影Material。它容許一個Mesh僅展現陰影,而非對象自己。

var shadowMaterial = new THREE.ShadowMaterial( { color: 0xeeeeee } );
shadowMaterial.opacity = 0.5;

-實現陰影效果

利用簡單要素構建複雜物體

目前爲止咱們作的一些簡單的例子還不錯,可若是咱們能實現元素複用的話事情會更簡單。

代碼連接 第三步

代碼貢獻者 (@agar)

來自 CodePen.

-能肯定的是,這些拼合的多邊形變得更小巧了

在codepen中點擊並拖拽以得到更清晰的效果。

經過對多邊形對象進行合併和分層操做,咱們能夠開始建立更多的複雜形狀。

下面的操做是擴展Three.Group對象以求在構造器中建立複雜形狀。

var Decoration = function() {

    // Run the Group constructor with the given arguments
    THREE.Group.apply(this, arguments);

    // A random color assignment
    var colors = ['#ff0051', '#f56762','#a53c6c','#f19fa0','#72bdbf','#47689b'];

    // The main bauble is an Octahedron
    var bauble = new THREE.Mesh(
        addNoise(new THREE.OctahedronGeometry(12,1), 2),
        new THREE.MeshStandardMaterial( {
            color: colors[Math.floor(Math.random()*colors.length)],
            shading: THREE.FlatShading ,
            metalness: 0,
            roughness: 1
    } )
    );
    bauble.castShadow = true;
    bauble.receiveShadow = true;
    bauble.rotateZ(Math.random()*Math.PI*2);
    bauble.rotateY(Math.random()*Math.PI*2);
    this.add(bauble);

    // A cylinder to represent the top attachment
    var shapeOne = new THREE.Mesh(
        addNoise(new THREE.CylinderGeometry(4, 6, 10, 6, 1), 0.5),
        new THREE.MeshStandardMaterial( {
            color: 0xf8db08,
            shading: THREE.FlatShading ,
            metalness: 0,
            roughness: 1
        } )
    );
    shapeOne.position.y += 8;
    shapeOne.castShadow = true;
    shapeOne.receiveShadow = true;
    this.add(shapeOne);
};
Decoration.prototype = Object.create(THREE.Group.prototype);
Decoration.prototype.constructor = Decoration;

-在構造器中建立複雜形狀

咱們如今能屢次複用拼合獲得的多邊形來給咱們的場景添加多重距離,用比單首創建每個元素更少的工做量讓樹木更真實。

var decoration = new Decoration();
decoration.position.y += 10;
scene.add(decoration);

-裝飾樹幹

另外一個建議是給建立的對象添加一個隨機的元素。

在對象的Geometry內移動頂點,以添加一個隨機組織的元素來下降形狀複雜度。若沒有這些小缺陷,作出來的物體會有點合成的感受。我使用了一個輔助函數來給Geometry的頂點隨機添加噪點。

function addNoise(geometry, noiseX, noiseY, noiseZ) {
    var noiseX = noiseX || 2;
    var noiseY = noiseY || noiseX;
    var noiseZ = noiseZ || noiseY;
    for(var i = 0; i < geometry.vertices.length; i++){
        var v = geometry.vertices[i];
        v.x += -noiseX / 2 + Math.random() * noiseX;
        v.y += -noiseY / 2 + Math.random() * noiseY;
        v.z += -noiseZ / 2 + Math.random() * noiseZ;
    }
    return geometry;
}

-添加噪點可使對象更真實

實現動做

目前爲止咱們只爲WebGLRender實現了一個單獨的渲染調用。爲了向咱們的場景中添加一些動做,咱們須要作出一些更新。

代碼連接 第四步

代碼貢獻者 (@agar)

來自 CodePen.

-觀察下多面體催眠式的緩慢旋轉

渲染循環

爲了使瀏覽器適應咱們的更新速度,咱們正在使用瀏覽器動做請求框架API來調用一個新的渲染函數。

requestAnimationFrame(render);
function render() {
    // Update camera position based on the controls
    controls.update();

    // Re-render the scene
    renderer.render(scene, camera);

    // Loop
    requestAnimationFrame(render);
}

-利用動做請求框架建立一個渲染循環

超時更新元素

如今,我會對複雜對象作出一些改變,每次建立距離時給裝飾物初始化一個隨機旋轉速度。

this.rotationSpeed = Math.random() * 0.02 + 0.005;
this.rotationPosition = Math.random();

-進入旋轉

咱們一樣設置了一個能夠被調用來基於當前值值繞Y軸旋轉的新函數。須要指出的是旋轉速度基於瀏覽器取得的幀率,但對這個簡單的例子來講還好。對處理這個過程而言,你必定會用到數學函數。

Decoration.prototype.updatePosition = function() {
    this.rotationPosition += this.rotationSpeed;
    this.rotation.y = (Math.sin(this.rotationPosition));
};

-觀察裝飾物旋轉狀況

隨着一個更新函數的定義,每運行一次咱們就能經過更新渲染循環來從新計算每一個元素每次被建立的位置。

function render() {
    // Update camera position based on the controls
    controls.update();

    // Loop through items in the scene and update their position
    for(var d = 0; d < decorations.length; d++) {
        decorations[d].updatePosition();
    }

    // Re-render the scene
    renderer.render(scene, camera);

    // Loop
    requestAnimationFrame(render);
}

-從新計算元素位置

把以上的幾個例子結合在一塊兒

代碼連接 第五步

代碼貢獻者 (@agar)

來自 CodePen.

-3D聖誕樹:徹底成型,裝飾完美

最終的產品總算出來了。僅僅使用了基礎功能,咱們已經構建出一個交互式的3D聖誕樹,而且創建了一個平面的二維場景。

但這只是使用WebGL的開始。當這項技術快速發展的時候,會出現許多可供選擇的資源,還有能正確指導你的教程。如下是資源連接:

你還在等什麼?嘗試下WebGL和Three.js吧,開始建立你本身的3D效果。若是你作了一些有趣的玩意,請告訴我。我很樂意欣賞一下。

分享

Matt Agar

關於做者

擁有超過15年的工程經驗,Matt是August的創始成員之一,並團結了世界上一批很優秀的先後端開發者。做爲能適應任何情景的問題解決者,Matt從實用角度和大方向上審視項目的技術問題。當他不在解決問題時,他必定在搭建一個虛構的動物王國並和年輕的家人一塊兒探索戶外。

聯繫方式

相關文章
相關標籤/搜索