diff --git a/examples/files.json b/examples/files.json index 8ab08300fb3e07..0f28782c997e8c 100644 --- a/examples/files.json +++ b/examples/files.json @@ -421,6 +421,7 @@ "webgpu_multisampled_renderbuffers", "webgpu_occlusion", "webgpu_ocean", + "webgpu_oit", "webgpu_parallax_uv", "webgpu_particles", "webgpu_particles_soft", diff --git a/examples/jsm/objects/GaussianSplatMesh.js b/examples/jsm/objects/GaussianSplatMesh.js index f81363ed5ac55d..0bb59dafd8052c 100644 --- a/examples/jsm/objects/GaussianSplatMesh.js +++ b/examples/jsm/objects/GaussianSplatMesh.js @@ -1,9 +1,13 @@ import { + Box3, BufferAttribute, + Color, InstancedBufferGeometry, + Matrix3, Matrix4, Mesh, NodeMaterial, + Sphere, StorageBufferAttribute, Vector2, Vector3 @@ -48,6 +52,7 @@ const BIN_COUNT = 4096; const WORKGROUP_SIZE = 256; const SORT_DIRECTION_THRESHOLD = 0.9995; const KERNEL_2D_SIZE = 0.3; +const SPLAT_KERNEL_CUTOFF = 2; const MAX_SCREEN_SPACE_SPLAT_SIZE = 1024; const CLIP_XY = 1.4; @@ -58,6 +63,8 @@ const _sortDirection = /*@__PURE__*/ new Vector3(); const _sortDepthRange = /*@__PURE__*/ new Vector2(); const _worldMatrixInverse = /*@__PURE__*/ new Matrix4(); const _modelViewMatrix = /*@__PURE__*/ new Matrix4(); +const _splatBox = /*@__PURE__*/ new Box3(); +const _splat = {}; /** * A minimal renderer for 3D Gaussian splat geometry. @@ -128,6 +135,22 @@ class GaussianSplatMesh extends Mesh { */ this.splatGeometry = splatGeometry; + /** + * The bounding box of the splats. Can be computed via {@link GaussianSplatMesh#computeBoundingBox}. + * + * @type {?Box3} + * @default null + */ + this.boundingBox = null; + + /** + * The bounding sphere of the splats. Can be computed via {@link GaussianSplatMesh#computeBoundingSphere}. + * + * @type {?Sphere} + * @default null + */ + this.boundingSphere = null; + /** * Whether to sort automatically in `onBeforeRender`. * @@ -135,8 +158,6 @@ class GaussianSplatMesh extends Mesh { */ this.autoSort = autoSort; - this.frustumCulled = false; - this._buffers = buffers; this._sort = sort; this._sortMatrix = uniform( new Matrix4() ); @@ -239,6 +260,121 @@ class GaussianSplatMesh extends Mesh { } + /** + * Returns the splat at the given index. + * + * Members that the target does not already have are created, so an empty object can be passed + * and then reused across calls to avoid allocating. + * + * @param {number} index - The splat index. + * @param {Object} [target] - The object the splat is written to. + * @return {Object} The target, with `position`, `covariance`, `color`, `opacity` and `radius` set. + */ + getSplat( index, target = {} ) { + + const geometry = this.splatGeometry; + const positionAttribute = geometry.getAttribute( 'position' ); + const covarianceAttribute = geometry.getAttribute( 'covariance' ); + const colorAttribute = geometry.getAttribute( 'color' ); + + if ( target.position === undefined ) { + + target.position = new Vector3(); + + } + + if ( target.covariance === undefined ) { + + target.covariance = new Matrix3(); + + } + + if ( target.color === undefined ) { + + target.color = new Color(); + + } + + target.position.fromBufferAttribute( positionAttribute, index ); + + const c00 = covarianceAttribute.getComponent( index, 0 ); + const c01 = covarianceAttribute.getComponent( index, 1 ); + const c02 = covarianceAttribute.getComponent( index, 2 ); + const c11 = covarianceAttribute.getComponent( index, 3 ); + const c12 = covarianceAttribute.getComponent( index, 4 ); + const c22 = covarianceAttribute.getComponent( index, 5 ); + + // the attribute holds the upper triangle of the symmetric covariance + target.covariance.set( + c00, c01, c02, + c01, c11, c12, + c02, c12, c22 + ); + + target.color.fromBufferAttribute( colorAttribute, index ); + target.opacity = colorAttribute.getW( index ); + + // the radius of the drawn largest extent + target.radius = SPLAT_KERNEL_CUTOFF * Math.sqrt( Math.max( c00, c11, c22, 0 ) ); + + return target; + + } + + /** + * Computes the bounding box of the splats, updating {@link GaussianSplatMesh#boundingBox}. + * + * Each splat is expanded by its own extent rather than treated as a point, so the bounds cover + * what is drawn. + */ + computeBoundingBox() { + + if ( this.boundingBox === null ) this.boundingBox = new Box3(); + + this.boundingBox.makeEmpty(); + + const count = this.splatGeometry.getAttribute( 'position' ).count; + + for ( let i = 0; i < count; i ++ ) { + + this.getSplat( i, _splat ); + + _splatBox.set( _splat.position, _splat.position ).expandByScalar( _splat.radius ); + this.boundingBox.union( _splatBox ); + + } + + } + + /** + * Computes the bounding sphere of the splats, updating {@link GaussianSplatMesh#boundingSphere}. + * + * Each splat is expanded by its own extent rather than treated as a point, so the bounds cover + * what is drawn. + */ + computeBoundingSphere() { + + if ( this.boundingSphere === null ) this.boundingSphere = new Sphere(); + + this.computeBoundingBox(); + this.boundingBox.getBoundingSphere( this.boundingSphere ); + + let maxRadius = 0; + const center = this.boundingSphere.center; + const count = this.splatGeometry.getAttribute( 'position' ).count; + + for ( let i = 0; i < count; i ++ ) { + + this.getSplat( i, _splat ); + + maxRadius = Math.max( maxRadius, center.distanceTo( _splat.position ) + _splat.radius ); + + } + + this.boundingSphere.radius = maxRadius; + + } + /** * Updates the draw order if the camera or mesh orientation has changed enough * to need a new sort. @@ -295,12 +431,14 @@ class GaussianSplatMesh extends Mesh { this._sortMatrix.value.multiplyMatrices( camera.matrixWorldInverse, this.matrixWorld ); - _worldCenter.copy( this.splatGeometry.boundingSphere.center ).applyMatrix4( this.matrixWorld ); + if ( this.boundingSphere === null ) this.computeBoundingSphere(); + + _worldCenter.copy( this.boundingSphere.center ).applyMatrix4( this.matrixWorld ); _viewCenter.copy( _worldCenter ).applyMatrix4( camera.matrixWorldInverse ); _worldScale.setFromMatrixScale( this.matrixWorld ); - const radius = this.splatGeometry.boundingSphere.radius * Math.max( _worldScale.x, _worldScale.y, _worldScale.z ); + const radius = this.boundingSphere.radius * Math.max( _worldScale.x, _worldScale.y, _worldScale.z ); const depth = - _viewCenter.z; const nearDepth = Math.max( camera.near, depth - radius ); const farDepth = Math.max( nearDepth + 0.0001, depth + radius ); diff --git a/examples/jsm/tsl/display/OITPassNode.js b/examples/jsm/tsl/display/OITPassNode.js new file mode 100644 index 00000000000000..ef9170afee683c --- /dev/null +++ b/examples/jsm/tsl/display/OITPassNode.js @@ -0,0 +1,331 @@ +import { PassNode, RenderTarget, BlendMode, RendererUtils, Vector2, HalfFloatType, UnsignedByteType, RedFormat, CustomBlending, NormalBlending, OneFactor, ZeroFactor, OneMinusSrcColorFactor } from 'three/webgpu'; +import { float, mix, mrt, output, positionView, texture, vec4 } from 'three/tsl'; + +const _size = /*@__PURE__*/ new Vector2(); + +let _rendererState, _sceneState; + +/** + * A render pass node that renders the scene with Order-Independent Transparency + * based on the Weighted Blended OIT technique by McGuire and Bavoil. + * + * Transparent objects are rendered in a separate pass into two accumulation + * targets (a weighted color sum and the pixel's revealage) which are + * then composited over the rest of the scene. Since the result does not depend + * on the draw order, artifacts from sorting-based transparency like popping or + * incorrectly resolved intersecting geometry are avoided. + * + * Only transparent materials using `NormalBlending` and no transmission qualify + * for OIT. All other objects are rendered as usual. MSAA is not supported. + * + * MRT configurations assigned via `setMRT()` apply to the default pass only. + * OIT-qualified objects contribute to the color output but not to custom + * MRT outputs since a pixel may accumulate multiple transparent surfaces. + * + * ```js + * const renderPipeline = new THREE.RenderPipeline( renderer ); + * renderPipeline.outputNode = oitPass( scene, camera ); + * ``` + * + * References: + * - {@link https://jcgt.org/published/0002/02/09/} + * - {@link https://casual-effects.blogspot.com/2014/03/weighted-blended-order-independent.html} + * + * @augments PassNode + * @three_import import { oitPass } from 'three/addons/tsl/display/OITPassNode.js'; + */ +class OITPassNode extends PassNode { + + static get type() { + + return 'OITPassNode'; + + } + + /** + * Constructs a new OIT pass node. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + * @param {Object} [options={}] - Options for the internal render target. + */ + constructor( scene, camera, options = {} ) { + + super( PassNode.COLOR, scene, camera, { ...options, samples: 0 } ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isOITPassNode = true; + + /** + * The depth-based weight of a transparent fragment, see equations (7) to (9) + * in the paper. When `null`, equation (9) is used. Must be assigned before + * the first render. + * + * @type {?Node} + * @default null + */ + this.weightNode = null; + + // the accumulation target shares the depth of the default pass so transparent + // fragments are depth-tested against the opaque scene (without depth writes) + + const oitRenderTarget = new RenderTarget( 1, 1, { count: 2 } ); + oitRenderTarget.depthTexture = this.renderTarget.depthTexture; + + const accumTexture = oitRenderTarget.textures[ 0 ]; // RGBA16 + accumTexture.name = 'accum'; + accumTexture.type = HalfFloatType; + + const revealageTexture = oitRenderTarget.textures[ 1 ]; // R8 + revealageTexture.name = 'revealage'; + revealageTexture.format = RedFormat; + revealageTexture.type = UnsignedByteType; + + /** + * The render target holding the OIT accumulation textures. + * + * @private + * @type {RenderTarget} + */ + this._oitRenderTarget = oitRenderTarget; + + /** + * The MRT configuration for the OIT pass. + * + * @private + * @type {?MRTNode} + */ + this._oitMRTNode = null; + + /** + * The renderer of the current frame. + * + * @private + * @type {?Renderer} + */ + this._renderer = null; + + /** + * Renders opaque objects and transparent objects that do not qualify for OIT. + * + * @private + * @type {Function} + */ + this._defaultRenderObjectFunction = ( object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId ) => { + + if ( isOITCapable( material ) === false ) { + + this._renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId ); + + } + + }; + + /** + * Renders OIT-qualified objects into the accumulation targets. + * + * @private + * @type {Function} + */ + this._oitRenderObjectFunction = ( object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId ) => { + + if ( isOITCapable( material ) === true ) { + + const currentDepthWrite = material.depthWrite; + + material.depthWrite = false; + + this._renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId ); + + material.depthWrite = currentDepthWrite; + + } + + }; + + } + + /** + * Returns the MRT configuration for the OIT pass. + * + * @private + * @return {MRTNode} The MRT node. + */ + _getMRTNode() { + + if ( this._oitMRTNode === null ) { + + const alpha = output.a; + + let weight = this.weightNode; + + if ( weight === null ) { + + // equation (9) from the paper, based on the linear eye-space depth + + const z = positionView.z.negate(); + + weight = alpha.mul( float( 0.03 ).div( z.div( 200 ).pow( 4 ).add( 1e-5 ) ).clamp( 1e-2, 3e3 ) ); + + } + + // since the revealage target is single-channel, the alpha must be blended + // via its red channel + + const accumBlending = new BlendMode( CustomBlending ); + accumBlending.blendSrc = OneFactor; + accumBlending.blendDst = OneFactor; + + const revealageBlending = new BlendMode( CustomBlending ); + revealageBlending.blendSrc = ZeroFactor; + revealageBlending.blendDst = OneMinusSrcColorFactor; + + this._oitMRTNode = mrt( { + accum: vec4( output.rgb.mul( alpha ), alpha ).mul( weight ), + revealage: alpha + } ).setBlendMode( 'accum', accumBlending ).setBlendMode( 'revealage', revealageBlending ) + .setClearColor( 'accum', 0x000000, 0 ).setClearColor( 'revealage', 0xffffff, 1 ); + + } + + return this._oitMRTNode; + + } + + setSize( width, height ) { + + super.setSize( width, height ); + + this._oitRenderTarget.setSize( this.renderTarget.width, this.renderTarget.height ); + + } + + setup( builder ) { + + const beautyNode = super.setup( builder ); + + const accumNode = texture( this._oitRenderTarget.textures[ 0 ] ); + const revealageNode = texture( this._oitRenderTarget.textures[ 1 ] ).r; + + const accumColor = accumNode.rgb.div( accumNode.a.max( 1e-5 ) ); + + return vec4( mix( accumColor, beautyNode.rgb, revealageNode ), beautyNode.a ); + + } + + updateBefore( frame ) { + + const { renderer } = frame; + const { scene, camera } = this; + + this._renderer = renderer; + + renderer.getDrawingBufferSize( _size ); + this.setSize( _size.width, _size.height ); + + _rendererState = RendererUtils.saveRendererState( renderer, _rendererState ); + + const currentAutoClearColor = renderer.autoClearColor; + const currentAutoClearDepth = renderer.autoClearDepth; + const currentAutoClearStencil = renderer.autoClearStencil; + const currentTransparent = renderer.transparent; + const currentOpaque = renderer.opaque; + const currentMask = camera.layers.mask; + + this._cameraNear.value = camera.near; + this._cameraFar.value = camera.far; + + if ( this._layers !== null ) { + + camera.layers.mask = this._layers.mask; + + } + + renderer.autoClear = this.autoClear; + renderer.autoClearColor = this.autoClearColor; + renderer.autoClearDepth = this.autoClearDepth; + renderer.autoClearStencil = this.autoClearStencil; + + // default pass: opaque objects and transparent objects that do not qualify for OIT + + renderer.setMRT( this._mrt ); + renderer.setRenderTarget( this.renderTarget ); + renderer.setRenderObjectFunction( this._defaultRenderObjectFunction ); + + renderer.render( scene, camera ); + + // OIT pass: accumulate the weighted colors and the revealage of all OIT-qualified objects + + _sceneState = RendererUtils.resetSceneState( scene, _sceneState ); // the background must not affect the accumulation targets + + renderer.setRenderTarget( this._oitRenderTarget ); + renderer.setMRT( this._getMRTNode() ); + renderer.setRenderObjectFunction( this._oitRenderObjectFunction ); + renderer.autoClearDepth = false; // the depth buffer is shared with the default pass + renderer.opaque = false; + renderer.transparent = true; + + renderer.render( scene, camera ); + + // restore + + RendererUtils.restoreSceneState( scene, _sceneState ); + RendererUtils.restoreRendererState( renderer, _rendererState ); + + renderer.autoClearColor = currentAutoClearColor; + renderer.autoClearDepth = currentAutoClearDepth; + renderer.autoClearStencil = currentAutoClearStencil; + renderer.transparent = currentTransparent; + renderer.opaque = currentOpaque; + + camera.layers.mask = currentMask; + + this._renderer = null; + + } + + dispose() { + + super.dispose(); + + this._oitRenderTarget.dispose(); + + } + +} + +/** + * Returns `true` if the given material qualifies for OIT. + * + * @param {Material} material - The material to check. + * @return {boolean} Whether the material qualifies for OIT or not. + */ +function isOITCapable( material ) { + + return material.transparent === true && material.blending === NormalBlending && + ( material.transmission > 0 ) === false && + ! ( material.transmissionNode && material.transmissionNode.isNode ) && + ! ( material.backdropNode && material.backdropNode.isNode ); + +} + + +export default OITPassNode; + +/** + * TSL function for creating an OIT pass node. + * + * @tsl + * @function + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + * @param {Object} [options={}] - Options for the internal render target. + * @returns {OITPassNode} + */ +export const oitPass = ( scene, camera, options ) => new OITPassNode( scene, camera, options ); diff --git a/examples/screenshots/webgpu_oit.jpg b/examples/screenshots/webgpu_oit.jpg new file mode 100644 index 00000000000000..5ce4fbe8144060 Binary files /dev/null and b/examples/screenshots/webgpu_oit.jpg differ diff --git a/examples/webgpu_oit.html b/examples/webgpu_oit.html new file mode 100644 index 00000000000000..83a40e06f2e99f --- /dev/null +++ b/examples/webgpu_oit.html @@ -0,0 +1,205 @@ + + + + three.js webgpu - order-independent transparency + + + + + + + + + + +
+ + +
+ three.jsOrder-Independent Transparency +
+ + Weighted Blended OIT resolves intersecting and overlapping transparent surfaces per pixel. +
+ + + + + + diff --git a/src/Three.Core.js b/src/Three.Core.js index 3186280a6c5ffc..211c3d49581798 100644 --- a/src/Three.Core.js +++ b/src/Three.Core.js @@ -61,7 +61,6 @@ export { PointLight } from './lights/PointLight.js'; export { RectAreaLight } from './lights/RectAreaLight.js'; export { HemisphereLight } from './lights/HemisphereLight.js'; export { SunLight } from './lights/SunLight.js'; -export { SunLightShadow } from './lights/SunLightShadow.js'; export { DirectionalLight } from './lights/DirectionalLight.js'; export { AmbientLight } from './lights/AmbientLight.js'; export { Light } from './lights/Light.js'; diff --git a/src/lights/SunLight.js b/src/lights/SunLight.js index dd38710367d32d..189a50d4ced37b 100644 --- a/src/lights/SunLight.js +++ b/src/lights/SunLight.js @@ -1,5 +1,6 @@ import { Light } from './Light.js'; import { SunLightShadow } from './SunLightShadow.js'; +import { Object3D } from '../core/Object3D.js'; /** * A sun-like light that gets emitted in a specific direction, with rays that @@ -46,16 +47,16 @@ class SunLight extends Light { this.type = 'SunLight'; + this.position.copy( Object3D.DEFAULT_UP ); + this.updateMatrix(); + /** - * The light's shadow configuration. + * This property holds the light's shadow configuration. * * @type {SunLightShadow} */ this.shadow = new SunLightShadow(); - this.position.set( 0, 1, 0 ); - this.updateMatrix(); - } dispose() { diff --git a/src/lights/SunLightShadow.js b/src/lights/SunLightShadow.js index 85a2db5c4fd39d..bd4f791ceed6f9 100644 --- a/src/lights/SunLightShadow.js +++ b/src/lights/SunLightShadow.js @@ -10,10 +10,31 @@ const _viewToLightMatrix = /*@__PURE__*/ new Matrix4(); const _lightDirection = /*@__PURE__*/ new Vector3(); const _up = /*@__PURE__*/ new Vector3(); const _center = /*@__PURE__*/ new Vector3(); -const _corner = /*@__PURE__*/ new Vector3(); -const _nearCorners = [ new Vector3(), new Vector3(), new Vector3(), new Vector3() ]; -const _farCorners = [ new Vector3(), new Vector3(), new Vector3(), new Vector3() ]; -const _cascadeCorners = [ new Vector3(), new Vector3(), new Vector3(), new Vector3(), new Vector3(), new Vector3(), new Vector3(), new Vector3() ]; + +const _nearCorners = [ + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3() +]; + +const _farCorners = [ + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3() +]; + +const _cascadeCorners = [ + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3() +]; // must match the cascade count in the sun shadow shader chunks @@ -140,6 +161,10 @@ class SunLightShadow extends LightShadow { } + const resolutionX = this.mapSize.x * ( 1 - 2 * insetX ); + const resolutionY = this.mapSize.y * ( 1 - 2 * insetY ); + const resolution = Math.min( resolutionX, resolutionY ); + const camera = this.camera; const cameraNear = viewCamera.near; const cameraFar = Math.max( cameraNear + 1e-6, Math.min( camera.far, viewCamera.far ) ); @@ -249,10 +274,6 @@ class SunLightShadow extends LightShadow { // snap to the texel grid to avoid shimmering when the view camera moves - const resolutionX = this.mapSize.width * this._viewports[ i ].z; - const resolutionY = this.mapSize.height * this._viewports[ i ].w; - const resolution = Math.min( resolutionX, resolutionY ); - if ( resolution > 1 ) { // pad by half a texel so snapping cannot clip a frustum corner @@ -271,8 +292,7 @@ class SunLightShadow extends LightShadow { const cascadeCamera = this._cameras[ i ]; cascadeCamera.position.copy( _center ); - cascadeCamera.up.copy( _up ); - cascadeCamera.lookAt( _corner.copy( _center ).add( _lightDirection ) ); + cascadeCamera.quaternion.setFromRotationMatrix( _lightOrientationMatrix ); cascadeCamera.left = - radius; cascadeCamera.right = radius; cascadeCamera.top = radius; diff --git a/src/math/MathUtils.js b/src/math/MathUtils.js index c4013a93574e7c..1bff57c25209e3 100644 --- a/src/math/MathUtils.js +++ b/src/math/MathUtils.js @@ -402,6 +402,7 @@ function denormalize( value, array ) { return value / 65535.0; case Uint8Array: + case Uint8ClampedArray: return value / 255.0; @@ -449,6 +450,7 @@ function normalize( value, array ) { return Math.round( value * 65535.0 ); case Uint8Array: + case Uint8ClampedArray: return Math.round( value * 255.0 ); diff --git a/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js b/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js index 943d78279cf0b7..7f81748bf3fd9e 100644 --- a/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js +++ b/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js @@ -95,6 +95,7 @@ float getSpotAttenuation( const in float coneCosine, const in float penumbraCosi #endif + #if NUM_DIR_LIGHTS > 0 struct DirectionalLight { diff --git a/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js b/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js index 7cc8fc00f79457..748b3beaa7b001 100644 --- a/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js +++ b/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js @@ -296,7 +296,7 @@ export default /* glsl */` sampler2D shadowMap, #endif SunLightShadow sunLightShadow, - const in int shadowIndex + int shadowIndex ) { vec4 shadowWorldPosition = vec4( vSunShadowWorldPosition.xyz + vSunShadowWorldNormal * sunLightShadow.shadowNormalBias, 1.0 ); @@ -317,7 +317,7 @@ export default /* glsl */` float cascadeShadow = getShadow( shadowMap, sunLightShadow.shadowMapSize, sunLightShadow.shadowIntensity, sunLightShadow.shadowBias, sunLightShadow.shadowRadius, sunShadowMatrix[ cascadeOffset + i ] * shadowWorldPosition ); - shadow = viewDepth < cascade.z ? cascadeShadow : mix( cascadeShadow, shadow, smoothstep( cascade.z, cascade.y, viewDepth ) ); + shadow = mix( cascadeShadow, shadow, smoothstep( cascade.z, cascade.y, viewDepth ) ); } diff --git a/src/renderers/webgl/WebGLLights.js b/src/renderers/webgl/WebGLLights.js index 52c5ce19766757..5c0855221f1974 100644 --- a/src/renderers/webgl/WebGLLights.js +++ b/src/renderers/webgl/WebGLLights.js @@ -308,10 +308,10 @@ function WebGLLights( extensions ) { // four cascades per sun light, matching the sun shadow shader chunks - for ( let i = 0; i < 4; i ++ ) { + for ( let j = 0; j < 4; j ++ ) { - state.sunShadowMatrix[ numSunShadows * 4 + i ] = shadow.getMatrix( i ); - state.sunShadowCascade[ numSunShadows * 4 + i ] = shadow._cascadeData[ i ]; + state.sunShadowMatrix[ numSunShadows * 4 + j ] = shadow.getMatrix( j ); + state.sunShadowCascade[ numSunShadows * 4 + j ] = shadow._cascadeData[ j ]; } @@ -517,9 +517,9 @@ function WebGLLights( extensions ) { state.directionalShadowMatrix.length = numDirectionalShadows; state.pointShadow.length = numPointShadows; state.pointShadowMap.length = numPointShadows; + state.pointShadowMatrix.length = numPointShadows; state.spotShadow.length = numSpotShadows; state.spotShadowMap.length = numSpotShadows; - state.pointShadowMatrix.length = numPointShadows; state.spotLightMatrix.length = numSpotShadows + numSpotMaps - numSpotShadowsWithMaps; state.spotLightMap.length = numSpotMaps; state.numSpotLightShadowsWithMaps = numSpotShadowsWithMaps; diff --git a/src/renderers/webgl/WebGLShadowMap.js b/src/renderers/webgl/WebGLShadowMap.js index dcc5b906355fdc..a94ac763d67fdc 100644 --- a/src/renderers/webgl/WebGLShadowMap.js +++ b/src/renderers/webgl/WebGLShadowMap.js @@ -171,7 +171,6 @@ function WebGLShadowMap( renderer, objects, capabilities ) { _shadowMapSize.copy( shadow.mapSize ); - const viewportCount = shadow.getViewportCount(); const shadowFrameExtents = shadow.getFrameExtents(); _shadowMapSize.multiply( shadowFrameExtents ); @@ -287,7 +286,7 @@ function WebGLShadowMap( renderer, objects, capabilities ) { // For cube render targets (PointLights), render all 6 faces. Sun lights // render one atlas viewport per cascade. - const faceCount = shadow.map.isWebGLCubeRenderTarget ? 6 : viewportCount; + const faceCount = shadow.map.isWebGLCubeRenderTarget ? 6 : shadow.getViewportCount(); if ( light.isPointLight !== true ) shadow.updateMatrices( light, camera );