HTML5技术

HTML5版3D实验室二:摄像机、投影、3D旋转及缩放

字号+ 作者:当耐特砖家 来源: 2014-11-16 21:26 我要评论( )

【简述】 3D效果分两种,一种是伪3D,一种是3D. 伪3D:是人类按照自己的经验,在二维平面制造出3D的效果。 3D:是通过大量的计算将3D世界中所有点投影到二维平面中。 本系列的所有演

【简述】

3D效果分两种,一种是伪3D,一种是3D.

伪3D:是人类按照自己的经验,在二维平面制造出3D的效果。

3D:是通过大量的计算将3D世界中所有点投影到二维平面中。

本系列的所有演示都是3D,非伪3D。本文将穿插图片、公式、代码、演示,让读者深刻理解3D的基本概念极其思想。

【对象及概念介绍】

对象一:摄像机。

大家都有一个基本常识,在不同的角度观看到的物体是不同的。摄像机对象有自己的空间的坐标(vidiconX,vidiconY,vidiconZ)。

对象二:显示屏

任何三维物体,都会以二维的形式投影在显示屏上,显示屏垂直于摄像机的观测方向,所以摄像机的空间坐标变化,会导致显示屏的坐标系的变换。

对象三:被观察测物体

任何物体都是有无数个点构成,每个点有自己的空间坐标(x,y,z),显示屏介于摄像机和物体之间。

为了降低复杂度,本文将显示屏和被观测物体所处的坐标系公用一套(x,y),所有的旋转都是物体旋转,摄像机不动!

缩放原理:摄像机不动,被观察测物体不动,显示屏离摄像机越近,缩放比例越小,显示屏离摄像机越远,缩放比例越大。

【投影分析】

我们来看下面这张图:

3Dp1

 

因为,我们将显示屏和被观测物体共用一个坐标系,所以,我们可以计算出点(x1,y1,z1)投影到显示屏上的点的缩放比例为:

h / Math.abs(vidiconZ - z1)

所以投影后的坐标为:

x = x1 * h / Math.abs(vidiconZ - z);

y=  y1 * h / Math.abs(vidiconZ - z);

有了以上这些知识,我们可以轻松的在Canvas里画一个正方体(再次强调,是根据计算的结果画,非人类经验)。

