Fix T49750: Cycles wrong ray differentials for perspective and stereo cameras.

This commit is contained in:
2016-10-22 15:59:23 +02:00
parent b5d527ff6c
commit 9d0ac94d52
2 changed files with 102 additions and 77 deletions

View File

@@ -105,39 +105,61 @@ ccl_device void camera_sample_perspective(KernelGlobals *kg, float raster_x, flo
}
#endif
float3 tP = transform_point(&cameratoworld, ray->P);
float3 tD = transform_direction(&cameratoworld, ray->D);
ray->P = spherical_stereo_position(kg, tD, tP);
ray->D = spherical_stereo_direction(kg, tD, tP, ray->P);
ray->P = transform_point(&cameratoworld, ray->P);
ray->D = normalize(transform_direction(&cameratoworld, ray->D));
bool use_stereo = kernel_data.cam.interocular_offset != 0.0f;
if (!use_stereo) {
/* No stereo */
#ifdef __RAY_DIFFERENTIALS__
float3 Dcenter = transform_direction(&cameratoworld, Pcamera);
ray->dP = differential3_zero();
ray->dD.dx = normalize(Dcenter + float4_to_float3(kernel_data.cam.dx)) - normalize(Dcenter);
ray->dD.dy = normalize(Dcenter + float4_to_float3(kernel_data.cam.dy)) - normalize(Dcenter);
#endif
}
else {
/* Spherical stereo */
spherical_stereo_transform(kg, &ray->P, &ray->D);
#ifdef __RAY_DIFFERENTIALS__
/* ray differential */
ray->dP = differential3_zero();
/* Ray differentials, computed from scratch using the raster coordinates
* because we don't want to be affected by depth of field. We compute
* ray origin and direction for the center and two neighbouring pixels
* and simply take their differences. */
float3 Pnostereo = transform_point(&cameratoworld, make_float3(0.0f, 0.0f, 0.0f));
float3 tD_diff = transform_direction(&cameratoworld, Pcamera);
float3 Pdiff = spherical_stereo_position(kg, tD_diff, Pcamera);
float3 Ddiff = spherical_stereo_direction(kg, tD_diff, Pcamera, Pdiff);
float3 Pcenter = Pnostereo;
float3 Dcenter = Pcamera;
Dcenter = normalize(transform_direction(&cameratoworld, Dcenter));
spherical_stereo_transform(kg, &Pcenter, &Dcenter);
tP = transform_perspective(&rastertocamera,
make_float3(raster_x + 1.0f, raster_y, 0.0f));
tD = tD_diff + float4_to_float3(kernel_data.cam.dx);
Pcamera = spherical_stereo_position(kg, tD, tP);
ray->dD.dx = spherical_stereo_direction(kg, tD, tP, Pcamera) - Ddiff;
ray->dP.dx = Pcamera - Pdiff;
float3 Px = Pnostereo;
float3 Dx = transform_perspective(&rastertocamera, make_float3(raster_x + 1.0f, raster_y, 0.0f));
Dx = normalize(transform_direction(&cameratoworld, Dx));
spherical_stereo_transform(kg, &Px, &Dx);
tP = transform_perspective(&rastertocamera,
make_float3(raster_x, raster_y + 1.0f, 0.0f));
tD = tD_diff + float4_to_float3(kernel_data.cam.dy);
Pcamera = spherical_stereo_position(kg, tD, tP);
ray->dD.dy = spherical_stereo_direction(kg, tD, tP, Pcamera) - Ddiff;
/* dP.dy is zero, since the omnidirectional panorama only shift the eyes horizontally */
ray->dP.dx = Px - Pcenter;
ray->dD.dx = Dx - Dcenter;
float3 Py = Pnostereo;
float3 Dy = transform_perspective(&rastertocamera, make_float3(raster_x, raster_y + 1.0f, 0.0f));
Dy = normalize(transform_direction(&cameratoworld, Dy));
spherical_stereo_transform(kg, &Py, &Dy);
ray->dP.dy = Py - Pcenter;
ray->dD.dy = Dy - Dcenter;
#endif
}
#ifdef __CAMERA_CLIPPING__
/* clipping */
float3 Pclip = normalize(Pcamera);
float z_inv = 1.0f / Pclip.z;
ray->P += kernel_data.cam.nearclip*ray->D * z_inv;
float z_inv = 1.0f / normalize(Pcamera).z;
float nearclip = kernel_data.cam.nearclip * z_inv;
ray->P += nearclip * ray->D;
ray->dP.dx += nearclip * ray->dD.dx;
ray->dP.dy += nearclip * ray->dD.dy;
ray->t = kernel_data.cam.cliplength * z_inv;
#else
ray->t = FLT_MAX;
@@ -268,36 +290,57 @@ ccl_device_inline void camera_sample_panorama(KernelGlobals *kg,
}
#endif
float3 tP = transform_point(&cameratoworld, ray->P);
float3 tD = transform_direction(&cameratoworld, ray->D);
ray->P = spherical_stereo_position(kg, tD, tP);
ray->D = spherical_stereo_direction(kg, tD, tP, ray->P);
ray->P = transform_point(&cameratoworld, ray->P);
ray->D = normalize(transform_direction(&cameratoworld, ray->D));
/* Stereo transform */
bool use_stereo = kernel_data.cam.interocular_offset != 0.0f;
if (use_stereo) {
spherical_stereo_transform(kg, &ray->P, &ray->D);
}
#ifdef __RAY_DIFFERENTIALS__
/* ray differential */
ray->dP = differential3_zero();
/* Ray differentials, computed from scratch using the raster coordinates
* because we don't want to be affected by depth of field. We compute
* ray origin and direction for the center and two neighbouring pixels
* and simply take their differences. */
float3 Pcenter = Pcamera;
float3 Dcenter = panorama_to_direction(kg, Pcenter.x, Pcenter.y);
Pcenter = transform_point(&cameratoworld, Pcenter);
Dcenter = normalize(transform_direction(&cameratoworld, Dcenter));
if (use_stereo) {
spherical_stereo_transform(kg, &Pcenter, &Dcenter);
}
tP = transform_perspective(&rastertocamera, make_float3(raster_x, raster_y, 0.0f));
tD = transform_direction(&cameratoworld, panorama_to_direction(kg, tP.x, tP.y));
float3 Pdiff = spherical_stereo_position(kg, tD, tP);
float3 Ddiff = spherical_stereo_direction(kg, tD, tP, Pdiff);
float3 Px = transform_perspective(&rastertocamera, make_float3(raster_x + 1.0f, raster_y, 0.0f));
float3 Dx = panorama_to_direction(kg, Px.x, Px.y);
Px = transform_point(&cameratoworld, Px);
Dx = normalize(transform_direction(&cameratoworld, Dx));
if (use_stereo) {
spherical_stereo_transform(kg, &Px, &Dx);
}
tP = transform_perspective(&rastertocamera, make_float3(raster_x + 1.0f, raster_y, 0.0f));
tD = transform_direction(&cameratoworld, panorama_to_direction(kg, tP.x, tP.y));
Pcamera = spherical_stereo_position(kg, tD, tP);
ray->dD.dx = spherical_stereo_direction(kg, tD, tP, Pcamera) - Ddiff;
ray->dP.dx = Pcamera - Pdiff;
ray->dP.dx = Px - Pcenter;
ray->dD.dx = Dx - Dcenter;
tP = transform_perspective(&rastertocamera, make_float3(raster_x, raster_y + 1.0f, 0.0f));
tD = transform_direction(&cameratoworld, panorama_to_direction(kg, tP.x, tP.y));
Pcamera = spherical_stereo_position(kg, tD, tP);
ray->dD.dy = spherical_stereo_direction(kg, tD, tP, Pcamera) - Ddiff;
/* dP.dy is zero, since the omnidirectional panorama only shift the eyes horizontally */
float3 Py = transform_perspective(&rastertocamera, make_float3(raster_x, raster_y + 1.0f, 0.0f));
float3 Dy = panorama_to_direction(kg, Py.x, Py.y);
Py = transform_point(&cameratoworld, Py);
Dy = normalize(transform_direction(&cameratoworld, Dy));
if (use_stereo) {
spherical_stereo_transform(kg, &Py, &Dy);
}
ray->dP.dy = Py - Pcenter;
ray->dD.dy = Dy - Dcenter;
#endif
#ifdef __CAMERA_CLIPPING__
/* clipping */
ray->P += kernel_data.cam.nearclip*ray->D;
float nearclip = kernel_data.cam.nearclip;
ray->P += nearclip * ray->D;
ray->dP.dx += nearclip * ray->dD.dx;
ray->dP.dy += nearclip * ray->dD.dy;
ray->t = kernel_data.cam.cliplength;
#else
ray->t = FLT_MAX;

