1
0
Fork 0
flightgear/Hints/view-frustum-culling

987 lines
38 KiB
Text
Raw Normal View History

1999-04-05 21:32:32 +00:00
From sbaker@link.com Thu Jan 29 23:16:07 1998
X-VM-v5-Data: ([nil nil nil nil t nil nil nil nil]
["5377" "Thu" "29" "January" "1998" "23:16:36" "-0600" "Steve Baker" "sbaker@link.com" "<Pine.SGI.3.96.980129224404.29062A-100000@lechter.bgm.link.com>" "139" "Re: View frustum culling" "^From:" nil nil "1" nil nil nil nil nil]
nil)
Received: from lfkw10.bgm.link.com (bgm.link.com [130.210.2.10])
by meserv.me.umn.edu (8.8.8/8.8.6) with ESMTP id XAA03024
for <curt@me.umn.edu>; Thu, 29 Jan 1998 23:16:06 -0600 (CST)
Received: from lechter.bgm.link.com (lechter.bgm.link.com [130.210.239.45])
by lfkw10.bgm.link.com (8.8.6/HTI-Hack-8.8.4) with SMTP
id XAA18220 for <curt@me.umn.edu>; Thu, 29 Jan 1998 23:15:35 -0600 (CST)
X-Sender: steve@lechter.bgm.link.com
Reply-To: Steve Baker <sbaker@link.com>
In-Reply-To: <199801292145.PAA04212@kenai.me.umn.edu>
Message-ID: <Pine.SGI.3.96.980129224404.29062A-100000@lechter.bgm.link.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII
From: Steve Baker <sbaker@link.com>
To: "Curtis L. Olson" <curt@me.umn.edu>
Subject: Re: View frustum culling
Date: Thu, 29 Jan 1998 23:16:36 -0600 (CST)
On Thu, 29 Jan 1998, Curtis L. Olson wrote:
> Questions ...
>
> Let's say I know the following:
>
> Eye pt. = (0, 0, 0)
> "look at" vector
> "view up" vector
> field of view angle
> Assume a "square" view volume cross section (i.e. the far clip plane
> would be a square)
(That is almost never the case since you want the image to fit the
screen shape - but OK - for argument's sake, same HFOV as VFOV).
> I can calcuation the near and far clip planes pretty easily with this
> info.
Assuming you know the near and far clip ranges - yes.
> Now, to calculate the left and right planes I could rotate the look at
> vector about the view up vector by -1/2 * fov and +1/2 * fov
> respectively. The view up vector already lies in both these planes,
> so then I'd have two vectors in each plane and then I could calculate
> the normals to get the equations of these planes
Yes, that would work.
> Then to calculate the top and bottom planes I could rotate the view up
> vector by 90 degrees about the look at vector to get a vector in both
> these planes, then I could rotate the look at vector about this new
> vector by -1/2 * fov and +1/2 * fov to get the second vector in each
> of these planes, and crunch the math just like I did for the left and
> right planes.
...or you could just rotate the left or right plane normal by 90 degrees
about the lookat vector (but only for your hypothetical square FOV).
> Does this sound reasonable, or am I missing some obvious tricks?
It's *reasonable* if you want the view planes in the coordinate system
of the world. However, I'd argue about that premise.
I did the opposite - I keep the view frustum in the coordinate system
of the eye and rotate the world into that coordinate system. This
costs at most one extra transform (by the inverse of the eye-rel-world
matrix) per frame (at the root of the database tree).
The advantages are *huge* in the clipping code (which is where I
assume you are going with this question) since the plane equations
for the near and far planes (in eye coordinates) are trivial:
General Equation of a Plane:
Ax + By + Cz + D == 0
Far clip plane:
A == 0 ;
B == 0 ;
C == -1 ;
D == far_clip_range ;
Near clip plane:
A == 0 ;
B == 0 ;
C == 1 ;
D == -near_clip_range ;
Also, since the left and right clip planes are now vertical, we
know that B==0 and for the top and bottom planes, A==0.
In addition, because of symmetry about the Z axis, the A of
the left plane is equal to -A for the right, and the B of
the top plane is equal to -B of the bottom.
Furthermore, since D is just the distance of the closest
point of the plane to the origin - and all four planes
go through the eyepoint - and that *is* the origin - that
means that the 'D' component of all four edge equations is
always zero.
Notice that since the frustum is in eye coordinates,
there is no need to worry about lookat or viewup vectors
since these are 0,0,1 and 0,1,0 respectively - and the
eye point is always at 0,0,0 by definition. This also
means that you only need to calculate those plane equations
once - rather than once per frame as with your scheme.
When you come to ask the question: "Is this bounding sphere
cleanly inside the frustum, cleanly outside the frustum or
(annoyingly) straddling it?", you will want to insert the
x,y,z of the center of the sphere (relative to the eye
in the 'eye' coordinate system) into each of the
six plane equations in turn to compute the distance from the
sphere center to the plane - and compare that to the radius
of the sphere. When you write out the math for this, using
the full plane equations (as you would have to do with your
scheme), you find that all those zeros and symmetry effects
result in some pretty amazing optimisations.
Additionally, this is the OpenGL way. You stuff your frustum
matrix into the 'PROJECTION_MATRIX', put the inverse of
your eyepoint transform into the MODELVIEW_MATRIX and proceed
to push/multiply/pop the transforms of each of the models onto
that same matrix. Notice that the frustum is never transformed
into world space.
I suggest you go back and re-read the last L-O-N-G email I
sent about clipping, I explain my suggested method there
in more detail.
Note that I'm not saying that what you propose is wrong -
just that IMHO (and *only* IMHO), my way is going to be
a lot simpler and cheaper to implement. However, there may
be other reasons to want to do it your way.
The complexity of MANY 3D graphics problems often depends
critically on your choice of coordinate system. For
clipping, doing the math in the eye coordinate system
results in a *tiny* amount of math per sphere tested.
If you ever need to do lighting calculations, then
carrying them out in a coordinate system with the
light source at the origin makes life a gazillion
times easier too. The same kinds of things will also
crop up in collision detection as well.
Steve Baker 817-619-8776 (Vox/Vox-Mail)
Raytheon Systems Inc. 817-619-4028 (Fax)
2200 Arlington Downs Road SBaker@link.com (eMail)
Arlington, Texas. TX 76005-6171 SJBaker1@airmail.net (Personal eMail)
http://www.hti.com http://web2.airmail.net/sjbaker1 (personal)
** Beware of Geeks bearing GIF's. **
From sbaker@link.com Fri Jan 30 14:08:05 1998
X-VM-v5-Data: ([nil nil nil nil t nil nil nil nil]
["4594" "Fri" "30" "January" "1998" "14:08:27" "-0600" "Steve Baker" "sbaker@link.com" "<Pine.SGI.3.96.980130135437.20130B-100000@lechter.bgm.link.com>" "120" "Re: View frustum culling" "^From:" nil nil "1" nil nil nil nil nil]
nil)
Received: from lfkw10.bgm.link.com (bgm.link.com [130.210.2.10])
by meserv.me.umn.edu (8.8.8/8.8.6) with ESMTP id OAA21393
for <curt@me.umn.edu>; Fri, 30 Jan 1998 14:08:03 -0600 (CST)
Received: from lechter.bgm.link.com (lechter.bgm.link.com [130.210.239.45])
by lfkw10.bgm.link.com (8.8.6/HTI-Hack-8.8.4) with SMTP
id OAA10811 for <curt@me.umn.edu>; Fri, 30 Jan 1998 14:07:27 -0600 (CST)
X-Sender: steve@lechter.bgm.link.com
Reply-To: Steve Baker <sbaker@link.com>
In-Reply-To: <199801301948.NAA25718@kenai.me.umn.edu>
Message-ID: <Pine.SGI.3.96.980130135437.20130B-100000@lechter.bgm.link.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII
From: Steve Baker <sbaker@link.com>
To: "Curtis L. Olson" <curt@me.umn.edu>
Subject: Re: View frustum culling
Date: Fri, 30 Jan 1998 14:08:27 -0600 (CST)
On Fri, 30 Jan 1998, Curtis L. Olson wrote:
> Steve Baker writes:
> > I did the opposite - I keep the view frustum in the coordinate
> > system of the eye and rotate the world into that coordinate
> > system. This costs at most one extra transform (by the inverse of
> > the eye-rel-world matrix) per frame (at the root of the database
> > tree).
> >
> > I suggest you go back and re-read the last L-O-N-G email I sent
> > about clipping, I explain my suggested method there in more detail.
>
> Yes I have that in front of me. I guess it wasn't clear to me that
> you were using this greatly simplifed view frustum specially placed in
> eye coordinates.
That's the key to doing this stuff *quickly*.
> > The complexity of MANY 3D graphics problems often depends critically
> > on your choice of coordinate system. For clipping, doing the math in
> > the eye coordinate system results in a *tiny* amount of math per
> > sphere tested.
>
> Yes ... your description all makes sense and looks like it makes view
> frustum culling very simple to implement.
>
> Ok, so now I'm going to try to zero in a bit more on the source of my
> confusion, which involves getting from my world to your eye
> coordinates.
Let's adopt a naming convention for matrices here:
A_rel_B
means the matrix that positions objects in A relative to
the object B.
Some facts:
B_rel_A is just the inverse of A_rel_B.
A_rel_B * B_rel_C == A_rel_C
Hence: (for a flat-earth world - just for the moment)
If the eyepoint is 10km east of the origin (eyepoint.x == 10,000)
then:
eye_rel_world is a matrix that translates by 10000m in the X
direction.
world_rel_eye is the inverse of eye_rel_world - which in this
case is just a matrix that translates by -10000m in X.
Taking the inverse of a general matrix isn't nice - but for simple
rotate/translate matrices it isn't too bad - and you only do it
once per frame anyway.
> Lets say that I have a bunch of scenery tiles that are properly
> oriented, but translated to near (0, 0, 0) to avoid problems with
> "float" precision.
>
> I assume based on your previous message that you define your view
> frustum once at the beginning of your program in eye coordinates to
> simplify all this math.
Yep - you *might* want to allow the user to change it sometimes - but
basically, it's always the same number unless you resize the window,
zoom the image, etc.
> This moves the "hard" part to generating a transformation matrix that
> maps world coordinates into eye coordinates.
But world_rel_eye is just the inverse of eye_rel_world - and that's
just the rotation/translation of the eyepoint relative to some
arbitary point.
> You say ...
>
> > Additionally, this is the OpenGL way. You stuff your frustum matrix
> > into the 'PROJECTION_MATRIX', put the inverse of your eyepoint
> > transform into the MODELVIEW_MATRIX and proceed to push/multiply/pop
> > the transforms of each of the models onto that same matrix. Notice
> > that the frustum is never transformed into world space.
>
> Ok, so does this mean that every iteration I have to generate the
> world -> eye transformation matrix by hand ... i.e. calculate the
> first translation matrix, calculate the rotation matrix, calculate the
> second translation matrix, calculate the shear matrix, calculate the
> scaling matrix, and then combine all these together, then invert it
> and stuff it on the MODELVIEW stack?
Yes - except that the eye_rel_world isn't usually scaled or sheared or
anything. Just heading/pitch/roll/x/y/z.
> Or is there a way to get OpenGL to do this work for me?
No - not really - there is no glInvertMatrix (AFAIK) - and in any
case, on machines with geometry accelleration doing a glGetMatrix
is *death* to performance.
> If not, can I at least use OpenGL's rotates,
> and transformations to avoid spending two weeks debugging picky math
> routines?
No - don't debug new math routines either - let me find some out on the
web for you. I'm sure I know of a good set. I'll email you from home
tonight - I'm a bit busy right now.
Flight Gear will definitely need a good, robust set of math routines -
better to have a solid library than to try to kludge something in OpenGL.
Steve Baker 817-619-8776 (Vox/Vox-Mail)
Raytheon Systems Inc. 817-619-4028 (Fax)
2200 Arlington Downs Road SBaker@link.com (eMail)
Arlington, Texas. TX 76005-6171 SJBaker1@airmail.net (Personal eMail)
http://www.hti.com http://web2.airmail.net/sjbaker1 (personal)
** Beware of Geeks bearing GIF's. **
From sbaker@link.com Fri Jan 30 22:51:59 1998
X-VM-v5-Data: ([nil nil nil nil nil nil nil nil nil]
["4301" "Fri" "30" "January" "1998" "22:52:31" "-0600" "Steve Baker" "sbaker@link.com" nil "103" "Re: View frustum culling" "^From:" nil nil "1" nil nil nil nil nil]
nil)
Received: from lfkw10.bgm.link.com (bgm.link.com [130.210.2.10])
by meserv.me.umn.edu (8.8.8/8.8.6) with ESMTP id WAA03743
for <curt@me.umn.edu>; Fri, 30 Jan 1998 22:51:58 -0600 (CST)
Received: from lechter.bgm.link.com (lechter.bgm.link.com [130.210.239.45])
by lfkw10.bgm.link.com (8.8.6/HTI-Hack-8.8.4) with SMTP
id WAA08997 for <curt@me.umn.edu>; Fri, 30 Jan 1998 22:51:26 -0600 (CST)
X-Sender: steve@lechter.bgm.link.com
Reply-To: Steve Baker <sbaker@link.com>
In-Reply-To: <199801302035.OAA26234@kenai.me.umn.edu>
Message-ID: <Pine.SGI.3.96.980130223546.27307A-100000@lechter.bgm.link.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII
From: Steve Baker <sbaker@link.com>
To: "Curtis L. Olson" <curt@me.umn.edu>
Subject: Re: View frustum culling
Date: Fri, 30 Jan 1998 22:52:31 -0600 (CST)
On Fri, 30 Jan 1998, Curtis L. Olson wrote:
> Steve Baker writes:
> > But world_rel_eye is just the inverse of eye_rel_world - and that's
> > just the rotation/translation of the eyepoint relative to some
> > arbitary point.
>
> Ok, from what you are saying, if I think about it, I've probably
> already got a suitable matrix or something close to it laying around
> someplace. I have a matrix that does the proper rotations to align
> world coordinates with the local "up" for the aircraft. I'd probably
> just need to pre-multiply the proper translation matrix with that
> rotate matrix, then invert it, and stuff it onto the MODELVIEW stack.
You *must* be doing the right thing at the start of the frame - or
how else would the graphics be coming out right? (Oh - unless you
are using that weird OpenGL 'lookat' function or something).
> > No - don't debug new math routines either - let me find some out on
> > the web for you. I'm sure I know of a good set. I'll email you from
> > home tonight - I'm a bit busy right now.
> >
> > Flight Gear will definitely need a good, robust set of math routines
> > - better to have a solid library than to try to kludge something in
> > OpenGL.
>
> You may scream when you hear this, or not, I'm not sure ... but I've
> been using the matrix and vector routines from SRGP and SPHIGS.
I don't know these - but it's hard to imagine how anyone could screw
up the implementation of a matrix math library - so they are probably OK.
I have accumulated by own matrix/quaternion library that I've used
for the last 10 years - every new project starts with it - and adds to
it, so now I don't even have to think about how to drive it. That's
a very liberating thing.
Another good source for a matrix lib is inside the Mesa sources. These
are particularly interesting because the latest Beta release has
super-optimised machine code for Intel CPU's under Linux as a conditional
compiled option. You'd need to copy and change the names of the routines
though to avoid the names clashing with the real Mesa routines.
One *IMPORTANT* thing is that when you invert the eye_rel_world matrix
to get world_rel_eye, don't use a general purpose matrix invert routine
since those are *REALLY* inefficient bits of code for matrices that
are guaranteed to be pure rotate/translate. Instead, just do this:
/*
This definition of a matrix is
easier to deal with than a float[16] -
but you can safely pass this kind
of matrix directly to OpenGL routines
like glMultMatrix and glLoadMatrix.
*/
typedef float fgMat [ 4 ][ 4 ] ;
/*
Transpose/Negate is a poor man's invert.
It can *only* be used when matrix is a
simple rotate-translate - but it's a
gazillion times faster than a full-blown
invert.
*/
void fgTransposeNegateMat( fgMat dst, fgMat src )
{
/* Transpose the 3x3 rotation sub-matrix */
dst[0][0] = src[0][0] ; dst[0][1] = src[1][0] ; dst[0][2] = src[2][0] ;
dst[1][0] = src[0][1] ; dst[1][1] = src[1][1] ; dst[1][2] = src[2][1] ;
dst[2][0] = src[0][2] ; dst[2][1] = src[1][2] ; dst[2][2] = src[2][2] ;
/* Negate the translate part */
dst[3][0] = -src[3][0] ; dst[3][1] = -src[3][1] ; dst[3][2] = -src[3][2] ;
/* Populate the rest */
dst[0][3] = dst[1][3] = dst[2][3] = 0.0f ; dst[3][3] = 1.0f ;
}
> Debugging the low level routines, though, is sometimes the easy part,
> debugging the usage of them is where it can often get hairy ... :-)
I agree - matrices are truly horrible to get your head around. Have
you looked into Quaternions yet? They are v.interesting for the
flight dynamics people because they don't suffer from 'gymbal lock'
like H,P,R angles and are easier to renormalize than matrices.
You don't want to use Quaternions for the main graphics code - but
it's easy to form a Matrix from a Quaternion - and Quaternions are
a much better way to represent rotation than the usual three angles.
Steve Baker 817-619-8776 (Vox/Vox-Mail)
Raytheon Systems Inc. 817-619-4028 (Fax)
2200 Arlington Downs Road SBaker@link.com (eMail)
Arlington, Texas. TX 76005-6171 SJBaker1@airmail.net (Personal eMail)
http://www.hti.com http://web2.airmail.net/sjbaker1 (personal)
** Beware of Geeks bearing GIF's. **
From owner-flight-gear@me.umn.edu Tue May 19 07:45:59 1998
X-VM-v5-Data: ([nil nil nil nil nil nil nil nil nil]
["4413" "Tue" "19" "May" "1998" "07:44:47" "-0500" "Steve Baker" "sbaker@link.com" nil "116" "Re: [FGFS] View Frustum Culling" "^From:" nil nil "5" nil nil nil nil nil]
nil)
Received: (from majordom@localhost)
by meserv.me.umn.edu (8.8.8/8.8.8) id HAA04944
for flight-gear-outgoing; Tue, 19 May 1998 07:45:59 -0500 (CDT)
X-Authentication-Warning: meserv.me.umn.edu: majordom set sender to owner-flight-gear@me.umn.edu using -f
Received: from lfkw10.bgm.link.com (bgm.link.com [130.210.2.10])
by meserv.me.umn.edu (8.8.8/8.8.8) with ESMTP id HAA04940
for <flight-gear@me.umn.edu>; Tue, 19 May 1998 07:45:55 -0500 (CDT)
Received: from borgus.bgm.link.com (borgus.bgm.link.com [130.210.236.13])
by lfkw10.bgm.link.com (8.8.6/HTI-Hack-8.8.4) with SMTP
id HAA29586 for <flight-gear@me.umn.edu>; Tue, 19 May 1998 07:45:23 -0500 (CDT)
X-Sender: steve@borgus.bgm.link.com
In-Reply-To: <199805182119.QAA04388@kenai.me.umn.edu>
Message-ID: <Pine.SGI.3.96.980519070916.5720B-100000@borgus.bgm.link.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII
Precedence: bulk
Reply-To: flight-gear@me.umn.edu
From: Steve Baker <sbaker@link.com>
Sender: owner-flight-gear@me.umn.edu
To: flight-gear@me.umn.edu
Subject: Re: [FGFS] View Frustum Culling
Date: Tue, 19 May 1998 07:44:47 -0500 (CDT)
On Mon, 18 May 1998, Curtis L. Olson wrote:
> Steve Baker writes:
> > Are you only planning to cull at the tile level? You probably ought
> > to cull at the triangle strip level (at least on machines without
> > geometry hardware).
>
> You are always trying to make things more complicated ... :-)
It's my experience that things are perfectly capable of getting
more complicated without any help from me :-)
> > You can check a bounding sphere against the view frustum in about 4
> > multiplies and four additions - and you only need to do that for
> > spheres inside tiles that straddle the frustum. Each test that
> > excludes a tstrip will save a bunch of vertex transforms - so you
> > should get another big win in doing that.
>
> Ok, a couple of questions. Do you recommend pre-calculating and
> storing the bounding sphere info for each tri-strip in the data file,
> or should I calculate it at load time?
Well, since you only have one CPU to use for both rendering and
realtime database paging, you'll want to minimise the amount of
calculations when loading. The bounding sphere calculation
that most people do is very simple - you probably would want to do it
offline eventually - but you can probably do it on loading the database
for now just so you can get the culling stuff finished. So long as you
don't do it every frame you'll be OK in the short term.
The basic bounding sphere algorithm that most people (including me)
use is v.simple. Just find the maximum and minimum x, y and z values
in the data set, position the center of the sphere halfway between
the minimum and maximum in each axis - then go through the points
to find the one thats furthest from that center point - that distance
is the radius. (You can compare the square of the ranges to get the
longest range - so you only need to do one sqrt per sphere when you
need to compute the actual radius).
There has been some discussion of 'better' algorithms that (presumable)
produce tighter spheres than the simple method described above. On
my 'to do' list here at work is to evaluate these various algorithms to
see which actually produces the tightest spheres.
tighter spheres == better culling == lower polygon counts.
I'm pretty sceptical about these algorithms being *significantly*
better than the simple one - but even a few percent improvement
is worth having if I can do it offline and steal the code from
someone who can do 'math'.
This one looks interesting:
http://vision.ucsd.edu/~dwhite/ball.html
...although being iterative, I wouldn't want to do it in my
database loader code.
Same applies to this one:
http://cm.bell-labs.com/who/clarkson/center.html
Both have source code - which is just as well since I can't understand
the math behind either of them!
If your culling math starts to take too much time then you'll want to
do a hierarchical cull. If I were you though I'd probably just
do this:
for each terrain tile
{
if ( outside the frustum )
continue ;
if ( inside the frustum )
draw all the tristrips
else /* straddling the frustum */
{
for each tristrip
if ( inside or straddling the frustum )
draw the tristrip
}
}
(Of course 'draw the tristrip' might actually mean 'add the tristrip to
the appropriate bucket so we can draw it later')
> To impliment this sort of
> scheme I suppose I would need to keep each tri-strip in it's own
> display list.
Yep - but you need to do that so that you can....
> ...sort the objects into buckets by material properties.
Definitely. Switching material properties (especially texture map)
is very costly on most hardware OpenGL's. Sorting is an absolute
must once you start you use more than one kind of texture on the
terrain skin.
> I suppose this would entail defining material properties in the data
> file and otherwise sprucing up the data file format (and internal data
> structures) a bit.
Eventually.
Steve Baker (817)619-8776 (Vox/Vox-Mail)
Raytheon Systems Inc. (817)619-4028 (Fax)
Work: SBaker@link.com http://www.hti.com
Home: SJBaker1@airmail.net http://web2.airmail.net/sjbaker1
-------------------------------------
Please visit the FGFS web page: http://www.menet.umn.edu/~curt/fgfs/
For help on using this list (especially unsubscribing), send a message to
"flight-gear-request@me.umn.edu" with a single line of text: "help".
From sbaker@link.com Mon May 18 07:39:58 1998
X-VM-v5-Data: ([nil nil nil nil nil nil nil nil nil]
["10518" "Mon" "18" "May" "1998" "07:39:06" "-0500" "Steve Baker" "sbaker@link.com" nil "295" "Re: view frustum culling" "^From:" nil nil "5" nil nil nil nil nil]
nil)
Received: from lfkw10.bgm.link.com (bgm.link.com [130.210.2.10])
by meserv.me.umn.edu (8.8.8/8.8.8) with ESMTP id HAA01318
for <curt@me.umn.edu>; Mon, 18 May 1998 07:39:57 -0500 (CDT)
Received: from sutcliffe.bgm.link.com (sutcliffe.bgm.link.com [130.210.236.18])
by lfkw10.bgm.link.com (8.8.6/HTI-Hack-8.8.4) with SMTP
id HAA03232 for <curt@me.umn.edu>; Mon, 18 May 1998 07:39:26 -0500 (CDT)
X-Sender: steve@sutcliffe.bgm.link.com
Reply-To: Steve Baker <sbaker@link.com>
In-Reply-To: <199805152113.QAA12303@kenai.me.umn.edu>
Message-ID: <Pine.SGI.3.96.980518071249.21179A-100000@sutcliffe.bgm.link.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII
From: Steve Baker <sbaker@link.com>
To: "Curtis L. Olson" <curt@me.umn.edu>
Subject: Re: view frustum culling
Date: Mon, 18 May 1998 07:39:06 -0500 (CDT)
On Fri, 15 May 1998, Curtis L. Olson wrote:
> I was just planning to work out the math myself, but it's not coming
> out nearly as simple as what you had. The near/far clipping plane is
> trivial, but I was working through the sides and started to see a few
> sqrt()'s and such start to creep in, and I don't remember seeing this
> with your pseudo-code.
Certainly shouldn't need sqrt's.
How about this:
You said:
> Anyone know anything about view frustum culling?
Yep.
Two issues:
1) Scene hierarchy generation (offline).
2) Runtime culling.
Hierarchy:
==========
There are lots of ways to do this. I usually build an heirerchical description
of each terrain tile. I typically build a tree structure that's organized
as follows:
| The World.
|
___________________|___
| | | | | | |
* * * * * * * Terrain tiles currently loaded
| | | | | | |
|
|
_____|_____
| | | |
* * * * Quarter-tiles
| | | |
|
|
_____|_____
| | | |
* * * * Sixteenth-tiles
| | | |
...and so on down until the number of polygons in each 'object' gets 'small enough'.
When you do this, don't try to split polygons when they cross a quarter or a
sixteenth tile boundary - just dump each polygon into the nearest 'bucket' to
it's centroid.
Do your tri-stripping on the leaf nodes of this tree - so that each tristrip
is contained entirely within one bucket.
Eventually, you will need to include buildings, roads, rivers, etc. Since these
need to be culled by level of detail, it is often useful to put them into a separate
tree structure that parallels the terrain 'skin' structure.
Finally, compute a bounding sphere around each leaf node, find the best fit
sphere by finding the maximum and minimim x, y and z of the tristrips in that
leaf node, taking the mid-point and then finding the vertex that is furthest
from that center point and using it as the radius.
Compute the bounding sphere for each level in the tree (everywhere where there is
a '*' in my diagram).
Runtime:
========
At runtime, you walk that tree every frame, testing the bounding sphere against
the view frustum.
* If the sphere lies entirely outside the view frustum then stop traversal
for that node. There is no need to test any of the nodes beneath this one
(we know that none of their leaf tristrips are visible).
* If the sphere lies entirely inside the view frustum then traverse immediately
to all of the leaves below this node without doing any more sphere testing
on them - draw all of the tristrips that are there. (We know they are all visible)
* If the sphere straddles the view frustum then check each daughter node in
turn by applying this algorithm on them recursively. If a leaf node straddles
the view frustrum then it's bad luck, you just draw all the tristrips it
contains and let OpenGL do the work.
You might also want to put a 'transition range' onto each node and if it
lies beyond that range cull it. You can also use this to conveniently
switch levels of detail by having multiple versions of each object in
the tree.
Testing a sphere against the View Frustum:
==========================================
In most cases, we can describe the volume of space that you can see
through the little glass window on the front of your CRT using a
Frustum (frequently mis-spelled as Frustrum or Fustrum even in some
text books).
A frustum is a truncated pyramid - which typically bounded by six
planes called:
NEAR, FAR, LEFT, RIGHT, TOP, BOTTOM
There are applications that require additional clipping planes (eg for
non-rectangular screens) - extending the work described in this
to cater for that is not hard).
In principal, all six planes can be constructed as general plane
equations:
A x + B y + C z + D == 0
However, for most applications, NEAR and FAR are parallel to the
screen, LEFT, RIGHT,TOP and BOTTOM all meet at the eye and the eye lies
along a vector that extends out from the center of the screen and is
perpendicular to it. This simplifies the equations considerably for
practical applications.
Transforms.
~~~~~~~~~~~
It is easiest to perform culling in a coordinate system where the
eyepoint is at the origin and the line from the eye through the center
of the screen lies along one major axis with the edges of the screen
parallel to the remaining two axes. This coordinate system is called
'Eye Space'.
Testing a Sphere against a Frustum.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The most important thing to bear in mind about culling is that the
first trivial-reject test you apply is by far the most time-critical.
This test is always applied to more nodes than any of the subsequent
tests.
So, do the cheapest test first.
This is typically the NEAR plane test. Everything behind the viewers
head gets chopped out - and it's an especially cheap test.
if ( obj_sphere.center.z < near_plane - obj_sphere.radius )
REJECT!!
...next do the second cheapest test (assuming you know that your
database could possibly extend beyond the far clip plane)...
if ( obj_sphere.center.z - obj_sphere.radius > far_plane )
REJECT!!
...and *then* (for each of the other 4 planes) do...
if ( distance( obj.position, plane ) <= obj_sphere.radius )
REJECT!!
(The algorithm for computing that 'distance()' function is described
below).
It's also useful to know that in many applications, you cull more
objects from the left and right faces of the frustum than you do from
the top and bottom - so test left, then right, then bottom then top.
Also, with bounding sphere tests, you shouldn't forget to do
total-accept as well as total-reject tests. Once you know that an
object's sphere is TOTALLY on screen, you don't have to descend into
the daughter objects to cull-test them...you *know* they are all
on-screen.
Another way to look at that it to remember which of the six possible
plane tests didn't even touch the sphere - as you work your way down
the object hierarchy, you can accumulate those flags and avoid even
testing those planes that a parent sphere has already cleanly passed.
If you do this then a vast percentage of your spheres will only need to
be tested against one plane. However, for the normal case of a simple
frustum - when you examine the fully optimised
distance-of-point-from-plane code (below), you may well conclude that
this additional logic doesn't justify the paltry amount of additional
math that it might save.
Computing the Distance from Sphere Center to Clipping Plane.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A plane can be represented by the equation
Ax + By + Cz + D = 0 ;
A,B,C is just the surface normal of the plane and D is the shortest
distance from the origin to the plane.
So, if you need to find the distance of a point from the plane, just
imagine a new plane that goes through your test point and is parallel
to the plane you want to test. The plane equation of that new plane
would be:
A'x + B'y + C'z + D' = 0 ;
Since the two planes are parallel, their surface normals are the same,
so
A' == A B' == B C' == C D' == D + distance_between_the_two_planes
...the only thing that's different is their D values - which differ by
the distance of your test point from the original plane.
So, for a point (x,y,z), the distance 'd' from the plane (A,B,C,D) is
derived as:
d = D' - D
= -A'x - B'y - C'z - D
= -Ax - By - Cz - D
= -( [ABC]dot[xyz] + D )
A dot-product of the point and the surface normal of the plane, plus
the distance from the plane to the origin. Three multiplies, three
additions and a negation.
As an aside - if you consider the point (x,y,z) as a FOUR element
homogeneous vector (x,y,z,w) then 'w' is 1.0 and you can compute the
distance by simply taking the four element dot-product of (A,B,C,D)
with (x,y,z,w). If you have fast 4x4 matrix math hardware in your
machine then you can use it to compute the distance from a point to all
four planes in a single operation!
That's the general result for an arbitary plane - but culling to the
view frustum is a very special case. If you are working in eye-relative
coordinates (IMHO this is best), then since all TOP,BOTTOM,LEFT,RIGHT
planes of the frustum meet at the eye - and since the eye is at the
origin (by definition), then D is always zero for those planes and that
saves you a subtract.
If you are feeling even more in need of optimisation - then you can
save one multiply per plane by realising that (for rectangular screens)
one of the three components of the plane equation will always be zero.
So, for the LEFT clip plane, the Y component of the normal of the plane
is zero, so the distance to the left or right plane is just
d = -( Ax + Cz )
...and to the top or bottom plane it's just:
d = -( By + Cz )
Furthermore, we know that the A component for the LEFT plane is just
the negation of the A component of the RIGHT plane, and the C component
is the same for both LEFT and RIGHT (and similarly, the B component of
the TOP plane, is the negation of the B component for the BOTTOM plane
and the C component is the same for both TOP and BOTTOM). This means
that you only need four multiplies and four additions to do the entire
job. (Since you are only using this for culling, you don't need the
minus sign - just reverse the conditional).
The NEAR and FAR planes are typically parallel to the X/Y plane. That
means that A and B are both zero and C is one (or minus-one) - but D is
not zero, so the math boils down to an add and a negate:
d = -(z + D)
Conclusions.
~~~~~~~~~~~~
Sphere-based culling can be extremely cost-effective. It's so cheap
that even if you feel the need to use a bounding cubeoid (or even a yet
more complex shape), it's still worth doing a sphere-based cull first
just to get rid of the trivial accept and reject cases.
Steve Baker (817)619-8776 (Vox/Vox-Mail)
Raytheon Systems Inc. (817)619-4028 (Fax)
Work: SBaker@link.com http://www.hti.com
Home: SJBaker1@airmail.net http://web2.airmail.net/sjbaker1
From owner-flight-gear@me.umn.edu Tue May 26 08:43:36 1998
X-VM-v5-Data: ([nil nil nil nil nil nil nil nil nil]
["2090" "Tue" "26" "May" "1998" "08:42:09" "-0500" "Steve Baker" "sbaker@link.com" nil "69" "Re: [FGFS] View frustum culling" "^From:" nil nil "5" nil nil nil nil nil]
nil)
Received: (from majordom@localhost)
by meserv.me.umn.edu (8.8.8/8.8.8) id IAA29744
for flight-gear-outgoing; Tue, 26 May 1998 08:43:36 -0500 (CDT)
X-Authentication-Warning: meserv.me.umn.edu: majordom set sender to owner-flight-gear@me.umn.edu using -f
Received: from lfkw10.bgm.link.com (bgm.link.com [130.210.2.10])
by meserv.me.umn.edu (8.8.8/8.8.8) with ESMTP id IAA29740
for <flight-gear@me.umn.edu>; Tue, 26 May 1998 08:43:31 -0500 (CDT)
Received: from borgus.bgm.link.com (borgus.bgm.link.com [130.210.236.13])
by lfkw10.bgm.link.com (8.8.6/HTI-Hack-8.8.4) with SMTP
id IAA02170 for <flight-gear@me.umn.edu>; Tue, 26 May 1998 08:43:01 -0500 (CDT)
X-Sender: steve@borgus.bgm.link.com
In-Reply-To: <199805240245.VAA00603@kenai.me.umn.edu>
Message-ID: <Pine.SGI.3.96.980526083427.2282G-100000@borgus.bgm.link.com>
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII
Precedence: bulk
Reply-To: flight-gear@me.umn.edu
From: Steve Baker <sbaker@link.com>
Sender: owner-flight-gear@me.umn.edu
To: flight-gear@me.umn.edu
Subject: Re: [FGFS] View frustum culling
Date: Tue, 26 May 1998 08:42:09 -0500 (CDT)
On Sat, 23 May 1998, Curtis L. Olson wrote:
> Gene Buckle writes:
> > If you want to email me a cygnus/mesa binary, I'd be happy to give
> > you new speed figures.
>
> Gene,
>
> The new binaries expect a modified scenery format. My impression for
> now based on culled vs. drawn percentages is that there was a much
> bigger jump between no culling and tile culling, than between tile
> culling and fragment culling.
That's to be expected...
(The eye is in the center of the diagram - looking up:
No Culling - draw this much:
________\_____________/________
| | \ | / | |
| | \ | / | |
|_______|___\___|___/___|_______|
| | \ | / | |
| | \ | / | |
|_______|______\|/______|_______|
| | | | |
| | | | |
|_______|_______|_______|_______|
| | | | |
| | | | |
|_______|_______|_______|_______|
Tile culling - draw this much:
_\_____________/_
| \ | / |
| \ | / |
|___\___|___/___|
| \ | / |
| \ | / |
|______\|/______|
Tile *and* tstrip culling - draw maybe this much:
\_____________/_
|_\ | /_|
|_\ | / |
|_\___|___/__|
|\ | /_|
|_\ | /_|
|\|/|
Clearly most of the savings were in the tile culling, but providing the
culling itself is done reasonably efficiently, the tstrip culling is
still worth-while.
Steve Baker (817)619-8776 (Vox/Vox-Mail)
Raytheon Systems Inc. (817)619-4028 (Fax)
Work: SBaker@link.com http://www.hti.com
Home: SJBaker1@airmail.net http://web2.airmail.net/sjbaker1
-------------------------------------
Please visit the FGFS web page: http://www.menet.umn.edu/~curt/fgfs/
For help on using this list (especially unsubscribing), send a message to
"flight-gear-request@me.umn.edu" with a single line of text: "help".