Documente Academic
Documente Profesional
Documente Cultură
Acknowledgements
Firstly thank you to all people who played with my application in order to see whether it worked, how it worked and for giving tips on how to improve the stuff: Andy Vickers, for being very interested in the work I did leading to multiple (long) conversations which made me realize how to build stuff often, mental support and application testing. Arjan Pragt for testing my application and providing lots and lots of negative feedback through the years which motivated me to keep on programming until perfection Pieter Bos, mainly for supplying the idea that feathered brush tiles should be have their feather factor for transparency. The people at Kung-Fu, especially Sebastiaan Blom, for sticking up with me talking about my project way too much. Most importantly a BIG thank you to my girlfriend: Che Bich Quynh Nhu (also known as Lyly. And sorry sweetie that I cannot write the name entirely correct with the bars over the letters, but Word just does not support that) for sticking up with me programming most of the time and you having to come over to my place to do work here. Finally I wish I could send out a big thank you to websites such as www.opengl.org, www.flipcode.com, and www.gamedev.net, but as it turned out the contents of these websites were too basic to be used at all. However, a big thank you goes out to the people of the websites of www.boost.com, www.dinkumware.com and www.cplusplus.com for providing easy to use and (more importantly) accurate libraries and library descriptions. Ebor Jan Folkertsma
Legal Information
The names of all companies, organizations, product, domain names, e-mail addresses, logos, people, places and events mentioned in this document are either patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. I used information from the following websites: Warcraft III: Reign of Chaos is property of Blizzard Entertainment URL: http://www.blizzard.com/war3 Command & Conquer Generals is property of EA Games URL: http://www.eagames.com/official/cc/generals/us/home.jsp Emperor: Battle For Dune is property of EA Games URL: http://www.eagames.com/ Microsoft Visual Studio .NET is property of Microsoft URL: http://www.microsoft.com/ Doxygen GNU General Public License Copyright 1997-2004 by Dimitri van Heesch. URL: http://www.doxygen.org/ DevCPP is property of BloodShed Software URL: http://www.bloodshed.net Borland C++ Builder is property of Borland URL: http://www.borland.com MilkShape3D is property of ChumbaLum Soft URL: http://www.swissquake.ch/chumbalum-soft 3D Studio Max is property of Discreet URL: http://www.discreet.com GIMP is GNU URL: http://www.gimp.org Adobe Photoshop is property of Adobe URL: http://www.adobe.com The names of actual companies and products mentioned herein may be the trademarks of their respective owners.
General Information
All the 3D imagery presented in this document is created by using either premature demo material I created, end result material I created, or by using a (free) 3D modeling program. This means that the actual product sometimes looks different than the illustrations found in this document. I did not include graphics for the games/programs I mention in this document since that would require me to request authorization from the companies that own these products, which seems to be a long term process. For images related to those products I encourage you to visit their websites, which are listed in the Legal information chapter. The document contains a lot of images for illustration and most of these images are quite large as well. In my view I could not include less or smaller images without severe consequences regarding the reader to understand what I meant. Most of the images are also in color and a grey scale of the image will not show the right amount of detail to understand what I explain. Therefore it is recommended to view this document in either a color print or on-screen.
Contents
Acknowledgements ......................................................................................................... 1 Legal Information ........................................................................................................... 2 General Information ........................................................................................................ 3 Introduction .................................................................................................................... 6 Motivation ...................................................................................................................... 7 Code ............................................................................................................................... 8 Code Component Structure ............................................................................................. 9 Terrain Version 1 ........................................................................................................ 9 Terrain Version 2 ...................................................................................................... 10 Terrain Editor................................................................................................................ 11 Camera...................................................................................................................... 11 Brushes ..................................................................................................................... 11 Deforming the terrain mesh ....................................................................................... 12 Applying tile textures ................................................................................................ 12 Terrain .......................................................................................................................... 15 Internal structure (both versions) ............................................................................... 15 Point Map (both versions) ......................................................................................... 15 Tile Map (both versions) ........................................................................................... 16 Basic tiles (both versions).......................................................................................... 17 Complex tiles (not implemented)............................................................................... 17 Tile Structure (first version) .......................................................................................... 18 Tile Graphics............................................................................................................. 18 Tile Graphic Layering ............................................................................................... 19 Tile Graphic Collections............................................................................................ 20 Tile Graphic Sets....................................................................................................... 21 Tile Structure Problems (first version)........................................................................... 22 TileGraphic mismatching .......................................................................................... 22 TileGraphicCollection texture layout......................................................................... 23 Tile Structure (second version)...................................................................................... 24 TiledTexture.............................................................................................................. 24 TiledTexture layering ................................................................................................ 25 Tile Structure Problems (second version) ...................................................................... 27 Too many textures..................................................................................................... 27 Color Blending Artifacts ........................................................................................... 28 Triangle Blending Artifacts ....................................................................................... 29 Tile Blending Artifacts .............................................................................................. 30 Tile Storage............................................................................................................... 34 Rendering speed ........................................................................................................ 36 Visibility processing (both versions) ............................................................................. 38 2D (Normal) Quad-tree ............................................................................................. 38 3D Quad-tree............................................................................................................. 38 Lighting the terrain........................................................................................................ 39 Per-Vertex-Lighting (both versions) .......................................................................... 39 The implementation of Per-vertex-lighting ............................................................ 39
Shadow Mapping (version 2)..................................................................................... 40 The implementation of Shadow mapping............................................................... 41 Shadow Mapping problems (version 2) ......................................................................... 46 Ray-tracer problems .................................................................................................. 46 Generic Problems .......................................................................................................... 49 Brushes ..................................................................................................................... 49 Texture Loading ........................................................................................................ 49 Texture Compression ................................................................................................ 50 Features that did not make it.......................................................................................... 51 Meshes ...................................................................................................................... 51 Water ........................................................................................................................ 53 Conclusion / Evaluation ................................................................................................ 54 Code ......................................................................................................................... 54 Theory....................................................................................................................... 55 General ..................................................................................................................... 55 Results ...................................................................................................................... 56 Appendix A: System specification................................................................................ 57 Required.................................................................................................................... 57 Recommended........................................................................................................... 57 Appendix B: Development tools specification ............................................................... 58 Appendix C: References................................................................................................ 59 Books........................................................................................................................ 59 Websites.................................................................................................................... 59
Introduction
This document describes the free project I did for my 4th year of Computer Science at the University of Twente. The project as it yields 4 study points (equal to approximately 5.7 ECTS, equal to 160 hours), since 3D computer graphics tend take up a lot of time while developing. Doing the project for less study points will seriously diminish the amount of features, since building the core of the engine will take up the major part of the time. Adding features to it is then less work and can be done quite rapidly, which will result in a more complete engine as a whole. I based this study point amount on my previous experience with these kinds of projects. I created two versions of a 3D tile based terrain game engine. This means that instead of the more usual Computer Science related terrain engines, which are based on some Level Of Detail (LOD) algorithms, the terrain will consist out of rectangular tiles. The engine supports deformation of the terrain mesh and placement tile textures on the terrain. All this functionality should is supported by a terrain editor I developed, which is the main application. Though the engine will be a game engine, as stated above, the application I have developed is a terrain editor. I did not create an explicit game, since that would require many other aspects such as game-play, artificial intelligence, path finding, etc which is beyond my capabilities given the time frame.
Motivation
A 3D tile based terrain game might look strange from the viewpoint of a Computer Science course, since in general people doing a comparable assignment will opt for a terrain engine that uses Dynamic Mesh LOD. I have my own reasons however not to choose for such a type of engine which I will motivate now. First of all, in my opinion building a terrain engine that uses Dynamic Mesh LOD (DMLOD) is not as creative as I would like it to be. I will not say that creating a DMLOD terrain is not creative at all, since from the programmer s point of view there might be a lot that can be done in several ways, forcing the programmer to think of a creative solution. But in my eyes doing such a project is basically implementing an algorithm invented by other people (for example: ROAM or Bin-Triangle-Trees) and building your own extensions to it. Instead I would like to develop my own kind of terrain tile based engine. Note my own , because a lot of stuff has already been accomplished in 3D computer graphics and my ideas are probably done before in some form. The second reason for choosing this type of engine is that in the future I would like to work in the game industry. 3D tile based terrain engines are emerging in current games, as Real-Time Tactical/Strategy games switch from 2D to 3D with the increasing processing power of current day 3D graphic accelerator cards. For example: Warcraft III: Reign of Chaos, Command & Conquer Generals and Emperor: Battle for Dune are just a few recent games employing 3D tile based terrain engines with great success. Gaining experience with the proposed type of engine is actually a good candidate for putting on my portfolio list when I want to show my work to a game company in the future.
Code
I start with the code because it gives some insight about the software components which in effect makes it easier to understand the other sections. The code for the project consists out of two home-made engine components: Vision engine Tile Terrain engine
The Vision engine is a generic engine with functionality for many different kinds of applications (not necessarily games). It was built as a base layer on which the TileTerrain engine is built. Development of the Vision engine was, for the major part, simultaneously with the implementation of the TileTerrain engine. The Vision engine is subdivided in several components: Common DataTypes FileSystem Math RenderSystem
The Vision engine components are based on well know principles such as Data structures, Files, Rendering (OpenGL based), which I consider unrelated and mostly trivial. Therefore I will not deal with that code at all in this document, the documentation for this is on the CD in the form of Doxygen generated HTML manual (see the readme.txt file on the CD s root for more information.) For both versions of the TileTerrain code I will show a rough component wise interaction diagram to let u gain some insight in how everything works. Note a rough component wise interaction because I noticed that the full diagram is not very helpful at all, since the design involves a lot of class interactions. For more information regarding to the functionality I point you to the Doxygen generated HTML manual (see the readme.txt file on the CD s root for more information.) For future extensions to the code it is handy to have a code style so that adaptations are in the same style. I did not include the code style in this document as this document only focuses on the ideas I used, not the actual implementations. The code style can be found on the CD in the Documentation/Engine Usage directory with the filename Code Style.doc (see the readme.txt file on the CD s root for more information.)
Camera
TerrainEditor
Brush
Visible tiles
Terrain
QuadTree
ElevationMap
TileMap
Environment
ElevationGrid
Weather
NormalGrid
Sun
Terrain Version 2
Borland C++ Builder GUI Application
Camera
TerrainEditor
Brush
Terrain
Sun
PointMap
TileMap
Visibility
ColorGrid
TileGrid
QuadTree
ElevationGrid
Visible tiles
NormalGrid
Figure 2: Code Component overview of TileTerrain version 2.
10
Terrain Editor
This chapter deals with some quick information to understand the terrain editor, which is the best way to see what I mean when I am explaining graphical things.
Camera
Viewing the terrain is done using a camera. In the terrain editor the camera can zoom in / out, pan and rotate. The camera is controlled in the terrain editor application by both scrollbars and by mouse commands. Rolling the mouse-wheel up provides zoom in, rolling the mouse-wheel down provides zoom out, holding the mouse-wheel down while dragging the mouse to the left or right makes the camera rotate and holding the rightmouse button while dragging the mouse pans the camera.
Brushes
All operations on the terrain are based on a concept I call Brushes. The level editor uses brushes to deform the terrain mesh and to apply tile textures to the terrain. Brushes basically are regions following the contours or the terrain. A Brush consists out of a solid and a feathered part. The solid part performs 100% affection of the desired operation. The feathered part performs X% affection of the desired operation, where X is based on the distance to the solid part of the Brush. Figure 3 shows the visual appearance of two Brushes.
Figure 3: Brushes The left side is a square brush. The right side is a round brush. Each brush is made up out of rectangles that will follow the contours of the terrain. When applied the green portion of the brush will apply the total effect of the brush s function and the blue portion of the brush will apply only a percentage of the effect the brush s function. The amount of intensity of the blue part indicates this percentage. This gives in the terrain editor a quick overview of what the result of applying the brush would be. The green area of the brush is called the solid brush. The blue area of the brush is called the feathered brush because it acts like it is feathered.
Note that the solid and / or feathered size can and sometimes must be zero, but at least one of them has to be non-zero for any effect to occur.
11
Each brush function has a strength parameter that can be adjusted to alter its influence. For example, the Lowering the terrain function has a parameter that specifies the strength of the lowering. The Jittering the terrain function has a parameter that specifies the strength of the roughness of the terrain. The other brush functions have comparable parameters. See Figure 4 for an example of applying the Raising the terrain brush for the brushes specified in Figure 3.
Figure 4: Terrain mesh deformation The left side shows the application of the square brush from Figure 3. The right side shows the application of the round brush from Figure 3. The solid brush parts effect the terrain deformation completely. This results in a plateau style of terrain with nice interpolated sides that lead up to it.
12
The actual usage and result of the tile application is quite different between the two versions. In version 1 the usage is based on selecting a TileGraphicCollection and using either a random or selected TileGraphic. The result is the image below.
In this image one can see the individual grass leaves hanging over the dirt (brown) surface. The grass tiles them selves are decorated, in the image above one sees a stone, some yellow flowers and some dirt in the grass. Although this image uses about eight tiles in width and six in height it does not show any repetitive patterns. That is because the Random checkbox gives different graphics for the tiles. Terrain editor version 1, therefore the image above, is based on untiled alpha textures (It is not of importance that you do not understand yet what this means, just keep it in mind).
13
The second version of the editor offers far more possible textures for the tiles (I will explain why later). Selecting a texture consists out of selection a texture from the texture list or selecting a slot from the texture palette and then applying the brush to the terrain, the result is shown below.
In this image one cannot see details like grass leaves hanging over like in figure 5. Instead of the crisp edges in figure 5 this figure blends the tiles over into each other. Further more this image also uses about eight tiles in width and six in height, but it does show some repetitive patterns already. If you look at the dark spot near the bottom-right of the image, you will see that same pattern 2 times more; in the top-left en in the topright part of the image. Note that the image is a bit distorted because we look at the tiles form a 30 degree angle. Terrain editor version 2, therefore the image above, is based on tiled (non-alpha) textures (It is not of importance that you do not understand yet what this means, just keep it in mind).
14
Terrain
This chapter deals with how the terrain works. I made two versions I will describe both and then discuss the differences between them. Because both types of terrain share some commonalities I will describe these only once and I will indicate per feature to which version it belongs between parentheses per section, these can be (first), (second) or (both). The main purpose of this document is to describe the ideas associated with real time terrain rendering of game related terrains. This implies that a fair amount of explanation is concerned with optimizing certain data structures.
15
The second version of the terrain contained the following point map components: ElevationGrid ColorGrid NormalGrid
The ElevationGrid stores elevation information and deals with all operations that manipulate the elevation of the terrain. In the versions I made the elevation values range from 0.0f to 16.0f with a precision of 1.0f / 16.0f. This enables you to load / save the elevation data from / to a grey scale image (not supported by the terrain editor, supported in the code though). It is possible to change these values in future adaptations very easily. The ColorGrid (second version only) stores per vertex color information, calculated based on the sun s color and the NormalGrid, which is required in the rendering process (see Problems: Color Blending Artifacts for more information). The NormalGrid stores per vertex normals that are used to calculate per vertex color. In the first version the normals are simply fed to OpenGL which determines the per vertex color. In the second version the normals are only required for determining the values of the vertex colors in the ColorGrid component, the game version of the terrain does not have to include this information. The PointMap s (or ElevationMap for version 1) purpose is to encapsulate all elevation based operations in a single class. This is required because changing for example the ElevationGrid requires recalculation of the NormalGrid and ColorGrid. Further more all operations on the terrain mesh are in the form of a Command which only needs to store parts of the data to (un)execute. Thus calling a method of the PointMap class handles all calls to the Elevation / Color / Normal-Grid classes so that internal consistency is assured.
16
Figure 8 The same terrain as in Figure 7, but this time with some complex tiles on the terrain. The tiles in the front are rectangular tiles. The other tiles that make up the mountain are complex tiles that actually are complex meshes. This can be seen by the much higher density of the wire frame for the complex tiles.
The complex tiles are be predefined meshes that have some properties that will make them blend into the terrain naturally. For a very good example of this concept, see the Warcraft III: Reign of Chaos WorldBuilder level editor application.
17
Tile Graphics
TileGraphics are images that are placed on the tile rectangles of the terrain. Each TileGraphic has dimensions of 64x64 pixels and is stored as a 24 or 32 bit image which is called a TileGraphicCollection. Currently it is only important to understand that a TileGraphic is (a portion of) a texture. The application of TileGraphics to the terrain is based on the Brush that affects the terrain (note that only the solid part of the Brush is used when altering the Tile Graphics). See Figure 9 for a thorough visual explanation.
Figure 9 This figure shows a 4x4 tile grid snapshot. The red lines indicate the Tile rectangles. The blue points indicate the vertices, out of which the terrain mesh consists, that make up the Tiles. The green square in the centre represents the solid part of the Brush. The background contains grass. When the user applies the brush to the terrain (while in change Tile Graphic mode) all Tiles touched by the green square, these are the numbered 1,2,3,4, will be affected. How they are affected is explained in the Tile GraphicCollection section.
18
The layering method requires each TileGraphicSet (for more information see the: TileGraphicSet section) to have a layer order for each TileGraphicCollection. For example: Suppose that you have four types of TileGraphicCollections in your TileGraphicSet: Dirt, Grass, High Grass and Street. The layer order is: Dirt = 1, Grass = 2, High Grass = 3 and Street = 4. The base layer is Dirt (= 1). This means that placing any of the other three types of tile graphics on it will make them appear above the dirt. Street (= 4) Tile Graphics are the top-most graphics that will appear to be above everything else. In Figure 10 the Street TileGraphicCollection has a higher layer order so that it appears to be on top of the grass. Using this technique the worst case scenario is a tile with a base layer and on it four different TileGraphicCollection on each edge. This will result in a total of five layers to be rendered which is quite slow.
19
Figure 11: TileGraphic layering template This will be explained by help of figure 9. In figure 9 there are four tiles that are numbered: 1, 2, 3 and 4. In this figure you see squares filled with binary numbers. The binary numbers can be transformed to decimal numbers by using the following scheme. Top-Left square is 2^0, top-right is 2^1, bottom-left is 2^2 and bottom right is 2^3. The binary cells correspond to the four vertices used by each rectangular tile. The black color indicates that the currently selected TileGraphicCollection is used there. So if you would apply the brush as it is in figure 9 (with a different TileGraphicCollection than grass) the tile with number 1 would have the TileGraphic layering template with number 9 (the centre one in this image) and the tile with number 2 would have the TileGraphic layering template with number 1 (the top left one). From this it is note hard to see that there are 2^4 = 16 possible TileGraphic templates. We can skip the one with all zeros however since that would mean that only the base layer is showing. That leaves 14 corner graphics and at least 1 centre graphic.
The fourteen corner graphics and at least one centre graphic amount to fifteen images in total. The fourteen corner TileGraphics are required just in case you build a level that
20
needs them all, even the more exceptional ones. The corner TileGraphics offer just a single image for each possible combination. Offering more images for each combination might sound tempting, but reality shows that it requires too much memory from the video card. Even though this single image restraint might seem very harsh, practice has shown that it is possible to create very nice looking terrains with only single images for the corner Tile Graphics (for example see some of the maps in Warcraft III: Reign of Chaos). Key to the success is not allowing the terrain to have the same border repeating over and over for more than three or four times. I will not go further into this, because it has nothing to do with the engine, but more with the creativity of the terrain designer. Only having a single centre TileGraphic that will be used on every tile that requires complete coverage of the TileGraphic makes the terrain look boring again. Therefore the engine supports multiple centre TileGraphics. Each of these graphics should be slightly different. An example of this is shown in Figure 12 Figure 12, which shows two types of centre Tile Graphics for the Street Tile Graphic Collection. For Two centre Tile Graphics to make the terrain look less monotonous. The an example of less monotonous terrain see Figure red lines indicate the Tile Graphic 10 in the section: Tile Graphic Layering, which uses rectangles. the centre Tile Graphics presented in Figure 12. This technique avoids the visually disturbing effects of seeing repetition in the TileGraphics, when used appropriately. Yet again this is actually the terrain designer s task so I will not go into this subject.
21
Figure 13: TileGraphic mismatching Visually disturbing lines between the TileGraphics because the TileGraphicCollection texture might have some color differences for each TileGraphic.
22
layout
for
13
10
12
The generic composition for TileGraphicCollection textures. The texture is 256x256 pixels (32bpp). Note that this texture has only two centre pieces. For texture with more centre pieces the texture size will be 256x512 and the remaining centre pieces will be added on the bottom side of the texture. To use this new layout I built a translation table that fetches the right TileGraphic from the TileGraphicCollection.
11
14
15
15+
23
TiledTexture
1 2 3 4 1 2 3 4 As I stated before the second version of the TileTerrain works with tiled textures without alpha channel. In version 1 the a TileGraphics are the rectangles in the b texture (the TileGraphicCollection) which are applied to the tiles. In version c 2 we no longer deal with TileGraphics / TileGraphicCollections / TileGraphic- d Sets. There is a TiledTexture which a gives the texture coordinates for the tiles based on the tile s position relative to b the world s starting point (x = 0 and z = 0). When a texture is tiling over four c tiles for example (as in figure 16) the texture is repeated every four tiles. This d requires the textures to be tileable or in Figure 16: TiledTexture other words the texture should be able to wrap around. Fortunately tileable The top 1, 2, 3, 4 indicate the repetitive pattern over textures are the most common format the textures x axis, the a, b, c, d indicate the for textures in the 3D graphics scene. repetitive pattern over the textures y axis. The red The process of creating tileable texture lines indicate the tiles. of photographs is also a quite easy process and therefore using this format does not raise the obstacle of texture creation that version 1 does.
The thing that should be determined for this method is over how many tiles a texture tiles. There are two possible approaches for this: (1) Using the texture s size and dividing it by the number of pixels per tile on both the texture s x and y axis. (2) Deriving the tileability by loading it from an external resource. Because textures must have dimensions that are powers of two, option 1 does not provide much freedom. In my case I used 64 texels per tile which leaves me to use the following texture sizes: 64 = 1 tile, 128 = 2 tiles, 256 = 4 tiles, 512 = 8 tiles and 1024 = 16 tiles. It is clear from this that this method does not exhibit much flexibility with regard to the number of tiles a texture occupies. Therefore I choose for option 2 and I use the texture s filename to determine the virtual texture dimensions. I prefix the texture s filename with the number of pixels I want it to be in width followed by a # character, for example 256#Grass.bmp. This 24
method allows me to actually store more detail on some tiles, for example if I have a texture 64#Detail.bmp which actually uses 256x256 pixels these are going to be mapped on a single tile nevertheless providing much detail for the tiles textured with it. The big disadvantage of this method is that the textures will always repeat the same relative to the world s origin. This does not pose a problem however, since the viewer hardly ever knows it s position relative to the world origin and blending with other types of textures (that are almost the same) makes it virtually impossible to see this. For the Terrain editor I needed to group the textures together so that finding which one to use was easy. To that goal I also used the TiledTexture s filename, the format is <size>#<category><number><alpha>, where <size> is the required size of the textures (not the actual size) used to determine over how many tiles it repeats. <category> is the category as found in the terrain editor. <number> is a two digit number indicating the number of the texture. <alpha> is a single alphabetical character to indicate the sub group, these subgroups are used in the terrain editor so that it is easy to see which TiledTextures blend well into eachother.
TiledTexture layering
The layering of the TiledTexture happens in almost the same way as described in the TileGraphic layering section. There are some differences however which I will discuss now. First of all TiledTextures do not have a specific layer order. In the first version the layer order was used to determine which type of graphics lay on top of another. Version 2 uses blending between two textures as can be seen in figure 17. This way of interpolation is the same in all layering orders (for example applying a dirt texture on top of a grass texture yields the same result as applying a grass texture on top of a dirt texture) making the layer order, hence the TileGraphicSet, redundant. Note that this is the same technique as used by Command & Conquer Generals .
Figure 17: Blending TiledTextures This figure shows a blending between two types of TiledTexutres: Dirt (on the left) and Grass (on the right)
The blending between the tiles uses the same system as the TileGraphic templates described in figure 11, but this time the binary values for the edges are not used to calculate the right TileGraphic. The binary values now stand for the alpha value of the vertex on that corner. By using Gouraud shading for interpolating the color, hence alpha, values between the vertices this produces a 0%-100% blend between vertices with alpha
25
= 0.0f and alpha = 1.0f, this can be seen in figure 18. Unfortunately this gave some nasty side effects which are discussed in the Triangle Blending Artifacts section, in the Problems chapter. 1 0 0 = 0
Figure 18: Blending algorithm example In this example it is assumed that the bottom layer (what is already there) is dirt and that we have currently selected a grass TiledTexture that we want to apply to some tiles. The top part of the figure is the same as the TileGraphic template described in figure 11. The bottom-left of the figure shows the alpha values for the vertices of the tile corresponding to the values determined in the template (above). The bottom-right of the image shows the result of the operation. (The theoretical result, the real result might differ, for more information refer to the Triangle Blending Artifacts section.)
V0, a = 1
V3, a = 0
V1, a = 0
V2, a = 0
26
27
editor I will not go deeper into this subject. It would be nice to make some function that calculates the currently occupied memory for the textures on a terrain. (Note that such functionality is not directly present in the current version of the code, but with a slight modification to that code it can be added easily because the Texture class already has a CalculateUsedMemory() method.)
Figure 20: Color blending artifacts The left side show what is required, the right side shows what blending artifacts caused. Notice that the luminosity of the right image is too bright for the blended tiles.
In figure 20 the left side of the figure displays the image as it should have been. The right side of the figure displays the image that resulted from using multi-pass rendering using the glBlend(...) function. The artifacts occur because the glBlend(...) function takes the current pixel color in the render buffer into consideration and blends this with the color of the current texture. Normally we render the tiles with code like:
glTexCoord2f(...); glNormal3f(...); glColor4f(...); glVertex3f(...);
But because the alpha blended tiles need the alpha component for each vertex, we need code like:
glTexCoord2f(...); glColor4f(..., vertex_alpha); glVertex3f(...);
28
The call to the glColor4f(...) function disables the OpenGL lighting calculations so that a call to the glNormal3fv(...) function does not serve any purpose. This problem was solved by calculating the color components for the vertices manually (the dot product of the light s direction with the vertex s normal yields the cosines value for the angle between the light and the normal, which in turn defines the color intensity for the vertex.) Another benefit of calculating the vertex colors by hand is that at rendering time OpenGL does not need to be concerned of the lighting calculations for the terrain. A minor disadvantage resulting from this method is that we now need to recalculate the colors our self when the material of the terrain changed.
Figure 21: Quad to triangle This figure shows how OpenGL triangulates the quads given the vertices v0, v1, v2 and v3. Note that the order in which the vertices are passed is v0, v1, v2 and v3. The left part of the figure shows two triangles in a / layout and the right part of the figure shows two triangles in a \ layout.
V1
V2
V0
V1
Because the Gouraud shading interpolates the vertices on a per triangle basis this yields some strange blending artifacts, also see figure 22.
Figure 22: Gouraud shading This figure shows how Gouraud shading interpolates on a per triangle basis. The left part of the figure shows two triangles in a / layout and the right part of the figure shows two triangles in a \ layout. Note that the left part of the figure looks more correct, but thatis just because these colors tend to blend in that fashion, it can look just as wrong as the right side does now (given the wrong colors).
This blending in combination with the TileGraphic templates (figure 11) made the blending look squared for some tiles, also see figure 23. To deal with this problem I
29
added a property to the tiles indicating in which order the vertices should be given to OpenGL. There is a slight side effect by doing that however. Since the vertices differ in elevation rendering a /triangle layout uses different elevation values on the edges of the triangles than a \triangle layout. The only way to overcome this is to render all layers of the tile with the same triangle layout, which in turn messes up the blending. Nevertheless this method proved useful because I set the tile s triangle layout based on the TileGraphic layout of the top most layer, since that is the layer you see most of. This only leaves us with visually disturbing cases sparsely and a placing the textures cleverly eliminates them all. This is beyond the scope of this document as it is the terrain designer s task.
Figure 23: Triangle Blending Artifacts This figure shows the triangle blending artifacts that occur because of Gouraud shading in combination with the TileGraphic template structure (figure 11). To show the differences clearly the wrong blend uses a red texture and the right blend uses a green texture, the effect is the same for normal textures such as dirt, grass etc. The top part of the figure uses a / triangle layout for all the tiles. The bottom part of the figure uses a / triangle layout for the top-left and bottom-right corners (since these were correct already). It uses a \ triangle layout for the bottom-left and top-right corners which blend correctly now.
30
detailed explanation: figure 24 and figure 25. To achieve the correct blend and still keep each vertex alpha unique, as required by the algorithm, I do the following: Traverse all layers from top to bottom and every vertex with alpha 1 will also have alpha 1 for all vertices in the layers beneath. This invalidates the uniqueness property and I run the inverse algorithm before running the algorithm that calculates the correct vertex alpha.
31
Base layer
1 1 0 2 3 0
1 1 2 0 0 0
Blending =
Layer 1
Blending =
Layer 2
Blending =
Figure 24: Tile Blending Artifact (wrong blend) The top 3 strips indicate the layers we currently have: base layer, layer 1 and layer 2. The base layer is filled with grass, layer 1 has a / shape of sand and layer 2 had the top-left part of the tile of dirt. The corresponding TileGraphic templates are indicated by the bit fields. The middle-left part of the figure indicates what happens when the base layer is blended with the layer 1 layer. The sand of layer 1 is blended with the grass correctly. The middle-right part of the figure indicates what happens if the result of the previous blend is blended with the layer 2 layer. The dirt of layer 2 is blended with the result of the previous blend operation which blends which is the grass since the previous layer did not fully overlay the grass layer on that corner. This is considered a wrong blend because you get strips of sand with edges of dirt. The lower part of the image demonstrates the full effect of this wrong blending, which is visually disturbing.
32
Base layer
1 1 2 2 3 0
1 1 2 0 0 0
Blending =
Layer 1
Blending =
Layer 2
Blending =
Figure 25: Tile Blending Artifact (correct blend) The top 3 strips are the same as described in figure 24. The middle-left part of the figure indicates what happens when the base layer is blended with the layer 1 layer. The sand of layer 1 is blended with the grass correctly. The middle-right part of the figure indicates what happens if the result of the previous blend is blended with the layer 2 layer. The dirt of layer 2 is blended with the result of the previous blend operation which is correct now. The lower part of the image demonstrates the full effect of this correct blending. It is much harder to see the transition edges using the correct blending method.
33
Tile Storage
My first implementation of the Blended Tiles concept (version 2) used a tile structure of 168 bytes:
class TiledTexture; struct TileTextureRectangle { TiledTexture *texture; float l, t, r, b; }; struct TileAlphaRectangle { float a0, a1, a2, a3; }; struct Tile { Tile() : texture_count(0) { } unsigned int TileTextureRectangle TileAlphaRectangle }; texture_count; texture_rectangle[5]; alpha_rectangle[4];
Each tile has a maximum of 5 layers, 5 texture layers of which the upper 4 are blended tiles. On it self 168 bytes does not seem that bad, but consider a terrain of 256x256 tiles, that are 65536 tiles each 168 bytes totaling almost 11 MB of tile data. Or even worse, consider a 1024x1024 terrain totaling 168 MB of tile data. This is an unacceptable amount of memory for something that simple, so optimization was required. It was soon clear that since the alpha for each vertex could only be 0 or 1 this could be optimized using bit fields. Another thing that sprung in mind is that pointers each require 32 bits (4 bytes), but I was planning on using at most 256 different textures for the terrain. To keep future extensions in mind I made it possible to use 1024 different textures for the terrain by using a bit field of 10 bits per texture. Further, since the number of layers is at most 5 a 3 bit bit field suffices for the layer count. The last and most saving optimization was that of the texture coordinates. In the old code I saved the texture coordinates in the tile structure, but I could just store the texture alone, since the TiledTexture coordinates are directly derived from this tile (using the relative offset in the world). After this reordering the Tile structure has the following shape:
34
struct Tile { ... ... ... // 32 bits block unsigned int unsigned int unsigned int unsigned int unsigned int
// 32 bits block unsigned int unsigned int unsigned int unsigned int unsigned unsigned unsigned unsigned unsigned unsigned unsigned unsigned int int int int int int int int
tex3 tex4 a1_v2 a1_v3 a2_v0 a2_v1 a2_v2 a2_v3 a3_v0 a3_v1 a3_v2 a3_v3 a4_v0 a4_v1
: 10; : 10; : 1; : 1; : : : : : : : : 1; 1; 1; 1; 1; 1; 1; 1;
: 1; : 1;
// 32 bits block unsigned int unsigned int unsigned int unsigned int };
(For a full listing of this structure see the Tile.hpp file) This structure takes only 12 bytes instead of the 168 bytes previously. The main disadvantage with the new structure is that the algorithm for calculating which layer uses which texture, which layers should be removed, etc, cannot access the structure components easily. Therefore I made some access functions that make this a little bit less hard. Never the less the layer calculation algorithm code has really dramatically increased in complexity.
35
Rendering speed
Because I use efficient storage of the tiles (to conserve memory) I had to extract per tile information for each rendered tile. This resulted in 54 fps, which is unacceptable. Batching vertex data would be beneficial. Recalculating the visible mesh data every time the camera moved (in cluster based fashion) improved the frame rate by a factor of almost 10, yielding far above the 500 fps. The Recalculation of the visible mesh data is a task that has to be performed fast and because of that you cannot free and reallocate all the memory every time you do it. The most obvious reason for this is that it is very hard to predict the amount of memory needed for each frame and the calculation is quite costly as well. To resolve this problem I created a template GrowArray class of which the main purpose is to store data in array form and be able to clear the array without actually releasig the memory. The GrowArray class has an append method for appending data to the array (internally new memory is allocated for the current amount + N, where N is the specified grow size for the class, components larger than it was. Data is copied and old memory released). This whole process is transparent to the programmer. The benefit of this approach is that you will have approximately the memory required for storing the components (your are not reserving way to much memory). The bad side of the algorithm is that if there is a big leap in the amount of vertex components to store, the GrowArray will perform multiple reallocations and copies of memory. Fortunately this seldom happens and the accompanied performance loss is for a single frame only. Using indices to avoid even more duplicate vertex data is not an option since using indices requires the index to point into every array (vertex, color, texture coordinates, etc). In my case only the texture coordinates changed for the alpha blended tiles, but it still required me to duplicate the blended color values at least and the position data because the layers are do not have same set of tiles. To further improve the rendering speed, I batched consecutive tiles that share the same texture. This saves a lot of texture state changes in the OpenGL implementation which improves performance. Note that I only batch consecutive tiles not all the tiles in the cluster. Although performance could be increased even more if I batch all tiles in the cluster by texture handle, this would put more strain on the recalculation of the visible mesh data. Because this is a world editor the mesh data is changing rapidly and the extra time spent on this optimized batching will result in frame rate drops below the limit of what is acceptable. This does not mean that this method is impossible at all. One could consider such an optimized batch organization for the final terrain, in other words the terrains that ship with the game. This is possible because in that situation the batched data will just be pre-calculated and stored in the terrain file on the disk. This is not part of this assignment, because I did not make a game, and therefore I will not go deeper into this matter.
36
One last optimization I implemented is the use of texture compression for the video cards that support it. Texture compression reduces the size of the textures in the video memory and therefore less texture data transfers from system memory to video card memory have to be made. This optimization also causes the image quality to be decreased a bit, but since it is not really visible I left it that way. (except for the shadow maps, see the Texture Compression section in the Problems chapter for more information on that subject). Note that there might be a better way of rendering using some of the various extensions that are available, but this was not the goal of my rendering algorithm at the time of creation. Multi texturing for example will improve the rendering speed even more, but at this time that has not been implemented yet. The use of triangle strips could also improve the rendering speed due to less data sent to the video card.
37
2D (Normal) Quad-tree
A Quad-tree is a 2D recursive subdivision method that divides a rectangle into 4 sub rectangles when a division is required. In the TileTerrain engine the sub division is repeated until a defined cluster size is met. The cluster is in the final version of the terrain is 4, which means that each leaf of the quad-tree has 4x4 = 16 tiles. This cluster size was determined after extensive performance testing. The cluster size speeds up the rendering because rendering 16 tiles is quicker than traversing the nodes of the quad tree a few steps further. (Note that this might differ per video card.)
Figure 26: 3 Level Quad-tree subdivision. The left part of the figure is the first level of sub division (the root). The middle part of the figure is the second level of sub division; the green lines indicate the children s node limits based on the root node. The right part of the figure is the third level of sub division; the blue lines indicate the children s node limits based on the second level.
3D Quad-tree
I use a blend of the normal Quad-tree in the following sense. I use a Quad-tree for virtually dividing the terrain. Note virtually because I do not actually use the terrain s vertex data for this process. The 3D part comes from the fact that I give each Quad-tree node a minimum and maximum elevation value (which are equal to the global minimum and maximum of the terrain) for every visibility / ray-intersecting operations. Testing the Quad-tree only occurs when the camera moves and when the mouse moves over the terrain. This gives high frame rates when the screen is not scrolled, but the frame rate drops a bit when moving the camera.
38
A nice feature that can be implemented easily using per vertex lighting is that you can change the color of the light, or the direction of it, that shines upon the vertices (not implemented in the terrain editor, but it is adjustable in the code). When this technique is used appropriately the effect of different light intensities that are encountered on a day (for example: dusk, noon, evening, dawn, etc.) can be simulated. Per vertex lighting had some problems associated with the blending of the tiles, for more information see the Tile Structure Problems (second version) chapter in the Color Blending Artifacts section.
39
The directional lighting formula I used for lighting the vertices uses both ambient and diffuse color. Ambient light is the base intensity that the vertex always receives. For the diffuse component of the directional light only the angle of the incoming light is needed for each vertex. Since the intensity of the directional light is equivalent to the cosine of the angle between the incoming light and the normal for the surface at the incoming light point. The angle between the two vectors is easily determined by the formulae: cos angle = (V dot W) / (|V| |W|). The |V||W| calculation is relatively slow, because it requires two square roots and seven multiplications. Fortunately the |V||W| calculation can be ignored in the case that both the V and W vector are normalized vectors, which I enforced. The resulting angle then becomes the following formulae: cos angle = V dot W. The total light calculation I used therefore is: Vertex-Color = Ambient-Color Intensity = DotProduct(LightDirection, SurfaceNormal) Local-Diffuse-Color = Diffuse-Color * Intensity Vertex-Color += Local-Diffuse-Color After this calculation the Vertex-Color is clamped to fall in the [0.0f, 1.0f] range, to assure normal visual results (specifying colors larger than 1.0f create a scene wash out).
40
Interpolated elevation grid generation The first step in the shadow-mapping algorithm is to calculate an interpolated elevation grid, where the resolution of that grid is N times larger than that of the original elevation grid. The N is determined by the number of lumels per tile. There are two possible ways of interpolating the original elevation grid: Physical interpolation (based on the tile s triangles) Logical interpolation
The physical interpolation is based on the layout of the triangles in the tile. The most important property of this approach is that it tends to follow the terrain closely. Although that property is desired to move units around on the terrain, it is not desired for creating the shadow image because that would clearly show the triangular shape of the terrain mesh in the shadow image, which is visually disturbing. The logical interpolation is based on a bi-linear interpolation between the vertices that affect the elevation. This interpolation is in essence the same as the bi-linear color interpolation used in rendering known as Gouraud shading. This does not follow the terrain closely but interpolates the elevation making the transitions smoother, therefore this method is chosen. Note that logical interpolation can also be used to let the camera follow the terrain, since it provides a smooth interpolation into all directions to the neighboring tiles (not implemented).
41
Shadow image generation The second step in the shadow mapping algorithm is the generation of the shadow image itself.
(0,0
(1,0)
(2,0)
For every elevation value in the interpolated elevation grid the algorithm starts moving in (0,1) (1,1) (2,1) the minus x direction and in the positive y direction (as shown in figure 29). Then the algorithm checks whether the elevation value of that cell is higher than the original elevation plus the elevation that the light s Figure 29: Shadow Image generation ray ray adds over that distance. In the mean while the ray s distance is maintained to The thick black lines represent the tile produce shadows that are less opaque when borders and the small squares represent the they are further away from the intersection interpolated elevation grid values. The red line represents the direction of the ray. Each point. To give the algorithm a bit more interpolated elevation grid that is intersected speed the rest of the ray is ignore when the by the ray is tested to determine whether the currently tested elevation is greater than the light can reach the ray s origin. global maximum (in which case no more intersections can occur) or when the x and / or y coordinates are smaller / greater than their minimum / maximum. To make the results of the algorithm less harsh we test for at least N intersections before making it a shadow lumel. After experimenting with the shadowing algorithm I found out that it visually pleasing to make the shadows that are further away from their creation point less opaque, see figure 30.
Figure 30: Less opaque shadows based on intersection distance On the left side you see the terrain lit with the raw, without post processing, shadow image. The hard shadow edges are very apparent in the centre of the image. The image on the right is the shadow image itself. (Note that the image on the right might look the same as the terrain, but the terrain has actual elevation. The shadow image is applied to the terrain on a per polygon basis. So the image is not rendered over the terrain as a flat quad).
42
Shadow image post processing After the shadow image has been generated, there is some post image processing to enhance the visual quality of the shadow image. In my situation the post image processing consists out of applying a cubic blurring / smoothing filter. The filter determines for every pixel the pixel s color based on the color of it self and all surrounding pixels (The number of pixels added is 4 for the corners, 6 for the edges and 9 for the centre pixels). A single blur / smooth pass is not good enough for eliminating the hard edges, so multiple passed were done. Too many passes wash out the shadow image too much and take too much time. Therefore I experimented with different settings and found out that a value of 4 is sufficient, see figure 31 for more information. (Note that this amount is configurable at compile time by setting a #define directive in the Defines.hpp file).
Figure 31: Shadow image blur / smooth passes From left to right, the shadow image with respectively: 1, 4 and 8 times the blur / smooth filter applied.
Shadow image to shadow map conversion. The final step in the shadow map creation is to convert the shadow image to (multiple) shadow maps. The shadow image can be any dimension, but the rendering hardware requires a texture with dimensions that are powers of 2 and limited in size. These are not the only factors to take into consideration. Another very important thing is the amount of memory the shadow map uses. For example, a terrain of 256x256 with 4 lumels per tile requires a 1024x1024 shadow image. On most recent hardware, this texture size is supported so there actually is not a problem. The thing to keep into consideration however is that most of the time you only see a small portion of the landscape and hence see the shadow images for those portions only. Therefore keeping a texture with large dimension in the video card s texture memory is a waste of that memory (Note that a shadow image takes 3 color components and therefore a single 1024x1024 texture uses 3 MB of texture memory). Therefore I split the shadow images into smaller textures. Unfortunately I could not make the shadow map textures fitting to the cluster size, since the cluster size is 4 tiles which is just too small because together with the 4 lumels per tile, this would create textures of 16x16 texels. On itself this is possible, but not very efficient because every texture object stores some internal information. In the example
43
that is used in this section, the terrain is 32x32 tiles with 4x4 lumels per tile and a shadow texture size of 64x64. The shadow image that is created is 128x128 (32x4 in both dimensions) and therefore there will be 4 shadow textures generated, see figure 32.
Figure 32: Splitting the shadow image into four shadow maps. The left part of the figure shows the shadow image as a whole. The right part of the image shows how the image will be subdivided to generate the shadow map textures.
A problem with this approach is that the edges of the shadow textures are repeated. This results in large dark lines at the edges of the shadow textures. According to the OpenGL specification this is simply solved by adding a border to the texture image. Experimentation with this feature unfortunately pointed out that the border functionality is not supported by much recent hardware. (Most of the consumer level 3D cards has no hardware support for texture borders, Quoted from www.opengl.org, topic posted by Serge K.) Basically there are two solutions to this problem that tend to work: 1) manipulating the texture coordinates Figure 33: Black texture border artifacts manually so that the texture does not blend The black lines from the top to bottom and from with the border color, and 2) using the the left to right in the centre of the image are the GL_EXT_texture_edge_clamp extension result of an OpenGL texture blending artifact. to fix it. The first method is tedious because you have to adjust the code. The second method works perfectly, but only OpenGL specification 1.2.1 and above support it. According to the required system specifications (see the appendix A: System Specifications) the program requires at least a GeForce-II MX or better, these cards support this extension, and therefore this method is used.
44
The final result After all these steps the shadow map is generated quickly and precisely with less opaque shadows that are further removed from the geometry that created the shadow, see figure 34 for the result.
45
(0,0)
(1,0)
(2,0)
(0,1)
(1,1)
(2,1)
The thick black lines represent the tile borders. The green colored grid is the tile lumel grid for the tile (0, 0). The small cells represent the lumel coordinates generated on the fly (note that in contrary to figure 29 the small cells are not values in the interpolated elevation grid, since the ray-tracer method does not require that).
For each of these lumels a 3D location on the terrain was calculated and then a ray from this point in the direction of where the light was coming was generated (The length of the ray large enough to span the terrain.) The brute force approach was to test every ray against every triangle of the terrain. This might not seem a very bad idea in the first place, since the amount of triangles for an average terrain is not that bad. Suppose an average terrain is 128x128 tiles, then each tile contains 2 triangles, which gives a total of 32768 triangles. The problem resides in the fact that for each of the lumels in the tile you need to test for an intersection between any of these polygons. If we go to the extreme and take an 256x256 terrain with an 8x8 lumels per tile grid this would require a lot of calculations: 256x256 = 65536 tiles = 131072 triangles with totally 256x256x8x8 = 4194304 lumels to calculate. This implies 4194304x131072 ~ 5.5*1011 calculations. For ease of calculations let us assume that each test costs 1*10-6 (one millionth of a) second then the total calculation of the shadow map would cost approximately 6 days. Clearly the brute force method did not suffice. I optimized the algorithm by first testing the ray against the 3D Quad-tree structure. The 3D Quad-tree has an axis aligned subdivision and each node can therefore be encapsulated by an axis aligned bounding box. The ray intersection for an axis aligned bounding box is quite fast and because the Quad-tree subdivides the terrain recursively we are quickly discarding polygons that cannot possibly intersect the ray. To illustrate this idea see figure 36 on the following page.
46
Figure 36: Ray AABB intersections A) first level B) second level Assumed is a Quad-tree with 3 levels, which implies a 16x16 grid (each cluster is 4x4 tiles). AABB = Axis Aligned Bounding Box A) The ray is tested against the AABB of the first level of the Quad-tree, it intersects so we proceed to the next level. B) The second level of the Quad-tree is tested against the ray, only the top-left, top-right and bottom-left Quad-tree nodes intersect with the ray, so these are expanded. C) The third and final level of the tree, the same procedure is repeated. D) The actual intersections found are colored yellow. This means we do not need to test the ray intersections for all polygons in the white areas.
C) third level
D) actual intersections
Another optimization I used was to check the maximum elevation per tile against the minimum elevation for the currently checked tile. It is easy to see that if the maximum value of a tile is less than the minimum value of the current tile it is impossible for the light ray (which never goes underground) to hit it. These optimizations were able to generate a shadow map for a 32x32 tiles terrain using an 8x8 lumel grid per tile in approximately 8 or 9 seconds. This speed increase is quite big, but it is not enough for the future. For example: generating the shadow map for a 256x256 tiles terrain using a 8x8 lumel grid would take approximately 10 minutes at this speed. This is unacceptable, especially when one realizes that I was planning on implementing dynamic terrain shadow maps (based on the position of the sun). For this moment we will leave this speed problem aside however and continue with the next problem. The second problem is that the generation of the shadow map sometimes introduced holes in the shadow map. Also the cutoff of the shadows (on the lower part) seemed unrealistic, see figure 37.
47
Figure 37: Shadow map holes It is clear that the shadow map has some holes in it. Also the cutoff of the shadow on the hill tops seems unrealistic. Note that in the image the shadows are blue, this was not the final result, but it is displayed in this way to have more contrast in the image.
Although I am not completely sure about the origins of these holes I have an explanation which is most likely the cause: point in convex polygon testing. The method I used for testing whether a ray is intersecting a polygon consists out of 2 parts. The first part determines whether a ray is intersecting with a plane and yields the intersection point (which cannot go wrong). The second part determines whether the intersection point calculated in the first part is inside the convex polygon. The method I used to determine this is to calculate the angles between the intersection point and all the vertices. If the sum of these angles equals 360 degrees the point is inside the polygon, otherwise it is not. A problem with this method is however that points lying on the edge of the polygon or on a vertex of the polygon might sometimes be seen as outside the polygon. There is another approach to determine whether the ray is inside the polygon or not: for each edge of the polygon, build a plane (using the normal vector of the polygon) and test the point against this plane. This method will never yield outside results while it is actually inside (though the opposite might occur depending on the virtual thickness of the plane, this effect is less disturbing however.) A serious problem with this method is however that this type of checking takes a lot more time than the summed-angle approach. And since the speed of the shadow generation algorithm was already unacceptable I did not try this approach further.
48
Generic Problems
This chapter lists the generic problems that I have not discussed yet and they are not specifically related to either version of the TileTerrain. Note that only the two most disturbing problems are treated since there is no space left.
Brushes
The first implementation of the brushes had an array of apply-factors for each brush object. This array was 32x32 in size and its elements were floats (each 4 bytes large). This implied that each brush object was around 32x32x4 = 4096 bytes large. While updating the terrain editor so that it supports macro-commands this gave some memory problems. The macro-commands allow multiple applications of a brush object in a single command. (A mouse-apply-start starts the command, the movement of the mouse keeps applying the brush and finally a mouse-apply-end ends the command.) Because each subcommand of a macro-command requires a brush object this consumes memory fast. Therefore a memory optimization of the brush class was required. The optimized version of the brush does not have an array that holds the apply-factors. Instead the brush s values are calculated on the fly. This severely reduced the brush size solving the memory problem.
Texture Loading
Normal texture loading does not take much time, unless you create mipmaps for the texture. The problem with mipmapping is that you have to scale the image down till a resolution of 1x1 is reached (note that this is not mandatory using some extensions, but I will ignore that for the sake of simplicity). More loading time is added because I implemented some features that enable flexibility of rendering hardware. These features are all calculated while loading the texture and include things like: changing the resolution of the texture, changing the color depth of the texture, forcing texture wrapping modes, etc. Changing the resolution of the texture on the fly is especially expensive. Both mipmapping and changing the resolution of the texture are slow because they scale the images using the (quite generic) gluScaleImage function. So how slow is slow? For example: loading a 24-bit texture of 512x512 costs more than 600 milliseconds on my machine. It is not hard to see that loading a full terrain with about 128 textures takes way too much time in this fashion. A solution for this problem is to write a more specific, hence faster image scale function. This can reduce the texture loading quite a bit, but another, better solution for this problem lies in writing a loader for the .DDS mipmapped texture file format. (http://developer.nvidia.com/object/nv_texture_tools.html) This format stores the lower resolution images in a file so that the scaling operation is performed only in preprocessing time. Unfortunately I did not have time to implement this in my texture class. 49
Texture Compression
To save texture memory I have used texture compression on video cards that support it. However compression unfortunately causes some artifacts on the shadow-map however that is really disturbing, see the image on the left (Note that the problem is exaggerated because I use almost totally white textures).
These artifacts arise due to the nature of texture compression for which every 4x4 block of pixels is encoded using 8 bytes (no alpha). (GL_COMPRESSED_RGB _S3TC_DXT1_EXT). In combination with the already low resolution (4 lumes per tile) of the shadow map this generates a lot of interference. Because of this I disabled the texture compression for the lightmaps (only) in the final terrain editor version, the result can be seen in the image on the right.
Note that texture compression still is enabled for the textures them selves as this is hardly noticeable.
50
Figure 40: Some meshes I made (Part 1) Top Left: Old Ford out of the year 1946 (238 polygons), simply created by applying a colorize filter on parts of the original texture (see figure 41) and applying that texture to the same mesh. This technique can be used to speed up the development of meshes without breaking the uniformity of the meshes in general. One could slightly modify the original mesh as well to prevent the feeling that the meshes are all the same. Top Right: M1A2 Abrahams (untextured, 389 polygons) tank
Left: Water Tower (241 polygons) first model that uses a texture with an alpha channel to block out parts of the mesh (mainly the X construction that are attached to the pillars and the ladder)
51
Figure 41: Some meshes I made (Part 2) Left: Top Right: Bottom Right: Lighthouse, this lighthouse has a rotating top with a light beam attached to it that rotates as well. (288 polygons) Old Ford out of the year 1946 (238 polygons) Wooden Cabin (110 polygons)
52
Water
I implemented some basic animated water. The animation is a mesh based animation for which the data is pre calculated according to some configurable parameters: wave amplitude (per tile), mesh density (overall) and speed of water animation. Unfortunately my water implementation was not visually attractive. Although the mesh is deformed, this is hardly visible because you never see the water mesh from the side (which is the only position that would allow you to see the actual mesh deformation). The mesh also tends to take up quite a lot of rendering time (depending on the density of the water mesh). Another disturbing feature is that the tiling character of the water is clearly visible. The final problem with my water implementation is that the edges where the water touches the solid terrain are really hard (also see the image below). There is a really nice thing about this water however. The water gets darker blue as the water gets deeper, which is comparable to real life water. A better implementation would have been to apply an animated texture onto a planar surface. To remove the hard edges I would have to set alpha for the vertices that lay inside the terrain to zero make the transition invisible. Note: If you want to see the water in action in real-time, check the Applications/Water directory on the CDROM.
53
Conclusion / Evaluation
This chapter deals with the conclusion and evaluation of my work as I see it. It is divided into four sections: the code, theory, overall and result sections.
Code
The first thing I noticed, but I always have that with projects, is that I should better decide before coding what I want and how it works, write down the dependencies etc, then make a design for that and then start implementing. Like I said, I always have that with projects and it is most likely to happen every time I do something new, something I have never done before. I think that a person just can not think of the right abstractions before he had some experiences in the corresponding field of work. I guess that is just the difference between beginners (me) and experts, they tend to make the right decisions the first time just because they experienced something with past projects. Secondly I noticed that the code is highly cohesive, a large part of the objects are interconnected intensively resulting in a lot of calls just forwarding code. I think that although university tells us to use an object oriented approach (as we are taught by using Java) one should see this in the correct light. My purely object oriented approach for example tends to be a bit too object oriented for its purpose. Afterwards I feel that it would have been better if I programmed the TileTerrain component as a set of (on the level of the TileTerrain component) global variables that are manipulated by a class that is exported to the programmer that uses the Terrain, like a sort of a wrapper class. After the project I studied some professional project source code (for example Quake 2, Half Life 2, Doom II, Abuse and the Fly3D SDK) and I found that such an approach is indeed more manageable with less function forwarding and less object tangling. Such an approach would have saved me the thick pasta layer I currently have. Another annoying thing with the object oriented programming style I used is that I have a lot of data hiding (which people make you believe is good) and I prefixed these private data members with an m_ since that is the Microsoft way. That turned out to be a big mistake. Every time I had to move a data member from private to public (to increase the speed of the code for example) I had to find / replace all the instances. This sometimes had the nasty side effect that the code broke due to a method parameter then having the same name as the data member which would give something like filename = filename instead of m_filename = filename. What really annoyed me was that the method documentation with the Doxygen comment style was obstructing my visual overview of the code. I just could not see all the functions in one view anymore since there were multiple lines of comment in my way. Now do not get me wrong and think that I would like to do without the documentation, I do not, but I think that I should have placed the major part of the documentation way later in the project. Because while I was working I knew what every function did, the documentation actually is only for other programmers and for later reference. I managed anyway by using the nice text hiding system Microsoft Visual Studio .NET offers though. 54
Theory
What really amazed me is that stuff I though to be really simple to implement or fix, actually turned out really hard. Take the shadow-map generation for example. The theory behind this technique is really simple, just shoot a ray from the lumel you have to the light source and if it hits a polygon you do not receive light from that source. In practice however, there are major difficulties related to this ranging from purely implementation wise to side affects created by the application of the shadow image. Implementation difficulties are for example: speed of the algorithm, preciseness of the algorithm, etc. (This has been discussed elaborately in the Shadows section) But there are other really difficult things that happen when you apply the shadow image: Since the color grid already indicates color, an application of the shadow image would result in double blackness of the shadows for the parts of the terrain mesh where the mesh is also affected by the color of the vertices. This is difficult to solve because when you turn off the colorper-vertex data, you do not receive any shadow coloring for the parts that are facing away from the light but do not result in a ray intersection. That kind of stuff is hard to detect on paper. The theory behind caching vertex data is also really easy, but as it turns out you have to fill the cache buffers with data of which you do not know the size yet, you should perform some optimization that batches the primitives, etc and still keep it fast enough so that the user does not notice anything. All in all this turns out to be really simple on paper, but in practice a bit harder. Also theoretical algorithms are perfect in precision and speed, but as it turns out implementing them is a world of its own.
General
During the course of the project I ran into numerous problems ranging from bad design, machine limitations (memory wise, render speed wise, etc), unsatisfying results (mainly the first tiling system) and just too much hacked code to be able to work with. I think that in the end getting the stuff to render at high speed was the most difficult task I faced. The most difficult part of the code is the part that makes the rendering efficient with all the tiling code etc. For example: I can write a brute force Tiling system like that of version 1 within 6 hours (I did), but as soon as optimization kicks in you are in for a total recode or two. Also, when all the visibility stuff gets in it is harder to find errors and fix them.
55
Results
In the end the result is in my eyes somewhat disappointing. The visual quality of the rendered image is based quite strong on the talents of the person who builds the level. He / she should have insight in which tile types can be blended into each other without creating a fake look. Also he has to know how large an elevation difference can be without stretching the texture too much, since large elevation deltas stretch the tiled texture too much resulting in a visually disturbing image. Further, there are not only are there less features completed than I would have like there to be, but the features are not able to do what they are able to do in theory. For example: I would have liked the terrain to be able to reflect the change of time in the day (color changing and shadows getting shorter/longer based on the position of the sun) but the algorithms just are to slow to update that kind of stuff on run-time. The lack of features is mainly that I have some code for loading meshes (with animation and hierarchical data), but I did not have the time to implement it so that it can actually be used. The terrain s water is not ready yet which makes the terrain look a bit empty as well. The project required me to work for around 160 hours. Suffice to say that I did not make this by a long long long shot, I think a multiplication of about 5 or 6 times that amount is more appropriate. Nevertheless I had my fun, gained some experience and finally got a terrain editor that had a lot of positive responses from the people I know and care about. Well, it has been fun. But to all good things in life must come an end, and here it is. I guess I will go to bed now where my girlfriend is already sleeping and spend the weekend with her again, since I really have not given her the amount of attention she deserves the past few weeks. Goodnight. Ebor Jan Folkertsma
56
Required
System: Processor: Memory: Hard Disc: CD/DVD: Graphic Card: Input: Windows 2000 / XP Pentium III 1000 MHz, or equivalent / better processor 128 MB RAM 500 MB 4x speed GeForce II MX or better, with at least 16 MB of video memory. Keyboard and mouse (3 mouse buttons preferred)
Recommended
System: Processor: Memory: Hard Disc: CD/DVD: Graphic Card: Input: Windows 2000 / XP Pentium III 1500 MHz, or equivalent / better processor 256 MB RAM 500 MB 4x speed GeForce III/IV, ATI Radeon 8500 or better, with 64 MB of video memory. Keyboard and Mouse with 3 mouse buttons
57
Programming IDE DevCPP with the MinGW compiler (www.bloodshed.net) * Borland C++ Builder (www.borland.com) * Microsoft Visual Studio .NET (www.microsoft.com)
Painting Programs The GIMP (www.gimp.org) * Adobe Photoshop (www.adobe.com) * Paint Shop Pro (www.jasc.com)
58
Appendix C: References
Books
3D Games (Real-time rendering and Software Technology), Alan Wat, Fabio Policarpo, Copyright 2001 by ACM Press, A division of the Association for Computing Machinery, Inc. OpenGL game programming, Kevin Hawkins, Dave Astle, Copyright 2001 by Prima Tech. OpenGL Programming Guide (The Red Book), Dave Shreiner, Mason Woo et al, Copyright 1994 by Silicon Graphics, Inc. OpenGL Reference (The Blue Book) , Dave Shreiner, Mason Woo et al, Copyright 1994 by Silicon Graphics, Inc. The OpenGL Extensions Guide, Eric Lengyel, Copyright 2003 Charles River media The C++ Standard Library (A tutorial and reference), Nicolai M. Josuttis, Copyright 1999 by Addison-Wesley.
Websites
Boost libraries URL: http://www.boost.com C Plus Plus resources URL: http://www.cplusplus.com DinkumWare C++ specificatie URL: http://www.dinkumware.com OpenGL site URL: http://www.opengl.org Simple direct media (SDL) URL: http://www.libsdl.org
59