Jelajahi Sumber

Merge branch 'dev' into city

# Conflicts:
#	examples/screenshots/webgpu_generator_building.jpg
#	examples/screenshots/webgpu_generator_city.jpg
Mr.doob 2 minggu lalu
induk
melakukan
f613ed6397

+ 14 - 2
examples/jsm/exporters/GLTFExporter.js

@@ -616,7 +616,8 @@ class GLTFWriter {
 			attributesNormalized: new Map(),
 			materials: new Map(),
 			textures: new Map(),
-			images: new Map()
+			images: new Map(),
+			normalMaps: new Map()
 		};
 
 		this.textureUtils = null;
@@ -1750,7 +1751,18 @@ class GLTFWriter {
 
 			if ( flipX || flipY ) {
 
-				normalMap = await this.buildNormalMapTextureAsync( material.normalMap, flipX, flipY );
+				if ( cache.normalMaps.has( material.normalMap ) === false ) cache.normalMaps.set( material.normalMap, {} );
+
+				const cachedVariants = cache.normalMaps.get( material.normalMap );
+				const cacheKey = `${flipX}:${flipY}`;
+
+				if ( cachedVariants[ cacheKey ] === undefined ) {
+
+					cachedVariants[ cacheKey ] = await this.buildNormalMapTextureAsync( material.normalMap, flipX, flipY );
+
+				}
+
+				normalMap = cachedVariants[ cacheKey ];
 
 			}
 

+ 57 - 33
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 );
 
 			}
 

+ 61 - 36
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 ) );
 
 			} );
 

TEMPAT SAMPAH
examples/screenshots/webgl_animation_keyframes.jpg


TEMPAT SAMPAH
examples/screenshots/webgl_lightprobes_sponza.jpg


TEMPAT SAMPAH
examples/screenshots/webgl_shaders_ocean.jpg


TEMPAT SAMPAH
examples/screenshots/webgl_shaders_sky.jpg


TEMPAT SAMPAH
examples/screenshots/webgpu_custom_fog.jpg


TEMPAT SAMPAH
examples/screenshots/webgpu_lightprobes_sponza.jpg


TEMPAT SAMPAH
examples/screenshots/webgpu_ocean.jpg


TEMPAT SAMPAH
examples/screenshots/webgpu_sky.jpg


+ 18 - 5
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 );
 
 			}

+ 18 - 3
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 );
 
 			}

+ 4 - 0
src/nodes/lighting/ShadowNode.js

@@ -850,6 +850,10 @@ class ShadowNode extends ShadowBaseNode {
 	 */
 	updateBefore( frame ) {
 
+		// do not render shadow maps during precompilation
+
+		if ( frame.renderer._isPreCompiling === true ) return;
+
 		const { shadow } = this;
 
 		let needsUpdate = shadow.needsUpdate || shadow.autoUpdate;

+ 4 - 4
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 );
 
 						}
 

+ 24 - 3
src/renderers/common/Geometries.js

@@ -186,8 +186,9 @@ class Geometries extends DataMap {
 
 			this.info.memory.geometries --;
 
+			// index
+
 			const index = geometry.index;
-			const geometryAttributes = renderObject.getAttributes();
 
 			if ( index !== null ) {
 
@@ -195,12 +196,16 @@ class Geometries extends DataMap {
 
 			}
 
-			for ( const geometryAttribute of geometryAttributes ) {
+			// geometry attributes
+
+			for ( const attribute of Object.values( geometry.attributes ) ) {
 
-				this.attributes.delete( geometryAttribute );
+				this.attributes.delete( attribute );
 
 			}
 
+			// wireframe attributes
+
 			const wireframeAttribute = this.wireframes.get( geometry );
 
 			if ( wireframeAttribute !== undefined ) {
@@ -209,6 +214,22 @@ class Geometries extends DataMap {
 
 			}
 
+			// node attributes (TODO: Remove this bit once we support BufferAttribute.dispose())
+
+			const currentAttributes = new Set( Object.values( renderObject.geometry.attributes ) );
+
+			for ( const attribute of renderObject.getAttributes() ) {
+
+				if ( currentAttributes.has( attribute ) === false ) {
+
+					this.attributes.delete( attribute );
+
+				}
+
+			}
+
+			//
+
 			geometry.removeEventListener( 'dispose', onDispose );
 
 			this._geometryDisposeListeners.delete( geometry );

+ 8 - 0
src/renderers/common/RenderContext.js

@@ -230,6 +230,14 @@ class RenderContext {
 		 */
 		this.camera = null;
 
+		/**
+		 * Whether a fullscreen pass is rendered or not.
+		 *
+		 * @type {boolean}
+		 * @default false
+		 */
+		this.fullscreenPass = false;
+
 		/**
 		 * This flag can be used for type testing.
 		 *

+ 5 - 10
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();
-
-		}
-
 	}
 
 	/**

+ 26 - 13
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.
@@ -661,6 +661,16 @@ class Renderer {
 		 */
 		this._compilationPromises = null;
 
+		/**
+		 * Whether the renderer is currently precompiling a render object in
+		 * `compileAsync()`.
+		 *
+		 * @private
+		 * @type {boolean}
+		 * @default false
+		 */
+		this._isPreCompiling = false;
+
 		/**
 		 * When an override material is in use, this property points to the current
 		 * source material during the rendering of a render object.
@@ -951,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 );
 
 		}
 
@@ -1048,10 +1057,12 @@ class Renderer {
 			// Use async node building to yield to main thread
 			await this._nodes.getForRenderAsync( renderObject );
 
+			this._isPreCompiling = true; // note: no awaits are allowed when this flag is true otherwise the state leaks outside of this method
 			this._nodes.updateBefore( renderObject );
 			this._geometries.updateForRender( renderObject );
 			this._nodes.updateForRender( renderObject );
 			this._bindings.updateForRender( renderObject );
+			this._isPreCompiling = false;
 
 			// Wait for pipeline creation
 			const pipelinePromises = [];
@@ -1062,7 +1073,9 @@ class Renderer {
 
 			}
 
+			this._isPreCompiling = true;
 			this._nodes.updateAfter( renderObject );
+			this._isPreCompiling = false;
 
 			// Yield between objects to allow animation frames
 			await yieldToMain();
@@ -1661,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 );
 
 		}
 
@@ -1683,7 +1695,7 @@ class Renderer {
 
 		if ( this.sortObjects === true ) {
 
-			renderList.sort( this._opaqueSort, this._transparentSort, camera.reversedDepth );
+			renderList.sort( this._opaqueSort, this._transparentSort );
 
 		}
 
@@ -1719,6 +1731,7 @@ class Renderer {
 		renderContext.activeCubeFace = activeCubeFace;
 		renderContext.activeMipmapLevel = activeMipmapLevel;
 		renderContext.occlusionQueryCount = renderList.occlusionQueryCount;
+		renderContext.fullscreenPass = scene.isQuadMesh === true;
 
 		//
 
@@ -2489,8 +2502,8 @@ class Renderer {
 	 * The current number of samples used for multi-sample anti-aliasing (MSAA).
 	 *
 	 * When rendering to a custom render target, the number of samples of that render target is used.
-	 * If the renderer needs an internal framebuffer target for tone mapping or color space conversion,
-	 * the number of samples is set to 0.
+	 * The number of samples is set to 0 when the renderer needs an internal framebuffer target for
+	 * tone mapping or color space conversion, or when rendering a fullscreen quad to screen.
 	 *
 	 * @type {number}
 	 */
@@ -2502,7 +2515,7 @@ class Renderer {
 
 			samples = this._renderTarget.samples;
 
-		} else if ( this.needsFrameBufferTarget ) {
+		} else if ( this.needsFrameBufferTarget || this._currentRenderContext?.fullscreenPass === true ) {
 
 			samples = 0;
 

+ 6 - 10
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() {

+ 20 - 2
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;
+
+			}
 
 		}
 

+ 14 - 8
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 );
 
 				} );
 

粤ICP备19079148号