rustial-renderer-wgpu 0.0.1

Pure WGPU renderer for the rustial 2.5D map engine
Documentation
//! Sky / atmosphere render pipeline.
//!
//! A fullscreen-triangle pipeline that renders a Rayleigh + Mie
//! atmospheric scattering sky behind all scene geometry.  The sky
//! is drawn at depth = 1.0 with depth-test but no depth-write so
//! scene geometry always wins.

use bytemuck::{Pod, Zeroable};

/// GPU-side uniform for the sky shader.
///
/// Layout (std140):
/// - `inv_view_proj`: mat4x4<f32>
/// - `sun_dir`:       vec4<f32>  (xyz = direction toward sun, w = intensity)
/// - `rayleigh_tint`: vec4<f32>  (rgb = tint, w = sky_enabled 1/0)
/// - `mie_tint`:      vec4<f32>  (rgb = tint, w = opacity)
#[repr(C)]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub struct SkyUniform {
    /// Inverse view-projection matrix for ray unprojection.
    pub inv_view_proj: [[f32; 4]; 4],
    /// Sun direction (xyz) and intensity (w).
    pub sun_dir: [f32; 4],
    /// Rayleigh tint (rgb) and sky-enabled flag (w).
    pub rayleigh_tint: [f32; 4],
    /// Mie tint (rgb) and opacity (w).
    pub mie_tint: [f32; 4],
}

impl Default for SkyUniform {
    fn default() -> Self {
        Self {
            inv_view_proj: glam::Mat4::IDENTITY.to_cols_array_2d(),
            sun_dir: [0.0, 0.707, 0.707, 0.0],
            rayleigh_tint: [1.0, 1.0, 1.0, 0.0], // w=0 → disabled
            mie_tint: [1.0, 1.0, 1.0, 1.0],
        }
    }
}

/// The sky render pipeline.
pub struct SkyPipeline {
    /// Render pipeline for the fullscreen sky triangle.
    pub pipeline: wgpu::RenderPipeline,
    /// Bind group layout for the sky uniform (group 0).
    pub uniform_bind_group_layout: wgpu::BindGroupLayout,
}

impl SkyPipeline {
    /// Create the sky pipeline.
    pub fn new(device: &wgpu::Device, surface_format: wgpu::TextureFormat) -> Self {
        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("sky_shader"),
            source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/sky.wgsl").into()),
        });

        let uniform_bind_group_layout =
            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
                label: Some("sky_uniform_bgl"),
                entries: &[wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
                    ty: wgpu::BindingType::Buffer {
                        ty: wgpu::BufferBindingType::Uniform,
                        has_dynamic_offset: false,
                        min_binding_size: wgpu::BufferSize::new(
                            std::mem::size_of::<SkyUniform>() as u64
                        ),
                    },
                    count: None,
                }],
            });

        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
            label: Some("sky_pipeline_layout"),
            bind_group_layouts: &[&uniform_bind_group_layout],
            push_constant_ranges: &[],
        });

        // Alpha blending so the sky can be partially transparent.
        let blend = wgpu::BlendState {
            color: wgpu::BlendComponent {
                src_factor: wgpu::BlendFactor::SrcAlpha,
                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
                operation: wgpu::BlendOperation::Add,
            },
            alpha: wgpu::BlendComponent {
                src_factor: wgpu::BlendFactor::One,
                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
                operation: wgpu::BlendOperation::Add,
            },
        };

        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("sky_pipeline"),
            layout: Some(&pipeline_layout),
            vertex: wgpu::VertexState {
                module: &shader,
                entry_point: Some("vs_main"),
                buffers: &[], // fullscreen triangle from vertex_index
                compilation_options: wgpu::PipelineCompilationOptions::default(),
            },
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: Some("fs_main"),
                targets: &[Some(wgpu::ColorTargetState {
                    format: surface_format,
                    blend: Some(blend),
                    write_mask: wgpu::ColorWrites::ALL,
                })],
                compilation_options: wgpu::PipelineCompilationOptions::default(),
            }),
            primitive: wgpu::PrimitiveState {
                topology: wgpu::PrimitiveTopology::TriangleList,
                front_face: wgpu::FrontFace::Ccw,
                cull_mode: None,
                ..Default::default()
            },
            // Depth: test against existing (LessEqual) but do NOT write.
            // The sky triangle sits at z=1.0 (far plane) so only pixels
            // not covered by scene geometry are shaded.
            depth_stencil: Some(wgpu::DepthStencilState {
                format: wgpu::TextureFormat::Depth32Float,
                depth_write_enabled: false,
                depth_compare: wgpu::CompareFunction::LessEqual,
                stencil: wgpu::StencilState::default(),
                bias: wgpu::DepthBiasState::default(),
            }),
            multisample: wgpu::MultisampleState::default(),
            multiview: None,
            cache: None,
        });

        Self {
            pipeline,
            uniform_bind_group_layout,
        }
    }
}