1 簡介
爲了從不同的角度可視化一個場景(Scene),虛擬相機(virtual camera)就經常派上用場了。虛擬相機的設置(OpenGL中常通過gluPerspective和gluLookAt函數設置)決定了從屏幕中可以看到什麼。
View平截頭體(frustum)包羅了當前屏幕上一切潛在的可見實體(之所以說潛在的可見實體,是因爲有些實體可能被遮擋住了)。它通過相機的設置來定義,當使用透視投影(perspective projection)時,平截頭體就像一個被截掉塔尖的金字塔。
金字塔的頂點就是相機的位置,塔底就是遠平面(far plane)。金字塔在近平面(near plane)位置截斷,因此叫做平截頭體。
平截頭體內部的所有元素都是潛在可見的,至少部分可見。所以沒有必要渲染那些平截頭體外部的元素,因爲它們根本看不到。
上圖中,所有綠色(完全在View的平截頭體內)和黃色(部分可見)的圖形都將被渲染。但是紅色的圖形不會被渲染。需要留意的是綠色的球體不可見,它被黃色橢圓遮擋住了,但它也會被渲染,因爲它在View的平截頭體內。
View平截頭體篩選目的就是要甄別什麼在平截頭體內部,篩選所有不在其內部的。只有其內部的實體被送往圖形硬件,最後請求硬件渲染它們,同時保存那些不可見的節點。由於3D世界中僅有可見節點駐留在顯存內,所以某種程度上提高了應用程序的性能。這更有希望描述整個3D世界。
如果平截頭體內這部分比整個場景小得多,那麼這個試驗就顯得意義非凡。但是小得多的標準是什麼呢?這取決於應用程序。極端的情況是整個場景總是完全可見,這時View平截頭體篩選只是浪費時間,因爲沒什麼可篩選的。幸運的是這種篩選技術非常容易實現,並且相比性能上的顯著提升,這個值得一試。
http://www.lighthouse3d.com/opengl/viewfrustum/index.php?intro
2 View平截頭體的形狀
在這一節裏,View平截頭體的形狀通過OpenGL程序教學來展開。假設用gluPerspective函數定義了一個透視投影,並且相機位置通過gluLookAt來設置。
首先,讓我們看看這些函數的參數(均爲float型):
l gluPerspective(fov, ratio, nearDist, farDist);
l gluLookAt(px, py, pz, lx, ly, lz, ux, uy, uz);
P(px, py, pz)是相機的位置,View射線通過d=L(lx, ly, lz)-P(px, py, pz)定義。遠近平面都垂直於View射線,它們距離相機位置的距離分別爲nearDist,farDist。其矩形邊界大小事距離和fov,ratio(ratio between the horizontal and vertical fields of view)的函數。
Hnear = 2 * tan(fov/2) * nearDist
Wnear = Hear * ratio
Hfar = 2 * tan(fov/2) * farDist;
Wfar = Hfar * ratio
爲了執行View平截頭體篩選,需要按下面兩步操作:
l 提取平截頭體的數據 – 每次平截頭體改變後都需要做,i.e.,相機或透視模式改變時。
l 根據平截頭體測試對象是否被篩選 – 這一步需要在每一幀執行。如果每個對象在每一幀之間保持篩選狀態不變,那麼僅僅需要在相機移動(比如平截頭體更新或透視模式改變)時做測試。
http://www.lighthouse3d.com/opengl/viewfrustum/index.php?defvf
3 幾何方法 – 提取平面
在自然空間中運用幾何方法,可以通過View平截頭體的數據計算定義frustum邊界的六個平面:near, far, top, bottom, left & right。
這些平面的法向量(normal)指向View平截頭體內部。通過檢測對象在平面的哪一側,就可以確定一個對象是否在平截頭體內。具體可以計算點到平面的有符號距離(signed distance)來實現,如果它在法向量所指的一側,即距離爲正值,該對象在平面右側。如果一個對象位於六個平面的右側,則它在平截頭體內。
本節論述瞭如何計算定義平截頭體的六個平面的方法。Testing將在後面的詳述。
一種方法是,首先確定平截頭體的八個頂點,然後用這些頂點來定義六個平面。
下圖顯示了上面提及的頂點,它們可以用來計算這六個平面。
定義點的標記法是這樣的:第一個字母表示在near plane(n)還是far plane(f)上;第二個字母表示改點的top(t)或bottom(b)位置;第三個字母表示left(l)或right(r)位置。
回顧上一節提到的一些知識:
l P - 相機位置
l D - 相機View射線的矢量方向。這裏假設該向量已標準化。
l nearDist - 相機到near plane的距離
l Hnear - near plane的高
l Wnear - near plane的寬
l farDist - 相機到far plane的距離
l Hfar - far plane的高
l Wfar - far plane的寬
另外還需要一些單位向量,也就是up vector和right vector。前者通過單位化向量(ux, uy, uz)得到(即gluLookAt函數的後三個參數);後者通過計算up vector和d vector的叉積得到。如下圖,顯示瞭如何得到far plane的左上角點ftl。
具體可以通過下面的式子計算ftl點,
fc = p + d * farDist
ftl = fc + (up * Hfar/2) - (right * Wfar/2)
其他點這樣計算,
ftr = fc + (up * Hfar/2) + (right * Wfar/2)
fbl = fc - (up * Hfar/2) - (right * Wfar/2)
fbr = fc - (up * Hfar/2) + (right * Wfar/2)
nc = p + d * nearDist;
ntl = nc + (up * Hnear/2) - (right * Wnear/2)
ntr = nc + (up * Hnear/2) + (right * Wnear/2)
nbl = nc - (up * Hnear/2) - (right * Wnear/2)
nbr = nc - (up * Hnear/2) + (right * Wnear/2)
三個點可以定義一個平面。例如,可以用ftl, ftr, fbr定義far plane,但應該保持法向方向的一致性,所有法向都指向view frustum內部。
這種方法計算near/far planes可以做一些優化。一個平面可由一個法向和一個點定義,這些平面都可以基於相機的定義計算出來。near plane可由平面上的點nc和法向d定義,far plane可由-d和點fc定義。
其他平面也可以用一種更高效的方式計算出來,即利用一個法向量和一個點來定義平面。下面的代碼給出了right plane的法向量。
nc = p + d * nearDist
fc = p + d * farDist
a = (nc + right * Wnear/2) - p
a.normalize();
normalRight = up x a;
http://www.lighthouse3d.com/opengl/viewfrustum/index.php?gaplanes