Skip to content

Conversation

@RenaudRohlinger
Copy link
Collaborator

@RenaudRohlinger RenaudRohlinger commented Nov 21, 2025

Related issue: #32295 (comment)

Description
Implemented a generic overrideContext mechanism in TSL. This allows replacing specific nodes within a context, similar to
replaceDefaultUV but applicable to any node.

Usage example:

const visualizeNormal = Fn( () => {
    return normalLocal.normalize().mul( 0.5 ).add( 0.5 );
} );
// Override normalLocal with a custom vector
const customNormal = vec3( 0, 1, 0 );
const material = new MeshBasicNodeMaterial();
material.colorNode = overrideContext( visualizeNormal(), normalLocal, customNormal );

Added webgpu_tsl_override_context demonstrating normalLocal override on instanced meshes, where normals are transformed (rotated) per instance via the context override.
image

This contribution is funded by Utsubo & Threejs Blocks

Edit:
Ultimately I want to be able to inline to the stack instead of returning a new node, Almost got it but did hit a quota limit so will wait tomorrow:

material.colorNode = Fn(() => {

	overrideContext( visualizeNormal(), normalLocal, instanceNormal );
	overrideContext( darken(), materialColor, something );

	return materialColor.mul(normalLocal.normalize().mul( 0.5 ).add( 0.5 ))
})()

@github-actions
Copy link

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 356.97
86.61
356.97
86.61
+0 B
+0 B
WebGPU 618.37
173.6
618.6
173.69
+236 B
+90 B
WebGPU Nodes 616.97
173.35
617.21
173.44
+236 B
+89 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 488.7
121.39
488.7
121.39
+0 B
+0 B
WebGPU 690.41
189.47
690.61
189.55
+205 B
+82 B
WebGPU Nodes 631.82
172.64
632.02
172.72
+205 B
+79 B

<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.
@sunag
Copy link
Collaborator

sunag commented Nov 21, 2025

I also implemented it, but hadn't committed it: 37daca1
The implementation is quite similar...

Ultimately I want to be able to inline to the stack instead of returning a new node, Almost got it but did hit a quota limit so will wait tomorrow:

I think context substitution should be done by the node flow, or globally via material.contextNode. This way it can conflict with other functions that use this syntax after other function use it, non respecting the flow, only consider the last substitution.

This would be the syntax I was thinking of proposing.

material.contextNode = overrideNode( node, callback );
// or
material.colorNode = someNode.overrideNode( positionLocal, () => positionLocal.mul( .5 ) );

Implemented a generic overrideContext mechanism in TSL. This allows replacing specific nodes within a context, similar to
replaceDefaultUV but applicable to any node.

I have some fears about global replacements; they can be a short-term solution if used as a standard, so I'd like to know more use cases where this is indispensable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants