User Tools

Site Tools


This is an old revision of the document!


The Materials (cmat files) Manual

Ca3DE materials are defined in material script files, which in turn are simple ASCII text files with file name suffix .cmat. This page describes how such “cmat scripts” can be written, their syntax and semantics.

Overview

First of all, here is a simple example for a material definition script. It was taken from the Ca3D-Engine/Games/DeathMatch/Materials/Kai.cmat file, which also contains several other, very similar material definitions:

    Textures/Kai/3r_metpan01        // Material definitions start with the material name.
    {
        diffusemap  Textures/Kai/3r_metpan01_diff.png     // This line says which texture is used as diffuse-map.
        normalmap   Textures/Kai/3r_metpan01_norm.png
        specularmap Textures/Kai/3r_metpan01_spec.png
        lightmap    $lightmap
    }

Before we dig into the details about keywords and structure of such material definitions, here are some general properties of cmat files:

  • cmat files are simple ASCII text files, containing material definition scripts. Their file name ends with .cmat.
  • All statements in such files are case-sensitive. That means that text and Text are not the same. This is also true for filenames, like the Textures/Kai/3r_metpan01_diff.png filename above, because some operating systems like Linux have a case-sensitive file-system.
  • C++ style comments are allowed in material scripts: // This is a comment.
  • The cmat files for MOD “MyMOD” are all stored in the Ca3D-Engine/Games/MyMOD/Materials/ directory and its subdirectories. This is necessary because the Ca3D-Engine automatically scans this directory for material scripts whenever MOD “MyMOD” is run.
  • Tokens in cmat files are separated by white-space and these individual characters: ( { [ ] } ) ,
  • Quoted tokens are recognized. That is, everything between two "..." is considered as one statement, even if white-space or one of the above characters is inside it. That means, if for example you really want to have a material name like my(new and cool)material, then you have to enclose it in quotation marks like this: "my(new and cool)material" in order to account for both the white-space and the brackets. Using quotation marks is not recommended, though! They're mostly useful if somebody created textures with weird file names like for example "{_SomeFile.bmp". Write MyNewAndCoolMaterial or My/New/And/Cool/Material or something similar for your material names instead.

Material definitions always start with the material name. In the example above, that's Textures/Kai/3r_metpan01. You can name materials almost anything you like. If you want to use white-space, commas or brackets in their name, you'll have to put the name into quotation marks as mentioned above. However, it is important that the name is unique. If the same material name appears again in any other cmat file of the same MOD, the engine will use only the first occurance, so the chances are 50:50 that your material wins over the other. Most materials that come with Ca3DE have a filename that roughly resembles the name of its texture image files. That is often a helpful hint for conveniently working with the material, but by no means a requirement.

The body of the material definition is enclosed in a pair of { … }. In many cases, it will only contain a few texture map specification statements like in the example above.

Texture Map Specifications

The simplest form of a material definition is to only specify the texture map images that compose the material. The previous example was an example for such a simple definition.

A texture map specification starts with a keyword (e.g. diffusemap) and is followed by a “map composition”.

Map compositions are normally just the path plus file name of a texture image relative to the MOD directory, like for example Textures/Kai/3r_metpan01_diff.png. However, map compositions can also be more complex constructs as explained in the next section Map Compositions.

The initial keyword (diffusemap etc.) defines how the texture image is used in dynamic lighting computations during rendering. This is very similar to Doom3 materials, and both Ca3DE and Doom3 implement in this regard a form of the Phong lighting model.

Here comes a list of all available keywords for texture map specifications, along with a short description of their default meaning. (The default is dynamic Phong lighting, which however can be overridden, as explained at Shader Specifications.)

  • diffusemap The texture map image that defines the diffuse color (or diffuse reflectivity) of the material. The alpha channel of the diffuse-map specifies the translucency of the material.
  • normalmap The texture map image that specifies the color-encoded normal-map of the materials surface. The normal vectors must be specified in tangent space, range compressed (-1 to 1 maps to 0 to 1), and the y-axis points top-down. Note that heightmaps can be converted into normal-maps as described in section Map Compositions below.
  • specularmap The image for the materials specular highlights.
  • lumamap This texture map image defines the luminance for this material. Note that the light emittance that is defined here is local only (as is a general property of the Phong lighting model). It is not cast onto other surfaces.
  • lightmap This materials lightmap image. This image is normally computed and provided by the 3D engine or the program with which you use the material. Although you can provide texture map file names as for the above keyword, that rarely ever makes sense. Normally, simply specify the special lightmap $lightmap in combination with the lightmap keyword in order to use whatever lightmap the 3D engine provides for this material. This is demonstrated in the preceding example.
  • shlmap The image that contains color-encoded coefficients for Spherical Harmonic Lighting for this material. As for lightmaps, you may specify arbitrary texture map file names with this keyword, but normally just use $shlmap in order to let the engine supply the proper SHL map.
  • cubeMap A six-sided cube-map image that is used e.g. for environmental mapping. Normally, cube-map specifications are per default ignored, because they are not regularly used with Phong lighting. Instead, they are specially activated as described in section Shader Specifications. As a single cube-map is actually composed of six individual images, a special convention for its file name is employed: A '#' character in the file name is automatically replaced with the six possible cube-map suffixes _px, _nx, _py, _ny, _pz and _nz. For example, consider this material definition from the Ca3D-Engine/Games/DeathMatch/Materials/SkyDomes.cmat file:
        Textures/SkyDomes/PK_BrightDay2
        {
            AmbientShader A_SkyDome
            LightShader   none      // == noDynLight
    
            // The '#' in the next line is auto-replaced with the relevant suffixes (_px, _ny, ...).
            cubeMap Textures/SkyDomes/PK_BrightDay2#.png, wrapS clampToEdge, wrapT clampToEdge
    
            // ...
        }

    This example has keywords and elements that will be explained further below, but for now observe that the file name that is assigned to the cube-map is Textures/SkyDomes/PK_BrightDay2#.png. When the Material System loads the six individual images from disk, it will therefore load them from the files Textures/SkyDomes/PK_BrightDay2_px.png, Textures/SkyDomes/PK_BrightDay2_nx.png, Textures/SkyDomes/PK_BrightDay2_py.png, and so on.

  • cubeMap2 This is like the above cubeMap keyword. It allows you to specify a second cube-map for special-purpose shaders that require two cube-maps. This keyword too is normally ignored unless a shader is specified that makes use of it. See Shader Specifications for more details.

You can specify arbitrary combinations of these keywords in one material, as only the diffusemap keyword is mandatory. However, if you use the same keyword more than once, only the last occurrence is considered. The order of the keywords occurrences is not relevant.

Map Compositions

Texture map image specifications with the above keywords can not only be simple file names, but also by more powerful Map Compositions. A map composition is a description of how a single texture map image is composited from several source images on disk. Here is an example for a simple material whose normal-map is defined by a complex map composition:

    Textures/Kai/barrel_rst
    {
        diffusemap Textures/Kai/barrel_rst_diff.png
        normalmap  combineNMs(MyNm1.png, hm2nm(add(MyHm2.jpg, MyHm3.tga)))
        lightmap   $lightmap
    }

(This example is overly complex for demonstration purposes, and not really meaningful. Real-life examples are normally much simpler.)

