Spaces:
Running
Running
| 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 }; | |