diff --git a/examples/jsm/objects/Sky.js b/examples/jsm/objects/Sky.js index 52d4a2159b1c88..0214e3294a61b0 100644 --- a/examples/jsm/objects/Sky.js +++ b/examples/jsm/objects/Sky.js @@ -181,31 +181,35 @@ Sky.SkyShader = { uniform float showSunDisc; uniform float time; - // Cloud noise functions - float hash( vec2 p ) { - return fract( sin( dot( p, vec2( 127.1, 311.7 ) ) ) * 43758.5453123 ); + // gradient at a lattice corner; sinless hash so every GPU produces the same clouds + vec2 gradient( vec2 i ) { + vec3 p = fract( i.xyx * vec3( 0.1031, 0.1030, 0.0973 ) ); + p += dot( p, p.yzx + 33.33 ); + return fract( ( p.xx + p.yz ) * p.zy ) * 2.0 - 1.0; } + // 2D gradient noise: isotropic lobes like Perlin at value-noise cost float noise( vec2 p ) { vec2 i = floor( p ); vec2 f = fract( p ); - f = f * f * ( 3.0 - 2.0 * f ); - float a = hash( i ); - float b = hash( i + vec2( 1.0, 0.0 ) ); - float c = hash( i + vec2( 0.0, 1.0 ) ); - float d = hash( i + vec2( 1.0, 1.0 ) ); - return mix( mix( a, b, f.x ), mix( c, d, f.x ), f.y ); + vec2 u = f * f * f * ( f * ( f * 6.0 - 15.0 ) + 10.0 ); // quintic fade + float a = dot( gradient( i ), f ); + float b = dot( gradient( i + vec2( 1.0, 0.0 ) ), f - vec2( 1.0, 0.0 ) ); + float c = dot( gradient( i + vec2( 0.0, 1.0 ) ), f - vec2( 0.0, 1.0 ) ); + float d = dot( gradient( i + vec2( 1.0, 1.0 ) ), f - vec2( 1.0, 1.0 ) ); + return mix( mix( a, b, u.x ), mix( c, d, u.x ), u.y ) * 1.6; // ~[-1,1] } - float fbm( vec2 p ) { - float value = 0.0; - float amplitude = 0.5; - for ( int i = 0; i < 5; i ++ ) { - value += amplitude * noise( p ); - p *= 2.0; + // fbm; per-octave drift makes clouds billow instead of scrolling as a rigid stamp + float fbm( vec2 p, float drift ) { + float result = 0.0; + float amplitude = 1.0; + for ( int i = 0; i < 4; i ++ ) { + result += amplitude * noise( p ); amplitude *= 0.5; + p = p * 2.0 + drift; } - return value; + return result; } // constants for atmospheric scattering @@ -282,30 +286,50 @@ Sky.SkyShader = { cloudUV *= cloudScale; cloudUV += time * cloudSpeed; - // Multi-octave noise for fluffy clouds - float cloudNoise = fbm( cloudUV * 1000.0 ); - cloudNoise += 0.5 * fbm( cloudUV * 2000.0 + 3.7 ); - cloudNoise = cloudNoise * 0.5 + 0.5; + // Cloud density field + float evolve = time * cloudSpeed * 300.0; + float cloudNoise = clamp( fbm( cloudUV * 1000.0, evolve ) * 0.7 + 0.5, 0.0, 1.0 ); - // Apply coverage threshold - float cloudMask = smoothstep( 1.0 - cloudCoverage, 1.0 - cloudCoverage + 0.3, cloudNoise ); + // Large-scale coverage variation: clear gaps next to dense banks + float region = noise( cloudUV * 300.0 ) * 0.37 + 0.5; + float cov = clamp( cloudCoverage + ( region - 0.5 ) * 0.6, 0.0, 1.0 ); + + // Carve clouds where noise rises above the coverage level + float threshold = 1.0 - cov; + float cloudMask = smoothstep( threshold, threshold + 0.3, cloudNoise ); // Fade clouds near horizon (adjusted by elevation) - float horizonFade = smoothstep( 0.0, 0.1 + 0.2 * cloudElevation, direction.y ); + float horizonFade = smoothstep( 0.0, 0.03 + 0.06 * cloudElevation, direction.y ); cloudMask *= horizonFade; - // Cloud lighting based on sun position - float sunInfluence = dot( direction, vSunDirection ) * 0.5 + 0.5; - float daylight = max( 0.0, vSunDirection.y * 2.0 ); + // Cloud lighting from the sky's own radiance + float dayFactor = smoothstep( -0.08, 0.3, vSunDirection.y ); + vec3 sunColor = vSunE * Fex * 0.22 * 0.04; // 0.22 ~ albedo/pi, 0.04 = exposure; the aerial composite adds the eye-leg extinction + vec3 skyAmbient = Lin * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); + + // Beer-powder self-shadow from the sampled density + float depth = max( 0.0, cloudNoise - threshold ); + float beer = exp( depth * -4.0 ); + float powder = 1.0 - beer * beer; // beer*beer == exp(-8*depth) + float shade = mix( 0.45, 1.0, clamp( beer * powder * 2.6, 0.0, 1.0 ) ); // 2.6 = 1/0.385, normalizes beer*powder peak to 1 + + // Henyey-Greenstein forward lobe ( g = 0.7 ): silver lining on rims toward the sun + float silver = clamp( 0.51 / pow( 1.49 - cosTheta * 1.4, 1.5 ), 0.0, 3.0 ); // 0.51=1-g^2, 1.49=1+g^2, 1.4=2g + float edge = cloudMask * ( 1.0 - cloudMask ) * 4.0; + + vec3 cloudColor = skyAmbient + sunColor * shade; + cloudColor += sunColor * silver * edge * 0.6; + cloudColor *= max( dayFactor, 0.03 ); + + // Cloud opacity via Beer's law: density sets how solid the clouds get + float alpha = ( 1.0 - exp( depth * cloudDensity * -12.0 ) ) * horizonFade; - // Base cloud color affected by atmosphere - vec3 atmosphereColor = Lin * 0.04; - vec3 cloudColor = mix( vec3( 0.3 ), vec3( 1.0 ), daylight ); - cloudColor = mix( cloudColor, atmosphereColor + vec3( 1.0 ), sunInfluence * 0.5 ); - cloudColor *= vSunE * 0.00002; + // Occlude the sun disc/glow behind opaque cloud + texColor -= L0 * 0.04 * alpha; - // Blend clouds with sky - texColor = mix( texColor, cloudColor, cloudMask * cloudDensity ); + // Composite through the atmosphere so distant clouds dissolve into haze + vec3 cloudAerial = mix( texColor, cloudColor, Fex ); + texColor = mix( texColor, cloudAerial, alpha ); } diff --git a/examples/jsm/objects/SkyMesh.js b/examples/jsm/objects/SkyMesh.js index 19cd71796b023e..74d019de3751a2 100644 --- a/examples/jsm/objects/SkyMesh.js +++ b/examples/jsm/objects/SkyMesh.js @@ -6,7 +6,7 @@ import { NodeMaterial } from 'three/webgpu'; -import { Fn, float, vec2, vec3, acos, add, mul, clamp, cos, dot, exp, max, mix, modelViewProjection, normalize, positionWorld, pow, smoothstep, sub, varyingProperty, vec4, uniform, cameraPosition, fract, floor, sin, time, Loop, If } from 'three/tsl'; +import { Fn, float, floor, fract, vec2, vec3, acos, add, mul, clamp, cos, dot, exp, max, mix, modelViewProjection, normalize, positionWorld, pow, smoothstep, sub, varyingProperty, vec4, uniform, cameraPosition, time, If, Loop } from 'three/tsl'; /** * Represents a skydome for scene backgrounds. Based on [A Practical Analytic Model for Daylight](https://www.researchgate.net/publication/220720443_A_Practical_Analytic_Model_for_Daylight) @@ -284,44 +284,48 @@ class SkyMesh extends Mesh { const texColor = add( Lin, L0 ).mul( 0.04 ).add( vec3( 0.0, 0.0003, 0.00075 ) ).toVar(); - // Cloud noise functions - const hash = Fn( ( [ p ] ) => { + // gradient at a lattice corner; sinless hash so every GPU produces the same clouds + const gradient = Fn( ( [ i ] ) => { - return fract( sin( dot( p, vec2( 127.1, 311.7 ) ) ).mul( 43758.5453123 ) ); + const p = fract( i.xyx.mul( vec3( 0.1031, 0.1030, 0.0973 ) ) ).toVar(); + p.addAssign( dot( p, p.yzx.add( 33.33 ) ) ); + + return fract( p.xx.add( p.yz ).mul( p.zy ) ).mul( 2.0 ).sub( 1.0 ); } ); - const noise = Fn( ( [ p_immutable ] ) => { + // 2D gradient noise: isotropic lobes like Perlin at value-noise cost + const noise = Fn( ( [ p ] ) => { - const p = vec2( p_immutable ).toVar(); const i = floor( p ); const f = fract( p ); - const ff = f.mul( f ).mul( sub( 3.0, f.mul( 2.0 ) ) ); + const u = f.mul( f ).mul( f ).mul( f.mul( f.mul( 6.0 ).sub( 15.0 ) ).add( 10.0 ) ); // quintic fade - const a = hash( i ); - const b = hash( add( i, vec2( 1.0, 0.0 ) ) ); - const c = hash( add( i, vec2( 0.0, 1.0 ) ) ); - const d = hash( add( i, vec2( 1.0, 1.0 ) ) ); + const a = dot( gradient( i ), f ); + const b = dot( gradient( i.add( vec2( 1.0, 0.0 ) ) ), f.sub( vec2( 1.0, 0.0 ) ) ); + const c = dot( gradient( i.add( vec2( 0.0, 1.0 ) ) ), f.sub( vec2( 0.0, 1.0 ) ) ); + const d = dot( gradient( i.add( vec2( 1.0, 1.0 ) ) ), f.sub( vec2( 1.0, 1.0 ) ) ); - return mix( mix( a, b, ff.x ), mix( c, d, ff.x ), ff.y ); + return mix( mix( a, b, u.x ), mix( c, d, u.x ), u.y ).mul( 1.6 ); // ~[-1,1] } ); - const fbm = Fn( ( [ p_immutable ] ) => { + // fbm; per-octave drift makes clouds billow instead of scrolling as a rigid stamp + const fbm = Fn( ( [ position, drift ] ) => { - const p = vec2( p_immutable ).toVar(); - const value = float( 0.0 ).toVar(); - const amplitude = float( 0.5 ).toVar(); + const p = vec2( position ).toVar(); + const result = float( 0.0 ).toVar(); + const amplitude = float( 1.0 ).toVar(); - Loop( 5, () => { + Loop( 4, () => { - value.addAssign( amplitude.mul( noise( p ) ) ); - p.mulAssign( 2.0 ); + result.addAssign( amplitude.mul( noise( p ) ) ); amplitude.mulAssign( 0.5 ); + p.mulAssign( 2.0 ).addAssign( drift ); } ); - return value; + return result; } ); @@ -334,29 +338,50 @@ class SkyMesh extends Mesh { cloudUV.mulAssign( this.cloudScale ); cloudUV.addAssign( time.mul( this.cloudSpeed ) ); - // Multi-octave noise for fluffy clouds - const cloudNoise = fbm( cloudUV.mul( 1000.0 ) ).add( fbm( cloudUV.mul( 2000.0 ).add( 3.7 ) ).mul( 0.5 ) ).toVar(); - cloudNoise.assign( cloudNoise.mul( 0.5 ).add( 0.5 ) ); + // Cloud density field + const evolve = time.mul( this.cloudSpeed ).mul( 300.0 ); + const cloudNoise = fbm( cloudUV.mul( 1000.0 ), evolve ).mul( 0.7 ).add( 0.5 ).clamp( 0.0, 1.0 ).toVar(); + + // Large-scale coverage variation: clear gaps next to dense banks + const region = noise( cloudUV.mul( 300.0 ) ).mul( 0.37 ).add( 0.5 ); + const cov = clamp( this.cloudCoverage.add( region.sub( 0.5 ).mul( 0.6 ) ), 0.0, 1.0 ); - // Apply coverage threshold - const cloudMask = smoothstep( sub( 1.0, this.cloudCoverage ), sub( 1.0, this.cloudCoverage ).add( 0.3 ), cloudNoise ).toVar(); + // Carve clouds where noise rises above the coverage level + const threshold = sub( 1.0, cov ).toVar(); + const cloudMask = smoothstep( threshold, threshold.add( 0.3 ), cloudNoise ).toVar(); // Fade clouds near horizon (adjusted by elevation) - const horizonFade = smoothstep( 0.0, add( 0.1, mul( 0.2, this.cloudElevation ) ), direction.y ); + const horizonFade = smoothstep( 0.0, add( 0.03, mul( 0.06, this.cloudElevation ) ), direction.y ); cloudMask.mulAssign( horizonFade ); - // Cloud lighting based on sun position - const sunInfluence = dot( direction, vSunDirection ).mul( 0.5 ).add( 0.5 ); - const daylight = max( 0.0, vSunDirection.y.mul( 2.0 ) ); + // Cloud lighting from the sky's own radiance + const dayFactor = smoothstep( - 0.08, 0.3, vSunDirection.y ); + const sunColor = vSunE.mul( Fex ).mul( 0.22 ).mul( 0.04 ).toVar(); // 0.22 ~ albedo/pi, 0.04 = exposure; the aerial composite adds the eye-leg extinction + const skyAmbient = Lin.mul( 0.04 ).add( vec3( 0.0, 0.0003, 0.00075 ) ); + + // Beer-powder self-shadow from the sampled density + const depth = max( 0.0, cloudNoise.sub( threshold ) ).toVar(); + const beer = exp( depth.mul( - 4.0 ) ).toVar(); + const powder = sub( 1.0, beer.mul( beer ) ); // beer*beer == exp(-8*depth) + const shade = mix( 0.45, 1.0, beer.mul( powder ).mul( 2.6 ).clamp( 0.0, 1.0 ) ); // 2.6 = 1/0.385, normalizes beer*powder peak to 1 + + // Henyey-Greenstein forward lobe ( g = 0.7 ): silver lining on rims toward the sun + const silver = float( 0.51 ).div( pow( sub( 1.49, cosTheta.mul( 1.4 ) ), 1.5 ) ).clamp( 0.0, 3.0 ); // 0.51=1-g^2, 1.49=1+g^2, 1.4=2g + const edge = cloudMask.mul( sub( 1.0, cloudMask ) ).mul( 4.0 ); + + const cloudColor = skyAmbient.add( sunColor.mul( shade ) ).toVar(); + cloudColor.addAssign( sunColor.mul( silver ).mul( edge ).mul( 0.6 ) ); + cloudColor.mulAssign( dayFactor.max( 0.03 ) ); + + // Cloud opacity via Beer's law: density sets how solid the clouds get + const alpha = sub( 1.0, exp( depth.mul( this.cloudDensity ).mul( - 12.0 ) ) ).mul( horizonFade ).toVar(); - // Base cloud color affected by atmosphere - const atmosphereColor = Lin.mul( 0.04 ); - const cloudColor = mix( vec3( 0.3 ), vec3( 1.0 ), daylight ).toVar(); - cloudColor.assign( mix( cloudColor, atmosphereColor.add( vec3( 1.0 ) ), sunInfluence.mul( 0.5 ) ) ); - cloudColor.mulAssign( vSunE.mul( 0.00002 ) ); + // Occlude the sun disc/glow behind opaque cloud + texColor.subAssign( L0.mul( 0.04 ).mul( alpha ) ); - // Blend clouds with sky - texColor.assign( mix( texColor, cloudColor, cloudMask.mul( this.cloudDensity ) ) ); + // Composite through the atmosphere so distant clouds dissolve into haze + const cloudAerial = mix( texColor, cloudColor, Fex ); + texColor.assign( mix( texColor, cloudAerial, alpha ) ); } ); diff --git a/examples/screenshots/webgl_animation_keyframes.jpg b/examples/screenshots/webgl_animation_keyframes.jpg index e8ab2af72e7592..88ac98ca0879d0 100644 Binary files a/examples/screenshots/webgl_animation_keyframes.jpg and b/examples/screenshots/webgl_animation_keyframes.jpg differ diff --git a/examples/screenshots/webgl_lightprobes_sponza.jpg b/examples/screenshots/webgl_lightprobes_sponza.jpg index 65f777b7594a85..5df162349247ec 100644 Binary files a/examples/screenshots/webgl_lightprobes_sponza.jpg and b/examples/screenshots/webgl_lightprobes_sponza.jpg differ diff --git a/examples/screenshots/webgl_shaders_ocean.jpg b/examples/screenshots/webgl_shaders_ocean.jpg index 50c5d5cf1e7d09..7ae7ba0adb12ba 100644 Binary files a/examples/screenshots/webgl_shaders_ocean.jpg and b/examples/screenshots/webgl_shaders_ocean.jpg differ diff --git a/examples/screenshots/webgl_shaders_sky.jpg b/examples/screenshots/webgl_shaders_sky.jpg index 6a9057581d972a..04a87e61d86085 100644 Binary files a/examples/screenshots/webgl_shaders_sky.jpg and b/examples/screenshots/webgl_shaders_sky.jpg differ diff --git a/examples/screenshots/webgpu_custom_fog.jpg b/examples/screenshots/webgpu_custom_fog.jpg index d05b3d73af1eae..4a6be76d532feb 100644 Binary files a/examples/screenshots/webgpu_custom_fog.jpg and b/examples/screenshots/webgpu_custom_fog.jpg differ diff --git a/examples/screenshots/webgpu_generator_building.jpg b/examples/screenshots/webgpu_generator_building.jpg index 576834575db20a..3221c641bc534c 100644 Binary files a/examples/screenshots/webgpu_generator_building.jpg and b/examples/screenshots/webgpu_generator_building.jpg differ diff --git a/examples/screenshots/webgpu_generator_city.jpg b/examples/screenshots/webgpu_generator_city.jpg index fb8b2b71a261e4..c68acf87226596 100644 Binary files a/examples/screenshots/webgpu_generator_city.jpg and b/examples/screenshots/webgpu_generator_city.jpg differ diff --git a/examples/screenshots/webgpu_lightprobes_sponza.jpg b/examples/screenshots/webgpu_lightprobes_sponza.jpg index 0e3632479b9a65..483604a5c083a2 100644 Binary files a/examples/screenshots/webgpu_lightprobes_sponza.jpg and b/examples/screenshots/webgpu_lightprobes_sponza.jpg differ diff --git a/examples/screenshots/webgpu_ocean.jpg b/examples/screenshots/webgpu_ocean.jpg index 7657ce710c4f36..ff66c03f84bdaa 100644 Binary files a/examples/screenshots/webgpu_ocean.jpg and b/examples/screenshots/webgpu_ocean.jpg differ diff --git a/examples/screenshots/webgpu_sky.jpg b/examples/screenshots/webgpu_sky.jpg index e6f3351c202437..16707dd6a8dea4 100644 Binary files a/examples/screenshots/webgpu_sky.jpg and b/examples/screenshots/webgpu_sky.jpg differ diff --git a/examples/webgl_shaders_sky.html b/examples/webgl_shaders_sky.html index 3d7de6f41abaa5..f9418a4deef39e 100644 --- a/examples/webgl_shaders_sky.html +++ b/examples/webgl_shaders_sky.html @@ -36,6 +36,8 @@ let sky, sun; + let sphere, cubeCamera; + init(); function initSky() { @@ -54,9 +56,9 @@ rayleigh: 3, mieCoefficient: 0.005, mieDirectionalG: 0.7, - elevation: 2, - azimuth: 180, - exposure: renderer.toneMappingExposure, + elevation: 65, + azimuth: 0, + exposure: 0.05, cloudCoverage: 0.4, cloudDensity: 0.4, cloudElevation: 0.5, @@ -113,8 +115,14 @@ scene = new THREE.Scene(); - const helper = new THREE.GridHelper( 10000, 2, 0xffffff, 0xffffff ); - scene.add( helper ); + const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { type: THREE.HalfFloatType } ); + cubeCamera = new THREE.CubeCamera( 1, 1000, cubeRenderTarget ); + + sphere = new THREE.Mesh( + new THREE.SphereGeometry( 400, 64, 32 ), + new THREE.MeshBasicMaterial( { envMap: cubeRenderTarget.texture } ) + ); + scene.add( sphere ); renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio( window.devicePixelRatio ); @@ -147,6 +155,11 @@ function animate() { sky.material.uniforms[ 'time' ].value = performance.now() * 0.001; + + sphere.visible = false; + cubeCamera.update( renderer, scene ); + sphere.visible = true; + renderer.render( scene, camera ); } diff --git a/examples/webgpu_sky.html b/examples/webgpu_sky.html index b6ec8300424491..ca01c48880511b 100644 --- a/examples/webgpu_sky.html +++ b/examples/webgpu_sky.html @@ -48,6 +48,8 @@ let sky, sun; + let sphere, cubeCamera; + init(); function initSky() { @@ -66,9 +68,9 @@ rayleigh: 3, mieCoefficient: 0.005, mieDirectionalG: 0.7, - elevation: 2, - azimuth: 180, - exposure: renderer.toneMappingExposure, + elevation: 65, + azimuth: 0, + exposure: 0.05, cloudCoverage: 0.4, cloudDensity: 0.4, cloudElevation: 0.5, @@ -124,6 +126,15 @@ scene = new THREE.Scene(); + const cubeRenderTarget = new THREE.CubeRenderTarget( 256, { type: THREE.HalfFloatType } ); + cubeCamera = new THREE.CubeCamera( 1, 1000, cubeRenderTarget ); + + sphere = new THREE.Mesh( + new THREE.SphereGeometry( 400, 64, 32 ), + new THREE.MeshBasicNodeMaterial( { envMap: cubeRenderTarget.texture } ) + ); + scene.add( sphere ); + renderer = new THREE.WebGPURenderer(); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); @@ -155,6 +166,10 @@ function animate() { + sphere.visible = false; + cubeCamera.update( renderer, scene ); + sphere.visible = true; + renderer.render( scene, camera ); } diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index dfc294b8c207f9..4765508366293d 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -1692,7 +1692,7 @@ class WebGLRenderer { if ( _this.sortObjects === true ) { - currentRenderList.sort( _opaqueSort, _transparentSort, camera.reversedDepth ); + currentRenderList.sort( _opaqueSort, _transparentSort ); } @@ -1882,7 +1882,7 @@ class WebGLRenderer { if ( material.visible ) { - currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null ); + currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null, camera ); } @@ -1926,7 +1926,7 @@ class WebGLRenderer { if ( groupMaterial && groupMaterial.visible ) { - currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group ); + currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group, camera ); } @@ -1934,7 +1934,7 @@ class WebGLRenderer { } else if ( material.visible ) { - currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null ); + currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null, camera ); } diff --git a/src/renderers/common/RenderList.js b/src/renderers/common/RenderList.js index 2563fc19260360..dd51de6dfc1020 100644 --- a/src/renderers/common/RenderList.js +++ b/src/renderers/common/RenderList.js @@ -300,6 +300,10 @@ class RenderList { */ push( object, geometry, material, groupOrder, z, group, clippingContext ) { + // with a reversed depth buffer the projected z is inverted + + if ( this.camera.reversedDepth === true ) z = - z; + const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext ); if ( object.occlusionTest === true && this._lastOcclusionObject !== object ) { @@ -384,22 +388,13 @@ class RenderList { * * @param {?function(any, any): number} customOpaqueSort - A custom sort function for opaque objects. * @param {?function(any, any): number} customTransparentSort - A custom sort function for transparent objects. - * @param {boolean} reversedDepth - Whether a reversed depth buffer is used or not. */ - sort( customOpaqueSort, customTransparentSort, reversedDepth ) { + sort( customOpaqueSort, customTransparentSort ) { if ( this.opaque.length > 1 ) this.opaque.sort( customOpaqueSort || painterSortStable ); if ( this.transparentDoublePass.length > 1 ) this.transparentDoublePass.sort( customTransparentSort || reversePainterSortStable ); if ( this.transparent.length > 1 ) this.transparent.sort( customTransparentSort || reversePainterSortStable ); - if ( reversedDepth ) { - - this.opaque.reverse(); - this.transparentDoublePass.reverse(); - this.transparent.reverse(); - - } - } /** diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js index bf96303e5234b4..bd19701ea00789 100644 --- a/src/renderers/common/Renderer.js +++ b/src/renderers/common/Renderer.js @@ -272,7 +272,7 @@ class Renderer { * @type {number} * @default 0 */ - this._samples = samples || ( antialias === true ) ? 4 : 0; + this._samples = samples || ( antialias === true ? 4 : 0 ); /** * OnCanvasTargetResize callback function. @@ -961,16 +961,15 @@ class Renderer { // - const frustum = camera.isArrayCamera ? _frustumArray : _frustum; + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); if ( camera.isArrayCamera ) { - frustum.setFromArrayCamera( camera ); + _frustumArray.setFromArrayCamera( camera ); } else { - _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - frustum.setFromProjectionMatrix( _projScreenMatrix, camera.coordinateSystem, camera.reversedDepth ); + _frustum.setFromProjectionMatrix( _projScreenMatrix, camera.coordinateSystem, camera.reversedDepth ); } @@ -1675,16 +1674,15 @@ class Renderer { // - const frustum = camera.isArrayCamera ? _frustumArray : _frustum; + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); if ( camera.isArrayCamera ) { - frustum.setFromArrayCamera( camera ); + _frustumArray.setFromArrayCamera( camera ); } else { - _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - frustum.setFromProjectionMatrix( _projScreenMatrix, camera.coordinateSystem, camera.reversedDepth ); + _frustum.setFromProjectionMatrix( _projScreenMatrix, camera.coordinateSystem, camera.reversedDepth ); } @@ -1697,7 +1695,7 @@ class Renderer { if ( this.sortObjects === true ) { - renderList.sort( this._opaqueSort, this._transparentSort, camera.reversedDepth ); + renderList.sort( this._opaqueSort, this._transparentSort ); } diff --git a/src/renderers/webgl/WebGLRenderLists.js b/src/renderers/webgl/WebGLRenderLists.js index 415c9794467acf..857c49f24e8df9 100644 --- a/src/renderers/webgl/WebGLRenderLists.js +++ b/src/renderers/webgl/WebGLRenderLists.js @@ -119,7 +119,11 @@ function WebGLRenderList() { } - function push( object, geometry, material, groupOrder, z, group ) { + function push( object, geometry, material, groupOrder, z, group, camera ) { + + // with a reversed depth buffer the projected z is inverted + + if ( camera.reversedDepth === true ) z = - z; const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); @@ -159,20 +163,12 @@ function WebGLRenderList() { } - function sort( customOpaqueSort, customTransparentSort, reversedDepth ) { + function sort( customOpaqueSort, customTransparentSort ) { if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable ); if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable ); if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable ); - if ( reversedDepth ) { - - opaque.reverse(); - transmissive.reverse(); - transparent.reverse(); - - } - } function finish() { diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 06e203693ed9b1..88133d6e591884 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -900,6 +900,8 @@ class WebGPUBackend extends Backend { // + const renderTarget = renderContext.renderTarget; + if ( renderContext.depth ) { if ( renderContext.clearDepth ) { @@ -913,7 +915,15 @@ class WebGPUBackend extends Backend { } - depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + if ( renderContext.sampleCount > 1 && renderTarget?.resolveDepthBuffer === false ) { + + depthStencilAttachment.depthStoreOp = GPUStoreOp.Discard; + + } else { + + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + + } } @@ -930,7 +940,15 @@ class WebGPUBackend extends Backend { } - depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + if ( renderContext.sampleCount > 1 && renderTarget?.resolveStencilBuffer === false ) { + + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Discard; + + } else { + + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + + } } diff --git a/test/unit/src/renderers/webgl/WebGLRenderLists.tests.js b/test/unit/src/renderers/webgl/WebGLRenderLists.tests.js index 5a871a00a183e0..8aad49c97599db 100644 --- a/test/unit/src/renderers/webgl/WebGLRenderLists.tests.js +++ b/test/unit/src/renderers/webgl/WebGLRenderLists.tests.js @@ -1,5 +1,6 @@ import { WebGLRenderLists, WebGLRenderList } from '../../../../../src/renderers/webgl/WebGLRenderLists.js'; import { Scene } from '../../../../../src/scenes/Scene.js'; +import { Camera } from '../../../../../src/cameras/Camera.js'; export default QUnit.module( 'Renderers', () => { @@ -30,12 +31,13 @@ export default QUnit.module( 'Renderers', () => { QUnit.test( 'init', ( assert ) => { const list = new WebGLRenderList(); + const camera = new Camera(); assert.ok( list.transparent.length === 0, 'Transparent list defaults to length 0.' ); assert.ok( list.opaque.length === 0, 'Opaque list defaults to length 0.' ); - list.push( {}, {}, { transparent: true }, 0, 0, {} ); - list.push( {}, {}, { transparent: false }, 0, 0, {} ); + list.push( {}, {}, { transparent: true }, 0, 0, {}, camera ); + list.push( {}, {}, { transparent: false }, 0, 0, {}, camera ); assert.ok( list.transparent.length === 1, 'Transparent list is length 1 after adding transparent item.' ); assert.ok( list.opaque.length === 1, 'Opaque list is length 1 after adding opaque item.' ); @@ -50,6 +52,8 @@ export default QUnit.module( 'Renderers', () => { QUnit.test( 'push', ( assert ) => { const list = new WebGLRenderList(); + const camera = new Camera(); + const objA = { id: 'A', renderOrder: 0 }; const matA = { transparent: true }; const geoA = {}; @@ -66,7 +70,7 @@ export default QUnit.module( 'Renderers', () => { const matD = { transparent: false }; const geoD = {}; - list.push( objA, geoA, matA, 0, 0.5, {} ); + list.push( objA, geoA, matA, 0, 0.5, {}, camera ); assert.ok( list.transparent.length === 1, 'Transparent list is length 1 after adding transparent item.' ); assert.ok( list.opaque.length === 0, 'Opaque list is length 0 after adding transparent item.' ); assert.deepEqual( @@ -85,7 +89,7 @@ export default QUnit.module( 'Renderers', () => { 'The first transparent render list item is structured correctly.' ); - list.push( objB, geoB, matB, 1, 1.5, {} ); + list.push( objB, geoB, matB, 1, 1.5, {}, camera ); assert.ok( list.transparent.length === 2, 'Transparent list is length 2 after adding second transparent item.' ); assert.ok( list.opaque.length === 0, 'Opaque list is length 0 after adding second transparent item.' ); assert.deepEqual( @@ -104,7 +108,7 @@ export default QUnit.module( 'Renderers', () => { 'The second transparent render list item is structured correctly.' ); - list.push( objC, geoC, matC, 2, 2.5, {} ); + list.push( objC, geoC, matC, 2, 2.5, {}, camera ); assert.ok( list.transparent.length === 2, 'Transparent list is length 2 after adding first opaque item.' ); assert.ok( list.opaque.length === 1, 'Opaque list is length 1 after adding first opaque item.' ); assert.deepEqual( @@ -123,7 +127,7 @@ export default QUnit.module( 'Renderers', () => { 'The first opaque render list item is structured correctly.' ); - list.push( objD, geoD, matD, 3, 3.5, {} ); + list.push( objD, geoD, matD, 3, 3.5, {}, camera ); assert.ok( list.transparent.length === 2, 'Transparent list is length 2 after adding second opaque item.' ); assert.ok( list.opaque.length === 2, 'Opaque list is length 2 after adding second opaque item.' ); assert.deepEqual( @@ -245,12 +249,14 @@ export default QUnit.module( 'Renderers', () => { QUnit.test( 'sort', ( assert ) => { const list = new WebGLRenderList(); + const camera = new Camera(); + const items = [ { id: 4 }, { id: 5 }, { id: 2 }, { id: 3 } ]; items.forEach( item => { - list.push( item, {}, { transparent: true }, 0, 0, {} ); - list.push( item, {}, { transparent: false }, 0, 0, {} ); + list.push( item, {}, { transparent: true }, 0, 0, {}, camera ); + list.push( item, {}, { transparent: false }, 0, 0, {}, camera ); } );