View File

@@ -224,24 +224,18 @@ ccl_device_inline float2 direction_to_panorama(KernelGlobals *kg, float3 dir)
}
}
ccl_device_inline float3 spherical_stereo_position(KernelGlobals *kg,
float3 dir,
float3 pos)
ccl_device_inline void spherical_stereo_transform(KernelGlobals *kg, float3 *P, float3 *D)
{
float interocular_offset = kernel_data.cam.interocular_offset;
/* Interocular offset of zero means either non stereo, or stereo without
* spherical stereo.
*/
if(interocular_offset == 0.0f) {
return pos;
}
* spherical stereo. */
kernel_assert(interocular_offset != 0.0f);
if(kernel_data.cam.pole_merge_angle_to > 0.0f) {
float3 normalized_direction = normalize(dir);
const float pole_merge_angle_from = kernel_data.cam.pole_merge_angle_from,
pole_merge_angle_to = kernel_data.cam.pole_merge_angle_to;
float altitude = fabsf(safe_asinf(normalized_direction.z));
float altitude = fabsf(safe_asinf(D->z));
if(altitude > pole_merge_angle_to) {
interocular_offset = 0.0f;
}
@@ -253,32 +247,20 @@ ccl_device_inline float3 spherical_stereo_position(KernelGlobals *kg,
}
float3 up = make_float3(0.0f, 0.0f, 1.0f);
float3 side = normalize(cross(dir, up));
float3 side = normalize(cross(*D, up));
float3 stereo_offset = side * interocular_offset;
return pos + (side * interocular_offset);
}
*P += stereo_offset;
/* NOTE: Ensures direction is normalized. */
ccl_device float3 spherical_stereo_direction(KernelGlobals *kg,
float3 dir,
float3 pos,
float3 newpos)
{
/* Convergence distance is FLT_MAX in the case of parallel convergence mode,
* no need to modify direction in this case either. */
const float convergence_distance = kernel_data.cam.convergence_distance;
const float3 normalized_dir = normalize(dir);
/* Interocular offset of zero means either no stereo, or stereo without
* spherical stereo.
* Convergence distance is FLT_MAX in the case of parallel convergence mode,
* no need to mdify direction in this case either.
*/
if(kernel_data.cam.interocular_offset == 0.0f ||
convergence_distance == FLT_MAX)
{
return normalized_dir;
}
float3 screenpos = pos + (normalized_dir * convergence_distance);
return normalize(screenpos - newpos);
if(convergence_distance != FLT_MAX)
{
float3 screen_offset = convergence_distance * (*D);
*D = normalize(screen_offset - stereo_offset);
}
}
CCL_NAMESPACE_END