原文地址:http://www.linuxgraphics.cn/graphics/opengl_view_frustum_culling.html
背景
視錐體(frustum),是指場景中攝像機的可見的一個錐體范圍。它有上、下、左、右、近、遠,共6個面組成。在視錐體內的景物可見,反之則不可見。為提高性能,只對其中與視錐體有交集的對象進行繪制。
![]() |
視錐體 |
我們計算出視錐體六個面的空間平面方程,將點坐標分別代入六個面的平面方程做比較,則可以判斷點是否在視錐體內。
空間平面方程可表示為:
Ax+By+Cz=0
對於點(x1, y1, z1),有
若 Ax1+By1+Cz1 = 0,則點在平面上; 若 Ax1+By1+Cz1 < 0,則點在平面的一側; 若 Ax1+By1+Cz1 = 0,則點在平面的另一側;
求視錐平面系數1
這里介紹的算法,可以直接從世界、觀察以及投影矩陣中計算出Viewing Frustum的六個面。它快速,准確,並且允許我們在相機空間(camera space)、世界空間(world space)或着物體空間(object space)快速確定Frustum planes。
我們先僅僅從投影矩陣(project)開始,也就是假設世界矩陣(world)和觀察矩陣(view)都是單位化了的矩陣。這就意味着相機位於世界坐標系下的原點,並且朝向Z軸的正方向。
定義一個頂點v(x y z w=1)和一個4*4的投影矩陣M=m(i,j),然后我們使用該矩陣M對頂點v進行轉換,轉換后的頂點為v'= (x' y' z' w'),可以寫成這樣:
轉換后,viewing frustum實際上就變成了一個與軸平行的盒子,如果頂點 v' 在這個盒子里,那么轉換前的頂點 v 就在轉換前的viewing frustum里。在OpenGL下,如果下面的幾個不等式都成立的話,那么 v' 就在這個盒子里。
-w' < x' < w' -w' < y' < w' -w' < z' < w'
可得到如下結論,列在下表里:
我們假設現在想測試 x' 是否在左半邊空間,只需判斷
-w < x'
用上面的信息,等式我們可以寫成:
−(v • row4 ) < (v • row1 ) 0 < (v • row4 ) + (v • row1 ) 0 < v • (row4 + row1 )
寫到這里,其實已經等於描繪出了轉換前的viewing frustum的左裁剪面的平面方程:
x(m41 + m11) + y(m42 + m12) + z(m43 + m13) + w(m44 + m14) = 0
當W = 1,我們可簡單成如下形式:
x(m41 + m11) + y(m42 + m12) + z(m43 + m13) + (m44 + m14) = 0
這就給出了一個基本平面方程:
ax + by + cz + d = 0
其中,a = ( m41 + m11) , b = ( m42 + m12 ), c = ( m43 + m13) , d = ( m44 + m14 )
到這里左裁剪面就得到了。重復以上幾步,可推導出到其他的幾個裁剪面,具體見參考文獻1.
需要注意的是:最終得到的平面方程都是沒有單位化的(平面的法向量不是單位向量),並且法向量指向空間的內部。這就是說,如果要判斷 v 在空間內部,那么6個面必須都滿足ax + by + cz + d > 0
到目前為止,我們都是假設世界矩陣( world )和觀察矩陣( view )都是單位化了的矩陣。但是,本算法並不想受這種條件的限制,而是希望可以在任何條件下都能使用。實際上,這也並不復雜,並且簡單得令人難以置信。如果你 仔細想一下就會立刻明白了,所以我們不再對此進行詳細解釋了,下面給出3個結論:
- 1. 如果矩陣 M 等於投影矩陣 P ( M = P ),那么算法給出的裁剪面是在相機空間(camera space)
- 2. 如果矩陣 M 等於觀察矩陣 V 和投影矩陣 P 的組合( M = V * P ),那么算法給出的裁剪面是在世界空間(world space)
- 3. 如果矩陣 M 等於世界矩陣 W,觀察矩陣 V 和投影矩陣 P 的組合( M = W* V * P ),呢么算法給出的裁剪面是在物體空間(object space)
判斷節點是否在視錐內
通過各種包圍體方法求出近似包圍體,對包圍體上的各個點對視錐六個面作判斷,存在以下三種情況:
- 如果所有頂點都在視錐范圍內,則待判區域一定在視錐范圍內;
- 如果只有部分頂點在視錐范圍內,則待判區域與視錐體相交,我們同樣視為可見;
- 如果所有頂點都不在視錐范圍內,那么待判區域很可能不可見了,但有一種情況例外,就是視錐體在長方體以內,這種情況我們要加以區分。
基於OpenGL實現
float g_frustumPlanes[6][4]; void calculateFrustumPlanes( void ) { float p[16]; // projection matrix float mv[16]; // model-view matrix float mvp[16]; // model-view-projection matrix float t; glGetFloatv( GL_PROJECTION_MATRIX, p ); glGetFloatv( GL_MODELVIEW_MATRIX, mv ); // // Concatenate the projection matrix and the model-view matrix to produce // a combined model-view-projection matrix. // mvp[ 0] = mv[ 0] * p[ 0] + mv[ 1] * p[ 4] + mv[ 2] * p[ 8] + mv[ 3] * p[12]; mvp[ 1] = mv[ 0] * p[ 1] + mv[ 1] * p[ 5] + mv[ 2] * p[ 9] + mv[ 3] * p[13]; mvp[ 2] = mv[ 0] * p[ 2] + mv[ 1] * p[ 6] + mv[ 2] * p[10] + mv[ 3] * p[14]; mvp[ 3] = mv[ 0] * p[ 3] + mv[ 1] * p[ 7] + mv[ 2] * p[11] + mv[ 3] * p[15]; mvp[ 4] = mv[ 4] * p[ 0] + mv[ 5] * p[ 4] + mv[ 6] * p[ 8] + mv[ 7] * p[12]; mvp[ 5] = mv[ 4] * p[ 1] + mv[ 5] * p[ 5] + mv[ 6] * p[ 9] + mv[ 7] * p[13]; mvp[ 6] = mv[ 4] * p[ 2] + mv[ 5] * p[ 6] + mv[ 6] * p[10] + mv[ 7] * p[14]; mvp[ 7] = mv[ 4] * p[ 3] + mv[ 5] * p[ 7] + mv[ 6] * p[11] + mv[ 7] * p[15]; mvp[ 8] = mv[ 8] * p[ 0] + mv[ 9] * p[ 4] + mv[10] * p[ 8] + mv[11] * p[12]; mvp[ 9] = mv[ 8] * p[ 1] + mv[ 9] * p[ 5] + mv[10] * p[ 9] + mv[11] * p[13]; mvp[10] = mv[ 8] * p[ 2] + mv[ 9] * p[ 6] + mv[10] * p[10] + mv[11] * p[14]; mvp[11] = mv[ 8] * p[ 3] + mv[ 9] * p[ 7] + mv[10] * p[11] + mv[11] * p[15]; mvp[12] = mv[12] * p[ 0] + mv[13] * p[ 4] + mv[14] * p[ 8] + mv[15] * p[12]; mvp[13] = mv[12] * p[ 1] + mv[13] * p[ 5] + mv[14] * p[ 9] + mv[15] * p[13]; mvp[14] = mv[12] * p[ 2] + mv[13] * p[ 6] + mv[14] * p[10] + mv[15] * p[14]; mvp[15] = mv[12] * p[ 3] + mv[13] * p[ 7] + mv[14] * p[11] + mv[15] * p[15]; // // Extract the frustum's right clipping plane and normalize it. // g_frustumPlanes[0][0] = mvp[ 3] - mvp[ 0]; g_frustumPlanes[0][1] = mvp[ 7] - mvp[ 4]; g_frustumPlanes[0][2] = mvp[11] - mvp[ 8]; g_frustumPlanes[0][3] = mvp[15] - mvp[12]; t = (float) sqrt( g_frustumPlanes[0][0] * g_frustumPlanes[0][0] + g_frustumPlanes[0][1] * g_frustumPlanes[0][1] + g_frustumPlanes[0][2] * g_frustumPlanes[0][2] ); g_frustumPlanes[0][0] /= t; g_frustumPlanes[0][1] /= t; g_frustumPlanes[0][2] /= t; g_frustumPlanes[0][3] /= t; // // Extract the frustum's left clipping plane and normalize it. // g_frustumPlanes[1][0] = mvp[ 3] + mvp[ 0]; g_frustumPlanes[1][1] = mvp[ 7] + mvp[ 4]; g_frustumPlanes[1][2] = mvp[11] + mvp[ 8]; g_frustumPlanes[1][3] = mvp[15] + mvp[12]; t = (float) sqrt( g_frustumPlanes[1][0] * g_frustumPlanes[1][0] + g_frustumPlanes[1][1] * g_frustumPlanes[1][1] + g_frustumPlanes[1][2] * g_frustumPlanes[1][2] ); g_frustumPlanes[1][0] /= t; g_frustumPlanes[1][1] /= t; g_frustumPlanes[1][2] /= t; g_frustumPlanes[1][3] /= t; // // Extract the frustum's bottom clipping plane and normalize it. // g_frustumPlanes[2][0] = mvp[ 3] + mvp[ 1]; g_frustumPlanes[2][1] = mvp[ 7] + mvp[ 5]; g_frustumPlanes[2][2] = mvp[11] + mvp[ 9]; g_frustumPlanes[2][3] = mvp[15] + mvp[13]; t = (float) sqrt( g_frustumPlanes[2][0] * g_frustumPlanes[2][0] + g_frustumPlanes[2][1] * g_frustumPlanes[2][1] + g_frustumPlanes[2][2] * g_frustumPlanes[2][2] ); g_frustumPlanes[2][0] /= t; g_frustumPlanes[2][1] /= t; g_frustumPlanes[2][2] /= t; g_frustumPlanes[2][3] /= t; // // Extract the frustum's top clipping plane and normalize it. // g_frustumPlanes[3][0] = mvp[ 3] - mvp[ 1]; g_frustumPlanes[3][1] = mvp[ 7] - mvp[ 5]; g_frustumPlanes[3][2] = mvp[11] - mvp[ 9]; g_frustumPlanes[3][3] = mvp[15] - mvp[13]; t = (float) sqrt( g_frustumPlanes[3][0] * g_frustumPlanes[3][0] + g_frustumPlanes[3][1] * g_frustumPlanes[3][1] + g_frustumPlanes[3][2] * g_frustumPlanes[3][2] ); g_frustumPlanes[3][0] /= t; g_frustumPlanes[3][1] /= t; g_frustumPlanes[3][2] /= t; g_frustumPlanes[3][3] /= t; // // Extract the frustum's far clipping plane and normalize it. // g_frustumPlanes[4][0] = mvp[ 3] - mvp[ 2]; g_frustumPlanes[4][1] = mvp[ 7] - mvp[ 6]; g_frustumPlanes[4][2] = mvp[11] - mvp[10]; g_frustumPlanes[4][3] = mvp[15] - mvp[14]; t = (float) sqrt( g_frustumPlanes[4][0] * g_frustumPlanes[4][0] + g_frustumPlanes[4][1] * g_frustumPlanes[4][1] + g_frustumPlanes[4][2] * g_frustumPlanes[4][2] ); g_frustumPlanes[4][0] /= t; g_frustumPlanes[4][1] /= t; g_frustumPlanes[4][2] /= t; g_frustumPlanes[4][3] /= t; // // Extract the frustum's near clipping plane and normalize it. // g_frustumPlanes[5][0] = mvp[ 3] + mvp[ 2]; g_frustumPlanes[5][1] = mvp[ 7] + mvp[ 6]; g_frustumPlanes[5][2] = mvp[11] + mvp[10]; g_frustumPlanes[5][3] = mvp[15] + mvp[14]; t = (float) sqrt( g_frustumPlanes[5][0] * g_frustumPlanes[5][0] + g_frustumPlanes[5][1] * g_frustumPlanes[5][1] + g_frustumPlanes[5][2] * g_frustumPlanes[5][2] ); g_frustumPlanes[5][0] /= t; g_frustumPlanes[5][1] /= t; g_frustumPlanes[5][2] /= t; g_frustumPlanes[5][3] /= t; } bool isBoundingSphereInFrustum( float x, float y, float z) { for( int i = 0; i < 6; ++i ) { if( g_frustumPlanes[i][0] * x + g_frustumPlanes[i][1] * y + g_frustumPlanes[i][2] * z + g_frustumPlanes[i][3] <= 0) return false; } return true; }