Visibility
Testing
Author
:
Jack Hoxley
Written : 14th March 2001
Contact : [Web]
[Email]
Download : Graph_16.Zip
[16 Kb]
Contents
of this lesson:
1. Introduction
2. Vector Based Culling
3. Frustum Culling
4. Projection/Bounding Box Culling
5. Tips and Tricks
1.
Introduction
By
this point in the series you should be getting fairly familiar with 3D programming
and the Direct3D interfaces; the chances are that you've also started your own
little project, or begun thinking about it (if you like 3D that is). This section
of "useful techniques" are articles that explain things that aren't
necessarily part of Direct3D or it's interfaces, but may well be required when
using them - or are made easier using them. Many of these tricks use the D3DX
libraries maths functions to simplify things, but where relevent the actual
technique behind them will be explained.
The
first useful technique I want to cover is that of visibility testing. Someone
once said (I dont know who): "The fastest polygons are the ones you dont
draw", this should make perfect sense, we only have a finite amount of
power to play with, and when you want your game to run at any sort of reasonable
pace you'll always be juggling between what you can do and what you want to
do. Despite the ever increasing power that 3D cards are providing we dont want
to waste any time or resources that we dont need to, the simplest way is to
reduce the number of objects or vertices that we are being sent to render. Whilst
Direct3D will clip geometry to fit the screen it still does quite a lot of processing
on hidden and/or offscreen geometry - geometry that it is quite likely the player
will never see. To stop Direct3D wasting valuable time programmers employ a
handful of tricks and algorithms to determine whether or not geometry should
be rendered (based on it being on/off screen).
There
are 100's of different methods for doing this, different ones will suit different
worlds/game types - its up to you to choose the one you like the most or design
a custom version based on multiple different methods. This article covers 3
types of culling (removal of geometry we dont want) methods, and then offers
some tips and tricks for using these algorithms.
2.
Vector Based Culling
This
first method is an extremely simple one, and was designed by myself for use
with "3D-World" (the final piece for this series), it may well have
been documented and used before, but I didn't read up on it before hand. The
major flaw with this algorithm is that it only suits very specific environments,
landscapes particularly - buildings/indoors scenes as well, but other engine
types will not always work so well using this technique.
The
basis of this technique is the usage of a common 3D graphics formula, the Dot
Product - this is covered again in Useful Techniques number 2, so I'll only
skim through it here. The dot product of two vectors (that are normalised) will
give us a value representative of the angle between them (The cosine to be precise).
We can use this value to tell if a point is in front of the camera, and within
a given degree of the camera's direction.
UV
= (U.X * V.X) + (U.Y * V.Y) + (U.Z * V.Z)
UV = (U.X * V.X) + (U.Y * V.Y)
The Dot product formula for 3D vectors (top), and 2D vectors (Bottom). If the
vectors are normalised (a length of 1) then the result will lie between -1 and
+1. If we take the vector U to be the direction that the camera is facing (calculated
using the Unit Circle Theorem) and V to be the vector from the camera to the
point in question, we can calculate the angle, which will give us the results
depicted below. Currently it's only in 2D, but that's partly because it's easier
to show as a diagram.
All
of the above vectors (represented by black lines) are compared with a vector
going straight up - [0,1], a vector going the same direction gets a result of
1.0 - as shown, a vector going perpendicular to [0,1] will get a result of 0.0,
and any vector between will get a value between 0.0 and 1.0. Any vector going
in a direction away from [0,1] will get a negative value - also shown on the
diagram.
Using
this information it is going to be quite simple to construct a test that rejects
any points that are behind it, or out of range given a reference value. When
setting up your projection matrix you will have specified a FOV angle (usually
in radians), if we convert this angle to a vector we can say any comparisons
with a result less than this is NOT in view, and any comparisons with a result
greater than this IS in view. To calculate the comparison value we can use a
simple bit of trig maths - this can be done on paper before hand, once you have
the values you can store them as a CONST value and use them forever after -
they wont change. The most commonly used angles are 45, 60 and 90 degrees, the
following diagram illustrates how we can work the dot product reference value
out. If we take the view cone as a 2D triangle, where the known angle is half
of the total view angle (two triangles will make up the whole cone), we can
take a two known distances and either use trig to find the third, or use pythagorus's
theorem:
The
above triangles represent 45, 60 and 90 degree view angles, for simplicity I'm
making the adjacent = to 1, and we want to find the opposite (labelled ?) -
we will then have a 2D coordinate for the outer corner (not the 90 degree one)
proportional to the origin (which is where the corner with the angle is). We
can then normalise this vector, and then compare it with the vector for the
adjacent, [1,0] to get the value we want. Here we go:
The
length ?'d, using inverse tan will be:
45
Degree cone
Tan(22.5) = ? / 1
? = Tan(22.5)
? = 0.4142135624
60
Degree cone
Tan(30) = ? / 1
? = Tan(30)
? = 0.5773502692
90
Degree cone
Tan(45) = ? / 1
? = Tan(45)
? = 1
We
now know the coordinates of the point:
45
Degrees : [1, 0.4142135624]
60 Degrees : [1, 0.5773502692]
90 Degrees : [1, 1]
if
we normalise these vectors we get the direction from the origin to the point:
45
Degrees : [0.9238795325, 0.3826834324]
60 Degrees : [0.8660254038, 0.5]
90 Degrees : [0.7071067812, 0.7071067812]
If
we now perform a 2D Dot product calculation on the adjacent vector ( [1,0] )
and the vectors listed above we'll get our reference value for each view angle,
the results are:
45
Degrees : 0.923879325
60 Degrees : 0.8660254038
90 Degrees : 0.7071037812
You
may notice that these value are just the X component of original vectors; this
is simply because the adjacent vector, [1,0], doesn't alter the X component
(multiply by 1) and the Y component is made to be 0 (multiply by 0) - resulting
in the X component being the Dot product value.
You
can store these values and use them whenever you like without recalculating
them, and if you need a different angle you can quite easily re-write the above
calculations to compensate. Also note that you may need to allow a little room
when doing the culling, the 3D World final piece uses a 45 degree view angle,
yet a dot product comparison figure of 0.865 and 0.845 (depending on what it's
rendering) - originally it used 0.924, but based on observation it occasionally
clipped points that it shouldn't have done - so I kept taking away a little
bit until I decided it wasn't elminintating many triangles - it still does sometimes,
but compared with the amount of excess triangles it draws I kept it as it is.
So
we've come this far, and not a great deal has happened. We need to write some
code that uses all these facts and bits of theory - a simple function that will
take a set of points and return if it's visible or not.
Public
Function
CheckPointVisible(CamPos
As
D3DVECTOR2,
CamAng
As
Single,
DrawDepth
As
Single,
_
PointToTest
As
D3DVECTOR2,
DPGreaterThan
As
Single)
As
Boolean
'//0. Any variables
Dim
vDir
As
D3DVECTOR2
Dim
vTmp
As
D3DVECTOR2
Dim
DOT As
Single
'//1. Get vector from camera to point
D3DXVec2Subtract
vDir,
PointToTest,
CamPos
'//2. Normalise vector
D3DXVec2Normalize
vDir,
vDir
'//3. get DOT product
vTmp.X
=
UnitCircle(CInt(CamAng)).X
vTmp.Y
=
UnitCircle(CInt(CamAng)).Y
DOT =
D3DXVec2Dot(vTmp,
vDir)
'//4. Check against DPGreaterThan and distance check
If DOT
>
DPGreaterThan
Then
'In
the
correct
cone
area
If
GetDist2D(PointToTest.X,
PointToTest.Y,
CamPos.X,
CamPos.Y)
<=
DrawDepth
Then
'we're within the correct distance
CheckPointVisible
= True
End If
Else
'if we're within DPGreaterThan - 0.165 and within 20m of camera we'll add it
If DOT
> (DPGreaterThan
-
0.165)
Then
If
GetDist2D(PointToTest.X,
PointToTest.Y,
CamPos.X,
CamPos.Y)
<=
20
Then
CheckPointVisible
= True
End If
End If
End If
End
Function |
|
|
|
Not
too complicated really, First it gets the direction vector from the camera to
the point in question, then it compares this to the vector defined by the angle
the camera is pointing (we could also calculate this by using vAt-vEye from
the view matrix). Next it performs the dot product calculation, it then takes
the result and compares it with the reference value, if it passes it checks
the distance, if it's within range then the point is visible - the functions
returns true. If it fails it does an additional check where it's more lenient,
and only passes it if it's very close to the camera - during testing I found
that there were occasional anomalies close to the camera - this little bit of
code solved it.
A
couple of things to note about the above code:
1) It uses the D3DX library, whilst this is an article about Direct3D you can
replace these calls with equivelent functions (should you be using a newer/older
version of DirectX, or not using DirectX at all...)
2) It uses the standard GetDist2D() call - this is a simple function that returns
the distance between two points in 2D space, and looks like:
Public
Function
GetDist2D(X1
As
Single,
Y1 As
Single,
X2 As
Single,
Y2 As
Single)
As
Single
GetDist2D
=
Sqr(Abs(((X1
- X2)
* (X1
- X2))
+ ((Y1
- Y2)
* (Y1
-
Y2))))
End
Function |
|
|
|
3)
It uses the unit circle theorem, in this case it just reads from a lookup array
of precalculated integer angles (0 to 360), If you haven't seen this before
check out the maths article later on. Briefly though - the X and Y components
can be calculated like so:
Public
Function
GetVectorFromAngle(Theta
As
Double)
As
D3DVECTOR2
Theta
=
Theta
* ((4
*
Atn(1))
/ 180) 'convert angle to radians
GetVectorFromAngle.X
=
Cos(Theta)
GetVectorFromAngle.Y
=
Sin(Theta)
End
Function |
|
|
|
Done!
Using
the above function youcan now tell in 2D if a point is within a given angle,
if you modify this it will tell you if the point is within a cone defined by
the angle. This method is only really useful for landscape or planar worlds
- where everything originates from the same (or similiar) height, like the insides
of an office/building. When using height mapped worlds (for landscapes), you
can test the center of every tile for visibility - the grid the map is based
on is 2D (ignoring the Y/height dimension). For a working example of this wait
for the (or go see if it's ready) 3D-World final piece.
3.
Frustum Culling
Frustum
culling is one of the more common techniques used - it's based in full 3D unlike
the vector one above, and is fairly simple to implement, whilst not incredibly
accurate it usually does the job adequately.
First
off, what is the frustum? this is the area of 3D space that is projected onto
the screen when you render a scene. It is defined by a series of planes, these
planes are then used to reject geometry. The frustum is illustrated using the
following diagram:
(My apologies for the appalling diagram!)
In
the above diagram, the blue arrow shows where the camera is facing/looking,
the smaller square is the near clipping plane (only vertices beyond this are
accepted), the larger square is the far clipping plane (only vertices in front
of this are accepted), and the 4 sides joining them up are the side clipping
planes. the size and shape of this volume are defined by the viewport settings,
projection matrix and the view matrix.
What
we need to do is construct a set of 6 planes based on this shape, and suited
to fit our world settings. The code for this is quite complicated, and is included
in the DirectX8 SDK helper libraries, which is where this next piece of code
comes from (I didn't write it is what I'm saying!):
'//Borrowed from the SDK's D3DUTIL module... Computes the clip planes for the view frustum...
Sub ComputeClipPlanes(veye As D3DVECTOR, vat As D3DVECTOR, vUp As D3DVECTOR, _
fov As Single, front As Single, back As Single,
aspect As Single)
Dim vDir As D3DVECTOR
Dim vright As D3DVECTOR
Dim vFrontCenter As D3DVECTOR
Dim vFrontUp As D3DVECTOR
Dim vFrontRight As D3DVECTOR
Dim vBackCenter As D3DVECTOR
Dim vBackRight As D3DVECTOR
Dim vbackLeft As D3DVECTOR
Dim vBackRightTop As D3DVECTOR
Dim vBackLeftTop As D3DVECTOR
Dim vBackRightBot As D3DVECTOR
Dim vBackLeftBot As D3DVECTOR
Dim DX As Single
Dim dY As Single
'Establish our basis vector
D3DXVec3Subtract vDir, vat, veye
D3DXVec3Normalize vDir, vDir
D3DXVec3Normalize vUp, vUp
D3DXVec3Cross vright, vDir, vUp
DX = Tan(fov / 2) * back
dY = DX * aspect
'
'
'
/| vbackleft (top,bot)
' / |
' vfront |
' /| |
' eye ----|
vbackcenter
' \| |
' \ |dx
' \ |
'
\| vbackright (top,bot)
'
'
'compute vbackcenter
D3DXVec3Scale vBackCenter, vDir, back
D3DXVec3Add vBackCenter, vBackCenter, veye
'compute vbackright
D3DXVec3Scale vBackRight, vright, DX
D3DXVec3Add vBackRight, vBackCenter, vBackRight
'compute vbackleft
D3DXVec3Scale vbackLeft, vright, -DX
D3DXVec3Add vbackLeft, vBackCenter, vbackLeft
'compute vbackrighttop
D3DXVec3Scale vBackRightTop, vUp, dY
D3DXVec3Add vBackRightTop, vBackRight, vBackRightTop
'compute vbacklefttop
D3DXVec3Scale vBackLeftTop, vUp, dY
D3DXVec3Add vBackLeftTop, vBackRight, vBackLeftTop
'compute vbackrightbot
D3DXVec3Scale vBackRightBot, vUp, -dY
D3DXVec3Add vBackRightBot, vBackRight, vBackRightBot
'compute vbackleftbot
D3DXVec3Scale vBackLeftBot, vUp, -dY
D3DXVec3Add vBackLeftBot, vBackRight, vBackLeftBot
'compute vfrontcenter
D3DXVec3Scale vFrontCenter, vDir, front
D3DXVec3Add vFrontCenter, vFrontCenter, veye
'compute vfrontright
D3DXVec3Scale vFrontRight, vright, DX
D3DXVec3Add vFrontRight, vFrontCenter, vFrontRight
'compute vfrontup
D3DXVec3Scale vFrontUp, vUp, dY
D3DXVec3Add vFrontUp, vFrontCenter, vFrontUp
'front plane
D3DXPlaneFromPointNormal FrustumPlanes(0), veye, vDir
'back plane
Dim vnegdir As D3DVECTOR
D3DXVec3Scale vnegdir, vDir, -1
D3DXPlaneFromPointNormal FrustumPlanes(1), vBackCenter,
vnegdir
'right plane
D3DXPlaneFromPoints FrustumPlanes(2), veye, vBackRightTop,
vBackRightBot
'left plane
D3DXPlaneFromPoints FrustumPlanes(3), veye, vBackLeftTop,
vBackLeftBot
'top plane
D3DXPlaneFromPoints FrustumPlanes(4), veye, vBackLeftTop,
vBackRightTop
'bot plane
D3DXPlaneFromPoints FrustumPlanes(5), veye, vBackRightBot,
vBackLeftBot
End Sub |
|
|
|
looks
nasty doesn't it... you dont really need to understand it in order to use it
- so dont worry about how it does things. In order to use this code you must
have an array of 6 D3DPLANE objects, and you must call this function everytime
you change the camera position (not everytime you want to check a points visibility).
An example:
'//In
the
declarations
section
Private FrustumPlanes(0 To 5) As D3DPLANE
'//When you move the camera around
ComputeClipPlanes camerafrom, CameraTo, MakeVector(0, 1, 0), PI / 4, 0,
DrawDepth, 1 |
|
|
|
the
ComputeClipPlanes( ) call uses the same parameters that you have done for your
view and projection matrices.
Finally,
after we have constructed the information that we need we have to be able to
use it. The next piece of code (also from the SDK files) shows us how we can
pass a sphere and it'll tell us if it's visible or not:
Private
Function
CheckSphere(Center
As
D3DVECTOR,
Radius
As
Single)
As
Boolean
Dim
TCenter
As
D3DVECTOR
Dim
Matrix
As
D3DMATRIX,
I As
Long
Dim
Dist
As
Single
D3DDevice.GetTransform
D3DTS_WORLD,
Matrix
D3DXVec3TransformCoord
TCenter,
Center,
Matrix
For I
= 0 To
5
Dist =
D3DXPlaneDotCoord(FrustumPlanes(I),
TCenter)
If
Dist
<
-Radius
Then
CheckSphere
=
False 'not visible
Exit
Function
End If
Next I
CheckSphere
= True 'visible
End
Function |
|
|
|
In
order to use this we pass the point in question as the Center parameter, and
a radius of your choice as the second. The radius can be very small (0.00001
or something) if you only want to check the point, if you want to check a large
area you can increase the radius. If any part of the sphere is visible then
the function returns true.
The
fact that it returns true for any of the sphere being visible is it's only downfall,
but it only means that you need to be a little more careful when you're using
the method. If you used an entire sphere for a model, and the model only occupied
a small part of the sphere (yet it required a large radius) then you could get
the situation where it renders the model - but it's not actually on screen.
To solve this you would want to investigate using multiple spheres and correlating
the results.
4.
Projection/Bounding Box culling
This
final method is my current favourite, I first read up on it in the Visual
Weather Documentation, having since explored its usage I've decided it's
a very easy to use, fast and very accurate as well - just what I like.
What
we are trying to achieve with these algorithms is to rule out any unnecessary
geometry that wont appear on screen, therefore, if we could find out where a
point will be projected from 3D space into 2D space we would be able to tell
if it's within the screen boundaries. Fairly simple really. We could then expand
this to be using a bounding box method, where we choose a relevent size box
that suits the model we're querying - we then work out if any/all of the 8 corners
of the cube will be on the sceen, if any of them are then at least part of the
model should be visible. If you wanted you could check every major vertex in
the model, but for complicated models this would be quite slow - and start to
eat away from the speed advantage that we're chasing.
Luckily
for us, DirectX8, in the form of the D3DX helper library has a little function
that allows us to give a 3D point and recieve the 2D coordinate for where it
would be rendered on the frame buffer, it looks like this:
D3DXVec3Project( _
VOut As D3DVECTOR, _
V As D3DVECTOR, _
Viewport As D3DVIEWPORT8, _
Projection As D3DMATRIX, _
View As D3DMATRIX, _
World As D3DMATRIX)
|
It's
fairly easy to use, the parameters required are:
VOut
as D3DVECTOR - this holds the screen space coordinates for the point you
want transformed, the first two components X and Y are what you'd expect,
the Z component gives you the depth buffer value that the point would recieve
(you can then tell if it's behind certain things, or beyond a certain distance).
V as D3DVECTOR - this is the point that we want to transform, bare
in mind that this point will be pre-transformed by the world matrix first
and therefore may not be exactly where you think it is.
ViewPort as D3DVIEWPORT8 - This describes the screen that its transforming
to, this is simply a desciption of how the device is set up - resolution etc...
you can retrieve the current viewport settings by calling D3DDevice.GetViewport
and providing an empty D3DVIEWPORT8 structure
Projection as D3DMATRIX - This is the current projection matrix, it's
important that this is accurate
View as D3DMATRIX - This is the current view matrix or camera setup
- this is obviously important...
World as D3DMATRIX - This is the current world transformation state,
your point will be transformed by this matrix before being projected onto
the screen
If
we pass the correct parameters, we'll be given a 2D coordinate that tells
us where the point would appear if we were going to render it. On a side note,
this method is extremely useful if you want names (in 2D) to be presented
above a player (3D) in a game, or want text/health bars to follow a character/unit
around.
Now
we can get the 2D point, we need to know what to do with it, how we coordinate
the rest of the data with a bounding box to tell if an area of 3D space is going
to be visible. The following diagram will help explain all the possible situations:
Take
the above representation of 9 bounding boxes, we're only interested in the vertices,
not the lines - the lines are there just to make it easier to see. If we go
through all of these cubes we can tell if they're in or out of the blue square,
which represents the visible area of the screen (0-1024 and 0-768 for example),
a cube is visible if any of the vertices is within the blue square - simple
as that, a square is not visible if all of the vertices are outside the square.
In the above diagram all but cube 7 are going to be partly visible. Unfortunately
it's not going to be as simple as that, take the next diagram for example:
In
this diagrame both of the triangles will of been rejected based on our previous
rules - yet it's quite obvious that they are both going to be partly visible.
To take this into account we need to know where off the screen the points are
- are they to the left, to the right, above or below? only if all the vertices
are to one of the extremes can we be certain that it's definately not a visible
region. To sum things up the following list will be the rules for acceptance/rejection:
if any of the vertices are within the screen area the box is PARTLY VISIBLE
if all of the vertices are within the screen area the box is TOTALLY
VISIBLE
if all of the vertices are to a far extreme (up/down/left/right) then
the box is TOTALLY INVISIBLE
if all of the vertices are out of the screen area, but not in the same
extreme the box is PARTLY VISIBLE
if the above is true, but all the vertices are greater than the two adjacent
extremes then the box is TOTALLY INVISIBLE
The
above set of rules should work for 99.9% of cases, you may find the odd exception
to the rule, if you do ammend the list and carry on...
We
now want to generate a generic function that returns one of eight states, the
reason for having lots of states will be seen later, where we need to know what
sort of invisible we're talking about. The eight states are: Partly Visible,
Totally Visible, Totally Invisible, To Screen Left, To Screen Right, To Screen
Top, To Screen Bottom and To Screen Back. The code I've written for this is
the following:
'##
This
function
takes
a 3D
point
and
transforms
it
into
screenspace,
'## it
also
checks
against
the
depth
buffer
and
screen
boundaries,
resulting
'## in
either
Totally
visible
or
Partly
visible
return
values.
Private Function ProjectionVisibilityTesting(Point3D As D3DVECTOR) As VisTest
'//0. Any variables
Dim vRet As D3DVECTOR
Dim VP As D3DVIEWPORT8
'//1. Collect the data
D3DDevice.GetViewport VP
D3DXVec3Project vRet, Point3D, VP, matProj, matView, matWorld
'//2. correlate data
If vRet.X < VP.X Then
ProjectionVisibilityTesting =
TOSCREENLEFT
Exit Function
ElseIf vRet.X > VP.Width Then
ProjectionVisibilityTesting =
TOSCREENRIGHT
Exit Function
ElseIf vRet.Y < VP.Y Then
ProjectionVisibilityTesting =
TOSCREENTOP
Exit Function
ElseIf vRet.Y > VP.Height Then
ProjectionVisibilityTesting =
TOSCREENBOTTOM
Exit Function
ElseIf vRet.Z > VP.MaxZ Then
ProjectionVisibilityTesting =
TOSCREENBACK
Exit Function
ElseIf vRet.Z < VP.MinZ Then
ProjectionVisibilityTesting =
TOSCREENBOTTOM
Exit Function
Else
ProjectionVisibilityTesting =
TOTALLYVISIBLE
Exit Function
End If
End Function |
|
|
|
this
function can then be extended to take a 3D box, representing an area of space
- and check if that's visible or not, it is this code that required the greater
detail of invisibility flags - this function would not work properly without
them. The great power of this part is that you can rule out vast areas of 3D
space with only 6 calculations, if the corners of the box are all invisible,
every point inside will also be invisible...
Private
Function
ProjectionVisibilityBoundingBox(BBox
As
Box3D)
As
VisTest
'//0. Any variables
Dim
Results(0
To 7)
As
VisTest
'//1. Perform tests
Results(0)
=
ProjectionVisibilityTesting(BBox.Coord(0))
Results(1)
=
ProjectionVisibilityTesting(BBox.Coord(1))
Results(2)
=
ProjectionVisibilityTesting(BBox.Coord(2))
Results(3)
=
ProjectionVisibilityTesting(BBox.Coord(3))
Results(4)
=
ProjectionVisibilityTesting(BBox.Coord(4))
Results(5)
=
ProjectionVisibilityTesting(BBox.Coord(5))
Results(6)
=
ProjectionVisibilityTesting(BBox.Coord(6))
Results(7)
=
ProjectionVisibilityTesting(BBox.Coord(7))
'//2. correlate the data
'//handle simplest cases first:
If
Results(0)
=
TOTALLYVISIBLE
And
Results(1)
=
TOTALLYVISIBLE
And
Results(2)
=
TOTALLYVISIBLE
_
And
Results(3)
=
TOTALLYVISIBLE
And
Results(4)
=
TOTALLYVISIBLE
And
Results(5)
=
TOTALLYVISIBLE
_
And
Results(6)
=
TOTALLYVISIBLE
And
Results(7)
=
TOTALLYVISIBLE
Then
'if all of them are visible then return totally visible
ProjectionVisibilityBoundingBox
=
TOTALLYVISIBLE
Exit
Function
ElseIf
Results(0)
=
TOTALLYVISIBLE
Or
Results(1)
=
TOTALLYVISIBLE
Or
Results(2)
=
TOTALLYVISIBLE
_
Or
Results(3)
=
TOTALLYVISIBLE
Or
Results(4)
=
TOTALLYVISIBLE
Or
Results(5)
=
TOTALLYVISIBLE
_
Or
Results(6)
=
TOTALLYVISIBLE
Or
Results(7)
=
TOTALLYVISIBLE
Then
'if any of them are in the viewing area, return partially visible
ProjectionVisibilityBoundingBox
=
PARTLYVISIBLE
Exit
Function
End If
'//Handle the extremes/invisibility cases
If
Results(0)
=
TOSCREENBACK
And
Results(1)
=
TOSCREENBACK
And
Results(2)
=
TOSCREENBACK
_
And
Results(3)
=
TOSCREENBACK
And
Results(4)
=
TOSCREENBACK
And
Results(5)
=
TOSCREENBACK
_
And
Results(6)
=
TOSCREENBACK
And
Results(7)
=
TOSCREENBACK
Then
'all of the points are beyond the back clipping plain
ProjectionVisibilityBoundingBox
=
TOTALLYINVISIBLE
Exit
Function
ElseIf
Results(0)
=
TOSCREENLEFT
And
Results(1)
=
TOSCREENLEFT
And
Results(2)
=
TOSCREENLEFT
_
And
Results(3)
=
TOSCREENLEFT
And
Results(4)
=
TOSCREENLEFT
And
Results(5)
=
TOSCREENLEFT
_
And
Results(6)
=
TOSCREENLEFT
And
Results(7)
=
TOSCREENLEFT
Then
'all of the points are to the left of the screen
ProjectionVisibilityBoundingBox
=
TOTALLYINVISIBLE
Exit
Function
ElseIf
Results(0)
=
TOSCREENRIGHT
And
Results(1)
=
TOSCREENRIGHT
And
Results(2)
=
TOSCREENRIGHT
_
And
Results(3)
=
TOSCREENRIGHT
And
Results(4)
=
TOSCREENRIGHT
And
Results(5)
=
TOSCREENRIGHT
_
And
Results(6)
=
TOSCREENRIGHT
And
Results(7)
=
TOSCREENRIGHT
Then
'all of the points are to the right of the screen
ProjectionVisibilityBoundingBox
=
TOTALLYINVISIBLE
Exit
Function
ElseIf
Results(0)
=
TOSCREENTOP
And
Results(1)
=
TOSCREENTOP
And
Results(2)
=
TOSCREENTOP
_
And
Results(3)
=
TOSCREENTOP
And
Results(4)
=
TOSCREENTOP
And
Results(5)
=
TOSCREENTOP
_
And
Results(6)
=
TOSCREENTOP
And
Results(7)
=
TOSCREENTOP
Then
'all of the points are off the top of the screen
ProjectionVisibilityBoundingBox
=
TOTALLYINVISIBLE
Exit
Function
ElseIf
Results(0)
=
TOSCREENBOTTOM
And
Results(1)
=
TOSCREENBOTTOM
And
Results(2)
=
TOSCREENBOTTOM
_
And
Results(3)
=
TOSCREENBOTTOM
And
Results(4)
=
TOSCREENBOTTOM
And
Results(5)
=
TOSCREENBOTTOM
_
And
Results(6)
=
TOSCREENBOTTOM
And
Results(7)
=
TOSCREENBOTTOM
Then
'all of the points are off the bottom of the screen...
ProjectionVisibilityBoundingBox
=
TOTALLYINVISIBLE
Exit
Function
End If
'//Handle the tricky cases, where
they occupy two extremes...
'these have already been handled by
the above logic system, currently it will only
'flag invisibility if the tile has
ALL vertices in the same extreme, therefore allowing those
'boxes that occupy two extremes. It
also handles the sub-case of this where it's in two extremes
'AND out of range - this is also
handled, in order to have this case the vertices must be beyond 2 extremes,
'and it's already handled for 1
extreme - so there's no need to check for two.
End
Function |
|
|
|
We
can now use these functions quite simply to render a tile (or not), the following
code excerpt takes a 3D flat tile and checks it's visibility before rendering
it:
box.Coord(0)
=
VertList(0).P
box.Coord(1)
=
VertList(1).P
box.Coord(2)
=
VertList(2).P
box.Coord(3)
=
VertList(3).P
box.Coord(4)
=
VertList(0).P 'because it's a flat tile, we make the cube flat...
box.Coord(5)
=
VertList(1).P
box.Coord(6)
=
VertList(2).P
box.Coord(7)
=
VertList(3).P
res =
ProjectionVisibilityBoundingBox(box)
If res
=
PARTLYVISIBLE
Or res
=
TOTALLYVISIBLE
Then
TileDrawn
= True
D3DDevice.DrawPrimitiveUP
D3DPT_TRIANGLESTRIP,
2,
VertList(0),
Len(VertList(0))
End If |
|
|
|
simple
really!
5.
Tips and Tricks
Now
that you have 3 techniques under your belt I'll discuss some further uses for
these techniques, and a few other simple tricks for reducing the amount of geometry
you need to render. The following list is by no means a definative guide - just
a starter, you can look up these ideas further, or combine and alter them to
fit whatever you want to do...
-
LOD Algorithms. Level Of Detail algorithms can reduce the amount of geometry
you need to render, or in the case of landscapes and wide open spaces can
dramatically increase the visible distance. Think about it logically, the
geometry in the distance will not be visible in quite the same detail as that
in the foreground, so why render it in such a way. Reducing the texture detail
and geometry density (vertex/triangle count) will allow you to draw things
further away without using up large amounts of processing time. LOD algorithms
can be quite complicated, but there are several good articles around.
- Invisible
Geometry. This is more likely to be a thing that you'll add to your level
editor rather than do it in realtime. If you have your view locked to a certain
position or have it restricted to certain areas then use this to your advantage
- dont draw, or even consider drawing parts of a world that will never be
visible. A good example of this would be the tops of mountains - if the camera
will never get up high enough to see the top of a mountain there's no point
even creating the mountain top - let alone checking it for visibility every
frame. The same can go for rooms in a first-person environment, if the level
designer puts a room, or some geometry that can never be seen by the camera/player
remove it. Determining if a piece of geometry will be visible could be extremely
difficult - so this method may or may not suit your game engine.
- Unlit/dark
geometry. Take the first person example again, if an entire room is unlit
and completely dark in some cases you could get away without drawing any of
it! this only works if there isn't anything behind that will become visible
(sky/other parts of the level).
- Bounding
Box sectors. This was mentioned briefly in the projection culling section.
If you have your level divided into large, regular sections, or you can design
your world to be - testing against the bounding box of this area will let
you know if the entire area is visible/invisible - in either case you know
that there is no need to visibility-test anything inside the box as it's either
all visible or all invisible. It gets a little more tricky when only part
of the box is visible.
- Quadtrees.
These are an extremely popular technique for landscape engines and extend
the bounding box theory. This algorithm can reduce the number of visibility
tests by 80% in some cases, meaning that whilst not drawing any excess geometry
you also reduce the number of calculations done to achieve this. There is
a good article on www.gamedev.net about
this.
- BSP
(Binary Space Partions) Tree. These tend to always be mentioned in terms of
first person shooters, but they can be used for other game types as well.
The principle behind them is very similiar to that of a quadtree. Check out
GameDev.net or Gamasutra.com
for articles about this technique
techniques
do not need to be limited by anything in this article, designing a custom method
that suits your game will often be much better than trying to twist your game
to fit another algorithm. If you are at the planning stage of a game then designing
the world to suit one of these algorithms could be a good idea though...
Hopefully
you now have all of the information required to get started on optimising the
rendering part of your game. Several of the ideas in section 5 could have been
covered here, but it would have made for a massive article so I decided against
it - in favour of pointing you to better dedicated articles.
As
usual you can download the complete source from the top of the
page.
|