View Frustum Culling Tutorial

来源:互联网 发布:排列组合公式算法c50 编辑:程序博客网 时间:2024/05/01 00:42

1 简介

为了从不同的角度可视化一个场景(Scene),虚拟相机(virtual camera)就经常派上用场了。虚拟相机的设置(OpenGL中常通过gluPerspectivegluLookAt函数设置)决定了从屏幕中可以看到什么。

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射线,它们距离相机位置的距离分别为nearDistfarDist。其矩形边界大小事距离和fovratioratio 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 vectorright vector。前者通过单位化向量(ux, uy, uz)得到(即gluLookAt函数的后三个参数);后者通过计算up vectord 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