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

Meshes with negative scale transforms are not rendered correctly #4738

Closed
bitshifter opened this issue May 13, 2022 · 11 comments · Fixed by #8859
Closed

Meshes with negative scale transforms are not rendered correctly #4738

bitshifter opened this issue May 13, 2022 · 11 comments · Fixed by #8859
Labels
A-Assets Load files from disk to use for things like images, models, and sounds A-Rendering Drawing game state to the screen C-Bug An unexpected or incorrect behavior

Comments

@bitshifter
Copy link
Contributor

bitshifter commented May 13, 2022

Bevy version

0.7

Operating system & version

Windows 10

What you did

edit: this was discovered via gltf loading, however a simple way to reproduce it is to modify the 3d_scene example an apply a negative scale to the cube transform.

I downloaded Kenney Car Kit from https://kenney.nl/assets/car-kit and loaded the Models/GLTF format/suv.glb#Scene0 asset in a bevy project.

When examining the vehicle in bevy, the wheels on the left of the car are not visible and the wheel on the back of the car is partially visible.

I opened up this asset in Blender and found that the nodes that we not rendering were using negative scale on the x axis to mirror the wheel.

If you load this asset up in the scene viewer example it is a bit clearer there, the negative scale nodes are rendered, but it looks like back face culling is inverted or something.

cargo run --example scene_viewer suv.glb

What you expected to happen

Negative scaled objects should be rendered correctly.

What actually happened

Negative scaled objects were not rendered correctly.

Additional information

bevy:

bevy

blender:

blender

@bitshifter bitshifter added C-Bug An unexpected or incorrect behavior S-Needs-Triage This issue needs to be labelled labels May 13, 2022
@bitshifter bitshifter changed the title Negative scale gltf nodes are not rendered Negative scale gltf nodes are not rendered correctly May 13, 2022
@alice-i-cecile alice-i-cecile added A-Rendering Drawing game state to the screen A-Assets Load files from disk to use for things like images, models, and sounds and removed S-Needs-Triage This issue needs to be labelled labels May 13, 2022
@bjorn3
Copy link
Contributor

bjorn3 commented May 13, 2022

That is likely backface culling which I believe blender has disabled by default while pretty much every game engine has enabled it ny default.

@superdump
Copy link
Contributor

Exactly that. So unless blender or something else indicates in the glTF that those things need to be rendered double-sided or with front face culling instead, it would have to be fixed up in code. The StandardMaterial has cull_mode and double_sided. For the minimal change, I'd say try setting the StandardMaterial cull_mode on the wheels to Some(Face::Front). If the lighting looks weird, then I guess maybe set double_sided to true.

@bitshifter
Copy link
Contributor Author

Just to clarify, you both are of the opinion that the asset is incorrect, that it looks correct in blender because blender doesn't back face cull and this is not a bug in bevy?

@bitshifter
Copy link
Contributor Author

bitshifter commented May 13, 2022

I don't know if this was authored in Blender, I imported it to into Blender to examine the node transforms, back face culling was enabled on the tire material. I've inspected this asset in https://gltf-viewer.donmccurdy.com (three.js) and I've imported it into Unreal and Godot, it worked fine in all cases.

Unreal and Godot don't support gltf at runtime (Bevy is unusual in this regard) but both have import pipelines that can convert gltf to their native formats. In Unreal's case it only imported the static meshes and materials, not any hierarchy. It imported unique meshes with the local transform (i.e. already reflected) baked in for each wheel mesh. In Godot I just dragged the asset into the editor and some "magic" happened. Looking at the Godot docs there are quite a few options for the gltf importer, I don't know which one was used, but all wheels render correctly and back face culling is enabled on the wheel material.

I am not familiar enough with Bevy internals to know what the problem is exactly, but it does seem like Bevy isn't able to handle negative scale gltf nodes correctly. I don't know that this is a render issue, it could be the loading process needs to transform meshes so that they can be rendered correctly.

edit1: Godot import workflow docs https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_scenes.html#import-workflows

edit2: Unreal imported a copy of each wheel mesh untransformed, so they're all exactly the same mesh but there are 5 of them,..

edit3: https://gltf-viewer.donmccurdy.com uses three.js, no idea what they are doing for culling

@superdump
Copy link
Contributor

Interesting indeed. I wonder what they all do. Doing anything automatic with front/back face culling based on negative scale feels risky because it could well be that the mesh has negative scale because you’re supposed to be inside it and the negative scale then makes front faces outside the volume of the mesh be front faces inside the volume of the mesh and then flipping that would break such meshes.

