Repository navigation
Game renderer
GameRenderer draws the Village walk at 24 frames a second or more, at 1920 by 1080 pixels, on four CPU cores. It draws less than Renderer draws, and it draws it in a different way. GameQuality sets how finely each part of a frame is drawn. A slow machine can turn each part down, and a fast machine can turn each part up.
The modules are renderers/game_renderer.mojo, render/game_math.mojo, render/game_smooth.mojo, render/game_upscale.mojo and extensions/village/quality.mojo. See Renderer for the general renderer.
Give a preset to game_quality, and give the quality to PlayParameters:
from extensions.village.play import PlayParameters
from extensions.village.quality import LOW, game_quality
var parameters = PlayParameters(game_quality(LOW))On the command line, give the preset after the window's size:
mojo run -I . examples/village_walk.mojo --play 1920 1080 low| Setting | LOW |
MEDIUM |
HIGH |
ULTRA |
|---|---|---|---|---|
render_scale |
A third | A half | Two thirds | One |
plants |
FEWEST_PLANTS |
FEWER_PLANTS |
MANY_PLANTS |
ALL_PLANTS |
grass_wind_every |
4 frames | 2 frames | 1 frame | 1 frame |
sky_step |
3 pixels | 2 pixels | 2 pixels | 1 pixel |
water_step |
8 pixels | 4 pixels | 2 pixels | 1 pixel |
water_refresh_every |
4 frames | 2 frames | 1 frame | 1 frame |
animal_coarseness |
3 | 2 | 1.5 | 1 |
shadows |
CACHED_SHADOWS |
CACHED_SHADOWS |
CACHED_SHADOWS |
FULL_SHADOWS |
shadow_size |
1,024 | 2,048 | 2,048 | 4,096 |
smooth_edges |
True | True | True | True |
You can change each setting alone. GameQuality.check refuses a value out of range:
var quality = game_quality(LOW)
quality.render_scale = 0.5
quality.shadows = FULL_SHADOWS
quality.check()-
render_scaleis the share of the window's width and height that the frame is drawn at, from a quarter to one.upscale_intostretches the frame to the window, bilinear. -
plantssets how many leaves and blades grow near the camera.plantedalso draws the ground, the mountains and the canyon's wall more coarsely.ALL_PLANTSkeeps the still picture's meadow. -
grass_wind_everysets how often a cluster of grass grows again for the wind. A cluster grows again when its level changes, too. Zero keeps the grass still. -
sky_stepis the step between two samples of the sky, from 1 to 32 pixels. The samples between are mixed bilinearly. A cloud of the sky cube is smaller than 8 pixels across its edge, so a coarser step draws it as boxes. Each task finds the samples of one band of rows. A sample's direction is mixed from the directions through three corners of the window. -
water_stepis the step between two shaded samples of the pond's water, from 1 to 64 pixels. The pixels between are mixed from the four samples round them. -
water_refresh_everysets how many frames share one ocean surface and its caustics. From two frames, one frame finds the next surface and the next frame finds its caustics. -
animal_coarsenessmultiplies the cell of each animal's mesh, from 1 to 8. A coarser animal has fewer triangles. -
shadowsandshadow_sizeset how the sun's shadows are drawn. See Shadows. -
smooth_edgessmooths the stair steps along the frame's edges before the frame is stretched. See Smooth edges.
smooth_edges in render/game_smooth.mojo is the simplest form of FXAA. It runs on the small frame, after the water and before the stretch, so a step is smoothed before the stretch makes it larger.
- A pixel and its four diagonal neighbors give a range of brightness. Where the range is less than 0.0833, or less than a sixth of the brightest, the pixel keeps its color. These are FXAA's own defaults.
- The edge runs across the larger change between the neighbors. The step along it is scaled so that its shorter part is one pixel, and each part is held within 8 pixels.
- The samples at a third and at two thirds of the step are averaged. The samples at its two ends are mixed in too, unless the mix leaves the range of brightness.
Each sample is bilinear between pixel centers. The brightness is the luma of the sRGB bytes. Sixteen pixels are tested and blended at once. The rows are cut into small runs over the cores, and every count of cores gives the same frame. VillageGame.smooth_ms holds the time it took. At LOW it is about a millisecond.
mojo run -I . bench/village_game_bench.mojo low 1920 1080 240The bench walks a scripted player through the village. It prints the median, the 95th percentile and the slowest time of each stage, as JSON. These are the times on the four-core host that the project tests on:
| Preset | Frame size | Median frame | 95th percentile | Median frames a second |
|---|---|---|---|---|
LOW |
640 by 360 | 29.4 ms | 41.5 ms | 34 |
MEDIUM |
960 by 540 | 75.4 ms | 98.1 ms | 13 |
GameRenderer.timings holds the time of each stage of the last frame, in milliseconds. The stages are the shadow maps, the gathering, the vertices, the setup, the bins, the raster, the sky and the resolve.
-
The geometry is read once. Each geometry is copied into flat arrays the first time it is drawn.
GeometryStore.replacegives a geometry a new generation, and the renderer reads it again then. - The light is found at the vertices. Each vertex keeps its ambient and sky light apart from its sun light. The fog veils both at the vertex.
- The triangles are binned into tiles. Each triangle is clipped at the near plane and culled by its facing and its bounds. It is listed in each 64-pixel tile it touches.
- Each tile is drawn on its own core, sixteen pixels at a time. The raster writes only the depth and the triangle's number. A pixel that is drawn over ten times costs ten depth tests and one shading.
- Each tile is resolved. A pixel's triangle gives its light, and its shadow is looked up. An empty pixel shows the sky. The light is tone mapped and encoded as sRGB bytes.
The pond's water is composed into the frame before it is stretched, because the water needs the depth.
By default the renderer reads a material's map at each vertex. Set pixel_maps to read it at each pixel instead:
var fast = GameRenderer(640, 360)
fast.pixel_maps = TrueThe village game sets it, so the houses show their brick, plaster, wood and tiles. See Village houses.
- The levels. The first time a map is drawn, the renderer reads it into linear floats. Each level halves the one before it, by the mean of each 2 by 2 block of texels. The chain stops at one texel or 16 levels.
- The level of a triangle. The setup compares a triangle's area on the screen with its area in texels. A triangle reads the level at which one texel covers about one pixel.
-
The texel. The resolve mixes each pixel's texture coordinates from its three corners, with perspective. It mixes the four texels round them, bilinear. Each side wraps by its own mode, as
Texture.samplewraps it. A repeated side wraps round. A mirrored side folds back on every other tile. A clamped side holds its edge. - The light. The texel tints the pixel's light, the sun's and the sky's. The fog veils the tinted light after, at the pixel, so a far textured roof keeps its haze.
The texel scales all the light of the vertices. A material that shines or glows, a phong material or one with an emissive color, would have its highlight or its glow tinted too. So such a material reads its map at each vertex, where the map tints only its albedo.
The renderer keeps each map by its texture id. After a texture's texels or wraps change, or before it draws a scene of another Assets, call forget_maps. Each map is then read again when it is next drawn.
A map read at each pixel ignores the texture's offset, repeat and rotation.
The setup keeps each triangle's depth as a plane over the screen. It writes the plane from the first corner's depth and the other two corners' differences from it. A long thin triangle near the horizon has huge edge terms, which cancel. Weighted by the depths themselves, they once left a far triangle of ground nearer than a wall at some pixels. Weighted by the small differences, the far ground stays behind the wall.
| Mode | What is drawn |
|---|---|
NO_SHADOWS |
No shadow. |
STILL_SHADOWS |
One map of the still casters, drawn once. The moving casters cast no shadow. |
CACHED_SHADOWS |
The still map, drawn once, and a small map round focus of the moving casters, drawn each frame. |
FULL_SHADOWS |
Both maps, drawn each frame. The swaying trees' shadows sway too. |
set_moving names the moving nodes, such as the people and the animals. refresh_shadows draws the still map again at the next frame. A pixel takes the darker of the two maps, with a 2 by 2 filter.
The still map keeps the casters as they were when it was drawn. So in the village game, the grass casts only with FULL_SHADOWS: its clusters are shown, hidden and grown again as the player walks. A tree's shadow keeps the crown level that the tree wore in the first frame. Call refresh_shadows after the still casters change.
-
scene.meshesandscene.skinned_meshes, opaque, on a layer that the camera sees. A part whose material is notvisibleis not drawn. The sun's maps use the camera's layers too, as three.js's shadow map does, and the still map is drawn again when the camera's layers change. -
BASIC,LAMBERT,PHONG,STANDARDandPHYSICALmaterials. A standard or a physical material is lit as a Lambert one. - Vertex colors, a color, an emissive color, and a map read at each vertex or at each pixel.
- A node program, only as a
GameShadingregistered for it says. It can light a back face as its front, read a baked sun and sky, and sway in the wind. - Ambient, hemisphere and directional lights. The cascades of one sun count once.
- Exponential squared fog, or no fog.
- A color or a cube background.
The renderer refuses other materials, a second hemisphere light, a second sun, and point and spot lights.
The two renderers share the ACES curve, the fog and the sRGB encoding. The ACES curve is aces_tone in render/tonemap.mojo, and the fog is exp2_veil in core/fog.mojo. The game renderer calls each of them at sixteen lanes. Both scale each light by three.js's RECIPROCAL_PI. A flat floor in the parity tests is within two levels of a byte in each channel.
The two renderers differ in one way. GameRenderer lights each vertex and mixes the light across the triangle. Renderer lights each pixel. A curved surface and a highlight are therefore less exact in a game frame.
- The renderer draws no transparent or alpha-tested surface.
- Without
pixel_maps, a map is read at the vertices, so a map's detail between the vertices is lost. - The frame is drawn on the CPU. There is no GPU path for the game renderer yet.
Tutorials
How-to
- Install
- Run the checks
- GPU backend
- Coverage
- Measure examples
- Add a feature
- Add a scanned building
- Write documentation
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
- C ABI and hosts
- Math
- Norm consumers
- Math addons
- Units
- Coverage tool
- Commands
- Examples
- Benchmarks
- Extensions
- Shared physics
- Physics query snapshots
- Continuous collision
- Tick force restoration
- Water
- Vegetation
- Village
- Streamline
- 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
- Mixamo animations
- Audio-aligned game faces
- Converted assets
- Mesh quality
- Garments
- Outfits and armor
- Cloth and spring bones
- Attire estimates
- 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
- Scans as building models
- CUDA teardown hang
- Mojo compiler hang