體素風格遊戲中,環境光剔除(Ambient Occuling)技術的實現

首先,解釋清楚什麼是環境光剔除(Ambient Occlusion short for AO)

AO技術可以讓虛擬環境更加真實而有效地模擬出現實世界的光照,其基本思想是,從一個點的每個方向散射的環境光,被場景遠處的地方接收到的數量,估算出接收到的光數量,就代表該點處的AO值。所以,有了這個想法的基礎,通過數學上或經驗的論證,可以給定點可見的半球表面積來計算:

                                            

給場景添加環境光剔除,可以極大的提高視覺保真度,正因如此,如何計算環境光剔除的方法已經引起了很多的思考,但就目前的狀況來看,有兩種方式實現:

  1. 靜態的算法:提前計算好場景中幾何體的環境光剔除
  2. 動態的算法:每幀計算場景中幾何體的環境光剔除

著名的算法是:SSAO(Screen—Space Ambient Occlusion),其基本思路是,通過攝像機的深度緩衝(z緩衝區),來近似計算出每個像素的可訪問性,這樣就可以用來遮蔽屏幕上的所有像素了。SSAO技術的亮點在於,很容易集成到現有的渲染管線中(尤其是延遲着色—後期效果處理),但是不足之處在於,深度緩衝區並不能真實地表示場景中幾何體,所以有時候會出現許多奇怪的僞像。

體素場景中的環境光剔除技術

講到這裏,對於體素類的遊戲,要是先環境光剔除,有一個非常快的算法。其基本思想是,一個體素的每個頂點處的環境光剔除值僅跟其相鄰的體素相關。基於此,一個頂點可能最多有4種的剔除類型:

                                                                    

由此,我們可以推導出一個計算公式,邊1和邊2的值(0和1)取決於其側面的體素的存在與否,Corner的值取決於其角部的體素存在與否。公式如下:

                                      

具體來講,將一個cube劃分成六個面,每個面上四個點,每個點搜索它的左、右、上、對角四個方向上有沒有cube。


    /// <summary>
    /// 24 個點,每個點下有四個方向:上、左、右、對角
    /// </summary>
public static List<List<Vector3>> Pads = new List<List<Vector3>>
    {
        new List<Vector3>()
        {
            new Vector3(0,0,1),
            new Vector3(1,0,1),
            new Vector3(0,-1,1),
            new Vector3(1,-1,1),
        },
        new List<Vector3>()
        {
            new Vector3(0,0,1),
            new Vector3(-1,0,1),
            new Vector3(0,-1,1),
            new Vector3(-1,-1,1),
        },
        new List<Vector3>()
        {
            new Vector3(0,0,1),
            new Vector3(1,0,1),
            new Vector3(0,1,1),
            new Vector3(1,1,1),
        },
        new List<Vector3>()
        {
            new Vector3(0,0,1),
            new Vector3(-1,0,1),
            new Vector3(0,1,1),
            new Vector3(-1,1,1),
        },
        new List<Vector3>()
        {
            new Vector3(0,1,0),
            new Vector3(0,1,-1),
            new Vector3(1,1,0),
            new Vector3(1,1,-1),
        },
        new List<Vector3>()
        {
            new Vector3(0,1,0),
            new Vector3(-1,1,0),
            new Vector3(0,1,-1),
            new Vector3(-1,1,-1),
        },
        new List<Vector3>()
        {
            new Vector3(0,0,-1),
            new Vector3(1,0,-1),
            new Vector3(0,-1,-1),
            new Vector3(1,-1,-1),
        },
        new List<Vector3>()
        {
            new Vector3(0,0,-1),
            new Vector3(-1,0,-1),
            new Vector3(0,-1,-1),
            new Vector3(-1,-1,-1),
        },
        new List<Vector3>()
        {
            new Vector3(0,1,0),
            new Vector3(1,1,0),
            new Vector3(0,1,1),
            new Vector3(1,1,1),
        },
        new List<Vector3>()
        {
            new Vector3(0,1,0),
            new Vector3(-1,1,0),
            new Vector3(0,1,1),
            new Vector3(-1,1,1),
        },
        new List<Vector3>()
        {
            new Vector3(0,0,-1),
            new Vector3(1,0,-1),
            new Vector3(0,1,-1),
            new Vector3(1,1,-1),
        },
        new List<Vector3>()
        {
            new Vector3(0,0,-1),
            new Vector3(-1,0,-1),
            new Vector3(0,1,-1),
            new Vector3(-1,1,-1),
        },
        new List<Vector3>()
        {
            new Vector3(0,-1,0),
            new Vector3(1,-1,0),
            new Vector3(0,-1,-1),
            new Vector3(1,-1,-1),
        },
        new List<Vector3>()
        {
            new Vector3(0,-1,0),
            new Vector3(1,-1,0),
            new Vector3(0,-1,1),
            new Vector3(1,-1,1),
        },
        new List<Vector3>()
        {
            new Vector3(0,-1,0),
            new Vector3(-1,-1,0),
            new Vector3(0,-1,1),
            new Vector3(-1,-1,1),
        },
        new List<Vector3>()
        {
            new Vector3(0,-1,0),
            new Vector3(-1,-1,0),
            new Vector3(0,-1,-1),
            new Vector3(-1,-1,-1),
        },
        new List<Vector3>()
        {
            new Vector3(-1,0,0),
            new Vector3(-1, -1,0),
            new Vector3(-1,0,1),
            new Vector3(-1,-1,1),
        },
        new List<Vector3>()
        {
            new Vector3(-1,0,0),
            new Vector3(-1,1,0),
            new Vector3(-1,0,1),
            new Vector3(-1,1,1),
        },
        new List<Vector3>()
        {
            new Vector3(-1,0,0),
            new Vector3(-1,1,0),
            new Vector3(-1,0,-1),
            new Vector3(-1,1,-1),
        },
        new List<Vector3>()
        {
            new Vector3(-1,0,0),
            new Vector3(-1,-1,0),
            new Vector3(-1,0,-1),
            new Vector3(-1,-1,-1),
        },
        new List<Vector3>()
        {
            new Vector3(1,0,0),
            new Vector3(1,-1,0),
            new Vector3(1,0,-1),
            new Vector3(1,-1,-1),
        },
        new List<Vector3>()
        {
            new Vector3(1,0,0),
            new Vector3(1,1,0),
            new Vector3(1,0,-1),
            new Vector3(1,1,-1),
        },
        new List<Vector3>()
        {
            new Vector3(1,0,0),
            new Vector3(1,1,0),
            new Vector3(1,0,1),
            new Vector3(1,1,1),
        },
        new List<Vector3>()
        {
            new Vector3(1,0,0),
            new Vector3(1,0,1),
            new Vector3(1,-1,0),
            new Vector3(1,-1,1),
        },

    };

