我在《WebGL簡易教程(五):圖形變換(模型、視圖、投影變換)》這篇博文裏詳細講解了OpenGL\WebGL關於繪製場景的圖形變換過程,並推導了相應的模型變換矩陣、視圖變換矩陣以及投影變換矩陣。這裏我就經過three.js這個圖形引擎,驗證一下其推導是否正確,順便學習下three.js是如何進行圖形變換的。javascript
three.js已經提供了向量類和矩陣類,定義而且查看一個4階矩陣類:css
var m = new THREE.Matrix4(); m.set(11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34, 41, 42, 43, 44); console.log(m);
輸出結果:
html
說明THREE.Matrix4內部是列主序存儲的,而咱們理論描述的矩陣都爲行主序。java
在場景中新建一個平面:web
// create the ground plane var planeGeometry = new THREE.PlaneGeometry(60, 20); var planeMaterial = new THREE.MeshBasicMaterial({ color: 0xAAAAAA }); var plane = new THREE.Mesh(planeGeometry, planeMaterial); // add the plane to the scene scene.add(plane);
three.js中場景節點的基類都是Object3D,Object3D包含了3種矩陣對象:app
平移這個mesh:dom
plane.position.set(15, 8, -10);
根據推導獲得平移矩陣爲:異步
輸出這個Mesh:
學習
繞X軸旋轉:webgl
plane.rotation.x = THREE.Math.degToRad(30);
對應的旋轉矩陣:
輸出信息:
繞Y軸旋轉:
plane.rotation.y = THREE.Math.degToRad(30);
對應的旋轉矩陣:
輸出信息:
繞Z軸旋轉:
plane.rotation.z = THREE.Math.degToRad(30);
對應的旋轉矩陣:
輸出信息:
在場景中新建一個Camera:
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
這裏建立了一個透視投影的相機,通常創建的都是對稱的透視投影,推導的透視投影矩陣爲:
爲了驗證其推導是否正確,輸出這個camera,查看projectionMatrix,也就是透視投影矩陣:
經過Camera能夠設置視圖矩陣:
camera.position.set(0, 0, 100); //相機的位置 camera.up.set(0, 1, 0); //相機以哪一個方向爲上方 camera.lookAt(new THREE.Vector3(1, 2, 3)); //相機看向哪一個座標
根據《WebGL簡易教程(五):圖形變換(模型、視圖、投影變換)》中的描述,能夠經過three.js的矩陣運算來推導其視圖矩陣:
var eye = new THREE.Vector3(0, 0, 100); var up = new THREE.Vector3(0, 1, 0); var at = new THREE.Vector3(1, 2, 3); var N = new THREE.Vector3(); N.subVectors(eye, at); N.normalize(); var U = new THREE.Vector3(); U.crossVectors(up, N); U.normalize(); var V = new THREE.Vector3(); V.crossVectors(N, U); V.normalize(); var R = new THREE.Matrix4(); R.set(U.x, U.y, U.z, 0, V.x, V.y, V.z, 0, N.x, N.y, N.z, 0, 0, 0, 0, 1); var T = new THREE.Matrix4(); T.set(1, 0, 0, -eye.x, 0, 1, 0, -eye.y, 0, 0, 1, -eye.z, 0, 0, 0, 1); var V = new THREE.Matrix4(); V.multiplyMatrices(R, T); console.log(V);
其推導公式以下:
最後輸出它們的矩陣值:
二者的計算結果基本時一致的。須要注意的是Camera中表達視圖矩陣的成員變量是Camera.matrixWorldInverse。它的邏輯應該是視圖矩陣與模型矩陣互爲逆矩陣,模型矩陣也能夠稱爲世界矩陣,那麼世界矩陣的逆矩陣就是視圖矩陣了。
能夠經過給着色器傳值來驗證計算的模型視圖投影矩陣(如下稱MVP矩陣)是否正確。對於一個任何事情都不作的着色器來講:
vertexShader: ` void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }` , fragmentShader: ` void main() { gl_FragColor = vec4(0.556, 0.0, 0.0, 1.0) }`
projectionMatrix和modelViewMatrix分別是three.js中內置的投影矩陣和模型視圖矩陣。那麼能夠作一個簡單的驗證工做,將計算獲得的MVP矩陣傳入到着色器中,代替這兩個矩陣,若是最終獲得的值是正確的,那麼就說明計算的MVP矩陣是正確的。
實例代碼以下:
<!DOCTYPE html> <html> <head> <title>Example 01.01 - Basic skeleton</title> <meta charset="UTF-8" /> <script type="text/javascript" charset="UTF-8" src="../three/three.js"></script> <script type="text/javascript" charset="UTF-8" src="../three/controls/TrackballControls.js"></script> <script type="text/javascript" charset="UTF-8" src="../three/libs/stats.min.js"></script> <script type="text/javascript" charset="UTF-8" src="../three/libs/util.js"></script> <script type="text/javascript" src="MatrixDemo.js"></script> <link rel="stylesheet" href="../css/default.css"> </head> <body> <!-- Div which will hold the Output --> <div id="webgl-output"></div> <!-- Javascript code that runs our Three.js examples --> <script type="text/javascript"> (function () { // contains the code for the example init(); })(); </script> </body> </html>
'use strict'; THREE.StretchShader = { uniforms: { "sw" : {type:'b', value : false}, "mvpMatrix" : {type:'m4',value:new THREE.Matrix4()} }, // vertexShader: ` uniform mat4 mvpMatrix; uniform bool sw; void main() { if(sw) { gl_Position = mvpMatrix * vec4( position, 1.0 ); }else{ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); } }` , // fragmentShader: ` uniform bool sw; void main() { if(sw) { gl_FragColor = vec4(0.556, 0.0, 0.0, 1.0); }else { gl_FragColor = vec4(0.556, 0.8945, 0.9296, 1.0); } }` }; function init() { //console.log("Using Three.js version: " + THREE.REVISION); // create a scene, that will hold all our elements such as objects, cameras and lights. var scene = new THREE.Scene(); // create a camera, which defines where we're looking at. var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); // position and point the camera to the center of the scene camera.position.set(0, 0, 100); //相機的位置 camera.up.set(0, 1, 0); //相機以哪一個方向爲上方 camera.lookAt(new THREE.Vector3(1, 2, 3)); //相機看向哪一個座標 // create a render and set the size var renderer = new THREE.WebGLRenderer(); renderer.setClearColor(new THREE.Color(0x000000)); renderer.setSize(window.innerWidth, window.innerHeight); // add the output of the renderer to the html element document.getElementById("webgl-output").appendChild(renderer.domElement); // create the ground plane var planeGeometry = new THREE.PlaneGeometry(60, 20); // var planeMaterial = new THREE.MeshBasicMaterial({ // color: 0xAAAAAA // }); var planeMaterial = new THREE.ShaderMaterial({ uniforms: THREE.StretchShader.uniforms, vertexShader: THREE.StretchShader.vertexShader, fragmentShader: THREE.StretchShader.fragmentShader }); var plane = new THREE.Mesh(planeGeometry, planeMaterial); // add the plane to the scene scene.add(plane); // rotate and position the plane plane.position.set(15, 8, -10); plane.rotation.x = THREE.Math.degToRad(30); plane.rotation.y = THREE.Math.degToRad(45); plane.rotation.z = THREE.Math.degToRad(60); render(); var farmeCount = 0; function render() { var mvpMatrix = new THREE.Matrix4(); mvpMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); mvpMatrix.multiplyMatrices(mvpMatrix, plane.matrixWorld); THREE.StretchShader.uniforms.mvpMatrix.value = mvpMatrix; if(farmeCount % 60 === 0){ THREE.StretchShader.uniforms.sw.value = !THREE.StretchShader.uniforms.sw.value; } farmeCount = requestAnimationFrame(render); renderer.render(scene, camera); } }
這段代碼的意思是,給着色器傳入了計算好的MVP矩陣變量mvpMatrix,以及一個開關變量sw。開關變量會每60幀變一次,若是爲假,會使用內置的projectionMatrix和modelViewMatrix來計算頂點值,此時場景中的物體顏色會顯示爲藍色;若是開關變量爲真,則會使用傳入的計算好的mvpMatrix計算頂點值,此時場景中的物體顏色會顯示爲紅色。運行截圖以下:
能夠看到場景中的物體的顏色在紅色與藍色之間來回切換,且物體位置沒有任何變化,說明咱們計算的MVP矩陣是正確的。
在使用JS的console.log()進行打印camera對象的時候,會發現若是不調用render()的話(或者單步調式),其內部的matrix相關的成員變量仍然是初始化的值,得不到想要的結果。而console.log()能夠認爲是異步的,調用render()以後,就能夠獲得正確的camera對象了。