This patch implements blue-noise dithered sampling as described by Nathan Vegdahl (https://psychopath.io/post/2022_07_24_owen_scrambling_based_dithered_blue_noise_sampling), which in turn is based on "Screen-Space Blue-Noise Diffusion of Monte Carlo Sampling Error via Hierarchical Ordering of Pixels"(https://repository.kaust.edu.sa/items/1269ae24-2596-400b-a839-e54486033a93). The basic idea is simple: Instead of generating independent sequences for each pixel by scrambling them, we use a single sequence for the entire image, with each pixel getting one chunk of the samples. The ordering across pixels is determined by hierarchical scrambling of the pixel's position along a space-filling curve, which ends up being pretty much the same operation as already used for the underlying sequence. This results in a more high-frequency noise distribution, which appears smoother despite not being less noisy overall. The main limitation at the moment is that the improvement is only clear if the full sample amount is used per pixel, so interactive preview rendering and adaptive sampling will not receive the benefit. One exception to this is that when using the new "Automatic" setting, the first sample in interactive rendering will also be blue-noise-distributed. The sampling mode option is now exposed in the UI, with the three options being Blue Noise (the new mode), Classic (the previous Tabulated Sobol method) and the new default, Automatic (blue noise, with the additional property of ensuring the first sample is also blue-noise-distributed in interactive rendering). When debug mode is enabled, additional options appear, such as Sobol-Burley. Note that the scrambling distance option is not compatible with the blue-noise pattern. Pull Request: https://projects.blender.org/blender/blender/pulls/118479
233 lines
8.9 KiB
C
233 lines
8.9 KiB
C
/* SPDX-FileCopyrightText: 2011-2023 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#pragma once
|
|
|
|
#include "kernel/bvh/bvh.h"
|
|
#include "kernel/integrator/path_state.h"
|
|
#include "kernel/integrator/shade_surface.h"
|
|
#include "kernel/integrator/shadow_linking.h"
|
|
#include "kernel/light/light.h"
|
|
#include "kernel/sample/lcg.h"
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
#ifdef __SHADOW_LINKING__
|
|
|
|
# define SHADOW_LINK_MAX_INTERSECTION_COUNT 1024
|
|
|
|
/* Intersect mesh objects.
|
|
*
|
|
* Returns the total number of emissive surfaces hit, and the intersection contains a random
|
|
* intersected emitter to which the dedicated shadow ray is to eb traced.
|
|
*
|
|
* NOTE: Sets the ray tmax to the maximum intersection distance (past which no lights are to be
|
|
* considered for shadow linking). */
|
|
ccl_device int shadow_linking_pick_mesh_intersection(KernelGlobals kg,
|
|
IntegratorState state,
|
|
ccl_private Ray *ccl_restrict ray,
|
|
const int object_receiver,
|
|
ccl_private Intersection *ccl_restrict
|
|
linked_isect,
|
|
ccl_private uint *lcg_state,
|
|
int num_hits)
|
|
{
|
|
/* The tmin will be offset, so store its current value and restore later on, allowing a separate
|
|
* light intersection loop starting from the actual ray origin. */
|
|
const float old_tmin = ray->tmin;
|
|
|
|
const uint visibility = path_state_ray_visibility(state);
|
|
|
|
int transparent_bounce = INTEGRATOR_STATE(state, path, transparent_bounce);
|
|
int volume_bounce = INTEGRATOR_STATE(state, path, volume_bounce);
|
|
|
|
/* TODO: Replace the look with sequential calls to the kernel, similar to the transparent shadow
|
|
* intersection kernel. */
|
|
for (int i = 0; i < SHADOW_LINK_MAX_INTERSECTION_COUNT; i++) {
|
|
Intersection current_isect ccl_optional_struct_init;
|
|
current_isect.object = OBJECT_NONE;
|
|
current_isect.prim = PRIM_NONE;
|
|
|
|
const bool hit = scene_intersect(kg, ray, visibility, ¤t_isect);
|
|
if (!hit) {
|
|
break;
|
|
}
|
|
|
|
/* Only record primitives that potentially have emission.
|
|
* TODO: optimize with a dedicated ray visibility flag, which could then also be
|
|
* used once lights are in the BVH as geometry? */
|
|
const int shader = intersection_get_shader(kg, ¤t_isect);
|
|
const int shader_flags = kernel_data_fetch(shaders, shader).flags;
|
|
if (light_link_object_match(kg, object_receiver, current_isect.object) &&
|
|
(shader_flags & SD_HAS_EMISSION))
|
|
{
|
|
const uint64_t set_membership =
|
|
kernel_data_fetch(objects, current_isect.object).shadow_set_membership;
|
|
if (set_membership != LIGHT_LINK_MASK_ALL) {
|
|
++num_hits;
|
|
|
|
if ((linked_isect->prim == PRIM_NONE) || (lcg_step_float(lcg_state) < 1.0f / num_hits)) {
|
|
*linked_isect = current_isect;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Contribution from the lights past the default opaque blocker is accumulated
|
|
* using the main path. */
|
|
if (!(shader_flags & (SD_HAS_ONLY_VOLUME | SD_HAS_TRANSPARENT_SHADOW))) {
|
|
const uint blocker_set = kernel_data_fetch(objects, current_isect.object).blocker_shadow_set;
|
|
if (blocker_set == 0) {
|
|
ray->tmax = current_isect.t;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
/* Lights past the maximum allowed transparency bounce do not contribute any light, so
|
|
* consider them as fully blocked and only consider lights prior to this intersection. */
|
|
if (shader_flags & SD_HAS_TRANSPARENT_SHADOW) {
|
|
++transparent_bounce;
|
|
if (transparent_bounce >= kernel_data.integrator.transparent_max_bounce) {
|
|
ray->tmax = current_isect.t;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
kernel_assert(shader_flags & SD_HAS_ONLY_VOLUME);
|
|
++volume_bounce;
|
|
if (volume_bounce >= kernel_data.integrator.max_volume_bounce) {
|
|
ray->tmax = current_isect.t;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Move the ray forward. */
|
|
ray->tmin = intersection_t_offset(current_isect.t);
|
|
}
|
|
|
|
ray->tmin = old_tmin;
|
|
|
|
return num_hits;
|
|
}
|
|
|
|
/* Pick a light for tracing a shadow ray for the shadow linking.
|
|
* Picks a random light which is intersected by the given ray, and stores the intersection result.
|
|
* If no lights were hit false is returned.
|
|
*
|
|
* NOTE: Sets the ray tmax to the maximum intersection distance (past which no lights are to be
|
|
* considered for shadow linking). */
|
|
ccl_device bool shadow_linking_pick_light_intersection(KernelGlobals kg,
|
|
IntegratorState state,
|
|
ccl_private Ray *ccl_restrict ray,
|
|
ccl_private Intersection *ccl_restrict
|
|
linked_isect)
|
|
{
|
|
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
|
|
|
|
const int last_type = INTEGRATOR_STATE(state, isect, type);
|
|
|
|
const int object_receiver = light_link_receiver_forward(kg, state);
|
|
|
|
uint lcg_state = lcg_state_init(INTEGRATOR_STATE(state, path, rng_pixel),
|
|
INTEGRATOR_STATE(state, path, rng_offset),
|
|
INTEGRATOR_STATE(state, path, sample),
|
|
0x68bc21eb);
|
|
|
|
/* Indicate that no intersection has been picked yet. */
|
|
linked_isect->prim = PRIM_NONE;
|
|
|
|
int num_hits = 0;
|
|
|
|
// TODO: Only if there are emissive meshes in the scene?
|
|
|
|
// TODO: Only if the ray hits any light? As in, check that there is a light first, before
|
|
// tracing potentially expensive ray.
|
|
|
|
num_hits = shadow_linking_pick_mesh_intersection(
|
|
kg, state, ray, object_receiver, linked_isect, &lcg_state, num_hits);
|
|
|
|
num_hits = lights_intersect_shadow_linked(kg,
|
|
ray,
|
|
linked_isect,
|
|
ray->self.prim,
|
|
ray->self.object,
|
|
last_type,
|
|
path_flag,
|
|
object_receiver,
|
|
&lcg_state,
|
|
num_hits);
|
|
|
|
if (num_hits == 0) {
|
|
return false;
|
|
}
|
|
|
|
INTEGRATOR_STATE_WRITE(state, shadow_link, dedicated_light_weight) = num_hits;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Check whether a special shadow ray is needed to calculate direct light contribution which comes
|
|
* from emitters which are behind objects which are blocking light for the main path, but are
|
|
* excluded from blocking light via shadow linking.
|
|
*
|
|
* If a special ray is needed a blocked light kernel is scheduled and true is returned, otherwise
|
|
* false is returned. */
|
|
ccl_device bool shadow_linking_intersect(KernelGlobals kg, IntegratorState state)
|
|
{
|
|
/* Verify that the kernel is only scheduled if it is actually needed. */
|
|
kernel_assert(shadow_linking_scene_need_shadow_ray(kg));
|
|
|
|
/* Read ray from integrator state into local memory. */
|
|
Ray ray ccl_optional_struct_init;
|
|
integrator_state_read_ray(state, &ray);
|
|
|
|
ray.self.prim = INTEGRATOR_STATE(state, isect, prim);
|
|
ray.self.object = INTEGRATOR_STATE(state, isect, object);
|
|
ray.self.light_object = OBJECT_NONE;
|
|
ray.self.light_prim = PRIM_NONE;
|
|
ray.self.light = LAMP_NONE;
|
|
|
|
Intersection isect ccl_optional_struct_init;
|
|
if (!shadow_linking_pick_light_intersection(kg, state, &ray, &isect)) {
|
|
/* No light is hit, no need in the extra shadow ray for the direct light. */
|
|
return false;
|
|
}
|
|
|
|
/* Make a copy of primitives needed by the main path self-intersection check before writing the
|
|
* new intersection. Those primitives will be restored before the main path is returned to the
|
|
* intersect_closest state. */
|
|
shadow_linking_store_last_primitives(state);
|
|
|
|
/* Write intersection result into global integrator state memory, so that the
|
|
* shade_dedicated_light kernel can use it for calculation of the light sample. */
|
|
integrator_state_write_isect(state, &isect);
|
|
|
|
integrator_path_next(kg,
|
|
state,
|
|
DEVICE_KERNEL_INTEGRATOR_INTERSECT_DEDICATED_LIGHT,
|
|
DEVICE_KERNEL_INTEGRATOR_SHADE_DEDICATED_LIGHT);
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif /* __SHADOW_LINKING__ */
|
|
|
|
ccl_device void integrator_intersect_dedicated_light(KernelGlobals kg, IntegratorState state)
|
|
{
|
|
PROFILING_INIT(kg, PROFILING_INTERSECT_DEDICATED_LIGHT);
|
|
|
|
#ifdef __SHADOW_LINKING__
|
|
if (shadow_linking_intersect(kg, state)) {
|
|
return;
|
|
}
|
|
#else
|
|
kernel_assert(!"integrator_intersect_dedicated_light is not supposed to be scheduled");
|
|
#endif
|
|
|
|
integrator_shade_surface_next_kernel<DEVICE_KERNEL_INTEGRATOR_INTERSECT_DEDICATED_LIGHT>(kg,
|
|
state);
|
|
}
|
|
|
|
CCL_NAMESPACE_END
|