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

invert face culling for negatively scaled gltf nodes #8859

Merged
merged 6 commits into from
Sep 18, 2023

Conversation

robtfm
Copy link
Contributor

@robtfm robtfm commented Jun 16, 2023

Objective

according to khronos, gltf nodes with inverted scales should invert the winding order of the mesh data. this is to allow negative scale to be used for mirrored geometry.

Solution

in the gltf loader, create a separate material with cull_mode set to Face::Front when the node scale is negative.

note/alternatives:
this applies for nodes where the scale is negative at gltf import time. that seems like enough for the mentioned use case of mirrored geometry. it doesn't help when scales dynamically go negative at runtime, but you can always set double sided in that case.

i don't think there's any practical difference between using front-face culling and setting a clockwise winding order explicitly, but winding order is supported by wgpu so we could add the field to StandardMaterial/StandardMaterialKey and set it directly on the pipeline descriptor if there's a reason to. it wouldn't help with dynamic scale adjustments anyway, and would still require a separate material.

fixes #4738, probably fixes #7901.

@robtfm robtfm added A-Rendering Drawing game state to the screen C-Bug An unexpected or incorrect behavior labels Jun 16, 2023
@robtfm
Copy link
Contributor Author

robtfm commented Jun 16, 2023

tested and seems fine with examples from https://github.com/KhronosGroup/glTF-Asset-Generator/blob/master/Output/Positive/Node_NegativeScale/README.md.

i had to generate indices to get the normal maps to load (#8862)

@robtfm robtfm requested a review from mockersf August 26, 2023 14:37
@rparrett
Copy link
Contributor

I believe this should fix #4738 and possibly #7901.

@robtfm
Copy link
Contributor Author

robtfm commented Sep 14, 2023

I believe this should fix #4738 and possibly #7901.

It’ll definitely fix the first. I’ve also found lighting works correctly with inverse scaled meshes so probably the second too - I can’t check though as the file link is dead.

) -> Result<(), GltfError> {
let transform = gltf_node.transform();
let mut gltf_error = None;
let transform = Transform::from_matrix(Mat4::from_cols_array_2d(&transform.matrix()));
let world_transform = *parent_transform * transform;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transform * Transform is a lossy operation. I'm concerned the determinant might end up incorrect in some transform hierarchies.
I suggest passing a GlobalTransform around instead to be certain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm happy to do this. it'll be slower, but this isn't a hot code path.

in the gltf spec it says that you can either use a translation + scale + rotation, or a matrix which must decompose into those parts, and explicitly cannot skew or shear.

since we only care about the scale, and the scales are just Vec3-multiplied at each step, i'm pretty sure this is safe. i guess i could pass the scale down as a Vec3 instead of the whole transform if that would be clearer / avoid potential future misuse.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the gltf spec it says that you can either use a translation + scale + rotation, or a matrix which must decompose into those parts, and explicitly cannot skew or shear.

I think that implementation note is referring purely to the local transform stored in the gltf format, but the note is unfortunately not making that very clear.

since we only care about the scale, and the scales are just Vec3-multiplied at each step, i'm pretty sure this is safe.

I don't have the math intuition to know whether the loss of skew and shear still keeps the determinant the same 🤷

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that implementation note is referring purely to the local transform stored in the gltf format, but the note is unfortunately not making that very clear.

yes, but that's all we're dealing with here. at this point the gltf isn't part of the world heirarchy, we just initialize the transform to IDENTITY at the gltf root node.

Co-authored-by: François <mockersf@gmail.com>
@mockersf mockersf added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Sep 17, 2023
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Sep 18, 2023
Merged via the queue into bevyengine:main with commit 28060f3 Sep 18, 2023
22 checks passed
@cart cart mentioned this pull request Oct 13, 2023
43 tasks
rdrpenguin04 pushed a commit to rdrpenguin04/bevy that referenced this pull request Jan 9, 2024
# Objective

according to
[khronos](KhronosGroup/glTF#1697), gltf nodes
with inverted scales should invert the winding order of the mesh data.
this is to allow negative scale to be used for mirrored geometry.

## Solution

in the gltf loader, create a separate material with `cull_mode` set to
`Face::Front` when the node scale is negative.

note/alternatives:
this applies for nodes where the scale is negative at gltf import time.
that seems like enough for the mentioned use case of mirrored geometry.
it doesn't help when scales dynamically go negative at runtime, but you
can always set double sided in that case.

i don't think there's any practical difference between using front-face
culling and setting a clockwise winding order explicitly, but winding
order is supported by wgpu so we could add the field to
StandardMaterial/StandardMaterialKey and set it directly on the pipeline
descriptor if there's a reason to. it wouldn't help with dynamic scale
adjustments anyway, and would still require a separate material.

fixes bevyengine#4738, probably fixes bevyengine#7901.

---------

Co-authored-by: François <mockersf@gmail.com>
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-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
6 participants