Introduction to Ultimate 3D

The fact that you are reading this text makes me assume that you have decided to try out Ultimate 3D. This is a good choice. If you have been trying to make 3D games with the 3D functions of Game Maker before, you will have noticed that it's incredibly difficult to make really good-looking 3D graphics with it. And if you have succeeded to make pretty good graphics with it (which took a lot of time for sure) you may have noticed that they require a PC that could run high end games.

Game Maker is a great program to make games easily, but its 3D drawing functions are very basic. They aren't meant to be used for making really complex 3D graphics. And if people try to use them for this purpose they will have difficulties reaching this aim. So if you want to make complex 3D graphics with Game Maker you will need some extension. You may have tried some other 3D extension for Game Maker before. But most of them are pretty difficult to use, aren't they?

That's why there's Ultimate 3D. It's an attempt to make an easy to use extension for Game Maker that can be used to make extremely complex 3D graphics. With the old versions of Ultimate 3D (v. 1.0 to 1.31) you could make quite nice 3D graphics, but there weren't many possibilities to make special effects and not enough possibilities to optimize games that use really big levels. For that reason I've spent more than one year on developing Ultimate 3D v. 2.0. It has tons of new features that give you the ability to make great special effects and to build giant levels without getting a bad performance. Actually it's a complete remake. Ultimate 3D 2.0 has become what it was meant to be. And Ultimate 3D 2.1 makes it complete with basically new functions for collision detection and collision response and many small, but useful, new features.

Since one of the most basic principles of Ultimate 3D is that it's meant to be extremely user-friendly, the tutorials are very detailed. They are divided into a part for novice users and a part for advanced users. This subdivision is meant to make it easier for you to get started with Ultimate 3D. The features described in the part for novice users are really easy to use. It will not take you long to understand how they work but they'll already give you the possibility to make pretty cool games. But when you go on learning the features described in the part for advanced users it will give you the possibility to make really amazing graphics. Using the Ultimate 3D shader effects management you can create very advanced special effects. Also the built-in features such as the bump mapping and parallax mapping or the terrain renderer are really impressive but not difficult to use.

But well, you will see for yourself. Have fun while learning how to make 3D games :D .

Credits

Special thanks go to Matthew Unrath (Harkathmaker), who has helped to improve the grammatical correctness of this help file and to MysteriXYZ, who has improved this help file in terms of formatting and content and wrote a nice epilogue. Also I want to thank everybody who has helped to find mistakes in this help file. Your continuous feedback improves the quality of the documentation of Ultimate 3D.

© Christoph Peters. Some rights reserved.

Creative Commons License XHTML 1.0 Transitional


Getting started

The file it all begins with

Before you can start using Ultimate 3D, you need some Game Maker file to work in. It's obvious that you can not use an empty project for this purpose. You need the scripts and the basic objects that belong to Ultimate 3D. If you have seen the demo that comes with Ultimate 3D you may want to use this as a base for your game. But I do not recommend that. If you really want to make your own game you should start from scratch, in which case the stuff in the demo will not be of much use to you. If you just want to mod the Demo somehow you can use it. But people will notice that when you release it and they will not like it.

You should start making your first Ultimate 3D project by creating a new folder and copying the BasicGM5.gmd (resp. BasicGM6.gm6 or BasicGM7.gmk) file. This can be obtained from the Ultimate 3D Software Development Kit (U3D SDK) that can be downloaded at Ultimate3D.org. This Game Maker file contains everything you need to get started with Ultimate 3D (apart from the knowledge which is in the tutorials you are reading currently ;) ). There's one example instance of every kind of Ultimate 3D object, as well as the control object that is required to use Ultimate 3D. The only room contains a senseless combination of these objects. When you start making a game, you should delete every object except the control object and the camera object.

There are a few things you should notice about the room. If you create a new room that is to be used with Ultimate 3D, it has to meet these requirements:
- The room uses views and the first view is enabled.
- The background color is disabled. This is not necessary but it's a waste of computing time if you let Game Maker draw its background color.
- The room has to contain exactly one control object and a camera object.

I recommend creating two sub-folders in your project folder. The first one should be called gfx and should be used for saving textures. The second one should be called models and I guess it's obvious what it's supposed to contain. But that's up to you.

The view system

There's one thing about the use of a 3D extension for Game Maker you will not like. When you use Ultimate 3D it will render to the first view of Game Maker. While it does this you can not use Game Maker to draw to this view anymore. It is simply not possible for two totally different programs to render at the same part of the screen simultaneously. For this reason Ultimate 3D does not just have functions for drawing 3D scenes; there's also a couple of functions to draw 2D graphics and text. Also, the first view does not need to cover the whole Game Maker window. You can use multiple views and as long as they do not try to cover the Ultimate 3D view it will be no problem for Game Maker to use them. Another thing is that you cannot switch fullscreen mode while using Ultimate 3D. So if you want to use fullscreen mode you have to enable it in the Game Maker options or in the Ultimate3DOptions() script.

The camera object

So let's get to the first practical thing: the camera object. By default, the camera object has a piece of code for mouse control in its step event and a couple of calls to the Ultimate 3D function Move in its keyboard events. If you do not like that, feel free to remove it. But how impolite. I did not introduce you to the function Move. So have a look at this:

This function moves the object that calls it in 3D space.

Move(
Longitude,
Latitude,
Speed
)

Longitude, Latitude
These two parameters describe the direction the object should move in. It works in the same way as on a globe. Longitude describes how much the object moves up or down. 0 means that it does not change its height, 90 means that it moves straight down and -90 means that it moves straight up. Latitude gives the horizontal direction the object moves in. Note that it differs from the direction variable of Game Maker. To convert a direction value of Game Maker into a Latitude value of Ultimate 3D without changing the direction, you have to use this little formula: Latitude=direction+90.

Speed
This parameter gives the speed the object should move with. Note that you have to call the function every step to make the object move with this speed.

From now on every function that has parameters will be described like this. If you want just a quick information while programming you can also open the script. Every script contains a short description of the function and its parameters.

By the way, you may have noticed that Move(...) is written with a capital letter as are all other functions of Ultimate 3D. This has a couple of reasons. The first one is that I'm used to functions called like this from C++. The second one is that you can see whether a function belongs to Game Maker or to Ultimate 3D easily this way. If there wasn't this difference there would have been a prefix instead. So Move would not be called Move but u3d_move. In my opinion it's much easier to press the shift key sometimes than writing u3d and _ all the time. Besides, it looks better.

But let's get back to the topic... the camera object. In Ultimate 3D every 3D object in the scene must be represented by an instance of a Game Maker object. You can then change the properties of the scene objects by simply changing the variables of their Game Maker objects. An Ultimate 3D function such as MoveCamera() will pass them to Ultimate 3D automatically. Camera objects have the following variables:

x, y, z
These variables give the position of the camera. Since we are in 3D space now there aren't only x and y anymore. They now have a new friend called z. It defines the height of the object. So if you increase z the object will move up, if you decrease it the object will move down. The combination of x, y and z is called a coordinate. You should remember this word. It may appear again.

height
In first person games the camera usually has a particular height above the ground. This height can be implemented using this variable. The actual z-coordinate of the camera (the one Ultimate 3D works with) is not z but z+height.

rotx, roty, rotz
These variables give the rotation of the camera. In short rotx describes the pitch of the object, roty describes the yaw and rotz describes the roll. A more detailed description of what these variables mean is given in the second part of the tutorial "Using primitive objects".

view
This variable defines the double vertical angle of view of the camera. As with any angle in Ultimate 3D it must be in degrees.

min_range, max_range
The camera will only show objects that are farther away than min_range and closer than max_range. All other objects will be invisible. You should use reasonable values for these variables. If min_range is too small or max_range too big you may get strange display errors. Objects may cover other objects that are in front of them.

falling, falling_speed (optional)
If falling is equal to true the z coordinate of the camera will be increased by falling_speed in every step and falling_speed will be decreased by global.gravitation (by decreasing I mean that every step falling_speed will become smaller. Negative speed is down, and positive speed is up). These variables are meant to be used in combination with the premade collision functions CollisionH(), CollisionV(), CollisionZ() and CollisionTerrainModel().

follow, perspective, rotatex, rotatey, distance (optional)
follow can be set to the ID of a Game Maker object to make the camera follow this object. The followed object can hold a value for the variable height to make the camera follow a point above or below the actual position of the object. perspective defines the way the camera will follow the object. It can be 0 for an inertial camera on the ground, 1 for a fixed third person camera or 2 for a flexible third person camera. If perspective is 1 or 2 you can change rotatex and rotatey to give a rotation offset. distance defines how far the camera stays from the object it follows.

So far that's all you need to know about the camera object. Actually there are a few more variables and functions for cameras, but those are described in the tutorials for advanced users.

So now you know how to create a camera object. Unfortunately there's nothing it will show you so far (at least if you have cleaned up the room). But do not worry, that will change very soon.

Loading textures

Before we get to creating the first visible objects there's one other thing you need to know about: loading textures. For those who do not know, here's a short explanation of what a texture is: A texture is (usually) a two-dimensional picture that gets created by an application that uses 3D graphics. Then this picture can be applied to an object. Without textures all objects in 3D graphics would be made up of only one color and that would be really boring. For that reason it's very important to have good textures. They make your games look much more realistic.

To load a texture you only need one very simple function:

LoadTexture(
TextureFile,
TextureIndex,
Width, Height
)

TextureFile
Since Ultimate 3D can not access the resources of Game Maker, the texture has to be saved into an external file. This file can be a bmp, jpg, tga, png, dds, ppm or dib file (NOT gif). I recommend saving textures in a sub folder called gfx. But if you want to hide your textures from the people who play your game you can also attach them to your executable using Game Maker's data files or including mechanism and load them from its temporary directory.

TextureIndex
The index the texture is to be associated with. It can be an arbitrary number in the range from 0 to 998. An index is similar to an id. When you want to use the texture later you have to know the index you associated it with.

Width, Height
Usually you can simply ignore these two parameters. If you enter 0 or nothing for both of them, Ultimate 3D will get the height and the width of the texture from the file. But if you enter values that differ from the actual size of the texture it will be scaled to this size. Note that only powers of two or zero can be passed for these parameters. For more information read on.

There's one important thing you have to know about textures. Most graphics devices work only with textures that have dimensions made up of powers of two. In addition, some graphics devices can work only with quadratic textures, but that's a rare case. Anyway, your textures will be scaled up if they do not have dimensions made up of powers of two, to make them have valid dimensions. To avoid this unnecessary quality loss you should make all your textures use these dimensions. Some examples for valid dimensions are 256*256, 128*512 and 1024*64. A texture with a size of 257*257 would get scaled up to 512*512 and this is a real waste of memory.

Another thing you need to know is how to make textures transparent. There are two ways to do this. The best one is to use a file format that supports transparency such as tga or png. The Gimp (GNU Image Manipulation Program) supports these file formats. It's a great open source image editing program. If you prefer using other file formats you can get transparency anyway. Simply use the transparency color which is (r=0; g=128; b=64). All pixels that have exactly this color will be set to transparent by Ultimate 3D.

I recommend loading all textures in the create event of the control object. This way you can get a list of all textures loaded including their indices. But that's up to you.

Here's a really small code example that demonstrates how to use this function correctly:

LoadTexture("gfx/WallTexture.png",1);


Using primitive objects

The concept behind primitive objects (in short, primitives) is to give you the possibility to create simple scenes easily. Large amounts of primitives can also be rendered very efficiently. Defining an object in Ultimate 3D works in the following way. As I already said, every object in Ultimate 3D must be represented by an instance of an object in Game Maker. So you have to create a new object first. Then you've got to define a couple of variables for the object. Finally you call a function that passes the values of the variables to Ultimate 3D and creates the object. So let's get started with the first visible object.

Wall objects

A wall object is totally flat and orientated vertically. Before creating it you have to set the following variables:

height
This variable indicates the height of the wall.

x2, y2
These two variables indicate where the wall goes to from its x- and y-position. The following graphic illustrates this:

Illustration not available

texture
You should set this variable to one of the indices you assigned a texture to when using the function LoadTexture(...). This texture will then be used for the wall. If you do not want to use a texture you can set this variable to -1.

texx1, texy1, texx2, texy2 (optional)
These variables are optional. If you do not set these variables then the whole texture will be used. They define two so-called texture coordinates. The texture coordinates x=0 and y=0 (in short (0|0)) refer to the top left of a texture. The texture coordinates (0.5|0.5) refer to the middle of a texture, the texture coordinates (0|1) to the bottom left of a texture, the texture coordinates (1|1) to the bottom right, and so on. The wall object will use the part of the texture that lies between the two given texture coordinates. The texture coordinates can also be made up of values bigger than 1. Then the texture will be wrapped (the same as tiling but for textures).

There are two additional variables but currently you do not need to know about them. Floor and cube primitives have them, too. They are described in the tutorial Adding lights.

Now that you have set up all required variables you can call CreateWall() once. This function does not take any parameters. It gets all the information it needs from the variables. However, you have to put Step() into the step event of the object and Destroy() into its destroy event. Neither of them require any parameters. Destroy() has to be put into the destroy event because otherwise Ultimate 3D would not know when the object gets destroyed. It simply would not disappear in this case. What Step() does is the topic of the next part of this tutorial. So hang on ;) .

Transforming primitives

Transforming... strange word. Sounds a bit complicated, does not it? Well, do not be scared, you already know how to transform something; you just do not know that it's called transforming. Transforming something means changing its orientation. If you take an object and move it around, that's a transformation. If you rotate it that's a transformation as well. There's one transformation that can not be done in real life, but you already know it from 2D stuff: scaling something. Scaling means changing the size of an object. An object can be scaled on three axes. All these processes are called transformations. So the thing you should remember is that a transformation is the combination of scaling (changing size), rotation, and translation (moving).

And now to transform primitives. You do know how to do this, because you have transformed another object before (if you read the tutorials completely so far): the camera object. Like the camera object, every primitive has the variables x, y, z, rotx, roty and rotz. Additionally, they have scalx, scaly, scalz. Since the explanation of the meaning of rotx, roty and rotz wasn't that detailed in the tutorial about camera objects, here's a more detailed one and a description of scalx, scaly and scalz.

rotx, roty, rotz
As I already said in the tutorial about camera objects, the variable rotx gives the pitch rotation of the object, the variable roty gives the yaw rotation and the variable rotz gives the roll rotation. The following graphic illustrates this.

Illustration not available

Increasing rotx will rotate the front of the object down, while decreasing it will move the object up. The yaw rotation (roty) rotates the object without changing the z-coordinates of the points of the object. Finally there's the roll rotation. I think there's not much to say about this. Note that the roll rotation is applied first, then the pitch rotation follows and finally the yaw rotation gets applied.
A short comment on that: If you have been reading carefully, you may have noticed that roty rotates around the z-axis whereas rotz rotates around the y-axis. This little inconsistency is a remnant from the first versions of Ultimate 3D. These variables are named according to the coordinate system that's used by Ultimate 3D internally. You do not need to care about that, just keep in mind that roty rotates around the z-axis and rotz rotates around the y-axis.

scalx, scaly, scalz
The concept of scaling is quite easy to understand. A scaling value of one means that nothing changes, a scaling value of two means that everything becomes twice as big, and a scaling value of 0.5 means that everything becomes half the size. Usually you do not use negative scaling values. The only thing that's left to explain is why there are three scaling values. That's because this way you have the possibility to scale the object differently along the three axes of the 3D coordinate system. But in most cases you want to scale your object in the same way along all axes so you should set up the same value for all three scaling values. Note that scaling is done before rotation.

Another thing you must never forget is that every Ultimate 3D object must contain a call to the Step() function within its step event. Otherwise, the variables will not affect the transformation of the model!

Floor objects

Floor objects do not have an expansion along the z-axis, meaning that they are totally flat and horizontal. The variables you have to set up before creating floor objects are the following:

width, height
width describes the expansion of the floor object along the x-axis and height the expansion along the y-axis.

texture
Index of the texture that is to be used for this object or -1 for none. For a more detailed description have a look at the text about wall objects.

texx1, texy1, texx2, texy2 (optional)
The part of the texture that is to be used. For a more detailed description have a look at the text about wall objects.

As you can see there aren't many changes in comparison to wall objects. Again there are two additional variables that will be introduced in the tutorial about light sources. After setting up these variables you have to call CreateFloor() to initialize the floor object. And as usual you need Step() and Destroy() in the corresponding events.

Cube objects

Cube objects are a little bit more complex than floors and walls, but they can be used for pretty cool things. In the old demo of Ultimate 3D v. 1.31 all buildings were made up of simple cubes. As with other primitives, you have to set up a few variables before you can create a cube. Here's a list of them:

width, height, depth2
The dimensions of the cube. width gives the expansion along the x-axis, depth2 gives the expansion along the y-axis and height gives the expansion along the z-axis. depth2 is called "depth2" instead of "depth" because the variable depth is used by Game Maker.

texx1, texy1, texz1, texx2, texy2, texz2 (optional)
The part of the texture that is to be used. For a more detailed description of these parameters have a look at the text about wall objects. Since cube objects are three dimensional whereas walls are two dimensional, cube objects have three pairs of these parameters.

texture
Index of the texture that is to be used for this object or -1 for none. For a more detailed description have a look at the text about wall objects.

subdivide_texture (optional)
As everyone knows a cube has six sides. If you set this variable to false all of them will use the same part of the texture. If you set it to true the texture will be subdivided into six parts and each side of the cube will use a different part of the texture. The following graphic illustrates how this works.

Illustration not available

When dispart_texture is false, the entire texture is displayed on each side. When it is true, each side shows only a part.

originx, originy, originz (optional)
These variables usually take values in the range from 0 to 1. They define where the origin of the cube lies. For example, entering 0.5 for all of them would mean that the origin of the cube lies exactly in the middle. Entering the correct values for these variables is very important, especially when you rotate the cube. The following graphic illustrates how it works:

Illustration not available

After setting up these variables you have to call CreateCube() once to initialize the cube object. And as usual you need Step() and Destroy() in the corresponding events.

Billboard objects

Billboards differ a bit from all other 3D objects. The reason is simple. They aren't 3D. It often happens that extremely complex geometry is required for particular objects. Trees are the best example. To avoid this complex geometry you can use billboards. In older games you often see that the trees are 2D while everything else is 3D. That's what billboards are. Billboards are 2D objects in 3D space. They are fixed to one point in 3D space and their size on the screen changes depending on the distance of the camera.

Anyway, the creation of billboards is not any harder than the creation of other primitive objects. Here are the variables you have to set up for them:

width, height
The dimensions of the billboard.

originx, originy
These variables define the origin of the billboard. The origin is the point of the billboard that does not change its position in 3D space when the camera rotates. Usually it's somewhere in the middle of the billboard. In principle it works in the same way as the variables originx/y/z of cube objects. So have a look at the description of those.

texture
Index of the texture that is to be used for this object or -1 for none. For a more detailed description have a look at the text about wall objects.

texx1, texy1, texx2, texy2 (optional)
The part of the texture that is to be used. For a more detailed description have a look at the text about wall objects.

After setting up these variables you have to call Create2D() once to initialize the billboard object. And as usual you need Step() and Destroy() in the corresponding events.

Polygon objects

In the part of this documentation for advanced users you will find out that arbitrary models can be handled as primitives. But currently you do not know about this so polygon objects are the most complex primitive objects you get to know. Before telling you what a polygon object is I'm going to explain what the term vertex means. A vertex is a point in 3D space, with a texture coordinate, that's part of a 3D object. A triangle is always made up of three vertices and every 3D object is made up of a couple of triangles. So one could call vertices the smallest unit of 3D objects.

Polygons do not get defined by a couple of variables. Instead you call three functions. You need an instance of an object for every polygon object. The first function you have to call is called BeginPolygon(...). It tells Ultimate 3D that you want to create a polygon object and it prepares everything. Here's the description of the function:

BeginPolygon(
VertexNumber
)

VertexNumber
The number of vertices the polygon you are about to create will have. The value of this variable should always be bigger than or equal to three, otherwise you will not get a real polygon.

After calling BeginPolygon(...) you have to call AddVertex(...) multiple times depending on the value you passed to BeginPolygon(...). Here's the description of AddVertex(...):

AddVertex(
X,Y,Z,
U,V
)

X, Y, Z
The position of the vertex. Note that this position gets transformed depending on the values you have set up for x, y, z, scalx/y/z and rotx/y/z. So the position is given in object space.

U, V
The texture coordinate of the vertex. To get a detailed description of what a texture coordinate is have a look at the description of the variables texx1 and texy1 of wall objects.

When you have called AddVertex(...) often enough you can call CreatePolygon() to create the polygon object. As with every other primitive object it has the texture variable and the transformation variables (for more information have a look at the description of wall objects and at the text about transforming primitives). For this reason you have to put Step() into the step event again and Destroy() into the destroy event.

Here's a code example that demonstrates how to create a simple polygon object:

BeginPolygon(5);
AddVertex(0,0,0,0,1);
AddVertex(0,0,5,0,0.33);
AddVertex(0,2.5,7.5,0.5,0.0);
AddVertex(0,5,5,1,0.33);
AddVertex(0,5,0,1,1);
CreatePolygon();
texture=1;

This picture shows the result of these function calls:

Illustration not available

Modifying polygon objects

Any single vertex of polygon objects can be changed after the creation of the polygon. To do this there are two functions: one to get data about the polygon object, and one to change the data of the polygon object. Here's a description of both of them:

This function retrieves data about the polygon object that calls the function. It can be called after CreatePolygon().

GetVertex(
VertexIndex,
ElementIndex
)

VertexIndex
This parameter identifies the vertex you want to get information about. The vertex that has been added to the polygon first has the index 0. The highest valid index is VertexNumber-1.

ElementIndex
This parameter tells Ultimate 3D what kind of information is to be retrieved. Here's the meaning of every value:
0 = X;
1 = Y;
2 = Z;
3 = U;
4 = V

This function changes the data of one vertex within a polygon object that calls the function. It can be called after CreatePolygon().

SetVertex(
VertexIndex,
X,Y,Z,
U,V
)

VertexIndex
This parameter identifies the vertex whose information you want to change. The vertex that has been added to the polygon first has the index 0. The highest valid index is VertexNumber-1.

X, Y, Z
The position of the vertex. For more information have a look at the description of the function AddVertex().

U, V
The texture coordinate of the vertex. To get a detailed description of what a texture coordinate is, have a look at the description of the variables texx1 and texy1 of wall objects.

That's all you need to modify the geometry of a polygon. Here's an example for this. If placed in the step event of the polygon object it moves the third vertex up a bit in every step.

SetVertex(2, GetVertex(2,0), GetVertex(2,1)+0.1, GetVertex(2,2), GetVertex(2,3), GetVertex(2,4));

Changing the material of primitives

In its simplest case, changing the material of an object means changing its color. To do this Ultimate 3D offers a very simple function:

This function changes the material of the primitive object that calls it.

SetMaterial(
Red,
Green,
Blue,
Alpha
)

Red, Green, Blue
These parameters define the new color that is to be used. The values for the parameters can be in the range from 0 to 255. (0|0|0) means black and (255|255|255) means white.

Alpha
This parameter gives the opacity of the object. It can be in the range from 0 to 255. 0 means no opacity (fully transparent) and 255 means fully opaque.

This function changes the emissive part of the material. The emissive part of a material is not influenced by lighting. A good example for a material that should use emissive lighting is a TV screen. It emits light so its brightness does not depend on the light. You can also use emissive materials if you do not want objects to be influenced by the light. For example, it usually looks quite weird if billboards are influenced by light.

SetMaterialEmissive(
Red,
Green,
Blue
)

Red, Green, Blue
The color that is to be used for the emissive part of the material. Again the values can be in the range from 0 to 255.

Updating primitive objects

You already know that you can change the transformation of a primitive after its creation using the variables x, y, z, rotx/y/z, scalx/y/z. The same can be done with the texture. It can be changed at any time. But what about the other properties of a primitive? For example, if you want to animate a texture by changing texx1, texy1, texx2 and texy2? If you change these variables after creating the primitive, the object does not change. For this reason there's the function RecreatePrimitive(). It does exactly what the name says. It destroys the primitive and recreates it with the new data of the variables.


Changing the environment

So now you are able to create simple scenes using primitive objects. But a scene is not only made up of objects. There are more things that belong to a complete scene. Some of them will be described now. This chapter is really easy, because Ultimate 3D does everything automatically. You just have to tell it what you want.

Ambient lighting

Ambient lighting is the light that is everywhere. If you look around in your room you'll probably notice that there's no place that's absolutely dark. Even if something is not directly lit there's always enough adventitious light to brighten it up a bit. Of course everything is actually lit by something else. Objects reflect light and that's what lights the objects that are not directly lit by light sources. But simulating this behavior would take awfully huge amounts of computing time. For that reason there's the ambient lighting.

Ambient lighting is the simplest kind of lighting one could imagine. Everything gets lit by it with the same intensity and the same color. For this reason ambient lighting is made up of only one color value. This color can be changed by the following function:

SetAmbient(
R,
G,
B
)

R, G, B
The new color of the ambient light. The values for these parameters have to be in the range from 0 to 255.

By default the ambient lighting has the color (128|128|128). So if you write SetAmbient(255,0,0) everything will glow red. If you write SetAmbient(0,0,0) all objects except for those that have an emissive material would be totally black. By changing the ambient lighting you can strongly influence the atmosphere of your game. You can switch between day and night or you can create an apocalyptic atmosphere by using red light.

Setting up 3D backgrounds

Another thing that's very important for the atmosphere is the background image. Ultimate 3D supports three different kinds of backgrounds. Currently you are using the first one – a solid color. The background color can be changed by changing the values of the variables control.BG_r, control.BG_g and control.BG_b. You can give them values in the range from 0 to 255.

The second kind of background is called a sky sphere. In this case you have one 2D image that contains a 360° picture of the environment. This picture gets used as the texture for a sphere that surrounds the scene. The disadvantage of sky spheres is that you get strange distortions at the top. All degrees of latitude meet in one point there. That is not that pretty. But in most games it's usually not necessary to look straight up. So most of the time sky spheres are useful anyway.

But what about games where the background has to look good in all directions? The best example for this is a space shooter. Words like up and down have no meaning in space. For cases like this Ultimate 3D supports another kind of background that does not lead to any distortions: a sky cube. A sky cube is not made up of one 2D image, but six quadratic images that get put together into a cube. As with the sphere, the cube surrounds the whole scene. Creating the pictures required for sky cubes is more difficult than creating sphere maps. But the results are better indeed.

Now that you know which kinds of backgrounds are supported let's get to the practical realization. There's only one function to load backgrounds:

This function loads either a sphere map or six textures for a cube map depending on the parameter given.

LoadBackground(
File
)

File
You have to enter a string for this parameter. If the string contains only a file name, this file will be loaded and used as the texture for the sky sphere. If the file string contains a * sign this sign will be replaced by the words "Front", "Back", "Top", "Bottom", "Left" and "Right". So Ultimate 3D loads six different files. They will be used for a sky cube.

If you do not want to use a background anymore you can call UnloadBackground(). Then the solid color will be used again.

Setting up fog

Thick fog is rare in most parts of the world. In computer games it occurs more often. But actually there's almost always a little bit of fog. On rainy days everything that's far away gets gray and colorless. On really hot evenings everything that's far away has a very dark orange tone. So you can almost always use fog; usually it looks quite cool. It supports the impression of real depth. However, using too much fog will make people think you couldn't offer a higher visual range. In some cases this may actually be true. Then fog is the lesser of two evils. It's better than having everything just disappear when it gets too far away from the camera.

Setting up fog is not any more difficult than setting up a background picture. There's one function to enable it and one to disable it. Here's the one that enables it:

SetFog(
R,
G,
B,
StartDistance,
EndDistance
)

R, G, B
The fog color. The values for these variables have to be in the range from 0 to 255.

StartDistance, EndDistance
The distance values for the fog. Everything that's closer to the camera than StartDistance will not be influenced by the fog. Everything that's farther away from the camera than EndDistance will have the fog color. Everything in between these values Ultimate 3D will interpolate linearly.

If you want to disable fog simply call DisableFog().


Adding light sources

Ambient lighting is fine, but it's obvious that you do not get far with it. For a really lively scene you need more dynamic light sources, things that do not light everything in the same way. For this reason Ultimate 3D supports per-vertex lighting. This works in the following way. You have to add a couple of light sources to the scene. Ultimate 3D will then use these light sources to calculate one color value for every vertex. This color will be interpolated over the triangle that is connected with the vertex. Ultimate 3D also supports per pixel lighting. But that's one of the techniques for advanced users.

There are three different types of light sources. The first one is called directional light. The classic example for a directional light is the sun. It lights everything from the same direction. No matter where you are (ignoring the fact that the earth is a sphere) the sun always seems to be at the same place in the sky. The opposite of a directional light is a point light. A point light can be compared to a light bulb that's hanging in the middle of a room. The left wall will be lit from the right and the right wall will be lit from the left because the light bulb is hanging between them. The farther away something is from the point light, the less it will be lit by the light source. The third type of light source is a spot light. A spot light is similar to a point light. But in contrast to the point light, it does not brighten up everything around it, but only the parts that lie in a defined cone of light.

As with primitive objects you need an instance of an object for every light source. And again you set up a few variables first, then you add a function call to create the light source and finally you put Step() and Destroy() into the corresponding events.

Directional light

So here are the variables you need to set up before creating a directional light.

rotx, roty
The names of these two variables are relics from very old versions of Ultimate 3D, because I did not want to break the backwards compatibility. Today I would call them longitude and latitude. They define the direction of the directional light source. To get a description of what longitude and latitude mean have a look at the description of the Move(...) function in the tutorial "Getting started".

r, g, b
These variables define the color of the light source. The values are usually in the range from 0 to 255. By increasing the values of these variables you increase the intensity of the light. You can also specify negative values for these variables. Then the light source will darken the objects that are lit by it.

Directional lights do not have a position and a range, because it simply does not matter. They are omnipresent. After setting up these variables you can call LightDirectional() to initialize the directional light source. If you haven't forgotten to include Step() in the step event you can change all parameters of all light sources at any time.

Point light

Let's go on with the variables of point lights.

x, y, z
These variables define the position of the point light source. The closer something is to this point, the stronger it will be lit by the point light.

range
The range of the point light. Everything that's farther away from the light source will not be affected by it. The bigger the range, the stronger the light's intensity.

r, g, b
These variables define the color of the light source. The values are usually in the range from 0 to 255. By increasing the values of these variables you increase the intensity of the light. You can also specify negative values for these variables. Then the light source will darken the objects that are lit by it.

After setting up these variables you can call LightPoint() to initialize the point light. Again you can change all properties of the light source at any time if you do not forget about Step().

Spot light

Let's get to the last type of light sources, the spot lights. They are a bit more complex than point lights and directional lights and they require more computing time. Here are their variables:

x, y, z
These variables define the position of the spot light source. The closer something is to this point, the stronger it will be lit by the light.

range
The range of the spot light. Everything that's farther away than this from the light source will not be affected by it. The bigger the range, the stronger the light's intensity.

rotx, roty
These two variables define the direction of the spot light. rotx is equivalent to longitude and roty is equivalent to latitude.

deg
This defines the angle that is lighted by the spot light. If the angle between the ray from the light source to the vertex and the direction vector of the light source defined by rotx and roty is bigger than this, the vertex will not be lit by the light source. In other words, it controls how wide the spotlight's area is.

r, g, b
These variables define the color of the light source. The values are usually in the range from 0 to 255. By increasing the values of these variables you increase the intensity of the light. You can also specify negative values for these variables. Then the light source will darken the objects that are lit by it.

After setting up these variables you can call LightSpot() to initialize the spot light and if you do not forget about the call to Step() in the step event you can change all properties at any time.

Subdividing primitives

Time to fulfill a promise. In the chapter about primitives I said that I did not list all the variables of the different primitive objects and that a few additional variables would be introduced here. You would not have understood why these variables exist without knowing about vertex based lighting. So here are the new variables:

partsx, partsy, (partsz)
These variables can be used to subdivide the primitives along the particular axes. partsz is listed in brackets because only cubes need it. Of course polygons do not have these variables. By subdividing a primitive you increase its vertex count. So a subdivided primitive will be lit with more detail since lighting is done on a per-vertex basis. The variables can be set to any positive integer value. But do not overdo this! It can greatly lower your fps.


Using model objects

If you've only used the Game Maker 3D functions before, this feature will amaze you. With Game Maker it is really complicated to load a model. In Ultimate 3D you simply call one function. It can load animated *.an8, *.3ds, *.md2, *.x, *.ms3d and *.u3d files. Just by being able to load models you can create extremely complex scenes with many different cool objects. And you will not find many modeling programs that can not export to one of the file formats supported by Ultimate 3D. So you have the choice.

If you haven't had any modeling experience so far I recommend getting started with modeling by learning how to use Anim8or by Steven Glanville at www.Anim8or.com . It's a very user-friendly modeling program that can be used to make quite complex models. It also has a very cool system to animate objects using skeleton animation. The best part is that Ultimate 3D supports Anim8or files (*.an8). If you are looking for a more professional free modeling program I recommend trying Blender at www.blender.org . Getting started with it is really difficult, but there are really good tutorials linked on the website and once you know how to use it you will love it. It can export to almost every popular file format. In the Ultimate 3D Community there's a pinned topic in the sub category Game graphics that contains a list of modeling programs and other things you need to make 3D games.

The different file formats

Every file format supported by Ultimate 3D has its advantages and disadvantages. Here's a list of them including their individual characteristics.

*.3ds
The 3ds file format is great for a very simple reason. It's supported by many modeling programs. Originally it was the file format of 3D Studio Max, but for some unknown reason it has become kind of standard for many modeling programs. *.3ds files support skeleton animation. Unfortunately there aren't that many non-commercial programs that are able to export animated *.3ds files. Furthermore, the *.3ds model file format is a little outdated because it does not support vertex skinning - which is important when animating characters - and it does not store the normals of vertices.

*.an8
The big disadvantage of the Anim8or file format is that Anim8or is the only modeling program that supports it. The advantage is that it supports skeleton animation. This way Anim8or can be used to create animated models for Ultimate 3D. Unfortunately, the animation support of Ultimate 3D for *.an8 is not absolutely complete because Steven Glanville (the maker of Anim8or) hasn't documented all chunks that are needed to load scenes from Anim8or files yet. But figures and sequences are supported completely, including animations that use bone influences or skin weights. So you can make animations for characters and other objects using *.an8 files. Before loading an *.an8 file with Ultimate 3D you have to convert all the geometry in it to meshes (Build->Convert to Mesh). Despite you should be careful with the power factors in the "Material Editor". By default they are 0.3, 0.7, 0.2, 0.0. It is usually better to set them to 1.0, 1.0, 0.0, 0.0. Otherwise your model will be quite dark in Ultimate 3D.

*.md2
The *.md2 file format is very special. Originally it was used by the Quake II engine to save the player models. The unusual thing about it is that it uses a different method for creating animations than all other supported model file formats. Instead of connecting single meshes using so-called bones and saving the transformation of these bones (this technique is called skeleton animation), it saves one mesh for every single frame. In Ultimate 3D v. 1.31 you always got quite unsmooth animations when using *.md2 files because they did not contain enough frames. Ultimate 3D v. 2.0 uses a technique called vertex tweening to interpolate between the single frames of the *.md2 files. This way you get totally smooth animations. The disadvantages of *.md2 files are that they take more memory than other model files and that their animations can not be manipulated. Their advantages are that some programs that do not support other animated model file formats do support animated *.md2 files, and that there are tons of cool *.md2 models on the web because people have been designing characters for Quake II.

*.x
*.x is the DirectX model file format. It's quite old, but it is pretty decent and most professional modeling programs, like Blender, 3D Studio Max, Maya, Milkshape 3D and many others support it through plugins. Many of these plugins can be found on this website. It supports animations with or without vertex skinning, materials and whatever else a game developer could wish for.

*.ms3d
*.ms3d is the model file format of the modeling program Milkshape 3D. It supports multiple materials and animations with or without vertex skinning. There are plugins to export it for many different modeling programs. If you are having difficulties with a *.x export plugin, the *.ms3d model file format may be the perfect solution.

*.u3d
As the name implies, *.u3d is the Ultimate 3D model file format. Currently there are only two programs that can export it. Ultimate 3D itself (and all applications, which are based upon it) and Blender through this export plugin. There's a couple of different reasons for its existence.
The first one is that it can save an Ultimate 3D model object as is. All effects and changes that have been applied to it will get saved. When you read on to the advanced chapters you will see that this can make things a lot easier. A nice example for this is the fire from the Fire and Water Demo. It requires quite a lot of code to apply the effect to it, but you can simply export the complete model to a *.u3d file. When loading this single model file you'll have animated fire. Also, some computing time intensive data like multiple levels of detail can be saved to Ultimate 3D model files. This can shorten the loading time significantly.
The second one is that you can export all textures and effect files that are used by a model object to the *.u3d file as well. This way you can hide your resources.
And the last reason is that the Ultimate 3D model file format features a decent password encryption mechanism. So if you want to hide your resources from those who play your games, this is no problem. You can simply export all your models and textures to encrypted *.u3d files. Exporting model objects to Ultimate 3D model files will be dealt with in the chapter about advanced model management.
Comment: If you want to add support for the Ultimate 3D model file format to an application that is not based upon Ultimate 3D you can find chunk descriptions on Ultimate3D.org.

Loading models

As I already said you need only one tiny function to load a model. But since every model object requires an instance of a Game Maker object it can be associated with, you have to set up a few variables first:

file
The model file. I recommend putting all models into a sub-directory called models, but that's up to you. If you have set up textures for particular materials of your model you have to place them in the folder gfx. Ultimate 3D will load them from there automatically then.

password (optional)
If file refers to an encrypted Ultimate 3D model file, this variable has to be set to a string giving the password. Otherwise you do not need to set up this variable.

After setting these variables you can call LoadMesh() to create the model object. The name of this function is another remaining from earlier versions of Ultimate 3D. They supported only *.x files and those were handled as one single mesh. Now LoadMesh() can load models that are made up of lots of meshes with skeletons and animations. Since model objects can be transformed in the same way as primitives (using the variables x, y, z, scalx/y/z and rotx/y/z) you have to put Step() in the step event of the model object again. As usual, you have to call Destroy() in the destroy event.

There are a few more variables to handle the animation. Here's a list of them:

frame
If this variable has a positive value, this frame will be used. If frame is negative, the value will be used as animation speed. So if frame is equal to -1 you get a new frame every step.

first_frame, last_frame
These two variables define a range of frames through which Ultimate 3D cycles if frame is negative. If first_frame is bigger than last_frame the animation will run backward.

To find out which frame of the animation of the model is currently displayed, you can call GetCurrentFrame(). It does not take any parameters and returns the frame that's currently displayed for the model object it's called by. For *.md2 models there's also another variable you already know from primitives. Since *.md2 files do not contain information about their texture, you set up the texture for *.md2 models in the same way as for primitive objects: by setting the variable texture to the index of a texture loaded using LoadTexture(...).


Drawing 2D objects

Since Game Maker can not draw into the view of Ultimate 3D, you have to use Ultimate 3D itself if you want to draw some 2D stuff in there. To do this, Ultimate 3D offers two functions to draw textures and four functions to draw text.

Drawing textures

Let's begin with those that are used to draw textures in 2D to the screen:

DrawTex(
TextureIndex,
X,
Y,
Scaling,
Rotation,
Alpha
)

TextureIndex
This parameter gives the index of the texture that is to be drawn. The texture index is the value you entered as second parameter when loading the texture using LoadTexture(...).

X, Y
The position where the texture is to be drawn in window space (relative to the left top of the first view).

Scaling
The scale that is to be used. 1 means no change, 0.5 means half size and 2 means double size.

Rotation
This parameter can be used to rotate the texture counter-clockwise in degrees. If it's 0 the texture does not get rotated at all.

Alpha
This parameter has to lie in the range from 0 to 255 where 0 means no opacity (fully transparent) and 255 means fully opaque.

If this function does not offer all the functionality you need, there's a second function to draw textures that are more complex. Basically it does the same thing, but there are more possibilities to modify the result.

DrawTexEx(
TextureIndex,
Left, Top,
Width, Height,
X, Y,
ScalingX, ScalingY,
Rotation,
R, G, B, A
)

Left, Top, Width, Height
These parameters identify the part of the texture that is to be used. The parameters should lie in the range from 0 to 1. They are texture coordinates. At the top left the texture coordinates given by left and top will be used, and at the bottom right the texture coordinates left+width and top+height will be used.

ScalingX, ScalingY
The scale that is to be used. 1 means no change, 0.5 means half size and 2 means double size.

R, G, B, A
These parameters give the color that is to be used as the "material" for drawing the texture. The values have to lie in the range from 0 to 255. A gives the opacity, where 0 means no opacity (fully transparent) and 255 means fully opaque.

That's all you need to know to draw textures.

Drawing text

Drawing text is also quite easy. First you set up a font, then you use it to draw a text. To set up the font you have to use the following function:

SetFont(
FontName,
FontIndex,
TextSize,
TextAttributes,
R,G,B,A
)

FontName
This is the name of the file in the fonts directory of Windows that contains the font you want to use. An example for this would be "Times New Roman" or "Arial".

FontIndex
This is the index you want to associate the font with. It works in the same way as the indices of textures. The highest valid index is 99.

TextSize
The size of the text as you know it from office programs.

TextAttributes
The text attributes. 0 means the normal font, 1 means bold, 2 means italic and 3 means bold and italic.

R, G, B, A
The text color. All values have to lie in the range from 0 to 255. A defines the opacity of the text where 0 means no opacity (fully transparent) and 255 means fully opaque.

A call to this function always requires some computing time. For this reason I recommend creating all fonts in the create event of the control object. If you want to change their color that's no problem. You can simply use the following function:

SetFontColor(
FontIndex,
R,G,B,A
)

FontIndex
The index of the font you want to change.

R, G, B, A
The new color of the font.

When you are done with creating the font you can use it to draw text. There are two different ways to draw text. You can either draw standard 2D text, or you can draw a text in the 3D scene. Let's start with the easier thing: drawing text in 2D.

DrawText(
FontIndex,
X, Y,
Text
)

FontIndex
The index of the font that is to be used for drawing the text.

X, Y
The position of the text relative to the top left corner of the first view.

Text
A string that contains the text you want to draw.

Drawing 3D text is a bit more complicated. Basically, you enter a complete transformation (as for primitives) instead of entering only an X and a Y.

Draw3DText(
Text,
FontIndex,
X, Y, Z,
RotX, RotY, RotZ,
ScalX, ScalY, ScalZ
)

Text
A string that contains the text you want to draw.

FontIndex
The index of the font that is to be used for drawing the text.

X, Y, Z, RotX, RotY, RotZ, ScalX, ScalY, ScalZ
The transformation of the 3D text. To get information about the meaning of the single variables have a look at the second sub chapter of the tutorial about primitive objects.

Note that 3D text will always draw over everything else in the scene, including 2D textures and text.


Miscellaneous functions

A few of the functions that belong to Ultimate 3D do not fit into the other tutorials. These are listed here.

The following function does exactly the same thing as mouse_check_button, because this Game Maker function does not work with Ultimate 3D.

MouseCheckButton(
MouseButton
)

MouseButton
This parameter can take the Game Maker constants mb_left, mb_right, mb_middle and mb_none.

Since Ultimate 3D can also be used with Game Maker 5.0 which did not have the function mouse_set_screen_position(...), Ultimate 3D offers functions to replace this function.

This function sets global.mouse_x and global.mouse_y to the mouse position in screen space.

GetMousePos()

This function can be used to reset the mouse position in screen space.

SetMousePos(
X, Y
)

X, Y
The new position of the cursor in screen space.

Apart from the functions to work with the mouse there are two functions to take screenshots. They are also very simple.

TakeScreenshot(
BMPFile
)

BMPFile
A string that contains the ending .bmp. This is the file to which the function will save the screen shot.

The second one allows you to define the part of the screen you want to save:

TakeScreenshotEx(
BMPFile,
Unused,
Left, Top, Right, Bottom
)

Unused
Just ignore this. It was used in a previous version and now it's obsolete.

Left, Top, Right, Bottom
These parameters specify the area of the screen that is to be saved to file. The values are given relative to the left top of the first view.


Advanced functions

The features you have seen so far are pretty cool. You can make nice games with them but the graphics you get will not be really impressive. Besides, you will get problems with the frame rate if you do not know about tricks to optimize your game. With the techniques described in the part for advanced users you can make amazing graphics and giant levels. But you should not move on to the part for advanced users if you haven't understood the stuff in the part for novice users yet. It's the basic knowledge for the advanced stuff.

Some things you will learn in the next tutorials are easier than you may think (for example the parallax and bump mapping), while others are really complicated (e. g. learning how to program shader effects). But for all these things Ultimate 3D offers interfaces that are as easy to use as possible. Most of the work is done by Ultimate 3D, you just have to tell it what you want. And now have fun while learning how to make incredible graphics ;) .


Advanced model management

While working with Ultimate 3D you will come to appreciate the importance of model objects very quickly. In contrast to primitive objects, model objects are extremely flexible. But rendering model objects takes more computing time and models with high triangle counts can greatly slow down the performance of your games. For this reason this tutorial is going to deal with a couple of features that can be used to render models more efficiently.

Managing model files

Sometimes it happens that there are objects in your game that do not exist from the beginning but get created during game play. If these objects were loaded at the time they were needed, it would lead to a short lag. *.md2 files and *.an8 files can take especially large amounts of time to load. For this reason, you can preload models using the following function:

PreloadMesh(
File,
Password
)

File
The file that contains the model you want to preload.

Password
If File is an encrypted Ultimate 3D model file, you can enter the correct password for decryption to this parameter. Otherwise you can put either an empty string or nothing.

Once you have called this function for a model, creating a model object from it will not take any significant amount of time anymore. This way you can avoid a disturbing lag. When you load a model through LoadMesh() and destroy the object after that, you get the same effect. Ultimate 3D will never load the same model twice, because it keeps the model data in memory, even if the objects based on this model get destroyed. This way it avoids endless loading times in a reliable way. Of course, this behavior is not always appreciated. If you switch from one level to another, it would be a waste of memory to still have all models from the previous level loaded. For this reason Ultimate 3D allows you to release models explicitly.

The following function releases the given model file so that it does not take memory anymore and returns true if it succeeds. If there's still an object based on this model when you call this function, it will not release the model, to avoid conflicts. In this case it returns false.

ReleaseModel(
File
)

File
The file that contains the model you want to release.

Exporting to *.u3d files

This is an easy one. There's only one function to export model objects to Ultimate 3D model files and it takes only three parameters. So here you go:

This function exports the model object it's called by to the given file. The resulting Ultimate 3D model file will hold a representation of the model as it was at the time this function was called. The only information that will not be saved is the information that can be set up through variables.

ExportToU3DFile(
OutputFile,
Password,
ExportResources
)

OutputFile
The desired output file for the model object. If the call to the function finishes successfully, this file will contain the Ultimate 3D model file.

Password
The password that is to be used to encrypt the Ultimate 3D model file. If this is an empty string or null, the file will be exported unencrypted. The longer the password is, the more secure is the protection.

ExportResources
This parameter can take true or false. If you pass false the Ultimate 3D model file will not contain the textures and effects. These will be loaded from files as usual. Only the data of the *.ufx files will be exported. If you pass true the file will be a stand alone model file. It will not require any other files to be loaded. All textures and shaders will be included. Note that models that use a render target texture as a texture for one of the materials cannot export their resources.

Of course, no guarantee can be given on the safety of the password encryption mechanism of Ultimate 3D. Specialists would probably be able to decrypt encrypted Ultimate 3D model files. However, it should be safe enough to keep away casual game developers or hackers. Even though I own the code that does the decrypting, I would not be able to decrypt an Ultimate 3D model file without the correct password.

Creating multiple levels of detail

Models can be extremely complex. Their complexity is directly connected to their triangle count. As such, models with a very high triangle count will reduce the performance of your game, so you'll get lower frame rates. For this reason it's important to always use as few triangles as possible. Often the fully detailed version of the model is not needed at a distance. If the object is farther away from the camera, its level of detail can be decreased. This technique is commonly referred to as LoD (level of detail). Ultimate 3D can create LoDs automatically and use them for given distances. The following picture shows an example of a model that has been simplified using the built-in LoD generator of Ultimate 3D.

Illustration not available

The difference between the first and the second version of the model is barely noticeable, but the triangle count has been decreased by 40%. The third version of the model has a triangle count that's decreased by 70%. However, the basic shape of the model is still recognizable. At a very long distance this low level of detail is perfectly suitable, even for high-graphics games. Using LoDs is extremely easy. You simply call one function in which you tell Ultimate 3D about the requested level of detail and the distance at which it is to be used. Here's the syntax:

This function creates LoDs with the given percentage triangle count for the model that calls the function and uses them for the given distance:

CreateLODChain(
CameraDistance1, LoD1,
CameraDistance2, LoD2,
CameraDistance3, LoD3,
CameraDistance4, LoD4,
CameraDistance5, LoD5
)

CameraDistance1, CameraDistance2, ..., CameraDistance5
The distances to the camera the LoD is to be used for. So the original LoD is used at distances from 0 to CameraDistance1, LoD1 is used at camera distances from CameraDistance1 to CameraDistance2 and so on.

LoD1, LoD2, LoD3, LoD4, LoD5
The levels of detail that are to be created. This is a percentage value so 100 means an unchanged triangle count and 0 means the lowest triangle count achievable (usually 3 triangles). The parameters have to fulfill LoD1>LoD2>=LoD3>=LoD4>=LoD5. If one of these values is zero this tells Ultimate 3D to stop creating additional LoDs.

LoDs can greatly increase the frame rate of your game, but they will also require memory and computing time (to be created). Remember that you must not create LoDs for *.md2 files. Creating them for every single frame can take a few minutes. Finally here is a small code example on how to create LoDs for a model. This code should be used at the end of the create event of a model object:

CreateLODChain(100,70,500,40,1000,20);

Creating animations from multiple model files

If you wish, you can create animations from multiple files instead of using file formats with support for skeleton animation (*.3ds, *.x or *.an8) or *.md2 files. To do this you have to add every single frame by calling the following function after creating the object with LoadMesh():

AddFrame(
File,
Password
)

File
The file that contains the frame that is to be added.

Password
If File is an encrypted Ultimate 3D model file, you can pass the correct password for decryption to this parameter. Otherwise you can pass either an empty string or nothing.

Since you would need a lot of frames to get a fluent animation, there's a possibility to enable vertex tweening for animations (vertex tweening is the technique that's enabled by default for *.md2 models). This will only work if all added frames have the same number of meshes, vertices and triangles. The function that enables vertex tweening is called EnableVertexTweening() and does not take any parameters.

Creating primitives from models

As I already said in the chapter about primitive objects, primitives get rendered extremely efficiently. All primitives that use the same material and the same texture get rendered simultaneously. This way the graphics device can process them much more efficiently. And in games it will often happen that you have simple objects that are too complex to be represented by the usual primitive objects, but appear very often. So usually you have to create them as model objects, but that would be inefficient. For that reason there's a function to create primitives from model files. Before creating the primitive object from the model you have to set up two variables:

file
The file that contains the model you want to create a primitive object from.

material
The index of the model material that is to be used for the primitive object.

password (optional)
If file refers to an encrypted Ultimate 3D model file, this variable has to be set to a string giving the password. Otherwise you do not need to set up this variable.

After setting up these variables you have to call CreatePrimitiveFromModel() once to create the primitive object. As with any other primitive object you have to put a call to Step() and Destroy() into the corresponding events. Primitive objects created from models also have the variable texture to set up the texture that is to be used. Note that only the first mesh of the model will be used to create the primitive object. It is useful to use *.an8 files with this function, because when loading *.an8 files Ultimate 3D creates only one mesh per object.

Creating custom backgrounds

The built in system for backgrounds of Ultimate 3D is nice, but it is very limited. If you want backgrounds that change over time or backgrounds, which use some special effects this can not be done with the built in system. In this case a more versatile technique is needed. Objects, which can be manipulated in complex ways are needed. Model objects are objects like this. For this reason Ultimate 3D gives you the possibility to use model objects as background objects.

Every model object can be set to be in background mode. Then this model object will not be effected by fog anymore and it will be covered by all other objects. This makes it seem to be in the background. To reach that it does not become obvious that it is not really in the background you should make it follow the camera, by setting its position to the position of the camera in every step. Enabling background mode is easy.

This function enables or disables background mode for the model object it is called by. It does not work for model objects, which use vertex skinning (because these models need to be rendered at a particular time since they use software vertex processing and backgrounds are rendered at a different time).

SwitchBackgroundMode(
EnableBackgroundMode
)

EnableBackgroundMode
If this parameter is true background mode will be enabled for the model object, otherwise it will be disabled.

Once the first model object gets set to background mode the built in system for backgrounds gets disabled completely. This will look very strange, since all objects will leave trails, so you should better make your model object in background mode so that it covers everything around the camera. You can also use multiple model objects in background mode, but you need to be careful then. Background objects are covered by everything, even by other background objects. Which background object covers which background object depends on the rendering order. For this reason you should make sure that the objects, which should have a lower depth, get created later. Also you should avoid that background objects cover parts of themselves, since this leads to the same problem. The best shapes for background models definitely are spheres and cubes.

If you keep these things in mind you can get very good results with this feature. You can have a sun, that travels over the sky, changing clouds, daytime changing effects, clouds, which are lit in realtime and a lot more. Good backgrounds are a key to impressive graphics. You should use this feature to its full potential.


Materials and special effects

Model materials can be very diverse in Ultimate 3D. Each material can use its own combination of textures, as well as shader effects. The whole shader effect system of Ultimate 3D is directly connected to the material system. Using shader effects means setting up properties for materials. But custom shader effects aren't the topic of this tutorial. Here you will learn the basics you need to set up shaders and other cool effects that work without shaders. You are also going to learn about two great special effect techniques: Environment mapping and the famous per pixel lighting and parallax mapping.

Loading and creating special textures

There are not only the 2D textures you learned about in the very first tutorial. There are also two other kinds of textures. The first type is a height map. Height maps are gray scale pictures that contain height information. White means a very high pixel and black means a very low pixel. It's important that you understand what a height map is, because you will need them for many effects. For this reason I'm going to use an example to explain it.

Have a look at the next texture. It shows a simple brick wall. Since the picture at the left defines the colors of the pixels which is influenced by the diffuse lighting, it is referred to as a diffuse map. To the right of it you see the height map.

Illustration not available

Note how the gaps between the stones are black in the height map because they are lower than the rest. The stones are white, because they are higher. But some stones aren't built in that well and for that reason they are darker in the height map. They are lower than the rest.

So what are height maps good for? They are incredibly useful because they can be used to give information about the surfaces of objects. This way you can simulate this structure, which makes the graphics of your game much more detailed and realistic. That's what per pixel lighting and parallax mapping do. Unfortunately the height map itself is useless for these effects. They need another thing called normal map. But that's something you do not need to care about. Ultimate 3D does it for you. You just have to use another function to load height maps:

This function loads a height map from the given file and computes a normal map for it. A copy of the height map gets saved to the alpha channel of the resulting texture.

LoadHeightMap(
TextureIndex,
HeightMapFile,
HeightFactor
)

TextureIndex
The index the texture is to be associated with. You should be familiar with this parameter from the description of the function LoadTexture(...).

HeightMapFile
The name of the file that contains the height map (including the path and the extension).

HeightFactor
This is the only new parameter. When computing a normal map Ultimate 3D needs to know how high the texture is relative to the texture width and height. One could say that it defines the meaning of the color white in the height map. A nice value for this parameter is 16.

There's a second type of textures you do not know yet. This one is a bit strange. At first you will not know what to do with it, but that will change very soon. These textures are called cube maps. They aren't made up of only one 2D picture but of six quadratic pictures. In principle it works in the same way as sky cubes (the background images). You have six images that are put together to form a cube. In this case you do not use 2D texture coordinates but 3D texture coordinates. You'll see how to do this later and learn what they're good for. Loading cube maps is similar to loading sky cubes. There's only one function to do it and it takes only one string to identify all six files:

This function creates a cube texture from the given six files and associates it with the given index.

LoadCubeTexture(
FileScheme,
TextureIndex,
CubeWidth
)

FileScheme
The scheme of the file. This string has to contain an "*". It will be replaced by the words "Left", "Right", "Top", "Bottom", "Front" and "Back". So if you write "gfx/CubeMap*.png" Ultimate 3D would search for the files "CubeMapLeft.png", "CubeMapRight.png", "CubeMapTop.png" and so on.

TextureIndex
The index the texture is to be associated with. You should be familiar with this parameter from the description of the function LoadTexture(...).

CubeWidth
The size each texture will be scaled to (if it does not have this size). This parameter has to be a power of two.

Textures do take a lot of memory, so it's not good to have too many textures loaded at the same time. To give you the possibility to get rid of the loaded textures there's the function ReleaseTexture(...). It deletes a texture from memory and sets the index it was associated with back to its original state. Here's its description:

ReleaseTexture(
TextureIndex
)

TextureIndex
The index of the texture you want to release.

Note that cube textures are not supported by some really old graphics devices. For this reason there is the function GetCubeMapSupport() which returns true if cube maps are supported or false if they aren't.

Material indices

Many functions described below take an argument called MaterialIndex. I will explain what this means once here to avoid endless repetitions. As you should know from your modeling experience, a model can use different materials for the different surfaces of the model. Most modeling programs offer the possibility to give each material a name. This is very useful, because if you are using a modeling program that does not offer this possibility you will have to guess the index of the material. I recommend giving your materials names that make sense. Do not just call them material01, material02 and so on. It will make your code easier to read.

*.an8 files and *.3ds files contain information about the names of the materials. *.md2 files can have only one material which is always called "material_md2". *.x files do not contain the names of the materials. These materials get called "material00" to "materialXX". The great thing about material names is that you do not need to care about the meaning of material indices if you know their name, because you can simply use a function to get it.

This function returns the index of the material with the given index or the index of the first material (0) if no material with this name exists. It is not case sensitive.

GetMaterialIndex(
MaterialName
)

MaterialName
The name of the material you want to retrieve an index from. This parameter is a string (like "BrightWood" or "Metalic material 2").

Sometimes it also happens that you want to do something for every single material of a model. In this case you can simply use a for-loop. To get the number of materials in the model you can use GetMaterialCount().

Changing the material color

As with primitive objects you can change the color of the materials of models whenever you want. Again there are two functions for this purpose, one to set the diffuse part of the material and one to set the emissive part. Here they are.

This function changes the diffuse color of the given material of the object it is called by.

SetModelMaterial(
MaterialIndex,
R, G, B, A
)

MaterialIndex
The index of the material you want to change the diffuse color for.

R, G, B, A
The new diffuse color of the material. The values for these parameters should lie in the range from 0 to 255.

This function changes the emissive color of the given material of the object it is called by.

SetModelMaterialEmissive(
MaterialIndex,
R, G, B
)

MaterialIndex
The index of the material you want to change the emissive color for.

R, G, B
The new emissive color of the material. The values for these parameters should lie in the range from 0 to 255.

In addition you can set up a specular color and a specular power for model materials. Specular highlights make objects look glossy, by brightening them up strongly at the position where they reflect the light source. They are good for plastic materials and for wet surfaces. Before we can get to the function, which sets up the specular color for a model material you need to learn about three additional variables for light objects, which have not been mentioned in the chapter about light sources, since they belong into the advanced part of this help file. Here is their description.

spec_r, spec_g, spec_b
These variables specify the specular color of a light source. All of them should be in the range from 0 to 255, but they can also be higher or lower. They can be changed at any time (if Step() is at the place it should be at). This color gets multiplied by the specular color set up for the model material to determine the actual specular color. These variables allow you to control which light sources result in specular highlights and which do not, how strong the specular highlights are and which color they have.

Once you have set up positive values for these variables in some light sources it makes sense to call the following function for some models. It is not necessary to use this function though. Ultimate 3D can load the specular color and the specular power from *.u3d, *.an8, *.x, *.3ds and *.ms3d files so you can simply set these values in your modeling program.

This function sets up a new specular color and specular power for the given material of the model object the function is called by.

SetModelMaterialSpecular(
MaterialIndex,
R, G, B,
Power
)

MaterialIndex
The index of the material you want to change the specular color and specular power for.

R, G, B
The specular color, which is to be set up. These values should lie in the range from 0 to 255.

Power
The specular power. This is a power in the mathematical sense. Right after its computation the strength factor for the specular highlight is as matte as the diffuse lighting. Then it gets raised to the given power. This way the specular highlight gets harder. The higher the power is the harder is the specular highlight. A good value for hard specular highlights is 32.

Whether specular highlights look good is a matter of taste. In my opinion they do not. They make things seem artificial and unrealistic. The only materials they should be used for are wet materials and plastics. On everything else they look quite bad. For reflective materials you should better use environment mapping. So read on.

Environment mapping

Weird name, great technique. In principle, the name describes it very well. Environment mapping is a technique in which you take pictures of the environment and map them interactively onto an object. When using environment mapping you do not have static texture coordinates anymore. Instead the graphics device calculates them in every single frame based on the position of the vertex, the orientation of the triangle and the position of the camera.

There are two different kinds of environment mapping.
The first one is called spherical environment mapping or, in short, sphere mapping. For this technique you need a sphere map. That's a texture that contains a picture of the environment as it would be reflected by a chrome sphere. On the Ultimate 3D website you'll find a program to create these sphere maps.
The other technique is called cubical environment mapping or, in short, cube mapping. For this technique you need the cube maps I introduced you to in one of the previous subchapters.

Enabling environment mapping is really easy. You just have to call one function in which you tell Ultimate 3D what material you want to use environment mapping for and which texture contains the environment map:

This function sets up environment mapping for the given material of the object that calls this function.

SetMaterialEnvironmentMap(
MaterialIndex,
TextureIndex
)

MaterialIndex
The index of the material you want to apply environment mapping to.

TextureIndex
The index of the texture that contains the environment map. If this is the index of a normal texture, spherical environment mapping will be used, otherwise cubical environment mapping will be used. If you want to disable environment mapping for this material, pass -1.

When using an environment mapping you should either use a very dark image as environment map or you should set the material of the model to a very dark color, because the environment map gets applied by adding the color of the texel (pixel of a texture) in the environment map to the previous color of the pixel. If you do not like this you can change it using the function SetMaterialStageTextureOperation(...) (see below).

Bump mapping and parallax mapping

Now it gets really interesting. This technique is unbelievably popular because it looks so great. In fact I do not like the term bump mapping because it can refer to many many different techniques. What I'm talking about here is also known as per pixel lighting, normal mapping or dot3 bump mapping. It's a technique to light materials on a per pixel base, simulating their surfaces using height maps. Parallax mapping is a technique where a height map is used to warp a texture depending on the camera position. This makes it seem really 3-dimensional. It creates a feeling of real depth. To give you an idea of what this means, here's a little screen shot:

Illustration not available

It shows a model that's lit by three light sources (represented by the small spheres). The model is made up of no more than 12 triangles. The effect of real depth and detailed lighting is created by the special effects. This can be seen best on the bottom side of the room. The green light source lights only the left side of the bricks at the right and only the right side of the bricks at the left. Also, you can not see the backside of the bricks, due to the parallax mapping. Instead, the front sides of the bricks seem much bigger.

Parallax mapping and bump mapping are extremely complex shader effects. It requires a lot of math and low-level programming to realize them (especially when using Direct 3D 8.1). Fortunately you are using Ultimate 3D ;) . You have to do no more than calling one function. The rest of the work is done by Ultimate 3D. So here you go:

This function enables parallax and/or bump mapping for the given material of the object it's called by.

ApplyParallaxAndBumpMapping(
MaterialIndex,
HeightMapIndex,
ParallaxFactor,
LightSourceID1,LightSourceID2,LightSourceID3
)

MaterialIndex
The index of the material the effect is to be applied to.

HeightMapIndex
The index of the height map that was loaded using LoadHeightMap(...) (alternatively you can use LoadTexture(...) to load a precomputed normal map with the height map in the alpha channel). You can put -1 for this parameter if you do not want to simulate a surface structure, but that would be a waste of computing time and the parallax mapping would not make sense at all.

ParallaxFactor
This parameter tells Ultimate 3D how strong the parallax effect should be. This depends on how the texture is mapped onto the model. Usually the value is quite small (for the screenshot above I used 0.02). If you enter too big of a value for this parameter, the fact that the texture is only warped will become obvious and everything will look rather weird. So be careful with this. If you pass 0 for this parameter no parallax mapping will be used, which takes less computing time.

LightSourceID1, ..., LightSourceID3
The number of light sources that can be used for per pixel lighting is limited due to the limitations of shader model 1.4. It's really not possible to implement more light sources on per pixel base with this shader model. Implementing three of them was already difficult enough. For that reason you have to enter the Game Maker IDs of the light source objects you want to use for the effect here. LightSourceID1 can be the ID of a directional light or a point light. The other two can only be point lights. The fewer light sources you use, the less computing time will be required. If you do not enter any light source, bump mapping will be disabled.

As I already said these two effects are really complex. Ultimate 3D is hiding a lot from you here to make it easier for you. When you read on to the chapter about shader programming, you will get a better understanding of what it actually does. For this reason you have to know about a few things you must not do when using this effect.
First of all you should know that per pixel specular lighting is not supported as a built in feature. So if you enable per pixel lighting through ApplyParallaxAndBumpMapping(...) all specular highlights will disappear.
You can not use environment mapping or other multi texturing effects simultaneously with parallax and bump mapping, nor can you use other shader effects for this material. Also, applying parallax and bump mapping reduces the quality of the normal vectors of the model, which are needed for per vertex lighting. Other components of the same model, which do not use per pixel lighting may be lit worse and the quality of environment mapping effects would be worse, too.
Despite you can not apply parallax or bump mapping to objects which use vertex tweening (that is always the case for *.md2 files). Also, models which use vertex skinning should not have more than 28 bones in case they use bump mapping or parallax mapping. All bones with higher indices will not get transformed correctly.

Finally you have to know that not every graphics device supports parallax mapping and bump mapping. These effects are shader effects and they require pixel shader model 1.4. For that reason you have to call GetSupportedPSVersion() first. This function returns the highest supported pixel shader version as a decimal number. This can be 1.0, 1.1, 1.2, 1.3 or 1.4. So to find out whether parallax mapping and bump mapping are supported you have got to write:

if(GetSupportedPSVersion()>=1.4){
// Parallax mapping and bump mapping are supported
}
else{
// Parallax mapping and bump mapping are not supported
}

Multi texturing functions

A material does not have to use only one texture. You've already seen two examples of multi texturing. When you use environment mapping, a material can use a diffuse map and an environment map at the same time. When you use parallax mapping or bump mapping, a material uses a diffuse map and a height map at the same time. Depending on the graphics device, a PC can use up to eight textures simultaneously. The function GetSimultaneousTextureCount() returns how many textures can be used simultaneously on this PC. In the worst case it's only one.

Multi texturing can be used for cool effects and shader programming is not imaginable without multi texturing. One important application of multi texturing is light mapping, a technique where you create a texture that describes the lighting of an object using a program such as Deled and map it onto the object. This way you can get really detailed but very static lighting without needing much computing time.

But how does it work? In principle it's quite simple. Depending on the graphics device you have up to eight so-called texture stages. Each texture stage uses a texture, a set of texture coordinates of the mesh and a texture operation. The most complicated thing about this is the texture operation. It defines how the texture that is used by this stage should be applied to the result of the previous stages. For example the pixel color could be added (that's how it's done when using environment mapping) or multiplied. These are the two most common texture operations. To set up these three properties of the texture stages there are three functions:

This function sets the texture, used by the given texture stage of the given material, for the object the function is called by.

SetMaterialStageTexture(
MaterialIndex,
TextureStage,
TextureIndex
)

MaterialIndex
The index of the material you want to modify.

TextureStage
The index of the stage you want to modify. This parameter can take values in the range from 0 to 7.

TextureIndex
The index of the new texture that is to be used for the texture stage.

This function changes the texture coordinate set, used by the given stage of the given material, for the object that calls it.

SetMaterialStageTexCoord(
MaterialIndex,
TextureStage,
TextureCoordinateSetIndex
)

MaterialIndex
The index of the material that is to be modified.

TextureStage
The texture stage that is to be modified. This parameter can take values in the range from 0 to 7.

