使用WebGL + Three.js製作動畫場景 3D圖像,技術,打造產品,還有互聯網:這些只是我愛好的一小部分。 現在,感謝WebGL的出現-一個新的JavaScriptAPI,它可以在不依賴任何插件的情況下渲染瀏覽器中的3D圖像-這讓3D渲染操作變得異常簡單。 隨著虛擬現實和增強現實應用的發展, ...
使用WebGL + Three.js製作動畫場景
3D圖像,技術,打造產品,還有互聯網:這些只是我愛好的一小部分。
現在,感謝WebGL的出現-一個新的JavaScriptAPI,它可以在不依賴任何插件的情況下渲染瀏覽器中的3D圖像-這讓3D渲染操作變得異常簡單。
隨著虛擬現實和增強現實應用的發展,大型廠商們開始轉向數字化觸覺體驗,這是令人動心的一項技術。
或者,至少那些已經投資的人這一年還抱有希望-11億美金流入VR和AR領域.
從Abbey Road Studios的谷歌交互之旅到拍攝Deadliest Catch用到的艦隊,通過在非真實世界給觀眾沉浸式的體驗,所有的產品,服務和環境得以實現更好的配合。
由於人們能接觸到更多體驗性的技術,2D開始變得有些單調。這是事實。
讓我們現實點。目前看來,很多致力於創造體驗的應用仍處在技術探索階段,對大多數商業領域而言前景不算明朗。
或者說他們真的創造了令人激動的體驗嗎?
走進WebGL:一項實用與靈活的技術,可以創造更強沉浸式3D內容。無論是Porsche展示一輛新911的細節,還是NASA重點介紹的火星是什麼樣子,或者是J Dlla備受喜愛的甜甜圈專輯慶典,WebGL能應用於很多領域來表現各種類型的故事。
為讓你熟悉這項強大的技術,我打算做一個關於它是如何工作的簡要概括,還有使用Three.js(一個基於WebGL API的JavaScript庫)一步步創造簡單3D環境的快速教程。
首先,什麼是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庫展示了更複雜的效果。為了練習需要,我會儘量寫的簡單,用低複雜度的環境來展示僅靠理解的基礎知識能實現什麼效果。
我打算構建一個我們已使用過的例子
讓我們開始用瞭解的基礎知識做點東西吧。
一個渲染器,一個場景,還有一個相機
代碼鏈接第一步
貢獻者 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的開始。當這項技術快速發展的時候,會出現許多可供選擇的資源,還有能正確指導你的教程。以下是資源鏈接:
The Github repo for three.js, full of examples and endless learning opportunities.
Assorted built-in helpers for cameras, lights, axes etc.
DatGui: create an interface that you can use to modify variables.
stats.js: a handy JavaScript performance monitor (for framerate).
An excellent and very detailed tutorial on creating a Three.js mini game, The Aviator, from the very talented guys at Codrops.
Plus heaps of great low poly examples on Codepen from Karim Maaloul.
你還在等什麼?嘗試下WebGL和Three.js吧,開始創建你自己的3D效果。如果你做了一些有趣的玩意,請告訴我。我很樂意欣賞一下。
分享
關於作者
擁有超過15年的工程經驗,Matt是August的創始成員之一,並團結了世界上一批很優秀的前後端開發者。作為能適應任何情景的問題解決者,Matt從實用角度和大方向上審視項目的技術問題。當他不在解決問題時,他一定在搭建一個虛構的動物王國並和年輕的家人一起探索戶外。
聯繫方式
-
本文轉載自:眾成翻譯
譯者:VicSusi
鏈接:http://www.zcfy.cc/article/4705
原文:https://www.august.com.au/blog/animating-scenes-with-webgl-three-js