構造完一個 cube 上 24 個頂點處的4個方向(這24個點的順序必須和方塊 mesh 中的頂點一一對應),接下來就判斷每個點的每個方向上時候有沒有方塊存在:

private static int isOccupied(Vector3 pos)
    {
        int x = (int)pos.x;
        int y = (int)pos.y;
        int z = (int)pos.z;

        if (x < 0 || y < 0 || z < 0)
        {
            return 0;
        }

        if (// constrain condition, x y z can not beyond ths largest shape)
        {
            return // your condition, if cube exists at position (x,y,z) 1,else 0;
        }
        else
        {
            return 0;
        }        
    }

 準備完成之後,需要計算,其主要邏輯如下:

    /// <summary>
    /// 計算 pos 處的 cube 在全體 cubes 中的 AO 值,保存在每個頂點顏色的 alpha 值中
    /// </summary>
    /// <param name="pos">該 cube 的位置信息</param>
    /// <param name="color">該 cube 的顏色</param>
    /// <returns></returns>
public static Color[] GetAOVetexColorsByPosAndColor(Vector3 pos, Color color)
    {
        Color[] colors = new Color[24];
        for (int i = 0; i < 24; i++)
        {
            List<Vector3> offset = Pads[i];
            int u = isOccupied(pos + offset[0]);
            colors[i] = new Color(color.r, color.g, color.b);
            if (u == 1)
            {
                colors[i].a = 1;
                continue;
            }
            int r = isOccupied(pos + offset[1]);
            int l = isOccupied(pos + offset[2]);
            int c = isOccupied(pos + offset[3]);
            if (l == 1 && r == 1)
            {
                colors[i].a = 0.6f;
                continue;
            }
            else
            {
                int type = 3 - (r + l + c);
                switch (type)
                {
                    case 0:
                        colors[i].a = 0.7f;
                        break;
                    case 1:
                        colors[i].a = 0.8f;
                        break;
                    case 2:
                        colors[i].a = 0.85f;
                        break;
                    case 3:
                        colors[i].a = 1f;
                        break;
                    default:
                        break;
                }
            }
        }
        return colors; 
    }

調用如下代碼實現:

    public void UpdateAOColor()
    {
        this.vertexAOColors = VertexAOUtils.GetAOVetexColorsByPosAndColor(this.point.pos, this.color);
        this.mesh.colors = this.vertexAOColors;
    }

以上的邏輯,最主要的是那24個點的構造,每個點有四個方向:up、left、right、corner;這四個方向決定了這個點出的AO值類型,得到類型之後,我們定義一個規則:左右都有的類型alpha值爲 0.6,0類型的 alpha 值0.75, 1 類型的爲0.8, 2 類型的爲 0.85,3 類型的爲1(即不做處理)因此,將一個 mesh 的 AO 信息存儲在 mesh 的 colors 中,由此可見,跟上面的分類對上號。

當然,計算完顏色信息之後,還需要在 shader 中使用頂點的顏色信息信息,計算一些效果,關於這部分先不表,下次我們詳細說明如何在 shader 中使用 mesh 的顏色信息在屏幕上正確渲染出 AO 效果。

參看:https://0fps.net/2013/07/03/ambient-occlusion-for-minecraft-like-worlds/

這位大神說的很明白,關於這個帖子的其他問題,我們後面一一進行拆解。下回見。

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