按视锥体裁剪你的场景(译)
原文地址:http://www.sjbaker.org/steve/omniv/frustcull.html
Culling your scene to the View Frustum.
按视锥体裁剪你的场景
OpenGL can correctly reject polygons that lie 'off the edge' of the screen - and clip those that straddle the edge of the screen, but the cost of transmitting all those vertices to OpenGL only to have them thrown away is pretty high.
OpenGL可以正确的拒绝和剪切在屏幕边缘以外,和横跨屏幕边缘的的多边形,这些多边形的顶点传给OpenGL后仅仅是将他们扔掉,但是传输的代价是巨大的。
You could write code to reject those polygons yourself - but that would be pretty pointless because it would probably take as long - if not longer - than OpenGL takes.
你可以自己编码拒绝那些多边形,但是那将会没什么意义,因为自己编码,可能耗费至少和OpenGL相等的时间,假设这个时间不比OpenGL所耗费的多。
The optimal solution is to do a coarse level cull in your application - rejecting entire objects that lie completely off the screen - and let OpenGL do the fine-grained cull at the level of individual polygons. This approach will greatly speed up most OpenGL applications.
最好的办法就是在你的应用程序里做一个粗略的处理,就是拒绝那些整个对象完全处于屏幕外面的,然后让OpenGL精细的处理剔除单个多边形。这种方法将会大大加快大部分OpenGL程序。
This paper describes one efficient method for attacking this problem.
本文针对这个问题提出了一个有效的办法。
What is a "View Volume" ?
什么是“视觉体”
The first thing to observe is that the screen is a 2D thing - but the objects we are culling are 3D. The way to resolve that is to imagine you are culling to a pyramid-shaped 3D volume with the apex of the pyramid at your eye - and with the four triangular faces of the pyramid lined up with the edges of the screen. The far end of the pyramid is cut off at the "far clip plane".
首先需要注意到的是屏幕是一个2D的东西,但是我们剔除的对象是3D的。解决的办法就是想象你正在剔除一个金字塔形的3D体,金字塔的顶点对准你的眼睛,金字塔的4个三角面,在屏幕的边缘依次排开。金字塔的底部被“远裁剪平面”切断。
There is also a need to cull objects that are closer than the plane of the screen (including objects that are behind the user's head). Hence, the cull operation actually works in a truncated pyramid. The correct mathematical term for such a solid is a 'Frustum' (frequently mis-spelled Frustrum or Fustrum for some reason).
那些在屏幕平面前面的对象也会被剔除(包括那些在用户头部后面的对象)。因此,剔除操作其实是在截断的金字塔中进行的。正确的数学术语叫做“视锥体”(有些原因导致经常拼写成Frustrum或者Fustrum)。
Everything that's completely inside the 'view frustum' needs to be drawn - everything that's completely outside can certainly be thrown away - anything that crosses the frustum is...erm... something we'll let OpenGL worry about at the polygon level - so we'll need to draw those objects also.
那些完全处于“视锥体”之内的事物都需要绘制,那些完全处于“视锥体”之外的事物可以非常确定需要被丢弃,任何横跨视锥体的事物。。。还是让OpenGL在多边形的级别去关心吧,所以我们也需要绘制这些对象。
How do you Cull to a View Frustum?
如何按照视锥体进行剔除?
Well, think of the frustum as four intersecting infinite planes (actually, it's six because near and far are also clipping planes - if you are using the custom clip planes that OpenGL supports, you might want to toss those in too).
把视锥体想象成4个相交的无限平面(事实上,应该有6个面,因为近端和远端还有两个切面,如果你使用OpenGL的自定义切面,你可能会希望将这两个切面也一起考虑)。
Since we don't want to duplicate the work of OpenGL at the vertex level, we'll want to test an entire object in one go - without looking at each vertex in turn. The way to do that is to 'summarise' the extent of the object using either a sphere or cubeoid that just encloses all of the objects' vertices. This is called a 'bounding volume'.
由于我们不希望重复OpenGL在顶点级的工作,我们希望可以一次就测试整个对象,并且不需要依次查看每个顶点。实现的方法就是用一个可以包含该对象所有顶点的外接球体或者正方体,去“归总”对象的范围。这被称为“外接体”。
Personally, I prefer to use a bounding sphere - but opinions vary on this point.
我个人喜欢外接球体,但是这不是唯一的选择。
So, the problem boils down to "How do I tell if an arbitrary sphere is either inside or crossing the six planes of the frustum?" - Fortunately, there is a really easy way to do that.
所以,问题归结到“我怎么知道一个任意的球体,是在视锥体内的六个平面之内,还是横跨了这六个平面?”,幸运的,有一个很简单的办法可以解决。
Sphere/Frustum Intersection Tests
球体/视锥体相交测试
We'll need to figure out the plane equation for each one of those six planes:
我们需要搞清楚那六个平面的方程:
A*x + B*y + C*z + D = 0
...that plane equation calculation can be done one-time whenever the view frustum is set up.
视锥体设置好以后就可以求出平面方程了。
Note that for an arbitrary point (px,py,pz), the distance to the plane is:
注意,对于任意一个点(px, py, pz),它到平面的距离公式:
d = A*px + B*py + C*pz + D
...so for each vertex of your bounding volume, you can measure the distance to each of those planes.
所以对于外接体的每个顶点,你可以计算顶点到每个平面的距离。
For a sphere: Just toss the center point of the sphere into the equation above - and if the (signed) distance places the center point more than 'radius' units outside any of the planes then you can cull the object.
对于球体:只需把球体的球心点,代入上面的等式,得到(有符号的)距离,如果这个距离大于球体的“半径”单位,在所有的六个平面之外,那就可以把这个对象剔除了。
That was easy!
这非常简单!
Efficiency Improvements
性能优化
But what happens if you have a LOT of objects? One solution is to arrange to group several objects together - compute a bounding sphere that contains ALL of them and test that first. If this 'super-sphere' is entirely outside the view frustum then there is no need to test the objects inside that super-sphere - they can all be culled instantly. Similarly, if the super-sphere lies entirely INSIDE the view frustum, you can go on and draw all those lesser objects without testing them further.
但是如果有很多对象呢?一个办法是将对象按组分类,计算包含所有对象的外接球体,并先测试外接球体。如果这个“超级球体”整个在视锥体的外部,那就不要去测试超级球体内部的对象了,它们可以马上被剔除掉。同样的,如果这个超级球体整个在视锥体的内部,就可以继续绘制球体的小对象了,而不需要进一步的做测试。
Only if the super-sphere straddles the frustum do you need to go and test all of those smaller objects.
除非这个超级球体横跨视锥体,你才需要去测试球体内较小的对象。
To generalize this, you might want a hierarchy of spheres- containing-spheres - how deep and how 'wide' that tree needs to be depends on your application.
概括来说,你可能需要一个有层次的球体,球体的深度及“宽度”取决于你的应用程序。
If do you use a hierarchical bounding volume scheme - then you can do more: You can (for example) flag *which* planes you were entirely 'inside' of and not re-test them for lesser objects.
如果你使用一个分层的外接球体方案,那你可以处理的更多:你可以(例如)标志球体完全在哪些平面“之内”,并且不用重新测试小对象。
Still, for each sphere, you may need to do as many as six sphere-center-to-plane distance calculations.
不过,对于每个球体,你可能需要计算六次球心到平面的距离。
...Or do you?
或者,你可以?
If you have a relatively 'normal' symmetrical view frustum, there are some simplifications that save a lot of CPU time in calculating those six distances. Notice that:
如果你的视锥体是一个相对“正常”的对称体,有一些简化的方法,可以节约计算六个距离的CPU时间。注意这些:
- In the plane equation mentioned above, (A,B,C) is the surface normal of the plane (I make them all face inwards) and D is the distance from the origin to the plane - measured such that D is positive for planes that face the origin and negative from those that don't. 在上面提到的平面方程中,(A, B, C)是平面的法线(假设这些法线都是朝里的),D就是从原点到平面的距离,当法线朝向原点时,D为正,否则为负。
- For near and far planes, you know that A and B are zero, C is either +1 or -1 and D is plus or minus the distance to the plane. 对于近截面和远截面,我们可以知道A和B都是0,C是+1或者-1,D是正的或负的平面的距离。
- For the left and right plane B is zero and so is D. 对于左截面和右截面,B和D都是0。
- For the top and bottom planes A is zero and so is D. 对于上截面和下截面,A和D都是0。
- C is the same for the left and right planes and also the same for top and bottom planes. 左截面、右截面、上截面、下截面的C是相同的。
- A is positive for the left plane and negative for the right, but the magnitudes are the same. 左截面的A是正的,右截面的A是负的,但是大小是一样的。
- B is positive for the top plane and negative for the bottom, but the magnitudes are the same. 上截面的B是正的,下截面的B是负的,但是大小是一样的。
Knowing this can make testing much cheaper. Just a handful of multiplies per sphere in general.
知道了这些测试可以更简单。对于每个球体,一般只有极少数的乘法。
翻译水平有限,欢迎指正。

浙公网安备 33010602011771号