攝像機、投影、3D旋轉、縮放
2011-09-23 08:18 【當耐特】 閱讀(20841) 評論(64) 收藏 舉報簡述
3D效果分兩種,一種是偽3D骨架,一種是3D實體.
3D骨架:是通過大量的計算將3D世界中所有點投影到二維平面中。
3D實體:通過攝像機向投影面發射射線與世界中的物體交匯,把與物體交匯點的顏色渲染到投影面 (光線追蹤的基礎) 。本系列的所有演示都是3D骨架,非3D實體。本文將穿插圖片、公式、代碼、演示,讓讀者深刻理解3D的基本概念極其思想。
對象及概念介紹
對象一:攝像機。
大家都有一個基本常識,在不同的角度觀看到的物體是不同的。攝像機對象有自己的空間的坐標(vidiconX,vidiconY,vidiconZ)。
對象二:顯示屏
任何三維物體,都會以二維的形式投影在顯示屏上,顯示屏垂直于攝像機的觀測方向,所以攝像機的空間坐標變化,會導致顯示屏的坐標系的變換。
對象三:被觀察測物體
任何物體都是有無數個點構成,每個點有自己的空間坐標(x,y,z),顯示屏介于攝像機和物體之間。
為了降低復雜度,本文將顯示屏和被觀測物體所處的坐標系公用一套(x,y),所有的旋轉都是物體旋轉,攝像機不動!
縮放原理:攝像機不動,被觀察測物體不動,顯示屏離攝像機越近,縮放比例越小,顯示屏離攝像機越遠,縮放比例越大。
投影分析
我們來看下面這張圖:
因為,我們將顯示屏和被觀測物體共用一個坐標系,所以,我們可以計算出點(x1,y1,z1)投影到顯示屏上的點的縮放比例為:
h / Math.abs(vidiconZ - z1)
所以投影后的坐標為:
x = x1 * h / Math.abs(vidiconZ - z);
y= y1 * h / Math.abs(vidiconZ - z);
有了以上這些知識,我們可以輕松的在Canvas里畫一個正方體(再次強調,是根據計算的結果畫,非人類經驗)。
<canvas id="myCanvas" width="700" height="500" style="border: 1px solid #c3c3c3;"> Your browser does not support the canvas element. </canvas> <script type="text/javascript"> var c = document.getElementById("myCanvas"); var cxt = c.getContext("2d"); cxt.lineWidth = 3; //正方體8個頂點 var Point1 = { x: 100, y: 100, z: 100 }; var Point2 = { x: 100, y: 100, z: -100 }; var Point3 = { x: -100, y: 100, z: -100 }; var Point4 = { x: -100, y: 100, z: 100 }; var Point_1 = { x: 100, y: -100, z: 100 }; var Point_2 = { x: 100, y: -100, z: -100 }; var Point_3 = { x: -100, y: -100, z: -100 }; var Point_4 = { x: -100, y: -100, z: 100 }; var startX = 250; var startY = 250; //攝像機到顯示屏的距離 var distance = 500; //攝像機位置 var eyePosition = { x: 0, y: 0, z: 700 }; function changeDistance() { Point1.x = Point1.x * distance / Math.abs(eyePosition.z - Point1.z); Point1.y = Point1.y * distance / Math.abs(eyePosition.z - Point1.z); Point2.x = Point2.x * distance / Math.abs(eyePosition.z - Point2.z); Point2.y = Point2.y * distance / Math.abs(eyePosition.z - Point2.z); Point3.x = Point3.x * distance / Math.abs(eyePosition.z - Point3.z); Point3.y = Point3.y * distance / Math.abs(eyePosition.z - Point3.z); Point4.x = Point4.x * distance / Math.abs(eyePosition.z - Point4.z); Point4.y = Point4.y * distance / Math.abs(eyePosition.z - Point4.z); Point_1.x = Point_1.x * distance / Math.abs(eyePosition.z - Point_1.z); Point_1.y = Point_1.y * distance / Math.abs(eyePosition.z - Point_1.z); Point_2.x = Point_2.x * distance / Math.abs(eyePosition.z - Point_2.z); Point_2.y = Point_2.y * distance / Math.abs(eyePosition.z - Point_2.z); Point_3.x = Point_3.x * distance / Math.abs(eyePosition.z - Point_3.z); Point_3.y = Point_3.y * distance / Math.abs(eyePosition.z - Point_3.z); Point_4.x = Point_4.x * distance / Math.abs(eyePosition.z - Point_4.z); Point_4.y = Point_4.y * distance / Math.abs(eyePosition.z - Point_4.z); } var drawCube = function () { changeDistance(); cxt.beginPath(); cxt.moveTo(startX + Point1.x, startY - Point1.y); cxt.lineTo(startX + Point2.x, startY - Point2.y); cxt.lineTo(startX + Point3.x, startY - Point3.y); cxt.lineTo(startX + Point4.x, startY - Point4.y); cxt.lineTo(startX + Point1.x, startY - Point1.y); cxt.moveTo(startX + Point_1.x, startY - Point_1.y); cxt.lineTo(startX + Point_2.x, startY - Point_2.y); cxt.lineTo(startX + Point_3.x, startY - Point_3.y); cxt.lineTo(startX + Point_4.x, startY - Point_4.y); cxt.lineTo(startX + Point_1.x, startY - Point_1.y); cxt.moveTo(startX + Point2.x, startY - Point2.y); cxt.lineTo(startX + Point_2.x, startY - Point_2.y); cxt.moveTo(startX + Point1.x, startY - Point1.y); cxt.lineTo(startX + Point_1.x, startY - Point_1.y); cxt.moveTo(startX + Point3.x, startY - Point3.y); cxt.lineTo(startX + Point_3.x, startY - Point_3.y); cxt.moveTo(startX + Point4.x, startY - Point4.y); cxt.lineTo(startX + Point_4.x, startY - Point_4.y); cxt.stroke(); } </script> <div id="show"> </div> <input type="button" onclick="drawCube();" value="開始畫立方體" style="width: 135px" />
演示
當然我們可以重構一下,將8個點都放到Array中。
<script type="text/javascript"> var c = document.getElementById("myCanvas"); var cxt = c.getContext("2d"); cxt.lineWidth = 3; //正方體8個頂點 var Point = new Array(); Point[0] = { x: 100, y: 100, z: 100 }; Point[1] = { x: 100, y: 100, z: -100 }; Point[2] = { x: -100, y: 100, z: -100 }; Point[3] = { x: -100, y: 100, z: 100 }; Point[4] = { x: 100, y: -100, z: 100 }; Point[5] = { x: 100, y: -100, z: -100 }; Point[6] = { x: -100, y: -100, z: -100 }; Point[7] = { x: -100, y: -100, z: 100 }; var startX = 250; var startY = 250; //攝像機到顯示屏的距離 var distance = 500; //攝像機位置 var eyePosition = { x: 0, y: 0, z: 700 }; function changeDistance() { for (var i = 0; i < Point.length; i++) { Point[i].x = Point[i].x * distance / Math.abs(eyePosition.z - Point[i].z); Point[i].y = Point[i].y * distance / Math.abs(eyePosition.z - Point[i].z); } } var drawCube = function () { changeDistance(); cxt.beginPath(); cxt.moveTo(startX + Point[0].x, startY - Point[0].y); cxt.lineTo(startX + Point[1].x, startY - Point[1].y); cxt.lineTo(startX + Point[2].x, startY - Point[2].y); cxt.lineTo(startX + Point[3].x, startY - Point[3].y); cxt.lineTo(startX + Point[0].x, startY - Point[0].y); cxt.moveTo(startX + Point[4].x, startY - Point[4].y); cxt.lineTo(startX + Point[5].x, startY - Point[5].y); cxt.lineTo(startX + Point[6].x, startY - Point[6].y); cxt.lineTo(startX + Point[7].x, startY - Point[7].y); cxt.lineTo(startX + Point[4].x, startY - Point[4].y); cxt.moveTo(startX + Point[1].x, startY - Point[1].y); cxt.lineTo(startX + Point[5].x, startY - Point[5].y); cxt.moveTo(startX + Point[0].x, startY - Point[0].y); cxt.lineTo(startX + Point[4].x, startY - Point[4].y); cxt.moveTo(startX + Point[2].x, startY - Point[2].y); cxt.lineTo(startX + Point[6].x, startY - Point[6].y); cxt.moveTo(startX + Point[3].x, startY - Point[3].y); cxt.lineTo(startX + Point[7].x, startY - Point[7].y); cxt.stroke(); } </script>
現在,我們看到了正方體正常的顯示在畫布當中,那么我們現在來用演示證明一下縮放原理
縮放原理:攝像機不動,被觀察測物體不動,顯示屏離攝像機越近,縮放比例越小,顯示屏離攝像機越遠,縮放比例越大。
<script language="javascript" type="text/javascript" src="lib/uglifyjs-parser.js"></script> <script language="javascript" type="text/javascript" src="src/jscex.js"></script> <script language="javascript" type="text/javascript" src="src/jscex.builderBase.js"></script> <script language="javascript" type="text/javascript" src="src/jscex.async.js"></script> <!--[if IE]> <script language="javascript" type="text/javascript" src="lib/json2.js"></script> <script language="javascript"> Jscex.config.codeGenerator = function (code) { return "false || " + code; } </script> <![endif]--> <canvas id="myCanvas" width="700" height="500" style="border: 1px solid #c3c3c3;"> Your browser does not support the canvas element. </canvas> <script type="text/javascript"> var c = document.getElementById("myCanvas"); var cxt = c.getContext("2d"); cxt.lineWidth = 3; var Point = new Array(); var startX = 250; var startY = 250; var distance = 500; var eyePosition = { x: 0, y: 0, z: 700 }; function init() { Point[0] = { x: 100, y: 100, z: 100 }; Point[1] = { x: 100, y: 100, z: -100 }; Point[2] = { x: -100, y: 100, z: -100 }; Point[3] = { x: -100, y: 100, z: 100 }; Point[4] = { x: 100, y: -100, z: 100 }; Point[5] = { x: 100, y: -100, z: -100 }; Point[6] = { x: -100, y: -100, z: -100 }; Point[7] = { x: -100, y: -100, z: 100 }; } function changeDistance() { for (var i = 0; i < Point.length; i++) { Point[i].x = Point[i].x * distance / Math.abs(eyePosition.z - Point[i].z); Point[i].y = Point[i].y * distance / Math.abs(eyePosition.z - Point[i].z); } } var drawCube = function (increment) { cxt.clearRect(0, 0, 1200, 1200); init(); distance += increment; changeDistance(); cxt.beginPath(); cxt.moveTo(startX + Point[0].x, startY - Point[0].y); cxt.lineTo(startX + Point[1].x, startY - Point[1].y); cxt.lineTo(startX + Point[2].x, startY - Point[2].y); cxt.lineTo(startX + Point[3].x, startY - Point[3].y); cxt.lineTo(startX + Point[0].x, startY - Point[0].y); cxt.moveTo(startX + Point[4].x, startY - Point[4].y); cxt.lineTo(startX + Point[5].x, startY - Point[5].y); cxt.lineTo(startX + Point[6].x, startY - Point[6].y); cxt.lineTo(startX + Point[7].x, startY - Point[7].y); cxt.lineTo(startX + Point[4].x, startY - Point[4].y); cxt.moveTo(startX + Point[1].x, startY - Point[1].y); cxt.lineTo(startX + Point[5].x, startY - Point[5].y); cxt.moveTo(startX + Point[0].x, startY - Point[0].y); cxt.lineTo(startX + Point[4].x, startY - Point[4].y); cxt.moveTo(startX + Point[2].x, startY - Point[2].y); cxt.lineTo(startX + Point[6].x, startY - Point[6].y); cxt.moveTo(startX + Point[3].x, startY - Point[3].y); cxt.lineTo(startX + Point[7].x, startY - Point[7].y); cxt.stroke(); } var reduceDrawCubeAsync = eval(Jscex.compile("async", function () { //當攝像機到顯示屏的距離大于750,退出循環· while (distance < 750) { drawCube(10); $await(Jscex.Async.sleep(100)); } })); var magnifyDrawCubeAsync = eval(Jscex.compile("async", function () { //當攝像機到顯示屏的距離小于150,退出循環· while (distance > 150) { drawCube(-10); $await(Jscex.Async.sleep(100)); } })); var executeAsync = eval(Jscex.compile("async", function () { $await(reduceDrawCubeAsync()); $await(magnifyDrawCubeAsync()); })); </script> <div id="show"> </div> <input type="button" onclick="executeAsync().start();" value="開始移動顯示屏" style="width: 135px" />
可以看到,我們定義了兩個異步任務reduceDrawCubeAsync 和magnifyDrawCubeAsync ,把它們放到executeAsync 隊列當中,
他們會從上倒下,依次執行。
演示
3D旋轉
上面講了攝像機,投影以及縮放的原理以及實現,下面看旋轉。
首先,在三維坐標系當中,任何角度的任何旋轉可以拆分成三類:
a.繞X軸方向的旋轉,此時,y和z發生變化,x不變。
b.繞Y軸方向的旋轉,此時,x和z發生變化,y不變。
a.繞Z軸方向的旋轉,此時,x和y發生變化,x不變。
那么x和z到底變化多少呢?我們可以看一下切面圖,然后計算出坐標的變化!
或者我們也可以直接翻到大學教材書本第七章【三維旋轉矩陣】:
我們拿繞y軸旋轉為例子,如:
//旋轉 function rotate(angle) { for (var i = 0; i < Points.length; i++) { var tempX = Points[i].x; Points[i].x = Points[i].x * Math.cos(angle) - Points[i].z * Math.sin(angle); Points[i].z = Points[i].z * Math.cos(angle) + tempX * Math.sin(angle); } }
我們要記住,旋轉之后的坐標是在坐標系當中的坐標,我們還要講其投影到顯示屏,所以我們應當先旋轉---再投影,順序不能弄反。
定義一個角度轉弧度:
function degToRad(a) { return (a / (360 / (2 * Math.PI))); }
立方體顏色變化:
function randomColor() { var arrHex = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"]; var strHex = "#"; var index; for (var i = 0; i < 6; i++) { index = Math.round(Math.random() * 15); strHex += arrHex[index]; } return strHex; }
旋轉控制核心,我們依然用Jscex:
var currentAngle = 0; var drawCube2 = function () { cxt2.clearRect(0, 0, 1200, 1200); init(); rotate(degToRad(currentAngle)) changedistance2(); cxt2.strokeStyle = randomColor(); cxt2.beginPath(); cxt2.moveTo(startX + Points[0].x, startY - Points[0].y); cxt2.lineTo(startX + Points[1].x, startY - Points[1].y); cxt2.lineTo(startX + Points[2].x, startY - Points[2].y); cxt2.lineTo(startX + Points[3].x, startY - Points[3].y); cxt2.lineTo(startX + Points[0].x, startY - Points[0].y); cxt2.moveTo(startX + Points[4].x, startY - Points[4].y); cxt2.lineTo(startX + Points[5].x, startY - Points[5].y); cxt2.lineTo(startX + Points[6].x, startY - Points[6].y); cxt2.lineTo(startX + Points[7].x, startY - Points[7].y); cxt2.lineTo(startX + Points[4].x, startY - Points[4].y); cxt2.moveTo(startX + Points[1].x, startY - Points[1].y); cxt2.lineTo(startX + Points[5].x, startY - Points[5].y); cxt2.moveTo(startX + Points[0].x, startY - Points[0].y); cxt2.lineTo(startX + Points[4].x, startY - Points[4].y); cxt2.moveTo(startX + Points[2].x, startY - Points[2].y); cxt2.lineTo(startX + Points[6].x, startY - Points[6].y); cxt2.moveTo(startX + Points[3].x, startY - Points[3].y); cxt2.lineTo(startX + Points[7].x, startY - Points[7].y); cxt2.stroke(); } drawCube2() var rotateAsync = eval(Jscex.compile("async", function () { while (true) { currentAngle += 5; drawCube2(); $await(Jscex.Async.sleep(100)); } }));
演示
我們也可以讓它繞著X軸旋轉:
for (var i = 0; i < Points4.length; i++) { var tempY = Points4[i].y; Points4[i].y = Points4[i].z * Math.sin(angle) - Points4[i].y * Math.cos(angle); Points4[i].z = tempY * Math.sin(angle) + Points4[i].z * Math.cos(angle); }
演示
因為任何角度的任何旋轉可以拆分成三類,我們可以同時繞X軸和Y軸旋轉:
function rotate(angle) { for (var i = 0; i < Points2.length; i++) { var tempX = Points2[i].x; var tempZ = Points2[i].z; Points2[i].x = Points2[i].x * Math.cos(angle) - Points2[i].z * Math.sin(angle); Points2[i].z = Points2[i].z * Math.cos(angle) + tempX * Math.sin(angle); } for (var i = 0; i < Points2.length; i++) { var tempY = Points2[i].y; Points2[i].y = Points2[i].y * Math.cos(angle) - Points2[i].z * Math.sin(angle); Points2[i].z = tempY * Math.sin(angle) + Points2[i].z * Math.cos(angle); } }
演示
總結
本文介紹了攝像機、投影、旋轉、縮放等概念,并加以實現。本文為了降低復雜度,攝像機的位置不變,在真實的場景當中,比如一些3D游戲,如魔獸世界,攝像機和物體是都可以改變位置。



浙公網安備 33010602011771號