@bitshifter
Copy link
Contributor Author

Yeah I'm not sure. I was looking through the Godot importer code, there is a lot of it. Once the scene is imported, I didn't see any way of inspecting it to know if the negative scale was preserved.

I think for my use I will split these up into separate meshes and create the hierarchy in code manually, since trying to access the child components of a gltf scene was proving quite difficult. But I think this is pretty common to use negative scale to flip meshes, especially for low poly assets, so it will probably come up again.

@superdump
Copy link
Contributor

I expect all four wheels are using the same material, and the two wheels with the negative scale are not visible as they are being back-face culled. The way to fix that would be to set the material's cull_mode to Some(Face::Front) as mentioned. However, that would make the right-side non-negative-scale wheels get culled instead. So I think you would have to duplicate the material from the right-side wheels, modify the cull_mode, add this new material asset to the appropriate assets resource to get a new material handle, and then set the material handle component of the left wheels to that. Awkward.

Needs more understanding into how other engines manage to handle this without just disabling back face culling.

@bitshifter
Copy link
Contributor Author

In this case I can rotate the wheels instead of reflecting them since they are symmetrical.

I found a few suggestions on how to deal with negative scale here https://www.gamedev.net/forums/topic/640616-negative-scaling-flips-face-winding-affects-backface-culling/5045155/.

tl;dr:

  • don't allow negative scale :)
  • disable face culling :D
  • pick a culling order based on the number of negatively scaled axes.
  • pick an index buffer based on the same (and have an alternative index buffer for each mesh, where the winding order is reversed).

I suspect this might be an issue in Bevy if I negative scale anything, without needing to go through gltf, I will check that when I have time.

@bitshifter
Copy link
Contributor Author

bitshifter commented May 15, 2022

I applied negative scale to one of the transforms in the parenting example (e.g. .with_scale(Vec3::(-1.0, 1.0, 1.0))) and found the culling is backwards in that case too. I guess in this instance it is easy for someone to change material properties, whereas with the gltf the transform can be buried deep in a hierarchy and is pretty difficult for a user to change post load (I personally couldn't work out how to access the scene child components a few levels deep). So while negative scale could possibly be handled automatically across the board, it might make sense to handle the gltf case sooner since it's harder for users to do something about it.

The StandardMaterial has cull_mode and double_sided. For the minimal change, I'd say try setting the StandardMaterial cull_mode on the wheels to Some(Face::Front). If the lighting looks weird, then I guess maybe set double_sided to true.

I tried this in the 3d_scene example by setting x scale to -1 and applying a StandardMaterial with cull_mode set to Face::Front, it rendered correctly, lighting and shadows looked OK.

@bitshifter bitshifter changed the title Negative scale gltf nodes are not rendered correctly Meshes with negative scale transforms are not rendered correctly May 15, 2022
@superdump
Copy link
Contributor

My argument so far has been that we can’t absolutely know whether the intention was to use a -1 scale and the thing be viewed from inside its volume with its front faces on the interior, or if like the wheels it is still meant to be viewed from its exterior.

That can simply mean that if we detect -1 scaling then we disable face culling for children unless they use -1 scaling again. That could be a simple solution for glTF. It has a performance cost I guess as backfaces won’t be culled, and so I guess rasterisation would happen up to the point of early-z testing at least and hopefully the visible faces were already drawn. But otherwise I guess it would incur an overdraw cost.

@mgustavsson
Copy link

mgustavsson commented Jan 12, 2023

Hi I just found this conversation when I was having this issue in my own renderer (actually not bevy-related).

FYI I found out there actually is a correct way to handle this according to the GLTF spec 3.7.4:

When a mesh primitive uses any triangle-based topology (i.e., triangles, triangle strip, or triangle fan), the determinant of the node’s global transform defines the winding order of that primitive. If the determinant is a positive value, the winding order triangle faces is counterclockwise; in the opposite case, the winding order is clockwise.

https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#indices-and-names

So if you want to support this either you can change the winding order / face culling setting when rendering based on the determinant, or create a second set of indices with swapped winding order and choose between those depending on the determinant.

github-merge-queue bot pushed a commit that referenced this issue Sep 18, 2023
# 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 #4738, probably fixes #7901.

---------

Co-authored-by: François <[email protected]>
rdrpenguin04 pushed a commit to rdrpenguin04/bevy that referenced this issue 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 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Assets Load files from disk to use for things like images, models, and sounds A-Rendering Drawing game state to the screen C-Bug An unexpected or incorrect behavior
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants