Away3D引擎學習筆記(三)模型拾取(翻譯)

原文詳見http://away3d.com/tutorials/Introduction_to_Mouse_Picking。本文如有翻譯不對的地方,敬請指出。git

 

本教程詳細介紹了Away3D 4.x中鼠標交互問題。github

內容:

n  介紹算法

n  Hello Picking編程

n  Entity屬性多線程

n  View屬性app

n  UV繪製ide

n  總結性能

介紹

每一個3D引擎都須要解決一個很是基礎的問題:鼠標下面是什麼?在3D圖形學中,這一般會涉及到拾取。雖然這個問題看似簡單、直接,但它實際上涉及到較難的數學和很是高效的算法。與2D拾取相比,這個問題確實會更復雜。在3D場景中檢測光標下面是哪一個物體多是個編程造價昂貴的工做。但彆着急!考慮到Flash Player的能力和瓶頸,Away3D爲處理這個問題提供了一系列精心設計的特性,爲每一個特殊的實現給予使用者充分的靈活性去尋求拾取的精度和性能間的平衡點。學習

本文咱們將會探究引擎的拾取特性。咱們將會知道拾取用法的簡便性,並將理解在一些高級苛刻的項目中如何掌控這種細微差異,從而達到高效的實現。測試

Hello Picking

單刀直入,進入話題。請看下面的app示例和示例下的代碼。

 

示例1(請點擊圖片進行加載app)。3D場景中基本的鼠標交互。懸停在物體上方,物體材料會從灰色變成紅色。源碼

示例中關鍵代碼以下所示:

anObj.mouseEnabled=true;

anObj.addEventListener(MouseEvent3D.MOUSE_OVER,onObjectMouseOver);

privatefunction onObjectMouseOver(event:MouseEvent3D):void{
 //do stuff
}

固然,如同在2D中同樣,你可使用全部其餘的鼠標事件類型,不過你得知道並熟悉這個接口。這裏沒什麼新奇之處。可是就像前面講述的同樣,在3D中拾取會變得很複雜。因此讓咱們看看在3D引擎中到底發生了什麼。

Entity屬性

在Away3D引擎中,有一系列的屬性用於管理拾取。

MouseEnabled

那麼,引擎是如何拾取的呢?總的來講,引擎會以照相機爲源點發出一條射線,穿過鼠標在電腦屏幕上的位置,直線進入3D場景中。這條射線可能會碰撞到場景中的幾個物體。一種肯定碰撞到哪些物體的方式是使用原射線碰撞數學。這會涉及到計算射線與球體的碰撞,射線與軸對齊包圍盒(AABB)的碰撞,射線與三角形的碰撞等等內容。這個過程當中代價最爲昂貴的就是要弄清楚射線是否碰撞到了網格(Mesh)內的一個三角形,而這個網格可能聚合了大量數目的三角形。目前,不是隻有射線跟蹤拾取一種方式。實際上,Away3D提供了兩種不一樣的拾取方式,默認使用射線跟蹤拾取法。咱們將會在本文的後續內容中探討這個緣由。射線跟蹤法以下圖1所示。

 

圖1.跟蹤碰撞法:從照相機發出射線,穿過屏幕,進入一個網格的包圍盒和網格的三角形內部.

設置物體的屬性mouseEnabled爲true,便可使其參與這些碰撞計算。此屬性默認設置爲flase,以免進行可能的複雜的運算。

如今,你給物體上設置了mouseEnabled爲true但沒有進行事件監聽,而這產生的結果就是射線跟蹤拾取被阻斷了。即物體與射線相撞了,但沒有觸發鼠標事件,如示例2所示。與示例1相比,示例2中僅是註釋掉了立方體的事件監聽。

 

示例2.MouseEnabled和偵聽器。兩個物體都設置了mouseEnabled屬性,但只有一個添加了鼠標事件偵聽器。未設置偵聽器的物體僅是阻斷了交互。源碼

若被阻斷了交互的物體屬性mouseEnabled爲false,拾取射線則會穿過它,而且徹底忽視它的存在。再次提醒,全部場景中物體的默認行爲都是這樣,除非你設置了mouseEnabled屬性爲true。在Flash 2D顯示API中多數是以mouseEnabled這種方式設置是否拾取,但由於在二維中拾取代價低廉,因此mouseEnabled默認值爲true。

Picking Colliders

在前面示例中(若將照相機旋轉到很是特殊的一些角度),你可能注意到了球體周圍的拾取不是很準確。在示例1或示例2中,若將鼠標懸停在很是接近球體的地方,即便實際上鼠標沒在物體上,仍會觸發一個MOUSE_OVER事件。這並非咱們所預期的。

 

圖2. 3D物體的鼠標交互,默認設置爲BOUNDS_ONLY.

Away3D引擎默認拾取精度爲BOUNDS_ONLY級別。結合圖1中的射線跟蹤碰撞法,咱們會發現這種默認設置會使射線中止在網格包圍盒的表面,從而阻止了射線與網格內三角形(多是成百上千,甚至百萬級別的三角形)的碰撞計算。若要提升拾取的準確度,僅須要讓射線繼續前行。使用下面的屬性能夠作到這點。

anObject.pickingCollider = PickingColliderType.BOUNDS_ONLY; // default

//anObject.pickingCollider = PickingColliderType.AS3_FIRST_ENCOUNTERED;

//anObject.pickingCollider = PickingColliderType.AS3_BEST_HIT;

// etc…

BOUNDS_ONLY是引擎提供的代價最爲低廉的拾取碰撞方式,在多少場景中這個精度已經夠用了。若想要更高的精度,咱們僅需選取一種不一樣的碰撞方式:

£  PickingColliderType.BOUNDS_ONLY(默認值)

計算射線與包圍盒的碰撞。

£  PickingColliderType.AS3_FIRST_ENCOUNTERED

計算射線與網格全部三角形的碰撞。與BOUNDS_ONLY相比,這種方式耗費至關大,而且網格內三角形數目越大耗費越高昂。在這種碰撞算法中,一旦檢測到與射線碰撞的一個三角形面就會中止與其他三角形面的碰撞檢測,雖然事實上這個檢測到的碰撞面可能並非網格中離照相機最近的表面。

£  PickingColliderType.AS3_BEST_HIT

這種計算方式跟上一個同樣,可是卻更具體,它會計算出沿射線離照相機最近的網格三角形面是哪個。射線與網格可能會有不止一個碰撞面,這種狀況下,若咱們想確切地知道在網格上的是哪一個碰撞點,該計算方式便頗有其存在的價值了。舉個例子:網格表明的是一個咖啡杯,AS3_FIRST_ENCOUNTERED可能會錯誤地認爲是與杯子的內側發生了碰撞;AS3_BEST_HIT在檢測到與射線發生碰撞的第一個三角形面後不會停下來,而是會找出全部的碰撞面,並計算出最優的那一個。

£  PickingColliderType.PB_FIRST_ENCOUNTERED

這種方式跟AS3_FIRST_ENCOUNTERED同樣,但射線碰撞算法採用的不是純ActionScript,而是pixel bender。通過證實,這種方式對高模拾取速度快,對低模拾取速度慢。若可能的話,pixel bender利用的是多線程。這意味着在桌面上這種方式每每會很快,可是在較爲簡單的CPU環境下,如移動設備上,可能就達不到這個效果了。此外,iOS的AIR版本目前不支持pixel bender,設置此種方式將不起做用。

£  PickingColliderType.PB_BEST_HIT

跟AS3_BEST_HIT同樣,但射線碰撞算法採用的是pixel bender。

£  PickingColliderType. AUTO_FIRST_ENCOUNTERED

跟AS3_FIRST_ENCOUNTERED同樣。可是這種方式會根據網格的多邊形數量自動決定採用ActionScript仍是pixel bender。

£  PickingColliderType. AUTO_BEST_HIT

同AUTO_FIRST_ENCOUNTERED。可是算法過程同AS3_BEST_HIT。

 

如你所見,這裏提供了許多種選擇!要決定使用哪一種拾取碰撞方式是個微妙的話題。理解每種可用的拾取碰撞類型對於尋求性能和準確度間的最佳平衡點很重要。例如,app中渲染的是一個高聚的網格,你想在網格上獲取很是精準的拾取,那麼你將選取PB_BEST_HIT;如若在遊戲中,要在一個低聚的網格上進行單擊,而且這個網格只佔屏幕的一小部分,那顯然不會選擇PB_BEST_HIT。這種低劣的選擇尤爲是在給許多低聚的物體設置這種類型的鼠標交互後,會浪費資源。決定使用哪一個碰撞類型是很是重要的。可是一旦理解了每種碰撞類型的算法過程,這將是個簡單的任務。因此,對於圖2中的問題,咱們只需給物體設置一個不一樣的碰撞類型。

1 sphere.pickingCollider = PickingColliderType.AS3_FIRST_ENCOUNTERED;

或者,咱們僅需將球體的包圍盒由AxisAlignedBoundingBox(Away3D將全部的包圍盒默認設置爲AxisAlignedBoundingBox)改變爲BoundingSphere。這樣無需使射線進入包圍盒內而且進行射線與三角形的碰撞計算,咱們即可得到拾取的精準度。固然,這是一個很是特殊,幾乎是理論層次的例子。可是,咱們須要銘記這種拾取技術的核心是包圍盒,而且不一樣形狀的包圍盒可能會頗有用。記住這點很重要。如何修改包圍盒,以下代碼所示:

1 sphere.bounds = new BoundingSphere();

這兩種方法均可以解決上述拾取精準度的問題,結果如示例3中所示:

 

示例3.經過修改拾取碰撞類型或是包圍盒類型,從而在球上進行更精準的拾取。源碼

View 屬性

除了Entity具備拾取屬性外,Away3D的拾取還包括全局屬性。如今開始學習這些全局屬性。

Shader vs. Raycast Mouse Pickers

如前面所述,Away3D爲拾取計算提供了一種徹底不一樣的方法。目前爲止,咱們已經研究了單個對象的拾取屬性。咱們也能夠在全局範圍內改變拾取的工做方式。

1 view.mousePicker = PickingType.SHADER;

有如下幾個枚舉值供選擇:

£  PickingType.RAYCAST_FIRST_ENCOUNTERED(默認值)

使用射線跟蹤做爲拾取方式。一旦成功找到第一個被渲染的物體(first successful renderable)就會中止繼續搜索。

£  PickingType.RAYCAST_BEST_HIT

使用射線跟蹤做爲拾取方式。在全部的碰撞渲染體(all the colliding renderables)中,計算最佳的碰撞渲染體。

£  PickingType.SHADER

使用着色技術做爲拾取方式。老是計算最佳的那個碰撞體。

前兩個選項間的差異是很細微的。咱們首先來看一下前兩個選項與第三個選項間的區別,這表明着徹底不一樣的兩種拾取技術。如前面所述,前兩個枚舉值所表示的拾取技術都是基於場景中射線與實體的碰撞計算。第三個選項(SHADER)根本不是計算射線與幾何體的碰撞(ray-geometry collisions),而是首先用特殊的顏色將鼠標周圍場景的一部分渲染成一個臨時緩衝,而後分析鼠標正下方的顏色以找出是碰撞到了哪一個物體。這種技術是很難形象化的。事實上是最高端的3D引擎在使用這種拾取技術。經證實,這種技術在精度和性能方面都是很是有效的。遺憾的是,目前這種技術在Flash的Stage3D上性能沒那麼好。這是由於,它須要經過Context3D的方法drawToBitmapData()將GPU上的圖像傳送到CPU,而drawToBitmapData()的速度不快。你能夠從這篇文章中瞭解到這些限制。這種技術介紹了一個嚴重的瓶頸,這個瓶頸也就是Away3D還提供了可選的射線碰撞技術,並默認使用這種拾取的惟一緣由。經證實,在Flash中RAYCAST速度比SHADER快。請記住,在每一個視圖(view)中,你只能使用一種拾取技術,不能同時使用。即就目前而言,不能讓一些物體使用着色(shader),而讓另一些物體使用射線(raycast)。