TextureCoordinateSetIndex
In the tutorial about mesh manipulation you'll learn that a mesh does not need to have only one set of texture coordinates, but that it can have up to eight multi dimensional texture coordinate sets. This parameter gives the index of the texture coordinate set that is to be used for this texture stage. By default this parameter is 0 (the first texture coordinate set). Depending on the number of texture coordinate sets you have created for the mesh, this parameter can be in the range from 0 to 7. Also, you can enter -1 to get environment mapping texture coordinates, -2 to get environment mapping texture coordinates calculated with another technique, or -3 to get the screen coordinates of the vertex as texture coordinates.

This function changes the texture operation, used by the given stage of the given material, for the object that calls this function.

SetMaterialStageTextureOperation(
MaterialIndex,
TextureStage,
TextureOperation
)

MaterialIndex
The index of the material that is to be modified.

TextureStage
The texture stage that is to be modified. This parameter can take values in the range from 0 to 7.

TextureOperation
As I already said, the texture operation defines how the texture is applied to the result of the previous texture stages. The first texture stage always multiplies the diffuse color, calculated using the material information and the light sources, with the texture color and multiplies the result by two. The second stage takes the color that is the result of the previous stage, and combines it with the color of the new texture using the texture operation you've set up. The same is done for the other stages. Each integer value you enter for this parameter corresponds to a texture operation. Here's a list of the possible values and their meanings:
1: This texture operation multiplies the current color with the texture color: Texture*Current
2: This texture operation multiplies the current color with the texture color and multiplies the result by two: Texture*Current*2
3: This texture operation multiplies the current color with the texture color and multiplies the result by four: Texture*Current*4
4: This texture operation adds the current color to the texture color: Texture+Current
5: This texture operation takes the current color and subtracts 128 from the r, g and b data. Then it adds the color of the texture: Texture+Current-128
6: This texture operation takes the current color and subtracts 128 from the r, g and b data. Then it adds the color of the texture and multiplies the result by two: (Texture+Current-128)*2
7: This texture operation takes the current color and subtracts it from the color of the texture: Texture-Current
8: This texture operation takes the current color and calculates a dot product with the color information scaled into the range from -1 to 1: RGB=( (Texture.R-128)/128*(Current.R-128)/128 + (Texture.G-128)/128*(Current.G-128)/128 + (Texture.B-128)/128*(Current.B-128)/128 )*255

When you use multi texturing the alpha component will always use a multiplicative texture operation (Final.a=Texture.a*Current.a). So this means that you can use up to eight textures to do the alpha blending. This can be useful in some cases, since it allows you to change the alpha of a material in every single stage.

Multi texturing can be used for nice effects, so play around with it a bit. But even more importantly, it gives you the ability to use shader effects. So hang on, in the next tutorial it will become really advanced. You'll learn how to use your own shader effects with Ultimate 3D. Writing custom shaders is difficult but you should give it a try. There are many good tutorials on the Internet and once you know how to write shaders nothing can stop you. They can be used for about any imaginable effect. Basically a pixel shader takes the place of the texture operation. This means that you can use lots of different arithmetic operations to do computations with the color values of the different textures. You can realize any effect you want to make. It's the ultimate solution for all your special effect needs ;) .


Using shader effects

If you have played some current games you probably already know the term shaders. A simple definition is that shaders are something you can create visual effects with. But this answer is not precise enough, so let's hear the detailed one. There are two different kinds of shaders: vertex shaders and pixel shaders. Vertex shaders can be used alone, while pixel shaders are always used in combination with vertex shaders.

Usually Ultimate 3D uses the so-called fixed function pipeline to render models. The fixed function pipeline is part of Direct3D. It computes the vertex data, meaning that it calculates the vertex position in screen space, the vertex lighting, and a couple of other bits of information. Then the computed information gets passed on and is used to draw the single triangles pixel by pixel. When you are using shaders you do not use the fixed function pipeline anymore, but the programmable function pipeline. This means that the way Ultimate 3D computes the vertex data and the pixel colors can be changed in any way.

Vertex shaders are very short programs that get executed for every single vertex in every single frame to compute their data. This way you can change the geometry of objects without the mesh manipulation functions of Ultimate 3D, and you can change the way textures get mapped on the object by modifying the way the final texture coordinate gets calculated. Basically, you can realize a custom lighting engine. Pixel shaders are even shorter programs that get executed for every single pixel in every single frame to compute their data. They can retrieve the texture coordinates that have been calculated by the vertex shader and they can access textures. The result of the computations in the pixel shader is always only one single color value that will be used as color for the pixel. Note that shaders can be created and edited in any text editor, e.g. Notepad. The following picture illustrates how they work:

Illustration not available

First the T&L or the Vertex Shader processes the vertices. Then these vertices get passed to the rasterizer. It determines which pixels need to be rendered for the triangles of the mesh. Finally the fixed function pipeline or the pixel shader calculates the pixel color and writes it to the screen. Actually, there are a few more steps in the rendering pipeline, like the fog being applied after the pixel shader, but mentioning those in the illustration would make it more complicated than necessary.

Just to give you an idea of how a shader may look, here's an example. It shows a very simple shader. It just renders the object applying its material color and its first texture. A shader effect is always made up of two or three files. A vertex shader file (*.vsh) has to be available; a pixel shader file (*.psh) is optional. There also has to be an Ultimate 3D effects file (*.ufx) that contains information about how the vertex shader and the pixel shader need to be used by Ultimate 3D.

Here's the vertex shader:

// This has to be the first line in every vertex shader
vs.1.1

// This code line transforms the vertex position in mesh space into
// screen space. m4x4 is the command that does this. oPos is the name
// of the output register the shader has to save the screen space
// position to. The vertex position in mesh space is always saved in
// the register v0. c0 is a variable that gets set before the model
// gets rendered, a so called constant register. Constant registers get
// defined by the *.ufx file. It contains the beginning of the
// transformation matrix that is needed to calculate the screen space
// position.
m4x4 oPos, v0, c0

// This code line simply passes the material color, by copying it from
// the fifth constant register to the diffuse color output register.
mov oD0, c4

// This code line passes the first texture coordinate of the mesh by
// copying it from the seventh vertex data register to the texture
// data output register.
mov oT0, v7

Here's the pixel shader:

// This can be ps.1.0, ps.1.1, ps.1.2, ps.1.3 or ps.1.4. If you
// use higher pixel shader versions less PCs will support the effect,
// but you will have more possibilities. This example uses ps.1.4
// because the code is simpler in this version.
ps.1.4

// Use the first texture coordinate (t0), to get a texture color from
// the first texture (r0) and save the result to the temporary register
// r0. Comment: The r0 has two purposes here: It identifies the texture
// (it would be r1 for the second texture) and it is used as output
// register.
texld r0, t0

// Multiply the texture color (r0) by the material color (v0) and save
// the result to the output register (r0). The value of r0 at the end
// of the shader code will be used as color for the pixel.
mul r0, r0, v0

And here's the Ultimate 3D effect file:

// This has to be the first line of every Ultimate 3D effect file
Ultimate3DEffectFile 1.0

// Use the vertex shader (assuming that it's saved to this file)
VertexShaderFile effects/VertexShader.vsh
// Use the pixel shader. As I already said the vertex shader could
// also be used without a pixel shader, anyway it's used here.
PixelShaderFile effects/PixelShader.psh

// c0 to c3 have to contain the correct transformation matrix in the
// vertex shader
VSConstant 0 VS_CONST_WORLD_VIEW_PROJ_TRANS_1
VSConstant 1 VS_CONST_WORLD_VIEW_PROJ_TRANS_2
VSConstant 2 VS_CONST_WORLD_VIEW_PROJ_TRANS_3
VSConstant 3 VS_CONST_WORLD_VIEW_PROJ_TRANS_4
// c4 has to contain the material color in the vertex shader
VSConstant 4 VS_CONST_MAT_DIFFUSE

It's only seven lines of shader code and two of them are always the same. So, as you can see, shader programming is not too hard. However, it requires a little bit of mathematical knowledge. Some of this knowledge can be read up in the tutorial about the math functions of Ultimate 3D. If you skip this tutorial that's no problem. But one day you may notice that you have become a better programmer and that you feel ready for it now. And then you should come back to expand your horizon.

In modern games you see practically nothing that does not use shader effects. They are extremely important for modern graphics. Once you are able to program shaders you have the ultimate solution for all your special effect needs. Just to give you an idea of what you are missing when you skip this chapter, here are a few examples for techniques that can be realized with shaders easily:
- Reflection and refraction mapping for perfect water
- Post screen shaders for effects such as HDR and motion blurring
- Texture perturbation for great weather effects and extremely realistic fire
- Post screen warp effects
- And many more. There's probably no one who knows about all of the possible techniques.

Also, the Ultimate 3D demo contains a couple of cool shader effects. Unfortunately it would take too long to write a complete introduction into shader programming here. Fortunately other people have done this before, namely Wolfgang Engel. He has written great tutorials to get started with shader programming.

This is a great tutorial about programming vertex shaders by Wolfgang Engel.

This is another tutorial by Wolfgang Engel about pixel shader programming (note that you can not use pixel shaders without vertex shaders).

And here's a third tutorial by him that gives a nice example on shader programming by showing how to realize different per pixel lighting techniques with it.

You should not miss the Microsoft vertex shader reference and the pixel shader reference, either. It's not a tutorial but a place where you can look up anything about shader programming and where you get really reliable information.

Once you've read and understood Wolfgang Engel's tutorials, you should have the theoretical base you need to program shaders. Anyway learning by doing is the best way to learn shader programming. So just play around with it. Not every shader you are making has to fulfill some purpose as long as you learn something by writing it. If you have any questions about shader programming you can ask them at the Ultimate 3D Community for sure. To program shaders that are compatible with Ultimate 3D you need one piece of information: you need to know which vertex shader input data register contains what kind of data. So here's a little list:
v0 - Position in mesh space
v1 - Skinning weight data for models with skinning, second position for models with tweening
v2 - Four matrix indices for models with skinning, second normal for models with tweening
v3 - (First) normal vector
v7 to v14 - Up to eight one- to four-dimensional texture coordinates

Introduction to the *.ufx file format

Shader programming is difficult enough, so it would be hard if you had to care about calculating the values for the shader constants as well. Because of this, I invented the *.ufx file format. It's a file format that manages shader effects, including all information that belongs to them. If you are using a *.ufx file for your shader effect you will not have to do anything after loading it. You do not need to calculate a big amount of shader constants because you can choose from a long list of commonly used shader constants for every single constant register in the *.ufx file. And the constants that are actually constant for every usage of the shader can be set up in the *.ufx file or using the def instruction of the shader. You can also define alternative effects that are to be used if the required shader version is not supported (these are also called fallback techniques).

So you see that the *.ufx file format can save you a lot of work. Unfortunately it's not that user-friendly currently. But do not worry, I'll write an effect composer tool when I get the time. In this tool there'll be a drag and drop system to create *.ufx files and a graphics output that shows you the result immediately.

Let's get to the explanation of the *.ufx file format. Every *.ufx file must be made up of a couple of lines. Each line can contain exactly one chunk. A chunk always begins with a chunk name. Then a couple of parameters for the chunk can follow, which can be strings or numbers. Note that you can put in comments wherever you want by writing "//". The first chunk that should be in the *.ufx file is:

Ultimate3DEffectFile %version

version
Currently there are the versions 1.0 and 1.1. Every 1.0 effect file is also a valid 1.1 shader and will get compiled accordingly. So you should always write 1.1 here.

Next you have to define the shader files that are to be used for the effect (they should be *.vsh and *.psh files). There are two different chunks for this:

VertexShaderFile %FileNameWithPath

FileNameWithPath
The file name and the path of the shader file relative to the folder that contains the game executable, e.g. effects\EffectVertexShader.vsh.

If you have been working with the Ultimate 3D effect file format 1.0 you may be wondering where the TextureCoordinateSize parameters have gone. The answer is that Ultimate 3D determines them automatically now, depending on the vertex data available in the model. This makes the shader system more fail-safe. If you try to apply a shader effect for a model that does not have the required vertex data an error message will pop up. After you've defined the vertex shader you can define a pixel shader:

PixelShaderFile %FileNameWithPath

FileNameWithPath
The file name and the path of the shader file relative to the folder that contains the game executable, e.g. effects\EffectPixelShader.psh.

Note that you do not have to set up a pixel shader. Next you can configure the constants that need to be set up for the shaders. There's only one chunk for this but it can take a long list of parameters. Understanding what the parameters mean requires an understanding of matrix and vector calculations. Here's the constant chunk for vertex shaders:

VSConstant %ConstantIndex %ConstantIdentifier %ManuallySettableValues

ConstantIndex
The index of the constant register this constant is to be associated with. This can be a value in the range from 0 to 95.

ConstantIdentifier
What follows is a long list of all constant registers Ultimate 3D can compute for you automatically. All matrices or vectors that refer to the direction or the position of other objects are already transformed to mesh space! It's important to keep this in mind. Transforming directions or positions into mesh space before passing them to the vertex shader is much more efficient than doing it for every single vertex in the vertex shader. And usually the position in world space is not needed. In case you should need it anyway you can get it by transforming it back using the mesh to world space transformation matrix. The exception from this are models with vertex skinning, because those have many different mesh to world space transformations. For those the constant information is passed in world space and the world to mesh space matrix and the mesh to world space matrix are always the identity matrix. Color information is always passed using values in the range from 0 to 1 instead of values in the range from 0 to 255.
VS_CONST_MANUAL_SETTABLE: This parameter tells Ultimate 3D that it's supposed to set the constant to the values given in the next parameter (ManualSettableValues).
VS_CONST_WORLD_VIEW_PROJ_TRANS_1: The first column of the product of the world, view and projection transformation which results in the mesh to projection space transformation matrix.
VS_CONST_WORLD_VIEW_PROJ_TRANS_2: The second column of the same matrix.
VS_CONST_WORLD_VIEW_PROJ_TRANS_3: The third column of the same matrix.
VS_CONST_WORLD_VIEW_PROJ_TRANS_4: The fourth column of the same matrix.
VS_CONST_WORLD_VIEW_TRANS_1: The first column of the product of the world and view transformation which results in the mesh to view space transformation matrix.
VS_CONST_WORLD_VIEW_TRANS_2: The second column of the same matrix.
VS_CONST_WORLD_VIEW_TRANS_3: The third column of the same matrix.
VS_CONST_WORLD_VIEW_TRANS_4: The fourth column of the same matrix.
VS_CONST_WORLD_TRANS_1: The first column of the mesh to world space matrix.
VS_CONST_WORLD_TRANS_2: The second column of the mesh to world space matrix.
VS_CONST_WORLD_TRANS_3: The third column of the mesh to world space matrix.
VS_CONST_WORLD_TRANS_4: The the fourth column of the mesh to world space matrix.
VS_CONST_INV_WORLD_TRANS_1: The first column of the world to mesh space matrix.
VS_CONST_INV_WORLD_TRANS_2: The second column of the world to mesh space matrix.
VS_CONST_INV_WORLD_TRANS_3: The third column of the world to mesh space matrix.
VS_CONST_INV_WORLD_TRANS_4: The fourth column of the world to mesh space matrix.
VS_CONST_VIEW_TRANS_1: The first column of the world to view space matrix.
VS_CONST_VIEW_TRANS_2: The second column of the world to view space matrix.
VS_CONST_VIEW_TRANS_3: The third column of the world to view space matrix.
VS_CONST_VIEW_TRANS_4: The fourth column of the world to view space matrix.
VS_CONST_PROJ_TRANS_1: The first column of the view to projection space matrix.
VS_CONST_PROJ_TRANS_2: The second column of the view to projection space matrix.
VS_CONST_PROJ_TRANS_3: The third column of the view to projection space matrix.
VS_CONST_PROJ_TRANS_4: The fourth column of the view to projection space matrix.
VS_CONST_MATRIX_STACK: This constant is needed for models with vertex skinning. It differs from all other constants because it does not use only one constant register, but all constant registers that are behind the defined constant register. The constant register it's set up for will contain the values x=number of passed matrices, y=constant register of first matrix, z=3, w=1. These values are needed to convert the matrix indices of v2 to constant register offsets. All constant registers behind VS_CONST_MATRIX_STACK will be overwritten with transposed 4x3 mesh to world space matrices of the single bones. You can use m4x3 to transform vectors with them.
VS_CONST_CAMERA_POS: The camera position (in mesh space).
VS_CONST_MAT_AMBIENT: The ambient color of the material.
VS_CONST_MAT_DIFFUSE: The diffuse color of the material.
VS_CONST_MAT_EMISSIVE: The emissive color of the material.
VS_CONST_LIGHT_AMBIENT_COLOR: The color of the ambient light.
VS_CONST_LIGHT0_COLOR: The light color of the first light source set up for this shader (see below).
VS_CONST_LIGHT1_COLOR: The light color of the second light source set up for this shader (see below).
VS_CONST_LIGHT2_COLOR: The light color of the third light source set up for this shader (see below).
VS_CONST_LIGHT_DIRECTIONAL_DIRECTION: The direction of the directional light source set up as first light source (see below).
VS_CONST_LIGHT_POINT0_POSITION_INV_RANGE: This parameter will make Ultimate 3D pass the mesh space position of the first light source to the x-, y- and z-components of the shader constant and the reciprocal of the light range to the w-component.
VS_CONST_LIGHT_POINT1_POSITION_INV_RANGE: Same as above but for the second light source.
VS_CONST_LIGHT_POINT2_POSITION_INV_RANGE: Same as above but for the third light source.
VS_CONST_FOG_DISTANCE: This parameter will make Ultimate 3D pass the start distance of the fog to the x-component, the end distance of the fog to the y-component, the reciprocal start distance to the z-component and the reciprocal end distance to the w-component.
VS_CONST_CURRENT_TIME: This parameter will make Ultimate 3D pass the time that has elapsed since the system was started in seconds to the x-component, the time that has elapsed since Ultimate 3D was initialized to the y-component, 0 to the z-component and 1 to the w-component.
VS_CONST_CURRENT_FRAME: This parameter will make Ultimate 3D pass the fractional part of the current frame value to the x-component and one minus the content of the x-component to the y-component. The z-component will contain 0 and the w-component will contain 1. This constant is meant to be used to implement vertex tweening.

ManuallySettableValues
This parameter is only required if you passed VS_CONST_MANUAL_SETTABLE for the previous parameter. In this case you have to give four real values here that will be set up as values for the x-, y-, z- and w-component of the shader constant.

Setting up pixel shader constants is easier, because you do not have that large a selection of different constants. Pixel shaders work on a lower level than vertex shaders and can take at most eight shader constants. That's why the choice is smaller here. Here's the chunk that's required to set up pixel shader constants:

PSConstant %ConstantIndex %ConstantIdentifier %ManuallySettableValues

ConstantIndex
The index of the constant register you want to set up. This value can be in the range from 0 to 7.

ConstantIdentifier
As I already said there aren't that many different identifiers as for vertex shader constants. Here's a list of them:
PS_CONST_MANUAL_SETTABLE: This parameter tells Ultimate 3D that it's supposed to set the constant to the values given in the next parameter (ManualSettableValues).
PS_CONST_LIGHT_AMBIENT_COLOR: The color of the ambient lighting.
PS_CONST_LIGHT_AMBIENT_COLOR_MATERIAL: The product of the color of the ambient light and the ambient color of the material.
PS_CONST_LIGHT0_COLOR: The color of the first light source that's set up for this effect (see below).
PS_CONST_LIGHT1_COLOR: The color of the second light source that's set up for this effect (see below).
PS_CONST_LIGHT2_COLOR: The color of the third light source that's set up for this effect (see below).
PS_CONST_LIGHT0_COLOR_MATERIAL: The color of the first light source that's set up for this shader multiplied by the diffuse color of the material.
PS_CONST_LIGHT1_COLOR_MATERIAL: The color of the second light source that's set up for this shader multiplied by the diffuse color of the material.
PS_CONST_LIGHT2_COLOR_MATERIAL: The color of the third light source that's set up for this shader multiplied by the diffuse color of the material.
PS_CONST_MAT_AMBIENT: The ambient color of the material.
PS_CONST_MAT_DIFFUSE: The diffuse color of the material.
PS_CONST_MAT_EMISSIVE: The emissive color of the material.

ManuallySettableValues
This parameter is only required if you have passed PS_CONST_MANUAL_SETTABLE for the previous parameter. In this case you have to give four real values here that will be set up as values for the x-, y-, z- and w-component of the shader constant.

These are all chunks you need to create a shader effect. But what about fallback techniques? They're actually quite easy. You just have to write:

FallBackTechnique{
}

Between the two brackets you can put in one VertexShaderFile chunk, one PixelShaderFile chunk, and as many VSConstant and PSConstant chunks as you want.

Since that was a lot of definitions here's one example for a valid *.ufx file (assuming that the shader files it refers to exist).

// Here's an example *.ufx file
Ultimate3DEffectFile 1.0

// The pixel shader below does use pixel shader model 1.4
VertexShaderFile shaders/ShaderEffect1_4.vsh
PixelShaderFile shaders/ShaderEffect1_4.psh

VSConstant 0 VS_CONST_WORLD_VIEW_PROJ_1
VSConstant 1 VS_CONST_WORLD_VIEW_PROJ_2
VSConstant 2 VS_CONST_WORLD_VIEW_PROJ_3
VSConstant 3 VS_CONST_WORLD_VIEW_PROJ_4

VSConstant 4 VS_CONST_MAT_DIFFUSE

// The shader needs these values to calculate something. They could be also set up with the def instruction of the vertex shader.
VSConstant 5 VS_CONST_MANUAL_SETTABLE 3.14 6.28 -1 5

// This fallback technique will be used if pixel shader model 1.4 is not supported
FallBackTechnique{
// The pixel shader below uses pixel shader model 1.2
VertexShaderFile shaders/ShaderEffect1_2.vsh
PixelShaderFile shaders/ShaderEffect1_2.psh
VSConstant 0 VS_CONST_WORLD_VIEW_PROJ_1
VSConstant 1 VS_CONST_WORLD_VIEW_PROJ_2
VSConstant 2 VS_CONST_WORLD_VIEW_PROJ_3
VSConstant 3 VS_CONST_WORLD_VIEW_PROJ_4

VSConstant 4 VS_CONST_MAT_DIFFUSE

VSConstant 5 VS_CONST_MANUAL_SETTABLE 3.14 6.28 -1 5
}

Applying a shader

That was a lot of theory. Now let's get back to the practical use of shader effects. Setting up shader effects is pretty easy. Every material of a model can have its own shader effect. To set it up you need only one function:

This function loads the given *.ufx file and uses it for the given material of the object that calls the function.

LoadMaterialEffect(
MaterialIndex,
UFXFile
)

MaterialIndex
The index of the material you want to apply the shader effect for.

UFXFile
The *.ufx file that is to be used for this material. If you pass an empty string for this parameter ("") the current effect will be disabled.

If you have been using vertex shaders with Ultimate 3D v. 1.31 you may remember that there was no *.ufx file format. Instead you just loaded the shader and it got a set of preset shader constants. Since I did not want to break the downwards compatibility completely, there is a function in Ultimate 3D 2.0 and higher, which works in a similar fashion. It does not ask for a *.ufx file, but for a *.vsh file.

This function sets up the given vertex shader for the given material using the following set of constants:
0: VS_CONST_VIEW_TRANS_1
1: VS_CONST_VIEW_TRANS_2
2: VS_CONST_VIEW_TRANS_3
3: VS_CONST_VIEW_TRANS_4
4: VS_CONST_WORLD_VIEW_PROJ_TRANS_1
5: VS_CONST_WORLD_VIEW_PROJ_TRANS_2
6: VS_CONST_WORLD_VIEW_PROJ_TRANS_3
7: VS_CONST_WORLD_VIEW_PROJ_TRANS_4
8: VS_CONST_WORLD_TRANS_1
9: VS_CONST_WORLD_TRANS_2
10: VS_CONST_WORLD_TRANS_3
11: VS_CONST_WORLD_TRANS_4
12: VS_CONST_MAT_DIFFUSE

CreateMaterialVSEffect(
MaterialIndex,
VertexShaderFile
)

MaterialIndex
The index of the material the vertex shader effect is to be applied to.

VertexShaderFile
The file name and path of the vertex shader that is to be used (e.g. "effects\VSEffect.vsh").

I do not recommend using this function unless you want to use shaders that are made for Ultimate 3D v 1.31. If you aren't, you should create a *.ufx file. The great thing about *.ufx files is that they actually do most of the work for you. If you want to support the Ultimate 3D Community and know how to write shader effects you should make a package of nice shader effects including the *.ufx files and post it on the Ultimate 3D Community. Having a nice collection of premade shader effects there would be great.

Setting up shader constants

Most required shader constants can be set up using the *.ufx files. But what about those that can not be calculated by Ultimate 3D automatically? For those, there are two simple functions to change the values of vertex and pixel shader constants that do not get set up by Ultimate 3D automatically. Here's a description of them:

This function sets up a new value for the vertex shader constant register with the given index, in case it does not get set up by Ultimate 3D automatically.

SetMaterialEffectVSConstant(
MaterialIndex,
VSConstantIndex,
XValue, YValue, ZValue, WValue
)

MaterialIndex
The index of the material that uses the effect.

VSConstantIndex
The index of the constant register you want to modify. This value has to be in the range from 0 to 95.

XValue, YValue, ZValue, WValue
The new values for the constant register. They can be arbitrary real values.

The function SetMaterialEffectPSConstant(...) does exactly the same thing for pixel shader constants. The only difference is that the constant index has to be in the range from 0 to 7. For that reason I'm not going to explain it explicitly here.

Earlier in the chapter, I said 'see below' a few times when talking about constant registers that give information about light sources. So see here. You can set up three light sources that are to be used for the shader effect. To do this there's the following function:

This function sets up the three light sources that are to be used for the shader effect of the given material of the model object.

SetMaterialEffectLightSource(
MaterialIndex,
LightSourceID1, LightSourceID2, LightSourceID3
)

MaterialIndex
The index of the material the light sources are to be set up for.

LightSourceID1, LightSourceID2, LightSourceID3
The Game Maker ID of the light sources that should be used for the shader effect. You can enter the id of the objects for these parameters. If you do not need all three light sources just put 0 for the ones you do not want.

If you have read the whole tutorial and the tutorials about shader programming you should be really happy. You now know how to use the most flexible and widely usable feature of Ultimate 3D. Do not underestimate this. At first it may be that you do not know what to do with shaders, but soon you'll get some ideas. Besides, there are tons of tutorials on the Internet about really great effects that can be realized using shaders. Usually there's even an example given. I recommend playing around with shaders a lot to learn them. It's really the best way to collect experience here. Not every effect you make must make sense, as long as you learn something from making it. Just by changing single lines in the shader code you can get interesting effects you couldn't make without shaders. For example, you can enter the vertex normal as diffuse color for the vertex (in the vertex shader).

So have fun :D .


Post screen shaders

This is a really interesting topic, because it deals with a very powerful feature. Once you know what a post screen shader is you'll agree with me. So here's the explanation. You already should know what a standard shader effect is. If not, please read the chapter that deals with them. If you do not know how to write shaders you can not write post screen shaders and if you do not know what they are you can not even use them. Normal shader effects are great, because they can be used for many very decent effects. However, their possibilities are limited, because they can be applied only to single materials of models. This is a disadvantage post screen shaders do not have. They do not get applied to particular materials or models, but to the whole screen. After Ultimate 3D has rendered the complete scene, it applies the post screen shaders to the rendered image. This makes them powerful.

A simple example for this is that you want everything to be black and white for some reason. You could realize this by creating black and white copies of all textures and by changing all material colors to gray scale. You could also use a pixel shader effect for absolutely everything that's visible. Neither is desirable because it requires that you change every single object in your scene. With a post screen shader this can be realized much easier. You simply write one (very simple) post screen shader that implements the black and white effect. Then you apply it and you are done without even touching any scene objects. The post screen shader just takes the rendered image, takes the color out of every single pixel, and outputs it to the screen.

This is an example in which the post screen shader saves you a lot of work, but it does not do something that would not be possible without it. So here's another one that couldn't be done without post screen shaders: a simple blur effect. Imagine the protagonist of your game has been poisoned. Now you need some cool effect to show the player that he is not fine. A blur effect that applies to the whole screen is just perfect for this purpose. With usual shader effects this couldn't be implemented, but with post screen shaders it's no problem.

The range of possible effects is wide. Bloom, blur and different motion blur effects are common uses of post screen shaders. Additionally, post screen shaders can be used for numerous warp effects. Various exotic effects can also be realized. For example you can invert the colors, change the gamma, double the seen image (for drunk protagonists ;) ), darken or brighten up particular areas of the screen, highlight the contours for comic style graphics or make post screen shaders that make everything look as in an old black and white movie that has been watched thousands of times. Another thing they can be used for are depth of field effects, but that's a very advanced and computing time intensive technique.

The way post screen shaders work

This is a short chapter and the processes that are being described here are done automatically by Ultimate 3D. Nevertheless, it's important to know about these things, because otherwise you will not be able to understand why post screen shaders need to look the way they are meant to look. When you start using a post screen shader Ultimate 3D will not render the scene directly to the screen anymore. Instead it will render everything to a texture. When that's done, this texture contains everything you'd usually see on the screen. Then it renders a quad, which covers the whole screen and uses this texture. And this quad uses your post screen shader. This way you can define how the texture, which contains the rendered image of the scene, will get rendered to the screen.

It's also possible to use multiple post screen shaders. They will be applied one after the other. If you use two post screen shaders, Ultimate 3D will render the complete scene to a texture. Then it will render a quad that uses this scene texture to a second texture using the first post screen shader you defined. In the next step it renders this second texture to the screen using the second post screen shader you defined. If there were a third post screen shader the second one would have rendered to the first texture again and this first texture would get rendered to the screen using the third post screen shader. This means that there will be never more than two textures for all post screen shaders, so in terms of memory usage post screen shaders are very economical.

