Skip to main content

scenix_material/
traits.rs

1/// Material alpha behavior.
2#[derive(Clone, Copy, Debug, PartialEq)]
3#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
4pub enum AlphaMode {
5    /// Fully opaque material.
6    Opaque,
7    /// Alpha-tested material using the stored cutoff threshold.
8    Mask(f32),
9    /// Alpha-blended material.
10    Blend,
11}
12
13impl AlphaMode {
14    /// Returns the pipeline-level alpha mode.
15    #[inline]
16    pub const fn pipeline_alpha(self) -> PipelineAlphaMode {
17        match self {
18            Self::Opaque => PipelineAlphaMode::Opaque,
19            Self::Mask(_) => PipelineAlphaMode::Mask,
20            Self::Blend => PipelineAlphaMode::Blend,
21        }
22    }
23
24    /// Returns whether this mode needs transparent sorting and blending.
25    #[inline]
26    pub const fn is_transparent(self) -> bool {
27        matches!(self, Self::Blend)
28    }
29
30    /// Returns the alpha-test cutoff for masked materials.
31    #[inline]
32    pub const fn cutoff(self) -> Option<f32> {
33        match self {
34            Self::Mask(cutoff) => Some(cutoff),
35            Self::Opaque | Self::Blend => None,
36        }
37    }
38}
39
40impl Default for AlphaMode {
41    #[inline]
42    fn default() -> Self {
43        Self::Opaque
44    }
45}
46
47/// Pipeline-level alpha mode. This is hashable because the cutoff value is not
48/// part of render-pipeline selection.
49#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51pub enum PipelineAlphaMode {
52    /// Fully opaque pipeline.
53    #[default]
54    Opaque,
55    /// Alpha-tested pipeline.
56    Mask,
57    /// Alpha-blended pipeline.
58    Blend,
59}
60
61/// Built-in shader family used by a material.
62#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64pub enum ShaderKind {
65    /// Physically based metallic-roughness shader.
66    #[default]
67    Pbr,
68    /// Advanced physical surface shader.
69    Physical,
70    /// Constant-color unlit shader.
71    Unlit,
72    /// Diffuse Lambert shader.
73    Lambert,
74    /// Cel/toon shader.
75    Toon,
76    /// Surface-normal debug shader.
77    Normal,
78    /// Wireframe shader.
79    Wireframe,
80    /// Depth-only shader.
81    Depth,
82    /// Line shader.
83    Line,
84    /// Point-sprite shader.
85    Points,
86    /// User-provided WGSL shader sources, identified by a stable hash.
87    Custom(u64),
88}
89
90/// Compact renderer pipeline selector.
91#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93pub struct PipelineKey {
94    /// Shader family.
95    pub shader: ShaderKind,
96    /// Pipeline-level alpha mode.
97    pub alpha: PipelineAlphaMode,
98    /// Material feature flags.
99    pub feature_bits: u64,
100}
101
102impl PipelineKey {
103    /// Creates a pipeline key.
104    #[inline]
105    pub const fn new(shader: ShaderKind, alpha: PipelineAlphaMode, feature_bits: u64) -> Self {
106        Self {
107            shader,
108            alpha,
109            feature_bits,
110        }
111    }
112
113    /// Returns a key with one feature flag enabled.
114    #[inline]
115    pub const fn with_feature(mut self, feature: u64) -> Self {
116        self.feature_bits |= feature;
117        self
118    }
119
120    /// Returns whether the feature flag is enabled.
121    #[inline]
122    pub const fn has_feature(self, feature: u64) -> bool {
123        self.feature_bits & feature != 0
124    }
125}
126
127/// Material is rendered double-sided.
128pub const FEATURE_DOUBLE_SIDED: u64 = 1 << 0;
129/// Base color texture is bound.
130pub const FEATURE_ALBEDO_TEXTURE: u64 = 1 << 1;
131/// Metallic-roughness texture is bound.
132pub const FEATURE_METALLIC_ROUGHNESS_TEXTURE: u64 = 1 << 2;
133/// Normal map is bound.
134pub const FEATURE_NORMAL_TEXTURE: u64 = 1 << 3;
135/// Occlusion texture is bound.
136pub const FEATURE_OCCLUSION_TEXTURE: u64 = 1 << 4;
137/// Emissive texture is bound.
138pub const FEATURE_EMISSIVE_TEXTURE: u64 = 1 << 5;
139/// Gradient/ramp texture is bound.
140pub const FEATURE_GRADIENT_TEXTURE: u64 = 1 << 6;
141/// Clearcoat lobe is active.
142pub const FEATURE_CLEARCOAT: u64 = 1 << 7;
143/// Sheen lobe is active.
144pub const FEATURE_SHEEN: u64 = 1 << 8;
145/// Transmission path is active.
146pub const FEATURE_TRANSMISSION: u64 = 1 << 9;
147/// Iridescence path is active.
148pub const FEATURE_IRIDESCENCE: u64 = 1 << 10;
149/// Flat normal shading is active.
150pub const FEATURE_FLAT_SHADING: u64 = 1 << 11;
151/// Normals are evaluated in world space.
152pub const FEATURE_WORLD_SPACE: u64 = 1 << 12;
153/// Wireframe rendering path is active.
154pub const FEATURE_WIREFRAME: u64 = 1 << 13;
155/// Dashed line path is active.
156pub const FEATURE_DASHED: u64 = 1 << 14;
157/// Point size attenuation is active.
158pub const FEATURE_SIZE_ATTENUATION: u64 = 1 << 15;
159/// Custom shader has texture bindings.
160pub const FEATURE_CUSTOM_TEXTURES: u64 = 1 << 16;
161/// Material expects vertex colors.
162pub const FEATURE_VERTEX_COLORS: u64 = 1 << 17;
163/// Toon outline path is active.
164pub const FEATURE_OUTLINE: u64 = 1 << 18;
165
166/// CPU-side material description with no GPU dependency.
167pub trait Material: Send + Sync + 'static {
168    /// Returns the renderer pipeline selector for this material state.
169    fn pipeline_key(&self) -> PipelineKey;
170
171    /// Returns whether the material should be rendered in a transparent path.
172    fn is_transparent(&self) -> bool;
173
174    /// Returns whether back-face culling should be disabled.
175    fn double_sided(&self) -> bool;
176
177    /// Returns the alpha-test cutoff for masked materials.
178    fn alpha_cutoff(&self) -> Option<f32>;
179}
180
181#[inline]
182pub(crate) const fn double_sided_bit(double_sided: bool) -> u64 {
183    if double_sided {
184        FEATURE_DOUBLE_SIDED
185    } else {
186        0
187    }
188}
189
190#[inline]
191pub(crate) const fn option_texture_bit<T>(texture: &Option<T>, feature: u64) -> u64 {
192    if texture.is_some() { feature } else { 0 }
193}
194
195/// Stable FNV-1a hash for shader source identity.
196#[inline]
197pub(crate) fn stable_shader_id(vertex_wgsl: &str, fragment_wgsl: &str) -> u64 {
198    const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
199    const PRIME: u64 = 0x0000_0100_0000_01b3;
200
201    let mut hash = OFFSET;
202    for byte in vertex_wgsl.as_bytes() {
203        hash ^= *byte as u64;
204        hash = hash.wrapping_mul(PRIME);
205    }
206    hash ^= 0xff;
207    hash = hash.wrapping_mul(PRIME);
208    for byte in fragment_wgsl.as_bytes() {
209        hash ^= *byte as u64;
210        hash = hash.wrapping_mul(PRIME);
211    }
212    hash
213}