使用three.js實現機器人手臂的運動效果
Three.js 是一款運行在瀏覽器中的 3D 引擎,你可以用它創建各種三維場景,包括了攝影機、光影、材質等各種對象。你可以在它的主頁上看到許多精彩的演示。不過,這款引擎目前還處在比較不成熟的開發階段,其不夠豐富的 API 以及匱乏的文檔增加了初學者的學習難度(尤其是文檔的匱乏)three.js的代碼托管在github上面。——百度百科
Three.js封裝了OpenGl ES 2.0 的API,使得我們更容易在瀏覽器上開發各種圖形效果。比起使用winform,MFC等形式,這個代碼庫可以讓我們避開一些winform和MFC的細節問題,而專註於圖形的繪制。不過由於這個庫也並沒有封裝到非常完美,所以有些部分還需要我們自己來實現。
在此貼上Three.js中文文檔的連接地址Three.js 中文文檔 | 參考手冊 | 使用指南 | 動畫特效實例 | 踏得網
雖然這個鏈接上的文檔有一些錯誤,還有許多坑,很多東西都沒有說明清楚,不過,也是可以參考的。
如果要直接看效果,可以直接下載文章末尾的源代碼,附帶了操作方式。還需要使用電腦瀏覽器打開代碼,因為沒有對移動端做適配,所以移動端效果不好。
接下來,讓我們實現一個機器人的手臂運動效果。效果如下圖
很醜對不對,但這只是一個教程,所以美化可以自己來做,過多的美化工作反而會增加學習的復雜程度。
我們制作這個機器人,需要解決的一些問題是:
1.如何讓我們的圖形沿著任意軸轉動;
2.如何使用鍵盤控制其轉動;
這兩個問題解決了,那麽我們的機器人也就非常容易繪制了。接下來,就開始詳解代碼以及思路了。
首先,我們需要生成一個canvas,因為在html5中,所有的繪圖工作都是在canvas中進行。我們可以寫一個canvas標簽,也可以讓Three.js幫我們生成一個。這裏我們讓Three.js幫我們生成:
1 var scene = new THREE.Scene(); 2 var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);3 camera.position.z = 5; 4 camera.position.y = 1; 5 camera.position.x = 1.5; 6 camera.lookAt(new THREE.Vector3(0, 0, 0)); 7 scene.add(camera); 8 var renderer = new THREE.WebGLRenderer(); 9 renderer.setSize(window.innerWidth, window.innerHeight); 10 document.body.appendChild(renderer.domElement);
為什麽相機要進行位置的調整呢?這是因為,如果我們不調整位置,那麽我們的視角是正對著物體的,物體的3D效果會因為視角問題而顯得不那麽明顯。
接下來,我們要繪制機器人的頭,腿,身體。需要註意的是,如果我們為我們生成的圖形選擇了不合適的材質,會導致3D效果觀察不明顯。關於材質的選擇,已經超出了要講述的範圍,所以為了演示簡單,我們只使用基本材質。代碼如下:
1 //機器人身體 2 var geometry = new THREE.BoxGeometry(1, 1, 1, 8, 8, 8); 3 var material = new THREE.MeshBasicMaterial({ 4 color: 0x00ff00, 5 wireframe: true //這裏是為了讓其變成網格狀態,從而使其更容易觀察,默認為false 6 }); 7 var cube = new THREE.Mesh(geometry, material); 8 cube.position.y = -1; 9 //機器人頭部 10 var head = new THREE.SphereGeometry(0.5, 32, 32); 11 var material = new THREE.MeshBasicMaterial({ 12 color: 0x0909FD, 13 wireframe: true 14 }); 15 var sp = new THREE.Mesh(head, material); 16 //機器人雙腿 17 var geometry = new THREE.BoxGeometry(0.1, 1, 0.1); 18 var material = new THREE.MeshBasicMaterial({ 19 color: 0xFF2E67, 20 wireframe: true 21 }); 22 var left_leg = new THREE.Mesh(geometry, material); 23 var right_leg = new THREE.Mesh(geometry, material); 24 left_leg.position.x = -0.3; 25 left_leg.position.y = -2; 26 right_leg.position.x = 0.3; 27 right_leg.position.y = -2;
我們的機器人就畫成了這樣:
接下來,就到了最關鍵的地方:機器人手臂的轉動。很多剛學習的同學都發現,我們的圖形的旋轉軸是在圖形的正中心。什麽意思呢?看這個圖:
我在圖中已經標識了我們生成物體的坐標軸,可以看出,坐標軸實際上是在位置的重心。我們通過函數或者設置屬性的方法,只能繞著這三個軸轉。有同學問,可不可以通過先移位,再旋轉,再移回原位置的方式來解決這個問題呢?我們的計算機圖形學課本上是這麽教的,在winfoem和MFC上也是可以做到的,但由於three.js生成的物體的坐標軸的特殊性,我們無法通過這種形式來解決(親測)。有興趣的同學可以自己試一試。
那麽,我們該怎麽辦呢?我們查找手冊,發現three.js中有一個類,Object3D()。這個類生成一個3D的模型,也可以說是容器。那麽,我們可不可以通過生成一個容器,然後再將我們需要的圖形放入這個容器,再調整容器和圖形的位置來實現圖形繞任意軸旋轉呢?很顯然,是可以的,如果你能想到,說明你的智商還是值得稱道的。不過想不到也沒有任何問題,因為我們需要承認自己的平庸,才能激勵自己努力學習。接下來,我們來探討,如何進行旋轉操作。
首先,我們先生成一個容器對象,並且加入坐標軸顯示,看看我們生成的容器是什麽樣的
1 var upbox = new THREE.Object3D(); 2 scene.add(upbox); 3 upbox.add(new THREE.AxisHelper(3));
效果如下:
可以看出,雖然我們無法看到這個容器,但這個容器還是有其對應的坐標位置。我們可以通過設置其position屬性來移動它的位置,或者旋轉它。接下來,我們把我們生成的立方體放入這個容器,同時也顯示我們立方體的坐標軸。代碼如下:
1 var geometry = new THREE.BoxGeometry(1, 1, 1, 8, 8, 8); 2 var material = new THREE.MeshBasicMaterial({ 3 color: 0x00ff00, 4 wireframe: true //這裏是為了讓其變成網格狀態,從而使其更容易觀察,默認為false 5 }); 6 var cube = new THREE.Mesh(geometry, material); 7 cube.add(new THREE.AxisHelper(3)); 8 9 var upbox = new THREE.Object3D(); 10 scene.add(upbox); 11 upbox.add(new THREE.AxisHelper(3)); 12 13 var render = function() { 14 requestAnimationFrame(render); 15 upbox.add(cube); //將立方體放入容器 16 renderer.render(scene, camera); 17 }
效果如圖:
可以看到,新加入的這個立方體的坐標軸和這個容器的坐標軸重疊了。而此時,關鍵的來了,此時我們的立方體的位移已經不是相對於世界坐標了,而是相對於其父容器的坐標。也就是說,我們的立方體現在已經不是相對於世界坐標系的原點來移動了,而是相對於容器的坐標原點。由於截圖來表示世界坐標系不是很直觀,所以大家可以試著移動一下容器的位置,移動容器的位置,立方體的位置也會跟著容器的變化而變化。
接下來,我們來移動立方體的位置:
1 cube.position.x = 1; 2 cube.position.y = -1;
效果如下:
可以看出,我們的立方體已經移動了,而且是相對於容器的原點來移動的。那麽,我們來試一下旋轉我們的容器:
1 upbox.rotation.z = 1;
效果如下:
我們的立方體就跟著容器轉了起來。
那麽,我麽就可以得到一個結論:我們可以將我們的容器的坐標軸當作我們需要移動的坐標軸,然後把我們的圖形移動到適當的的位置,我們需要讓立方體按x=1旋轉,就把容器移動到x=1的位置;我們需要讓立方體按y=1轉,就把我們的容器移動到y=1的位置來旋轉。不過這樣也有一個問題,那就是我們的立方體的坐標已經不是相對於世界坐標了,這裏需要註意。之前說道,只要把立方體移到適當的位置就可以了,那麽這個適當的位置是哪裏呢?文字描述非常復雜,我們用代碼和效果來說明:
1 cube.add(new THREE.AxisHelper(1)); 2 cube.position.x = 0.5; 3 cube.position.y = 0.5; 4 cube.position.z = -0.5;
效果圖:
這個位置就可以說成是適當的位置,我們直接設置好容器的坐標,再讓容器旋轉,就達到了我們繞任意軸旋轉的功能。當然,按照個人的需求,這個適當位置是可以調整的。
如果我們需要實現復雜的控制,那麽我們就可以采用容器裏套容器的方法。這個和動畫中的骨骼非常像,不過也不一樣。接下來,我們就可以繼續接著之前的那個機器人來繪制我們的機器人手臂了。
1 //機器人上臂 2 var upbox = new THREE.Object3D(); 3 upbox.position.x = 0.5; 4 upbox.position.y = -1.0; 5 scene.add(upbox); 6 upbox.add(new THREE.AxisHelper(2)); 7 var geometry = new THREE.BoxGeometry(1, 0.1, 0.1); 8 var material = new THREE.MeshBasicMaterial({ 9 color: 0xFF2E67, 10 wireframe: true 11 } 12 13 ); 14 var ge = new THREE.Mesh(geometry, material); 15 ge.position.x = 0.5; 16 ge.position.y = 0; 17 //機器人下臂 18 var box = new THREE.Object3D(); 19 box.position.x = 1.0; 20 box.position.y = 0.0; 21 scene.add(box); 22 //box.add(new THREE.AxisHelper(2)); 23 var geometry = new THREE.BoxGeometry(0.1, 1, 0.1); 24 var material = new THREE.MeshBasicMaterial({ 25 color: 0xFF2E67, 26 wireframe: true 27 } 28 29 ); 30 var ge_next = new THREE.Mesh(geometry, material); 31 ge_next.position.y = 0.5; 32 box.add(ge_next); 33 //機器人手掌 34 var handbox = new THREE.Object3D(); 35 handbox.position.x = 0; 36 handbox.position.y = 0.0; 37 scene.add(handbox); 38 //handbox.add(new THREE.AxisHelper(2)); 39 var geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5, 4, 4, 4); 40 var material = new THREE.MeshBasicMaterial({ 41 color: 0xFF2E67, 42 wireframe: true 43 } 44 45 ); 46 var hand = new THREE.Mesh(geometry, material); 47 hand.position.y = 1.2; 48 //機器人中指 49 var zhongzhibox = new THREE.Object3D(); 50 zhongzhibox.position.x = 0; 51 zhongzhibox.position.y = 1.5; 52 scene.add(zhongzhibox); 53 //zhongzhibox.add(new THREE.AxisHelper(2)); 54 var geometry = new THREE.BoxGeometry(0.1, 0.5, 0.1); 55 var material = new THREE.MeshBasicMaterial({ 56 color: 0xFF2E67, 57 wireframe: true 58 } 59 60 ); 61 var zhongzhi = new THREE.Mesh(geometry, material); 62 zhongzhi.position.y = 0.2; 63 scene.add(cube); 64 scene.add(sp); 65 scene.add(ge); 66 scene.add(ge_next); 67 scene.add(hand); 68 scene.add(zhongzhi); 69 scene.add(left_leg); 70 scene.add(right_leg);
其中,也有容器中嵌套容器的操作。
那麽,就該解決第二個問題了。我們如何通過鍵盤控制我們的機器人手臂呢?
很簡單,使用事件綁定。我們可以給onkeydown這個事件綁定操作,來實現控制機器人的手臂。類似於:
1 onkeydown = function(event) { 2 if(angle > 0 || angle <= -1.5) { 3 angle = 0; 4 return false; 5 } 6 else if(event.keyCode == 38) { 7 angle -= 0.2; 8 } 9 }
為什麽有一個angle呢?因為我們需要判斷我們的機器人的手臂運動的角度,畢竟是手臂,轉動的角度需要有一個限制對吧?但是呢,由於比較復雜(其實是懶),所以我只實現了兩個部位的限制。
實現了上述的代碼,我們應該就可以畫出一個可以控制的機器人了。
下面是源代碼:
機器人手臂源代碼
使用three.js實現機器人手臂的運動效果