The expressions that are valid to define a map composition are defined as follows. Please note that the arbitrary nesting of expressions is expressly permitted, yielding great freedom for artists.

  • filename This is the most simple expression: a path plus a filename, as e.g. Textures/Kai/barrel_rst_diff.png in the above example for the diffusemap. The path is relative to the directory of the MOD for which this material script was written. Supported file extensions include png, tga, jpg and bmp.
  • add(e1, e2) This expression adds the colors of e1 and e2, where e1 and e2 can be arbitrary sub-expressions. The resulting RGBA values are clamped to 1.0.
  • mul(e1, e2) This expression multiplies/modulates/filters the colors of e1 and e2.
  • combineNMs(e1, e2) Treats the colors of e1 and e2 as color-compressed normal vectors, and combines or “adds” them in a mathematically correct fashion. (This it not the same as the add(…) operation.)
  • hm2nm(e1, scale) Assumes that e1 is a gray-scale heightmap and converts it into a normal-map. The relative height of the heightmap is scaled by factor scale in order to weaken or pronounce the resulting effect. Values between 1.0 and 10.0 are normal use, but numbers greater then 10.0, less then 1.0, or even negative numbers are allowed, too.
  • flipNMyAxis(e1) Considers the colors of e1 as color compressed normal-vectors, and flips their y-component. This is useful for normal-maps that have their y-component pointing into the wrong direction. Such normal-maps occurred in the early days of dynamic lighting or were created for other programs than Ca3DE. This function is for fixing such cases, and should rarely be needed.
  • renormalize(e1) Considers the colors of e1 as color compressed normal-vectors, and renormalizes them (scales them to unit length). This is mostly useful for testing and debugging.
  • blue2alpha(e1) This function is for use with old diffuse-maps. It replaces the alpha channel of e1 with value 0.0 (transparent) if the RGB color at this pixel is pure blue (0.0, 0.0, 1.0), and 1.0 (opaque) otherwise. Moreover, pure blue pixels are replaced with the average pixel color of the non-blue pixels in order to account for texture filtering.
  • (automatic scaling) Whenever you employ one of the above expressions to combine the results of two expressions e1 and e2 that have different lateral dimensions, e2 is automatically scaled to match the size of e1.

You can apply map composition expressions to all above mentioned texture map specification keywords, i.e. they work with diffusemap, normalmap, specularmap, cubemap, etc.

Technically, a map composition is completed before the Ca3D-Engine or the graphics board see them. In other words, the engine or the 3D hardware never see the individual images, only the composite result. Everything that is done by these composition steps could also be pre-worked by the artist in an image processing software. There would be no difference for the engine, the hardware, or in the resource (memory) consumption. Note that this feature has nothing to do with dynamic lighting or how a texture map image is combined with images of other texture map specification keywords!

Finally, you can specify several comma-separated options for the map composition:

  • minFilter This controls MipMap usage and the filter that is used for texture minification. It must be followed by one of
    • nearest or point
    • linear or bilinear
    • nearest_mipmap_nearest
    • nearest_mipmap_linear
    • linear_mipmap_nearest
    • linear_mipmap_linear or trilinear (This is the default.)
  • magFilter This controls the filter that is used for texture magnification and must be followed by one of
    • nearest or point
    • linear or bilinear (This is the default.)
  • wrapS This controls horizontal texture wrapping and must be followed by one of
    • repeat for repeating the texture in horizontal direction. This is the default.
    • clamp for clamping the texture in horizontal direction.
    • clampToEdge for clamping the texture in horizontal direction to its edge color. Often useful with cube-map images.
  • wrapT This controls vertical texture wrapping and must be followed by one of
    • repeat for repeating the texture in vertical direction. This is the default.
    • clamp for clamping the texture in vertical direction.
    • clampToEdge for clamping the texture in vertical direction to its edge color. Often useful with cube-map images.
  • noScaleDown specifies that the texture image is never scaled down, not even if the user selects a medium or low texture detail setting for tuning the graphics performance. Used e.g. for the Ca3DE splash screen logo, which would get blurred otherwise. (Look into Games/DeathMatch/Materials/Splash.cmat if you want to toy around with it a little :) ).

Shader Specifications

In the Ca3DE Material System, a Shader is a module of compiled C++ code. A shader is responsible for rendering a mesh of geometry with a given material in exactly one way that is specific to that shader.

