+
+
+ This example demonstrates texture gather
+ Left canvas is using WebGPU Backend, right canvas is WebGL Backend.
+ The top half gathers color values and the bottom half gathers depth comparison.
+
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index 6c74da34006e60..4353bfa6796fd4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1822,9 +1822,9 @@
"license": "MIT"
},
"node_modules/eslint-plugin-compat": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-7.0.1.tgz",
- "integrity": "sha512-wDID2fVIAfxV9R1uSkCn5HscnNu8yMxDF1IaQGyD1C6XuWwJbuaDgMOSkVgOom0LzY8z0fXXXCy7AQQTERQUvQ==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-7.0.2.tgz",
+ "integrity": "sha512-gN8hF+4NzMsHUbr4m/TYZK0FtW3DcV4g8rXpTsY2EV5xiRD8jsilUlB9lNSkGGX0veDCxMhKSWbSd+faJByQDA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1837,7 +1837,7 @@
"semver": "^7.6.2"
},
"engines": {
- "node": ">=18.x"
+ "node": ">=22.x"
},
"peerDependencies": {
"eslint": "^9.0.0 || ^10.0.0"
@@ -2244,9 +2244,9 @@
}
},
"node_modules/globals": {
- "version": "17.5.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz",
- "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==",
+ "version": "17.6.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz",
+ "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==",
"dev": true,
"license": "MIT",
"engines": {
diff --git a/src/nodes/accessors/TextureNode.js b/src/nodes/accessors/TextureNode.js
index cc0e061ef21dec..dbb66cb074ec1c 100644
--- a/src/nodes/accessors/TextureNode.js
+++ b/src/nodes/accessors/TextureNode.js
@@ -99,6 +99,15 @@ class TextureNode extends UniformNode {
*/
this.gradNode = null;
+ /**
+ * Represents the optional index constant of the channel to gather.
+ * This must be in range [0, 3] and a compile-time constant.
+ *
+ * @type {?Node}
+ * @default null
+ */
+ this.gatherNode = null;
+
/**
* Represents the optional texel offset applied to the unnormalized texture
* coordinate before sampling the texture.
@@ -219,7 +228,13 @@ class TextureNode extends UniformNode {
*/
generateNodeType( /*builder*/ ) {
- if ( this.value.isDepthTexture === true ) return 'float';
+ if ( this.value.isDepthTexture === true ) {
+
+ if ( this.gatherNode === null ) return 'float';
+
+ return 'vec4';
+
+ }
if ( this.value.type === UnsignedIntType ) {
@@ -429,6 +444,7 @@ class TextureNode extends UniformNode {
properties.compareNode = compareNode;
properties.compareStepNode = compareStepNode;
properties.gradNode = this.gradNode;
+ properties.gatherNode = this.gatherNode;
properties.depthNode = this.depthNode;
properties.offsetNode = this.offsetNode;
@@ -471,10 +487,12 @@ class TextureNode extends UniformNode {
* @param {?string} depthSnippet - The depth snippet.
* @param {?string} compareSnippet - The compare snippet.
* @param {?Array} gradSnippet - The grad snippet.
+ * @param {?string} gatherSnippet - The gather snippet.
* @param {?string} offsetSnippet - The offset snippet.
+ * @param {?string} flipYSnippet - The y-flip snippet. Only used for WebGL.
* @return {string} The generated code snippet.
*/
- generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet, offsetSnippet ) {
+ generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet, gatherSnippet, offsetSnippet, flipYSnippet ) {
const texture = this.value;
@@ -488,6 +506,18 @@ class TextureNode extends UniformNode {
snippet = builder.generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet, offsetSnippet );
+ } else if ( gatherSnippet ) {
+
+ if ( compareSnippet ) {
+
+ snippet = builder.generateTextureGatherCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, offsetSnippet, flipYSnippet );
+
+ } else {
+
+ snippet = builder.generateTextureGather( texture, textureProperty, uvSnippet, gatherSnippet, depthSnippet, offsetSnippet, flipYSnippet );
+
+ }
+
} else if ( compareSnippet ) {
snippet = builder.generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, offsetSnippet );
@@ -536,13 +566,13 @@ class TextureNode extends UniformNode {
const nodeData = builder.getDataFromNode( this );
- const nodeType = this.getNodeType( builder );
+ let nodeType = this.getNodeType( builder );
let propertyName = nodeData.propertyName;
if ( propertyName === undefined ) {
- const { uvNode, levelNode, biasNode, compareNode, compareStepNode, depthNode, gradNode, offsetNode } = properties;
+ const { uvNode, levelNode, biasNode, compareNode, compareStepNode, depthNode, gradNode, gatherNode, offsetNode } = properties;
const uvSnippet = this.generateUV( builder, uvNode );
const levelSnippet = levelNode ? levelNode.build( builder, 'float' ) : null;
@@ -551,7 +581,15 @@ class TextureNode extends UniformNode {
const compareSnippet = compareNode ? compareNode.build( builder, 'float' ) : null;
const compareStepSnippet = compareStepNode ? compareStepNode.build( builder, 'float' ) : null;
const gradSnippet = gradNode ? [ gradNode[ 0 ].build( builder, 'vec2' ), gradNode[ 1 ].build( builder, 'vec2' ) ] : null;
+ const gatherSnippet = gatherNode ? gatherNode.build( builder, 'int' ) : null;
const offsetSnippet = offsetNode ? this.generateOffset( builder, offsetNode ) : null;
+ const flipYSnippet = this._flipYUniform ? this._flipYUniform.build( builder, 'bool' ) : null;
+
+ if ( gatherSnippet ) {
+
+ nodeType = 'vec4';
+
+ }
let finalDepthSnippet = depthSnippet;
@@ -565,7 +603,7 @@ class TextureNode extends UniformNode {
propertyName = builder.getPropertyName( nodeVar );
- let snippet = this.generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, finalDepthSnippet, compareSnippet, gradSnippet, offsetSnippet );
+ let snippet = this.generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, finalDepthSnippet, compareSnippet, gradSnippet, gatherSnippet, offsetSnippet, flipYSnippet );
if ( compareStepSnippet !== null ) {
@@ -772,6 +810,22 @@ class TextureNode extends UniformNode {
}
+ /**
+ * Gathers four texels from the texture.
+ *
+ * @param {[Node]} gatherNode - The index of the channel to read. This must be in range [0, 3] and a compile-time constant.
+ * @return {TextureNode} A texture node representing the texture sample.
+ */
+ gather( gatherNode = 0 ) {
+
+ const textureNode = this.clone();
+ textureNode.gatherNode = nodeObject( gatherNode );
+ textureNode.referenceNode = this.getBase();
+
+ return nodeObject( textureNode );
+
+ }
+
/**
* Samples the texture by defining a depth node.
*
@@ -868,6 +922,7 @@ class TextureNode extends UniformNode {
newNode.depthNode = this.depthNode;
newNode.compareNode = this.compareNode;
newNode.gradNode = this.gradNode;
+ newNode.gatherNode = this.gatherNode;
newNode.offsetNode = this.offsetNode;
return newNode;
diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js
index 8f43867fec0d29..bab01cc00b212b 100644
--- a/src/nodes/display/PassNode.js
+++ b/src/nodes/display/PassNode.js
@@ -155,6 +155,7 @@ class PassMultipleTextureNode extends PassTextureNode {
newNode.depthNode = this.depthNode;
newNode.compareNode = this.compareNode;
newNode.gradNode = this.gradNode;
+ newNode.gatherNode = this.gatherNode;
newNode.offsetNode = this.offsetNode;
return newNode;
diff --git a/src/nodes/utils/ReflectorNode.js b/src/nodes/utils/ReflectorNode.js
index 5c5227a60e1ebb..75670fe1a274ad 100644
--- a/src/nodes/utils/ReflectorNode.js
+++ b/src/nodes/utils/ReflectorNode.js
@@ -163,6 +163,7 @@ class ReflectorNode extends TextureNode {
newNode.depthNode = this.depthNode;
newNode.compareNode = this.compareNode;
newNode.gradNode = this.gradNode;
+ newNode.gatherNode = this.gatherNode;
newNode.offsetNode = this.offsetNode;
newNode._reflectorBaseNode = this._reflectorBaseNode;
diff --git a/src/renderers/common/Backend.js b/src/renderers/common/Backend.js
index c0631941e33b35..fb09e3259eaa0d 100644
--- a/src/renderers/common/Backend.js
+++ b/src/renderers/common/Backend.js
@@ -277,9 +277,10 @@ class Backend {
*
* @abstract
* @param {Texture} texture - The texture to update the sampler for.
+ * @param {TextureNode} textureNode - The texture node to update the sampler with.
* @return {string} The current sampler key.
*/
- updateSampler( /*texture*/ ) { }
+ updateSampler( /*texture, textureNode*/ ) { }
/**
* Creates a default texture for the given texture that can be used
diff --git a/src/renderers/common/Bindings.js b/src/renderers/common/Bindings.js
index f2f906f55e9479..e0902417499a3b 100644
--- a/src/renderers/common/Bindings.js
+++ b/src/renderers/common/Bindings.js
@@ -202,7 +202,7 @@ class Bindings extends DataMap {
} else if ( binding.isSampler ) {
- this.textures.updateSampler( binding.texture );
+ this.textures.updateSampler( binding.texture, binding.textureNode );
} else if ( binding.isStorageBuffer ) {
@@ -410,7 +410,7 @@ class Bindings extends DataMap {
if ( updated ) {
- const samplerKey = this.textures.updateSampler( binding.texture );
+ const samplerKey = this.textures.updateSampler( binding.texture, binding.textureNode );
if ( binding.samplerKey !== samplerKey ) {
diff --git a/src/renderers/common/Textures.js b/src/renderers/common/Textures.js
index 145e7463406fb2..bd7ac761a2fb46 100644
--- a/src/renderers/common/Textures.js
+++ b/src/renderers/common/Textures.js
@@ -419,11 +419,12 @@ class Textures extends DataMap {
* them when the texture parameters match.
*
* @param {Texture} texture - The texture to update the sampler for.
+ * @param {TextureNode} textureNode - The texture node to update the sampler with.
* @return {string} The current sampler key.
*/
- updateSampler( texture ) {
+ updateSampler( texture, textureNode ) {
- return this.backend.updateSampler( texture );
+ return this.backend.updateSampler( texture, textureNode );
}
diff --git a/src/renderers/webgl-fallback/WebGLBackend.js b/src/renderers/webgl-fallback/WebGLBackend.js
index 707c12022bf3f4..eefa2b2e04a839 100644
--- a/src/renderers/webgl-fallback/WebGLBackend.js
+++ b/src/renderers/webgl-fallback/WebGLBackend.js
@@ -1418,9 +1418,10 @@ class WebGLBackend extends Backend {
* This method does nothing since WebGL 2 has no concept of samplers.
*
* @param {Texture} texture - The texture to update the sampler for.
+ * @param {TextureNode} textureNode - The texture node to update the sampler with.
* @return {string} The current sampler key.
*/
- updateSampler( /*texture*/ ) {
+ updateSampler( /*texture, textureNode*/ ) {
return '';
diff --git a/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js b/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js
index 5e18fb3c051398..a2653e6679c4d2 100644
--- a/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js
+++ b/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js
@@ -11,7 +11,67 @@ import { error } from '../../../utils.js';
const glslPolyfills = {
bitcast_int_uint: new CodeNode( /* glsl */'uint tsl_bitcast_int_to_uint ( int x ) { return floatBitsToUint( intBitsToFloat ( x ) ); }' ),
- bitcast_uint_int: new CodeNode( /* glsl */'uint tsl_bitcast_uint_to_int ( uint x ) { return floatBitsToInt( uintBitsToFloat ( x ) ); }' )
+ bitcast_uint_int: new CodeNode( /* glsl */'uint tsl_bitcast_uint_to_int ( uint x ) { return floatBitsToInt( uintBitsToFloat ( x ) ); }' ),
+ textureGather: new CodeNode( /* glsl */`
+vec4 tsl_textureGather( const int comp, sampler2D map, vec2 coord, ivec2 offset, bool flipY ) {
+ if ( flipY ) offset.y = - offset.y;
+ vec2 size = vec2( textureSize( map, 0 ) );
+ vec2 st = floor( coord * size + vec2( offset ) - 0.5 );
+ vec4 ij = vec4( st + 0.5, st + 1.5 ) / size.xyxy;
+ vec4 ret = vec4(
+ textureLod( map, ij.xw, 0.0 )[ comp ],
+ textureLod( map, ij.zw, 0.0 )[ comp ],
+ textureLod( map, ij.zy, 0.0 )[ comp ],
+ textureLod( map, ij.xy, 0.0 )[ comp ]
+ );
+ return flipY ? ret.wzyx : ret;
+}
+` ),
+ textureGatherArray: new CodeNode( /* glsl */`
+vec4 tsl_textureGather_array( const int comp, sampler2DArray map, vec3 coord, ivec2 offset, bool flipY ) {
+ if ( flipY ) offset.y = - offset.y;
+ vec2 size = vec2( textureSize( map, 0 ).xy );
+ vec2 st = floor( coord.xy * size + vec2( offset ) - 0.5 );
+ vec4 ij = vec4( st + 0.5, st + 1.5 ) / size.xyxy;
+ vec4 ret = vec4(
+ textureLod( map, vec3( ij.xw, coord.z ), 0.0 )[ comp ],
+ textureLod( map, vec3( ij.zw, coord.z ), 0.0 )[ comp ],
+ textureLod( map, vec3( ij.zy, coord.z ), 0.0 )[ comp ],
+ textureLod( map, vec3( ij.xy, coord.z ), 0.0 )[ comp ]
+ );
+ return flipY ? ret.wzyx : ret;
+}
+` ),
+ textureGatherCompare: new CodeNode( /* glsl */`
+vec4 tsl_textureGatherCompare( sampler2DShadow map, vec2 coord, ivec2 offset, float ref, bool flipY ) {
+ if ( flipY ) offset.y = - offset.y;
+ vec2 size = vec2( textureSize( map, 0 ) );
+ vec2 st = floor( coord * size + vec2( offset ) - 0.5 );
+ vec4 ij = vec4( st + 0.5, st + 1.5 ) / size.xyxy;
+ vec4 ret = vec4(
+ textureLod( map, vec3( ij.xw, ref ), 0.0 ),
+ textureLod( map, vec3( ij.zw, ref ), 0.0 ),
+ textureLod( map, vec3( ij.zy, ref ), 0.0 ),
+ textureLod( map, vec3( ij.xy, ref ), 0.0 )
+ );
+ return flipY ? ret.wzyx : ret;
+}
+` ),
+ textureGatherCompareArray: new CodeNode( /* glsl */`
+vec4 tsl_textureGatherCompare_array( sampler2DArrayShadow map, vec3 coord, ivec2 offset, float ref, bool flipY ) {
+ if ( flipY ) offset.y = - offset.y;
+ vec2 size = vec2( textureSize( map, 0 ).xy );
+ vec2 st = floor( coord.xy * size + vec2( offset ) - 0.5 );
+ vec4 ij = vec4( st + 0.5, st + 1.5 ) / size.xyxy;
+ vec4 ret = vec4(
+ texture( map, vec4( ij.xw, coord.z, ref ) ),
+ texture( map, vec4( ij.zw, coord.z, ref ) ),
+ texture( map, vec4( ij.zy, coord.z, ref ) ),
+ texture( map, vec4( ij.xy, coord.z, ref ) )
+ );
+ return flipY ? ret.wzyx : ret;
+}
+` )
};
const glslMethods = {
@@ -182,7 +242,7 @@ class GLSLNodeBuilder extends NodeBuilder {
*
* @param {string} type - The output type to bitcast to.
* @param {string} inputType - The input type of the.
- * @return {string} The resolved WGSL bitcast invocation.
+ * @return {string} The resolved GLSL bitcast invocation.
*/
getBitcastMethod( type, inputType ) {
@@ -665,6 +725,72 @@ ${ flowData.code }
}
+ /**
+ * Generates the GLSL snippet for gathering four texels from the given texture.
+ *
+ * @param {Texture} texture - The texture.
+ * @param {string} textureProperty - The name of the texture uniform in the shader.
+ * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling.
+ * @param {string} gatherSnippet - A GLSL snippet that represents the index of the channel to read.
+ * @param {?string} depthSnippet - A GLSL snippet that represents 0-based texture array index to sample.
+ * @param {?string} offsetSnippet - A GLSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
+ * @param {?string} flipYSnippet - A GLSL snippet that represents the y-flip. Only used for WebGL.
+ * @return {string} The GLSL snippet.
+ */
+ generateTextureGather( texture, textureProperty, uvSnippet, gatherSnippet, depthSnippet, offsetSnippet, flipYSnippet ) {
+
+ if ( texture.isDepthTexture ) gatherSnippet = '0';
+
+ if ( offsetSnippet === null ) offsetSnippet = 'ivec2( 0 )';
+
+ if ( flipYSnippet === null ) flipYSnippet = 'false';
+
+ if ( depthSnippet ) {
+
+ this._include( 'textureGatherArray' );
+
+ return `tsl_textureGather_array( ${gatherSnippet}, ${ textureProperty }, vec3( ${ uvSnippet }, ${ depthSnippet } ), ${ offsetSnippet }, ${ flipYSnippet } )`;
+
+ }
+
+ this._include( 'textureGather' );
+
+ return `tsl_textureGather( ${gatherSnippet}, ${ textureProperty }, ${ uvSnippet }, ${ offsetSnippet }, ${ flipYSnippet } )`;
+
+ }
+
+ /**
+ * Generates the GLSL snippet for performing a depth comparison on four texels in the given depth texture.
+ *
+ * @param {Texture} texture - The texture.
+ * @param {string} textureProperty - The name of the texture uniform in the shader.
+ * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling.
+ * @param {string} compareSnippet - A GLSL snippet that represents the reference value.
+ * @param {?string} depthSnippet - A GLSL snippet that represents 0-based texture array index to sample.
+ * @param {?string} offsetSnippet - A GLSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
+ * @param {?string} flipYSnippet - A GLSL snippet that represents the y-flip. Only used for WebGL.
+ * @return {string} The GLSL snippet.
+ */
+ generateTextureGatherCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, offsetSnippet, flipYSnippet ) {
+
+ if ( offsetSnippet === null ) offsetSnippet = 'ivec2( 0 )';
+
+ if ( flipYSnippet === null ) flipYSnippet = 'false';
+
+ if ( depthSnippet ) {
+
+ this._include( 'textureGatherCompareArray' );
+
+ return `tsl_textureGatherCompare_array( ${ textureProperty }, vec3( ${ uvSnippet }, ${depthSnippet} ), ${ offsetSnippet }, ${ compareSnippet }, ${ flipYSnippet } )`;
+
+ }
+
+ this._include( 'textureGatherCompare' );
+
+ return `tsl_textureGatherCompare( ${ textureProperty }, ${ uvSnippet }, ${ offsetSnippet }, ${ compareSnippet }, ${ flipYSnippet } )`;
+
+ }
+
/**
* Returns the uniforms of the given shader stage as a GLSL string.
*
@@ -685,7 +811,8 @@ ${ flowData.code }
if ( uniform.type === 'texture' || uniform.type === 'texture3D' ) {
- const texture = uniform.node.value;
+ const textureNode = uniform.node;
+ const texture = textureNode.value;
let typePrefix = '';
@@ -707,7 +834,7 @@ ${ flowData.code }
snippet = `${typePrefix}sampler3D ${ uniform.name };`;
- } else if ( texture.compareFunction ) {
+ } else if ( texture.compareFunction && textureNode.compareNode !== null ) {
if ( texture.isArrayTexture === true ) {
diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js
index 0ffb056329cfb0..bb055354ebd5d9 100644
--- a/src/renderers/webgpu/WebGPUBackend.js
+++ b/src/renderers/webgpu/WebGPUBackend.js
@@ -1950,11 +1950,12 @@ class WebGPUBackend extends Backend {
* Updates a GPU sampler for the given texture.
*
* @param {Texture} texture - The texture to update the sampler for.
+ * @param {TextureNode} textureNode - The texture node to update the sampler with.
* @return {string} The current sampler key.
*/
- updateSampler( texture ) {
+ updateSampler( texture, textureNode ) {
- return this.textureUtils.updateSampler( texture );
+ return this.textureUtils.updateSampler( texture, textureNode );
}
diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js
index 45dc7fef7d8d22..b640bfb959f531 100644
--- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js
+++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js
@@ -352,7 +352,7 @@ class WGSLNodeBuilder extends NodeBuilder {
*/
generateWrapFunction( texture ) {
- const functionName = `tsl_coord_${ wrapNames[ texture.wrapS ] }S_${ wrapNames[ texture.wrapT ] }_${ texture.is3DTexture || texture.isData3DTexture ? '3d' : '2d' }T`;
+ const functionName = `tsl_coord_${ wrapNames[ texture.wrapS ] }S_${ wrapNames[ texture.wrapT ] }T_${ texture.is3DTexture || texture.isData3DTexture ? '3d' : '2d' }`;
let nodeCode = wgslCodeCache[ functionName ];
@@ -850,6 +850,80 @@ class WGSLNodeBuilder extends NodeBuilder {
}
+ /**
+ * Generates the WGSL snippet for gathering four texels from the given texture.
+ *
+ * @param {Texture} texture - The texture.
+ * @param {string} textureProperty - The name of the texture uniform in the shader.
+ * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling.
+ * @param {string} gatherSnippet - A WGSL snippet that represents the index of the channel to read.
+ * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample.
+ * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
+ * @param {?string} flipYSnippet - A WGSL snippet that represents the y-flip. Only used for WebGL.
+ * @return {string} The WGSL snippet.
+ */
+ generateTextureGather( texture, textureProperty, uvSnippet, gatherSnippet, depthSnippet, offsetSnippet ) {
+
+ const componentSnippet = texture.isDepthTexture === true ? '' : `${gatherSnippet}, `;
+
+ if ( depthSnippet ) {
+
+ if ( offsetSnippet ) {
+
+ return `textureGather( ${componentSnippet}${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ offsetSnippet } )`;
+
+ }
+
+ return `textureGather( ${componentSnippet}${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet } )`;
+
+ }
+
+ if ( offsetSnippet ) {
+
+ return `textureGather( ${componentSnippet}${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ offsetSnippet } )`;
+
+ }
+
+ return `textureGather( ${componentSnippet}${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet })`;
+
+ }
+
+ /**
+ * Generates the WGSL snippet for performing a depth comparison on four texels in the given depth texture.
+ *
+ * @param {Texture} texture - The texture.
+ * @param {string} textureProperty - The name of the texture uniform in the shader.
+ * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling.
+ * @param {string} compareSnippet - A WGSL snippet that represents the reference value.
+ * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample.
+ * @param {?string} offsetSnippet - A WGSL snippet that represents the offset that will be applied to the unnormalized texture coordinate before sampling the texture.
+ * @param {?string} flipYSnippet - A WGSL snippet that represents the y-flip. Only used for WebGL.
+ * @return {string} The WGSL snippet.
+ */
+ generateTextureGatherCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, offsetSnippet ) {
+
+ if ( depthSnippet ) {
+
+ if ( offsetSnippet ) {
+
+ return `textureGatherCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet }, ${ offsetSnippet } )`;
+
+ }
+
+ return `textureGatherCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet })`;
+
+ }
+
+ if ( offsetSnippet ) {
+
+ return `textureGatherCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ compareSnippet }, ${ offsetSnippet } )`;
+
+ }
+
+ return `textureGatherCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ compareSnippet })`;
+
+ }
+
/**
* Generates the WGSL snippet when sampling textures with explicit mip level.
*
@@ -1127,7 +1201,8 @@ class WGSLNodeBuilder extends NodeBuilder {
texture.setVisibility( gpuShaderStageLib[ shaderStage ] );
// Cube textures always need samplers (they use textureSampleLevel, not textureLoad)
- const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false );
+ // Also textureGather always need sampler.
+ const needsSampler = node.value.isCubeTexture === true || ( this.isUnfilterable( node.value ) === false && texture.store === false ) || node.gatherNode !== null;
if ( needsSampler ) {
@@ -1908,14 +1983,16 @@ ${ flowData.code }
if ( uniform.type === 'texture' || uniform.type === 'cubeTexture' || uniform.type === 'cubeDepthTexture' || uniform.type === 'storageTexture' || uniform.type === 'texture3D' ) {
- const texture = uniform.node.value;
+ const textureNode = uniform.node;
+ const texture = textureNode.value;
// Cube textures always need samplers (they use textureSampleLevel, not textureLoad)
- const needsSampler = texture.isCubeTexture === true || ( this.isUnfilterable( texture ) === false && uniform.node.isStorageTextureNode !== true );
+ // Also textureGather always need sampler.
+ const needsSampler = texture.isCubeTexture === true || ( this.isUnfilterable( texture ) === false && textureNode.isStorageTextureNode !== true ) || textureNode.gatherNode !== null;
if ( needsSampler ) {
- if ( this.isSampleCompare( texture ) ) {
+ if ( this.isSampleCompare( texture ) && textureNode.compareNode !== null ) {
bindingSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var ${ uniform.name }_sampler : sampler_comparison;` );
diff --git a/src/renderers/webgpu/utils/WebGPUBindingUtils.js b/src/renderers/webgpu/utils/WebGPUBindingUtils.js
index 7871c35c99ad18..8bf4dfadf0e454 100644
--- a/src/renderers/webgpu/utils/WebGPUBindingUtils.js
+++ b/src/renderers/webgpu/utils/WebGPUBindingUtils.js
@@ -533,7 +533,7 @@ class WebGPUBindingUtils {
if ( binding.texture.isDepthTexture ) {
- if ( binding.texture.compareFunction !== null && backend.hasCompatibility( Compatibility.TEXTURE_COMPARE ) ) {
+ if ( binding.texture.compareFunction !== null && binding.textureNode.compareNode !== null && backend.hasCompatibility( Compatibility.TEXTURE_COMPARE ) ) {
sampler.type = GPUSamplerBindingType.Comparison;
diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js
index 9905bfb0433589..4fbbefa933e40b 100644
--- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js
+++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js
@@ -125,15 +125,17 @@ class WebGPUTextureUtils {
* Creates a GPU sampler for the given texture.
*
* @param {Texture} texture - The texture to create the sampler for.
+ * @param {TextureNode} textureNode - The texture node to update the sampler with.
* @return {string} The current sampler key.
*/
- updateSampler( texture ) {
+ updateSampler( texture, textureNode ) {
const backend = this.backend;
const samplerKey = texture.minFilter + '-' + texture.magFilter + '-' +
texture.wrapS + '-' + texture.wrapT + '-' + ( texture.wrapR || '0' ) + '-' +
- texture.anisotropy + '-' + ( texture.compareFunction || 0 );
+ texture.anisotropy + '-' + ( texture.isDepthTexture === true ? 1 : 0 ) + '-' +
+ ( texture.compareFunction !== null && textureNode.compareNode !== null ? texture.compareFunction : 0 );
let samplerData = this._samplerCache.get( samplerKey );
@@ -150,7 +152,7 @@ class WebGPUTextureUtils {
};
// Depth textures without compare function must use non-filtering (nearest) sampling
- if ( texture.isDepthTexture && texture.compareFunction === null ) {
+ if ( texture.isDepthTexture && ( texture.compareFunction === null || textureNode.compareNode === null ) ) {
samplerDescriptorGPU.magFilter = GPUFilterMode.Nearest;
samplerDescriptorGPU.minFilter = GPUFilterMode.Nearest;
@@ -166,7 +168,7 @@ class WebGPUTextureUtils {
}
- if ( texture.isDepthTexture && texture.compareFunction !== null && backend.hasCompatibility( Compatibility.TEXTURE_COMPARE ) ) {
+ if ( texture.isDepthTexture && texture.compareFunction !== null && textureNode.compareNode !== null && backend.hasCompatibility( Compatibility.TEXTURE_COMPARE ) ) {
samplerDescriptorGPU.compare = _compareToWebGPU[ texture.compareFunction ];