diff --git a/examples/files.js b/examples/files.js index a84dc6bae96e69..e35f16db166bce 100644 --- a/examples/files.js +++ b/examples/files.js @@ -298,6 +298,7 @@ var files = { "webgl_custom_attributes_points2", "webgl_custom_attributes_points3", "webgl_materials_modified", + "webgl_materials_modified2", "webgl_raymarching_reflect", "webgl_shadowmap_pcss", "webgl_simple_gi", diff --git a/examples/js/SpecGlossMultiUVInstanceExample.js b/examples/js/SpecGlossMultiUVInstanceExample.js new file mode 100644 index 00000000000000..e74061ed3b3e00 --- /dev/null +++ b/examples/js/SpecGlossMultiUVInstanceExample.js @@ -0,0 +1,383 @@ +// some utils + +function addOrMergeProp( material, propName, data ) { + + if ( material[ propName ] ) { + + Object.assign( material[ propName ], data ); + + } else { + + material[ propName ] = data; + + } + +} + +//serialize +function toJSON(){ + var res = THREE.Material.prototype.toJSON.call( + this, + undefined, + this._serializationManager.serialize.bind(this._serializationManager) + ) + this._serializationManager.afterSerialize.call(this._serializationManager,res) + return res +} + +// from three's texture transform api, to be applied to a uniform matrix +function setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) { + + var c = Math.cos( rotation ); + var s = Math.sin( rotation ); + + this.set( + sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, + - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, + 0, 0, 0 + ); + +} + +//spec gloss stuff --------------------------------------------------------- + +//this extends the shader to use specular gloss PBR model instead of rough/metal + +var specularMapFragmentChunk = [ + 'vec3 specularFactor = specular;', + '#ifdef USE_SPECULARMAP', + ' vec4 texelSpecular = texture2D( specularMap, vUv );', + ' texelSpecular = sRGBToLinear( texelSpecular );', + ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' specularFactor *= texelSpecular.rgb;', + '#endif', + // 'gl_FragColor = vec4(vec3(specularFactor),1.);', + // 'return;', +].join( '\n' ); + +var glossinessMapFragmentChunk = [ + 'float glossinessFactor = glossiness;', + '#ifdef USE_GLOSSINESSMAP', + ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', + ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' glossinessFactor *= texelGlossiness.a;', + // 'gl_FragColor = vec4(vec3(glossinessFactor),1.);', + // 'return;', + '#endif', +].join( '\n' ); + +var lightPhysicalFragmentChunk = [ + 'PhysicalMaterial material;', + 'material.diffuseColor = diffuseColor.rgb;', + 'material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );', + 'material.specularColor = specularFactor.rgb;', +].join( '\n' ); + +var SHADER_INCLUDES_SPEC_GLOSS = { + roughnessmap_fragment: specularMapFragmentChunk, + metalnessmap_fragment: glossinessMapFragmentChunk, + lights_physical_fragment: lightPhysicalFragmentChunk, +}; + +function decorateMaterialWithSpecGloss( material ) { + + if ( material.isSpecGlossExtended ) return material; + + material.isSpecGlossExtended = true; + + // these are the extra uniforms, but instead of being stored in .userData, or some such place + // a designated prop could be used + var shaderUniforms = { + specular: { value: new THREE.Color().setHex( 0xffffff ), type: 'vec3', stage: 'fragment' }, //fragment can be ommitted (defaults to it) but for sake of clarity + glossiness: { value: 1, type: 'float', stage: 'fragment' }, + glossinessMap: { value: null, type: 'sampler2D', stage: 'fragment' }, + specularMap: { value: null, type: 'sampler2D', stage: 'fragment' }, + }; + + var shaderIncludes = Object.assign({},SHADER_INCLUDES_SPEC_GLOSS) + + var defines = {USE_GLOSSINESSMAP: ''} + + //conflicts could be resolved here + addOrMergeProp( material, 'shaderUniforms', shaderUniforms ); + addOrMergeProp( material, 'shaderIncludes', shaderIncludes ); + addOrMergeProp( material, 'defines', defines ); + + delete material.metalnessMap + delete material.roughnessMap + + //expose uniforms as props for a cleaner interface (but shaderUniforms is also available so this can be omitted) + //it just leads to a cleaner more familiar interface (PhongMaterial has specularMap, so this now has it too) + for ( let propName in shaderUniforms ) { + + Object.defineProperty( material, propName, { + get: ()=> shaderUniforms[ propName ].value, + set: ( v )=> (shaderUniforms[ propName ].value = v), + } ); + + } + + if(!material._serializationManager) material._serializationManager = new SerializationManager() + var f = function(data,meta){ + if( !data.metadata.extensions ) data.metadata.extensions = {} + data.metadata.extensions.isSpecGlossExtended = true + data.glossiness = this.glossiness + data.specular = this.specular.getHex() + if(this.glossinessMap && this.glossinessMap.isTexture) data.glossinessMap = this.glossinessMap.toJSON( meta ).uuid + }.bind(material) + + material._serializationManager.addFunction(f) + + material.toJSON = toJSON.bind(material) + + return material + +} + +// multi uv stuff --------------------------------------------------------- + +// this moves the transform from textures to the material, textures become just data + +//list of maps to be extended, these are easy +var DEFAULT_MAP_LIST = [ + 'alphaMap', + 'specularMap', + 'map', + 'emissiveMap', + 'metalnessMap', + 'roughnessMap', + 'glossinessMap' //this one is from the other example, but if its there it should work, this can be solved to work together somehow +]; + +//this can be programatic +//it tells the extension where to look for certain maps +//these follow the /texture2D( $mapname, vUv )/ pattern +//normal map is a bit more complex and would require a non programatic chunk +var PROP_TO_CHUNK_MAP = { + 'alphaMap': 'alphamap_fragment', + 'specularMap': 'specularmap_fragment', + 'map': 'map_fragment', + 'emissiveMap': 'emissivemap_fragment', + 'metalnessMap': 'metalnessmap_fragment', + 'roughnessMap': 'roughnessmap_fragment', + 'glossinessMap': 'metalnessmap_fragment', //this one cant be programatic because it belongs to another override, could be a specific check somewhere else + 'specularMapGloss': 'roughnessmap_fragment', +}; + +//some utils + +var mapRegex = /texture2D\( (.*Map|map), vUv \)/gm //look for the pattern /texture2D( $someMap, vUv )/ + +//because the other extension changes roughnessMap to specularMap we need the $1 to replace the name, otherwise it could be `mapName` +function getReplaceString(mapName){ + return `texture2D( $1, ( ${getUniformNameFromProp(mapName)} * vec3( vUv, 1. ) ).xy )` +} + +//in order to keep the uniform name obvious that it belongs to the GLSL context, and to make it as private sounding as possible +function getUniformNameFromProp(prop){ + return `u_${prop}Transform` +} + +//a utility to add the necessary transform properties to a material based on an arbitrary map name +//so if specularMap is provided it will create these Vector2, a float, and an updateMatrix method +//this is very similar to the Texture transform interface the only difference being that the props are prefixed +//myTexture.repeat vs myMaterial.specularMapRepeat +function addMapTransformPropsToMaterial( material, mapName ){ + + let _mapName = mapName + material[`${mapName}Repeat`] = new THREE.Vector2(1,1) + material[`${mapName}Offset`] = new THREE.Vector2() + material[`${mapName}Center`] = new THREE.Vector2() + material[`${mapName}Rotation`] = 0 + material[`${mapName}UpdateMatrix`] = function(){ + this.shaderUniforms[getUniformNameFromProp(_mapName)].value + .setUvTransform( + this[`${_mapName}Offset`].x, + this[`${_mapName}Offset`].y, + this[`${_mapName}Repeat`].x, + this[`${_mapName}Repeat`].y, + this[`${_mapName}Rotation`], + this[`${_mapName}Center`].x, + this[`${_mapName}Center`].y, + ) + }.bind(material) +} + + +function decorateMaterialWithPerMapTransforms( material, mapList ) { + + if ( material.isPerMapTransformExtended ) return material; + + material.isPerMapTransformExtended = true; + + //one can provide a subset from outside + mapList = mapList || DEFAULT_MAP_LIST; + + var shaderUniforms = {} + var shaderIncludes = {} + var serialize = [] + + for ( var i = 0; i < mapList.length; i ++ ) { + + var mapName = mapList[ i ]; + + + if ( material[ mapName ] !== undefined ) { + + addMapTransformPropsToMaterial(material, mapName) + + var uniform = { value: new THREE.Matrix3(), type:'mat3', stage: 'fragment' }; + uniform.value.setUvTransform = setUvTransform.bind( uniform.value ); + + shaderUniforms[getUniformNameFromProp(mapName)] = uniform + + //this is for resolving the conflict, its not the most elegant solution but it works + //i believe that this would be solved by refactoring the shader templates + var lookup = mapName + if( material.isSpecGlossExtended && mapName === 'specularMap'){ + lookup = 'specularMapGloss' + } + serialize.push(mapName) + + //based on the map name ie. specularMap or even an extended glossinessMap pick a chunk + var chunkName = PROP_TO_CHUNK_MAP[lookup] + + //if there already is a chunk from some extension, pick that, otherwise copy the default chunk + var shaderChunk = (material.shaderIncludes && material.shaderIncludes[chunkName]) || THREE.ShaderChunk[chunkName] + + //apply the string transformation, this contains the copy of whatever chunk was provided (default or custom) + shaderChunk = shaderChunk.replace( mapRegex , getReplaceString(mapName) ) + + //provide this copy as the include chunk, this shader wont look up THREE.ShaderChunk + //and doesnt have to wait for onBeforeCompile to do the transformation + //final transformed chunk is already stored here in this context sync + shaderIncludes[ chunkName ] = shaderChunk + + } + + } + + //combine with other chunks + addOrMergeProp( material, 'shaderUniforms', shaderUniforms ); + addOrMergeProp( material, 'shaderIncludes', shaderIncludes ); + + if(!material._serializationManager) material._serializationManager = new SerializationManager() + + material._serializationManager.addFunction(((data,meta)=>{ + if( !data.metadata.extensions ) data.metadata.extensions = {} + data.metadata.extensions.isPerMapTransformExtended = true + + serialize.forEach(mapName=>{ + data[`${mapName}Repeat`] = material[`${mapName}Repeat`].toArray() + data[`${mapName}Offset`] = material[`${mapName}Offset`].toArray() + data[`${mapName}Center`] = material[`${mapName}Center`].toArray() + data[`${mapName}Rotation`] = material[`${mapName}Rotation`] + }) + + return data + + }).bind(material)) + + + material._serializationManager.addAfterFunction( + function( data ){ + delete data.roughnessMap + delete data.roughness + delete data.metalnessMap + delete data.metalness + } + ) + + material.toJSON = toJSON.bind(material) + + return material +} + +// simple instance stuff from lambert example --------------------------------------------------------- + +//this is a stage after begin_vertex, this would be more elegant with hooks and template refactor +var after_vertex_transform_chunk = ` + transformed *= instanceScale; //the value present in transformed is in model space, + transformed = transformed + instanceOffset; +` + +function decorateMaterialWithSimpleInstancing( material ) { + + if( material.isSimpleInstanceExtended ) return material + + material.isSimpleInstanceExtended = true + + //make a custom chunk that includes a copy of the default chunk from THREE.ShaderChunk + //followed by a custom chunk, that is simply appended to the copy + var shaderIncludes = { + begin_vertex:` + ${THREE.ShaderChunk.begin_vertex} + ${after_vertex_transform_chunk} + ` + } + + //no good global chunk, but could be uv_pars, heres how to make it work with onbeforecompile + //because this is somewhat of a set and forget thing, onBeforeCompile (or onBeforeParse) is + //perfectly valid to use here + //"here are some attribute names, whenver you get around to assemblying the shader on WebGL level use them" + //A uniform (over an attribute) would be better if it were available in this scope + + var attributeInjection = ` + attribute vec3 instanceOffset; + attribute float instanceScale; + ` + + material.onBeforeCompile = shader => { + shader.vertexShader = ` + ${attributeInjection} + ${shader.vertexShader} + ` + } + + //alternatively one can use `uv_pars_vertex` + //since displacement map is used in almost all of the shaders, this chunk is present + //depth for example, has this chunk, so whatever attribute is added to StandardMaterial + //is also going to be added to DepthMaterial + /*shaderIncludes = { + uv_pars_vertex: ` + ${attributeInjection} + ${THREE.ShaderChunk.uv_pars_vertex} + ` + }*/ + + addOrMergeProp( material, 'shaderIncludes', shaderIncludes ); + + if(!material._serializationManager) material._serializationManager = new SerializationManager() + + material._serializationManager.addFunction((data)=>{ + if( !data.metadata.extensions ) data.metadata.extensions = {} + data.metadata.extensions.isSimpleInstanceExtended = true + }) + + material.toJSON = toJSON.bind(material) + + return material + +} + + +function SerializationManager(){ + this.processFunctions = [] + this.afterFunctions = [] +} + +SerializationManager.prototype = { + addFunction: function( func ){ + this.processFunctions.push(func) + }, + serialize(data, meta){ + this.processFunctions.forEach(f=>f(data,meta)) + return data + }, + afterSerialize(data){ + this.afterFunctions.forEach(f=>f(data)) + }, + addAfterFunction: function( func ){ + this.afterFunctions.push(func) + } +} \ No newline at end of file diff --git a/examples/js/loaders/GLTFLoader.js b/examples/js/loaders/GLTFLoader.js index b0353d1954ab62..f24927773a21f3 100644 --- a/examples/js/loaders/GLTFLoader.js +++ b/examples/js/loaders/GLTFLoader.js @@ -565,7 +565,7 @@ THREE.GLTFLoader = ( function () { getMaterialType: function () { - return THREE.ShaderMaterial; + return 'SPEC_GLOSS'; }, @@ -573,72 +573,6 @@ THREE.GLTFLoader = ( function () { var pbrSpecularGlossiness = material.extensions[ this.name ]; - var shader = THREE.ShaderLib[ 'standard' ]; - - var uniforms = THREE.UniformsUtils.clone( shader.uniforms ); - - var specularMapParsFragmentChunk = [ - '#ifdef USE_SPECULARMAP', - ' uniform sampler2D specularMap;', - '#endif' - ].join( '\n' ); - - var glossinessMapParsFragmentChunk = [ - '#ifdef USE_GLOSSINESSMAP', - ' uniform sampler2D glossinessMap;', - '#endif' - ].join( '\n' ); - - var specularMapFragmentChunk = [ - 'vec3 specularFactor = specular;', - '#ifdef USE_SPECULARMAP', - ' vec4 texelSpecular = texture2D( specularMap, vUv );', - ' texelSpecular = sRGBToLinear( texelSpecular );', - ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', - ' specularFactor *= texelSpecular.rgb;', - '#endif' - ].join( '\n' ); - - var glossinessMapFragmentChunk = [ - 'float glossinessFactor = glossiness;', - '#ifdef USE_GLOSSINESSMAP', - ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', - ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', - ' glossinessFactor *= texelGlossiness.a;', - '#endif' - ].join( '\n' ); - - var lightPhysicalFragmentChunk = [ - 'PhysicalMaterial material;', - 'material.diffuseColor = diffuseColor.rgb;', - 'material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );', - 'material.specularColor = specularFactor.rgb;', - ].join( '\n' ); - - var fragmentShader = shader.fragmentShader - .replace( 'uniform float roughness;', 'uniform vec3 specular;' ) - .replace( 'uniform float metalness;', 'uniform float glossiness;' ) - .replace( '#include ', specularMapParsFragmentChunk ) - .replace( '#include ', glossinessMapParsFragmentChunk ) - .replace( '#include ', specularMapFragmentChunk ) - .replace( '#include ', glossinessMapFragmentChunk ) - .replace( '#include ', lightPhysicalFragmentChunk ); - - delete uniforms.roughness; - delete uniforms.metalness; - delete uniforms.roughnessMap; - delete uniforms.metalnessMap; - - uniforms.specular = { value: new THREE.Color().setHex( 0x111111 ) }; - uniforms.glossiness = { value: 0.5 }; - uniforms.specularMap = { value: null }; - uniforms.glossinessMap = { value: null }; - - params.vertexShader = shader.vertexShader; - params.fragmentShader = fragmentShader; - params.uniforms = uniforms; - params.defines = { 'STANDARD': '' }; - params.color = new THREE.Color( 1.0, 1.0, 1.0 ); params.opacity = 1.0; @@ -683,19 +617,14 @@ THREE.GLTFLoader = ( function () { createMaterial: function ( params ) { - // setup material properties based on MeshStandardMaterial for Specular-Glossiness - - var material = new THREE.ShaderMaterial( { - defines: params.defines, - vertexShader: params.vertexShader, - fragmentShader: params.fragmentShader, - uniforms: params.uniforms, + var material = new THREE.MeshStandardMaterial( { fog: true, - lights: true, opacity: params.opacity, transparent: params.transparent } ); + decorateMaterialWithSpecGloss( material ); + material.isGLTFSpecularGlossinessMaterial = true; material.color = params.color; @@ -722,10 +651,22 @@ THREE.GLTFLoader = ( function () { material.displacementScale = 1; material.displacementBias = 0; - material.specularMap = params.specularMap === undefined ? null : params.specularMap; + if ( params.specularMap ) { + + material.specularMap = params.specularMap; + material.defines.USE_SPECULARMAP = ''; + + } + material.specular = params.specular; - material.glossinessMap = params.glossinessMap === undefined ? null : params.glossinessMap; + if ( params.glossinessMap ) { + + material.glossinessMap = params.glossinessMap; + material.defines.USE_GLOSSINESSMAP = ''; + + } + material.glossiness = params.glossiness; material.alphaMap = null; @@ -735,8 +676,6 @@ THREE.GLTFLoader = ( function () { material.refractionRatio = 0.98; - material.extensions.derivatives = true; - return material; }, @@ -771,157 +710,13 @@ THREE.GLTFLoader = ( function () { }, - // Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer. - refreshUniforms: function ( renderer, scene, camera, geometry, material, group ) { - - if ( material.isGLTFSpecularGlossinessMaterial !== true ) { - - return; - - } - - var uniforms = material.uniforms; - var defines = material.defines; - - uniforms.opacity.value = material.opacity; - - uniforms.diffuse.value.copy( material.color ); - uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); - - uniforms.map.value = material.map; - uniforms.specularMap.value = material.specularMap; - uniforms.alphaMap.value = material.alphaMap; - - uniforms.lightMap.value = material.lightMap; - uniforms.lightMapIntensity.value = material.lightMapIntensity; - - uniforms.aoMap.value = material.aoMap; - uniforms.aoMapIntensity.value = material.aoMapIntensity; - - // uv repeat and offset setting priorities - // 1. color map - // 2. specular map - // 3. normal map - // 4. bump map - // 5. alpha map - // 6. emissive map - - var uvScaleMap; - - if ( material.map ) { - - uvScaleMap = material.map; - - } else if ( material.specularMap ) { - - uvScaleMap = material.specularMap; - - } else if ( material.displacementMap ) { - - uvScaleMap = material.displacementMap; - - } else if ( material.normalMap ) { - - uvScaleMap = material.normalMap; - - } else if ( material.bumpMap ) { - - uvScaleMap = material.bumpMap; - - } else if ( material.glossinessMap ) { - - uvScaleMap = material.glossinessMap; - - } else if ( material.alphaMap ) { - - uvScaleMap = material.alphaMap; - - } else if ( material.emissiveMap ) { - - uvScaleMap = material.emissiveMap; - - } - - if ( uvScaleMap !== undefined ) { - - // backwards compatibility - if ( uvScaleMap.isWebGLRenderTarget ) { - - uvScaleMap = uvScaleMap.texture; - - } - - var offset; - var repeat; - - if ( uvScaleMap.matrix !== undefined ) { - - // > r88. - - if ( uvScaleMap.matrixAutoUpdate === true ) { - - offset = uvScaleMap.offset; - repeat = uvScaleMap.repeat; - var rotation = uvScaleMap.rotation; - var center = uvScaleMap.center; - - uvScaleMap.matrix.setUvTransform( offset.x, offset.y, repeat.x, repeat.y, rotation, center.x, center.y ); - - } - - uniforms.uvTransform.value.copy( uvScaleMap.matrix ); - - } else { - - // <= r87. Remove when reasonable. - - offset = uvScaleMap.offset; - repeat = uvScaleMap.repeat; - - uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y ); - - } - - } - - uniforms.envMap.value = material.envMap; - uniforms.envMapIntensity.value = material.envMapIntensity; - uniforms.flipEnvMap.value = ( material.envMap && material.envMap.isCubeTexture ) ? - 1 : 1; - - uniforms.refractionRatio.value = material.refractionRatio; - - uniforms.specular.value.copy( material.specular ); - uniforms.glossiness.value = material.glossiness; - - uniforms.glossinessMap.value = material.glossinessMap; - - uniforms.emissiveMap.value = material.emissiveMap; - uniforms.bumpMap.value = material.bumpMap; - uniforms.normalMap.value = material.normalMap; - - uniforms.displacementMap.value = material.displacementMap; - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; - - if ( uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined ) { - - defines.USE_GLOSSINESSMAP = ''; - // set USE_ROUGHNESSMAP to enable vUv - defines.USE_ROUGHNESSMAP = ''; - - } - - if ( uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined ) { - - delete defines.USE_GLOSSINESSMAP; - delete defines.USE_ROUGHNESSMAP; - - } - + refreshUniforms: function () { + /*NOOP*/ } }; + } /*********************************/ @@ -2219,8 +2014,8 @@ THREE.GLTFLoader = ( function () { return Promise.all( pending ).then( function () { var material; - - if ( materialType === THREE.ShaderMaterial ) { + // if ( materialType === THREE.ShaderMaterial ) { + if ( materialType === 'SPEC_GLOSS' ) { material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); diff --git a/examples/serializedResult.json b/examples/serializedResult.json new file mode 100644 index 00000000000000..e88b4dd6b9e15c --- /dev/null +++ b/examples/serializedResult.json @@ -0,0 +1,156 @@ +{ + "metadata": { + "version": 4.5, + "type": "Material", + "generator": "Material.toJSON", + "extensions": { + "isSpecGlossExtended": true, + "isPerMapTransformExtended": true, + "isSimpleInstanceExtended": true + } + }, + "glossiness": 1, + "specular": 16777215, + "glossinessMap": "2B8CE6E5-7846-4D84-B4BE-3D4211AE2BBA", + "alphaMapRepeat": [ + 1, + 1 + ], + "alphaMapOffset": [ + 0, + 0 + ], + "alphaMapCenter": [ + 0, + 0 + ], + "alphaMapRotation": 0, + "specularMapRepeat": [ + 1, + 1 + ], + "specularMapOffset": [ + 0, + 0 + ], + "specularMapCenter": [ + 0, + 0 + ], + "specularMapRotation": 0, + "mapRepeat": [ + 1, + 1 + ], + "mapOffset": [ + 0, + 0 + ], + "mapCenter": [ + 0, + 0 + ], + "mapRotation": 0, + "emissiveMapRepeat": [ + 1, + 1 + ], + "emissiveMapOffset": [ + 0, + 0 + ], + "emissiveMapCenter": [ + 0, + 0 + ], + "emissiveMapRotation": 0, + "glossinessMapRepeat": [ + 1, + 1 + ], + "glossinessMapOffset": [ + 0, + 0 + ], + "glossinessMapCenter": [ + 0, + 0 + ], + "glossinessMapRotation": 0, + "uuid": "B1168031-2FEB-4362-BFF5-04D2B366BA17", + "type": "MeshStandardMaterial", + "color": 16758090, + "emissive": 0, + "map": "2B8CE6E5-7846-4D84-B4BE-3D4211AE2BBA", + "specularMap": "2B8CE6E5-7846-4D84-B4BE-3D4211AE2BBA", + "envMap": "58FDD3E2-882B-4F66-B4E2-BAE2C367595C", + "depthFunc": 3, + "depthTest": true, + "depthWrite": true, + "textures": [ + { + "uuid": "2B8CE6E5-7846-4D84-B4BE-3D4211AE2BBA", + "name": "texture", + "mapping": 300, + "repeat": [ + 1, + 1 + ], + "offset": [ + 0, + 0 + ], + "center": [ + 0, + 0 + ], + "rotation": 0, + "wrap": [ + 1000, + 1000 + ], + "format": 1022, + "minFilter": 1008, + "magFilter": 1006, + "anisotropy": 1, + "flipY": true, + "image": "B440D9F5-D47E-40D1-B912-DBFF0AE719AE" + }, + { + "uuid": "58FDD3E2-882B-4F66-B4E2-BAE2C367595C", + "name": "envMap", + "mapping": 305, + "repeat": [ + 1, + 1 + ], + "offset": [ + 0, + 0 + ], + "center": [ + 0, + 0 + ], + "rotation": 0, + "wrap": [ + 1001, + 1001 + ], + "format": 1022, + "minFilter": 1008, + "magFilter": 1006, + "anisotropy": 1, + "flipY": true, + "image": "14DDC238-F4C1-4D89-AED5-597BD2927B84" + } + ], + "images": [ + { + "uuid": "B440D9F5-D47E-40D1-B912-DBFF0AE719AE", + }, + { + "uuid": "14DDC238-F4C1-4D89-AED5-597BD2927B84", + } + ] +} \ No newline at end of file diff --git a/examples/webgl_loader_gltf_extensions.html b/examples/webgl_loader_gltf_extensions.html index 7eb1fcef031808..da3de65ffd1a89 100644 --- a/examples/webgl_loader_gltf_extensions.html +++ b/examples/webgl_loader_gltf_extensions.html @@ -49,12 +49,13 @@
- - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/src/materials/Material.js b/src/materials/Material.js index 3d8f37c3e344c9..4cdb0ab7b88d8b 100644 --- a/src/materials/Material.js +++ b/src/materials/Material.js @@ -133,7 +133,7 @@ Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ), }, - toJSON: function ( meta ) { + toJSON: function ( meta, onWillSerialize ) { var isRoot = ( meta === undefined || typeof meta === 'string' ); @@ -154,6 +154,8 @@ Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ), } }; + onWillSerialize( data, meta ); + // standard Material serialization data.uuid = this.uuid; data.type = this.type; diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 891c8e43b56bcd..ec525d66ed2b96 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -1474,11 +1474,41 @@ function WebGLRenderer( parameters ) { var shader = ShaderLib[ parameters.shaderID ]; + var combinedUniforms = undefined !== material.shaderUniforms ? + UniformsUtils.merge( [ UniformsUtils.clone( shader.uniforms ), material.shaderUniforms ] ) : + UniformsUtils.clone( shader.uniforms ); + + var shaderUniformsGLSLFrag = ''; //collect the GLSL in here + var shaderUniformsGLSLVert = ''; //collect the GLSL in here + + for ( var uniformName in material.shaderUniforms ) { + + var uniform = material.shaderUniforms[ uniformName ]; + var type = uniform.type; + var stage = uniform.stage; + + if ( type ) { + + if ( stage === 'vertex' ) { //dunno what to do here, maybe if not provided should inject into both + + shaderUniformsGLSLVert += 'uniform ' + type + ' ' + uniformName + ';\n'; + + } else { + + shaderUniformsGLSLFrag += 'uniform ' + type + ' ' + uniformName + ';\n'; + + } + + } + + } + materialProperties.shader = { name: material.type, - uniforms: UniformsUtils.clone( shader.uniforms ), - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader + uniforms: combinedUniforms, + vertexShader: shaderUniformsGLSLVert + shader.vertexShader, + fragmentShader: shaderUniformsGLSLFrag + shader.fragmentShader //maybe not use the same + }; } else { @@ -1797,8 +1827,14 @@ function WebGLRenderer( parameters ) { } - // refresh uniforms common to several materials + //refresh custom provided uniforms + if ( undefined !== material.shaderUniforms ) { + + refreshUniformsCustom( m_uniforms, material ); + } + + // refresh uniforms common to several materials if ( fog && material.fog ) { refreshUniformsFog( m_uniforms, fog ); @@ -2041,6 +2077,17 @@ function WebGLRenderer( parameters ) { } + //refresh custom provided uniforms + function refreshUniformsCustom( uniforms, material ) { + + for ( var uniform in material.shaderUniforms ) { + + uniforms[ uniform ].value = material.shaderUniforms[ uniform ].value; + + } + + } + function refreshUniformsLine( uniforms, material ) { uniforms.diffuse.value = material.color; diff --git a/src/renderers/webgl/WebGLProgram.js b/src/renderers/webgl/WebGLProgram.js index b2ffa49f26971b..f4e4c778725d3f 100644 --- a/src/renderers/webgl/WebGLProgram.js +++ b/src/renderers/webgl/WebGLProgram.js @@ -158,13 +158,15 @@ function replaceClippingPlaneNums( string, parameters ) { } -function parseIncludes( string ) { +//consider provided dictionary when parsing the includes (not just THREE.ShaderChunk) +function parseIncludes( string, materialIncludes ) { var pattern = /^[ \t]*#include +<([\w\d.]+)>/gm; function replace( match, include ) { - var replace = ShaderChunk[ include ]; + //if there are material includes provided use those instead of the default chunks: + var replace = undefined !== materialIncludes[ include ] ? materialIncludes[ include ] : ShaderChunk[ include ]; if ( replace === undefined ) { @@ -208,6 +210,8 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters var defines = material.defines; + var materialIncludes = material.shaderIncludes; //custom chunks + var vertexShader = shader.vertexShader; var fragmentShader = shader.fragmentShader; @@ -289,6 +293,8 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters var customDefines = generateDefines( defines ); + var customIncludes = undefined !== materialIncludes ? materialIncludes : {}; //user is not aware of this feature, fine + // var program = gl.createProgram(); @@ -503,11 +509,12 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters } - vertexShader = parseIncludes( vertexShader ); + // provide optional chunk dictionary customIncludes + vertexShader = parseIncludes( vertexShader, customIncludes ); vertexShader = replaceLightNums( vertexShader, parameters ); vertexShader = replaceClippingPlaneNums( vertexShader, parameters ); - fragmentShader = parseIncludes( fragmentShader ); + fragmentShader = parseIncludes( fragmentShader, customIncludes ); fragmentShader = replaceLightNums( fragmentShader, parameters ); fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters ); diff --git a/src/renderers/webgl/WebGLPrograms.js b/src/renderers/webgl/WebGLPrograms.js index 633a3397e7c7a1..3d3825f788ab8a 100644 --- a/src/renderers/webgl/WebGLPrograms.js +++ b/src/renderers/webgl/WebGLPrograms.js @@ -241,10 +241,20 @@ function WebGLPrograms( renderer, extensions, capabilities ) { } - array.push( material.onBeforeCompile.toString() ); - array.push( renderer.gammaOutput ); + // if there is this dictionary present + if ( material.shaderIncludes !== undefined ) { + + for ( var include in material.shaderIncludes ) { + + // hash with chunks? + array.push( material.shaderIncludes[ include ] ); + + } + + } + return array.join(); };