three.js icon indicating copy to clipboard operation
three.js copied to clipboard

MeshLambertMaterial: convert to per-fragment shading

Open WestLangley opened this issue 2 years ago • 17 comments

This PR converts MeshLambertMaterial from Gouraud (per-vertex) shading to per-fragment shading.

  1. MeshLambertMaterial now supports:

    • normal maps
    • object-space normal maps
    • bump maps
    • displacement maps
    • tangents
    • flat-shading using screen-space derivatives
  2. Shadows are now correctly modeled as the absence of light, just as the other built-in materials that respond to light. The shadowMask approximation, which results in solid-black shadows, is no longer needed.

  3. No features have been removed. In particular, the legacy specularMap is still supported.

  4. It remains to see if per-vertex shading has a detrimental effect on mobile performance.

  5. As suggested by @mrdoob offline, I will create a custom ShaderMaterial that uses Lambert illumination and Gouraud (per-vertex) shading, and add it to the /examples/jsm/materials/ directory. It will have the same feature set as the legacy version of MeshLambertMaterial.

WestLangley avatar Aug 05 '22 00:08 WestLangley

It remains to see if per-vertex shading has a detrimental effect on mobile performance.

It would be interesting to see the performance implications of this PR. Meaning before it is merged.

I know from several users that they use MeshLambertMaterial in a mobile and VR context because MeshStandardMaterial as well as MeshPhongMaterial are slower.

Mugen87 avatar Aug 05 '22 07:08 Mugen87

I have conducted some tests with Lambert vs. Phong on a modern M1 Macbook Pro. Not exactly a low-end mobile gpu, but still good for testing performance. The benchmark was rendering a single torus not at 800x600 resolution with an pixel ratio of 10 to firmly test the speed of fillrate. MeshLambertMaterial was only 2% faster than MeshPhongMaterial.

Here is the scene rendered (for reference): aha

N8python avatar Aug 05 '22 12:08 N8python

Sorry, but such a simple scene on such a powerful device is not a viable test case. This needs to be primarily tested with low and mid-range smart phones with a fragment shader bound code (so it's possible to measure a difference between gouraud and phong shading)

Mugen87 avatar Aug 05 '22 13:08 Mugen87

A few ideas on what might be needed to measure this:

  1. low-end mobile device
  2. selected material (lambert, phong, standard) filling the viewport at full resolution
  3. perhaps add 2-3x overdraw with several layers of transparent PlaneGeometries
  4. measure both framerate, and average time spent on .render(sceen, camera) per frame

donmccurdy avatar Aug 05 '22 13:08 donmccurdy

Sorry, but such a simple scene on such a powerful device is not a viable test case. This needs to be primarily tested with low and mid-range smart phones with a fragment shader bound code (so it's possible to measure a difference between gouraud and phong shading)

This makes sense. Sorry for wasting your time!

N8python avatar Aug 05 '22 18:08 N8python

Sorry if this has been discussed somewhere else that I've missed. I believe users are choosing MeshLambertMaterial today because it has better performance than Phong on GPU-bound mobile devices, presumably because of the per-vertex vs. per-fragment shading.

Assuming we want to maintain both a per-fragment Lambert material and a per-vertex Gourad shader (https://github.com/mrdoob/three.js/pull/24467), we have several choices:

  • (A) Lambert in core, Gourad in examples/jsm
  • (B) Gourad in core, Lambert in examples/jsm
  • (C) Both in examples/jsm
  • (D) Both in core

For bundle size reasons, I doubt we want (D). If users are indeed looking for per-vertex shading, rather than a Lambert shading model, then I don't think (A) has much benefit. I would be fine with (B) or (C).

donmccurdy avatar Aug 08 '22 17:08 donmccurdy

I have seen users use MeshLambertMaterial for the performance, others for the illumination model.

//

I am trying to make the shader code more maintainable. Part of that is removing per-vertex materials from Core. Hence, MeshLambertMaterial is converted to per-fragment. A side benefit is it is able to become more feature-rich.

Concurrently, per-vertex MeshGourardMaterial with Lambert illumination is added to the examples in https://github.com/mrdoob/three.js/pull/24467 for users who need a per-vertex material for mobile.

So, I'd say the current proposal would be considered as Option (E).

//

Option (F) would be to remove Lambert from Core and rename the newly-added MeshGourardMaterial in the examples to MeshLambertMaterial. In that case, we would just say: "MeshLambertMaterial has been moved to the examples".

WestLangley avatar Aug 08 '22 18:08 WestLangley

TBH, I don't see the purpose of a separate per-fragment Lambert material. Why not directly using MeshPhongMaterial with zero specular?

I understand the library will be easier to maintain if no materials with per-vertex lighting are part of the core. However, I have the feeling a decision is made without enough information about the performance benefits of per-vertex lighting. Hence, I think it's important to make a performance analysis before this PR is merged.

Mugen87 avatar Aug 08 '22 18:08 Mugen87

Why not directly using MeshPhongMaterial with zero specular?

Because it is not the same, but MeshPhongMaterial could be modified so it is the same.

WestLangley avatar Aug 08 '22 18:08 WestLangley

Because it is not the same,

I what way would it be different? Sorry, I have not read the new lambert shader in detail yet.

Mugen87 avatar Aug 08 '22 18:08 Mugen87

Why not directly using MeshPhongMaterial with zero specular?

@Mugen87 Did you mean zero-shininess?

Because even if shininess is zero, the fresnel goes to 1 at grazing angles. So specular reflections still remain.

WestLangley avatar Aug 08 '22 18:08 WestLangley

@WestLangley I think your (E) and my (A) are the same, right? I guess what I mean is, why keep Lambert in core at all, if it has primarily been used for performance of per-vertex shading and we're removing that "feature" from it? Why not move both to examples/jsm?

donmccurdy avatar Aug 08 '22 19:08 donmccurdy

@donmccurdy

I think your (E) and my (A) are the same, right?

Yes, if your (A) is referring to the new per-pixel Lambert with the normalMap support.

I guess what I mean is, why keep Lambert in core at all, if it has primarily been used for per-vertex shading

I think that is a false premise. Users use Lambert for the illumination model, too.

Why not move both to examples/jsm?

That is an option, too. But, instead, you could do Option (F), which is equivalent to just moving per-vertex Lambert from Core to the examples.

WestLangley avatar Aug 08 '22 20:08 WestLangley

Ok, thanks @WestLangley! It sounds like a primary goal here is to simplify what we're maintaining in core, and to not have the additional complexity of a per-vertex shader there. Moving per-vertex to MeshGouradMaterial in examples/jsm, and having MeshLambertMaterial be per-fragment could be the most direct way to improve things. Many users probably assumed MeshLambertMaterial was per-fragment anyway — the naming was a bit confusing, and this PR fixes that.

I'm satisfied with keeping MeshLambertMaterial (now per-fragment) in core. 👍🏻

donmccurdy avatar Aug 08 '22 21:08 donmccurdy

Ok, thanks @WestLangley! It sounds like a primary goal here is to simplify what we're maintaining in core, and to not have the additional complexity of a per-vertex shader there. Moving per-vertex to MeshGouradMaterial in examples/jsm, and having MeshLambertMaterial be per-fragment could be the most direct way to improve things. Many users probably assumed MeshLambertMaterial was per-fragment anyway — the naming was a bit confusing, and this PR fixes that.

Exactly this 👍

mrdoob avatar Aug 09 '22 05:08 mrdoob

Ok, thanks @WestLangley! It sounds like a primary goal here is to simplify what we're maintaining in core, and to not have the additional complexity of a per-vertex shader there. Moving per-vertex to MeshGouradMaterial in examples/jsm, and having MeshLambertMaterial be per-fragment could be the most direct way to improve things. Many users probably assumed MeshLambertMaterial was per-fragment anyway — the naming was a bit confusing, and this PR fixes that. I'm satisfied with keeping MeshLambertMaterial (now per-fragment) in core. 👍🏻

Okay, I'm convinced^^, too. Since MeshGouraudMaterial will be in place, users can fallback to this material if the shading performance actually becomes a problem.

Mugen87 avatar Aug 09 '22 07:08 Mugen87

Okay, I'm convinced^^, too.

Yay!

WestLangley avatar Aug 09 '22 10:08 WestLangley

Thanks!

mrdoob avatar Aug 10 '22 06:08 mrdoob

I guess we no longer need this file?

https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/lights_lambert_vertex.glsl.js

mrdoob avatar Aug 10 '22 06:08 mrdoob

I guess we no longer need this file?

We do need it. Gouraud replicates legacy Lambert.

WestLangley avatar Aug 10 '22 15:08 WestLangley

Maybe we remove the chunk but copy the code directly into GouraudShader? Better to keep only shader chunks in the core which are actually used by built-in materials.

Mugen87 avatar Aug 10 '22 17:08 Mugen87