浏览代码

WebGPURenderer: Texture2D Array (#27190)

* WebGPURenderer: Texture2D Array

* WebGLNodeBuilder: Fix function names

* WebGLBackend: Add Texture2D Array

* Rename: getSamplerLevelNode() -> getTextureLevelNode()

* Simplifies the name of IBL context functions

* cleanup
sunag 1 年之前
父节点
当前提交
a091faa512

+ 1 - 0
examples/files.json

@@ -351,6 +351,7 @@
 		"webgpu_skinning_instancing",
 		"webgpu_skinning_instancing",
 		"webgpu_skinning_points",
 		"webgpu_skinning_points",
 		"webgpu_sprites",
 		"webgpu_sprites",
+		"webgpu_textures_2d-array",
 		"webgpu_tsl_editor",
 		"webgpu_tsl_editor",
 		"webgpu_tsl_transpiler",
 		"webgpu_tsl_transpiler",
 		"webgpu_video_panorama"
 		"webgpu_video_panorama"

+ 3 - 76
examples/jsm/nodes/accessors/CubeTextureNode.js

@@ -1,9 +1,6 @@
 import TextureNode from './TextureNode.js';
 import TextureNode from './TextureNode.js';
-import UniformNode from '../core/UniformNode.js';
 import { reflectVector } from './ReflectVectorNode.js';
 import { reflectVector } from './ReflectVectorNode.js';
 import { addNodeClass } from '../core/Node.js';
 import { addNodeClass } from '../core/Node.js';
-import { colorSpaceToLinear } from '../display/ColorSpaceNode.js';
-import { expression } from '../code/ExpressionNode.js';
 import { addNodeElement, nodeProxy, vec3 } from '../shadernode/ShaderNode.js';
 import { addNodeElement, nodeProxy, vec3 } from '../shadernode/ShaderNode.js';
 
 
 class CubeTextureNode extends TextureNode {
 class CubeTextureNode extends TextureNode {
@@ -30,80 +27,10 @@ class CubeTextureNode extends TextureNode {
 
 
 	setUpdateMatrix( /*updateMatrix*/ ) { } // Ignore .updateMatrix for CubeTextureNode
 	setUpdateMatrix( /*updateMatrix*/ ) { } // Ignore .updateMatrix for CubeTextureNode
 
 
-	generate( builder, output ) {
+	generateUV( builder, uvNode ) {
 
 
-		const { uvNode, levelNode } = builder.getNodeProperties( this );
-
-		const texture = this.value;
-
-		if ( ! texture || texture.isCubeTexture !== true ) {
-
-			throw new Error( 'CubeTextureNode: Need a three.js cube texture.' );
-
-		}
-
-		const textureProperty = UniformNode.prototype.generate.call( this, builder, 'cubeTexture' );
-
-		if ( output === 'sampler' ) {
-
-			return textureProperty + '_sampler';
-
-		} else if ( builder.isReference( output ) ) {
-
-			return textureProperty;
-
-		} else {
-
-			const nodeData = builder.getDataFromNode( this );
-
-			let propertyName = nodeData.propertyName;
-
-			if ( propertyName === undefined ) {
-
-				const cubeUV = vec3( uvNode.x.negate(), uvNode.yz );
-				const uvSnippet = cubeUV.build( builder, 'vec3' );
-
-				const nodeVar = builder.getVarFromNode( this );
-
-				propertyName = builder.getPropertyName( nodeVar );
-
-				let snippet = null;
-
-				if ( levelNode && levelNode.isNode === true ) {
-
-					const levelSnippet = levelNode.build( builder, 'float' );
-
-					snippet = builder.getTextureLevel( this, textureProperty, uvSnippet, levelSnippet );
-
-				} else {
-
-					snippet = builder.getTexture( this, textureProperty, uvSnippet );
-
-				}
-
-				builder.addLineFlowCode( `${propertyName} = ${snippet}` );
-
-				if ( builder.context.tempWrite !== false ) {
-
-					nodeData.snippet = snippet;
-					nodeData.propertyName = propertyName;
-
-				}
-
-			}
-
-			let snippet = propertyName;
-			const nodeType = this.getNodeType( builder );
-
-			if ( builder.needsColorSpaceToLinear( this.value ) ) {
-
-				snippet = colorSpaceToLinear( expression( snippet, nodeType ), this.value.colorSpace ).setup( builder ).build( builder, nodeType );
-
-			}
-
-			return builder.format( snippet, nodeType, output );
-
-		}
+		const cubeUV = vec3( uvNode.x.negate(), uvNode.yz );
+		return cubeUV.build( builder, 'vec3' );
 
 
 	}
 	}
 
 

+ 106 - 42
examples/jsm/nodes/accessors/TextureNode.js

@@ -2,15 +2,15 @@ import UniformNode, { uniform } from '../core/UniformNode.js';
 import { uv } from './UVNode.js';
 import { uv } from './UVNode.js';
 import { textureSize } from './TextureSizeNode.js';
 import { textureSize } from './TextureSizeNode.js';
 import { colorSpaceToLinear } from '../display/ColorSpaceNode.js';
 import { colorSpaceToLinear } from '../display/ColorSpaceNode.js';
-import { context } from '../core/ContextNode.js';
 import { expression } from '../code/ExpressionNode.js';
 import { expression } from '../code/ExpressionNode.js';
 import { addNodeClass } from '../core/Node.js';
 import { addNodeClass } from '../core/Node.js';
+import { maxMipLevel } from '../utils/MaxMipLevelNode.js';
 import { addNodeElement, nodeProxy, vec3, nodeObject } from '../shadernode/ShaderNode.js';
 import { addNodeElement, nodeProxy, vec3, nodeObject } from '../shadernode/ShaderNode.js';
 import { NodeUpdateType } from '../core/constants.js';
 import { NodeUpdateType } from '../core/constants.js';
 
 
 class TextureNode extends UniformNode {
 class TextureNode extends UniformNode {
 
 
-	constructor( value, uvNode = null, levelNode = null, compareNode = null ) {
+	constructor( value, uvNode = null, levelNode = null ) {
 
 
 		super( value );
 		super( value );
 
 
@@ -18,8 +18,10 @@ class TextureNode extends UniformNode {
 
 
 		this.uvNode = uvNode;
 		this.uvNode = uvNode;
 		this.levelNode = levelNode;
 		this.levelNode = levelNode;
-		this.compareNode = compareNode;
+		this.compareNode = null;
+		this.depthNode = null;
 
 
+		this.sampler = true;
 		this.updateMatrix = false;
 		this.updateMatrix = false;
 		this.updateType = NodeUpdateType.NONE;
 		this.updateType = NodeUpdateType.NONE;
 
 
@@ -84,9 +86,9 @@ class TextureNode extends UniformNode {
 
 
 		let uvNode = this.uvNode;
 		let uvNode = this.uvNode;
 
 
-		if ( ( uvNode === null || builder.context.forceUVContext === true ) && builder.context.getUVNode ) {
+		if ( ( uvNode === null || builder.context.forceUVContext === true ) && builder.context.getUV ) {
 
 
-			uvNode = builder.context.getUVNode( this );
+			uvNode = builder.context.getUV( this );
 
 
 		}
 		}
 
 
@@ -102,41 +104,81 @@ class TextureNode extends UniformNode {
 
 
 		let levelNode = this.levelNode;
 		let levelNode = this.levelNode;
 
 
-		if ( levelNode === null && builder.context.getSamplerLevelNode ) {
+		if ( levelNode === null && builder.context.getTextureLevel ) {
 
 
-			levelNode = builder.context.getSamplerLevelNode( this );
+			levelNode = builder.context.getTextureLevel( this );
+
+		}
+
+		if ( levelNode !== null && builder.context.getTextureLevelAlgorithm !== undefined ) {
+
+			levelNode = builder.context.getTextureLevelAlgorithm( this, levelNode );
 
 
 		}
 		}
 
 
 		//
 		//
 
 
 		properties.uvNode = uvNode;
 		properties.uvNode = uvNode;
-		properties.levelNode = levelNode ? builder.context.getMIPLevelAlgorithmNode( this, levelNode ) : null;
+		properties.levelNode = levelNode;
+		properties.compareNode = this.compareNode;
+		properties.depthNode = this.depthNode;
 
 
 	}
 	}
 
 
-	generate( builder, output ) {
+	generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, depthSnippet, compareSnippet ) {
 
 
-		const properties = builder.getNodeProperties( this );
+		const texture = this.value;
 
 
-		let { uvNode } = properties;
-		const { levelNode } = properties;
+		let snippet;
 
 
-		const compareNode = this.compareNode;
-		const texture = this.value;
+		if ( levelSnippet ) {
 
 
-		if ( ! texture || texture.isTexture !== true ) {
+			snippet = builder.generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet );
 
 
-			throw new Error( 'TextureNode: Need a three.js texture.' );
+		} else if ( compareSnippet ) {
+
+			snippet = builder.generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet );
+
+		} else if ( this.sampler === false ) {
+
+			snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, depthSnippet );
+
+		} else {
+
+			snippet = builder.generateTexture( texture, textureProperty, uvSnippet, depthSnippet );
 
 
 		}
 		}
 
 
+		return snippet;
+
+	}
+
+	generateUV( builder, uvNode ) {
+
+		const texture = this.value;
+
 		if ( builder.isFlipY() && ( texture.isFramebufferTexture === true || texture.isDepthTexture === true ) ) {
 		if ( builder.isFlipY() && ( texture.isFramebufferTexture === true || texture.isDepthTexture === true ) ) {
 
 
 			uvNode = uvNode.setY( uvNode.y.fract().oneMinus() );
 			uvNode = uvNode.setY( uvNode.y.fract().oneMinus() );
 
 
 		}
 		}
 
 
+		return uvNode.build( builder, this.sampler === true ? 'vec2' : 'uvec2' );
+
+	}
+
+	generate( builder, output ) {
+
+		const properties = builder.getNodeProperties( this );
+
+		const texture = this.value;
+
+		if ( ! texture || texture.isTexture !== true ) {
+
+			throw new Error( 'TextureNode: Need a three.js texture.' );
+
+		}
+
 		const textureProperty = super.generate( builder, 'property' );
 		const textureProperty = super.generate( builder, 'property' );
 
 
 		if ( output === 'sampler' ) {
 		if ( output === 'sampler' ) {
@@ -155,30 +197,18 @@ class TextureNode extends UniformNode {
 
 
 			if ( propertyName === undefined ) {
 			if ( propertyName === undefined ) {
 
 
-				const uvSnippet = uvNode.build( builder, 'vec2' );
-				const nodeVar = builder.getVarFromNode( this );
+				const { uvNode, levelNode, compareNode, depthNode } = properties;
 
 
-				propertyName = builder.getPropertyName( nodeVar );
-
-				let snippet = null;
-
-				if ( levelNode && levelNode.isNode === true ) {
-
-					const levelSnippet = levelNode.build( builder, 'float' );
-
-					snippet = builder.getTextureLevel( texture, textureProperty, uvSnippet, levelSnippet );
+				const uvSnippet = this.generateUV( builder, uvNode );
+				const levelSnippet = levelNode ? levelNode.build( builder, 'float' ) : null;
+				const depthSnippet = depthNode ? depthNode.build( builder, 'uint' ) : null;
+				const compareSnippet = compareNode ? compareNode.build( builder, 'float' ) : null;
 
 
-				} else if ( compareNode !== null ) {
-
-					const compareSnippet = compareNode.build( builder, 'float' );
-
-					snippet = builder.getTextureCompare( texture, textureProperty, uvSnippet, compareSnippet );
+				const nodeVar = builder.getVarFromNode( this );
 
 
-				} else {
+				propertyName = builder.getPropertyName( nodeVar );
 
 
-					snippet = builder.getTexture( texture, textureProperty, uvSnippet );
-
-				}
+				const snippet = this.generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, depthSnippet, compareSnippet );
 
 
 				builder.addLineFlowCode( `${propertyName} = ${snippet}` );
 				builder.addLineFlowCode( `${propertyName} = ${snippet}` );
 
 
@@ -194,9 +224,9 @@ class TextureNode extends UniformNode {
 			let snippet = propertyName;
 			let snippet = propertyName;
 			const nodeType = this.getNodeType( builder );
 			const nodeType = this.getNodeType( builder );
 
 
-			if ( builder.needsColorSpaceToLinear( this.value ) ) {
+			if ( builder.needsColorSpaceToLinear( texture ) ) {
 
 
-				snippet = colorSpaceToLinear( expression( snippet, nodeType ), this.value.colorSpace ).setup( builder ).build( builder, nodeType );
+				snippet = colorSpaceToLinear( expression( snippet, nodeType ), texture.colorSpace ).setup( builder ).build( builder, nodeType );
 
 
 			}
 			}
 
 
@@ -206,6 +236,22 @@ class TextureNode extends UniformNode {
 
 
 	}
 	}
 
 
+	setSampler( value ) {
+
+		this.sampler = value;
+
+		return this;
+
+	}
+
+	getSampler() {
+
+		return this.sampler;
+
+	}
+
+	// @TODO: Move to TSL
+
 	uv( uvNode ) {
 	uv( uvNode ) {
 
 
 		const textureNode = this.clone();
 		const textureNode = this.clone();
@@ -215,14 +261,21 @@ class TextureNode extends UniformNode {
 
 
 	}
 	}
 
 
+	blur( levelNode ) {
+
+		const textureNode = this.clone();
+		textureNode.levelNode = levelNode.mul( maxMipLevel( textureNode ) );
+
+		return nodeObject( textureNode );
+
+	}
+
 	level( levelNode ) {
 	level( levelNode ) {
 
 
 		const textureNode = this.clone();
 		const textureNode = this.clone();
 		textureNode.levelNode = levelNode;
 		textureNode.levelNode = levelNode;
 
 
-		return context( textureNode, {
-			getMIPLevelAlgorithmNode: ( reqTextureNode, levelNode ) => levelNode
-		} );
+		return textureNode;
 
 
 	}
 	}
 
 
@@ -241,6 +294,17 @@ class TextureNode extends UniformNode {
 
 
 	}
 	}
 
 
+	depth( depthNode ) {
+
+		const textureNode = this.clone();
+		textureNode.depthNode = nodeObject( depthNode );
+
+		return nodeObject( textureNode );
+
+	}
+
+	// --
+
 	serialize( data ) {
 	serialize( data ) {
 
 
 		super.serialize( data );
 		super.serialize( data );
@@ -271,7 +335,7 @@ class TextureNode extends UniformNode {
 
 
 	clone() {
 	clone() {
 
 
-		return new this.constructor( this.value, this.uvNode, this.levelNode, this.compareNode );
+		return new this.constructor( this.value, this.uvNode, this.levelNode );
 
 
 	}
 	}
 
 

+ 1 - 1
examples/jsm/nodes/core/AttributeNode.js

@@ -87,7 +87,7 @@ class AttributeNode extends Node {
 
 
 			console.warn( `AttributeNode: Attribute "${ attributeName }" not found.` );
 			console.warn( `AttributeNode: Attribute "${ attributeName }" not found.` );
 
 
-			return builder.getConst( nodeType );
+			return builder.generateConst( nodeType );
 
 
 		}
 		}
 
 

+ 1 - 1
examples/jsm/nodes/core/ConstNode.js

@@ -13,7 +13,7 @@ class ConstNode extends InputNode {
 
 
 	generateConst( builder ) {
 	generateConst( builder ) {
 
 
-		return builder.getConst( this.getNodeType( builder ), this.value );
+		return builder.generateConst( this.getNodeType( builder ), this.value );
 
 
 	}
 	}
 
 

+ 9 - 13
examples/jsm/nodes/core/NodeBuilder.js

@@ -19,8 +19,6 @@ import { REVISION, RenderTarget, NoColorSpace, LinearEncoding, sRGBEncoding, SRG
 import { stack } from './StackNode.js';
 import { stack } from './StackNode.js';
 import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js';
 import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js';
 
 
-import { maxMipLevel } from '../utils/MaxMipLevelNode.js';
-
 import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js';
 import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js';
 import ChainMap from '../../renderers/common/ChainMap.js';
 import ChainMap from '../../renderers/common/ChainMap.js';
 
 
@@ -99,8 +97,7 @@ class NodeBuilder {
 
 
 		this.context = {
 		this.context = {
 			keywords: new NodeKeywords(),
 			keywords: new NodeKeywords(),
-			material: this.material,
-			getMIPLevelAlgorithmNode: ( textureNode, levelNode ) => levelNode.mul( maxMipLevel( textureNode ) )
+			material: this.material
 		};
 		};
 
 
 		this.cache = new NodeCache();
 		this.cache = new NodeCache();
@@ -335,20 +332,19 @@ class NodeBuilder {
 
 
 	}
 	}
 
 
-	getTexture( /* texture, textureProperty, uvSnippet */ ) {
+	generateTexture( /* texture, textureProperty, uvSnippet */ ) {
 
 
 		console.warn( 'Abstract function.' );
 		console.warn( 'Abstract function.' );
 
 
 	}
 	}
 
 
-	getTextureLevel( /* texture, textureProperty, uvSnippet, levelSnippet */ ) {
+	generateTextureLod( /* texture, textureProperty, uvSnippet, levelSnippet */ ) {
 
 
 		console.warn( 'Abstract function.' );
 		console.warn( 'Abstract function.' );
 
 
 	}
 	}
 
 
-	// @TODO: rename to .generateConst()
-	getConst( type, value = null ) {
+	generateConst( type, value = null ) {
 
 
 		if ( value === null ) {
 		if ( value === null ) {
 
 
@@ -371,23 +367,23 @@ class NodeBuilder {
 
 
 		const componentType = this.getComponentType( type );
 		const componentType = this.getComponentType( type );
 
 
-		const getConst = value => this.getConst( componentType, value );
+		const generateConst = value => this.generateConst( componentType, value );
 
 
 		if ( typeLength === 2 ) {
 		if ( typeLength === 2 ) {
 
 
-			return `${ this.getType( type ) }( ${ getConst( value.x ) }, ${ getConst( value.y ) } )`;
+			return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) } )`;
 
 
 		} else if ( typeLength === 3 ) {
 		} else if ( typeLength === 3 ) {
 
 
-			return `${ this.getType( type ) }( ${ getConst( value.x ) }, ${ getConst( value.y ) }, ${ getConst( value.z ) } )`;
+			return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) } )`;
 
 
 		} else if ( typeLength === 4 ) {
 		} else if ( typeLength === 4 ) {
 
 
-			return `${ this.getType( type ) }( ${ getConst( value.x ) }, ${ getConst( value.y ) }, ${ getConst( value.z ) }, ${ getConst( value.w ) } )`;
+			return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) }, ${ generateConst( value.w ) } )`;
 
 
 		} else if ( typeLength > 4 && value && ( value.isMatrix3 || value.isMatrix4 ) ) {
 		} else if ( typeLength > 4 && value && ( value.isMatrix3 || value.isMatrix4 ) ) {
 
 
-			return `${ this.getType( type ) }( ${ value.elements.map( getConst ).join( ', ' ) } )`;
+			return `${ this.getType( type ) }( ${ value.elements.map( generateConst ).join( ', ' ) } )`;
 
 
 		} else if ( typeLength > 4 ) {
 		} else if ( typeLength > 4 ) {
 
 

+ 1 - 1
examples/jsm/nodes/display/BumpMapNode.js

@@ -35,7 +35,7 @@ const dHdxy_fwd = tslFn( ( { textureNode, bumpScale } ) => {
 	const uvNode = texNode.uvNode || uv();
 	const uvNode = texNode.uvNode || uv();
 
 
 	// It's used to preserve the same TextureNode instance
 	// It's used to preserve the same TextureNode instance
-	const sampleTexture = ( uv ) => textureNode.cache().context( { getUVNode: () => uv, forceUVContext: true } );
+	const sampleTexture = ( uv ) => textureNode.cache().context( { getUV: () => uv, forceUVContext: true } );
 
 
 	return vec2(
 	return vec2(
 		float( sampleTexture( uvNode.add( uvNode.dFdx() ) ) ).sub( Hll ),
 		float( sampleTexture( uvNode.add( uvNode.dFdx() ) ) ).sub( Hll ),

+ 6 - 6
examples/jsm/nodes/lighting/EnvironmentNode.js

@@ -88,7 +88,7 @@ const createRadianceContext = ( roughnessNode, normalViewNode ) => {
 	let textureUVNode = null;
 	let textureUVNode = null;
 
 
 	return {
 	return {
-		getUVNode: ( textureNode ) => {
+		getUV: ( textureNode ) => {
 
 
 			let node = null;
 			let node = null;
 
 
@@ -121,12 +121,12 @@ const createRadianceContext = ( roughnessNode, normalViewNode ) => {
 			return node;
 			return node;
 
 
 		},
 		},
-		getSamplerLevelNode: () => {
+		getTextureLevel: () => {
 
 
 			return roughnessNode;
 			return roughnessNode;
 
 
 		},
 		},
-		getMIPLevelAlgorithmNode: ( textureNode, levelNode ) => {
+		getTextureLevelAlgorithm: ( textureNode, levelNode ) => {
 
 
 			return specularMIPLevel( textureNode, levelNode );
 			return specularMIPLevel( textureNode, levelNode );
 
 
@@ -140,7 +140,7 @@ const createIrradianceContext = ( normalWorldNode ) => {
 	let textureUVNode = null;
 	let textureUVNode = null;
 
 
 	return {
 	return {
-		getUVNode: ( textureNode ) => {
+		getUV: ( textureNode ) => {
 
 
 			let node = null;
 			let node = null;
 
 
@@ -166,12 +166,12 @@ const createIrradianceContext = ( normalWorldNode ) => {
 			return node;
 			return node;
 
 
 		},
 		},
-		getSamplerLevelNode: () => {
+		getTextureLevel: () => {
 
 
 			return float( 1 );
 			return float( 1 );
 
 
 		},
 		},
-		getMIPLevelAlgorithmNode: ( textureNode, levelNode ) => {
+		getTextureLevelAlgorithm: ( textureNode, levelNode ) => {
 
 
 			return specularMIPLevel( textureNode, levelNode );
 			return specularMIPLevel( textureNode, levelNode );
 
 

+ 2 - 2
examples/jsm/renderers/common/Background.js

@@ -55,8 +55,8 @@ class Background extends DataMap {
 
 
 				this.backgroundMeshNode = context( backgroundNode, {
 				this.backgroundMeshNode = context( backgroundNode, {
 					// @TODO: Add Texture2D support using node context
 					// @TODO: Add Texture2D support using node context
-					getUVNode: () => normalWorld,
-					getSamplerLevelNode: () => backgroundBlurriness
+					getUV: () => normalWorld,
+					getTextureLevel: () => backgroundBlurriness
 				} ).mul( backgroundIntensity );
 				} ).mul( backgroundIntensity );
 
 
 				let viewProj = modelViewProjection();
 				let viewProj = modelViewProjection();

+ 2 - 2
examples/jsm/renderers/webgl-legacy/nodes/WebGLNodeBuilder.js

@@ -417,7 +417,7 @@ class WebGLNodeBuilder extends NodeBuilder {
 
 
 	}
 	}
 
 
-	getTexture( texture, textureProperty, uvSnippet ) {
+	generateTexture( texture, textureProperty, uvSnippet ) {
 
 
 		if ( texture.isTextureCube ) {
 		if ( texture.isTextureCube ) {
 
 
@@ -431,7 +431,7 @@ class WebGLNodeBuilder extends NodeBuilder {
 
 
 	}
 	}
 
 
-	getTextureBias( texture, textureProperty, uvSnippet, biasSnippet ) {
+	generateTextureLevel( texture, textureProperty, uvSnippet, biasSnippet ) {
 
 
 		if ( this.material.extensions !== undefined ) this.material.extensions.shaderTextureLOD = true;
 		if ( this.material.extensions !== undefined ) this.material.extensions.shaderTextureLOD = true;
 
 

+ 12 - 2
examples/jsm/renderers/webgl/WebGLBackend.js

@@ -481,7 +481,7 @@ class WebGLBackend extends Backend {
 	createTexture( texture, options ) {
 	createTexture( texture, options ) {
 
 
 		const { gl, utils, textureUtils } = this;
 		const { gl, utils, textureUtils } = this;
-		const { levels, width, height } = options;
+		const { levels, width, height, depth } = options;
 
 
 		const glFormat = utils.convert( texture.format, texture.colorSpace );
 		const glFormat = utils.convert( texture.format, texture.colorSpace );
 		const glType = utils.convert( texture.type );
 		const glType = utils.convert( texture.type );
@@ -501,7 +501,11 @@ class WebGLBackend extends Backend {
 
 
 		gl.bindTexture( glTextureType, textureGPU );
 		gl.bindTexture( glTextureType, textureGPU );
 
 
-		if ( ! texture.isVideoTexture ) {
+		if ( texture.isDataArrayTexture ) {
+
+			gl.texStorage3D( gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, width, height, depth );
+
+		} else if ( ! texture.isVideoTexture ) {
 
 
 			gl.texStorage2D( glTextureType, levels, glInternalFormat, width, height );
 			gl.texStorage2D( glTextureType, levels, glInternalFormat, width, height );
 
 
@@ -553,6 +557,12 @@ class WebGLBackend extends Backend {
 
 
 			}
 			}
 
 
+		} else if ( texture.isDataArrayTexture ) {
+
+			const image = options.image;
+
+			gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data );
+
 		} else if ( texture.isVideoTexture ) {
 		} else if ( texture.isVideoTexture ) {
 
 
 			texture.update();
 			texture.update();

+ 22 - 13
examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js

@@ -74,35 +74,37 @@ ${ flowData.code }
 
 
 	}
 	}
 
 
-	getTexture( texture, textureProperty, uvSnippet ) {
+	generateTexture( texture, textureProperty, uvSnippet, depthSnippet ) {
 
 
 		if ( texture.isTextureCube ) {
 		if ( texture.isTextureCube ) {
 
 
-			return `textureCube( ${textureProperty}, ${uvSnippet} )`;
+			return `textureCube( ${ textureProperty }, ${ uvSnippet } )`;
 
 
 		} else if ( texture.isDepthTexture ) {
 		} else if ( texture.isDepthTexture ) {
 
 
-			return `texture( ${textureProperty}, ${uvSnippet} ).x`;
+			return `texture( ${ textureProperty }, ${ uvSnippet } ).x`;
 
 
 		} else {
 		} else {
 
 
-			return `texture( ${textureProperty}, ${uvSnippet} )`;
+			if ( depthSnippet ) uvSnippet = `vec3( ${ uvSnippet }, ${ depthSnippet } )`;
+
+			return `texture( ${ textureProperty }, ${ uvSnippet } )`;
 
 
 		}
 		}
 
 
 	}
 	}
 
 
-	getTextureLevel( texture, textureProperty, uvSnippet, biasSnippet ) {
+	generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet ) {
 
 
-		return `textureLod( ${textureProperty}, ${uvSnippet}, ${biasSnippet} )`;
+		return `textureLod( ${ textureProperty }, ${ uvSnippet }, ${ levelSnippet } )`;
 
 
 	}
 	}
 
 
-	getTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, shaderStage = this.shaderStage ) {
+	generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, shaderStage = this.shaderStage ) {
 
 
 		if ( shaderStage === 'fragment' ) {
 		if ( shaderStage === 'fragment' ) {
 
 
-			return `texture( ${textureProperty}, vec3( ${uvSnippet}, ${compareSnippet} ) )`;
+			return `texture( ${ textureProperty }, vec3( ${ uvSnippet }, ${ compareSnippet } ) )`;
 
 
 		} else {
 		} else {
 
 
@@ -148,19 +150,25 @@ ${ flowData.code }
 
 
 			if ( uniform.type === 'texture' ) {
 			if ( uniform.type === 'texture' ) {
 
 
-				if ( uniform.node.value.compareFunction ) {
+				const texture = uniform.node.value;
+
+				if ( texture.compareFunction ) {
+
+					snippet = `sampler2DShadow ${ uniform.name };`;
+
+				} else if ( texture.isDataArrayTexture === true ) {
 
 
-					snippet = `sampler2DShadow ${uniform.name};`;
+					snippet = `sampler2DArray ${ uniform.name };`;
 
 
 				} else {
 				} else {
 
 
-					snippet = `sampler2D ${uniform.name};`;
+					snippet = `sampler2D ${ uniform.name };`;
 
 
 				}
 				}
 
 
 			} else if ( uniform.type === 'cubeTexture' ) {
 			} else if ( uniform.type === 'cubeTexture' ) {
 
 
-				snippet = `samplerCube ${uniform.name};`;
+				snippet = `samplerCube ${ uniform.name };`;
 
 
 			} else if ( uniform.type === 'buffer' ) {
 			} else if ( uniform.type === 'buffer' ) {
 
 
@@ -169,7 +177,7 @@ ${ flowData.code }
 				const bufferCount = bufferNode.bufferCount;
 				const bufferCount = bufferNode.bufferCount;
 
 
 				const bufferCountSnippet = bufferCount > 0 ? bufferCount : '';
 				const bufferCountSnippet = bufferCount > 0 ? bufferCount : '';
-				snippet = `${bufferNode.name} {\n\t${bufferType} ${uniform.name}[${bufferCountSnippet}];\n};\n`;
+				snippet = `${bufferNode.name} {\n\t${ bufferType } ${ uniform.name }[${ bufferCountSnippet }];\n};\n`;
 
 
 			} else {
 			} else {
 
 
@@ -419,6 +427,7 @@ ${ this.getSignature() }
 // precision
 // precision
 precision highp float;
 precision highp float;
 precision highp int;
 precision highp int;
+precision highp sampler2DArray;
 precision lowp sampler2DShadow;
 precision lowp sampler2DShadow;
 
 
 // uniforms
 // uniforms

+ 4 - 0
examples/jsm/renderers/webgl/utils/WebGLTextureUtils.js

@@ -78,6 +78,10 @@ class WebGLTextureUtils {
 
 
 			glTextureType = gl.TEXTURE_CUBE_MAP;
 			glTextureType = gl.TEXTURE_CUBE_MAP;
 
 
+		} else if ( texture.isDataArrayTexture === true ) {
+
+			glTextureType = gl.TEXTURE_2D_ARRAY;
+
 		} else {
 		} else {
 
 
 			glTextureType = gl.TEXTURE_2D;
 			glTextureType = gl.TEXTURE_2D;

+ 32 - 20
examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -133,25 +133,33 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 
 	}
 	}
 
 
-	_getSampler( texture, textureProperty, uvSnippet, shaderStage = this.shaderStage ) {
+	_generateTextureSample( texture, textureProperty, uvSnippet, depthSnippet, shaderStage = this.shaderStage ) {
 
 
 		if ( shaderStage === 'fragment' ) {
 		if ( shaderStage === 'fragment' ) {
 
 
-			return `textureSample( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet} )`;
+			if ( depthSnippet ) {
+
+				return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet } )`;
+
+			} else {
+
+				return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet } )`;
+
+			}
 
 
 		} else {
 		} else {
 
 
-			return this.getTextureLoad( texture, textureProperty, uvSnippet );
+			return this.generateTextureLod( texture, textureProperty, uvSnippet );
 
 
 		}
 		}
 
 
 	}
 	}
 
 
-	_getVideoSampler( textureProperty, uvSnippet, shaderStage = this.shaderStage ) {
+	_generateVideoSample( textureProperty, uvSnippet, shaderStage = this.shaderStage ) {
 
 
 		if ( shaderStage === 'fragment' ) {
 		if ( shaderStage === 'fragment' ) {
 
 
-			return `textureSampleBaseClampToEdge( ${textureProperty}, ${textureProperty}_sampler, vec2<f32>( ${uvSnippet}.x, 1.0 - ${uvSnippet}.y ) )`;
+			return `textureSampleBaseClampToEdge( ${ textureProperty }, ${ textureProperty }_sampler, vec2<f32>( ${ uvSnippet }.x, 1.0 - ${ uvSnippet }.y ) )`;
 
 
 		} else {
 		} else {
 
 
@@ -161,27 +169,27 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 
 	}
 	}
 
 
-	_getSamplerLevel( texture, textureProperty, uvSnippet, biasSnippet, shaderStage = this.shaderStage ) {
+	_generateTextureSampleLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, shaderStage = this.shaderStage ) {
 
 
 		if ( shaderStage === 'fragment' && this.isUnfilterable( texture ) === false ) {
 		if ( shaderStage === 'fragment' && this.isUnfilterable( texture ) === false ) {
 
 
-			return `textureSampleLevel( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${biasSnippet} )`;
+			return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ levelSnippet } )`;
 
 
 		} else {
 		} else {
 
 
-			return this.getTextureLoad( texture, textureProperty, uvSnippet, biasSnippet );
+			return this.generateTextureLod( texture, textureProperty, uvSnippet, levelSnippet );
 
 
 		}
 		}
 
 
 	}
 	}
 
 
-	getTextureLoad( texture, textureProperty, uvSnippet, biasSnippet = '0' ) {
+	generateTextureLod( texture, textureProperty, uvSnippet, levelSnippet = '0' ) {
 
 
 		this._include( 'repeatWrapping' );
 		this._include( 'repeatWrapping' );
 
 
-		const dimension = `textureDimensions( ${textureProperty}, 0 )`;
+		const dimension = `textureDimensions( ${ textureProperty }, 0 )`;
 
 
-		return `textureLoad( ${textureProperty}, threejs_repeatWrapping( ${uvSnippet}, ${dimension} ), i32( ${biasSnippet} ) )`;
+		return `textureLoad( ${ textureProperty }, threejs_repeatWrapping( ${ uvSnippet }, ${ dimension } ), i32( ${ levelSnippet } ) )`;
 
 
 	}
 	}
 
 
@@ -191,21 +199,21 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 
 	}
 	}
 
 
-	getTexture( texture, textureProperty, uvSnippet, shaderStage = this.shaderStage ) {
+	generateTexture( texture, textureProperty, uvSnippet, depthSnippet, shaderStage = this.shaderStage ) {
 
 
 		let snippet = null;
 		let snippet = null;
 
 
 		if ( texture.isVideoTexture === true ) {
 		if ( texture.isVideoTexture === true ) {
 
 
-			snippet = this._getVideoSampler( textureProperty, uvSnippet, shaderStage );
+			snippet = this._generateVideoSample( textureProperty, uvSnippet, shaderStage );
 
 
 		} else if ( this.isUnfilterable( texture ) ) {
 		} else if ( this.isUnfilterable( texture ) ) {
 
 
-			snippet = this.getTextureLoad( texture, textureProperty, uvSnippet );
+			snippet = this.generateTextureLod( texture, textureProperty, uvSnippet, '0', depthSnippet, shaderStage );
 
 
 		} else {
 		} else {
 
 
-			snippet = this._getSampler( texture, textureProperty, uvSnippet, shaderStage );
+			snippet = this._generateTextureSample( texture, textureProperty, uvSnippet, depthSnippet, shaderStage );
 
 
 		}
 		}
 
 
@@ -213,11 +221,11 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 
 	}
 	}
 
 
-	getTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, shaderStage = this.shaderStage ) {
+	generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, shaderStage = this.shaderStage ) {
 
 
 		if ( shaderStage === 'fragment' ) {
 		if ( shaderStage === 'fragment' ) {
 
 
-			return `textureSampleCompare( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${compareSnippet} )`;
+			return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ compareSnippet } )`;
 
 
 		} else {
 		} else {
 
 
@@ -227,17 +235,17 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 
 	}
 	}
 
 
-	getTextureLevel( texture, textureProperty, uvSnippet, biasSnippet, shaderStage = this.shaderStage ) {
+	generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, shaderStage = this.shaderStage ) {
 
 
 		let snippet = null;
 		let snippet = null;
 
 
 		if ( texture.isVideoTexture === true ) {
 		if ( texture.isVideoTexture === true ) {
 
 
-			snippet = this._getVideoSampler( textureProperty, uvSnippet, shaderStage );
+			snippet = this._generateVideoSample( textureProperty, uvSnippet, shaderStage );
 
 
 		} else {
 		} else {
 
 
-			snippet = this._getSamplerLevel( texture, textureProperty, uvSnippet, biasSnippet, shaderStage );
+			snippet = this._generateTextureSampleLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, shaderStage );
 
 
 		}
 		}
 
 
@@ -687,6 +695,10 @@ ${ flowData.code }
 
 
 					textureType = 'texture_cube<f32>';
 					textureType = 'texture_cube<f32>';
 
 
+				} else if ( texture.isDataArrayTexture === true ) {
+
+					textureType = 'texture_2d_array<f32>';
+
 				} else if ( texture.isDepthTexture === true ) {
 				} else if ( texture.isDepthTexture === true ) {
 
 
 					textureType = 'texture_depth_2d';
 					textureType = 'texture_depth_2d';

+ 8 - 0
examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js

@@ -85,6 +85,10 @@ class WebGPUBindingUtils {
 
 
 					texture.viewDimension = GPUTextureViewDimension.Cube;
 					texture.viewDimension = GPUTextureViewDimension.Cube;
 
 
+				} else if ( binding.texture.isDataArrayTexture ) {
+
+					texture.viewDimension = GPUTextureViewDimension.TwoDArray;
+
 				}
 				}
 
 
 				bindingGPU.texture = texture;
 				bindingGPU.texture = texture;
@@ -196,6 +200,10 @@ class WebGPUBindingUtils {
 
 
 					dimensionViewGPU = GPUTextureViewDimension.Cube;
 					dimensionViewGPU = GPUTextureViewDimension.Cube;
 
 
+				} else if ( binding.texture.isDataArrayTexture ) {
+
+					dimensionViewGPU = GPUTextureViewDimension.TwoDArray;
+
 				} else {
 				} else {
 
 
 					dimensionViewGPU = GPUTextureViewDimension.TwoD;
 					dimensionViewGPU = GPUTextureViewDimension.TwoD;

+ 12 - 4
examples/jsm/renderers/webgpu/utils/WebGPUTextureUtils.js

@@ -237,10 +237,18 @@ class WebGPUTextureUtils {
 
 
 		// transfer texture data
 		// transfer texture data
 
 
-		if ( texture.isDataTexture || texture.isDataArrayTexture || texture.isData3DTexture ) {
+		if ( texture.isDataTexture || texture.isData3DTexture ) {
 
 
 			this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, false );
 			this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, false );
 
 
+		} else if ( texture.isDataArrayTexture ) {
+
+			for ( let i = 0; i < options.image.depth; i ++ ) {
+
+				this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, i, false, i );
+
+			}
+
 		} else if ( texture.isCompressedTexture ) {
 		} else if ( texture.isCompressedTexture ) {
 
 
 			this._copyCompressedBufferToTexture( texture.mipmaps, textureData.texture, textureDescriptorGPU );
 			this._copyCompressedBufferToTexture( texture.mipmaps, textureData.texture, textureDescriptorGPU );
@@ -437,7 +445,7 @@ class WebGPUTextureUtils {
 
 
 	}
 	}
 
 
-	_copyBufferToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY ) {
+	_copyBufferToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY, depth = 0 ) {
 
 
 		// @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()
 		// @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture()
 		// @TODO: Consider to support valid buffer layouts with other formats like RGB
 		// @TODO: Consider to support valid buffer layouts with other formats like RGB
@@ -457,13 +465,13 @@ class WebGPUTextureUtils {
 			},
 			},
 			data,
 			data,
 			{
 			{
-				offset: 0,
+				offset: image.width * image.height * bytesPerTexel * depth,
 				bytesPerRow
 				bytesPerRow
 			},
 			},
 			{
 			{
 				width: image.width,
 				width: image.width,
 				height: image.height,
 				height: image.height,
-				depthOrArrayLayers: ( image.depth !== undefined ) ? image.depth : 1
+				depthOrArrayLayers: 1
 			} );
 			} );
 
 
 		if ( flipY === true ) {
 		if ( flipY === true ) {

二进制
examples/screenshots/webgpu_textures_2d-array.jpg


+ 2 - 2
examples/webgpu_cubemap_adjustments.html

@@ -27,7 +27,7 @@
 		<script type="module">
 		<script type="module">
 
 
 			import * as THREE from 'three';
 			import * as THREE from 'three';
-			import { uniform, mix, cubeTexture, reference, positionLocal, positionWorld, normalWorld, positionWorldDirection, reflectVector, toneMapping } from 'three/nodes';
+			import { uniform, mix, cubeTexture, reference, positionLocal, positionWorld, normalWorld, positionWorldDirection, reflectVector, toneMapping, maxMipLevel } from 'three/nodes';
 
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
 			import WebGL from 'three/addons/capabilities/WebGL.js';
 			import WebGL from 'three/addons/capabilities/WebGL.js';
@@ -122,7 +122,7 @@
 				scene.environmentNode = getEnvironmentNode( reflectVector, positionWorld );
 				scene.environmentNode = getEnvironmentNode( reflectVector, positionWorld );
 
 
 				scene.backgroundNode = getEnvironmentNode( positionWorldDirection, positionLocal ).context( {
 				scene.backgroundNode = getEnvironmentNode( positionWorldDirection, positionLocal ).context( {
-					getSamplerLevelNode: () => blurNode
+					getTextureLevel: ( textureNode ) => blurNode.mul( maxMipLevel( textureNode ) )
 				} );
 				} );
 
 
 				// scene objects
 				// scene objects

+ 2 - 2
examples/webgpu_cubemap_mix.html

@@ -27,7 +27,7 @@
 		<script type="module">
 		<script type="module">
 
 
 			import * as THREE from 'three';
 			import * as THREE from 'three';
-			import { mix, oscSine, timerLocal, cubeTexture, float, toneMapping } from 'three/nodes';
+			import { mix, oscSine, timerLocal, cubeTexture, maxMipLevel, toneMapping } from 'three/nodes';
 
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
 			import WebGL from 'three/addons/capabilities/WebGL.js';
 			import WebGL from 'three/addons/capabilities/WebGL.js';
@@ -81,7 +81,7 @@
 				scene.environmentNode = mix( cubeTexture( cube2Texture ), cubeTexture( cube1Texture ), oscSine( timerLocal( .1 ) ) );
 				scene.environmentNode = mix( cubeTexture( cube2Texture ), cubeTexture( cube1Texture ), oscSine( timerLocal( .1 ) ) );
 
 
 				scene.backgroundNode = scene.environmentNode.context( {
 				scene.backgroundNode = scene.environmentNode.context( {
-					getSamplerLevelNode: () => float( 1 )
+					getTextureLevel: ( textureNode ) => maxMipLevel( textureNode )
 				} );
 				} );
 
 
 				const loader = new GLTFLoader().setPath( 'models/gltf/DamagedHelmet/glTF/' );
 				const loader = new GLTFLoader().setPath( 'models/gltf/DamagedHelmet/glTF/' );

+ 134 - 0
examples/webgpu_textures_2d-array.html

@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - 2D texture array</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">
+	</head>
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - 2D Texture array<br />
+			Scanned head data by
+			<a href="https://www.codeproject.com/Articles/352270/Getting-started-with-Volume-Rendering" target="_blank" rel="noopener">Divine Augustine</a><br />
+			licensed under
+			<a href="https://www.codeproject.com/info/cpol10.aspx" target="_blank" rel="noopener">CPOL</a>
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/",
+					"three/nodes": "./jsm/nodes/Nodes.js"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import { MeshBasicNodeMaterial, texture, uv, oscTriangle, timerLocal } from 'three/nodes';
+
+			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+			import { unzipSync } from 'three/addons/libs/fflate.module.js';
+
+			if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
+
+				document.body.appendChild( WebGPU.getErrorMessage() );
+
+				throw new Error( 'No WebGPU or WebGL2 support' );
+
+			}
+
+			let camera, scene, mesh, renderer, stats;
+
+			const planeWidth = 50;
+			const planeHeight = 50;
+
+			init();
+
+			function init() {
+
+				const container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 2000 );
+				camera.position.z = 70;
+
+				scene = new THREE.Scene();
+
+				// width 256, height 256, depth 109, 8-bit, zip archived raw data
+
+				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 map = new THREE.DataArrayTexture( array, 256, 256, 109 );
+						map.format = THREE.RedFormat;
+						map.needsUpdate = true;
+
+						let coord = uv();
+						coord = coord.setY( coord.y.oneMinus() ); // flip y
+
+						let oscLayers = oscTriangle( timerLocal( .5 ) ); // [ /\/ ] triangle osc animation
+						oscLayers = oscLayers.add( 1 ).mul( .5 ); // convert osc range of [ -1, 1 ] to [ 0, 1 ]
+						oscLayers = oscLayers.mul( map.image.depth ); // scale osc range to texture depth
+
+						const material = new MeshBasicNodeMaterial();
+						material.colorNode = texture( map, coord ).depth( oscLayers ).r.remap( 0, 1, - .1, 1.8 ); // remap to make it more visible
+
+						const geometry = new THREE.PlaneGeometry( planeWidth, planeHeight );
+
+						mesh = new THREE.Mesh( geometry, material );
+
+						scene.add( mesh );
+
+					} );
+
+				renderer = new WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				container.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>