Unity 2D Light (1) - Light Mesh

2D陰影

生成2d陰影通常有兩種方案,一種是基於物理射線生成Light Mesh(也有叫ShadowMesh,我以爲叫LightMesh更貼切點)。另外一種同unity3D陰影原理,就有是生成ShadowMap。html

這篇記錄使用射線生成LightMesh的兩種方法。git


方法1:經過射線掃描可視區域

由於使用了物理射線,因此須要遮擋物體有碰撞器(Collider)組件。ide

參考

SIGHT & LIGHT性能

基本流程

  1. 經過射線按照角度依次遍歷可視區域
  2. 若是射線擊中物體則保存擊中點,未擊中則保存射線終點
  3. 若是當前射線的擊中狀態與前一擊中狀態不一樣則使用二分法找到邊角頂點

EG: AB兩射線中間發射一條射線C,未擊中則重複在AC中間再發射一條射線,直到擊中物體。擊中點D並不必定是邊緣點,偏差不可避免。若是到達設定最大次數仍未擊中,這時候視做A點爲邊角頂點便可。優化

image

  1. 構建三角面片

結果

若是設定了最大射線距離,結果會趨近一個圓。3d

image

邊角頂點(綠點)檢測偏差,轉角偏差沒法避免,只能經過增長射線來減小視覺瑕疵(基本上要增長到300以上纔沒有明顯的邊緣抖動現象),這樣會減小性能。code

image

若是限定射線距離,射線穿過邊角,下圖這種狀況會穿過物體錯誤。htm

image

解決辦法仍是要準確判斷擊中點是不是邊角,使用collider.OverlapPoint能夠判斷射線穿過碰撞體並停在碰撞體內。(紅框內的空白不應存在,並且還可能穿過碰撞體)blog

image

因此在沒有碰撞體頂點的參與計算下,瑕疵仍是挺多的。這樣還不如直接用第二種方法。固然增長足夠多的射線能夠解決視覺上的大部分瑕疵。排序

這種方案並不適合做爲2D陰影生成,可是做爲其餘好比人物視野顯示卻是挺合適。

image

image

優化(未驗證)

  1. 記錄全部碰撞體頂點
  2. 只發射光源到頂點的射線
  3. 經過兩個偏移(左右各偏移一點點)射線判斷是不是邊角頂點(若是有插值過的頂點法線更容易判斷)
  4. 其餘步驟相似

方法2(推薦):經過射線掃描邊端點

邏輯上掃描的是邊端點,概念上掃描的實際上是線段。

參考

2d Visibility

原理

掃描線段,記錄距離最接近光源的線段,當最近線段變化則使用兩條射線與前以最近線段相交構建三角網格。核心點是最近線段判斷和線段排序(保證先掃到線段開端,再掃線段結束端)。

image

基本流程

1. 初始化線段列表

經過邊頂點(即輪廓頂點),儲存全部線段到一個列表裏。

輪廓邊緣頂點獲取方式多種多樣,可使用默認Sprite頂點、Collider、Custom Physics Shape等。

默認生成的Sprite頂點並不徹底貼合邊緣。

image

Collider除了PolygonCollider2D其餘都須要另行計算。

推薦使用Custom Physics Shape,自動生成的更貼合邊緣,最重要的是能夠自定義。

image

自定義陰影投射外形幾乎是必需的。

image

2. 線段分割

光的邊界通常設定爲一個虛擬的正方形,將正方形的四條邊插入線段列表。

分割全部相交線段。

image

可選優化:1.相同邊合併。2.裁剪掉正方形外的線段。3.裁剪物體內的線段即只保留相交物體的輪廓邊線段(這個應該稍複雜點)

3. 初始化端點列表

將線段兩端儲存在一個列表。

線段頂點同時是一個線段的開始和另外一個線段的結束,因此端點列表的大小是頂點大小的兩倍。也就是說將有兩個位置同樣的端點,但一個表明線段開端,另外一個表明線段結束保存在端點列表裏。

端點須要包括的數據:1. 位置,2. 是不是開始端點,3. 端點所在的線段

接下來是比較重要的端點排序:

1. 計算端點相對與光源中心的弧度 radian
2. 經過弧度交換線段的開始和結束(180°的弧度突變處須要手動處理)
3. 經過弧度排序全部端點,相同弧度開始端點在前

Atan2計算出弧度的大小範圍是 (-3.14, 3.14] 即從-180°逆時針遞增,180°達到最大。因此180°爲起始掃描線。

之因此排序是保證接下來掃描時,首先掃到的是線段開端。

4. 掃描

參考文章2d Visibility上的交互示例是順時針掃描,我這邊是逆時針。不過沒什麼影響,只要保證先掃到線段開端,順逆時針的結果是同樣的。

起始掃描爲-180°,固然實際上遍歷的是排好序的端點列表。

掃描步驟僞代碼:

list<線段> open; // 保存當前掃描的線段,按與光源中心點的距離排序,即最接近光源中心的排最前面
beginRadian; // 掃描線所在弧度,初始化爲最初掃描到的最近線段的開端弧度
foreach (端點 in 端點列表)
{
    最近的線段_old = open.first() // 獲取最近的線段,可能爲空
    
    if(端點 是 開始) 將端點所在的線段保存到open(需排序)。
    else 將端點所在的線段從open中刪除。 // 由於前面排序保證了老是先掃描到開端,因此掃到結束端點時open必然有其所在的線段。
    
    最近的線段_new = open.first()
    if(最近的線段_new != 最近的線段_old)
    {
        保存構建三角網格頂點。 // 光源中心以 beginRadian,當前遍歷端點的弧度構建兩個射線 與 最近的線段_old 相交得兩個交點+光源中心構成三角網格的三個頂點。
        beginRadian = 端點的弧度
    }
}
  • 線段排序

線段排序參考 segment-sorting

  • 弧度突變區域線段處理

因爲端點遍歷的開端是最小弧度值的那個,下圖這種狀況,就會致使先掃到結束端,掃描快結束的時候才掃到開始端。

image

不經優化的作法是不構建三角網格先掃描一輪(原文中的作法),這樣結束時open裏就有當前掃描的線段(初始掃描射線穿過的線段)。優化的作法應該在前面排序處理弧度突變線段時處理。

5. 構建Mesh

image

源碼

link

相關文章
相關標籤/搜索