基礎
看了岑安大大的教程學習了3d基礎,之前寫了篇總結,覺得寫的太散廢話太多,重寫一篇。
本文需要實現的效果如下:3d球
岑安的兩篇教程寫的很棒,但我感覺改變下順序或許會更好理解。
我們把畫布(此文所講所見所得均基於canvas)的中心當做是一個空間三維系的中心,畫布的x和y軸正方向分別當做三維系的x和y軸的正方向,把垂直畫布向內當做z軸正方向,那么三維系大致如下圖:
我們假設空間中有個點在圍繞y軸轉動,那么轉動的軌跡就是個圓;如果畫面感強的畫,可以想象出就像地球上的某個點由於地球的自轉做的向心運動。那么問題來了,如果三維系里的某個點圍繞y軸轉動確定的角度后,能計算出轉動后的三維坐標嗎?
答案是肯定的,空間上的某個點(已知三維坐標)圍繞x軸y軸或者z軸轉動一定角度后,都能計算出相應的三維坐標。
過程貌似是個線性代數里的矩陣變換,數學渣渣跪...
直接把它寫在矢量類里了:
// 矢量旋轉 Vector3.prototype.rotateX = function(angleX) { var cosx = Math.cos(angleX); var sinx = Math.sin(angleX); var y1 = this.y * cosx - this.z * sinx; var z1 = this.y * sinx + this.z * cosx; this.y = y1; this.z = z1; }; Vector3.prototype.rotateY = function(angleY) { var cosy = Math.cos(angleY); var siny = Math.sin(angleY); var x1 = this.z * siny + this.x * cosy; var z1 = this.z * cosy - this.x * siny; this.x = x1; this.z = z1; };
更近一步,空間上的某個點圍繞任意軸轉動一定角度,都能計算出轉動后的三維坐標。有興趣的可以參考【自己給自己題目做】:如何在Canvas上實現魔方效果
那么就好辦了,我們在三維空間初始化一些點(當做球心),隨意賦給他們xyz坐標值,隨意設定旋轉角度,那么旋轉后的xyz值也能很輕松地得到了!當然有了這些還只是成功了一半,我們的canvas只支持2d,如果支持3d問題就解決了,我們還得把三維降到二維上。我們知道,如果一個球在三維系上圍繞y軸轉動,球的大小是不會變化的,但是如果體現在二維上,z值越大時,球體在視覺上的體現就是越小(跟z值有關);在三維上,y值是不會變化的,但是在二維視覺上的體現不應該是這樣,y值是應該變化的(跟z值有關)。另一方面,球體的任意角度視圖都是一個圓形。
於是我們需要“把z方向扁平化”,即把z軸數值的大小體現在x和y上:
// focalLength 表示當前焦距,一般可設為一個常量 var focalLength = 250; // 把z方向扁平化 var scale = focalLength / (focalLength + this.z); this.x2 = this.garden.vpx + this.x * scale; this.y2 = this.garden.vpy + this.y * scale; this.radius = this.ballR * scale;
z方向扁平化使得z方向的值體現在x、y和r的大小以及球的透明度等變量上,一方面由於球體的旋轉我們需要時刻調整球體在三維系上的坐標,另一方面在draw的時候需要計算二維上的坐標。
所以簡單的來說:
-
- 初始化球心在三維系上的坐標以及球的顏色半徑等屬性(三維系原點的設定)
- 將三維降到二維,繪制
- 根據旋轉角度重新計算三維坐標
- 將三維降到二維,繪制
- 反復重復
這里還有個注意點就是每幀重繪時,根據小球在三維系的z值排個序,因為后面的會被前面的擋住,所以繪制應該有個順序。
3d標簽雲
有了以上的基礎,可以動手做個3d標簽雲。理論上3d標簽雲中的標簽應該是個a標簽,是可以點擊跳轉的,這里為了方便,不添加點擊跳轉功能了(直接在canvas上)。
思路應該很簡單,在一個球面上初始化一些點,把球換成文字就可以了。
怎樣構造球體獲得坐標?充分認識到了數學的必要性...
不懂數學,試了下覺得角度的取值有兩種方法:
- (0 <= θ <= PI && 0 <= Φ <= 2 * PI)
- (0 <= θ <= 2 * PI && 0 <= Φ <= PI)
可以驗證x*x+y*y+z*z確實等於r*r。
有了公式,我們可以枚舉角度獲得坐標。
如果有n個點,我需要平均分配在球面上,怎么做?我們引入第二個公式:
初始化坐標:
var all = 100; // 100個 for(var i = 1; i <= all; i++) { var a1 = Math.acos(1 - (2 * i) / all); var a2 = a1 * Math.sqrt(all * Math.PI); var x = 150 * Math.sin(a1) * Math.cos(a2); // r = 150 var y = 150 * Math.sin(a1) * Math.sin(a2); var z = 150 * Math.cos(a1); garden.createBall(x, y, z); }
點-線-面
有了點,根據點-線-面的原理,線和面也呼之欲出了。
稍微動點腦筋搞點創意,就能搞點看起來炫酷(實際上沒花頭)的小東東。
比如下面這樣:
總結
學習3d主要是為了做粒子特效。
這只是很基礎的入門,我也只會這么點,有更進一步興趣的可以參考岑安、當耐特、W·Axes或者miloyip的博客。
岑安的JCanvas也可以看看,添加了很多事件代理。