Skip to content

Commit dc5aafe

Browse files
authored
Add specular to MeshPhysicalMaterial and GLTFLoader KHR_materials_specular support (mrdoob#22156)
* Add specularStrength and specular to MeshPhysicalMaterial * GLTFLoader: Add KHR_materials_specular support * Add specularStrength and specular to GUI in webgl_materials_physical_transmission example * Update webgl_loader_gltf_transmission example * Update example screenshot * MeshPhysicalMaterial: Rename specularStrength and specular to specularIntensity and specularTint
1 parent dd69eba commit dc5aafe

18 files changed

+225
-18
lines changed

docs/examples/en/loaders/GLTFLoader.html

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ <h2>Extensions</h2>
3232
<li>KHR_materials_clearcoat</li>
3333
<li>KHR_materials_ior</li>
3434
<li>KHR_materials_pbrSpecularGlossiness</li>
35+
<li>KHR_materials_specular</li>
3536
<li>KHR_materials_transmission</li>
3637
<li>KHR_materials_unlit</li>
3738
<li>KHR_materials_volume</li>

docs/examples/zh/loaders/GLTFLoader.html

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ <h2>扩展</h2>
3030
<li>KHR_materials_clearcoat</li>
3131
<li>KHR_materials_ior</li>
3232
<li>KHR_materials_pbrSpecularGlossiness</li>
33+
<li>KHR_materials_specular</li>
3334
<li>KHR_materials_transmission</li>
3435
<li>KHR_materials_unlit</li>
3536
<li>KHR_materials_volume</li>

examples/jsm/loaders/GLTFLoader.js

+77-1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ class GLTFLoader extends Loader {
111111

112112
} );
113113

114+
this.register( function ( parser ) {
115+
116+
return new GLTFMaterialsSpecularExtension( parser );
117+
118+
} );
119+
114120
this.register( function ( parser ) {
115121

116122
return new GLTFLightsExtension( parser );
@@ -421,6 +427,7 @@ const EXTENSIONS = {
421427
KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat',
422428
KHR_MATERIALS_IOR: 'KHR_materials_ior',
423429
KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness',
430+
KHR_MATERIALS_SPECULAR: 'KHR_materials_specular',
424431
KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission',
425432
KHR_MATERIALS_UNLIT: 'KHR_materials_unlit',
426433
KHR_MATERIALS_VOLUME: 'KHR_materials_volume',
@@ -861,6 +868,73 @@ class GLTFMaterialsIorExtension {
861868

862869
}
863870

871+
/**
872+
* Materials specular Extension
873+
*
874+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular
875+
*/
876+
class GLTFMaterialsSpecularExtension {
877+
878+
constructor( parser ) {
879+
880+
this.parser = parser;
881+
this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR;
882+
883+
}
884+
885+
getMaterialType( materialIndex ) {
886+
887+
const parser = this.parser;
888+
const materialDef = parser.json.materials[ materialIndex ];
889+
890+
if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
891+
892+
return MeshPhysicalMaterial;
893+
894+
}
895+
896+
extendMaterialParams( materialIndex, materialParams ) {
897+
898+
const parser = this.parser;
899+
const materialDef = parser.json.materials[ materialIndex ];
900+
901+
if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
902+
903+
return Promise.resolve();
904+
905+
}
906+
907+
const pending = [];
908+
909+
const extension = materialDef.extensions[ this.name ];
910+
911+
materialParams.specularIntensity = extension.specularFactor !== undefined ? extension.specularFactor : 1.0;
912+
913+
if ( extension.specularTexture !== undefined ) {
914+
915+
pending.push( parser.assignTexture( materialParams, 'specularIntensityMap', extension.specularTexture ) );
916+
917+
}
918+
919+
const colorArray = extension.specularColorFactor || [ 1, 1, 1 ];
920+
materialParams.specularTint = new Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] );
921+
922+
if ( extension.specularColorTexture !== undefined ) {
923+
924+
pending.push( parser.assignTexture( materialParams, 'specularTintMap', extension.specularColorTexture ).then( function ( texture ) {
925+
926+
texture.encoding = sRGBEncoding;
927+
928+
} ) );
929+
930+
}
931+
932+
return Promise.all( pending );
933+
934+
}
935+
936+
}
937+
864938
/**
865939
* BasisU Texture Extension
866940
*
@@ -2780,7 +2854,7 @@ class GLTFParser {
27802854
* @param {Object} materialParams
27812855
* @param {string} mapName
27822856
* @param {Object} mapDef
2783-
* @return {Promise}
2857+
* @return {Promise<Texture>}
27842858
*/
27852859
assignTexture( materialParams, mapName, mapDef ) {
27862860

@@ -2812,6 +2886,8 @@ class GLTFParser {
28122886

28132887
materialParams[ mapName ] = texture;
28142888

2889+
return texture;
2890+
28152891
} );
28162892

