講講不怎麼有用卻頗有意義的包圍體測試

大多遊戲程序員和圖形程序都知道渲染流水線這個概念,它的本質是將3D的場景映射到顯示屏上的一系列操做。它主要分3個階段:應用程序階段,幾何階段,光柵化階段。將攝像機位置,光照,模型的圖元輸入到幾何階段即是應用程序階段。進行多邊形和頂點操做把3d數據映射到2d的階段即是幾何階段。給定進過變換和投影以後的頂點,顏色,紋理座標,給每一個像素正確配色,這個階段叫光柵化階段。具體的流水線概念,這篇文章不作詳細介紹,這篇文章主要講解幾何階段中的包圍體測試。程序員

幾何階段簡介

幾何階段分幾個階段,流水線上按順序爲頂點數據等執行如下操做c#

  1. 模型視圖變換:模型空間變換到觀察空間
  2. 頂點着色:光照處理
  3. 投影:將模型變換到一個單位立方體內
  4. 裁剪:裁剪不顯示的部分
  5. 屏幕映射:映射到屏幕座標系

而物體剔除和背面消隱是處於模型視圖變換的可選操做。數據結構

包圍體測試的價值

在模型視圖變換階段時須要將物體座標系的點先轉換到世界座標系,而後再由世界座標系轉換到視圖座標系(相機座標系)函數

然而執行世界座標到相機座標前,須要進行幾個測試。來判斷物體對相機是否可見,避免場景過大時形成大量多餘的計算,而後準確無誤地渲染物體。而這些測試被稱爲隱藏面消除。工具

執行的測試有2種,一種是背面消隱,一種是包圍體測試。測試

背面消隱的原理能夠看這篇博客,我這篇就主要講不多被說起的包圍體測試的原理this

講原理了

原理很簡單:就是用一個球體把物體包起來,而後判斷球體是否在視景體外,是則丟棄這個物體。‘.net

半徑的得到

如今咱們假設有一個物體,包含一組頂點,要找到離中心遠最遠的頂點,最遠頂點到中心點的距離即是球體的半徑,代碼實現以下3d

class GameObject
    {
        public float max_radius = 0;//最大半徑
        public Mesh mesh;//網格
        public Vector3 position = Vector3.zero;//座標
        public Vector3 rotation = Vector3.zero;
        public Matrix4x4 ObjectToWorldMatrix;//模型-世界矩陣
        public GameObject(Mesh mesh,Vector3 position)
        {
            this.mesh = mesh;
            this.position = position;
            CalculateMaxRadius();
        }
        /// <summary>
        /// 計算半徑
        /// </summary>
        private void CalculateMaxRadius()
        {
            int size = mesh.Vertices.Length;
            for (int i = 0; i < size; ++i)
            {
                //計算物體包圍球的最大半徑
                max_radius = System.Math.Max(max_radius,
                    Vector3.DistanceSquare(position, mesh.Vertices[i].m_vertex.position));
            }
            max_radius = (float)System.Math.Sqrt(max_radius);
        }


    }

判斷球體是否在視景體外

爲了便於理解,我用畫圖工具畫了個視景體的圖。視景體就是塗畫的淺藍色部門區域。code

相機座標系俯視圖

不過爲了計算,程序裏的視景體是個由遠裁剪面,近裁剪面,y = ymax(視景體內點y座標的最大值),y = ymin,x = xmax,x = xmin六個面構成的長方體,咱們只須要判斷球體是否在長方體內便可。也就是判斷球體的6個臨界頂點是否在視景體內便可。

遠近裁剪面

首先咱們判斷球體是否在遠近裁剪面間

只須要判斷球體z座標最大的點的z座標值是否在比近裁面的z值小,球體z座標最小的點的z座標值是否比遠裁面的z值大便可。代碼實現以下

//遠近裁剪面裁剪
if (SphereCenterPos.z - go.max_radius> camera.zf||
    SphereCenterPos.z + go.max_radius < camera.zn)
{
    return go.mesh.CullFlag = true;
}

左右裁剪面和上下裁剪面

接下來是對左右裁剪面和上下裁剪面的判斷,不過因爲須要計算xmin,ymin,xmax,ymax,咱們須要先得到攝像頭的焦距

焦距就是位於視景體內的黃線長度,也就是視景體的z深度

焦距的計算和攝像頭數據結構定義代碼以下

class Camera
    {
        /// <summary>
        /// 觀察角,弧度
        /// </summary>
        public float fov;
        /// <summary>
        /// 寬縱比
        /// </summary>
        public float aspect;
        /// <summary>
        /// 近裁平面
        /// </summary>
        public float zn;
        /// <summary>
        /// 遠裁平面
        /// </summary>
        public float zf;
 
        /// <summary>
        /// 屏幕寬度
        /// </summary>
        public int ScreenHeight;
 
        /// <summary>
        /// 世界-視圖 4x4矩陣
        /// </summary>
        public Matrix4x4 WorldToViewMatrix;
        /// <summary>
        /// 視圖-投影 4x4矩陣
        /// </summary>
        public Matrix4x4 ViewToProjectionMatrix;
 
        public Vector3 pos;
        public Vector3 lookAt;
        public Vector3 up;
 
        /// <summary>
        /// 焦距
        /// </summary>
        public float FocalLength
        {
            get
            {
                return (float)(1f/ System.Math.Tan(fov * 0.5f) * ScreenHeight/2);
            }
        }
 
 
    }

經過代碼咱們能夠看到,焦距的計算,只是簡單的三角函數的變換,不作詳細介紹,不理解能夠結合下圖來理解

y-z平面

完整代碼

/// <summary>
        /// 物體剔除-包圍球測試
        /// </summary>
        private bool CullObject(GameObject go,Vector3 SphereCenterPos)
        {
            if (go.mesh.CullFlag)
                return true;
            Camera camera = Rendering_pipeline.MainCamera;
 
            //遠近裁剪面裁剪
            if (SphereCenterPos.z - go.max_radius> camera.zf||
               SphereCenterPos.z + go.max_radius < camera.zn)
            {
                return go.mesh.CullFlag = true;
            }
 
            float FocalLength = camera.FocalLength;//得到焦距
 
            //左右裁剪面剔除
            float z_test = 0.5f * camera.aspect * camera.ScreenHeight *
                SphereCenterPos.z / FocalLength;
            if (SphereCenterPos.x - go.max_radius > z_test ||
               SphereCenterPos.x + go.max_radius < -z_test)
            {
                return go.mesh.CullFlag = true;
            }
 
            //上下裁剪面剔除
            z_test = 0.5f * camera.ScreenHeight *
                SphereCenterPos.z / FocalLength;
            if (SphereCenterPos.y - go.max_radius > z_test ||
              SphereCenterPos.y + go.max_radius < -z_test)
            {
                return go.mesh.CullFlag = true;
            }
 
            return go.mesh.CullFlag = false;
        }

包圍體測試的侷限性

程序中的視景體實際上並無成功表明整個視景體,包圍球也不必定很好地表明整個物體。並且存在物體部分位於視景體的狀況,包圍體測試不必定管用。但這不表明包圍體測試沒有意義,至少在遊戲中,能有效避免對不可見的大型區域進行處理。

相關文章
相關標籤/搜索