Jump to content

Ninja Lights

From SA Docs
Revision as of 03:10, 1 March 2026 by PkR (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

"Ninja Lights" refers to the lighting used by the Ninja library of the Katana SDK.

Ninja Lights in Sonic Adventure

Note: Ninja Lights are not implemented in the Lantern Engine mod, so the information below is irrelevant to the PC version.

Sonic Adventure (Dreamcast) uses two lighting systems: the palette-based LANTERN system and the Ninja Lights. The game's model rendering functions have "Lantern" and "Ninja" variants, and depending on the function used the game can either draw the model with palette lighting or use Ninja Lights.

The SL file contains data for setting up the Ninja lights. This information consists of: light color (three floats for R/G/B), diffuse multiplier, specular power and ambient multiplier. Both diffuse and specular components are the same color. The “ignore specular” material flag disables the specular component per mesh.

Types of Ninja Lights Setup

There are three types of lights used for non-palettized models in the game:

Lighting Setup Description Used by
Two Ninja Lights Non-palettized lighting that uses two Ninja Lights. These lights use NJS_LIGHT structures described in the Katana SDK. One is set up using the parameters in the SL file (let’s call it the ENV Light), and the other is set up in code, usually in level binaries (let’s call it the DIR light). The ENV light has one configurable color that is shared for diffuse and specular, the DIR light can have different diffuse and specular color (though the game doesn’t seem to use that). It can also have a different light direction. Common switch, Hint monitor, OEggKanban (glowing Eggman logo in Final Egg, tube parts only), Chaos puddle in some cutscenes, Aoki Switch (top)
Simple Ninja Light Non-palettized lighting that uses one Ninja Light using the parameters from the SL file except the color, which is always white. Rock bits in Red Mountain, non-Jewel Chao, Master Emerald pieces, Froggy in Gamma’s Emerald Coast
Easy Light Non-palettized lighting that sets up the Easy Light (not NJS_LIGHT) using the parameters from the SL file. The Easy light is described in the Katana SDK. It doesn’t support specular lighting. Snowboard, OSandSwitch (Sand Hill switch), shadow blob, Chambr (chambers with opening doors in Sky Deck), debug objects, Aoki Switch (bottom), E101’s missiles, Hedgehog Hammer targets

Rendering Functions and their SADX Counterparts

In SA1, most models were rendered with palette lighting using the game's custom function, but some models were rendered by calling Ninja drawing functions directly, which made them use the Ninja lights system. There are three light setups depending on the specific variation of the Ninja draw function:

Light setup Functions in SADX (PC, 2004) Comment
Regular njDrawModel (0077EF70) Allows to use multiple NJS_LIGHTs of various types (directional, point, spot). Specular is supported.
Simple ___SAnjSimpleDrawModel (00408520), __SAnjSimpleDrawObject (0040A130) Only the first registered NJS_LIGHT can be used. It is set up as a directional light, and intensity and color settings cannot be adjusted. Specular is supported.
Easy njEasyDrawObject (0040A100), njEasyDrawModel (004084F0), njEasyDrawMotion (00406FA0), njEasyDrawShapeMotion (00406FE0) Uses one "easy" light (not NJS_LIGHT). Specular is not supported. From the SDK: The light vector uses the screen space coordinate system. E.g. if set up like njSetEasyLight( 0.0f, 0.0f, 1.0f );, the light source will always be in front of the screen. To set the light source in 3D space, they must be converted with the current matrix:
light.x = LIGHT3D_X;
light.y = LIGHT3D_Y;
light.z = LIGHT3D_Z;
njCalcVector( NULL, &light, &lwork );
njSetEasyLight( lwork.x, lwork.y, lworkz );
After this, the Easy light source will be at LIGHT3D_X, LIGHT3D_Y, LIGHT3D_Z.

The SADX functions are a rough match since SADX changed draw functions for a lot of objects, and all that is left is some wrapper functions that call a more generic draw function:

  1. The Regular functions are difficult to tell from the paletted functions, so the above table is an approximation.
  2. The Easy functions are mostly still there.
  3. The Simple functions only have two stubs which aren't called by anything other than the Chao Name Machine, which doesn't match SA1.

Lighting Data

The data sources for the lights are as follows:

Light Type Direction Color Diffuse, specular, ambient intensity
Regular - light 1 Directional (0, -1, 0) rotated using RY and RZ values from the SL file From the SL file From the SL file
Regular - light 2 Directional njSetLightDirection called in the skybox display function njSetLightColor called in the skybox display function njSetLightIntensity called in the skybox display function
Simple Directional (0, -1, 0) rotated using RY and RZ values from the SL file White (in SA1 anyway) From the SL file
Easy Directional (0, -1, 0) rotated using RY and RZ values from the SL file From the SL file From the SL file, no specular

In addition to these lights, there can be custom lights set up by objects' code, which will affect all objects rendered with Regular functions. These custom lights can be directional, spot or point, with or without specular. The custom lights are created with the function le_CreateLight (0040A510 in SADX) and deleted with the function le_DeleteLight (0040A550 in SADX).

In the Dreamcast version, these functions use pointers to actual NJS_LIGHTs. In SADX, the Ninja Lights system appears to have been replaced by a system using the structure LE_LIGHT_ENV (what is also known as "Stage lights"). Instead of specific pointers, there is now an array of four "current stage lights" called le_env at 03ABD9F8. It also has a corresponding array of four Ninja light flags le_env_typ at 03ABDBB8. So instead of NJS_LIGHT pointers, the light create/delete functions manipulate data in these arrays in SADX.

Interestingly enough, the data for the light "Regular- light 2" is completely the same in SADX. It's still set up by the skybox display functions which call njSetLightColor etc. with the same arguments as the Dreamcast version, so basically we don't need the data from this post. However, now that these functions are redirected to modify the first entry in the le_env array, this data is overwritten every time a stage loads. That is happening because the function le_set_env (0040A95) copies over all light data from the SADX stage lights array (00900E88), which happens every time a light type is set and whenever a new level loads.

The following behavior of Ninja Lights is different from palette lighting:

NJD_FLAG_IGNORE_SPECULAR disables specular lighting completely (per material).

NJD_FLAG_USE_FLAT enables flat shading (used by Master Emerald pieces).

Custom Lights

There's a leftover function in SADX that creates custom lights associated with specific objects: asSetLight at 0040ADC0. It uses a taskwk pointer, an NJS_LIGHT pointer and a pointer to a structure called LightInfo:

struct LightInfo 
{
int lsrc; // Ninja light flags 
float spc; 
float dif; 
float amb; 
NJS_ARGB argb; 
NJS_POINT3 pos; // Light position (for spot and point lights) 
NJS_POINT3 vec; // Light direction 
float nrang; // Distance at which light source attenuation begins 
float frang; // Distance to cut the effect of the light source (range) 
int iang; // The angle at which the specular or spotlight is at maximum intensity 
int oang; // The angle at which the specular or spotlight effect ceases 
};

The following Ninja light flags can be used. The ones in bold are used in SA1, and the ones crossed out are unlikely to have been used in SA1.

Flags Flag name Light type Ambient Diffuse Specular
0x01 NJD_AMBIENT Ambient × ×
0x02 NJD_DIR_LIGHT Directional × ×
0x03 NJD_LAMBERT_DIR Directional ×
0x04 NJD_POINT_LIGHT Point × ×
0x05 NJD_LAMBERT_POINT Point ×
0x08 NJD_SPOT_LIGHT Spot × ×
0x10 NJD_SPEC_DIR Directional ×
0x13 NJD_PHONG_DIR Directional
0x20 NJD_SPEC_POINT Point ×
0x25 NJD_PHONG_POINT Point
0x40 NJD_USER_LIGHT Call a custom callback function
0x80 NJD_SIMPLE_LIGHT Simplified lighting calculations (NOT the "Simple light" described earlier)
0xC0 NJD_BLOCK_LIGHT Simplified lighting calculations

The following custom lights have some leftover code and data in SADX. These also exist in SA1 but they aren't visible normally because the models that were supposed to be lit by them are rendered with palette lighting.

  1. Ice Cap background light: NJD_PHONG_POINT, LightInfo structure at 00C67FA0. Uses angles.
  2. "Chaos underlight": NJD_POINT_LIGHT, NJS_LIGHT structure at 03D0D5B8. The light is initialized using the function ChaosUnderLightInit (007AD250) that has a "radius" argument which goes unused in SADX but in SA1 it should be the light range. In Chaos' main functions, the light's matrix is transformed to match Chaos' position.
  3. Some light in Knuckles' story cutscene: NJD_SIMPLE_LIGHT | NJD_LAMBERT_DIR, function at 006868F0.
  4. Lights in Gamma's briefing cutscene: NJD_SIMPLE_LIGHT | NJD_LAMBERT_DIR, function at 006F4570. The light is a leftover, but this effect was adapted to palette lighting, and palette-related functions are called here too.

Specular Lighting

The Specular calculation formula for the Ninja Lights is currently unknown for Basic models. For Chunk models (which SA1 DC doesn't use), the formula was reversed by Exant for Simple and Easy lights: