(Instance) mesh and normals with transformations

I’m trying to create my own simple directional light, hijacking Three’s lambert shader for more artistic control than Threejs own directional light allows. My initial though was that this wouldn’t be too difficult haha, but alas.

If I apply no transform to the mesh – or the mesh instance – the light behaves as I would expect, but as soon as I add rotation or scale transform (like flipping an axis with -1) the normal no longer matches.

Are normals always defined in object (local) space? If so how do I convert it to world space (where i define my light directions)? Just using the modelMatrix or instanceMatrix won’t work as the translation part would mess up the normal. I would assume Three’s own direction light directions are also defined in global space, but I’m struggling to actually find the source code for that part or for the shader parts.

I’ve tried using transfomedNormal, but that does not seem correct and I’m not sure what space that is in. My directional light are defined as a simple Vector3(0, 0, -1) (backside) and Vector3(-1, 0, 0) (right side).

There is also a new Matrix3().getNormalMatrix() but I’m not sure what that does so i can recreate it GLSL side.

Any idea how I can get this to work?

Normals are in object local space (the meshes space).
They are transformed through the normalMatrix which is computed from the top 3x3 of the matrixWorld. I think matrix.getNormalMatrix() does that in JS side. In glsl, you can grab the top 3x3 just by converting to mat3…

This site: three-shaderlib-skim

Lets you view the source code of all the built in materials vertex+fragment shaders… Select vertex or fragment, then unfold all to see how the sausage is made.

something like: (pseudocode)

vec3 objectNormal = normal;

...
// Here you have to scale objectNormal by the scale of the objects matrix!
...

mat3 normalMatrix = mat3(matrixWorld);
transformedNormal = normalize( normalMatrix * objectNormal );

One approach I like to use in this situation is to use onBeforeCompile to modify the materials shader before it gets uploaded.

It’s often easier to identify which chunk you need to modify (using shaderlib skim ^ ) and then splice the shader chunks, than it is to write/duplicate the whole geometric rendering context from scratch in your code.

If you really only need 1 or 2 lights and nothing else, then I suppose it might not be too hard to write as a single/simple shader but… ya. Sounds tricky.

1 Like

thanks, @manthrax ! that shader chunk explorer is incredible useful, instead of manually going through Threejs GitHub repo!

Yeah, I’m using onBeforeCompile to hijack the lambert material for this, and I had been thinking for a while that if I remove the translation from the matrixWorld then I could use that to transform something to world space. I made this (silly) thing

    mat4 removeTranslation(mat4 mat) {
        mat4 m = mat4(mat);
        m[3][0] = 0.;
        m[3][1] = 0.;
        m[3][2] = 0.;
        return m;
    }

and that actually does works. But i guess converting to a mat3 essentially does the same by dropping the last column?

1 Like