agentleFS
mattstrublemattstruble/skills/game-rendering/SKILL.md

game-rendering

Skill2 starsChanged 4 months agoMITView on GitHub

You MUST consult this skill when reasoning about 3D rendering pipelines — coordinate spaces (model/world/view/clip/screen), perspective vs orthographic projection, affine transformations, triangle rasterization, barycentric coordinates, z-buffering, backface and frustum culling, UV mapping with perspective-correct interpolation, and lighting models (flat, Phong, multi-light blending). Also trigger when implementing a software renderer, debugging projection issues, or reasoning about what a GPU does internally. NOT for shader implementation in a specific engine (see godot-shader). NOT for art direction or visual legibility (see game-visuals). NOT for engine-specific renderer configuration.

The file

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Rule: Use perspective for any scene where depth perception matters.

    line 131
  2. Very specific:

    Perspective division: After the projection matrix, clip-space coordinates are in homogeneous form (x, y, z, w).

    line 159

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

3D Rendering Pipeline

Engine-agnostic reference for 3D rendering concepts: coordinate spaces, transformations, rasterization, visibility, texturing, and lighting. Synthesized from Marian Pekar's 3D Software Renderer in Odin from Scratch tutorial series (marianpekar.com).

Relationship to other skills: This skill covers the what and why of rendering math. For Odin/raylib implementation specifics, see odin-gamedev/references/raylib-rendering.md. For shader implementation in Godot, see godot-shader. For art direction and visual communication, see game-visuals. For diagnosing frame-rate and GPU bottlenecks, see game-performance.


Pipeline Overview

A 3D renderer transforms geometry through a series of coordinate spaces before producing pixels. Each space has a distinct purpose; each transition is a matrix multiplication.

  [Model Space]
       |
       | Model Matrix (T × R × S)
       v
  [World Space]
       |
       | View Matrix (camera transform)
       v
  [View Space]
       |
       | Projection Matrix (perspective or orthographic)
       v
  [Clip Space]
       |
       | Perspective Division (÷ w)
       v
  [NDC Space]   (-1 to +1 on each axis)
       |
       | Viewport Transform
       v
  [Screen Space]  (pixel coordinates)
SpaceWhat it representsKey operation
ModelGeometry relative to the object's own originArtist-authored mesh data
WorldAll objects in a shared coordinate systemModel matrix positions/orients the object
ViewWorld seen from the camera's perspective; camera at originView matrix inverts the camera transform
ClipHomogeneous coordinates ready for clippingProjection matrix encodes FOV, aspect, near/far
NDCNormalized Device Coordinates; cube [-1,1]³Divide x,y,z by w
ScreenInteger pixel coordinatesViewport scale and Y-flip

See references/transformations.md for the full matrix forms and derivations.


Mesh Data

A mesh is the input to the rendering pipeline. At minimum it contains:

FieldTypeDescription
vertices[]Vector3Positions in model space
normals[]Vector3Surface normals per vertex (unit vectors)
uvs[]Vector2Texture coordinates per vertex, range [0,1]
triangles[]TriangleIndex triples into the vertex/normal/UV arrays

A Triangle stores three indices: one for each vertex, normal, and UV. This allows vertices to be shared between triangles (reducing memory) while allowing different normals or UVs at the same position (required at hard edges and UV seams).

OBJ file format (Pekar Part IX) stores exactly this structure: v lines for vertices, vn for normals, vt for UVs, f lines for faces with vertex/uv/normal index triples. Loading an OBJ means parsing these lines and building the arrays above.


Per-Frame Rendering Loop

The full pipeline, from scene data to pixels, runs once per frame:

// Per frame:
clear(framebuffer, background_color)
clear(zbuffer, 0)          // 0 = "nothing drawn"; 1/w convention (larger = closer)