你可能會產生這樣的疑問:既然已經證實shader較慢了,爲何Away3D還提供基於shader的拾取技術?由於在一些場合下仍是頗有必要的。例如,在處理GPU動畫和旋轉時,幾何體可能被CPU後臺改變了,而在GPU上射線方法不可能知道頂點的位置,也就不可以正確進行射線幾何體的碰撞計算了。由於shader方法不是基於射線幾何體的碰撞,而且處理的是屏幕上實實在在看到的東西,因此在處理運動體這種須要高度精準的拾取時,就選用這種方法。示例4展現了在運動體上使用基於射線與着色拾取技術的效果。這個演示中,咱們將使用一個球體來追蹤鼠標射線與網格的碰撞點,還使用一條線段來追蹤網格上基於這個碰撞點的法向量。

 

示例4.在運動體上的拾取。對非變換的幾何體使用射線拾取法,對可見幾何體的正確碰撞使用着色法。源碼

請注意射線法看不到GPU上頂點的變換,而是隻能做用於靜態的網格。而着色法中不存在這個問題。此外,基於shader的拾取法會忽略」.pickingCollider」這個實體(Entity)的屬性,由於這個屬性僅適用於基於射線的拾取法。而shader拾取法關心的是實體的」.shaderPickingDetails」屬性。

1 anObject.shaderPickingDetails = true;

這個屬性的做用僅是不管是否存在鼠標事件,實體中是否包含位置、法向量等數據,都會告知着色拾取器進行計算。同使用射線法同樣,計算這些信息也是須要開銷的,因此應該在須要的時候才選取這種方式。此屬性若設置爲false,將會觸發鼠標事件,可是事件的一些屬性將是空值或無效值。該屬性默認設置爲false,示例4中,咱們需將其啓用以使用着色拾取器取到鼠標事件在場景中的發生位置。

但願Adobe公司有一天能解決drawToBitmapData()的瓶頸,使咱們的生活變得更容易!

The Raycast Mouse Pickers

在前面的部分,咱們討論了射線拾取器與着色拾取器間的不一樣。是時候討論這兩個可用的射線拾取器——RAYCAST_FIRST_ENCOUNTERED和RAYCAST_BEST_HIT——間的不一樣了。這倆其實都是相同的拾取器,但略有不一樣的設置。這裏「最佳碰撞」和「第一次」的區別同Entity屬性」.pickingCollider」的枚舉值「最佳碰撞」和「第一次」。只是在這種狀況下,測試中止檢查的標準是」renderable」,而不是三角形。

£  PickingType.RAYCAST_FIRST_ENCOUNTERED(默認值)

使用射線拾取技術,成功遇到第一個可呈現碰撞體就中止測試。

£  PickingType.RAYCAST_BEST_HIT

使用射線拾取技術,從全部可呈現碰撞體中找出最佳的。

可是「可呈現」(renderable)是指什麼呢?射線拾取系統依賴的是實體的包圍盒,一個實體卻能夠包含多個子網格。這些子網格共用一個單一的包圍盒。一個子網格就是一個可呈現體,一個可呈現體指:能夠繪製到屏幕上的對象。子網格的存在是由於Stage3D限制了單緩衝中可放置元素的數目。因此,一個網格中多邊形的數目超出了這個限制,就會在一個新的子網格中建立一系列新緩衝區。這些緩衝區表明頂點、法向量、uv等。選取了RAYCAST_FIRST_ENCOUNTERED後,拾取系統將不會關心哪個子可呈現碰撞體離照相機最近,而一旦找到一個活躍碰撞體就會中止碰撞檢查。而選取RAYCAST_BEST_HIT後,拾取系統將會在全部的可呈現體中拾取到離照相機最近的那一個。RAYCAST_BEST_HIT專爲有大網格分紅多個子網格的應用場合而設計。