Another thing you should note about chained post screen shaders is that the order they get applied in does matter. Here's a simple example of this. Let's say you have a scene in which everything is blue for some reason (unrealistic, but it's a good example right now). The red channel and the green channel are always zero. And let's say that you've got two post screen shaders. The first one makes everything gray scale by taking the arithmetic mean of all three channels, and the second one ignores the green channel and the blue channel and replicates the values of the red channel to all three channels. If you apply the red-replication post screen shader before the gray scale post screen shader, the red-replication post screen shader will output a black picture, because the red channel is not used in the scene and the gray scale shader will not change this black picture. If you use the red-replication post screen shader after the gray scale post screen shader the gray scale shader will output a darker version of the blue channel to all three channels and the red-replication shader will have some positive data to output. So the results differ, depending on the order of the effects. For this reason you can set up a priority for every post screen shader.

The use of post screen shaders

It may seem a bit strange, but post screen shaders get represented by Game Maker objects just like models, primitives and terrains. The reason for this is that it makes their use very comfortable. You simply have one object for every post screen shader effect and whenever you need one of them you simply create an instance of the corresponding object. The rest is done by the object automatically. Also it happens very often that the same post screen shader needs to be used multiple times to reach an effect that's strong enough. In this case you can simply create multiple instances of the post screen shader object. And if a Game Maker room always uses a particular post screen shader you just place an instance of the post screen shader object in this room and you're done. No elaborate coding.

So the only thing you need to know now is how to create these post screen shader objects. It's quite easy. The complicated part is writing the shaders, but if that's too difficult for you, you can always use shaders made by others. The creation of valid post screen shaders will be dealt with below. The creation of post screen shader objects is similar to the creation of all other Ultimate 3D objects. In the first step you need to set up a couple of variables. Then you call a function to create the post screen shader. After that you can call some other functions to set up other options for the post screen shader. Finally you need to put a call to the Destroy() script in the destroy event. Step() is not needed. Here's a list of the variables that are available for post screen shaders. There are only four:

vertex_shader
The vertex shader that belongs to this post screen shader. This variable has to be set to a string, e.g. "effects/BlurPSS.vsh".

pixel_shader
The pixel shader that belongs to this post screen shader. This variable has to be set to a string, e.g. "effects/BlurPSS.psh". Note that the creation of the post screen shader effect will fail, without showing an error in case the required pixel shader version is not supported. For this reason you have to call GetSupportedPSVersion() before creating the post screen shader to make sure that the required pixel shader version is supported.

camera_index (optional)
Every post screen shader applies to the rendered image of a particular camera object. If this is not the default camera, you have to set this variable to the index of the particular camera, which is the value of the number variable of that camera (ranging from 1 to 31, as 0 refers to the default camera), not the id of the object.

priority (optional)
As explained above, the order post screen shaders get applied in does matter. For this reason you can use this variable to assign priorities to post screen shader objects. This can be any floating point value. The higher the value of this variable is, the earlier the post screen shader gets applied in each frame.

Once you are done with setting up all these variables you can use the function CreatePostScreenShader() to create the post screen shader. Now you may be wondering why post screen shaders do not use the Ultimate 3D effect file format (*.ufx). The reason is that it would have made things unnecessarily complicated. Since post screen shaders do not get applied to 3D objects but to the whole screen, there is not much precomputed constant information that could be set up for them. There's no information about materials, the transformation of scene objects does not matter and the light sources are already applied. Usually all you need is some color information or some set of factors to change the strength of the effect. These can be set up either through the def instruction of the particular shader, or through the following two functions:

This function sets up a particular vertex shader constant register for the post screen shader object it's called by. It is able to overwrite constant registers that have been set up through the def instruction of vertex shaders.

SetPSSVSConstant(
VSConstantIndex,
Value1, Value2, Value3, Value4
)

VSConstantIndex
The index of the vertex shader constant register you want to modify. This can be an arbitrary value in the range from 0 to 95.

Value1, Value2, Value3, Value4
The values you want to set up for this constant register. Value1 gets copied to the x-component, Value2 gets copied to the y-component, Value3 gets copied to the z-component and Value4 gets copied to the w-component.

This function sets up a particular pixel shader constant register for the post screen shader object it's called by. It is not able to overwrite constant registers that have been set up through the def instruction of pixel shaders.

SetPSSPSConstant(
PSConstantIndex,
Value1, Value2, Value3, Value4
)

PSConstantIndex
The index of the pixel shader constant register you want to modify. This can be an arbitrary value in the range from 0 to 7.

Value1, Value2, Value3, Value4
The values you want to set up for this constant register.

But the shader constants aren't everything you can set up for post screen shaders. They can also have one texture set up for every texture stage. Only the texture of the first texture stage (stage 0) can not be changed, because this stage always uses the scene texture. The textures of all other stages can be set up through the following function.

This function sets up the given texture as a texture for the given texture stage of the post screen shader object it's called by:

SetPSSTexture(
TextureStage,
TextureIndex
)

TextureStage
The index of the stage you want to set up another texture for. Since the first texture stage always uses the scene texture and can not be modified, this has to be a value in the range from 1 to 7.

TextureIndex
The index of the texture you want to set up for this stage, which is the return value of the particular texture loading function.

When writing post screen shaders it happens quite often that you can take advantage of particular texture filters to make your effects look better. For example if you use linear filtering for the scene texture this will already make it a bit blurry, which can be desired or not. For this reason you can set up an individual texture filter for every single texture stage for post screen shaders. By default, the first stage uses nearest point filtering and all other stages use the texture filter that has been set up through SetFilter(...), which is linear filtering by default. If you want to set up another filter you can use the following function to do this:

This function sets up a different texture filter for a particular texture stage of the post screen shader object that calls this function:

SetPSSFilter(
TextureStage,
FilterType
)

TextureStage
The texture stage you want to set up a new filter for. This can be an arbitrary value in the range from 0 to 7.

FilterType
The filter you want to set up for this stage. To get a description of the meaning of the single values you can pass for this parameter, please refer to the description of the function SetFilter(...).

Writing post screen shaders

Post screen shaders are written in the same assembler language as the usual shader effects. However, they are usually quite different. The vertex shaders, especially, can differ very strongly. The first reason is that no matrix calculations need to be done, because the vertex position register is already in projection space. The second reason is that the quads, that are being used to apply the post screen shaders, are made up of no more than four vertices. So every vertex shader of a post screen shader does not compute more than four vertices, which is about nothing in comparison to the hundreds and thousands of vertices that need to be computed for shaders that are used for 3D models. For this reason you do not need to care about optimization in post screen vertex shaders. If ten additional lines of vertex shader code can save you one line of pixel shader code, or if they make the use of the post screen shader effect a little bit more comfortable, you should use them.

The most important thing you need to know to be able to write valid post screen shaders, are the available vertex data registers. These are v0, v7 and v8. Here's a description of their contents:

v0 contains the vertex position in projection space. Since it's already in projection space you can simply use a mov instruction to pass it to the oPos output register. No matrix is needed.

v7 contains the texture coordinates that need to be used for the scene texture.

v8 contains texture coordinates in the range from 0 to 1, which can be used for all other textures.

The reason that v7 and v8 are different is that textures must always have dimensions made up of powers of two, whereas render targets can have an arbitrary size. So if you have a viewport with a size of 800*600, the scene texture will have the dimensions 1024*1024, so the texture coordinates of v7 will be in the range from 0 to 800/1024 for the x-coordinate, and in the range from 0 to 600/1024 for the y-coordinate.

After all this theory here's a little example post screen shader. It uses the texture coordinate distortion map that's supposed to be set up for stage 1 to distort the image on the screen. This little example post screen shader is already very powerful. It can be used for cool warp effects.

Here's the vertex shader code:

vs.1.1

mov oPos, v0
mov oT0, v7
mov oT1, v8

Couldn't be easier, could it :D ? Here's the pixel shader code:

ps.1.4

texcrd r0.rgb, t0
texld r1, t1

mad r1.rgb, r1, c0, r0

phase

texld r0, r1

The pixel shader does the following. First it gets the first texture coordinate for this pixel and the data from the distortion map. Then it multiplies the data from the distortion map by a factor and adds the pixel's first texture coordinate. Finally it uses this new texture coordinate to look up a texel in the scene texture. This will be the output of this shader. To use this shader you need to set up the distortion map for stage 1 and a distortion strength factor for the first pixel shader constant. That's all.


Real-time shadows

Oh yeah, real-time shadows. I've been waiting for these so long and now I'm gonna use them for absolutely everything in my game. Each leaf of a tree is going to cast its shadow onto the terrain. That's going to look so cool! Absolutely everything will have its own shadow, just like in reality!

If that's what you are thinking right now I have to disappoint you. That's simply not doable. No PC would be able to run this smoothly. Nevertheless, the real-time stencil shadows of Ultimate 3D are a cool feature. Scenes without shadows seem unauthentic. Things need to be shadowed to look realistic. Shadows help the human eye to understand the three dimensional structure of the two dimensional pictures seen on the screen. In shocking games shadows can contribute to the dramatic effect of the happenings greatly. They can give you an idea of things you do not see directly. This way a shadow can scare the hell out of people playing a game, at least if it's done right.

It's not quite wrong to see the real-time shadow casting as a method to improve the dramatic potential of your game, but it needs to be used carefully to get a proper performance. In this context the most important lesson is that real-time shadows should be used only for non-static objects and light sources. For static shadows you can use techniques like light mapping, which can be realized through the multi texturing functions and are a lot more efficient.

Ultimate 3D uses hardware accelerated stencil shadows. You do not need to know how these shadows work, but you should know about the following advantages and disadvantages of this technique. The advantages are mainly the extremely high quality of the resulting shadows, which are absolutely precise for every pixel, the low required hardware capabilities (no pixel shader support is required), the reliability, the trouble-free combination with shader effects like per pixel lighting and the fact that they are fully hardware accelerated, so they do not stress the CPU that much. The disadvantages are that your models have to fulfill some conditions to be valid shadow casters; that they stress the GPU quite a lot; and that soft shadows aren't possible.

Using real-time shadows

The use of real-time shadows in Ultimate 3D is very easy. The first thing you have to do is make sure that they are supported. If so you can enable shadow casting for particular light sources, set up particular model objects as shadow casters and set up model objects, terrain objects or primitive objects as shadow receivers. The first thing you have to do to be able to use shadows, is setting global.u3d_z_buffer_format to 32 in the Ultimate3DOptions() script. For more information about the meaning of this variable, please refer to the chapter about Ultimate 3D options. The reason that there can not be shadows without a 32-bit z-buffer is that 8-bits of the z-buffer are used as stencil buffer which is needed to compute the shadows. If the PC you are using is current enough and if the z-buffer has the right format, the function GetShadowSupport() will return true, which means that you can use real-time shadows. Otherwise you can't. All shadow related functions will not do anything then.

Now that you've made sure that shadows are supported you can call the following function to enable shadows for a particular light source.

This function enables or disables shadow casting for the light source object it's being called by. It has to be called after LightDirectional(), LightPoint() or LightSpot().

SwitchShadows(
Enable
)

Enable
If you pass true for this parameter, Ultimate 3D will enable shadow casting for this light source, otherwise it will disable it.

Next you can define which model objects cast shadows for which light sources.

This function enables or disables shadow casting for the model object it's being called by. If the model uses vertex skinning it must not use more than 28 bones, since all additional bones will get transformed incorrectly. The reason for this behavior is the limited number of vertex shader constants available in the used Direct3D version.

SwitchShadowCasting(
LightSourceID,
Enable
)

LightSourceID
The Game Maker id of the light source object this model should cast shadows for.

Enable
Whether shadow casting is to be enabled (true) or disabled (false).

Finally you need to be able to set which objects can receive shadows. That's just as easy.

This function turns the receiving of shadows cast by a particular light source on or off. It applies to the terrain, model or primitive object it's called by.

SwitchShadowReceiving(
LightSourceID,
Enable
)

LightSourceID
The light source you want to enable or disable shadow receiving for.

Enable
If you pass true, the object will receive shadows from the given light source, otherwise it won't. Note that terrains will not receive any shadows unless they've called CalculateTerrainLightMap(...) at least once after calling this function.

Now you can enable shadows for light sources, shadow casting for models, and shadow receiving for different object types. That's all you need to know to use shadows with Ultimate 3D. However, the results you'd get with this knowledge would be quite poor in the majority of all cases. The reason for this is that stencil shadows require special geometry to work properly. The geometry mustn't have any holes and it should have triangles representing the edge unless it has a high triangle count. Only if this geometry is available will the shadows look realistic. Luckily, Ultimate3D contains a function to alter a model's geometry so it meets these conditions. As usual you do not have a lot of work with this, because Ultimate 3D does everything automatically. You just have to ask it for this service.

This function creates geometry that's optimized for shadow casting for the model object it's called by. It should be called after LoadMesh() (and the call to CreateLODChain(...) if applicable).

CreateShadowOptimizedGeometry(
LODIndex,
CreateConnectionPatches
)

LODIndex
Through this parameter you can define for which level of detail you want to create shadow optimized geometry. Often it makes sense to use lower levels of detail for the shadow casting since the shadows do not need to be that precise. The highest level of detail with shadow optimized geometry will be used for the shadow casting. For this parameter, 0 refers to the highest level of detail (the unchanged model), 1 refers to the next-lower level of detail and so on.

CreateConnectionPatches
If you pass true for this parameter, this function will create geometry that leads to absolutely precise shadows, but the price is high. It requires the creation of two triangles for every edge of the model. If you pass false these connection patches will not get created, which leads to a higher performance but less detailed shadows.

Unfortunately even this function fails to optimize the geometry correctly in particular cases. In this case you'll get ugly results. The shadow will not stop behind the model. Instead it will go on to infinity in long dark stripes. So what's the condition the model has to fulfill to avoid this effect? It's quite easy to do but a bit hard to understand. All edges in the model mustn't be shared by more than two triangles. Here's a little illustration to clarify this.

Illustration not available

The geometry on the left could be optimized perfectly, because it's made up of single sided edges (blue) and two sided edges (green) only. But the piece of geometry on the right has two edges that are shared by three triangles (red). For this reason the mechanism Ultimate 3D uses to fill holes in model geometry can not find a proper solution and that's why the function will not result in valid shadow optimized geometry in this case. That's why no shadow casting model in your scene can have a triangle configuration like this. It's up to you to make that sure. Ultimate 3D can not do this for you, because a mathematical mechanism that leads to a unique solution can not be found in this situation. But that's a low price for absolutely realistic real-time shadows, is not it ;) ?

Shadow checklist

Recently problems with the shadow casting functions have been reported on the Ultimate 3D Community quite often. In the majority of all cases this was the result of wrong use. It is indeed quite hard to debug problems related to shadowing, because if only one component is wrong the whole shadowing will not work and there is no way to find out whether it is the light source, the shadow caster or the shadow receiver that's not functioning. For this reason here's a checklist you can work with, if your shadows aren't working. It explains all common mistakes and explains how to correct them. Read this before you post any questions related to non-functioning stencil shadows on the forum.

The most common mistake is that SwitchShadowCasting(...) and SwitchShadowReceiving(...) get called before the light source has been created and initialized. In this case the LightSourceID parameter will be invalid and as a result of this shadow casting or shadow receiving will not work. The safest way to avoid this problem is placing calls to these two functions in the step event of the objects. They do not do anything computing time intensive, so it will not be that bad for the performance.

Another mistake that has been done a couple of times is that CreateShadowOptimizedGeometry(...) hasn't been called for the shadow casting model objects. You should always call this function for shadow casters. Without it you will get wrong, imprecise or no shadows in most cases. The only exception from this are models with an extremely high triangle count. For those it may work without a call to this function, but if shadows aren't working and you aren't using CreateShadowOptimizedGeometry(...) you should change that immediately.

If you have problems with using terrain objects as shadow receivers you should check the return value of GetSimultaneousTextureCount(...). If it's smaller than three your system does not support terrain light mapping in combination with texture splatting. And without light mapping you can not get shadows. Another thing you should notice is that terrains with a custom light map can not receive shadows.

If you have functioning shadows, but get strange shadow stripes, which go on to infinity, read the explanation of three sided edges above. Those are usually the cause of this problem. Another possible cause of this problem is that you aren't calling CreateShadowOptimizedGeometry(...) correctly.

One last possible cause of non-functioning shadows is an inappropriate proportion between camera.min_range and camera.max_range. If the result of camera.max_range/camera.min_range is extremely high it can happen that rounding errors make the far-clipping plane go infinite far away. Since the far-clipping plane plays an important role for shadow casters shadow casting will not work in this case. To fix this you simply need to increase camera.min_range and/or decrease camera.max_range.


Model manipulation

In some cases it may happen that you have to modify the geometry of an object while the game is running; for example, if you have an object that can be deformed by hitting it with a particular weapon. This deformation could be realized using a vertex shader. But that's quite complicated. For this reason Ultimate 3D gives you the possibility to manipulate the geometry of a mesh in real time. You can edit every single vertex of the mesh. You can also modify the way the vertices get put together into triangles. And you can change the material that's being used for a triangle. You could actually use Ultimate 3D to make a modeling program, since it's possible to create meshes from nothing.

It is also quite common that the animation of a model has to be changed. For example, in 3rd person games you may want the torso or the head of the character to rotate when he looks in another direction. This can be done with Ultimate 3D in two different ways.

Manipulating mesh geometry

Before I explain how to manipulate mesh geometry, I'll give an explanation of the term mesh itself, since it may be that you do not have an exact idea of what it is. A mesh is a piece of geometry. It can be made up of an arbitrary number of vertices and triangles. If you have an *.md2 file, every frame is one mesh. If you have a *.3ds file, it may be that the model is made up of multiple meshes. If you have an animated *.an8 file, every object you created in Anim8or is one mesh. In addition, a copy of every single mesh will be created when you add a new LoD to a model.

It's very important to keep in mind that Ultimate 3D will never create the same mesh twice! If you load a model from a file, all objects that are based on this model will use the same meshes. It would be an unbelievable waste of memory if it wasn't done like this. So if you modify the mesh of one object, this mesh will be modified for every single object that's loaded from the same file. For other information, like materials and bones, this is not the case because these do not take much memory.

Now there's another new term you need to know about. So far you have learned that every 3D object is made up of vertices and that these vertices are put together into triangles. So every triangle is made up of three vertices. Now have a look at the following illustration.

Illustration not available

As you can see, this rectangle is made up of nine different vertices (marked by the little squares) and eight triangles. Since every triangle is made up of three vertices, you probably assumed that this little mesh requires 8*3=24 vertices to be described. But there are only nine different vertices, and only two of them are used by only one single triangle (marked green). The vertex in the middle is used by six triangles. And things like this happen really often. Fortunately, a simple method was invented a long time ago to avoid this waste of memory. If you have complex models, it may happen that the model has twice as many triangles as vertices. So how does it work? It's pretty simple. You save every vertex exactly once. The order does not matter. Then you add a so-called index buffer (and that's the term you need to remember) that contains three indices (integer numbers that refer to vertices) for every triangle. So actually a triangle is not made up of three vertices, but of three references to vertices.

After this bit of theory it's time for some practice. In Ultimate 3D you can lock any mesh of any model object at any time. Locking a mesh means preparing it for retrieving and changing its information. This is necessary because the memory the mesh data is saved to may be managed by the graphics device. It is very important that you unlock every mesh you lock. If you fail to do this, it may happen that an access violation occurs when the mesh gets rendered, and that can lead to miscellaneous rendering errors or one of Game Maker's "unexpected errors". To lock meshes there's the following function:

This function locks the given mesh of the object it's called by.

LockMesh(
LockedMeshIndex,
MeshIndex,
Frame,
LoD
)

LockedMeshIndex
In Ultimate 3D you can not lock more than 32 meshes at the same time. It would have been no problem to set up a higher limit here, but if you do not forget about unlocking meshes you will never have more than 32 meshes locked at the same time anyway. The value for this parameter has to be in the range from 0 to 31. It's the index the locked mesh will be associated with.

MeshIndex
The index of the mesh you want to lock. I recommend writing a for-loop to loop through all meshes of the object. You can use the function GetMeshCount() to get the number of meshes an object has per frame.

Frame
The frame in which you want to modify a mesh. If you aren't using this function to lock a mesh of a model loaded from a *.md2 file, or of an object that has been created with AddFrame(...), you should always put 0 for this parameter.

LoD
The level of detail you want to modify. The highest level of detail has the index 0. This is also the value you should enter if you aren't using multiple levels of detail for the model.

This function unlocks a mesh and sets the information associated with the locked mesh index back to the defaults. It has to be called once for every call to LockMesh(...)!

UnlockMesh(
LockedMeshIndex
)

LockedMeshIndex
The index of the mesh you want to unlock.

Once you've locked a mesh you can modify it in any imaginable way. You can modify its vertex data, its triangle index data and the materials that are associated with the single triangles. You can also reference all this information. For each of these tasks there's a different function:

This function modifies a single vertex of the locked mesh with the given index:

SetLockedMeshVertex(
LockedMeshIndex,
VertexIndex,
ValueIndex,
NewValue
)

LockedMeshIndex
The index of the locked mesh you want to modify. This is the value you put as the first parameter when calling LockMesh(...) to lock the mesh.

VertexIndex
The index of the vertex you want to modify. I recommend writing a for-loop to cycle through all the vertices. You can use GetLockedMeshVertexCount(LockedMeshIndex) to get the number of vertices the given locked mesh has.

ValueIndex
This parameter gives the part of the vertex information that is to be changed. Since Ultimate 3D uses a different coordinate system than Game Maker, this information is sorted in the following way:
0 = VertexPositionX
1 = VertexPositionZ
2 = VertexPositionY
3 = VertexNormalX
4 = VertexNormalZ
5 = VertexNormalY
6 = VertexTextureCoordinate1U
7 = VertexTextureCoordinate1V
Then the texture coordinates that have been added using AddTextureCoordinateSet(...) follow. The vertex normal is a vector needed to calculate the lighting of objects. Usually there's no need to modify it manually. If you change the geometry of the mesh, you should call RecalculateNormals() once to update the normals.

NewValue
The new value you want to set up for the given part of the given vertex.

This function returns a part of the information of a single vertex of the given mesh:

GetLockedMeshVertex(
LockedMeshIndex,
VertexIndex,
ValueIndex
)

LockedMeshIndex
The index of the locked mesh you want to modify. This is the value you put as the first parameter when calling LockMesh(...) to lock the mesh.

VertexIndex
The index of the vertex you want to modify. I recommend writing a for-loop to cycle through all the vertices. You can use GetLockedMeshVertexCount(LockedMeshIndex) to get the number of vertices the given locked mesh has.

ValueIndex
This parameter is exactly the same as ValueIndex in SetLockedMeshVertex(...), so have a look at the description there.

This function changes the vertices that are being used for the triangle with the given index of the given locked mesh.

SetLockedMeshTriangle(
LockedMeshIndex,
TriangleIndex,
VertexIndexA, VertexIndexB, VertexIndexC
)

LockedMeshIndex
The index of the locked mesh you want to modify. This is the value you put as the first parameter when calling LockMesh(...) to lock the mesh.

TriangleIndex
The index of the triangle you want to change. I recommend writing a for-loop to cycle through all the triangles. You can use the function GetLockedMeshTriangleCount(LockedMeshIndex) to get the triangle count of the given locked mesh.

VertexIndexA, VertexIndexB, VertexIndexC
The indices of the vertices that are to be used for the triangle. Note that the order of these parameters do matter, due to the back face culling. You must never enter a value bigger than the vertex count of the given locked mesh here. This could lead to serious display problems.

This function retrieves a single vertex index of a triangle in the given locked mesh:

GetLockedMeshTriangle(
LockedMeshIndex,
TriangleIndex,
RequestedVertexIndex
)

LockedMeshIndex
The index of the locked mesh you want to retrieve information from. This is the value you entered for the first parameter of the LockMesh(...) function when locking the mesh.

TriangleIndex
The index of the triangle you want to retrieve information about. I recommend writing a for-loop to cycle through all the triangles. You can use the function GetLockedMeshTriangleCount(LockedMeshIndex) to get the triangle count of the given locked mesh.

RequestedVertexIndex
For this parameter you can pass 0, 1 or 2 to retrieve the first, second or third vertex index of the triangle.

This function changes the material that is used for the given triangle of the given mesh:

SetLockedMeshTriangleMaterial(
LockedMeshIndex,
TriangleIndex,
NewMaterialIndex
)

LockedMeshIndex
The index of the locked mesh you want to change the triangle's material for. This is the value entered for the first parameter of the LockMesh(...) function when locking the mesh.

TriangleIndex
The index of the triangle whose material you want to change. I recommend writing a for-loop to cycle through all the triangles. You can use the function GetLockedMeshTriangleCount(LockedMeshIndex) to get the triangle count of the given locked mesh.

NewMaterialIndex
The new index of the material you want to set up for the triangle. You can use GetMaterialIndex(...) to get this value.

This function returns the index of the material that's currently being used by the given triangle of the given locked mesh.

GetLockedMeshTriangleMaterial(
LockedMeshIndex,
TriangleIndex
)

LockedMeshIndex
The index of the locked mesh you want to retrieve information from. This is the value you put for the first parameter of the LockMesh(...) function when locking the mesh.

TriangleIndex
The index of the triangle you want to get information about. I recommend writing a for-loop to cycle through all the triangles. You can use the function GetLockedMeshTriangleCount(LockedMeshIndex) to get the triangle count of the given locked mesh.

There is another model manipulation function which has been mentioned a couple of times before. It can be used to add a new set of texture coordinates to all meshes of an object (the meshes mustn't be locked when doing this). You have already seen how to fill the added texture coordinates with data using the function SetLockedMeshVertex(...). One easy way to get the data is creating two versions of the same model with two different sets of texture coordinates. Then you can create a temporary instance of the second version of the model, lock its meshes and the meshes of the first model, and copy the texture coordinates of the second model to the first model in a few for-loops. When you are done with this you can destroy the temporary second model. But let's get to the function.

This function can be used to add a new multi-dimensional texture coordinate set to all meshes of the object that calls the function (and all objects that are based on the same model!).

AddTextureCoordinateSet(
DimensionCount
)

DimensionCount
The number of dimensions the texture coordinate set should have. This value can be an integer value in the range from 1 to 4. One dimension means that there's only one real value in the texture coordinate (U), two dimensions mean that there are two (U, V), three dimensions means that there are three (U, V, W) and four means that there are four (U, V, W, Q).

Finally there's a function that makes the use of multiple texture coordinate sets a lot easier. Often you simply need to load two different texture coordinate sets for one model. For example light mapping requires one texture coordinate set for the diffuse maps and a second texture coordinate set for the light map. The problem is that none of the supported model file formats except *.u3d support multiple texture coordinate sets. The solution to this problem is creating two versions of the model, each of them having a different texture coordinate set. Then you can use the following function to combine both of them.

This function copies all texture coordinate sets of the given model to the meshes of the model object that calls this function and to all objects that are based on the same model file. The meshes of this object mustn't be locked.

AddTextureCoordinateSetsFromModel(
ModelFile, Password
)

ModelFile
The model file that holds the texture coordinate sets that are to be added. This model file must have the same number of meshes as the model object calling this function, and all meshes must have the same number of vertices.

Password
If ModelFile refers to an encrypted Ultimate 3D model file, you can pass the correct password for this parameter. Otherwise you have to pass nothing or an empty string.

Manipulating animations

Especially in third person games it happens very often that the character needs to move interactively. For example if you've got a character with a weapon aiming somewhere, his torso has to rotate up and down while the player aims up and down, to make it look realistic. Ultimate 3D offers two different ways to do this. The first one simply changes the rotation of a particular bone. This can be used to reach the desired effect quite easily, but it does not necessarily look that good. For this reason there's also the possibility to make particular bones use other frame values than other bones. This way you can for example make the upper part of the body of a character use a different animation than the rest. For both methods you need a function to retrieve the index of the bone you want to modify.

This function returns the index of the bone with the given name, or 0 if it does not exist.

GetBoneIndex(
BoneName
)

BoneName
The name of the bone. What this is depends on the modeling program you are using.

If you want to do something automatically for all bones in a model it is useful to write a loop, which cycles through all bones in a model. For this purpose you need to know how many bones are part of the model. This information can be retrieved through the function GetBoneCount() which returns the number of bones available in the model object it is called by. All integer values in the range from 0 to GetBoneCount()-1 are valid bone indices. Another function, which is useful when you want to have code that works with bones automatically is the following.

This function returns the index of the parent bone of the bone with the given index. If the bone does not have a parent bone -1 is returned.

GetBoneParent(
BoneIndex
)

BoneIndex
The index of the bone for which you want to retrieve the index of the parent.

The next function is the one which can be used to rotate a single bone. It usually looks quite unrealistic when it is used for character models, but it can be very useful for mechanical objects like tanks. For example it is very easy to make the turret of a tank rotate with this function.

This function deletes all rotation keys of the bone with the given index of the object that calls the function and creates one single new rotation key with the given rotation.

SetBoneRotation(
BoneIndex,
RotationX, RotationY, RotationZ
)

BoneIndex
The index of the bone you want to change.

RotationX, RotationY, RotationZ
The new rotation. The variables get interpreted in the same way as the variables rotx, roty and rotz of model objects.

Here is the last function, which can be used for really authentic interactive character animations. For example you could make a model that has an animation for the upper part of the body in which it aims up and down and different running animations for the legs. Then this function could be used to make the upper part of the body aim up and down depending on the place the player aims at, while the legs are running into different directions or standing.

This function can be used to set up another frame value for a particular bone of the object it is called by.

SetBoneFrame(
BoneIndex,
Frame,
AffectChildBones)

BoneIndex
The index of the bone you want to change the frame value for.

Frame
A positive value giving an absolute frame value, which is to be used for the bone. It will be used instead of the value of the frame variable, resp. the return value of GetCurrentFrame(). If you pass a negative value instead, the model will use the value given by frame again.

AffectChildBones
If you pass true for this parameter, the given frame value will be used not only for the bone with the index BoneIndex, but also for all bones that are attached to this bone. Otherwise only this bone will be affected. If one of the child bones has its own individual frame value set up through SetBoneFrame(...) this one will be used instead, no matter whether you pass true or false.

Another important type of animation manipulation is the interpolation between two poses within an animation. Imagine your character is currently running. Then the player stops pressing the key that makes him run, so the character has to hold. Now it would be quite ugly if the animation would just jump back to the idle animation. It would be nicer if it would interpolate from the running animation to the idle animation smoothly. This is what the interpolation of poses is good for.

This function starts an interpolation from the current pose to the pose the model has at the given destination frame.

StartPoseInterpolation(
DestinationFrame,
InterpolationDuration
)

DestinationFrame
The frame at which the model has the destination pose.

InterpolationDuration
The time the interpolation will take in steps. Over this time interval the object will be interpolating between the poses. After that it will continue playing the animation at DestinationFrame unless the values of first_frame and last_frame do not allow this. If you want to abort the interpolation of a pose you can call this function again and pass zero for this parameter.

When you have a model with very many different animations you may find it uncomfortable to have all these animations in one file, with one animation sequence after the other. It would be nicer to have one file for every animation. Ultimate 3D offers a feature for this purpose. It is not a perfect solution, but it works. It allows you to load animations from model files.

This function copies an animation from a given model file to the model object it is called by. It does this by replacing all keyframes in the animation of this model object, by those saved in the given model file.

CopyAnimation(
SourceFile,
SourceFilePassword,
CopyRotationKeysOnly
)

SourceFile
The source file from which you want to copy the animation. You may want to preload this model file through PreloadMesh(...) to avoid a delay.

SourceFilePassword
If the source file is an encrypted Ultimate 3D model file (*.u3d) you can pass the password to this parameter. Otherwise you should pass an empty string.

CopyRotationKeysOnly
If this parameter is true the function will copy exceptionally rotation keys, otherwise all keys well be copied. If the skeleton of the destination model object does not match the skeleton of the source model exactly this may be desirable.

This function should be used carefully. It can only work correctly if the source model and the destination model object have identical skeletons. If you pass true for CopyRotationKeysOnly it may also work for models, with slightly different skeletons, but you should not rely on this.

Creating custom meshes

In some cases it's necessary to create meshes from scratch. This can easily be done with Ultimate 3D. You create these objects in the same way as all other objects. First you set up a few variables, then you call a creation function once, and finally you have to add Step() and Destroy() to the corresponding events.

material_count
The number of materials the new object will have. This value cannot be zero.

vertex_count
The number of vertices the only mesh of the new object will have. It must be bigger than three.

triangle_count
The number of triangles the only mesh of the new object will have. This value cannot be zero.

After setting up these variables you can call CreateEmptyMesh(). Then an object with the given material, vertex and triangle count will be created and associated with the object that called the function. After you have added Step() and Destroy() the object is ready to be used. But before you get some visible results you have to use the model manipulation functions I introduced above to fill the mesh with valid geometry. By default all vertex data and index data will be filled with zeros and all materials will be white and untextured.

Premade model manipulation functions

There is a couple of mesh manipulation operations which need to be done very often. Ultimate 3D offers a few easy-to-use functions to perform these operations. The most important one recalculates the normal vectors that are required to get correct per vertex lighting. Another one smoothes the normals of the meshes. This means that it searches for vertices that have the same position but a different normal to fix this. The third one calculates something called inverse tangent space matrices for every single vertex. A brief explanation of what this means can be found below.

This function calculates a vertex normal for every vertex of every mesh of the object that has called the function. It should be called whenever you change the mesh geometry, because then the old normals will not be current anymore. It does not take any arguments.

RecalculateNormals()

It happens quite often that visible creases occur in the surface if the normals are calculate this way. Since normals are not only required for light calculations but also for effects such as cel-shading and environment mapping this is very undesirable. The following illustration shows a model, which uses environment mapping, looks if there are creases in it.

Illustration not available

To avoid this, Ultimate 3D offers a simple function. It searches for creases in the model and if it finds some it smoothes them. Here is its description.

This function searches for vertices with identical positions, but different normals. If it finds some it checks whether the angle between their normals is smaller than the given angle and if so it smoothes them. Note that this process is very computing time intensive.

SmoothNormals(
SmoothingAngle
)

SmoothingAngle
The smoothing angle in degrees. The function smoothes the normals of two vertices only, if the angle between them is smaller than this angle.

The next function has a very special purpose. Unless you want to get into pixel shader programming you do not need to undestand it. You may want to skip the following explanation. Lots of special effects, especially those, which implement some sort of per pixel lighting, use normal maps. Normal maps give normals in the space of the texture. This space is called tangent space. Information like the position of light sources usually is given in the space of the mesh. For this reason a way is needed to get the information about the light source from mesh space into tangent space. A matrix (see the chapter about math functions) is needed that brings information from mesh space into tangent space. And since every triangle is textured differently this matrix is needed for every vertex.

This function calculates inverse tangent space matrices (mesh to tangent space) for each vertex of each mesh of the object that calls the function, meaning that it overwrites the mesh normals with the third column of the matrix. Usually the third colum is very similar to the normals, so this is no big change. It also adds two new 3D texture coordinate sets. The first one of them will be used to save the first column of the inverse tangent space matrices, the second one will be used to save the second column. The function does not take any parameters.

AddInverseTangentSpaceMatrices()

Cel-shading

Cel-shading is a technique that's commonly used to create graphics that look as if they are part of a comic. It can also be used to mark particular objects by creating an outline around them. Cel-shading basically creates an extruded version of each mesh and renders it using reverse back face culling. This results in an outline around the model. When applying cel-shading you can define the width of the outline in mesh space and the color of the outline. Commonly the color of the outline is solid black. But you can also use other colors to achieve other pretty cool effects. For example you can use a transparent red to get a strong accent for the object the effect is applied to. Enabling cel-shading is pretty easy. Here's the function that does it:

This function enables cel-shading for the model object it's called by.

ApplyCelShading(
LineWidth,
LineColorR, LineColorG, LineColorB, LineColorA
)

LineWidth
The width of the outline you want to create in mesh space.

LineColorR, LineColorG, LineColorB, LineColorA
The color of the outline. The values for these parameters should be between 0 to 255. LineColorA gives the opacity of the outline. 0 means no opacity (fully transparent) and 255 means fully opaque.

Note that cel-shading uses a vertex shader. For this reason models that use vertex skinning and cel-shading mustn't use more than 28 bones, otherwise some bones will not get applied correctly.


Creating particle effects

Particle effects. What a great thing. You can have thousands of little billboards that get computed interactively based on the information you give about the desired particle behavior. The particle effects system has been completely redesigned for Ultimate 3D 2.0 and has been improved a little in Ultimate 3D 2.1. It is really flexible. First, there are many settings you can change to define the properties of the particles such as origin, velocity, acceleration, size and color. Second, you can add gravity emitters that work realistically. You could actually use the particle effects system with the gravity emitters to make a solar system simulator :D. Finally, you can set up modifiers that change the behavior of particles whenever they are in the area you set up for them. These could be used, for example, to create a room that reflects the particles when they hit one of the walls, changing their color. Additionally, every single particle in Ultimate 3D can have an unlimited size. And using particle effects is quite easy, so do not miss the following tutorial ;) .

Creating a particle system and setting up basic properties

Creating a particle system is probably the easiest thing one could imagine. You do not have to set up any variables; all you have to do is put CreateParticleSystem() into the creation event of an object. But it is obvious that that can not be all. You have to tell Ultimate 3D what you want the particle system to look like somehow. For this reason, you have to call a couple of other functions after the call to CreateParticleSystem(). There are also two variables, which can be set up at any time. The first one is texture, which defines the index of the used texture. The second one is optional and is called aspect_ratio. You can use this if you do not want quadratic particles. The height of the particles always equals their size, the width is the size multiplied by aspect_ratio. The other options are set through functions. The first two functions define the number of particles that will be created each step, and the lifetime of the particles. The following functions will always have some of the same parameters, one with a minimal value and one with a maximal value. Ultimate 3D will choose a random value in the range from the minimal value to the maximal value whenever it needs this value. This way everything gets a bit random and every particle gets an individual behavior. So here are the two functions:

This function sets up the number of particles the particle system will create.

SetParticleCount(
MinimumParticleCount,
MaximumParticleCount,
MaximumTotalParticleCount
)

MinimumParticleCount, MaximumParticleCount
The number of particles that are to be created per step.

MaximumTotalParticleCount
The maximum number of particles that can exist at the same time. If the absolute number of particles gets close to this value, Ultimate 3D will stop creating particles until enough particles have exceeded their lifetime. Usually a good value for this is (MaximumLifetime+MinimumLifetime)/2*(MaximumParticleCount+MinimumParticleCount)/2 .

This function sets up the lifetime of the particles of the particle system the function is called by. The lifetime is the time that passes from their creation until they get destroyed (in steps).

SetParticleLifetime(
MinimumParticleLifetime,
MaximumParticleLifetime
)

MinimumParticleLifetime, MaximumParticleLifetime
The lifetime of the particles that are to be set up.

Now it gets a little bit more complicated. You've got to define where the particles get created. If you want to you can simply leave out this step. Then all particles will be created at the same position. But that'd be kind of boring. To define the origin you have to give a transformation and a real value. Imagine you have a model of a sphere with a radius of one. If you apply the transformation you enter into the next function to this sphere model, the model will show you the areas particles can be created in. The second real value will be used as scaling for a second sphere that uses the same transformation. This is the other border that limits the area particles may get created in. The following graphic illustrates this.

Illustration not available

The first sphere gets transformed using the transformation you enter into the next function. Then the second sphere gets transformed using the same transformation and scaled by the given second sphere radius. Then the area between these two spheres is the origin of the particles. Here's the function to set up these properties:

This function sets up the particle origin. The position of the origin is not set up by this function since it often has to be changed very frequently. For this reason you have to set up the variables x, y, and z for every particle system.

SetParticleOrigin(
SecondSphereRadius,
RotationX, RotationY, RotationZ,
ScalingX, ScalingY, ScalingZ
)

SecondSphereRadius
The radius of the second sphere which is the second border for the origin of the particles. If you put 0 for this parameter, the particle origin will be an exact sphere.

RotationX, RotationY, RotationZ
The rotation that is to be used as transformation for the two spheres. Usually you do not need this parameter, since rotating the origin only makes sense if you do not enter the same value for all three scaling parameters.

ScalingX, ScalingY, ScalingZ
The scaling that is to be applied to the two spheres.

The next two functions set up the particle velocity and the particle acceleration (acceleration is the change of the velocity per step). This is pretty easy. The only thing you have to keep in mind about these functions is that the longitude has to be in the range from -90 to 90 because otherwise a method Ultimate 3D uses to achieve a regular spread of the particles will not work correctly.

This function sets up the initial velocity of the particles that are created by the particle system.

SetParticleVelocity(
MinLongitude, MinLatitude,
MinVelocity,
MaxLongitude, MaxLatitude,
MaxVelocity
)

MinLongitude, MinLatitude, MaxLongitude, MaxLatitude
The direction of the movement given in degrees. The values for longitude should be in the range of -90 to 90.

MinVelocity, MaxVelocity
The speed of the movement per step.

This function sets up the initial acceleration of the particles that are created by the particle system.

SetParticleAcceleration(
MinLongitude, MinLatitude,
MinAcceleration,
MaxLongitude, MaxLatitude,
MaxAcceleration
)

MinLongitude, MinLatitude, MaxLongitude, MaxLatitude
The direction of the movement change given in degrees. The values for longitude should be in the range of -90 to 90.

MinAcceleration, MaxAcceleration
The amount of movement change per step.

Next you can set up something that's called initial center repulsion. It happens quite often that all particles are supposed to move away from the origin of the particle system. One example for this would be a particle system for explosions. That's what you need the initial center repulsion for. When a new particle gets created the initial center repulsion factor gets multiplied by the difference of the position of the particle and the position of the particle system. Then the result gets added to the initial velocity of the particle ( InitialVelocity+=(ParticlePosition-ParticleSystemPosition)*RepulsionFactor). The repulsion factor gets set up individually for every axis. Here's the function:

This sets up the initial center repulsion factor:

SetInitialCenterRepulsion(
RepulsionFactorX, RepulsionFactorY, RepulsionFactorZ
)

RepulsionFactorX, RepulsionFactorY, RepulsionFactorZ
The repulsion factor along each axis. To get a description of what the repulsion factor is have a look at the text above.

Next you can set up the color and the size of the particles. For each of them you can set up a range of possible initial values and a range of possible final values. The final values will be used by the particle at the end of its lifetime. Here are the functions that are available to set up these values:

This function sets up a range of possible initial color values for the particles.

SetInitialParticleColor(
MinimumR, MinimumG, MinimumB, MinimumA,
MaximumR, MaximumG, MaximumB, MaximumA
)

MinimumR, MinimumG, MinimumB, MinimumA
The minimum color values for the initial color of the particle. These values have to be in the range from 0 to 255. MinimumA gives the minimum amount of opacity (0 for no opacity (fully transparent), 255 for fully opaque).

MaximumR, MaximumG, MaximumB, MaximumA
The maximum color values for the initial color of the particle. These values have to lie in the range from 0 to 255 as well.

This function sets up a range of possible final color values for the particles.

SetFinalParticleColor(
MinimumR, MinimumG, MinimumB, MinimumA,
MaximumR, MaximumG, MaximumB, MaximumA
)

MinimumR, MinimumG, MinimumB, MinimumA, MaximumR, MaximumG, MaximumB, MaximumA
These parameters have the same meaning as for SetInitialParticleColor(...) but this time they refer to the final particle color.

This function sets up a range of possible initial and final particle sizes. The size values are given in world space so they get interpreted in the same way as the parameters you can set up for billboards.

SetParticleSize(
MinInitialSize, MaxInitialSize,
MinFinalSize, MaxFinalSize
)

MinInitialSize, MaxInitialSize
The range of possible initial sizes for the particles.

MinFinalSize, MaxFinalSize
The range of possible sizes the particles may have at the end of their lifetime.

Finally you can specify a rotation for the particles. Making the particles rotate makes the particle system look a lot more chaotic and this greatly adds to its natural look.

This function specifies the intial particle rotation and their rotation speed.

SetParticleRotation(
MinInitialRotation, MaxInitialRotation,
MinRotationSpeed, MaxRotationSpeed
)

MinInitialRotation, MaxInitialRotation
The range of possible initial rotations for the particles in degrees. A rotation of zero means, that the top of the texture is at the top of the particle. Bigger rotations make the particles rotate in screen space in clockwise direction.

MinRotationSpeed, MaxRotationSpeed
The range of possible rotation speeds for the particles in degrees. The rotation speed will be added to the rotation of a particle once per step.

Now that you know how to set up the properties of your particle system, it's time to make it become alive. To do this you have to put a function call into the step event of the object you've created for your particle system, but this time it's not Step(). The reason for this is that the step function of particle systems does take one parameter and does not need to be called in every single step, because it takes quite a bit of computing time depending on the number of particles, gravity emitters and modifiers. It makes no sense to perform a step for a complex particle system if it is not visible currently. Here's the function:

This function performs a step of the given length for the particle system the function is called by.

ParticleSystemStep(
StepLength
)

StepLength
The length of the step you want to perform. One could also call this a time factor. If you put one for this, one step will be performed for every call to this function. If you put 0.5, everything will run at half speed, and if you put 2, everything will run twice as fast. Note that a big time factor can lead to imprecise calculations, especially when using modifiers and gravity emitters.

Finally you have to put a call to Destroy() into the destroy event of the particle system object, otherwise the particle system will still be visible after destroying the object it was associated with.

If you want to get very shiny particle effects with a volumetric effect you may want to use additive blending for the particle effects system. Additive blending means that every particle adds to the color on the screen instead of covering it. Without additive blending five particles in a row seen from the front, look exactly the same as a single particle, since the particle at the front covers the other four particles. With additive blending the colors of the particles are added together, leading to a very bright spot (brighter than a single particle). Enabling additive blending is easy.

This function enables or disables additive blending for the object it is called by.

SwitchAdditiveBlending(
UseAdditiveBlending
)

UseAdditiveBlending
If this parameter is true additive blending will be used for this object, if it is false it will not be used.

This function does not only work for particle systems, but also for model, terrain and primitive objects. Though you should use it for these object types only if they are partially transparent. Otherwise the results may not be desirable due to the z-buffering.

Gravity emitters

Gravity emitters are really cool. They can make particles move on paths that are quite frequent but never the same. Gravity emitters can also emit "negative" gravity, so they push away particles which is useful for many different kinds of effects. The gravity emitters use real physics so even if you do not know anything about Newton's theory of gravity, this will lead to some feeling of natural behavior. Note that gravity emitters aren't meant to be used to make gravity that comes always from the same direction. This can be achieved more easily by setting up a constant acceleration.

Using gravity emitters is really easy. You just have to set up their position in world space and the intensity of the gravity they should emit. The rest is done automatically. Gravity emitters should be used carefully since they have to be applied to every single particle which takes a bit computing time. Ultimate 3D sets a limit of 32 gravity emitters per particle system. Here's the function you have to use to create gravity emitters:

This function adds a new gravity emitter to the particle system the function is called by and returns its ID, which can be used to modify it afterwards.

AddGravityEmitter(
PositionX, PositionY, PositionZ,
GravityEmitterMass
)

PositionX, PositionY, PositionZ
The position of the gravity emitter given in world space.

GravityEmitterMass
The mass of the gravity emitter. A good value for this parameter depends on the distances in your particle system, so you should find one by playing around with it.

This function can be used to modify or remove a gravity emitter you have created before.

SetGravityEmitter(
GravityEmitterID,
PositionX, PositionY, PositionZ,
GravityEmitterMass
)

GravityEmitterID
The ID of the gravity emitter you want to modify. This is the value AddGravityEmitter(...) returned when creating the gravity emitter.

PositionX, PositionY, PositionZ
The new position of the gravity emitter given in world space.

GravityEmitterMass
The new mass of the gravity emitter. If you enter 0 for this parameter the gravity emitter will be removed.

Particle modifiers

Often particles need to change when they get into some particular area. For example if you make a particle system to add some spray to a waterfall, the particles have to stream upwards after they hit the surface of the water. Things like this can be done easily using particle modifiers. Particle modifiers change particles whenever the particles are in the area you have defined for them. They can change any property of the particles (lifetime, velocity, acceleration, color and size). This makes the particle systems really flexible and lively.

Setting up the properties of particle modifiers is quite similar to setting up the properties of the particle system. Everything is done through function calls and many properties are similar to those you can set up for the particle systems. But before setting up properties for particle modifiers you have to create them. To do this you have to call AddModifier(). This function will return the ID of the modifier which has to be saved to a variable, to be able to set up any properties for the modifier. Here are the functions you need to set up the properties of the modifier:

This function sets up the area that's affected by the particle modifier. This works in the same way as setting up the particle origin (see SetParticleOrigin(...) ). Whenever a particle is in this area it gets modified:

SetModifierEffectedArea(
ModifierID,
SecondSphereRadius,
PositionX, PositionY, PositionZ,
RotationX, RotationY, RotationZ,
ScalingX, ScalingY, ScalingZ
)

ModifierID
The ID of the modifier you want to change.

SecondSphereRadius
The radius of the second sphere which is the second border for the affected area.

PositionX/Y/Z, RotationX/Y/Z, ScalingX/Y/Z
The transformation of the two spheres that are the borders of the affected area.

This function sets up a modifier that will change the life time of a particle when it's in the area of that modifier. So if you set this to one, a particle would never die while it's in the affected area, because its lifetime gets increased (and decreased) by one in every single step.

SetModifierLifetimeChange(
ModifierID,
LifetimeChange
)

ModifierID
The ID of the modifier you want to change.

LifetimeChange
The change of the lifetime per step.

The next two functions set up how the modifier changes the velocity and the acceleration of the particles that are in its affected area. Both take a factor and a summand. Whenever a particle is in the affected area, its velocity or acceleration gets changed using the following formula: Value=Value*Factor+Summand. In this formula "Value" refers to the velocity or the acceleration. So by setting up a velocity factor which is a bit lower than 1, you could implement friction. By setting up a negative velocity factor you can reflect particles. Here are the functions for setting up these properties:

This function sets up the velocity modifier of the particles in the affected area:

SetModifierVelocityChange(
ModifierID,
VelocityFactorX, VelocityFactorY, VelocityFactorZ,
VelocitySummandX, VelocitySummandY, VelocitySummandZ
)

ModifierID
The ID of the modifier you want to change.

VelocityFactorX, VelocityFactorY, VelocityFactorZ
The factor that the velocity of the particle is to be multiplied by in every step while it's in the affected area of the modifier. It can be set up individually for every single axis.

VelocitySummandX, VelocitySummandY, VelocitySummandZ
The summand that the velocity of the particle is to be added to in every step while it's in the affected area of the modifier. It can be set up individually for every single axis.

This function sets up the acceleration modifier of the particles in the affected area:

SetModifierAccelerationChange(
ModifierID,
AccelerationFactorX, AccelerationFactorY, AccelerationFactorZ,
AccelerationSummandX, AccelerationSummandY, AccelerationSummandZ
)

ModifierID
The ID of the modifier you want to change.

AccelerationFactorX, AccelerationFactorY, AccelerationFactorZ
The factor that is to be multiplied by the acceleration of every particle in every step while it's in the affected area of the modifier. It can be set up individually for every single axis.

AccelerationSummandX, AccelerationSummandY, AccelerationSummandZ
The summand that is to be added to the acceleration of every particle in every step while it's in the affected area of the modifier. It can be set up individually for every single axis.

Next you can set up a new final color and a new final size for the particles that enter the area of the particle modifier. Since you change only the new final values, there's no rapid change. Instead there will be a smooth interpolation. Here are the functions:

This function sets up a range of possible colors that may be used as new final color for particles that enter the area of the given particle modifier.

SetModifierNewFinalColor(
ModifierID,
MinimumR, MinimumG, MinimumB, MinimumA,
MaximumR, MaximumG, MaximumB, MaximumA
)

ModifierID
The ID of the modifier you want to change.

MinimumR/G/B/A, MaximumR/G/B/A
The range of possible colors that may be set up as the new final color for particles that enter the area of the modifier. The values for these parameters should lie in the range of 0 to 255.

This function changes the final size for particles that enter the area of the modifier.

SetModifierNewFinalSize(
ModifierID,
MinimumNewFinalSize, MaximumNewFinalSize
)

ModifierID
The ID of the modifier you want to change.

MinimumNewFinalSize, MaximumNewFinalSize
The range of possible new final sizes given in world space.


The terrain renderer

The terrain renderer can be used to create giant terrains from height maps. It uses the most efficient techniques for rendering big detailed terrains available. These are called geo-mip-mapping and texture splatting. Since it's difficult to understand how to use the terrain renderer correctly without knowing how these techniques work, I'm going to explain them now.

Geo-mip-mapping and texture splatting

So what is geo-mip-mapping? It is a technique used to create different levels of detail for terrains efficiently. The terrain gets split up into a couple of quadratic tiles. Each of these tiles has five different LoDs with a triangle count in the range from 512 to 2. Due to the efficient structure of the LoDs all of them can use the same vertex buffer. The following graphic illustrates how the different LoDs look.

Illustration not available

Depending on the height difference of the tile and the distance to the camera, Ultimate 3D will decide which level of detail is to be used for rendering the tile. This extreme variety of the LoDs makes the terrain renderer incredibly efficient. One problem you are confronted with frequently when rendering terrain, is how to get good textures for the terrain. Every part of the terrain should have a different color, but a texture that covers the whole terrain would be either extremely big or extremely undetailed. Neither is desirable. One technique to avoid this is the use of a detail map, but that does not look very good.

For this reason there is a technique called texture splatting. When using texture splatting, you can use as many terrain textures as you want, which will be wrapped over the terrain (wrapping is the same as tiling but for textures). Then you can use alpha maps to define the transparencies of textures at different points. The following graphic illustrates how this works:

Illustration not available

Texture splatting gives you the possibility to make your terrain look really detailed and lively. You can use a stone texture for slopes and grass or dirt textures for flat areas. Also, you can put snow on the peaks of your mountains and last but not least you get the possibility to use a separate texture for paths the character can walk on. Note that texture splatting requires support for multi texturing with at least two textures. This means that the return value of GetSimultaneousTextureCount() has to be >=2.

Using the terrain renderer

Using the terrain renderer is really easy. The difficult thing is making the diffuse, height and alpha maps (there are programs to do this). But that's not the topic right now. Here you will learn how to create terrain, and how to set up the different textures for it. As with any other object in Ultimate 3D, a terrain requires its own Game Maker object. And again you have to set up a couple of variables before creating the terrain. Here's a list of them:

x, y, z
The origin of the terrain. Every point on the terrain will have an x-, y- and z-coordinate that's higher than the value of the corresponding variable.

height_map
The file that contains the height map you want to use for creating the terrain. This could be, for example, "gfx/TerrainHeightMap.png". The height map can be grayscale, but if you need more precision you can also use all channels to specify the height. The formula, which is used to get from an R, G, B, A color with each value in the range from 0 to 255 to the height of the vertex is z+height*(R*2^24+G*2^16+B*2^8+A)/2^32.

tile_count_x, tile_count_y
The number of tiles along the x-axis and the y-axis. These are two of the three important sizes that give the size of the terrain. They also define how detailed the terrain will be and how much computing time and memory will be required for rendering and creating it. Since a terrain is made up of really complex and detailed geometry you should not use too high values here. 100*100 would already be very big. Its geometry would take about 57 MB RAM.

tile_size
This is the third value that's important for the size of the terrain. It gives the size along the x- and y-axis of each tile in world space. Since tiles are always quadratic this is only one value.

height
The height of the terrain. A black pixel in the height map would result in a terrain z-position that's equal to the value of the variable z, while a white pixel in the height map would lead to a z-position that's equal to z+height.

base_texture
A string giving the file that contains the first texture that is to be applied to the terrain. This texture does not need an alpha map since it does not cover any other texture.

wrap_count_x, wrap_count_y
The wrap count along the x- and y-axis for the first texture. The wrap count is a value that defines how often the texture is to be repeated. Usually the values for these variables are quite similar to those you've set up for the variables tile_count_x and tile_count_y.

terrain_lod (optional)
The level of detail factor for the terrain which influences the way Ultimate 3D chooses the LoDs when rendering the tiles. This value is optional so you do not need to set it up. In this case Ultimate 3D will use 1 as LoD factor. If you set up a value lower than 1, higher LoDs will be used, if you enter a higher value, lower LoDs will be used.

light_map_resolution (optional)
The resolution that is to be used for the light map (see below for more information about the light map). By default this is 256. One light map is used for the complete terrain. The value for this variable has to be a power of two.

alpha_map_resolution (optional)
The resolution that is to be used for the alpha map of each tile. By default this is 32. Every terrain tile has its own set of alpha maps, so big values for this variable will result in very strong memory usage. A good value for this usually lies somewhere around 2*light_map_resolution/(tile_count_x+tile_count_y). The value for this variable has to be a power of two.

After setting up these variables you can call CreateTerrain() once to create the terrain and associate it with the object the function has been called by. Next you have to add the other textures to the terrain. If there are no other textures that need to be applied, you just have to call ApplyTerrainTextures(). Since terrains are very static objects you do not have to put Step() into the step event this time. However, you've still got to put Destroy() into the destroy event. If you want to use more than one texture using texture splatting, you've got to put a couple of other function calls between the calls to CreateTerrain() and ApplyTerrainTextures(). Since each texture covers the textures that have been applied before it, the order of the function calls matters. Here's the function:

This function adds a texture to the terrain it is called by, using texture splatting:

AddTerrainTexture(
DiffuseMap,
AlphaMap,
WrapCountX, WrapCountY,
AlphaMapChannel
)

DiffuseMap
A string giving the file that contains the diffuse map you want to add to the terrain.

AlphaMap
A string giving the file that contains the alpha map you want to use.

WrapCountX, WrapCountY
The wrap count along the x- and y-axis for this diffuse map. The wrap count is a value that defines how often the texture is to be repeated. To make it a bit less obvious that the textures get repeated, you should use different wrap counts for different textures.

AlphaMapChannel
The channel of the alpha map file, from which the information is to be taken. This can be 0 to use the brightness (for grayscale alpha maps), 1 to use the red channel, 2 to use the green channel, 3 to use the blue channel or 4 to use the alpha channel. Thanks to this parameter one alpha map file can contain alpha maps for multiple diffuse maps.

That is all you need to know about creating terrains. With these few functions you can create giant landscapes that look quite detailed. This would not be possible with normal terrain models. So the terrain renderer is a blessing for people who want to make games with many big levels that play in the outside world.

There are also two useful functions to retrieve information about a terrain object at a given position. One can be used to get the height of the terrain at a particular position, the other one can be used to find out which texture is used how strong at a particular position. Here is their description:

This function returns the height of a given terrain object at a given position. It takes very few computing time.

GetTerrainHeightAtPos(
TerrainID,
X, Y
)

TerrainID
The ID of the terrain object from which you want to retrieve a height value.

X, Y
The position at which you want the height to be determined.

This function returns the intensity of a given texture at a given position on the given terrain. The return value is a real value in the range from 0 to 1, where 0 means that the texture can not be seen at this position and 1 means that the texture is the only one that can be seen at this position.

GetTextureStrengthAtPosition(
TerrainID,
TextureIndex,
X, Y
)

TerrainID
The ID of the terrain object from which you want to retrieve a texture strength.

TextureIndex
The index of the texture you want to get information about. 0 refers to the base diffuse map, 1 refers to the first added texture and so on.

X, Y
The position at which you want the texture strength to be determined.

These two functions are very useful. The first one can be used for simple and very efficient collision detection on terrains, the second one can be used to determine the material, which is present at a particular position. If you want to spread vegetation over a terrain you can use GetTextureStrengthAtPosition(...) to reach that the vegetation is not created on massive rock.

Calculating the light map

If you have already created some terrain you may have noticed that it does not get lit by the light sources. You should be glad about this; it would look awful if vertex lighting were used because the vertex count of the tiles changes whenever the LoD changes. And since the human eye is very sensitive to sudden changes, that would not look good at all. For this reason terrains use a light map instead. A light map is a texture that gets mapped over the whole terrain with a multiplicative texture operation. Note that this requires support for multi texturing with three textures, so the return value of GetSimultaneousTextureCount() has to be >=3.

Ultimate 3D calculates the light map automatically. You just have to tell it which light sources should be used for creating the light map. Often it's not good to use light sources that move a lot, because recalculating the light map in every single frame takes quite a lot of computing time. Instead you should use only the light sources that do not change rapidly. I recommend using a piece of code that makes Ultimate 3D recalculate the light map frequently (e.g. once a second). Here's the function you need to make Ultimate 3D calculate the light map.

This function makes Ultimate 3D recalculate the light map for the terrain object the function has been called by.

CalculateTerrainLightMap(
LightSourceID1, LightSourceID2, ..., LightSourceID10
)

LightSourceID1, LightSourceID2, ..., LightSourceID10
The Game Maker IDs of the light source objects you want to use for calculating the light map. If you put 0 for all of them, Ultimate 3D will use all light sources that are part of the scene.

If the built-in light map calculation function does not satisfy your requirements that's no problem. Ultimate 3D allows you to use a custom light map instead. You create it in any graphics editing software or programs like Earth Sculptor. This way you can use light maps with precomputed shadows or another computation method. If there's a forest somewhere on your terrain you can darken the light map there, to get a shadowed ground. Things like this couldn't be done efficiently without the possibility of setting up custom light maps. So as you can easily see, this function is quite powerful.

This function will set up a texture as custom light map.

SetCustomTerrainLightMap(
TextureIndex
)

TextureIndex
The index of the texture, which is to be set up as new terrain light map. If you want to switch back to the automatically calculated light map, you can pass an unused index for this parameter.

Deforming a terrain

In games with heavy weapons you will not get around deforming the terrain sometimes. If a bomb has exploded a crater has to be created. This can actually contribute to the game play, since players may hide in this crater then. Ultimate 3D offers one very simple function for terrain deformation, which leads to the desired results in most cases.

This function raises or lowers the terrain in the surrounding of a given position.

DeformTerrain(
DeformationCenterX, DeformationCenterY,
DeformationRadius,
RaiseLevel
)

DeformationCenterX, DeformationCenterY
The origin of the deformation in world space coordinates. This is the position at which the deformation will be maximal.

DeformationRadius
The radius around the deformation center in which the terrain will be deformed.

RaiseLevel
The amount by which the terrain will be raised (positive value) or lowered (negative value).

At the deformation center the terrain is raised or lowered by RaiseLevel, at a distance bigger than or equal to DeformationRadius the terrain does not change. Inbetween a cosine curve is used to get a smooth shape. The following graphic illustrates this.

Illustration not available

This method leads to very nice spheric craters. It is efficient enough to be called during game play, but it should not be called every step or similarly frequent. Note that it can not lower terrain below z and can not raise terrain above z+height. If you are using it you should make sure that there is some space, before these limits are reached.

It is recommended not to use the same value for the parameters DeformationRadius and RaiseLevel every time you call it. You should add a randomly computed value. The reason why this is necessary is that Ultimate 3D also modifies the height and normal map, which is used internally to compute the light map. If you use the same parameters every time rounding errors can occur always at the same place. This will result in strange effects in the automatically computed light map. It looks as if stairs were created in the terrain. Random values for the parameters are the easiest way to avoid this (e.g. you could pass 150+random(30) for DeformationRadius).

Now what happens, if you want to save the game after using DeformTerrain(...)? All these craters can be relevant for the gameplay, so they definitely have to be saved among with the other data. The easiest way to do this is saving the height map of the terrain. Ultimate 3D offers a simple function for this purpose.

This function saves the height map of the terrain object it is called by to the given file. All changes, which have been made to the terrain through DeformTerrain(...) are included in this height map. The height map always is a grey scale height map:

SaveTerrainHeightMap(
OutputFile
)

OutputFile
The file to which the height map should be saved. The extension of this file has to be either .bmp or .dds.

Now you know how to change the shape of a terrain and you can even save the resulting data. If you also want to know how to change the appearance of the surface of a terrain read on.

Creating terrain decals

Often it is necessary to cover the terrain with other textures in some places. For example at places where an explosion has happened a texture with burned dirt should appear. The texturing of the terrain itself can not be changed, but it is possible to create overlays on the terrain, which cover it perfectly and use the desired texture. These overlays are called terrain decals. Ultimate 3D features automatic creation of terrain decals. Terrain decals are model objects and therefore they require their own Game Maker object. In this object you have to set up four variables before the creation of the decal:

terrain_object
The ID of the terrain object on which you want to create a decal.

center_x, center_y
The center position of the decal in world space.

size
The size of the decal. Terrain decals are always quadratic unless they need to be cropped, because they are at the border of the terrain.

After setting up these variables you can call CreateTerrainDecal() once to create the decal. As usual you need to place Destroy() in the destroy event. Step() can be placed in the step event, but usually this is not necessary since terrain decals are static. Changing the transformation variables is usually not desired. The variables x, y, z, rotx, roty and rotz should equal zero and scalx, scaly and scalz should equal one. Otherwise the terrain will be in the wrong place.

The created model object can be modified just like every other model object. You can use SetMaterialStageTexture(...) to supply it with a texture, you can use SetModelMaterial(...) to change its color and you can even modify its geometry with the model manipulation functions. The object has multiple levels of detail to reach that it matches the shape of the terrain properly, no matter which LoD this part of the terrain is currently using. This works quite well, but still it may happen that parts of the decal get covered by the terrain sometimes. If this is the case you can increase the value of the variable z a little and call Step(). This will fix the problem. It is up to you to find the perfect value for z. It depends on the terrain and on the size of the decal.

After its creation a terrain decal is completely independent from the terrain it has been created for. It will not change, if the terrain gets deformed or destroyed. For this reason you have to recreate it by yourself, if it has to adapt to a changed terrain. To do so just call Destroy() and CreateTerrainDecal(). If you have changed the terrain decal after its creation you will have to do these changes again.

Creating height and alpha maps

For the creation of height and alpha maps, I can recommend a very comfortable and user-friendly program called Earth Sculptor. Its use is really intuitive and it comes with a detailed manual. Unfortunately the unregistered version is a bit limited. Once you have designed some terrain in Earth Sculptor you can save it. The program will then save a height map, a compressed alpha map, and a light map. The height map can be used by Ultimate 3D as is. You can also use the light map as a custom light map if you want to. The alpha map can also be used as it gets exported thanks to the AlphaMapChannel parameter of AddTerrainTexture(...).

Other free programs for the creation of terrains are Terragen, World Machine, Terranim8or and L3DT. An explanation of how to export height maps from Terranim8or is available on the Ultimate 3D Community and a tutorial on the use of L3DT by Ruud v A can be found in the Ultimate 3D Wiki.


Multiple cameras

By default you have exactly one camera object in your game. But there are particular situations in which more camera objects are required. For example you may want to make a split screen multiplayer game. Or you may want to add security cameras to your game which take pictures that can be displayed on monitors. Also, you may want to add real reflections to your water planes or to other objects. For all these things you need additional camera objects.

In Ultimate 3D you can use up to 32 camera objects at the same time. Using them is quite simple. In principle all camera objects work in the same way as the first one. The only difference is that you have to define the destination the other camera objects should render to. There are two different kinds of destinations. You can either render to a user defined part of the screen as usual or you can render to a special texture. These special textures can be applied to objects, just like standard textures. They can also be drawn to the screen using DrawTex(...) or DrawTexEx(...).

Adding a new camera to the scene

Every camera object has the same variables as the first camera object, which are the following: x, y, z, height, rotx, roty, rotz, view, min_range, max_range, falling, falling_speed, follow, perspective, and distance. To get information about these variables, have a look at the tutorial Getting started. There's also a couple of new variables you need to know about. The first camera object has them as well, but if you do not set them up manually they get set to default values. So here are the new variables:

number
The index of this camera object. The first camera object has the index 0 (there always has to be a camera with number=0). The highest valid value for this variable is 31. You should never have two camera objects with the same index at the same time.

v_left, v_top, v_width, v_height
The position and the dimensions of the viewport you want to use for the camera. This is the area this camera will render to. Note that these values have to be given relative to the left top of the first Game Maker view.

actual_aspect
If you do not need this variable, you can simply ignore it. By default, Ultimate 3D will calculate the aspect of the viewport by dividing v_width/v_height. But since render target textures must have dimensions made up of powers of two, it may happen that this quotient is not the aspect you actually want. In this case you can use this variable to set up another one.

render_target
If you do not want to render to a texture you can simply ignore this variable. Otherwise, you have to set it to the index of the render target texture you want. Render target textures can be created using the function CreateTextureRenderTarget(...) (see below).

Every camera object has to call MoveCamera() in its step event and Destroy() in its destroy event. MoveCamera() will transmit any change of the variables to Ultimate 3D. If the camera has a texture render target set up you have to call RenderToTexture() to make it render. Of course there's no need to call this in every single step. If the objects that use the texture aren't visible it would be a giant waste of computing time to call it. Also, things like a security camera do not need to update their picture in every step. Ten times a second should be enough. This way you can make your program much more efficient.

Clipping planes and orthogonal cameras

Sometimes particular cameras do not need to render everything. There are two possibilities to code this. The easiest one is the use of the advanced culling functions. With these functions you can define which cameras render which objects. But sometimes this is not precise enough. For example, when programming effects for water reflections, you need one camera that renders only the world above the water surface. To do this you can use something called clipping planes. You simply define an arbitrary plane. Everything above the plane will be rendered, and the rest will be invisible. Here's the function to set up clipping planes for the camera it's called by:

SetClippingPlane(
ClippingPlaneIndex,
PlaneOriginX, PlaneOriginY, PlaneOriginZ,
PlaneOrientationLongitude, PlaneOrientationLatitude
)

ClippingPlaneIndex
There can be up to six clipping planes per camera, depending on the graphics device. GetClippingPlaneSupport() returns the number of supported clipping planes. Through this parameter you define which clipping plane you want to set. It can be a value in the range from 0 to 5.

PlaneOriginX/Y/Z
This is an arbitrary point that lies in the plane you want to set up as clipping plane.

PlaneOrientationLongitude, PlaneOrientationLatitude
These two variables define the orientation of the plane (the direction the plane is facing).

Another small but useful feature of Ultimate 3D is that it allows the use of cameras with an orthogonal projection. These are mainly used for utilities like level editors or modeling programs. In an orthographic view objects that are farther away do not seem to be smaller (nor do they seem bigger). There's no vanishing point. You do not have an angle giving the field of view, because the boundaries of the visible area are always parallel. Instead you give a rectangular area, which should be visible by giving a height and a width. Orthogonal cameras are something that can not exist in reality, but for different kinds of editors they are very useful, because they reduce the seen image to two axes.

This function enables an orthogonal projection for the camera it's called by.

SetOrtho(
UseOrthogonalProjection,
SeenAreaWidth, SeenAreaHeight
)

UseOrthogonalProjection
If you pass true for this parameter, Ultimate 3D will enable an orthogonal projection for this camera, otherwise it will get disabled.

SeenAreaWidth, SeenAreaHeight
The width and height of the area that should be visible. These are used instead of the value of the variable view.

Note that orthogonal cameras will not be able to see anything that lies behind them, just like usual cameras. For this reason you have to move them away from the scene far enough.

Creating render target textures and rendering to cube maps

Rendering to textures is a very widely usable technique. You can render an image to a texture and use it like any other texture. Also, render to texture functions are required to make post screen shaders, which can be used for effects such as HDR, motion-blurring or effects that make your game look like a really old movie. Using a post screen shader means that you render your scene to a texture instead of rendering it directly to the screen. Then you render the texture which contains a picture of the scene to the screen, applying some shader effect to it. A later version of Ultimate 3D will make the use of post screen shaders a bit easier.

Since you already know how to set up a texture as a render target for a camera, the only thing you've got to learn about now is how to create a render target texture. To do this you need only one simple function:

This function creates a render target texture with the given dimensions and associates it with the given index.

CreateTextureRenderTarget(
TextureIndex,
RequestedWidth, RequestedHeight
)

TextureIndex
The index the texture is to be associated with. This can be an arbitrary integer value in the range of 0 to 998.

RequestedWidth, RequestedHeight
The requested dimensions for the render target texture. Like any other type of texture, most graphics devices do not support render target textures that are not made up of powers of two. Some really old graphics devices support only quadratic textures.

It's also possible to render to cube textures. This way you can make absolutely realistic reflections. You render a picture of the environment to a cube texture and then you use cubic environment mapping to map this picture of the environment onto an object. This is the perfect technique for making reflections, but it's not difficult to use. The first step is the creation of a cube texture that can be used as render target. To do this you have to call only one function:

This function creates a cube texture with the given dimensions that can be used as a render target and associates it with the given index.

CreateCubeTextureRenderTarget(
TextureIndex,
RequestedEdgeLength
)

TextureIndex
The index the texture is to be associated with. This can be an arbitrary integer value from 0 to 998.

RequestedEdgeLength
The side length of the cube texture you want to create. The cube texture will be made up of six 2D textures with a size of RequestedEdgeLength * RequestedEdgeLength. You should enter a power of two for this parameter since most graphics devices do not support other texture sizes.

Rendering to cube textures is not done using a camera object. It's much easier. Since all directions are represented by a cube texture, there's no need to give a view direction or an angle of view. The only information that matters is the position, and possibly a color with an alpha value. When the whole scene has been rendered to the cube texture, vertex quads with this color and transparency value will be drawn over the finished texture. You should always enter an alpha value lower than 255, or else the whole rendering process would make no sense since the results would not be visible. You should usually put something like r=g=b=0 and a=128. Here's the function:

This function renders the scene as it's seen from the given position to the cube texture render target.

RenderToCubeTexture(
CubeTextureIndex,
PositionX, PositionY, PositionZ,
ColorR, ColorG, ColorB, ColorA
RoomIndex1, RoomIndex2, ..., RoomIndex8
)

CubeTextureIndex
The index of the cube texture render target you want to render to. This has to be the index of a cube texture render target that has been created using CreateCubeTextureRenderTarget(...), otherwise this function will fail.

PositionX, PositionY, PositionZ
The position from where the scene should be rendered.

ColorR, ColorG, ColorB, ColorA
The color of the vertex quads that are to be rendered to the cube texture after rendering the scene. The values for these parameters should be in the range from 0 to 255. If you do not want these vertex quads to be drawn, just put 0 for ColorA. Passing 255 for ColorA makes no sense, because then you would not see the scene.

RoomIndex1, RoomIndex2, ..., RoomIndex8
The indices of the rooms that are to be rendered. For more information about room indices have a look at the advanced culling functions. Room 0 will always be rendered, regardless of the values you pass for this parameter.

Drawing 2D graphics to textures

I would have called this subchapter "Drawing textures and text to textures" if that did not sound too weird. But that's just what you will be able to do once you've read and understood it. Imagine you have a control panel in your game on which miscellaneous settings get displayed. So you have to draw 2D graphics to a texture that can be used as a texture for the monitor. That's just what this feature is meant to be used for. You already know the functions that can be used for drawing textures and text to textures: DrawTexEx(...) and DrawText(...). There's only one additional parameter I did not tell you about in the novice section to avoid confusing you. So here's the full syntax of these two functions.

DrawTexEx(
TextureIndex, Left,Top, Width,Height, X,Y, ScalingX,ScalingY, Rotation, R,G,B,A,
CameraIndex
)

CameraIndex
The index of the camera to whose render target you want to draw the texture.

DrawText(
FontIndex, X, Y, Text,
CameraIndex
)

CameraIndex
See above.

Note that the texture will not be drawn to the render target texture before the corresponding camera object calls RenderToTexture().


Math functions

Ultimate 3D offers a set of useful functions, which can be used to perform calculations with vectors and matrices. Mathematically, matrices can be very complex objects and vectors even more so. But for our purposes we will only work with two easy-to-understand special cases. For those of you who are impatient, here is the description in one sentence. Matrices can be used to describe transformations and vectors are sets of three floating point values that can be used to describe a position or a direction. But let's go into a bit more detail.

A vector is made up of an x-coordinate, a y-coordinate and a z-coordinate. You should already be familiar with vectors describing positions from the x, y and z variables of objects. But vectors can also describe a direction with a length. For this purpose they are seen relative to the origin. If you draw a line from the origin (the vector with the coordinates (0|0|0)) to the vector describing the direction, the direction your imaginary pencil went in is the direction the vector points to. You can imagine it as an arrow. And if you want to move an object with a position described by a position vector, into a direction described by a direction vector, you just have to add the direction vector to the position vector. If a direction vector has a length of one it's called a normalized vector.

A matrix can be used to describe a transformation. In case you have forgotten what a transformation is, here's an explanation again: in Ultimate3D, a transformation is described by three groups of variables: x, y, and z; rotx, roty, and rotz; and scalx, scaly, and scalz. The rotx/y/z variables perform a rotation, the scalx/y/z variables perform a scaling and the x, y and z variables perform a translation. So every transformation can be seen as a combination of scaling, translation and rotation.

The great thing about matrices as a method to describe transformations, is that you can use them for different calculations very efficiently. They can also be used to solve problems which would be very complicated to solve without matrices. There's a very useful notation that can be used to describe the function of each matrix. This notation can be explained very well through a little example. If you have a model in Ultimate 3D, all information about its geometry is given in the coordinate system of the model. The model has its own coordinate system, the one you made in the modeling program. This coordinate system is called model space. The transformation that's defined through the transformation variables of this model transforms the information that is given in model space into the global coordinate system, which is called world space. For this reason the transformation is called a model-to-world space transformation. So the notation is that a transformation that transforms something from space A into space B is called an A to B space transformation or an A to B space matrix. The terms matrix and transformation can be used more or less synonymously in this context.

Now if you have an A to B space transformation and a B to C space transformation you can transform the A to B space transformation by the B to C space transformation and as a result you'll get an A to C space transformation. So if you transform a vector by this combined matrix it will get transformed from space A to space C directly! That's why matrices are so incredibly useful. You can chain them in any way you want. If you want to transform a group of objects, you can build a matrix that describes this transformation and use it to transform the single transformation matrices of the objects. Then they'll behave as if they were glued together, just like you want it to be. With transformation matrices you can easily rotate around the whole world. If you want to, you can turn it head over. You can transform it into any coordinate system you want.

Now that was a lot of theory. But this theoretical background knowledge will make the practical use of the matrix and vector calculation functions a lot easier. So let's go on with the practical part.

The vector and matrix calculation functions

Since Game Maker can not handle data structures like vectors and matrices on its own, Ultimate 3D does the work. Game Maker just gets identifiers as return values. These identifiers can either be the return value of a function, or one of the parameters. Almost every matrix or vector calculation function takes a parameter that's called OutputMatrixID or OutputVectorID. To avoid endless repetitions, I'm going to explain how this parameter works only once.

OutputMatrixID / OutputVectorID
The ID of the matrix or vector you want the return of the function to be written to. If you do not want to overwrite an existing matrix or vector you can pass -1 instead. Then Ultimate 3D will create a new matrix or vector and return its ID. You have to save this ID. When you do not need this matrix or vector anymore, you have to release the matrix or vector using ReleaseMatrix(MatrixID) or ReleaseVector(VectorID). Otherwise the matrix or vector will not get deleted and Ultimate 3D will tell you about too many matrices or vectors being created after a while.

Passing -1 to one of these two parameters is the only possibility to create a new matrix or vector. But lots of matrices already exist. Namely these are the transformation matrices of all model, primitive and camera objects. All these matrices can be accessed through one simple function.

This function returns the ID of the transformation matrix of the object with the given ID:

GetObjectTransformation(
ObjectID
)

ObjectID
The Game Maker ID of a model, primitive or camera object.

Through the matrix ID GetObjectTransformation(...) gives you, you can read or write to the matrix of an object. This way you can change its transformation without touching its transformation variables. But you should always do this after the call to Step(), because Step() usually overwrites the transformation matrix of an object. Besides object transformation matrices, there is another group of matrices that already exists in large amounts: the transformation matrices of bones and meshes. These can be retrieved as well, but you can not just retrieve their IDs.

This function copies the bone-to-world space transformation matrix of the given bone to the given output matrix.

GetBoneTransformation(
OutputMatrixID,
BoneIndex
)

OutputMatrixID
See above.

BoneIndex
The index of the bone you want to retrieve the transformation of. You can use GetBoneIndex(...) to get this value.

The matrix that gets returned by this function will always be the transformation matrix of the frame that can be seen on the screen at the moment, so if you use this function to append objects to bones, it may happen that you get a following effect. To avoid this you can use UpdateSkeleton() to update the bone transformation matrices of the model object that calls this function. But be careful with this. It takes quite a bit of computing time. UpdateSkeleton() is also useful for the following function.

If you are using the model manipulation functions you will often need to get the transformation matrix of meshes. Those are needed to get vertex positions from mesh space into world space. A single mesh can be used multiple times within a model, especially in models created with Anim8or. For example, if you have modeled an object for the upper leg and append this object to the bone for the left leg and to the bone for the right leg this mesh will be used twice. For this reason two functions are needed to be able to retrieve the mesh transformations.

This function returns how often the given mesh is used in the model object it is called by.

GetMeshOccurrenceCount(
MeshIndex
)

MeshIndex
The index of the mesh, for which you want to retrieve the occurrence count. This can be any integer value in the range from 0 to GetMeshCount()-1.

This function outputs the current mesh to world space transformation of the given occurrence of a given mesh:

GetMeshOccurrenceTransformation(
OutputMatrixID,
MeshIndex,
OccurrenceIndex
)

OutputMatrixID
See above.

MeshIndex
The index of the mesh, for which you want to retrieve the transformation of one of its occurrences. This can be any integer value in the range from 0 to GetMeshCount()-1.

OccurrenceIndex
The index of the occurrence of the given mesh, for which you want to retrieve a transformation matrix. This can be any integer value in the range from 0 to GetMeshOccurrenceCount()-1.

Now that you know all functions that can be used to retrieve existing matrices, it's time to make you familiar with the matrix creation functions, which allow you to create new transformation matrices. They are all quite similar, so I'm just going to list them right here. The parameters are explained below.

This function creates a matrix that performs a translation:

CreateTranslationMatrix(
OutputMatrixID,
TranslationX, TranslationY, TranslationZ
)

This function creates a matrix that performs a scaling:

CreateScalingMatrix(
OutputMatrixID,
ScalingX, ScalingY, ScalingZ
)

This function creates a matrix that performs a rotation:

CreateRotationMatrix(
OutputMatrixID,
RotationX, RotationY, RotationZ
)

This function creates a full transformation matrix which performs a scaling, a translation and a rotation:

CreateTransformationMatrix(
OutputMatrixID,
TranslationX, TranslationY, TranslationZ,
RotationX, RotationY, RotationZ,
ScalingX, ScalingY, ScalingZ
)

OutputMatrixID
See above.

TranslationX, TranslationY, TranslationZ
These variables define a translation. They are equivalent to the transformation variables x, y and z.

RotationX, RotationY, RotationZ
These variables define a rotation. They are equivalent to the transformation variables rotx, roty and rotz.

ScalingX, ScalingY, ScalingZ
These variables define a scaling. They are equivalent to the transformation variables scalx, scaly and scalz.

But creating matrices is not everything; you also need some functions to perform calculations with them. The first function that's available in this context does not really calculate anything, it just copies some data. However, it can be helpful in many cases.

This function copies a matrix to the given output matrix.

CopyMatrix(
OutputMatrixID,
InputMatrixID
)

OutputMatrixID
See above.

InputMatrixID
The identifier of the matrix you want to copy to the output matrix.

The next function transforms one matrix by another matrix:

TransformMatrix(
OutputMatrixID,
InputMatrixID1, InputMatrixID2
)

OutputMatrixID
See above.

InputMatrixID1, InputMatrixID2
The two input matrices. The first matrix will get transformed by the second matrix. This means that if the first matrix is an A to B space matrix and the second matrix is a B to C space matrix, the resulting matrix will be an A to C space matrix. Note that the order of the matrices does matter.

This function inverts a matrix. The inverse of a matrix is a matrix that does the exact opposite transformation of the original matrix. So if the original matrix is an A to B space matrix, the inverse of this matrix will be a B to A space matrix. If you transform a matrix by its inverse, the resulting matrix will be a matrix that does not do anything (the identity matrix). This is because if you go from A space to B space and then from B space back to A space, the result will be a transformation from A space to A space, which is no transformation at all.

InvertMatrix(
OutputMatrixID,
InputMatrixID
)

OutputMatrixID
See above.

InputMatrixID
The ID of the matrix you want to invert. Note that matrices with a scaling value of 0 can not be inverted. If the input matrix can not be inverted the input matrix itself will get returned.

This function interpolates between two given matrices using a very good interpolation method. The translation and the scaling are interpolated linearly, the rotation is interpolated using a method called spherical linear interpolation.

InterpolateMatrices(
OutputMatrixID,
InputMatrixID1, InputMatrixID2,
InterpolationFactor
)

OutputMatrixID
See above.

InputMatrixID1, InputMatrixID2
The two matrices, between which you want to perform the linear interpolation.

InterpolationFactor
A factor, which defines the influence of each of the two matrices. Usually this is a value in the range from zero to one. At 0.0 the output matrix will equal the first input matrix, at 1.0 the output matrix will equal the second matrix.

The next function can be used to retrieve the entries of a matrix. You need to know how transformation matrices work to be able to have any use of this function. The most common use of this function is using it to pass matrices to vertex shaders.

GetMatrixEntry(
InputMatrixID,
LineIndex, ColumnIndex
)

InputMatrixID
The ID of the matrix from which you want to retrieve an entry.

LineIndex, ColumnIndex
The index of the line and the column from which you want to retrieve an entry. Both values should be integers in the range from 0 to 3, where 0 refers to the first line resp. column and 3 to the last.

Now you can relax, because the really complicated part lies behind you. You can now use your knowledge about matrices to transform complete objects. But matrices can also be used to transform single vectors. Before you can do so you need to know how to create vectors. So let's go on with this.

The following function creates a vector from the given x-, y-, and z-coordinates.

CreateVector(
OutputVectorID,
X, Y, Z
)

OutputVectorID
See above.

X, Y, Z
The x-, y-, and z-coordinates you want to create the vector with.

Another possible way of creating vectors is creating them from a longitude and a latitude angle. To do this you can use the following function:

CreateDirectionVector(
OutputVectorID,
Longitude, Latitude
)

OutputVectorID
See above.

Longitude, Latitude
The longitude and latitude angles you want to create this vector from. To get a description of how longitude and latitude angles work look at the description of the function Move(...).

To retrieve information from vectors you can use the following function:

GetVector(
VectorID,
VectorElementID
)

VectorID
The identifier of the vector from which you want to retrieve information.

VectorElementID
This parameter identifies the coordinate that is to be returned. This can be 1 for the x-coordinate, 2 for the y-coordinate or 3 for the z-coordinate.

The next function is very basic, but it can be useful though. It takes a vector and copies it:

CopyVector(
OutputVectorID,
InputVectorID
)

OutputVectorID
See above.

InputVectorID
The vector, which is to be copied to the given output vector.

When you have got two vectors you can take the sum of them. The vectors get added together component by component. So (x | y | z)+(x' | y' | z') will result in (x+x' | y+y' | z+z'). This is done by the following function:

CalculateVectorSum(
OutputVectorID,
InputVectorID1, InputVectorID2
)

OutputVectorID
See above.

InputVectorID1, InputVectorID2
The identifiers of the vectors you want to add together.

Often you also need to compute the difference between two vectors. This can be done through the following function.

CalculateVectorDifference(
OutputVectorID,
InputVectorID1, InputVectorID2
)

OutputVectorID
See above.

InputVecorID1
The ID of the vector, which should function as minuend.

InputVectorID2
The ID of the vector, which should function as subtrahend.

The other basic mathematical operation of vectors is multiplying them by a scalar. If you have a vector v=(x | y | z) and a scalar (or floating point value) k, k*v will be (k*x | k*y | k*z). This scalar multiplication is done by the following function:

CalculateVectorScalarProduct(
OutputVectorID,
InputVectorID,
ScalarFactor
)

OutputVectorID
See above.

InputVectorID
The identifier of the vector which is to be multiplied by a scalar.

ScalarFactor
A floating point value which will be multiplied by every single component of the vector.

Especially for direction vectors it is a quite common case that they need to be normalized. Normalizing a vector means multiplying it by the reciprocal of its length to reach that its new length is exactly one. As I already said vectors with a length of one are called normalized vectors. This can be done easily through the following function:

NormalizeVector(
InputOutputVectorID
)

InputOutputVectorID
The ID of the vector, which is to be normalized. This vector is the input and the output. After the function call the vector saved in InputOutputVectorID has a length of exactly one and points still in the same direction.

Often the length of a vector is needed. The length of a vector v=(x | y | z), which is referred to as |v|, is defined by sqrt(x*x+y*y+z*z), so it is the distance from the origin to the vector (seen as position vector). The following function can be used to get the length of a vector. The length is its return value.

CalculateVectorLength(
VectorID
)

VectorID
The identifier of the vector you want to calculate the length for.

If you are working with direction vectors it is often necessary to convert them back to longitude and latitude values. For example you may need them to use them as parameters for CheckRayIntersection(...). Computing those manually is a bit complicated. For this reason Ultimate 3D has handy functions for this purpose.

This function computes the longitude angle, which describes the given vector correctly and returns it.

CalculateVectorLongitude(
VectorID
)

VectorID
The ID of the vector for which you want to determine the longitude. The vector does not need to be normalized.

This function computes the latitude angle, which describes the given vector correctly and returns it.

CalculateVectorLatitude(
VectorID
)

VectorID
The ID of the vector for which you want to determine the latitude. The vector does not need to be normalized.

Now a really useful function follows. It is not surprising that it exists, but it is handy anyway. It can be used to transform a vector by a matrix.

The following function transforms the given vector by the given matrix. If the vector is given in A space and the matrix describes an A to B space transformation the resulting vector will be the same vector in B space.

TransformVector(
OutputVectorID,
InputVectorID,
InputMatrixID
)

OutputVectorID
See above.

InputVectorID
The identifier of the vector which is to be transformed.

InputMatrixID
The identifier of the input matrix the vector is to be transformed by.

Sometimes this function can be quite impractical though. E.g. if you transform a direction vector you usually just want the scaling and the rotation to be applied, while the translation is of no interest. And if you have already rotated a vector using some other matrix it may be that you just want the translation of a matrix to be applied. In cases like this the following function is very useful.

This function transforms the given vector by the given matrix using only the given components of the transformation. If all components of the transformation are used (true passed for the last three parameters) this function does exactly the same as TransformVector(...). All combinations of values are valid for the last three parameters:

TransformVectorEx(
OutputVectorID,
InputVectorID,
InputMatrixID,
ApplyRotation,
ApplyTranslation,
ApplyScaling
)

OutputVectorID
See above.

InputVectorID
The identifier of the vector which is to be transformed.

InputMatrixID
The identifier of the input matrix, which contains the transformation that is to be applied to the vector in part or in whole.

ApplyRotation
If you pass true for this parameter the rotation of the transformation in the given matrix effects the vector, otherwise it does not.

ApplyTranslation
If you pass true for this parameter the translation of the transformation in the given matrix effects the vector, otherwise it does not.

ApplyScaling
If you pass true for this parameter the scaling of the transformation in the given matrix effects the vector, otherwise it does not.

