Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

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.

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.

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.

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.

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.

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.