Scene
Description
Section titled “Description”A Scene is the top-level container for the real-world things you render:
Models,
Cameras,
Lights (directional, point, and
spot are all one type), and any custom
SceneObject. It owns one or more
Passes underneath and implements
Renderable, so you hand
the whole scene to the Renderer
in a single call.
The split mirrors glTF / USD: a scene is a flat list of nodes (geometry,
viewpoints, lights), and the renderer is the orchestrator that walks the
scene and produces a frame. The Scene keeps the user-facing API in the
real-world layer; the GPU primitives (Pass, Shader, Texture) stay
underneath and don’t leak into the call site.
Lazy + sync, like Shader and Pass
Section titled “Lazy + sync, like Shader and Pass”Scene::new() is synchronous and takes no Renderer. The first time a
SceneObject is added, the Scene
creates a default Pass to hold it. The first time the Scene is rendered, the
underlying GPU resources initialise on demand. This is the lazy-init pattern
the rest of FragmentColor follows.
An open, composable pass graph
Section titled “An open, composable pass graph”A Scene owns one ordered Vec<Pass>. Loaders, builders, and your own code
all append into the same list, and the renderer walks it in order. No pass is
privileged in render order: the default Pass that scene.add(&model) targets
is an ordinary member, slotted in at the point of your first add. After
Scene::load the whole
graph is in your hands. Read it with
list_passes or
get_pass, restructure
it with add_pass,
remove_pass, and
set_passes, and
configure each pass (load_previous, clear color, viewport, target) the same
way you would a hand-built one. That makes a loaded Scene compose onto any
other Pass in a frame, instead of clearing whatever it lands on.
Default Camera + Light at render time
Section titled “Default Camera + Light at render time”A Scene that only carries Models would normally render black: shaders need a camera projection and at least one light for the lighting term to be non-zero. To make the “hello world” path render something recognisable, the Scene injects sensible defaults when the user hasn’t supplied them:
- Default Camera:
Camera::perspective(60°, 1.0, 0.1, 100.0)looking from[0, 0, 5]at the origin with+Yup. - Default Light: a
Light::directionalaimed at[0.0, -0.3, -1.0]with full-white color, providing a forward-tilted fill so a front-facing quad reads as lit rather than silhouetted.
These only fire when no user Camera / Light has been added; as soon as you
add your own, your values win. A composition caller that drives every uniform
from the host can suppress them with
no_defaults (or the
per-kind no_default_camera / no_default_light), or swap the stock values
out with set_default_camera / set_default_light.
Methods
Section titled “Methods”Scene::load
Section titled “Scene::load”Build a Scene from a serialized 3D file. Pass a path (.gltf or .glb)
or in-memory .glb bytes straight to Scene::load. The format is
inferred from the input, so there’s nothing extra to name.
Scene::load is synchronous and takes no Renderer. Any textures the parser
encounters are queued as pending uploads on the resulting Materials; the
renderer drains them on first render, or earlier if you call
Renderer::load.
Today’s coverage: static glTF, meaning mesh primitives (POSITION + NORMAL + UV0 +
indices), PBR-MR materials with all five texture slots, per-node transforms
flattened into Model matrices, glTF camera nodes, and KHR_lights_punctual
lights. Animation, skinning, morph targets, and material extensions beyond
PBR-MR are out of scope; they parse cleanly and the loader ignores them.
Skipping embedded cameras and lights
Section titled “Skipping embedded cameras and lights”By default the loader instantiates the glTF file’s own camera and light nodes.
When you bring your own (a spring-arm rig, a UI-locked overlay camera, an
animated key/fill setup) the embedded ones would only fight for the same
shader uniforms. Load through gltf(...) to get a builder with cameras(false)
and lights(false) toggles:
1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Camera, Scene, SceneSource};
// Load geometry + materials only; drop the file's camera and lights.let scene = Scene::load( SceneSource::gltf("path/to/model.glb") .cameras(false) .lights(false),)?;
// Supply your own camera; it's now the only one the Scene tracks.let camera = Camera::perspective(1.047, 16.0 / 9.0, 0.1, 100.0) .look_at([0.0, 1.5, 4.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);scene.add(&camera)?;2 collapsed lines
Ok(())}Threading and WASM
Section titled “Threading and WASM”Scene::load is fully synchronous: the underlying importer decodes embedded
images and buffers on the calling thread. A 100 MB .glb freezes that thread
for the duration. Two implications:
- Native: spawn
Scene::loadon a worker thread (std::thread::spawn,tokio::task::spawn_blocking, …) if you can’t afford a frame stall. - WASM: a sync load on the main thread freezes the page, and the path form
isn’t available (the importer goes through
std::fs). Fetch the.glbbytes viafetchinside a Web Worker, hand them toScene::load(bytes), then transfer the produced Scene back.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::Scene;
// A path — `.gltf` JSON (with external buffers/images) or a `.glb` container.let scene = Scene::load("path/to/model.gltf")?;
// In-memory `.glb` bytes — fetched from disk, the network, or another// asset pipeline before this point.let glb_bytes: Vec<u8> = std::fs::read("path/to/model.glb")?;let scene2 = Scene::load(glb_bytes)?;3 collapsed lines
let _ = (scene, scene2);Ok(())}import { Scene } from "fragmentcolor";
// Fetch the `.glb` container as bytes, then load it. The same call accepts// a path string on native; on web pass the bytes instead.const response = await fetch("/healthcheck/public/model.glb");const bytes = new Uint8Array(await response.arrayBuffer());const scene = Scene.load(bytes);from fragmentcolor import Scene
# A path — `.gltf` JSON (with external buffers/images) or a `.glb` container.scene = Scene.load("path/to/model.gltf")
# In-memory `.glb` bytes — fetched from disk, the network, or another# asset pipeline before this point.glb_bytes = open("path/to/model.glb", "rb").read()scene2 = Scene.load(glb_bytes)import FragmentColor
// A path — """.gltf""" JSON (with external buffers/images) or a """.glb""" container.let scene = try await Scene.load("path/to/model.gltf")
// In-memory """.glb""" bytes — fetched from disk, the network, or another// asset pipeline before this point.let glb_bytes = "/healthcheck/public/favicon.png"let scene2 = try await Scene.load(glb_bytes)import org.fragmentcolor.*
// A path — """.gltf""" JSON (with external buffers/images) or a """.glb""" container.val scene = Scene.load("path/to/model.gltf")
val bytes: ByteArray = byteArrayOf()// In-memory """.glb""" bytes — fetched from disk, the network, or another// asset pipeline before this point.val png: ByteArray = byteArrayOf()val glb_bytes = "/healthcheck/public/favicon.png"val scene2 = Scene.load(glb_bytes)Scene::new
Section titled “Scene::new”Build an empty Scene. No Renderer argument, no async, nothing to await.
The Scene’s GPU resources initialise lazily when it’s first rendered.
The first call to Scene::add
creates a default Pass under the
hood and routes the object into it. Multiple
SceneObjects share that one Pass
unless you explicitly add more via
Scene::add_pass.
Scene is Clone (shallow Arc-share); cloning gives another handle to
the same underlying scene, so mutations on one clone are visible on the
other.
Example
Section titled “Example”use fragmentcolor::Scene;
let scene = Scene::new();// scene is empty; add Models / Cameras / Lights with `scene.add(...)`.1 collapsed line
let _ = scene;import { Scene } from "fragmentcolor";
const scene = new Scene();// scene is empty; add Models / Cameras / Lights with `scene.add(...)`.from fragmentcolor import Scene
scene = Scene()# scene is empty; add Models / Cameras / Lights with `scene.add(...)`.import FragmentColor
let scene = Scene()// scene is empty; add Models / Cameras / Lights with """scene.add(...)""".import org.fragmentcolor.*
val scene = Scene()// scene is empty; add Models / Cameras / Lights with """scene.add(...)""".Scene::add
Section titled “Scene::add”Absorb any SceneObject (a
Model,
Camera,
Light, or a user-defined
node that implements the trait) into the scene. The Scene routes it
onto its default Pass, which
is created lazily on the first call.
Each kind brings its own attach behaviour: a Model queues a draw with its
own per-instance transform; a Camera or Light wires its uniforms into
every shader the pass renders, both the ones already there and the ones
added afterwards. Camera and Light hold Arc-shared state, so subsequent
mutations (camera.look_at(...), light.set_color(...)) propagate to
every shader on the pass with no further add call.
Returns Result<&Scene, PassError> so Models can fail at attach time when
the Mesh layout doesn’t match the Material’s shader; Cameras and Lights
always succeed. Chain with ? between calls.
If you add Models without adding a Camera or any Light, the Scene injects
sensible defaults at render time (Camera::perspective looking from
[0, 0, 5], a white Light::directional pointing slightly off-axis) so
the first frame renders something recognisable. Add your own to take over.
Example
Section titled “Example”1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Camera, Light, Material, Mesh, Model, Renderer, Scene, Vertex};
let renderer = Renderer::new();
let mesh = Mesh::new();mesh.add_vertex( Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),);let model = Model::new(mesh, Material::pbr());
let camera = Camera::perspective(1.047, 1.0, 0.1, 100.0) .look_at([0.0, 0.0, 3.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);let sun = Light::directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9]);
let scene = Scene::new();scene.add(&model)?;scene.add(&camera)?;scene.add(&sun)?;
// Updating the camera later is enough — every shader on the scene picks// the new view_proj up at the next render.camera.look_at([3.0, 1.0, 5.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);4 collapsed lines
let _ = renderer;Ok(())}fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }import { Camera, Light, Material, Mesh, Model, Renderer, Scene, Vertex } from "fragmentcolor";
const renderer = new Renderer();
const mesh = new Mesh();mesh.addVertex( Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]), );const model = new Model(mesh, Material.pbr());
const camera = Camera.perspective(1.047, 1.0, 0.1, 100.0).lookAt([0.0, 0.0, 3.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);const sun = Light.directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9]);
const scene = new Scene();scene.add(model);scene.add(camera);scene.add(sun);
// Updating the camera later is enough — every shader on the scene picks// the new view_proj up at the next render.camera.lookAt([3.0, 1.0, 5.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);from fragmentcolor import Camera, Light, Material, Mesh, Model, Renderer, Scene, Vertex
renderer = Renderer()
mesh = Mesh()mesh.add_vertex( Vertex.pbr([0.0, 0.5, 0.0]).set(Vertex.UV0, [0.5, 1.0]),)model = Model(mesh, Material.pbr())
camera = Camera.perspective(1.047, 1.0, 0.1, 100.0).look_at([0.0, 0.0, 3.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0])sun = Light.directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9])
scene = Scene()scene.add(model)scene.add(camera)scene.add(sun)
# Updating the camera later is enough — every shader on the scene picks# the new view_proj up at the next render.camera.look_at([3.0, 1.0, 5.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0])import FragmentColor
let renderer = Renderer()
let mesh = Mesh()try mesh.addVertex( try Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]))let model = Model(mesh, Material.pbr())
let camera = try Camera.perspective(1.047, 1.0, 0.1, 100.0).lookAt([0.0, 0.0, 3.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0])let sun = try Light.directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9])
let scene = Scene()try scene.add(model)try scene.add(camera)try scene.add(sun)
// Updating the camera later is enough — every shader on the scene picks// the view_proj up at the next render.try camera.lookAt([3.0, 1.0, 5.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0])import org.fragmentcolor.*
val renderer = Renderer()
val mesh = Mesh()mesh.addVertex( Vertex.pbr(listOf(0.0f, 0.5f, 0.0f)).set("uv0", floatArrayOf(0.5f, 1.0f)), )val model = Model(mesh, Material.pbr())
val camera = Camera.perspective(1.047f, 1.0f, 0.1f, 100.0f).lookAt(listOf(0.0f, 0.0f, 3.0f), listOf(0.0f, 0.0f, 0.0f), listOf(0.0f, 1.0f, 0.0f))val sun = Light.directional(listOf(0.3f, -1.0f, -0.4f), listOf(1.0f, 0.95f, 0.9f))
val scene = Scene()scene.add(model)scene.add(camera)scene.add(sun)
// Updating the camera later is enough — every shader on the scene picks// the view_proj up at the next render.camera.lookAt(listOf(3.0f, 1.0f, 5.0f), listOf(0.0f, 0.0f, 0.0f), listOf(0.0f, 1.0f, 0.0f))Scene::add_to
Section titled “Scene::add_to”Add a SceneObject (Model / Camera /
Light / custom) to a specific Pass in the graph, addressed by index or by
name. It’s the targeted form of
add: where add routes
into the scene’s default pass, add_to lets you pick the pass yourself, which
matters once a scene holds more than one.
Pass an index (matching get_pass)
or a name (matching find_pass).
An index out of range, or a name that matches nothing, is an error. The
Scene-level bookkeeping is identical to add: the object surfaces in
models() / cameras() / lights(), and a Camera or Light there still
suppresses the matching default-injection.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Material, Mesh, Model, Pass, Scene, Vertex};
let mesh = Mesh::new();mesh.add_vertex( Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),);let model = Model::new(mesh, Material::pbr());
let scene = Scene::new();scene.add_pass(&Pass::new("geometry"));
// Target the pass by name (or pass its index: scene.add_to(0, &model)).scene.add_to("geometry", &model)?;3 collapsed lines
assert_eq!(scene.models().len(), 1);Ok(())}import { Material, Mesh, Model, Pass, Scene, Vertex } from "fragmentcolor";
const mesh = new Mesh();mesh.addVertex( Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]), );const model = new Model(mesh, Material.pbr());
const scene = new Scene();scene.addPass(new Pass("geometry"));
// Target the pass by name (or pass its index: scene.addTo(0, model)).scene.addTo("geometry", model);from fragmentcolor import Material, Mesh, Model, Pass, Scene, Vertex
mesh = Mesh()mesh.add_vertex( Vertex.pbr([0.0, 0.5, 0.0]).set(Vertex.UV0, [0.5, 1.0]),)model = Model(mesh, Material.pbr())
scene = Scene()scene.add_pass(Pass("geometry"))
# Target the pass by name (or pass its index: scene.add_to(0, model)).scene.add_to("geometry", model)import FragmentColor
let mesh = Mesh()try mesh.addVertex( try Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]))let model = Model(mesh, Material.pbr())
let scene = Scene()scene.addPass(Pass("geometry"))
// Target the pass by name (or pass its index: scene.addTo(0, model)).try scene.addTo("geometry", model)import org.fragmentcolor.*
val mesh = Mesh()mesh.addVertex( Vertex.pbr(listOf(0.0f, 0.5f, 0.0f)).set("uv0", floatArrayOf(0.5f, 1.0f)), )val model = Model(mesh, Material.pbr())
val scene = Scene()scene.addPass(Pass("geometry"))
// Target the pass by name (or pass its index: scene.addTo(0, model)).scene.addTo("geometry", model)Scene::models
Section titled “Scene::models”Return a snapshot of every Model
added to this Scene via Scene::add,
including Models the loader instantiated from glTF mesh nodes.
Each entry is an Arc-shared clone of the original handle. Mutating one
of them (set_visible, translate, set_transform, …) propagates live
to every shader the Model was wired into, no re-attach needed. The
returned Vec is the snapshot at call time. Adding more Models after
the call doesn’t grow this Vec, but the handles you already have stay
live.
Order: insertion order. Loader-produced Models appear in glTF node-walk order; user-added Models appear after.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Material, Mesh, Model, Scene, Vertex};
let mesh = Mesh::new();mesh.add_vertex( Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),);let model = Model::new(mesh, Material::pbr());
let scene = Scene::new();scene.add(&model)?;
// LOD switch: hide every model the user just loaded, based on a// camera-distance heuristic the caller computes elsewhere.for m in scene.models() { m.set_visible(false);}2 collapsed lines
Ok(())}import { Material, Mesh, Model, Scene, Vertex } from "fragmentcolor";
const mesh = new Mesh();mesh.addVertex( Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]), );const model = new Model(mesh, Material.pbr());
const scene = new Scene();scene.add(model);
// LOD switch: hide every model the user just loaded, based on a// camera-distance heuristic the caller computes elsewhere.for (const m of scene.models()) { m.setVisible(false); };from fragmentcolor import Material, Mesh, Model, Scene, Vertex
mesh = Mesh()mesh.add_vertex( Vertex.pbr([0.0, 0.5, 0.0]).set(Vertex.UV0, [0.5, 1.0]),)model = Model(mesh, Material.pbr())
scene = Scene()scene.add(model)
# LOD switch: hide every model the user just loaded, based on a# camera-distance heuristic the caller computes elsewhere.for m in scene.models(): m.set_visible(False)import FragmentColor
let mesh = Mesh()try mesh.addVertex( try Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]))let model = Model(mesh, Material.pbr())
let scene = Scene()try scene.add(model)
// LOD switch: hide every model the user just loaded, based on a// camera-distance heuristic the caller computes elsewhere.for m in scene.models() { m.setVisible(false)}import org.fragmentcolor.*
val mesh = Mesh()mesh.addVertex( Vertex.pbr(listOf(0.0f, 0.5f, 0.0f)).set("uv0", floatArrayOf(0.5f, 1.0f)), )val model = Model(mesh, Material.pbr())
val scene = Scene()scene.add(model)
// LOD switch: hide every model the user just loaded, based on a// camera-distance heuristic the caller computes elsewhere.for (m in scene.models()) { m.setVisible(false)}Scene::cameras
Section titled “Scene::cameras”Return a snapshot of every Camera
added to this Scene via Scene::add,
including Cameras the loader instantiated from glTF camera nodes
(unless you skipped them via the camera filter on
Scene::load).
Each entry is an Arc-shared clone of the original handle.
camera.look_at(...) / camera.set_aspect(...) on a returned handle
propagates the new view + projection to every shader the Camera is wired
into, the same live semantics as the Camera handle you originally added.
When the Scene rendered with a defaulted Camera (no user-supplied Camera
when the first render landed), the auto-injected default appears in
this list too, so a consumer who wants to drive the default camera per
frame can grab scene.cameras().first() and call look_at on it.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::Scene;
let scene = Scene::load("path/to/model.glb")?;
// Animate every camera the glTF shipped per frame instead of supplying// our own. Most scenes carry a single camera, so the loop body usually// runs once.for camera in scene.cameras() { camera.look_at([0.0, 1.5, 4.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]); camera.set_aspect(16.0 / 9.0);}2 collapsed lines
Ok(())}import { Scene } from "fragmentcolor";
const response = await fetch("/healthcheck/public/model.glb");const bytes = new Uint8Array(await response.arrayBuffer());const scene = Scene.load(bytes);
// Animate every camera the glTF shipped per frame instead of supplying// our own. Most scenes carry a single camera, so the loop body usually// runs once.for (const camera of scene.cameras()) {camera.lookAt([0.0, 1.5, 4.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);camera.setAspect(16.0 / 9.0);}from fragmentcolor import Scene
scene = Scene.load("path/to/model.glb")
# Animate every camera the glTF shipped per frame instead of supplying# our own. Most scenes carry a single camera, so the loop body usually# runs once.for camera in scene.cameras(): camera.look_at([0.0, 1.5, 4.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]) camera.set_aspect(16.0 / 9.0)import FragmentColor
let scene = try await Scene.load("path/to/model.glb")
// Animate every camera the glTF shipped per frame instead of supplying// our own. Most scenes carry a single camera, so the loop body usually// runs once.for camera in scene.cameras() { try camera.lookAt([0.0, 1.5, 4.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]) camera.setAspect(16.0 / 9.0)}import org.fragmentcolor.*
val scene = Scene.load("path/to/model.glb")
// Animate every camera the glTF shipped per frame instead of supplying// our own. Most scenes carry a single camera, so the loop body usually// runs once.for (camera in scene.cameras()) { camera.lookAt(listOf(0.0f, 1.5f, 4.0f), listOf(0.0f, 0.0f, 0.0f), listOf(0.0f, 1.0f, 0.0f)) camera.setAspect(16.0f / 9.0f)}Scene::lights
Section titled “Scene::lights”Return a snapshot of every Light
added to this Scene via Scene::add,
including Lights the loader instantiated from glTF
KHR_lights_punctual nodes (unless you skipped them via the light filter
on Scene::load).
Each entry is an Arc-shared clone. Mutating a returned handle
(set_color, set_intensity, set_position, …) propagates to every
shader the Light occupies a slot in.
The default-injected Light (auto-fired when the Scene first renders with no user-supplied Light) appears in this list too, so consumers can grab the default and tweak it instead of supplanting it.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::Scene;
let scene = Scene::load("path/to/model.glb")?;
// Darken every loaded light to half intensity for a moody pass.for light in scene.lights() { let current = light.intensity(); light.set_intensity(current * 0.5);}2 collapsed lines
Ok(())}import { Scene } from "fragmentcolor";
const response = await fetch("/healthcheck/public/model.glb");const bytes = new Uint8Array(await response.arrayBuffer());const scene = Scene.load(bytes);
// Darken every loaded light to half intensity for a moody pass.for (const light of scene.lights()) {const current = light.intensity();light.setIntensity(current * 0.5);}from fragmentcolor import Scene
scene = Scene.load("path/to/model.glb")
# Darken every loaded light to half intensity for a moody pass.for light in scene.lights(): current = light.intensity() light.set_intensity(current * 0.5)import FragmentColor
let scene = try await Scene.load("path/to/model.glb")
// Darken every loaded light to half intensity for a moody pass.for light in scene.lights() { let current = light.intensity() light.setIntensity(current * 0.5)}import org.fragmentcolor.*
val scene = Scene.load("path/to/model.glb")
// Darken every loaded light to half intensity for a moody pass.for (light in scene.lights()) { val current = light.intensity() light.setIntensity(current * 0.5f)}Scene::ambient
Section titled “Scene::ambient”Set the scene-wide ambient color. The PBR shader adds
albedo * ambient to every fragment regardless of which lights are
attached, so unlit faces don’t read pitch-black and lit faces get a
subtle warm/cool fill on top of direct lighting.
Defaults: every Material seeded by Material::pbr() starts with
ambient = [0.03, 0.03, 0.03] (the dim grey the PBR shader had
hardcoded before this knob existed). Calling Scene::ambient overrides
that for every shader the scene visits today and for any Models added
afterwards: the value is stashed on the Scene and re-stamped onto
shaders that join later via Scene::add.
Returns a handle to the same Scene (Arc-shared) for chaining.
Example
Section titled “Example”1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Light, Material, Mesh, Model, Renderer, Scene, Vertex};
let renderer = Renderer::new();let target = renderer.create_texture_target([64, 64]).await?;
let mesh = Mesh::new();mesh.add_vertex( Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),);
let scene = Scene::new();// Warm dusk ambient — applies to every Material added below.scene.ambient([0.06, 0.04, 0.03]);scene.add(&Model::new(mesh, Material::pbr()))?;scene.add(&Light::directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9]))?;
renderer.render(&scene, &target)?;3 collapsed lines
Ok(())}fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }import { Light, Material, Mesh, Model, Renderer, Scene, Vertex } from "fragmentcolor";
const renderer = new Renderer();const target = await renderer.createTextureTarget([64, 64]);
const mesh = new Mesh();mesh.addVertex( Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]), );
const scene = new Scene();// Warm dusk ambient — applies to every Material added below.scene.ambient([0.06, 0.04, 0.03]);scene.add(new Model(mesh, Material.pbr()));scene.add(Light.directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9]));
renderer.render(scene, target);from fragmentcolor import Light, Material, Mesh, Model, Renderer, Scene, Vertex
renderer = Renderer()target = renderer.create_texture_target([64, 64])
mesh = Mesh()mesh.add_vertex( Vertex.pbr([0.0, 0.5, 0.0]).set(Vertex.UV0, [0.5, 1.0]),)
scene = Scene()# Warm dusk ambient — applies to every Material added below.scene.ambient([0.06, 0.04, 0.03])scene.add(Model(mesh, Material.pbr()))scene.add(Light.directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9]))
renderer.render(scene, target)import FragmentColor
let renderer = Renderer()let target = try await renderer.createTextureTarget([64, 64])
let mesh = Mesh()try mesh.addVertex( try Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]))
let scene = Scene()// Warm dusk ambient — applies to every Material added below.try scene.ambient([0.06, 0.04, 0.03])try scene.add(Model(mesh, Material.pbr()))try scene.add(Light.directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9]))
try renderer.render(scene, target)import org.fragmentcolor.*
val renderer = Renderer()val target = renderer.createTextureTarget(64u, 64u)
val mesh = Mesh()mesh.addVertex( Vertex.pbr(listOf(0.0f, 0.5f, 0.0f)).set("uv0", floatArrayOf(0.5f, 1.0f)), )
val scene = Scene()// Warm dusk ambient — applies to every Material added below.scene.ambient(listOf(0.06f, 0.04f, 0.03f))scene.add(Model(mesh, Material.pbr()))scene.add(Light.directional(listOf(0.3f, -1.0f, -0.4f), listOf(1.0f, 0.95f, 0.9f)))
renderer.render(scene, target)Scene::add_pass
Section titled “Scene::add_pass”Append a user-built Pass to the
scene’s pass graph. A Scene owns one ordered list of passes, and add_pass
pushes onto the end. Order is the order you build it: passes render in vec
order, and the default pass that holds scene.add(&model) geometry is an
ordinary member of that list, slotted in at the point of your first add.
That makes add_pass the hook for shadow maps, depth pre-passes, screen-space
backdrops, and post-effects alike. Insert before your geometry for a backdrop,
after it for an overlay. To inspect or reorder the result, reach for
list_passes,
get_pass,
remove_pass, and
set_passes.
The Pass is cloned (shallow Arc-share) when stored, so further changes you make to the original handle reach the Scene’s copy too.
Each new Pass clears by default
Section titled “Each new Pass clears by default”Every Pass::new(name) starts with a clear-to-transparent input. The first
render of the frame for that Pass wipes its colour attachment to
[0, 0, 0, 0]. When you chain passes that read each other’s output, the
downstream Pass clears the upstream Pass’s output unless you opt out:
- Call
pass.load_previous()on the downstream Pass to keep the previous contents around (the wgpuLoadOp::Loadequivalent). - Or call
pass.set_clear_color([r, g, b, a])to choose a specific clear value (still a clear, just a different colour).
Scene::add_pass stores each pass independently. There’s no automatic
chaining of their attachments. To share a target between two passes, route
them through the same set_target(...) and use load_previous to compose.
Example
Section titled “Example”1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Material, Mesh, Model, Pass, Renderer, Scene, Vertex};
let renderer = Renderer::new();
let mesh = Mesh::new();mesh.add_vertex( Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),);let model = Model::new(mesh, Material::pbr());
// A backdrop pass that clears to a soft blue before the scene's main draw.let backdrop = Pass::new("backdrop");backdrop.set_clear_color([0.05, 0.08, 0.12, 1.0]);
let scene = Scene::new();scene.add_pass(&backdrop);scene.add(&model)?;
4 collapsed lines
let _ = renderer;Ok(())}fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }import { Material, Mesh, Model, Pass, Renderer, Scene, Vertex } from "fragmentcolor";
const renderer = new Renderer();
const mesh = new Mesh();mesh.addVertex( Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]), );const model = new Model(mesh, Material.pbr());
// A backdrop pass that clears to a soft blue before the scene's main draw.const backdrop = new Pass("backdrop");backdrop.setClearColor([0.05, 0.08, 0.12, 1.0]);
const scene = new Scene();scene.addPass(backdrop);scene.add(model);from fragmentcolor import Material, Mesh, Model, Pass, Renderer, Scene, Vertex
renderer = Renderer()
mesh = Mesh()mesh.add_vertex( Vertex.pbr([0.0, 0.5, 0.0]).set(Vertex.UV0, [0.5, 1.0]),)model = Model(mesh, Material.pbr())
# A backdrop pass that clears to a soft blue before the scene's main draw.backdrop = Pass("backdrop")backdrop.set_clear_color([0.05, 0.08, 0.12, 1.0])
scene = Scene()scene.add_pass(backdrop)scene.add(model)import FragmentColor
let renderer = Renderer()
let mesh = Mesh()try mesh.addVertex( try Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]))let model = Model(mesh, Material.pbr())
// A backdrop pass that clears to a soft blue before the scene's main draw.let backdrop = Pass("backdrop")try backdrop.setClearColor([0.05, 0.08, 0.12, 1.0])
let scene = Scene()scene.addPass(backdrop)try scene.add(model)import org.fragmentcolor.*
val renderer = Renderer()
val mesh = Mesh()mesh.addVertex( Vertex.pbr(listOf(0.0f, 0.5f, 0.0f)).set("uv0", floatArrayOf(0.5f, 1.0f)), )val model = Model(mesh, Material.pbr())
// A backdrop pass that clears to a soft blue before the scene's main draw.val backdrop = Pass("backdrop")backdrop.setClearColor(listOf(0.05f, 0.08f, 0.12f, 1.0f))
val scene = Scene()scene.addPass(backdrop)scene.add(model)Scene::remove_pass
Section titled “Scene::remove_pass”Remove a Pass from the scene’s
pass graph. Identity is by handle: Pass is Clone and Arc-backed, so the
handle you pass in is matched against the graph by pointer, not by name.
Returns true when a pass left the graph, false when the handle wasn’t
there.
If the removed pass was the default pass that Scene::add
routes objects into, the Scene forgets it. The next add builds a fresh
default pass at the end of the graph.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Pass, Scene};
let scene = Scene::new();let backdrop = Pass::new("backdrop");let overlay = Pass::new("overlay");scene.add_pass(&backdrop);scene.add_pass(&overlay);
// Drop the backdrop; the overlay stays.let removed = scene.remove_pass(&backdrop);4 collapsed lines
assert!(removed);assert_eq!(scene.list_passes().len(), 1);Ok(())}import { Pass, Scene } from "fragmentcolor";
const scene = new Scene();const backdrop = new Pass("backdrop");const overlay = new Pass("overlay");scene.addPass(backdrop);scene.addPass(overlay);
// Drop the backdrop; the overlay stays.const removed = scene.removePass(backdrop);from fragmentcolor import Pass, Scene
scene = Scene()backdrop = Pass("backdrop")overlay = Pass("overlay")scene.add_pass(backdrop)scene.add_pass(overlay)
# Drop the backdrop; the overlay stays.removed = scene.remove_pass(backdrop)import FragmentColor
let scene = Scene()let backdrop = Pass("backdrop")let overlay = Pass("overlay")scene.addPass(backdrop)scene.addPass(overlay)
// Drop the backdrop; the overlay stays.let removed = scene.removePass(backdrop)import org.fragmentcolor.*
val scene = Scene()val backdrop = Pass("backdrop")val overlay = Pass("overlay")scene.addPass(backdrop)scene.addPass(overlay)
// Drop the backdrop; the overlay stays.val removed = scene.removePass(backdrop)Scene::get_pass
Section titled “Scene::get_pass”Read one Pass from the graph by
index, in render order. Returns None when the index is out of range.
The returned Pass is an Arc-shared clone of the one the Scene renders.
Configuring it (load_previous, set_clear_color, set_target) drives the
live pass, no re-insert needed. To borrow the whole graph at once, use
Scene::list_passes.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Pass, Scene};
let scene = Scene::new();scene.add_pass(&Pass::new("backdrop"));scene.add_pass(&Pass::new("geometry"));
// Fetch the second pass (index 1) to reconfigure it. An out-of-range// index returns None instead.let geometry = scene.get_pass(1);7 collapsed lines
assert!(geometry.is_some());assert!(scene.get_pass(2).is_none());if let Some(pass) = geometry { pass.load_previous();}Ok(())}import { Pass, Scene } from "fragmentcolor";
const scene = new Scene();scene.addPass(new Pass("backdrop"));scene.addPass(new Pass("geometry"));
// Fetch the second pass (index 1) to reconfigure it. An out-of-range// index returns null instead.const geometry = scene.getPass(1);from fragmentcolor import Pass, Scene
scene = Scene()scene.add_pass(Pass("backdrop"))scene.add_pass(Pass("geometry"))
# Fetch the second pass (index 1) to reconfigure it. An out-of-range# index returns None instead.geometry = scene.get_pass(1)import FragmentColor
let scene = Scene()scene.addPass(Pass("backdrop"))scene.addPass(Pass("geometry"))
// Fetch the second pass (index 1) to reconfigure it. An out-of-range// index returns nil instead.let geometry = scene.getPass(1)import org.fragmentcolor.*
val scene = Scene()scene.addPass(Pass("backdrop"))scene.addPass(Pass("geometry"))
// Fetch the second pass (index 1) to reconfigure it. An out-of-range// index returns null instead.val geometry = scene.getPass(1u)Scene::find_pass
Section titled “Scene::find_pass”Find a Pass in the scene’s graph
by name, returning None when nothing matches. Names come from
Pass::new (or whatever label a
loader assigned), and you can read one back with
Pass::name.
This is the name-addressed counterpart to
get_pass, which
addresses by index. Names aren’t required to be unique; find_pass returns the
first match in render order. The returned Pass is an Arc-shared clone, so
configuring it drives the pass the Scene renders.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Pass, Scene};
let scene = Scene::new();scene.add_pass(&Pass::new("backdrop"));scene.add_pass(&Pass::new("geometry"));
// Look the geometry pass up by name to reconfigure it. A name with no// match returns None instead.let geometry = scene.find_pass("geometry");4 collapsed lines
assert!(geometry.is_some());assert!(scene.find_pass("missing").is_none());Ok(())}import { Pass, Scene } from "fragmentcolor";
const scene = new Scene();scene.addPass(new Pass("backdrop"));scene.addPass(new Pass("geometry"));
// Look the geometry pass up by name to reconfigure it. A name with no// match returns null instead.const geometry = scene.findPass("geometry");from fragmentcolor import Pass, Scene
scene = Scene()scene.add_pass(Pass("backdrop"))scene.add_pass(Pass("geometry"))
# Look the geometry pass up by name to reconfigure it. A name with no# match returns None instead.geometry = scene.find_pass("geometry")import FragmentColor
let scene = Scene()scene.addPass(Pass("backdrop"))scene.addPass(Pass("geometry"))
// Look the geometry pass up by name to reconfigure it. A name with no// match returns nil instead.let geometry = scene.findPass("geometry")import org.fragmentcolor.*
val scene = Scene()scene.addPass(Pass("backdrop"))scene.addPass(Pass("geometry"))
// Look the geometry pass up by name to reconfigure it. A name with no// match returns null instead.val geometry = scene.findPass("geometry")Scene::list_passes
Section titled “Scene::list_passes”Return a snapshot of every Pass
in the scene, in render order. Each entry is an Arc-shared clone, so
configuring one (load_previous, set_clear_color, set_viewport,
set_target) drives the pass the Scene renders. The returned Vec is the
snapshot at call time; appending passes afterward doesn’t grow it.
This is the composition hook. After
Scene::load the whole
pass graph is in your hands: walk it and call load_previous so the scene
composes onto a previous draw instead of clearing it, then hand the Scene to
the renderer alongside whatever else the frame needs.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Material, Mesh, Model, Scene, Vertex};
let mesh = Mesh::new();mesh.add_vertex( Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),);let scene = Scene::new();scene.add(&Model::new(mesh, Material::pbr()))?;
// Compose, don't clear: keep whatever the previous pass drew.for p in scene.list_passes() { p.load_previous();}2 collapsed lines
Ok(())}import { Material, Mesh, Model, Scene, Vertex } from "fragmentcolor";
const mesh = new Mesh();mesh.addVertex( Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]), );const scene = new Scene();scene.add(new Model(mesh, Material.pbr()));
// Compose, don't clear: keep whatever the previous pass drew.for (const p of scene.listPasses()) { p.loadPrevious(); };from fragmentcolor import Material, Mesh, Model, Scene, Vertex
mesh = Mesh()mesh.add_vertex( Vertex.pbr([0.0, 0.5, 0.0]).set(Vertex.UV0, [0.5, 1.0]),)scene = Scene()scene.add(Model(mesh, Material.pbr()))
# Compose, don't clear: keep whatever the previous pass drew.for p in scene.list_passes(): p.load_previous()import FragmentColor
let mesh = Mesh()try mesh.addVertex( try Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]))let scene = Scene()try scene.add(Model(mesh, Material.pbr()))
// Compose, don't clear: keep whatever the previous pass drew.for p in scene.listPasses() { p.loadPrevious()}import org.fragmentcolor.*
val mesh = Mesh()mesh.addVertex( Vertex.pbr(listOf(0.0f, 0.5f, 0.0f)).set("uv0", floatArrayOf(0.5f, 1.0f)), )val scene = Scene()scene.add(Model(mesh, Material.pbr()))
// Compose, don't clear: keep whatever the previous pass drew.for (p in scene.listPasses()) { p.loadPrevious()}Scene::set_passes
Section titled “Scene::set_passes”Replace the scene’s entire pass graph with a new ordered list. The passes
render in vec order. This is the sugar for “load, reorder, render” when you’d
rather hand over a fresh ordering than call
remove_pass and
add_pass one at a
time.
If the default pass that Scene::add
routes objects into survives in the new list, the Scene keeps using it. If you
drop it, the Scene forgets it and the next add builds a fresh one at the end.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Pass, Scene};
let scene = Scene::new();scene.add_pass(&Pass::new("scratch"));
// Swap in a deliberate order: shadow map, then geometry, then overlay.scene.set_passes(vec![ Pass::new("shadow"), Pass::new("geometry"), Pass::new("overlay"),]);3 collapsed lines
assert_eq!(scene.list_passes().len(), 3);Ok(())}import { Pass, Scene } from "fragmentcolor";
const scene = new Scene();scene.addPass(new Pass("scratch"));
// Swap in a deliberate order: shadow map, then geometry, then overlay.scene.setPasses([ new Pass("shadow"), new Pass("geometry"), new Pass("overlay"), ]);from fragmentcolor import Pass, Scene
scene = Scene()scene.add_pass(Pass("scratch"))
# Swap in a deliberate order: shadow map, then geometry, then overlay.scene.set_passes([ Pass("shadow"), Pass("geometry"), Pass("overlay"),])import FragmentColor
let scene = Scene()scene.addPass(Pass("scratch"))
// Swap in a deliberate order: shadow map, then geometry, then overlay.scene.setPasses([ Pass("shadow"), Pass("geometry"), Pass("overlay"),])import org.fragmentcolor.*
val scene = Scene()scene.addPass(Pass("scratch"))
// Swap in a deliberate order: shadow map, then geometry, then overlay.scene.setPasses(arrayOf(Pass("shadow"), Pass("geometry"), Pass("overlay"),))Scene::no_defaults
Section titled “Scene::no_defaults”Turn off both default-Camera and default-Light injection. By default a Scene
carrying Models but no Camera or Light injects a stock perspective camera and
a white directional light at first render, so the hello-world path shows
something recognisable. A composition caller that drives view_proj and the
light slots from the host doesn’t want those stock values layered on top.
no_defaults leaves the pass graph exactly as built.
For finer control, reach for the per-kind switches
no_default_camera
and no_default_light.
To keep injection on but swap the stock values for your own, use
set_default_camera
and set_default_light.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Material, Mesh, Model, Scene, Vertex};
let mesh = Mesh::new();mesh.add_vertex( Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),);let scene = Scene::new();scene.add(&Model::new(mesh, Material::pbr()))?;
// The host overrides every uniform, so suppress FC's stock camera + light.scene.no_defaults();for p in scene.list_passes() { p.load_previous();}2 collapsed lines
Ok(())}import { Material, Mesh, Model, Scene, Vertex } from "fragmentcolor";
const mesh = new Mesh();mesh.addVertex( Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]), );const scene = new Scene();scene.add(new Model(mesh, Material.pbr()));
// The host overrides every uniform, so suppress FC's stock camera + light.scene.noDefaults();for (const p of scene.listPasses()) { p.loadPrevious(); };from fragmentcolor import Material, Mesh, Model, Scene, Vertex
mesh = Mesh()mesh.add_vertex( Vertex.pbr([0.0, 0.5, 0.0]).set(Vertex.UV0, [0.5, 1.0]),)scene = Scene()scene.add(Model(mesh, Material.pbr()))
# The host overrides every uniform, so suppress FC's stock camera + light.scene.no_defaults()for p in scene.list_passes(): p.load_previous()import FragmentColor
let mesh = Mesh()try mesh.addVertex( try Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]))let scene = Scene()try scene.add(Model(mesh, Material.pbr()))
// The host overrides every uniform, so suppress FC's stock camera + light.scene.noDefaults()for p in scene.listPasses() { p.loadPrevious()}import org.fragmentcolor.*
val mesh = Mesh()mesh.addVertex( Vertex.pbr(listOf(0.0f, 0.5f, 0.0f)).set("uv0", floatArrayOf(0.5f, 1.0f)), )val scene = Scene()scene.add(Model(mesh, Material.pbr()))
// The host overrides every uniform, so suppress FC's stock camera + light.scene.noDefaults()for (p in scene.listPasses()) { p.loadPrevious()}Scene::no_default_camera
Section titled “Scene::no_default_camera”Turn off default-Camera injection while leaving the default Light in place. By default a Scene with Models but no Camera injects a stock perspective camera at first render. Call this when the host supplies the view projection and the stock camera would only fight for the same uniform.
To turn off both kinds at once, use
no_defaults. To
keep injection on but supply your own camera, use
set_default_camera.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Material, Mesh, Model, Scene, Vertex};
let mesh = Mesh::new();mesh.add_vertex( Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),);let scene = Scene::new();scene.add(&Model::new(mesh, Material::pbr()))?;
scene.no_default_camera();2 collapsed lines
Ok(())}import { Material, Mesh, Model, Scene, Vertex } from "fragmentcolor";
const mesh = new Mesh();mesh.addVertex( Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]), );const scene = new Scene();scene.add(new Model(mesh, Material.pbr()));
scene.noDefaultCamera();from fragmentcolor import Material, Mesh, Model, Scene, Vertex
mesh = Mesh()mesh.add_vertex( Vertex.pbr([0.0, 0.5, 0.0]).set(Vertex.UV0, [0.5, 1.0]),)scene = Scene()scene.add(Model(mesh, Material.pbr()))
scene.no_default_camera()import FragmentColor
let mesh = Mesh()try mesh.addVertex( try Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]))let scene = Scene()try scene.add(Model(mesh, Material.pbr()))
scene.noDefaultCamera()import org.fragmentcolor.*
val mesh = Mesh()mesh.addVertex( Vertex.pbr(listOf(0.0f, 0.5f, 0.0f)).set("uv0", floatArrayOf(0.5f, 1.0f)), )val scene = Scene()scene.add(Model(mesh, Material.pbr()))
scene.noDefaultCamera()Scene::no_default_light
Section titled “Scene::no_default_light”Turn off default-Light injection while leaving the default Camera in place.
By default a Scene with Models but no Light injects a white directional light
at first render so the geometry reads as lit. Call this when the host owns the
light rig and the stock light would only compete for the same
lights.lights[..] slots.
To turn off both kinds at once, use
no_defaults. To
keep injection on but supply your own light, use
set_default_light.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Material, Mesh, Model, Scene, Vertex};
let mesh = Mesh::new();mesh.add_vertex( Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),);let scene = Scene::new();scene.add(&Model::new(mesh, Material::pbr()))?;
scene.no_default_light();2 collapsed lines
Ok(())}import { Material, Mesh, Model, Scene, Vertex } from "fragmentcolor";
const mesh = new Mesh();mesh.addVertex( Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]), );const scene = new Scene();scene.add(new Model(mesh, Material.pbr()));
scene.noDefaultLight();from fragmentcolor import Material, Mesh, Model, Scene, Vertex
mesh = Mesh()mesh.add_vertex( Vertex.pbr([0.0, 0.5, 0.0]).set(Vertex.UV0, [0.5, 1.0]),)scene = Scene()scene.add(Model(mesh, Material.pbr()))
scene.no_default_light()import FragmentColor
let mesh = Mesh()try mesh.addVertex( try Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]))let scene = Scene()try scene.add(Model(mesh, Material.pbr()))
scene.noDefaultLight()import org.fragmentcolor.*
val mesh = Mesh()mesh.addVertex( Vertex.pbr(listOf(0.0f, 0.5f, 0.0f)).set("uv0", floatArrayOf(0.5f, 1.0f)), )val scene = Scene()scene.add(Model(mesh, Material.pbr()))
scene.noDefaultLight()Scene::set_default_camera
Section titled “Scene::set_default_camera”Supply your own default Camera
for the Scene to inject at first render in place of FC’s stock perspective
camera. This fires only when no Camera arrived through
Scene::add; an explicit
scene.add(&camera) still wins.
Naming a default re-arms injection. If
no_default_camera
ran earlier, calling set_default_camera turns camera injection back on with
the camera you pass.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Camera, Material, Mesh, Model, Scene, Vertex};
let mesh = Mesh::new();mesh.add_vertex( Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),);let scene = Scene::new();scene.add(&Model::new(mesh, Material::pbr()))?;
let camera = Camera::perspective(1.047, 16.0 / 9.0, 0.1, 100.0) .look_at([0.0, 1.5, 4.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);scene.set_default_camera(&camera);2 collapsed lines
Ok(())}import { Camera, Material, Mesh, Model, Scene, Vertex } from "fragmentcolor";
const mesh = new Mesh();mesh.addVertex( Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]), );const scene = new Scene();scene.add(new Model(mesh, Material.pbr()));
const camera = Camera.perspective(1.047, 16.0 / 9.0, 0.1, 100.0).lookAt([0.0, 1.5, 4.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]);scene.setDefaultCamera(camera);from fragmentcolor import Camera, Material, Mesh, Model, Scene, Vertex
mesh = Mesh()mesh.add_vertex( Vertex.pbr([0.0, 0.5, 0.0]).set(Vertex.UV0, [0.5, 1.0]),)scene = Scene()scene.add(Model(mesh, Material.pbr()))
camera = Camera.perspective(1.047, 16.0 / 9.0, 0.1, 100.0).look_at([0.0, 1.5, 4.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0])scene.set_default_camera(camera)import FragmentColor
let mesh = Mesh()try mesh.addVertex( try Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]))let scene = Scene()try scene.add(Model(mesh, Material.pbr()))
let camera = try Camera.perspective(1.047, 16.0 / 9.0, 0.1, 100.0).lookAt([0.0, 1.5, 4.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0])scene.setDefaultCamera(camera)import org.fragmentcolor.*
val mesh = Mesh()mesh.addVertex( Vertex.pbr(listOf(0.0f, 0.5f, 0.0f)).set("uv0", floatArrayOf(0.5f, 1.0f)), )val scene = Scene()scene.add(Model(mesh, Material.pbr()))
val camera = Camera.perspective(1.047f, 16.0f / 9.0f, 0.1f, 100.0f).lookAt(listOf(0.0f, 1.5f, 4.0f), listOf(0.0f, 0.0f, 0.0f), listOf(0.0f, 1.0f, 0.0f))scene.setDefaultCamera(camera)Scene::set_default_light
Section titled “Scene::set_default_light”Supply your own default Light
for the Scene to inject at first render in place of FC’s stock white
directional light. This fires only when no Light arrived through
Scene::add; an explicit
scene.add(&light) still wins.
Naming a default re-arms injection. If
no_default_light
ran earlier, calling set_default_light turns light injection back on with
the light you pass.
Example
Section titled “Example”1 collapsed line
fn main() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Light, Material, Mesh, Model, Scene, Vertex};
let mesh = Mesh::new();mesh.add_vertex( Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),);let scene = Scene::new();scene.add(&Model::new(mesh, Material::pbr()))?;
let key = Light::directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9]);scene.set_default_light(&key);2 collapsed lines
Ok(())}import { Light, Material, Mesh, Model, Scene, Vertex } from "fragmentcolor";
const mesh = new Mesh();mesh.addVertex( Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]), );const scene = new Scene();scene.add(new Model(mesh, Material.pbr()));
const key = Light.directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9]);scene.setDefaultLight(key);from fragmentcolor import Light, Material, Mesh, Model, Scene, Vertex
mesh = Mesh()mesh.add_vertex( Vertex.pbr([0.0, 0.5, 0.0]).set(Vertex.UV0, [0.5, 1.0]),)scene = Scene()scene.add(Model(mesh, Material.pbr()))
key = Light.directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9])scene.set_default_light(key)import FragmentColor
let mesh = Mesh()try mesh.addVertex( try Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]))let scene = Scene()try scene.add(Model(mesh, Material.pbr()))
let key = try Light.directional([0.3, -1.0, -0.4], [1.0, 0.95, 0.9])scene.setDefaultLight(key)import org.fragmentcolor.*
val mesh = Mesh()mesh.addVertex( Vertex.pbr(listOf(0.0f, 0.5f, 0.0f)).set("uv0", floatArrayOf(0.5f, 1.0f)), )val scene = Scene()scene.add(Model(mesh, Material.pbr()))
val key = Light.directional(listOf(0.3f, -1.0f, -0.4f), listOf(1.0f, 0.95f, 0.9f))scene.setDefaultLight(key)