這是一個很是細微的差異,確實不容易掌握,可是最好注意這點。RAYCAST_BEST_HIT的使用應該不是很廣泛,可是也有其應用場景。例如,若是沿着鼠標射線的軌跡對齊一組立方體,使用RAYCAST_FIRST_ENCOUNTERED對其進行拾取就能夠作的很好;可是若是對於一樣的幾何體,導入的是一個網格,網格包含了一組對齊的立方體(是的,奇怪的狀況),那麼使用RAYCAST_BEST_HIT是不會有問題的。下面的示例5說明了這點。咱們添加了另外一個朝向擊中點的圓錐示蹤來告訴咱們它在哪裏。

 

示例5.含多個子網格的一個網格上的兩個可用的射線拾取器。RAYCAST_BEST_HIT有效,而RAYCAST_FIRST_ENCOUNTERED沒效。源碼

如你所見,若是鼠標射線擊中多餘一個子網格,RAYCAST_FIRST_ENCOUNTERED將辨別不出那個最佳的碰撞體。然而,RAYCAST_BEST_HIT老是能找到那個對的。請注意在網格上是咱們是如何使用AS3_FIRST_ENCOUNTERED碰撞器的,不然用於檢查碰撞的對象不是子網格的三角形而是網格的包圍盒……更糟糕!一樣,請注意咱們將子對象基於z軸進行了無序排列。若是不作這點,樹中的子網格順序將會同它們與射線碰撞的順序發生巧合,因此會僅因巧合而產生正確的結果。

目前,咱們已經講述了3個可用的鼠標拾取器和獨立對象的7個可用拾取碰撞法。是挺多的!不過不要擔憂,到這裏實際上咱們已經覆蓋了Away3D引擎拾取的幾乎全部內容。全部這些爲咱們在項目工程中有效地使用鼠標交互提供了靈活性。若不懂每一個特性,你會感到疑惑,可是一旦理解了,爲每種實現作出決定將很容易。

UV繪製

拾取系統中最後一點值得一提的就是:MouseEvent3D。簡單,但很重要。示例4和5中使用到的MouseEvent3D的屬性scenePosition,容許咱們追蹤網格上射線與鼠標交點的位置。MouseEvent3D還提供了其餘一系列有用的屬性:

l  scenePosition

場景空間中事件碰撞位置

l  localPosition

物體本地空間中事件碰撞位置

l  sceneNormal

場景空間中碰撞點的法向量

l  localNormal

物體本地空間中碰撞點的法向量

l  uv

在碰撞點內插的uv座標。射線包圍盒碰撞方式和着色碰撞方式下,uv是不可用的。

l  screenX and screenY

鼠標事件在屏幕空間中的位置

l  material

碰撞呈現體的材料

l  etc…

以下示例將使用位置、法向量和uv屬性繪製一條表明事件法向量的線段,並在物體材質上進行繪製。

 

示例6.在物體上繪製。源碼

在這個示例中,咱們使用了PB_BEST_HIT做爲網格的拾取器。考慮到這個網格的多邊形數目這個選取是合理的。

如你所見,MouseEvent3D提供了關於事件的有用信息,提供了3D場景中複雜鼠標交互所需的幾乎全部信息。太有趣了!

總結

咱們已經看到了,3D拾取不是一個無足輕重的話題。要開發出高質量的應用程序,咱們須要正確地理解並掌握它。很幸運Away3D爲如此重要的工做提供了這麼一個靈活的特性。

咱們但願本教程對您有所幫助,但願您對Away3D的拾取原理有了很好的理解。

相關文章
相關標籤/搜索