Assets/Model

From Doomsday Wiki
Jump to: navigation, search

< Assets

This article is about 3D model assets that can be rendered in the game world using the GL2 model renderer. The article applies to the latest unstable build of Doomsday. Documentation for version 1.15 can be found in the history.

Model assets

Models representing things (model.thing.*)

Example:

 asset model.thing.possessed {
    path = "possessed.md5mesh"
 }

This asset definition should be understood as "3D model asset to be used for representing things of type POSSESSED in the game world". The thing type must be one of the thing types defined in the loaded game.

Models representing player weapons (model.weapon.*)

Example:

 asset model.weapon.wand {
   path = "wand.md5mesh"
 }

This asset is for a player weapon model that will be drawn instead of a 2D psprite. The weapon identifiers (e.g., "wand") are defined by the game plugins in their Values definitions ("Weapon Info" section).

You should scale weapon models so that they have an appropriate size in world units (compared to player height, for example).

Asset metadata

Work in progress: The exact syntax and structure of model assets is still being worked on and may change in upcoming releases/builds.

Common metadata for models

Variable Description
path Model file to be used. The path is relative to the file where the metadata is defined (e.g., Info of the package). See Supported 3D model formats for details about file formats.
front Front vector in the model's coordinate system. Determines which way the model will appear in the game world. For instance, a monster's front vector defines the "forward" direction of the model. For player weapon models, the front vector defines the direction the weapon is being pointed at (away from the eye).
front <1, 0, 0>
up Up vector in the model's coordinate system. Determines which way the model will appear in the game world.
up <0, 0, 1>
offset Offset vector applied to the model's origin when it is placed into the world. The X component moves the object sideways in relation to the direction it is facing. The Y component moves the object vertically (positive Y is up). The Z component moves the object forward or backward (positive Z is backwards).
offset <0, 33, 0>
mirror True causes the model's coordinate system to be flipped on the axis that is perpendicular to both the defined front and up vectors. The default is False, with no mirroring applied.
autoscale True causes the model to be autoscaled to the corresponding thing's height. If False (the default), no additional scaling is applied. This is useful if the model has been designed in world units. Always design weapon models in world units, they do not support autoscaling.
alignment.yaw Yaw angle alignment mode. The allowed values are:
  • False for no alignment (the default).
  • view for aligning the yaw angle so the object faces the viewer.
  • movement for aligning the yaw angle to the movement direction of the object.
  • random for a randomly selected, non-changing angle that is unique for each object.
The entire object is rotated as a whole around its origin.
alignment.pitch Pitch angle alignment mode. The allowed values are:
  • False for no alignment (the default).
  • view for aligning the pitch angle so the object faces the viewer.
  • movement for aligning the pitch angle to the movement direction of the object.
The entire object is rotated as a whole around its origin.
material.* Material definitions. Each material specifies texture maps for all the meshes in the model.
animation.(state) Metadata for the animation triggered when the thing enters state (state) (used with model.thing.*).
render.* Instructions for the renderer, e.g., which blending to use on which meshes.

Positioning

The front and up vectors can be used both for orienting the model appropriately from its private coordinate system to world space, and to change its size.

After the model has been oriented to world space, the offset vector is applied to the origin of the model. The offset is applied before the object is turned to face its target angle.

In version 1.15, models use a default scaling factor that adjusts their height (in world space) to be equivalent to the thing's height as defined in the Thing definition.

Materials

In version 2.0, model materials are collections of texture images: each material specifies a set of texture maps for each of the meshes in the model. None of the texture maps are required; if a mesh is missing one or more texture maps, the default textures will be used instead. Any other material properties specified in the model file are currently ignored.

The shader model.skeletal.normal_specular_emission (from the net.dengine.client.renderer package) is used by default to render all 3D models. It supports normal maps, specular reflections, and emitted light. It is possible to render the model with a different shader, and also use multiple rendering passes with different shaders.

By default, the materials found in the model file are used when finding the texture images for a model. For instance, in an MD5 model, the "shader" property is interpreted as the path of the diffuse color map. The model asset definition can override these, though.

Technical detail: Even though the shader identifier begins with "model.", it won't be confused with model assets because shader definitions are always read from the shaders.dei files found in loaded packages. The shaders end up in a de::GLShaderBank where they are stored as compiled OpenGL shader objects. In other words, shaders and model assets exist in different namespaces.

Variants

The material definition can specify multiple variants, i.e., alternative materials. Each material is a complete set of texture maps for all meshes. Any textures left unspecified are replaced with the default textures.

The simplest possible material definition simply assigns texture maps to a mesh. Meshes can be identified using the names assigned to them in the model file, or with a @N notation where N is the index of the mesh (@0 is the first mesh).

material.@0 {
    # ...textures maps...
}

Example of multiple meshes, with only one material being used:

material {
    @0 {
        # ...textures maps...
    }
    @1 {
        # ...textures maps...
    }
    mesh_with_a_name {
        # ...textures maps...
    }
}

In the above examples, the material being modified is the default one, called "default". They could also be written as follows:

material {
    variant "default" {
        @0 {
            # ...textures maps...
        }
    }
}

Variants can be defined by adding new variant blocks:

material {
    variant "default" {
        @0 {
            # ...textures maps...
        }
    }
    variant "damaged" {
        @0 {
            # ...textures maps...
        }
    }
}

Use the material variable in the render block to specify which material is used for rendering. The material can also be changed from timeline scripts.

You can use one texture image in multiple meshes and materials — only one copy of it is stored for rendering.

Tip about using inheritance. If there are variants that are very similar to each other, you can use the inherits keyword to copy definitions. For example:

asset model.thing.ettin {
    material {
        @0 {
            diffuseMap = "Ettin_Diffuse_1024.tga"
            normalMap = "Ettin_Normal_1024.tga"
            specularMap = "Ettin_SpecGloss_1024.tga"
            emissiveMap = "Ettin_Emissive_1024.tga"
        }
        variant "damaged" {
            group @0 inherits model.thing.ettin.material.@0 {
                diffuseMap = "Ettin_Diffuse_Damaged_1024.tga"
            }
        }
    }
    # ...

The identifier "model.thing.ettin.material.@0" refers to the block above: the topmost block is "model.thing.ettin", and "material.@0" is the block where the texture maps for mesh 0 are defined. Note that the group keyword is required when using this syntax.

Texture maps

The pixel size of the texture maps has no meaning: texture coordinates are always normalized in the range 0...1, and are mapped to the size of the texture being used. This enables having alternative texture maps (e.g., low and high resolution) and choosing one at package load time.

Material metadata
Variable Description
diffuseMap Path of the image to be used for surface color (RGB) and the overall opacity (A).
normalMap Path of the RGB image to be used for normal vectors. The image must contain unit-length vectors stored in the RGB components: 0=-1.0, 128=0.0, 255=1.0. If not provided, Doomsday falls back to a normal map where the normals point straight up (RGB 128,128,255 => XYZ 0,0,1). Alpha component is ignored.
heightMap Path of a height field image to be used for generating a normal map. The height field is a monochrome image where dark pixels have a lower height than light pixels. Alpha component is ignored.
specularMap Path of the RGBA image that specifies specular reflection parameters. The color of the reflected light is multiplied by the specular map RGB. The sharpness of the reflection is determined by the alpha component: 0 produces a dull/large reflection while 255 produces a very sharp/small one.
emissiveMap Path of the RGBA image that contains additional light on the surface. This is simply added to the pixel colors after ambient, diffuse, and specular light is calculated.

Example:

asset model.thing.possessed {
    path = "possessed.md5mesh"
    material.@0 {
        heightMap = "bodyheights.png"
        emissiveMap = "bodyemission.png"
    }
}

Selecting a material for drawing

The material to use for drawing is specified in the render block:

render {
    material = "damaged"
}

If omitted, the default material (called "default") is used.

Additionally, each rendering pass may specify separately which material is used for drawing. In both cases, the material can be changed from timeline scripts:

render {
    pass "effects" {
        material = "other"
        # ...other pass parameters...
    }
}
 
animation {
    timeline "example" {
        # setting the main material
        script at 0.0 { material = "other" }
        script at 1.0 { material = "default" }
    }
    timeline "example2" {
        # setting a pass-specific material
        script at 0.5 { effects.material = "default" }
    }
}

Animations

Unlike in the past, 3D model animation is done in terms of animation sequences rather than individual keyframes or states. This means that the animation sequences defined in the model file are played as specified in the file; the model asset's job is to map the game object's animation sequences to corresponding sequences in the 3D model.

In version 1.15, the only way to animate models is to trigger an animation sequence when a state is entered.

Animation metadata
Variable Description
animation.(state).(anim) Metadata for animation sequence identified by (anim). The identifier can either be the name of the animation sequence (if specified in the model file) or an index number in the format @N, with @0 being the first sequence. (state) is one of the states that the thing can be in.

The example below causes the state POSS_STND to trigger the animation sequence #0 found in the model file.

    animation {
        state POSS_STND {
            sequence @0 {}
        }
    }

Sequences

Variable Description
alwaysTrigger Normally an animation sequence is only triggered when a state sequence first begins. If the state sequence restarts from the beginning (for instance when a weapon refires), the animation is by default not triggered again. This ensures that the model's animation sequence timings are not overridden by the states. Setting alwaysTrigger to True disables this check and causes the model animation sequence to be started every time the specified state is encountered. The default is False.
duration Duration for the animation sequence (in floating-point seconds). The default behavior, when this variable is omitted, is to use the animation sequence duration specified in the model file. Consider using this variable when it is inconvenient to change the animation sequence durations of the original model.
looping True causes the animation sequence to loop. Note that after each loop finishes, the animation may pick another variant as determined by the random probabilities. The default is False, which makes the animation stop at the end of the sequence.
node Name of animation root node. If left blank, the animation affects the entire model.
priority Priority of the animation sequence. Lower priority animations will not interrupt higher priority ones, but instead will be queued up for starting after the higher priority animation finishes (if it ever does). Must be an integer. If omitted, defaults to 1.
prob Probability (0...1) that this animation sequence is chosen when looking for sequences to trigger. Allows defining multiple alternatives out of which one will randomly end up being used. The default is 1.0.

You may define any number of sequences for one state. Every sequence must have a unique name. The sequences are checked in the order they are found in the definitions. Below is an example of two variations of an animation sequence. Sequence #0 is started with a 50% probability; #1 is started as the other alternative. (Note how the animation group can be collapsed using the dot notation.)

    state animation.POSS_STND {
        sequence @0 { prob = 0.5 }
        sequence @1 { prob = 1.0 }
    }

The following shows a more complete example of player weapon animations:

asset model.weapon.wand {
    path = "wand2.md5mesh"
 
    front <1, 0, 0>
    up    <0, 0, 1>
 
    animation {
        state MWANDUP {
            sequence "equip" {}
        }
        state MWANDDOWN {
            sequence "unequip" {}
        }
        state MWANDREADY {
            sequence "idle_rare" { 
                prob = 0.1
                looping = True
                priority = 0
            }
            sequence "idle" { 
                looping = True 
                priority = 0
            }
        }
        state MWANDATK_1 {
            sequence "attack1" { prob = 0.333 }
            sequence "attack2" { prob = 0.333 }
            sequence "attack3" {}
        }
    }
}

There are a couple of things to note:

  • The idle animations have priority 0, putting them below the default of 1. This means the idle sequences will not interrupt the (un)equip or attack animations.
  • There are two looping variants of the idle animations. However, whenever any of them finishes a loop, it may trigger any of the variants. Therefore, "idle_rare" will be triggered every now and then while "idle" will be looping most of the time.
  • There are three different attack sequences, out of which one is randomly chosen for each attack. The probabilities are evenly distributed.

Timelines

Animation sequences can optionally define a timeline of scripted actions. The scripts are written using Doomsday Script and can be triggered at specific points in time when an animation sequence is ongoing. The timeline is synchronized with the model's animation sequence, so any scripts set to run at a time past the end of the sequence's duration will not run. While the scripts have access to the complete Doomsday Script runtime environment, they are primarily intended for making timed changes to the model's properties.

Timelines can be defined either inside sequence blocks, or they can be children of the main animation group in which case they can be shared by many animation sequences.

Example of a timeline inside a sequence:

    state animation.MWANDUP {
        sequence "equip" {
            timeline { 
                script at 0 { print "Equip animation started" }
                script at 0.5 { print "Half a second gone" }
            }                
        }
    }

Example of a shared timeline:

    animation {
        timeline "flash" {
            script at 0 {
                uEmission.setValueFrom(1, 0, 0.9, 0.5)
            }
        }            
 
        state MWANDATK_1 {
            sequence "attack1" { timeline = flash }
        }
    }

The script keyword causes the block's contents to be parsed as Doomsday Script. The at attribute is required for each script: it tells the parser to store the script for running at a later time. (A plain script block without any attributes is executed during parsing of the Info document.)

Animation variables

Variables can be declared inside the animation block:

    animation {
        variable gunRotation {
            node = "gun"
            axis <0, 0, 1>
        }
    }

Currently (starting with build 1844) these variables can be used for specifying additional rotation angles that affect parts of the model skeleton.

In the above example, the variable gunRotation would be available in scripts, with two members: gunRotation.angle is a Core.Animation object that determines the rotation angle (degrees), and gunRotation.speed is another Core.Animation object that determines a rotation speed (in degrees per second). The rotation speed is applied independently of the angle.

This means that the variables support two, independent operating modes: you can control the rotation angle directly by using angle, or you can control the rotation speed using speed and let the renderer calculate how much the angle changes over time. The latter is useful for simulating objects spinning freely in space (around an axis).

Animation variable declaration
Variable Description
node Skeleton node whose rotation will be modified. Any animations normally affecting the node will be applied in addition to the rotation specified with the variable.
axis Axis for the rotation. The axis is fixed and specified in the local space of the skeleton node.
angle Initial rotation angle (degrees). The default is 0.
speed Initial rotation speed (degrees per second). The default is 0.

Shaders and variables

The render block is used to specify the shader that is used to render the model. If render.shader is omitted, the default one will be used ("model.skeletal.generic").

    render {
        shader = "model.skeletal.generic"
    }

The render block may also declare any number of variables whose values will be passed to the shaders in use. The name of the variable must match an input variable (uniform) in the OpenGL shader. The values of the variables can be animated and the variables are exposed in Doomsday Script as Core.Animation objects.

    render {
         shader = "model.skeletal.generic"
         variable uEmission { value = 0 }
    }

Shaders available in net.dengine.client.renderer

See: models.dei in the net.dengine.client.renderer package.

Variable definition

Variable Description
value Initial value for the variable. Also determines the type of the variable:
  • value = 0 → float
  • value <0, 0> → vec2
  • value <0, 0, 0> → vec3
  • value <0, 0, 0, 0> → vec4
wrap Optional value repeat wrapping range (i.e., not clamping) for float variables. For example, this would wrap the value within the range 0...1 (like a GL texture coordinate):
wrap <0, 1>
wrap.x For vec2/3/4 variables, the optional value repeat wrapping range for the X component. The other components are wrap.y, wrap.z, and wrap.w.

Rendering passes

You can optionally specify exactly how the meshes of the model should be rendered. With the rendering pass definitions, you can change the drawing order of the meshes and change the blending mode. You may also include meshes in multiple passes or omit them completely from all passes.

Each pass is either enabled or disabled. These flags are stored individually for each instance of the model, so they may be used during animations.

If no passes are specified, all meshes are drawn in a single pass with basic alpha blending (<SrcAlpha, OneMinusSrcAlpha>).

Blending is specified using the OpenGL blending function factors and an operator: (sourceFactor) * sourceColor (operator) (destFactor) * destColor

Here is an example of how rendering passes are defined:

    render {
        pass "body" {
            meshes <@2>
        }
        pass "effects" {
            meshes <@0, @1, @3, @4>
            blendFunc <SrcAlpha, One>
        }
    }

The names of the pass blocks ("body" and "effects" above) are unique identifiers for the rendering passes. They can be used in Doomsday Script to access the pass's variables:

    render {
        pass "body" {
            meshes <@2>
            enabled = False
            variable uEmission { value = 0.5 }
        }
    }
    animation {
        timeline "example" {
            script at 0 {
                body.enabled = True
                body.uEmission.setValueFrom(1, 0, 0.9, 0.5)
            }
            script at 0.5 {
                body.enabled = False
            }
        }            
    }

Pass definition

Variable DScript Description
enabled yes Determines whether the pass is enabled or disabled. Disabled passes are skipped during drawing. Passes are enabled by default. Passes can be enabled/disabled via Doomsday Script during an animation.
shader Name of the shader to use when drawing the pass. If omitted, the model's shader is used instead.
material yes Specifies the material to use during the rendering pass. This affects all the meshes drawn during the pass. The material set here only affects this pass, overriding the material set by render.material.
meshes List of meshes to draw during this rendering pass. The mesh identifiers can either be the mesh names (if specified in the model file) or an index number in the format @N, with @0 being the first mesh. Order within this list is not retained: meshes are drawn in the order they are found in the model file (ascending index numbers). Cannot be
blendFunc Blending function RGB factors used when drawing. Note that the factor names are case sensitive. The default factors are:
blendFunc <SrcAlpha, OneMinusSrcAlpha>
The available factors are:
  • Zero
  • One
  • SrcColor
  • OneMinusSrcColor
  • SrcAlpha
  • OneMinusSrcAlpha
  • DestColor
  • OneMinusDescColor
  • DestAlpha
  • OneMinusDestAlpha
blendOp Blending function operator used when drawing. Note that the operator names are case sensitive. The default operator is:
blendOp = Add
The available operators are:
  • Add
  • Subtract
  • ReverseSubtract
depthFunc Depth test comparison function. The default function is:
depthFunc = Less
The available functions are:
  • Never
  • Always
  • Equal
  • NotEqual
  • Less
  • Greater
  • LessOrEqual
  • GreaterOrEqual
depthWrite Determines whether writing to the depth buffer is enabled or disabled. The default is True, i.e., depth values are written to the depth buffer.

Scripting

The Doomsday Script variables automatically available in timeline scripts are:

Variable Description
ID Text string with the asset identifier (e.g., "model.thing.possessed").
ASSET Asset metadata, providing read access to the animation and render blocks, and any other data specified inside the asset block. Changing these values has no effect on already initialized objects, because the definitions are only checked when the model is first being prepared for an object.
THING Map object being animated (when the asset is a model.thing.*).
PLAYER Player whose weapon is being animated (when the asset is model.weapon.*).
material Material to use when drawing, unless a rendering pass specifies another one.
(shader variable) Shader variables declared in the render block (e.g., uEmission above).
(pass).(variable) Shader variables declared in rendering passes (e.g., body.uEmission).
(pass).enabled Other pass variables in the pass records (e.g., body.enabled).
(pass).material Material to use when drawing a rendering pass (if declared in the definition).

Optimization tips

The general rule of thumb for the best performance is to minimize the size of textures and the number of GL resources needed by the model.

  • Each material variant reserves a separate copy of the vertex data of the model.
  • Use as few enabled rendering passes at a time as possible. You may have a large number of disabled rendering passes, though.
  • All textures used by a model are stored in a large atlas. This means there are no restrictions on texture sizes, i.e., they do not have to be powers of two (e.g., 1024x1024). However, because the atlas needs borders and other padding around the textures stored in it, it is recommended that the model's textures are a bit smaller than power-of-two sizes. For instance, four 1000x1000 textures will easily fit on an atlas of 2048x2048; however, only one 1024x1024 texture would fit.