本次給大家帶來的是基於上幾次canvas矢量圖形渲染器的基礎上做的三維方面的展示。 本系列目錄
先上demo,點擊二三維切換,可以在2d 和3d 之間切換。“添加三維矩形”可以添加隨機位置的不規則矩形。
1.我們的等角世界。
等角投影是一種用簡單的二維圖像,模擬三維圖像的技術。比較真正的三維技術來說,等角投影構建的三維圖像計算更簡單。但始終是模擬的技術,其真實效果還有很大的差距,但是等角投影在現在的游戲界用的還是很多的。比如RPG、SLG....
1。投影
我們將三維物體繪制到二維平面上的過程,叫做投影。比如說用我們的眼睛觀察世界其實也是一種投影的過程。
下面我們就說說什么是等角投影:
等角投影其實是一種軸測投影,也就是說物體的大小不會隨着與相機的距離發生改變。(其實也就是說不會發生近大遠小的改變)。
下圖是用我們的lib庫構建的一張圖片。
兩個矩形的長、寬、高都相等,距相機的位置不一樣(按常理我們看到的圖形必定是近大遠小)。通過等角投影后顯示在二維平面上的圖形卻保持了相同的大小,這就是等角投影的作用。
在這樣的投影中。X、Y、Z的夾角都回相同,如下圖
更多的等角投影的理論知識我們可以參閱 :Flash ActionScript 3.0 動畫高級教程 這本書的第四章。
2.等角世界與屏幕坐標的轉換。
由於我們的矢量圖形是構建在世界坐標系之上的,我們在進行轉換的時候需要首先將等角世界中的三維圖形轉換為二維世界坐標的圖形,再通過以前解析二維世界坐標的方法轉換為屏幕坐標。
1.等角坐標轉換為世界坐標。
在這之前我們首先構造了一個名為Point3D的類型,來描述三維坐標下的點:
//CLASS: POINT3D類型。 function Point3D(x, y, z){ this.x = x; this.y = y; this.z = z; } //轉換為世界坐標,用於最后的顯示 Point3D.prototype.toDisPlayPoint = function() { var x = this.x - this.z; var y = this.y * this.CORRECT + (this.x + this.z) * 0.5; return new Point(x, y); } Point3D.prototype.CORRECT = 1.2247; Point3D.prototype.geoType = "Point3D"; Point3D.prototype.clone = function() { return new Point3D(this.x, this.y, this.z); }
這個類里面,有一個 “toDisPlayPoint” 的方法,他將三維坐標下的點轉換為二維世界坐標下的點。至於那個公式是怎么來的,大家可以參考等角投影的相應知識,這里就不做詳細的推導了。
有了三維下點,我們下面需要在渲染器中對其進行解析。
//獲得一個點的屏幕顯示位置。 Canvas.prototype.getLocalXY = function(point, threeD) { threeD = threeD || this.layer.threeD; if(threeD && this.layer.threeD) { if(point.geoType !== "Point3D") { point = new Point3D(point.x, threeD.height, point.y); } point = point.toDisPlayPoint(); } var resolution = this.layer.getRes(); var extent = this.layer.bounds; var x = (point.x / resolution + (-extent.left / resolution)); var y = ((extent.top / resolution) - point.y / resolution); return {x: x, y: y}; }
首先我們判斷當前的Layer是否啟用了三維顯示。如果啟用了三維顯示,那我們按照下面的方法進行操作:
1.判斷傳入的點是否已經是三維點,如果不是則把二維點轉換為三維點,通常三維點的y值也就是layer圖層定義的y值。
2.將三維點轉換為二維世界中的點。
3.顯示二維世界中的點。
這樣我們就把三維下的點顯示到屏幕上,而且實現了二維自動轉三維的功能。
ThreeD這個類現在還很簡單,只有一個表示y坐標的height屬性:
//CLASS:等角世界3D效果。 function ThreeD(height) { this.height = height; }
3.屏幕坐標與等角世界坐標的轉換。
1.首先我們需要把屏幕坐標轉換為可顯示的二維坐標
這個我們已經在以前的隨筆中實現了:
//通過屏幕坐標獲取世界坐標。 Layer.prototype.getPositionFromPx = function (px) { return new Point((px.x + this.bounds.left / this.res) * this.res, (this.bounds.top/this.res - px.y) * this.res); }
下面我們在point類中添加下面這個兩個方法,用於把二維世界坐標轉換為等角世界中的坐標:
//做所顯示用的point,轉換為3d下的point值。 Point.prototype.disToWorldPoint3D = function(height) { var x = this.y + this.x * 0.5; var y = height || 0; var z = this.y - this.x * 0.5; return new Point3D(x, y, z); } //做所顯示用的point,轉換為世界坐標系下的point(x,0,z)值。 Point.prototype.disToWorldPoint = function(height) { var x = this.y + this.x * 0.5; var z = this.y - this.x * 0.5; return new Point(x, z); }
通過以上的步驟就把屏幕坐標轉換為等角世界中的坐標啦。
2.二維圖像中的三維屬性。
這一部分我們來說說二維圖形是怎么自動變成三維的。首先我們需要區分下可以變成三維圖形的類和不能變化的。
可以三維化的類:
所有繼承自LinerRing的類(LinerRing,Star)。
不能三維化的類:
Circle, Point, Img。
這三個類不能轉換為三維,是因為在等角中二維的圓和三維的圓顯示結構都一樣,圖片就不說了他本身就一二維的。
明確了實現后我們需要為所有繼承自LinerRing的圖形定義一高度屬性:
function Geometry(threeD){ this.id = CanvasSketch.getId("geomtry_"); //增加3d的屬性。 if(threeD) { this.threeD = threeD; } }
使用的時候就非常簡單了,比如說我們需要構造一個在屏幕中心的正方形,可以通過以下語句實現:
var vectors = []; var points = []; var threeD = new ThreeD(10); points.push(new Point(-10, -10), new Point(-10, 10), new Point(10, 10), new Point(10, -10)); var linerRing = new Vector(new LinerRing(points, threeD), groundStyle); vectors.push(linerRing); layer.addVectors(vectors);
我們定義了一個LinerRing的三維對象,只需要和以前一樣定義底面的矩形,並設置一高度程序內部就可以將其渲染為三維的矩形,如圖:
但是,不知道大家想到沒有在程序內部是如何實現的?
請看下面的代碼:
Canvas.prototype.drawLinerRing = function(geometry, style, id) { this.rendererPath(geometry, {fill: style.fill, stroke: style.stroke}, id); //如果我們使用的是3d世界中的繪圖,且我們的圖形高度大於0。 if(this.layer.threeD && geometry.threeD && geometry.threeD.height > 0) { //繪制頂面。 var linerRing3d = this.convertPathTothreeD(geometry); this.rendererPath(linerRing3d, {fill: style.fill, stroke: style.stroke}, id); //繪制側面。 var points2d = geometry.points; var points3d = linerRing3d.points; for(var i = 0, len = points2d.length - 1; i< len; i++) { var p1 = new Point3D(points2d[i].x, 0, points2d[i].y); var p2 = points3d[i]; var p3 = points3d[i + 1]; var p4 = new Point3D(points2d[i + 1].x, 0, points2d[i + 1].y); var edgePoints = [p1, p2, p3, p4]; var edge = new LinerRing(edgePoints); this.rendererPath(edge, {fill: style.fill, stroke: style.stroke}, id); } } this.setCanvasStyle("reset"); } Canvas.prototype.convertPathTothreeD = function(geometry) { var points = geometry.points; var height = geometry.threeD.height; var points3d = []; for(var i = 0, len = points.length; i < len; i++) { //每一個點加入高度。 var point3d = new Point3D(points[i].x, height, points[i].y); points3d.push(point3d); } var linerRing3d = new LinerRing(points3d); return linerRing3d; }
不管是二維或是三維的圖形,我們首先必須繪制底面,之后我們通過convertPathTothreeD,這個方法生成頂面並繪制,之后生成每個側面並繪制。
這里頂面的繪制和側面的繪制都是傳入的三維點,而底面是二維點。
3.二三維切換
這個就很easy了,我們已經實現了二維和三維的顯示只需要這樣調用就可以了:
//切換2D顯示。 function changetwoD() { layer.threeD = null; layer.reDraw(); } //切換3D顯示。 function changethreeD() { layer.threeD = threeD; layer.reDraw(); }
redraw方法其實就是重繪了一遍當前中心點,當前zoom的渲染器。
Layer.prototype.reDraw = function() { this.moveTo(this.zoom, this.center); }
至此我們就將一個基本的二三維一體化的渲染器做好了,當然我們這里還有很多缺陷:不能前后排序,沒有光照等等。。在以后的隨筆中會慢慢加入這些
下次預告: 1.三維景深排序。
2.多圖層疊加顯示。
嘗試一下:下載本次隨筆所有源碼+DEMO,大家可以自己玩玩,里面還有不上東西在本次隨筆中沒有講到,如范圍的三維轉換等。。