ThreeJS快速入門
引言
本文主要是講解Three.js的相關概念,幫助讀者對Three.js以及相關知識形成比較完整的理解。n
近年來web得到了快速的發展。隨著HTML5的普及,網頁的表現能力越來越強大。網頁上已經可以做出很多複雜的動畫,精美的效果。
但是,人總是貪的。那麼,在此之上還能做什麼呢?其中一種就是通過WebGL在網頁中繪製高性能的3D圖形。OpenGL,WebGL到Three.js
OpenGL大概許多人都有所耳聞,它是最常用的跨平台圖形庫。
WebGL是基於OpenGL設計的面向web的圖形標準,提供了一系列JavaScript API,通過這些API進行圖形渲染將得以利用圖形硬體從而獲得較高性能。 而Three.js是通過對WebGL介面的封裝與簡化而形成的一個易用的圖形庫。簡單點的說法:WebGL可以看成是瀏覽器給我們提供的介面,在javascript中可以直接用這些API進行3D圖形的繪製;而Three.js就是在這些介面上又幫我們封裝得更好用一些。
WebGL與Three.js對比
既然有了WebGL,我們為什麼還需要Three.js?
這是因為WebGL門檻相對較高,需要相對較多的數學知識。雖然WebGL提供的是面向前端的API,但本質上WebGL跟前端開發完全是兩個不同的方向,知識的重疊很少。相關性只是他們都在web平台上,都是用javascript而已。一個前端程序員或許還熟悉解析幾何,但是還熟悉線性代數的應該寥寥無幾了(比如求個逆轉置矩陣試試?),更何況使用中強調矩陣運算中的物理意義,這在教學中也是比較缺失的。 因此,前端工程師想要短時間上手WebGL還是挺有難度的。 於是,Three.js對WebGL提供的介面進行了非常好的封裝,簡化了很多細節,大大降低了學習成本。並且,幾乎沒有損失WebGL的靈活性。 因此,從Three.js入手是值得推薦的,這可以讓你在較短的學習後就能面對大部分需求場景。Three.js的學習問題
Three.js的入門是相對簡單的,但是當我們真的去學的時候,會發現一個很尷尬的問題:相關的學習資料很少。
通常這種流行的庫都有很完善的文檔,很多時候跟著官方的文檔或官方的入門教程學習就是最好的路線。但Three不是的,它的文檔對初學者來說太過簡明扼要。 不過官方提供了非常豐富的examples,幾乎所有你需要的用法都在某個example中有所體現。但這些example不太適合用來入門,倒是適合入門之後的進一步學習。這裡推薦一些相對較好的教程:
Three.js入門指南
這是一份很好的Three.js 輕量級入門教程,作者文筆很好,基礎知識講解得簡明易懂。Three.js開發指南(第一版中文版)
Learning Three.js- Second EditionLearning Three.js:The JavaScript 3D Library for WebGL是現在不多的也是最好的Three.js入門書,比較全面地講解了Three.js的各種功能。 如果有能力的話,建議閱讀英文版第二版,出版於2015年,與現在的Three.js區別很小。 中文版翻譯自出版於2012年的原書第一版,大部分概念是適用的,但很多細節已經有所改變。 Three.js入門教程這是對國外一份教程的翻譯,一共六篇文章。講解不多,更多的是展示各個基本功能怎麼用。更適合有一些圖形基礎的同學。當然,實際的學習過程中這些資料肯定是不太夠的,遇到問題還是要自己去查資料。不過這裡要提醒一下,Three.js的更新是相當頻繁的,現在是r80版本,自2010年4月發布r1以來,這已經是第72個版本了(中間有的版本號跳過了)。因此,在網上找到的資料有些可能是不適合當前版本的,需要注意甄別(前面推薦的資料也都或多或少存在這樣的問題)。
Three.js中的一些概念
要在屏幕上展示3D圖形,思路大體上都是這樣的:
- 構建一個三維空間
- Three中稱之為場景(Scene)
- 選擇一個觀察點,並確定觀察方向/角度等
- Three中稱之為相機(Camera)
- 在場景中添加供觀察的物體
- Three中的物體有很多種,包括Mesh,Line,Points等,它們都繼承自Object3D類
- 將觀察到的場景渲染到屏幕上的指定區域
- Three中使用Renderer完成這一工作
下面來具體看一看Three中的這些概念。
Scene
場景是所有物體的容器,也對應著我們創建的三維世界。
Camera
坐標系
Camera是三維世界中的觀察者,為了觀察這個世界,首先我們要描述空間中的位置。
Three中使用採用常見的右手坐標系定位。三維投影
Three中的相機有兩種,分別是正投影相機THREE.OrthographicCamera和透視投影相機THREE.PerspectiveCamera。
正交投影與透視投影的區別如上圖所示,左圖是正交投影,物體發出的光平行地投射到屏幕上,遠近的方塊都是一樣大的;右圖是透視投影,近大遠小,符合我們平時看東西的感覺。 維基百科:三維投影正交投影相機
註:圖中的」視點」對應著Three中的Camera。 這裡補充一個視景體的概念:視景體是一個幾何體,只有視景體內的物體才會被我們看到,視景體之外的物體將被裁剪掉。這是為了去除不必要的運算。 正交投影相機的視景體是一個長方體,OrthographicCamera的構造函數是這樣的:OrthographicCamera( left, right, top, bottom, near, far )Camera本身可以看作是一個點,left則表示左平面在左右方向上與Camera的距離。另外幾個參數同理。於是六個參數分別定義了視景體六個面的位置。
可以近似地認為,視景體里的物體平行投影到近平面上,然後近平面上的圖像被渲染到屏幕上。
透視投影相機
透視投影相機的視景體是個四稜台,它的構造函數是這樣的:PerspectiveCamera( fov, aspect, near, far ) fov對應著圖中的視角,是上下兩面的夾角。aspect是近平面的寬高比。在加上近平面距離near,遠平面距離far,就可以唯一確定這個視景體了。 透視投影相機很符合我們通常的看東西的感覺,因此大多數情況下我們都是用透視投影相機展示3D效果。Objects
有了相機,總要看點什麼吧?在場景中添加一些物體吧。
Three中供顯示的物體有很多,它們都繼承自Object3D類,這裡我們主要看一下Mesh和Points兩種。Mesh
我們都知道,計算機的世界裡,一條弧線是由有限個點構成的有限條線段連接得到的。線段很多時,看起來就是一條平滑的弧線了。
計算機中的三維模型也是類似的,普遍的做法是用三角形組成的網格來描述,我們把這種模型稱之為Mesh模型。
這是那隻著名的斯坦福兔子。它在3D圖形中的地位與數字圖像處理領域中著名的lena是類似的。 看這隻兔子,隨著三角形數量的增加,它的表面越來越平滑/準確。在Three中,Mesh的構造函數是這樣的:Mesh( geometry, material )
geometry是它的形狀,material是它的材質。 不止是Mesh,創建很多物體都要用到這兩個屬性。下面我們來看看這兩個重要的屬性。Geometry
Geometry,形狀,相當直觀。Geometry通過存儲模型用到的點集和點間關係(哪些點構成一個三角形)來達到描述物體形狀的目的。
Three提供了立方體(其實是長方體)、平面(其實是長方形)、球體、圓形、圓柱、圓台等許多基本形狀;你也可以通過自己定義每個點的位置來構造形狀;
對於比較複雜的形狀,我們還可以通過外部的模型文件導入。Material
Material,材質,這就沒有形狀那麼直觀了。
材質其實是物體表面除了形狀以為所有可視屬性的集合,例如色彩、紋理、光滑度、透明度、反射率、折射率、發光度。 這裡講一下材質(Material)、貼圖(Map)和紋理(Texture)的關係。 材質上面已經提到了,它包括了貼圖以及其它。 貼圖其實是『貼』和『圖』,它包括了圖片和圖片應當貼到什麼位置。 紋理嘛,其實就是『圖』了。 Three提供了多種材質可供選擇,能夠自由地選擇漫反射/鏡面反射等材質。Points
講完了Mesh,我們來看看另一種Object——Points。
Points其實就是一堆點的集合,它在之前很長時間都被稱為ParticleSystem(粒子系統),r68版本時更名為PointCloud,r72版本時才更名為Points。更名主要是因為,Mr.doob認為,粒子系統應當是包括粒子和相關的物理特性的處理的一套完整體系,而Three中的Points簡單得多。因此最終這個類被命名為Points。
Points能夠用來實現的典型效果是這樣的:官方exampleLight
神說:要有光!
光影效果是讓畫面豐富的重要因素。 Three提供了包括環境光AmbientLight、點光源PointLight、 聚光燈SpotLight、方向光DirectionalLight、半球光HemisphereLight等多種光源。 只要在場景中添加需要的光源就好了。Renderer
在場景中建立了各種物體,也有了光,還有觀察物體的相機,是時候把看到的東西渲染到屏幕上了。這就是Render做的事情了。
Renderer綁定一個canvas對象,並可以設置大小,默認背景顏色等屬性。 調用Renderer的render函數,傳入scene和camera,就可以把圖像渲染到canvas中了。讓畫面動起來
現在,一個靜態的畫面已經可以得到了,怎麼才能讓它動起來?
很簡單的想法,改變場景中object的位置啊角度啊各種屬性,然後重新調用render函數渲染就好了。
那麼重新渲染的時機怎麼確定? HTML5為我們提供了requestAnimFrame,它會自動在每次頁面重繪前調用傳入的函數。 如果我們一開始這樣渲染:function render()n{n renderer.render(scene, camera);n}n
只需要改成這樣:
function render()n{n requestAnimationFrame(render);n object.position.x += 1;n renderer.render(scene, camera);n}n
object就可以動起來了!
舉個栗子
下面我們用一個簡單的例子來梳理一下這個過程。
首先寫一個有Canvas元素的頁面吧。<!DOCTYPE html>n<html>n<head>n <meta charset="UTF-8">n <title>立方體</title>n <script src="http://sqimg.qq.com/qq_product_operations/mma/javanli_test/lib/three.min.js"></script>n <style type="text/css">n html, body {n margin: 0;n padding: 0;n }n #three_canvas {n position: absolute;n width: 100%;n height: 100%;n }n </style>n</head>n<body>n <canvas id="three_canvas"></canvas>n</body>n</html>n
下面來做Javascript的部分
首先初始化Rendererfunction initRenderer() {n width = document.getElementById(three_canvas).clientWidth;n height = document.getElementById(three_canvas).clientHeight;n renderer = new THREE.WebGLRenderer({n //將Canvas綁定到renderern canvas: document.getElementById(three_canvas)n });n renderer.setSize(width, height);//將渲染的大小設為與Canvas相同n renderer.setClearColor(0xFFFFFF, 1.0);//設置默認顏色與透明度n}n
初始化場景:
function initScene() {n scene = new THREE.Scene();n}n
初始化相機:
function initCamera() {n //簡單的正交投影相機,正對著視口的中心,視口大小與Canvas大小相同。n camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 1, 1000);n //設置相機的位置n camera.position.x = 0;n camera.position.y = 0;n camera.position.z = 200;n //設置相機的上方向n camera.up.x = 0;n camera.up.y = 1;n camera.up.z = 0;n //設置相機聚焦的位置(其實就是確定一個方向)n camera.lookAt({n x: 0,n y: 0,n z: 0n });n}n
要唯一確定一個相機的位置與方向,position、up、lookAt三個屬性是缺一不可的。
這裡我們創建了一個正交投影相機,這裡我將視景體大小與屏幕解析度保持一致只是為了方便,這樣坐標系中的一個單位長度就對應屏幕的一個像素了。 我們將相機放在Z軸上,面向坐標原點,相機的上方向為Y軸方向,注意up的方向和lookAt的方向必然是垂直的(類比自己的頭就知道了)。下面添加一個立方體到場景中:
function initObject() {n //創建一個邊長為100的立方體n var geometry = new THREE.CubeGeometry(100, 100, 100);n object = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());n scene.add(object);n}n
注意我們使用了法向材質MeshNormalMaterial,這樣立方體每個面的顏色與這個面對著的方向是相關的,更便於觀察/調試。
在這個簡單的demo里我不打算添加光影效果,而法向材質對光也是沒有反應的。
最後來創建一個動畫循環吧function render() {n requestAnimationFrame(render);n object.rotation.x += 0.05;n object.rotation.y += 0.05;n renderer.render(scene, camera);n}n
每次重繪都讓這個立方體轉動一點點。
當頁面載入好時,調用前面這些函數就好了function threeStart() {n initRenderer();n initCamera();n initScene();n initObject();n render();n}nwindow.onload = threeStart();n
完整的demo是這個樣子的:
<!DOCTYPE html>n<html>n<head>n <meta charset="UTF-8">n <title>立方體</title>n <script src="http://sqimg.qq.com/qq_product_operations/mma/javanli_test/lib/three.min.js"></script>n <style type="text/css">n html, body {n margin: 0;n padding: 0;n }nn #three_canvas {n position: absolute;n width: 100%;n height: 100%;n }n </style>n</head>n<body>n<canvas id="three_canvas"></canvas>n<script>n var renderer, camera, scene, light, object;n var width, height;n function initRenderer() {n width = document.getElementById(three_canvas).clientWidth;n height = document.getElementById(three_canvas).clientHeight;n renderer = new THREE.WebGLRenderer({n canvas: document.getElementById(three_canvas)n });n renderer.setSize(width, height);n renderer.setClearColor(0xFFFFFF, 1.0);n }nn function initCamera() {n camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 1, 1000);n camera.position.x = 0;n camera.position.y = 0;n camera.position.z = 200;n camera.up.x = 0;n camera.up.y = 1;n camera.up.z = 0;n camera.lookAt({n x: 0,n y: 0,n z: 0n });n }n function initScene() {n scene = new THREE.Scene();n }n function initObject() {n var geometry = new THREE.CubeGeometry(100, 100, 100);n object = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());n scene.add(object);n }n function render() {n requestAnimationFrame(render);n object.rotation.x += 0.05;n object.rotation.y += 0.05;n renderer.render(scene, camera);n }n function threeStart() {n initRenderer();n initCamera();n initScene();n initObject();n render();n }n window.onload = threeStart();n</script>n</body>n</html>n
保存為html後打開,在屏幕中央會顯示這樣一個轉動的立方體
小結
對Three.js的介紹就到這裡了,本文對Three中的重要組件基本上都有提到。其實想要總結的東西還有很多,但寫在這一篇里可能會顯得很累贅,這篇文章的初衷是想要讀者讀後對Three.js有一個直觀的大體上的理解,並不打算牽涉太多細節。
祝好~推薦閱讀:
※HTML5網頁端如何調用手機瀏覽器分享功能?
※Egg.js 搭建 JWT/OAuth 認證伺服器
※[git 進階] 同一電腦同時配置 github 和 gitlab
※【掘金小報】第七期 你怎麼看待技術圈撕逼?