Skip to content

Commit 12f550e

Browse files
authored
MeshPhysicalMaterial: Support iridescence / thin-film materials (#23869)
* Add iridescence parameters to Physical Material * Add iridescence fragment shader code * Iridescence shader integration and glTF loading * Update iridescence default values * Add KHR_materials_iridescence to supported extensions in GLTFLoader docu * Enable iridescence in Editor * Update build results * Remove build files from PR * Honor iridescence parameters in program cache * Use range for iridescence thin-film thickness * Fixed linting errors * Fix always-true conditional * Add iridescence to glTF export * Instantiate iridescenceThicknessRange in GLTFLoader if undefined * Make iridescence Fresnel consistent for IBL * Rename iridescenceIOR to iridescenceIor * Rename iridescenceIor back to iridescenceIOR except for glTF extension
1 parent 70e0000 commit 12f550e

27 files changed

+929
-25
lines changed

docs/examples/en/loaders/GLTFLoader.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ <h2>Extensions</h2>
3939
<li>KHR_materials_pbrSpecularGlossiness</li>
4040
<li>KHR_materials_specular</li>
4141
<li>KHR_materials_transmission</li>
42+
<li>KHR_materials_iridescence</li>
4243
<li>KHR_materials_unlit</li>
4344
<li>KHR_materials_volume</li>
4445
<li>KHR_mesh_quantization</li>

docs/examples/zh/loaders/GLTFLoader.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ <h2>扩展</h2>
3737
<li>KHR_materials_pbrSpecularGlossiness</li>
3838
<li>KHR_materials_specular</li>
3939
<li>KHR_materials_transmission</li>
40+
<li>KHR_materials_iridescence</li>
4041
<li>KHR_materials_unlit</li>
4142
<li>KHR_materials_volume</li>
4243
<li>KHR_mesh_quantization</li>

editor/js/Sidebar.Material.MapProperty.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import * as THREE from 'three';
22

3-
import { UICheckbox, UINumber, UIRow, UIText } from './libs/ui.js';
3+
import { UICheckbox, UIDiv, UINumber, UIRow, UIText } from './libs/ui.js';
44
import { UITexture } from './libs/ui.three.js';
55
import { SetMaterialMapCommand } from './commands/SetMaterialMapCommand.js';
66
import { SetMaterialValueCommand } from './commands/SetMaterialValueCommand.js';
7+
import { SetMaterialRangeCommand } from './commands/SetMaterialRangeCommand.js';
78
import { SetMaterialVectorCommand } from './commands/SetMaterialVectorCommand.js';
89

910
function SidebarMaterialMapProperty( editor, property, name ) {
@@ -51,6 +52,36 @@ function SidebarMaterialMapProperty( editor, property, name ) {
5152

5253
}
5354

55+
let rangeMin, rangeMax;
56+
57+
if ( property === 'iridescenceThicknessMap' ) {
58+
59+
const range = new UIDiv().setMarginLeft( '3px' );
60+
container.add( range );
61+
62+
const rangeMinRow = new UIRow().setMarginBottom( '0px' ).setStyle( 'min-height', '0px' );
63+
range.add( rangeMinRow );
64+
65+
rangeMinRow.add( new UIText( 'min:' ).setWidth( '35px' ) );
66+
67+
rangeMin = new UINumber().setWidth( '40px' ).onChange( onRangeChange );
68+
rangeMinRow.add( rangeMin );
69+
70+
const rangeMaxRow = new UIRow().setMarginBottom( '6px' ).setStyle( 'min-height', '0px' );
71+
range.add( rangeMaxRow );
72+
73+
rangeMaxRow.add( new UIText( 'max:' ).setWidth( '35px' ) );
74+
75+
rangeMax = new UINumber().setWidth( '40px' ).onChange( onRangeChange );
76+
rangeMaxRow.add( rangeMax );
77+
78+
// Additional settings for iridescenceThicknessMap
79+
// Please add conditional if more maps are having a range property
80+
rangeMin.setPrecision( 0 ).setRange( 0, Infinity ).setNudge( 1 ).setStep( 10 ).setUnit( 'nm' );
81+
rangeMax.setPrecision( 0 ).setRange( 0, Infinity ).setNudge( 1 ).setStep( 10 ).setUnit( 'nm' );
82+
83+
}
84+
5485
let object = null;
5586
let material = null;
5687

@@ -127,6 +158,18 @@ function SidebarMaterialMapProperty( editor, property, name ) {
127158

128159
}
129160

161+
function onRangeChange() {
162+
163+
const value = [ rangeMin.getValue(), rangeMax.getValue() ];
164+
165+
if ( material[ `${ mapType }Range` ][ 0 ] !== value[ 0 ] || material[ `${ mapType }Range` ][ 1 ] !== value[ 1 ] ) {
166+
167+
editor.execute( new SetMaterialRangeCommand( editor, object, `${ mapType }Range`, value[ 0 ], value[ 1 ], 0 /* TODOL currentMaterialSlot */ ) );
168+
169+
}
170+
171+
}
172+
130173
function update() {
131174

132175
if ( object === null ) return;
@@ -164,6 +207,13 @@ function SidebarMaterialMapProperty( editor, property, name ) {
164207

165208
}
166209

210+
if ( rangeMin !== undefined ) {
211+
212+
rangeMin.setValue( material[ `${ mapType }Range` ][ 0 ] );
213+
rangeMax.setValue( material[ `${ mapType }Range` ][ 1 ] );
214+
215+
}
216+
167217
container.setDisplay( '' );
168218

169219
} else {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { UINumber, UIRow, UIText } from './libs/ui.js';
2+
import { SetMaterialRangeCommand } from './commands/SetMaterialRangeCommand.js';
3+
4+
function SidebarMaterialRangeValueProperty( editor, property, name, isMin, range = [ - Infinity, Infinity ], precision = 2, step = 1, nudge = 0.01, unit = '' ) {
5+
6+
const signals = editor.signals;
7+
8+
const container = new UIRow();
9+
container.add( new UIText( name ).setWidth( '90px' ) );
10+
11+
const number = new UINumber().setWidth( '60px' ).setRange( range[ 0 ], range[ 1 ] ).setPrecision( precision ).setStep( step ).setNudge( nudge ).setUnit( unit ).onChange( onChange );
12+
container.add( number );
13+
14+
let object = null;
15+
let material = null;
16+
17+
function onChange() {
18+
19+
if ( material[ property ][ isMin ? 0 : 1 ] !== number.getValue() ) {
20+
21+
const minValue = isMin ? number.getValue() : material[ property ][ 0 ];
22+
const maxValue = isMin ? material[ property ][ 1 ] : number.getValue();
23+
24+
editor.execute( new SetMaterialRangeCommand( editor, object, property, minValue, maxValue, 0 /* TODO: currentMaterialSlot */ ) );
25+
26+
}
27+
28+
}
29+
30+
function update() {
31+
32+
if ( object === null ) return;
33+
if ( object.material === undefined ) return;
34+
35+
material = object.material;
36+
37+
if ( property in material ) {
38+
39+
number.setValue( material[ property ][ isMin ? 0 : 1 ] );
40+
container.setDisplay( '' );
41+
42+
} else {
43+
44+
container.setDisplay( 'none' );
45+
46+
}
47+
48+
}
49+
50+
//
51+
52+
signals.objectSelected.add( function ( selected ) {
53+
54+
object = selected;
55+
56+
update();
57+
58+
} );
59+
60+
signals.materialChanged.add( update );
61+
62+
return container;
63+
64+
}
65+
66+
export { SidebarMaterialRangeValueProperty };

editor/js/Sidebar.Material.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { SidebarMaterialColorProperty } from './Sidebar.Material.ColorProperty.j
1010
import { SidebarMaterialConstantProperty } from './Sidebar.Material.ConstantProperty.js';
1111
import { SidebarMaterialMapProperty } from './Sidebar.Material.MapProperty.js';
1212
import { SidebarMaterialNumberProperty } from './Sidebar.Material.NumberProperty.js';
13+
import { SidebarMaterialRangeValueProperty } from './Sidebar.Material.RangeValueProperty.js';
1314
import { SidebarMaterialProgram } from './Sidebar.Material.Program.js';
1415

1516
function SidebarMaterial( editor ) {
@@ -130,6 +131,21 @@ function SidebarMaterial( editor ) {
130131
const materialClearcoatRoughness = new SidebarMaterialNumberProperty( editor, 'clearcoatRoughness', strings.getKey( 'sidebar/material/clearcoatroughness' ), [ 0, 1 ] );
131132
container.add( materialClearcoatRoughness );
132133

134+
// iridescence
135+
136+
const materialIridescence = new SidebarMaterialNumberProperty( editor, 'iridescence', strings.getKey( 'sidebar/material/iridescence' ), [ 0, 1 ] );
137+
container.add( materialIridescence );
138+
139+
// iridescenceIOR
140+
141+
const materialIridescenceIOR = new SidebarMaterialNumberProperty( editor, 'iridescenceIOR', strings.getKey( 'sidebar/material/iridescenceIOR' ), [ 1, 5 ] );
142+
container.add( materialIridescenceIOR );
143+
144+
// iridescenceThicknessMax
145+
146+
const materialIridescenceThicknessMax = new SidebarMaterialRangeValueProperty( editor, 'iridescenceThicknessRange', strings.getKey( 'sidebar/material/iridescenceThicknessMax' ), false, [ 0, Infinity ], 0, 10, 1, 'nm' );
147+
container.add( materialIridescenceThicknessMax );
148+
133149
// transmission
134150

135151
const materialTransmission = new SidebarMaterialNumberProperty( editor, 'transmission', strings.getKey( 'sidebar/material/transmission' ), [ 0, 1 ] );
@@ -220,6 +236,16 @@ function SidebarMaterial( editor ) {
220236
const materialMetalnessMap = new SidebarMaterialMapProperty( editor, 'metalnessMap', strings.getKey( 'sidebar/material/metalnessmap' ) );
221237
container.add( materialMetalnessMap );
222238

239+
// iridescence map
240+
241+
const materialIridescenceMap = new SidebarMaterialMapProperty( editor, 'iridescenceMap', strings.getKey( 'sidebar/material/iridescencemap' ) );
242+
container.add( materialIridescenceMap );
243+
244+
// iridescence thickness map
245+
246+
const materialIridescenceThicknessMap = new SidebarMaterialMapProperty( editor, 'iridescenceThicknessMap', strings.getKey( 'sidebar/material/iridescencethicknessmap' ) );
247+
container.add( materialIridescenceThicknessMap );
248+
223249
// env map
224250

225251
const materialEnvMap = new SidebarMaterialMapProperty( editor, 'envMap', strings.getKey( 'sidebar/material/envmap' ) );

editor/js/Strings.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@ function Strings( config ) {
257257
'sidebar/material/shininess': 'Shininess',
258258
'sidebar/material/clearcoat': 'Clearcoat',
259259
'sidebar/material/clearcoatroughness': 'Clearcoat Roughness',
260+
'sidebar/material/iridescence': 'Iridescence',
261+
'sidebar/material/iridescenceIOR': 'Thin-Film IOR',
262+
'sidebar/material/iridescenceThicknessMax': 'Thin-Film Thickness',
260263
'sidebar/material/transmission': 'Transmission',
261264
'sidebar/material/attenuationDistance': 'Attenuation Distance',
262265
'sidebar/material/attenuationColor': 'Attenuation Color',
@@ -272,6 +275,8 @@ function Strings( config ) {
272275
'sidebar/material/roughnessmap': 'Rough. Map',
273276
'sidebar/material/metalnessmap': 'Metal. Map',
274277
'sidebar/material/specularmap': 'Specular Map',
278+
'sidebar/material/iridescencemap': 'Irid. Map',
279+
'sidebar/material/iridescencethicknessmap': 'Thin-Film Thickness Map',
275280
'sidebar/material/envmap': 'Env Map',
276281
'sidebar/material/lightmap': 'Light Map',
277282
'sidebar/material/aomap': 'AO Map',
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { Command } from '../Command.js';
2+
3+
/**
4+
* @param editor Editor
5+
* @param object THREE.Object3D
6+
* @param attributeName string
7+
* @param newMinValue number
8+
* @param newMaxValue number
9+
* @constructor
10+
*/
11+
class SetMaterialRangeCommand extends Command {
12+
13+
constructor( editor, object, attributeName, newMinValue, newMaxValue, materialSlot ) {
14+
15+
super( editor );
16+
17+
this.type = 'SetMaterialRangeCommand';
18+
this.name = `Set Material.${attributeName}`;
19+
this.updatable = true;
20+
21+
this.object = object;
22+
this.material = this.editor.getObjectMaterial( object, materialSlot );
23+
24+
this.oldRange = ( this.material !== undefined && this.material[ attributeName ] !== undefined ) ? [ ...this.material[ attributeName ] ] : undefined;
25+
this.newRange = [ newMinValue, newMaxValue ];
26+
27+
this.attributeName = attributeName;
28+
29+
}
30+
31+
execute() {
32+
33+
this.material[ this.attributeName ] = [ ...this.newRange ];
34+
this.material.needsUpdate = true;
35+
36+
this.editor.signals.objectChanged.dispatch( this.object );
37+
this.editor.signals.materialChanged.dispatch( this.material );
38+
39+
}
40+
41+
undo() {
42+
43+
this.material[ this.attributeName ] = [ ...this.oldRange ];
44+
this.material.needsUpdate = true;
45+
46+
this.editor.signals.objectChanged.dispatch( this.object );
47+
this.editor.signals.materialChanged.dispatch( this.material );
48+
49+
}
50+
51+
update( cmd ) {
52+
53+
this.newRange = [ ...cmd.newRange ];
54+
55+
}
56+
57+
toJSON() {
58+
59+
const output = super.toJSON( this );
60+
61+
output.objectUuid = this.object.uuid;
62+
output.attributeName = this.attributeName;
63+
output.oldRange = [ ...this.oldRange ];
64+
output.newRange = [ ...this.newRange ];
65+
66+
return output;
67+
68+
}
69+
70+
fromJSON( json ) {
71+
72+
super.fromJSON( json );
73+
74+
this.attributeName = json.attributeName;
75+
this.oldRange = [ ...json.oldRange ];
76+
this.newRange = [ ...json.newRange ];
77+
this.object = this.editor.objectByUuid( json.objectUuid );
78+
79+
}
80+
81+
}
82+
83+
export { SetMaterialRangeCommand };

0 commit comments

Comments
 (0)