Repository navigation
Why interpolation is perspective correct
Color, normals, texture coordinates and world positions are interpolated through 1 / w, not straight across the screen. Depth is not. Both choices are what a graphics API does, for the same reasons.
Perspective squeezes the far half of a triangle into fewer pixels. Barycentric weights measured on the screen therefore do not match the weights the surface itself sees. An attribute interpolated with screen weights drifts from what the geometry says, and a texture slides and warps as the camera moves.
Each corner's value is weighted by its inv_w, and the sum is divided by the interpolated inv_w:
attribute = sum(w_i * a_i * inv_w_i) / sum(w_i * inv_w_i)
The error this removes grows with how much perspective one triangle spans. It is invisible on a subdivided sphere and obvious on a floor drawn as two triangles.
Depth in normalized device space is already divided by w. The projection makes it linear in screen space on purpose, so a depth buffer can interpolate it linearly. Correcting it again would be wrong.
An orthographic projection leaves w at one. Every inv_w is one, and the correction divides by one. Nothing branches on the camera type.

examples/uv.mojo renders two frames of one floor plane with its texture coordinates written out as color. The first is correct. The second forces every inv_w to one. Every covered pixel differs, by up to 142 levels of 255.
Both frames come from one Renderer.prepare call. That is why prepare is public. The affine frame is the same prepared triangles with the perspective thrown away. Nothing but the correction can account for the difference.
Tutorials
How-to
Reference
- Scene graph
- Rotations
- Cameras
- Geometry
- Geometry addons
- Lofts and clipping groups
- Sculptor
- Procedural generators
- Meshes and assets
- Lines
- Points and sprites
- Helpers
- Scene objects
- Animated objects
- Raycasting
- Curves and paths
- Animation
- Skinning
- Materials
- Node materials
- TSL functions
- Lights
- Lighting addons
- Fog
- Textures
- Renderer
- Renderer hooks and material flags
- Rasterization
- GPU backend
- Render target
- Post-processing
- GPU computation
- Compute nodes
- Progressive light map
- Gaussian splats
- Voxel global illumination
- SVG renderer
- Image files
- Image decode queue
- Model files
- More model files
- Exporters
- Scene JSON
- Windowing and controls
- Math
- Norm consumers
- Math addons
- Units
- Coverage tool
- Commands
- Examples
- Benchmarks
- Extensions
- Shared physics
- Physics query snapshots
- Continuous collision
- Tick force restoration
- Water
- Animals
- Buildings
- CARLA
- Femur
- Tibia
- Fibula
- Patella
- Knee
- Muscles
- Vessels
- Lymph
- Nerves
- Integument
- Leg
- Foot
- Pelvis
- Torso
- Arm
- Hand
- Head
- Genome
- Game humanoid
- Humanoid fidelity
- Audio-aligned game faces
- Converted assets
- Mesh quality
- Segment inertia
Explanation
- Software rasterizer
- Scene graph as an array
- Linear color
- Normals are not colors
- Perspective correction
- Fixed-point coverage
- Per-fragment shading
- Mipmaps
- Sorted transparency
- Types and checks
- Render hooks as a trait
- Shared CPU and GPU code
- Bands and threads
- PNG structure
- Extensions beside the port
- CUDA teardown hang
- Mojo compiler hang