Glide model lighting
Model lighting supplies encoded RGB at each polygon vertex before rasterization.
See the shading overview for flat and smooth shading, the color pipeline, and links to the related terrain, palette and framebuffer rules.
Texture transparency
The queued-model draw flags come from the ROB segment’s attribute byte for ordinary archive model submission. Bit 0 selects point filtering instead of bilinear; bit 1 enables chroma-key rejection for textured faces. See ROB segment attributes.
The software Voodoo 1 pipeline compares the filtered texture RGB with the chroma-key RGB before applying vertex-light modulation. A matching RGB is rejected. Filtering uses the encoded palette colors, including the key color; it does not interpolate a transparent alpha mask and apply an alpha cutoff.
Display initialization selects indexed palette texture mode. Each texture’s chroma key is the selected palette’s entry-zero RGB, copied without channel quantization. Palette selection uploads the complete 256-entry RGB table to texture unit zero. Palette-zero texels retain their RGB through filtering even when the material enables chroma rejection.
Palette ownership
Indexed textures use the palette table selected for the draw, including textures
with an embedded BSI CMAP. Reading that record does not select or upload a new
palette. World rendering uses the converted world COL table,
so its six-bit channel precision applies before model texture filtering as well
as terrain filtering. The book menu selects a separate interface palette and
restores the world palette when closing. Sky drawing temporarily selects its
own palette and restores the previously selected table afterward.
The world-palette adjustment path adds a separate signed offset to each input
channel, then multiplies by that channel’s fixed-point scale. Addition and
multiplication retain the low 32 bits. The signed product is shifted right by
eight bits with sign extension, then clamped to 0..63. A scale of 256 is unity
when the arithmetic does not overflow. The adjusted channels are expanded by
multiplication by four; palette index zero is forced to black. The resulting
table is installed and selected before the adjustment routine returns.
Startup initializes all three palette channel offsets to zero. World entry resets all three fixed-point scales to 256 before installing its destination palette.
World fade opacity is separate from the indexed palette table. The fade setter
negates its signed fade input, clamps the result to 0..256, and stores the
black-overlay opacity without reading the supplied palette or selecting a table.
When the initial world-view fade completes, the world loop applies the
world-palette adjustment again before clearing the overlay. Intermediate fade
opacity values therefore do not specify different palette RGB values.
Indexed solid-color model faces resolve their color index through the selected palette when the runtime model is prepared. The resolved RGB is stored in the runtime face, rather than looked up again for every draw. Direct packed-color faces follow their separate decoding path.
Actor texture-set substitution preserves this prepared solid RGB. It changes eligible texture-bank references, not solid colors. ROB draw flags select sampling and chroma rejection independently of the palette that supplied the retained solid RGB.
World entry installs and selects the converted destination palette before preparing its ROB archive. Indexed solid faces in that archive therefore retain the converted world RGB, including black palette index zero, before model lighting. Loading-progress drawing temporarily selects the interface palette and restores the renderer-selected palette before model preparation continues.
Inventory model preparation is deferred. Loading INVENTRY.ROB for an examined
item suppresses the archive loader’s automatic model preparation. After loading,
the item model named by ITEM.INI is resolved and explicitly prepared. That
preparation resolves indexed solid colors through the palette selected at that
point; the inventory model-loading helper does not select a palette itself.
The inventory screen draws before handling View. From that draw’s return through
Examine’s item lookup and clock pause to model preparation, the selected palette
is unchanged.
Placed flat drawing
Ordinary MPSF flats use point-sampled textures and palette chroma-key rejection. They use constant alpha, independent of texture alpha. An alpha input below 254 selects source-alpha and one-minus-source-alpha RGB blending; an input of 254 or greater selects source replacement. The constant alpha channel is the low byte of the rounded alpha input. Alpha-channel blend factors select source replacement in both cases. The alpha input and its default come from the FLAT attributes.
Their generated local face normal is (0, 0, -1). The camera-facing instance
matrix also transforms local-light positions into the generated quad’s local
coordinates before applying the ordinary vertex-light calculation.
Flat submission flag 0x400 instead makes the instance upright. It sets the
local up axis to world up, removes the vertical components from the other two
axes, and normalizes their horizontal projections independently. Ordinary MPSF
submission cannot select this flag: its base flag is 1, combined with the
byte-sized drawing flags from a matching FLAT attribute.
Flat submission does not test drawing flags 0x02, 0x04, 0x08 or 0x10.
A FLAT attribute that sets any of them draws the same as one that clears them.
These flats retain depth writes and the less-than depth test. Textured RGB uses the ordinary vertex-light modulation path. Their submitted face brightness is 255 when the alpha input is below 254; otherwise it comes from the current flat-brightness input. This is a face-brightness contribution, not a replacement for the subsequent lighting calculation.
Flat image dimensions
A centered ordinary flat spans the source image width and height multiplied by its texture scale and submission geometry scale. Its half extents use half of those dimensions. Its bounding radius is the quantized source-image diagonal multiplied by half the same scale. Neither geometry nor bound uses the reduced texture allocation dimensions, and flat submission has no 64-texel size cutoff.
The texture descriptor rounds allocation dimensions up to powers of two, capped
at 256. Allocation sizes of 128 and 256 use the corresponding reduction factors
from SYSTEM.INI [3dfx] text_scale. The pair lists the 256-size factor first
and the 128-size factor second. Missing settings default to 4,2. A factor of
one disables reduction.
Source dimensions and the image’s texture scale remain separate from these
allocation and sampling parameters.
Texture allocation and indexed upload
The Glide allocation descriptor supports power-of-two dimensions from 1 through
256. Its aspect ratio is limited to 8:1: when the requested pair is more
elongated, the smaller allocation dimension increases to one eighth of the
larger. For maximum allocation dimension M, both fixed sampling levels are
8 - log2(M).
With unit horizontal and vertical sampling strides, the descriptor’s two
coordinate scales are both 256 / M. Setting source dimensions smaller than
the allocation preserves those scales, including non-power-of-two source
sizes. Source-image dimensions and allocation dimensions remain distinct.
The indexed upload path with unit strides copies source texels row by row. Unused columns to the right and unused rows below are filled with palette index zero. It does not stretch or repeat the source image into that padding. A supplied source row pitch controls the input row stride; the default pitch is the source width.
For BSI loading, each source axis is rounded up to a power of two before
applying its configured 128- or 256-size reduction factor. Each axis retains
that factor as its sampling stride. Aspect-ratio padding follows reduction.
For source dimension D, sampling stride S, and maximum reduced allocation
dimension M, the coordinate scale is 256 * floor(D / S) / (M * D), stored
as a 32-bit float. Source dimensions themselves are retained without reduction.
The fixed sampling level is 8 - log2(M).
Indexed reduction samples source columns and rows starting at zero and stepping by their respective strides. It does not average neighboring texels. Remaining allocation columns and rows are still filled with palette index zero.
Texture address selection
Glide initialization sets texture unit zero to wrap in both directions. Ordinary model binding retains that address mode. Wrapping uses the allocation dimensions, including zero-filled padding, rather than the original source-image dimensions.
In the DOSBox-X Voodoo path, point filtering selects an integer texel from each
signed perspective coordinate Q by arithmetic shift right by level + 18,
then masks with the corresponding allocation dimension minus one. It does not
round to the nearest integer. Bilinear filtering instead selects the neighboring
pair and four-bit fractions using the
fixed texture address rule.
The texture’s fixed level is the allocation level described above.
Flat submission order
Ordinary placed flats prepend to the spatial list selected by the low sixteen bits of their integer X/Z placement coordinates, shifted right by nine bits. Their active list appends those cell lists in the normal runtime spatial selection order. Within each cell, this reverses file insertion order. Static models are submitted before these flats. Bound rejection happens before queue insertion; polygon clipping happens later, while drawing the queue.
The normal queue’s alpha partition does not compare depth. For ordinary flats,
alpha below 254 selects the partitioned category. When that category is present,
a tail cursor starts at the final queue entry and skips selected entries while
it is greater than zero. A forward cursor visits indices strictly below
queue_length - 1 - selected_count. Whenever the forward entry is selected,
it swaps with the tail entry, then decrements the tail and again skips selected
entries while greater than zero. The resulting order is not stable and need not
place every selected entry after every other entry. With an opaque model prefix,
partitioning the flat suffix independently gives the same flat order.
Flat framebuffer blending
Ordinary flat drawing enables four-by-four framebuffer dithering and alpha dither subtraction. In the software Voodoo 1 renderer, constant-alpha blending uses the same integer destination reconstruction and blend arithmetic as the sun, with the flat’s constant eight-bit alpha in place of filtered texture alpha. Source RGB is the encoded, lit scene color. The separately truncated source and destination terms are added before final RGB565 framebuffer quantization. Depth testing and depth writes remain enabled for these blended fragments.
Draw-flag ownership
A newly initialized render instance has zero draw flags and constant alpha 255. Animated actor group loading checks the first group’s name against the loaded ROB model lists. When that lookup succeeds, it copies the segment attribute byte into the actor’s render instance. It also replaces constant alpha when the segment alpha is between 1 and 253 inclusive. A missing archive entry leaves those initialized values intact. Later groups do not repeat this assignment. These are group-loading assignments, not per-frame filtering defaults.
Ordinary static-model submission refreshes the instance’s draw flags and constant alpha from the selected archive model’s prepared metadata before queueing it. The archive loader copies the segment attribute byte into that metadata and maps segment alpha 0, 254 and 255 to constant alpha 255. Other segment alpha values are retained. Static-model name lookup searches the loaded ROB model lists and reports an error when no entry exists; it does not use the standalone animated-model file loader as a fallback.
Special flat lighting bypass
Flat submission flag 0x80 selects a constant-color drawing path. The three
inputs otherwise used for face brightness, constant alpha and geometry size
instead supply red, green and blue. They are truncated to integers and packed
as 0x00RRGGBB. The queued instance uses draw flags 0x0c, constant alpha
255 and face brightness 255. Submission flags 0x100 and 0x200 still add
their corresponding depth flags.
This path clears the retained face-vertex RGB source and cursor, the selected local-light count and the transformed directional-light vector before polygon preparation. It does not shade the flat through the ordinary retained-color, directional or local-light calculation. Its texture-coordinate extents use twice the source dimensions times the texture coordinate scales, independently of the three color inputs.
Actor-flat drawing starts with submission flags 1 and selects special mode from
bit 0x80 of the matching texture attribute in the map’s FLAT table.
A missing attribute leaves the ordinary path selected. The selected image frame
is passed separately and does not add submission flags.
Actor-flat drawing adds submission flag 0x200 to this special path and supplies
red, green and blue values of 200. The queued polygon draw selects constant RGB
without multiplying it by sampled texture RGB. It still binds the image and
passes its texture coordinates, but the color combiner uses the supplied RGB
rather than texture or interpolated vertex RGB. The ordinary texture-times-vertex
color mode is restored after the queued draw completes.
The special queued draw selects texture alpha rather than the instance’s
constant alpha. RGB blending uses source alpha and one minus source alpha;
the alpha-channel blend factors select source replacement. Binding the image
with draw flags 0x0c disables chroma-key rejection, including when the image
and frame already match the texture-binding cache. Draw flag 0x02 enables
chroma rejection and supplies the image’s chroma-key RGB instead. Texture-alpha
selection is independent of the constant-RGB color selection.
The indexed image path prepares and binds eight-bit palette textures. In the software Voodoo 1 renderer, palette lookup supplies alpha 255 for every index, including index zero. The indexed byte selects a palette entry; it does not supply an alpha value. Bilinear filtering of these palette samples preserves their constant alpha. This palette format is separate from the alpha-palette format available on Voodoo 2.
The three color inputs do not request a geometry scale or constant alpha.
The special path keeps the ordinary flat’s vertex positions at the caller’s geometry scale. Horizontal and vertical flip flags still reverse its local axes; the three constant-color inputs do not resize or reposition its vertices.
Ambient and directional contribution
Models share the ambient RGB, directional RGB and normalized world direction specified in terrain-lighting.md. The model transform maps the world direction into model coordinates using the transpose of its linear matrix. Shading consumes the supplied model normal without normalizing it again.
Each face adds its unsigned brightness byte to all three ambient channels.
For ambient A, face brightness B, directional strength S, transformed
direction D and supplied normal N, the contribution in each channel is
A + B + max(0, -dot(D, N)) * S.
Local lights add to this contribution before each channel is clamped to
0..255. For a solid-color face, the clamped channels are then multiplied
by the corresponding palette RGB channel divided by 256. Textured faces
use the resulting RGB to modulate the texture. RGB interpolates in screen
space, while texture coordinates use perspective correction. There is no
physical material response, scene exposure or filmic tone mapping.
The runtime face’s brightness is the byte following its vertex count. Model
preparation shifts the stored face byte left by two bits and retains the low
eight bits. In v4.0/v5.0 model files the stored byte is the field named tex_hi
in the model format, so the runtime contribution is
(tex_hi * 4) & 255. Generated runtime faces supply their brightness directly
and do not pass through this file-model conversion. The packed runtime material
marks solid colors separately from texture references.
A model instance may retain precomputed per-face-vertex RGB. That RGB replaces the ambient, face-brightness and directional contribution, including its solid color tint. Local lights still add to it, followed by channel clamping; the solid tint is not applied a second time.
Polygon clipping
The clipped model preparation path computes each source corner’s RGB before clipping. It transforms positions into projection-scaled camera coordinates and uses the same six-plane volume and plane order as terrain polygon preparation.
At a crossing edge, RGB and texture coordinates use the same linear edge parameter as camera position. Generated components are stored as 32-bit floats. RGB is not multiplied by depth or reciprocal depth during clipping. Projection and screen-space RGB interpolation occur after this preparation. Consequently, clipping depth-weighted RGB and dividing it by the new depth is not equivalent to the model preparation rule.
Polygon projection
The ordinary model submission path projects the surviving clipped polygon as an ordered triangle fan. The first corner remains the anchor; each subsequent pair of corners forms the next triangle.
For each corner, reciprocal camera depth is stored as a 32-bit float before
projection. Screen X and Y use that stored reciprocal, the viewport center
and the corresponding viewport scale. Screen X has a 32-bit float store before
adding the bias 786432; Y retains the wider intermediate until the biased
store. Both biased screen coordinates are stored as 32-bit floats. This is the
same 1/16-pixel quantization used by
terrain projection.
The submitted corner retains its clipped RGB. Its vertex alpha is 255.
Each texture coordinate is multiplied by the stored reciprocal depth, and
that product is stored as a 32-bit float for Glide. Reciprocal depth is also
submitted separately for perspective correction.
Screen-space color setup
Ordinary model fans use the same Glide triangle color-gradient setup as terrain. The submitted RGB values become separate starting-color and screen-gradient registers after screen-Y ordering. Each fan triangle receives its own setup.
The installed Glide driver’s iterated-color and texture-times-iterated-color modes enable subpixel adjustment whenever the color-combine state is set. This does not depend on the prior subpixel-enable bit. Window initialization clears the RGB/depth clamp state; color-combine changes preserve that state. The software Voodoo renderer’s signed color conversion and subpixel adjustment therefore apply to the default model path as well as terrain.
Ordinary textured model drawing selects texture-times-iterated-color. Its encoded texture-byte modulation uses the same integer texture combination as terrain. Solid-color submission uses iterated RGB without a texture product.
Local-light model selection
A local light carries a world position, signed RGB strengths and a radius in
engine coordinates. Registration retains its squared radius and an attenuation
numerator (radius * radius + 3 * radius) / 256.
For model origin O, model radius R, light position L and light radius r,
the light is selected when dot(L - O, L - O) - R * R <= r * r.
The boundary is inclusive. This differs from testing whether two spheres overlap.
The model radius is the stored model bound; the selection test does not scale it
by the instance transform.
Selected light positions are transformed into model coordinates by multiplying
L - O by the transpose of the model’s linear matrix. This uses the matrix
entries directly, including any scale.
Local-light vertex contribution
For a selected light at transformed model-space position L, let
V = vertex_position - L and q = dot(V, V). Normalize V with the
quantized reciprocal square root described in
terrain lighting, retaining the
supplied model normal’s magnitude. Only a negative dot product between that
normalized vector and the supplied normal contributes light.
The distance calculation uses a separate quantized square-root table. For
unbiased exponent e of q and its upper seven fraction bits m, the index is
m + 128 * (e & 1). Table entry i contains the upper seven fraction bits of
the single-precision square root of (1 + (i % 128) / 128) * 2^(i / 128),
where division of the index by 128 is integer division. Combine those bits
with exponent floor(e / 2) + 127 to obtain distance d.
The attenuation is (radius * radius + 3 * radius) / (256 * (q + 3 * d)).
A negative attenuation contributes nothing. Multiply a nonnegative attenuation
by the negated normal dot product, then multiply each signed RGB strength by
that weight and add it to the current channel. There is no per-vertex cutoff
at the light radius. A vertex coincident with the light contributes nothing
because its normalized delta and dot product are zero.
Accumulate local lights before clamping and solid-color tinting. Retained
instance RGB receives local contributions and clamping without a second tint.
The distance-table initializer selects single-precision x87 arithmetic with
round-to-nearest; intermediate arithmetic rounds to single precision. The
attenuation denominator evaluates as (2 * d + q) + d; its numerator evaluates
as ((2 * radius + radius * radius) + radius) / 256.
Retained instance colors
Prelighting traverses faces in authored order and vertices in each face’s
order. It uses each face’s brightness and material together with the vertex
position and supplied normal, through the same model-lighting calculation.
The resulting clamped, tinted RGB channels are rounded to integers with
round-to-nearest, ties to even, then packed as 0x00RRGGBB. The retained
array contains one packed entry per face vertex, including repeated mesh
vertices shared by different faces. An existing instance buffer is reused
when prelighting is recalculated.
World prelighting
World prelighting is enabled by default. The npl command-line option disables
it. The world-entry pass requires both a nonempty static-object list and a
nonempty admitted map-light list. It registers the map lights and bakes retained
face-vertex colors for the static instances. With either list empty, this pass
does not bake those instances.
For world-entry prelighting, spatial cells are allocated in first-occurrence
order: static placements first, then admitted map lights. The cell key is
((z & 0xfe00) >> 2) + ((x & 0xffff) >> 9) using the low coordinate bits.
Lights prepend to their cell’s list. Collection concatenates cells in ascending
allocation order, retaining reverse insertion order within each cell. Light
registration and model selection preserve this order for accumulation.
Ordinary world drawing registers the map-light list only in editor mode. Outside that mode, the retained colors carry the baked map-light contributions; the map lights are not reapplied by that draw loop. Other registered local lights can still contribute through the retained-color shading path.
Local-light records
MPOB object kind 6 defines a runtime local light. With objects_load enabled, its
signed world ID must match the current world; its activation byte does not
gate this branch. The loader initializes the light as locked and enabled,
with zero positional offset and no intensity or radius modulation. Current
and starting intensity, radius and signed RGB come from the authored record.
These objects enter spatial-cell lists separately from MPSL map lights.
Outside editor mode, world drawing submits their enabled, positive-radius
lights from the active object list before drawing static models. Their current
signed RGB and radius contribute through the local-light calculation, including
when a static model already has retained colors. They are not part of the
world-entry map-light bake.
RgmFile::object_lights(world_id) returns these admitted authored records in
file order, before runtime script changes and active-region selection.
Script light state and cleanup
Actor-attached light records retain a lock flag, an enabled flag and an unsigned byte inactivity counter. During light maintenance, a nonzero lock forces the enabled flag on.
LightOff returns zero. If the actor has no light record, it does not allocate
one. Otherwise it clears the lock, enabled flag and inactivity counter without
changing the retained offsets, intensity, radius, colors or modulation settings.
It does not immediately release the record.
When an actor light is submitted for drawing, the engine resolves its retained offset through the actor’s current orientation and uses the resulting position for the local-light record. The light therefore follows the actor’s current pose; a map placement position is not a permanent substitute for the actor’s runtime position.
While a record is disabled, maintenance adds the low byte of the elapsed tick count to its inactivity counter with byte wrapping. It releases the record and clears the actor’s association only when the resulting unsigned counter exceeds 32. Enabled records do not accumulate this inactivity counter.
Light(radius, intensity) enables the record and replaces both the starting and
current radius and intensity. LightRadius and LightIntensity each replace
both copies of their respective value, enable the record and clear its inactivity
counter. These setters store the low sixteen bits of their arguments.
Modulation maintenance
The Glide executable maintains light records on both eligible ordinary actors and active spatial objects during its normal world update. Ordinary actor maintenance precedes that actor’s ordinary SOUP dispatch. Spatial-object maintenance follows its SOUP dispatch, before the temporary actor state is written back to the object. Actor eligibility and maintenance bypasses still apply. Ordinary actor light state advances even when the drawing pass does not submit it as a local light.
LightFlicker(amplitude, period) configures intensity modulation;
LightSize(amplitude, period) configures radius modulation. Each stores its
arguments as sixteen-bit words and resets its phase timer to zero. Neither
setter enables a disabled light record. A zero amplitude disables that channel’s
phase advancement and random draws.
Enabled records process intensity before radius. Each active channel adds the shared whole nominal-tick increment to its sixteen-bit phase with wrapping. The resulting phase and period are compared as signed sixteen-bit values. When the phase is at least the period, it resets to zero and the channel takes one random sample. Excess ticks do not carry forward or produce additional samples in that maintenance pass. These are the same nominal ticks used by animation, rather than render frames or milliseconds.
For positive amplitude A, the new value is the channel’s starting value plus
bounded_random(A) - floor(A / 2), stored with sixteen-bit wrapping. Intensity
has no lower clamp. After both channels, the current radius is interpreted as a
signed sixteen-bit value and raised to one if it is smaller. This radius clamp
also applies when neither channel takes a sample. Disabled records perform
inactivity cleanup instead of modulation.
The shared random generator advances a wrapping unsigned 32-bit state with
state = state * 1103515245 + 12345 and returns (state >> 16) & 32767.
The bounded helper consumes one such result. For positive bounds at most
16384, it masks the result with one less than the smallest power of two that
is at least the bound, with a minimum mask of one. Larger positive bounds use
the unmasked result. It then subtracts the bound repeatedly until the result
is smaller than the bound. This produces a biased distribution for many
bounds; it does not redraw rejected values. Actor order and intensity-before-
radius order therefore determine which samples each channel consumes.
Glide submission boundary
The normal Glide world draw keeps ordinary model actors and lightweight spatial objects in separate active lists. It registers light descriptors from the active spatial-object list. Owning a light record does not add an ordinary model actor to that list, and the ordinary model-actor draw traversal does not register its light record. Script-created light state on such an actor is therefore not an additional local-light emitter in this drawing pass.
Local-light registration forwards the current signed RGB strengths and radius. The retained intensity word is not an input to this registration and does not scale RGB during submission.
Runtime object-light region
The normal world loop selects spatial lists using the target camera’s integer
X/Z coordinates before camera smoothing. Each axis starts at target - 4608.
If that value is negative, the start is zero; otherwise, when target + 4608
exceeds 65535, the start is 56319. Shift each start right by nine bits to obtain
the first cell coordinate.
Selection visits an 18-by-18 cell square in row order, omitting its four corners. The grid has 128 cells per row. Within a cell, object lights follow reverse insertion order. Lists are reused while the first selected cell remains the same. The final rendered camera position can differ from the target used for this selection.
Map-light records
RgmFile::prelight_lights(world_id) returns the world-entry accumulation order
when static loading is enabled. RgmFile::local_lights(world_id) returns
admitted authored records in file order with signed
RGB, radius and positions in engine coordinates. RGM inspection metadata retains
these values and the activation and world fields. Placement extraction does not
convert them to standard punctual lights: their signed contribution and custom
falloff require engine shading.