XML/HTML Code复制内容到剪贴板
  1. <canvas id="myCanvas" width="700" height="500" style="border: 1px solid #c3c3c3;">    
  2. Your browser does not support the canvas element.    
  3. </canvas>    
  4.     <script type="text/javascript">    
  5.         var c = document.getElementById("myCanvas");    
  6.         var cccxt = c.getContext("2d");    
  7.         cxt.lineWidth = 3;    
  8.         //正方体8个顶点    
  9.         var Point1 = { x: 100, y: 100, z: 100 };    
  10.         var Point2 = { x: 100, y: 100, z: -100 };    
  11.         var Point3 = { x: -100, y: 100, z: -100 };    
  12.         var Point4 = { x: -100, y: 100, z: 100 };    
  13.         var Point_1 = { x: 100, y: -100, z: 100 };    
  14.         var Point_2 = { x: 100, y: -100, z: -100 };    
  15.         var Point_3 = { x: -100, y: -100, z: -100 };    
  16.         var Point_4 = { x: -100, y: -100, z: 100 };    
  17.         var startX = 250;    
  18.         var startY = 250;    
  19.         //摄像机到显示屏的距离    
  20.         var distance = 500;    
  21.         //摄像机位置    
  22.         var eyePosition = { x: 0, y: 0, z: 700 };       
  23.         function changeDistance() {    
  24.             Point1Point1Point1.x = Point1.x * distance / Math.abs(eyePosition.z - Point1.z);    
  25.             Point1Point1Point1.y = Point1.y * distance / Math.abs(eyePosition.z - Point1.z);    
  26.             Point2Point2Point2.x = Point2.x * distance / Math.abs(eyePosition.z - Point2.z);    
  27.             Point2Point2Point2.y = Point2.y * distance / Math.abs(eyePosition.z - Point2.z);    
  28.             Point3Point3Point3.x = Point3.x * distance / Math.abs(eyePosition.z - Point3.z);    
  29.             Point3Point3Point3.y = Point3.y * distance / Math.abs(eyePosition.z - Point3.z);    
  30.             Point4Point4Point4.x = Point4.x * distance / Math.abs(eyePosition.z - Point4.z);    
  31.             Point4Point4Point4.y = Point4.y * distance / Math.abs(eyePosition.z - Point4.z);    
  32.             Point_1Point_1Point_1.x = Point_1.x * distance / Math.abs(eyePosition.z - Point_1.z);    
  33.             Point_1Point_1Point_1.y = Point_1.y * distance / Math.abs(eyePosition.z - Point_1.z);    
  34.             Point_2Point_2Point_2.x = Point_2.x * distance / Math.abs(eyePosition.z - Point_2.z);    
  35.             Point_2Point_2Point_2.y = Point_2.y * distance / Math.abs(eyePosition.z - Point_2.z);    
  36.             Point_3Point_3Point_3.x = Point_3.x * distance / Math.abs(eyePosition.z - Point_3.z);    
  37.             Point_3Point_3Point_3.y = Point_3.y * distance / Math.abs(eyePosition.z - Point_3.z);    
  38.             Point_4Point_4Point_4.x = Point_4.x * distance / Math.abs(eyePosition.z - Point_4.z);    
  39.             Point_4Point_4Point_4.y = Point_4.y * distance / Math.abs(eyePosition.z - Point_4.z);    
  40.         }    
  41.         var drawCube = function () {    
  42.             changeDistance();    
  43.             cxt.beginPath();    
  44.             cxt.moveTo(startX + Point1.x, startY - Point1.y);    
  45.             cxt.lineTo(startX + Point2.x, startY - Point2.y);    
  46.             cxt.lineTo(startX + Point3.x, startY - Point3.y);    
  47.             cxt.lineTo(startX + Point4.x, startY - Point4.y);    
  48.             cxt.lineTo(startX + Point1.x, startY - Point1.y);    
  49.             cxt.moveTo(startX + Point_1.x, startY - Point_1.y);    
  50.             cxt.lineTo(startX + Point_2.x, startY - Point_2.y);    
  51.             cxt.lineTo(startX + Point_3.x, startY - Point_3.y);    
  52.             cxt.lineTo(startX + Point_4.x, startY - Point_4.y);    
  53.             cxt.lineTo(startX + Point_1.x, startY - Point_1.y);    
  54.             cxt.moveTo(startX + Point2.x, startY - Point2.y);    
  55.             cxt.lineTo(startX + Point_2.x, startY - Point_2.y);    
  56.             cxt.moveTo(startX + Point1.x, startY - Point1.y);    
  57.             cxt.lineTo(startX + Point_1.x, startY - Point_1.y);    
  58.             cxt.moveTo(startX + Point3.x, startY - Point3.y);    
  59.             cxt.lineTo(startX + Point_3.x, startY - Point_3.y);    
  60.             cxt.moveTo(startX + Point4.x, startY - Point4.y);    
  61.             cxt.lineTo(startX + Point_4.x, startY - Point_4.y);    
  62.             cxt.stroke();    
  63.         }          
  64.     </script>    
  65.     <div id="show">    
  66.     </div>    
  67.     <input type="button" onclick="drawCube ();" value="开始画立方体"     
  68.         style="width: 135px" />    

 【演示】

当然我们可以重构一下,将8个点都放到Array中

