Skip to main content

pfx_materials/
detail.rs

1use crate::ageing::{apply, scratch_marked, scratch_marked_coat};
2use crate::kinds::{CoatWobble, Crinkle, Fibre, Grime, PlankWood, Scratch, WallMottle};
3use crate::material::{At, Material, NoiseKind, NoiseLayer};
4use crate::math::{
5    add3, clamp01, dot, fract, lerp, lerp3, mul3, normalize, scale3, smoothstep, sub3,
6};
7use crate::noise::{Hash, fbm3, flow, hash3, value_noise3, value_noise3_gradient};
8
9fn noise(p: [f32; 3]) -> f32 {
10    value_noise3(p, Hash::Xor)
11}
12
13fn fbm(p: [f32; 3], octaves: i32) -> f32 {
14    fbm3(p, octaves, Hash::Nested)
15}
16
17#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct Resolved {
19    pub material: Material,
20    pub normal: [f32; 3],
21}
22
23pub fn resolve(material: &Material, age: f32, at: &At) -> Resolved {
24    let mut material = apply(material, age, at);
25    let mut normal = if dot(at.normal, at.normal) <= 1e-12 {
26        [0.0, 0.0, 1.0]
27    } else {
28        normalize(at.normal)
29    };
30    for layer in material.layers {
31        if layer.active() {
32            sample_layer(&mut material, &mut normal, layer, at);
33        }
34    }
35    Resolved { material, normal }
36}
37
38fn sample_layer(material: &mut Material, normal: &mut [f32; 3], layer: NoiseLayer, at: &At) {
39    let amp = layer.amplitude;
40    let p = shift(at.position, layer.seed);
41    let uv = at.uv;
42    match layer.kind {
43        NoiseKind::None => {}
44        NoiseKind::Fibre => fibre(material, normal, p, amp, Fibre::from_params(&layer.params)),
45        NoiseKind::Crinkle => {
46            let c = Crinkle::from_params(&layer.params);
47            let g = add3(
48                scale3(value_noise3_gradient(p, c.frequencies[0]), c.gains[0] * amp),
49                scale3(value_noise3_gradient(p, c.frequencies[1]), c.gains[1] * amp),
50            );
51            *normal = perturb(*normal, g);
52        }
53        NoiseKind::PlankWood => {
54            if layer.active() {
55                plank_wood(material, p, amp, PlankWood::from_params(&layer.params));
56            }
57        }
58        NoiseKind::WallMottle => wall_mottle(
59            material,
60            normal,
61            p,
62            amp,
63            WallMottle::from_params(&layer.params),
64        ),
65        NoiseKind::Grime => grime(material, p, amp, Grime::from_params(&layer.params)),
66        NoiseKind::Scratch => {
67            let scales = Scratch::from_params(&layer.params);
68            if material.clearcoat > 0.0 {
69                let (base, roughness) = scratch_marked_coat(
70                    material.base,
71                    material.clearcoat_roughness,
72                    amp,
73                    p,
74                    layer.seed,
75                    scales,
76                );
77                material.base = base;
78                material.clearcoat_roughness = roughness;
79            } else {
80                let (base, roughness) = scratch_marked(
81                    material.base,
82                    material.roughness,
83                    amp,
84                    p,
85                    layer.seed,
86                    scales,
87                );
88                material.base = base;
89                material.roughness = roughness;
90            }
91        }
92        NoiseKind::CoatWobble => {
93            if layer.active() {
94                *normal = perturb(
95                    *normal,
96                    coat_wobble(p, amp, CoatWobble::from_params(&layer.params)),
97                );
98            }
99        }
100        NoiseKind::Leaf => {
101            let spots = fbm([uv[0] * 12.0, uv[1] * 12.0, 2.0], 4);
102            tint_factor(material, 0.7 + 0.6 * spots, amp);
103            height_relief(normal, at, amp, |uv, _| leaf_height(uv));
104        }
105        NoiseKind::Bark => {
106            let n = fbm(scale3(p, 300.0), 4);
107            tint_factor(material, 0.75 + 0.5 * n, amp);
108            height_relief(normal, at, amp, |_, p| bark_height(p));
109        }
110        NoiseKind::Flow => {
111            let n = flow([uv[0] * layer.frequency, uv[1] * layer.frequency], at.time);
112            material.thin_film = (material.thin_film + amp * (n - 0.5) * 2.0).max(0.0);
113        }
114        NoiseKind::Value => {
115            let n = noise(scale3(p, layer.frequency));
116            tint_factor(material, 1.0 + (n - 0.5) * 2.0, amp);
117        }
118        NoiseKind::Fbm => {
119            let n = fbm(scale3(p, layer.frequency), 4);
120            tint_factor(material, 1.0 + (n - 0.5) * 2.0, amp);
121        }
122    }
123}
124
125fn fibre(material: &mut Material, normal: &mut [f32; 3], p: [f32; 3], amount: f32, f: Fibre) {
126    let fibre = noise([p[0] * f.across, p[1] * f.along, p[2] * f.across]) * 0.6
127        + noise(scale3(p, f.fine)) * 0.4;
128    let mottle = noise(scale3(p, f.mottle));
129    let factor = 1.0 + ((fibre - 0.5) * f.fibre_gain + (mottle - 0.5) * f.mottle_gain) * amount;
130    material.base = scale3(material.base, factor);
131    let g = add3(
132        scale3(value_noise3_gradient(p, f.ripple), f.ripple_gain * amount),
133        scale3(value_noise3_gradient(p, f.swell), f.swell_gain * amount),
134    );
135    *normal = perturb(*normal, g);
136}
137
138fn plank_wood(material: &mut Material, p: [f32; 3], amp: f32, w: PlankWood) {
139    let plank = (p[2] / w.width).floor();
140    let tone = hash3([plank as i32, 7, 3], Hash::Xor);
141    let across = fract(p[2] / w.width);
142    let seam = smoothstep(0.0, 0.006, across) + smoothstep(0.0, 0.006, 1.0 - across) - 1.0;
143    let warp = noise([p[0] * 1.4, plank * 3.7, p[2] * 6.0]) * 0.9
144        + noise([p[0] * 6.0, plank, p[2] * 20.0]) * 0.25;
145    let rings =
146        fract((p[2] + warp * w.warp) * w.rings + noise([p[0] * 0.8, plank, 0.0]) * w.jitter);
147    let figure = smoothstep(0.55, 0.95, rings) * 0.55
148        + noise([p[0] * w.figure[0], p[2] * w.figure[1], plank]) * 0.25;
149    let factor = (0.78 + tone * w.tone)
150        * (1.08 - figure * w.figure_depth)
151        * lerp(w.seam_floor, 1.0, seam.clamp(0.0, 1.0));
152    tint_factor(material, factor, amp);
153    let alpha = material.roughness * material.roughness;
154    let next = (alpha * (0.85 + figure * w.roughness)).min(1.0).sqrt();
155    material.roughness = lerp(material.roughness, next, clamp01(amp));
156}
157
158fn wall_mottle(
159    material: &mut Material,
160    normal: &mut [f32; 3],
161    p: [f32; 3],
162    amp: f32,
163    w: WallMottle,
164) {
165    let broad = noise(scale3(p, w.broad[0])) * 0.6 + noise(scale3(p, w.broad[1])) * 0.4;
166    let fine = noise(scale3(p, w.fine[0])) * 0.6 + noise(scale3(p, w.fine[1])) * 0.4;
167    let factor = 1.0 + (broad - 0.5) * w.broad_gain + (fine - 0.5) * w.fine_gain;
168    tint_factor(material, factor, amp);
169    let g = add3(
170        scale3(value_noise3_gradient(p, w.swell[0]), w.swell_gains[0] * amp),
171        scale3(value_noise3_gradient(p, w.swell[1]), w.swell_gains[1] * amp),
172    );
173    *normal = perturb(*normal, g);
174}
175
176fn grime(material: &mut Material, p: [f32; 3], amp: f32, g: Grime) {
177    let grime = noise(scale3(p, g.frequencies[0])) * 0.6 + noise(scale3(p, g.frequencies[1])) * 0.4;
178    let wear = smoothstep(0.35, 0.75, grime);
179    let tint = lerp3(g.dirty, g.clean, wear);
180    material.base = mul3(material.base, lerp3([1.0, 1.0, 1.0], tint, clamp01(amp)));
181    let alpha = material.roughness * material.roughness;
182    let next = (alpha * lerp(g.roughness[0], g.roughness[1], wear))
183        .min(1.0)
184        .sqrt();
185    material.roughness = lerp(material.roughness, next, clamp01(amp));
186}
187
188fn leaf_height(uv: [f32; 2]) -> f32 {
189    let vein = (uv[1] * 40.0 + uv[0] * 3.0).sin().abs().powi(12);
190    0.0002 * fbm([uv[0] * 30.0, uv[1] * 30.0, 1.0], 3) - 0.00008 * vein
191}
192
193fn bark_height(p: [f32; 3]) -> f32 {
194    0.0006 * fbm([p[0] * 200.0, p[1] * 60.0, p[2] * 200.0], 4)
195}
196
197fn height_relief(
198    normal: &mut [f32; 3],
199    at: &At,
200    amp: f32,
201    height: impl Fn([f32; 2], [f32; 3]) -> f32,
202) {
203    let (t, b) = basis(*normal);
204    let e = 0.00012;
205    let h0 = height(at.uv, at.position);
206    let hu = height([at.uv[0] + e, at.uv[1]], add3(at.position, scale3(t, e)));
207    let hv = height([at.uv[0], at.uv[1] + e], add3(at.position, scale3(b, e)));
208    let bent = normalize(sub3(
209        sub3(*normal, scale3(t, (hu - h0) / e * amp)),
210        scale3(b, (hv - h0) / e * amp),
211    ));
212    if dot(bent, *normal) > 0.05 {
213        *normal = bent;
214    }
215}
216
217fn coat_wobble(p: [f32; 3], z: f32, c: CoatWobble) -> [f32; 3] {
218    add3(
219        scale3(value_noise3_gradient(p, c.frequencies[0]), z),
220        add3(
221            scale3(value_noise3_gradient(p, c.frequencies[1]), z * c.weights[0]),
222            scale3(value_noise3_gradient(p, c.frequencies[2]), z * c.weights[1]),
223        ),
224    )
225}
226
227fn tint_factor(material: &mut Material, factor: f32, amp: f32) {
228    let target = scale3(material.base, factor);
229    material.base = lerp3(material.base, target, clamp01(amp));
230}
231
232fn perturb(n: [f32; 3], g: [f32; 3]) -> [f32; 3] {
233    let along = dot(g, n);
234    normalize(sub3(n, sub3(g, scale3(n, along))))
235}
236
237fn shift(p: [f32; 3], seed: u32) -> [f32; 3] {
238    if seed == 0 {
239        p
240    } else {
241        [p[0] + seed as f32 * 0.001, p[1] + seed as f32 * 0.001, p[2]]
242    }
243}
244
245fn basis(n: [f32; 3]) -> ([f32; 3], [f32; 3]) {
246    let s = if n[2] >= 0.0 { 1.0 } else { -1.0 };
247    let a = -1.0 / (s + n[2]);
248    let b = n[0] * n[1] * a;
249    let t = [1.0 + s * n[0] * n[0] * a, s * b, -s * n[0]];
250    let bt = [b, s + n[1] * n[1] * a, -n[1]];
251    (t, bt)
252}