[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

7.6.8 Rendering Loop

Written by Jorrit Tyberghein, jorrit.tyberghein@uz.kuleuven.ac.be.

Warning: About half of the information in this document is out of date and no longer accurately reflects the state of the rendering engine in Crystal Space.

Here is a run-through of the main rendering loop. This document is not an explanation of portal technology. It just explains how the main rendering loop in Crystal Space works so that you can have a quick idea of where you have to go to see a particular part of the algorithm work. This is a rather technical document and not intended for people who are only interested in using Crystal Space in their own projects. It is intended for people who want to know how Crystal Space works internally.

To understand this you should know how portals are used in Crystal Space. You should also read the tutorial (see section 5.2 Simple Tutorial 1) as this explains the basics for using Crystal Space. This document is baseed upon the `simple' application (`CS/apps/simple/') because this discussion looks a lot like a tutorial.

First we start in `apps/simple/simple.cpp'. In the main() function we initialize our engine. This is an instance of the class `csEngine' which actually represents the engine. I will not explain how all the initialization works. This is explained in the tutorial. But I will go straight to csEngine::Draw() which is called indirectly from within Simple::NextFrame(). It is called indirectly because first we call csView::Draw() which then calls csEngine::Draw().

World Rendering (csEngine::Draw())

The method csEngine::Draw() is located in the file `CS/libs/csengine/engine.cpp'.

csEngine::Draw() first sets up the initial `csRenderView' structure. This structure is defined in `CS/include/csengine/rview.h' and is the main structure which is used throughout the entire rendering process. It collects all data that is required for rendering the recursive portal structure.

Basically it contains the following information:

view
This is the current clipper. A clipper is a 2D polygon which defines what is visible. Every object is clipped to the view first. Every time we go through a portal the view is modified.

g3d
g2d
These are pointers to our 3D and 2D graphics subsystems.

clip_plane
This is a special plane. If `do_clip_plane' is used then we clip all geometry in 3D to `clip_plane'. This is normally not used except in special cases where we have portals that arrive in the middle of a sector. In that case the portal will be used as an extra clipping plane because we don't want to render everything that is behind the arrival plane of the portal.

callback
callback_data
These are used for special purposes which are not relevant to this discussion.

Another important thing is that `csRenderView' is actually a subclass of `csCamera' (`CS/include/csengine/camera.h') so all camera functionality is present as well.

To set up the initial `csRenderView' structure csEngine::Draw() creates a new instance based upon the given camera.

After this csEngine::Draw() gets the current sector from the camera and calls csSector::Draw() (`CS/libs/csengine/sector.cpp'). This will essentially draw the whole screen as discussed below.

After doing this (now that the screen is fully updated) we optionally draw halos. Halos are drawn on top of everything else since they are an effect in the eyes.

Sector Rendering (csSector::Draw())

This method csSector::Draw() is located in `CS/libs/csengine/sector.cpp'.

csSector::Draw() is responsible for rendering everything in the current sector. It will do this by first rendering the walls of the sector (using Z-fill only). It is possible that there is a BSP tree attached to the sector. In that case csSector::Draw() will use that BSP tree to sort the wall polygons back to front.

Before actually starting to render, it will first transform the sector to the camera position so that (0,0,0) is the camera point, (1,0,0) is one unit right of the camera, (0,1,0) is one unit above the camera, and (0,0,1) is one unit above the camera. This is done in csPolygonSet::NewTransformation() and csPolygonSet::TransformWorld2Cam() (see `CS/libs/csengine/basic/polyset.cpp'). The call to NewTransformation() is needed because it is possible that we render the same sector multiple times in the same recursion tree. Because of this we don't actually want to loose the transformation that occured the previous time so NewTransformation() takes care of potentially storing the old transformation and setting a new one (mirrors and other space warping portals make this feature essential).

After that actual drawing of the transformed polygons is performed by the method csSector::DrawPolygons(), which in turn calls csPolygonSet::DrawPolygonArray(). The `csPolygonSet' class is implemented in `CS/libs/csengine/basic/polyset.cpp'), and DrawPolygonArray() is discussed further in the next section.

