From 12f550e0f44f68e5b6c946dd242ee08e42d209e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Sch=C3=B6n?= <35911659+PascalSchoen@users.noreply.github.com> Date: Wed, 25 May 2022 12:31:41 +0200 Subject: [PATCH] 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 --- docs/examples/en/loaders/GLTFLoader.html | 1 + docs/examples/zh/loaders/GLTFLoader.html | 1 + editor/js/Sidebar.Material.MapProperty.js | 52 ++++++++- .../js/Sidebar.Material.RangeValueProperty.js | 66 +++++++++++ editor/js/Sidebar.Material.js | 26 +++++ editor/js/Strings.js | 5 + editor/js/commands/SetMaterialRangeCommand.js | 83 ++++++++++++++ examples/js/loaders/GLTFLoader.js | 92 +++++++++++++++ examples/jsm/exporters/GLTFExporter.js | 60 ++++++++++ examples/jsm/loaders/GLTFLoader.js | 95 ++++++++++++++++ .../renderers/webgl/nodes/WebGLNodeBuilder.js | 51 +++++++++ src/loaders/MaterialLoader.js | 6 + src/materials/Material.js | 16 +++ src/materials/MeshPhysicalMaterial.js | 30 +++++ src/renderers/shaders/ShaderChunk.js | 4 + .../shaders/ShaderChunk/bsdfs.glsl.js | 46 ++++++++ .../shaders/ShaderChunk/common.glsl.js | 1 + .../ShaderChunk/iridescence_fragment.glsl.js | 107 ++++++++++++++++++ .../iridescence_pars_fragment.glsl.js | 14 +++ .../ShaderChunk/lights_fragment_begin.glsl.js | 25 ++++ .../lights_physical_fragment.glsl.js | 23 ++++ .../lights_physical_pars_fragment.glsl.js | 48 +++++++- src/renderers/shaders/ShaderLib.js | 6 + .../shaders/ShaderLib/meshphysical.glsl.js | 9 ++ src/renderers/webgl/WebGLMaterials.js | 39 ++++++- src/renderers/webgl/WebGLProgram.js | 7 ++ src/renderers/webgl/WebGLPrograms.js | 41 ++++--- 27 files changed, 929 insertions(+), 25 deletions(-) create mode 100644 editor/js/Sidebar.Material.RangeValueProperty.js create mode 100644 editor/js/commands/SetMaterialRangeCommand.js create mode 100644 src/renderers/shaders/ShaderChunk/iridescence_fragment.glsl.js create mode 100644 src/renderers/shaders/ShaderChunk/iridescence_pars_fragment.glsl.js diff --git a/docs/examples/en/loaders/GLTFLoader.html b/docs/examples/en/loaders/GLTFLoader.html index 8621ea03a090e3..99e67d46bd4ee0 100644 --- a/docs/examples/en/loaders/GLTFLoader.html +++ b/docs/examples/en/loaders/GLTFLoader.html @@ -39,6 +39,7 @@

Extensions

  • KHR_materials_pbrSpecularGlossiness
  • KHR_materials_specular
  • KHR_materials_transmission
  • +
  • KHR_materials_iridescence
  • KHR_materials_unlit
  • KHR_materials_volume
  • KHR_mesh_quantization
  • diff --git a/docs/examples/zh/loaders/GLTFLoader.html b/docs/examples/zh/loaders/GLTFLoader.html index fe9b1dd2316d9c..b564dce24120da 100644 --- a/docs/examples/zh/loaders/GLTFLoader.html +++ b/docs/examples/zh/loaders/GLTFLoader.html @@ -37,6 +37,7 @@

    扩展

  • KHR_materials_pbrSpecularGlossiness
  • KHR_materials_specular
  • KHR_materials_transmission
  • +
  • KHR_materials_iridescence
  • KHR_materials_unlit
  • KHR_materials_volume
  • KHR_mesh_quantization
  • diff --git a/editor/js/Sidebar.Material.MapProperty.js b/editor/js/Sidebar.Material.MapProperty.js index 0f04cca072d938..d943599fdd6d5f 100644 --- a/editor/js/Sidebar.Material.MapProperty.js +++ b/editor/js/Sidebar.Material.MapProperty.js @@ -1,9 +1,10 @@ import * as THREE from 'three'; -import { UICheckbox, UINumber, UIRow, UIText } from './libs/ui.js'; +import { UICheckbox, UIDiv, UINumber, UIRow, UIText } from './libs/ui.js'; import { UITexture } from './libs/ui.three.js'; import { SetMaterialMapCommand } from './commands/SetMaterialMapCommand.js'; import { SetMaterialValueCommand } from './commands/SetMaterialValueCommand.js'; +import { SetMaterialRangeCommand } from './commands/SetMaterialRangeCommand.js'; import { SetMaterialVectorCommand } from './commands/SetMaterialVectorCommand.js'; function SidebarMaterialMapProperty( editor, property, name ) { @@ -51,6 +52,36 @@ function SidebarMaterialMapProperty( editor, property, name ) { } + let rangeMin, rangeMax; + + if ( property === 'iridescenceThicknessMap' ) { + + const range = new UIDiv().setMarginLeft( '3px' ); + container.add( range ); + + const rangeMinRow = new UIRow().setMarginBottom( '0px' ).setStyle( 'min-height', '0px' ); + range.add( rangeMinRow ); + + rangeMinRow.add( new UIText( 'min:' ).setWidth( '35px' ) ); + + rangeMin = new UINumber().setWidth( '40px' ).onChange( onRangeChange ); + rangeMinRow.add( rangeMin ); + + const rangeMaxRow = new UIRow().setMarginBottom( '6px' ).setStyle( 'min-height', '0px' ); + range.add( rangeMaxRow ); + + rangeMaxRow.add( new UIText( 'max:' ).setWidth( '35px' ) ); + + rangeMax = new UINumber().setWidth( '40px' ).onChange( onRangeChange ); + rangeMaxRow.add( rangeMax ); + + // Additional settings for iridescenceThicknessMap + // Please add conditional if more maps are having a range property + rangeMin.setPrecision( 0 ).setRange( 0, Infinity ).setNudge( 1 ).setStep( 10 ).setUnit( 'nm' ); + rangeMax.setPrecision( 0 ).setRange( 0, Infinity ).setNudge( 1 ).setStep( 10 ).setUnit( 'nm' ); + + } + let object = null; let material = null; @@ -127,6 +158,18 @@ function SidebarMaterialMapProperty( editor, property, name ) { } + function onRangeChange() { + + const value = [ rangeMin.getValue(), rangeMax.getValue() ]; + + if ( material[ `${ mapType }Range` ][ 0 ] !== value[ 0 ] || material[ `${ mapType }Range` ][ 1 ] !== value[ 1 ] ) { + + editor.execute( new SetMaterialRangeCommand( editor, object, `${ mapType }Range`, value[ 0 ], value[ 1 ], 0 /* TODOL currentMaterialSlot */ ) ); + + } + + } + function update() { if ( object === null ) return; @@ -164,6 +207,13 @@ function SidebarMaterialMapProperty( editor, property, name ) { } + if ( rangeMin !== undefined ) { + + rangeMin.setValue( material[ `${ mapType }Range` ][ 0 ] ); + rangeMax.setValue( material[ `${ mapType }Range` ][ 1 ] ); + + } + container.setDisplay( '' ); } else { diff --git a/editor/js/Sidebar.Material.RangeValueProperty.js b/editor/js/Sidebar.Material.RangeValueProperty.js new file mode 100644 index 00000000000000..5f89838f3c63f7 --- /dev/null +++ b/editor/js/Sidebar.Material.RangeValueProperty.js @@ -0,0 +1,66 @@ +import { UINumber, UIRow, UIText } from './libs/ui.js'; +import { SetMaterialRangeCommand } from './commands/SetMaterialRangeCommand.js'; + +function SidebarMaterialRangeValueProperty( editor, property, name, isMin, range = [ - Infinity, Infinity ], precision = 2, step = 1, nudge = 0.01, unit = '' ) { + + const signals = editor.signals; + + const container = new UIRow(); + container.add( new UIText( name ).setWidth( '90px' ) ); + + const number = new UINumber().setWidth( '60px' ).setRange( range[ 0 ], range[ 1 ] ).setPrecision( precision ).setStep( step ).setNudge( nudge ).setUnit( unit ).onChange( onChange ); + container.add( number ); + + let object = null; + let material = null; + + function onChange() { + + if ( material[ property ][ isMin ? 0 : 1 ] !== number.getValue() ) { + + const minValue = isMin ? number.getValue() : material[ property ][ 0 ]; + const maxValue = isMin ? material[ property ][ 1 ] : number.getValue(); + + editor.execute( new SetMaterialRangeCommand( editor, object, property, minValue, maxValue, 0 /* TODO: currentMaterialSlot */ ) ); + + } + + } + + function update() { + + if ( object === null ) return; + if ( object.material === undefined ) return; + + material = object.material; + + if ( property in material ) { + + number.setValue( material[ property ][ isMin ? 0 : 1 ] ); + container.setDisplay( '' ); + + } else { + + container.setDisplay( 'none' ); + + } + + } + + // + + signals.objectSelected.add( function ( selected ) { + + object = selected; + + update(); + + } ); + + signals.materialChanged.add( update ); + + return container; + +} + +export { SidebarMaterialRangeValueProperty }; diff --git a/editor/js/Sidebar.Material.js b/editor/js/Sidebar.Material.js index a0e76010e0a083..b700658d76b88a 100644 --- a/editor/js/Sidebar.Material.js +++ b/editor/js/Sidebar.Material.js @@ -10,6 +10,7 @@ import { SidebarMaterialColorProperty } from './Sidebar.Material.ColorProperty.j import { SidebarMaterialConstantProperty } from './Sidebar.Material.ConstantProperty.js'; import { SidebarMaterialMapProperty } from './Sidebar.Material.MapProperty.js'; import { SidebarMaterialNumberProperty } from './Sidebar.Material.NumberProperty.js'; +import { SidebarMaterialRangeValueProperty } from './Sidebar.Material.RangeValueProperty.js'; import { SidebarMaterialProgram } from './Sidebar.Material.Program.js'; function SidebarMaterial( editor ) { @@ -130,6 +131,21 @@ function SidebarMaterial( editor ) { const materialClearcoatRoughness = new SidebarMaterialNumberProperty( editor, 'clearcoatRoughness', strings.getKey( 'sidebar/material/clearcoatroughness' ), [ 0, 1 ] ); container.add( materialClearcoatRoughness ); + // iridescence + + const materialIridescence = new SidebarMaterialNumberProperty( editor, 'iridescence', strings.getKey( 'sidebar/material/iridescence' ), [ 0, 1 ] ); + container.add( materialIridescence ); + + // iridescenceIOR + + const materialIridescenceIOR = new SidebarMaterialNumberProperty( editor, 'iridescenceIOR', strings.getKey( 'sidebar/material/iridescenceIOR' ), [ 1, 5 ] ); + container.add( materialIridescenceIOR ); + + // iridescenceThicknessMax + + const materialIridescenceThicknessMax = new SidebarMaterialRangeValueProperty( editor, 'iridescenceThicknessRange', strings.getKey( 'sidebar/material/iridescenceThicknessMax' ), false, [ 0, Infinity ], 0, 10, 1, 'nm' ); + container.add( materialIridescenceThicknessMax ); + // transmission const materialTransmission = new SidebarMaterialNumberProperty( editor, 'transmission', strings.getKey( 'sidebar/material/transmission' ), [ 0, 1 ] ); @@ -220,6 +236,16 @@ function SidebarMaterial( editor ) { const materialMetalnessMap = new SidebarMaterialMapProperty( editor, 'metalnessMap', strings.getKey( 'sidebar/material/metalnessmap' ) ); container.add( materialMetalnessMap ); + // iridescence map + + const materialIridescenceMap = new SidebarMaterialMapProperty( editor, 'iridescenceMap', strings.getKey( 'sidebar/material/iridescencemap' ) ); + container.add( materialIridescenceMap ); + + // iridescence thickness map + + const materialIridescenceThicknessMap = new SidebarMaterialMapProperty( editor, 'iridescenceThicknessMap', strings.getKey( 'sidebar/material/iridescencethicknessmap' ) ); + container.add( materialIridescenceThicknessMap ); + // env map const materialEnvMap = new SidebarMaterialMapProperty( editor, 'envMap', strings.getKey( 'sidebar/material/envmap' ) ); diff --git a/editor/js/Strings.js b/editor/js/Strings.js index 6584f4cb2715ae..e9481ba6597bbe 100644 --- a/editor/js/Strings.js +++ b/editor/js/Strings.js @@ -257,6 +257,9 @@ function Strings( config ) { 'sidebar/material/shininess': 'Shininess', 'sidebar/material/clearcoat': 'Clearcoat', 'sidebar/material/clearcoatroughness': 'Clearcoat Roughness', + 'sidebar/material/iridescence': 'Iridescence', + 'sidebar/material/iridescenceIOR': 'Thin-Film IOR', + 'sidebar/material/iridescenceThicknessMax': 'Thin-Film Thickness', 'sidebar/material/transmission': 'Transmission', 'sidebar/material/attenuationDistance': 'Attenuation Distance', 'sidebar/material/attenuationColor': 'Attenuation Color', @@ -272,6 +275,8 @@ function Strings( config ) { 'sidebar/material/roughnessmap': 'Rough. Map', 'sidebar/material/metalnessmap': 'Metal. Map', 'sidebar/material/specularmap': 'Specular Map', + 'sidebar/material/iridescencemap': 'Irid. Map', + 'sidebar/material/iridescencethicknessmap': 'Thin-Film Thickness Map', 'sidebar/material/envmap': 'Env Map', 'sidebar/material/lightmap': 'Light Map', 'sidebar/material/aomap': 'AO Map', diff --git a/editor/js/commands/SetMaterialRangeCommand.js b/editor/js/commands/SetMaterialRangeCommand.js new file mode 100644 index 00000000000000..a2f02be12b7c4d --- /dev/null +++ b/editor/js/commands/SetMaterialRangeCommand.js @@ -0,0 +1,83 @@ +import { Command } from '../Command.js'; + +/** + * @param editor Editor + * @param object THREE.Object3D + * @param attributeName string + * @param newMinValue number + * @param newMaxValue number + * @constructor + */ +class SetMaterialRangeCommand extends Command { + + constructor( editor, object, attributeName, newMinValue, newMaxValue, materialSlot ) { + + super( editor ); + + this.type = 'SetMaterialRangeCommand'; + this.name = `Set Material.${attributeName}`; + this.updatable = true; + + this.object = object; + this.material = this.editor.getObjectMaterial( object, materialSlot ); + + this.oldRange = ( this.material !== undefined && this.material[ attributeName ] !== undefined ) ? [ ...this.material[ attributeName ] ] : undefined; + this.newRange = [ newMinValue, newMaxValue ]; + + this.attributeName = attributeName; + + } + + execute() { + + this.material[ this.attributeName ] = [ ...this.newRange ]; + this.material.needsUpdate = true; + + this.editor.signals.objectChanged.dispatch( this.object ); + this.editor.signals.materialChanged.dispatch( this.material ); + + } + + undo() { + + this.material[ this.attributeName ] = [ ...this.oldRange ]; + this.material.needsUpdate = true; + + this.editor.signals.objectChanged.dispatch( this.object ); + this.editor.signals.materialChanged.dispatch( this.material ); + + } + + update( cmd ) { + + this.newRange = [ ...cmd.newRange ]; + + } + + toJSON() { + + const output = super.toJSON( this ); + + output.objectUuid = this.object.uuid; + output.attributeName = this.attributeName; + output.oldRange = [ ...this.oldRange ]; + output.newRange = [ ...this.newRange ]; + + return output; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.attributeName = json.attributeName; + this.oldRange = [ ...json.oldRange ]; + this.newRange = [ ...json.newRange ]; + this.object = this.editor.objectByUuid( json.objectUuid ); + + } + +} + +export { SetMaterialRangeCommand }; diff --git a/examples/js/loaders/GLTFLoader.js b/examples/js/loaders/GLTFLoader.js index 457ee1c14267ae..db2b12905fc33b 100644 --- a/examples/js/loaders/GLTFLoader.js +++ b/examples/js/loaders/GLTFLoader.js @@ -53,6 +53,11 @@ return new GLTFMaterialsSpecularExtension( parser ); + } ); + this.register( function ( parser ) { + + return new GLTFMaterialsIridescenceExtension( parser ); + } ); this.register( function ( parser ) { @@ -359,6 +364,7 @@ KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness', KHR_MATERIALS_SHEEN: 'KHR_materials_sheen', KHR_MATERIALS_SPECULAR: 'KHR_materials_specular', + KHR_MATERIALS_IRIDESCENCE: 'KHR_materials_iridescence', KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission', KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', KHR_MATERIALS_VOLUME: 'KHR_materials_volume', @@ -655,6 +661,92 @@ } + } + /** + * Iridescence Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence + */ + + + class GLTFMaterialsIridescenceExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_IRIDESCENCE; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + return THREE.MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + const extension = materialDef.extensions[ this.name ]; + + if ( extension.iridescenceFactor !== undefined ) { + + materialParams.iridescence = extension.iridescenceFactor; + + } + + if ( extension.iridescenceTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'iridescenceMap', extension.iridescenceTexture ) ); + + } + + if ( extension.iridescenceIor !== undefined ) { + + materialParams.iridescenceIOR = extension.iridescenceIor; + + } + + if ( materialParams.iridescenceThicknessRange === undefined ) { + + materialParams.iridescenceThicknessRange = [ 100, 400 ]; + + } + + if ( extension.iridescenceThicknessMinimum !== undefined ) { + + materialParams.iridescenceThicknessRange[ 0 ] = extension.iridescenceThicknessMinimum; + + } + + if ( extension.iridescenceThicknessMaximum !== undefined ) { + + materialParams.iridescenceThicknessRange[ 1 ] = extension.iridescenceThicknessMaximum; + + } + + if ( extension.iridescenceThicknessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'iridescenceThicknessMap', extension.iridescenceThicknessTexture ) ); + + } + + return Promise.all( pending ); + + } + } /** * Sheen Materials Extension diff --git a/examples/jsm/exporters/GLTFExporter.js b/examples/jsm/exporters/GLTFExporter.js index 15ad4565a00c72..791a395cac87e0 100644 --- a/examples/jsm/exporters/GLTFExporter.js +++ b/examples/jsm/exporters/GLTFExporter.js @@ -64,6 +64,12 @@ class GLTFExporter { } ); + this.register( function ( writer ) { + + return new GLTFMaterialsIridescenceExtension( writer ); + + } ); + } register( callback ) { @@ -2411,6 +2417,60 @@ class GLTFMaterialsClearcoatExtension { } +/** + * Iridescence Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence + */ +class GLTFMaterialsIridescenceExtension { + + constructor( writer ) { + + this.writer = writer; + this.name = 'KHR_materials_iridescence'; + + } + + writeMaterial( material, materialDef ) { + + if ( ! material.isMeshPhysicalMaterial ) return; + + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; + + const extensionDef = {}; + + extensionDef.iridescenceFactor = material.iridescence; + + if ( material.iridescenceMap ) { + + const iridescenceMapDef = { index: writer.processTexture( material.iridescenceMap ) }; + writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap ); + extensionDef.iridescenceTexture = iridescenceMapDef; + + } + + extensionDef.iridescenceIor = material.iridescenceIOR; + extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[ 0 ]; + extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[ 1 ]; + + if ( material.iridescenceThicknessMap ) { + + const iridescenceThicknessMapDef = { index: writer.processTexture( material.iridescenceThicknessMap ) }; + writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap ); + extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef; + + } + + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; + + extensionsUsed[ this.name ] = true; + + } + +} + /** * Transmission Materials Extension * diff --git a/examples/jsm/loaders/GLTFLoader.js b/examples/jsm/loaders/GLTFLoader.js index 4498dc52112c39..bdb4445e37042b 100644 --- a/examples/jsm/loaders/GLTFLoader.js +++ b/examples/jsm/loaders/GLTFLoader.js @@ -129,6 +129,12 @@ class GLTFLoader extends Loader { } ); + this.register( function ( parser ) { + + return new GLTFMaterialsIridescenceExtension( parser ); + + } ); + this.register( function ( parser ) { return new GLTFLightsExtension( parser ); @@ -454,6 +460,7 @@ const EXTENSIONS = { KHR_MATERIALS_SHEEN: 'KHR_materials_sheen', KHR_MATERIALS_SPECULAR: 'KHR_materials_specular', KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission', + KHR_MATERIALS_IRIDESCENCE: 'KHR_materials_iridescence', KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', KHR_MATERIALS_VOLUME: 'KHR_materials_volume', KHR_TEXTURE_BASISU: 'KHR_texture_basisu', @@ -768,6 +775,94 @@ class GLTFMaterialsClearcoatExtension { } +/** + * Iridescence Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence + */ +class GLTFMaterialsIridescenceExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_IRIDESCENCE; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + + const extension = materialDef.extensions[ this.name ]; + + if ( extension.iridescenceFactor !== undefined ) { + + materialParams.iridescence = extension.iridescenceFactor; + + } + + if ( extension.iridescenceTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'iridescenceMap', extension.iridescenceTexture ) ); + + } + + if ( extension.iridescenceIor !== undefined ) { + + materialParams.iridescenceIOR = extension.iridescenceIor; + + } + + if ( materialParams.iridescenceThicknessRange === undefined ) { + + materialParams.iridescenceThicknessRange = [ 100, 400 ]; + + } + + if ( extension.iridescenceThicknessMinimum !== undefined ) { + + materialParams.iridescenceThicknessRange[ 0 ] = extension.iridescenceThicknessMinimum; + + } + + if ( extension.iridescenceThicknessMaximum !== undefined ) { + + materialParams.iridescenceThicknessRange[ 1 ] = extension.iridescenceThicknessMaximum; + + } + + if ( extension.iridescenceThicknessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'iridescenceThicknessMap', extension.iridescenceThicknessTexture ) ); + + } + + return Promise.all( pending ); + + } + +} + /** * Sheen Materials Extension * diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js index f4025ee46823aa..17ec4fc48e4fc6 100644 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js @@ -141,6 +141,24 @@ class WebGLNodeBuilder extends NodeBuilder { } + if ( material.iridescenceNode && material.iridescenceNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( material.iridescenceNode, 'IRIDESCENCE', 'float' ) ); + + } + + if ( material.iridescenceIORNode && material.iridescenceIORNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( material.iridescenceIORNode, 'IRIDESCENCE_IOR', 'float' ) ); + + } + + if ( material.iridescenceThicknessNode && material.iridescenceThicknessNode.isNode ) { + + this.addSlot( 'fragment', new SlotNode( material.iridescenceThicknessNode, 'IRIDESCENCE_THICKNESS', 'float' ) ); + + } + if ( material.envNode && material.envNode.isNode ) { const envRadianceNode = new WebGLPhysicalContextNode( WebGLPhysicalContextNode.RADIANCE, material.envNode ); @@ -423,6 +441,9 @@ ${this.shader[ getShaderStageProperty( shaderStage ) ]} const metalnessNode = this.getSlot( 'fragment', 'METALNESS' ); const clearcoatNode = this.getSlot( 'fragment', 'CLEARCOAT' ); const clearcoatRoughnessNode = this.getSlot( 'fragment', 'CLEARCOAT_ROUGHNESS' ); + const iridescenceNode = this.getSlot( 'fragment', 'IRIDESCENCE' ); + const iridescenceIORNode = this.getSlot( 'fragment', 'IRIDESCENCE_IOR' ); + const iridescenceThicknessNode = this.getSlot( 'fragment', 'IRIDESCENCE_THICKNESS' ); const positionNode = this.getSlot( 'vertex', 'POSITION' ); const sizeNode = this.getSlot( 'vertex', 'SIZE' ); @@ -507,6 +528,36 @@ ${this.shader[ getShaderStageProperty( shaderStage ) ]} } + if ( iridescenceNode !== undefined ) { + + this.addCodeAfterSnippet( + 'fragment', + 'iridescence_fragment', + `${iridescenceNode.code}\n\tmaterial.iridescence = ${iridescenceNode.result};` + ); + + } + + if ( iridescenceIORNode !== undefined ) { + + this.addCodeAfterSnippet( + 'fragment', + 'iridescence_fragment', + `${iridescenceIORNode.code}\n\tmaterial.iridescenceIOR = ${iridescenceIORNode.result};` + ); + + } + + if ( iridescenceThicknessNode !== undefined ) { + + this.addCodeAfterSnippet( + 'fragment', + 'iridescence_fragment', + `${iridescenceThicknessNode.code}\n\tmaterial.iridescenceThickness = ${iridescenceThicknessNode.result};` + ); + + } + if ( positionNode !== undefined ) { this.addCodeAfterInclude( diff --git a/src/loaders/MaterialLoader.js b/src/loaders/MaterialLoader.js index b53c16401fb4f8..56156309da1453 100644 --- a/src/loaders/MaterialLoader.js +++ b/src/loaders/MaterialLoader.js @@ -84,6 +84,9 @@ class MaterialLoader extends Loader { if ( json.shininess !== undefined ) material.shininess = json.shininess; if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat; if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness; + if ( json.iridescence !== undefined ) material.iridescence = json.iridescence; + if ( json.iridescenceIOR !== undefined ) material.iridescenceIOR = json.iridescenceIOR; + if ( json.iridescenceThicknessRange !== undefined ) material.iridescenceThicknessRange = json.iridescenceThicknessRange; if ( json.transmission !== undefined ) material.transmission = json.transmission; if ( json.thickness !== undefined ) material.thickness = json.thickness; if ( json.attenuationDistance !== undefined ) material.attenuationDistance = json.attenuationDistance; @@ -284,6 +287,9 @@ class MaterialLoader extends Loader { if ( json.clearcoatNormalMap !== undefined ) material.clearcoatNormalMap = getTexture( json.clearcoatNormalMap ); if ( json.clearcoatNormalScale !== undefined ) material.clearcoatNormalScale = new Vector2().fromArray( json.clearcoatNormalScale ); + if ( json.iridescenceMap !== undefined ) material.iridescenceMap = getTexture( json.iridescenceMap ); + if ( json.iridescenceThicknessMap !== undefined ) material.iridescenceThicknessMap = getTexture( json.iridescenceThicknessMap ); + if ( json.transmissionMap !== undefined ) material.transmissionMap = getTexture( json.transmissionMap ); if ( json.thicknessMap !== undefined ) material.thicknessMap = getTexture( json.thicknessMap ); diff --git a/src/materials/Material.js b/src/materials/Material.js index f5baabb7fae1c5..0cfb34f42aaa29 100644 --- a/src/materials/Material.js +++ b/src/materials/Material.js @@ -222,6 +222,22 @@ class Material extends EventDispatcher { } + if ( this.iridescence !== undefined ) data.iridescence = this.iridescence; + if ( this.iridescenceIOR !== undefined ) data.iridescenceIOR = this.iridescenceIOR; + if ( this.iridescenceThicknessRange !== undefined ) data.iridescenceThicknessRange = this.iridescenceThicknessRange; + + if ( this.iridescenceMap && this.iridescenceMap.isTexture ) { + + data.iridescenceMap = this.iridescenceMap.toJSON( meta ).uuid; + + } + + if ( this.iridescenceThicknessMap && this.iridescenceThicknessMap.isTexture ) { + + data.iridescenceThicknessMap = this.iridescenceThicknessMap.toJSON( meta ).uuid; + + } + if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid; if ( this.matcap && this.matcap.isTexture ) data.matcap = this.matcap.toJSON( meta ).uuid; if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; diff --git a/src/materials/MeshPhysicalMaterial.js b/src/materials/MeshPhysicalMaterial.js index c26a30c5d75b85..45292a20fa0763 100644 --- a/src/materials/MeshPhysicalMaterial.js +++ b/src/materials/MeshPhysicalMaterial.js @@ -41,6 +41,11 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { } } ); + this.iridescenceMap = null; + this.iridescenceIOR = 1.3; + this.iridescenceThicknessRange = [ 100, 400 ]; + this.iridescenceThicknessMap = null; + this.sheenColor = new Color( 0x000000 ); this.sheenColorMap = null; this.sheenRoughness = 1.0; @@ -60,6 +65,7 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { this._sheen = 0.0; this._clearcoat = 0; + this._iridescence = 0; this._transmission = 0; this.setValues( parameters ); @@ -102,6 +108,24 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { } + get iridescence() { + + return this._iridescence; + + } + + set iridescence( value ) { + + if ( this._iridescence > 0 !== value > 0 ) { + + this.version ++; + + } + + this._iridescence = value; + + } + get transmission() { return this._transmission; @@ -140,6 +164,12 @@ class MeshPhysicalMaterial extends MeshStandardMaterial { this.ior = source.ior; + this.iridescence = source.iridescence; + this.iridescenceMap = source.iridescenceMap; + this.iridescenceIOR = source.iridescenceIOR; + this.iridescenceThicknessRange = [ ...source.iridescenceThicknessRange ]; + this.iridescenceThicknessMap = source.iridescenceThicknessMap; + this.sheen = source.sheen; this.sheenColor.copy( source.sheenColor ); this.sheenColorMap = source.sheenColorMap; diff --git a/src/renderers/shaders/ShaderChunk.js b/src/renderers/shaders/ShaderChunk.js index ffbcd12944e5b2..a6053380bca1b9 100644 --- a/src/renderers/shaders/ShaderChunk.js +++ b/src/renderers/shaders/ShaderChunk.js @@ -7,6 +7,7 @@ import aomap_pars_fragment from './ShaderChunk/aomap_pars_fragment.glsl.js'; import begin_vertex from './ShaderChunk/begin_vertex.glsl.js'; import beginnormal_vertex from './ShaderChunk/beginnormal_vertex.glsl.js'; import bsdfs from './ShaderChunk/bsdfs.glsl.js'; +import iridescence_fragment from './ShaderChunk/iridescence_fragment.glsl.js'; import bumpmap_pars_fragment from './ShaderChunk/bumpmap_pars_fragment.glsl.js'; import clipping_planes_fragment from './ShaderChunk/clipping_planes_fragment.glsl.js'; import clipping_planes_pars_fragment from './ShaderChunk/clipping_planes_pars_fragment.glsl.js'; @@ -72,6 +73,7 @@ import normalmap_pars_fragment from './ShaderChunk/normalmap_pars_fragment.glsl. import clearcoat_normal_fragment_begin from './ShaderChunk/clearcoat_normal_fragment_begin.glsl.js'; import clearcoat_normal_fragment_maps from './ShaderChunk/clearcoat_normal_fragment_maps.glsl.js'; import clearcoat_pars_fragment from './ShaderChunk/clearcoat_pars_fragment.glsl.js'; +import iridescence_pars_fragment from './ShaderChunk/iridescence_pars_fragment.glsl.js'; import output_fragment from './ShaderChunk/output_fragment.glsl.js'; import packing from './ShaderChunk/packing.glsl.js'; import premultiplied_alpha_fragment from './ShaderChunk/premultiplied_alpha_fragment.glsl.js'; @@ -129,6 +131,7 @@ export const ShaderChunk = { begin_vertex: begin_vertex, beginnormal_vertex: beginnormal_vertex, bsdfs: bsdfs, + iridescence_fragment: iridescence_fragment, bumpmap_pars_fragment: bumpmap_pars_fragment, clipping_planes_fragment: clipping_planes_fragment, clipping_planes_pars_fragment: clipping_planes_pars_fragment, @@ -194,6 +197,7 @@ export const ShaderChunk = { clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin, clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps, clearcoat_pars_fragment: clearcoat_pars_fragment, + iridescence_pars_fragment: iridescence_pars_fragment, output_fragment: output_fragment, packing: packing, premultiplied_alpha_fragment: premultiplied_alpha_fragment, diff --git a/src/renderers/shaders/ShaderChunk/bsdfs.glsl.js b/src/renderers/shaders/ShaderChunk/bsdfs.glsl.js index d13de5fdb7cea7..57605914bbadfe 100644 --- a/src/renderers/shaders/ShaderChunk/bsdfs.glsl.js +++ b/src/renderers/shaders/ShaderChunk/bsdfs.glsl.js @@ -19,6 +19,27 @@ vec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) { } // validated +float F_Schlick( const in float f0, const in float f90, const in float dotVH ) { + + // Original approximation by Christophe Schlick '94 + // float fresnel = pow( 1.0 - dotVH, 5.0 ); + + // Optimized variant (presented by Epic at SIGGRAPH '13) + // https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + float fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH ); + + return f0 * ( 1.0 - fresnel ) + ( f90 * fresnel ); + +} // validated + +vec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) { + float x = clamp( 1.0 - dotVH, 0.0, 1.0 ); + float x2 = x * x; + float x5 = clamp( x * x2 * x2, 0.0, 0.9999 ); + + return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 ); +} + // Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2 // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf float V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) { @@ -67,6 +88,31 @@ vec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 norm } +#ifdef USE_IRIDESCENCE + +vec3 BRDF_GGX_Iridescence( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 f0, const in float f90, const in float iridescence, const in vec3 iridescenceFresnel, const in float roughness ) { + + float alpha = pow2( roughness ); // UE4's roughness + + vec3 halfDir = normalize( lightDir + viewDir ); + + float dotNL = saturate( dot( normal, lightDir ) ); + float dotNV = saturate( dot( normal, viewDir ) ); + float dotNH = saturate( dot( normal, halfDir ) ); + float dotVH = saturate( dot( viewDir, halfDir ) ); + + vec3 F = mix(F_Schlick( f0, f90, dotVH ), iridescenceFresnel, iridescence); + + float V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV ); + + float D = D_GGX( alpha, dotNH ); + + return F * ( V * D ); + +} + +#endif + // Rect Area Light // Real-Time Polygonal-Light Shading with Linearly Transformed Cosines diff --git a/src/renderers/shaders/ShaderChunk/common.glsl.js b/src/renderers/shaders/ShaderChunk/common.glsl.js index e309511e41b4da..808b78b74146dd 100644 --- a/src/renderers/shaders/ShaderChunk/common.glsl.js +++ b/src/renderers/shaders/ShaderChunk/common.glsl.js @@ -13,6 +13,7 @@ export default /* glsl */` #define whiteComplement( a ) ( 1.0 - saturate( a ) ) float pow2( const in float x ) { return x*x; } +vec3 pow2( const in vec3 x ) { return x*x; } float pow3( const in float x ) { return x*x*x; } float pow4( const in float x ) { float x2 = x*x; return x2*x2; } float max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); } diff --git a/src/renderers/shaders/ShaderChunk/iridescence_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/iridescence_fragment.glsl.js new file mode 100644 index 00000000000000..ef894c1bd1db24 --- /dev/null +++ b/src/renderers/shaders/ShaderChunk/iridescence_fragment.glsl.js @@ -0,0 +1,107 @@ +export default /* glsl */` + +#ifdef USE_IRIDESCENCE + +// XYZ to sRGB color space +const mat3 XYZ_TO_REC709 = mat3( + 3.2404542, -0.9692660, 0.0556434, + -1.5371385, 1.8760108, -0.2040259, + -0.4985314, 0.0415560, 1.0572252 +); + +// Assume air interface for top +// Note: We don't handle the case fresnel0 == 1 +vec3 Fresnel0ToIor( vec3 fresnel0 ) { + vec3 sqrtF0 = sqrt( fresnel0 ); + return ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 ); +} + +// Conversion FO/IOR +vec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) { + return pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) ); +} + +// ior is a value between 1.0 and 3.0. 1.0 is air interface +float IorToFresnel0( float transmittedIor, float incidentIor ) { + return pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor )); +} + +// Fresnel equations for dielectric/dielectric interfaces. +// Ref: https://belcour.github.io/blog/research/2017/05/01/brdf-thin-film.html +// Evaluation XYZ sensitivity curves in Fourier space +vec3 evalSensitivity( float OPD, vec3 shift ) { + float phase = 2.0 * PI * OPD * 1.0e-9; + vec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 ); + vec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 ); + vec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 ); + + vec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( -pow2( phase ) * var ); + xyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[0] ) * exp( -4.5282e+09 * pow2( phase ) ); + xyz /= 1.0685e-7; + + vec3 srgb = XYZ_TO_REC709 * xyz; + return srgb; +} + +vec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) { + vec3 I; + + // Force iridescenceIOR -> outsideIOR when thinFilmThickness -> 0.0 + float iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) ); + // Evaluate the cosTheta on the base layer (Snell law) + float sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) ); + + // Handle TIR: + float cosTheta2Sq = 1.0 - sinTheta2Sq; + if ( cosTheta2Sq < 0.0 ) { + return vec3( 1.0 ); + } + + float cosTheta2 = sqrt( cosTheta2Sq ); + + // First interface + float R0 = IorToFresnel0( iridescenceIOR, outsideIOR ); + float R12 = F_Schlick( R0, 1.0, cosTheta1 ); + float R21 = R12; + float T121 = 1.0 - R12; + float phi12 = 0.0; + if ( iridescenceIOR < outsideIOR ) phi12 = PI; + float phi21 = PI - phi12; + + // Second interface + vec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) ); // guard against 1.0 + vec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR ); + vec3 R23 = F_Schlick( R1, 1.0, cosTheta2 ); + vec3 phi23 = vec3( 0.0 ); + if ( baseIOR[0] < iridescenceIOR ) phi23[0] = PI; + if ( baseIOR[1] < iridescenceIOR ) phi23[1] = PI; + if ( baseIOR[2] < iridescenceIOR ) phi23[2] = PI; + + // Phase shift + float OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2; + vec3 phi = vec3( phi21 ) + phi23; + + // Compound terms + vec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 ); + vec3 r123 = sqrt( R123 ); + vec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 ); + + // Reflectance term for m = 0 (DC term amplitude) + vec3 C0 = R12 + Rs; + I = C0; + + // Reflectance term for m > 0 (pairs of diracs) + vec3 Cm = Rs - T121; + for ( int m = 1; m <= 2; ++m ) { + Cm *= r123; + vec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi ); + I += Cm * Sm; + } + + // Since out of gamut colors might be produced, negative color values are clamped to 0. + return max( I, vec3( 0.0 ) ); +} + +#endif + +`; diff --git a/src/renderers/shaders/ShaderChunk/iridescence_pars_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/iridescence_pars_fragment.glsl.js new file mode 100644 index 00000000000000..b58bb93404ff93 --- /dev/null +++ b/src/renderers/shaders/ShaderChunk/iridescence_pars_fragment.glsl.js @@ -0,0 +1,14 @@ +export default /* glsl */` + +#ifdef USE_IRIDESCENCEMAP + + uniform sampler2D iridescenceMap; + +#endif + +#ifdef USE_IRIDESCENCE_THICKNESSMAP + + uniform sampler2D iridescenceThicknessMap; + +#endif +`; diff --git a/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js b/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js index 53aca993466960..51795f1d670041 100644 --- a/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js +++ b/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js @@ -26,6 +26,31 @@ geometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPositi #endif +#ifdef USE_IRIDESCENCE + +float dotNV = saturate( dot( normal, geometry.viewDir ) ); + +if ( material.iridescenceThickness == 0.0 ) { + + material.iridescence = 0.0; + +} else { + + material.iridescence = saturate( material.iridescence ); + +} + +if ( material.iridescence > 0.0 ) { + + material.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNV, material.iridescenceThickness, material.specularColor ); + + // Iridescence F0 approximation + material.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNV ); + +} + +#endif + IncidentLight directLight; #if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct ) diff --git a/src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js index 1e47d88909f3cd..ed7b462e43f8ca 100644 --- a/src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js @@ -73,6 +73,29 @@ material.roughness = min( material.roughness, 1.0 ); #endif +#ifdef USE_IRIDESCENCE + + material.iridescence = iridescence; + material.iridescenceIOR = iridescenceIOR; + + #ifdef USE_IRIDESCENCEMAP + + material.iridescence *= texture2D( iridescenceMap, vUv ).r; + + #endif + + #ifdef USE_IRIDESCENCE_THICKNESSMAP + + material.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vUv ).g + iridescenceThicknessMinimum; + + #else + + material.iridescenceThickness = iridescenceThicknessMaximum; + + #endif + +#endif + #ifdef USE_SHEEN material.sheenColor = sheenColor; diff --git a/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js index 61a116d53cd4f1..efb8f9a3fca233 100644 --- a/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js @@ -13,6 +13,14 @@ struct PhysicalMaterial { float clearcoatF90; #endif + #ifdef USE_IRIDESCENCE + float iridescence; + float iridescenceIOR; + float iridescenceThickness; + vec3 iridescenceFresnel; + vec3 iridescenceF0; + #endif + #ifdef USE_SHEEN vec3 sheenColor; float sheenRoughness; @@ -76,16 +84,30 @@ vec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 // Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting" // Approximates multiscattering in order to preserve energy. // http://www.jcgt.org/published/0008/01/03/ +#ifdef USE_IRIDESCENCE +void computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) { +#else void computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) { +#endif vec2 fab = DFGApprox( normal, viewDir, roughness ); - vec3 FssEss = specularColor * fab.x + specularF90 * fab.y; + #ifdef USE_IRIDESCENCE + + vec3 Fr = mix( specularColor, iridescenceF0, iridescence ); + + #else + + vec3 Fr = specularColor; + + #endif + + vec3 FssEss = Fr * fab.x + specularF90 * fab.y; float Ess = fab.x + fab.y; float Ems = 1.0 - Ess; - vec3 Favg = specularColor + ( 1.0 - specularColor ) * 0.047619; // 1/21 + vec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619; // 1/21 vec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg ); singleScatter += FssEss; @@ -157,8 +179,15 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricC #endif - reflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness ); + #ifdef USE_IRIDESCENCE + reflectedLight.directSpecular += irradiance * BRDF_GGX_Iridescence( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness ); + + #else + + reflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness ); + + #endif reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor ); } @@ -189,9 +218,18 @@ void RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradia vec3 multiScattering = vec3( 0.0 ); vec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI; - computeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering ); + #ifdef USE_IRIDESCENCE + + computeMultiscatteringIridescence( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering ); + + #else + + computeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering ); + + #endif - vec3 diffuse = material.diffuseColor * ( 1.0 - ( singleScattering + multiScattering ) ); + vec3 totalScattering = singleScattering + multiScattering; + vec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) ); reflectedLight.indirectSpecular += radiance * singleScattering; reflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance; diff --git a/src/renderers/shaders/ShaderLib.js b/src/renderers/shaders/ShaderLib.js index 0eb045a995d6ec..126df13b543a3d 100644 --- a/src/renderers/shaders/ShaderLib.js +++ b/src/renderers/shaders/ShaderLib.js @@ -296,6 +296,12 @@ ShaderLib.physical = { clearcoatRoughnessMap: { value: null }, clearcoatNormalScale: { value: new Vector2( 1, 1 ) }, clearcoatNormalMap: { value: null }, + iridescence: { value: 0 }, + iridescenceMap: { value: null }, + iridescenceIOR: { value: 1.3 }, + iridescenceThicknessMinimum: { value: 100 }, + iridescenceThicknessMaximum: { value: 400 }, + iridescenceThicknessMap: { value: null }, sheen: { value: 0 }, sheenColor: { value: new Color( 0x000000 ) }, sheenColorMap: { value: null }, diff --git a/src/renderers/shaders/ShaderLib/meshphysical.glsl.js b/src/renderers/shaders/ShaderLib/meshphysical.glsl.js index 7f8b30807c0c1d..6a1352fe77d9f1 100644 --- a/src/renderers/shaders/ShaderLib/meshphysical.glsl.js +++ b/src/renderers/shaders/ShaderLib/meshphysical.glsl.js @@ -94,6 +94,13 @@ uniform float opacity; uniform float clearcoatRoughness; #endif +#ifdef USE_IRIDESCENCE + uniform float iridescence; + uniform float iridescenceIOR; + uniform float iridescenceThicknessMinimum; + uniform float iridescenceThicknessMaximum; +#endif + #ifdef USE_SHEEN uniform vec3 sheenColor; uniform float sheenRoughness; @@ -122,6 +129,7 @@ varying vec3 vViewPosition; #include #include #include +#include #include #include #include @@ -134,6 +142,7 @@ varying vec3 vViewPosition; #include #include #include +#include #include #include #include diff --git a/src/renderers/webgl/WebGLMaterials.js b/src/renderers/webgl/WebGLMaterials.js index 405c6280625f6d..188aa8113e8cb1 100644 --- a/src/renderers/webgl/WebGLMaterials.js +++ b/src/renderers/webgl/WebGLMaterials.js @@ -214,10 +214,12 @@ function WebGLMaterials( renderer, properties ) { // 10. clearcoat map // 11. clearcoat normal map // 12. clearcoat roughnessMap map - // 13. specular intensity map - // 14. specular tint map - // 15. transmission map - // 16. thickness map + // 13. iridescence map + // 14. iridescence thickness map + // 15. specular intensity map + // 16. specular tint map + // 17. transmission map + // 18. thickness map let uvScaleMap; @@ -269,6 +271,14 @@ function WebGLMaterials( renderer, properties ) { uvScaleMap = material.clearcoatRoughnessMap; + } else if ( material.iridescenceMap ) { + + uvScaleMap = material.iridescenceMap; + + } else if ( material.iridescenceThicknessMap ) { + + uvScaleMap = material.iridescenceThicknessMap; + } else if ( material.specularIntensityMap ) { uvScaleMap = material.specularIntensityMap; @@ -576,6 +586,27 @@ function WebGLMaterials( renderer, properties ) { } + if ( material.iridescence > 0 ) { + + uniforms.iridescence.value = material.iridescence; + uniforms.iridescenceIOR.value = material.iridescenceIOR; + uniforms.iridescenceThicknessMinimum.value = material.iridescenceThicknessRange[ 0 ]; + uniforms.iridescenceThicknessMaximum.value = material.iridescenceThicknessRange[ 1 ]; + + if ( material.iridescenceMap ) { + + uniforms.iridescenceMap.value = material.iridescenceMap; + + } + + if ( material.iridescenceThicknessMap ) { + + uniforms.iridescenceThicknessMap.value = material.iridescenceThicknessMap; + + } + + } + if ( material.transmission > 0 ) { uniforms.transmission.value = material.transmission; diff --git a/src/renderers/webgl/WebGLProgram.js b/src/renderers/webgl/WebGLProgram.js index 88311195de26e8..051f22e15b77b3 100644 --- a/src/renderers/webgl/WebGLProgram.js +++ b/src/renderers/webgl/WebGLProgram.js @@ -478,6 +478,9 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', + parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', + parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', + parameters.displacementMap && parameters.supportsVertexTextures ? '#define USE_DISPLACEMENTMAP' : '', parameters.specularMap ? '#define USE_SPECULARMAP' : '', @@ -633,6 +636,10 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', + parameters.iridescence ? '#define USE_IRIDESCENCE' : '', + parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', + parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', + parameters.specularMap ? '#define USE_SPECULARMAP' : '', parameters.specularIntensityMap ? '#define USE_SPECULARINTENSITYMAP' : '', parameters.specularColorMap ? '#define USE_SPECULARCOLORMAP' : '', diff --git a/src/renderers/webgl/WebGLPrograms.js b/src/renderers/webgl/WebGLPrograms.js index 50e45f7bd2a480..d70851732d83f9 100644 --- a/src/renderers/webgl/WebGLPrograms.js +++ b/src/renderers/webgl/WebGLPrograms.js @@ -99,6 +99,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities const useAlphaTest = material.alphaTest > 0; const useClearcoat = material.clearcoat > 0; + const useIridescence = material.iridescence > 0; const parameters = { @@ -144,6 +145,10 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities clearcoatRoughnessMap: useClearcoat && !! material.clearcoatRoughnessMap, clearcoatNormalMap: useClearcoat && !! material.clearcoatNormalMap, + iridescence: useIridescence, + iridescenceMap: useIridescence && !! material.iridescenceMap, + iridescenceThicknessMap: useIridescence && !! material.iridescenceThicknessMap, + displacementMap: !! material.displacementMap, roughnessMap: !! material.roughnessMap, metalnessMap: !! material.metalnessMap, @@ -171,8 +176,8 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities vertexTangents: ( !! material.normalMap && !! geometry.attributes.tangent ), vertexColors: material.vertexColors, vertexAlphas: material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4, - vertexUvs: !! material.map || !! material.bumpMap || !! material.normalMap || !! material.specularMap || !! material.alphaMap || !! material.emissiveMap || !! material.roughnessMap || !! material.metalnessMap || !! material.clearcoatMap || !! material.clearcoatRoughnessMap || !! material.clearcoatNormalMap || !! material.displacementMap || !! material.transmissionMap || !! material.thicknessMap || !! material.specularIntensityMap || !! material.specularColorMap || !! material.sheenColorMap || !! material.sheenRoughnessMap, - uvsVertexOnly: ! ( !! material.map || !! material.bumpMap || !! material.normalMap || !! material.specularMap || !! material.alphaMap || !! material.emissiveMap || !! material.roughnessMap || !! material.metalnessMap || !! material.clearcoatNormalMap || material.transmission > 0 || !! material.transmissionMap || !! material.thicknessMap || !! material.specularIntensityMap || !! material.specularColorMap || material.sheen > 0 || !! material.sheenColorMap || !! material.sheenRoughnessMap ) && !! material.displacementMap, + vertexUvs: !! material.map || !! material.bumpMap || !! material.normalMap || !! material.specularMap || !! material.alphaMap || !! material.emissiveMap || !! material.roughnessMap || !! material.metalnessMap || !! material.clearcoatMap || !! material.clearcoatRoughnessMap || !! material.clearcoatNormalMap || !! material.iridescenceMap || !! material.iridescenceThicknessMap || !! material.displacementMap || !! material.transmissionMap || !! material.thicknessMap || !! material.specularIntensityMap || !! material.specularColorMap || !! material.sheenColorMap || !! material.sheenRoughnessMap, + uvsVertexOnly: ! ( !! material.map || !! material.bumpMap || !! material.normalMap || !! material.specularMap || !! material.alphaMap || !! material.emissiveMap || !! material.roughnessMap || !! material.metalnessMap || !! material.clearcoatNormalMap || !! material.iridescenceMap || !! material.iridescenceThicknessMap || material.transmission > 0 || !! material.transmissionMap || !! material.thicknessMap || !! material.specularIntensityMap || !! material.specularColorMap || material.sheen > 0 || !! material.sheenColorMap || !! material.sheenRoughnessMap ) && !! material.displacementMap, fog: !! fog, useFog: material.fog === true, @@ -347,32 +352,38 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities _programLayers.enable( 16 ); if ( parameters.clearcoatNormalMap ) _programLayers.enable( 17 ); - if ( parameters.displacementMap ) + if ( parameters.iridescence ) _programLayers.enable( 18 ); - if ( parameters.specularMap ) + if ( parameters.iridescenceMap ) _programLayers.enable( 19 ); - if ( parameters.roughnessMap ) + if ( parameters.iridescenceThicknessMap ) _programLayers.enable( 20 ); - if ( parameters.metalnessMap ) + if ( parameters.displacementMap ) _programLayers.enable( 21 ); - if ( parameters.gradientMap ) + if ( parameters.specularMap ) _programLayers.enable( 22 ); - if ( parameters.alphaMap ) + if ( parameters.roughnessMap ) _programLayers.enable( 23 ); - if ( parameters.alphaTest ) + if ( parameters.metalnessMap ) _programLayers.enable( 24 ); - if ( parameters.vertexColors ) + if ( parameters.gradientMap ) _programLayers.enable( 25 ); - if ( parameters.vertexAlphas ) + if ( parameters.alphaMap ) _programLayers.enable( 26 ); - if ( parameters.vertexUvs ) + if ( parameters.alphaTest ) _programLayers.enable( 27 ); - if ( parameters.vertexTangents ) + if ( parameters.vertexColors ) _programLayers.enable( 28 ); - if ( parameters.uvsVertexOnly ) + if ( parameters.vertexAlphas ) _programLayers.enable( 29 ); - if ( parameters.fog ) + if ( parameters.vertexUvs ) _programLayers.enable( 30 ); + if ( parameters.vertexTangents ) + _programLayers.enable( 31 ); + if ( parameters.uvsVertexOnly ) + _programLayers.enable( 32 ); + if ( parameters.fog ) + _programLayers.enable( 33 ); array.push( _programLayers.mask ); _programLayers.disableAll();