Fix: Cycles: Wrong derivative handling in OptiX OSL transform()

osl_transform_triple(), osl_transform_dvmdv() and so on are supposed to apply
the given transform in the context of OSL's auto-differentiation system.
Therefore, the given input is a dual vector, containing both the value as v[0]
and its derivatives w.r.t. X and Y in v[1] and v[2].

However, the existing code treats these as a simple list of vectors, applying
the same operation to all three instead of propagating the derivatives.
On top of that, it also treated the given matrix input as if there were three
of them, which isn't the case.

Therefore, this commit replaces the implementation to do the right thing.
The Vector and Normal case are straightforward since the operation is linear,
so applying the same operation to all three vectors works.
The Point case is a bit more complicated, but not too bad when written out.

This bug mostly became apparent when using Object or Camera texture coordinates
with a Bump node, since that node uses OSL differentials and Object/Camera
coordinates are implemented using transform().

I'm pretty sure that all the other builtin functions (e.g. sin) at the bottom
of services_gpu.h have the same problem, but one thing at a time...

Pull Request: https://projects.blender.org/blender/blender/pulls/138045
This commit is contained in:
Lukas Stockner
2025-04-28 12:46:54 +02:00
parent d4a57e6cbb
commit 8bc9f174d3
2 changed files with 31 additions and 5 deletions

View File

@@ -581,9 +581,8 @@ ccl_device_extern void osl_transform_dvmdv(ccl_private float3 *res,
const ccl_private float *m,
const ccl_private float3 *v)
{
for (int i = 0; i < 3; ++i) {
osl_transform_vmv(res + i, m + i * 16, v + i);
}
const ProjectionTransform tfm_m = make_projection(m);
res[0] = transform_perspective_deriv(&tfm_m, v[0], v[1], v[2], res[1], res[2]);
}
ccl_device_extern void osl_transformv_vmv(ccl_private float3 *res,
@@ -598,8 +597,9 @@ ccl_device_extern void osl_transformv_dvmdv(ccl_private float3 *res,
const ccl_private float *m,
const ccl_private float3 *v)
{
const Transform tfm_m = make_transform(m);
for (int i = 0; i < 3; ++i) {
osl_transformv_vmv(res + i, m + i * 16, v + i);
res[i] = transform_direction(&tfm_m, v[i]);
}
}
@@ -615,8 +615,9 @@ ccl_device_extern void osl_transformn_dvmdv(ccl_private float3 *res,
const ccl_private float *m,
const ccl_private float3 *v)
{
const Transform tfm_m = transform_inverse(make_transform(m));
for (int i = 0; i < 3; ++i) {
osl_transformn_vmv(res + i, m + i * 16, v + i);
res[i] = transform_direction_transposed(&tfm_m, v[i]);
}
}

View File

@@ -111,6 +111,31 @@ ccl_device_inline float3 transform_perspective_direction(const ccl_private Proje
return c;
}
/* Applies transform t to point a with given derivatives da/dx and da/dy.
* Returns t(a) and sets out_dx/dy to the values of dt(a)/dx and dt(a)/dy, respectively. */
ccl_device_inline float3 transform_perspective_deriv(const ccl_private ProjectionTransform *t,
const float3 a,
const float3 dx,
const float3 dy,
ccl_private float3 &out_dx,
ccl_private float3 &out_dy)
{
const float4 b = make_float4(a.x, a.y, a.z, 1.0f);
const float3 c = make_float3(dot(t->x, b), dot(t->y, b), dot(t->z, b));
const float w = dot(t->w, b);
if (w != 0.0f) {
out_dx = (transform_perspective_direction(t, dx) - dot(make_float3(t->w), dx) * c) / w;
out_dy = (transform_perspective_direction(t, dy) - dot(make_float3(t->w), dy) * c) / w;
return c / w;
}
else {
out_dx = zero_float3();
out_dy = zero_float3();
return zero_float3();
}
}
ccl_device_inline ProjectionTransform make_projection(const float a,
const float b,
const float c,