Backface Directional Lighting

As I was discussing privately with @titansoftime, I’ve modified Instancing Lambert example and the backface detection/rendering looks fine.

So it does not seem like a instancing related problem. Probably need to re-calculate normals after quaternion rotations, that would be my guess.

Here’s a simplified fiddle demonstrating the lighting issue I am experiencing (merged BufferGeometry).

https://jsfiddle.net/titansoftime/rosncw0m/

So, the “problem” here is that lambert shader reverts normal for backfaces. So, this is the expected behavior. lights_lambert_vertex - meshlambert_frag

The ideal workaround would be to create a custom shader adapting these to your needs. In this case, probably removing all the vLightBack calculation and just using vLightFront independant of which side is being drawn.

A hacky way to do it, is just replacing the reflectedLight.directDiffuse using onBeforeCompile to just account for vLightFront. Modified JSFiddle

But keep in mind that all the extra back light logic is still being calculated, and given the amount of instances… it would be worth removing all of that.

2 Likes

That’s it, perfect! Thank you!

Exactly what I was looking for.

I used this to remove the (now) unnecessary calculations:

shader.fragmentShader = shader.fragmentShader.replace('vLightBack += saturate( -dotNL ) * directLightColor_Diffuse;',"\n");

2 Likes

Just a thought…

Is something that maybe should be an option in the API itself?

Not sure how common this issue is.

Hi again :sweat_smile:
Do you perhaps know where in the meshStandardMaterial shader code can I find the equivalent of

#ifdef DOUBLE_SIDED
reflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;
#else
reflectedLight.directDiffuse = vLightFront;
#endif

from the meshLambertMaterial? In other words, i’d like to apply the same logic to the standard material, but im getting lost in all the #includes :sweat:

normal_fragment_begin

Assuming you don’t need tangent and flat shading, just replace include with normal declaration:

shader.fragmentShader = shader.fragmentShader.replace(
  '#include <normal_fragment_begin>',
  'vec3 normal = normalize( vNormal );'
);

JSFiddle Example

3 Likes

Hope everyone’s well.
Today i migrated from version r122 to r145. I see that perturbNormal2Arb function now gets 4 values instead of 3. 4h value is now faceDirection.
I use meshphongmaterial and altough i add the line like below;

shader.fragmentShader = shader.fragmentShader.replace(
#include <normal_fragment_begin>’,
[
‘float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;’,
‘vec3 normal = normalize( vNormal );’
].join(‘\n’)
);

my grasses are not working like before. I shared some images below;
R122

R145

Do you have any idea how to solve this issue? Grass needs to be looking like the r122 version and also needs to work with lights. Any help would be appreciated.

Thanks
Mert

1 Like

It’s hard to tell what i’m looking at, the lower one also seems like having a sorting issue and using transparency.

For grass and such if you want to use lighting it’s always required to alter the normals, even if there was a dublicate for both sides, there is only one direction the light comes from.

The lighting spots on the second also look weird with sharp light spots
image

Well working version here with r122: https://jamir.io
I will check my vertex sorting function but i dont think it is related with that.
Light wasnt affecting with different rotations of planes when i replace like the above code. If it was working on r122 it should also work on r145. There must be a change on r145 that affects the initial vnormal value maybe?

Can you reproduce your issue in a simple codepen or fiddle?

So I just went from r142 to r146dev and am noticing the same thing.

My (@sciecode) “Backside Fix” broke =[

Something must have changed in the built in shaders.

Edit* Yes, the Lambert fragment shaders (at least) are now very different. This line is completely gone it seems:

#ifdef DOUBLE_SIDED\n\t\treflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\n\t#else\n\t\treflectedLight.directDiffuse = vLightFront;\n\t#endif

And outgoingLight is now set here instead of reflectedLight.directDiffuse. It’s completely different.

Not really sure where to start on this one. I’ll start messin with it.

1 Like

MeshLambertMaterial is no longer based on goroud shading ( vertex interpolated ) it is now based on phong shaded (fragment interpolated) MeshLambertMaterial: convert to per-fragment shading

So if you guys still want to use that bit that adjusted normals, you’ll need to replace old MeshLambertMaterial with the new MeshGoroudMaterial.

Fix will still work with on that variant, which, in reality, is the exact same code.

1 Like

If you want to ignore the faceDirection on any particular mesh group (folliage for instance) still using the new version of MeshLambertMaterial, you’ll need to ignore this bit of shader code directly on the fragment shader, specifically we are looking at this chunk: normal_fragment_begin

So the hack becomes:

shader.fragmentShader = shader.fragmentShader.replace(
  '#include <normal_fragment_begin>',
  'vec3 normal = normalize( vNormal );\nvec3 geometryNormal = normal;\n'
);

Which if you compare with the original shader, ignores DOUBLE_SIDED, USE_TANGENT pre-processor conditions and prevents your normal from being mirrored. This code should also work properly for any other variant of the core materials, since maintainers made sure that every one is using this same chunk. This is now a more standard “hack”. Let me know if you guys have any trouble.

Oh i see now there are front-facing blades, looked like grass-blade-less ground on first sight. And good to mention that change here @sciecode :+1:

Also as suggestion to @jackiejean388 for small foliage like grass ground aligned normals can make you get rid of any such shading issue blending it more seamless with the ground. Just making the blade normal face the direction of the ground.

1 Like

Thanks sciecode!
What i have done previously with the phongmaterial like below as you mentioned before

Now i changed into this;

shader.fragmentShader = shader.fragmentShader.replace(
#include <normal_fragment_begin>’,
[
‘float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;’,
‘vec3 normal = normalize( vNormal );’
].join(‘\n’)
);
mat.userData.shader = shader;

But this deosnt work either. Do you have a solution for the phong?

Thanks, Mert.

This goes inside material.onBeforeCompile, which is a pre-compile callback that Three.js exposes for custom shader changes. Follows an example:

folliageMaterial.onBeforeCompile = function swapNormals( shader, renderer ) {

    shader.fragmentShader = shader.fragmentShader.replace(
        '#include <normal_fragment_begin>',
        'vec3 normal = normalize( vNormal );\nvec3 geometryNormal = normal;\n'
);

This should work with MeshLambertMaterial, MeshPhongMaterial, MeshStandardMaterial & MeshPhysicalMaterial.

2 Likes

Once again (3+ years later lol) you save the day! Perfect! Thank you so much!

1 Like

You are missing the last line vec3 geometryNormal = normal;

Your code version would be:

shader.fragmentShader = shader.fragmentShader.replace(
‘#include <normal_fragment_begin>’,
[
‘float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;’,
‘vec3 normal = normalize( vNormal );’,
'vec3 geometryNormal = normal;'
].join(‘\n’)
);
1 Like

Thanks, yes i already tried it but didnt work for me and i dont know why
Shared below;