Each renderer of the MatSys comes with a large, pre-built library of shaders. When the MatSys and the current renderer is initialized on program start, each material gets automatically assigned the best shader from the renderers shader library. That is, depending on which texture map specifications you have defined for a material, the shader that can best handle the given combination is found and assigned to that material.

In truth however, not only one but two shaders are assigned to the material. This is because one shader is specific to rendering the ambient part of the material, i.e. everything that is rendered even if no light source is present. The second shader is of a different type and renders the per-light-source contribution (diffuse lighting and specular highlights) of that material.

Here is the crucial point: The material scripts described so far do only allow you to define a limited number of material effects, namely all those that can be specified by different combinations of the texture map image keywords as explained in section Texture_Map_Specifications. This may seem like a serious limitation. However, not only can probably the big majority of your materials be defined with the simple means as presented above (more material keywords are presented in subsequent subsections), but more important is that these limitations can be overcome.

That means that you can actually override the Material Systems default shader assignment to a material, and force your own shader to be selected. This is one of the key properties of the Ca3DE Material System, which makes it very powerful and flexible: Whenever you want to achieve a material effect that is not covered by the built-in shaders, you can specify your custom shader to render the desired effect. Writing your own shader gives you the ultimative freedom to employ the latest rendering technology as your heart desires. This does of course require you or someone you work with to write a new shader in C++, and to compile and link it to the Material System. Writing new custom shaders for the Ca3DE MatSys will be explained for future released of the SDK.

Overriding the auto-selected shaders and thus installing your custom shaders is achieved with the ambientShader and lightShader keywords that are followed by the name of the custom shader. Here is an example, where the existence of two custom, self-made shaders is assumed:

    Textures/Kai/barrel_rst
    {
        diffusemap    Textures/Kai/barrel_rst_diff.png
        normalmap     Textures/Kai/barrel_rst_norm.png
        specularmap   Textures/Kai/barrel_rst_spec.png
        lightmap      $lightmap

        ambientShader myCarMetallicBlue_ambient
        lightShader   myCarMetallicBlue_light
    }

As custom shaders are free to do anything they want, they may or may not redefine the meaning of the texture map image specification keywords. For example, they may use the normal-map for purposes different than just obtaining surface shape information from it, or they may even use the normalmap keyword for specifying a regular color image that is used for arbitrary purposes. Information about what exactly a custom shaders does with the specified texture map images can be learned from the shaders author.

Keyword Reference

Additional keywords are available in the body of material definitions. These keywords broaden the range of applications that can be handled by the built-in default shaders, and often reduce the need to write custom shaders.

The following material parameters are global rendering parameters. They affect the entire material, but only how it is rendered. These parameters are never looked at by the map compile tools.

  • noDraw If specified, this material does not render at all, it becomes invisible. This is probably mostly useful for debugging. (For mappers: noDraw does not cause CaBSP to remove any faces from the world. If you want faces to be actually removed, you should rather use the meta_XXX_TODO keyword in order to make CaBSP remove them right from the start.)
  • noDynLight If specified, this material does not receive any light from dynamic light sources, only the ambient contribution is rendered. (Technically, the material does not even get a per-light-source shader assigned.) Useful e.g. for sky domes, additive effects like particles, translucent surfaces like water and glass etc.
  • twoSided Normally, the back or “inside” faces of materials are not rendered. This renders both sides of the material. Useful for “indefinitely” thin objects like metal grates and fences, spider webs, … Note that two-sided faces are currently not properly lit by dynamic light when seen from the back side.
  • noScaleDown Prevents the texture images from being scaled down when the user optimizes the game graphics. Useful for fonts, HUDs, lightmaps, some model textures (see example below), and everything else that must not get mixed up or blurred by image filtering. Not yet implemented – texture images are currently never scaled down.
  • noMipMaps Prevents the creation of mip-maps for all texture images of this material. Often combined with and useful for similar purposes as NoScaleDown. The figures belows show an example for noScaleDown and noMipMaps.

An example for the noScaleDown and noMipMaps keywords. Notice the small glitch in the center image, which is a result of mipmaps applied to the texture shown in the left figure. Such glitches are even more disturbing and better visible with animated models, e.g. when the head of the model does slightly turn. With the noScaleDown and noMipMaps keywords applied in the material script, the glitch disappears as shown in the right figure. Rather than using noScaleDown and noMipMaps, another possible solution had been to put the textures for the skin and hair into separate texture images.

The following parameters do only affect the ambient contribution of a material (as implemented by the Material Systems ambient default shaders). The map compile tools never look at these parameters. Some of these parameters take expressions as their arguments, which are denoted by $expr. Expressions can be simple numbers or more complex constructs, they are discussed in greater detail in subsection Expressions

  • alphaTest $expr Activates the alpha test with the result of $expr as the reference value. The alpha test passes if and only if the alpha value of the ambient contribution (which normally comes from the diffuse-map) is greater than the reference value. Note that the reference value can be specified as an expression, that is, it can be varying over time. This can produce interesting effects (i.e. materials that appear to grow or shrink) if the diffuse-map comes with an appropriate alpha channel. Negative values turn the alpha test off. The test is off by default.
  • blendFunc $src_factor $dst_factor This parameter determines the blend function for the ambient contribution of the material. $src_factor and $dst_factor must be one of zero, one, dst_color, src_color, one_minus_dst_color, one_minus_src_color, dst_alpha, src_alpha, one_minus_dst_alpha, or one_minus_src_alpha. Note that not all combinations make sense. Using blendFunc will be documented in greater detail in future releases of this text. Per default, blending is turned off.
  • red $expr
  • green $expr
  • blue $expr
  • alpha $expr
  • rgb $expr
  • rgba $expr These parameters all define expressions for (channels of) the color with which the ambient contribution is modulated. I have defined the default ambient shaders such that for materials that have luma-maps, only the luma-map is modulated. Materials that have no luma-map get the entire ambient contribution modulated. This allows to create effects such as panels that have flickering LEDs, glowing lights etc. Note that you can specify different expressions for different color channels. That is, if you have a luma-map for a computer panel that has both red and green LEDs, you can for example have the red LEDs morse SOS, while the green LEDs change gradually by a sinus function. The default expression for all color channels is 1.0 (identity).
  • useMeshColors Additionally to the above color definitions, this will modulate the materials color with the colors that are specified with the vertices of the mesh that is to be rendered. This option does currently only work in a very limited set of shaders, and is mostly useful internally in CaWE, the Ca3DE World Editor, e.g. for rendering wire-frame stuff. You will rarely ever need this in a real-world material script.

Finally, here are the meta-parameters that are taken into account by the Ca3DE map compile tools. These parameters are not directly related to the rendering of the material.

  • meta_RadiantExitance_Values[4] Radiant Exitance RGB values plus intensity (scale). Used by CaLight.
  • meta_RadiantExitance_ByImage_FileName Radiant Exitance RGB values from image file. Used by CaLight.
  • meta_RadiantExitance_ByImage_Scale Radiant Exitance intensity (scale) for the RGB values from image file. Used by CaLight.
  • meta_AlphaModulatesRadiosityLight Makes CaLight handle the DiffMapComps alpha channel and the RGBAGens properly. For fences, grates, glass, water, etc.
  • meta_NoLightMap Makes the compile tools not cover this material with a lightmap.

Expressions

Some of the above keywords cannot only take numbers as their arguments, but entire mathematical expressions. An expression is a combination of numbers, mathematical operations, symbols, and table look-ups. Here is an example for such an expression:

    (1 + sinTable[ time*0.25 + 3 ]) / 2

Unfortunately, there are also bad news: The current parser of the Ca3DE MatSys is not yet powerful enough to parse expressions that use operator infix notation like the one above. Instead of +, -, *, … we therefore have to use explicit prefix notation, which is much easier to parse. The prefix notation is explained below. Here is the above example in prefix notation that we have to use until I can improve the parser to support infix notation:

    div(add(1, sinTable[ add(mul(time, 0.25), 3) ]), 2)

