Skip to content
Open
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
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@
"webgpu_tsl_galaxy",
"webgpu_tsl_halftone",
"webgpu_tsl_interoperability",
"webgpu_tsl_override_context",
"webgpu_tsl_procedural_terrain",
"webgpu_tsl_raging_sea",
"webgpu_tsl_transpiler",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
136 changes: 136 additions & 0 deletions examples/webgpu_tsl_override_context.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Three.js webgpu - tsl override context</title>
<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">three.js</a> - WebGPU - TSL Override Context<br />
Left: Default Normals | Right: Overridden Normals (Rotated via Context)
</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 { float, vec3, vec4, mat4, normalLocal, positionLocal, Fn, overrideContext, instanceIndex, transformNormal } from 'three/tsl';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import float.

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

let camera, scene, renderer;

init();

function init() {

camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.z = 10;

scene = new THREE.Scene();

// TSL Function that uses normalLocal
const visualizeNormal = Fn( () => {

return normalLocal.normalize().mul( 0.5 ).add( 0.5 );

} );

const geometry = new THREE.SphereGeometry( 0.5, 32, 16 );
const count = 5;

// 1. Left Mesh: Instanced, Vertical Stack, Default Normals
const material1 = new THREE.MeshBasicNodeMaterial();

// Move instances in Y based on instanceIndex
const offset1 = vec3( 0, instanceIndex.toFloat().mul( 1.5 ).sub( 3 ), 0 );
material1.positionNode = positionLocal.add( offset1 );

material1.colorNode = visualizeNormal();

const mesh1 = new THREE.Mesh( geometry, material1 );
mesh1.position.x = - 2;
mesh1.count = count; // Enable instancing
scene.add( mesh1 );

// 2. Right Mesh: Instanced, Vertical Stack, Overridden Normals
const material2 = new THREE.MeshBasicNodeMaterial();

// Move instances in Y based on instanceIndex
const y = instanceIndex.toFloat().mul( 1.5 ).sub( 3 );
const offset2 = vec3( 0, y, 0 );
material2.positionNode = positionLocal.add( offset2 );

// Construct instance matrix with rotation to demonstrate normal transformation
const angle = instanceIndex.toFloat().mul( 0.5 );
const s = angle.sin();
const c = angle.cos();

// Rotation around Y axis + Translation
const instanceMatrixNode = mat4(
vec4( c, 0, s, 0 ),
vec4( 0, 1, 0, 0 ),
vec4( s.negate(), 0, c, 0 ),
vec4( 0, y, 0, 1 )
);

// Transform normalLocal using the instance matrix
// Note: Since it's a pure translation, the normal direction won't change,
// but this demonstrates the mechanism.
const instanceNormal = transformNormal( normalLocal, instanceMatrixNode );

// Override normalLocal in the visualizeNormal function context
material2.colorNode = overrideContext( visualizeNormal(), normalLocal, instanceNormal );

const mesh2 = new THREE.Mesh( geometry, material2 );
mesh2.position.x = 2;
mesh2.count = count; // Enable instancing
scene.add( mesh2 );

//

renderer = new THREE.WebGPURenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
document.body.appendChild( renderer.domElement );

const controls = new OrbitControls( camera, renderer.domElement );
controls.enableDamping = true;

window.addEventListener( 'resize', onWindowResize );

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

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

}

function animate() {

renderer.render( scene, camera );

}

</script>

</body>
</html>
1 change: 1 addition & 0 deletions src/Three.TSL.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export const compute = TSL.compute;
export const computeKernel = TSL.computeKernel;
export const computeSkinning = TSL.computeSkinning;
export const context = TSL.context;
export const overrideContext = TSL.overrideContext;
export const convert = TSL.convert;
export const convertColorSpace = TSL.convertColorSpace;
export const convertToTexture = TSL.convertToTexture;
Expand Down
19 changes: 19 additions & 0 deletions src/nodes/core/ContextNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,26 @@ export function label( node, name ) {

}

/**
* TSL function for overriding a context for a given node.
*
* @tsl
* @function
* @param {Node} node - The node whose context should be modified.
* @param {Node} targetNode - The node that will be replaced.
* @param {Node} sourceNode - The node that will replace the targetNode.
* @returns {ContextNode}
*/
export const overrideContext = ( node, targetNode, sourceNode ) => {

const cleanSource = context( sourceNode, { [ targetNode.uuid ]: undefined } );

return context( node, { [ targetNode.uuid ]: cleanSource } );

};

addMethodChaining( 'context', context );
addMethodChaining( 'label', label );
addMethodChaining( 'uniformFlow', uniformFlow );
addMethodChaining( 'setName', setName );
addMethodChaining( 'overrideContext', overrideContext );
10 changes: 10 additions & 0 deletions src/nodes/core/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,16 @@ class Node extends EventDispatcher {
*/
build( builder, output = null ) {

const nodeFromContext = builder.context[ this.uuid ];

if ( nodeFromContext !== undefined ) {

return nodeFromContext.build( builder, output );

}

//

const refNode = this.getShared( builder );

if ( this !== refNode ) {
Expand Down
8 changes: 8 additions & 0 deletions src/nodes/core/VarNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,14 @@ class VarNode extends Node {

const builder = params[ 0 ];

const nodeFromContext = builder.context[ this.uuid ];

if ( nodeFromContext !== undefined ) {

return nodeFromContext.build( ...params );

}

if ( this._hasStack( builder ) === false && builder.buildStage === 'setup' ) {

if ( builder.context.nodeLoop || builder.context.nodeBlock ) {
Expand Down