import Material from './Material'; import Shader from './Shader'; import standardEssl from './shader/source/standard.glsl.js'; import util from './core/util'; // Import standard shader Shader['import'](standardEssl); var TEXTURE_PROPERTIES = ['diffuseMap', 'normalMap', 'roughnessMap', 'metalnessMap', 'emissiveMap', 'environmentMap', 'brdfLookup', 'ssaoMap', 'aoMap']; var SIMPLE_PROPERTIES = ['color', 'emission', 'emissionIntensity', 'alpha', 'roughness', 'metalness', 'uvRepeat', 'uvOffset', 'aoIntensity', 'alphaCutoff', 'normalScale']; var PROPERTIES_CHANGE_SHADER = ['linear', 'encodeRGBM', 'decodeRGBM', 'doubleSided', 'alphaTest', 'roughnessChannel', 'metalnessChannel', 'environmentMapPrefiltered']; var NUM_DEFINE_MAP = { 'roughnessChannel': 'ROUGHNESS_CHANNEL', 'metalnessChannel': 'METALNESS_CHANNEL' }; var BOOL_DEFINE_MAP = { 'linear': 'SRGB_DECODE', 'encodeRGBM': 'RGBM_ENCODE', 'decodeRGBM': 'RGBM_DECODE', 'doubleSided': 'DOUBLE_SIDED', 'alphaTest': 'ALPHA_TEST', 'environmentMapPrefiltered': 'ENVIRONMENTMAP_PREFILTER' }; var standardShader; /** * Standard material without custom shader. * @constructor clay.StandardMaterial * @extends clay.Base * @example * var mat = new clay.StandardMaterial({ * color: [1, 1, 1], * diffuseMap: diffuseTexture * }); * mat.roughness = 1; */ var StandardMaterial = Material.extend(function () { if (!standardShader) { standardShader = new Shader(Shader.source('clay.standardMR.vertex'), Shader.source('clay.standardMR.fragment')); } return /** @lends clay.StandardMaterial# */ { shader: standardShader }; }, function (option) { // PENDING util.extend(this, option); // Extend after shader is created. util.defaults(this, /** @lends clay.StandardMaterial# */ { /** * @type {Array.} * @default [1, 1, 1] */ color: [1, 1, 1], /** * @type {Array.} * @default [0, 0, 0] */ emission: [0, 0, 0], /** * @type {number} * @default 0 */ emissionIntensity: 0, /** * @type {number} * @default 0.5 */ roughness: 0.5, /** * @type {number} * @default 0 */ metalness: 0, /** * @type {number} * @default 1 */ alpha: 1, /** * @type {boolean} */ alphaTest: false, /** * Cutoff threshold for alpha test * @type {number} */ alphaCutoff: 0.9, /** * Scalar multiplier applied to each normal vector of normal texture. * * @type {number} * * XXX This value is considered only if a normal texture is specified. */ normalScale: 1.0, /** * @type {boolean} */ // TODO Must disable culling. doubleSided: false, /** * @type {clay.Texture2D} */ diffuseMap: null, /** * @type {clay.Texture2D} */ normalMap: null, /** * @type {clay.Texture2D} */ roughnessMap: null, /** * @type {clay.Texture2D} */ metalnessMap: null, /** * @type {clay.Texture2D} */ emissiveMap: null, /** * @type {clay.TextureCube} */ environmentMap: null, /** * @type {clay.BoundingBox} */ environmentBox: null, /** * BRDF Lookup is generated by clay.util.cubemap.integrateBrdf * @type {clay.Texture2D} */ brdfLookup: null, /** * @type {clay.Texture2D} */ ssaoMap: null, /** * @type {clay.Texture2D} */ aoMap: null, /** * @type {Array.} * @default [1, 1] */ uvRepeat: [1, 1], /** * @type {Array.} * @default [0, 0] */ uvOffset: [0, 0], /** * @type {number} * @default 1 */ aoIntensity: 1, /** * @type {boolean} */ environmentMapPrefiltered: false, /** * @type {boolean} */ linear: false, /** * @type {boolean} */ encodeRGBM: false, /** * @type {boolean} */ decodeRGBM: false, /** * @type {Number} */ roughnessChannel: 0, /** * @type {Number} */ metalnessChannel: 1 }); }, { clone: function () { var material = new StandardMaterial({ name: this.name }); TEXTURE_PROPERTIES.forEach(function (propName) { if (this[propName]) { material[propName] = this[propName]; } }, this); SIMPLE_PROPERTIES.concat(PROPERTIES_CHANGE_SHADER).forEach(function (propName) { material[propName] = this[propName]; }, this); return material; } }); SIMPLE_PROPERTIES.forEach(function (propName) { Object.defineProperty(StandardMaterial.prototype, propName, { get: function () { return this.get(propName); }, set: function (value) { this.setUniform(propName, value); } }); }); TEXTURE_PROPERTIES.forEach(function (propName) { Object.defineProperty(StandardMaterial.prototype, propName, { get: function () { return this.get(propName); }, set: function (value) { this.setUniform(propName, value); } }); }); PROPERTIES_CHANGE_SHADER.forEach(function (propName) { var privateKey = '_' + propName; Object.defineProperty(StandardMaterial.prototype, propName, { get: function () { return this[privateKey]; }, set: function (value) { this[privateKey] = value; if (propName in NUM_DEFINE_MAP) { var defineName = NUM_DEFINE_MAP[propName]; this.define('fragment', defineName, value); } else { var defineName = BOOL_DEFINE_MAP[propName]; value ? this.define('fragment', defineName) : this.undefine('fragment', defineName); } } }); }); Object.defineProperty(StandardMaterial.prototype, 'environmentBox', { get: function () { var envBox = this._environmentBox; if (envBox) { envBox.min.setArray(this.get('environmentBoxMin')); envBox.max.setArray(this.get('environmentBoxMax')); } return envBox; }, set: function (value) { this._environmentBox = value; var uniforms = this.uniforms = this.uniforms || {}; uniforms['environmentBoxMin'] = uniforms['environmentBoxMin'] || { value: null }; uniforms['environmentBoxMax'] = uniforms['environmentBoxMax'] || { value: null }; // TODO Can't detect operation like box.min = new Vector() if (value) { this.setUniform('environmentBoxMin', value.min.array); this.setUniform('environmentBoxMax', value.max.array); } if (value) { this.define('fragment', 'PARALLAX_CORRECTED'); } else { this.undefine('fragment', 'PARALLAX_CORRECTED'); } } }); export default StandardMaterial;