Baked lighting and the level shader
STUNTBOOST’s levels are lit in Blender with Cycles and the result is stored in textures. The game draws a level mostly from its
baked colour and adds only dynamic shadows on top. The facts below are read from the game’s code (STUNTBOOST.dll, the BytingLib
assemblies) and its compiled effects (Render.fx, build 25832744). Observed · STUNTBOOST
Bake groups and atlases
Section titled “Bake groups and atlases”A bake group is a top-level collection of the Blender scene (Level and Room) exported with its own baked texture. Its
meshes are merged into bake_N_object meshes over one atlas: Content/Models/Resources/<Level>.export/Bake<N>_Denoised.xnb, a
QOI image (mostly 4096 × 4096). Observed · STUNTBOOST A second image per bake
group, Bake<N>_DIRECT_Denoised, is stored as the bake material’s KHR_materials_specular colour texture. It is not a
specular colour: it is the bake’s direct-light mask, the light that reached each texel straight from the sun without
bounces, and the game binds it as DiffuseTex. Observed · STUNTBOOST
How densely an object is baked is the exporter’s UV scale (per object, collection and vertex group, multiplying); the bake’s resolution, samples, denoise and margin are scene settings, with separate Fast Bake and Release Bake sets. The custom Blender build adds a Cycles Color Spill light-path setting that boosts first-bounce indirect light for a more nostalgic look. Sourced · StuntboostTools documentation
The baked branch of the level shader
Section titled “The baked branch of the level shader”A level that has a node with the =SetBakedLighting() call is drawn by Scene3D.UseShading(baked: true): DynamicObject is
false, DiffuseLightingEnabled is false, and only dynamic shadows are added. That branch of Render.fx is unlit:
color = atlas × (1 + k · ShadowColor) × lerp(AmbientLightInShadow, 1, 1 − k)
with k = clamp((DIRECT.r − 0.07) × 1.075, 0, 1) × dynamic shadow. DIRECT is the direct-light mask; it only gates the dynamic
shadows (the board’s) to texels that were lit directly. There is no N·L term, no specular, and the roughness and metallic factors
are not used. LevelShadingDefault sets ShadowColor to 0 and AmbientLightInShadow to 0.5. Observed · STUNTBOOST
The baked branch also turns off the shadow depth bias (EnableShadowDepthBias false) and sets ShadowsEnabled to whether
dynamic shadows are drawn that frame. A room’s models are built as baked models (DrawableModel(..., bakedModel: true)), and a
model draws only in the pass whose baked flag matches its own, so a room’s materials are drawn with the baked branch whether
or not their names begin bake_mat. Observed · STUNTBOOST · the managed code of build 26237614
In the compiled pixel shader, ps_uniforms_vec4[39] is DynamicObject, [50] is DiffuseLightingEnabled, [36] and [37] are
the metallic and roughness factors, and ps_s0 is AlbedoTex, ps_s5 is DiffuseTex; the constant 1.075269 (= 1 / 0.93)
and −0.07 sit among its defined constants. Proven · STUNTBOOST
Other lighting in the shader
Section titled “Other lighting in the shader”Render.fx also has cascaded real-time sun shadows (ShadowMapSplitCount, ViewProjectionLight[4], ShadowDepthBiases[4],
sampled from LightDepthTex), ambient light, distance fog (FogStart, FogLength, FogColor), a volumetric-light technique
with blue noise, SSAO and depth of field in separate effects. Observed · STUNTBOOST
The output is color ^ (1 / Gamma) with Gamma defaulting to 1.0. The shader applies no colour grading of its own. Observed · STUNTBOOST
Colours of the level tables are read as bytes before the shader sees them. A colour written in parentheses has each float
multiplied by 255 and truncated to a byte, a missing component being 255, and Level.ReloadShading turns the bytes back into
float vectors, so a table’s colour reaches the shader quantised to 1/255; a hex colour is read by ColorExtension.FromHex.
Observed · STUNTBOOST · the managed code of builds 25832744 and 26237614
A model’s vertex colour is on whenever its glTF has an attribute whose name begins _COLOR: BytingLib maps every such attribute
to the colour usage, _COLOR_SHIFT (the outline shift) included, which sets
VertexColorEnabled though the attribute is not a glTF COLOR_0. A glTF material without metallic or roughness factors keeps
the shader’s constructor values of 0.5, not glTF’s default of 1. Observed · STUNTBOOST · the managed code of build 26237614
The world matrices reach the shader in two more forms: WorldInverseTranspose is the inverse transpose of the world matrix’s
3x3 part, and WorldRotate the rotation of its decomposed quaternion (the identity when the matrix is not finite). A render with
UseDepthOutputTechnique set, which the scene sets from its depth render target, uses the RenderWithDepthOutput technique (or
its instanced form) instead of Render. Observed · STUNTBOOST · the managed code of build 26237614
The sun’s direction is defined in Blender by the world’s sky texture (and also drives the skybox); a level can place regions with
another sun (CustomSunRegion). Sourced · StuntboostTools documentation
Shown alone, Bake<N>_Denoised looks much darker than the game draws the level, although the baked branch above can only
darken the atlas (with the default ShadowColor its factor is at most 1). Where the difference comes from (a post effect such as Post.fx or Post2D.fx, the
atlas encoding, or the display path) is not established.
One reading of the code is that the post passes are only SSAO, the edge outlines, depth of field and the speed smear, so any colour grading is baked into the atlas. Inferred · STUNTBOOST That would not by itself make the atlas look darker than the game, and the tools’ documentation lists overriding post-processing and colour grading among its undocumented features (custom maps), which suggests the game grades at run time somewhere. Inferred · STUNTBOOST