Skip to content

Conversation

@momoluna444
Copy link
Contributor

Objective

To simplify the effort required to create and add a custom mesh pipeline.

Replaces #21880
Closes #21127

Solution

Add MeshPassPlugin to handle the extract, specialize, and queue stages required to create a new mesh pipeline.

Usage:

Details
#[derive(Clone, Copy, Default, Component, ExtractComponent)]
struct OutlinePass;

impl MeshPass for OutlinePass {
    type ViewKeySource = MainPass;
    type Specializer = MaterialPipelineSpecializer;
    type PhaseItems = (OutlineOpaque3d, ...);
}

#[derive(BinnedPhaseItem)]
struct OutlineOpaque3d(#[phase_item(skip(PhaseItemExt))] Opaque3d);

impl PhaseItemExt for OutlineOpaque3d {
    type PhaseFamily = BinnedPhaseFamily<Self>;
    type ExtractCondition = NoExtractCondition;
    type RenderCommand = DrawMaterial;
    const PHASE_TYPES: RenderPhaseType = RenderPhaseType::Opaque;
}

impl Material for OutlineMaterial {
	fn shaders() -> PassShaders {
        let mut pass_shaders = PassShaders::default();
        pass_shaders.extend([
            (MainPass::id(), ShaderSet::default()),
            (OutlinePass::id(), ShaderSet { vertex: PATH.into(), fragment: PATH.into() }),
        ]);
        pass_shaders
    }
}

fn main() {
    App::new()
        .add_plugins((MeshPassPlugin::<OutlinePass>::default(), ...))
        ...
}

For more details, please refer to custom_mesh_pass.rs.

In this version of MeshPass, we have removed the cumbersome Option<Res<PhasesN<MP>>>. Instead, we utilize all_tuples! to implement the required methods directly on the QueryItem of (&mut RenderPhase, ...) and call them within the queue_material_meshes system. This approach nearly eliminates all boilerplate code while still maintaining static dispatch.

Yes, this approach is based on making XXXRenderPhase a component. The main reason is: while we could do something similar with (ResMut<ViewXXXRenderPhases<T>>, ...), we would need to handle multiple layers of conversion from (ResMut<ViewXXXRenderPhases<T>>, ...) to (Option<&mut XXXRenderPhase<T>>, ...). To address this, we would have to write more traits and macros, which would be difficult to maintain. The flat XXXRenderPhase structure avoids this issue, making the implementation more straightforward and simpler.

Testing

I ran the examples for the modules affected by this migration and did not observe any breaks.

Showcase

Details Screenshot From 2025-12-23 21-54-00

@Zeophlite Zeophlite added A-Rendering Drawing game state to the screen M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide D-Complex Quite challenging from either a design or technical perspective. Ask for help! M-Release-Note Work that should be called out in the blog due to impact S-Needs-Review Needs reviewer attention (from anyone!) to move forward S-Needs-SME Decision or review from an SME is required labels Dec 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Rendering Drawing game state to the screen D-Complex Quite challenging from either a design or technical perspective. Ask for help! M-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide M-Release-Note Work that should be called out in the blog due to impact S-Needs-Review Needs reviewer attention (from anyone!) to move forward S-Needs-SME Decision or review from an SME is required

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Need a simpler way to add custom prepasses (light camera & main camera)

2 participants