Expressions are defined recursively as follows:

  • $num A number.
  • $var A variable. Please see below for a list of all valid variables.
  • $table[ $expr ] A table look-up. The value of the table at position $expr is returned. The details of the look-up depend on the tables definition. The default tables sinTable and cosTable are always predefined. Custom tables can be defined as described in subsection Tables. Note that table look-ups are normalized. That is, all table elements are accessed by indices between 0.0 and 1.0, that is, the fractional part of $expr.
  • add($expr1, $expr2) Adds the results of $expr1 and $expr2.
  • sub($expr1, $expr2) Subtracts the results of $expr1 and $expr2.
  • mul($expr1, $expr2) Multiplies the results of $expr1 and $expr2.
  • div($expr1, $expr2) Divides the results of $expr1 and $expr2 if $expr2 is not 0. Otherwise, this evaluates to 0.

The following variables are defined:

  • time The current system time in seconds, starting from zero.

Tables and table look-ups are described in subsection Tables.

Tables

Another type of object that you can define in material scripts are tables. Tables are a very powerful means to express arbitrary functions for use in expressions. Expressions are explained in detail above in subsection Expressions.

Table definitions start with the keyword table, followed by the name of the table. Then come two {s, the table data values separated by commas, and then the closing two }s. Here is an example of a table definition:

    table myFlickerTable { { 0.5, 1, 0, 0.2, 0.0, 0.8, 0.1, 0.7 } }

The elements of tables can be accessed from expressions as explained in subsection Expressions. It is important to note that the indices for table access are treated as normalized indices. That means that all values of a table are accessed with numbers that are greater or equal to 0.0 and less than 1.0. In the above table, which has eight elements, you'd get 0.5 for myFlickerTable[0.0], 1 for myFlickerTable[0.125], 0.2 for myFlickerTable[0.375] and so on.

As tables normally wrap, that is, start over when you read over their end, you will get 0.5 also for myFlickerTable[1.0] (but 0.7 for myFlickerTable[0.9999]). You may want to think of this as only the fractional part of the index being taken for the look-up, while the integer part is ignored. That means that for example the expression

    rgb myFlickerTable[time]

runs through the entire table exactly once per second, no matter how many elements the table actually has.

Between the two opening {, one or both of the keywords snap and clamp are allowed. snap defines that there is no linear interpolation between the table values. For example, we just said that myFlickerTable[0.0] yields 0.5 and myFlickerTable[0.125] yields 1.0. But what does myFlickerTable[0.0625] (the middle between 0.0 and 0.125) yield? If snap has not been specified, the value will automatically be interpolated between the table elements, and the result would be 0.75. If however snap has been defined, the result would be “snapped” to the next lower table element, and the result would be 0.5. Figure TODO demonstrates plots of two tables that differ in the use of the snap keyword but are identical otherwise. Interpolation is useful whenever you want to have a table to encode functions that have “smooth transitions”. Snapping is useful whenever you want to have a table to encode functions that have “hard transitions”. For example, in order to have LEDs flicker the SOS morse code, you'd use this table:

    table sosTable { snap { 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0,
                            1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0 } }

The clamp keywords defines what happens if a table is accessed at indices outside of the normalized range 0.0 to 1.0. Without clamp, the default behaviour is to wrap. With clamping enabled, the indices are clamped to yield the smallest or greatest table element. Figure FIXME TODO demonstrates plots of two tables that differ in the use of the clamp keyword but are identical otherwise.

Tables must be defined before their first use. As an exception, the two tables sinTable and cosTable are implicitly predefined and can always be used without prior declaration. The scope of a table begins at its definition and ends at the end of the material script. Note that although in the above examples only table element values between 0.0 and 1.0 occur, arbitrary numbers are allowed.

matsys/cmat_manual.1127640260.txt.gz · Last modified: 2013-01-07 12:07 (external edit)