体素风格游戏中,环境光剔除(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/

这位大神说的很明白,关于这个帖子的其他问题,我们后面一一进行拆解。下回见。

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