mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
102.64k stars 35.37k forks source link

Certain GLTF models not receiving shadows on Windows #21483

Open jhancock532 opened 3 years ago

jhancock532 commented 3 years ago

Minecraft Education edition has a tool for exporting .GLB files directly from the game, but the resulting GLB file won't receive shadows when displayed using three.js on Windows devices.

This issue occurs when using Windows browsers (Firefox, Chrome, Edge), but not when using MacOS browsers (Chrome, Firefox, and Safari). See the original thread on discourse.threejs.org for more information.

Steps to reproduce the behavior:

  1. Go to the demo website.
  2. Note that on Windows, the red sphere does not cast a shadow onto the Minecraft model.

Live example https://2cf782ea.minecraft-website.pages.dev/ Source code can be found in this GitHub repository.

Expected behavior The red sphere should cast a shadow onto the 3D model.

Screenshots Windows users don't see the sphere shadow, its absence circled in red.

Windows Example

Mac users see the shadow as expected, as well as the model casting shadows on itself.

Mac Example

Platform:

Mugen87 commented 3 years ago

I can reproduce this on a Windows 10 laptop with Chrome.

mrdoob commented 3 years ago

I'm a Mac user and I don't see the shadow 😁

Screen Shot 2021-03-18 at 3 40 53 PM
Mugen87 commented 3 years ago

That is strange 🤔 . It works fine on my iMac...

Mugen87 commented 3 years ago

@jhancock532 Do you mind providing a second version of your app (via a second URL) with the following changes:

jhancock532 commented 3 years ago

@Mugen87 Here's the new link with the updates made as requested.

https://426d15f3.minecraft-website.pages.dev/

Nothing has changed on my end, no new errors or logs.

image

Mugen87 commented 3 years ago

@jhancock532 I've investigated the issue closer and could fix it on my Windows laptop by adding vertex normals. Can you please add the following line into your onLoad() callback where you enable shadows for the child objects?

child.castShadow = true;
child.receiveShadow = true;

child.geometry.computeVertexNormals(); // FIX

It would be great if you can update https://426d15f3.minecraft-website.pages.dev/ with that fix.

Mugen87 commented 3 years ago

@mrdoob The OP's asset has no vertex normals and it seems this behavior is somewhat undefined since the normal attribute in the shader might have different default values depending on the platform. To me, this is an issue in the exporter since they should always export vertex normals. At least when lit materials are going to be used.

We could try to detect this issue and generate vertex normals if not present however they will not be required for all use cases.

jhancock532 commented 3 years ago

Thank you, this has fixed my issue. I'm afraid I can't update the old URL due to how Cloudflare Pages works, but I have deployed the fixed example to https://61c69ff5.minecraft-website.pages.dev/.

donmccurdy commented 3 years ago

... this behavior is somewhat undefined since the normal attribute in the shader might have different default values depending on the platform. To me, this is an issue in the exporter since they should always export vertex normals. At least when lit materials are going to be used.

GLTFLoader will automatically set material.flatShading = true when vertex normals are not included, since that's the behavior glTF specifies. Does this effectively mean that we don't intend to support shadows with material.flatShading = true? I'm not sure I understand why this setting would have platform-specific effects.

Mugen87 commented 3 years ago

GLTFLoader will automatically set material.flatShading = true when vertex normals are not included,

I've missed that bit. Then this issue needs further investigation. I wonder why the existence of vertex normals makes a difference although flat shading is used 🤔 .

Mugen87 commented 3 years ago

Okay, I think I have found the problematic line. It is:

https://github.com/mrdoob/three.js/blob/53d08e665ea5296a62734c4a09243a46c3f0f0f1/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js#L7

When no vertex normals are present, normal has an undefined value and thus objectNormal and transformedNormal. Using an undefined value in the above line breaks shadowWorldNormal and thus vDirectionalShadowCoord can't be computed.

So it seems vertex normals are required for receiving shadows even when flat shading is used. This is also true for other features like displacement maps.

However, I'm not aware of an approach to compute vertex normals in the vertex shader. It seems the only way of fixing this is the usage of toNonIndexed() and computeVertexNormals() (instead of setting flatShading to true).

Mugen87 commented 3 years ago

Couldn't we just generate flat vertex normals in GLTFLoader instead of setting flatShading to true?

donmccurdy commented 3 years ago

I'd generally prefer not to add O(n) per-vertex costs for all users as solutions to avoid less common issues for specific use cases. If WebGLRenderer requires vertex normals for an object to receive shadows, perhaps it can print a warning when those conditions aren't met?

WestLangley commented 3 years ago

I expect the solution is to fix #18915 to handle the case when flatShading is true and normals are not present.

/ping @Oletus.

(Setting flatShading to false when normals are not present is a user error. GLTFLoader silently handles that case.)

gkjohnson commented 3 years ago

From #22056 we've been running into this issue as of r118 and I'm trying to think of a way to fix it. Some our geometry does not have normals and we'd prefer not to regenerate them because the mesh changes frequently and vertex normal generation is expensive.

One (temporary?) solution would be to set shadowWorldNormal to 0 0 0 on this line if the FLAT_SHADED define is set though of course that means normal bias won't work on materials that do have normals and are flat shaded but that would be the simplest solution to at least get something working more as expected. Perhaps another define could be added to indicate if the geometry does have vertex normals so shadowWorldNormal is only set to 0 0 0 if the geometry is flat shaded and has no vertex normals.

However, I'm not aware of an approach to compute vertex normals in the vertex shader.

I think the true fix for this would be to compute the normal bias offset in the fragment shader if flat shading is enabled but that's clearly a more complicated and expensive change. Another option might be to apply the normal offset when rendering the shadow map rather than when sampling the shadow map but I'd have to look into the implementation a bit more to see if the same result could be achieved.