bevy_dither_post_process/
lib.rs

1#![warn(missing_docs)]
2
3//! A plugin for the Bevy game engine which provides a black and white dither post-process effect
4//! using Bayer ordered dithering.
5
6use bevy::{
7    asset::embedded_asset,
8    core_pipeline::core_3d::graph::{Core3d, Node3d},
9    prelude::*,
10    render::{
11        extract_component::ExtractComponentPlugin,
12        render_graph::{RenderGraphApp, ViewNodeRunner},
13        render_resource::{PipelineCache, SpecializedRenderPipelines, TextureFormat},
14        view::{ExtractedView, ViewTarget},
15        Render, RenderApp, RenderSet,
16    },
17};
18use components::DitherPostProcessPipelineId;
19use resources::{DitherPostProcessPipeline, DitherPostProcessingPipelineKey};
20
21use crate::components::DitherPostProcessSettings;
22
23pub use nodes::DitherRenderLabel;
24
25/// Plugin which provides dither post-processing functionality
26pub struct DitherPostProcessPlugin;
27
28/// Components used by this plugin
29pub mod components;
30
31mod nodes;
32mod resources;
33
34impl Plugin for DitherPostProcessPlugin {
35    fn build(&self, app: &mut App) {
36        embedded_asset!(app, "../assets/shaders/dither_post_process.wgsl");
37
38        app.add_plugins((ExtractComponentPlugin::<DitherPostProcessSettings>::default(),));
39
40        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
41            return;
42        };
43
44        render_app
45            .add_systems(
46                Render,
47                prepare_post_processing_pipelines.in_set(RenderSet::Prepare),
48            )
49            .init_resource::<SpecializedRenderPipelines<DitherPostProcessPipeline>>()
50            .add_render_graph_node::<ViewNodeRunner<nodes::DitherRenderNode>>(
51                Core3d,
52                nodes::DitherRenderLabel,
53            )
54            .add_render_graph_edges(
55                Core3d,
56                (
57                    Node3d::Tonemapping,
58                    nodes::DitherRenderLabel,
59                    Node3d::EndMainPassPostProcessing,
60                ),
61            );
62    }
63
64    fn finish(&self, app: &mut App) {
65        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
66            return;
67        };
68
69        render_app.init_resource::<resources::DitherPostProcessPipeline>();
70    }
71}
72
73fn prepare_post_processing_pipelines(
74    mut commands: Commands,
75    pipeline_cache: Res<PipelineCache>,
76    mut pipelines: ResMut<SpecializedRenderPipelines<DitherPostProcessPipeline>>,
77    post_processing_pipeline: Res<DitherPostProcessPipeline>,
78    views: Query<(Entity, &ExtractedView), With<DitherPostProcessSettings>>,
79) {
80    for (entity, view) in views.iter() {
81        let pipeline_id = pipelines.specialize(
82            &pipeline_cache,
83            &post_processing_pipeline,
84            DitherPostProcessingPipelineKey {
85                texture_format: if view.hdr {
86                    ViewTarget::TEXTURE_FORMAT_HDR
87                } else {
88                    TextureFormat::bevy_default()
89                },
90            },
91        );
92
93        commands
94            .entity(entity)
95            .insert(DitherPostProcessPipelineId(pipeline_id));
96    }
97}