use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
pub enum AlphaMode {
#[default]
Opaque,
Mask { cutoff: f32 },
Blend,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum MaterialClass {
PbrLit,
PbrLitUntextured,
PbrEmissive,
PbrMasked,
PbrTransparent,
Unlit,
SpriteLit2D,
SpriteUnlit2D,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RtQuality {
Full,
ShadowsOnly,
GiOnly,
}
impl Default for RtQuality {
fn default() -> Self {
Self::Full
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SceneDreamMode {
PbrDefault,
PbrLightweight,
Unlit,
SpriteLit2D,
SpriteUnlit2D,
PbrRayTraced(RtQuality),
Custom,
}
impl Default for SceneDreamMode {
fn default() -> Self {
Self::PbrDefault
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TonemapOperator {
AcesFilmic,
Uncharted2,
Reinhard,
None,
}
impl Default for TonemapOperator {
fn default() -> Self {
Self::AcesFilmic
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PbrMaterial {
pub base_color: [f32; 4],
pub emissive: [f32; 3],
pub roughness: f32,
pub metallic: f32,
pub base_color_tex: Option<u32>,
pub normal_tex: Option<u32>,
pub emissive_tex: Option<u32>,
pub roughness_metalness_tex: Option<u32>,
pub occlusion_tex: Option<u32>,
pub occlusion_strength: f32,
pub normal_scale: f32,
pub emissive_strength: f32,
pub alpha_mode: AlphaMode,
pub double_sided: bool,
}
pub type LitMaterialDesc = PbrMaterial;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum DreamLightingQuality {
Low,
Medium,
High,
Ultra,
Cinematic,
}
impl Default for DreamLightingQuality {
fn default() -> Self {
Self::Medium
}
}
impl DreamLightingQuality {
pub fn auto_detect(ray_tracing: bool, max_storage_bytes: u64, max_tlas_instances: u32) -> Self {
if !ray_tracing {
if max_storage_bytes >= 64 * 1024 * 1024 {
return Self::Medium;
}
return Self::Low;
}
if max_tlas_instances >= 16_000_000 {
Self::Ultra
} else {
Self::High
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DreamLightingConfig {
pub rt_gi_enabled: bool,
pub rt_shadows_enabled: bool,
pub ssgi_enabled: bool,
pub ssao_enabled: bool,
pub ssr_enabled: bool,
pub taa_enabled: bool,
pub screen_traces_enabled: bool,
pub sharc_hash_capacity: u32,
pub sharc_sparse_rate: f32,
pub sharc_max_bounces: u32,
pub sharc_grid_log_base: f32,
pub denoiser_enabled: bool,
pub denoiser_temporal_alpha: f32,
pub hiz_enabled: bool,
pub motion_vectors_enabled: bool,
pub csm_cascades: u32,
pub reflection_max_roughness: f32,
pub ibl_resolution: u32,
pub volumetric_fog_enabled: bool,
pub dof_enabled: bool,
pub dream_tsr_enabled: bool,
}
impl Default for DreamLightingConfig {
fn default() -> Self {
Self::for_quality(DreamLightingQuality::Medium)
}
}
impl DreamLightingConfig {
pub fn for_quality(quality: DreamLightingQuality) -> Self {
match quality {
DreamLightingQuality::Low => Self {
rt_gi_enabled: false,
rt_shadows_enabled: false,
ssgi_enabled: false,
ssao_enabled: false,
ssr_enabled: false,
taa_enabled: false,
screen_traces_enabled: false,
sharc_hash_capacity: 0,
sharc_sparse_rate: 0.0,
sharc_max_bounces: 0,
sharc_grid_log_base: 2.0,
denoiser_enabled: false,
denoiser_temporal_alpha: 0.0,
hiz_enabled: false,
motion_vectors_enabled: false,
csm_cascades: 2,
reflection_max_roughness: 0.0,
ibl_resolution: 32,
volumetric_fog_enabled: false,
dof_enabled: false,
dream_tsr_enabled: false,
},
DreamLightingQuality::Medium => Self {
rt_gi_enabled: false,
rt_shadows_enabled: false,
ssgi_enabled: true,
ssao_enabled: true,
ssr_enabled: true,
taa_enabled: true,
screen_traces_enabled: false,
sharc_hash_capacity: 0,
sharc_sparse_rate: 0.0,
sharc_max_bounces: 0,
sharc_grid_log_base: 2.0,
denoiser_enabled: false,
denoiser_temporal_alpha: 0.0,
hiz_enabled: false,
motion_vectors_enabled: true,
csm_cascades: 4,
reflection_max_roughness: 0.3,
ibl_resolution: 64,
volumetric_fog_enabled: false,
dof_enabled: false,
dream_tsr_enabled: false,
},
DreamLightingQuality::High => Self {
rt_gi_enabled: true,
rt_shadows_enabled: true,
ssgi_enabled: false,
ssao_enabled: true,
ssr_enabled: true,
taa_enabled: true,
screen_traces_enabled: true,
sharc_hash_capacity: 1 << 18, sharc_sparse_rate: 0.02,
sharc_max_bounces: 2,
sharc_grid_log_base: 2.5,
denoiser_enabled: true,
denoiser_temporal_alpha: 0.80,
hiz_enabled: true,
motion_vectors_enabled: true,
csm_cascades: 0, reflection_max_roughness: 0.4,
ibl_resolution: 128,
volumetric_fog_enabled: false,
dof_enabled: false,
dream_tsr_enabled: false,
},
DreamLightingQuality::Ultra => Self {
rt_gi_enabled: true,
rt_shadows_enabled: true,
ssgi_enabled: false,
ssao_enabled: true,
ssr_enabled: true,
taa_enabled: true,
screen_traces_enabled: true,
sharc_hash_capacity: 1 << 20, sharc_sparse_rate: 0.04,
sharc_max_bounces: 3,
sharc_grid_log_base: 2.0,
denoiser_enabled: true,
denoiser_temporal_alpha: 0.85,
hiz_enabled: true,
motion_vectors_enabled: true,
csm_cascades: 0, reflection_max_roughness: 0.5,
ibl_resolution: 128,
volumetric_fog_enabled: true,
dof_enabled: false,
dream_tsr_enabled: false,
},
DreamLightingQuality::Cinematic => Self {
rt_gi_enabled: true,
rt_shadows_enabled: true,
ssgi_enabled: false,
ssao_enabled: true,
ssr_enabled: true,
taa_enabled: false, screen_traces_enabled: true,
sharc_hash_capacity: 1 << 22, sharc_sparse_rate: 0.08,
sharc_max_bounces: 5,
sharc_grid_log_base: 1.5,
denoiser_enabled: true,
denoiser_temporal_alpha: 0.90,
hiz_enabled: true,
motion_vectors_enabled: true,
csm_cascades: 0, reflection_max_roughness: 0.6,
ibl_resolution: 256,
volumetric_fog_enabled: true,
dof_enabled: true,
dream_tsr_enabled: true,
},
}
}
pub fn infer_quality(&self) -> DreamLightingQuality {
if self.dream_tsr_enabled {
DreamLightingQuality::Cinematic
} else if self.rt_gi_enabled && self.sharc_hash_capacity >= (1 << 20) {
DreamLightingQuality::Ultra
} else if self.rt_gi_enabled {
DreamLightingQuality::High
} else if self.ssgi_enabled {
DreamLightingQuality::Medium
} else {
DreamLightingQuality::Low
}
}
}
impl Default for PbrMaterial {
fn default() -> Self {
Self {
base_color: [1.0, 1.0, 1.0, 1.0],
emissive: [0.0, 0.0, 0.0],
roughness: 0.5,
metallic: 0.0,
base_color_tex: None,
normal_tex: None,
emissive_tex: None,
roughness_metalness_tex: None,
occlusion_tex: None,
occlusion_strength: 1.0,
normal_scale: 1.0,
emissive_strength: 1.0,
alpha_mode: AlphaMode::Opaque,
double_sided: false,
}
}
}
impl PbrMaterial {
pub fn painted_metal() -> Self {
Self {
roughness: 0.4,
metallic: 1.0,
base_color: [0.8, 0.1, 0.1, 1.0],
..Default::default()
}
}
pub fn brushed_steel() -> Self {
Self {
roughness: 0.3,
metallic: 1.0,
base_color: [0.7, 0.7, 0.7, 1.0],
..Default::default()
}
}
pub fn matte_plastic() -> Self {
Self {
roughness: 0.9,
metallic: 0.0,
base_color: [0.8, 0.8, 0.8, 1.0],
..Default::default()
}
}
pub fn glossy_ceramic() -> Self {
Self {
roughness: 0.15,
metallic: 0.0,
base_color: [0.95, 0.95, 0.9, 1.0],
..Default::default()
}
}
pub fn rough_wood() -> Self {
Self {
roughness: 0.85,
metallic: 0.0,
base_color: [0.55, 0.35, 0.2, 1.0],
..Default::default()
}
}
pub fn polished_marble() -> Self {
Self {
roughness: 0.2,
metallic: 0.0,
base_color: [0.95, 0.93, 0.88, 1.0],
..Default::default()
}
}
pub fn wet_stone() -> Self {
Self {
roughness: 0.3,
metallic: 0.0,
base_color: [0.4, 0.4, 0.4, 1.0],
..Default::default()
}
}
pub fn gold() -> Self {
Self {
roughness: 0.25,
metallic: 1.0,
base_color: [1.0, 0.76, 0.33, 1.0],
..Default::default()
}
}
pub fn copper() -> Self {
Self {
roughness: 0.3,
metallic: 1.0,
base_color: [0.95, 0.64, 0.54, 1.0],
..Default::default()
}
}
pub fn rubber() -> Self {
Self {
roughness: 0.95,
metallic: 0.0,
base_color: [0.15, 0.15, 0.15, 1.0],
..Default::default()
}
}
pub fn glass() -> Self {
Self {
roughness: 0.05,
metallic: 0.0,
alpha_mode: AlphaMode::Blend,
base_color: [1.0, 1.0, 1.0, 0.3],
..Default::default()
}
}
pub fn fabric() -> Self {
Self {
roughness: 0.8,
metallic: 0.0,
base_color: [0.6, 0.5, 0.4, 1.0],
..Default::default()
}
}
pub fn skin() -> Self {
Self {
roughness: 0.5,
metallic: 0.0,
base_color: [0.9, 0.7, 0.6, 1.0],
..Default::default()
}
}
pub fn emissive_panel() -> Self {
Self {
emissive: [5.0, 5.0, 5.0],
emissive_strength: 2.0,
base_color: [0.1, 0.1, 0.1, 1.0],
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alpha_mode_default_opaque() {
assert_eq!(AlphaMode::default(), AlphaMode::Opaque);
}
#[test]
fn pbr_material_default() {
let m = PbrMaterial::default();
assert_eq!(m.base_color, [1.0, 1.0, 1.0, 1.0]);
assert_eq!(m.roughness, 0.5);
assert_eq!(m.metallic, 0.0);
assert!(!m.double_sided);
}
#[test]
fn alpha_mask_cutoff() {
let mode = AlphaMode::Mask { cutoff: 0.5 };
if let AlphaMode::Mask { cutoff } = mode {
assert_eq!(cutoff, 0.5);
} else {
panic!("expected Mask");
}
}
#[test]
fn serde_roundtrip() {
let m = PbrMaterial {
roughness: 0.8,
metallic: 1.0,
alpha_mode: AlphaMode::Blend,
..Default::default()
};
let json = serde_json::to_string(&m).unwrap();
let back: PbrMaterial = serde_json::from_str(&json).unwrap();
assert_eq!(m, back);
}
#[test]
fn pbr_material_new_fields_default() {
let m = PbrMaterial::default();
assert_eq!(m.roughness_metalness_tex, None);
assert_eq!(m.occlusion_tex, None);
assert_eq!(m.occlusion_strength, 1.0);
assert_eq!(m.normal_scale, 1.0);
assert_eq!(m.emissive_strength, 1.0);
}
#[test]
fn scene_dream_mode_default() {
assert_eq!(SceneDreamMode::default(), SceneDreamMode::PbrDefault);
}
#[test]
fn rt_quality_default() {
assert_eq!(RtQuality::default(), RtQuality::Full);
}
#[test]
fn scene_dream_mode_ray_traced() {
let mode = SceneDreamMode::PbrRayTraced(RtQuality::Full);
assert_ne!(mode, SceneDreamMode::PbrDefault);
let mode2 = SceneDreamMode::PbrRayTraced(RtQuality::ShadowsOnly);
assert_ne!(mode, mode2);
}
#[test]
fn serde_roundtrip_rt_quality() {
let q = RtQuality::GiOnly;
let json = serde_json::to_string(&q).unwrap();
let back: RtQuality = serde_json::from_str(&json).unwrap();
assert_eq!(q, back);
}
#[test]
fn serde_roundtrip_pbr_ray_traced() {
let mode = SceneDreamMode::PbrRayTraced(RtQuality::Full);
let json = serde_json::to_string(&mode).unwrap();
let back: SceneDreamMode = serde_json::from_str(&json).unwrap();
assert_eq!(mode, back);
}
#[test]
fn tonemap_operator_default() {
assert_eq!(TonemapOperator::default(), TonemapOperator::AcesFilmic);
}
#[test]
fn material_class_variants() {
let variants = [
MaterialClass::PbrLit,
MaterialClass::PbrLitUntextured,
MaterialClass::PbrEmissive,
MaterialClass::PbrMasked,
MaterialClass::PbrTransparent,
MaterialClass::Unlit,
MaterialClass::SpriteLit2D,
MaterialClass::SpriteUnlit2D,
];
assert_eq!(variants.len(), 8);
}
#[test]
fn preset_painted_metal() {
let m = PbrMaterial::painted_metal();
assert_eq!(m.roughness, 0.4);
assert_eq!(m.metallic, 1.0);
assert_eq!(m.base_color, [0.8, 0.1, 0.1, 1.0]);
}
#[test]
fn preset_glass_is_blend() {
let m = PbrMaterial::glass();
assert_eq!(m.alpha_mode, AlphaMode::Blend);
assert_eq!(m.base_color[3], 0.3);
}
#[test]
fn serde_roundtrip_scene_dream_mode() {
let mode = SceneDreamMode::SpriteLit2D;
let json = serde_json::to_string(&mode).unwrap();
let back: SceneDreamMode = serde_json::from_str(&json).unwrap();
assert_eq!(mode, back);
}
#[test]
fn serde_roundtrip_tonemap() {
let op = TonemapOperator::Uncharted2;
let json = serde_json::to_string(&op).unwrap();
let back: TonemapOperator = serde_json::from_str(&json).unwrap();
assert_eq!(op, back);
}
#[test]
fn serde_roundtrip_material_class() {
let cls = MaterialClass::PbrEmissive;
let json = serde_json::to_string(&cls).unwrap();
let back: MaterialClass = serde_json::from_str(&json).unwrap();
assert_eq!(cls, back);
}
#[test]
fn dream_lighting_quality_default() {
assert_eq!(DreamLightingQuality::default(), DreamLightingQuality::Medium);
}
#[test]
fn dream_lighting_config_default_matches_medium() {
let config = DreamLightingConfig::default();
let medium = DreamLightingConfig::for_quality(DreamLightingQuality::Medium);
assert_eq!(config.rt_gi_enabled, medium.rt_gi_enabled);
assert_eq!(config.ssao_enabled, medium.ssao_enabled);
assert_eq!(config.csm_cascades, medium.csm_cascades);
assert!(!config.hiz_enabled); assert_eq!(config.ibl_resolution, 64); assert!((config.reflection_max_roughness - 0.3).abs() < 0.01);
}
#[test]
fn quality_preset_low_no_rt() {
let config = DreamLightingConfig::for_quality(DreamLightingQuality::Low);
assert!(!config.rt_gi_enabled);
assert!(!config.rt_shadows_enabled);
assert!(!config.ssgi_enabled);
assert!(!config.ssao_enabled); assert!(!config.taa_enabled); assert_eq!(config.ibl_resolution, 32);
assert_eq!(config.csm_cascades, 2);
}
#[test]
fn quality_preset_high_has_rt() {
let config = DreamLightingConfig::for_quality(DreamLightingQuality::High);
assert!(config.rt_gi_enabled);
assert!(config.rt_shadows_enabled);
assert!(config.screen_traces_enabled);
assert_eq!(config.sharc_hash_capacity, 1 << 18); assert_eq!(config.sharc_max_bounces, 2); assert_eq!(config.csm_cascades, 0); assert!((config.sharc_sparse_rate - 0.02).abs() < 0.001);
assert!((config.sharc_grid_log_base - 2.5).abs() < 0.01);
assert!((config.denoiser_temporal_alpha - 0.80).abs() < 0.01);
assert_eq!(config.ibl_resolution, 128);
}
#[test]
fn quality_preset_cinematic_has_tsr() {
let config = DreamLightingConfig::for_quality(DreamLightingQuality::Cinematic);
assert!(config.dream_tsr_enabled);
assert!(!config.taa_enabled); assert_eq!(config.sharc_max_bounces, 5); assert_eq!(config.sharc_hash_capacity, 1 << 22);
assert_eq!(config.csm_cascades, 0); assert_eq!(config.ibl_resolution, 256);
assert!((config.sharc_grid_log_base - 1.5).abs() < 0.01);
assert!((config.denoiser_temporal_alpha - 0.90).abs() < 0.01);
assert!((config.reflection_max_roughness - 0.6).abs() < 0.01);
}
#[test]
fn auto_detect_no_rt_64mb_returns_medium() {
let q = DreamLightingQuality::auto_detect(false, 64 * 1024 * 1024, 0);
assert_eq!(q, DreamLightingQuality::Medium);
}
#[test]
fn auto_detect_no_rt_low_storage_returns_low() {
let q = DreamLightingQuality::auto_detect(false, 32 * 1024 * 1024, 0);
assert_eq!(q, DreamLightingQuality::Low);
}
#[test]
fn auto_detect_rt_16m_tlas_returns_ultra() {
let q = DreamLightingQuality::auto_detect(true, 512 * 1024 * 1024, 16_000_000);
assert_eq!(q, DreamLightingQuality::Ultra);
}
#[test]
fn auto_detect_rt_below_16m_returns_high() {
let q = DreamLightingQuality::auto_detect(true, 256 * 1024 * 1024, 100_000);
assert_eq!(q, DreamLightingQuality::High);
}
#[test]
fn infer_quality_roundtrip() {
for quality in [
DreamLightingQuality::Low,
DreamLightingQuality::Medium,
DreamLightingQuality::High,
DreamLightingQuality::Ultra,
DreamLightingQuality::Cinematic,
] {
let config = DreamLightingConfig::for_quality(quality);
assert_eq!(
config.infer_quality(),
quality,
"infer_quality failed for {:?}",
quality
);
}
}
#[test]
fn serde_roundtrip_dream_lighting_quality() {
let q = DreamLightingQuality::Cinematic;
let json = serde_json::to_string(&q).unwrap();
let back: DreamLightingQuality = serde_json::from_str(&json).unwrap();
assert_eq!(q, back);
}
#[test]
fn serde_roundtrip_dream_lighting_config() {
let config = DreamLightingConfig::for_quality(DreamLightingQuality::Ultra);
let json = serde_json::to_string(&config).unwrap();
let back: DreamLightingConfig = serde_json::from_str(&json).unwrap();
assert_eq!(config.rt_gi_enabled, back.rt_gi_enabled);
assert_eq!(config.sharc_hash_capacity, back.sharc_hash_capacity);
assert_eq!(config.dream_tsr_enabled, back.dream_tsr_enabled);
}
}