import { Color } from '../../math/Color.js'; import { Matrix4 } from '../../math/Matrix4.js'; import { Vector2 } from '../../math/Vector2.js'; import { Vector3 } from '../../math/Vector3.js'; import { UniformsLib } from '../shaders/UniformsLib.js'; function UniformsCache() { const lights = {}; return { get: function (light) { if (lights[light.id] !== undefined) { return lights[light.id]; } let uniforms; switch (light.type) { case 'DirectionalLight': uniforms = { direction: new Vector3(), color: new Color(), }; break; case 'SpotLight': uniforms = { position: new Vector3(), direction: new Vector3(), color: new Color(), distance: 0, coneCos: 0, penumbraCos: 0, decay: 0, }; break; case 'PointLight': uniforms = { position: new Vector3(), color: new Color(), distance: 0, decay: 0, }; break; case 'HemisphereLight': uniforms = { direction: new Vector3(), skyColor: new Color(), groundColor: new Color(), }; break; case 'RectAreaLight': uniforms = { color: new Color(), position: new Vector3(), halfWidth: new Vector3(), halfHeight: new Vector3(), }; break; } lights[light.id] = uniforms; return uniforms; }, }; } function ShadowUniformsCache() { const lights = {}; return { get: function (light) { if (lights[light.id] !== undefined) { return lights[light.id]; } let uniforms; switch (light.type) { case 'DirectionalLight': uniforms = { shadowBias: 0, shadowNormalBias: 0, shadowRadius: 1, shadowMapSize: new Vector2(), }; break; case 'SpotLight': uniforms = { shadowBias: 0, shadowNormalBias: 0, shadowRadius: 1, shadowMapSize: new Vector2(), }; break; case 'PointLight': uniforms = { shadowBias: 0, shadowNormalBias: 0, shadowRadius: 1, shadowMapSize: new Vector2(), shadowCameraNear: 1, shadowCameraFar: 1000, }; break; // TODO (abelnation): set RectAreaLight shadow uniforms } lights[light.id] = uniforms; return uniforms; }, }; } let nextVersion = 0; function shadowCastingLightsFirst(lightA, lightB) { return (lightB.castShadow ? 1 : 0) - (lightA.castShadow ? 1 : 0); } function WebGLLights(extensions, capabilities) { const cache = new UniformsCache(); const shadowCache = ShadowUniformsCache(); const state = { version: 0, hash: { directionalLength: -1, pointLength: -1, spotLength: -1, rectAreaLength: -1, hemiLength: -1, numDirectionalShadows: -1, numPointShadows: -1, numSpotShadows: -1, }, ambient: [0, 0, 0], probe: [], directional: [], directionalShadow: [], directionalShadowMap: [], directionalShadowMatrix: [], spot: [], spotShadow: [], spotShadowMap: [], spotShadowMatrix: [], rectArea: [], rectAreaLTC1: null, rectAreaLTC2: null, point: [], pointShadow: [], pointShadowMap: [], pointShadowMatrix: [], hemi: [], }; for (let i = 0; i < 9; i++) state.probe.push(new Vector3()); const vector3 = new Vector3(); const matrix4 = new Matrix4(); const matrix42 = new Matrix4(); function setup(lights, physicallyCorrectLights) { let r = 0, g = 0, b = 0; for (let i = 0; i < 9; i++) state.probe[i].set(0, 0, 0); let directionalLength = 0; let pointLength = 0; let spotLength = 0; let rectAreaLength = 0; let hemiLength = 0; let numDirectionalShadows = 0; let numPointShadows = 0; let numSpotShadows = 0; lights.sort(shadowCastingLightsFirst); // artist-friendly light intensity scaling factor const scaleFactor = physicallyCorrectLights !== true ? Math.PI : 1; for (let i = 0, l = lights.length; i < l; i++) { const light = lights[i]; const color = light.color; const intensity = light.intensity; const distance = light.distance; const shadowMap = light.shadow && light.shadow.map ? light.shadow.map.texture : null; if (light.isAmbientLight) { r += color.r * intensity * scaleFactor; g += color.g * intensity * scaleFactor; b += color.b * intensity * scaleFactor; } else if (light.isLightProbe) { for (let j = 0; j < 9; j++) { state.probe[j].addScaledVector(light.sh.coefficients[j], intensity); } } else if (light.isDirectionalLight) { const uniforms = cache.get(light); uniforms.color.copy(light.color).multiplyScalar(light.intensity * scaleFactor); if (light.castShadow) { const shadow = light.shadow; const shadowUniforms = shadowCache.get(light); shadowUniforms.shadowBias = shadow.bias; shadowUniforms.shadowNormalBias = shadow.normalBias; shadowUniforms.shadowRadius = shadow.radius; shadowUniforms.shadowMapSize = shadow.mapSize; state.directionalShadow[directionalLength] = shadowUniforms; state.directionalShadowMap[directionalLength] = shadowMap; state.directionalShadowMatrix[directionalLength] = light.shadow.matrix; numDirectionalShadows++; } state.directional[directionalLength] = uniforms; directionalLength++; } else if (light.isSpotLight) { const uniforms = cache.get(light); uniforms.position.setFromMatrixPosition(light.matrixWorld); uniforms.color.copy(color).multiplyScalar(intensity * scaleFactor); uniforms.distance = distance; uniforms.coneCos = Math.cos(light.angle); uniforms.penumbraCos = Math.cos(light.angle * (1 - light.penumbra)); uniforms.decay = light.decay; if (light.castShadow) { const shadow = light.shadow; const shadowUniforms = shadowCache.get(light); shadowUniforms.shadowBias = shadow.bias; shadowUniforms.shadowNormalBias = shadow.normalBias; shadowUniforms.shadowRadius = shadow.radius; shadowUniforms.shadowMapSize = shadow.mapSize; state.spotShadow[spotLength] = shadowUniforms; state.spotShadowMap[spotLength] = shadowMap; state.spotShadowMatrix[spotLength] = light.shadow.matrix; numSpotShadows++; } state.spot[spotLength] = uniforms; spotLength++; } else if (light.isRectAreaLight) { const uniforms = cache.get(light); // (a) intensity is the total visible light emitted //uniforms.color.copy( color ).multiplyScalar( intensity / ( light.width * light.height * Math.PI ) ); // (b) intensity is the brightness of the light uniforms.color.copy(color).multiplyScalar(intensity); uniforms.halfWidth.set(light.width * 0.5, 0.0, 0.0); uniforms.halfHeight.set(0.0, light.height * 0.5, 0.0); state.rectArea[rectAreaLength] = uniforms; rectAreaLength++; } else if (light.isPointLight) { const uniforms = cache.get(light); uniforms.color.copy(light.color).multiplyScalar(light.intensity * scaleFactor); uniforms.distance = light.distance; uniforms.decay = light.decay; if (light.castShadow) { const shadow = light.shadow; const shadowUniforms = shadowCache.get(light); shadowUniforms.shadowBias = shadow.bias; shadowUniforms.shadowNormalBias = shadow.normalBias; shadowUniforms.shadowRadius = shadow.radius; shadowUniforms.shadowMapSize = shadow.mapSize; shadowUniforms.shadowCameraNear = shadow.camera.near; shadowUniforms.shadowCameraFar = shadow.camera.far; state.pointShadow[pointLength] = shadowUniforms; state.pointShadowMap[pointLength] = shadowMap; state.pointShadowMatrix[pointLength] = light.shadow.matrix; numPointShadows++; } state.point[pointLength] = uniforms; pointLength++; } else if (light.isHemisphereLight) { const uniforms = cache.get(light); uniforms.skyColor.copy(light.color).multiplyScalar(intensity * scaleFactor); uniforms.groundColor.copy(light.groundColor).multiplyScalar(intensity * scaleFactor); state.hemi[hemiLength] = uniforms; hemiLength++; } } if (rectAreaLength > 0) { if (capabilities.isWebGL2) { // WebGL 2 state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; } else { // WebGL 1 if (extensions.has('OES_texture_float_linear') === true) { state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; } else if (extensions.has('OES_texture_half_float_linear') === true) { state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; } else { console.error('THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.'); } } } state.ambient[0] = r; state.ambient[1] = g; state.ambient[2] = b; const hash = state.hash; if ( hash.directionalLength !== directionalLength || hash.pointLength !== pointLength || hash.spotLength !== spotLength || hash.rectAreaLength !== rectAreaLength || hash.hemiLength !== hemiLength || hash.numDirectionalShadows !== numDirectionalShadows || hash.numPointShadows !== numPointShadows || hash.numSpotShadows !== numSpotShadows ) { state.directional.length = directionalLength; state.spot.length = spotLength; state.rectArea.length = rectAreaLength; state.point.length = pointLength; state.hemi.length = hemiLength; state.directionalShadow.length = numDirectionalShadows; state.directionalShadowMap.length = numDirectionalShadows; state.pointShadow.length = numPointShadows; state.pointShadowMap.length = numPointShadows; state.spotShadow.length = numSpotShadows; state.spotShadowMap.length = numSpotShadows; state.directionalShadowMatrix.length = numDirectionalShadows; state.pointShadowMatrix.length = numPointShadows; state.spotShadowMatrix.length = numSpotShadows; hash.directionalLength = directionalLength; hash.pointLength = pointLength; hash.spotLength = spotLength; hash.rectAreaLength = rectAreaLength; hash.hemiLength = hemiLength; hash.numDirectionalShadows = numDirectionalShadows; hash.numPointShadows = numPointShadows; hash.numSpotShadows = numSpotShadows; state.version = nextVersion++; } } function setupView(lights, camera) { let directionalLength = 0; let pointLength = 0; let spotLength = 0; let rectAreaLength = 0; let hemiLength = 0; const viewMatrix = camera.matrixWorldInverse; for (let i = 0, l = lights.length; i < l; i++) { const light = lights[i]; if (light.isDirectionalLight) { const uniforms = state.directional[directionalLength]; uniforms.direction.setFromMatrixPosition(light.matrixWorld); vector3.setFromMatrixPosition(light.target.matrixWorld); uniforms.direction.sub(vector3); uniforms.direction.transformDirection(viewMatrix); directionalLength++; } else if (light.isSpotLight) { const uniforms = state.spot[spotLength]; uniforms.position.setFromMatrixPosition(light.matrixWorld); uniforms.position.applyMatrix4(viewMatrix); uniforms.direction.setFromMatrixPosition(light.matrixWorld); vector3.setFromMatrixPosition(light.target.matrixWorld); uniforms.direction.sub(vector3); uniforms.direction.transformDirection(viewMatrix); spotLength++; } else if (light.isRectAreaLight) { const uniforms = state.rectArea[rectAreaLength]; uniforms.position.setFromMatrixPosition(light.matrixWorld); uniforms.position.applyMatrix4(viewMatrix); // extract local rotation of light to derive width/height half vectors matrix42.identity(); matrix4.copy(light.matrixWorld); matrix4.premultiply(viewMatrix); matrix42.extractRotation(matrix4); uniforms.halfWidth.set(light.width * 0.5, 0.0, 0.0); uniforms.halfHeight.set(0.0, light.height * 0.5, 0.0); uniforms.halfWidth.applyMatrix4(matrix42); uniforms.halfHeight.applyMatrix4(matrix42); rectAreaLength++; } else if (light.isPointLight) { const uniforms = state.point[pointLength]; uniforms.position.setFromMatrixPosition(light.matrixWorld); uniforms.position.applyMatrix4(viewMatrix); pointLength++; } else if (light.isHemisphereLight) { const uniforms = state.hemi[hemiLength]; uniforms.direction.setFromMatrixPosition(light.matrixWorld); uniforms.direction.transformDirection(viewMatrix); uniforms.direction.normalize(); hemiLength++; } } } return { setup: setup, setupView: setupView, state: state, }; } export { WebGLLights };