Keel ships two independent renderers — a 2D sprite batcher and a 3D PBR-lite mesh renderer — both built on ModernGL. You call a singleDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/VKSFY/keel/llms.txt
Use this file to discover all available pages before exploring further.
setup_* function; the renderer wires its own Phase.RENDER system and returns a setup object with the registries you need to load assets.
Both renderers can be active simultaneously. The 3D renderer detects the presence of SpriteBatch2D as a world resource and skips its own framebuffer clear when the 2D renderer has already cleared it, so 2D sprites composit as an overlay on top of 3D geometry.
2D Renderer
setup_renderer_2d
TextureAtlas, SpriteBatch2D, and sprite shader, inserts them as world resources, and registers a Phase.RENDER system that queries (Transform2D, Sprite) and draws every sprite with one instanced draw call per texture ID. Idempotent — the second call returns the cached setup object.
Requires: app.ctx (a moderngl.Context) — provided automatically by keel.App.
Renderer2DSetup
Returned by setup_renderer_2d. All fields are already wired as world resources — you rarely need to hold onto this object beyond initial asset loading.
The
TextureAtlas that owns all GPU textures. Call atlas.load(path) to upload an image and receive a stable integer texture ID. Supports up to 16 textures.Compiled GLSL program cache. Internal; not normally needed by user code.
The instanced sprite renderer. The
Phase.RENDER system calls sprite_batch.render(...) automatically. Use batch.last_draw_calls for profiling.The registered render system function. Stored for reference; not normally called directly.
TextureAtlas
TextureAtlas is an integer-keyed store of up to 16 moderngl.Texture objects. It is the bridge between file paths and the Sprite.texture_id field.
TextureAtlas is limited to 16 slots — the minimum the OpenGL specification guarantees for fragment shader sampler arrays. Plan your art as sprite sheets or atlases to stay within this limit.Camera2D
Camera2D is an ECS component. Spawn exactly one entity with it; the 2D renderer reads the first Camera2D found each frame and builds the orthographic view matrix from its fields. If no Camera2D exists, a default centered matrix is used (world origin at bottom-left, pixel-to-world ratio 1:1).
World-space X coordinate the camera is centered on. The default matrix centers on
(viewport_width / 2, viewport_height / 2).World-space Y coordinate the camera is centered on.
Orthographic zoom level.
2.0 halves the visible world area (zooms in). 0.5 doubles it (zooms out).Camera rotation in radians. The view matrix applies the inverse rotation so the world appears to rotate opposite to the camera.
setup_tilemap
int32 NumPy array where 0 = empty and N maps to atlas texture ID N − 1. The world is partitioned into 16×16-tile chunks; each chunk bakes one VAO at load time, so the per-frame cost is one draw call per non-empty chunk.
Requires setup_renderer_2d to have been called first.
2D
int32 array of shape (rows, cols). 0 = empty tile. Values 1..N map to TextureAtlas slot N − 1.Width of each tile in world pixels.
Height of each tile in world pixels.
setup_tilemap again with new tile_data re-bakes the chunks without registering a second render system.
3D Renderer
setup_renderer_3d
MeshRegistry, MaterialRegistry, and PBR-lite shader, inserts them as world resources, and registers a Phase.RENDER system that queries (Transform3D, MeshRenderer), frustum-culls entities, resolves parent-chain model matrices, uploads per-frame lighting uniforms, and draws every surviving mesh. Idempotent.
Renderer3DSetup
Upload
Mesh objects and receive stable integer IDs used by MeshRenderer.mesh_id. Call mesh_registry.add(mesh) → returns the mesh_id integer.Store
Material objects and receive stable integer IDs used by MeshRenderer.material_id. ID 0 is always the default mid-gray material. Call material_registry.add(material) → returns the material_id integer.The
Renderer3D instance that owns the per-frame render loop. Exposes last_draw_calls, last_culled, and last_point_lights for profiling.Compiled 3D GLSL program cache. Internal.
The registered
Phase.RENDER system function.Camera3D
Camera3D is an ECS component for perspective projection. Spawn exactly one entity carrying it; the 3D renderer reads the first one found each frame and builds a perspective projection matrix plus an Euler-angle view matrix.
World-space X position of the camera.
World-space Y position (up axis).
World-space Z position. Positive Z is behind the default look direction.
Rotation around X (look up/down) in radians. Applied second in YXZ order.
Rotation around Y (look left/right) in radians. Applied first in YXZ order.
Rotation around Z in radians. Applied third in YXZ order.
Vertical field of view in radians. Default ≈ 60°. Must be in
(0, π).Near clip plane distance. Must be
> 0.Far clip plane distance. Must be
> near.Mesh Primitives
Thekeel.renderer3d module ships three procedural mesh generators. All return a Mesh dataclass with vertices (shape (N, 8) float32 — position.xyz + normal.xyz + uv.xy) and indices (uint32 triangle list).
make_cube()
make_sphere(subdivisions)
subdivisions scales the horizontal and vertical segment counts: 1 → coarse (8 × 6 segments), 4 → smoother (20 × 10 segments).
Positive integer. Increases
h_segments = 4 * sub + 4 and v_segments = 2 * sub + 2. Higher values produce smoother geometry at the cost of more vertices.make_plane(width, depth)
normal = +Y. Four vertices, six indices. Use as a floor or ground.
Total width along X (half-extent =
width / 2).Total depth along Z (half-extent =
depth / 2).OBJLoader
v, vn, vt, and f directives. Triangulates n-gon faces via fan decomposition. Generates flat normals automatically when the OBJ file omits vn lines. Ignores mtllib, usemtl, g, o, s, and comment lines.
Material
Material is a plain Python dataclass carrying PBR-lite shading parameters. No texture maps in v0.1 — only scalar albedo, roughness, metallic, and emissive values.
Red component of the diffuse/specular base color. Range
[0.0, 1.0].Green component of the base color.
Blue component of the base color.
PBR roughness.
0.0 = mirror-smooth, 1.0 = fully diffuse.PBR metallic factor.
0.0 = dielectric (non-metal), 1.0 = conductor.Red self-emission added to the final color. Values above
1.0 produce HDR-like glow.Green self-emission.
Blue self-emission.
Lighting
Lighting data is provided through ECS components and world resources, not through global state. The 3D renderer collects them once per frame.DirectionalLight
Sun-style infinite light. The renderer reads the first DirectionalLight component found in the world; extras are ignored. If none exists, a default DirectionalLight() is used.
X component of the direction the light is travelling (not the source position). Normalized by the shader.
Y component.
Z component.
Red channel of the light color.
Green channel.
Blue channel.
Multiplier applied to the light color before shading.
PointLight
Positional light whose location comes from a co-located Transform3D on the same entity. Up to 8 point lights (MAX_POINT_LIGHTS = 8) are uploaded per frame, sorted nearest-camera-first. Excess lights are silently dropped.
Red channel of the light color.
Green channel.
Blue channel.
Multiplier on the light color.
Effective falloff radius in world units. Beyond this distance the contribution fades to zero.
AmbientLight
AmbientLight is a world resource, not an ECS component. Insert it once; the renderer reads it each frame. If absent, a default (0.1, 0.1, 0.1) ambient term is used.
Red ambient contribution added to every fragment.
Green ambient contribution.
Blue ambient contribution.
