Having a hard time figuring out TSL for WebGPU support

Hi ! I have been trying to update my code running with GLSL to TSL to support WebGPU on my three.js project. And so three js would be able to either compile it into GLSL or WGSL. The performance gain is welcome.
I need to update my code because WebGPU just ignores my custom shader since GLSL is not supported with it, although it works perfectly fine with WebGL.
Translating a GLSL shader code to TSL is unfortunately not so trivial and I’ve been struggling a lot to use it and get a clean result I can fully grasp.

For reference my custom shader is an implementation of dual quaternion skinning (with some scaling support).

I’ve been using three.js WebGPU-TSL-transpiler as a reference even if it seems to sometimes make mistakes.

GLSL source code (without the generic #<includes>)
uniform vec4 aq0[146];
uniform vec4 aq1[146];
uniform vec3 aqScale[146];

mat4 DQToMatrix(vec4 Qn, vec4 Qd) {
	mat4 M = mat4(0.0);
	float len2 = dot(Qn, Qn);
	float w = Qn.w, x = Qn.x, y = Qn.y, z = Qn.z;
	float t0 = Qd.w, t1 = Qd.x, t2 = Qd.y, t3 = Qd.z;

	M[0][0] = w*w + x*x - y*y - z*z;
	M[1][0] = 2.0*x*y - 2.0*w*z;
	M[2][0] = 2.0*x*z + 2.0*w*y;
	M[0][1] = 2.0*x*y + 2.0*w*z;
	M[1][1] = w*w + y*y - x*x - z*z;
	M[2][1] = 2.0*y*z - 2.0*w*x;
	M[0][2] = 2.0*x*z - 2.0*w*y;
	M[1][2] = 2.0*y*z + 2.0*w*x;
	M[2][2] = w*w + z*z - x*x - y*y;

	M[3][0] = -2.0*t0*x + 2.0*w*t1 - 2.0*t2*z + 2.0*y*t3;
	M[3][1] = -2.0*t0*y + 2.0*t1*z - 2.0*x*t3 + 2.0*w*t2;
	M[3][2] = -2.0*t0*z + 2.0*x*t2 + 2.0*w*t3 - 2.0*t1*y;

	M /= len2;
	M[3][3] = 1.0;

	return M;
}

void main() {
	vec4 dq0[4], dq1[4];
	dq0[0] = aq0[int(skinIndex.x)];
	dq1[0] = aq1[int(skinIndex.x)];
	dq0[1] = aq0[int(skinIndex.y)];
	dq1[1] = aq1[int(skinIndex.y)];
	dq0[2] = aq0[int(skinIndex.z)];
	dq1[2] = aq1[int(skinIndex.z)];
	dq0[3] = aq0[int(skinIndex.w)];
	dq1[3] = aq1[int(skinIndex.w)];

	for (int i = 1; i < 4; ++i) {
		if (dot(dq0[0], dq0[i]) < 0.0) {
			dq0[i] *= -1.0;
			dq1[i] *= -1.0;
		}
	}

	vec4 blend_q0 = dq0[0]*skinWeight.x + dq0[1]*skinWeight.y + dq0[2]*skinWeight.z + dq0[3]*skinWeight.w;
	vec4 blend_q1 = dq1[0]*skinWeight.x + dq1[1]*skinWeight.y + dq1[2]*skinWeight.z + dq1[3]*skinWeight.w;

	mat4 skinMat = DQToMatrix(blend_q0, blend_q1);

	vec3 blendedScale =
	aqScale[int(skinIndex.x)] * skinWeight.x +
	aqScale[int(skinIndex.y)] * skinWeight.y +
	aqScale[int(skinIndex.z)] * skinWeight.z +
	aqScale[int(skinIndex.w)] * skinWeight.w;

	vec3 pos = (skinMat * vec4(transformed, 1.0)).xyz;
	transformed = blendedScale * pos;

	vec3 norm = (skinMat * vec4(objectNormal, 0.0)).xyz;
	objectNormal = normalize(norm / blendedScale);
}

Below is all my JS code that injects the DQS to my meshes materials.

My DQS implementation
import * as THREE from 'three';

export function enableDQS(skinnedMeshList) {
    skinnedMeshList.forEach((mesh) => {
        if (mesh.userData.ready) return;

        const maxBones = mesh.skeleton.bones.length;
        const aq0 = new Float32Array(4 * maxBones);
        const aq1 = new Float32Array(4 * maxBones);
        const aqScale = new Float32Array(3 * maxBones);

        mesh.userData.dqsUniforms = { aq0, aq1, aqScale };
        mesh.userData.ready = true;

        mesh.material.onBeforeCompile = (shader) => {
            shader.uniforms.aq0 = { value: aq0 };
            shader.uniforms.aq1 = { value: aq1 };
            shader.uniforms.aqScale = { value: aqScale };

            shader.vertexShader = shader.vertexShader
                .replace(
                    `#include <common>`,
                    `#include <common>
                    uniform vec4 aq0[${maxBones}];
                    uniform vec4 aq1[${maxBones}];
                    uniform vec3 aqScale[${maxBones}];`
                )
                .replace(
                    '#include <skinning_vertex>',
                    `
                    vec4 dq0[4], dq1[4];
                    dq0[0] = aq0[int(skinIndex.x)];
                    dq1[0] = aq1[int(skinIndex.x)];
                    dq0[1] = aq0[int(skinIndex.y)];
                    dq1[1] = aq1[int(skinIndex.y)];
                    dq0[2] = aq0[int(skinIndex.z)];
                    dq1[2] = aq1[int(skinIndex.z)];
                    dq0[3] = aq0[int(skinIndex.w)];
                    dq1[3] = aq1[int(skinIndex.w)];

                    for (int i = 1; i < 4; ++i) {
                    if (dot(dq0[0], dq0[i]) < 0.0) {
                        dq0[i] *= -1.0;
                        dq1[i] *= -1.0;
                    }
                    }

                    vec4 blend_q0 = dq0[0]*skinWeight.x + dq0[1]*skinWeight.y + dq0[2]*skinWeight.z + dq0[3]*skinWeight.w;
                    vec4 blend_q1 = dq1[0]*skinWeight.x + dq1[1]*skinWeight.y + dq1[2]*skinWeight.z + dq1[3]*skinWeight.w;

                    mat4 skinMat = DQToMatrix(blend_q0, blend_q1);

                    vec3 blendedScale =
                    aqScale[int(skinIndex.x)] * skinWeight.x +
                    aqScale[int(skinIndex.y)] * skinWeight.y +
                    aqScale[int(skinIndex.z)] * skinWeight.z +
                    aqScale[int(skinIndex.w)] * skinWeight.w;

                    vec3 pos = (skinMat * vec4(transformed, 1.0)).xyz;
                    transformed = blendedScale * pos;

                    vec3 norm = (skinMat * vec4(objectNormal, 0.0)).xyz;
                    objectNormal = normalize(norm / blendedScale);
                `
                )
                .replace(
                    'void main() {',
                    `
                    mat4 DQToMatrix(vec4 Qn, vec4 Qd) {
                        mat4 M = mat4(0.0);
                        float len2 = dot(Qn, Qn);
                        float w = Qn.w, x = Qn.x, y = Qn.y, z = Qn.z;
                        float t0 = Qd.w, t1 = Qd.x, t2 = Qd.y, t3 = Qd.z;

                        M[0][0] = w*w + x*x - y*y - z*z;
                        M[1][0] = 2.0*x*y - 2.0*w*z;
                        M[2][0] = 2.0*x*z + 2.0*w*y;
                        M[0][1] = 2.0*x*y + 2.0*w*z;
                        M[1][1] = w*w + y*y - x*x - z*z;
                        M[2][1] = 2.0*y*z - 2.0*w*x;
                        M[0][2] = 2.0*x*z - 2.0*w*y;
                        M[1][2] = 2.0*y*z + 2.0*w*x;
                        M[2][2] = w*w + z*z - x*x - y*y;

                        M[3][0] = -2.0*t0*x + 2.0*w*t1 - 2.0*t2*z + 2.0*y*t3;
                        M[3][1] = -2.0*t0*y + 2.0*t1*z - 2.0*x*t3 + 2.0*w*t2;
                        M[3][2] = -2.0*t0*z + 2.0*x*t2 + 2.0*w*t3 - 2.0*t1*y;

                        M /= len2;
                        M[3][3] = 1.0;

                        return M;
                    }

                    void main() {
                    `
                );
        };

        mesh.material.skinning = false;
        mesh.material.morphTargets = true;
        mesh.material.needsUpdate = true;
    });
}
export function updateDQS(skinnedMeshList) {
    skinnedMeshList.forEach((mesh) => {
        if (!mesh.userData.ready) return;
        for (let i = 0; i < mesh.skeleton.bones.length; i++) {
            const bone = mesh.skeleton.bones[i];

            const boneMatrix = new THREE.Matrix4().multiplyMatrices(
                bone.matrixWorld,
                mesh.skeleton.boneInverses[i]
            );

            const t = new THREE.Vector3();
            const q = new THREE.Quaternion();
            const s = new THREE.Vector3();

            boneMatrix.decompose(t, q, s);

            const tQuat = new THREE.Quaternion(t.x, t.y, t.z, 0);
            const d = tQuat.clone().multiply(q);
            d.x *= 0.5;
            d.y *= 0.5;
            d.z *= 0.5;
            d.w *= 0.5;

            mesh.userData.dqsUniforms.aq0.set([q.x, q.y, q.z, q.w], i * 4);
            mesh.userData.dqsUniforms.aq1.set([d.x, d.y, d.z, d.w], i * 4);
            mesh.userData.dqsUniforms.aqScale.set([s.x, s.y, s.z], i * 3);
        }
    });
}

That code works without much issues.
Using Three.js Shading Language · mrdoob/three.js Wiki · GitHub for documentation, I’m trying to upgrade it to TSL. But every second line I get an error and something to fix. Attributes like skinIndex and skinWeight also proved difficult to use. The TSL approach is vastly different that the GLSL and I’m lost on how properly transmit the data to the vertex shader and let alone have a NodeMaterial that compiles successfully. I know I have to inject my vertex shader into the VertexNode attribute from a valid NodeMaterial using some Fn() function from TSL but after many tries I never got something that could compile. I’m also aware that I will certainly have to reconfigure my materials with NodeMaterial instances, but that’s an issue for later as I can’t make it work with a basic and fresh node material.

So if anyone feels like giving me some guidance on this endeavour, I would be extremely grateful.

Edit: Fixed confusion between GLSL and WGSL

Messing around with TSL and tweaking the code I can get from the online transpiler, I’m here:

const aq0 = uniform( new THREE.Quaternion() ).label('aq0');
const aq1 = uniform( new THREE.Quaternion() ).label('aq1');
const aqScale = uniform( new THREE.Vector3() ).label('aqScale');

const DQToMatrix = /*@__PURE__*/ Fn( ( [ Qn, Qd ] ) => {
    const M = mat4( 0.0 ).toVar();
    const len2 = dot( Qn, Qn );
    const w = Qn.w, x = Qn.x, y = Qn.y, z = Qn.z;
    const t0 = Qd.w, t1 = Qd.x, t2 = Qd.y, t3 = Qd.z;
    M[ 0 ][ 0 ].assign( w.mul( w ).add( x.mul( x ).sub( y.mul( y ) ).sub( z.mul( z ) ) ) );
    M[ 1 ][ 0 ].assign( mul( 2.0, x ).mul( y ).sub( mul( 2.0, w ).mul( z ) ) );
    M[ 2 ][ 0 ].assign( mul( 2.0, x ).mul( z ).add( mul( 2.0, w ).mul( y ) ) );
    M[ 0 ][ 1 ].assign( mul( 2.0, x ).mul( y ).add( mul( 2.0, w ).mul( z ) ) );
    M[ 1 ][ 1 ].assign( w.mul( w ).add( y.mul( y ).sub( x.mul( x ) ).sub( z.mul( z ) ) ) );
    M[ 2 ][ 1 ].assign( mul( 2.0, y ).mul( z ).sub( mul( 2.0, w ).mul( x ) ) );
    M[ 0 ][ 2 ].assign( mul( 2.0, x ).mul( z ).sub( mul( 2.0, w ).mul( y ) ) );
    M[ 1 ][ 2 ].assign( mul( 2.0, y ).mul( z ).add( mul( 2.0, w ).mul( x ) ) );
    M[ 2 ][ 2 ].assign( w.mul( w ).add( z.mul( z ).sub( x.mul( x ) ).sub( y.mul( y ) ) ) );
    M[ 3 ][ 0 ].assign( mul( - 2.0, t0 ).mul( x ).add( mul( 2.0, w ).mul( t1 ).sub( mul( 2.0, t2 ).mul( z ) ) ).add( mul( 2.0, y ).mul( t3 ) ) );
    M[ 3 ][ 1 ].assign( mul( - 2.0, t0 ).mul( y ).add( mul( 2.0, t1 ).mul( z ).sub( mul( 2.0, x ).mul( t3 ) ) ).add( mul( 2.0, w ).mul( t2 ) ) );
    M[ 3 ][ 2 ].assign( mul( - 2.0, t0 ).mul( z ).add( mul( 2.0, x ).mul( t2 ) ).add( mul( 2.0, w ).mul( t3 ).sub( mul( 2.0, t1 ).mul( y ) ) ) );
    M.divAssign( len2 );
    M[ 3 ][ 3 ].assign( 1.0 );

    return M;
});

const main = /*@__PURE__*/ Fn( () => {
    const dq0 = property( new THREE.Quaternion() ), dq1 = property( new THREE.Quaternion() );
    dq0[ 0 ].assign( aq0.element( int( attribute('skinIndex').x ) ) );
    dq1[ 0 ].assign( aq1.element( int( attribute('skinIndex').x ) ) );
    dq0[ 1 ].assign( aq0.element( int( attribute('skinIndex').y ) ) );
    dq1[ 1 ].assign( aq1.element( int( attribute('skinIndex').y ) ) );
    dq0[ 2 ].assign( aq0.element( int( attribute('skinIndex').z ) ) );
    dq1[ 2 ].assign( aq1.element( int( attribute('skinIndex').z ) ) );
    dq0[ 3 ].assign( aq0.element( int( attribute('skinIndex').w ) ) );
    dq1[ 3 ].assign( aq1.element( int( attribute('skinIndex').w ) ) );
    Loop( { start: 1, end: 4 }, ( { i } ) => {

        If( dot( dq0[ 0 ], dq0.element( i ) ).lessThan( 0.0 ), () => {

            dq0.element( i ).mulAssign( - 1.0 );
            dq1.element( i ).mulAssign( - 1.0 );

        } );

    } );
    const blend_q0 = dq0[ 0 ].mul( attribute('skinWeight').x ).add( dq0[ 1 ].mul( attribute('skinWeight').y ) ).add( dq0[ 2 ].mul( attribute('skinWeight').z ) ).add( dq0[ 3 ].mul( attribute('skinWeight').w ) );
    const blend_q1 = dq1[ 0 ].mul( attribute('skinWeight').x ).add( dq1[ 1 ].mul( attribute('skinWeight').y ) ).add( dq1[ 2 ].mul( attribute('skinWeight').z ) ).add( dq1[ 3 ].mul( attribute('skinWeight').w ) );
    const skinMat = DQToMatrix( blend_q0, blend_q1 );
    const blendedScale = aqScale.element( int( attribute('skinIndex').x ) ).mul( attribute('skinWeight').x ).add( aqScale.element( int( attribute('skinIndex').y ) ).mul( attribute('skinWeight').y ) ).add( aqScale.element( int( attribute('skinIndex').z ) ).mul( attribute('skinWeight').z ) ).add( aqScale.element( int( attribute('skinIndex').w ) ).mul( attribute('skinWeight').w ) );
    const pos = skinMat.mul( vec4( positionGeometry, 1.0 ) ).xyz;
    positionGeometry.assign( blendedScale.mul( pos ) );
    const norm = skinMat.mul( vec4( normalGeometry, 0.0 ) ).xyz;
    normalGeometry.assign( normalize( norm.div( blendedScale ) ) );
});

const mat = new THREE.MeshStandardNodeMaterial();
mat.vertexNode = main();
//for now overrides completely the original material for testing
mesh.material = mat;

It doesn’t work, debugging TSL is not super convenient, I’m getting errors like this one : How to use vertexNode of NodeMaterial - Questions - three.js forum
I’m also getting Error: Uniform "null" not declared this one sounds a little bit silly to me.

I’m pretty sure I’m not so far from a working code but without any example online to help me check what I’m doing with this VertexNode I’m not really making progress :confused:

Hey mate, can you make a codesandbox project, so I can take a look at this code

Hi! Well codesandbox.io crashed on me after 10min and was not nice with me D: Anyways, here is a simple code setup that is analogue to my app. I made a quick model that visually shows if the dual quaternion skinning from the shader is active or not.

CodeSandbox Demo here

As I said I can’t seem to find ways to make the TSL code work, it’s maybe still far from the correct TSL. There is a quick toggle on top of main.js as well as the imports to comment/uncomment to enable webgpu. Also I recommend turning off updateDQS() in the animation loop until enableDQS() is debugged.

Thanks for the help!

1 Like

Ok I had to delete my previous message, it was the wrong code sorry about that, but yeh so I did some testing but I wasn’t able to fix it with TSL so I had vertex shader inject to the MeshStandardNodeMaterial onBeforeCompile and it works with webgpu, but I can’t call it’s a “FIX”.
You can find it here
Sorry Mate

1 Like

Well, thanks for trying! We would really need to find a working example of an advanced vertexNode shader to build upon. Or maybe it’s just not possible to make such shader in TSL yet?

Also I’m sorry but the solution you concluded on is basically the default behaviour of webGPU, it ignores the shader because it doesn’t compute GLSL. You can see if you compare side by side my initial demo and your result, the mesh is not deformed with quaternions but with the regular matrix transforms instead and looses volume.

I am thinking about manually translating the shader to WGSL. (I don’t know so much about WGSL but there are many resources online that can help me. A lot more than with TSL at least.)
I think I read that I can make it so my GLSL shader would be set as fallback if webGPU is not loaded and by default use this WGSL code.

I’m not a big fan of this solution for these reasons:

  • TSL is meant for this scenario to begin with
  • TSL will be certainly a futureproof solution as we can easily imagine that if new shader languages or engines become available later, TSL would still be the “universal shader language” and bridge them in three.js

Although, on the other side, making it directly WGSL would have this advantage:

  • If I want to use my shader in another program using webGPU without three.js, my DQS shader would already be ready to use

Yeh, this is actually first time I tried TSL. So I asked Claude about the TSL conversion but the solutions he gave just throwing an error called “undefined mulAssign” and then I started going through mrdoobs TSL Guide but that didn’t really help either. So I end up with that vertex shader inject before compile. And yes that code just get ignored by WebGPU. I think prisoner849 could help on this one. I’m not sure tho.

Oh that sounds like a good idea, I think with WGSL you’ll have full control. Wish you luck mate!

Here is my humble suggestion.

In situations like this there are two approaches. The top-down approach (translating a complete working shader into TSL) failed. The issue here is that there might be many co-existing problems in the code and you need to fix all of them to get a working TSL.

My suggestion is to try the bottom-up approach, which I find easier when I work with TSL. Start with an empty TSL and gradually add features until you reach the functional level of the shader. Thus, in case of a problem, it would be easier to identify the cause.

Additionally, sometimes I use the .debug() method to see how a TSL expression is compiled. This helped me in a few situations so far.

3 Likes

Well after more trials & errors I think I finally managed to debug most of the TSL.
I’m pretty sure, my uniforms are well defined, the code reads.
>The codesandbox has been updated here<
The is no more critical errors. Although there is one issue still that I’m not sure how to deal with. That’s why the mesh doesn’t show at the moment. It doesn’t show in the codeSandbox console. But I get this warning :

Error while parsing WGSL: :170:20 error: unable to parse right side of assignment
  varyings.Vertex = ;
                    ^


 - While calling [Device].CreateShaderModule([ShaderModuleDescriptor ""vertex""]).

Followed later by:

[Invalid ShaderModule "vertex"] is invalid.
 - While validating vertex stage ([Invalid ShaderModule "vertex"], entryPoint: "main").
 - While validating vertex state.
 - While calling [Device].CreateRenderPipeline([RenderPipelineDescriptor ""renderPipeline_NodeMaterial_691""]).

I seems fairly fixable, although I’m not sure how to do so. My understanding is that when TSL generates the WGSL, it doesn’t find some kind of declaration in the vertexNode pointing to the modified vertex data or just looses it. I think it has to do with the final lines for the shader when applying the results to positionGeometry and normalGeometry.

Well now I’m pretty sure the return value I added does the trick to update the vertex positions after double checking with minimal code. Although for normals I will see later if I have something to do to apply them.

Last bug I have to fix seems to be related to typing issues when using the dot() function.

Two lines are affected by this:
149:

If(dot(dq0[0], dq0.element(i)).lessThan(0.0), () => {

and 49:

const len2 = dot(Qn, Qn);

The error I’m given is:

Error while parsing WGSL: :99:10 error: no matching call to 'dot(f32, f32)'

1 candidate function:
 • 'dot(vecN<T>  ✗ , vecN<T>  ✗ ) -> T' where:
      ✗  'T' is 'abstract-float', 'abstract-int', 'f32', 'i32', 'u32' or 'f16'

    if ( ( dot( nodeVar2[ 0u ], nodeVar2[ i ] ) < 0.0 ) ) {
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


 - While calling [Device].CreateShaderModule([ShaderModuleDescriptor ""vertex""]).

This corresponds to line 149. Once this line would be fixed it will throw the same error but for the code corresponding to line 49.

If I read this correctly this says that in:

If(dot(dq0[0], dq0.element(i)).lessThan(0.0), () => {

dot() is expecting two f32as parameters. Yet it reads dq0[]/nodeVar2 as abstract-float, its index 0/0u as abstract-int should both make a f32 to work. Then dq0.element()/nodeVar2 as f32, i as i32 and 0.0 as u32 or f16.
I have tried calling float() from TSL to try casting the variables to f32 but it did absolutely nothing. I’m not so sure how I should fix the typing, especially with JS.

For a minute I forgot that dot() is supposed to represent the dot product operation between two vectors. So the error certainly just tells me that the inputs it gets are being cast to f32 and so that the issue is with my variable and it’s not accessing the vectors but directly its components. I think I see it. I will update with the working code later once it’s all fixed.

I was almost posting this when I read your edit. Nevertheless, the error message (1) means that the compiler found dot function with two floats as arguments. The compiler can process several types of dot, but none of them is dot(float,float). (2) shows the compiler code where the error occurred, and (3) is most likely the source code that corresponds to (2).

1 Like

I’m almost there I think. Line 109 is the last culprit that prevents the shader from compiling. And this one is still a headache for me unfortunately. I’m pretty sure the dot(Qn, Qn) is fine and the error is a bit misleading. I think only the division on line 109 is a real issue. My current guess is that div() function in TSL doesn’t handle division between matrices and floats unlike GLSL did. But even then I’m not sure I am manipulating the matrix correctly and I’m having a hard time testing it.

Error while parsing WGSL: :130:15 error: no matching overload for 'operator / (f32, mat4x4<f32>)'

4 candidate operators:
 • 'operator / (T  ✓ , T  ✗ ) -> T' where:
      ✓  'T' is 'abstract-float', 'abstract-int', 'f32', 'i32', 'u32' or 'f16'
 • 'operator / (T  ✓ , vecN<T>  ✗ ) -> vecN<T>' where:
      ✓  'T' is 'abstract-float', 'abstract-int', 'f32', 'i32', 'u32' or 'f16'
 • 'operator / (vecN<T>  ✗ , T  ✗ ) -> vecN<T>' where:
      ✗  'T' is 'abstract-float', 'abstract-int', 'f32', 'i32', 'u32' or 'f16'
 • 'operator / (vecN<T>  ✗ , vecN<T>  ✗ ) -> vecN<T>' where:
      ✗  'T' is 'abstract-float', 'abstract-int', 'f32', 'i32', 'u32' or 'f16'

  nodeVar6 = ( dot( nodeVar4, nodeVar4 ) / nodeVar6 );
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


 - While calling [Device].CreateShaderModule([ShaderModuleDescriptor ""vertex""]).

If you comment the line 109, you can see that the mesh becomes visible again. But it is visibly without dual quaternions skinning at all. That part might just be a matter of some data getting lost in its way. For sure I double checked, the uniforms are getting the data though.

My current issues are:

  • I’m not sure if I’m using the mat4 correctly with the .element() it’s very hard to find documentation or examples
  • Debugging the TSL pipeline is a bit rough, since the variables are all prototype handlers that are getting filled with data. And if there is an issue most of the time it seems to still create a prototype with empty data. Maybe there are good methods to test TSL variables I’m not aware of ?

codesandbox.io/p/devbox/glsl-dqs-to-tsl-2j7lkn

Hm. That’s interesting and unexpected. When I checked the WGSL documentation I see matrix can be multiplied by a scalar, but cannot be divided by a scalar. What would happen if you rewrite line 109 to use multiplication instead of division?

M.mulAssign(reciprocal(len2));
2 Likes

It finally works!!
I just need to pipe my original material to the fragmentNode to get back colors. But it seems to work!

Thanks!!

1 Like

Ok, I getting there. I have some working material setup.
Implementing that made it more visible that I also had to implement manually the morph targets too before applying the DQS. My code for all that is almost ready but I’m struggling to find documentation about where to retrieve the morph attributes and how.

Three.js Shading Language · mrdoob/three.js Wiki · GitHub
Here I can’t find any mention of them beside this only sentence :

Position
The transformed term reflects the modifications applied by processes such as skinning , morphing , and similar techniques.
[ … ]

I was assuming that this would be something along the lines of attribute('morphInfluences'). But I’m missing documentation for this and I don’t really know where to look.

For today I must stop at this state. I updated the codesandbox too.

My current issue to solve is how to access mesh.geometry.morphAttributes in TSL.
mesh.geometry.attributes is accessed by attribute('name') and I figured which ones are available now. But morphAttributes are stored separately and I need to have this data per vertex unlike morphTargetInfluence that is per mesh and I already set a unform for that.