codecraft 0.1.1

A minimalist 3D game engine built on parts of Bevy (ECS, color) with wgpu and winit: OpenPBR materials, clustered lighting, an immediate-mode UI, audio and gamepad haptics
Documentation
//! The OpenPBR Surface material model.
//!
//! OpenPBR Surface is the Academy Software Foundation / Adobe / Autodesk
//! specification for a single uber-surface: one shader with a diffuse and
//! metallic base, a specular lobe over it, transmission, subsurface, fuzz, a
//! coat, thin film and emission, each with a weight that turns it off at
//! zero. It is what a material *is* here, rather than the handful of numbers
//! a particular shader happens to want -- see [`crate::material::Material`]
//! for those, which is what the Blinn-Phong shader still reads today.
//!
//! [`surface::OpenPbrSurface`] is a strongly typed model of the
//! `ND_open_pbr_surface_surfaceshader` MaterialX nodedef, version 1.1.1, with
//! all 41 of its inputs. [`Default`] gives the nodedef's own defaults, so a
//! material starts as the spec's neutral dielectric rather than as zeroes.
//!
//! # This is generated code
//!
//! `surface.rs` comes out of the `openpbr` project's `openpbr-gen`, which
//! reads `mtlx/open_pbr_surface.mtlx` and writes the struct, the [`Default`]
//! impl, the [`surface::INPUTS`] metadata table and the reflection helpers.
//! Do not edit it by hand: regenerate it there and copy it back.
//!
//! Exactly one line differs from the generator's output, and has to be
//! reapplied after a regeneration:
//!
//! ```text
//! -use crate::types::{Color3, InputError, InputSpec, Value, ValueType, Vector3};
//! +use super::types::{Color3, InputError, InputSpec, Value, ValueType, Vector3};
//! ```
//!
//! `types.rs` is copied across unchanged.

pub mod energy;
pub mod gpu;
pub mod surface;
pub mod types;

pub use energy::EnergyTables;
pub use gpu::{Material, material_of, material_of_scaled};
pub use surface::{INPUTS, OpenPbrSurface};
pub use types::{Color3, InputError, InputSpec, Value, ValueType, Vector3};

use bevy_color::LinearRgba;
use glam::Vec3;

/// A MaterialX `color3` is linear by definition, which is the same thing the
/// renderer means by [`LinearRgba`]: no conversion, just a change of name.
impl From<Color3> for LinearRgba {
    fn from(c: Color3) -> Self {
        LinearRgba::rgb(c.r, c.g, c.b)
    }
}

impl From<LinearRgba> for Color3 {
    fn from(c: LinearRgba) -> Self {
        Color3::new(c.red, c.green, c.blue)
    }
}

impl From<Color3> for Vec3 {
    fn from(c: Color3) -> Self {
        Vec3::new(c.r, c.g, c.b)
    }
}

impl From<Vector3> for Vec3 {
    fn from(v: Vector3) -> Self {
        Vec3::new(v.x, v.y, v.z)
    }
}

impl From<Vec3> for Vector3 {
    fn from(v: Vec3) -> Self {
        Vector3::new(v.x, v.y, v.z)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn a_new_surface_is_the_spec_default_not_zeroes() {
        let surface = OpenPbrSurface::default();
        assert_eq!(surface.base_weight, 1.0);
        assert_eq!(surface.base_color, Color3::new(0.8, 0.8, 0.8));
        assert_eq!(surface.specular_roughness, 0.3);
        assert_eq!(surface.specular_ior, 1.5);
        // Every lobe past the base starts switched off.
        assert_eq!(surface.transmission_weight, 0.0);
        assert_eq!(surface.subsurface_weight, 0.0);
        assert_eq!(surface.fuzz_weight, 0.0);
        assert_eq!(surface.coat_weight, 0.0);
        assert_eq!(surface.thin_film_weight, 0.0);
        assert_eq!(surface.emission_luminance, 0.0);
    }

    #[test]
    fn every_input_is_reachable_by_name() {
        let surface = OpenPbrSurface::default();
        assert_eq!(INPUTS.len(), 41);
        for name in OpenPbrSurface::INPUT_NAMES {
            assert!(
                OpenPbrSurface::input_spec(name).is_some(),
                "{name} has no spec",
            );
            assert!(surface.get(name).is_ok(), "{name} cannot be read");
        }
    }

    #[test]
    fn a_colour_crosses_into_the_renderer_unchanged() {
        // Both sides are linear, so this is a rename and not a conversion.
        let linear: LinearRgba = Color3::new(0.25, 0.5, 0.75).into();
        assert_eq!(linear, LinearRgba::rgb(0.25, 0.5, 0.75));
        assert_eq!(Color3::from(linear), Color3::new(0.25, 0.5, 0.75));
    }

    #[test]
    fn setting_an_input_by_name_reaches_the_field() {
        let mut surface = OpenPbrSurface::default();
        surface.set_from_str("base_metalness", "1.0").unwrap();
        assert_eq!(surface.base_metalness, 1.0);
        assert!(!surface.is_default("base_metalness").unwrap());

        surface.reset("base_metalness").unwrap();
        assert_eq!(surface.base_metalness, 0.0);
        assert!(surface.changed_inputs().is_empty());
    }
}

/// The OpenPBR shading library as one WGSL program, in dependency order.
///
/// WGSL has no `#include`, so a program is a string and assembling one is
/// concatenation -- the same thing the reference renderer does, in the same
/// order, because the order is what the port was checked against.
///
/// It declares no entry point and reads no camera: a pass that wants to shade
/// with it appends its own code, sets [`mat`](self) and calls
/// `openpbr_prepare` then `openpbr_bsdf_evaluate`.
pub const SHADER: &str = concat!(
    include_str!("wgsl/material.wgsl"),
    "\n",
    include_str!("wgsl/common.wgsl"),
    "\n",
    include_str!("wgsl/fuzz_brdf.wgsl"),
    "\n",
    include_str!("wgsl/coat_brdf.wgsl"),
    "\n",
    include_str!("wgsl/thin_film.wgsl"),
    "\n",
    include_str!("wgsl/specular_brdf.wgsl"),
    "\n",
    include_str!("wgsl/specular_btdf.wgsl"),
    "\n",
    include_str!("wgsl/metal_brdf.wgsl"),
    "\n",
    include_str!("wgsl/diffuse.wgsl"),
    "\n",
    include_str!("wgsl/openpbr_surface.wgsl"),
);