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.
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.
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.
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.
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()
.
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.
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).
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:
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.
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
}
// Parallax mapping and bump mapping are not supported
} |
---|
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 ;) .
© Christoph Peters. Some rights reserved.