stagecraft 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +207 -0
- data/Rakefile +8 -0
- data/docs/api.md +85 -0
- data/docs/tutorial.md +344 -0
- data/examples/01_spinning_cube.rb +13 -0
- data/examples/02_primitives.rb +21 -0
- data/examples/03_pbr_materials.rb +20 -0
- data/examples/04_lights.rb +22 -0
- data/examples/05_directional_shadow.rb +23 -0
- data/examples/06_transparency.rb +19 -0
- data/examples/07_texture.rb +22 -0
- data/examples/08_shader_material.rb +30 -0
- data/examples/09_gltf_loader.rb +24 -0
- data/examples/10_animation.rb +21 -0
- data/examples/11_scene_graph.rb +20 -0
- data/examples/12_orbit_controls.rb +13 -0
- data/examples/13_orthographic_camera.rb +18 -0
- data/examples/14_raycaster.rb +14 -0
- data/examples/15_offscreen.rb +22 -0
- data/examples/16_msaa.rb +13 -0
- data/examples/17_physics_binding.rb +22 -0
- data/examples/18_custom_geometry.rb +22 -0
- data/examples/19_imgui_stats.rb +24 -0
- data/examples/20_window_adapters.rb +15 -0
- data/examples/README.md +11 -0
- data/examples/support/demo.rb +49 -0
- data/lib/stagecraft/animation/action.rb +79 -0
- data/lib/stagecraft/animation/clip.rb +15 -0
- data/lib/stagecraft/animation/mixer.rb +97 -0
- data/lib/stagecraft/animation/track.rb +122 -0
- data/lib/stagecraft/app.rb +61 -0
- data/lib/stagecraft/cameras/camera.rb +15 -0
- data/lib/stagecraft/cameras/orthographic_camera.rb +60 -0
- data/lib/stagecraft/cameras/perspective_camera.rb +69 -0
- data/lib/stagecraft/color.rb +56 -0
- data/lib/stagecraft/controls/orbit_controls.rb +119 -0
- data/lib/stagecraft/core/attribute.rb +91 -0
- data/lib/stagecraft/core/bounding.rb +131 -0
- data/lib/stagecraft/core/geometry.rb +155 -0
- data/lib/stagecraft/core/mesh.rb +26 -0
- data/lib/stagecraft/core/node.rb +163 -0
- data/lib/stagecraft/core/observed_value.rb +182 -0
- data/lib/stagecraft/core/raycaster.rb +62 -0
- data/lib/stagecraft/core/scene.rb +11 -0
- data/lib/stagecraft/core/skin.rb +23 -0
- data/lib/stagecraft/geometries.rb +200 -0
- data/lib/stagecraft/lights/ambient_light.rb +10 -0
- data/lib/stagecraft/lights/directional_light.rb +31 -0
- data/lib/stagecraft/lights/light.rb +30 -0
- data/lib/stagecraft/lights/point_light.rb +23 -0
- data/lib/stagecraft/lights/spot_light.rb +36 -0
- data/lib/stagecraft/loaders/gltf_loader.rb +387 -0
- data/lib/stagecraft/materials/material.rb +58 -0
- data/lib/stagecraft/materials/pbr_material.rb +75 -0
- data/lib/stagecraft/materials/shader_material.rb +21 -0
- data/lib/stagecraft/materials/unlit_material.rb +26 -0
- data/lib/stagecraft/physics_binding.rb +63 -0
- data/lib/stagecraft/renderer/features.rb +58 -0
- data/lib/stagecraft/renderer/frame.rb +45 -0
- data/lib/stagecraft/renderer/frame_resources.rb +467 -0
- data/lib/stagecraft/renderer/gpu_context.rb +120 -0
- data/lib/stagecraft/renderer/pipeline_cache.rb +59 -0
- data/lib/stagecraft/renderer/pipeline_factory.rb +470 -0
- data/lib/stagecraft/renderer/render_list.rb +80 -0
- data/lib/stagecraft/renderer/renderer.rb +336 -0
- data/lib/stagecraft/renderer/resource_cache.rb +176 -0
- data/lib/stagecraft/renderer/shaders/common.wgsl +48 -0
- data/lib/stagecraft/renderer/shaders/pbr.wgsl +251 -0
- data/lib/stagecraft/renderer/shaders/shadow.wgsl +37 -0
- data/lib/stagecraft/renderer/shaders/skinning.wgsl +6 -0
- data/lib/stagecraft/renderer/shaders/tonemap.wgsl +37 -0
- data/lib/stagecraft/renderer/shaders/unlit.wgsl +89 -0
- data/lib/stagecraft/renderer/shaders.rb +64 -0
- data/lib/stagecraft/renderer/stats.rb +41 -0
- data/lib/stagecraft/renderer/uniform_packer.rb +91 -0
- data/lib/stagecraft/renderer/wgpu_compatibility.rb +41 -0
- data/lib/stagecraft/textures/cube_texture.rb +16 -0
- data/lib/stagecraft/textures/sampler_state.rb +58 -0
- data/lib/stagecraft/textures/texture.rb +71 -0
- data/lib/stagecraft/version.rb +5 -0
- data/lib/stagecraft/window/adapter.rb +38 -0
- data/lib/stagecraft/window/glfw.rb +67 -0
- data/lib/stagecraft/window/sdl3.rb +57 -0
- data/lib/stagecraft.rb +60 -0
- metadata +187 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6d1a90ae88df85e52c63bab31d3f5b3979806af0b88357fd19ef068db91ab8c1
|
|
4
|
+
data.tar.gz: 96c88c48a551fd3ad8247d2015af3aa134ba5f445bac148f60b0d8332c37f770
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8d695c4ed18c43a8b12e83d01c18f028e4b0e28daecabe0419ad531631f99d4a6a9ebe14e8946543214e53c64b1cf4fa66b739d27fe1d8913d3a0400b718e276
|
|
7
|
+
data.tar.gz: ac497bca14f3a5628111135289aa5824263864a79ebc60a9eaef8de55a70283f6a664a66f45a3cb3a381b63681a816241dcb5dafa443e4127c335d825344496f
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yudai Takada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Stagecraft
|
|
2
|
+
|
|
3
|
+
Stagecraft is a three.js-like scene graph and WebGPU renderer for Ruby. It
|
|
4
|
+
combines `wgpu`, `larb`, `rgltf`, and `texel` behind a compact
|
|
5
|
+
Scene / Mesh / Material / Camera / Renderer API.
|
|
6
|
+
|
|
7
|
+
The renderer uses WebGPU zero-to-one depth, PBR metallic-roughness materials,
|
|
8
|
+
HDR rendering with ACES tone mapping, frustum culling, transparent and opaque
|
|
9
|
+
queues, MSAA, directional shadow maps, skinning, and glTF animation.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Ruby 3.2 or newer
|
|
14
|
+
- A WebGPU backend supported by the `wgpu` gem
|
|
15
|
+
- SDL3 for the default window adapter
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Add Stagecraft to your bundle:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
bundle add stagecraft
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or install it directly:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
gem install stagecraft
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## First scene
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
require "stagecraft"
|
|
35
|
+
|
|
36
|
+
app = Stagecraft::App.new(title: "Hello Stagecraft", width: 1280, height: 720)
|
|
37
|
+
|
|
38
|
+
scene = Stagecraft::Scene.new
|
|
39
|
+
scene.background = Stagecraft::Color.new(0.05, 0.06, 0.08)
|
|
40
|
+
scene.add(Stagecraft::Lights::Ambient.new(intensity: 0.25))
|
|
41
|
+
|
|
42
|
+
sun = Stagecraft::Lights::Directional.new(intensity: 3.0, cast_shadow: true)
|
|
43
|
+
sun.position.set(4, 8, 4)
|
|
44
|
+
sun.look_at(Larb::Vec3.new)
|
|
45
|
+
scene.add(sun)
|
|
46
|
+
|
|
47
|
+
cube = Stagecraft::Mesh.new(
|
|
48
|
+
Stagecraft::Geometries.box(1, 1, 1),
|
|
49
|
+
Stagecraft::Materials::PBR.new(
|
|
50
|
+
base_color: "#e91e63",
|
|
51
|
+
roughness: 0.4,
|
|
52
|
+
metallic: 0.1
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
cube.cast_shadow = true
|
|
56
|
+
scene.add(cube)
|
|
57
|
+
|
|
58
|
+
camera = Stagecraft::Cameras::Perspective.new(
|
|
59
|
+
fov: 60,
|
|
60
|
+
aspect: app.aspect,
|
|
61
|
+
near: 0.1,
|
|
62
|
+
far: 100
|
|
63
|
+
)
|
|
64
|
+
camera.position.set(0, 2, 5)
|
|
65
|
+
controls = Stagecraft::Controls::Orbit.new(camera, app.window)
|
|
66
|
+
|
|
67
|
+
app.run do |dt|
|
|
68
|
+
controls.update(dt)
|
|
69
|
+
cube.rotation.rotate_y!(dt)
|
|
70
|
+
app.renderer.render(scene, camera)
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`App#run` clamps delta time to 100 ms, polls the window at vsync cadence, and
|
|
75
|
+
disposes its renderer and window when the loop ends.
|
|
76
|
+
|
|
77
|
+
## glTF and animation
|
|
78
|
+
|
|
79
|
+
`Loaders::GLTF` accepts a `.gltf` / `.glb` path or an existing
|
|
80
|
+
`Rgltf::Document`. It converts scenes, cameras, punctual lights, materials,
|
|
81
|
+
textures, skins, and animation channels:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
model = Stagecraft::Loaders::GLTF.load("assets/robot.glb")
|
|
85
|
+
scene.add(model.scene)
|
|
86
|
+
|
|
87
|
+
mixer = Stagecraft::Animation::Mixer.new(model.scene)
|
|
88
|
+
mixer.play(model.animations.first, loop: :repeat, fade_in: 0.25)
|
|
89
|
+
|
|
90
|
+
app.run do |dt|
|
|
91
|
+
mixer.update(dt)
|
|
92
|
+
app.renderer.render(scene, camera)
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
glTF `LINEAR`, `STEP`, and `CUBICSPLINE` tracks are supported. Multiple actions
|
|
97
|
+
blend by weight; rotations use normalized quaternion blending.
|
|
98
|
+
|
|
99
|
+
## Materials and textures
|
|
100
|
+
|
|
101
|
+
Stagecraft includes PBR, Unlit, and custom WGSL materials. Base color and
|
|
102
|
+
emissive textures are uploaded as sRGB; metallic-roughness, normal, and
|
|
103
|
+
occlusion textures stay linear. KHR texture transforms are carried into the
|
|
104
|
+
material uniforms.
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
texture = Stagecraft::Texture.load(
|
|
108
|
+
"assets/albedo.png",
|
|
109
|
+
channels: 4,
|
|
110
|
+
color_space: :srgb
|
|
111
|
+
)
|
|
112
|
+
material = Stagecraft::Materials::PBR.new(
|
|
113
|
+
base_color_map: texture,
|
|
114
|
+
metallic: 0.0,
|
|
115
|
+
roughness: 0.7
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`Materials::Shader` accepts a WGSL string or any object responding to
|
|
120
|
+
`#to_wgsl`. Stagecraft injects frame and object bindings and packs the uniform
|
|
121
|
+
hash according to WGSL alignment:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
material = Stagecraft::Materials::Shader.new(
|
|
125
|
+
wgsl: File.read("effect.wgsl"),
|
|
126
|
+
uniforms: {
|
|
127
|
+
tint: Stagecraft::Color.new("#7c4dff"),
|
|
128
|
+
speed: 2.0
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Custom shaders own bind group 1. Frame data is group 0 and object / skin data
|
|
134
|
+
is group 2. See [the tutorial](docs/tutorial.md#custom-wgsl) for the complete
|
|
135
|
+
contract.
|
|
136
|
+
|
|
137
|
+
## Headless rendering
|
|
138
|
+
|
|
139
|
+
An offscreen renderer returns a readable frame backed by a `Texel::Image`:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
renderer = Stagecraft::Renderer.offscreen(width: 512, height: 512, msaa: 4)
|
|
143
|
+
frame = renderer.render(scene, camera)
|
|
144
|
+
Texel.save(frame.read_pixels, "frame.png")
|
|
145
|
+
renderer.dispose
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Integration hooks
|
|
149
|
+
|
|
150
|
+
The renderer exposes `device`, `queue`, and `surface_format`. UI renderers such
|
|
151
|
+
as imgui-ruby can append commands after the main pass:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
renderer.on_after_render do |encoder, hdr_target_view|
|
|
155
|
+
overlay.render(encoder, hdr_target_view, stats: renderer.stats)
|
|
156
|
+
end
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`PhysicsBinding` connects any body exposing `position` and quaternion
|
|
160
|
+
`rotation`. `Materials::Shader` accepts rlsl-like objects through `#to_wgsl`.
|
|
161
|
+
|
|
162
|
+
## Resource lifetime and diagnostics
|
|
163
|
+
|
|
164
|
+
GPU resources are versioned and cached. Call `dispose` on mutable geometry and
|
|
165
|
+
textures when they are no longer needed, and always dispose standalone
|
|
166
|
+
renderers. `App` handles renderer shutdown automatically.
|
|
167
|
+
|
|
168
|
+
Set `STAGECRAFT_DEBUG=1` to report live render statistics once per second and
|
|
169
|
+
warn about cached resources remaining at renderer shutdown:
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
STAGECRAFT_DEBUG=1 bundle exec ruby examples/01_spinning_cube.rb
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Window adapters
|
|
176
|
+
|
|
177
|
+
SDL3 is the portable default. `window: :glfw` is available when the application
|
|
178
|
+
supplies the platform-specific WebGPU `surface_factory`, because glfw-ruby does
|
|
179
|
+
not expose a portable WebGPU surface handle.
|
|
180
|
+
|
|
181
|
+
An existing object implementing the window adapter protocol can be passed as
|
|
182
|
+
`window:`. It must provide `poll_events`, `should_close?`, `drawable_size`,
|
|
183
|
+
`create_surface(instance)`, and `close`.
|
|
184
|
+
|
|
185
|
+
## Learn and explore
|
|
186
|
+
|
|
187
|
+
- [Learn Stagecraft tutorial](docs/tutorial.md)
|
|
188
|
+
- [v1 public API map](docs/api.md)
|
|
189
|
+
- [20 runnable gallery examples](examples/README.md)
|
|
190
|
+
|
|
191
|
+
Use `STAGECRAFT_SMOKE=1` to close a window example after one frame.
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
bin/setup
|
|
197
|
+
bundle exec rake spec
|
|
198
|
+
bundle exec rake build
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
GPU-free unit tests cover scene traversal, bounds, culling, sorting, shader
|
|
202
|
+
composition, uniform layout, glTF conversion, and animation. Offscreen WebGPU
|
|
203
|
+
rendering can be used for platform integration tests.
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
Stagecraft is released under the [MIT License](LICENSE.txt).
|
data/Rakefile
ADDED
data/docs/api.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Stagecraft v1 public API
|
|
2
|
+
|
|
3
|
+
The following namespaces and entry points form the v1 API.
|
|
4
|
+
|
|
5
|
+
## Application and rendering
|
|
6
|
+
|
|
7
|
+
- `Stagecraft::App`
|
|
8
|
+
- `Stagecraft::Renderer`
|
|
9
|
+
- `Stagecraft::Renderer.offscreen`
|
|
10
|
+
- `Stagecraft::Renderer::Frame#read_pixels`
|
|
11
|
+
- `Stagecraft::Renderer::Stats`
|
|
12
|
+
- `Stagecraft::Window::Adapter`
|
|
13
|
+
- `Stagecraft::Window::Sdl3`
|
|
14
|
+
- `Stagecraft::Window::Glfw`
|
|
15
|
+
|
|
16
|
+
## Scene graph
|
|
17
|
+
|
|
18
|
+
- `Stagecraft::Node`
|
|
19
|
+
- `Stagecraft::MatrixNode`
|
|
20
|
+
- `Stagecraft::Scene`
|
|
21
|
+
- `Stagecraft::Mesh`
|
|
22
|
+
- `Stagecraft::Geometry`
|
|
23
|
+
- `Stagecraft::Attribute`
|
|
24
|
+
- `Stagecraft::Raycaster`
|
|
25
|
+
- `Stagecraft::Skin`
|
|
26
|
+
|
|
27
|
+
## Cameras and controls
|
|
28
|
+
|
|
29
|
+
- `Stagecraft::Cameras::Camera`
|
|
30
|
+
- `Stagecraft::Cameras::Perspective`
|
|
31
|
+
- `Stagecraft::Cameras::Orthographic`
|
|
32
|
+
- `Stagecraft::Controls::Orbit`
|
|
33
|
+
|
|
34
|
+
The longer `PerspectiveCamera`, `OrthographicCamera`, and `OrbitControls`
|
|
35
|
+
constants are compatibility aliases.
|
|
36
|
+
|
|
37
|
+
## Lights
|
|
38
|
+
|
|
39
|
+
- `Stagecraft::Lights::Ambient`
|
|
40
|
+
- `Stagecraft::Lights::Directional`
|
|
41
|
+
- `Stagecraft::Lights::Point`
|
|
42
|
+
- `Stagecraft::Lights::Spot`
|
|
43
|
+
- `Stagecraft::Lights::ShadowConfig`
|
|
44
|
+
|
|
45
|
+
## Materials and textures
|
|
46
|
+
|
|
47
|
+
- `Stagecraft::Materials::Base`
|
|
48
|
+
- `Stagecraft::Materials::Unlit`
|
|
49
|
+
- `Stagecraft::Materials::PBR`
|
|
50
|
+
- `Stagecraft::Materials::Shader`
|
|
51
|
+
- `Stagecraft::Textures::Texture`
|
|
52
|
+
- `Stagecraft::Textures::CubeTexture`
|
|
53
|
+
- `Stagecraft::Textures::SamplerState`
|
|
54
|
+
- `Stagecraft::Texture`
|
|
55
|
+
|
|
56
|
+
## Assets and animation
|
|
57
|
+
|
|
58
|
+
- `Stagecraft::Loaders::GLTF.load`
|
|
59
|
+
- `Stagecraft::Loaders::GLTF::Result`
|
|
60
|
+
- `Stagecraft::Animation::Track`
|
|
61
|
+
- `Stagecraft::Animation::Clip`
|
|
62
|
+
- `Stagecraft::Animation::Action`
|
|
63
|
+
- `Stagecraft::Animation::Mixer`
|
|
64
|
+
|
|
65
|
+
## Math-facing helpers
|
|
66
|
+
|
|
67
|
+
- `Stagecraft::Color`
|
|
68
|
+
- `Stagecraft::ObservedVec3`
|
|
69
|
+
- `Stagecraft::ObservedQuat`
|
|
70
|
+
- `Stagecraft::Bounding::Box3`
|
|
71
|
+
- `Stagecraft::Bounding::Sphere`
|
|
72
|
+
- `Stagecraft::Bounding::Plane`
|
|
73
|
+
- `Stagecraft::Bounding::Frustum`
|
|
74
|
+
|
|
75
|
+
## Primitive geometry and integration
|
|
76
|
+
|
|
77
|
+
- `Stagecraft::Geometries.box`
|
|
78
|
+
- `Stagecraft::Geometries.plane`
|
|
79
|
+
- `Stagecraft::Geometries.sphere`
|
|
80
|
+
- `Stagecraft::Geometries.cylinder`
|
|
81
|
+
- `Stagecraft::Geometries.torus`
|
|
82
|
+
- `Stagecraft::PhysicsBinding`
|
|
83
|
+
|
|
84
|
+
Nested renderer implementation types are public for inspection but are not part
|
|
85
|
+
of the compatibility contract.
|
data/docs/tutorial.md
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# Learn Stagecraft
|
|
2
|
+
|
|
3
|
+
This tutorial builds the mental model behind Stagecraft from the scene graph up
|
|
4
|
+
to the WebGPU frame. Every section corresponds to a runnable file in
|
|
5
|
+
[`examples/`](../examples/README.md).
|
|
6
|
+
|
|
7
|
+
## 1. The frame
|
|
8
|
+
|
|
9
|
+
A Stagecraft application has four moving parts:
|
|
10
|
+
|
|
11
|
+
1. A `Scene` owns a hierarchy of nodes.
|
|
12
|
+
2. A `Camera` turns world positions into WebGPU clip coordinates.
|
|
13
|
+
3. A `Renderer` collects visible meshes and encodes render passes.
|
|
14
|
+
4. `App` owns the window and supplies a clamped delta time.
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
app.run do |dt|
|
|
18
|
+
update_simulation(dt)
|
|
19
|
+
app.renderer.render(scene, camera)
|
|
20
|
+
end
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The render call performs world-matrix resolution, frustum culling, queue
|
|
24
|
+
sorting, uniform upload, shadow rendering, HDR rendering, and ACES tone mapping.
|
|
25
|
+
The surface is presented only after all callbacks have encoded their work.
|
|
26
|
+
|
|
27
|
+
## 2. Coordinates and transforms
|
|
28
|
+
|
|
29
|
+
Stagecraft uses `larb` values throughout. A node exposes observable position,
|
|
30
|
+
rotation, and scale values:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
node.position.set(1, 2, 3)
|
|
34
|
+
node.rotation.rotate_y!(Math::PI / 4)
|
|
35
|
+
node.scale.set(2, 2, 2)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The local transform is composed as translation × rotation × scale. A child's
|
|
39
|
+
world transform is its parent world transform multiplied by its local
|
|
40
|
+
transform. Version stamps make this lazy: unchanged branches do not recompute
|
|
41
|
+
their matrices.
|
|
42
|
+
|
|
43
|
+
Use `add`, `remove`, `traverse`, and `find` to work with a hierarchy:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
solar_system.add(sun, orbit)
|
|
47
|
+
orbit.add(planet)
|
|
48
|
+
planet = solar_system.find("planet")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Adding an existing child detaches it from its previous parent. Cycles are
|
|
52
|
+
rejected.
|
|
53
|
+
|
|
54
|
+
## 3. Cameras
|
|
55
|
+
|
|
56
|
+
Perspective cameras use degrees for vertical field of view:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
camera = Stagecraft::Cameras::Perspective.new(
|
|
60
|
+
fov: 60, aspect: 16.0 / 9.0, near: 0.1, far: 1_000
|
|
61
|
+
)
|
|
62
|
+
camera.position.set(0, 2, 5)
|
|
63
|
+
camera.look_at(Larb::Vec3.new)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Orthographic cameras use explicit view extents:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
camera = Stagecraft::Cameras::Orthographic.new(
|
|
70
|
+
left: -4, right: 4, top: 2.25, bottom: -2.25,
|
|
71
|
+
near: 0.1, far: 100
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Both projections map depth to WebGPU NDC z ∈ [0, 1]. Update a perspective
|
|
76
|
+
camera's `aspect` after a drawable-size change. The gallery helper demonstrates
|
|
77
|
+
the resize callback.
|
|
78
|
+
|
|
79
|
+
`Controls::Orbit` can rotate, pan, and zoom a camera with exponential damping:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
controls = Stagecraft::Controls::Orbit.new(camera, app.window)
|
|
83
|
+
app.run do |dt|
|
|
84
|
+
controls.update(dt)
|
|
85
|
+
app.renderer.render(scene, camera)
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 4. Geometry
|
|
90
|
+
|
|
91
|
+
A geometry is a set of packed vertex attributes plus an optional index buffer:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
geometry = Stagecraft::Geometry.new
|
|
95
|
+
geometry.set_attribute(
|
|
96
|
+
:position,
|
|
97
|
+
data: [0, 1, 0, -1, -1, 0, 1, -1, 0].pack("e*"),
|
|
98
|
+
format: :float32x3,
|
|
99
|
+
count: 3
|
|
100
|
+
)
|
|
101
|
+
geometry.compute_normals!
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Supported attribute names are `position`, `normal`, `uv`, `uv1`, `tangent`,
|
|
105
|
+
`color`, `joints`, and `weights`. Supported primitive topologies are points,
|
|
106
|
+
lines, line strips, triangle lists, and triangle strips.
|
|
107
|
+
|
|
108
|
+
Changing an attribute's packed `data` increments the owning geometry version,
|
|
109
|
+
so its GPU buffers are replaced on the next render. Bounds are calculated
|
|
110
|
+
lazily from float32x3 positions.
|
|
111
|
+
|
|
112
|
+
The `Geometries` module includes box, plane, sphere, cylinder, and torus
|
|
113
|
+
builders. Their indexed triangles are counter-clockwise on the front face.
|
|
114
|
+
|
|
115
|
+
## 5. Materials
|
|
116
|
+
|
|
117
|
+
### Unlit
|
|
118
|
+
|
|
119
|
+
Unlit materials return their color without lighting:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
material = Stagecraft::Materials::Unlit.new(
|
|
123
|
+
color: "#448aff",
|
|
124
|
+
opacity: 0.8,
|
|
125
|
+
transparent: true
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Metallic-roughness PBR
|
|
130
|
+
|
|
131
|
+
PBR combines Lambert diffuse with GGX / Smith / Fresnel-Schlick specular:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
material = Stagecraft::Materials::PBR.new(
|
|
135
|
+
base_color: "#eceff1",
|
|
136
|
+
metallic: 0.75,
|
|
137
|
+
roughness: 0.2,
|
|
138
|
+
emissive: "#ff6d00",
|
|
139
|
+
emissive_strength: 0.1
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Metallic and roughness are clamped API values in the 0..1 range. PBR supports
|
|
144
|
+
base color, metallic-roughness, normal, occlusion, and emissive maps.
|
|
145
|
+
|
|
146
|
+
Opaque meshes are sorted front-to-back by pipeline and material. Transparent
|
|
147
|
+
meshes are sorted back-to-front after `render_order`. Use `depth_write: false`
|
|
148
|
+
for layered alpha-blended surfaces.
|
|
149
|
+
|
|
150
|
+
## 6. Lights and shadows
|
|
151
|
+
|
|
152
|
+
Ambient light contributes a constant scene term. Directional, point, and spot
|
|
153
|
+
lights are packed into a storage buffer every frame:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
scene.add(Stagecraft::Lights::Ambient.new(intensity: 0.2))
|
|
157
|
+
|
|
158
|
+
sun = Stagecraft::Lights::Directional.new(
|
|
159
|
+
color: "#fff5e1",
|
|
160
|
+
intensity: 4.0,
|
|
161
|
+
cast_shadow: true
|
|
162
|
+
)
|
|
163
|
+
sun.position.set(4, 8, 5)
|
|
164
|
+
sun.look_at(Larb::Vec3.new)
|
|
165
|
+
scene.add(sun)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The first shadow-casting directional light renders a depth32float map. The main
|
|
169
|
+
PBR pass samples it with 3×3 percentage-closer filtering. Set
|
|
170
|
+
`mesh.cast_shadow` on occluders and `mesh.receive_shadow` on receivers.
|
|
171
|
+
|
|
172
|
+
Point and spot lights use the glTF punctual-light attenuation curve. Their
|
|
173
|
+
`range` avoids spending light outside a useful radius.
|
|
174
|
+
|
|
175
|
+
## 7. Textures
|
|
176
|
+
|
|
177
|
+
`Texture.load` delegates decoding to texel:
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
albedo = Stagecraft::Texture.load(
|
|
181
|
+
"albedo.png",
|
|
182
|
+
channels: 4,
|
|
183
|
+
color_space: :srgb
|
|
184
|
+
)
|
|
185
|
+
material.base_color_map = albedo
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Request four channels because WebGPU has no three-channel sampled format.
|
|
189
|
+
Texel builds mip levels and Stagecraft copies them with WebGPU row alignment.
|
|
190
|
+
Color maps and emissive maps should be sRGB; data maps should be linear.
|
|
191
|
+
|
|
192
|
+
Sampler state is immutable:
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
sampler = Stagecraft::Textures::SamplerState.new(
|
|
196
|
+
wrap_u: :repeat,
|
|
197
|
+
wrap_v: :repeat,
|
|
198
|
+
mag_filter: :linear,
|
|
199
|
+
min_filter: :linear,
|
|
200
|
+
mipmap_filter: :linear
|
|
201
|
+
)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## 8. glTF assets
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
result = Stagecraft::Loaders::GLTF.load("robot.glb")
|
|
208
|
+
scene.add(result.scene)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The result contains the default `scene`, every converted `scene`, all
|
|
212
|
+
`animations`, and converted `cameras`. Mesh primitives become child Mesh
|
|
213
|
+
objects so each primitive can keep its own material and topology.
|
|
214
|
+
|
|
215
|
+
The loader passes compatible packed accessors directly into Geometry. WebGPU
|
|
216
|
+
does not support u8 indices, so those are expanded to u32. glTF line loops and
|
|
217
|
+
triangle fans are expanded to supported indexed topologies.
|
|
218
|
+
|
|
219
|
+
KHR_materials_unlit, KHR_materials_emissive_strength,
|
|
220
|
+
KHR_texture_transform, and KHR_lights_punctual are converted. Images are
|
|
221
|
+
decoded by texel.
|
|
222
|
+
|
|
223
|
+
## 9. Skinning and animation
|
|
224
|
+
|
|
225
|
+
A skin stores joint nodes and inverse-bind matrices. Each frame the renderer
|
|
226
|
+
calculates mesh-local joint matrices and uploads them to bind group 2:
|
|
227
|
+
|
|
228
|
+
```text
|
|
229
|
+
inverse_bind × joint.world_matrix × mesh.world_matrix.inverse
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The vertex shader blends four matrices using the `joints` and `weights`
|
|
233
|
+
attributes.
|
|
234
|
+
|
|
235
|
+
Animation clips group tracks. A mixer resolves track targets once and advances
|
|
236
|
+
every playing action:
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
mixer = Stagecraft::Animation::Mixer.new(result.scene)
|
|
240
|
+
walk = mixer.play(result.animations.fetch(0), loop: :repeat)
|
|
241
|
+
walk.weight = 0.8
|
|
242
|
+
|
|
243
|
+
app.run do |dt|
|
|
244
|
+
mixer.update(dt)
|
|
245
|
+
renderer.render(scene, camera)
|
|
246
|
+
end
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Use `loop: :once`, `:repeat`, or `:ping_pong`. `fade_in:` raises an action's
|
|
250
|
+
weight from zero to one. Translation and scale use component interpolation;
|
|
251
|
+
rotations use slerp; glTF cubic spline tracks use Hermite tangents.
|
|
252
|
+
|
|
253
|
+
## 10. Custom WGSL
|
|
254
|
+
|
|
255
|
+
Custom shaders define `vs_main` and `fs_main`, and include the engine common
|
|
256
|
+
bindings:
|
|
257
|
+
|
|
258
|
+
```wgsl
|
|
259
|
+
//#include "common.wgsl"
|
|
260
|
+
|
|
261
|
+
struct VertexInput {
|
|
262
|
+
@location(0) position: vec3f,
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
@vertex fn vs_main(input: VertexInput) -> @builtin(position) vec4f {
|
|
266
|
+
return frame.view_proj * object.model * vec4f(input.position, 1.0);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
@fragment fn fs_main() -> @location(0) vec4f {
|
|
270
|
+
return material.tint;
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The associated Ruby material supplies the group 1 layout:
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
Stagecraft::Materials::Shader.new(
|
|
278
|
+
wgsl: source,
|
|
279
|
+
uniforms: { tint: Stagecraft::Color.new("#ff4081") }
|
|
280
|
+
)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Numeric, color, larb vector / quaternion / matrix, numeric array, and Texture
|
|
284
|
+
values are supported. The packer inserts WGSL padding and generates
|
|
285
|
+
`MaterialUniforms`. Texture values become paired sampler and texture bindings.
|
|
286
|
+
|
|
287
|
+
The stable binding contract is:
|
|
288
|
+
|
|
289
|
+
| Group | Content |
|
|
290
|
+
|---|---|
|
|
291
|
+
| 0 | frame uniform, light storage, shadow map and sampler |
|
|
292
|
+
| 1 | material uniform, material textures and samplers |
|
|
293
|
+
| 2 | dynamic object uniform and joint storage |
|
|
294
|
+
|
|
295
|
+
## 11. Picking and physics
|
|
296
|
+
|
|
297
|
+
Create a ray from normalized device coordinates and intersect scene bounds:
|
|
298
|
+
|
|
299
|
+
```ruby
|
|
300
|
+
ray = Stagecraft::Raycaster.new
|
|
301
|
+
ray.set_from_camera(pointer_x, pointer_y, camera)
|
|
302
|
+
hits = ray.intersect(scene)
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Hits are nearest-first and expose object, distance, and world point.
|
|
306
|
+
|
|
307
|
+
`PhysicsBinding` is deliberately duck typed:
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
binding = Stagecraft::PhysicsBinding.new
|
|
311
|
+
binding.bind(mesh, rigid_body)
|
|
312
|
+
binding.sync!(interpolation_alpha)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The body only needs `position` and quaternion `rotation`.
|
|
316
|
+
|
|
317
|
+
## 12. Offscreen and UI integration
|
|
318
|
+
|
|
319
|
+
Use `Renderer.offscreen` when no surface is needed. `Frame#read_pixels` returns
|
|
320
|
+
an sRGB, four-channel texel image.
|
|
321
|
+
|
|
322
|
+
For overlays, use `on_after_render`. The callback receives the shared command
|
|
323
|
+
encoder and HDR target view before tone mapping. The renderer exposes the same
|
|
324
|
+
`device`, `queue`, and `surface_format` required by a WebGPU UI backend.
|
|
325
|
+
|
|
326
|
+
## 13. Lifetime and performance
|
|
327
|
+
|
|
328
|
+
Geometry, texture, material, and world transforms use version keys. Pipeline
|
|
329
|
+
objects use a 256-entry LRU keyed by material type, shader features, vertex
|
|
330
|
+
layout, blending, depth, culling, sample count, target format, and pass.
|
|
331
|
+
|
|
332
|
+
Object uniforms occupy 256-byte dynamic slots in a three-frame ring buffer.
|
|
333
|
+
Lights and joints use storage buffers. CPU resources must be disposed
|
|
334
|
+
explicitly when their GPU allocations are no longer needed:
|
|
335
|
+
|
|
336
|
+
```ruby
|
|
337
|
+
geometry.dispose
|
|
338
|
+
texture.dispose
|
|
339
|
+
renderer.dispose
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Set `STAGECRAFT_DEBUG=1` while developing. `renderer.stats.to_h` always exposes
|
|
343
|
+
draw calls, triangles, live buffers, live textures, live pipelines, visible
|
|
344
|
+
objects, and culled objects.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/demo"
|
|
4
|
+
|
|
5
|
+
demo = StagecraftExamples::Demo.new(title: "01 — Spinning cube")
|
|
6
|
+
demo.light!
|
|
7
|
+
cube = Stagecraft::Mesh.new(
|
|
8
|
+
Stagecraft::Geometries.box,
|
|
9
|
+
Stagecraft::Materials::PBR.new(base_color: "#e91e63", roughness: 0.35, metallic: 0.15)
|
|
10
|
+
)
|
|
11
|
+
demo.scene.add(cube)
|
|
12
|
+
demo.update { |dt| cube.rotation.rotate_y!(dt) }
|
|
13
|
+
demo.run
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "support/demo"
|
|
4
|
+
|
|
5
|
+
demo = StagecraftExamples::Demo.new(title: "02 — Primitives")
|
|
6
|
+
demo.light!
|
|
7
|
+
geometries = [
|
|
8
|
+
Stagecraft::Geometries.box,
|
|
9
|
+
Stagecraft::Geometries.sphere,
|
|
10
|
+
Stagecraft::Geometries.cylinder(0.7, 0.7, 1.5),
|
|
11
|
+
Stagecraft::Geometries.torus
|
|
12
|
+
]
|
|
13
|
+
geometries.each_with_index do |geometry, index|
|
|
14
|
+
mesh = Stagecraft::Mesh.new(
|
|
15
|
+
geometry,
|
|
16
|
+
Stagecraft::Materials::PBR.new(base_color: ["#ff5252", "#40c4ff", "#69f0ae", "#ffd740"][index])
|
|
17
|
+
)
|
|
18
|
+
mesh.position.x = (index - 1.5) * 2.0
|
|
19
|
+
demo.scene.add(mesh)
|
|
20
|
+
end
|
|
21
|
+
demo.run
|