====== 3D model assets ======
This page is about 3D model assets that can be rendered in the game world using the [[modding:GL2 model renderer]]. The article applies to version [[version:2.2]].
Doomsday 1.x supports only [[ded:model|MD2/DMD models]].
===== 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 [[ded:thing]]s 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 [[ded: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).
===== 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 [[modding: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:
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. In [[version:2.1]], animation sequences can also be started from [[scripting_with_stateanimator|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 {}
}
}
==== 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. |
| ''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:
* 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 [[:script|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 [[:script|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 [[:script|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 [[modding: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 [[script:Core.Animation]] object that determines the rotation angle (degrees), and ''gunRotation.speed'' is another [[script: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 [[script:Core.Animation]] objects.
render {
shader = "model.skeletal.generic"
variable uEmission { value = 0 }
}
==== Shaders available in net.dengine.client.renderer ====
See: [[https://github.com/skyjake/Doomsday-Engine/blob/master/doomsday/apps/client/net.dengine.client.pack/renderer.pack/shaders/model.dei|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 (''blendFunc
The available factors are:\\ - Zero\\ - One\\ - SrcColor\\ - OneMinusSrcColor\\ - SrcAlpha\\ - OneMinusSrcAlpha\\ - DestColor\\ - OneMinusDestColor\\ - 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 state of 3D model assets can be manipulated via Doomsday Script.
* [[Scripting with StateAnimator]]
===== 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.