for each model in scene:
    M = model.transform.matrix()         // T × R × S

    for each triangle in model.mesh:
        // 1. Transform vertices to view space
        v0_view = V × M × triangle.v0
        v1_view = V × M × triangle.v1
        v2_view = V × M × triangle.v2

        // 2. Backface cull (in view space)
        if is_backfacing(v0_view, v1_view, v2_view): continue

        // 3. Project to clip space, then NDC
        v0_clip = P × v0_view
        v1_clip = P × v1_view
        v2_clip = P × v2_view

        // 4. Frustum cull
        if outside_frustum(v0_clip, v1_clip, v2_clip): continue

        // 5. Perspective divide → NDC
        v0_ndc = v0_clip.xyz / v0_clip.w
        ...

        // 6. NDC → screen space
        v0_screen = viewport_transform(v0_ndc)
        ...

        // 7. Rasterize: fill pixels, Z-test, interpolate UVs/normals, shade
        rasterize_triangle(v0_screen, v1_screen, v2_screen, ...)

Each step is covered in the reference files. The key insight is that culling happens as early as possible — backface culling in view space, frustum culling in clip space — to avoid rasterizing geometry that produces no pixels.


Decision Tables

Perspective vs. Orthographic Projection

CriterionPerspectiveOrthographic
Objects shrink with distanceYes — natural depth cueNo — parallel lines stay parallel
Typical use3D games, first/third-person viewsCAD, 2D-style 3D, isometric games
Depth stored asNon-linear (1/z distribution)Linear (clip.z; range [-1, +1])
UV interpolationMust be perspective-correctAffine interpolation is exact
Switch at runtimeYes — swap projection matrixYes — swap projection matrix

Rule: Use perspective for any scene where depth perception matters. Use orthographic when you want to preserve scale across distance (e.g., strategy games, blueprint views).

Flat vs. Phong Shading

CriterionFlat ShadingPhong Shading
Normal computedOnce per trianglePer pixel (interpolated from vertices)
Visual resultFaceted, hard edges between trianglesSmooth gradients across surfaces
Performance costLow — one dot product per triangleHigher — dot product per pixel
Best forLow-poly stylized art, early prototypingSmooth organic surfaces
RequiresTriangle normalPer-vertex normals in mesh data

Culling Strategy

TechniqueWhat it eliminatesCostWhen to apply
Backface cullingTriangles facing away from cameraVery low (one dot product)Always in closed meshes
Frustum cullingTriangles outside the view volumeLow (bounding box test)Always
Occlusion cullingGeometry hidden behind other geometryMedium–highComplex scenes with many overlapping objects

Note: Backface and frustum culling are nearly free and should always be enabled. Occlusion culling requires spatial data structures and is out of scope here.


Key Invariants

Handedness consistency. Pick right-handed or left-handed coordinates and stay consistent throughout the pipeline. Mixing conventions causes mirrored geometry or inverted depth. Right-handed is the mathematical convention (OpenGL, Vulkan default); left-handed is common in Direct3D. The view matrix construction and projection matrix signs depend on this choice.

Perspective division. After the projection matrix, clip-space coordinates are in homogeneous form (x, y, z, w). Dividing by w gives NDC. This division is what makes distant objects appear smaller — the projection matrix encodes distance into w.

Store 1/w for perspective-correct interpolation. Linear interpolation in screen space is only correct for attributes that are linear in screen space. Depth and UV coordinates are not linear in screen space after perspective projection — they are linear in view space (before projection). What is linear in screen space is 1/w, U/w, and V/w. Storing 1/w (the reciprocal of clip-space w) and interpolating it linearly in screen space, then dividing back, gives perspective-correct results. This is why the Z-buffer stores 1/w rather than the raw view-space depth.

Winding order determines front faces. Counter-clockwise (CCW) winding is front-facing in right-handed systems. Backface culling depends on this convention being consistent across all meshes.

Model matrix order: T × R × S. Translation, then rotation, then scale. Applied right-to-left to a vertex: scale first, then rotate, then translate. Changing this order produces different results — scale after rotation causes shearing.


Common Bugs — Symptoms → Cause

SymptomLikely CauseReference
Texture looks like it slides or distorts as the triangle rotatesAffine UV interpolation — not dividing by wreferences/texturing.md
Pixels overdraw incorrectly; wrong geometry appears in frontNo Z-buffer, or depth test direction reversedreferences/visibility.md
Back faces of mesh are visibleBackface culling disabled or winding order inconsistentreferences/visibility.md
Model disappears when camera gets closeNear-plane clipping not handled; geometry behind near plane projects incorrectlyreferences/visibility.md
Model looks inside-out or mirroredCoordinate system handedness mismatchreferences/transformations.md
Projection looks wrong (too wide, too narrow, stretched)Incorrect aspect ratio in projection matrix, or FOV in wrong units (degrees vs radians)references/transformations.md
Lighting is always maximum or always zeroNormal not normalized after interpolation, or dot product not clamped to [0,1]references/lighting.md
Flat-shaded triangles show seams at edgesExpected — flat shading is per-triangle; switch to Phong for smooth surfacesreferences/lighting.md
Z-fighting (flickering between two surfaces)Two surfaces at nearly identical depth; Z-buffer precision insufficientreferences/visibility.md
Triangle rasterization has gaps or double-drawn edgesIncorrect scanline bounds; off-by-one in FTFB splitreferences/rasterization.md

What's Not Covered Here

These topics are out of scope for this skill. They are mentioned only to orient you toward the right resource.

TopicWhere to look
Specular highlights (Blinn-Phong, Phong reflection model)godot-shader/references/lighting.md; external: learnopengl.com
Normal/bump mappinggodot-shader/references/lighting.md
Shadow mappingExternal: learnopengl.com/Advanced-Lighting/Shadows
Anti-aliasing (MSAA, FXAA, TAA)External: GPU vendor documentation
Physically-based rendering (PBR)External: learnopengl.com/PBR
GPU pipeline stages (vertex shaders, fragment shaders, rasterizer hardware)godot-shader; external: Khronos OpenGL specification
Post-processing effectsgodot-shader/references/vfx-and-postprocessing.md
Texture filtering (bilinear, trilinear, anisotropic)External: GPU documentation
Spatial data structures for occlusionExternal: game-engine-specific documentation

Worked Example: Tracing One Vertex Through the Pipeline

To make the coordinate space transitions concrete, here is a single vertex traced through each stage.

Setup: A cube vertex at model-space position (1, 1, 1). The cube is scaled by 0.5, rotated 45° around Y, and placed at world position (0, 0, -5). The camera is at the origin looking along -Z. FOV = 90°, aspect = 1.0, near = 0.1, far = 100.

// Model space
v_model = (1, 1, 1, 1)

// World space (after model matrix: scale 0.5, rotate 45° Y, translate (0,0,-5))
// Scale: (0.5, 0.5, 0.5)
// Rotate 45° Y: x' = 0.5*cos45 + 0.5*sin45 = 0.707, y' = 0.5, z' = -0.5*sin45 + 0.5*cos45 = 0
// Translate (0,0,-5): (0.707, 0.5, -5.0)
v_world = (0.707, 0.5, -5.0, 1)

// View space (camera at origin looking along -Z; view matrix is identity here)
v_view = (0.707, 0.5, -5.0, 1)

// Clip space (after perspective projection, FOV=90°, f=1/tan(45°)=1.0, aspect=1.0)
// clip.x = f/a * x = 0.707, clip.y = f * y = 0.5
// clip.z = -(far+n)/(far-n)*z - 2*far*n/(far-n) ≈ 4.805
// clip.w = -view.z = 5.0
v_clip = (0.707, 0.5, 4.805, 5.0)

// NDC (divide by w=5.0)
v_ndc = (0.141, 0.1, 0.961, 1)

// 1/w stored for depth and perspective-correct interpolation
inv_w = 1 / 5.0 = 0.2

// Screen space (800×600 viewport)
screenX = (0.141 * 0.5 + 0.5) * 800  ≈ 457   // right of center
screenY = (-0.1  * 0.5 + 0.5) * 600  = 270   // Y flipped; slightly above center

The vertex lands at approximately (457, 270) — right of center and slightly above the horizontal midline, which is correct for a vertex that is offset to the right and slightly above the camera's line of sight.

