Rendering

How the 3D scene is built and rendered.

Rendering Backend

Galaxy Eggbert uses the Urho3D scene graph for all rendering. The underlying graphics API depends on the engine backend and platform:

PlatformBackendGraphics API
Linux (U3D)EasyGL or SDL RendererOpenGL 3.x
Windows (U3D)SDL RendererOpenGL or D3D9/11
Web (Nova3D)SDL RendererWebGL

Galaxy Eggbert does not call any graphics API directly — all rendering is through Urho3D node components.

Scene Structure

Scene (Urho3D::Scene)
├─ Octree component         ← spatial indexing
├─ DebugRenderer component  ← F1 debug overlay
├─ "Zone" node
│    └─ Zone component      ← ambient light + fog per world
├─ "Sun" node
│    └─ Light (Directional) ← primary warm light
├─ "Fill" node
│    └─ Light (Directional) ← blue fill light
├─ "SkyDome" node (scale=500)
│    └─ StaticModel (Sphere.mdl) ← background sky sphere
├─ "Terrain" node
│    └─ … "Block" children  ← one child node per non-air voxel
│         └─ StaticModel (Box.mdl) + Material
├─ "Blupi" node
│    └─ BillboardSet        ← player sprite
└─ … object nodes (from Decor)
     └─ BillboardSet        ← enemy/collectible sprites

Terrain Rendering

Each non-air block in the world is spawned as a child of the Terrain node in GalaxyEggbertGame::SpawnTerrainNodes(). A Urho3D Box.mdl (unit cube) plus a material is created per block.

// Source: GalaxyEggbertGame.cpp, SpawnTerrainNodes()
auto* node = terrainRoot_->CreateChild("Block");
node->SetPosition(Vector3(wx - kWCX, wy, wz - kWCZ));
auto* sm = node->CreateComponent<StaticModel>();
sm->SetModel(boxModel);
sm->SetMaterial(GetTileMaterial(b.type()));

The world is centered at (kWCX, 0, kWCZ) = (50, 0, 50) in world coordinates, so Blupi starts near the origin of 3D space.

Tile Materials

Each block type maps to a region in Content/icons/object-m.png (1301×1431 px, 20 columns, 64×64 px tiles). The block type ID is the icon index into this sheet. Materials are cached in tileMatCache_ (unordered_map by block type) to avoid creating duplicate materials for the same block type.

// UV computation (BlockTypes::tileUV)
const int col = icon % 20;             // kSheetCols = 1301/64 = 20
const int row = icon / 20;
uScale = 64.0f / 1301.0f;
vScale = 64.0f / 1431.0f;
uOff   = col * uScale;
vOff   = row * vScale;

// Applied as material shader parameters:
mat->SetShaderParameter("UOffset", Vector4(uS, 0, 0, uOff));
mat->SetShaderParameter("VOffset", Vector4(0, vS, 0, vOff));

Sprite Rendering (Blupi)

Blupi is rendered as a BillboardSet with a single billboard. The billboard uses FC_ROTATE_XYZ (always faces camera from all angles). UV coordinates are set from the blupi.png sheet (600×2040 px, 60×60 tiles, 10 cols × 34 rows) based on the current animation action and frame tick.

Billboard* bb = sprite_->GetBillboard(0);
bb->size_ = Vector2(kHalfH * 2.0f, kHalfH * 2.0f);  // 1.4 × 1.4 units
bb->uv_   = Rect(u0, v0, u0 + uW, v0 + vH);
sprite_->Commit();

Object Sprite Rendering (Decor)

Each game object (enemy, collectible, etc.) is wrapped in an ObjectNode, which creates a BillboardSet UV-mapped from element.png (600×1740 px, 60×60 px tiles, 10 cols × 29 rows). The icon index is updated every frame by Decor::GetIcon().

Sky Dome

A sphere node (scale=500) uses the DiffSkydome.xml technique (renders at the far plane, no depth write, no fog). The texture is loaded from Content/backgrounds/decorNNN.png, where NNN is the region= value from the current level's .txt header.

// UpdateSkyDome(region)
char path[64];
snprintf(path, sizeof(path), "backgrounds/decor%03d.png", region);
auto* tex = cache->GetResource<Texture2D>(path);
// … apply to sphere StaticModel

Lighting

LightColorBrightnessDirection
Sun (directional)Warm white (1.0, 0.94, 0.80)2.0(−0.6, −1.0, −0.4)
Fill (directional)Blue-violet (0.35, 0.45, 0.85)0.55(0.65, −0.4, 0.5)

Neither light casts shadows (SetCastShadows(false)).

Per-World Sky Palette

WorldThemeAmbientFog
1GrasslandWarm green (0.28, 0.38, 0.22)Bright green (0.55, 0.72, 0.42)
2ForestDark green (0.18, 0.28, 0.16)Near-black green (0.12, 0.20, 0.10)
3Ice CavesPale blue (0.30, 0.35, 0.48)Light blue (0.62, 0.72, 0.88)
4Lava FieldsDeep red (0.38, 0.16, 0.08)Dim red (0.22, 0.08, 0.04)
5Space StationNear-black (0.05, 0.06, 0.18)Near-black (0.02, 0.03, 0.10)

2D UI / HUD Rendering

The HUD uses Urho3D's 2D UI layer: Text and BorderImage elements. Life icons are BorderImage sprites UV-mapped from blupi.png (icon 48 = col 8, row 4). Key icons use element.png (icon 215 = col 5, row 21). The gauge sprite is from jauge.png.

Debug Rendering

Pressing F1 toggles the debug geometry overlay:

if (drawDebug_ && scene_) {
    auto* dbg = scene_->GetComponent<DebugRenderer>();
    auto* oct = scene_->GetComponent<Octree>();
    if (dbg && oct) oct->DrawDebugGeometry(true);
}

Known Rendering Limitations