XML/HTML Code复制内容到剪贴板
  1. <script type="text/javascript">         var c = document.getElementById("myCanvas");  
  2.        var ccxt = c.getContext("2d");  
  3.        cxt.lineWidth = 3;  
  4.        //正方体8个顶点         var Point = new Array();  
  5.         Point[0] = { x: 100, y: 100, z: 100 };  
  6.         Point[1] = { x: 100, y: 100, z: -100 };  
  7.         Point[2] = { x: -100, y: 100, z: -100 };  
  8.         Point[3] = { x: -100, y: 100, z: 100 };  
  9.         Point[4] = { x: 100, y: -100, z: 100 };  
  10.         Point[5] = { x: 100, y: -100, z: -100 };  
  11.         Point[6] = { x: -100, y: -100, z: -100 };  
  12.         Point[7] = { x: -100, y: -100, z: 100 };  
  13.        var startX = 250;  
  14.        var startY = 250;  
  15.        //摄像机到显示屏的距离         var distance = 500;  
  16.        //摄像机位置         var eyePosition = { x: 0, y: 0, z: 700 };  
  17.        function changeDistance() {  
  18.            for (var i = 0; i < Point.length; i++) {  
  19.                Point[i].x = Point[i].x * distance / Math.abs(eyePosition.z - Point[i].z);  
  20.                Point[i].y = Point[i].y * distance / Math.abs(eyePosition.z - Point[i].z);  
  21.            }           
  22.        }  
  23.        var drawCube = function () {  
  24.            changeDistance();  
  25.            cxt.beginPath();  
  26.            cxt.moveTo(startX + Point[0].x, startY - Point[0].y);  
  27.            cxt.lineTo(startX + Point[1].x, startY - Point[1].y);  
  28.            cxt.lineTo(startX + Point[2].x, startY - Point[2].y);  
  29.            cxt.lineTo(startX + Point[3].x, startY - Point[3].y);           
  30.            cxt.lineTo(startX + Point[0].x, startY - Point[0].y);  
  31.            cxt.moveTo(startX + Point[4].x, startY - Point[4].y);  
  32.            cxt.lineTo(startX + Point[5].x, startY - Point[5].y);  
  33.            cxt.lineTo(startX + Point[6].x, startY - Point[6].y);  
  34.            cxt.lineTo(startX + Point[7].x, startY - Point[7].y);  
  35.            cxt.lineTo(startX + Point[4].x, startY - Point[4].y);  
  36.            cxt.moveTo(startX + Point[1].x, startY - Point[1].y);  
  37.            cxt.lineTo(startX + Point[5].x, startY - Point[5].y);  
  38.            cxt.moveTo(startX + Point[0].x, startY - Point[0].y);  
  39.            cxt.lineTo(startX + Point[4].x, startY - Point[4].y);  
  40.            cxt.moveTo(startX + Point[2].x, startY - Point[2].y);  
  41.            cxt.lineTo(startX + Point[6].x, startY - Point[6].y);  
  42.            cxt.moveTo(startX + Point[3].x, startY - Point[3].y);  
  43.            cxt.lineTo(startX + Point[7].x, startY - Point[7].y);  
  44.            cxt.stroke();  
  45.        }        
  46.    </script>   

现在,我们看到了正方体正常的显示在画布当中,那么我们现在来用演示证明一下缩放原理

缩放原理:摄像机不动,被观察测物体不动,显示屏离摄像机越近,缩放比例越小,显示屏离摄像机越远,缩放比例越大。

XML/HTML Code复制内容到剪贴板
  1. <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.    
  2. </canvas>     <script type="text/javascript">         var c = document.getElementById("myCanvas");    
  3.         var ccxt = c.getContext("2d");    
  4.         cxt.lineWidth = 3;    
  5.         var Point = new Array();       
  6.         var startX = 250;    
  7.         var startY = 250;    
  8.         var distance = 500;    
  9.         var eyePosition = { x: 0, y: 0, z: 700 };    
  10.         function init() {    
  11.             Point[0] = { x: 100, y: 100, z: 100 };    
  12.             Point[1] = { x: 100, y: 100, z: -100 };    
  13.             Point[2] = { x: -100, y: 100, z: -100 };    
  14.             Point[3] = { x: -100, y: 100, z: 100 };    
  15.             Point[4] = { x: 100, y: -100, z: 100 };    
  16.             Point[5] = { x: 100, y: -100, z: -100 };    
  17.             Point[6] = { x: -100, y: -100, z: -100 };    
  18.             Point[7] = { x: -100, y: -100, z: 100 };    
  19.         }    
  20.         function changeDistance() {    
  21.             for (var i = 0; i < Point.length; i++) {    
  22.                 Point[i].x = Point[i].x * distance / Math.abs(eyePosition.z - Point[i].z);    
  23.                 Point[i].y = Point[i].y * distance / Math.abs(eyePosition.z - Point[i].z);    
  24.             }             
  25.         }    
  26.         var drawCube = function (increment) {    
  27.             cxt.clearRect(0, 0, 1200, 1200);    
  28.             init();    
  29.             distance += increment;    
  30.             changeDistance();    
  31.             cxt.beginPath();    
  32.             cxt.moveTo(startX + Point[0].x, startY - Point[0].y);    
  33.             cxt.lineTo(startX + Point[1].x, startY - Point[1].y);    
  34.             cxt.lineTo(startX + Point[2].x, startY - Point[2].y);    
  35.             cxt.lineTo(startX + Point[3].x, startY - Point[3].y);    
  36.             cxt.lineTo(startX + Point[0].x, startY - Point[0].y);    
  37.             cxt.moveTo(startX + Point[4].x, startY - Point[4].y);    
  38.             cxt.lineTo(startX + Point[5].x, startY - Point[5].y);    
  39.             cxt.lineTo(startX + Point[6].x, startY - Point[6].y);    
  40.             cxt.lineTo(startX + Point[7].x, startY - Point[7].y);    
  41.             cxt.lineTo(startX + Point[4].x, startY - Point[4].y);    
  42.             cxt.moveTo(startX + Point[1].x, startY - Point[1].y);    
  43.             cxt.lineTo(startX + Point[5].x, startY - Point[5].y);    
  44.             cxt.moveTo(startX + Point[0].x, startY - Point[0].y);    
  45.             cxt.lineTo(startX + Point[4].x, startY - Point[4].y);    
  46.             cxt.moveTo(startX + Point[2].x, startY - Point[2].y);    
  47.             cxt.lineTo(startX + Point[6].x, startY - Point[6].y);    
  48.             cxt.moveTo(startX + Point[3].x, startY - Point[3].y);    
  49.             cxt.lineTo(startX + Point[7].x, startY - Point[7].y);    
  50.             cxt.stroke();    
  51.         }    
  52.                  var reduceDrawCubeAsync = eval(Jscex.compile("async", function () {    
  53.         //当摄像机到显示屏的距离大于750,退出循环·             while (distance < 750) {    
  54.                 drawCube(10);    
  55.                 $await(Jscex.Async.sleep(100));                 
  56.             }    
  57.         }));    
  58.         var magnifyDrawCubeAsync = eval(Jscex.compile("async", function () {    
  59.             //当摄像机到显示屏的距离小于150,退出循环·             while (distance > 150) {    
  60.                 drawCube(-10);    
  61.                 $await(Jscex.Async.sleep(100));    
  62.             }    
  63.         }));    
  64.         var executeAsync = eval(Jscex.compile("async", function () {    
  65.             $await(reduceDrawCubeAsync());    
  66.             $await(magnifyDrawCubeAsync());    
  67.         }));    
  68.     </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到底变化多少呢?我们可以看一下切面图,然后计算出坐标的变化!

3dp2

 

或者我们也可以直接翻到大学教材书本第七章【三维旋转矩阵】:

3Dp3

 

我们拿绕y轴旋转为例子,如:

JavaScript Code复制内容到剪贴板
  1. //旋转        function rotate(angle) {  
  2.            for (var i = 0; i < Points.length; i++) {  
  3.                var tempX = Points[i].x;  
  4.                Points[i].x = Points[i].x * Math.cos(angle) - Points[i].z * Math.sin(angle);  
  5.                Points[i].z = Points[i].z * Math.cos(angle) + tempX * Math.sin(angle);  
  6.            }   
  7.        }  

我们要记住,旋转之后的坐标是在坐标系当中的坐标,我们还要讲其投影到显示屏,所以我们应当先旋转---再投影,顺序不能弄反。

定义一个角度转弧度:

JavaScript Code复制内容到剪贴板
  1. function degToRad(a) {  
  2.            return (a / (360 / (2 * Math.PI)));  
  3.        }  

立方体颜色变化:

JavaScript Code复制内容到剪贴板
  1. function randomColor() {  
  2.            var arrHex = ["0""1""2""3""4""5""6""7""8""9""A""B""C""D""E""F"]; var strHex = "#";  
  3.          var index;  
  4.          for (var i = 0; i < 6; i++) {  
  5.                index = Math.round(Math.random() * 15);  
  6.              strHex += arrHex[index];  
  7.          }  
  8.          return strHex;  
  9.      }  

旋转控制核心,我们依然用Jscex:

JavaScript Code复制内容到剪贴板
  1. var currentAngle = 0;  
  2.         var drawCube2 = function () {       
  3.             cxt2.clearRect(0, 0, 1200, 1200);          
  4.             init();  
  5.             rotate(degToRad(currentAngle))  
  6.             changedistance2();  
  7.             cxt2.strokeStyle = randomColor();  
  8.             cxt2.beginPath();     
  9.             cxt2.moveTo(startX + Points[0].x, startY - Points[0].y);  
  10.             cxt2.lineTo(startX + Points[1].x, startY - Points[1].y);  
  11.             cxt2.lineTo(startX + Points[2].x, startY - Points[2].y);  
  12.             cxt2.lineTo(startX + Points[3].x, startY - Points[3].y);  
  13.             cxt2.lineTo(startX + Points[0].x, startY - Points[0].y);  
  14.             cxt2.moveTo(startX + Points[4].x, startY - Points[4].y);  
  15.             cxt2.lineTo(startX + Points[5].x, startY - Points[5].y);  
  16.             cxt2.lineTo(startX + Points[6].x, startY - Points[6].y);  
  17.             cxt2.lineTo(startX + Points[7].x, startY - Points[7].y);  
  18.             cxt2.lineTo(startX + Points[4].x, startY - Points[4].y);  
  19.             cxt2.moveTo(startX + Points[1].x, startY - Points[1].y);  
  20.             cxt2.lineTo(startX + Points[5].x, startY - Points[5].y);  
  21.             cxt2.moveTo(startX + Points[0].x, startY - Points[0].y);  
  22.             cxt2.lineTo(startX + Points[4].x, startY - Points[4].y);  
  23.             cxt2.moveTo(startX + Points[2].x, startY - Points[2].y);  
  24.             cxt2.lineTo(startX + Points[6].x, startY - Points[6].y);  
  25.             cxt2.moveTo(startX + Points[3].x, startY - Points[3].y);  
  26.             cxt2.lineTo(startX + Points[7].x, startY - Points[7].y);  
  27.             cxt2.stroke();  
  28.         }  
  29.         drawCube2()  
  30.         var rotateAsync = eval(Jscex.compile("async"function () {  
  31.              while (true) {  
  32.                 currentAngle += 5;  
  33.                 drawCube2();  
  34.                 $await(Jscex.Async.sleep(100));  
  35.             }  
  36.         }));  

我们也可以让它绕着X轴旋转:

JavaScript Code复制内容到剪贴板
  1. for (var i = 0; i < Points4.length; i++) {  
  2.                var tempY = Points4[i].y;  
  3.                Points4[i].y = Points4[i].z * Math.sin(angle) - Points4[i].y * Math.cos(angle);  
  4.                Points4[i].z = tempY * Math.sin(angle) + Points4[i].z * Math.cos(angle);  
  5.            }  

因为任何角度的任何旋转可以拆分成三类,我们可以同时绕X轴和Y轴旋转:

JavaScript Code复制内容到剪贴板
  1. //旋转  function rotate(angle) {  
  2.      for (var i = 0; i < Points2.length; i++) {  
  3.          var tempX = Points2[i].x;  
  4.          var tempZ = Points2[i].z;      
  5.          Points2[i].x = Points2[i].x * Math.cos(angle) - Points2[i].z * Math.sin(angle);  
  6.          Points2[i].z = Points2[i].z * Math.cos(angle) + tempX * Math.sin(angle);  
  7.      }  
  8.      for (var i = 0; i < Points2.length; i++) {  
  9.          var tempY = Points2[i].y;  
  10.          Points2[i].y = Points2[i].y * Math.cos(angle) - Points2[i].z * Math.sin(angle);  
  11.          Points2[i].z = tempY * Math.sin(angle) + Points2[i].z * Math.cos(angle);  
  12.      }        
  13.  }  

【总结】

本文介绍了摄像机、投影、旋转、缩放等概念,并加以实现。本文为了降低复杂度,摄像机的位置不变,在真实的场景当中,比如一些3D游戏,如魔兽世界,摄像机和物体是都可以改变位置,

那么,我们就应该建立两套坐标体系去适应复杂的场景。3D实验,还有很长的路要走,虽然是别人已经走过的路····

续篇会谈及在此基础上加入其他3D物理实验。希望各位支持,并给本人更多意见。

原文链接:http://www.cnblogs.com/iamzhanglei/archive/2011/09/23/2185627.html

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • 8款超酷的HTML5 3D图片动画源码 - 帅的相对论

    8款超酷的HTML5 3D图片动画源码 - 帅的相对论

    2015-12-17 09:45

  • 9个超绚丽的HTML5 3D图片动画特效 - 帅的相对论

    9个超绚丽的HTML5 3D图片动画特效 - 帅的相对论

    2015-12-15 13:00

  • HTML5游戏开发引擎,初识CreateJS - 请叫我头头哥

    HTML5游戏开发引擎,初识CreateJS - 请叫我头头哥

    2015-11-03 08:05

  • 无插件纯Web HTML5 3D机房 终结篇(新增资产管理、动环监控等内容) - twaver

    无插件纯Web HTML5 3D机房 终结篇(新增资产管理、动环监控等内容)

    2015-10-27 11:47

网友点评