THREE.WebGLRenderer.ShaderBuilder = function ( renderer, info ) { this.renderer = renderer; this.info = info; this.programs = [], this.programs_counter = 0; }; THREE.extend( THREE.WebGLRenderer.ShaderBuilder.prototype, { buildProgram: function ( shaderID, fragmentShader, vertexShader, uniforms, attributes, defines, parameters ) { var renderer = this.renderer; var p, pl, d, program, code; var chunks = []; // Generate code if ( shaderID ) { chunks.push( shaderID ); } else { chunks.push( fragmentShader ); chunks.push( vertexShader ); } for ( d in defines ) { chunks.push( d ); chunks.push( defines[ d ] ); } for ( p in parameters ) { chunks.push( p ); chunks.push( parameters[ p ] ); } code = chunks.join(); // Check if code has been already compiled for ( p = 0, pl = this.programs.length; p < pl; p ++ ) { var programInfo = this.programs[ p ]; if ( programInfo.code === code ) { //console.log( "Code already compiled." /*: \n\n" + code*/ ); programInfo.usedTimes ++; return programInfo.program; } } var shadowMapTypeDefine = "SHADOWMAP_TYPE_BASIC"; if ( parameters.shadowMapType === THREE.PCFShadowMap ) { shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF"; } else if ( parameters.shadowMapType === THREE.PCFSoftShadowMap ) { shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF_SOFT"; } //console.log( "building new program " ); // var customDefines = this.generateDefines( defines ); // var prefix_vertex = [ "precision " + renderer.precision + " float;", customDefines, renderer.supportsVertexTextures ? "#define VERTEX_TEXTURES" : "", parameters.gammaInput ? "#define GAMMA_INPUT" : "", parameters.gammaOutput ? "#define GAMMA_OUTPUT" : "", parameters.physicallyBasedShading ? "#define PHYSICALLY_BASED_SHADING" : "", "#define MAX_DIR_LIGHTS " + parameters.maxDirLights, "#define MAX_POINT_LIGHTS " + parameters.maxPointLights, "#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights, "#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights, "#define MAX_SHADOWS " + parameters.maxShadows, "#define MAX_BONES " + parameters.maxBones, parameters.map ? "#define USE_MAP" : "", parameters.envMap ? "#define USE_ENVMAP" : "", parameters.lightMap ? "#define USE_LIGHTMAP" : "", parameters.bumpMap ? "#define USE_BUMPMAP" : "", parameters.normalMap ? "#define USE_NORMALMAP" : "", parameters.specularMap ? "#define USE_SPECULARMAP" : "", parameters.vertexColors ? "#define USE_COLOR" : "", parameters.skinning ? "#define USE_SKINNING" : "", parameters.useVertexTexture ? "#define BONE_TEXTURE" : "", parameters.boneTextureWidth ? "#define N_BONE_PIXEL_X " + parameters.boneTextureWidth.toFixed( 1 ) : "", parameters.boneTextureHeight ? "#define N_BONE_PIXEL_Y " + parameters.boneTextureHeight.toFixed( 1 ) : "", parameters.morphTargets ? "#define USE_MORPHTARGETS" : "", parameters.morphNormals ? "#define USE_MORPHNORMALS" : "", parameters.perPixel ? "#define PHONG_PER_PIXEL" : "", parameters.wrapAround ? "#define WRAP_AROUND" : "", parameters.doubleSided ? "#define DOUBLE_SIDED" : "", parameters.flipSided ? "#define FLIP_SIDED" : "", parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "", parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "", parameters.shadowMapDebug ? "#define SHADOWMAP_DEBUG" : "", parameters.shadowMapCascade ? "#define SHADOWMAP_CASCADE" : "", parameters.sizeAttenuation ? "#define USE_SIZEATTENUATION" : "", "uniform mat4 modelMatrix;", "uniform mat4 modelViewMatrix;", "uniform mat4 projectionMatrix;", "uniform mat4 viewMatrix;", "uniform mat3 normalMatrix;", "uniform vec3 cameraPosition;", "attribute vec3 position;", "attribute vec3 normal;", "attribute vec2 uv;", "attribute vec2 uv2;", "#ifdef USE_COLOR", "attribute vec3 color;", "#endif", "#ifdef USE_MORPHTARGETS", "attribute vec3 morphTarget0;", "attribute vec3 morphTarget1;", "attribute vec3 morphTarget2;", "attribute vec3 morphTarget3;", "#ifdef USE_MORPHNORMALS", "attribute vec3 morphNormal0;", "attribute vec3 morphNormal1;", "attribute vec3 morphNormal2;", "attribute vec3 morphNormal3;", "#else", "attribute vec3 morphTarget4;", "attribute vec3 morphTarget5;", "attribute vec3 morphTarget6;", "attribute vec3 morphTarget7;", "#endif", "#endif", "#ifdef USE_SKINNING", "attribute vec4 skinIndex;", "attribute vec4 skinWeight;", "#endif", "" ].join("\n"); var prefix_fragment = [ "precision " + renderer.precision + " float;", ( parameters.bumpMap || parameters.normalMap ) ? "#extension GL_OES_standard_derivatives : enable" : "", customDefines, "#define MAX_DIR_LIGHTS " + parameters.maxDirLights, "#define MAX_POINT_LIGHTS " + parameters.maxPointLights, "#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights, "#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights, "#define MAX_SHADOWS " + parameters.maxShadows, parameters.alphaTest ? "#define ALPHATEST " + parameters.alphaTest: "", parameters.gammaInput ? "#define GAMMA_INPUT" : "", parameters.gammaOutput ? "#define GAMMA_OUTPUT" : "", parameters.physicallyBasedShading ? "#define PHYSICALLY_BASED_SHADING" : "", ( parameters.useFog && parameters.fog ) ? "#define USE_FOG" : "", ( parameters.useFog && parameters.fogExp ) ? "#define FOG_EXP2" : "", parameters.map ? "#define USE_MAP" : "", parameters.envMap ? "#define USE_ENVMAP" : "", parameters.lightMap ? "#define USE_LIGHTMAP" : "", parameters.bumpMap ? "#define USE_BUMPMAP" : "", parameters.normalMap ? "#define USE_NORMALMAP" : "", parameters.specularMap ? "#define USE_SPECULARMAP" : "", parameters.vertexColors ? "#define USE_COLOR" : "", parameters.metal ? "#define METAL" : "", parameters.perPixel ? "#define PHONG_PER_PIXEL" : "", parameters.wrapAround ? "#define WRAP_AROUND" : "", parameters.doubleSided ? "#define DOUBLE_SIDED" : "", parameters.flipSided ? "#define FLIP_SIDED" : "", parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "", parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "", parameters.shadowMapDebug ? "#define SHADOWMAP_DEBUG" : "", parameters.shadowMapCascade ? "#define SHADOWMAP_CASCADE" : "", "uniform mat4 viewMatrix;", "uniform vec3 cameraPosition;", "" ].join("\n"); program = renderer.compileShader(prefix_vertex + vertexShader, prefix_fragment + fragmentShader); //console.log( prefix_fragment + fragmentShader ); //console.log( prefix_vertex + vertexShader ); program.uniforms = {}; program.attributes = {}; var identifiers, u, a, i; // cache uniform locations identifiers = [ 'viewMatrix', 'modelViewMatrix', 'projectionMatrix', 'normalMatrix', 'modelMatrix', 'cameraPosition', 'morphTargetInfluences' ]; if ( parameters.useVertexTexture ) { identifiers.push( 'boneTexture' ); } else { identifiers.push( 'boneGlobalMatrices' ); } for ( u in uniforms ) { identifiers.push( u ); } this.cacheUniformLocations( program, identifiers ); // cache attributes locations identifiers = [ "position", "normal", "uv", "uv2", "tangent", "color", "skinIndex", "skinWeight", "lineDistance" ]; for ( i = 0; i < parameters.maxMorphTargets; i ++ ) { identifiers.push( "morphTarget" + i ); } for ( i = 0; i < parameters.maxMorphNormals; i ++ ) { identifiers.push( "morphNormal" + i ); } for ( a in attributes ) { identifiers.push( a ); } this.cacheAttributeLocations( program, identifiers ); program.id = this.programs_counter ++; this.programs.push( { program: program, code: code, usedTimes: 1 } ); this.info.memory.programs = this.programs.length; return program; }, generateDefines: function ( defines ) { var value, chunk, chunks = []; for ( var d in defines ) { value = defines[ d ]; if ( value === false ) continue; chunk = "#define " + d + " " + value; chunks.push( chunk ); } return chunks.join( "\n" ); }, // Shader parameters cache cacheUniformLocations: function ( program, identifiers ) { var i, l, id, renderer = this.renderer; for ( i = 0, l = identifiers.length; i < l; i ++ ) { id = identifiers[ i ]; program.uniforms[ id ] = renderer.getUniformLocation( program, id ); } }, cacheAttributeLocations: function ( program, identifiers ) { var i, l, id, renderer = this.renderer; for( i = 0, l = identifiers.length; i < l; i ++ ) { id = identifiers[ i ]; program.attributes[ id ] = renderer.getAttribLocation( program, id ); } }, removeProgram: function ( program ) { var i, il, programInfo; var deleteProgram = false; var programs = this.programs; for ( i = 0, il = programs.length; i < il; i ++ ) { programInfo = programs[ i ]; if ( programInfo.program === program ) { programInfo.usedTimes --; if ( programInfo.usedTimes === 0 ) { deleteProgram = true; } break; } } if ( deleteProgram === true ) { // avoid using array.splice, this is costlier than creating new array from scratch var newPrograms = []; for ( i = 0, il = programs.length; i < il; i ++ ) { programInfo = programs[ i ]; if ( programInfo.program !== program ) { newPrograms.push( programInfo ); } } this.programs = newPrograms; this.renderer.deleteProgram( program ); this.info.memory.programs --; } } } );