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.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +207 -0
  4. data/Rakefile +8 -0
  5. data/docs/api.md +85 -0
  6. data/docs/tutorial.md +344 -0
  7. data/examples/01_spinning_cube.rb +13 -0
  8. data/examples/02_primitives.rb +21 -0
  9. data/examples/03_pbr_materials.rb +20 -0
  10. data/examples/04_lights.rb +22 -0
  11. data/examples/05_directional_shadow.rb +23 -0
  12. data/examples/06_transparency.rb +19 -0
  13. data/examples/07_texture.rb +22 -0
  14. data/examples/08_shader_material.rb +30 -0
  15. data/examples/09_gltf_loader.rb +24 -0
  16. data/examples/10_animation.rb +21 -0
  17. data/examples/11_scene_graph.rb +20 -0
  18. data/examples/12_orbit_controls.rb +13 -0
  19. data/examples/13_orthographic_camera.rb +18 -0
  20. data/examples/14_raycaster.rb +14 -0
  21. data/examples/15_offscreen.rb +22 -0
  22. data/examples/16_msaa.rb +13 -0
  23. data/examples/17_physics_binding.rb +22 -0
  24. data/examples/18_custom_geometry.rb +22 -0
  25. data/examples/19_imgui_stats.rb +24 -0
  26. data/examples/20_window_adapters.rb +15 -0
  27. data/examples/README.md +11 -0
  28. data/examples/support/demo.rb +49 -0
  29. data/lib/stagecraft/animation/action.rb +79 -0
  30. data/lib/stagecraft/animation/clip.rb +15 -0
  31. data/lib/stagecraft/animation/mixer.rb +97 -0
  32. data/lib/stagecraft/animation/track.rb +122 -0
  33. data/lib/stagecraft/app.rb +61 -0
  34. data/lib/stagecraft/cameras/camera.rb +15 -0
  35. data/lib/stagecraft/cameras/orthographic_camera.rb +60 -0
  36. data/lib/stagecraft/cameras/perspective_camera.rb +69 -0
  37. data/lib/stagecraft/color.rb +56 -0
  38. data/lib/stagecraft/controls/orbit_controls.rb +119 -0
  39. data/lib/stagecraft/core/attribute.rb +91 -0
  40. data/lib/stagecraft/core/bounding.rb +131 -0
  41. data/lib/stagecraft/core/geometry.rb +155 -0
  42. data/lib/stagecraft/core/mesh.rb +26 -0
  43. data/lib/stagecraft/core/node.rb +163 -0
  44. data/lib/stagecraft/core/observed_value.rb +182 -0
  45. data/lib/stagecraft/core/raycaster.rb +62 -0
  46. data/lib/stagecraft/core/scene.rb +11 -0
  47. data/lib/stagecraft/core/skin.rb +23 -0
  48. data/lib/stagecraft/geometries.rb +200 -0
  49. data/lib/stagecraft/lights/ambient_light.rb +10 -0
  50. data/lib/stagecraft/lights/directional_light.rb +31 -0
  51. data/lib/stagecraft/lights/light.rb +30 -0
  52. data/lib/stagecraft/lights/point_light.rb +23 -0
  53. data/lib/stagecraft/lights/spot_light.rb +36 -0
  54. data/lib/stagecraft/loaders/gltf_loader.rb +387 -0
  55. data/lib/stagecraft/materials/material.rb +58 -0
  56. data/lib/stagecraft/materials/pbr_material.rb +75 -0
  57. data/lib/stagecraft/materials/shader_material.rb +21 -0
  58. data/lib/stagecraft/materials/unlit_material.rb +26 -0
  59. data/lib/stagecraft/physics_binding.rb +63 -0
  60. data/lib/stagecraft/renderer/features.rb +58 -0
  61. data/lib/stagecraft/renderer/frame.rb +45 -0
  62. data/lib/stagecraft/renderer/frame_resources.rb +467 -0
  63. data/lib/stagecraft/renderer/gpu_context.rb +120 -0
  64. data/lib/stagecraft/renderer/pipeline_cache.rb +59 -0
  65. data/lib/stagecraft/renderer/pipeline_factory.rb +470 -0
  66. data/lib/stagecraft/renderer/render_list.rb +80 -0
  67. data/lib/stagecraft/renderer/renderer.rb +336 -0
  68. data/lib/stagecraft/renderer/resource_cache.rb +176 -0
  69. data/lib/stagecraft/renderer/shaders/common.wgsl +48 -0
  70. data/lib/stagecraft/renderer/shaders/pbr.wgsl +251 -0
  71. data/lib/stagecraft/renderer/shaders/shadow.wgsl +37 -0
  72. data/lib/stagecraft/renderer/shaders/skinning.wgsl +6 -0
  73. data/lib/stagecraft/renderer/shaders/tonemap.wgsl +37 -0
  74. data/lib/stagecraft/renderer/shaders/unlit.wgsl +89 -0
  75. data/lib/stagecraft/renderer/shaders.rb +64 -0
  76. data/lib/stagecraft/renderer/stats.rb +41 -0
  77. data/lib/stagecraft/renderer/uniform_packer.rb +91 -0
  78. data/lib/stagecraft/renderer/wgpu_compatibility.rb +41 -0
  79. data/lib/stagecraft/textures/cube_texture.rb +16 -0
  80. data/lib/stagecraft/textures/sampler_state.rb +58 -0
  81. data/lib/stagecraft/textures/texture.rb +71 -0
  82. data/lib/stagecraft/version.rb +5 -0
  83. data/lib/stagecraft/window/adapter.rb +38 -0
  84. data/lib/stagecraft/window/glfw.rb +67 -0
  85. data/lib/stagecraft/window/sdl3.rb +57 -0
  86. data/lib/stagecraft.rb +60 -0
  87. 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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
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