在 IBM Bluemix 云平臺上開發(fā)并部署您的下一個應(yīng)用。 硬件加速的 3D 性能是目前每一個 JavaScript 開發(fā)都關(guān)注的事情,因為桌面和移動瀏覽器中對 WebGL 的支持幾乎無處不在。在此 WebGL 系列 的 第 2 部分,我們使用了以下兩個高級別的 WebGL 庫進行實驗:Three.js 和 SceneJS。您看到了這些庫如何通過簡單的、概念性的純 API 來封裝原始 WebGL 開發(fā)的復(fù)雜性,并實現(xiàn)快速的 3D 開發(fā)。您學(xué)習(xí)了如何:
立刻創(chuàng)建 3D 應(yīng)用程序和數(shù)據(jù)可視化,將目前所學(xué)的技巧付諸于實踐。此 系列 的最后這一篇文章將介紹 3D 用戶交互的一些概念和技術(shù),然后引導(dǎo)您完成兩個 3D 應(yīng)用程序的開發(fā):一個完整的 3D 井字過三關(guān)游戲,以及一個交互式數(shù)據(jù)可視化用戶界面。請參閱 下載,以獲得樣例代碼。 在本文結(jié)束時,您就會掌握在您即將到來的 Web 開發(fā)項目中應(yīng)用 WebGL 和處理 3D 需求的足夠知識。 3D 場景的用戶交互在此文章系列中,目前為止已經(jīng)構(gòu)建了包含 3D 動畫對象和移動攝像機(飛過或走過)的場景,但使用的都是用戶不能參與的預(yù)先編程的動作。用戶體驗一直類似于觀看視頻。在實踐中,許多 3D 應(yīng)用程序(以及其他許多游戲和數(shù)據(jù)可視化)需要與用戶進行交互。 利用 Three.js 等高層次的 WebGL 庫來增加用戶交互可能非常簡單。在瀏覽器中加載 imatlight.html。這是 第 2 部分 中提供的燈光和陰影效果示例(matlight.html),添加了攝像頭位置的 3D 用戶控制。圖 1 顯示了由 imatlight.html 渲染的場景。 圖 1. 可與用戶進行交互的 3D 場景(在 OS X 上的 Safari 中)開始與場景進行交互。通過使用鼠標(biāo),您可以:
通過這些運動和一些練習(xí),您應(yīng)該能夠有效地、快速地在 3D 渲染的場景中實現(xiàn)導(dǎo)航。 圖 2 顯示了進行一些交互后的 imatlight.html。您將從完全不同的角度查看 圖 1 的場景。 圖 2. 進行用戶交互后的另一個角度的 imatlight.html(在 OS X 上的 Chrome 中)清單 1 顯示了 imatlight.html 的代碼。突出顯示的行是添加到 matlight.html(來自 第 2 部分)的代碼,用于將用戶交互融入到場景中。 清單 1. 與 3D 場景 (imatlight.html) 進行交互<!doctype html> <html> <head> <title>developerWorks WebGL Three.js Interactive Lights and Shadows Effect Example</title> <script src="Three.js" ></script> <script src="js/controls/OrbitControls.js"></script> <script type="text/javascript"> function draw3D() { var controls; function animate() { requestAnimationFrame(animate); pyramid1.rotateY(Math.PI/180); sphere.rotateY(Math.PI/180); cube.rotateY(Math.PI/180); multi.rotateY(Math.PI/480); renderer.render(scene, camera); } function updateControls() { controls.update(); } var geo = new THREE.CylinderGeometry(0,2,2,4,1, true); var pyramid1 = new THREE.Mesh(geo, new THREE.MeshPhongMaterial({color:0xff0000})); pyramid1.position.set(-2.5, -1, 0); geo = new THREE.SphereGeometry(1, 25, 25); var sphere = new THREE.Mesh(geo, new THREE.MeshPhongMaterial({color:0x00ff00})); sphere.position.set(2.5, -1, 0); geo = new THREE.CubeGeometry(2,2,2); var cube = new THREE.Mesh(geo,new THREE.MeshPhongMaterial({color:0x0000ff }) ); cube.position.set(0, 1, 0); var camera = new THREE.PerspectiveCamera( 45, 1024/500,0.1, 100); camera.position.z = 10; camera.position.y = 1; controls = new THREE.OrbitControls( camera ); controls.addEventListener( 'change', updateControls ); var multi = new THREE.Object3D(); pyramid1.castShadow = true; sphere.castShadow = true; multi.add(cube); multi.add(pyramid1); multi.add(sphere); multi.position.z = 0; geo = new THREE.PlaneGeometry(20, 25); var floor = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({color :0xcfcfcf})); floor.material.side = THREE.DoubleSide; floor.rotation.x = Math.PI/2; floor.position.y = -2; floor.receiveShadow = true; var light = new THREE.DirectionalLight(0xe0e0e0); light.position.set(5,2,5).normalize(); light.castShadow = true; light.shadowDarkness = 0.5; light.shadowCameraRight = 5; light.shadowCameraLeft = -5; light.shadowCameraTop = 5; light.shadowCameraBottom = -5; light.shadowCameraNear = 2; light.shadowCameraFar = 100; var scene = new THREE.Scene(); scene.add(floor); scene.add(multi); scene.add(light); scene.add(new THREE.AmbientLight(0x101010)); var div = document.getElementById("shapecanvas2"); var renderer = new THREE.WebGLRenderer(); renderer.setSize(1024,500); renderer.setClearColor(0x000000, 1); renderer.shadowMapEnabled = true; div.appendChild( renderer.domElement ); animate(); } </script> </head> <body onload="draw3D();"> <span id="shapecanvas2" style="border: none;" width="1024" height="500"></span> <br/> </body> </html> 面向 Three.js 的 OrbitControls API可選的 Three.js 控制 APIThree.js 代碼分發(fā)包含用于 3D 硬件和用例的無數(shù)可供選擇的用戶輸入/輸出支持 API,這些 API 都位于源代碼目錄的 examples/js/controls 目錄中。TrackballControls.js 通過一個軌跡球支持用戶交互。FirstPersonControl.js 支持許多第一人稱視角(FPV)游戲玩家所熟悉的用戶交互模式。FlyControl.js 支持飛行模擬器式攝像機控制,支持滾動和俯仰。OculusControls.js 通過 Oculus Rift 支持用戶交互,Oculus Rift 是一個仍在開發(fā)中(并備受期待)的、精密的消費者頭部跟蹤沉浸式虛擬現(xiàn)實設(shè)備。 在 Three.js 中,是通過 OrbitControls.js API 來支持鼠標(biāo)交互的,該 API 位于 Three.js 源代碼樹的 examples/js/controls 目錄。因為不是所有 3D 應(yīng)用程序都要求用來與其他一些硬件設(shè)備進行交互的用戶交互、OrbitControls 和其他 API 是可選的庫(參閱 可選的 Three.js 控制 API 邊欄)。 OrbitControls 的工作原理是在 3D 場景內(nèi)與鼠標(biāo)輸入一致地移動攝像機的位置。下面的兩行代碼舉例說明了該控制,并使用來自 3D 場景的攝像機將其參數(shù)化: controls = new THREE.OrbitControls( camera ); controls.addEventListener( 'change', updateControls ); OrbitControls function updateControls() { controls.update(); } 在 imatlight.html 3D 場景中, 設(shè)計 3D 游戲您要開發(fā)的下一個項目是一個需要用戶交互的完全可玩的 3D 游戲:3D 井字過三關(guān)游戲。兩名對手玩家持不同顏色的棋子。一個玩家的棋子是紅色的,另一個玩家的棋子是綠色的。玩家輪流將自己的棋子放在一個 3x3x3 的立方體 “井字框架(cage)” 里面。三個棋子沿任意維度排成一行的第一個玩家將會獲得勝利。這里給出的代碼實現(xiàn)了一個電腦玩家(控制紅色棋子),您可以與其進行對戰(zhàn)。核心 “引擎” 代碼是獨立的,可以修改為人與人對戰(zhàn)的游戲,也許可以通過網(wǎng)絡(luò)實現(xiàn)對戰(zhàn)。 圖 3 顯示了玩這個游戲用的 3D 立方體。您可以看到 3x3x3=27 個位置,棋子可以放在這些位置上,所有位置都由白球占據(jù)。 圖 3. 3D 井字過三關(guān)游戲的比賽場地(在 OS X 上的 Firefox 中)當(dāng)玩家圍繞 “井字框架” 運動,思考下一步操作的時候,鼠標(biāo)經(jīng)過的每個可用位置都將變?yōu)辄S色。然后,玩家可以單擊選定的球,提交一步操作,球體就會變?yōu)樵撏婕业念伾?/p> 屏幕上顯示的文字指出了這是哪一個玩家的操作,并在其中一個玩家獲勝時顯示相關(guān)文字。圖 4 顯示了紅色代表的計算機玩家的獲勝組合,以及最終的屏幕顯示。在獲得勝利后,可單擊屏幕上任意位置來重置游戲。 圖 4. 紅色玩家的勝利(在 OS X 上的 Chrome 中)加載 tictacthreed.html,并自己試著玩一下這個游戲。就像在 imatlight.html 中那樣,您可以使用鼠標(biāo)圍繞游戲的井字框架運動。(兩個頁面都使用了 Three.js OrbitControls API。)計算機玩家并不是特別聰明,您可以很輕松地贏得游戲。在游戲結(jié)束時,單擊任意位置再次啟動一個新游戲。前一次比賽中的失敗方在新游戲中可以先走第一步。 在試玩幾場比賽后,查看一下 tictacthreed.html 的源代碼(參見 下載)。清單 2 中的代碼是 tictacthreed.html 的部分,用于創(chuàng)建放棋子的 3D 游戲井字框架。 清單 2. 創(chuàng)建 3D 游戲井字框架var base = new THREE.Geometry(); for (var z=-1; z<1; z++ ) { base.vertices.push( new THREE.Vector3(0, 0 ,z), new THREE.Vector3( 3, 0, z ), new THREE.Vector3(0, 1 ,z), new THREE.Vector3( 3, 1, z ), new THREE.Vector3(1, 2 ,z), new THREE.Vector3( 1, -1, z ), new THREE.Vector3(2, 2 ,z), new THREE.Vector3( 2, -1, z ) ); } for (var x=1; x<3; x++ ) { base.vertices.push( new THREE.Vector3(x, 1 ,1), new THREE.Vector3( x, 1, -2 ), new THREE.Vector3(x, 0, 1), new THREE.Vector3( x, 0, -2 ) ); } var cage = new THREE.Line(base, new THREE.LineBasicMaterial(), THREE.LinePieces ); cage.position.set(-1.5,-0.5, 0.5); 共有 12 條相交的直線形成這個井字框架。前四條線在 x-y 平面上。第二組的四條線的結(jié)束坐標(biāo)與第一組的相同,但其 z 分量是 -1,而不是 0。最后四條線包含兩組線條,它們的結(jié)束坐標(biāo)除了 x 值外都一樣。使用 生成標(biāo)志著游戲位置的球體為了生成用于標(biāo)志可用位置的白色球體,tictacthreed.html 使用了清單 3 中所示的迭代代碼。 清單 3. 生成白色球體var geo = new THREE.SphereGeometry(0.3, 25, 25); var range = [-1, 0, 1]; var idx = 0; range.forEach(function(x) { range.forEach(function(y) { range.forEach(function(z) { var tempS = new THREE.Mesh(geo, new THREE.MeshPhongMaterial({color:0xffffff})); tempS.ID = idx++; tempS.claim = UNCLAIMED; pos.push(tempS); tempS.position.set(x, y, z); scene.add(tempS); }) ) }); 計算機玩家的實現(xiàn)通過 球體的直徑都是 0.6(半徑為 0.3),因此用戶可以透過井字框架看到它們。清單 3 中的代碼創(chuàng)建了白色球體,并將它們放置到所有 27 個可玩的位置。需要注意的是,雖然所有 27 個球體都使用相同的幾何對象(這些對象被命名為 清單 3 的代碼還建立了一個名為 每個球體網(wǎng)格對象都有一個額外的屬性,??名為 圖 5 顯示了由 清單 3 的代碼生成的游戲位置的編號方案。每個數(shù)字代表該游戲位置(球體網(wǎng)格)在所生成的 圖 5. 游戲位置編號方案決定勝方游戲中有 49 個可能的獲勝組合,每個組合有三個位置。您可以根據(jù)圖 5 手動枚舉這些組合。 在 tictacthreed.html 中, 清單 4. |
節(jié)點類型 | 字段 | 描述 |
---|---|---|
Name | name | name 節(jié)點用于實現(xiàn)每個節(jié)點的 “拾取”(或 3D 選擇)。每個矩形條的惟一名稱是用算法根據(jù) x-z 平面上的矩形條位置來生成的。 |
Material | color | material 節(jié)點為每個矩形條提供其獨有的顏色。顏色用于渲染通過相關(guān)聯(lián)的數(shù)據(jù)點 dpoint["value"] 的值確定的矩形條。HighThreshold 和 PeakThreshold 用于確定是使用綠色、黃色還是紅色。 |
Translate | x | 在日期/時間序列中每繪制一個矩形條,x 坐標(biāo)增量 1 個單位。每個矩形條被渲染為 x-z 平面上的 1 個單位的正方形,代表一個月份的平均結(jié)果。 |
Translate | y | 矩形條的 y 位置通過數(shù)據(jù)點值 dpoint["value"] 來確定。它被除以 10,以縮小范圍。 |
Translate | z | 每個數(shù)據(jù)系列代表一個不同的數(shù)據(jù)中心,占據(jù)一個不同的 z 位置。 |
prims/box | ySize | 矩形條的 ySize 位置或高度通過數(shù)據(jù)點值 dpoint["value"] 來確定。它被除以 10,以縮小范圍。 |
在生成 3D 矩形條的節(jié)點時,該代碼還可以填充 node2data
散列。在對象拾取過程中,如果節(jié)點名稱可用,那么可以使用這個散列快速獲取數(shù)據(jù)點信息。您也可以在 SceneJS 節(jié)點內(nèi)創(chuàng)建一個 data
字段,以包含該數(shù)據(jù)。不過,通過檢索節(jié)點來獲得數(shù)據(jù)需要做的工作并不只是進行散列查找這么簡單。
像在 SceneJS 中的其他對象一樣,控制渲染后視圖的攝像機是通過 SceneJS 的場景圖中的節(jié)點來表示的。像 Three.js 一樣,SceneJS 支持使用鼠標(biāo)讓攝像機圍繞場景運動。要實現(xiàn)相機軌道運動,可使用包含在 SceneJS 分發(fā)中的 type:"camera/orbit"
插件替代傳統(tǒng)相機類型。在 sjbdvis.html 中的場景創(chuàng)建代碼封裝 coreScene
(突出顯示 — 10,000 個矩形條的場景)與一個軌道運動的攝像機節(jié)點:
scene = SceneJS.createScene({ canvasId:"shapecanvas2", nodes: [ { type:"cameras/orbit", look:{ x:80, y:0 }, yaw:0, pitch:-20, zoom:200, zoomSensitivity:10.0, nodes:[coreScene] } ] });
如果軌道運動的攝像機節(jié)點已經(jīng)到位,那么實現(xiàn)旋轉(zhuǎn)和縮放的鼠標(biāo)移動是由 SceneJS 庫處理的。look
、yaw
和 pitch
字段可用來控制攝像機的初始方向和位置。zoom
字段用于確定視圖最初看起來離被渲染的場景有多遠(yuǎn)。
SceneJS 中已啟用對所渲染對象的命中測試的支持。關(guān)鍵是要將每個可拾取對象包裝在 type:"name"
節(jié)點中。在 sjbdvis.html 中渲染的每個矩形條都被包裝在具有惟一名稱的 type:"name"
節(jié)點中,因此,可以從場景中拾取它。在 sjbdvis.html 中,當(dāng)用戶將光標(biāo)移動到被渲染的 3D 矩形條上面時,可使用對象拾取執(zhí)行屏幕上的狀態(tài)顯示的實時更新。每一個可拾取的 3D 矩形條都有以下包裝:
{ type:"name", name:unique node name, nodes:[ ... ] }
使用以下代碼,將當(dāng)前鼠標(biāo)位置捕獲到畫布的 mousemove
事件處理程序:
canvas.addEventListener('mousemove', function(event) { mouse = {x: event.clientX, y: event.clientY, updated: false, clicked: false}; }, false);
在更新全局鼠標(biāo)位置/狀態(tài)變量后,事件處理程序會立即返回,以保持響應(yīng)靈敏度。當(dāng)鼠標(biāo)圍繞矩形條移動時,全局 mouse
變量將會捕獲鼠標(biāo)的位置和狀態(tài)。通過以下代碼,在 SceneJS 場景刷新 ticks 時執(zhí)行對象拾取和相關(guān)的屏幕顯示更新:
scene.on("tick", function() { if (!mouse.updated) { scene.pick(mouse.x, mouse.y); mouse.updated = true; } });
在屏幕更新時,根據(jù)鼠標(biāo)的當(dāng)前保存的位置進行對象拾取。如果用戶的鼠標(biāo)懸停在某個矩形條上(或用戶單擊其中一個矩形條),那么這個特定矩形條對象將由 SceneJS 運行時拾取,并發(fā)出一個 SceneJS pick
事件,執(zhí)行一次回調(diào),以表示該對象被命中:
scene.on("pick", function (hit) { var name = hit.name; var datapoint = node2Data[hit.name]; document.getElementById("dmonth").innerHTML = datapoint["month"]; document.getElementById("dyear").innerHTML = datapoint["year"]; document.getElementById("dcenter").innerHTML = datapoint["location"]; if (mouse.clicked) { //simulate dive into detailed data alert("Show daily detailed reports for " + datapoint["month"] + ", " + datapoint["year"] + " at data center location " + datapoint["location"]); mouse.clicked = false; } });
使用 SceneJS 以編程方式修改所渲染的幾何形狀(基于數(shù)據(jù)點的值)相對容易一些,這提供了動態(tài)更新顯示統(tǒng)計數(shù)據(jù)的可能性??梢栽O(shè)想以下場景:添加一個 “聯(lián)網(wǎng)的實時數(shù)據(jù)提要”,以推動 sjbdvis.html 的更新,從而創(chuàng)建一個實時監(jiān)控控制臺,管理 100 個數(shù)據(jù)中心的多達(dá) 100 種不同的指標(biāo)。
hit
回調(diào)參數(shù)包含由用戶(在 hit.name
中)選取的 type:"name"
的名稱??梢允褂迷撁Q通過 nod2data
映射查找相關(guān)的數(shù)據(jù)點,數(shù)據(jù)點值被用來更新代表屏幕上的狀態(tài)顯示的 CSS 樣式 <div>
DOM 元素的內(nèi)容。如果單擊鼠標(biāo),則會彈出一個警報,顯示需要進一步分析的數(shù)據(jù)點 — 模擬由 UI 生成的數(shù)據(jù)獲取事件。
關(guān)于本 系列 的最后一個示例,還有一點需要注意。PC 鼠標(biāo)在與 3D 場景交互時就會真正體現(xiàn)出它的落后之處。為了用 3D 線索補充 2D 鼠標(biāo)輸入數(shù)據(jù),您必須在拖動鼠標(biāo)的同時單擊多個按鈕。理想情況下,通過具有 3D 功能的輸入設(shè)備可以直接輸入 3D 位置信息。
Leap Motion Controller 就是這種設(shè)備。這個被廣泛使用的設(shè)備是一個小型傳感器盒,通過 USB 3 連接到您的 PC 或 Mac。通過使用與設(shè)備的硬件紅外燈及攝像機數(shù)組一起工作的一個軟件驅(qū)動程序檢測 3D 位置和手勢信息。該設(shè)備可以精確地檢測手、手指以及在其檢測區(qū)域范圍內(nèi)的手持工具的位置和運動。
圖 9 顯示了一個 imatlight.html 版本(代碼 下載 中的名稱為 ilpmatlight.html)中的 3D 用戶交互,可將它顯示在大電視屏幕上,經(jīng)過修改,可將該版本與 Leap Motion Controller 結(jié)合使用。通過在 3D 空間中移動一只手,您可以平移、旋轉(zhuǎn)和縮放在 Three.js 場景中的攝像機,從不同的角度觀看場景。
Leap Motion Controller 的官方 JavaScript 支持軟件是 leapjs。它包含一個 node.js 服務(wù)器,該服務(wù)器與本機驅(qū)動程序進行通信,并通過 WebSocket 將傳感器信息發(fā)送到本地 JavaScript(瀏覽器)客戶端。除了 leapjs
之外,
threeleapcontrols 也是一組與 Leap Motion Controller 和 Three.js 配合使用的攝像機和對象控制。清單 12 顯示了 ilpmatlight.html 中的代碼更改,用于支持 3D 手位置控制輸入。
<!doctype html> <html> <head> <title>developerWorks WebGL Three.js 3D Interactive Lights and Shadows Effect Example with Leap Motion Controller</title> <script src="Three.js" ></script> <script src="leap.min.js"></script> <script src="LeapCameraControls.js"></script> <script type="text/javascript"> function draw3D() { var controls; Leap.loop(function(frame) { pyramid1.rotateY(Math.PI/180); sphere.rotateY(Math.PI/180); cube.rotateY(Math.PI/180); multi.rotateY(Math.PI/480); controls.update(frame); renderer.render(scene, camera); }); var geo = new THREE.CylinderGeometry(0,2,2,4,1, true); var pyramid1 = new THREE.Mesh(geo, new THREE.MeshPhongMaterial({color:0xff0000})); pyramid1.position.set(-2.5, -1, 0); ... var camera = new THREE.PerspectiveCamera( 45, 1024/500,0.1, 100); camera.position.z = 10; camera.position.y = 1; controls = new THREE.LeapCameraControls(camera); var multi = new THREE.Object3D(); pyramid1.castShadow = true; sphere.castShadow = true; ... var div = document.getElementById("shapecanvas2"); var renderer = new THREE.WebGLRenderer(); renderer.setSize(1024,500); renderer.setClearColor(0x000000, 1); renderer.shadowMapEnabled = true; div.appendChild( renderer.domElement ); } </script> </head> <body onload="draw3D();"> <span id="shapecanvas2" style="border: none;" width="1024" height="500"></span> <br/> </body> </html>
有整整一代計算機用戶從小就開始玩 3D 游戲,他們正準(zhǔn)備加入計算主流。他們渴望得到 3D 相關(guān)技術(shù)帶來的新穎的、創(chuàng)新的應(yīng)用程序,以提高生產(chǎn)率并改進他們的日常計算體驗。在這十年中,大量研究資金傾注到了大數(shù)據(jù)可視化技術(shù)、3D 掃描和 3D 打印中 — 也許這是歷史上第一次讓 3D 中的計算合法化。結(jié)合這種趨勢與當(dāng)今移動設(shè)備不斷增強的 3D 硬件渲染能力(以及能夠使用 WebGL 簡單而有效地駕馭這種能力),JavaScript 開發(fā)人員現(xiàn)在正處于一個令人興奮的浪潮的最前沿。
描述 | 名字 | 大小 |
---|---|---|
樣例代碼 | webGL3dl.zip | 339KB |
|