RealityKit and USDZ: Winding Order Issue with Negatively Scaled Meshes

Hi all,

I've encountered a potential issue with how the winding order of geometry is handled when their transformations involve negative scaling.

I created a simple test asset, a single triangle, to demonstrate this. The triangle's vertices are defined in a counter-clockwise ("right-handed") winding order, and its transform has a negative scale on the X-axis. According to the OpenUSD specification, this negative determinant in the transformation matrix should effectively reverse the winding order of the geometry:

However, any given gprim's local-to-world transformation can flip its effective orientation, when it contains an odd number of negative scales. This condition can be reliably detected using the (Jacobian) determinant of the local-to-world transform: if the determinant is less than zero, then the gprim's orientation has been flipped, and therefore one must apply the opposite handedness rule when computing its surface normals (or just flip the computed normals) for the purposes of hidden surface detection and lighting calculations.

When I view the asset in tools like Blender or Preview on macOS, it behaves as expected. The triangle's effective orientation is flipped to CW.

However, when the same asset is viewed in Reality Composer Pro or with QuickLook on iOS, its effective orientation remains CCW. In other words, the triangle faces the opposite direction.

My questions for the community and Apple are:

  1. Is this behavior in RealityKit a known issue?
  2. If this is a known issue, is there official guidance for DCC tools on how to export USDZ assets to ensure they appear correctly in the Apple ecosystem?

Any insights or recommendations would be greatly appreciated.

Edited the original post, as I mislabelled the orientations :) This stuff is easy to mix up!

Hello @alexchuber , thank you for your question!

I've been unable to reproduce your issue. Here are the steps I've followed:

  1. Create a right-triangle mesh in blender that is only 3 vertices.
  2. Export the mesh as a USDA and verify the vertices are CCW by opening the file in a text editor.
  3. Import the USDA into Reality Composer Pro
  4. Create a new scene in RCP and add the triangle usd to the scene.
  5. Apply a (-90, 0, 0) rotation to the Root entity. This is necessary for any model created in Blender, because Blender uses the Z axis for "up" while RCP uses Y. See this thread from another developer for a more detailed explanation.
  6. Apply a (-1, 1, 1) scale to the asset. Note that it is now invisible because of backface culling, because the applied scale has reversed the normal of the face. You will need to rotate the viewport camera around to the other side to see the face. Visually, the triangle appears exactly the same as it did before applying the rotation, because the viewport camera was rotated.

In the steps above, I do see that the winding order has become "effectively reversed" because the normal is now flipped after the rotation. Note that winding order is not something that is actually modified at runtime because it is data that exists in the asset itself.

Also note that Blender by default has backface culling disabled, meaning Blender renders both sides of every face by default, so the triangle does not become invisible when applying the -1 scale on the X axis in the way that it does inside RCP, which does use backface culling.

Perhaps you could elaborate more on the steps you took? Are you remembering to apply all transforms in Blender before exporting ([Object Mode] Object > Apply > All transforms)? If there's something specific you are trying to do that is related to winding order and face normals, I'd be happy to help with that! Additionally, I encourage you to file feedback using Feedback Assistant if you believe you are experiencing a bug. Thank you for your question!

RealityKit and USDZ: Winding Order Issue with Negatively Scaled Meshes
 
 
Q