Import Oxide engine (Stages 0–10) under MIT license
Full project snapshot migrated to new Gitea remote without history: engine, editor, physics, script, examples, tests, docs, and assets. Relicensed from GPLv3 to MIT and updated repo URLs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# Render Pass Pipeline
|
||||
|
||||
Stage 4 drew everything in one hardcoded pass. Stage 5 generalizes that into a
|
||||
[`RenderPipeline`]: an ordered, named list of composable [`RenderPass`]es that
|
||||
share one frame's targets. A project enables only the passes it needs — this is
|
||||
the mechanism behind **scalable fidelity**: a flat unlit/low-poly look (or a
|
||||
stylized post effect like a VCR filter) versus a full realistic stack with
|
||||
shadows and post-processing, paying only for the passes turned on.
|
||||
|
||||
The Stage-4 forward renderer is retrofitted onto this as [`ForwardPass`], so the
|
||||
default pipeline is just `[Clear, Forward]` and produces pixel-identical output.
|
||||
Later stages (shadows, post-process, overlay UI) add passes **without touching
|
||||
the renderer core** — they register a pass.
|
||||
|
||||
## The pieces
|
||||
|
||||
- [`RenderPass`] — a trait with one method, `run(&mut self, frame)`. Implement it
|
||||
to add a stage of the frame.
|
||||
- [`FrameContext`] — everything a pass operates on for one frame: the shared
|
||||
`color` target, size, clear color, camera + its world transform, lighting, and
|
||||
the (already culled) drawables.
|
||||
- [`RenderPipeline`] — owns the passes and runs every *enabled* one in order.
|
||||
- Built-in passes: [`ClearPass`] (clears the color target) and [`ForwardPass`]
|
||||
(the lit forward draw).
|
||||
|
||||
## Composing a frame
|
||||
|
||||
```rust
|
||||
use oxide_engine::render::{RenderPipeline, FrameContext, ForwardPass};
|
||||
# use oxide_engine::prelude::*;
|
||||
# fn demo(device: &oxide_engine::wgpu::Device, queue: &oxide_engine::wgpu::Queue,
|
||||
# target: &oxide_engine::wgpu::TextureView, cube: &GpuMesh) {
|
||||
// The default pipeline: Clear then Forward (pixel-identical to Stage 4).
|
||||
let mut pipeline = RenderPipeline::forward(device, oxide_engine::wgpu::TextureFormat::Rgba8Unorm);
|
||||
|
||||
let camera = Camera::default();
|
||||
let view = Transform::looking_at(Vec3::new(0.0, 0.0, 5.0), Vec3::ZERO, Vec3::Y);
|
||||
let lighting = Lighting::default();
|
||||
let objects = [RenderObject { mesh: cube, material: Material::diffuse(Color::RED), transform: Transform::IDENTITY }];
|
||||
|
||||
pipeline.render(&mut FrameContext {
|
||||
device, queue,
|
||||
color: target,
|
||||
size: (1280, 720),
|
||||
clear_color: Color::rgb(0.05, 0.06, 0.09),
|
||||
camera: &camera,
|
||||
view_transform: &view,
|
||||
lighting: &lighting,
|
||||
objects: &objects,
|
||||
});
|
||||
# }
|
||||
```
|
||||
|
||||
## Data-driven: add, toggle, remove
|
||||
|
||||
Passes are addressed by name and managed without touching any pass's code:
|
||||
|
||||
```rust
|
||||
# use oxide_engine::render::RenderPipeline;
|
||||
# struct Bloom; impl oxide_engine::render::RenderPass for Bloom {
|
||||
# fn run(&mut self, _f: &mut oxide_engine::render::FrameContext<'_>) {} }
|
||||
# let mut pipeline = RenderPipeline::new();
|
||||
pipeline.add_pass("forward", /* ForwardPass */
|
||||
# { struct F; impl oxide_engine::render::RenderPass for F { fn run(&mut self, _f: &mut oxide_engine::render::FrameContext<'_>) {} } F }
|
||||
);
|
||||
pipeline.add_pass("bloom", Bloom); // a post effect (Stage 13)
|
||||
pipeline.set_enabled("bloom", false); // turn it off, keep it registered
|
||||
pipeline.insert_before("forward", "shadows",
|
||||
# { struct S; impl oxide_engine::render::RenderPass for S { fn run(&mut self, _f: &mut oxide_engine::render::FrameContext<'_>) {} } S }
|
||||
); // slot a pass into a fixed position
|
||||
pipeline.remove("bloom"); // drop it entirely
|
||||
```
|
||||
|
||||
A stylized game ships a pipeline with no post passes (and pays nothing for them);
|
||||
a realistic game enables shadows, SSAO, bloom, tone-mapping. Same engine, same
|
||||
renderer core — different pass list.
|
||||
|
||||
## Windowed vs offscreen clearing
|
||||
|
||||
The window runner already clears the surface to the configured clear color before
|
||||
`App::render` runs, so the **editor and windowed examples use a forward-only
|
||||
pipeline** (no `ClearPass`) and let the runner clear. `RenderPipeline::forward`
|
||||
(Clear + Forward) is for offscreen/standalone rendering where nothing else
|
||||
clears the target — e.g. the headless render tests.
|
||||
|
||||
## Camera layer visibility
|
||||
|
||||
A [`Camera`](rendering.md) carries a `visibility` [`LayerMask`](layers.md): it
|
||||
renders an entity only if the entity's [`Layer`] is in that mask (default
|
||||
[`LayerMask::ALL`] — sees everything). The host applies it while gathering
|
||||
drawables:
|
||||
|
||||
```rust
|
||||
# use oxide_engine::prelude::*;
|
||||
# use oxide_engine::layer::Layer;
|
||||
# let scene = Scene::new();
|
||||
# let camera = Camera::default();
|
||||
# let entity = scene.entities().next();
|
||||
# if let Some(entity) = entity {
|
||||
let layer = scene.get::<Layer>(entity).map(|l| *l).unwrap_or_default();
|
||||
if camera.sees(layer) {
|
||||
// include this entity in the draw list
|
||||
}
|
||||
# }
|
||||
```
|
||||
|
||||
This is how a minimap camera, a first-person view-model camera, or editor-only
|
||||
gizmo layers are kept to their own cameras.
|
||||
|
||||
[`RenderPipeline`]: ../engine/src/render/pipeline.rs
|
||||
[`RenderPass`]: ../engine/src/render/pipeline.rs
|
||||
[`FrameContext`]: ../engine/src/render/pipeline.rs
|
||||
[`ClearPass`]: ../engine/src/render/pipeline.rs
|
||||
[`ForwardPass`]: ../engine/src/render/pipeline.rs
|
||||
[`Layer`]: ../engine/src/layer/components.rs
|
||||
Reference in New Issue
Block a user