flash3D開發基礎-------Stage3D和透視投影的使用5(轉)

文章轉自:http://www.adobe.com/cn/devnet/f ... ive-projection.html
必備知識
要求讀者具有建立和運行基於Stage3D API的ActionScript項目的經驗。 在閱讀下面提供的指南之前,你應該確保完成本系列教程的關於如何使用Stage3D的前面教程:
其它附加要求的產品:

用戶級別
中級


必需產品




引言
在本教程中,你將瞭解透視的概念。 透視是開發使用3D渲染技術的Flash項目時使用的一個基本主題。你將充分了解如何使用Stage3D API並且通過使用Stage3D的透視技術來渲染3D世界。本教程是一個關於使用Stage3D的系列教程的一部分,並且它是基於之前描述如何渲染一個三角形的若干教程的信息而編寫的。通過使用透視技術渲染3D場景,你能夠將該範例項目的功能提升至一個更高水平。


瞭解透視技術
在真實的世界中,我們可以按照一種稱爲“透視”的方式觀看物體。
透視是指一種遠處的對象比離你較近的對象看起來似乎要小一些的概念。 透視也指如果你坐在一條筆直的道路中央,那麼你實際看到道路邊緣能夠成爲兩條會聚的直線。
這就是透視。 在3D項目中,透視至關重要。 如果沒有透視技術,那麼3D世界看起來就不真實。
儘管這看起來似乎是自然並且顯而易見的道理,但是你必須考慮到,當你在電腦上創建3D渲染時,你是在電腦屏幕上試圖對3D世界進行模擬,而該屏幕採用的是2D平面。
設想在電腦屏幕後面有某種真實的3D場景,並且你能夠透過你的電腦屏幕的“玻璃”觀看它。通過使用透視技術,你的目標是創建能夠渲染投影到你屏幕“玻璃”上的對象的代碼,就好像在屏幕後面存在這麼一個真實的3D世界。而唯一需要說明的是該3D世界並非真實存在…它僅僅是3D世界的一個精確模擬。
因此,當使用3D渲染技術在3D世界中模擬一個場景,然後將該3D場景投影至你的電腦屏幕的2D表面時,該過程被稱爲透視投影。
首先,憑直覺預想一下你想要獲得的效果。 如果某個對象離觀看者較近,那麼該對象必須看起來較大。 如果該對象例觀看者較遠,那麼它必須看起來較小。 此外,如果某個對象不斷遠離觀看者,那麼你希望它沿着一條直線在屏幕中心匯聚,就好像它一直遠離直到消失在遠方。


將透視轉換成數學函數
當你觀看圖1中的插圖時,設想一下在你的3D場景中放置一個對象。 在3D世界中,該對象的位置可以用xW,yW,zW來描述,這是一個從視點角度看過去的具有座標原點的3D座標系統。 這是該對象實際存在的位置,它位於屏幕之外的3D場景中。



圖1. 一個3D對象的透視投影

當觀看者在屏幕上觀察該對象時,相應的3D對象會被投影至一個利用xP和yP描述的2D位置,它是2D座標系統的屏幕(投影平面)。
爲了將這些值放入一個數學公式中,我將使用3D座標系統作爲世界座標(world coordinate),其中,x座標軸指向右方,y軸指向上方,而z軸正方向指向屏幕內部。 3D原點是指觀看者眼睛的位置。 因此,屏幕的玻璃位於與z軸正交的(即直角)平面上,有時我將z軸稱之爲zProj。
你可以通過將世界位置(world position)xW和yW除以zW的方式計算出投影的位置xP和yP,如下所示:
  1. xP = K1 * xW  / zW
  2. yP = K2 * yW / zW
複製代碼
K1和K2是根據幾何因子得到的常數,這些幾何因子包括你的投影平面(你的視角)的寬高比以及你的眼睛的“視野”,並且它們考慮了廣角視野的範圍。
你能夠看到該轉換如何對透視進行模擬。 靠近屏幕兩側的點隨着與眼睛之間距離(zW)的增加而逐漸被推向中央位置。 與此同時,靠近中央(0,0)的點受到與眼睛之間距離的影響較小並且仍保持靠近中央的位置。
這種由z軸進行的分割就是衆所周知的“透視分割(perspective divide)”。
現在,假定3D場景中的某個對象被定義爲一系列頂點。 因此,通過對所有幾何圖形的頂點應用該類型的轉換,你可以有效地確保當對象遠離眼點時,它會逐漸縮小。
在下一章節中,你能夠將該透視投影規則應用到ActionScript中,而在你的Flash 3D項目中你可以使用這些ActionScript。


