Просмотр исходного кода

WebGPURenderer: Introduce RenderTarget3D and RenderTargetArray (#30155)

* WebGPURenderer: Introduce RenderTarget3D and RenderTargetArray

* Fix TextureHelperGPU

* revert WebGL3DRenderTarget.js

* cleanup

* simplify example

* cleanup

* revert screenshot

* improve example

* update example

* refactor renderContext to support renderTarget not rendered yet and fix clear in webgpubackend

* ignore example

* Fix flip UV of texture3D FBO in WebGL

* update screenshot

* use build in example

* cleanup example

* feedbacks clear/load

* small cleanup syntax

* Improved example

* rename example

* update puppeeteer
Renaud Rohlinger 1 год назад
Родитель
Сommit
05dbc5d9f2

+ 1 - 0
examples/files.json

@@ -408,6 +408,7 @@
 		"webgpu_procedural_texture",
 		"webgpu_reflection",
 		"webgpu_refraction",
+		"webgpu_rendertarget_2d-array_3d",
 		"webgpu_rtt",
 		"webgpu_sandbox",
 		"webgpu_shadertoy",

+ 15 - 5
examples/jsm/helpers/TextureHelperGPU.js

@@ -1,11 +1,13 @@
 import {
+	NodeMaterial,
 	BoxGeometry,
 	BufferAttribute,
 	Mesh,
 	PlaneGeometry,
+	DoubleSide,
 	Vector3,
 } from 'three';
-import { NodeMaterial, texture as textureNode, cubeTexture, texture3D, float, vec4 } from 'three/tsl';
+import { texture as textureNode, cubeTexture, texture3D, float, vec4, attribute } from 'three/tsl';
 import { mergeGeometries } from '../utils/BufferGeometryUtils.js';
 
 class TextureHelper extends Mesh {
@@ -13,17 +15,25 @@ class TextureHelper extends Mesh {
 	constructor( texture, width = 1, height = 1, depth = 1 ) {
 
 		const material = new NodeMaterial();
+		material.side = DoubleSide;
+		material.transparent = true;
 		material.name = 'TextureHelper';
 
 		let colorNode;
 
+		const uvw = attribute( 'uvw' );
+
 		if ( texture.isCubeTexture ) {
 
-			colorNode = cubeTexture( texture );
+			colorNode = cubeTexture( texture ).sample( uvw );
 
 		} else if ( texture.isData3DTexture || texture.isCompressed3DTexture ) {
 
-			colorNode = texture3D( texture );
+			colorNode = texture3D( texture ).sample( uvw );
+
+		} else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) {
+
+			colorNode = textureNode( texture ).sample( uvw.xy ).depth( uvw.z );
 
 		} else {
 
@@ -122,7 +132,7 @@ function createCubeGeometry( width, height, depth ) {
 	}
 
 	geometry.deleteAttribute( 'uv' );
-	geometry.setAttribute( 'uv', uvw );
+	geometry.setAttribute( 'uvw', uvw );
 
 	return geometry;
 
@@ -162,7 +172,7 @@ function createSliceGeometry( texture, width, height, depth ) {
 		}
 
 		geometry.deleteAttribute( 'uv' );
-		geometry.setAttribute( 'uv', uvw );
+		geometry.setAttribute( 'uvw', uvw );
 
 		geometries.push( geometry );
 

BIN
examples/screenshots/webgpu_rendertarget_2d-array_3d.jpg


BIN
examples/screenshots/webgpu_textures_2d-array_compressed.jpg


+ 1 - 0
examples/tags.json

@@ -144,6 +144,7 @@
 	"webgpu_postprocessing_ssaa": [ "msaa", "multisampled" ],
 	"webgpu_refraction": [ "water" ],
 	"webgpu_rtt": [ "renderTarget", "texture" ],
+	"webgpu_rendertarget_2d-array_3d": [ "renderTarget", "2d-array", "3d" ],
 	"webgpu_sky": [ "sun" ],
 	"webgpu_tonemapping": [ "gltf" ],
 	"webgpu_tsl_compute_attractors_particles": [ "gpgpu" ],

+ 339 - 0
examples/webgpu_rendertarget_2d-array_3d.html

@@ -0,0 +1,339 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - RenderTargetArray and RenderTarget3D</title>
+		<meta charset="UTF-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+		<style>
+			.viewport-label {
+				position: absolute;
+				color: white;
+				background-color: rgba(0, 0, 0, 0.7);
+				padding: 5px 10px;
+				border-radius: 4px;
+				font-family: monospace;
+				pointer-events: none;
+				z-index: 1000;
+				user-select: none;
+			}
+		</style>
+	</head>
+
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - WebGPU - RenderTargetArray and RenderTarget3D<br />
+		</div>
+		<div class="viewport-label" style="bottom: 2%; left: 2%;">DataArrayTexture</div>
+		<div class="viewport-label" style="bottom: 2%; left: 52%;">Data3DTexture</div>
+		<div class="viewport-label" style="bottom: 52%; left: 52%;">RenderTarget3D</div>
+		<div class="viewport-label" style="bottom: 52%; left: 2%;">RenderTargetArray</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.webgpu.js",
+					"three/webgpu": "../build/three.webgpu.js",
+					"three/tsl": "../build/three.tsl.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import { vec2, uniform, screenUV, color, texture, diffuseColor, attribute, vec3, vec4 } from 'three/tsl';
+			import Stats from 'three/addons/libs/stats.module.js';
+			import { TextureHelper } from 'three/addons/helpers/TextureHelperGPU.js';
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { unzipSync } from 'three/addons/libs/fflate.module.js';
+
+			let renderer, stats;
+			let views = [];
+
+			class View {
+
+				constructor( left, top, width, height ) {
+
+					this.left = left;
+					this.top = top;
+					this.width = width;
+					this.height = height;
+
+					const aspect = ( window.innerWidth * width ) / ( window.innerHeight * height );
+
+					// Set up perspective camera
+					this.camera = new THREE.PerspectiveCamera( 50, aspect, 0.1, 100 );
+					this.camera.position.set( - 7, 0, 10 );
+					this.camera.lookAt( 0, 0, 0 );
+					this.camera.updateProjectionMatrix();
+
+					this.scene = new THREE.Scene();
+
+
+					const normalizedUV = screenUV.mul( vec2( 1, - 1 ) ).add( vec2( 0, 1 ) ); // Flip Y and offset
+
+					// Calculate viewport center in normalized coordinates
+					const viewportCenter = vec2(
+						this.left + this.width * 0.5,
+						this.top + this.height * 0.5 // Invert Y coordinate for proper alignment
+					);
+
+					const distanceEffect = normalizedUV.distance( viewportCenter ).smoothstep( 0, 0.2 );
+
+					const backgroundEffect = color( this.top > 0 ? 0x212121 : 0x616161 ).sub( distanceEffect.pow( 0.3 ).mul( 0.1 ) );
+
+					this.scene.backgroundNode = backgroundEffect;
+
+				}
+
+				// Method to handle viewport resize
+				updateSize( left, top, width, height ) {
+
+					this.left = left;
+					this.top = top;
+					this.width = width;
+					this.height = height;
+
+					const aspect = ( window.innerWidth * width ) / ( window.innerHeight * height );
+					this.camera.aspect = aspect;
+					this.camera.updateProjectionMatrix();
+
+				}
+
+			}
+
+			async function init() {
+
+				const container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				renderer = new THREE.WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.autoClear = false;
+				renderer.setAnimationLoop( animate );
+				container.appendChild( renderer.domElement );
+
+				await renderer.init();
+
+				// Create views after renderer initialization
+				views = [
+					new View( 0.0, 0.0, 0.5, 0.5 ),
+					new View( 0.5, 0.0, 0.5, 0.5 ),
+					new View( 0.0, 0.5, 0.5, 0.5 ),
+					new View( 0.5, 0.5, 0.5, 0.5 )
+				];
+
+				// Add OrbitControls after views and renderer are created
+				views.forEach( view => {
+
+					view.controls = new OrbitControls( view.camera, renderer.domElement );
+					view.controls.minDistance = 1;
+					view.controls.maxDistance = 20;
+					view.controls.minAzimuthAngle = - Math.PI / 3;
+					view.controls.maxAzimuthAngle = Math.PI / 3;
+					view.controls.minPolarAngle = Math.PI / 4;
+					view.controls.maxPolarAngle = Math.PI / 1.25;
+					view.controls.enableDamping = true;
+
+				} );
+
+				const size = {
+					width: 256,
+					height: 256,
+					depth: 109
+				};
+
+				new THREE.FileLoader()
+					.setResponseType( 'arraybuffer' )
+					.load( 'textures/3d/head256x256x109.zip', function ( data ) {
+
+						const zip = unzipSync( new Uint8Array( data ) );
+						const array = new Uint8Array( zip[ 'head256x256x109' ].buffer );
+
+						const map3D = new THREE.Data3DTexture( array, size.width, size.height, size.depth );
+						map3D.name = 'Data3DTexture';
+						map3D.format = THREE.RedFormat;
+						map3D.minFilter = THREE.LinearFilter;
+						map3D.magFilter = THREE.LinearFilter;
+						map3D.unpackAlignment = 1;
+						map3D.needsUpdate = true;
+
+
+						const depth = size.depth / 20;
+
+						// 3D
+						const helper3D = new TextureHelper( map3D, 10, 10, depth );
+						helper3D.material.outputNode = vec4(
+							vec3( diffuseColor.r.mul( attribute( 'uvw' ).z.mul( diffuseColor.r ) ) ),
+							diffuseColor.r.mul( diffuseColor.a )
+						);
+						views[ 1 ].scene.add( helper3D );
+
+						const fbo3D = new THREE.RenderTarget3D( size.width, size.height, size.depth, {
+							depthBuffer: false,
+						} );
+						fbo3D.texture.name = 'RenderTarget3D';
+
+
+						const fbo3DHelper = new TextureHelper( fbo3D.texture, 10, 10, depth );
+						fbo3DHelper.material.outputNode = vec4(
+							vec3( diffuseColor.r ),
+							diffuseColor.r
+						);
+						views[ 3 ].scene.add( fbo3DHelper );
+
+
+
+
+						// 2D Array
+
+						const mapArray = new THREE.DataArrayTexture( array, size.width, size.height, size.depth );
+						mapArray.name = 'DataArrayTexture';
+						mapArray.format = THREE.RedFormat;
+						mapArray.minFilter = THREE.LinearFilter;
+						mapArray.magFilter = THREE.LinearFilter;
+						mapArray.unpackAlignment = 1;
+						mapArray.needsUpdate = true;
+
+						const helperArray = new TextureHelper( mapArray, 10, 10, depth );
+						helperArray.material.outputNode = vec4(
+							vec3( diffuseColor.r.mul( attribute( 'uvw' ).z.div( size.depth ).mul( diffuseColor.r ) ) ),
+							diffuseColor.r.mul( diffuseColor.a )
+						);
+						views[ 0 ].scene.add( helperArray );
+
+						// Setup render targets
+						const materialQuad = new THREE.NodeMaterial();
+						const uZCoord = uniform( 0 );
+						materialQuad.depthTest = false;
+						materialQuad.outputNode = vec4( texture( mapArray ).depth( uZCoord ).rgb, 1 );
+
+						const fboArray = new THREE.RenderTargetArray( size.width, size.height, size.depth, {
+							depthBuffer: false,
+						} );
+						fboArray.texture.name = 'RenderTargetArray';
+
+						const fboArrayHelper = new TextureHelper( fboArray.texture, 10, 10, depth );
+						fboArrayHelper.material.outputNode = vec4(
+							vec3( diffuseColor.r ),
+							diffuseColor.r
+						);
+						views[ 2 ].scene.add( fboArrayHelper );
+
+
+						const quadMesh = new THREE.QuadMesh( materialQuad );
+
+
+
+						// In WebGPU we need to clear all the layers of the 3D render target before rendering to it (WebGPU limitation?)
+						if ( renderer.backend.isWebGPUBackend ) {
+
+							const materialClear = new THREE.NodeMaterial();
+							materialClear.outputNode = vec4( 0 );
+							const clearQuadMesh = new THREE.QuadMesh( materialClear );
+							for ( let i = 0; i < size.depth; i ++ ) {
+			
+								renderer.setRenderTarget( fbo3D, i );
+								clearQuadMesh.render( renderer );
+
+							}
+
+						}
+
+						let j = 0;
+
+						const loop = () => {
+
+							if ( j === size.depth ) {
+
+								clearInterval( interval );
+								return;
+
+							}
+
+							// Disable viewport and scissor for FBO rendering
+							renderer.setViewport( 0, 0, size.width, size.height );
+							renderer.setScissor( 0, 0, size.width, size.height );
+							renderer.setScissorTest( false );
+
+							uZCoord.value = j;
+
+							renderer.setRenderTarget( fboArray, j );
+							renderer.clear();
+							quadMesh.render( renderer );
+
+							renderer.setRenderTarget( fbo3D, j );
+							renderer.clear();
+							quadMesh.render( renderer );
+
+							renderer.setRenderTarget( null );
+
+							j = ( j + 1 ) % size.depth;
+
+						};
+
+						const interval = setInterval( loop, 50 );
+
+						loop();
+
+					} );
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				const width = window.innerWidth;
+				const height = window.innerHeight;
+
+				renderer.setSize( width, height );
+
+				views.forEach( view => {
+
+					view.updateSize(
+						view.left,
+						view.top,
+						view.width,
+						view.height
+					);
+
+				} );
+
+			}
+
+			function animate() {
+
+				views.forEach( view => {
+
+					view.controls.update();
+
+					const left = Math.floor( view.left * window.innerWidth );
+					const bottom = Math.floor( ( 1 - view.top - view.height ) * window.innerHeight );
+					const width = Math.floor( view.width * window.innerWidth );
+					const height = Math.floor( view.height * window.innerHeight );
+
+					renderer.setViewport( left, bottom, width, height );
+					renderer.setScissor( left, bottom, width, height );
+					renderer.setScissorTest( true );
+
+					renderer.clear();
+					renderer.render( view.scene, view.camera );
+
+				} );
+
+				stats.update();
+
+			}
+
+			init();
+
+		</script>
+	</body>
+</html>

+ 2 - 0
src/Three.Core.js

@@ -85,6 +85,8 @@ export { AnimationMixer } from './animation/AnimationMixer.js';
 export { AnimationClip } from './animation/AnimationClip.js';
 export { AnimationAction } from './animation/AnimationAction.js';
 export { RenderTarget } from './core/RenderTarget.js';
+export { RenderTarget3D } from './core/RenderTarget3D.js';
+export { RenderTargetArray } from './core/RenderTargetArray.js';
 export { Uniform } from './core/Uniform.js';
 export { UniformsGroup } from './core/UniformsGroup.js';
 export { InstancedBufferGeometry } from './core/InstancedBufferGeometry.js';

+ 22 - 0
src/core/RenderTarget3D.js

@@ -0,0 +1,22 @@
+import { RenderTarget } from './RenderTarget.js';
+import { Data3DTexture } from '../textures/Data3DTexture.js';
+
+class RenderTarget3D extends RenderTarget {
+
+	constructor( width = 1, height = 1, depth = 1, options = {} ) {
+
+		super( width, height, options );
+
+		this.isRenderTarget3D = true;
+
+		this.depth = depth;
+
+		this.texture = new Data3DTexture( null, width, height, depth );
+
+		this.texture.isRenderTargetTexture = true;
+
+	}
+
+}
+
+export { RenderTarget3D };

+ 22 - 0
src/core/RenderTargetArray.js

@@ -0,0 +1,22 @@
+import { RenderTarget } from './RenderTarget.js';
+import { DataArrayTexture } from '../textures/DataArrayTexture.js';
+
+class RenderTargetArray extends RenderTarget {
+
+	constructor( width = 1, height = 1, depth = 1, options = {} ) {
+
+		super( width, height, options );
+
+		this.isRenderTargetArray = true;
+
+		this.depth = depth;
+
+		this.texture = new DataArrayTexture( null, width, height, depth );
+
+		this.texture.isRenderTargetTexture = true;
+
+	}
+
+}
+
+export { RenderTargetArray };

+ 18 - 1
src/nodes/accessors/Texture3DNode.js

@@ -1,5 +1,6 @@
 import TextureNode from './TextureNode.js';
-import { nodeProxy, vec3, Fn, If } from '../tsl/TSLBase.js';
+import { nodeProxy, vec3, Fn, If, int } from '../tsl/TSLBase.js';
+import { textureSize } from './TextureSizeNode.js';
 
 /** @module Texture3DNode **/
 
@@ -125,6 +126,22 @@ class Texture3DNode extends TextureNode {
 	 */
 	setupUV( builder, uvNode ) {
 
+		const texture = this.value;
+
+		if ( builder.isFlipY() && ( texture.isRenderTargetTexture === true || texture.isFramebufferTexture === true ) ) {
+
+			if ( this.sampler ) {
+
+				uvNode = uvNode.flipY();
+
+			} else {
+
+				uvNode = uvNode.setY( int( textureSize( this, this.levelNode ).y ).sub( uvNode.y ).sub( 1 ) );
+
+			}
+
+		}
+
 		return uvNode;
 
 	}

+ 11 - 2
src/renderers/common/RenderContexts.js

@@ -9,9 +9,18 @@ class RenderContexts {
 
 	}
 
-	get( scene, camera, renderTarget = null ) {
+	get( scene = null, camera = null, renderTarget = null ) {
+
+		const chainKey = [];
+		if ( scene !== null ) chainKey.push( scene );
+		if ( camera !== null ) chainKey.push( camera );
+
+		if ( chainKey.length === 0 ) {
+
+			chainKey.push( { id: 'default' } );
+
+		}
 
-		const chainKey = [ scene, camera ];
 
 		let attachmentState;
 

+ 12 - 3
src/renderers/common/Renderer.js

@@ -1836,17 +1836,26 @@ class Renderer {
 
 		const renderTarget = this._renderTarget || this._getFrameBufferTarget();
 
-		let renderTargetData = null;
+		let renderContext = null;
 
 		if ( renderTarget !== null ) {
 
 			this._textures.updateRenderTarget( renderTarget );
 
-			renderTargetData = this._textures.get( renderTarget );
+			const renderTargetData = this._textures.get( renderTarget );
+
+			renderContext = this._renderContexts.get( null, null, renderTarget );
+			renderContext.textures = renderTargetData.textures;
+			renderContext.depthTexture = renderTargetData.depthTexture;
+			renderContext.width = renderTargetData.width;
+			renderContext.height = renderTargetData.height;
+			renderContext.renderTarget = renderTarget;
+			renderContext.depth = renderTarget.depthBuffer;
+			renderContext.stencil = renderTarget.stencilBuffer;
 
 		}
 
-		this.backend.clear( color, depth, stencil, renderTargetData );
+		this.backend.clear( color, depth, stencil, renderContext );
 
 		if ( renderTarget !== null && this._renderTarget === null ) {
 

+ 15 - 1
src/renderers/webgl-fallback/WebGLBackend.js

@@ -1337,6 +1337,8 @@ class WebGLBackend extends Backend {
 			const { samples, depthBuffer, stencilBuffer } = renderTarget;
 
 			const isCube = renderTarget.isWebGLCubeRenderTarget === true;
+			const isRenderTarget3D = renderTarget.isRenderTarget3D === true;
+			const isRenderTargetArray = renderTarget.isRenderTargetArray === true;
 
 			let msaaFb = renderTargetContextData.msaaFrameBuffer;
 			let depthRenderbuffer = renderTargetContextData.depthRenderbuffer;
@@ -1390,7 +1392,19 @@ class WebGLBackend extends Backend {
 
 						const attachment = gl.COLOR_ATTACHMENT0 + i;
 
-						gl.framebufferTexture2D( gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, 0 );
+						if ( isRenderTarget3D || isRenderTargetArray ) {
+
+							const layer = this.renderer._activeCubeFace;
+
+							gl.framebufferTextureLayer( gl.FRAMEBUFFER, attachment, textureData.textureGPU, 0, layer );
+
+						} else {
+
+							gl.framebufferTexture2D( gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, 0 );
+
+						}
+
+
 
 					}
 

+ 44 - 39
src/renderers/webgpu/WebGPUBackend.js

@@ -206,7 +206,7 @@ class WebGPUBackend extends Backend {
 
 	}
 
-	_getRenderPassDescriptor( renderContext ) {
+	_getRenderPassDescriptor( renderContext, colorAttachmentsConfig = {} ) {
 
 		const renderTarget = renderContext.renderTarget;
 		const renderTargetData = this.get( renderTarget );
@@ -216,8 +216,11 @@ class WebGPUBackend extends Backend {
 		if ( descriptors === undefined ||
 			renderTargetData.width !== renderTarget.width ||
 			renderTargetData.height !== renderTarget.height ||
+			renderTargetData.dimensions !== renderTarget.dimensions ||
 			renderTargetData.activeMipmapLevel !== renderTarget.activeMipmapLevel ||
-			renderTargetData.samples !== renderTarget.samples
+			renderTargetData.activeCubeFace !== renderContext.activeCubeFace ||
+			renderTargetData.samples !== renderTarget.samples ||
+			renderTargetData.loadOp !== colorAttachmentsConfig.loadOp
 		) {
 
 			descriptors = {};
@@ -247,16 +250,37 @@ class WebGPUBackend extends Backend {
 			const textures = renderContext.textures;
 			const colorAttachments = [];
 
+			let sliceIndex;
+
 			for ( let i = 0; i < textures.length; i ++ ) {
 
 				const textureData = this.get( textures[ i ] );
 
-				const textureView = textureData.texture.createView( {
+				const viewDescriptor = {
+					label: `colorAttachment_${ i }`,
 					baseMipLevel: renderContext.activeMipmapLevel,
 					mipLevelCount: 1,
 					baseArrayLayer: renderContext.activeCubeFace,
+					arrayLayerCount: 1,
 					dimension: GPUTextureViewDimension.TwoD
-				} );
+				};
+
+				if ( renderTarget.isRenderTarget3D ) {
+
+					sliceIndex = renderContext.activeCubeFace;
+
+					viewDescriptor.baseArrayLayer = 0;
+					viewDescriptor.dimension = GPUTextureViewDimension.ThreeD;
+					viewDescriptor.depthOrArrayLayers = textures[ i ].image.depth;
+
+				} else if ( renderTarget.isRenderTargetArray ) {
+
+					viewDescriptor.dimension = GPUTextureViewDimension.TwoDArray;
+					viewDescriptor.depthOrArrayLayers = textures[ i ].image.depth;
+
+				}
+
+				const textureView = textureData.texture.createView( viewDescriptor );
 
 				let view, resolveTarget;
 
@@ -274,9 +298,11 @@ class WebGPUBackend extends Backend {
 
 				colorAttachments.push( {
 					view,
+					depthSlice: sliceIndex,
 					resolveTarget,
 					loadOp: GPULoadOp.Load,
-					storeOp: GPUStoreOp.Store
+					storeOp: GPUStoreOp.Store,
+					...colorAttachmentsConfig
 				} );
 
 			}
@@ -302,7 +328,11 @@ class WebGPUBackend extends Backend {
 			renderTargetData.width = renderTarget.width;
 			renderTargetData.height = renderTarget.height;
 			renderTargetData.samples = renderTarget.samples;
-			renderTargetData.activeMipmapLevel = renderTarget.activeMipmapLevel;
+			renderTargetData.activeMipmapLevel = renderContext.activeMipmapLevel;
+			renderTargetData.activeCubeFace = renderContext.activeCubeFace;
+			renderTargetData.dimensions = renderTarget.dimensions;
+			renderTargetData.depthSlice = sliceIndex;
+			renderTargetData.loadOp = colorAttachments[ 0 ].loadOp;
 
 		}
 
@@ -350,7 +380,7 @@ class WebGPUBackend extends Backend {
 
 		} else {
 
-			descriptor = this._getRenderPassDescriptor( renderContext );
+			descriptor = this._getRenderPassDescriptor( renderContext, { loadOp: GPULoadOp.Load } );
 
 		}
 
@@ -612,7 +642,7 @@ class WebGPUBackend extends Backend {
 
 	}
 
-	clear( color, depth, stencil, renderTargetData = null ) {
+	clear( color, depth, stencil, renderTargetContext = null ) {
 
 		const device = this.device;
 		const renderer = this.renderer;
@@ -645,7 +675,7 @@ class WebGPUBackend extends Backend {
 
 		}
 
-		if ( renderTargetData === null ) {
+		if ( renderTargetContext === null ) {
 
 			supportsDepth = renderer.depth;
 			supportsStencil = renderer.stencil;
@@ -672,45 +702,20 @@ class WebGPUBackend extends Backend {
 
 		} else {
 
-			supportsDepth = renderTargetData.depth;
-			supportsStencil = renderTargetData.stencil;
+			supportsDepth = renderTargetContext.depth;
+			supportsStencil = renderTargetContext.stencil;
 
 			if ( color ) {
 
-				for ( const texture of renderTargetData.textures ) {
+				const descriptor = this._getRenderPassDescriptor( renderTargetContext, { loadOp: GPULoadOp.Clear } );
 
-					const textureData = this.get( texture );
-					const textureView = textureData.texture.createView();
-
-					let view, resolveTarget;
-
-					if ( textureData.msaaTexture !== undefined ) {
-
-						view = textureData.msaaTexture.createView();
-						resolveTarget = textureView;
-
-					} else {
-
-						view = textureView;
-						resolveTarget = undefined;
-
-					}
-
-					colorAttachments.push( {
-						view,
-						resolveTarget,
-						clearValue,
-						loadOp: GPULoadOp.Clear,
-						storeOp: GPUStoreOp.Store
-					} );
-
-				}
+				colorAttachments = descriptor.colorAttachments;
 
 			}
 
 			if ( supportsDepth || supportsStencil ) {
 
-				const depthTextureData = this.get( renderTargetData.depthTexture );
+				const depthTextureData = this.get( renderTargetContext.depthTexture );
 
 				depthStencilAttachment = {
 					view: depthTextureData.texture.createView()

+ 50 - 6
src/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -241,7 +241,7 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 	generateWrapFunction( texture ) {
 
-		const functionName = `tsl_coord_${ wrapNames[ texture.wrapS ] }S_${ wrapNames[ texture.wrapT ] }T`;
+		const functionName = `tsl_coord_${ wrapNames[ texture.wrapS ] }S_${ wrapNames[ texture.wrapT ] }_${texture.isData3DTexture ? '3d' : '2d'}T`;
 
 		let nodeCode = wgslCodeCache[ functionName ];
 
@@ -249,7 +249,9 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 			const includes = [];
 
-			let code = `fn ${ functionName }( coord : vec2f ) -> vec2f {\n\n\treturn vec2f(\n`;
+			// For 3D textures, use vec3f; for texture arrays, keep vec2f since array index is separate
+			const coordType = texture.isData3DTexture ? 'vec3f' : 'vec2f';
+			let code = `fn ${functionName}( coord : ${coordType} ) -> ${coordType} {\n\n\treturn ${coordType}(\n`;
 
 			const addWrapSnippet = ( wrap, axis ) => {
 
@@ -287,6 +289,13 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 			addWrapSnippet( texture.wrapT, 'y' );
 
+			if ( texture.isData3DTexture ) {
+
+				code += ',\n';
+				addWrapSnippet( texture.wrapR, 'z' );
+
+			}
+
 			code += '\n\t);\n\n}\n';
 
 			wgslCodeCache[ functionName ] = nodeCode = new CodeNode( code, includes );
@@ -310,23 +319,57 @@ class WGSLNodeBuilder extends NodeBuilder {
 		if ( textureData.dimensionsSnippet[ levelSnippet ] === undefined ) {
 
 			let textureDimensionsParams;
+			let dimensionType;
 
 			const { primarySamples } = this.renderer.backend.utils.getTextureSampleData( texture );
+			const isMultisampled = primarySamples > 1;
+
+			if ( texture.isData3DTexture ) {
 
-			if ( primarySamples > 1 ) {
+				dimensionType = 'vec3<u32>';
+
+			} else {
+
+				// Regular 2D textures, depth textures, etc.
+				dimensionType = 'vec2<u32>';
+
+			}
+
+			// Build parameters string based on texture type and multisampling
+			if ( isMultisampled || texture.isVideoTexture || texture.isStorageTexture ) {
 
 				textureDimensionsParams = textureProperty;
 
 			} else {
 
-				textureDimensionsParams = `${ textureProperty }, u32( ${ levelSnippet } )`;
+				textureDimensionsParams = `${textureProperty}${levelSnippet ? `, u32( ${ levelSnippet } )` : ''}`;
 
 			}
 
-			textureDimensionNode = new VarNode( new ExpressionNode( `textureDimensions( ${ textureDimensionsParams } )`, 'uvec2' ) );
+			textureDimensionNode = new VarNode( new ExpressionNode( `textureDimensions( ${ textureDimensionsParams } )`, dimensionType ) );
 
 			textureData.dimensionsSnippet[ levelSnippet ] = textureDimensionNode;
 
+			if ( texture.isDataArrayTexture || texture.isData3DTexture ) {
+
+				textureData.arrayLayerCount = new VarNode(
+					new ExpressionNode(
+						`textureNumLayers(${textureProperty})`,
+						'u32'
+					)
+				);
+
+			}
+
+			// For cube textures, we know it's always 6 faces
+			if ( texture.isTextureCube ) {
+
+				textureData.cubeFaceCount = new VarNode(
+					new ExpressionNode( '6u', 'u32' )
+				);
+
+			}
+
 		}
 
 		return textureDimensionNode.build( this );
@@ -349,7 +392,8 @@ class WGSLNodeBuilder extends NodeBuilder {
 		const wrapFunction = this.generateWrapFunction( texture );
 		const textureDimension = this.generateTextureDimension( texture, textureProperty, levelSnippet );
 
-		const coordSnippet = `vec2u( ${ wrapFunction }( ${ uvSnippet } ) * vec2f( ${ textureDimension } ) )`;
+		const vecType = texture.isData3DTexture ? 'vec3' : 'vec2';
+		const coordSnippet = `${vecType}<u32>(${wrapFunction}(${uvSnippet}) * ${vecType}<f32>(${textureDimension}))`;
 
 		return this.generateTextureLoad( texture, textureProperty, coordSnippet, depthSnippet, levelSnippet );
 

+ 2 - 2
src/textures/Data3DTexture.js

@@ -6,9 +6,9 @@ class Data3DTexture extends Texture {
 	constructor( data = null, width = 1, height = 1, depth = 1 ) {
 
 		// We're going to add .setXXX() methods for setting properties later.
-		// Users can still set in DataTexture3D directly.
+		// Users can still set in Data3DTexture directly.
 		//
-		//	const texture = new THREE.DataTexture3D( data, width, height, depth );
+		//	const texture = new THREE.Data3DTexture( data, width, height, depth );
 		// 	texture.anisotropy = 16;
 		//
 		// See #14839

+ 2 - 0
test/e2e/puppeteer.js

@@ -156,6 +156,8 @@ const exceptionList = [
 	'webgpu_tsl_vfx_linkedparticles',
 	'webgpu_tsl_vfx_tornado',
 	'webgpu_textures_anisotropy',
+	'webgpu_textures_2d-array_compressed',
+	'webgpu_rendertarget_2d-array_3d',
 	'webgpu_materials_envmaps_bpcem',
 	'webgpu_postprocessing_ssr',
 	'webgpu_postprocessing_sobel',

粤ICP备19079148号