While drawing the polygons of the sector (which are the sector's walls) some of them may be portals. In that case csPolygonSet::DrawPolygonArray() will immediatelly perform recursion to the destination sector of the portal. This means creating a new `csRenderView' with the new view and then recursing into csSector::Draw() of the new sector. It is important to note that DrawPolygonArray() may already enter recursion for a new sector even though the first sector has not finished drawing yet.

So it is important to note that after drawing all sector walls we have in fact drawn the entire world visible from this sector (including things and sprites in other sectors). But we have not drawn things and sprites in this sector yet.

csSector::Draw() will then continue to draw all things in the current sector. There are some special cases here. It is possible that all non-moving things have been collected into one thing which has an attached BSP tree. This is the `static_thing'. If that exists we first render that `static_thing' by using back to front ordering and using Z-fill instead of Z-buffer. Note that all non-moving things which were collected into the static thing are still in the list of things for the sector. But you can recognize them because they IsMerged() returns true.

Now there are two important cases depending on wether or not there was a static thing in the previous case. If there was a static thing or if the sector itself had a BSP tree, then all remaining things have to be drawn using the Z-buffer (ignoring foggy things for the moment). In this case we can't use Z-fill anymore because there was a BSP tree used to render the previous sector or static thing.

If there was no static thing and there was no BSP tree for the sector then we can still use Z-fill to render all convex things provided we render them back to front. This part of the algorithm does not work correctly yet and that's the reason that it has been disabled.

In any case all foggy things are collected and then Z-sorted. This is also not correct but currently the only way. Here we need a better way to do Z-sorting.

Now we can proceed to drawing all sprites. Warning: The remainder of this paragraph is no longer true. Currently this algorithm is somewhat simple. It assumes that a sprite always lives in one sector. This is of course not always right and the sprite structure (`csSprite3D' in `CS/libs/csengine/objects/cssprite.h') already has provisions for the fact that a single sprite can live in several sectors at the same time. What we want to do here is to only draw a sprite if one of the below conditions is true:

  1. The current sector is being rendered through an alpha mapping portal.

  2. The sector we came from before entering the portal (in the render process) is also a sector where the sprite lives.

In the first case we have to clip the sprite to the portal and render only the side on this portal. When returning from the recursion we will have to render the other side of the sprite.

In the second case we don't render the sprite but it will be rendered when we return from our recursion because the sprite is also part of the previous sector.

The next step in csSector::Draw() is to queue all halos that were encountered in this sector so that they can be drawn later. A Z-buffer visibility test will be used later to make sure that the halo only appears when the center is visible.

Finally we conclude by optionally fogging the current sector if this is needed. There are currently two ways of fogging:

Fixme: Is the next paragraph even accurate anymore?

The last method does not work correctly at this time. The first method is only implemented by the software renderer.

The method csSector::Draw() ends by restoring the transformation by calling csPolygonSet::RestoreTransformation().

Polygon Array Rendering (csPolygonSet::DrawPolygonArray())

The implementation of csPolygonSet::DrawPolygonArray() can be found in the file `CS/libs/csengine/basic/polyset.cpp'.

Both `csSector' and `csThing' inherit from `csPolygonSet' so they both basically use the same way of rendering polygons. DrawPolygonArray() is responsible for that. DrawPolygonArray() is typically called on either the complete list of polygons for the `csThing' or `csSector' or else a subset which was computed from a BSP tree.

DrawPolygonArray() will process every polygon in the array in turn. It will basically call the following three functions for every polygon:

  1. csPolygon3D::ClipToPlane() (`CS/libs/csengine/polygon/polygon.cpp')

  2. csPolygon3D::DoPerspective()

  3. csPolygon2D::ClipAgainst()

ClipToPlane() will do a quick test to see if all vertices of the polygon are in front of the Z-plane. That will at least exclude polygons quickly which are behind the viewer. Then it will perform backface culling. Finally it will process the special `clip_plane' field in `csRenderView' which was mentioned in the beginning of this document. In other words, it will clip the polygon in 3D to that plane. This is rarely needed.

If ClipToPlane() fails then the polygon is not visible and we don't need to continue with the other steps.

DoPerspective() does perspective correction on the polygon and thus transforms the polygon from 3D to 2D. The 2D polygon will be placed in a special static variable of type `csPolygon2D' (called `clipped'). Finally it will also transform the plane of the polygon to camera space.

DoPerspective() can also fail in which case the polygon is not visible and processing can stop here.

Finally we do ClipAgainst(). This will clip the polygon to the current view. The clipping happens in place. The result will be in `clipper'.

If ClipAgainst() fails the polygon was completely clipped away and we don't need to show anything.

When all three steps above succeed we have a visible (part) of a polygon which has been perspective corrected. We can now render that polygon.

If the polygon is a portal then we initiate a recursive process by calling the method csPortal::Draw() (`CS/libs/csengine/polygon/portal.cpp') which will in turn call csSector::Draw() for the destination sector. csPortal::Draw() also takes care of possible space warping and also creation of the `clip_plane' in `csRenderView' if this should be needed.

If a portal has an alpha transparent texture superimposed then we will draw that texture on top of the portal when csPortal::Draw() returned. However since `clipped' is a static variable we need to copy it locally to be able to draw it again later. This happens in `keep_clipped' and `keep_plane'.

If the polygon was not a portal then we just render it. This happens in csPolygon2D::DrawFilled() (from the file `CS/libs/csengine/polygon/polygon.cpp') which is further explained in the next section.

That's basically it for this function.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

This document was generated using texi2html