天天看點

簡單的Viewing Frustum Culling

Viewing Frustum Culling是圖形繪制流水線中,将不可見物體(即不在視錐體内的物體)提前剔除的操作。

在實踐中,精确判斷物體的可見性開銷較大,因而通常用物體包圍球或包圍盒與視錐體(平截頭體,View frustum)做相交測試,以此粗略判斷物體是否可見。

進一步地,我們可以采用如下方式來大緻判斷一個球體與視錐體是否相交:

球與視錐體相交的必要(非充分)條件是:其中心P與視錐體的6個面的符号距離(Signed distance)d均小于球的半徑R。注意對于一個平面aX + bY+ cZ + d = 0 ([a, b, c]為平面法線方向N,d為平面與原點的符号距離), 一個點P(x, y, z)與該平面的符号距離為ax + by + cz + d。是以要粗略判斷物體與視錐體是否相交,隻需要将其包圍球與視錐體的6個面分别計算符号距離即可,如果其與任一面的符号距離大于R,則可安全剔除。

那麼如何算得視錐體每個面的面方程呢?一個比較通用的方法是求出視錐體的8個頂點,然後分别利用叉積和點積求出6個面的法線和原點距。 但如果我們已經知道投影矩陣,也可以用如下更簡單的方法算得面方程:(以Direct3D為例,OpenGL與之相似,但需要注意的是其規範化裝置坐标系的Z取值範圍為[-1,1]而非[0,1])

對于一個場景,設物體空間中有一球體,中心坐标為P,半徑為R,由某一視錐體定義的物體空間到規範化裝置空間的變換矩陣為M= World *View *Projection。現需要求該視錐體各個面在物體空間中的面方程。回顧規範化裝置坐标系的定義,我們可以很容易得知,M的效果可以看做是将視錐體的6個面分别變換到X=1, X=-1, Y=1, Y=-1, Z=0, Z=1這6個面上。例如,視錐體的近剪裁面經過M,會變換到Z=0平面上,遠剪裁面變換到Z=1平面上,其他4個面也是類似變換。那麼,對于位于視錐體近剪裁面上的任一一點P(x, y, z, 1),P' =P*M應該滿足P'.z / P'.w = 0, 即P'.z = 0,展開P*M可得:x*M._13 + y*M._23 + z*M._33 + M._43=0。這正是近剪裁面在物體空間中的平面方程。

同理,對于遠剪裁面有P'.z / P'.w = 1,展開P*M有x*(M._13-M._14) + y**(M._23-M._24)+ z*(M._33-M._34)+(M._43-M._44)=0,為遠剪裁面在物體空間中的平面方程。

同理可得很容易求得其他4個面的面方程。

如下示例了使用DirectXMath的求面方程以及相交測試的完整代碼:

// mWVP is the Word-View-Projection Matrix

XMFLOAT4 plane[6];
// x=1
plane[0].x = mWVP._11 - mWVP._14;
plane[0].y = mWVP._21 - mWVP._24;
plane[0].z = mWVP._31 - mWVP._34;
plane[0].w = mWVP._41 - mWVP._44;	
// x=-1
plane[1].x = -mWVP._14 - mWVP._11;
plane[1].y = -mWVP._24 - mWVP._21;
plane[1].z = -mWVP._34 - mWVP._31;
plane[1].w = -mWVP._44 - mWVP._41;
// y=1
plane[2].x = mWVP._12 - mWVP._14;
plane[2].y = mWVP._22 - mWVP._24;
plane[2].z = mWVP._32 - mWVP._34;
plane[2].w = mWVP._42 - mWVP._44;
// y=-1
plane[3].x = -mWVP._14 - mWVP._12;
plane[3].y = -mWVP._24 - mWVP._22;
plane[3].z = -mWVP._34 - mWVP._32;
plane[3].w = -mWVP._44 - mWVP._42;
// z=1
plane[4].x = mWVP._13 - mWVP._14;
plane[4].y = mWVP._23 - mWVP._24;
plane[4].z = mWVP._33 - mWVP._34;
plane[4].w = mWVP._43 - mWVP._44;
// z=0
plane[5].x = -mWVP._13;
plane[5].y = -mWVP._23;
plane[5].z = -mWVP._33;
plane[5].w = -mWVP._43;
XMVECTOR xmPlane[6];
// load and normalize
for( UINT i=0; i<6; i++ )
{
    xmPlane[i] = XMLoadFloat4( &plane[i] );
    xmPlane[i] = XMPlaneNormalize( xmPlane[i] );
}
// cull
for( UINT s=0; s<objects.size(); s++ ) // traverse all objects
{
    bool bInFrustum = true;
    XMVECTOR xmCenter = XMLoadFloat3( &objects[s].bSphere.center ); // bounding sphere
    float radius = objects[s].bSphere.radius;
    for( UINT i=0; i<6; i++ )
    {
        XMVECTOR xmD = XMPlaneDotCoord( xmPlane[i], xmCenter );
        float d; 
        XMStoreFloat( &d, xmD );
        if( d > radius )
        {
             bInFrustum = false;
             break;
        }
    }
    objects[s].isInFrustum = bInFrustum;
}
           

補充1:需要注意的是,透視投影的視錐體并非立方體,是以存在滿足前述判斷條件且與視錐體不相交的球體,但這種情況并不多見,故不做進一步判斷)

補充2:另外請注意上述代碼中面方程的符号。(例如plane[4]=[ -mWVP._13, -mWVP._23, -mWVP._33, -mWVP._43]定義了法線方向指向視錐體外的平面,而如果寫作[ mWVP._13, mWVP._23, mWVP._33, mWVP._43],則定義的是法線方向指向視錐體内的平面,此時需要将判斷條件更改為“若d<-R,則剔除”。)

轉載于:https://www.cnblogs.com/guoch/p/3140901.html