Skip to content
Ingo Ruhnke edited this page Mar 22, 2015 · 5 revisions

Needs more focus on highlight, color and light layers. Some other stuff should be split off.

To bring images on screen you have to execute drawing operations. There are several different types of drawing operations. Each of them depend on a number of parameters.

Common Parameters

These parameters are used for all drawing operations

  • Position, Rotation, Scale -- These 3 values together are called a modelview transform and are encoded as a 4x4 Matrix
  • Color -- the color is multiplied with each pixel that is drawn
  • blend_sfactor, blend_dfactor -- factors that are used for blending pixels with alpha values on screen (see glBlendFunc for details)

Text

Writes text on the screen. Parameters:

  • Font -- The font that is used to write the characters (the font has additional attributes like color of the text)

Surface

Draws a 2d bitmap (called Surface) on screen. Parameters:

  • Surface -- a pointer to a surface (a surface contains the opengl texture handle and uv coordinates)

VertexArray

VertexArray is currently one of the main bottlenecks of Windstille, recreating it on every draw is way to expensive, an object that can be reused should be used instead.

Executes a drawing command for opengl primiteves. (Typically used for GL_QUAD and GL_TRIANGLE)

  • texture -- texture handle of the opengl texture
  • type -- opengl primitive type
  • vertex list
  • normal list (optional, specified per vertex)
  • color list (optional, specified per vertex)

State Model and Transform Stack

When looking at typical code that does drawing operations, one observes that the common parameters are often the same for several drawing operations. It is convenient to not have to specify all parameters again and again for each drawing operation. So we created a state machine that maintains a "current" value for each of the common parameters.

You can also observe that most of the time the things you want to draw are given in a local coordinate system only. Typically you apply a "camera" transform to them or have even deeper parent/child relations where the coordinate system is relative to the parent. These cases can nicely be handled by a stack for the Position, Rotate and Scale attributes. Positions, Rotations and Scales are now specified relative to the topmost stack element. You can now simply push the transformation from child coordinate system to parent coordinate system on the stack and draw all childs in their own coordinate system.

Foreground/Background and Z-Positions

We're presenting a (pseudo) 3d dimensional world on a 2d dimensional screen. Handling Foreground and Background requires us to sort the Drawing Operations so that stuff in the foreground is drawn after stuff in the background and obscures the background.

It's very difficult to enforce a certain drawing order in the code, so we're introducing the concept of a z-position. DrawingOperations with higher z-positions will be executed after drawing operations with lower z-positions. This means that we can't execute the drawing operations imediately anymore, but we have to store the information needed to execute them. We call this info a Drawing Request. We fill a list with all Drawing Requests and sort them according to their z-positions and finally execute them in the correct order.

Implementation

The basic block of the implementation is the DrawingRequest class. It stores all information needed to execute a drawing operation. You can create custom subclasses from this class and override the draw() function to execute your custom drawing operations. You can expect that all common parameters that are maintained by the DrawingContext are already setup when the draw() function is called.

The DrawingContext class is responsible for the state management and maintains the modelview transform stack.

Optimisations

To correctly implement the drawing operations we have to create snaphots of the current drawing state and append it to all DrawingRequests. You can observe that for most drawing operations in a typical scene these snapshots contain identical values except for the position.

So we factor out the modelview and color+blendfunctions used in DrawingRequests to point to a shared block that is used by multiple Drawing Requests. This not only reduces memory consumption but also allows for more efficient restoring of the drawing state in opengl. As we can now compare the pointers of the ModelView and color+blenfunc objects and only reset these values if the pointers are different.

Layers

The current graphics engine works by using three layers, to each of the layers you can draw independly. They are only later down the pipeline flushed down to the screen. The multiple layers are necessary for once to properly sort the graphics and secondly they are needed to create certain effects. The layer are:

Color Layer

This is a normal layer, it contains just good old graphics as you would expect and is drawn as if you would draw directly to the screen.

Light Layer

This layer specifies which areas of the screen are visible and which aren't. At start everything is invisible (black), to make stuff visible one uses white. One can also use other colors, red, green, etc. The light layer is later multiplied with the color layer, thus everything that is black (0) gets blended out and everything else gets lit by the color found in the light layer. Drawing to this layer should almost always happen via additive blending.

Highlight Layer

The highlight layer is drawn ontop of the light layer, it is meant for lense-flares, glows and other effects that should be 100% visible all of the time and not covered by the light-layers shadow. It is the same as the color layer, only that the light layer doesn't get multiplied with it. Drawing should again be happening mostly additive, since thats gets closest to how real light acts.

GLSL Effects

GLSL, OpenGLs shader language, allows to do some very nice effects (fire, glass, shockwaves, etc.) the Windstille webpage has some videos of them. The good news is that they are rather easy to implement, the bad news is that one needs some framework to implement them properly, ie. first of one needs to get them through ClanLib and secondly for most effects one needs a copy of the current framebuffer, which however is rather expensive to read, so its hard to get. They are also rather GPU heavy, so when not handled with care they won't work on low-end graphic cards (GeforceFX5200, ATI9200) and they don't work at all on even older cards, so they would need to be optional.

[https://www.youtube.com/watch?v=xrA4bQik1Lc]

Clone this wiki locally