import { FrontSide, BackSide, DoubleSide, RGBAFormat, NearestFilter, LinearFilter, PCFShadowMap, VSMShadowMap, RGBADepthPacking, NoBlending, } from '../../constants.js'; import { WebGLRenderTarget } from '../WebGLRenderTarget.js'; import { MeshDepthMaterial } from '../../materials/MeshDepthMaterial.js'; import { MeshDistanceMaterial } from '../../materials/MeshDistanceMaterial.js'; import { ShaderMaterial } from '../../materials/ShaderMaterial.js'; import { BufferAttribute } from '../../core/BufferAttribute.js'; import { BufferGeometry } from '../../core/BufferGeometry.js'; import { Mesh } from '../../objects/Mesh.js'; import { Vector4 } from '../../math/Vector4.js'; import { Vector2 } from '../../math/Vector2.js'; import { Frustum } from '../../math/Frustum.js'; import * as vsm from '../shaders/ShaderLib/vsm.glsl.js'; function WebGLShadowMap(_renderer, _objects, _capabilities) { let _frustum = new Frustum(); const _shadowMapSize = new Vector2(), _viewportSize = new Vector2(), _viewport = new Vector4(), _depthMaterial = new MeshDepthMaterial({ depthPacking: RGBADepthPacking }), _distanceMaterial = new MeshDistanceMaterial(), _materialCache = {}, _maxTextureSize = _capabilities.maxTextureSize; const shadowSide = { 0: BackSide, 1: FrontSide, 2: DoubleSide }; const shadowMaterialVertical = new ShaderMaterial({ defines: { VSM_SAMPLES: 8, }, uniforms: { shadow_pass: { value: null }, resolution: { value: new Vector2() }, radius: { value: 4.0 }, }, vertexShader: vsm.vertex, fragmentShader: vsm.fragment, }); const shadowMaterialHorizontal = shadowMaterialVertical.clone(); shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1; const fullScreenTri = new BufferGeometry(); fullScreenTri.setAttribute('position', new BufferAttribute(new Float32Array([-1, -1, 0.5, 3, -1, 0.5, -1, 3, 0.5]), 3)); const fullScreenMesh = new Mesh(fullScreenTri, shadowMaterialVertical); const scope = this; this.enabled = false; this.autoUpdate = true; this.needsUpdate = false; this.type = PCFShadowMap; this.render = function (lights, scene, camera) { if (scope.enabled === false) return; if (scope.autoUpdate === false && scope.needsUpdate === false) return; if (lights.length === 0) return; const currentRenderTarget = _renderer.getRenderTarget(); const activeCubeFace = _renderer.getActiveCubeFace(); const activeMipmapLevel = _renderer.getActiveMipmapLevel(); const _state = _renderer.state; // Set GL state for depth map. _state.setBlending(NoBlending); _state.buffers.color.setClear(1, 1, 1, 1); _state.buffers.depth.setTest(true); _state.setScissorTest(false); // render depth map for (let i = 0, il = lights.length; i < il; i++) { const light = lights[i]; const shadow = light.shadow; if (shadow === undefined) { console.warn('THREE.WebGLShadowMap:', light, 'has no shadow.'); continue; } if (shadow.autoUpdate === false && shadow.needsUpdate === false) continue; _shadowMapSize.copy(shadow.mapSize); const shadowFrameExtents = shadow.getFrameExtents(); _shadowMapSize.multiply(shadowFrameExtents); _viewportSize.copy(shadow.mapSize); if (_shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize) { if (_shadowMapSize.x > _maxTextureSize) { _viewportSize.x = Math.floor(_maxTextureSize / shadowFrameExtents.x); _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x; shadow.mapSize.x = _viewportSize.x; } if (_shadowMapSize.y > _maxTextureSize) { _viewportSize.y = Math.floor(_maxTextureSize / shadowFrameExtents.y); _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y; shadow.mapSize.y = _viewportSize.y; } } if (shadow.map === null && !shadow.isPointLightShadow && this.type === VSMShadowMap) { const pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat }; shadow.map = new WebGLRenderTarget(_shadowMapSize.x, _shadowMapSize.y, pars); shadow.map.texture.name = light.name + '.shadowMap'; shadow.mapPass = new WebGLRenderTarget(_shadowMapSize.x, _shadowMapSize.y, pars); shadow.camera.updateProjectionMatrix(); } if (shadow.map === null) { const pars = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat }; shadow.map = new WebGLRenderTarget(_shadowMapSize.x, _shadowMapSize.y, pars); shadow.map.texture.name = light.name + '.shadowMap'; shadow.camera.updateProjectionMatrix(); } _renderer.setRenderTarget(shadow.map); _renderer.clear(); const viewportCount = shadow.getViewportCount(); for (let vp = 0; vp < viewportCount; vp++) { const viewport = shadow.getViewport(vp); _viewport.set(_viewportSize.x * viewport.x, _viewportSize.y * viewport.y, _viewportSize.x * viewport.z, _viewportSize.y * viewport.w); _state.viewport(_viewport); shadow.updateMatrices(light, vp); _frustum = shadow.getFrustum(); renderObject(scene, camera, shadow.camera, light, this.type); } // do blur pass for VSM if (!shadow.isPointLightShadow && this.type === VSMShadowMap) { VSMPass(shadow, camera); } shadow.needsUpdate = false; } scope.needsUpdate = false; _renderer.setRenderTarget(currentRenderTarget, activeCubeFace, activeMipmapLevel); }; function VSMPass(shadow, camera) { const geometry = _objects.update(fullScreenMesh); if (shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples) { shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples; shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples; shadowMaterialVertical.needsUpdate = true; shadowMaterialHorizontal.needsUpdate = true; } // vertical pass shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture; shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize; shadowMaterialVertical.uniforms.radius.value = shadow.radius; _renderer.setRenderTarget(shadow.mapPass); _renderer.clear(); _renderer.renderBufferDirect(camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null); // horizontal pass shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture; shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize; shadowMaterialHorizontal.uniforms.radius.value = shadow.radius; _renderer.setRenderTarget(shadow.map); _renderer.clear(); _renderer.renderBufferDirect(camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null); } function getDepthMaterial(object, geometry, material, light, shadowCameraNear, shadowCameraFar, type) { let result = null; const customMaterial = light.isPointLight === true ? object.customDistanceMaterial : object.customDepthMaterial; if (customMaterial !== undefined) { result = customMaterial; } else { result = light.isPointLight === true ? _distanceMaterial : _depthMaterial; } if ( (_renderer.localClippingEnabled && material.clipShadows === true && material.clippingPlanes.length !== 0) || (material.displacementMap && material.displacementScale !== 0) || (material.alphaMap && material.alphaTest > 0) ) { // in this case we need a unique material instance reflecting the // appropriate state const keyA = result.uuid, keyB = material.uuid; let materialsForVariant = _materialCache[keyA]; if (materialsForVariant === undefined) { materialsForVariant = {}; _materialCache[keyA] = materialsForVariant; } let cachedMaterial = materialsForVariant[keyB]; if (cachedMaterial === undefined) { cachedMaterial = result.clone(); materialsForVariant[keyB] = cachedMaterial; } result = cachedMaterial; } result.visible = material.visible; result.wireframe = material.wireframe; if (type === VSMShadowMap) { result.side = material.shadowSide !== null ? material.shadowSide : material.side; } else { result.side = material.shadowSide !== null ? material.shadowSide : shadowSide[material.side]; } result.alphaMap = material.alphaMap; result.alphaTest = material.alphaTest; result.clipShadows = material.clipShadows; result.clippingPlanes = material.clippingPlanes; result.clipIntersection = material.clipIntersection; result.displacementMap = material.displacementMap; result.displacementScale = material.displacementScale; result.displacementBias = material.displacementBias; result.wireframeLinewidth = material.wireframeLinewidth; result.linewidth = material.linewidth; if (light.isPointLight === true && result.isMeshDistanceMaterial === true) { result.referencePosition.setFromMatrixPosition(light.matrixWorld); result.nearDistance = shadowCameraNear; result.farDistance = shadowCameraFar; } return result; } function renderObject(object, camera, shadowCamera, light, type) { if (object.visible === false) return; const visible = object.layers.test(camera.layers); if (visible && (object.isMesh || object.isLine || object.isPoints)) { if ((object.castShadow || (object.receiveShadow && type === VSMShadowMap)) && (!object.frustumCulled || _frustum.intersectsObject(object))) { object.modelViewMatrix.multiplyMatrices(shadowCamera.matrixWorldInverse, object.matrixWorld); const geometry = _objects.update(object); const material = object.material; if (Array.isArray(material)) { const groups = geometry.groups; for (let k = 0, kl = groups.length; k < kl; k++) { const group = groups[k]; const groupMaterial = material[group.materialIndex]; if (groupMaterial && groupMaterial.visible) { const depthMaterial = getDepthMaterial(object, geometry, groupMaterial, light, shadowCamera.near, shadowCamera.far, type); _renderer.renderBufferDirect(shadowCamera, null, geometry, depthMaterial, object, group); } } } else if (material.visible) { const depthMaterial = getDepthMaterial(object, geometry, material, light, shadowCamera.near, shadowCamera.far, type); _renderer.renderBufferDirect(shadowCamera, null, geometry, depthMaterial, object, null); } } } const children = object.children; for (let i = 0, l = children.length; i < l; i++) { renderObject(children[i], camera, shadowCamera, light, type); } } } export { WebGLShadowMap };