Render scale¶
the 3D scene at a lower resolution, the UI at native resolution.
▶ Run in browserTags: 3d render-scale quality post-processing
WorldEnvironment.render_scale sets the resolution of the whole 3D image
chain (colour, depth, SSAO, bloom) as a fraction of the window: the scene
renders at ceil(window * render_scale) pixels a side and the tonemap pass
upscales it bilinearly to the window. It trades sharpness for fill rate: at
0.5 the GPU shades a quarter of the pixels. Above 1.0 it supersamples, and with
taa_enabled the upscale becomes temporal upsampling (see taau.py).
env = WorldEnvironment(render_scale=0.5)
env.render_scale = 0.75 # takes effect on the next frame
Screen-space 2D, such as the label and buttons here, draws at native resolution after the upscale, so it stays crisp at any scale. The fence of thin pillars is the easiest place to see the scene soften, and the glowing sphere shows that bloom runs at the reduced resolution too.
The four scales are one handler over four actions, @on_input(tuple(SCALES)),
which reads event.action to learn which was pressed. Each button is
Button(action=name): it presses that action’s key, so the key and the
button share the handler, and a ButtonGroup keeps one button checked.
Controls: 1 / 2 / 3 / 4 or buttons - render_scale 0.25 / 0.5 / 0.75 / 1.0
Source¶
1# /// simvx
2# tags = ["3d", "render-scale", "quality", "post-processing"]
3# screenshot_frame = 30
4# ///
5"""Render scale: the 3D scene at a lower resolution, the UI at native resolution.
6
7``WorldEnvironment.render_scale`` sets the resolution of the whole 3D image
8chain (colour, depth, SSAO, bloom) as a fraction of the window: the scene
9renders at ``ceil(window * render_scale)`` pixels a side and the tonemap pass
10upscales it bilinearly to the window. It trades sharpness for fill rate: at
110.5 the GPU shades a quarter of the pixels. Above 1.0 it supersamples, and with
12``taa_enabled`` the upscale becomes temporal upsampling (see ``taau.py``).
13
14 env = WorldEnvironment(render_scale=0.5)
15 env.render_scale = 0.75 # takes effect on the next frame
16
17Screen-space 2D, such as the label and buttons here, draws at native resolution
18after the upscale, so it stays crisp at any scale. The fence of thin pillars is
19the easiest place to see the scene soften, and the glowing sphere shows that
20bloom runs at the reduced resolution too.
21
22The four scales are one handler over four actions, ``@on_input(tuple(SCALES))``,
23which reads ``event.action`` to learn which was pressed. Each button is
24``Button(action=name)``: it presses that action's key, so the key and the
25button share the handler, and a ``ButtonGroup`` keeps one button checked.
26
27Controls:
28 1 / 2 / 3 / 4 or buttons - render_scale 0.25 / 0.5 / 0.75 / 1.0
29"""
30
31from simvx.core import (
32 Anchor,
33 Button,
34 ButtonGroup,
35 Camera3D,
36 CanvasLayer,
37 DirectionalLight3D,
38 HBoxContainer,
39 Key,
40 Label,
41 Material,
42 Mesh,
43 MeshInstance3D,
44 Node,
45 WorldEnvironment,
46 on_input,
47)
48from simvx.graphics import App
49
50SCALES = {"scale_25": 0.25, "scale_50": 0.5, "scale_75": 0.75, "scale_100": 1.0}
51
52
53class RenderScale(Node):
54 input_context = {
55 "scale_25": [Key.KEY_1],
56 "scale_50": [Key.KEY_2],
57 "scale_75": [Key.KEY_3],
58 "scale_100": [Key.KEY_4],
59 }
60
61 def on_ready(self):
62 self.env = self.add_child(WorldEnvironment(render_scale=0.5, bloom_enabled=True, bloom_threshold=1.2))
63 self.add_child(Camera3D(position=(0, 3.2, 9.0), look_at=(0, 1.2, 0)))
64 self.add_child(DirectionalLight3D(intensity=2.2, direction=(-0.5, -1.0, -0.4)))
65
66 ground = Material(colour=(0.55, 0.55, 0.6), roughness=0.9)
67 self.add_child(MeshInstance3D(mesh=Mesh.box((16, 0.5, 16)), material=ground, position=(0, -0.25, 0)))
68 pillar, brick = Mesh.box((0.18, 2.7, 0.18)), Material(colour=(0.85, 0.35, 0.2), roughness=0.5)
69 for i in range(-5, 6):
70 self.add_child(MeshInstance3D(mesh=pillar, material=brick, position=(i * 1.1, 1.4, -3.0)))
71 glow = Material(colour=(0.3, 0.7, 1.0), emissive_colour=(0.3, 0.6, 1.0, 4.0))
72 self.add_child(MeshInstance3D(mesh=Mesh.sphere(0.8), material=glow, position=(0, 1.3, 0.5)))
73 green = Material(colour=(0.35, 0.75, 0.4), roughness=0.35)
74 self.add_child(MeshInstance3D(mesh=Mesh.box(1.1), material=green, position=(2.8, 0.55, 1.6)))
75
76 hud = self.add_child(CanvasLayer())
77 self.status = hud.add_child(Label(anchor=Anchor.TOP_LEFT, inset=16, font_size=28))
78 bar = hud.add_child(HBoxContainer(anchor=Anchor.BOTTOM_LEFT, inset=16))
79 group = ButtonGroup()
80 self.buttons = {name: bar.add_child(Button(str(s), action=name, group=group)) for name, s in SCALES.items()}
81 self.show_scale("scale_50")
82
83 @on_input(tuple(SCALES))
84 def set_scale(self, event):
85 self.env.render_scale = SCALES[event.action]
86 self.show_scale(event.action)
87
88 def show_scale(self, name):
89 self.buttons[name].checked = True
90 self.status.text = f"render_scale = {self.env.render_scale}"
91
92
93if __name__ == "__main__":
94 App(title="Render Scale", width=1280, height=720).run(RenderScale())