Render scale

the 3D scene at half resolution, the UI at native resolution.

▶ Run in browser

Tags: 3d render-scale quality post-processing

WorldEnvironment.render_scale sizes the whole HDR chain (colour, depth, SSAO, bloom) at ceil(window * render_scale); the tonemap pass bilinearly upscales to the window (enable TAA to upgrade the upscale to temporal upsampling instead, see taau.py). This trades sharpness for fill-rate: the scene here renders at 0.5 (a quarter of the pixels) while the 2D overlay text stays crisp because screen-space 2D always draws at native resolution after the upscale. The thin pillars and the glowing sphere make the softening easy to see when switching scales at runtime.

Usage: uv run python examples/features/3d/render_scale.py

Controls: 1 / 2 / 3 / 4 - render_scale 0.25 / 0.5 / 0.75 / 1.0 Escape - quit

Source

  1"""Render scale: the 3D scene at half resolution, the UI at native resolution.
  2
  3``WorldEnvironment.render_scale`` sizes the whole HDR chain (colour, depth,
  4SSAO, bloom) at ``ceil(window * render_scale)``; the tonemap pass bilinearly
  5upscales to the window (enable TAA to upgrade the upscale to temporal
  6upsampling instead, see taau.py). This trades sharpness for fill-rate: the
  7scene here renders at 0.5 (a quarter of the pixels) while the 2D overlay text
  8stays crisp because screen-space 2D always draws at native resolution after
  9the upscale. The thin pillars and the glowing sphere make the softening easy
 10to see when switching scales at runtime.
 11
 12# /// simvx
 13# tags = ["3d", "render-scale", "quality", "post-processing"]
 14# screenshot_frame = 30
 15# ///
 16
 17Usage:
 18    uv run python examples/features/3d/render_scale.py
 19
 20Controls:
 21    1 / 2 / 3 / 4 - render_scale 0.25 / 0.5 / 0.75 / 1.0
 22    Escape        - quit
 23"""
 24
 25from simvx.core import (
 26    Camera3D,
 27    DirectionalLight3D,
 28    Input,
 29    InputMap,
 30    Key,
 31    Material,
 32    Mesh,
 33    MeshInstance3D,
 34    Node,
 35    Text2D,
 36    WorldEnvironment,
 37)
 38from simvx.graphics import App
 39
 40WIDTH, HEIGHT = 1280, 720
 41
 42_SCALES = {"scale_025": 0.25, "scale_050": 0.5, "scale_075": 0.75, "scale_100": 1.0}
 43
 44
 45class RenderScaleScene(Node):
 46    def on_ready(self):
 47        InputMap.add_action("quit", [Key.ESCAPE])
 48        InputMap.add_action("scale_025", [Key.KEY_1])
 49        InputMap.add_action("scale_050", [Key.KEY_2])
 50        InputMap.add_action("scale_075", [Key.KEY_3])
 51        InputMap.add_action("scale_100", [Key.KEY_4])
 52
 53        # Half-resolution HDR chain; bloom shows the chain itself is scaled.
 54        self.env = self.add_child(WorldEnvironment(render_scale=0.5, bloom_enabled=True, bloom_threshold=1.2))
 55
 56        self.add_child(Camera3D(position=(0, 3.2, 9.0), look_at=(0, 1.2, 0), up=(0, 1, 0)))
 57        sun = self.add_child(DirectionalLight3D(intensity=2.2))
 58        sun.direction = (-0.5, -1.0, -0.4)
 59
 60        # Ground slab.
 61        self.add_child(
 62            MeshInstance3D(
 63                mesh=Mesh.cube(1.0),
 64                material=Material(colour=(0.55, 0.55, 0.6, 1.0), roughness=0.9),
 65                position=(0, -0.25, 0),
 66                scale=(16.0, 0.5, 16.0),
 67            )
 68        )
 69        # A fence of thin pillars: high-frequency verticals that visibly soften
 70        # below scale 1.0 (the classic render-scale tell).
 71        for i in range(-5, 6):
 72            self.add_child(
 73                MeshInstance3D(
 74                    mesh=Mesh.cube(0.18),
 75                    material=Material(colour=(0.85, 0.35, 0.2, 1.0), roughness=0.5),
 76                    position=(i * 1.1, 1.4, -3.0),
 77                    scale=(1.0, 15.0, 1.0),
 78                )
 79            )
 80        # Emissive sphere: bloom extraction runs at the internal resolution.
 81        self.add_child(
 82            MeshInstance3D(
 83                mesh=Mesh.sphere(0.8),
 84                material=Material(colour=(0.3, 0.7, 1.0, 1.0), emissive_colour=(0.3, 0.6, 1.0, 4.0)),
 85                position=(0, 1.3, 0.5),
 86            )
 87        )
 88        self.add_child(
 89            MeshInstance3D(
 90                mesh=Mesh.cube(1.1),
 91                material=Material(colour=(0.35, 0.75, 0.4, 1.0), roughness=0.35),
 92                position=(2.8, 0.55, 1.6),
 93            )
 94        )
 95
 96        # Native-resolution UI: stays crisp at any render_scale.
 97        self.label = self.add_child(
 98            Text2D(text="render_scale = 0.5  (keys 1-4)", position=(20, 20), font_scale=1.8)
 99        )
100
101    def on_update(self, dt):
102        if Input.is_action_pressed("quit"):
103            self.app.quit()
104            return
105        for action, scale in _SCALES.items():
106            if Input.is_action_just_pressed(action) and self.env.render_scale != scale:
107                self.env.render_scale = scale
108                self.label.text = f"render_scale = {scale}  (keys 1-4)"
109
110
111def main():
112    App(width=WIDTH, height=HEIGHT, title="SimVX - Render Scale").run(RenderScaleScene())
113
114
115if __name__ == "__main__":
116    main()