Fix #154166: Cycles: Raycast + Bump artifacts

The raycast node disables self intersection when the ray start is the
surface position.
However, this fails when evaluating bump.

This commit adds an extra offset to the ray start (tmin) when
evaluating bump.

Co-authored-by: Brecht Van Lommel <brecht@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/154222
This commit is contained in:
Miguel Pozo 2026-03-12 15:33:08 +01:00
parent c806cc465c
commit 7da2065b9d
12 changed files with 66 additions and 18 deletions

View file

@ -759,25 +759,21 @@ bool OSLRenderServices::trace(TraceOpt &options,
ray.P = make_float3(P.x, P.y, P.z);
ray.D = make_float3(R.x, R.y, R.z);
ray.tmin = 0.0f;
ray.tmax = (options.maxdist == 1.0e30f) ? FLT_MAX : options.maxdist - options.mindist;
ray.tmin = options.mindist;
ray.tmax = (options.maxdist == 1.0e30f) ? FLT_MAX : options.maxdist;
ray.time = sd->time;
ray.self.object = OBJECT_NONE;
ray.self.prim = PRIM_NONE;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
if (options.mindist == 0.0f) {
if (ray.tmin == 0.0f) {
/* avoid self-intersections */
if (ray.P == sd->P) {
ray.self.object = sd->object;
ray.self.prim = sd->prim;
}
}
else {
/* offset for minimum distance */
ray.P += options.mindist * ray.D;
}
/* ray differentials */
differential3 dP;

View file

@ -8,14 +8,29 @@ shader node_raycast(point Position = P,
normal Direction = N,
float Length = 1.0,
int only_local = 0,
float bump_filter_width = 0.0,
output float IsHit = 0.0,
output float SelfHit = 0.0,
output float HitDistance = Length,
output point HitPosition = point(0.0, 0.0, 0.0),
output normal HitNormal = normal(0.0, 0.0, 0.0))
{
if (trace(
Position, Direction, "maxdist", Length, "traceset", only_local ? "__only_local__" : ""))
float mindist = 0.0;
if (bump_filter_width > 0.0) {
/* If evaluating for bump mapping at a shifted position, increase min
* distance by slightly more than the shift distance to avoid self
* intersections. */
mindist = bump_filter_width * max(length(Dx(P)), length(Dy(P))) * 1.1;
}
if (trace(Position,
Direction,
"maxdist",
Length,
"mindist",
mindist,
"traceset",
only_local ? "__only_local__" : ""))
{
IsHit = 1.0;
getmessage("trace", "hitself", SelfHit);

View file

@ -32,7 +32,8 @@ ccl_device RaycastResult svm_raycast(KernelGlobals kg,
float3 position,
float3 direction,
float distance,
bool only_local)
bool only_local,
float bump_filter_width)
{
RaycastResult result;
result.distance = -1.0f;
@ -49,13 +50,23 @@ ccl_device RaycastResult svm_raycast(KernelGlobals kg,
return result;
}
const bool avoid_self_intersection = isequal(position, sd->P);
float tmin = 0.0f;
bool avoid_self_intersection = false;
if (bump_filter_width > 0.0f) {
/* If evaluating for bump mapping at a shifted position, increase min
* distance by slightly more than the shift distance to avoid self
* intersections. */
tmin = bump_filter_width * sd->dP * 1.1f;
}
else {
avoid_self_intersection = isequal(position, sd->P);
}
/* Create ray. */
Ray ray;
ray.P = position;
ray.D = direction;
ray.tmin = 0.0f;
ray.tmin = tmin;
ray.tmax = distance;
ray.time = sd->time;
ray.self.object = avoid_self_intersection ? sd->object : OBJECT_NONE;
@ -99,12 +110,13 @@ ccl_device_inline
# else
ccl_device_noinline
# endif
void
int
svm_node_raycast(KernelGlobals kg,
ConstIntegratorGenericState state,
ccl_private ShaderData *sd,
ccl_private float *stack,
const uint4 node)
const uint4 node,
int offset)
{
uint position_offset;
uint direction_offset;
@ -128,13 +140,17 @@ ccl_device_noinline
float3 hit_position = make_float3(0.0f);
float3 hit_normal = make_float3(0.0f);
uint4 data_node = read_node(kg, &offset);
IF_KERNEL_NODES_FEATURE(RAYTRACE)
{
uint only_local = node.w;
const uint only_local = node.w;
const float bump_filter_width = __uint_as_float(data_node.x);
float3 position = stack_load_float3(stack, position_offset);
float3 direction = stack_load_float3(stack, direction_offset);
RaycastResult result = svm_raycast(kg, state, sd, position, direction, distance, only_local);
RaycastResult result = svm_raycast(
kg, state, sd, position, direction, distance, only_local, bump_filter_width);
if (result.distance >= 0.0f) {
is_hit = 1.0f;
@ -160,6 +176,8 @@ ccl_device_noinline
if (stack_valid(hit_normal_offset)) {
stack_store_float3(stack, hit_normal_offset, hit_normal);
}
return offset;
}
#endif /* __SHADER_RAYTRACE__ */

View file

@ -457,7 +457,7 @@ ccl_device void svm_eval_nodes(KernelGlobals kg,
svm_node_ao<node_feature_mask>(kg, state, sd, stack, node);
break;
SVM_CASE(NODE_RAYCAST)
svm_node_raycast<node_feature_mask>(kg, state, sd, stack, node);
offset = svm_node_raycast<node_feature_mask>(kg, state, sd, stack, node, offset);
break;
#endif
SVM_CASE(NODE_AOV_START)

View file

@ -8100,11 +8100,13 @@ void RaycastNode::compile(SVMCompiler &compiler)
compiler.stack_assign(hit_position_out),
compiler.stack_assign(hit_normal_out)),
only_local);
compiler.add_node(__float_as_uint((bump == SHADER_BUMP_CENTER) ? 0.0f : bump_filter_width));
}
void RaycastNode::compile(OSLCompiler &compiler)
{
compiler.parameter(this, "only_local");
compiler.parameter("bump_filter_width", (bump == SHADER_BUMP_CENTER) ? 0.0f : bump_filter_width);
compiler.add(this, "node_raycast");
}

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a63f5f6c3c1866c63da28bfff8d9aadd850863225f5ece71e7bc954a4537efe5
size 20803

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4d960bddd7a224162467d1e79f1ed33898e85a040557dc1ed68c94f6fafdc7cb
size 26302

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e3b9bd75e9afbe0cf486b05bdb190c65e3f8994c59db9a043b00e593ef75e70a
size 133815

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b8f0b7a04f7c214a49b91dc8a745fa23420fa31ae36032e21d81c7b6d9bac2b4
size 746

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b8f0b7a04f7c214a49b91dc8a745fa23420fa31ae36032e21d81c7b6d9bac2b4
size 746

View file

@ -42,6 +42,8 @@ BLOCKLIST = [
"light_path_is_diffuse_ray.blend",
# Blocked due to stochastic diffuse/transmission layering resulting in non-deterministic surfel lighting.
"principled_bsdf_transmission.blend",
# Blocked due to platform-dependent noise differences (likely floating-point/fast-math differences).
"raycast_bump.blend",
]
BLOCKLIST_METAL = [

View file

@ -100,7 +100,7 @@ def main():
parser = create_argparse()
args = parser.parse_args()
blocklist = ["raycast_hit.blend", "raycast_normal.blend", "raycast_position.blend"]
blocklist = ["raycast_hit.blend", "raycast_normal.blend", "raycast_position.blend", "raycast_bump.blend"]
if args.gpu_backend == "vulkan":
blocklist += BLOCKLIST_VULKAN