將透視數學函數轉換成代碼
當你將這種透視分割技術從一個數學表述轉換成代碼時,你需要使用矩陣。
當你第一次使用透視投影和矩陣時,其過程非常棘手。 矩陣轉換是一種線性轉換:需要轉換的矢量組件僅僅是輸入矢量的線性組合。 而線性轉換僅僅支持平移(translation)、旋轉(rotation)、縮放(scaling)以及歪斜(skewing)。 然而它們卻不支持透視分割(perspective divide)等操作,而透視分割是指一個組件分割另一個組件的操作。
現在,記住三維座標通常由(x,y,z,w)形式的四維矢量來表示,其中w的值通常是1。 基於矩陣的針對透視分割問題的解決方案是以創新的方式使用第四維座標w,這可以通過將zW座標存儲至已轉換的矢量的w座標來實現。
該已轉換的矢量的其它組件均爲xP和yP與zW的前乘形式。
你需要使用下面的轉換:
  1. xW -> xP' = xP * zW = K1 * xW
  2. yW -> yP' = yP * zW = K2 * yW
複製代碼
乍一看,由於你將投影到2D屏幕,因此僅僅計算投影座標xP和yP似乎就已經足夠。 但是,渲染管線(rendering pipeline)並不會完全對深度(z)座標的跟蹤,因爲它需要對以渲染的不同像素進行深度排序(depth-sort)。 相應的標準過程涉及計算“投影”zP (zP’),當zW的值等於zNear時它的值等於0,而我們將zNear定義爲“近剪切距離(near clipping distance)”。 近剪切距離(near clipping distance)正是我們希望渲染的最近的距離。 稍後,我將在本教程中更加詳細地討論剪切的概念,不過,下面是用於計算已轉換的zP'的公式,它將用於剪切:
  1. zW -> zP' = K3 * (zW - zNear)
複製代碼
使用線性矩陣轉換完全能夠實現整個轉換過程,因爲已轉換矢量均是需要轉換的世界矢量(world vector)的一個線性組合。
接下來,通過將轉換的x、y、z組件除以w可以獲得實際轉換的xP和yP值,如下所示:
  1. xP = K1 * xW
  2. yP = K2 * yW
  3. zP = K3 * (zW - zNear) / zW