Key observations from this trace:

  • The w component after projection equals the negated view-space z (-(-5.0) = 5.0)
  • 1/w = 0.2 is stored for depth testing and perspective-correct UV interpolation
  • The Y-flip in the viewport transform accounts for the screen's top-left origin
  • The x-offset from rotation (0.707 world units) is visible in the final screen position (457 vs center 400)

References

FileContentsRead when...
references/transformations.mdCoordinate spaces, all transformation matrices, view matrix construction, projection matrices, NDC→screenYou need matrix math, projection formulas, or are debugging coordinate space issues
references/rasterization.mdFTFB triangle rasterization, line drawing, barycentric coordinatesYou're implementing a rasterizer or need to interpolate per-vertex attributes
references/visibility.mdZ-buffer, backface culling, frustum cullingYou need depth testing or culling logic
references/texturing.mdUV mapping, texture sampling, perspective-correct interpolationYou're applying textures to triangles
references/lighting.mdFlat shading, Phong shading, multi-light blending, ambient/diffuseYou're implementing a lighting model

Relationship to Other Skills

godot-shader — Implements rendering concepts as GDSL shaders in Godot 4.x. When this skill explains the math (e.g., Lambertian diffuse), godot-shader provides the concrete .gdshader implementation. Use this skill to understand the concept; use godot-shader to write the code.

game-visuals — Art direction and visual communication: contrast, palette, legibility, simulation readability. This skill handles the rendering pipeline; game-visuals handles what the rendered image should communicate to the player.

game-performance — Diagnosing and fixing frame-rate problems: GPU bottlenecks, fill-rate, bandwidth, CPU submission overhead. This skill explains how the pipeline works; game-performance explains how to measure and fix it when it's too slow.

odin-gamedev — Odin/raylib glue patterns for custom rendering: the rl.Image software-framebuffer pattern, [^]rl.Color multi-pointer interop, power-of-2 texture sampling, and FPS overlay (see odin-gamedev/references/raylib-rendering.md). This skill covers the rendering math; Pekar's tutorial series at https://marianpekar.com/blog/software-renderer-in-odin-from-scratch-part-i is the complete worked implementation in Odin.


Further Reading

  • Marian Pekar, 3D Software Renderer in Odin from Scratch (14-part series): https://marianpekar.com/blog/software-renderer-in-odin-from-scratch-part-i — a complete ground-up implementation covering every topic in this skill
  • learnopengl.com — Comprehensive OpenGL tutorial covering the same pipeline concepts with GLSL shader implementations. Counterpoint: Jonathan Blow argues that funneling beginners straight to OpenGL is a mistake — it is long obsolete, recompiles shaders behind your back, and teaches wrong intuitions before you have the fundamentals to filter them.
  • scratchapixel.com — Deep mathematical derivations of rasterization, projection, and shading from first principles

Learning Path & Philosophy (Jonathan Blow's view)

Where to start. Don't learn graphics on OpenGL (long obsolete; recompiles shaders behind your back; teaches wrong intuitions) or Vulkan (too low-level for fundamentals — you'll spend months on API ceremony before drawing a triangle). Start on a higher-level layer — raylib, SDL3, three.js — or by writing your own software renderer. The software renderer path is particularly effective: every concept in this skill becomes concrete and debuggable without a driver in the way.

What knowledge lasts. The durable knowledge is the math and algorithms — linear algebra, transforms, clipping, sampling, dithering — not any specific API. APIs change; the math doesn't. Investing in the math pays off across every renderer you'll ever touch.

Math prerequisites. The core requirement is linear algebra: matrices, eigenvectors, SVD. There is almost no calculus in game programming — a physics engine is the rare exception. "Working with vectors" is linear algebra, not vector calculus; don't let the name mislead you into thinking you need a calculus background first.

Rendering philosophy. All real-time computer graphics is hacky approximations of a correct answer that is too expensive to compute. The craft is choosing the hack that fits your game. A hyper-specific hack that no one else would use — one tuned to your art style, your scene complexity, your target hardware — can pay off far better than a general-purpose solution. Don't be embarrassed by the hack; be precise about what it trades away.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.