godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
88.86k stars 20.15k forks source link

GLES2 cull_disabled does not orient surface normals towards the light #53762

Open goatchurchprime opened 2 years ago

goatchurchprime commented 2 years ago

Godot version

3.3.3.stable

System information

GLES2, Linux and Android Quest2 VR

Issue description

I have zero thickness walls (with "cull_disabled" to see both sides) with a SpotLight near the Camera.

This does properly show the "albedo_texture" on the back-faces, and gives a washed out appearance (as if only the ambient light is active).

Lower square in left image is when the MeshInstance faces towards the camera (and the light), Lower square in right image is when the MeshInstance faces away from the camera (and the light),

image image

The upper square is the result of playing with the shader code, by inverting the NORMAL vector, like so:

void vertex() {
    UV=UV*uv1_scale.xy+uv1_offset.xy;
    UV*=2.0;
    NORMAL = -NORMAL;
}

The result is that the square is bright on its back-face and dark on the front-face.

There is a light() function that can be used in the shader code, although it is not given to you by the "Convert to ShaderMaterial" option, and there are few clues about what its default code is.

I think when the light() function is not defined it is controlled by render_mode values such as diffuse_burley, specular_schlick_ggx.

It is not known whether a custom light() function would always lack the performance of these preset render_modes.

The one example of a custom light function given in the docs is:

void light() {    
    DIFFUSE_LIGHT += clamp(dot(NORMAL, LIGHT), 0.0, 1.0) * ATTENUATION * ALBEDO; 
}

Clearly this will give 0.0 whenever the NORMAL and the LIGHT point in opposite directions.

One way to fix this is to wrap an abs() around the dot() function so that the dot product is always positive.

The GLES3 renderer doesn't have this problem, but it does not appear that it is fixed with an abs() function. Instead, it looks like it inverts the NORMAL of a mesh when the backface is facing to the camera, since my inverted normal vertex shader (used on the top square) shows up dark on both sides.

It is hard to know where to look for these technical rendering details so I can work with them instead of against them.

I am using GLES2 because I am developing for the Quest2 VR, which isn't powerful enough to run GLES3

Steps to reproduce

Example below has an animation player to spin the mesh instance squares in view of the light and the camera

Minimal reproduction project

gles2lightbug.zip

Calinou commented 2 years ago

The OpenGL backface check isn't available in GLES2, so the only workaround is to duplicate your thin wall to make it visible on both sides.

Firepal commented 2 years ago

The OpenGL backface check isn't available in GLES2, so the only workaround is to duplicate your thin wall to make it visible on both sides.

Is the "backface check" you are referring to gl_FrontFacing? (or the FRONT_FACING built-in shader variable? ) Because it is exposed in Godot's GLES2 and works fine. (It's fragment-shader only, which makes sense since faces can't be represented in a vertex shader!)

I just tested putting if (!FRONT_FACING) { NORMAL *= -1.0; } anywhere in a custom shader's fragment function and it fixes the issue, so I'm fairly certain plopping that into GLES2's scene.glsl would do the trick?

Images of fix
Here's an image without the fix ![image](https://user-images.githubusercontent.com/29317321/137403920-6165231e-1440-44f1-acbd-41195adba219.png) Here's images with the fix (this is one mesh) ![image](https://user-images.githubusercontent.com/29317321/137403643-347d473b-c129-4024-a551-bcef3faa42c9.png) ![image](https://user-images.githubusercontent.com/29317321/137403352-ab9c15a2-28a8-4707-9522-b437498f32de.png)

(Maybe GLES2 is lacking in features but like come on, gl_FrontFacing seems to be from OpenGL 1.00! x)

goatchurchprime commented 2 years ago

That does work for my case, because I happen to have the spot-light positioned close to the camera, so this inverted normal is aligned correctly for it. I don't know how general this solution is (if this is good enough for lights in other places).

The FRONT_FACING feature is probably so you can apply a different base texture/colour depending on which side of the surface you are seeing.

I don't know the right answer, (hacking the light() function vs hacking the normal orientation), but whatever it is it should be as same as possible between GLES2 and GLES3.