複製代碼
上述所示的計算過程正是你在下面章節中需要建立的部分。
使用剪切空間和歸一化設備座標(Normalized Device Coordinates)
Stage3D期望你能夠在你的Vertex Shader中使用一個能夠將頂點轉換爲特殊空間的矩陣:
  1. (x, y, z, w) = (xP', yP', zP', zW)
複製代碼
在按照上面方式對xP’、yP’、zP’和zW進行定義,並且選定常數K1、K2和K3之後,在3D世界中所有可見點xP和yP均落在區間(-1,1)之間,並且zP落在區間(0,1)之間。
這意味着某個落在屏幕右邊緣的對象,一旦被投影之後,其xP將等於1,而落在屏幕左邊緣的對象,其xP將等於-1。
我們稱這一四維空間(xP’,yP’,zP’,zW)爲剪切空間(clip space),因爲它常常是剪切發生的區域。 (xP,yP,zP) 座標在分割之後具有區間(-1,1)(用於xP和yP)和區間(0,1)(用於zP),我們將其稱爲歸一化設備座標(Normalized Device Coordinates (NDC))。
Stage3D和GPU使用來源於剪切空間表單中的Shader輸出的數據,以便能夠繼續在內部使用透視分割技術。
剪切注意事項
位於“我們希望進行渲染的最近距離”的對象,即當zW = zNear時,其zP=0。 然而,那些位於較遠距離(定義爲zW = zFar)的對象則在NDC空間中被轉換成zP=1。
zNear和zFar能夠定義相應的剪切平面。 比zNear距離更近的對象將被剪切(不是拉伸),正如比zFar距離更遠的對象一樣。 此外,位於區間(-1,1)之外,座標爲xP和yP的對象也將被剪切。
爲簡單起見,我使用的點對象也位於此區間。 你可以對一個實際擴展的對象進行部分剪切,因爲其中一部分落在視圖之內,而其它的部分落在視圖之外。


使用PerspectiveMatrix3D
上述提到的用於獲取xP、yP、zP的NDC區間的K1、K2和K3的正確值爲:
  1. K1 = zProj / aspect
  2. K2 = zProj
  3. K3 = zFar / (zFar – zNear)
複製代碼
在本範例中,aspect表示視口寬高比。
覈查這些值很容易,因爲這些落在投影平面(即zW = zProj)上的世界點(world point)(xW,yW,zW)和xP = (-1, 1)、yP = (-1, 1)和zP = (0, 1)的NDC區間對應xW = (-aspect, aspect)和yW = (-1, 1)的世界區間(world range)。 位於不同距離zW的世界點(world point)將根據透視投影公式來進行相應的縮放。 相似地,zP區間(0,1)對應世界區間(world range)zW = (zNear, zFar)。
通常,根據fov(field of view,視野區域)角度(定義眼睛廣角視野範圍的角度),而非使用zProj距離來指定這些常數更爲便捷。 圖2給出了在投影參考系統的side視圖中定義的fov 和zProj。



圖2. 投影參考系統的side視圖

請使用下面的方法計算zProj的值:
  1. zProj = 1 / tg (fov/2)
複製代碼
相應的縮放常數變爲:
  1. K1 = 1 / (aspect*tg(fov/2))
  2. K2 = 1 / tg(fov/2)
  3. K3 = zFar / (zFar – zNear)
複製代碼
你可以使用由Adobe開發的Matrix3D類的一個簡單擴展功能來幫助實現該過程。 下載PerspectiveMatrix3D類;在編寫本文時,它已經很接近官方版本。
在下載該包之後,檢查一下PerspectiveMatrix3D類。 它能夠實現一些用來創建透視矩陣轉換的簡單函數,而本項目需要這些函數,它們包含常數K1,K2和K3的正確值:
  1. PerspectiveMatrix3D::perspectiveFieldOfViewLH
  2. PerspectiveMatrix3D::perspectiveFieldOfViewRH
複製代碼
在本教程中,我使用世界座標系統(world coordinate system),其中x指向右方,y指向上方,而z軸正方向指向屏幕,這是一個這是一個左手座標系統(left handed coordinate system)。因此,我將使用LH風格的矩陣函數。
在使用PerspectiveMatrix3D之後,創建適合Stage3D的透視矩陣的過程將變得非常簡單,因爲它僅僅要求你定義少量參數。 例如,你可以使用下面的代碼來設置必要變量的值:
  1. var aspect:Number = 4/3;
  2. var zNear:Number = 0.1;
  3. var zFar:Number = 1000;
  4. var fov:Number = 45*Math.PI/180;
  5. var projectionTransform:PerspectiveMatrix3D = new PerspectiveMatrix3D();
  6. projectionTransform.perspectiveFieldOfViewLH(fov, aspect, zNear, zFar);
複製代碼
如上所述,zNear和zFar 分別是近剪切平面和遠剪切平面;aspect是寬高比,而fov則是視角區域。

構建一個透視投影範例應用程序
在本章節中,你將使用矩陣來創建一個範例應用程序的簡單更新版本,我已經在我之前的Developer Center教程的一篇標題稱爲Hello Triangle的文章中提供該應用程序。 如果在學習本教程之前你還沒有完成相應的系列教程的閱讀,那麼請按照那些教程的指南來學習如何構建該範例項目的基礎組件,而你將利用下面步驟對該範例項目進行功能擴展。
在本範例中,你將渲染一個矩形而非一個三角形,這樣能夠更易於觀察到透視的效果。
首先,將投影矩陣附加(前乘)至用來定位和旋轉對象的矩陣轉換隊列中。 此外,下面的代碼還能夠給這些旋轉添加一個不同的自旋功能(spin),這樣能夠使得透視可見。

  1. var m:Matrix3D = new Matrix3D();
  2. m.appendRotation(getTimer()/30, Vector3D.Y_AXIS);
  3. m.appendRotation(getTimer()/10, Vector3D.X_AXIS);
  4. m.appendTranslation(0, 0, 2);
  5. m.append(projectionTransform);
複製代碼
下面是在透視投影演示應用程序中使用的完整代碼範例:
  1. public class PerspectiveProjection extends Sprite
  2. {
  3.         [Embed( source = "RockSmooth.jpg" )]
  4.         protected const TextureBitmap:Class;
  5.         
  6.         protected var context3D:Context3D;
  7.         protected var vertexbuffer:VertexBuffer3D;
  8.         protected var indexBuffer:IndexBuffer3D;
  9.         protected var program:Program3D;
  10.         protected var texture:Texture;
  11.         protected var projectionTransform:PerspectiveMatrix3D;
  12.         
  13.         
  14.         public function PerspectiveProjection()
  15.         {
  16.                 stage.stage3Ds[0].addEventListener( Event.CONTEXT3D_CREATE, initMolehill );
  17.                 stage.stage3Ds[0].requestContext3D();
  18.                 stage.scaleMode = StageScaleMode.NO_SCALE;
  19.                 stage.align = StageAlign.TOP_LEFT;                                       
  20.                 addEventListener(Event.ENTER_FRAME, onRender);
  21.         }
  22.         
  23.         protected function initMolehill(e:Event):void
  24.         {
  25.                 context3D = stage.stage3Ds[0].context3D;                        
  26.                 context3D.configureBackBuffer(800, 600, 1, true);
  27.                
  28.                 var vertices:Vector.<Number> = Vector.<Number>([
  29.                         -0.3,-0.3,0, 0, 0, // x, y, z, u, v
  30.                         -0.3, 0.3, 0, 0, 1,
  31.                         0.3, 0.3, 0, 1, 1,
  32.                         0.3, -0.3, 0, 1, 0]);
  33.                
  34.                 // 4 vertices, of 5 Numbers each
  35.                 vertexbuffer = context3D.createVertexBuffer(4, 5);
  36.                 // offset 0, 4 vertices
  37.                 vertexbuffer.uploadFromVector(vertices, 0, 4);
  38.                
  39.                 // total of 6 indices. 2 triangles by 3 vertices each
  40.                 indexBuffer = context3D.createIndexBuffer(6);                        
  41.                
  42.                 // offset 0, count 6
  43.                 indexBuffer.uploadFromVector (Vector.<uint>([0, 1, 2, 2, 3, 0]), 0, 6);
  44.                
  45.                 var bitmap:Bitmap = new TextureBitmap();
  46.                 texture = context3D.createTexture(bitmap.bitmapData.width, bitmap.bitmapData.height, Context3DTextureFormat.BGRA, false);
  47.                 texture.uploadFromBitmapData(bitmap.bitmapData);
  48.                
  49.                 var vertexShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler();
  50.                 vertexShaderAssembler.assemble( Context3DProgramType.VERTEX,
  51.                         "m44 op, va0, vc0\n" + // pos to clipspace
  52.                         "mov v0, va1" // copy uv
  53.                 );
  54.                 var fragmentShaderAssembler : AGALMiniAssembler= new AGALMiniAssembler();
  55.                 fragmentShaderAssembler.assemble( Context3DProgramType.FRAGMENT,
  56.                         "tex ft1, v0, fs0 <2d,linear,nomip>\n" +
  57.                         "mov oc, ft1"
  58.                 );
  59.                
  60.                 program = context3D.createProgram();
  61.                 program.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);
  62.                
  63.                 projectionTransform = new PerspectiveMatrix3D();
  64.                 var aspect:Number = 4/3;
  65.                 var zNear:Number = 0.1;
  66.                 var zFar:Number = 1000;
  67.                 var fov:Number = 45*Math.PI/180;
  68.                 projectionTransform.perspectiveFieldOfViewLH(fov, aspect, zNear, zFar);
  69.         }
  70.         
  71.         protected function onRender(e:Event):void
  72.         {
  73.                 if ( !context3D )
  74.                         return;
  75.                
  76.                 context3D.clear ( 1, 1, 1, 1 );
  77.                
  78.                 // vertex position to attribute register 0
  79.                 context3D.setVertexBufferAt (0, vertexbuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
  80.                 // uv coordinates to attribute register 1
  81.                 context3D.setVertexBufferAt(1, vertexbuffer, 3, Context3DVertexBufferFormat.FLOAT_2);
  82.                 // assign texture to texture sampler 0
  83.                 context3D.setTextureAt(0, texture);                        
  84.                 // assign shader program
  85.                 context3D.setProgram(program);
  86.                
  87.                 var m:Matrix3D = new Matrix3D();
  88.                 m.appendRotation(getTimer()/30, Vector3D.Y_AXIS);
  89.                 m.appendRotation(getTimer()/10, Vector3D.X_AXIS);
  90.                 m.appendTranslation(0, 0, 2);
  91.                 m.append(projectionTransform);
  92.                
  93.                 context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m, true);
  94.                
  95.                 context3D.drawTriangles(indexBuffer);
  96.                
  97.                 context3D.present();
  98.         }
  99. }
複製代碼
下一步閱讀方向
在本教程中,你已經瞭解3D渲染中一個最重要話題:透視投影。 在你較深入瞭解透視技術以及如何在Stage3D中實現它之後,你可以繼續學習本系列教程的下一教程。 在本系列教程的下一教程中,你將學習如何使用3D Camera並且瞭解如何在3D場景中實現一個運動的視點。
如需瞭解關於透視投影和3D相關數學函數的更多信息,我推薦你閱讀由James M. Van Verth和Lars M. Bishop編寫的一本名稱爲 “遊戲和互動應用程序的基本數學知識(Essential Mathematics for Games and Interactive Applications)”的好書。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章