How to exit early from a TSL node?

Is there a way to return a value if a condition is meet before the final return statement? There is an If node, but I’m not sure if it is possible to define the output if the condition is meet. Returning a value in the If condition callback seems to expect a type OutputStruct, but I’m not finding any information about it.

I imagine something like this:

const colorNode = tslFn(() => {
    If( active.not(), () => {
        earlyColor = texture(texture, uv())
        return earlyColor; // Color if condition is meet
    } )

    return finalColor; // Color if not
})

The standard way to do this in TSL isn’t to “return early” like you describe, but rather to conditionally assign a local variable, and then return that variable. You can turn a node into an “assignable” local variable in the shader by calling node.toVar():

const colorNode = Fn(() => {
    const returnVal = vec3().toVar();
    If(active.not(), () => {
        returnVal.assign(texture(texture, uv()));
    }).Else(() => {
        // I'm assuming "finalColor" comes
        // from some other scope
        returnVal.assign(finalColor);
    });
    return returnVal;
});

You can see this pattern repeated all over in the three.js library.
For example, in ParallaxBarrierPassNode (check out the parallaxBarrier node):

class ParallaxBarrierPassNode extends StereoCompositePassNode {
    static get type() {
        return 'ParallaxBarrierPassNode';
    }

    constructor(scene, camera) {
        super(scene, camera);
        this.isParallaxBarrierPassNode = true;
    }

    setup(builder) {
        const uvNode = uv();
        const parallaxBarrier = Fn(() => {
            const color = vec4().toVar();
            If(mod(screenCoordinate.y, 2).greaterThan(1), () => {
                color.assign(this._mapLeft.uv(uvNode));
            }).Else(() => {
                color.assign(this._mapRight.uv(uvNode));
            });
            return color;
        });
        const material = this._material || (this._material = new NodeMaterial());
        material.fragmentNode = parallaxBarrier().context(builder.getSharedContext());
        material.needsUpdate = true;
        return super.setup(builder);
    }
}
4 Likes

Beautiful, thank you! I tried something like that but was missing .toVar and .assign.

Also, for some reason I get that If(...).Else is not a function, but If(...).else (note the lower case e) works, using three v0.169.0, importing If from three/examples/jsm/nodes/Nodes.js

You’re welcome. I believe the “else” and “else if” functions have been changed to uppercase .Else() and .ElseIf() in more recent versions of three.js. See this excerpt from THREE.StackNode below:

else(...params) { // @deprecated, r168
    console.warn('TSL.StackNode: .else() has been renamed to .Else().');
    return this.Else(...params);
}

elseif(...params) { // @deprecated, r168
    console.warn('TSL.StackNode: .elseif() has been renamed to .ElseIf().');
    return this.ElseIf(...params);
}

Some other changes are listed here:
Why my TSL code for r167 does not work with r168?

1 Like