28172893
}
Binary file not shown.
Loading

examples/webgl_loader_gltf_transmission.html

+8-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<div id="info">
1212
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - GLTFLoader + <a href="https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission" target="_blank" rel="noopener">KHR_materials_transmission</a> extension<br />
1313
Iridescent Dish With Olives by <a href="https://github.com/echadwick-wayfair" target="_blank" rel="noopener">Eric Chadwick</a><br />
14-
<a href="https://hdrihaven.com/hdri/?h=quarry_01" target="_blank" rel="noopener">Quarry</a> by <a href="https://hdrihaven.com/" target="_blank" rel="noopener">HDRI Haven</a>
14+
<a href="https://hdrihaven.com/hdri/?h=royal_esplanade" target="_blank" rel="noopener">Royal Esplanade</a> by <a href="https://hdrihaven.com/" target="_blank" rel="noopener">HDRI Haven</a>
1515
</div>
1616

1717
<script type="module">
@@ -22,6 +22,8 @@
2222
import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
2323
import { RGBELoader } from './jsm/loaders/RGBELoader.js';
2424

25+
import { DRACOLoader } from './jsm/loaders/DRACOLoader.js';
26+
2527
let camera, scene, renderer, clock, mixer;
2628

2729
init();
@@ -42,7 +44,7 @@
4244
new RGBELoader()
4345
.setDataType( THREE.FloatType )
4446
.setPath( 'textures/equirectangular/' )
45-
.load( 'quarry_01_1k.hdr', function ( texture ) {
47+
.load( 'royal_esplanade_1k.hdr', function ( texture ) {
4648

4749
texture.mapping = THREE.EquirectangularReflectionMapping;
4850

@@ -51,7 +53,10 @@
5153

5254
// model
5355

54-
const loader = new GLTFLoader().setPath( 'models/gltf/' );
56+
const loader = new GLTFLoader()
57+
.setPath( 'models/gltf/' )
58+
.setDRACOLoader( new DRACOLoader().setDecoderPath( 'js/libs/draco/gltf/' ) );
59+
5560
loader.load( 'IridescentDishWithOlives.glb', function ( gltf ) {
5661

5762
mixer = new THREE.AnimationMixer( gltf.scene );

examples/webgl_materials_physical_transmission.html

+20
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
roughness: 0,
2828
reflectivity: 0.5,
2929
thickness: 0.01,
30+
specularIntensity: 1,
31+
specularTint: 0xffffff,
3032
envMapIntensity: 1,
3133
lightIntensity: 1,
3234
exposure: 1
@@ -89,6 +91,8 @@
8991
envMap: hdrEquirect,
9092
envMapIntensity: params.envMapIntensity,
9193
transmission: params.transmission, // use material.transmission for glass materials
94+
specularIntensity: params.specularIntensity,
95+
specularTint: params.specularTint,
9296
opacity: params.opacity,
9397
side: THREE.DoubleSide,
9498
transparent: true
@@ -166,6 +170,22 @@
166170

167171
} );
168172

173+
gui.add( params, 'specularIntensity', 0, 1, 0.01 )
174+
.onChange( function () {
175+
176+
material.specularIntensity = params.specularIntensity;
177+
render();
178+
179+
} );
180+
181+
gui.addColor( params, 'specularTint' )
182+
.onChange( function () {
183+
184+
material.specularTint.set( params.specularTint );
185+
render();
186+
187+
} );
188+
169189
gui.add( params, 'envMapIntensity', 0, 1, 0.01 )
170190
.name( 'envMap intensity' )
171191
.onChange( function () {

src/loaders/MaterialLoader.js

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ class MaterialLoader extends Loader {
7777
if ( json.sheen !== undefined ) material.sheen = new Color().setHex( json.sheen );
7878
if ( json.emissive !== undefined && material.emissive !== undefined ) material.emissive.setHex( json.emissive );
7979
if ( json.specular !== undefined && material.specular !== undefined ) material.specular.setHex( json.specular );
80+
if ( json.specularIntensity !== undefined ) material.specularIntensity = json.specularIntensity;
81+
if ( json.specularTint !== undefined && material.specularTint !== undefined ) material.specularTint.setHex( json.specularTint );
8082
if ( json.shininess !== undefined ) material.shininess = json.shininess;
8183
if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat;
8284
if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness;
@@ -258,6 +260,8 @@ class MaterialLoader extends Loader {
258260
if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity;
259261

260262
if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap );
263+
if ( json.specularIntensityMap !== undefined ) material.specularIntensityMap = getTexture( json.specularIntensityMap );
264+
if ( json.specularTintMap !== undefined ) material.specularTintMap = getTexture( json.specularTintMap );
261265

262266
if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap );
263267
if ( json.envMapIntensity !== undefined ) material.envMapIntensity = json.envMapIntensity;

src/materials/Material.js

+4
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ class Material extends EventDispatcher {
174174
if ( this.emissiveIntensity && this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity;
175175

176176
if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex();
177+
if ( this.specularIntensity !== undefined ) data.specularIntensity = this.specularIntensity;
178+
if ( this.specularTint && this.specularTint.isColor ) data.specularTint = this.specularTint.getHex();
177179
if ( this.shininess !== undefined ) data.shininess = this.shininess;
178180
if ( this.clearcoat !== undefined ) data.clearcoat = this.clearcoat;
179181
if ( this.clearcoatRoughness !== undefined ) data.clearcoatRoughness = this.clearcoatRoughness;
@@ -243,6 +245,8 @@ class Material extends EventDispatcher {
243245

244246
if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid;
245247
if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid;
248+
if ( this.specularIntensityMap && this.specularIntensityMap.isTexture ) data.specularIntensityMap = this.specularIntensityMap.toJSON( meta ).uuid;
249+
if ( this.specularTintMap && this.specularTintMap.isTexture ) data.specularTintMap = this.specularTintMap.toJSON( meta ).uuid;
246250

247251
if ( this.envMap && this.envMap.isTexture ) {
248252

src/materials/MeshPhysicalMaterial.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ import * as MathUtils from '../math/MathUtils.js';
2323
* thickness: <float>,
2424
* thicknessMap: new THREE.Texture( <Image> ),
2525
* attenuationDistance: <float>,
26-
* attenuationColor: <Color>
26+
* attenuationColor: <Color>,
27+
*
28+
* specularIntensity: <float>,
29+
* specularIntensityhMap: new THREE.Texture( <Image> ),
30+
* specularTint: <Color>,
31+
* specularTintMap: new THREE.Texture( <Image> )
2732
* }
2833
*/
2934

@@ -74,6 +79,11 @@ class MeshPhysicalMaterial extends MeshStandardMaterial {
7479
this.attenuationDistance = 0.0;
7580
this.attenuationColor = new Color( 1, 1, 1 );
7681

82+
this.specularIntensity = 1.0;
83+
this.specularIntensityMap = null;
84+
this.specularTint = new Color( 1, 1, 1 );
85+
this.specularTintMap = null;
86+
7787
this.setValues( parameters );
7888

7989
}
@@ -116,6 +126,11 @@ class MeshPhysicalMaterial extends MeshStandardMaterial {
116126
this.attenuationDistance = source.attenuationDistance;
117127
this.attenuationColor.copy( source.attenuationColor );
118128

129+
this.specularIntensity = source.specularIntensity;
130+
this.specularIntensityMap = source.specularIntensityMap;
131+
this.specularTint.copy( source.specularTint );
132+
this.specularTintMap = source.specularTintMap;
133+
119134
return this;
120135

121136
}

src/renderers/shaders/ShaderChunk/bsdfs.glsl.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ vec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {
5656
5757
} // validated
5858
59-
vec3 F_Schlick( const in vec3 specularColor, const in float dotVH ) {
59+
vec3 F_Schlick( const in vec3 f0, const in vec3 f90, const in float dotVH ) {
6060
6161
// Original approximation by Christophe Schlick '94
6262
// float fresnel = pow( 1.0 - dotVH, 5.0 );
@@ -65,7 +65,7 @@ vec3 F_Schlick( const in vec3 specularColor, const in float dotVH ) {
6565
// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
6666
float fresnel = exp2( ( -5.55473 * dotVH - 6.98316 ) * dotVH );
6767
68-
return ( 1.0 - specularColor ) * fresnel + specularColor;
68+
return ( f90 - f0 ) * fresnel + f0;
6969
7070
} // validated
7171
@@ -125,7 +125,7 @@ float D_GGX( const in float alpha, const in float dotNH ) {
125125
}
126126
127127
// GGX Distribution, Schlick Fresnel, GGX-Smith Visibility
128-
vec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {
128+
vec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in vec3 viewDir, const in vec3 normal, const in vec3 f0, const in vec3 f90, const in float roughness ) {
129129
130130
float alpha = pow2( roughness ); // UE4's roughness
131131
@@ -136,7 +136,7 @@ vec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in vec3 view
136136
float dotNH = saturate( dot( normal, halfDir ) );
137137
float dotLH = saturate( dot( incidentLight.direction, halfDir ) );
138138
139-
vec3 F = F_Schlick( specularColor, dotLH );
139+
vec3 F = F_Schlick( f0, f90, dotLH );
140140
141141
float G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );
142142
@@ -318,7 +318,7 @@ vec3 BRDF_Specular_BlinnPhong( const in IncidentLight incidentLight, const in Ge
318318
float dotNH = saturate( dot( geometry.normal, halfDir ) );
319319
float dotLH = saturate( dot( incidentLight.direction, halfDir ) );
320320
321-
vec3 F = F_Schlick( specularColor, dotLH );
321+
vec3 F = F_Schlick( specularColor, vec3( 1.0 ), dotLH );
322322
323323
float G = G_BlinnPhong_Implicit( /* dotNL, dotNV */ );
324324

src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js

+29-1
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,39 @@ material.specularRoughness = min( material.specularRoughness, 1.0 );
1111
1212
#ifdef REFLECTIVITY
1313
14-
material.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );
14+
#ifdef SPECULAR
15+
16+
vec3 specularIntensityFactor = vec3( specularIntensity );
17+
vec3 specularTintFactor = specularTint;
18+
19+
#ifdef USE_SPECULARINTENSITYMAP
20+
21+
specularIntensityFactor *= texture2D( specularIntensityMap, vUv ).a;
22+
23+
#endif
24+
25+
#ifdef USE_SPECULARTINTMAP
26+
27+
specularTintFactor *= specularTintMapTexelToLinear( texture2D( specularTintMap, vUv ) ).rgb;
28+
29+
#endif
30+
31+
material.specularColorF90 = mix( specularIntensityFactor, vec3( 1.0 ), metalnessFactor );
32+
33+
#else
34+
35+
vec3 specularIntensityFactor = vec3( 1.0 );
36+
vec3 specularTintFactor = vec3( 1.0 );
37+
material.specularColorF90 = vec3( 1.0 );
38+
39+
#endif
40+
41+
material.specularColor = mix( min( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ) * specularTintFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );
1542
1643
#else
1744
1845
material.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );
46+
material.specularColorF90 = vec3( 1.0 );
1947
2048
#endif
2149

src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ struct PhysicalMaterial {
44
vec3 diffuseColor;
55
float specularRoughness;
66
vec3 specularColor;
7+
vec3 specularColorF90;
78
89
#ifdef CLEARCOAT
910
float clearcoat;
@@ -93,7 +94,7 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricC
9394
9495
float clearcoatDHR = material.clearcoat * clearcoatDHRApprox( material.clearcoatRoughness, ccDotNL );
9596
96-
reflectedLight.directSpecular += ccIrradiance * material.clearcoat * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.clearcoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearcoatRoughness );
97+
reflectedLight.directSpecular += ccIrradiance * material.clearcoat * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.clearcoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), vec3( 1.0 ), material.clearcoatRoughness );
9798
9899
#else
99100
@@ -109,7 +110,7 @@ void RE_Direct_Physical( const in IncidentLight directLight, const in GeometricC
109110
material.sheenColor
110111
);
111112
#else
112-
reflectedLight.directSpecular += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.normal, material.specularColor, material.specularRoughness);
113+
reflectedLight.directSpecular += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.normal, material.specularColor, material.specularColorF90, material.specularRoughness);
113114
#endif
114115
115116
reflectedLight.directDiffuse += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );

0 commit comments

Comments
 (0)