Browse Source

SpotLightMap v3 (#21944)

* SpotLightMap

* Update webgl_lights_spotlight.html

Simplify spot light example.

* Update WebGLProgram.js

* Update lights_fragment_begin.glsl.js

* Update WebGLLights.js

* Update SpotLight.html

* Update SpotLight.html

* Update SpotLight.js

* Update shadowmap_vertex.glsl.js

* Update SpotLight.js

* Update lights_fragment_begin.glsl.js

* Update SpotLight.html

* Update SpotLight.html

* Update shadowmap_vertex.glsl.js

* Update shadowmap_vertex.glsl.js

* Update WebGLLights.js

* Update WebGLLights.js

* Update shadowmap_vertex.glsl.js

* Update WebGLLights.js

Co-authored-by: Michael Herzog <[email protected]>
Co-authored-by: mrdoob <[email protected]>
Mathieu Brédif 2 years ago
parent
commit
abe1dff3c2

+ 9 - 1
docs/api/en/lights/SpotLight.html

@@ -20,10 +20,11 @@
 
 
 		<h2>Code Example</h2>
 		<h2>Code Example</h2>
 		<code>
 		<code>
-		// white spotlight shining from the side, casting a shadow
+		// white spotlight shining from the side, modulated by a texture, casting a shadow
 
 
 		const spotLight = new THREE.SpotLight( 0xffffff );
 		const spotLight = new THREE.SpotLight( 0xffffff );
 		spotLight.position.set( 100, 1000, 100 );
 		spotLight.position.set( 100, 1000, 100 );
+		spotLight.map = new THREE.TextureLoader().load( url );
 
 
 		spotLight.castShadow = true;
 		spotLight.castShadow = true;
 
 
@@ -170,6 +171,13 @@ light.target = targetObject;
 			The spotlight will now track the target object.
 			The spotlight will now track the target object.
 		</p>
 		</p>
 
 
+		<h3>[property:Texture map]</h3>
+		<p>
+			A [page:Texture] used to modulate the color of the light. The spot light color is mixed
+			with the RGB value of this texture, with a ratio corresponding to its
+			alpha value. The cookie-like masking effect is reproduced using pixel values (0, 0, 0, 1-cookie_value).
+			*Warning*: [param:SpotLight map] is disabled if [param:SpotLight castShadow] is *false*. 
+		</p>
 
 
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 
 

+ 10 - 1
docs/api/zh/lights/SpotLight.html

@@ -19,10 +19,11 @@
 
 
 		<h2>代码示例</h2>
 		<h2>代码示例</h2>
 		<code>
 		<code>
-		// white spotlight shining from the side, casting a shadow
+		// white spotlight shining from the side, modulated by a texture, casting a shadow
 
 
 		const spotLight = new THREE.SpotLight( 0xffffff );
 		const spotLight = new THREE.SpotLight( 0xffffff );
 		spotLight.position.set( 100, 1000, 100 );
 		spotLight.position.set( 100, 1000, 100 );
+		spotLight.map = new THREE.TextureLoader().load( url );
 
 
 		spotLight.castShadow = true;
 		spotLight.castShadow = true;
 
 
@@ -144,6 +145,14 @@ light.target = targetObject;
 			完成上述操作后,聚光灯现在就可以追踪到目标对像了。
 			完成上述操作后,聚光灯现在就可以追踪到目标对像了。
 		</p>
 		</p>
 
 
+		<h3>[property:Texture map]</h3>
+		<p>
+			A [page:Texture] used to modulate the color of the light. The spot light color is mixed
+			with the RGB value of this texture, with a ratio corresponding to its
+			alpha value. The cookie-like masking effect is reproduced using pixel values (0, 0, 0, 1-cookie_value).
+			*Warning*: [param:SpotLight map] is disabled if [param:SpotLight castShadow] is *false*.
+		</p>
+
 
 
 		<h2>方法(Methods)</h2>
 		<h2>方法(Methods)</h2>
 
 

+ 23 - 1
examples/webgl_lights_spotlight.html

@@ -36,6 +36,8 @@
 
 
 			let spotLight, lightHelper, shadowCameraHelper;
 			let spotLight, lightHelper, shadowCameraHelper;
 
 
+			let textureUrls, textures;
+
 			let gui;
 			let gui;
 
 
 			function init() {
 			function init() {
@@ -64,6 +66,18 @@
 				const ambient = new THREE.AmbientLight( 0xffffff, 0.1 );
 				const ambient = new THREE.AmbientLight( 0xffffff, 0.1 );
 				scene.add( ambient );
 				scene.add( ambient );
 
 
+				const textureLoader = new THREE.TextureLoader();
+				textureUrls = [ 'none', 'uv_grid_opengl.jpg', 'sprite2.png', 'colors.png' ];
+				textures = { none: null }
+
+				for ( let i = 1; i < textureUrls.length; i ++ ) {
+
+					textures[ textureUrls[ i ] ] = textureLoader.load( 'textures/' + textureUrls[ i ] );
+					textures[ textureUrls[ i ] ].minFilter = THREE.LinearFilter;
+					textures[ textureUrls[ i ] ].magFilter = THREE.LinearFilter;
+
+				}
+
 				spotLight = new THREE.SpotLight( 0xffffff, 1 );
 				spotLight = new THREE.SpotLight( 0xffffff, 1 );
 				spotLight.position.set( 15, 40, 35 );
 				spotLight.position.set( 15, 40, 35 );
 				spotLight.angle = Math.PI / 4;
 				spotLight.angle = Math.PI / 4;
@@ -144,7 +158,8 @@
 					angle: spotLight.angle,
 					angle: spotLight.angle,
 					penumbra: spotLight.penumbra,
 					penumbra: spotLight.penumbra,
 					decay: spotLight.decay,
 					decay: spotLight.decay,
-					focus: spotLight.shadow.focus
+					focus: spotLight.shadow.focus,
+					map: 'none'
 				};
 				};
 
 
 				gui.addColor( params, 'light color' ).onChange( function ( val ) {
 				gui.addColor( params, 'light color' ).onChange( function ( val ) {
@@ -197,6 +212,13 @@
 
 
 				} );
 				} );
 
 
+				gui.add( params, 'map', textureUrls ).onChange( function ( val ) {
+
+					spotLight.map = textures[ val ];
+					render();
+
+				} );
+
 				gui.open();
 				gui.open();
 
 
 			}
 			}

+ 2 - 0
src/lights/SpotLight.js

@@ -22,6 +22,8 @@ class SpotLight extends Light {
 		this.penumbra = penumbra;
 		this.penumbra = penumbra;
 		this.decay = decay; // for physically correct lights, should be 2.
 		this.decay = decay; // for physically correct lights, should be 2.
 
 
+		this.map = null;
+
 		this.shadow = new SpotLightShadow();
 		this.shadow = new SpotLightShadow();
 
 
 	}
 	}

+ 2 - 1
src/renderers/WebGLRenderer.js

@@ -1428,7 +1428,8 @@ function WebGLRenderer( parameters = {} ) {
 			uniforms.directionalShadowMap.value = lights.state.directionalShadowMap;
 			uniforms.directionalShadowMap.value = lights.state.directionalShadowMap;
 			uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix;
 			uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix;
 			uniforms.spotShadowMap.value = lights.state.spotShadowMap;
 			uniforms.spotShadowMap.value = lights.state.spotShadowMap;
-			uniforms.spotShadowMatrix.value = lights.state.spotShadowMatrix;
+			uniforms.spotLightMatrix.value = lights.state.spotLightMatrix;
+			uniforms.spotLightMap.value = lights.state.spotLightMap;
 			uniforms.pointShadowMap.value = lights.state.pointShadowMap;
 			uniforms.pointShadowMap.value = lights.state.pointShadowMap;
 			uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix;
 			uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix;
 			// TODO (abelnation): add area lights shadow info to uniforms
 			// TODO (abelnation): add area lights shadow info to uniforms

+ 25 - 1
src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js

@@ -82,6 +82,10 @@ IncidentLight directLight;
 #if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )
 #if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )
 
 
 	SpotLight spotLight;
 	SpotLight spotLight;
+	vec4 spotColor;
+	vec3 spotLightCoord;
+	bool inSpotLightMap;
+
 	#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0
 	#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0
 	SpotLightShadow spotLightShadow;
 	SpotLightShadow spotLightShadow;
 	#endif
 	#endif
@@ -93,9 +97,29 @@ IncidentLight directLight;
 
 
 		getSpotLightInfo( spotLight, geometry, directLight );
 		getSpotLightInfo( spotLight, geometry, directLight );
 
 
+		// spot lights are ordered [shadows with maps, shadows without maps, maps without shadows, none]
+		#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )
+		#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX
+		#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )
+		#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS
+		#else
+		#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )
+		#endif
+
+		#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )
+			spotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;
+			inSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );
+			spotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );
+			inSpotLightMap = inSpotLightMap && ( spotColor.a > 0. );
+			directLight.visible = directLight.visible || inSpotLightMap;
+			directLight.color = inSpotLightMap ? mix( directLight.color, spotLight.color * spotColor.rgb, spotColor.a ) : directLight.color;
+		#endif
+
+		#undef SPOT_LIGHT_MAP_INDEX
+
 		#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )
 		#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )
 		spotLightShadow = spotLightShadows[ i ];
 		spotLightShadow = spotLightShadows[ i ];
-		directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;
+		directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;
 		#endif
 		#endif
 
 
 		RE_Direct( directLight, geometry, material, reflectedLight );
 		RE_Direct( directLight, geometry, material, reflectedLight );

+ 18 - 7
src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js

@@ -1,4 +1,16 @@
 export default /* glsl */`
 export default /* glsl */`
+#if NUM_SPOT_LIGHT_COORDS > 0
+
+  varying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];
+
+#endif
+
+#if NUM_SPOT_LIGHT_MAPS > 0
+
+  uniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];
+
+#endif
+
 #ifdef USE_SHADOWMAP
 #ifdef USE_SHADOWMAP
 
 
 	#if NUM_DIR_LIGHT_SHADOWS > 0
 	#if NUM_DIR_LIGHT_SHADOWS > 0
@@ -20,7 +32,6 @@ export default /* glsl */`
 	#if NUM_SPOT_LIGHT_SHADOWS > 0
 	#if NUM_SPOT_LIGHT_SHADOWS > 0
 
 
 		uniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];
 		uniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];
-		varying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];
 
 
 		struct SpotLightShadow {
 		struct SpotLightShadow {
 			float shadowBias;
 			float shadowBias;
@@ -159,22 +170,22 @@ export default /* glsl */`
 				texture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +
 				texture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +
 				texture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +
 				texture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +
 				texture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +
 				texture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +
-				mix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ), 
+				mix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),
 					 texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),
 					 texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),
 					 f.x ) +
 					 f.x ) +
-				mix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ), 
+				mix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),
 					 texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),
 					 texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),
 					 f.x ) +
 					 f.x ) +
-				mix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ), 
+				mix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),
 					 texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),
 					 texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),
 					 f.y ) +
 					 f.y ) +
-				mix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ), 
+				mix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),
 					 texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),
 					 texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),
 					 f.y ) +
 					 f.y ) +
-				mix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ), 
+				mix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),
 						  texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),
 						  texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),
 						  f.x ),
 						  f.x ),
-					 mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ), 
+					 mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),
 						  texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),
 						  texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),
 						  f.x ),
 						  f.x ),
 					 f.y )
 					 f.y )

+ 8 - 3
src/renderers/shaders/ShaderChunk/shadowmap_pars_vertex.glsl.js

@@ -1,4 +1,12 @@
 export default /* glsl */`
 export default /* glsl */`
+
+#if NUM_SPOT_LIGHT_COORDS > 0
+
+  uniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];
+  varying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];
+
+#endif
+
 #ifdef USE_SHADOWMAP
 #ifdef USE_SHADOWMAP
 
 
 	#if NUM_DIR_LIGHT_SHADOWS > 0
 	#if NUM_DIR_LIGHT_SHADOWS > 0
@@ -19,9 +27,6 @@ export default /* glsl */`
 
 
 	#if NUM_SPOT_LIGHT_SHADOWS > 0
 	#if NUM_SPOT_LIGHT_SHADOWS > 0
 
 
-		uniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHT_SHADOWS ];
-		varying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];
-
 		struct SpotLightShadow {
 		struct SpotLightShadow {
 			float shadowBias;
 			float shadowBias;
 			float shadowNormalBias;
 			float shadowNormalBias;

+ 9 - 6
src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js

@@ -1,7 +1,7 @@
 export default /* glsl */`
 export default /* glsl */`
-#ifdef USE_SHADOWMAP
+#if defined( USE_SHADOWMAP ) || ( NUM_SPOT_LIGHT_COORDS > 0 )
 
 
-	#if NUM_DIR_LIGHT_SHADOWS > 0 || NUM_SPOT_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0
+	#if NUM_DIR_LIGHT_SHADOWS > 0 || NUM_SPOT_LIGHT_COORDS > 0 || NUM_POINT_LIGHT_SHADOWS > 0
 
 
 		// Offsetting the position used for querying occlusion along the world normal can be used to reduce shadow acne.
 		// Offsetting the position used for querying occlusion along the world normal can be used to reduce shadow acne.
 		vec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );
 		vec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );
@@ -22,13 +22,16 @@ export default /* glsl */`
 
 
 	#endif
 	#endif
 
 
-	#if NUM_SPOT_LIGHT_SHADOWS > 0
+	#if NUM_SPOT_LIGHT_COORDS > 0
 
 
 	#pragma unroll_loop_start
 	#pragma unroll_loop_start
-	for ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {
+	for ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {
 
 
-		shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias, 0 );
-		vSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * shadowWorldPosition;
+		shadowWorldPosition = worldPosition;
+		#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )
+			shadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;
+		#endif
+		vSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;
 
 
 	}
 	}
 	#pragma unroll_loop_end
 	#pragma unroll_loop_end

+ 1 - 1
src/renderers/shaders/ShaderChunk/shadowmask_pars_fragment.glsl.js

@@ -28,7 +28,7 @@ float getShadowMask() {
 	for ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {
 	for ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {
 
 
 		spotLight = spotLightShadows[ i ];
 		spotLight = spotLightShadows[ i ];
-		shadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;
+		shadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;
 
 
 	}
 	}
 	#pragma unroll_loop_end
 	#pragma unroll_loop_end

+ 1 - 1
src/renderers/shaders/ShaderChunk/worldpos_vertex.glsl.js

@@ -1,5 +1,5 @@
 export default /* glsl */`
 export default /* glsl */`
-#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION )
+#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_MAPS > 0
 
 
 	vec4 worldPosition = vec4( transformed, 1.0 );
 	vec4 worldPosition = vec4( transformed, 1.0 );
 
 

+ 2 - 1
src/renderers/shaders/UniformsLib.js

@@ -145,8 +145,9 @@ const UniformsLib = {
 			shadowMapSize: {}
 			shadowMapSize: {}
 		} },
 		} },
 
 
+		spotLightMap: { value: [] },
 		spotShadowMap: { value: [] },
 		spotShadowMap: { value: [] },
-		spotShadowMatrix: { value: [] },
+		spotLightMatrix: { value: [] },
 
 
 		pointLights: { value: [], properties: {
 		pointLights: { value: [], properties: {
 			color: {},
 			color: {},

+ 37 - 13
src/renderers/webgl/WebGLLights.js

@@ -144,9 +144,9 @@ function ShadowUniformsCache() {
 
 
 let nextVersion = 0;
 let nextVersion = 0;
 
 
-function shadowCastingLightsFirst( lightA, lightB ) {
+function shadowCastingAndTexturingLightsFirst( lightA, lightB ) {
 
 
-	return ( lightB.castShadow ? 1 : 0 ) - ( lightA.castShadow ? 1 : 0 );
+	return ( lightB.castShadow ? 2 : 0 ) - ( lightA.castShadow ? 2 : 0 ) + ( lightB.map ? 1 : 0 ) - ( lightA.map ? 1 : 0 );
 
 
 }
 }
 
 
@@ -169,7 +169,8 @@ function WebGLLights( extensions, capabilities ) {
 
 
 			numDirectionalShadows: - 1,
 			numDirectionalShadows: - 1,
 			numPointShadows: - 1,
 			numPointShadows: - 1,
-			numSpotShadows: - 1
+			numSpotShadows: - 1,
+			numSpotMaps: - 1
 		},
 		},
 
 
 		ambient: [ 0, 0, 0 ],
 		ambient: [ 0, 0, 0 ],
@@ -179,9 +180,10 @@ function WebGLLights( extensions, capabilities ) {
 		directionalShadowMap: [],
 		directionalShadowMap: [],
 		directionalShadowMatrix: [],
 		directionalShadowMatrix: [],
 		spot: [],
 		spot: [],
+		spotLightMap: [],
 		spotShadow: [],
 		spotShadow: [],
 		spotShadowMap: [],
 		spotShadowMap: [],
-		spotShadowMatrix: [],
+		spotLightMatrix: [],
 		rectArea: [],
 		rectArea: [],
 		rectAreaLTC1: null,
 		rectAreaLTC1: null,
 		rectAreaLTC2: null,
 		rectAreaLTC2: null,
@@ -189,7 +191,8 @@ function WebGLLights( extensions, capabilities ) {
 		pointShadow: [],
 		pointShadow: [],
 		pointShadowMap: [],
 		pointShadowMap: [],
 		pointShadowMatrix: [],
 		pointShadowMatrix: [],
-		hemi: []
+		hemi: [],
+		numSpotLightShadowsWithMaps: 0
 
 
 	};
 	};
 
 
@@ -214,8 +217,11 @@ function WebGLLights( extensions, capabilities ) {
 		let numDirectionalShadows = 0;
 		let numDirectionalShadows = 0;
 		let numPointShadows = 0;
 		let numPointShadows = 0;
 		let numSpotShadows = 0;
 		let numSpotShadows = 0;
+		let numSpotMaps = 0;
+		let numSpotShadowsWithMaps = 0;
 
 
-		lights.sort( shadowCastingLightsFirst );
+		// ordering : [shadow casting + map texturing, map texturing, shadow casting, none ]
+		lights.sort( shadowCastingAndTexturingLightsFirst );
 
 
 		// artist-friendly light intensity scaling factor
 		// artist-friendly light intensity scaling factor
 		const scaleFactor = ( physicallyCorrectLights !== true ) ? Math.PI : 1;
 		const scaleFactor = ( physicallyCorrectLights !== true ) ? Math.PI : 1;
@@ -286,9 +292,26 @@ function WebGLLights( extensions, capabilities ) {
 				uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) );
 				uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) );
 				uniforms.decay = light.decay;
 				uniforms.decay = light.decay;
 
 
-				if ( light.castShadow ) {
+				state.spot[ spotLength ] = uniforms;
 
 
-					const shadow = light.shadow;
+				const shadow = light.shadow;
+
+				if ( light.map ) {
+
+					state.spotLightMap[ numSpotMaps ] = light.map;
+					numSpotMaps ++;
+
+					// make sure the lightMatrix is up to date
+					// TODO : do it if required only
+					shadow.updateMatrices( light );
+
+					if ( light.castShadow ) numSpotShadowsWithMaps ++;
+
+				}
+
+				state.spotLightMatrix[ spotLength ] = shadow.matrix;
+
+				if ( light.castShadow ) {
 
 
 					const shadowUniforms = shadowCache.get( light );
 					const shadowUniforms = shadowCache.get( light );
 
 
@@ -299,14 +322,11 @@ function WebGLLights( extensions, capabilities ) {
 
 
 					state.spotShadow[ spotLength ] = shadowUniforms;
 					state.spotShadow[ spotLength ] = shadowUniforms;
 					state.spotShadowMap[ spotLength ] = shadowMap;
 					state.spotShadowMap[ spotLength ] = shadowMap;
-					state.spotShadowMatrix[ spotLength ] = light.shadow.matrix;
 
 
 					numSpotShadows ++;
 					numSpotShadows ++;
 
 
 				}
 				}
 
 
-				state.spot[ spotLength ] = uniforms;
-
 				spotLength ++;
 				spotLength ++;
 
 
 			} else if ( light.isRectAreaLight ) {
 			} else if ( light.isRectAreaLight ) {
@@ -420,7 +440,8 @@ function WebGLLights( extensions, capabilities ) {
 			hash.hemiLength !== hemiLength ||
 			hash.hemiLength !== hemiLength ||
 			hash.numDirectionalShadows !== numDirectionalShadows ||
 			hash.numDirectionalShadows !== numDirectionalShadows ||
 			hash.numPointShadows !== numPointShadows ||
 			hash.numPointShadows !== numPointShadows ||
-			hash.numSpotShadows !== numSpotShadows ) {
+			hash.numSpotShadows !== numSpotShadows ||
+			hash.numSpotMaps !== numSpotMaps ) {
 
 
 			state.directional.length = directionalLength;
 			state.directional.length = directionalLength;
 			state.spot.length = spotLength;
 			state.spot.length = spotLength;
@@ -436,7 +457,9 @@ function WebGLLights( extensions, capabilities ) {
 			state.spotShadowMap.length = numSpotShadows;
 			state.spotShadowMap.length = numSpotShadows;
 			state.directionalShadowMatrix.length = numDirectionalShadows;
 			state.directionalShadowMatrix.length = numDirectionalShadows;
 			state.pointShadowMatrix.length = numPointShadows;
 			state.pointShadowMatrix.length = numPointShadows;
-			state.spotShadowMatrix.length = numSpotShadows;
+			state.spotLightMatrix.length = numSpotShadows + numSpotMaps - numSpotShadowsWithMaps;
+			state.spotLightMap.length = numSpotMaps;
+			state.numSpotLightShadowsWithMaps = numSpotShadowsWithMaps;
 
 
 			hash.directionalLength = directionalLength;
 			hash.directionalLength = directionalLength;
 			hash.pointLength = pointLength;
 			hash.pointLength = pointLength;
@@ -447,6 +470,7 @@ function WebGLLights( extensions, capabilities ) {
 			hash.numDirectionalShadows = numDirectionalShadows;
 			hash.numDirectionalShadows = numDirectionalShadows;
 			hash.numPointShadows = numPointShadows;
 			hash.numPointShadows = numPointShadows;
 			hash.numSpotShadows = numSpotShadows;
 			hash.numSpotShadows = numSpotShadows;
+			hash.numSpotMaps = numSpotMaps;
 
 
 			state.version = nextVersion ++;
 			state.version = nextVersion ++;
 
 

+ 5 - 0
src/renderers/webgl/WebGLProgram.js

@@ -176,13 +176,18 @@ function filterEmptyLine( string ) {
 
 
 function replaceLightNums( string, parameters ) {
 function replaceLightNums( string, parameters ) {
 
 
+	const numSpotLightCoords = parameters.numSpotLightShadows + parameters.numSpotLightMaps - parameters.numSpotLightShadowsWithMaps;
+
 	return string
 	return string
 		.replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights )
 		.replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights )
 		.replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights )
 		.replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights )
+		.replace( /NUM_SPOT_LIGHT_MAPS/g, parameters.numSpotLightMaps )
+		.replace( /NUM_SPOT_LIGHT_COORDS/g, numSpotLightCoords )
 		.replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights )
 		.replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights )
 		.replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights )
 		.replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights )
 		.replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights )
 		.replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights )
 		.replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows )
 		.replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows )
