本文是一篇簡單的webGL+threejs構建web三維視圖的入門教程,你可以了解到利用threejs創建簡單的三維圖形,並且控制圖形運動。若有不足,歡迎指出。
本文使用的框架是three.js
github地址:https://github.com/mrdoob/three.js, 官網:http://threejs.org/, 文檔:http://threejs.org/docs/, 本文中的示例已上傳github,地址:https://github.com/RizzleCi/three.js-demo一、創建場景
我們所見的視圖由兩個部分共同創建,scene和camera。首先定義一個場景:
var scene = new THREE.Scene();
然后定義一個相機:
var camera = new THREE.PerspectiveCamera( 90, width/height, 0.1, 1000 );
等等,定義相機需要視窗的長寬。現在我要讓我的繪圖顯示在頁面的一個區域(<div>)標簽中。我們來選中這個元素,獲取它的長寬。
var container = document.getElementById('canvasdiv'); var width = canvasdiv.clientWidth; var height = canvasdiv.clientHeight;
這樣再加上前面的一行代碼,我們就完成了對相機的定義。然后把相機位置設定在z軸上方便觀察。
camera.position.set(0,0,10)
現在我們需要一個渲染器把定義好的場景渲染出來。
var renderer = new THREE.WebGLRenderer();
給這個渲染器合適的大小。
renderer.setSize( width, height );
然后將其加入到dom中。
canvasdiv.appendChild( renderer.domElement );
(運行以后發現這其實就是一個canvas元素。其實我們也可以在html中創建canvas元素再將renderer綁定到它上面。
var renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('mainCanvas') });
)
最后進行渲染。
renderer.render(scene,camera);
這樣,就建立了一個簡單的3d場景。
二、繪制圖形
我將threejs中的物體理解為模型+材料。以一個長方體為例。
創建模型:
var geometry = new THREE.BoxGeometry( 1,2,1 );
定義材料:
var material = new THREE.MeshBasicMaterial( { color: 0x645d50 } );
有了這兩者,我們就可以構建一個長方體方塊了。
var cube = new THREE.Mesh( geometry, material );
我們將其添加到場景中顯示。
scene.add( cube );
這樣,一個三維的長方體就繪制完成了
關於其他形狀的繪制,張雯莉的threejs入門指南中介紹的很詳細,在此不多贅述。
這部分的完整代碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> #canvasdiv{ width: 600px; height: 600px; } </style> <script src="js/three.min.js"></script> </head> <body> <div class="main-content" id="canvasdiv"> </div> <script type="text/javascript" > var container = document.getElementById('canvasdiv') var scene = new THREE.Scene(); var width = canvasdiv.clientWidth; var height = canvasdiv.clientHeight; var camera = new THREE.PerspectiveCamera( 90, width/height, 0.1, 1000 ); camera.position.set(0,0,10); var renderer = new THREE.WebGLRenderer(); renderer.setSize( width, height ); canvasdiv.appendChild( renderer.domElement ); var geometry = new THREE.BoxGeometry( 2,1,1 ); var material = new THREE.MeshBasicMaterial( { color: 0x645d50 } ); var cube = new THREE.Mesh( geometry, material ); scene.add( cube ); renderer.render(scene,camera); </script> </body> </html>
看上去它只是一個長方形而已,但是它確實是一個立體圖形,你可以改變一下camera的位置來觀察一下。
camera.position.set( 5,3,10 );
好了,這樣看起來是一個立體的長方體了吧。
三、創建3d對象
繪制對象
大多數時候,我們需要講繪制的圖形整合到一起進行控制。此時,我們便需要一個3d對象。
在上面繪制的那個長方體上面再放一個球。
var geometry = new THREE.SphereGeometry( 0.5,100,100 ); var material = new THREE.MeshBasicMaterial( { color: 0xb9c16c } ); var ball = new THREE.Mesh( geometry,material ); ball.position.set( 0,0,1 ); scene.add(ball);
另說一句,默認放置mesh的位置是( 0,0,0 ),和改變相機位置一樣,我們可以用ball.position.set方法來改變圖形或對象的位置。因此動畫也利用這個方法來實現。
然后要把它們整合成一個對象。
首先我們創建一個對象。
var myobj = new THREE.Object3D();
然后把我們畫的圖形添加到對象里就ok啦。
myobj.add( cube );
myobj.add( ball );
這時候我們已經有了一個3d對象,它包含我們剛剛繪制的長方形和球。於是就沒有必要像原來那樣把圖形一個一個地放置到場景里,只需要把剛剛創建的對象放置到場景里。
scene.add( myobj );
完整代碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> #canvasdiv{ width: 600px; height: 600px; } </style> <script src="js/three.min.js"></script> </head> <body> <div class="main-content" id="canvasdiv"> </div> <script type="text/javascript" > var container = document.getElementById('canvasdiv') var scene = new THREE.Scene(); var width = canvasdiv.clientWidth; var height = canvasdiv.clientHeight; var camera = new THREE.PerspectiveCamera( 90, width/height, 0.1, 1000 ); camera.position.set( 0,0,10 ) var renderer = new THREE.WebGLRenderer(); renderer.setSize( width, height ); canvasdiv.appendChild( renderer.domElement ); var geometry = new THREE.BoxGeometry( 2,1,1 ); var material = new THREE.MeshBasicMaterial( { color: 0x645d50 } ); var cube = new THREE.Mesh( geometry, material ); //scene.add( cube ); var geometry = new THREE.SphereGeometry( 0.5,100,100 ); var material = new THREE.MeshBasicMaterial( { color: 0xb9c16c } ); var ball = new THREE.Mesh( geometry,material ); ball.position.set( 0,0,1 ); //scene.add(ball); var myobj = new THREE.Object3D(); myobj.add( cube ); myobj.add( ball ); scene.add( myobj ); renderer.render(scene,camera); </script> </body> </html>
顯示效果:
外部導入.obj文件
threejs支持從外部導入.obj文件,聽說這種文件是用3DsMax繪制的,用PS也可以編輯。我們需要引入OBJMTLLoader.js,MTLLoader.js文件;也有一個OBJLoader.js,但利用這個庫只能導入模型而不能導入繪制obj時添加的材質,個人感覺不是非常實用,就不做介紹了。這時候,我們需要把文件們放到一個服務器上,否則會出現跨域問題。
為了讓圖像更明顯,我們添加一些光線。
scene.add( new THREE.AmbientLight( 0xffffff ) );
這里,我們通過導入圖片來設置這個對象的紋理。
var texture = new THREE.Texture(); var loader = new THREE.ImageLoader( ); loader.load( 'tank.jpg', function ( image ) { texture.image = image; texture.needsUpdate = true; } );
開始導入我們的3D對象!
var loader = new THREE.OBJMTLLoader(); loader.load('tank.obj','tank.mtl',function(object){ tank = object; object.traverse(function(child){ if (child instanceof THREE.Mesh){ //將貼圖賦於材質 child.material.map = texture; child.material.transparent = true; } }); object.position.set(0,0,0); scene.add( object ); camera.lookAt( object.position ); renderer.render( scene,camera ); });
模型導入進去了,但是看起來還是很奇怪,這就要我們加一些其他光線渲染一下。在這里我們添加平行光線。
var directionalLight = new THREE.DirectionalLight( 0xffffff, 1.5 ); directionalLight.position.set( 1, 1, 1 ); scene.add( directionalLight );
變得光澤多了。
從外部導入obj的完整代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> #canvasdiv{ width: 1200px; height: 800px; } </style> <script src="js/three.min.js"></script> <script src="js/MTLLoader.js"></script> <script src="js/OBJMTLLoader.js"></script> </head> <body> <div class="main-content" id="canvasdiv"> </div> <script type="text/javascript" > var container = document.getElementById('canvasdiv') var scene = new THREE.Scene(); var width = canvasdiv.clientWidth; var height = canvasdiv.clientHeight; var camera = new THREE.PerspectiveCamera( 90, width/height, 0.1, 1000 ); camera.position.set( -10,10,10); var renderer = new THREE.WebGLRenderer(); renderer.setSize( width, height ); canvasdiv.appendChild( renderer.domElement ); var texture = new THREE.Texture(); var loader = new THREE.ImageLoader( ); loader.load( 'tank.jpg', function ( image ) { texture.image = image; texture.needsUpdate = true; } ); var loader = new THREE.OBJMTLLoader(); loader.load('tank.obj','tank.mtl',function(object){ tank = object; object.traverse(function(child){ if (child instanceof THREE.Mesh){ //將貼圖賦於材質 child.material.map = texture; //重點,沒有該句會導致PNG無法正確顯示透明效果 child.material.transparent = true; } }); object.position.set(0,0,0); scene.add( object ); camera.lookAt( object.position ); renderer.render( scene,camera ); }); scene.add( new THREE.AmbientLight( 0xffffff ) ); var directionalLight = new THREE.DirectionalLight( 0xffffff, 1.5 ); directionalLight.position.set( 1, 1, 1 ) scene.add( directionalLight ); renderer.render(scene,camera); </script> </body> </html>
四、動畫
現在我們想辦法讓這些圖形動起來。
在threejs中運用最多的動畫是用requestAnimationFrame()方法。也可以利用傳統的setInterval()做,但用這個會掉幀。
這里我們做一個render函數,來進行渲染和動畫調用。這里以前面添加了myobj對象的代碼為基礎。
現在來不斷地改變對象的角度方便對其進行觀察。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> #canvasdiv{ width: 600px; height: 600px; } </style> <script src="js/three.min.js"></script> </head> <body> <div class="main-content" id="canvasdiv"> </div> <script type="text/javascript" > var container,camera,scene,renderer,myobj; var init = function () { container = document.getElementById('canvasdiv') scene = new THREE.Scene(); var width = canvasdiv.clientWidth; var height = canvasdiv.clientHeight; camera = new THREE.PerspectiveCamera( 90, width/height, 0.1, 1000 ); camera.position.set( 0,0,10 ) renderer = new THREE.WebGLRenderer(); renderer.setSize( width, height ); canvasdiv.appendChild( renderer.domElement ); var geometry = new THREE.BoxGeometry( 2,1,1 ); var material = new THREE.MeshBasicMaterial( { color: 0x645d50 } ); var cube = new THREE.Mesh( geometry, material ); //scene.add( cube ); var geometry = new THREE.SphereGeometry( 0.5,100,100 ); var material = new THREE.MeshBasicMaterial( { color: 0xb9c16c } ); var ball = new THREE.Mesh( geometry,material ); ball.position.set( 0,0,1 ); //scene.add(ball); myobj = new THREE.Object3D(); myobj.add( cube ); myobj.add( ball ); scene.add( myobj ); } var render = function () { requestAnimationFrame( render ); myobj.rotation.x+=0.01; myobj.rotation.y+=0.01; renderer.render(scene,camera); } init() render() </script> </body> </html>
接着讓我們對動畫進行控制。
在我實現的項目中,是通過websocket連接后台傳入參數來控制對象運動的,這里就介紹一下使用參數控制吧。
這里有一個問題,就是requestAnimationFrame回調的函數不能帶有參數,否則會出現奇怪的bug。所以我選擇用一個全局對象來進行控制。
var control={ s:0, p:0, q:0, j:0, }
這里s是運動的速度,p,q,j分別是myobj將要運動到的位置的x,y,z坐標。我們先寫一個控制它在x軸上運動的函數:
var run = function () { if ( myobj.position.x<control.p ) { myobj.position.x += control.s; requestAnimationFrame( run ); renderer.render( scene,camera ) }; if ( myobj.position.x>control.p ) { myobj.position.x -= control.s; requestAnimationFrame( run ); renderer.render( scene,camera ) }; }
再在render函數中添加對run的調用requestAnimationFrame( run )。這樣就可以在命令行中改變對象control的值實現控制myobj的運動。但是在運動停止后我的瀏覽器為什么會變得很卡,而且運動速度回有變化。我還不知道原因。不知道有沒有朋友和我有同樣的問題。於是我把函數拆成了兩個,這樣瀏覽器性能好些。
var run = function () { if ( myobj.position.x<control.p ) { myobj.position.x += control.s; requestAnimationFrame( run ); }; renderer.render( scene,camera ) } var runx = function () { if ( myobj.position.x>control.p ) { myobj.position.x -= control.s; requestAnimationFrame( runx ); }; renderer.render( scene,camera ) }
同樣的,也可以寫出在y,z軸上運動的函數。
在x軸上運動的完整代碼:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> #canvasdiv{ width: 600px; height: 600px; } </style> <script src="js/three.min.js"></script> </head> <body> <div class="main-content" id="canvasdiv"> </div> <script type="text/javascript" > var container,camera,scene,renderer,myobj; var init = function () { container = document.getElementById('canvasdiv') scene = new THREE.Scene(); var width = canvasdiv.clientWidth; var height = canvasdiv.clientHeight; camera = new THREE.PerspectiveCamera( 90, width/height, 0.1, 1000 ); camera.position.set( 0,0,10 ) renderer = new THREE.WebGLRenderer(); renderer.setSize( width, height ); canvasdiv.appendChild( renderer.domElement ); var geometry = new THREE.BoxGeometry( 2,1,1 ); var material = new THREE.MeshBasicMaterial( { color: 0x645d50 } ); var cube = new THREE.Mesh( geometry, material ); //scene.add( cube ); var geometry = new THREE.SphereGeometry( 0.5,100,100 ); var material = new THREE.MeshBasicMaterial( { color: 0xb9c16c } ); var ball = new THREE.Mesh( geometry,material ); ball.position.set( 0,0,1 ); //scene.add(ball); myobj = new THREE.Object3D(); myobj.add( cube ); myobj.add( ball ); scene.add( myobj ); } var control={ s:0, p:0, q:0, j:0, } var run = function () { if ( myobj.position.x<control.p ) { myobj.position.x += control.s; requestAnimationFrame( run ); }; renderer.render( scene,camera ) } var runx = function () { if ( myobj.position.x>control.p ) { myobj.position.x -= control.s; requestAnimationFrame( runx ); }; renderer.render( scene,camera ) } var render = function () { requestAnimationFrame( run ); requestAnimationFrame( runx ); renderer.render(scene,camera); } init() render() </script> </body> </html>
這個入門教程就到這里了,感謝閱讀。