User Guide
Quick Reference
Modding
Development
User Guide
Quick Reference
Modding
Development
This page is about 3D model assets that can be rendered in the game world using the GL2 model renderer. The article applies to version 2.2.
Doomsday 1.x supports only MD2/DMD models.
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.
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).
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. |
fov | Custom FOV angle for rendering a player weapon model. Not set by default (value is zero), which means the default weapon FOV angle is used instead. Only affects player weapon models. |
alignment.yaw | Yaw angle alignment mode. The allowed values are:
|
alignment.pitch | Pitch angle alignment mode. The allowed values are:
|
opacityFromWeapon | Model's opacity is affected by the player weapon (psprite) opacity. Defaults to True . |
fullbrightFromWeapon | Model's ambient light level is affected by the player weapon (psprite) fullbright state. Default to False . |
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. |
notifiedStates | Name of the mobj state, or an array of mobj state names, that will cause a notification callback to be made to the ''onStateChange'' method of the model asset. The names are case sensitive (and should be in uppercase). By default this is empty, which means no state change callbacks are made. For performance reasons you should only perform script callbacks when absolutely necessary — remember that each object in the map will be processed separately. This value can also be changed at runtime via scripts. |
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 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.
In 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.
de::GLShaderBank
where they are stored as compiled OpenGL shader objects. In other words, shaders and model assets exist in different namespaces.
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.
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" } }
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" } } }
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 1.15, the only way to animate models is to trigger an animation sequence when a state is entered. In 2.1, animation sequences can also be started from scripts.
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 {}
}
}
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. |
mustFinish | If True , an equal-priority sequence will not override this sequence unless this sequence has finished playing for at least one full duration. Defaults to False . |
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:
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.)
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. |
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 } }
See: models.dei in the net.dengine.client.renderer package.
Variable | Description |
---|---|
value | Initial value for the variable. Also determines the type of the variable:value = 0 → floatvalue <0, 0> → vec2value <0, 0, 0> → vec3value <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 . |
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 } } }
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: |
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: |
depthFunc | — | Depth test comparison function. The default function is: depthFunc = Less
The available functions are: |
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. |
The state of 3D model assets can be manipulated via Doomsday Script.
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.