Besides these, Ultimate 3D offers some functions to perform common mathematical operations with vectors. The most common operation is the dot product. If you've got two vectors v=(x | y | z) and v'=(x' | y' | z') the dot product <v, v'> is equal to x*x' + y*y' + z*z'. The great thing about the dot product is that the scalar product is equal to the cosine of the angle between the two vectors, multiplied by the length of the first vector and the length of the second vector. So to put that in a little formula: if a is the angle between v and v' the scalar product is equal to cos(a) * |v| * |v'|. So if the vectors are normalized (have a length of one) the dot product is equal to the cosine of the angle. This means that the dot product will return 1 if the vectors are parallel and 0 if they are orthogonal. And that's only one of the many uses of the dot product. To implement directional lighting the only thing that needs to be done is calculate one dot product.

This function calculates the dot product of the given two vectors and returns it.

CalculateDotProduct(
InputVectorID1, InputVectorID2
)

InputVectorID1, InputVectorID2
The identifiers of the two vectors that are to be used to calculate the scalar product. The order of the vectors does not matter.

Another operation that is performed with two vectors quite often is calculating the cross product. The cross product results in a new vector, which is orthogonal to the two input vectors. The direction of this vector depends on the order in which the two input vectors are passed to this function. You can remember what this direction will be using your right hand: Build a coordinate system using your thumb (points to the left), your forefinger (points up) and your middle finger (points away from you). Now if the first vector points into the direction of your thumb and the second vector points into the direction of your forefinger, then the resulting vector will point into the direction of your middle finger.
The length of the vector equals twice the area of the triangle that is formed by the two input vectors in relation to the null vector (origin). It is also equal to the product of the lengths of the two input vectors and the sine of the angle between them. If the vectors are parallel, the returned vector will be the zero vector.

This function computes the cross product of two vectors.

CalculateCrossProduct(
OutputVectorID,
InputVectorID1, InputVectorID2
)

OutputVectorID
See above.

InputVectorID1, InputVectorID2
The identifiers of the vectors for which you want to calculate the cross product. The order does matter. If you swap these two parameters this will negate the resulting vector.

There is also another set of useful matrix related functions, which use vectors for their output data. They convert matrices back to transformation variables.

This function computes the translation of a given transformation matrix and outputs it to a vector.

ComputeMatrixTranslation(
OutputVectorID,
InputMatrixID
)

OutputVectorID
See above. After the call to this function the x-component will contain the x value, the y-component will contain the y value and the z-component will contain the z value.

InputMatrixID
The identifier of the matrix for which you want to determine the translation.

This function computes the scaling of a given transformation matrix and outputs it to a vector.

ComputeMatrixScaling(
OutputVectorID,
InputMatrixID
)

OutputVectorID
See above. After the call to this function the x-component will contain the scalx value, the y-component will contain the scaly value and the z-component will contain the scalz value.

InputMatrixID
The identifier of the matrix for which you want to determine the scaling.

This function computes the rotation of a given transformation matrix and outputs it to a vector.

ComputeMatrixRotationAngles(
OutputVectorID,
InputMatrixID
)

OutputVectorID
See above. After the call to this function the x-component will contain the rotx value, the y-component will contain the roty value and the z-component will contain the rotz value.

InputMatrixID
The identifier of the matrix for which you want to determine rotx, roty and rotz values that describe it correctly.

Since this is needed very often here is another useful function, which combines the above three functions. This function applies the transformation described by the given matrix, to the transformation variables of the object it is called by (x, y, z, rotx, roty, rotz, scalx, scaly, scalz).

ApplyTransformationMatrix(
InputMatrixID
)

InputMatrixID
The matrix from which you want to determine the new transformation for the object.

Finally here are some quite special math functions. They can be used to convert between screen coordinates and world space coordinates.

This function computes a direction vector, which describes the ray starting at the camera and going to whatever can be seen at the given pixel of the screen. Note that the returned vector is usually not normalized. The y-component of the returned vector is always one.

ScreenCoordToVector(
OutputVectorID,
ScreenCoordX, ScreenCoordY,
CameraIndex
)

OutputVectorID
See above.

ScreenCoordX, ScreenCoordY
A screen position in pixels, relative to the left top of the inner border of the Game Maker window. This is the screen position for which the corresponding ray will be computed.

CameraIndex
The index of the camera for which the operation is to be performed. If you want to calculate the ray for the default camera pass 0 or nothing, otherwise pass the value of the number variable of the camera object you want to check.

This function does the exact opposite operation of ScreenCoordToVector(...). It outputs a screen position vector from a given 3D world space coordinate. The x and y component of the output vector are viewport coordinates and the z component is the z coordinate of the given position transformed into camera space, which can be used to find out, whether the position is behind the camera.

CoordToScreen(
OutputVector,
X, Y, Z,
CameraIndex
)

OutputVectorID
See above.

X, Y, Z
The world space coordinates of the position that is to be translated into screen space.

CameraIndex
The index of the camera for which the operation is to be performed, which is the value of the number variable of the corresponding camera object. Pass 0 or nothing if you want to perform this operation for the default camera.


Performance optimization

If you are working on a big project it's extremely important that you care about good performance. It's a widespread belief that the graphics that can be made depend only on the 3D engine that's being used. Of course a better engine can lead to better results. But the more potential an engine has, the more difficult it is to use all the features that are required to make the game run fluently. The complexity of the game that can be made with an engine depends only on how many possibilities are offered to optimize performance of the game. Usually the easiest way to realize something is not the most efficient one. It needs some experience and knowledge to find the optimal solution. You also need an understanding of how Ultimate 3D works internally. This tutorial will help you to learn these things.

General hints

For advanced programmers the following general hints should be quite self-evident. But since it is extremely important to know about them they will be listed here again. One of the most important things you have to know is that the vertex count and the triangle count are extremely important factors for the performance of your game. Calculating all the data that is required for a vertex takes some computing time so you should use as few vertices as possible when making a new model for your game. If you are working in a professional modeling program such as Blender or 3D Studio Max you can use plugins to optimize your meshes (in Blender it is the Decimate modifier). Less complex modeling programs will at least have a feature that allows you to see how many vertices the model is made up of currently. This is useful for reducing the vertex count manually. Of course, the easiest way to get models with a low vertex count is not to waste vertices from the start of modeling. Be extremely careful with features that subdivide the faces of the models to make them look smoother. Also, do not use high subdivision counts when creating primitive objects (the variables partsx, partsy and partsz).

The next hint is less self-evident. The performance of your game also depends on how many pieces of geometry have to be rendered separately. If you have two pieces of geometry in your model, Ultimate 3D will only render them together if they are using the same material and are part of the same mesh. So the absolute worst case would be a model in which you have forty different meshes, each made up of no more than 20 triangles but still using three different materials. In this case Ultimate 3D would not be able to draw more than 10 triangles simultaneously. So be economical with the number of different materials and use meshes that are made up of a lot of geometry when modeling. Often the use of one textured material is more efficient than the use of three or four different materials and usually it even looks better this way.

You should not use too big textures since they take a lot of memory. The more memory your textures eat up the earlier the video memory of the graphics device will be full. Then system memory will need to be used and in this case the texture needs to be passed from the system memory to the video memory in every step, which is very time intensive. So if you have lots of textures loaded simultaneously you should release as many as possible by calling ReleaseTexture(...). Apart from that, you really should use dimensions that are made up of powers of two for all your textures. If you load a texture with a size of 512*512 it will be loaded with exactly this size, but if you load a texture with a size of 513*513 it will be scaled up to the next higher powers of two which are 1024*1024. It's quite obvious that this is a giant waste of memory and computing time.

Be economical with the number of light sources in your scene. If you have an extremely high number of light sources it will take a lot of computing time for every single vertex. In this case it may be more efficient to learn how to use Deled or other modeling software to realize light mapping. To get an efficient game you have to find a compromise between flexible and static solutions very often. Flexibility always requires a lot of computing time but is usually less work and makes the game seem more lively. Static solutions take more memory since you have some precomputed data, but will take much less computing time and may look much better since it does not matter how much time it took to precompute the static data. The comparison between per vertex or per pixel lighting and light mapping is a great example of this. A repetition of how expensive shadow casting is in terms of computing time hopefully is not needed here.

Finding the most efficient way to draw a piece of geometry

Ultimate 3D offers a couple of different possibilities to handle a piece of geometry loaded from a model file. You can create LoDs for it or you can handle it as a primitive object. Either of these possibilities could be the best one, depending on the situation. Sometimes it may be more efficient to draw a model with LoDs; sometimes the opposite may be the case. There are also cases in which it's much more efficient to handle geometry as a primitive object; in other cases it makes no sense at all. This subchapter contains some information about the correct use of the different ways to handle models and gives information on which is best in different scenarios.

Let's begin with the proper use of different levels of detail. It's a really practical feature that Ultimate 3D can do automatically and often it can greatly increase the performance of your game. In general, it should be used for any model that has to look very detailed when it's close to the camera. But as I already said you should never use it for models that use frame animation (either models loaded from *.md2 files or animations created using AddFrame(...) ). It also makes no sense to use LoDs if you can not get far enough away from the model anyway. For example, if you have some first person game in which you are always only in small rooms, the use of LoDs would be inefficient. In this case there's a much better way to optimize the game. It will be introduced below.

There are some cases in which it can lead to a giant improvement in performance when you use the function CreatePrimitiveFromModel() and other cases in which it would be extremely inefficient. In general, primitives are perfect for all kinds of simple, static objects the level is made up of. For example, if you have simple wall models that do not need any effects to look good and are made up of less than 100 vertices, it would be the perfect opportunity to use a primitive. Primitives are also perfect for rendering vegetation. So you really should use it if it's possible. Of course it's a bit difficult to make a complex model with only one texture but the result is worth the work. I recommend using *.an8 files for models you want to use with this function, because for those Ultimate 3D will put everything together to one mesh automatically.

Disabling alpha blending

In contrast to earlier versions of Ultimate 3D Ultimate 3D 2.0 beta 3 and newer has no problems with partially transparent textures. In earlier versions those lead to display failures very often. Unfortunately the price for this is quite high. The whole object needs to be rendered twice in case it uses a partially transparent texture. The first pass renders only the parts with full opacity, the second pass renders only the parts with full transparency. Since this can reduce the performance strongly, Ultimate 3D allows you to disable this method for particular objects. Then partially transparent stuff will become fully transparent, but it will speed things up a lot.

This function turns alpha blending on or off for the given model object or primitive object. By default alpha blending is turned on.

SwitchAlphaBlending(
EnableDisable
)

EnableDisable
If you pass true for this parameter, partially transparent parts will be rendered in a second pass (performance costly), otherwise partially transparent parts will become completely invisible (efficient).

Measuring important performance factors

To be able to tell, whether you have optimized your project well, you need to be able to measure the factors, which influence the performance. That are primarily two factors. The first one is the well-known triangle count. The second one is the number of draw calls, which were necessary to render the scene. Whenever Ultimate 3D passes a piece of geometry to the graphics device, to reach that it gets rendered one draw call is needed for this. For example every group of primitive objects with identical properties requires one draw call per step per camera and every model object requires one draw call per step per material per camera. These two performance relevant sizes can be retrieved easily in Ultimate 3D.

This function returns the number of triangles which were drawn in the previous step. This includes triangles, which did not get rendered due to backface culling.

GetDrawnTriangleCount()

This function returns the number of draw calls which were needed to draw the scene in the previous step.

GetDrawCallCount()

The return values of these functions can be very interesting. You can see how well the used culling techniques (see below) work and which objects have a strong impact on the drawn triangle count. You can see how the use of LoDs reduce the triangle count and how the use of primitive objects reduces the draw call count. While you are tuning the performance of your application you should add some code, which draws these sizes to the screen, so that you always can have an eye on them.

The advanced culling functions

The concept behind culling is quite simple. It means that you avoid rendering anything that is not visible currently. By default Ultimate 3D uses two culling techniques; back face culling and frustum culling. Back face culling means that only the front side of each triangle is being rendered. This way only half as much geometry needs to be rendered. Frustum culling means that only the objects that are in the visible area, the so called view frustum, of the camera get rendered. The following graphic illustrates what a view frustum is:

Illustration not available

You can also directly access Ultimate 3D's frustum culling. This is useful if some code needs to be executed only if a particular object is visible. For example if you have a security monitor, which uses a camera with texture render target, this camera needs to render only, if the security monitor can be seen. To find out whether an object is in the view frustum you can use the following function.

This function returns true if the given bounding box is in the view frustum of the camera or false if it is not.

CheckBoundingBoxVisibility(
BoundingBoxMinimumVectorID, BoundingBoxMaximumVectorID,
BoundingBoxTransformationID,
CameraIndex
)

BoundingBoxMinimumVectorID, BoundingBoxMaximumVectorID
The ID of two vectors defining the bounding box. The bounding box will be a cuboid, where the left bottom back corner has the coordinate BoundingBoxMinimumVectorID and the right top front corner has the coordinate BoundingBoxMaximumVectorID. If BoundingBoxMinimumVectorID is negative the vector (-1,-1,-1) will be used, if BoundingBoxMaximumVectorID is negative the vector (1,1,1) will be used.

BoundingBoxTransformationID
The ID of a transformation matrix, which will be used to transform the bounding box specified by the previous two parameters. If this parameter is negative no transformation will be used.

CameraIndex
The index of the camera, which has the view frustum that is to be used. This is the value of the variable number, not the ID of a camera object.

Anyway it is time to get back to the advanced culling functions. If some geometry gets rendered that is not visible, that means that it has been covered by another object. To avoid that geometry gets covered in this case, a third culling technique is required. And that's just what the advanced culling functions are good for. They work in the following way. First, you assign every object in the whole scene - whether it's a primitive, a model, a terrain, a particle system, or a light source - to a room. A room is nothing but an index and the sum of objects being assigned to it. By default every object is assigned to the room with index 0. Next you need to set up which room can be seen by which camera. By default only the room with index 0 is set to visible for every camera. This can be done either manually or Ultimate 3D can do it automatically.

In indoor levels this can be especially useful. Whenever the player opens a door, a new room gets set to visible. If the player goes through the door and closes it, the old room gets set back to invisible. This way you can reduce the number of rooms that have to be rendered to one or two. All other rooms are of no interest. The light sources will light only the objects, which are associated with the same room, which leads to a performance increase as well. The only exception from this rule are light sources which are associated with room 0. Those will light absolutely all objects.

The functions that are required for this feature are really simple. There are only two functions, one to assign objects to rooms, and one to set a room to visible or to invisible for a particular camera.

This function assigns the object it is called by to a room.

SetObjectRoom(
RoomIndex
)

RoomIndex
The index of the room you want to assign the object to. This can be an arbitrary integer value in the range from 0 to 1999.

This function sets the given room to invisible or to visible for the camera it is called by. Note that this function has to be called after MoveCamera().

SetRoomVisibility(
RoomIndex,
NewVisibilityState
)

RoomIndex
The index of the room you want to set up the visibility state for. This can be an arbitrary integer value in the range from 0 to 1999.

NewVisibilityState
The visibility state you want to set up for the given room. This can be either true to set the room to visible or false to set the room to invisible.

Above I said that setting rooms to visible or invisible can be done either manually or automatically. As you can see the manual way is very simple, but it will result in a lot of work for you. And you've got already enough work, assigning all objects to rooms. So the automatic way should be preferred in general. It's based on something called portals.

A portal can be a door, a window, a gate, a street, a canyon or anything else you can pass through or watch through. It's something that can lead the camera object or another object from a room A to a room B. Portals are being represented by the most simple shape, the circle. If a camera passes through a portal it will get into a new room, which means that this room will be visible. And all rooms that lie behind portals that are visible will be visible as well. Since this definition is quite abstract, it's going to be clarified through a couple of illustrations showing different situations.

Illustration not available

This is a very simple case. There are four different rooms, with four doors between them. Each door has a (blue) portal set up for it. Initially the camera is in room 1, but then it moves through the portal from room 1 to room 2, so after that it is in room 2 and Ultimate 3D is able to detect this, since it has information about the portal. About the same happens for the model object. At the beginning it is in room 1, so it will be only visible if room 1 is visible. Then it moves to room 4 and Ultimate 3D detects that, due to the portal. So after that it will only be visible if room 4 is visible. For model objects and other geometry objects this is everything that's needed, but for the camera this is a bit too simple, because the camera can possibly see more rooms than the one it is currently in. When the camera is in room 1 it can also see room 2 through the door. To be more precise it can see room 2 whenever it can see the portal from room 1 to room 2. So room 2 will be visible whenever the door to room 2 is visible. The following graphics illustrate this.

Illustration not available

At the left, the camera does not look at any portal, so no room except for the one the camera is currently in is visible. At the right, the camera does look at the portal from room 1 to room 2, so room 2 is also visible. A last special case follows. The camera can not only see rooms that are behind visible portals, which lead from the current room to another room, but also rooms that are behind a visible portal that leads from any visible room to another room. Again here's a graphic to illustrate this.

Illustration not available

As you can see room 1, room 2 and room 3 are visible. Room 1 is visible, because the camera is in this room, room 2 is visible because the portal from room 1 to room 2 is in the visible area of the camera and room 3 is visible, because a portal that neighbors a visible room and leads to room 3 is in the visible area of the camera. Something's going wrong here, though, because it's quite easy to see that room 3 is not actually visible. This is a little error that can easily occur with the portal engine of Ultimate 3D. But there's a way to avoid this problem in many cases. Usually doors close behind the character once he has passed through. If it's closed you can not see what's behind it anymore and this effect is being used by Ultimate 3D. In case a portal is set to closed, rooms that lie behind this portal will not be visible, even if the portal itself is in the visible area of the camera.

From all these examples you can see the utility of the portal engine very well. In a usual level in a bigger game there can easily be twenty rooms in a level. Rendering all of them in every frame would be total overkill for the CPU and the graphics device. Using the portal engine you can reduce the number of rooms that need to be rendered simultaneously to only one single room, which is quite an amazing performance gain. It gets even better, because using the portal engine takes no significant computing time at all. The only disadvantage is that it's quite a lot of work to specify all portals and to assign all objects (or at least most of them) to rooms. Different mechanisms, which could have assigned objects to rooms automatically, were considered during the development of Ultimate 3D 2.0, but most of them turned out to be not simple enough or not reliable enough. And seriously, how much work do you spend on your game? Seen in relation to the giant performance gain the bit of additional work is quite insignificant.

Well, that was a lot of theory, but if you understood all of it, the functions will be almost self-evident to you. The first function you need to know about enables the portal engine for a particular camera object.

This function enables the portal engine for the camera object it is being called by.

SwitchPortalEngine(
EnableDisable
)

EnableDisable
Whether the portal engine is to be enabled (true) or disabled (false).

Once the portal engine is enabled any call to SetRoomVisibility(...) will be ignored. Anyway there is a good replacement for this. This function can be used to enforce the visibility of a room for the camera it is called by, even if the portal engine is enabled.

SetRoomVisibilityEnforcement(
RoomIndex,
NewVisibilityEnforcementState
)

RoomIndex
The index of the room you want to set up the visibility enforcement state for. This can be an arbitrary integer value in the range from 0 to 1999.

NewVisibilityEnforcementState
The visibility enforcement state you want to set up for the given room. If this is true the room will be visible under all circumstances, otherwise the visibility depends on the portals (or on SetRoomVisibility(...) if the portal engine is disabled). For room 0 visibility enforcement is enabled by default, for all other rooms it is disabled by default.

The next four functions can be used to create, modify, close and destroy portals. As said above, portals are represented by circles. A circle is defined by an orientation (facing direction), a center and a radius. Additionally, the portal holds two room indices.

This function creates a portal using the given data and returns its identifier.

CreatePortal(
CenterX, CenterY, CenterZ,
FacingDirectionLongitude, FacingDirectionLatitude,
Radius,
RoomIndex1, RoomIndex2
)

CenterX/Y/Z
The center of the circle which defines the portal.

FacingDirectionLongitude, FacingDirectionLatitude
The facing direction of the circle which defines the portal. If you build a ray from these two angles the circle will be orthogonal to this ray.

Radius
The radius of the circle which defines the portal.

RoomIndex1, RoomIndex2
The indices of the rooms that are at the different sides of the circle. These must not be 0, because the room with the index 0 is always visible when the portal engine is enabled and should not be seen as an actual room. The order of these parameters does not matter. If a camera is in the room with RoomIndex1 and moves through the portal it will be in the room with RoomIndex2 after that, and vice versa.

This function can be used to modify a portal after creating it.

SetPortal(
PortalID,
FacingDirectionLongitude, FacingDirectionLatitude, Radius, RoomIndex1, RoomIndex2
)

PortalID
The identifier of the portal you want to modify. This is the return value of the call to CreatePortal(...) that created the portal.

FacingDirectionLongitude, FacingDirectionLatitude, Radius, RoomIndex1, RoomIndex2
See above.

This function can be used to open the portal (it is opened by default) or to close it.

OpenPortal(
PortalID,
OpenClose
)

PortalID
The identifier of the portal you want to modify. This is the return value of the call to CreatePortal(...) that created the portal.

OpenClose
Pass true to open this portal or false to close it. If a portal is closed the camera will still be able to pass through it to get to another room, because portals do not do any collision detection, but rooms that could be seen through the portal will not be visible anymore.

This function destroys the portal with the given identifier.

DestroyPortal(
PortalID
)

PortalID
The identifier of the portal you want to destroy. This is the return value of the call to CreatePortal(...) that created the portal.

A piece of information which can not be found through the use of portals is which room the camera is in initially. For this reason you need to be able to set this up manually. This can be done through the function SetObjectRoom(...) just like for all other objects.

The portal engine does not just give you a performance increase, it can also be very helpful to code gameplay-related things. For example you can start a particular video sequence if the camera passes through a particular portal for the first time. It can also be useful for AI programming. If the camera is in the same room as the enemy, the enemy needs to get active. To realize things like this, you need to be able to find out which room an object is currently in. This information can be retrieved through the following function.

This function retrieves the index of the room the object calling it is currently in.

GetObjectRoom()

Other than that you can avoid lots of unnecessary calculations by performing them only for objects which are in a visible room. For example particle systems do not need to be updated unless they can be seen by the camera, and objects do not need to check for collisions with objects in other rooms. To find out whether a room can be seen by a particular camera you can use the following function.

This function returns whether the room with the given index can be seen by the camera it is called by.

GetRoomVisibility(
RoomIndex
)

RoomIndex
The index of the room of which you want to retrieve the visibility state.

Lots of advantages of the use of the advanced culling functions have already been mentioned. They increase the performance strongly, they can be used to avoid the unnecessary execution of code, they make the collision detection more efficient, they can be helpful for AI and they give you the possibility to control which light source effects which object. Here comes another advantage. The use of the advanced culling functions also gives you the possibility to have different ambient lighting for different objects. SetAmbient(...) specifies a particular ambient lighting color for all rooms. You can change this color for single rooms using the following function.

This function changes the ambient lighting color for a given room.

SetRoomAmbient(
RoomIndex,
AmbientColorR, AmbientColorG, AmbientColorB
)

RoomIndex
The index of the room for which you want to specify a new ambient lighting color.

AmbientColorR, AmbientColorG, AmbientColorB
The ambient lighting color, which is to be set up for this room. For more information about ambient lighting refer to the description of SetAmbient(...).

Note that every call to SetAmbient(...) will overwrite the changes made by this function.


Collision detection and collision response

Have you ever seen somebody going through a wall? Probably not. In reality it is impossible to pass through solid surfaces and since games usually try to fake reality this behavior should be reproduced there. This is why good collision detection and collision response are needed. The collision detection has to determine whether objects have moved through or into obstacles in every step and if so the collision response has to determine a position to which the object can move without getting into obstacles.

The most common collision response for first and third person games makes objects slide along walls, if they try to move into them. This has the advantage that the player does not stop moving as he gets towards a wall, which is important as a sudden stop can be very irritating in fast games. With Ultimate 3D 2.0 and its ray tracing based collision detection it has been very difficult to get reliable collision detection. This was basically the biggest weakness of Ultimate 3D. For Ultimate 3D 2.1 I completely redesigned the entire collision detection system and made a built in collision response system. Now collision detection and collision response is one of Ultimate 3D's strengths. Credits for this achievement go to Kasper Fauerby who wrote a paper describing the technique the collision detection and collision response mechanisms used are based on.

Since the system is built in it is very easy to use and quite efficient, since no slow GML code needs to be executed. The sliding collision response described above can be implemented in five lines of code. You simply set all objects, which should be checked by the collision detection functions to solid through an Ultimate 3D function and call one function for the objects, which should perform collision detection and collision response. Then you are done.

Collision detection based upon ellipsoids

Before we can get to this one magic function, which solves all of your problems (at least those related to collisions), we need to do some theory. The first necessary consideration is, which shape can be used for good collision response. It should not have any corners, to avoid that it can hang somewhere, it should be versatile enough to contain objects with very different shapes and it should be easy to describe. The obvious choice is an ellipsoid (a scaled sphere). So we no longer need to think about collision detection for moving character models, we just need to think about collision detection for moving ellipsoids. The following illustration shows an ellipsoid.

Illustration not available

Games are usually frame based and therefore the objects do not really move continuous, they jump from one position to another in every step. In one step the object may not collide with anything and in the next step it may already be inside the geometry. The following illustration demonstrates this.

Illustration not available

It is not enough to check whether the green area is intersected, it is not enough to check whether the the yellow area is intersected and it is not enough to check whether the red area is intersected. All these areas need to be checked. So the shape for which collisions actually need to be detected is not an ellipsoid, but a so called swept ellipsoid. A swept ellipsoid is made up of two ellipsoids and a scaled cylinder connecting them. This is the shape Ultimate 3D works with. This should be enough theory for now.

The following function performs an intersection test between given geometry and the given swept ellipsoid and returns whether a collision has been detected (true) or not (false).

CheckSweptEllipsoidIntersection(
ObjectID,
StartX,StartY,StartZ,
DestinationX,DestinationY,DestinationZ,
RotX,RotY,RotZ,ScalingX,ScalingY,ScalingZ,
RoomIndex
)

ObjectID
The ID of the object for which you want to perform a collision detection. This can be the ID of a model, a terrain or a primitive object or the keyword all. In the latter case all objects, which are set to solid through SetObjectSolidity(...) and are in the right room will be checked. In addition all objects, which are set to globally solid through SetObjectSolidity(...), will be checked then, no matter which room they are in.

StartX, StartY, StartZ
The start position of the swept ellipsoid (see the illustration above).

DestinationX, DestinationY, DestinationZ
The destination position of the swept ellipsoid (see the illustration above). These parameters can equal StartX/Y/Z. In this case Ultimate 3D will perform a less computing time intensive unswept ellipsoid intersection test.

RotX, RotY, RotZ, ScalingX, ScalingY, ScalingZ
The rotation and scaling of the ellipsoid. The ellipsoid is a unit sphere (sphere with radius one) scaled by ScalingX/Y/Z and rotated by RotX/Y/Z.

RoomIndex
This parameter is only relevant if all is specified as first parameter. If it is negative objects in all rooms will be checked. Otherwise only objects, which are in the room with the given index and globally solid objects will be checked (see SetObjectRoom(...)).

Just knowing that a collision has occurred is quite useless though. More information is needed to be able to respond to the collision (in case you do not use the built in collision response). For this reason there is a couple of functions, which return relevant information about the occurred collisions. All these functions return information about the results of the last call to CheckSweptEllipsoidIntersection(...).

This function returns the number of triangles, which have been intersected by the swept ellipsoid specified in the last call to CheckSweptEllipsoidIntersection(...).

GetTriangleIntersectionCount()

This function returns the time at which the given intersected triangle has been intersected by the swept ellipsoid specified in the last call to CheckSweptEllipsoidIntersection(...). The return value lies in the range from 0.0 to 1.0, where 0.0 refers to the time at which the ellipsoid was at the start position of the swept unit sphere and 1.0 refers to the time at which it was at the destination position.

GetIntersectionTime(
IntersectedTriangleIndex
)

IntersectedTriangleIndex
The index of the intersected triangle for which you want to to retrieve the intersection time. This is an integer value in the range from 0 to GetTriangleIntersectionCount()-1.

This function outputs the position at which the swept ellipsoid specified in the last call to CheckSweptEllipsoidIntersection(...) has first intersected the given intersected triangle.

GetIntersectionPosition(
OutputVectorID,
IntersectedTriangleIndex
)

OutputVectorID
See the description in the chapter about the math functions.

IntersectedTriangleIndex
The index of the intersected triangle (see the description of GetIntersectionTime(...) for more details).

This function outputs the normal of the given intersected triangle. The normal of a triangle is a normalized vector, which lies perpendicular to the triangle and points away from its front side.

GetIntersectedTriangleNormal(
OutputVectorID,
IntersectedTriangleIndex
)

OutputVectorID
See the description in the chapter about the math functions.

IntersectedTriangleIndex
The index of the intersected triangle (see the description of GetIntersectionTime(...) for more details).

This function outputs the normal of the given intersected triangle multiplied by a factor so that the ellipsoid will have left the triangle if the returned vector is added to its destination position. This can be used for very simple collision response, but it will fail fast, if multiple triangles are involved into the collision.

GetPushAwayVector(
OutputVectorID,
IntersectedTriangleIndex
)

OutputVectorID
See the description in the chapter about the math functions.

IntersectedTriangleIndex
The index of the intersected triangle (see the description of GetIntersectionTime(...) for more details).

With these functions you can program custom collision response. For example you can make objects bounce back if they collide with something, or you can deform them dependent on how they collided. Anyway these collision response mechanisms are usually not what is wanted. The most common collision response mechanism still is the one with the sliding. And that is the one we will get to now. As I already said only one function is needed for this. It does all the collision detection and response automatically. Here it is.

This function checks whether the given ellipsoid collides with the given object(s) on its way from the start position to the destination position. If it does not collide the vector, which is output equals the destination position, otherwise a free position to which the ellipsoid can move is output.

AttemptMoveToPosition(
OutputVectorID,
ObjectID,
StartX,StartY,StartZ,
DestinationX,DestinationY,DestinationZ,
RotX,RotY,RotZ,ScalingX,ScalingY,ScalingZ,
RoomIndex
)

OutputVectorID
See the description in the chapter about the math functions.

ObjectID, StartX, StartY, StartZ, DestinationX, DestinationY, DestinationZ, RotX, RotY, RotZ, ScalingX, ScalingY, ScalingZ, RoomIndex
See the description of CheckSweptEllipsoidIntersection(...).

For an example on how to use this function see the code in the step event of the character object in the collision example on Ultimate 3D.org. Of course one more function needs to be explained here. It already has been mentioned above and it is really simple, though here is an explanation.

This function changes the solidity state of the model, primitive or terrain object it is called by.

SetObjectSolidity(
SolidityState,
GlobalSolidityState
)

SolidityState
The new solidity state, which is to be set up. This can be true, to reach that the object will be checked, if all is specified as parameter for ObjectID and if RoomIndex matches the room index of this object in a call to CheckSweptEllipsoidIntersection(...), AttemptMoveToPosition(...) or CheckRayIntersection(...). It can be false to reach that the object will not be checked if all is specified as parameter for ObjectID.

GlobalSoliditySate
The new global solidity state, which is to be set up. This can be true, to reach that the object will always be checked, if all is specified for ObjectID or false to reach that it will not be checked in this case. The difference between SolidityState and GlobalSolidityState is that GlobalSolidityState is independent from the RoomIndex parameter. Using this makes sense for objects, which are in rooms, which use enforced visibility, like doors (see SetRoomVisibilityEnforcement(...)).

This function combined with the portal engine (in particular with SetObjectRoom(...)) offers a very comfortable and efficient way to specify which objects need to be regarded by the automatic collision detection. You can use it so that objects perform collision detection always just for the objects, which are in the same room and for the objects which are between rooms, like doors (those can have global solidity set up). This makes the collision detection very efficient.

Ray tracing

Another method, which is important for collision detection is ray tracing or to be more precise the ray-mesh intersection test. A ray tracing tests checks whether a given ray intersects a piece of geometry. This is e.g. useful if you want to check, whether a weapon points at some object, to find out what is hit when the weapon is fired. To give you a a better understanding of what ray tracing means here is a more thorough explanation.

Imagine you have a ray. If you do not remember this from math at school, a ray is a straight line that begins at one point and is infinitely long. A good way to imagine this is a laser pointer. The position at which the laser pointer is, is the origin of the ray and the laser pointer emits an infinitely long ray (in theory). If you use the laser pointer to point at some object, you will see a small dot at the position where the ray meets the object. This is the point of interest. The following graphic illustrates this.

Illustration not available

A ray-mesh intersection test uses a mathematical method to test whether the given ray intersects triangles of the given object. If so it calculates the position at which the ray first intersects the object (the blue dot on the illustration). The ray tracing function is very similar to CheckSweptEllipsoidIntersection(...), but since rays are easier to describe than swept ellipsoids it is significantly simpler.

This function checks whether the given ray intersects the geometry of the given object(s). If so it returns the distance from the ray origin to the closest intersection position, otherwise it returns 100,000.

CheckRayIntersection(
ObjectID,
RayOriginX,RayOriginY,RayOriginZ,
RayDirectionLongitude,RayDirectionLatitude,
RoomIndex
)

ObjectID
See the description of CheckSweptEllipsoidIntersection(...).

RayOriginX, RayOriginY, RayOriginZ
The origin of the ray.

RayDirectionLongitude, RayDirectionLatitude
The direction of the ray. To get more information about these two parameters have a look at the description of the function Move(...).

RoomIndex
See the description of CheckSweptEllipsoidIntersection(...).

Again there are some functions to get more information about the intersected triangle.

This function outputs the normal vector of the triangle, which has been intersected by the ray specified in the last call to CheckRayIntersection(...). The normal of a triangle is a normalized vector, which lies perpendicular to the triangle and points away from its front side.

GetRayTracingNormal(
OutputVectorID
)

OutputVectorID
See the description in the chapter about the math functions.

This function returns the index of the material, which is used by the triangle intersected in the last call to CheckRayIntersection(...), which used a model object as parameter for ObjectID.

GetIntersectedMaterialIndex()

Using these functions you can find out more about the surrounding of an object. For example you can send a ray straight down from the hip of the character to find out how far the ground is away. Then you can use GetRayTracingNormal(...) to find out how strong the slope of the ground is at this position. And after that you can use GetIntersectedMaterialIndex() to find out, whether the character is standing on metal or on grass.


Ultimate 3D options

There is a couple of options in Ultimate 3D to modify the way it will load data, render scenes and handle errors. A few of these options have to be set up before Ultimate 3D gets initialized, while others can be changed at any time using the corresponding functions. Those that have to be set up before Ultimate 3D gets initialized get set up in the script Ultimate3DOptions(). This script is not meant to be called by the user. It will be called once when Ultimate 3D gets initialized, that's all. The fact that it's a script instead of a number of Game Maker constants results in an advantage for you, because this way you can load the options from a file. Currently there are seven options which get set up by setting up variables in the script Ultimate3DOptions():

global.u3d_splash_screen_file
You've got to set this variable to a string. If you do not want Ultimate 3D to display a splash screen while it's loading the resources, you can set this variable to an empty string (""). Otherwise, you should set up the filename including the ending and the path for this variable.

global.u3d_z_buffer_format
This variable can have either the value 16 or 32. If you set it to 32 Ultimate 3D will try to create a device that uses 32 bit z-buffering. Z-buffering is a technique that's used to avoid that objects that are in front of other objects get covered by objects behind them. If you set it to 16 Ultimate 3D will use 16 bit z-buffering for sure.

global.u3d_multi_sample_type
This variable can take an integer value in the range from 2 to 16 or 0. It defines the number of samples that will be used for antialiasing. In case it's 0 no antialiasing will be used. The higher the value is, the stronger will be the antialiasing. Note that the number of supported samples differs from graphics device to graphics device. Some graphics devices do not support antialiasing at all. Another thing you should note is that antialiasing will not have a any effect if post screen shaders are active.

global.u3d_device_type
If you do not have a good reason not to do so, you should set this variable to 0. Ultimate 3D will then create the most efficient device type that offers all required functionality. If you set it to 1, Ultimate 3D will create a device that uses software vertex processing. This means that the central processing unit (CPU) will calculate the vertex data instead of the graphics processing unit (GPU). Usually this makes no sense at all. The only case in which you should use this option is when there's a bug in the graphics device driver that leads to a crash when using particular features with hardware vertex processing. I have experienced this with an ATI driver when using vertex tweening. Finally you can set this variable to 2 to create a complete software device (using the Direct 3D reference layer). This will lead to extremely low frame rates but all features (cube mapping, shadow casting and pixel shader model 1.4) will be supported for sure.

global.u3d_log_file
This variable can be set to the filename (with path) of a text file, e.g. "LogFile.txt". If it is set to a string Ultimate 3D will create a log file, while it is running. To this log file it will output a lot of relevant information (in particular all error messages and warnings, information about the device and its capabilities and some information about the success of particular operations). If you, or a user of one of your Ultimate 3D based applications has problems with your application the log file will make it a lot easier to find the cause of this problem.

global.u3d_few_message_output
This variable can be set to true, to reduce the number of onscreen error messages Ultimate 3D will show. If it is set to true Ultimate 3D will only show error messages, if they are crucial and could make the application crash. The log file output is not effected by this.

global.u3d_character_set
This variable is used to modify the character set, which is used by Ultimate 3D to create fonts. If this variable is not set or is set to an empty string the default character set is used. This character set contains all human readable characters, which are not country-specific (ASCII codes 32 to 127). If the language you develop your game in contains special characters or if it uses its own alphabet, you need a different character set. For this purpose you have to set this variable to a string, which contains all characters, which should be included in the character set. A single character must not occur twice within the string ("AaBb" is valid, but "AaBbA" is not). Whitespaces should not be included. If a character is to be drawn, which is not contained within the character set a space will be drawn instead. The order of the characters does not matter. A very basic character set string could look like this:
"AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789-_.?,!+-/*'"

As I already said, the other options can be changed at any time using functions. The first available option is really easy to understand. If you are using Ultimate 3D you should know that it searches for model textures by default in the folder "gfx". If you want it to search them somewhere else you can use the following function to give a new path (including the slash at the end).

This function changes the directory Ultimate 3D searches model textures in.

SetTexturePath(
TextureDirectory
)

TextureDirectory
The directory that contains the textures including the slash at the end.

The next function does not actually change an option, but disables the render output of Ultimate 3D to give Game Maker the possibility to draw to the first view. It's called SwitchView() and does not take any parameters.

The next function changes the filtering method that is used for the given texture stage. The filtering method defines how Ultimate 3D will calculate a color from a texture and a texture coordinate. The default filtering method is linear filtering.

This function sets up a new filtering method for the given texture stage.

SetFilter(
TextureFilter,
TextureStage
)

TextureFilter
The filter that is to be used for filtering textures. Here's a list of the meanings of the different values you can enter for this parameter:
1: Nearest point filtering: The color of the closest texel will be used.
2: Linear filtering: The color of the closest four texels will be interpolated to get smoother results.
3: Anisotropic filtering: A more complex filtering method that can lead to better results. It is not supported by some graphics devices.

TextureStage
The texture stage you want to set up the texture filter for. This can be an arbitrary integer value in the range from 0 to 7. By default every texture stage uses linear filtering. If you pass -1 for this parameter, the specified texture filter will be used for drawing textures (using DrawTex(...) and DrawTexEx(...)). By setting up nearest point filtering for texture drawing you can avoid a quality loss.

The next function sets up how many mip maps are to be created for the textures that will be loaded. Creating mip maps means creating lower resolution versions of each texture that can be used depending on the distance to the object that uses it. Each mip map will have a resolution that's half as big as the resolution of the previous mip map (e. g.: Mip map count set to three leads to: 512*256; 256*128; 128*64).

This function sets up the number of mip maps to be created for the textures that will be loaded.

SetMipMapCount(
NewMipMapCount
)

NewMipMapCount
The new mip map count, which is the number of lower resolution copies that are to be created for every texture.

You can also specify how Ultimate 3D interpolates between mip maps. By default nearest point filtering is used for this purpose. This means that every pixel uses just one mip map. Between the mip maps there are sudden changes, which can be seen quite well, especially on detailed textures. You can tell exactly where the program stops using one mip map and continuse using a lower one. Setting up linear filtering to interpolate between mip maps can avoid this undesireable effect.

This function sets the filter, which is to be used to interpolate between mip maps in the given texture stage.

SetMipMapFilter(
TextureFilter,
TextureStage
)

TextureFilter
The filter that is to be used for interpolation between mip maps. By default nearest point filtering is used (1). The common alternative is linear filtering (2). For a detailed list of all available filters refer to the description of SetFilter(...).

TextureStage
The texture stage for which you want to set up the mip map filter. This can be an arbitrary integer value in the range from 0 to 7.

Finally there is a function to make Ultimate 3D switch to wireframe rendering for particular objects. Wireframe rendering means rendering only the outlines of the triangles. This feature is meant to be used for things like level editors. Using wireframe rendering to implement ingame effects usually does not look very good.

This function can be used to enable or disable wireframe rendering for the model, terrain or primitive object, which calls this function.

SwitchWireFrameMode(
EnableDisable
)

EnableDisable
If you pass true for this parameter wire frame rendering will get enabled, otherwise it will get disabled.

If you are using Game Maker 6.0 or higher there is one additional variable you need to know about. You may have noticed that Ultimate 3D does not show the cursor, even if the corresponding Game Maker option is enabled. The reason for this behavior is that Game Maker 6.0 and higher (in opposite to Game Maker 5.x) does not offer a possibility to find out whether it currently displays the cursor or not. For that reason you have to give this information manually. This can be done by changing the value of control.show_cursor. Setting it to true will make Ulitmate 3D show the cursor, setting it to false will disable the cursor.


Updating Ultimate 3D projects

Whenever a new version of Ultimate 3D gets released some scripts have changed within this version and others may have been added. For this reason you need to change them in your project if you want to update the project to a newer version of Ultimate 3D. This tutorial guides you through this process for every version. Just find the sub-chapter that matches your case and follow the steps. If you need to update from a very outdated version you can also chain the update steps, to get for example from Ultimate 3D 2.0 beta 3 to Ultimate 3D 2.0 final. Since I always do my best to keep the downwards compatibility the process of updating usually does not take more than fifteen minutes.

Updating an Ultimate 3D 2.1 beta project to Ultimate 3D 2.1 final

The update process from Ultimate 3D 2.1 beta to Ultimate 3D 2.1 final is very easy and completely optional. If you do not intend to use the new features you do not have to perform the update process. In this case it is sufficient to replace the old dll with the new one. It is recommended to perform the update though, since it is a very simple process. Just update the Init() script and add SaveTerrainHeightMap(...), CopyVector(...), NormalizeVector(...) and TransformVectorEx(...) and you are done.

Updating an Ultimate 3D 2.0 final project to Ultimate 3D 2.1 beta

Ultimate 3D 2.1 has many smaller new features and a basically new collision detection and collision response system. Therefore quite many scripts need to be added, some need to be modified and some (related to collision detection) need to be deleted. You will probably have to rewrite most of your collision detection and collision response code, but with the new functions this is very easy.

Here is a list of all scripts that need to be added. All these scripts are contained within the NewScripts.gml file, which comes with the SDK of Ultimate 3D 2.1, so you can import this scripts file to add them automatically.

  • SwitchBackgroundMode(...)
  • GetBoneCount()
  • GetBoneParent(...)
  • StartPoseInterpolation(...)
  • CopyAnimation(...)
  • SetModelMaterialSpecular(...)
  • SetObjectSolidity(...)
  • AttemptMoveToPosition(...)
  • CheckSweptEllipsoidIntersection(...)
  • GetTriangleIntersectionCount(...)
  • GetIntersectionTime(...)
  • GetIntersectionPosition(...)
  • GetIntersectedTriangleNormal(...)
  • GetPushAwayVector(...)
  • CheckRayIntersection(...)
  • GetIntersectedMaterialIndex(...)
  • GetTextureStrengthAtPosition(...)
  • DeformTerrain(...)
  • CreateTerrainDecal(...)
  • SetParticleRotation(...)
  • SwitchAdditiveBlending(...)
  • SetRoomVisibilityEnforcement(...)
  • SetRoomAmbient(...)
  • GetMeshOccurrenceCount(...)
  • GetMeshOccurrenceTransformation(...)
  • InterpolateMatrices(...)
  • GetMatrixEntry(...)
  • CoordToScreen(...)
  • CheckBoundingBoxVisibility(...)
  • CalculateVectorDifference(...)
  • CalculateVectorLongitude(...)
  • CalculateVectorLatitude(...)
  • ComputeMatrixTranslation(...)
  • ComputeMatrixScaling(...)
  • ApplyTransformationMatrix(...)
  • GetDrawnTriangleCount()
  • GetDrawCallCount()
  • SetMipMapFilter(...)

Here is a list of all scripts that need to be replaced by newer versions:

  • Step()
  • SetAmbient(...)
  • LightPoint()
  • LightDirectional()
  • LightSpot()
  • SmoothNormals(...)
  • SetModelMaterialEmissive(...)
  • CreateTerrain()
  • AddTerrainTexture(...)
  • CreateParticleSystem()
  • ParticleSystemStep(...)
  • MoveCamera()
  • SwitchWireFrameMode(...)
  • Init()
  • InitializeLightVariables() (Game Maker 6.0 and higher only)

For some of these functions the use has changed as well. SmoothNormals(...) and AddTerrainTexture(...) have an additional parameter now, but if you ignore this parameter reasonable default values will be used. SwitchWireFrameMode(...) does no longer enable or disable wireframe mode for all objects in the scene. Now it enables or disables it exceptionally for the object, which has called the function. Make sure that you regard these interface changes, when you update your project. Finally here is a list of the scripts, which need to be deleted:

  • GetDistanceToModel(...)
  • GetDistanceToPrimitive(...)
  • GetDistanceToAnyPrimitive(...)
  • GetDistanceToTerrain(...)
  • CoordToScreenX(...)
  • CoordToScreenY(...)
  • CoordToScreenZ(...)

Do not feel shocked about that though. There still is a function to perform a ray-mesh intersection test for all these object types and it is even more versatile than the old functions, since it can automatically do the ray-tracing test for a predefined set of solid objects. The new function is called CheckRayIntersection(...). Anyway, for collision detection and collision response there is an even better solution now. You can read about it in the rewritten help file chapter about this topic. It is a lot more reliable, easier to use and under the right circumstances even more efficient than the old method.

CoordToScreenX/Y/Z(...) have been replaced by a vector based version called CoordToScreen(...) now. Instead of calling three different functions, you call only one function, which returns a vector now.

Other than that there are some really minor changes. If you want to make room zero invisible you have to use SetRoomVisibilityEnforcement(...) instead of SetRoomVisibility(...) now. Terrain height maps are interpreted differently now, to add support for 32-bit height maps. If your terrain height map is not exactly grayscale you will get undesired results. The red channel of the height map has a very strong influence on the results now. If your height maps are perfect grayscale, you will not get any problems with the new way the data is interpreted in.

One last thing, that needs to be said here is that the order of inverse tangent space matrices has changed. In Ultimate 3D 2.0 final the normals got overwritten by the second column of the matrices and the third texture coordinate set got overwritten by the third column. Now the normals get overwritten by the third column and the third texture coordinate set gets overwritten by the second column. The great advantage of this is that the third column of the inverse tangent space matrices usually equals the normals. For this reason you can have materials, which use per pixel lighting and others which do not use it on one mesh at the same time now. Despite you do not need to call RecalculateNormals() if you disable per pixel lighting. This means, that if you are using vertex shaders, which use inverse tangent space matrices you need to swap v3 and v9. If you have exported Ultimate 3D model files with inverse tangent space matrices with recent versions of Ultimate 3D 2.0 you either have to reexport them or you have to call AddInverseTangentSpaceMatrices() after loading them.

Updating an Ultimate 3D 2.0 RC project to Ultimate 3D 2.0 final

Since Ultimate 3D 2.0 final does not have any new feature the only scripts that have to be updated are GetObjectTransformation() and Destroy(). Anyway it's recommended to update/add also the following scripts if you are using Game Maker 6.0 or higher. This will make it possible to use Ultimate 3D without the Game Maker option "Treat uninitialized variables as value 0". The following scripts need to be updated for this purpose:

  • CreateWall()
  • CreateFloor()
  • CreateCube()
  • Create2D()
  • CreatePolygon()
  • LightDirectional()
  • LightPoint()
  • LightSpot()
  • LoadMesh()
  • CreatePrimitiveFromModel()
  • CreateEmptyMesh()
  • ApplyParallaxAndBumpMapping()
  • SetMaterialEffectLightSource()
  • CreateTerrain()
  • CalculateTerrainLightMap(..,)
  • CreateParticleSystem()
  • MoveCamera()
  • CreatePostScreenShader()
  • Init()

    Despite the following four scripts need to be added to the "Private functions" group:

  • InitializePrimitiveVariables()
  • InitializeLightVariables()
  • InitializeVariable()
  • InitializeVariableGlobal()

Another optional update includes some changes to the code of the control object. The code in the destroy event and in the game end event should be updated and the code in the room end event should be added to make Ultimate 3D work properly with room changing.

Updating an Ultimate 3D 2.0 beta 3 project to Ultimate 3D 2.0 RC

Ultimate 3D 2.0 RC is what Ultimate 3D 2.0 was originally meant to be like. Since the development of Ultimate 3D 2.0 took a lot longer than expected, the beta versions were released with some missing features to reduce the delay of the release. These have been added now. To be able to use them you will have to update your Game Maker project a bit. So take five minutes to read this chapter, spend another ten minutes on doing what it says, and you can start using Ultimate 3D 2.0 RC with all its new features.

The first thing you have to do is download the most current SDK from Ultimate3D.org. Once you've got it you can replace the Ultimate3D.dll of Ultimate 3D 2.0 beta 3 in your project with the one from the release candidate. Right after that nothing will be working at all. First you have to replace a couple of scripts with their most current versions. Just open the old BasicGM5.gmd (resp. BasicGM6.gm6 or BasicGM7.gmk) and your Game Maker project, open the scripts that need to be replaced, and copy their new contents from the new version to the old one. Here's a list of all scripts that need to be replaced by newer versions:

  • CreatePrimitiveFromModel(...)
  • PreloadMesh(...)
  • LoadMesh()
  • AddFrame(...)
  • Step()
  • Destroy()
  • Init()
  • SetFilter()
  • GetBoneTransformation()
  • CreateTerrain()
  • DrawText()
  • CreateCube()
  • DrawTex()
  • DrawTexEx()
  • RenderToCubeTexture()
  • GetObjectTransformation()
  • MoveCamera()
  • SetObjectRoom()

Next you have to add the new scripts, to be able to use the new features. Again here is a list of them.

  • ExportToU3DFile(...)
  • SwitchAlphaBlending(...)
  • SetBoneFrame(...)
  • GetDistanceToTerrain(...)
  • SetCustomTerrainLightMap(...)
  • GetClippingPlaneSupport()
  • SwitchWireFrameMode(...)
  • UpdateSkeleton()
  • ReleaseModel(...)
  • AddTextureCoordinateSetsFromModel(...)
  • ComputeMatrixRotationAngles(...)
  • GetRayTracingNormal(...)
  • ScreenCoordToVector(...)
  • Everything in the group "Shadow and post screen shader functions":
    • GetShadowSupport()
    • SwitchShadows(...)
    • SwitchShadowCasting(...)
    • CreateShadowOptimizedGeometry(...)
    • SwitchShadowReceiving(...)
    • CreatePostScreenShader()
    • SetPSSTexture(...)
    • SetPSSVSConstant(...)
    • SetPSSPSConstant(...)
    • SetPSSFilter(...)
  • The portal engine functions:
    • SwitchPortalEngine(...)
    • GetObjectRoom()
    • GetRoomVisibility(...)
    • CreatePortal(...)
    • SetPortal(...)
    • OpenPortal(...)
    • DestroyPortal(...)

All of these scripts are available in an exported Game Maker script file, which comes with the SDK. Using this can save you some time adding them. Depending on the file you based your work on, you may have to add GetBoneTransformation(...), because this script was missing in some of the Game Maker files that came with Ultimate 3D 2.0 beta 3. Also, you should delete LoadAnimated3DS(), because it's just an outdated remnant from older versions of Ultimate 3D. It does nothing but call LoadMesh() anyway. Once you've done all these things your Ultimate 3D project should be working fine and you can start using the features of Ultimate 3D 2.0 RC.

Updating an Ultimate 3D 1.31 project to Ultimate 3D 2.0 beta 1

Ultimate 3D 2.0 is a complete remake of Ultimate 3D. There's no single line of code that's the same as in Ultimate 3D 1.31. For this reason it was not possible to keep everything the same. There's a couple of things that changed because the old solutions were not user-friendly enough, a couple of things that changed because the new features required these changes and a couple of things that just got removed to increase the performance of Ultimate 3D. This text will list all these changes and give a detailed step-by-step description on how to update projects from Ultimate 3D 1.31 to Ultimate 3D 2.0. It may take an hour or two to convert your project.

Regarding the features that are part of the novice section of Ultimate 3D almost nothing has changed. The only changes are that cube objects get defined differently and that the light is calculated in a more natural way. A few variables have also been added for primitive objects and LoadAnimated3DS() does not exist anymore (it's LoadMesh() now). The meaning of the variables rotx, roty and rotz have changed for camera objects and light sources. These variables now work exactly in the same way as the corresponding variables of primitive objects and models. Also, you can not use vertex shaders for primitive objects anymore which was a necessary change to make the primitive renderer much more efficient.

Most features in the advanced section did not exist in Ultimate 3D 1.31 so there is not much that could have changed. The particle effects system is completely new and the parameters of the ray tracing functions have changed a bit, due to the change of the meaning of the variables rotx and roty for camera objects. It works all in the same way now. That means that a value of 90 for rotx would make the camera look straight down. But let's get to converting projects now.

Step one: Replace and add all scripts

Realizing this part is really boring and will take a lot of time. But when you are done with this, your project is almost ready for Ultimate 3D 2.0. I recommend opening the new BasicGM5.gmd/gm6 and your Ultimate 3D 1.31 project in two instances of Game Maker and placing their windows at the left and the right half of the screen. The first thing you have to do to get the new scripts into your project, is delete the following old scripts that do not exist anymore:

  • CreateParticleSystem()
  • DestroyParticle(...)
  • GetParticleColorR/G/B/A(...)
  • GetParticleNumber(...)
  • GetParticlePosX/Y/Z(...)
  • GetParticleSize(...)
  • GetVSSupport()
  • LoadAnimated3DS()
  • LoadVS(...)
  • SetParticleColor(...)
  • SetParticleNumber(...)
  • SetParticlePos(...)
  • SetParticleColorEnd(...)
  • SetParticleColorStart(...)
  • SetParticleSize(...)
  • SetParticlesLifetime(...)
  • SetParticlesMovement(...)
  • SetParticlesMovementInc(...)
  • SetParticlesMovementOut(...)
  • SetParticleSystemBase(...)
  • SetViewport(...)
  • SetVS(...)
  • SetVSConstant(...)

Next you have to rename the following scripts to the given new names:

  • collision_h() to CollisionH()
  • collision_model() to CollisionTerrainModel()
  • collision_v() to CollisionV()
  • collision_z() to CollisionZ()
  • GetMaterialNumber(...) to GetMaterialIndex(...)
  • move(...) to Move(...)
  • SetMaterialSphereMap(...) to SetMaterialEnvironmentMap(...)
  • Snapshot(...) to TakeScreenshot(...)
  • SnapshotEx(...) to TakeScreenshotEx(...)

When you are done with these changes you should try to run your game once using the old dll of Ultimate 3D 1.31 to see which parts of the program will have to be changed or remade. If you have particle systems made with Ultimate 3D 1.31 it will be easiest to delete and remake them completely. If you are using vertex shaders for primitive objects in your project, you'll have to find a way to get the same effect without shaders, or you'll have to replace the primitive objects with model objects. When you are done with this the game should be working.

Now the really annoying part follows. You have to open every single script in the new BasicGM5.gmd/gm6 file, copy its contents, and replace the contents of the corresponding script in your project with this code. If the script is not part of the old version of Ultimate 3D you have to create it. You should redo the group structure as well. It's not that much work to create it but it will make everything a bit easier.

Step two: Changing objects

When you are done with replacing and adding all scripts you can run the game again using the new dll, but the result will not be that motivating. You will not see anything. But do not worry, that's quite easy to fix. You simply have to change the function calls that are done by the control object. In the end-step-event you will find a call to MoveCamera() and a call to the Game Maker function external_call(...). You've got to replace these two calls with the following piece of code after removing the if statement that's above these calls:

external_call(global.u3d_transmit_controller_information,BG_r,BG_g,BG_b,show_cursor);
external_call(global.u3d_render);

In the begin step event you have to remove the if statement again and you have to replace the call to external_call(...) with the following:

external_call(global.u3d_present);

You also have to replace the stuff in the destroy event and the game end event with the following piece of code:

external_call(global.u3d_cleanup);

When you are done with these changes you can run the game again and there should be some visible results. But some things may be a bit weird. The camera probably will not behave in the desired way and the collision detection may not be working correctly. This is due to the changed meaning of the variables rotx, roty and rotz and the parameters of GetDistanceToModel(...) and Move(...). There is no hint I could help you with in this context. You simply have to search for the codes that use these variables or functions and correct them. In many cases adding a few minuses will be all you have to do. Another thing you have to do is update any cube objects you might have. To see how they work now, please refer to the chapter about primitive objects.

There's also a couple of really small changes:

  • control.range has changed to camera.max_range
  • global.fall and global.speed (the variables that can be used to make camera objects jump or fall) have changed to camera.falling and camera.falling_speed
  • global.x and global.y (the output variables of GetMousePos(...)) have changed to global.mouse_x and global.mouse_y
  • Environment mapping additive works now. This means that a black environment map would not change anything at all while a white environment map would strongly brighten up everything.
  • The Alpha parameter of DrawTex(...) now takes values in the range from 0 to 255 instead of values in the range from 0 to 1.

When you are done with these changes everything should be working and your project is ready for use with Ultimate 3D 2.0. If there are still problems left, please post them on the Ultimate 3D Community.

Converting an Ultimate 3D project from Game Maker 5.x to Game Maker 6.0 or higher

Due to the lack of downwards-compatibility of Game Maker 6.0 you need to update some scripts when you convert an Ultimate 3D project from Game Maker 5.x to Game Maker 6.0 or higher. Despite several scripts differ, because the Game Maker 6.0 version of Ultimate 3D is made to work without the "Treat uninitialized variable as value 0" option. Those should be replaced by the correct versions, too. Here's a list of all scripts that need to be replaced by the corresponding Game Maker 6.0 versions from the BasicGM6.gm6 (resp. BasicGM7.gm7):

  • MoveCamera()
  • SetSoundPosition()
  • Init()
  • CreateWall()
  • CreateFloor()
  • CreateCube()
  • Create2D()
  • CreatePolygon()
  • LightDirectional()
  • LightPoint()
  • LightSpot()
  • LoadMesh()
  • CreatePrimitiveFromModel()
  • CreateEmptyMesh()
  • ApplyParallaxAndBumpMapping()
  • SetMaterialEffectLightSource()
  • CreateTerrain()
  • CalculateTerrainLightMap(...)
  • CreateTerrainDecal()
  • CreateParticleSystem()
  • CreatePostScreenShader()

Furthermore the following four scripts need to be added to the "Private functions" group:

  • InitializePrimitiveVariables()
  • InitializeLightVariables()
  • InitializeVariable()
  • InitializeVariableGlobal()

Despite some code within objects needs to be changed. The pieces of code in the BasicGM5.gmd which need to be modified to work with Game Maker 6.0 or higher are part of the room end event of the control object and the step event of the camera.


Epilogue - Written by MysteriXYZ

Why you should read this manual thoroughly...

So you've played around a bit with Ultimate 3D, but unfortunately, you've stumbled upon a problem you just can not solve. Oh what to do? The Ultimate 3D forum is probably the first thing that springs to mind. And we would indeed encourage you to become a member of our nice community, as that is the best place to help each other out with all things related to Ultimate 3D. And after all the brainstorming, you can even chill out in our fun-filled off-topic section.

But if your only reason to sign up is to get some quick answers that you can easily find within these chapters anyway, then do not be surprised if you do not make a good impression there.

Alright, so you prefer having the community members on your good side, but having to wade through pages and pages of dry text is not exactly your idea of having fun. Luckily, there's the function list you can browse through, and there's bound to be something in there that looks like it might be the solution to your problem. So you read a particular function description and come to the conclusion it's just what you needed. Oh yeah, some things were mentioned that you did not quite understand - probably explained in a previous chapter or something - but that would just distract you from what you're trying to accomplish right now, so that can wait. Full of enthusiasm you try it out and... oops, something is going horribly wrong. That has to be a bug! Upset, you log on to the forum and because you want to share your disappointment with everyone, you start a new topic and demand to know what's going on. After a couple of enlightening replies, you realize that it would have been simpler (and less embarrassing) to just read those extra bits of information in this manual.

Just keep in mind that in the time it takes to come up with new excuses not to read this help file, you could have read one more chapter... ;) . A good understanding of this documentation can make you go a long way in efficiently using Ultimate 3D. Now go and have some great fun with one of the easiest to use 3D engines you're likely ever to encounter!


Ultimate 3D 2.1 Game Maker extension package

The extension package for Ultimate 3D, which can be obtained from Ultimate3D.org, offers all the functionality that is needed to use Ultimate 3D with Game Maker 7.0. To be more precise it contains the Ultimate3D.dll, all functions that are explained within this help file and two action libraries. Its use can ease the work with Ultimate 3D and makes it more comfortable.

Warning: The extension package is quite new and therefore it has not gone through a long beta testing phase as the normal version of Ultimate 3D has. Although I have tested all components of the package carefully it is probably safer to use the normal version. You should also note that people who do not have the extension installed will not be able to view your project files correctly, if you are using the extension.

Getting started with the extension

There are several things Ultimate 3D always needs, which can not be included into the extension package. For this reason the extension package comes with a BasicGEX.gmk, which contains these things. Obviously the first thing you need to do to be able to use the extension is installing it through Game Maker's extension package manager. Once you've done that the BasicGEX.gmk will be working and it will show you the same things as the BasicGM7.gmk from the SDK. You should base your projects upon this file. If you do not want to do so you will have to copy the control object and the Ultimate3DOptions() script from the BasicGEX.gmk to your project and you'll have to set up the primary room correctly (as explained in Getting started).

The action libraries

The action libraries offer access to the most basic features from the novice section of Ultimate 3D. Do not expect to get very far with them. Using these user-friendly libraries does not compensate a thorough study of this help file. Do not expect to be able to use Ultimate 3D without being able to write code in the GML. The action libraries can save you some work, as they contain actions that are needed very often (like Step() and Destroy()), but they are not meant to make all features of Ultimate 3D accessible and they do not do so.

There are two libraries, the first one called U3D main, the second one called U3D misc. U3D main contains several actions to manage objects, create objects and change the environment. U3D misc. contains actions for modifying and transforming objects, for drawing 2D graphics and several other. What follows is an explanation of every action.

U3D main

Illustration not available Load texture
This action loads a texture from a given file and associates it with an integer texture index. For more information refer to the description of LoadTexture(...).

Illustration not available Step
Ultimate 3D associates Ultimate 3D objects (e.g. a single wall, a single billboard or a single model) with instances of Game Maker objects. These Game Maker objects have several variables that can be used to modify the Ultimate 3D object. This action transmits the data of these variables to Ultimate 3D. It should be placed in the step event of every Game Maker object, which is associated with an Ultimate 3D object, unless it's a camera. This action maps to Step().

Illustration not available Destroy
Destroys the Ultimate 3D object that is associated with this Game Maker object. To reach that Ultimate 3D objects get destroyed when the instances they are associated with get destroyed you should place this in the destroy event of every Game Maker object that is associated with an Ultimate 3D object. This action maps to Destroy().

Illustration not available Associate with primary camera
Sets up some variables and associates this object with the primary camera. To be more precise it sets the variables height and view to the given values, the viewport variables to match the primary view of Game maker, min_range to 1, max_range to 100000 and number to 0 and calls MoveCamera(...) after that.

Illustration not available Camera step
This action is the pendant to step for cameras. It transmits all information, which is saved within variables, from the Game Maker instance to the Ultimate 3D camera object. It should be placed in every step event of a camera object. Maps to MoveCamera().

Illustration not available Create wall
This action creates a wall from the position x, y, z to the position x+x2, y+y2, z+height with the texture with the given index and associates it with this Game Maker instance. It does this by setting up the variables x2, y2, height and texture and calling CreateWall() after that.

Illustration not available Create floor
This action creates a wall from the position x, y, z to the position x+width, y+height, z with the texture with the given index and associates it with this Game Maker instance. It does this by setting up the variables width, height and texture and calling CreateFloor() after that.

Illustration not available Create cube
This action creates a cube from the position x, y, z to the position x+width, y+height, z+depth with the texture with the given index and associates it with this Game Maker instance. It does this by setting up the variables width, height, depth2 and texture and calling CreateCube() after that.

Illustration not available Create billboard
This action creates a billboard with origin x, y, z the given width and height, the given relative billboard origin and the texture with the given index and associates it with this Game Maker instance. It does this by setting up the variables width, height, originx, originy and texture and calling Create2D() after that.

Illustration not available Create model
This action creates a model object from the given *.3ds, *.an8, *.md2, *.ms3d, *.u3d or *.x file and associates it with this instance. It does this by setting up the variable file and calling LoadMesh().

Illustration not available Create light directional
This action creates a directional light source with the given lighting direction and light color and associates it with this instance. It does this by setting up the variables r, g, b, rotx and roty and calling LightDirectional().

Illustration not available Create light point
This action creates a point light source with the given range and light color and associates it with this instance. It does this by setting up the variables r, g, b and range and calling LightPoint().

Illustration not available Create light point
This action creates a spot light source with the given lighting direction, range, lighting angle and light color and associates it with this instance. It does this by setting up the variables r, g, b and range, rotx, roty and deg and calling LightPoint().

Illustration not available Set ambient lighting
This action changes the intensity and color of the ambient lighting. Ambient lighting effects all objects with the same strength, independent from their position. This action maps to SetAmbient(...).

Illustration not available Set fog properties
This action sets up fog with the given color that starts at the given start distance and ends at the given end distance. At the end distance everything will have the fog color. This action maps to SetFog(...).

Illustration not available Disable fog
This action disables fog completely. It maps to DisableFog().

Illustration not available Set solid background color
This action sets up a solid background color and disables the sky sphere/sky cube in case it was used before.

Illustration not available Load sky sphere
This action loads a sky sphere from a given image file and uses it as a background. It maps to LoadBackground(...).

U3D misc.

Illustration not available Create instance 3D
This action extends Game Maker's instance creation action with an additional parameter allowing you to specify the z-position of the object. If relative is checked the coordinates will be interpreted relative to the coordinates of this instance, meaning that they will be added together.

Illustration not available Move through 3D angles
This action moves the object in 3D space using angles. For more information have a look at the description of the function Move(...).

Illustration not available Move through 3D coordinates
This action moves the object in 3D space using the given three coordinates. If relative is not checked the object gets set to the given position, otherwise the given coordinates get added to the current coordinates. This action will not effect the Ultimate 3D object unless the step action is placed in step event.

Illustration not available Rotate using Euler angles
This action rotates the object using the given three Euler angles. If relative is not checked the objects variables rotx, roty and rotz get set to the given values, otherwise the given angles get added to rotx, roty and rotz. This action will not effect the Ultimate 3D object unless the step action is placed in step event.

Illustration not available Scale object
This action scales the object using the given scaling factor. If relative is not checked the objects variables scalx, scaly and scalz get set to the given value, otherwise scalx, scaly and scalz get multiplied by the given values. This action will not effect the Ultimate 3D object unless the step action is placed in step event.

Illustration not available Set animation speed and animation area
This action sets the speed of the animation and defines the beginning frame and the ending frame of the animation cycle. The beginning and ending frame are optional, so you can have a 0 there if you want to play the complete animation. If relative is not checked the variable frame gets set to the negate of the given animation speed, otherwise frame gets subtracted by the animation speed. In both cases the variables first_frame and last_frame get set to the given values. This action will not effect the Ultimate 3D object unless the step action is placed in step event.

Illustration not available Draws a texture to the screen
This action draws the texture with the given index at the given position using the given scaling, rotation and transparency. For more information refer to the description of DrawTex(...).

Illustration not available Load and prepare true type font
This action loads the true type font with the given name and prepares it for efficient text drawing. The resulting font will be associated with the given index and will have the given text size, will be bold or italic if requested and will have the given color. For more information refer to the description of SetFont(...).

Illustration not available Use a prepared true type font to draw text
Uses the font with the given index to draw the given text at the given position. For more information have a look at the description of DrawText(...).

Illustration not available Take screenshot
Takes a screenshot of the Ultimate 3D view port and saves it to the given bitmap file.


Function list

AddFrame(File,Password)

AddGravityEmitter(PositionX, PositionY, PositionZ,GravityEmitterMass)

AddInverseTangentSpaceMatrices()

AddModifier()

AddTerrainTexture(DiffuseMap,AlphaMap,WrapCountX, WrapCountY,AlphaMapChannel)

AddTextureCoordinateSet(DimensionCount)

AddTextureCoordinateSetsFromModel(ModelFile, Password)

AddVertex(X,Y,Z,U,V)

ApplyCelShading(LineWidth,LineColorR, LineColorG, LineColorB, LineColorA)

ApplyParallaxAndBumpMapping(MaterialIndex,HeightMapIndex,ParallaxFactor,LightSourceID1,LightSourceID2,LightSourceID3)

ApplyTerrainTextures()

ApplyTransformationMatrix(InputMatrixID)

AttemptMoveToPosition(OutputVectorID,ObjectID,StartX,StartY,StartZ,DestinationX,DestinationY,DestinationZ,RotX,RotY,RotZ,ScalingX,ScalingY,ScalingZ,RoomIndex)

BeginPolygon(VertexNumber)

CalculateCrossProduct(OutputVectorID,InputVectorID1, InputVectorID2)

CalculateDotProduct(InputVectorID1, InputVectorID2)

CalculateTerrainLightMap(LightSourceID1, LightSourceID2, , LightSourceID10)

CalculateVectorDifference(OutputVectorID,InputVectorID1, InputVectorID2)

CalculateVectorLatitude(VectorID)

CalculateVectorLength(VectorID)

CalculateVectorLongitude(VectorID)

CalculateVectorScalarProduct(OutputVectorID,InputVectorID,ScalarFactor)

CalculateVectorSum(OutputVectorID,InputVectorID1, InputVectorID2)

CheckBoundingBoxVisibility(BoundingBoxMinimumVectorID, BoundingBoxMaximumVectorID,BoundingBoxTransformationID,CameraIndex)

CheckRayIntersection(ObjectID,RayOriginX,RayOriginY,RayOriginZ,RayDirectionLongitude,RayDirectionLatitude,RoomIndex)

CheckSweptEllipsoidIntersection(ObjectID,StartX,StartY,StartZ,DestinationX,DestinationY,DestinationZ,RotX,RotY,RotZ,ScalingX,ScalingY,ScalingZ,RoomIndex)

ComputeMatrixRotationAngles(OutputVectorID,InputMatrixID)

ComputeMatrixScaling(OutputVectorID,InputMatrixID)

ComputeMatrixTranslation(OutputVectorID,InputMatrixID)

CoordToScreen(OutputVector,X, Y, Z,CameraIndex)

CopyAnimation(SourceFile,SourceFilePassword,CopyRotationKeysOnly)

CopyMatrix(OutputMatrixID,InputMatrixID)

CopyVector(OutputVectorID,InputVectorID)

Create2D()

CreateCube()

CreateCubeTextureRenderTarget(TextureIndex,RequestedEdgeLength)

CreateDirectionVector(OutputVectorID,Longitude, Latitude)

CreateEmptyMesh()

CreateFloor()

CreateLODChain(CameraDistance1, LoD1,CameraDistance2, LoD2,CameraDistance3, LoD3,CameraDistance4, LoD4,CameraDistance5, LoD5)

CreateMaterialVSEffect(MaterialIndex,VertexShaderFile)

CreateParticleSystem()

CreatePolygon()

CreatePortal(CenterX, CenterY, CenterZ, FacingDirectionLongitude, FacingDirectionLatitude, Radius, RoomIndex1, RoomIndex2)

CreatePostScreenShader()

CreatePrimitiveFromModel()

CreateRotationMatrix(OutputMatrixID,RotationX, RotationY, RotationZ)

CreateScalingMatrix(OutputMatrixID,ScalingX, ScalingY, ScalingZ)

CreateShadowOptimizedGeometry(LODIndex,CreateConnectionPatches)

CreateTerrain()

CreateTerrainDecal()

CreateTextureRenderTarget(TextureIndex,RequestedWidth, RequestedHeight)

CreateTransformationMatrix(OutputMatrixID,TranslationX, TranslationY, TranslationZ,RotationX, RotationY, RotationZ,ScalingX, ScalingY, ScalingZ)

CreateTranslationMatrix(OutputMatrixID,TranslationX, TranslationY, TranslationZ)

CreateVector(OutputVectorID,X, Y, Z)

CreateWall()

DeformTerrain(DeformationCenterX, DeformationCenterY, DeformationRadius,RaiseLevel)

Destroy()

DestroyPortal(PortalID)

DisableFog()

Draw3DText(Text,FontIndex,X, Y, Z, RotX, RotY, RotZ, ScalX, ScalY, ScalZ)

DrawTex(TextureIndex,X,Y,Scaling,Rotation,Alpha)

DrawTexEx(TextureIndex, Left,Top, Width,Height, X,Y, ScalingX,ScalingY, Rotation, R,G,B,A,CameraIndex)

DrawTexEx(TextureIndex,Left, Top,Width, Height,X, Y,ScalingX, ScalingY,Rotation,R, G, B, A)

DrawText(FontIndex,X, Y,Text)

DrawText(FontIndex, X, Y, Text,CameraIndex)

EnableVertexTweening()

ExportToU3DFile(OutputFile,Password,ExportResources)

GetBoneCount()

GetBoneIndex(BoneName)

GetBoneParent(BoneIndex)

GetBoneTransformation(OutputMatrixID,BoneIndex)

GetClippingPlaneSupport()

GetCubeMapSupport()

GetCurrentFrame()

GetDrawCallCount()

GetDrawnTriangleCount()

GetIntersectedMaterialIndex()

GetIntersectedTriangleNormal(OutputVectorID,IntersectedTriangleIndex)

GetIntersectionPosition(OutputVectorID,IntersectedTriangleIndex)

GetIntersectionTime(IntersectedTriangleIndex)

GetLockedMeshTriangle(LockedMeshIndex,TriangleIndex,RequestedVertexIndex)

GetLockedMeshTriangleCount(LockedMeshIndex)

GetLockedMeshTriangleMaterial(LockedMeshIndex,TriangleIndex)

GetLockedMeshVertex(LockedMeshIndex,VertexIndex,ValueIndex)

GetLockedMeshVertexCount(LockedMeshIndex)

GetMaterialCount()

GetMaterialIndex(MaterialName)

GetMatrixEntry(InputMatrixID,LineIndex, ColumnIndex)

GetMeshCount()

GetMeshOccurrenceCount(MeshIndex)

GetMeshOccurrenceTransformation(OutputMatrixID,MeshIndex,OccurrenceIndex)

GetMousePos()

GetObjectRoom()

GetObjectTransformation(ObjectID)

GetPushAwayVector(OutputVectorID,IntersectedTriangleIndex)

GetRayTracingNormal(OutputVectorID)

GetRoomVisibility(RoomIndex)

GetShadowSupport()

GetSimultaneousTextureCount()

GetSupportedPSVersion()

GetTerrainHeightAtPos(TerrainID,X, Y)

GetTextureStrengthAtPosition(TerrainID,TextureIndex,X, Y)

GetTriangleIntersectionCount()

GetVector(VectorID,VectorElementID)

GetVertex(VertexIndex,ElementIndex)

InterpolateMatrices(OutputMatrixID,InputMatrixID1, InputMatrixID2,InterpolationFactor)

InvertMatrix(OutputMatrixID,InputMatrixID)

LightDirectional()

LightPoint()

LightSpot()

LoadBackground(File)

LoadCubeTexture(FileScheme,TextureIndex,CubeWidth)

LoadHeightMap(TextureIndex,HeightMapFile,HeightFactor)

LoadMaterialEffect(MaterialIndex,UFXFile)

LoadMesh()

LoadTexture(TextureFile,TextureIndex,Width, Height)

LockMesh(LockedMeshIndex,MeshIndex,Frame,LoD)

MouseCheckButton(MouseButton)

Move(Longitude,Latitude,Speed)

MoveCamera()

NormalizeVector(InputOutputVectorID)

OpenPortal(PortalID,OpenClose)

ParticleSystemStep(StepLength)

PreloadMesh(File,Password)

RecalculateNormals()

RecreatePrimitive()

ReleaseMatrix(MatrixID)

ReleaseModel(File)

ReleaseTexture(TextureIndex)

ReleaseVector(VectorID)

RenderToCubeTexture(CubeTextureIndex,PositionX, PositionY, PositionZ,ColorR, ColorG, ColorB, ColorARoomIndex1, RoomIndex2, , RoomIndex8)

RenderToTexture()

SaveTerrainHeightMap(OutputFile)

ScreenCoordToVector(OutputVectorID,ScreenCoordX, ScreenCoordY,CameraIndex)

SetAmbient(R,G,B)

SetBoneFrame(BoneIndex,Frame,AffectChildBones)

SetBoneRotation(BoneIndex,RotationX, RotationY, RotationZ)

SetClippingPlane(ClippingPlaneIndex,PlaneOriginX, PlaneOriginY, PlaneOriginZ,PlaneOrientationLongitude, PlaneOrientationLatitude)

SetCustomTerrainLightMap(TextureIndex)

SetFilter(TextureFilter,TextureStage)

SetFinalParticleColor(MinimumR, MinimumG, MinimumB, MinimumA,MaximumR, MaximumG, MaximumB, MaximumA)

SetFog(R,G,B,StartDistance,EndDistance)

SetFont(FontName,FontIndex,TextSize,TextAttributes,R,G,B,A)

SetFontColor(FontIndex,R,G,B,A)

SetGravityEmitter(GravityEmitterID,PositionX, PositionY, PositionZ,GravityEmitterMass)

SetInitialCenterRepulsion(RepulsionFactorX, RepulsionFactorY, RepulsionFactorZ)

SetInitialParticleColor(MinimumR, MinimumG, MinimumB, MinimumA,MaximumR, MaximumG, MaximumB, MaximumA)

SetLockedMeshTriangle(LockedMeshIndex,TriangleIndex,VertexIndexA, VertexIndexB, VertexIndexC)

SetLockedMeshTriangleMaterial(LockedMeshIndex,TriangleIndex,NewMaterialIndex)

SetLockedMeshVertex(LockedMeshIndex,VertexIndex,ValueIndex,NewValue)

SetMaterial(Red,Green,Blue,Alpha)

SetMaterialEffectLightSource(MaterialIndex,LightSourceID1, LightSourceID2, LightSourceID3)

SetMaterialEffectPSConstant()

SetMaterialEffectVSConstant(MaterialIndex,VSConstantIndex,XValue, YValue, ZValue, WValue)

SetMaterialEmissive(Red,Green,Blue)

SetMaterialEnvironmentMap(MaterialIndex,TextureIndex)

SetMaterialStageTexCoord(MaterialIndex,TextureStage,TextureCoordinateSetIndex)

SetMaterialStageTexture(MaterialIndex,TextureStage,TextureIndex)

SetMaterialStageTextureOperation(MaterialIndex,TextureStage,TextureOperation)

SetMipMapCount(NewMipMapCount)

SetMipMapFilter(TextureFilter,TextureStage)

SetModelMaterial(MaterialIndex,R, G, B, A)

SetModelMaterialEmissive(MaterialIndex,R, G, B)

SetModelMaterialSpecular(MaterialIndex,R, G, B,Power)

SetModifierAccelerationChange(ModifierID,AccelerationFactorX, AccelerationFactorY, AccelerationFactorZ,AccelerationSummandX, AccelerationSummandY, AccelerationSummandZ)

SetModifierEffectedArea(ModifierID,SecondSphereRadius,PositionX, PositionY, PositionZ,RotationX, RotationY, RotationZ,ScalingX, ScalingY, ScalingZ)

SetModifierLifetimeChange(ModifierID,LifetimeChange)

SetModifierNewFinalColor(ModifierID,MinimumR, MinimumG, MinimumB, MinimumA,MaximumR, MaximumG, MaximumB, MaximumA)

SetModifierNewFinalSize(ModifierID,MinimumNewFinalSize, MaximumNewFinalSize)

SetModifierVelocityChange(ModifierID,VelocityFactorX, VelocityFactorY, VelocityFactorZ,VelocitySummandX, VelocitySummandY, VelocitySummandZ)

SetMousePos(X, Y)

SetObjectRoom(RoomIndex)

SetObjectSolidity(SolidityState,GlobalSolidityState)

SetOrtho(UseOrthogonalProjection,SeenAreaWidth, SeenAreaHeight)

SetParticleAcceleration(MinLongitude, MinLatitude,MinAcceleration,MaxLongitude, MaxLatitude,MaxAcceleration)

SetParticleCount(MinimumParticleCount,MaximumParticleCount,MaximumTotalParticleCount)

SetParticleLifetime(MinimumParticleLifetime,MaximumParticleLifetime)

SetParticleOrigin(SecondSphereRadius,RotationX, RotationY, RotationZ,ScalingX, ScalingY, ScalingZ)

SetParticleRotation(MinInitialRotation, MaxInitialRotation,MinRotationSpeed, MaxRotationSpeed)

SetParticleSize(MinInitialSize, MaxInitialSize,MinFinalSize, MaxFinalSize)

SetParticleVelocity(MinLongitude, MinLatitude,MinVelocity,MaxLongitude, MaxLatitude,MaxVelocity)

SetPortal(PortalID,FacingDirectionLongitude, FacingDirectionLatitude, Radius, RoomIndex1, RoomIndex2)

SetPSSFilter(TextureStage,FilterType)

SetPSSPSConstant(PSConstantIndex,Value1, Value2, Value3, Value4)

SetPSSTexture(TextureStage,TextureIndex)

SetPSSVSConstant(VSConstantIndex,Value1, Value2, Value3, Value4)

SetRoomAmbient(RoomIndex,AmbientColorR, AmbientColorG, AmbientColorB)

SetRoomVisibility(RoomIndex,NewVisibilityState)

SetRoomVisibilityEnforcement(RoomIndex,NewVisibilityEnforcementState)

SetTexturePath(TextureDirectory)

SetVertex(VertexIndex,X,Y,Z,U,V)

SmoothNormals(SmoothingAngle)

StartPoseInterpolation(DestinationFrame,InterpolationDuration)

Step()

SwitchAdditiveBlending(UseAdditiveBlending)

SwitchAlphaBlending(EnableDisable)

SwitchBackgroundMode(EnableBackgroundMode)

SwitchPortalEngine(EnableDisable)

SwitchShadowCasting(LightSourceID,Enable)

SwitchShadowReceiving(LightSourceID,Enable)

SwitchShadows(Enable)

SwitchView()

SwitchWireFrameMode(EnableDisable)

TakeScreenshot(BMPFile)

TakeScreenshotEx(BMPFile,Unused,Left, Top, Right, Bottom)

TransformMatrix(OutputMatrixID,InputMatrixID1, InputMatrixID2)

TransformVector(OutputVectorID,InputVectorID,InputMatrixID)

TransformVectorEx(OutputVectorID,InputVectorID,InputMatrixID,ApplyRotation,ApplyTranslation,ApplyScaling)

Ultimate3DOptions()

UnloadBackground()

UnlockMesh(LockedMeshIndex)

UpdateSkeleton()