Page MenuHome

Non-Uniform scaling of hair emitters results in incorrect Cycles renders
Closed, ResolvedPublicBUG


Blender Version
Broken: 2.90, 5d2005c

Short description of error
When a hair emitter has non-uniform scaling applied, Cycles will render the hair particles incorrectly when they're part of an instanced BVH, for example when object motion blur is applied. Hair may abruptly change in width and/or shading.

Exact steps for others to reproduce the error
Open the attached Blender scene and render the animation.

Event Timeline

Stefan Werner (swerner) changed the task status from Needs Triage to Confirmed.Jul 6 2020, 1:35 PM
Stefan Werner (swerner) claimed this task.
Stefan Werner (swerner) created this task.

The cause seems to be that when BVH traversal goes into instanced BVHs, the inverse, non-uniformly scaled object matrix is being applied to the ray's origin and direction. The ray/curve intersection routines are unable to take that into account and both intersection calculation as well as the calculation of the normal are incorrect as a result.

One complex way of fixing this would be to handle such hair geometry in its own BVH, pull the non-uniform scaling out of the object matrices and bake it into the hair CVs. Uniform scaling and all the other transformations would still be applied as ususal. In scenes where the same hair object is instantiated multiple times with different scaling applied, this would lead to increased memory usage since those instances would need to be turned into real geometry.

This can be reproduced with Cycles' BVH, Embree and Optix (D8223), so this is not isolated to our intersection code.

Maybe we should just never do the optimization where we apply the transform for static objects, when there is non-uniformly scaled hair? That at least would avoid the abrupt changes.

For non-uniform scaling, the instanced result is closer to what I would expect to happen. It's not clear to me that it's actually good to somehow keep the hair round while a triangle mesh with equivalent shape would not do that either.

Are there practical use case for non-uniform scaling, or can we just fix the abrupt change and not worry about this further?

Come to think of - hair in motion is intersected correctly, scaled and squished according to the transformation matrix. The static ones are wrong. Not sure yet about whether normals are correct.

Brecht Van Lommel (brecht) changed the subtype of this task from "Report" to "Bug".Jul 7 2020, 2:28 PM

So what I'm proposing is to do this:

diff --git a/intern/cycles/render/object.cpp b/intern/cycles/render/object.cpp
index c45ae55..f200e40 100644
--- a/intern/cycles/render/object.cpp
+++ b/intern/cycles/render/object.cpp
@@ -823,6 +823,12 @@ void ObjectManager::apply_static_transforms(DeviceScene *dscene, Scene *scene, P
       Mesh *mesh = static_cast<Mesh *>(geom);
       apply = apply && mesh->subdivision_type == Mesh::SUBDIVISION_NONE;
+    else if (geom->type == Geometry::HAIR) {
+      /* Can't apply non-uniform scale to curves, this can't be represented by
+       * control points and radius alone. */
+      float scale;
+      apply = apply && transform_uniform_scale(object->tfm, scale);
+    }
     if (apply) {
       if (!(motion_blur && object->use_motion())) {

The normals are correct as far as I can tell. They are transformed from object to world space with the inverse transpose matrix just like we do for meshes.