Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/codeql-code-scanning.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql-config.yml
queries: security-and-quality

- name: Autobuild
uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/autobuild@e46ed2cbd01164d986452f91f178727624ae40d7 # v4

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4
with:
category: "/language:${{matrix.language}}"
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@
"webgpu_storage_buffer",
"webgpu_struct_drawindirect",
"webgpu_test_memory",
"webgpu_texturegather",
"webgpu_texturegrad",
"webgpu_textures_2d-array",
"webgpu_textures_2d-array_compressed",
Expand Down
Binary file added examples/screenshots/webgpu_texturegather.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
183 changes: 183 additions & 0 deletions examples/webgpu_texturegather.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<html lang="en">
<head>
<title>three.js webgpu - texture gather</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="example.css">
</head>
<body>

<div id="info">
<a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>

<div class="title-wrapper">
<a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>Texture Gather</span>
</div>

<small>
This example demonstrates texture gather
<br /> Left canvas is using WebGPU Backend, right canvas is WebGL Backend.
<br /> The top half gathers color values and the bottom half gathers depth comparison.
</small>
</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/webgpu';
import { If, vec4, uv, ivec2, texture, Fn } from 'three/tsl';

// WebGPU Backend
init();

// WebGL Backend
init( true );

async function init( forceWebGL = false ) {

const aspect = ( window.innerWidth / 2 ) / window.innerHeight;
const camera = new THREE.OrthographicCamera( - aspect, aspect );
camera.position.z = 2;

const scene = new THREE.Scene();

// texture

const material = new THREE.MeshBasicNodeMaterial( { color: 0xffffff } );

const colorNode = texture();
const depthNode = texture();

material.colorNode = Fn( () => {

const color = vec4( 1 ).toVar();

const vuv = uv().toVar();

If( vuv.y.greaterThan( 0.5 ), () => {

color.assign(
colorNode.sample( vuv.mul( 10 ) ).offset( ivec2( 0, 7 ) ).gather( 0 )
);

} ).Else( () => {

color.assign(
depthNode.sample( vuv ).offset( ivec2( 0, 7 ) ).gather( 0 ).compare( 1 )
);

} );

return color;

} )();

//

const plane = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), material );
scene.add( plane );

const renderer = new THREE.WebGPURenderer( { antialias: false, forceWebGL: forceWebGL } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth / 2, window.innerHeight );
renderer.setAnimationLoop( animate );
await renderer.init();

document.body.appendChild( renderer.domElement );
renderer.domElement.style.position = 'absolute';
renderer.domElement.style.top = '0';
renderer.domElement.style.left = '0';
renderer.domElement.style.width = '50%';
renderer.domElement.style.height = '100%';

if ( forceWebGL ) {

renderer.domElement.style.left = '50%';

scene.background = new THREE.Color( 0x212121 );

} else {

scene.background = new THREE.Color( 0x313131 );

}

//

const depthTexture = new THREE.DepthTexture();
depthTexture.compareFunction = THREE.LessEqualCompare;
const rt = new THREE.RenderTarget( 100, 100, {
depthTexture,
generateMipmaps: true,
minFilter: THREE.LinearMipmapLinearFilter
} );
rt.texture.wrapS = THREE.RepeatWrapping;
rt.texture.wrapT = THREE.RepeatWrapping;

const cameraZ = 2.5;
const rtScene = new THREE.Scene();
rtScene.background = new THREE.Color( 0x808080 );
const rtCamera = new THREE.PerspectiveCamera( 50, 1, cameraZ - 0.5 * Math.sqrt( 3 ), cameraZ + 0.5 * Math.sqrt( 3 ) );
rtCamera.position.z = cameraZ;

const dirLight = new THREE.DirectionalLight();
dirLight.position.set( 1, 1, 0 );
rtScene.add( dirLight );
rtScene.add( new THREE.AmbientLight( 0xffffff, 0.1 ) );

const box = new THREE.Mesh(
new THREE.BoxGeometry(),
new THREE.MeshStandardNodeMaterial( { color: 0xff0000 } )
);
box.rotation.set( Math.PI / 4, Math.PI / 4, 0 );
rtScene.add( box );

renderer.setRenderTarget( rt );
renderer.render( rtScene, rtCamera );
renderer.setRenderTarget( null );

colorNode.value = rt.texture;
depthNode.value = rt.depthTexture;

//

function animate() {

renderer.render( scene, camera );

}

window.addEventListener( 'resize', onWindowResize );

function onWindowResize() {

renderer.setSize( window.innerWidth / 2, window.innerHeight );

const aspect = ( window.innerWidth / 2 ) / window.innerHeight;

const frustumHeight = camera.top - camera.bottom;

camera.left = - frustumHeight * aspect / 2;
camera.right = frustumHeight * aspect / 2;

camera.updateProjectionMatrix();

renderer.render( scene, camera );

}

}

</script>
</body>
</html>
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 60 additions & 5 deletions src/nodes/accessors/TextureNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>}
* @default null
*/
this.gatherNode = null;

/**
* Represents the optional texel offset applied to the unnormalized texture
* coordinate before sampling the texture.
Expand Down Expand Up @@ -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 ) {

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -471,10 +487,12 @@ class TextureNode extends UniformNode {
* @param {?string} depthSnippet - The depth snippet.
* @param {?string} compareSnippet - The compare snippet.
* @param {?Array<string>} 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;

Expand All @@ -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 );
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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 ) {

Expand Down Expand Up @@ -772,6 +810,22 @@ class TextureNode extends UniformNode {

}

/**
* Gathers four texels from the texture.
*
* @param {[Node<int>]} 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.
*
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/nodes/display/PassNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading