Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly transform normals for non-uniform scales #2049

Closed
wants to merge 1 commit into from

Conversation

CptPotato
Copy link
Contributor

@CptPotato CptPotato commented Apr 29, 2021

This changes the way normals are transformed in the vertex shader of bevy_pbr. In order to correctly transform normals, the scaling actually has to be applied inversely (see this explanation for details). Otherwise non-uniform scales will produces incorrect normals.

My proposed change comes with the assumption that the Model matrix is orthogonal (i.e. does not contain shearing). This allows for some optimization, I can explain more if needed.

Here's a pancaked version of load_gltf as comparison:

screenshot

Since this is just a minor change I didn't bother creating an issue. Feel free to close if the solution doesn't seem fit.

@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior A-Rendering Drawing game state to the screen labels Apr 29, 2021
@alice-i-cecile
Copy link
Member

Three naive questions:

  1. Will this method be called repeatedly? Aka, do we care about optimizing here?
  2. Are there any realistic scenarios where users may want a non-orthogonal model matrix?
  3. Can we include some sort of assert or warning if this assumption is violated without compromising performance?

@CptPotato
Copy link
Contributor Author

CptPotato commented Apr 29, 2021

Three naive questions:

  1. Will this method be called repeatedly? Aka, do we care about optimizing here?
  2. Are there any realistic scenarios where users may want a non-orthogonal model matrix?
  3. Can we include some sort of assert or warning if this assumption is violated without compromising performance?
  1. Kind of? It's executed for all vertices every frame. It's running on the GPU so it's not a huge impact, but using the "optimized" version I went with does maybe save 20-30 or so arithmetic operations.
    Alternatively we could also just use the more general transpose(inverse(m)) and take that (minor) performance hit.
  2. Currently I don't think that's even possible in bevy to my knowledge. At least not without major changes from the user. The Transform component only stores position, rotation and scale which makes it impossible to produce shearing.
    From what I know, shearing is usually even something developers try to avoid, because it can get tricky to handle in many scenarios (collision detection, physics, culling, etc.)
  3. (see 2.): Not really applicable because it's currently not possible to produce that case.

@alice-i-cecile
Copy link
Member

Great, sounds like a perfectly reasonable optimization then. Thanks for the explanation :)

@CptPotato CptPotato marked this pull request as draft April 30, 2021 06:06
@CptPotato
Copy link
Contributor Author

There's a scenario that I haven't considered, yet, sorry.
I'll keep this as a draft until I tested and verified it.

@cart
Copy link
Member

cart commented May 1, 2021

It would also be worth investigating how other popular engines approach this calculation. It seems like they must be doing it by default, so its probably just a matter of peeking at their shaders. I'm familiar with godot's code, so I can just link to the relevant bit:

https://github.com/godotengine/godot/blob/4a7679e4dd9f68270b3d27797146f88491f182b8/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl#L119

@CptPotato
Copy link
Contributor Author

CptPotato commented May 1, 2021

@cart thanks for the input and link. I did take a look at Godot's shader code but to be honest, it doesn't look quite right to me. Taking things to the editor seems to confirm this (unless the bitflag of the shader needs to be enabled manually somehow):

screenshot

As mentioned in my comment above, to my knowledge transpose(inverse(m)) should be the general approach of inverting the scale of a mat3 and keeping everything else.
I went for the (non-shearing) optimization because invert(m) is a quite hefty operation. It's not too bad here since it's just a mat3 but I still opted for performance given that shearing can't even happen currently. Godot's approach of branching and passing it as a bitflag is interesting, but I don't have what it takes to bring that into bevy.

Also, I'll likely keep this as a draft until some things around #1755 are cleared up.

@mockersf mockersf added the S-Pre-Relicense This PR was made before Bevy added the Apache license. Cannot be merged or used for other work label Jul 16, 2021
@CptPotato
Copy link
Contributor Author

Closing, since this is for the old renderer and the new one already does this correctly.

@CptPotato CptPotato closed this Dec 6, 2021
@CptPotato CptPotato deleted the pbr-fix-normal branch December 6, 2021 07:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Bug An unexpected or incorrect behavior S-Pre-Relicense This PR was made before Bevy added the Apache license. Cannot be merged or used for other work
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants