@@ -132,6 +132,93 @@ impl Default for MaterialLayeredPbr {
132132}
133133
134134impl MaterialLayeredPbr {
135+ pub const CLEARCOAT_LOBE : u32 = 1 << 0 ;
136+ pub const SHEEN_LOBE : u32 = 1 << 1 ;
137+ pub const ANISOTROPY_LOBE : u32 = 1 << 2 ;
138+ pub const IRIDESCENCE_LOBE : u32 = 1 << 3 ;
139+ pub const SPECULAR_IOR_LOBE : u32 = 1 << 4 ;
140+
141+ /// Build the texture-free layered material used by the public authoring
142+ /// API. The lobe mask is authoritative: values belonging to an absent
143+ /// lobe are ignored so an omitted descriptor always restores glTF
144+ /// defaults and the allocation-free base-material path.
145+ #[ doc( hidden) ]
146+ #[ allow( clippy:: too_many_arguments) ]
147+ pub fn from_authoring_factors (
148+ lobe_mask : u32 ,
149+ clearcoat_factor : f32 ,
150+ clearcoat_roughness : f32 ,
151+ clearcoat_normal_scale : f32 ,
152+ specular_factor : f32 ,
153+ specular_color : [ f32 ; 3 ] ,
154+ ior : f32 ,
155+ sheen_color : [ f32 ; 3 ] ,
156+ sheen_roughness : f32 ,
157+ anisotropy_strength : f32 ,
158+ anisotropy_rotation : f32 ,
159+ iridescence_factor : f32 ,
160+ iridescence_ior : f32 ,
161+ iridescence_thickness_minimum : f32 ,
162+ iridescence_thickness_maximum : f32 ,
163+ ) -> Self {
164+ fn finite_or ( value : f32 , fallback : f32 ) -> f32 {
165+ if value. is_finite ( ) {
166+ value
167+ } else {
168+ fallback
169+ }
170+ }
171+ fn unit ( value : f32 , fallback : f32 ) -> f32 {
172+ finite_or ( value, fallback) . clamp ( 0.0 , 1.0 )
173+ }
174+ fn non_negative ( value : f32 , fallback : f32 ) -> f32 {
175+ finite_or ( value, fallback) . max ( 0.0 )
176+ }
177+ fn material_ior ( value : f32 ) -> f32 {
178+ let value = finite_or ( value, 1.5 ) ;
179+ if value == 0.0 {
180+ 0.0
181+ } else {
182+ value. max ( 1.0 )
183+ }
184+ }
185+
186+ let mut material = Self :: default ( ) ;
187+ if lobe_mask & Self :: CLEARCOAT_LOBE != 0 {
188+ material. clearcoat_authored = true ;
189+ material. clearcoat_factor = unit ( clearcoat_factor, 0.0 ) ;
190+ material. clearcoat_roughness_factor = unit ( clearcoat_roughness, 0.0 ) ;
191+ material. clearcoat_normal_scale = finite_or ( clearcoat_normal_scale, 1.0 ) ;
192+ }
193+ if lobe_mask & Self :: SPECULAR_IOR_LOBE != 0 {
194+ material. specular_authored = true ;
195+ material. specular_factor = unit ( specular_factor, 1.0 ) ;
196+ material. specular_color_factor = specular_color. map ( |value| non_negative ( value, 1.0 ) ) ;
197+ material. ior_authored = true ;
198+ material. ior = material_ior ( ior) ;
199+ }
200+ if lobe_mask & Self :: SHEEN_LOBE != 0 {
201+ material. sheen_authored = true ;
202+ material. sheen_color_factor = sheen_color. map ( |value| unit ( value, 0.0 ) ) ;
203+ material. sheen_roughness_factor = unit ( sheen_roughness, 0.0 ) ;
204+ }
205+ if lobe_mask & Self :: ANISOTROPY_LOBE != 0 {
206+ material. anisotropy_authored = true ;
207+ material. anisotropy_strength = unit ( anisotropy_strength, 0.0 ) ;
208+ material. anisotropy_rotation = finite_or ( anisotropy_rotation, 0.0 ) ;
209+ }
210+ if lobe_mask & Self :: IRIDESCENCE_LOBE != 0 {
211+ material. iridescence_authored = true ;
212+ material. iridescence_factor = unit ( iridescence_factor, 0.0 ) ;
213+ material. iridescence_ior = finite_or ( iridescence_ior, 1.3 ) . max ( 1.0 ) ;
214+ material. iridescence_thickness_minimum =
215+ non_negative ( iridescence_thickness_minimum, 100.0 ) ;
216+ material. iridescence_thickness_maximum =
217+ non_negative ( iridescence_thickness_maximum, 400.0 ) ;
218+ }
219+ material
220+ }
221+
135222 pub ( crate ) fn is_active ( self ) -> bool {
136223 self . has_clearcoat ( )
137224 || self . has_specular_ior ( )
@@ -241,6 +328,75 @@ impl MaterialLayeredPbr {
241328 }
242329}
243330
331+ #[ cfg( test) ]
332+ mod layered_pbr_authoring_tests {
333+ use super :: MaterialLayeredPbr ;
334+
335+ #[ test]
336+ fn omitted_lobes_restore_exact_defaults ( ) {
337+ let material = MaterialLayeredPbr :: from_authoring_factors (
338+ 0 ,
339+ 1.0 ,
340+ 1.0 ,
341+ 2.0 ,
342+ 0.0 ,
343+ [ 3.0 , 2.0 , 1.0 ] ,
344+ 1.1 ,
345+ [ 1.0 ; 3 ] ,
346+ 1.0 ,
347+ 1.0 ,
348+ 2.0 ,
349+ 1.0 ,
350+ 1.8 ,
351+ 20.0 ,
352+ 80.0 ,
353+ ) ;
354+ assert_eq ! ( material, MaterialLayeredPbr :: default ( ) ) ;
355+ assert ! ( !material. is_active( ) ) ;
356+ }
357+
358+ #[ test]
359+ fn authoring_factors_are_finite_and_range_safe ( ) {
360+ let mask = MaterialLayeredPbr :: CLEARCOAT_LOBE
361+ | MaterialLayeredPbr :: SPECULAR_IOR_LOBE
362+ | MaterialLayeredPbr :: SHEEN_LOBE
363+ | MaterialLayeredPbr :: ANISOTROPY_LOBE
364+ | MaterialLayeredPbr :: IRIDESCENCE_LOBE ;
365+ let material = MaterialLayeredPbr :: from_authoring_factors (
366+ mask,
367+ 2.0 ,
368+ -1.0 ,
369+ f32:: NAN ,
370+ -2.0 ,
371+ [ -1.0 , 2.0 , f32:: INFINITY ] ,
372+ 0.5 ,
373+ [ -1.0 , 0.5 , 2.0 ] ,
374+ f32:: NAN ,
375+ 4.0 ,
376+ f32:: INFINITY ,
377+ 3.0 ,
378+ 0.2 ,
379+ -10.0 ,
380+ f32:: NAN ,
381+ ) ;
382+ assert_eq ! ( material. clearcoat_factor, 1.0 ) ;
383+ assert_eq ! ( material. clearcoat_roughness_factor, 0.0 ) ;
384+ assert_eq ! ( material. clearcoat_normal_scale, 1.0 ) ;
385+ assert_eq ! ( material. specular_factor, 0.0 ) ;
386+ assert_eq ! ( material. specular_color_factor, [ 0.0 , 2.0 , 1.0 ] ) ;
387+ assert_eq ! ( material. ior, 1.0 ) ;
388+ assert_eq ! ( material. sheen_color_factor, [ 0.0 , 0.5 , 1.0 ] ) ;
389+ assert_eq ! ( material. sheen_roughness_factor, 0.0 ) ;
390+ assert_eq ! ( material. anisotropy_strength, 1.0 ) ;
391+ assert_eq ! ( material. anisotropy_rotation, 0.0 ) ;
392+ assert_eq ! ( material. iridescence_factor, 1.0 ) ;
393+ assert_eq ! ( material. iridescence_ior, 1.0 ) ;
394+ assert_eq ! ( material. iridescence_thickness_minimum, 0.0 ) ;
395+ assert_eq ! ( material. iridescence_thickness_maximum, 400.0 ) ;
396+ assert ! ( material. is_active( ) ) ;
397+ }
398+ }
399+
244400pub ( crate ) fn effective_material_ior ( ior : f32 ) -> f32 {
245401 if ior == 0.0 {
246402 1_000_000.0
0 commit comments