bevy_lit 0.9.1

A lighting 2d library for Bevy
Documentation
use bevy::{
    asset::{load_embedded_asset, Handle},
    core_pipeline::FullscreenShader,
    prelude::*,
    render::{
        render_resource::{
            binding_types::{sampler, texture_2d, uniform_buffer},
            BindGroupLayout, BindGroupLayoutEntries, BindGroupLayoutEntry, CachedRenderPipelineId,
            ColorTargetState, ColorWrites, FragmentState, PipelineCache, RenderPipelineDescriptor,
            SamplerBindingType, ShaderStages, ShaderType, SpecializedRenderPipeline,
            SpecializedRenderPipelines, TextureFormat, TextureSampleType,
        },
        renderer::RenderDevice,
        sync_world::RenderEntity,
        view::{ExtractedView, ViewTarget, ViewUniform},
        Extract,
    },
    shader::Shader,
};

use crate::settings::{AmbientLight2d, Lighting2dSettings, PenetrationSettings, RaymarchSettings};

#[derive(Resource)]
pub struct Lighting2dPostProcessPipelines {
    pub penetration_layout: BindGroupLayout,
    pub penetration_pipeline: CachedRenderPipelineId,
    pub blur_layout: BindGroupLayout,
    pub blur_pipeline: CachedRenderPipelineId,
}

fn create_post_process_pipeline(
    render_device: &RenderDevice,
    pipeline_cache: &PipelineCache,
    fullscreen_shader: &FullscreenShader,
    label: &'static str,
    shader: Handle<Shader>,
    entries: &[BindGroupLayoutEntry],
) -> (BindGroupLayout, CachedRenderPipelineId) {
    let layout = render_device.create_bind_group_layout(
        &(String::from(label) + "_bind_group_layout") as &str,
        entries,
    );

    let pipeline = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
        label: Some((String::from(label) + "_pipeline").into()),
        layout: vec![layout.clone()],
        vertex: fullscreen_shader.to_vertex_state(),
        fragment: Some(FragmentState {
            shader,
            shader_defs: vec![],
            entry_point: Some("fragment".into()),
            targets: vec![Some(ColorTargetState {
                format: TextureFormat::Rgba16Float,
                blend: None,
                write_mask: ColorWrites::ALL,
            })],
        }),
        push_constant_ranges: vec![],
        primitive: Default::default(),
        depth_stencil: None,
        multisample: Default::default(),
        zero_initialize_workgroup_memory: false,
    });

    (layout, pipeline)
}

pub fn init_post_process_pipelines(
    mut commands: Commands,
    render_device: Res<RenderDevice>,
    pipeline_cache: Res<PipelineCache>,
    asset_server: Res<AssetServer>,
    fullscreen_shader: Res<FullscreenShader>,
) {
    let (penetration_layout, penetration_pipeline) = create_post_process_pipeline(
        &render_device,
        &pipeline_cache,
        &fullscreen_shader,
        "penetration",
        load_embedded_asset!(asset_server.as_ref(), "penetration.wgsl"),
        &BindGroupLayoutEntries::sequential(
            ShaderStages::FRAGMENT,
            (
                uniform_buffer::<ViewUniform>(true),
                uniform_buffer::<ExtractedLighting2dSettings>(true),
                texture_2d(TextureSampleType::Float { filterable: true }),
                texture_2d(TextureSampleType::Float { filterable: true }),
                sampler(SamplerBindingType::Filtering),
            ),
        ),
    );

    let (blur_layout, blur_pipeline) = create_post_process_pipeline(
        &render_device,
        &pipeline_cache,
        &fullscreen_shader,
        "blur",
        load_embedded_asset!(asset_server.as_ref(), "blur.wgsl"),
        &BindGroupLayoutEntries::sequential(
            ShaderStages::FRAGMENT,
            (
                uniform_buffer::<ExtractedLighting2dSettings>(true),
                uniform_buffer::<IVec2>(false),
                texture_2d(TextureSampleType::Float { filterable: true }),
            ),
        ),
    );

    commands.insert_resource(Lighting2dPostProcessPipelines {
        penetration_layout,
        penetration_pipeline,
        blur_layout,
        blur_pipeline,
    });
}

#[derive(Resource)]
pub struct Lighting2dCompositePipeline {
    pub layout: BindGroupLayout,
    pub shader: Handle<Shader>,
    pub fullscreen_shader: FullscreenShader,
}

pub fn init_lighting2d_composite_pipeline(
    mut commands: Commands,
    render_device: Res<RenderDevice>,
    asset_server: Res<AssetServer>,
    fullscreen_shader: Res<FullscreenShader>,
) {
    commands.insert_resource(Lighting2dCompositePipeline {
        shader: load_embedded_asset!(asset_server.as_ref(), "composite.wgsl"),
        fullscreen_shader: fullscreen_shader.clone(),
        layout: render_device.create_bind_group_layout(
            "composite_bind_group_layout",
            &BindGroupLayoutEntries::sequential(
                ShaderStages::FRAGMENT,
                (
                    uniform_buffer::<ExtractedLighting2dSettings>(true),
                    texture_2d(TextureSampleType::Float { filterable: true }),
                    texture_2d(TextureSampleType::Float { filterable: true }),
                    sampler(SamplerBindingType::Filtering),
                ),
            ),
        ),
    });
}

#[derive(Eq, PartialEq, Hash, Clone, Copy)]
pub struct Lighting2dPipelineKey {
    pub hdr: bool,
    pub msaa_samples: u32,
}

impl SpecializedRenderPipeline for Lighting2dCompositePipeline {
    type Key = Lighting2dPipelineKey;

    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
        RenderPipelineDescriptor {
            label: Some("composite_pipeline".into()),
            layout: vec![self.layout.clone()],
            vertex: self.fullscreen_shader.to_vertex_state(),
            fragment: Some(FragmentState {
                shader: self.shader.clone(),
                shader_defs: vec![],
                entry_point: Some("fragment".into()),
                targets: vec![Some(ColorTargetState {
                    format: if key.hdr {
                        ViewTarget::TEXTURE_FORMAT_HDR
                    } else {
                        TextureFormat::bevy_default()
                    },
                    blend: None,
                    write_mask: ColorWrites::ALL,
                })],
            }),
            primitive: Default::default(),
            depth_stencil: None,
            multisample: Default::default(),
            push_constant_ranges: vec![],
            zero_initialize_workgroup_memory: false,
        }
    }
}

#[derive(Component, Clone, ShaderType)]
pub struct ExtractedLighting2dSettings {
    #[size(16)]
    pub raymarch: RaymarchSettings,
    pub penetration: PenetrationSettings,
    pub ambient_light: LinearRgba,
    pub scale: f32,
    pub tint_occluders: u32,
    pub edge_intensity: f32,
    pub blur: i32,
}

pub fn extract_lighting2d_settings(
    mut commands: Commands,
    ambient_light_query: Extract<
        Query<(RenderEntity, &Lighting2dSettings, &AmbientLight2d), With<Camera2d>>,
    >,
) {
    for (e, settings, ambient_light) in &ambient_light_query {
        commands.entity(e).insert(ExtractedLighting2dSettings {
            scale: settings.scale,
            ambient_light: ambient_light.color.to_linear() * ambient_light.intensity,
            raymarch: settings.raymarch.clone(),
            penetration: settings.penetration.clone(),
            tint_occluders: if settings.tint_occluders { 1 } else { 0 },
            edge_intensity: settings.edge_intensity,
            blur: settings.blur as i32,
        });
    }
}

#[derive(Component, Deref)]
pub struct Lighting2dCompositePipelineId(pub CachedRenderPipelineId);

pub fn prepare_composite_pipelines(
    mut commands: Commands,
    pipeline_cache: Res<PipelineCache>,
    mut composite_pipelines: ResMut<SpecializedRenderPipelines<Lighting2dCompositePipeline>>,
    composite_pipeline: Res<Lighting2dCompositePipeline>,
    views_query: Query<(Entity, &ExtractedView, &Msaa), With<ExtractedLighting2dSettings>>,
) {
    for (entity, view, msaa) in &views_query {
        commands
            .entity(entity)
            .insert(Lighting2dCompositePipelineId(
                composite_pipelines.specialize(
                    &pipeline_cache,
                    &composite_pipeline,
                    Lighting2dPipelineKey {
                        hdr: view.hdr,
                        msaa_samples: msaa.samples(),
                    },
                ),
            ));
    }
}