+		.replace( /NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g, parameters.numSpotLightShadowsWithMaps )
 		.replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows )
 		.replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows )
 		.replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows );
 		.replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows );
 
 

+ 4 - 0
src/renderers/webgl/WebGLPrograms.js

@@ -199,12 +199,14 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
 			numDirLights: lights.directional.length,
 			numDirLights: lights.directional.length,
 			numPointLights: lights.point.length,
 			numPointLights: lights.point.length,
 			numSpotLights: lights.spot.length,
 			numSpotLights: lights.spot.length,
+			numSpotLightMaps: lights.spotLightMap.length,
 			numRectAreaLights: lights.rectArea.length,
 			numRectAreaLights: lights.rectArea.length,
 			numHemiLights: lights.hemi.length,
 			numHemiLights: lights.hemi.length,
 
 
 			numDirLightShadows: lights.directionalShadowMap.length,
 			numDirLightShadows: lights.directionalShadowMap.length,
 			numPointLightShadows: lights.pointShadowMap.length,
 			numPointLightShadows: lights.pointShadowMap.length,
 			numSpotLightShadows: lights.spotShadowMap.length,
 			numSpotLightShadows: lights.spotShadowMap.length,
+			numSpotLightShadowsWithMaps: lights.numSpotLightShadowsWithMaps,
 
 
 			numClippingPlanes: clipping.numPlanes,
 			numClippingPlanes: clipping.numPlanes,
 			numClipIntersection: clipping.numIntersection,
 			numClipIntersection: clipping.numIntersection,
@@ -299,11 +301,13 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
 		array.push( parameters.numDirLights );
 		array.push( parameters.numDirLights );
 		array.push( parameters.numPointLights );
 		array.push( parameters.numPointLights );
 		array.push( parameters.numSpotLights );
 		array.push( parameters.numSpotLights );
+		array.push( parameters.numSpotLightMaps );
 		array.push( parameters.numHemiLights );
 		array.push( parameters.numHemiLights );
 		array.push( parameters.numRectAreaLights );
 		array.push( parameters.numRectAreaLights );
 		array.push( parameters.numDirLightShadows );
 		array.push( parameters.numDirLightShadows );
 		array.push( parameters.numPointLightShadows );
 		array.push( parameters.numPointLightShadows );
 		array.push( parameters.numSpotLightShadows );
 		array.push( parameters.numSpotLightShadows );
+		array.push( parameters.numSpotLightShadowsWithMaps );
 		array.push( parameters.shadowMapType );
 		array.push( parameters.shadowMapType );
 		array.push( parameters.toneMapping );
 		array.push( parameters.toneMapping );
 		array.push( parameters.numClippingPlanes );
 		array.push( parameters.numClippingPlanes );