Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Functions to evaluate displacement shader. */
#pragma once
#include "kernel/globals.h"
#ifdef __SVM__
# include "kernel/svm/svm.h"
#endif
#ifdef __OSL__
# include "kernel/osl/osl.h"
#endif
CCL_NAMESPACE_BEGIN
template<typename ConstIntegratorGenericState>
ccl_device void displacement_shader_eval(KernelGlobals kg,
ConstIntegratorGenericState state,
ccl_private ShaderData *sd)
{
sd->lcg_state = 0;
sd->num_closure = 0;
sd->num_closure_left = 0;
/* this will modify sd->P */
#ifdef __OSL__
if (kernel_data.kernel_features & KERNEL_FEATURE_OSL_SHADING) {
osl_eval_nodes<SHADER_TYPE_DISPLACEMENT>(
kg, state, sd, PATH_RAY_VISIBILITY_NONE, PATH_RAY_FLAG_NONE);
}
else
#endif
{
#ifdef __SVM__
svm_eval_nodes<KERNEL_FEATURE_NODE_MASK_DISPLACEMENT, SHADER_TYPE_DISPLACEMENT>(
kg, state, sd, nullptr, PATH_RAY_VISIBILITY_NONE, PATH_RAY_FLAG_NONE);
#endif
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,752 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/types.h"
#include "kernel/integrator/state.h"
#include "kernel/util/colorspace.h"
/* FIXME: The below include could be guarded behind `ifdef WITH_CYCLES_DEBUG`, but Metal
* pre-processing is not expanding guarded include files properly. */
#include "kernel/closure/bsdf.h"
#include "util/color.h"
CCL_NAMESPACE_BEGIN
#define GUIDING_FLT_LARGE 1.844e18f
#define GUIDING_MAX_LIGHT_DISTANCE 1e6f
/* Utilities. */
struct GuidingRISSample {
float3 rand;
float2 sampled_roughness;
/* The relative IOR of the outgoing media and the incoming media. */
float eta{1.0f};
int label;
float3 wo;
float bsdf_pdf{0.0f};
float guide_pdf{0.0f};
float ris_target{0.0f};
float ris_pdf{0.0f};
float ris_weight{0.0f};
float incoming_radiance_pdf{0.0f};
BsdfEval bsdf_eval;
float avg_bsdf_eval{0.0f};
Spectrum eval{zero_spectrum()};
};
ccl_device_forceinline bool calculate_ris_target(
ccl_attr_maybe_unused ccl_private GuidingRISSample *ris_sample,
ccl_attr_maybe_unused const ccl_private float guiding_sampling_prob)
{
#if defined(__PATH_GUIDING__)
const float pi_factor = 2.0f;
if (ris_sample->avg_bsdf_eval > 0.0f && ris_sample->bsdf_pdf > 1e-10f &&
ris_sample->guide_pdf > 0.0f)
{
ris_sample->ris_target = (ris_sample->avg_bsdf_eval *
((((1.0f - guiding_sampling_prob) * (1.0f / (pi_factor * M_PI_F))) +
(guiding_sampling_prob * ris_sample->incoming_radiance_pdf))));
ris_sample->ris_pdf = (0.5f * (ris_sample->bsdf_pdf + ris_sample->guide_pdf));
ris_sample->ris_weight = ris_sample->ris_target / ris_sample->ris_pdf;
return true;
}
ris_sample->ris_target = 0.0f;
ris_sample->ris_pdf = 0.0f;
return false;
#else
return false;
#endif
}
#if defined(__PATH_GUIDING__)
static pgl_vec3f guiding_vec3f(const float3 v)
{
return {v.x, v.y, v.z};
}
ccl_device_forceinline pgl_point3f guiding_point3f(const float3 v)
{
return {v.x, v.y, v.z};
}
ccl_device_forceinline float3 make_float3(const pgl_vec3f v)
{
return make_float3(v.x, v.y, v.z);
}
ccl_device_forceinline bool is_guiding_valid(const float3 v)
{
const Interval<float> interval = {-GUIDING_FLT_LARGE, GUIDING_FLT_LARGE};
bool valid = true;
valid &= isfinite_safe(v);
valid &= interval.contains(v.x);
valid &= interval.contains(v.y);
valid &= interval.contains(v.z);
return valid;
}
ccl_device_forceinline float3 clamp_guiding_position(const float3 p)
{
/* Clamping to the range of +/- GUIDING_FLT_LARGE / 5.0 to avoid potential numerical problems on
* the OpenPGL side. NOTE: The clamping is mainly a robustness fallback and might not be needed
* at all. */
return clamp(p, make_float3(-GUIDING_FLT_LARGE / 5.0f), make_float3(GUIDING_FLT_LARGE / 5.0f));
}
#endif
/* Path recording for guiding. */
/* Record Surface Interactions */
/* Records/Adds a new path segment with the current path vertex on a surface.
* If the path is not terminated this call is usually followed by a call of
* guiding_record_surface_bounce. */
ccl_device_forceinline void guiding_record_surface_segment(
ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState state,
ccl_attr_maybe_unused const ccl_private ShaderData *sd)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
const pgl_vec3f zero = guiding_vec3f(zero_float3());
const pgl_vec3f one = guiding_vec3f(one_float3());
state->guiding.path_segment = kg->opgl_path_segment_storage->NextSegment();
/* FIXME: investigate and fix why state->guiding.path_segment could be nullptr. */
kernel_assert(state->guiding.path_segment != nullptr);
if (state->guiding.path_segment != nullptr) {
float3 p = sd->P;
kernel_assert(is_guiding_valid(p));
p = clamp_guiding_position(p);
openpgl::cpp::SetPosition(state->guiding.path_segment, guiding_point3f(p));
openpgl::cpp::SetDirectionOut(state->guiding.path_segment, guiding_vec3f(sd->wi));
openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, false);
openpgl::cpp::SetScatteredContribution(state->guiding.path_segment, zero);
openpgl::cpp::SetDirectContribution(state->guiding.path_segment, zero);
openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, one);
openpgl::cpp::SetEta(state->guiding.path_segment, 1.0);
}
#endif
}
/* Records the surface scattering event at the current vertex position of the segment. */
ccl_device_forceinline void guiding_record_surface_bounce(
ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState state,
ccl_attr_maybe_unused const Spectrum weight,
ccl_attr_maybe_unused const float pdf,
ccl_attr_maybe_unused const float3 N,
ccl_attr_maybe_unused const float3 wo,
ccl_attr_maybe_unused const float2 roughness,
ccl_attr_maybe_unused const float eta)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
const float min_roughness = safe_sqrtf(fminf(roughness.x, roughness.y));
const bool is_delta = (min_roughness == 0.0f);
const float3 weight_rgb = spectrum_to_rgb(weight);
const float3 normal = clamp(N, -one_float3(), one_float3());
kernel_assert(state->guiding.path_segment != nullptr);
if (state->guiding.path_segment != nullptr) {
openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, guiding_vec3f(one_float3()));
openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, false);
openpgl::cpp::SetNormal(state->guiding.path_segment, guiding_vec3f(normal));
openpgl::cpp::SetDirectionIn(state->guiding.path_segment, guiding_vec3f(wo));
openpgl::cpp::SetPDFDirectionIn(state->guiding.path_segment, pdf);
openpgl::cpp::SetScatteringWeight(state->guiding.path_segment, guiding_vec3f(weight_rgb));
openpgl::cpp::SetIsDelta(state->guiding.path_segment, is_delta);
openpgl::cpp::SetEta(state->guiding.path_segment, eta);
openpgl::cpp::SetRoughness(state->guiding.path_segment, min_roughness);
}
#endif
}
/* Records the emission at the current surface intersection (physical or virtual) */
ccl_device_forceinline void guiding_record_surface_emission(
ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState state,
ccl_attr_maybe_unused const Spectrum Le,
ccl_attr_maybe_unused const float mis_weight)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
kernel_assert(state->guiding.path_segment != nullptr);
if (state->guiding.path_segment != nullptr) {
const float3 Le_rgb = spectrum_to_rgb(Le);
openpgl::cpp::SetDirectContribution(state->guiding.path_segment, guiding_vec3f(Le_rgb));
openpgl::cpp::SetMiWeight(state->guiding.path_segment, mis_weight);
}
#endif
}
/* Record BSSRDF Interactions */
/* Records/Adds a new path segment where the vertex position is the point of entry
* of the sub surface scattering boundary.
* If the path is not terminated this call is usually followed by a call of
* guiding_record_bssrdf_weight and guiding_record_bssrdf_bounce. */
ccl_device_forceinline void guiding_record_bssrdf_segment(ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState
state,
ccl_attr_maybe_unused const float3 P,
ccl_attr_maybe_unused const float3 wi)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
const pgl_vec3f zero = guiding_vec3f(zero_float3());
const pgl_vec3f one = guiding_vec3f(one_float3());
state->guiding.path_segment = kg->opgl_path_segment_storage->NextSegment();
/* FIXME: investigate and fix why state->guiding.path_segment could be nullptr. */
kernel_assert(state->guiding.path_segment != nullptr);
if (state->guiding.path_segment != nullptr) {
kernel_assert(is_guiding_valid(P));
float3 p = clamp_guiding_position(P);
openpgl::cpp::SetPosition(state->guiding.path_segment, guiding_point3f(p));
openpgl::cpp::SetDirectionOut(state->guiding.path_segment, guiding_vec3f(wi));
openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, true);
openpgl::cpp::SetScatteredContribution(state->guiding.path_segment, zero);
openpgl::cpp::SetDirectContribution(state->guiding.path_segment, zero);
openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, one);
openpgl::cpp::SetEta(state->guiding.path_segment, 1.0);
}
#endif
}
/* Records the transmission of the path at the point of entry while passing
* the surface boundary. */
ccl_device_forceinline void guiding_record_bssrdf_weight(
ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState state,
ccl_attr_maybe_unused const Spectrum weight,
ccl_attr_maybe_unused const Spectrum albedo)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
/* NOTE: Albedo left out here, will be included in guiding_record_bssrdf_bounce. */
const float3 weight_rgb = spectrum_to_rgb(safe_divide_color(weight, albedo));
kernel_assert(state->guiding.path_segment != nullptr);
if (state->guiding.path_segment != nullptr) {
openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment,
guiding_vec3f(zero_float3()));
openpgl::cpp::SetScatteringWeight(state->guiding.path_segment, guiding_vec3f(weight_rgb));
openpgl::cpp::SetIsDelta(state->guiding.path_segment, false);
openpgl::cpp::SetEta(state->guiding.path_segment, 1.0f);
openpgl::cpp::SetRoughness(state->guiding.path_segment, 1.0f);
}
#endif
}
/* Records the direction at the point of entry the path takes when sampling the SSS contribution.
* If not terminated this function is usually followed by a call of
* guiding_record_volume_transmission to record the transmittance between the point of entry and
* the point of exit. */
ccl_device_forceinline void guiding_record_bssrdf_bounce(
ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState state,
ccl_attr_maybe_unused const float pdf,
ccl_attr_maybe_unused const float3 N,
ccl_attr_maybe_unused const float3 wo,
ccl_attr_maybe_unused const Spectrum weight,
ccl_attr_maybe_unused const Spectrum albedo)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
const float3 normal = clamp(N, -one_float3(), one_float3());
const float3 weight_rgb = spectrum_to_rgb(weight * albedo);
kernel_assert(state->guiding.path_segment != nullptr);
if (state->guiding.path_segment != nullptr) {
openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, false);
openpgl::cpp::SetNormal(state->guiding.path_segment, guiding_vec3f(normal));
openpgl::cpp::SetDirectionIn(state->guiding.path_segment, guiding_vec3f(wo));
openpgl::cpp::SetPDFDirectionIn(state->guiding.path_segment, pdf);
openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, guiding_vec3f(weight_rgb));
}
#endif
}
/* Record Volume Interactions */
/* Records/Adds a new path segment with the current path vertex being inside a volume.
* If the path is not terminated this call is usually followed by a call of
* guiding_record_volume_bounce. */
ccl_device_forceinline void guiding_record_volume_segment(ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState
state,
ccl_attr_maybe_unused const float3 P,
ccl_attr_maybe_unused const float3 I)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
const pgl_vec3f zero = guiding_vec3f(zero_float3());
const pgl_vec3f one = guiding_vec3f(one_float3());
state->guiding.path_segment = kg->opgl_path_segment_storage->NextSegment();
/* FIXME: investigate and fix why state->guiding.path_segment could be nullptr. */
kernel_assert(state->guiding.path_segment != nullptr);
if (state->guiding.path_segment != nullptr) {
kernel_assert(is_guiding_valid(P));
float3 p = clamp_guiding_position(P);
openpgl::cpp::SetPosition(state->guiding.path_segment, guiding_point3f(p));
openpgl::cpp::SetDirectionOut(state->guiding.path_segment, guiding_vec3f(I));
openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, true);
openpgl::cpp::SetScatteredContribution(state->guiding.path_segment, zero);
openpgl::cpp::SetDirectContribution(state->guiding.path_segment, zero);
openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, one);
openpgl::cpp::SetEta(state->guiding.path_segment, 1.0);
}
#endif
}
/* Records the volume scattering event at the current vertex position of the segment. */
ccl_device_forceinline void guiding_record_volume_bounce(
ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState state,
ccl_attr_maybe_unused const Spectrum weight,
ccl_attr_maybe_unused const float pdf,
ccl_attr_maybe_unused const float3 wo,
ccl_attr_maybe_unused const float roughness)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
const float3 weight_rgb = spectrum_to_rgb(weight);
const float3 normal = make_float3(0.0f, 0.0f, 1.0f);
kernel_assert(state->guiding.path_segment != nullptr);
if (state->guiding.path_segment != nullptr) {
openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, true);
openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, guiding_vec3f(one_float3()));
openpgl::cpp::SetNormal(state->guiding.path_segment, guiding_vec3f(normal));
openpgl::cpp::SetDirectionIn(state->guiding.path_segment, guiding_vec3f(wo));
openpgl::cpp::SetPDFDirectionIn(state->guiding.path_segment, pdf);
openpgl::cpp::SetScatteringWeight(state->guiding.path_segment, guiding_vec3f(weight_rgb));
openpgl::cpp::SetIsDelta(state->guiding.path_segment, false);
openpgl::cpp::SetEta(state->guiding.path_segment, 1.0f);
openpgl::cpp::SetRoughness(state->guiding.path_segment, roughness);
}
#endif
}
/* Records the transmission (a.k.a. transmittance weight) between the current path segment
* and the next one, when the path is inside or passes a volume. */
ccl_device_forceinline void guiding_record_volume_transmission(
ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState state,
ccl_attr_maybe_unused const float3 transmittance_weight)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
if (state->guiding.path_segment) {
// TODO (sherholz): need to find a better way to avoid this check
if ((transmittance_weight[0] < 0.0f || !std::isfinite(transmittance_weight[0]) ||
std::isnan(transmittance_weight[0])) ||
(transmittance_weight[1] < 0.0f || !std::isfinite(transmittance_weight[1]) ||
std::isnan(transmittance_weight[1])) ||
(transmittance_weight[2] < 0.0f || !std::isfinite(transmittance_weight[2]) ||
std::isnan(transmittance_weight[2])))
{
}
else {
openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment,
guiding_vec3f(transmittance_weight));
}
}
#endif
}
/* Records the emission of a volume at the vertex of the current path segment. */
ccl_device_forceinline void guiding_record_volume_emission(ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState
state,
ccl_attr_maybe_unused const Spectrum Le)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
if (state->guiding.path_segment) {
const float3 Le_rgb = spectrum_to_rgb(Le);
openpgl::cpp::SetDirectContribution(state->guiding.path_segment, guiding_vec3f(Le_rgb));
openpgl::cpp::SetMiWeight(state->guiding.path_segment, 1.0f);
}
#endif
}
/* Record Light Interactions */
/* Adds a pseudo path vertex/segment when intersecting a virtual light source.
* (e.g., area, sphere, or disk light). This call is often followed
* a call of guiding_record_surface_emission, if the intersected light source
* emits light in the direction of the path. */
ccl_device_forceinline void guiding_record_light_surface_segment(
ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState state,
ccl_attr_maybe_unused const ccl_private Intersection *ccl_restrict isect)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
const pgl_vec3f zero = guiding_vec3f(zero_float3());
const pgl_vec3f one = guiding_vec3f(one_float3());
const float3 ray_P = INTEGRATOR_STATE(state, ray, P);
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
const float3 P = ray_P + isect->t * ray_D;
state->guiding.path_segment = kg->opgl_path_segment_storage->NextSegment();
/* FIXME: investigate and fix why state->guiding.path_segment could be nullptr. */
kernel_assert(state->guiding.path_segment != nullptr);
if (state->guiding.path_segment != nullptr) {
kernel_assert(is_guiding_valid(P));
float3 p = clamp_guiding_position(P);
openpgl::cpp::SetPosition(state->guiding.path_segment, guiding_point3f(p));
openpgl::cpp::SetDirectionOut(state->guiding.path_segment, guiding_vec3f(-ray_D));
openpgl::cpp::SetNormal(state->guiding.path_segment, guiding_vec3f(-ray_D));
openpgl::cpp::SetDirectionIn(state->guiding.path_segment, guiding_vec3f(ray_D));
openpgl::cpp::SetPDFDirectionIn(state->guiding.path_segment, 1.0f);
openpgl::cpp::SetVolumeScatter(state->guiding.path_segment, false);
openpgl::cpp::SetScatteredContribution(state->guiding.path_segment, zero);
openpgl::cpp::SetDirectContribution(state->guiding.path_segment, zero);
openpgl::cpp::SetTransmittanceWeight(state->guiding.path_segment, one);
openpgl::cpp::SetScatteringWeight(state->guiding.path_segment, one);
openpgl::cpp::SetEta(state->guiding.path_segment, 1.0f);
}
#endif
}
/* Records/Adds a final path segment when the path leaves the scene and
* intersects with a background light (e.g., background color,
* sun light, or env map). The vertex for this segment is placed along
* the current ray far out the scene. */
ccl_device_forceinline void guiding_record_background(ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState state,
ccl_attr_maybe_unused const Spectrum L,
ccl_attr_maybe_unused const float mis_weight)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
const float3 L_rgb = spectrum_to_rgb(L);
const float3 ray_P = INTEGRATOR_STATE(state, ray, P);
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
float3 P = ray_P + GUIDING_MAX_LIGHT_DISTANCE * ray_D;
kernel_assert(is_guiding_valid(P));
P = clamp_guiding_position(P);
const float3 normal = make_float3(0.0f, 0.0f, 1.0f);
openpgl::cpp::PathSegment background_segment;
openpgl::cpp::SetPosition(&background_segment, guiding_vec3f(P));
openpgl::cpp::SetNormal(&background_segment, guiding_vec3f(normal));
openpgl::cpp::SetDirectionOut(&background_segment, guiding_vec3f(-ray_D));
openpgl::cpp::SetDirectContribution(&background_segment, guiding_vec3f(L_rgb));
openpgl::cpp::SetMiWeight(&background_segment, mis_weight);
kg->opgl_path_segment_storage->AddSegment(background_segment);
#endif
}
/* Records direct lighting from either next event estimation or a dedicated BSDF
* sampled shadow ray. */
ccl_device_forceinline void guiding_record_direct_light(
ccl_attr_maybe_unused KernelGlobals kg, ccl_attr_maybe_unused IntegratorShadowState state)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!kernel_data.integrator.train_guiding) {
return;
}
const uint32_t path_flag = INTEGRATOR_STATE(state, shadow_path, flag);
if (path_flag & PATH_RAY_SHADOW_FOR_AO) {
return;
}
if (state->shadow_path.path_segment) {
/* Estimating the out-scattered radiance at the current path segment.
* NOTE: in the light linking case this estimate is the incoming radiance.*/
const Spectrum Lo = safe_divide_color(INTEGRATOR_STATE(state, shadow_path, throughput),
INTEGRATOR_STATE(state, shadow_path, unlit_throughput));
const float3 Lo_rgb = spectrum_to_rgb(Lo);
if (!(path_flag & PATH_RAY_SHADOW_FOR_LIGHT_LINKING)) {
/* Scattered contribution of a next event estimation (i.e., a direct light estimate
* scattered at the current path vertex towards the previous vertex). */
openpgl::cpp::AddScatteredContribution(state->shadow_path.path_segment,
guiding_vec3f(Lo_rgb));
}
else {
/* The contribution comes from a light linking forward ray. We need to record this
* contribution as scattered contribution at the current path segment. To be able to guide
* towards this light source we add a directional sample directly to the guiding
* training data storage. */
const float3 scattering_weight = make_float3(
state->shadow_path.path_segment->scatteringWeight);
openpgl::cpp::AddScatteredContribution(state->shadow_path.path_segment,
guiding_vec3f(scattering_weight * Lo_rgb));
/* Adding an additional training sample for the guiding cache in the direction of the linked
* light source. */
float dist = INTEGRATOR_STATE(state, shadow_ray, tmax);
openpgl::cpp::SampleData pgl_sample;
pgl_sample.direction = state->shadow_path.path_segment->directionIn;
pgl_sample.pdf = state->shadow_path.path_segment->pdfDirectionIn;
pgl_sample.position = state->shadow_path.path_segment->position;
pgl_sample.flags = state->shadow_path.path_segment->volumeScatter ?
openpgl::cpp::SampleData::EInsideVolume :
0;
pgl_sample.weight = safe_divide(reduce_max(Lo_rgb), pgl_sample.pdf);
if (!kernel_data.integrator.use_guiding_mis_weights) {
const float mis_weight = INTEGRATOR_STATE(
state, shadow_path, guiding_light_linking_mis_weight);
pgl_sample.weight = safe_divide(pgl_sample.weight, mis_weight);
}
/* Checking if the light source is an infinite one (e.g., background, sun). If so the
* distance is set to GUIDING_MAX_LIGHT_DISTANCE.
* NOTE: checking for FLT_MAX is not working.*/
pgl_sample.distance = dist > GUIDING_FLT_LARGE ? GUIDING_MAX_LIGHT_DISTANCE : dist;
kg->opgl_sample_data_storage->AddSample(pgl_sample);
}
}
#endif
}
/* Record Russian Roulette */
/* Records the probability of continuing the path at the current path segment. */
ccl_device_forceinline void guiding_record_continuation_probability(
ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState state,
ccl_attr_maybe_unused const float continuation_probability)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 1
if (!kernel_data.integrator.train_guiding) {
return;
}
assert((INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) == 0);
if (state->guiding.path_segment) {
openpgl::cpp::SetRussianRouletteProbability(state->guiding.path_segment,
continuation_probability);
}
#endif
}
/* Path guiding debug render passes. */
/* Write a set of path guiding related debug information (e.g., guiding probability at first
* bounce) into separate rendering passes. */
ccl_device_forceinline void guiding_write_debug_passes(
ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState state,
ccl_attr_maybe_unused const ccl_private ShaderData *sd,
ccl_attr_maybe_unused ccl_global float *ccl_restrict render_buffer)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
# ifdef WITH_CYCLES_DEBUG
if (!kernel_data.integrator.train_guiding) {
return;
}
if (INTEGRATOR_STATE(state, path, bounce) != 0) {
return;
}
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
if (kernel_data.film.pass_guiding_probability != PASS_UNUSED) {
float guiding_prob = state->guiding.surface_guiding_sampling_prob;
film_write_pass_float(buffer + kernel_data.film.pass_guiding_probability, guiding_prob);
}
if (kernel_data.film.pass_guiding_avg_roughness != PASS_UNUSED) {
float avg_roughness = 0.0f;
float sum_sample_weight = 0.0f;
for (int i = 0; i < sd->num_closure; i++) {
const ccl_private ShaderClosure *sc = &sd->closure[i];
if (!CLOSURE_IS_BSDF_OR_BSSRDF(sc->type)) {
continue;
}
avg_roughness += sc->sample_weight * bsdf_get_specular_roughness_squared(sc);
sum_sample_weight += sc->sample_weight;
}
avg_roughness = avg_roughness > 0.0f ? avg_roughness / sum_sample_weight : 0.0f;
film_write_pass_float(buffer + kernel_data.film.pass_guiding_avg_roughness, avg_roughness);
}
# else
(void)kg;
(void)state;
(void)sd;
(void)render_buffer;
# endif
#endif
}
/* Guided BSDFs */
ccl_device_forceinline bool guiding_bsdf_init(ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused const float3 P,
ccl_attr_maybe_unused const float3 N,
ccl_attr_maybe_unused ccl_private float &rand)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
if (guiding_ssd->Init(guiding_guiding_field, guiding_point3f(P), rand)) {
guiding_ssd->ApplyCosineProduct(guiding_point3f(N));
return true;
}
#endif
return false;
}
ccl_device_forceinline float guiding_bsdf_sample(ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused const float2 rand_bsdf,
ccl_attr_maybe_unused ccl_private float3 *wo)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
pgl_vec3f pgl_wo;
const pgl_point2f rand = {rand_bsdf.x, rand_bsdf.y};
const float pdf = guiding_ssd->SamplePDF(rand, pgl_wo);
*wo = make_float3(pgl_wo.x, pgl_wo.y, pgl_wo.z);
return pdf;
#else
return 0.0f;
#endif
}
ccl_device_forceinline float guiding_bsdf_pdf(ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused const float3 wo)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
return guiding_ssd->PDF(guiding_vec3f(wo));
#else
return 0.0f;
#endif
}
ccl_device_forceinline float guiding_surface_incoming_radiance_pdf(
ccl_attr_maybe_unused KernelGlobals kg, ccl_attr_maybe_unused const float3 wo)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
return guiding_ssd->IncomingRadiancePDF(guiding_vec3f(wo));
#else
return 0.0f;
#endif
}
/* Guided Volume Phases */
ccl_device_forceinline bool guiding_phase_init(ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused const float3 P,
ccl_attr_maybe_unused const float3 D,
ccl_attr_maybe_unused const float g,
ccl_attr_maybe_unused ccl_private float &rand)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
/* we do not need to guide almost delta phase functions */
if (fabsf(g) >= 0.99f) {
return false;
}
if (guiding_vsd->Init(guiding_guiding_field, guiding_point3f(P), rand)) {
guiding_vsd->ApplySingleLobeHenyeyGreensteinProduct(guiding_vec3f(D), g);
return true;
}
#endif
return false;
}
ccl_device_forceinline float guiding_phase_sample(ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused const float2 rand_phase,
ccl_attr_maybe_unused ccl_private float3 *wo)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
pgl_vec3f pgl_wo;
const pgl_point2f rand = {rand_phase.x, rand_phase.y};
const float pdf = guiding_vsd->SamplePDF(rand, pgl_wo);
*wo = make_float3(pgl_wo.x, pgl_wo.y, pgl_wo.z);
return pdf;
#else
return 0.0f;
#endif
}
ccl_device_forceinline float guiding_phase_pdf(ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused const float3 wo)
{
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
return guiding_vsd->PDF(guiding_vec3f(wo));
#else
return 0.0f;
#endif
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,343 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/camera/camera.h"
#include "kernel/film/adaptive_sampling.h"
#include "kernel/film/light_passes.h"
#include "kernel/integrator/intersect_closest.h"
#include "kernel/integrator/path_state.h"
#include "kernel/sample/pattern.h"
CCL_NAMESPACE_BEGIN
/* In order to perform anti-aliasing during baking, we jitter the input barycentric coordinates
* (which are for the center of the texel) within the texel.
* However, the baking code currently doesn't support going to neighboring triangle, so if the
* jittered location falls outside of the input triangle, we need to bring it back in somehow.
* Clamping is a bad choice here since it can produce noticeable artifacts at triangle edges,
* but properly uniformly sampling the intersection of triangle and texel would be very
* performance-heavy, so cheat by just trying different jittering until we end up inside the
* triangle.
* For triangles that are smaller than a texel, this might take too many attempts, so eventually
* we just give up and don't jitter in that case.
* This is not a particularly elegant solution, but it's probably the best we can do. */
ccl_device_inline void bake_jitter_barycentric(ccl_private float &u,
ccl_private float &v,
float2 rand_filter,
const float dudx,
const float dudy,
const float dvdx,
const float dvdy)
{
for (int i = 0; i < 10; i++) {
/* Offset UV according to differentials. */
const float jitterU = u + (rand_filter.x - 0.5f) * dudx + (rand_filter.y - 0.5f) * dudy;
const float jitterV = v + (rand_filter.x - 0.5f) * dvdx + (rand_filter.y - 0.5f) * dvdy;
/* If this location is inside the triangle, return. */
if (jitterU > 0.0f && jitterV > 0.0f && jitterU + jitterV < 1.0f) {
u = jitterU;
v = jitterV;
return;
}
/* Retry with new jitter value. */
rand_filter = hash_float2_to_float2(rand_filter);
}
/* Retries exceeded, give up and just use center value. */
}
/* Offset towards center of triangle to avoid ray-tracing precision issues. */
ccl_device float2 bake_offset_towards_center(
KernelGlobals kg, const int object, const int prim, const float u, const float v)
{
float3 tri_verts[3];
triangle_vertices(kg, object, prim, tri_verts);
/* Empirically determined values, by no means perfect. */
const float position_offset = 1e-4f;
const float uv_offset = 1e-5f;
/* Offset position towards center, amount relative to absolute size of position coordinates. */
const float3 P = u * tri_verts[0] + v * tri_verts[1] + (1.0f - u - v) * tri_verts[2];
const float3 center = (tri_verts[0] + tri_verts[1] + tri_verts[2]) / 3.0f;
const float3 to_center = center - P;
const float3 offset_P = P + normalize(to_center) *
min(len(to_center),
max(reduce_max(fabs(P)), 1.0f) * position_offset);
/* Compute barycentric coordinates at new position. */
const float3 v1 = tri_verts[1] - tri_verts[0];
const float3 v2 = tri_verts[2] - tri_verts[0];
const float3 vP = offset_P - tri_verts[0];
const float d11 = dot(v1, v1);
const float d12 = dot(v1, v2);
const float d22 = dot(v2, v2);
const float dP1 = dot(vP, v1);
const float dP2 = dot(vP, v2);
const float denom = d11 * d22 - d12 * d12;
if (denom == 0.0f) {
return make_float2(0.0f, 0.0f);
}
const float offset_v = clamp((d22 * dP1 - d12 * dP2) / denom, uv_offset, 1.0f - uv_offset);
const float offset_w = clamp((d11 * dP2 - d12 * dP1) / denom, uv_offset, 1.0f - uv_offset);
const float offset_u = clamp(1.0f - offset_v - offset_w, uv_offset, 1.0f - uv_offset);
return make_float2(offset_u, offset_v);
}
/* Return false to indicate that this pixel is finished.
* Used by CPU implementation to not attempt to sample pixel for multiple samples once its known
* that the pixel did converge. */
ccl_device bool integrator_init_from_bake(KernelGlobals kg,
IntegratorState state,
const ccl_global KernelWorkTile *ccl_restrict tile,
ccl_global float *render_buffer,
const int x,
const int y,
const int scheduled_sample)
{
PROFILING_INIT(kg, PROFILING_RAY_SETUP);
/* Initialize path state to give basic buffer access and allow early outputs. */
path_state_init(state, tile, x, y);
/* Check whether the pixel has converged and should not be sampled anymore. */
if (!film_need_sample_pixel(kg, state, render_buffer)) {
return false;
}
/* Always count the sample, even if the camera sample will reject the ray. */
const int sample = film_write_sample(
kg, state, render_buffer, scheduled_sample, tile->sample_offset);
/* Setup render buffers. */
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
ccl_global float *primitive = buffer + kernel_data.film.pass_bake_primitive;
ccl_global float *differential = buffer + kernel_data.film.pass_bake_differential;
int prim = __float_as_uint(primitive[2]);
if (prim == -1) {
/* Accumulate transparency for empty pixels. */
film_write_transparent(kg, 0, 1.0f, buffer);
return true;
}
prim += kernel_data.bake.tri_offset;
/* Random number generator. */
uint rng_pixel = 0;
if (kernel_data.film.pass_bake_seed != 0) {
const uint seed = __float_as_uint(buffer[kernel_data.film.pass_bake_seed]);
rng_pixel = hash_uint(seed) ^ kernel_data.integrator.seed;
}
else {
rng_pixel = path_rng_pixel_init(kg, sample, x, y);
}
const float2 rand_filter = (sample == 0) ? make_float2(0.5f, 0.5f) :
path_rng_2D(kg, rng_pixel, sample, PRNG_FILTER);
/* Initialize path state for path integration. */
path_state_init_integrator(kg, state, sample, rng_pixel, one_spectrum());
/* Barycentric UV. */
float u = primitive[0];
float v = primitive[1];
float dudx = differential[0];
float dudy = differential[1];
float dvdx = differential[2];
float dvdy = differential[3];
/* Exactly at vertex? Nudge inwards to avoid self-intersection. */
if ((u == 0.0f || u == 1.0f) && (v == 0.0f || v == 1.0f)) {
const float2 uv = bake_offset_towards_center(kg, kernel_data.bake.object_index, prim, u, v);
u = uv.x;
v = uv.y;
}
/* Sub-pixel offset. */
bake_jitter_barycentric(u, v, rand_filter, dudx, dudy, dvdx, dvdy);
/* Convert from Blender to Cycles/Embree/OptiX barycentric convention. */
const float tmp = u;
u = v;
v = 1.0f - tmp - v;
const float tmpdx = dudx;
const float tmpdy = dudy;
dudx = dvdx;
dudy = dvdy;
dvdx = -tmpdx - dvdx;
dvdy = -tmpdy - dvdy;
/* Position and normal on triangle. */
const int object = kernel_data.bake.object_index;
float3 P;
float3 Ng;
int shader;
triangle_point_normal(kg, object, prim, u, v, &P, &Ng, &shader);
const uint object_flag = kernel_data_fetch(object_flag, object);
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
const Transform tfm = object_fetch_transform(kg, object, OBJECT_TRANSFORM);
P = transform_point_auto(&tfm, P);
}
if (kernel_data.film.pass_background != PASS_UNUSED) {
/* Environment baking. */
/* Setup and write ray. */
Ray ray ccl_optional_struct_init;
ray.P = zero_float3();
ray.D = normalize(P);
ray.tmin = 0.0f;
ray.tmax = FLT_MAX;
ray.time = 0.5f;
ray.dP = differential_zero_compact();
ray.dD = differential_zero_compact();
integrator_state_write_ray(state, &ray);
/* Setup next kernel to execute. */
integrator_path_init(state, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND);
}
else {
/* Surface baking. */
float3 N = (shader & SHADER_SMOOTH_NORMAL) ?
triangle_smooth_normal(kg, Ng, object, object_flag, prim, u, v) :
Ng;
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
const Transform itfm = object_fetch_transform(kg, object, OBJECT_INVERSE_TRANSFORM);
N = normalize(transform_direction_transposed(&itfm, N));
Ng = normalize(transform_direction_transposed(&itfm, Ng));
}
const int shader_index = shader & SHADER_MASK;
const int shader_flags = kernel_data_fetch(shaders, shader_index).flags;
/* Fast path for position and normal passes not affected by shaders. */
if (kernel_data.film.pass_position != PASS_UNUSED) {
film_write_pass_float3(buffer + kernel_data.film.pass_position, P);
return true;
}
if (kernel_data.film.pass_normal != PASS_UNUSED && !(shader_flags & SD_HAS_BUMP)) {
film_write_pass_float3(buffer + kernel_data.film.pass_normal, N);
return true;
}
/* Setup ray. */
Ray ray ccl_optional_struct_init;
if (kernel_data.bake.use_camera) {
float3 D = camera_direction_from_point(kg, P);
const float DN = dot(D, N);
/* Nudge camera direction, so that the faces facing away from the camera still have
* somewhat usable shading. (Otherwise, glossy faces would be simply black.)
*
* The surface normal offset affects smooth surfaces. Lower values will make
* smooth surfaces more faceted, but higher values may show up from the camera
* at grazing angles.
*
* This value can actually be pretty high before it's noticeably wrong. */
const float surface_normal_offset = 0.2f;
/* Keep the ray direction at least `surface_normal_offset` "above" the smooth normal. */
if (DN <= surface_normal_offset) {
D -= N * (DN - surface_normal_offset);
D = normalize(D);
}
/* On the backside, just lerp towards the surface normal for the ray direction,
* as DN goes from 0.0 to -1.0. */
if (DN <= 0.0f) {
D = normalize(mix(D, N, -DN));
}
/* We don't want to bake the back face, so make sure the ray direction never
* goes behind the geometry (flat) normal. This is a fail-safe, and should rarely happen. */
const float true_normal_epsilon = 0.00001f;
if (dot(D, Ng) <= true_normal_epsilon) {
D -= Ng * (dot(D, Ng) - true_normal_epsilon);
D = normalize(D);
}
ray.P = P + D;
ray.D = -D;
}
else {
ray.P = P + N;
ray.D = -N;
}
ray.tmin = 0.0f;
ray.tmax = FLT_MAX;
ray.time = 0.5f;
/* Setup differentials. */
float3 dPdu;
float3 dPdv;
triangle_dPdudv(kg, object, prim, &dPdu, &dPdv);
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
const Transform tfm = object_fetch_transform(kg, object, OBJECT_TRANSFORM);
dPdu = transform_direction(&tfm, dPdu);
dPdv = transform_direction(&tfm, dPdv);
}
differential3 dP;
dP.dx = dPdu * dudx + dPdv * dvdx;
dP.dy = dPdu * dudy + dPdv * dvdy;
ray.dP = differential_make_compact(dP);
ray.dD = differential_zero_compact();
/* Write ray. */
integrator_state_write_ray(state, &ray);
/* Setup and write intersection. */
Intersection isect ccl_optional_struct_init;
isect.object = kernel_data.bake.object_index;
isect.prim = prim;
isect.u = u;
isect.v = v;
isect.t = 1.0f;
isect.type = PRIMITIVE_TRIANGLE;
integrator_state_write_isect(state, &isect);
/* Setup next kernel to execute. */
const bool use_caustics = kernel_data.integrator.use_caustics &&
(object_flag & SD_OBJECT_CAUSTICS_RECEIVER);
const bool use_raytrace_kernel = (shader_flags & SD_HAS_RAYTRACE);
if (use_caustics) {
integrator_path_init(state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE);
}
else if (use_raytrace_kernel) {
integrator_path_init_sorted(
kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader_index);
}
else {
integrator_path_init_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader_index);
}
#ifdef __SHADOW_CATCHER__
integrator_split_shadow_catcher(kg, state, &isect, render_buffer);
#endif
}
return true;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,145 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/camera/camera.h"
#include "kernel/film/adaptive_sampling.h"
#include "kernel/film/light_passes.h"
#include "kernel/integrator/path_state.h"
#include "kernel/integrator/state_util.h"
#include "kernel/sample/pattern.h"
CCL_NAMESPACE_BEGIN
ccl_device_inline Spectrum integrate_camera_sample(KernelGlobals kg,
const int sample,
const int x,
const int y,
const uint rng_pixel,
ccl_private Ray *ray,
ccl_private int &r_cache_miss)
{
/* Filter sampling. */
const float2 rand_filter = (sample == 0) ? make_float2(0.5f, 0.5f) :
path_rng_2D(kg, rng_pixel, sample, PRNG_FILTER);
/* Motion blur (time) and depth of field (lens) sampling. (time, lens_x, lens_y) */
const bool use_motionblur = kernel_data.cam.shuttertime != -1.0f;
const bool use_dof = kernel_data.cam.aperturesize > 0.0f;
const bool use_custom_cam = kernel_data.cam.type == CAMERA_CUSTOM;
const float3 rand_time_lens = (use_motionblur || use_dof || use_custom_cam) ?
path_rng_3D(kg, rng_pixel, sample, PRNG_LENS_TIME) :
zero_float3();
/* We use x for time and y,z for lens because in practice with Sobol
* sampling this seems to give better convergence when an object is
* both motion blurred and out of focus, without significantly harming
* convergence for focal blur alone. This is a little surprising,
* because one would expect using x,y for lens (the 2d part) would be
* best, since x,y are the best stratified. Since it's not entirely
* clear why this is, this is probably worth revisiting at some point
* to investigate further. */
const float rand_time = rand_time_lens.x;
const float2 rand_lens = make_float2(rand_time_lens.y, rand_time_lens.z);
/* Generate camera ray. */
return camera_sample(kg, x, y, rand_filter, rand_time, rand_lens, ray, r_cache_miss);
}
/* Return false to indicate that this pixel is finished.
* Used by CPU implementation to not attempt to sample pixel for multiple samples once its known
* that the pixel did converge. */
ccl_device bool integrator_init_from_camera(KernelGlobals kg,
IntegratorState state,
const ccl_global KernelWorkTile *ccl_restrict tile,
ccl_global float *render_buffer,
const int x_,
const int y_,
const int scheduled_sample)
{
PROFILING_INIT(kg, PROFILING_RAY_SETUP);
int x, y, sample;
if (tile == nullptr) {
/* Restart from miss. Reconstruct x, y, sample from state. */
const uint pixel_index = INTEGRATOR_STATE(state, path, render_pixel_index);
x = pixel_index % (int)kernel_data.cam.width;
y = pixel_index / (int)kernel_data.cam.width;
sample = INTEGRATOR_STATE(state, path, sample);
}
else {
x = x_;
y = y_;
/* Initialize path state to give basic buffer access and allow early outputs. */
path_state_init(state, tile, x, y);
/* Check whether the pixel has converged and should not be sampled anymore. */
if (!film_need_sample_pixel(kg, state, render_buffer)) {
return false;
}
/* Count the sample and get an effective sample for this pixel. */
sample = film_write_sample(kg, state, render_buffer, scheduled_sample, tile->sample_offset);
}
/* Initialize random number seed for path. */
const uint rng_pixel = path_rng_pixel_init(kg, sample, x, y);
/* Generate camera ray. */
Ray ray;
int cache_miss = 0;
Spectrum T = integrate_camera_sample(kg, sample, x, y, rng_pixel, &ray, cache_miss);
if (cache_miss) {
if (tile != nullptr) {
integrator_path_init(state, DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA);
INTEGRATOR_STATE_WRITE(state, path, sample) = sample;
}
integrator_path_cache_miss(state, DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA);
return true;
}
if (is_zero(T)) {
if (tile == nullptr) {
integrator_path_terminate(
kg, state, render_buffer, DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA);
}
return true;
}
/* Write camera ray to state. */
integrator_state_write_ray(state, &ray);
if (tile == nullptr) {
/* Re-initialize path state for path integration. */
path_state_init_integrator(kg, state, sample, rng_pixel, T);
integrator_path_next(state,
DEVICE_KERNEL_INTEGRATOR_INIT_FROM_CAMERA,
kernel_data.cam.is_inside_volume ?
DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK :
DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST);
}
else {
/* Initialize path state for path integration. */
path_state_init_integrator(kg, state, sample, rng_pixel, T);
/* Continue with intersect_closest kernel, optionally initializing volume
* stack before that if the camera may be inside a volume. */
if (kernel_data.cam.is_inside_volume) {
integrator_path_init(state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK);
}
else {
integrator_path_init(state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST);
}
}
return true;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,448 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/film/light_passes.h"
#include "kernel/integrator/guiding.h"
#include "kernel/integrator/path_state.h"
#include "kernel/integrator/shadow_catcher.h"
#include "kernel/light/light.h"
#include "kernel/bvh/bvh.h"
CCL_NAMESPACE_BEGIN
ccl_device_forceinline bool integrator_intersect_skip_lights(KernelGlobals kg,
IntegratorState state)
{
/* When direct lighting is disabled for baking, we skip light sampling in
* integrate_surface_direct_light for the first bounce. Therefore, in order
* for MIS to be consistent, we also need to skip evaluating lights here. */
return (kernel_data.integrator.filter_closures & FILTER_CLOSURE_DIRECT_LIGHT) &&
(INTEGRATOR_STATE(state, path, bounce) == 1);
}
ccl_device_forceinline bool integrator_intersect_terminate(KernelGlobals kg,
IntegratorState state,
const int shader_flags)
{
/* Optional AO bounce termination.
* We continue evaluating emissive/transparent surfaces and volumes, similar
* to direct lighting. Only if we know there are none can we terminate the
* path immediately. */
if (path_state_ao_bounce(kg, state)) {
if (shader_flags & (SD_HAS_TRANSPARENT_SHADOW | SD_HAS_EMISSION)) {
INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
}
#ifdef __VOLUME__
else if (!integrator_state_volume_stack_is_empty(kg, state)) {
INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_TERMINATE_AFTER_VOLUME;
}
#endif
else {
return true;
}
}
/* Load random number state. */
RNGState rng_state;
path_state_rng_load(state, &rng_state);
/* We perform path termination in this kernel to avoid launching shade_surface
* and evaluating the shader when not needed. Only for emission and transparent
* surfaces in front of emission do we need to evaluate the shader, since we
* perform MIS as part of indirect rays. */
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const float continuation_probability = path_state_continuation_probability(kg, state, path_flag);
INTEGRATOR_STATE_WRITE(state, path, continuation_probability) = continuation_probability;
guiding_record_continuation_probability(kg, state, continuation_probability);
if (continuation_probability != 1.0f) {
const float terminate = path_state_rng_1D(kg, &rng_state, PRNG_TERMINATE);
if (continuation_probability == 0.0f || terminate >= continuation_probability) {
if (shader_flags & SD_HAS_EMISSION) {
/* Mark path to be terminated right after shader evaluation on the surface. */
INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_TERMINATE_ON_NEXT_SURFACE;
}
#ifdef __VOLUME__
else if (!integrator_state_volume_stack_is_empty(kg, state)) {
/* TODO: only do this for emissive volumes. */
INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_TERMINATE_IN_NEXT_VOLUME;
}
#endif
else {
return true;
}
}
}
return false;
}
#ifdef __SHADOW_CATCHER__
/* Split path if a shadow catcher was hit. */
ccl_device_forceinline void integrator_split_shadow_catcher(
KernelGlobals kg,
IntegratorState state,
const ccl_private Intersection *ccl_restrict isect,
ccl_global float *ccl_restrict render_buffer)
{
/* Test if we hit a shadow catcher object, and potentially split the path to continue tracing two
* paths from here. */
const uint object_flags = intersection_get_object_flags(kg, isect);
if (!kernel_shadow_catcher_is_path_split_bounce(kg, state, object_flags)) {
return;
}
film_write_shadow_catcher_bounce_data(kg, state, render_buffer);
/* Mark state as having done a shadow catcher split so that it stops contributing to
* the shadow catcher matte pass, but keeps contributing to the combined pass. */
INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_SHADOW_CATCHER_HIT;
/* Copy current state to new state. */
state = integrator_state_shadow_catcher_split(kg, state);
/* Initialize new state.
*
* Note that the splitting leaves kernel and sorting counters as-is, so use INIT semantic for
* the matte path. */
/* Mark current state so that it will only track contribution of shadow catcher objects ignoring
* non-catcher objects. */
INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_SHADOW_CATCHER_PASS;
/* Shadow catcher path does not use guiding.
* Clear the path_segment to ensure we do not reference possibly stale data from the main path.
*/
# ifdef __PATH_GUIDING__
INTEGRATOR_STATE_WRITE(state, guiding, path_segment) = nullptr;
# endif
if (kernel_data.film.pass_background != PASS_UNUSED && !kernel_data.background.transparent) {
/* If using background pass, schedule background shading kernel so that we have a background
* to alpha-over on. The background kernel will then continue the path afterwards. */
INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_SHADOW_CATCHER_BACKGROUND;
integrator_path_init(state, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND);
return;
}
# ifdef __VOLUME__
if (!integrator_state_volume_stack_is_empty(kg, state)) {
/* Volume stack is not empty. Re-init the volume stack to exclude any non-shadow catcher
* objects from it, and then continue shading volume and shadow catcher surface after. */
integrator_path_init(state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK);
return;
}
# endif
/* Continue with shading shadow catcher surface. */
const int shader = intersection_get_shader(kg, isect);
const int flags = kernel_data_fetch(shaders, shader).flags;
const bool use_caustics = kernel_data.integrator.use_caustics &&
(object_flags & SD_OBJECT_CAUSTICS_RECEIVER);
const bool use_raytrace_kernel = (flags & SD_HAS_RAYTRACE);
if (use_caustics) {
integrator_path_init(state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE);
}
else if (use_raytrace_kernel) {
integrator_path_init_sorted(
kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader);
}
else {
integrator_path_init_sorted(kg, state, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader);
}
}
/* Schedule next kernel to be executed after updating volume stack for shadow catcher. */
template<DeviceKernel current_kernel>
ccl_device_forceinline void integrator_intersect_next_kernel_after_shadow_catcher_volume(
KernelGlobals kg, IntegratorState state)
{
/* Continue with shading shadow catcher surface. Same as integrator_split_shadow_catcher, but
* using NEXT instead of INIT. */
Intersection isect ccl_optional_struct_init;
integrator_state_read_isect(state, &isect);
const int shader = intersection_get_shader(kg, &isect);
const int flags = kernel_data_fetch(shaders, shader).flags;
const uint object_flags = intersection_get_object_flags(kg, &isect);
const bool use_caustics = kernel_data.integrator.use_caustics &&
(object_flags & SD_OBJECT_CAUSTICS_RECEIVER);
const bool use_raytrace_kernel = (flags & SD_HAS_RAYTRACE);
if (use_caustics) {
integrator_path_next(state, current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE);
}
else if (use_raytrace_kernel) {
integrator_path_next_sorted(
kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader);
}
else {
integrator_path_next_sorted(
kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader);
}
}
/* Schedule next kernel to be executed after executing background shader for shadow catcher. */
template<DeviceKernel current_kernel>
ccl_device_forceinline void integrator_intersect_next_kernel_after_shadow_catcher_background(
KernelGlobals kg, IntegratorState state)
{
# ifdef __VOLUME__
/* Same logic as integrator_split_shadow_catcher, but using NEXT instead of INIT. */
if (!integrator_state_volume_stack_is_empty(kg, state)) {
/* Volume stack is not empty. Re-init the volume stack to exclude any non-shadow catcher
* objects from it, and then continue shading volume and shadow catcher surface after. */
integrator_path_next(state, current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK);
return;
}
# endif
/* Continue with shading shadow catcher surface. */
integrator_intersect_next_kernel_after_shadow_catcher_volume<current_kernel>(kg, state);
}
#endif
/* Schedule next kernel to be executed after intersect closest.
*
* Note that current_kernel is a template value since making this a variable
* leads to poor performance with CUDA atomics. */
template<DeviceKernel current_kernel>
ccl_device_forceinline void integrator_intersect_next_kernel(
KernelGlobals kg,
IntegratorState state,
const ccl_private Intersection *ccl_restrict isect,
ccl_global float *ccl_restrict render_buffer,
const bool hit)
{
/* Continue with volume kernel if we are inside a volume, regardless if we hit anything. */
#ifdef __VOLUME__
if (!integrator_state_volume_stack_is_empty(kg, state)) {
const bool hit_surface = hit && !(isect->type & PRIMITIVE_LAMP);
const int shader = (hit_surface) ? intersection_get_shader(kg, isect) : SHADER_NONE;
const int flags = (hit_surface) ? kernel_data_fetch(shaders, shader).flags : 0;
if (!integrator_intersect_terminate(kg, state, flags)) {
if (kernel_data.integrator.volume_ray_marching) {
integrator_path_next(
state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME_RAY_MARCHING);
}
else {
integrator_path_next(state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME);
}
}
else {
integrator_path_terminate(kg, state, render_buffer, current_kernel);
}
return;
}
#endif
if (hit) {
/* Hit a surface, continue with light or surface kernel. */
if (isect->type & PRIMITIVE_LAMP) {
integrator_path_next(state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_FORWARD);
}
else {
/* Hit a surface, continue with surface kernel unless terminated. */
const int shader = intersection_get_shader(kg, isect);
const int flags = kernel_data_fetch(shaders, shader).flags;
if (!integrator_intersect_terminate(kg, state, flags)) {
const uint object_flags = intersection_get_object_flags(kg, isect);
const bool use_caustics = kernel_data.integrator.use_caustics &&
(object_flags & SD_OBJECT_CAUSTICS_RECEIVER);
const bool use_raytrace_kernel = (flags & SD_HAS_RAYTRACE);
if (use_caustics) {
integrator_path_next(state, current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE);
}
else if (use_raytrace_kernel) {
integrator_path_next_sorted(
kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader);
}
else {
integrator_path_next_sorted(
kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader);
}
#ifdef __SHADOW_CATCHER__
/* Handle shadow catcher. */
integrator_split_shadow_catcher(kg, state, isect, render_buffer);
#endif
}
else {
integrator_path_terminate(kg, state, render_buffer, current_kernel);
}
}
}
else {
/* Nothing hit, continue with background kernel. */
if (integrator_intersect_skip_lights(kg, state)) {
integrator_path_terminate(kg, state, render_buffer, current_kernel);
}
else {
integrator_path_next(state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND);
}
}
}
/* Schedule next kernel to be executed after shade volume.
*
* The logic here matches integrator_intersect_next_kernel, except that
* volume shading and termination testing have already been done. */
template<DeviceKernel current_kernel>
ccl_device_forceinline void integrator_intersect_next_kernel_after_volume(
KernelGlobals kg,
IntegratorState state,
const ccl_private Intersection *ccl_restrict isect,
ccl_global float *ccl_restrict render_buffer)
{
if (isect->prim != PRIM_NONE) {
/* Hit a surface, continue with light or surface kernel. */
if (isect->type & PRIMITIVE_LAMP) {
integrator_path_next(state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_FORWARD);
return;
}
/* Hit a surface, continue with surface kernel unless terminated. */
const int shader = intersection_get_shader(kg, isect);
const int flags = kernel_data_fetch(shaders, shader).flags;
const uint object_flags = intersection_get_object_flags(kg, isect);
const bool use_caustics = kernel_data.integrator.use_caustics &&
(object_flags & SD_OBJECT_CAUSTICS_RECEIVER);
const bool use_raytrace_kernel = (flags & SD_HAS_RAYTRACE);
if (use_caustics) {
integrator_path_next(state, current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE);
}
else if (use_raytrace_kernel) {
integrator_path_next_sorted(
kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE, shader);
}
else {
integrator_path_next_sorted(
kg, state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE, shader);
}
#ifdef __SHADOW_CATCHER__
/* Handle shadow catcher. */
integrator_split_shadow_catcher(kg, state, isect, render_buffer);
#endif
return;
}
/* Nothing hit, continue with background kernel. */
if (integrator_intersect_skip_lights(kg, state)) {
integrator_path_terminate(kg, state, render_buffer, current_kernel);
}
else {
integrator_path_next(state, current_kernel, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND);
}
}
ccl_device void integrator_intersect_closest(KernelGlobals kg,
IntegratorState state,
ccl_global float *ccl_restrict render_buffer)
{
PROFILING_INIT(kg, PROFILING_INTERSECT_CLOSEST);
/* Read ray from integrator state into local memory. */
Ray ray ccl_optional_struct_init;
integrator_state_read_ray(state, &ray);
kernel_assert(ray.tmax != 0.0f);
const uint visibility = path_state_ray_visibility(state);
const int last_isect_prim = INTEGRATOR_STATE(state, isect, prim);
const int last_isect_object = INTEGRATOR_STATE(state, isect, object);
/* Trick to use short AO rays to approximate indirect light at the end of the path. */
if (path_state_ao_bounce(kg, state)) {
ray.tmax = kernel_data.integrator.ao_bounces_distance;
if (last_isect_object != OBJECT_NONE) {
const float object_ao_distance = kernel_data_fetch(objects, last_isect_object).ao_distance;
if (object_ao_distance != 0.0f) {
ray.tmax = object_ao_distance;
}
}
}
/* Scene Intersection. */
Intersection isect ccl_optional_struct_init;
isect.object = OBJECT_NONE;
isect.prim = PRIM_NONE;
ray.self.object = last_isect_object;
ray.self.prim = last_isect_prim;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
bool hit = scene_intersect(kg, &ray, visibility, &isect);
/* TODO: remove this and do it in the various intersection functions instead. */
if (!hit) {
isect.prim = PRIM_NONE;
}
/* Setup mnee flag to signal last intersection with a caster */
const PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
#ifdef __MNEE__
/* Path culling logic for MNEE (removes fireflies at the cost of bias) */
if (kernel_data.integrator.use_caustics) {
/* The following firefly removal mechanism works by culling light connections when
* a ray comes from a caustic caster directly after bouncing off a different caustic
* receiver */
bool from_caustic_caster = false;
bool from_caustic_receiver = false;
if (!(path_visibility & PATH_RAY_VISIBILITY_CAMERA) && last_isect_object != OBJECT_NONE) {
const uint object_flags = kernel_data_fetch(object_flag, last_isect_object);
from_caustic_receiver = (object_flags & SD_OBJECT_CAUSTICS_RECEIVER);
from_caustic_caster = (object_flags & SD_OBJECT_CAUSTICS_CASTER);
}
const bool has_receiver_ancestor = INTEGRATOR_STATE(state, path, mnee) &
PATH_MNEE_RECEIVER_ANCESTOR;
INTEGRATOR_STATE_WRITE(state, path, mnee) &= ~PATH_MNEE_CULL_LIGHT_CONNECTION;
if (from_caustic_caster && has_receiver_ancestor) {
INTEGRATOR_STATE_WRITE(state, path, mnee) |= PATH_MNEE_CULL_LIGHT_CONNECTION;
}
if (from_caustic_receiver) {
INTEGRATOR_STATE_WRITE(state, path, mnee) |= PATH_MNEE_RECEIVER_ANCESTOR;
}
}
#endif /* __MNEE__ */
/* Light intersection for MIS. */
if (kernel_data.integrator.use_light_mis && !integrator_intersect_skip_lights(kg, state)) {
/* NOTE: if we make lights visible to camera rays, we'll need to initialize
* these in the path_state_init. */
const int last_type = INTEGRATOR_STATE(state, isect, type);
hit = lights_intersect(kg,
state,
&ray,
&isect,
last_isect_prim,
last_isect_object,
last_type,
path_visibility,
path_flag) ||
hit;
}
/* Write intersection result into global integrator state memory. */
integrator_state_write_isect(state, &isect);
/* Setup up next kernel to be executed. */
integrator_intersect_next_kernel<DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST>(
kg, state, &isect, render_buffer, hit);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,234 @@
/* 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_bounds_bounce = INTEGRATOR_STATE(state, path, volume_bounds_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, &current_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, &current_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_ONLY_VOLUME) {
++volume_bounds_bounce;
if (volume_bounds_bounce >= VOLUME_BOUNDS_MAX) {
ray->tmax = current_isect.t;
break;
}
}
else {
kernel_assert(shader_flags & SD_HAS_TRANSPARENT_SHADOW);
++transparent_bounce;
if (transparent_bounce >= kernel_data.integrator.transparent_max_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 PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
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_visibility,
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;
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(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>(state);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/integrator/mnee.h"
#include "kernel/integrator/shade_surface.h"
CCL_NAMESPACE_BEGIN
#ifdef __MNEE__
/* Sample a light and run the MNEE manifold walk for a caustic receiver. On a successful walk
* the result is written to a shadow slot for integrator_shade_surface, otherwise nothing is
* written and direct light is sampled there. */
ccl_device_forceinline ShaderEvalResult
integrate_surface_mnee(KernelGlobals kg,
IntegratorState state,
ccl_private ShaderData *sd,
const ccl_private RNGState *rng_state)
{
/* Kernel must only be scheduled for caustic receivers. */
kernel_assert(sd->object_flag & SD_OBJECT_CAUSTICS_RECEIVER);
if (!kernel_data.integrator.use_direct_light) {
return SHADER_EVAL_OK;
}
/* Sample position on a light. */
LightSample ls ccl_optional_struct_init;
{
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
const float3 rand_light = path_state_rng_3D(kg, rng_state, PRNG_LIGHT);
if (!light_sample_from_position(kg,
rand_light,
sd->time,
sd->P,
sd->N,
light_link_receiver_nee(kg, sd),
sd->flag,
bounce,
path_flag,
&ls))
{
return SHADER_EVAL_OK;
}
}
kernel_assert(ls.pdf != 0.0f);
/* The manifold walk connects a caustic light to the receiver across reflection; transmission
* caustics and triangle lights are not handled. */
if (ls.type == LIGHT_TRIANGLE || dot(ls.D, sd->N) < 0.0f) {
return SHADER_EVAL_OK;
}
if (!kernel_data_fetch(lights, ls.prim).use_caustics) {
return SHADER_EVAL_OK;
}
ShaderDataCausticsStorage emission_sd_storage;
ccl_private ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage);
Spectrum mnee_throughput = zero_spectrum();
float3 mnee_wo = zero_float3();
int mnee_vertex_count = 0;
const ShaderEvalResult result = kernel_path_mnee_sample(
kg, state, sd, emission_sd, rng_state, &ls, &mnee_throughput, &mnee_wo, mnee_vertex_count);
if (result == SHADER_EVAL_CACHE_MISS) {
return SHADER_EVAL_CACHE_MISS;
}
/* Store MNEE state in a shadow state, to avoid increasing path state size.
* This is then turned into an actual shadow ray state in shade_surface, or discarded. */
if (mnee_vertex_count > 0) {
Ray ray ccl_optional_struct_init;
light_sample_to_surface_shadow_ray(kg, emission_sd, &ls, &ray);
IntegratorShadowState shadow_state = integrator_shadow_path_init(
kg, state, DEVICE_KERNEL_INTEGRATOR_SHADOW_PATH_MNEE_PENDING, false);
integrator_state_write_mnee(
state, shadow_state, &ls, &ray, mnee_vertex_count, mnee_throughput, mnee_wo);
}
return SHADER_EVAL_OK;
}
#endif /* __MNEE__ */
ccl_device void integrator_intersect_mnee(KernelGlobals kg, IntegratorState state)
{
PROFILING_INIT(kg, PROFILING_SHADE_SURFACE_DIRECT_LIGHT);
ShaderData sd;
integrate_surface_shader_setup(kg, state, &sd);
const int shader = sd.shader & SHADER_MASK;
#ifdef __MNEE__
RNGState rng_state;
path_state_rng_load(state, &rng_state);
const ShaderEvalResult result = integrate_surface_mnee(kg, state, &sd, &rng_state);
if (result == SHADER_EVAL_CACHE_MISS) {
integrator_path_cache_miss(state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE);
return;
}
#endif
integrator_path_next_sorted(kg,
state,
DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE,
DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE,
shader);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,186 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/types.h"
#include "kernel/bvh/bvh.h"
#include "kernel/integrator/state.h"
#include "kernel/integrator/state_flow.h"
#include "kernel/integrator/state_util.h"
CCL_NAMESPACE_BEGIN
/* Visibility for the shadow ray. */
ccl_device_forceinline uint integrate_intersect_shadow_visibility(ConstIntegratorShadowState state)
{
uint visibility = PATH_RAY_VISIBILITY_SHADOW;
#ifdef __SHADOW_CATCHER__
const uint32_t path_flag = INTEGRATOR_STATE(state, shadow_path, flag);
visibility = SHADOW_CATCHER_PATH_VISIBILITY(path_flag, visibility);
#endif
return visibility;
}
ccl_device bool integrate_intersect_shadow_opaque(KernelGlobals kg,
IntegratorShadowState state,
const ccl_private Ray *ray,
const uint visibility)
{
/* Mask which will pick only opaque visibility bits from the `visibility`.
* Calculate the mask at compile time: the visibility will either be a high bits for the shadow
* catcher objects, or lower bits for the regular objects (there is no need to check the path
* state here again). */
constexpr const uint opaque_mask = SHADOW_CATCHER_VISIBILITY_SHIFT(
PATH_RAY_VISIBILITY_SHADOW_OPAQUE) |
PATH_RAY_VISIBILITY_SHADOW_OPAQUE;
const bool opaque_hit = scene_intersect_shadow(kg, ray, visibility & opaque_mask);
/* Only record the number of hits if nothing was hit, so that the shadow shading kernel does not
* consider any intersections. There is no need to write anything to the state if the hit is
* opaque because in this case the path is terminated. */
if (!opaque_hit) {
INTEGRATOR_STATE_WRITE(state, shadow_path, packed_num_hits) = 0;
}
return opaque_hit;
}
ccl_device_forceinline int integrate_shadow_max_transparent_hits(KernelGlobals kg,
ConstIntegratorShadowState state)
{
const int transparent_max_bounce = kernel_data.integrator.transparent_max_bounce;
const int transparent_bounce = INTEGRATOR_STATE(state, shadow_path, transparent_bounce);
return max(transparent_max_bounce - transparent_bounce, 0);
}
#ifdef __TRANSPARENT_SHADOWS__
# ifndef __KERNEL_GPU__
ccl_device int shadow_intersections_compare(const void *a, const void *b)
{
const Intersection *isect_a = (const Intersection *)a;
const Intersection *isect_b = (const Intersection *)b;
if (isect_a->t < isect_b->t) {
return -1;
}
if (isect_a->t > isect_b->t) {
return 1;
}
return 0;
}
# endif
ccl_device_inline void sort_shadow_intersections(IntegratorShadowState state, uint num_hits)
{
kernel_assert(num_hits > 0);
# ifdef __KERNEL_GPU__
/* Use bubble sort which has more friendly memory pattern on GPU. */
bool swapped;
do {
swapped = false;
for (int j = 0; j < num_hits - 1; ++j) {
if (INTEGRATOR_STATE_ARRAY(state, shadow_isect, j, t) >
INTEGRATOR_STATE_ARRAY(state, shadow_isect, j + 1, t))
{
struct Intersection tmp_j ccl_optional_struct_init;
struct Intersection tmp_j_1 ccl_optional_struct_init;
integrator_state_read_shadow_isect(state, &tmp_j, j);
integrator_state_read_shadow_isect(state, &tmp_j_1, j + 1);
integrator_state_write_shadow_isect(state, &tmp_j_1, j);
integrator_state_write_shadow_isect(state, &tmp_j, j + 1);
swapped = true;
}
}
--num_hits;
} while (swapped);
# else
Intersection *isect_array = (Intersection *)state->shadow_isect;
qsort(isect_array, num_hits, sizeof(Intersection), shadow_intersections_compare);
# endif
}
ccl_device bool integrate_intersect_shadow_transparent(KernelGlobals kg,
IntegratorShadowState state,
const ccl_private Ray *ray,
const uint visibility)
{
/* Limit the number hits to the max transparent bounces allowed and the size that we
* have available in the integrator state. */
const uint max_transparent_hits = integrate_shadow_max_transparent_hits(kg, state);
uint num_hits = 0;
float throughput = 1.0f;
scene_intersect_shadow_all(
kg, state, ray, visibility, max_transparent_hits, &num_hits, &throughput);
const bool opaque_hit = (throughput == 0.0f);
/* Computed throughput from baked shadow transparency, where we can bypass recording
* intersections and shader evaluation. */
if (throughput != 1.0f) {
INTEGRATOR_STATE_WRITE(state, shadow_path, throughput) *= throughput;
}
if (!opaque_hit) {
const uint num_recorded_hits = min(num_hits, (uint)INTEGRATOR_SHADOW_ISECT_SIZE);
if (num_recorded_hits > 0) {
sort_shadow_intersections(state, num_recorded_hits);
}
INTEGRATOR_STATE_WRITE(state, shadow_path, packed_num_hits) = num_hits;
}
else {
INTEGRATOR_STATE_WRITE(state, shadow_path, packed_num_hits) = 0;
}
return opaque_hit;
}
#endif
ccl_device void integrator_intersect_shadow(KernelGlobals kg, IntegratorShadowState state)
{
PROFILING_INIT(kg, PROFILING_INTERSECT_SHADOW);
/* Read ray from integrator state into local memory. */
Ray ray ccl_optional_struct_init;
integrator_state_read_shadow_ray(state, &ray);
integrator_state_read_shadow_ray_self(state, &ray);
/* Compute visibility. */
const uint visibility = integrate_intersect_shadow_visibility(state);
#ifdef __TRANSPARENT_SHADOWS__
/* TODO: compile different kernels depending on this? Especially for OptiX
* conditional trace calls are bad. */
const bool opaque_hit = (kernel_data.integrator.transparent_shadows) ?
integrate_intersect_shadow_transparent(kg, state, &ray, visibility) :
integrate_intersect_shadow_opaque(kg, state, &ray, visibility);
#else
const bool opaque_hit = integrate_intersect_shadow_opaque(kg, state, &ray, visibility);
#endif
if (opaque_hit) {
/* Hit an opaque surface, shadow path ends here. */
integrator_shadow_path_terminate(state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW);
return;
}
/* Hit nothing or transparent surfaces, continue to shadow kernel
* for shading and render buffer output.
*
* TODO: could also write to render buffer directly if no transparent shadows?
* Could save a kernel execution for the common case. */
integrator_shadow_path_next(
state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,24 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/integrator/subsurface.h"
CCL_NAMESPACE_BEGIN
ccl_device void integrator_intersect_subsurface(KernelGlobals kg, IntegratorState state)
{
PROFILING_INIT(kg, PROFILING_INTERSECT_SUBSURFACE);
#ifdef __SUBSURFACE__
if (subsurface_scatter(kg, state)) {
return;
}
#endif
integrator_path_terminate(kg, state, nullptr, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,252 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/bvh/bvh.h"
#include "kernel/geom/shader_data.h"
#include "kernel/integrator/intersect_closest.h"
#include "kernel/integrator/volume_stack.h"
CCL_NAMESPACE_BEGIN
ccl_device void integrator_volume_stack_update_for_subsurface(KernelGlobals kg,
IntegratorState state,
const float3 from_P,
const float3 to_P)
{
#ifdef __VOLUME__
PROFILING_INIT(kg, PROFILING_INTERSECT_VOLUME_STACK);
ShaderDataTinyStorage stack_sd_storage;
ccl_private ShaderData *stack_sd = AS_SHADER_DATA(&stack_sd_storage);
kernel_assert(kernel_data.integrator.use_volumes);
Ray volume_ray ccl_optional_struct_init;
volume_ray.P = from_P;
volume_ray.D = safe_normalize_len(to_P - from_P, &volume_ray.tmax);
volume_ray.tmin = 0.0f;
volume_ray.self.object = INTEGRATOR_STATE(state, isect, object);
volume_ray.self.prim = INTEGRATOR_STATE(state, isect, prim);
volume_ray.self.light_object = OBJECT_NONE;
volume_ray.self.light_prim = PRIM_NONE;
/* Store to avoid global fetches on every intersection step. */
const uint volume_stack_size = kernel_data.volume_stack_size;
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const PathRayVisibility visibility = SHADOW_CATCHER_PATH_VISIBILITY(path_flag,
PATH_RAY_VISIBILITY_ALL);
# ifdef __VOLUME_RECORD_ALL__
Intersection hits[2 * MAX_VOLUME_STACK_SIZE + 1];
const uint num_hits = scene_intersect_volume(
kg, &volume_ray, hits, 2 * volume_stack_size, visibility);
if (num_hits > 0) {
Intersection *isect = hits;
qsort(hits, num_hits, sizeof(Intersection), intersections_compare);
for (uint hit = 0; hit < num_hits; ++hit, ++isect) {
/* Ignore self, SSS itself already enters and exits the object. */
if (isect->object == volume_ray.self.object) {
continue;
}
shader_setup_from_ray(kg, stack_sd, &volume_ray, isect);
volume_stack_enter_exit<false>(kg, state, stack_sd);
}
}
# else
Intersection isect;
int step = 0;
while (step < 2 * volume_stack_size &&
scene_intersect_volume(kg, &volume_ray, &isect, visibility))
{
/* Ignore self, SSS itself already enters and exits the object. */
if (isect.object != volume_ray.self.object) {
shader_setup_from_ray(kg, stack_sd, &volume_ray, &isect);
volume_stack_enter_exit<false>(kg, state, stack_sd);
}
/* Move ray forward. */
volume_ray.tmin = intersection_t_offset(isect.t);
volume_ray.self.object = isect.object;
volume_ray.self.prim = isect.prim;
++step;
}
# endif
}
ccl_device void integrator_volume_stack_init(KernelGlobals kg, IntegratorState state)
{
PROFILING_INIT(kg, PROFILING_INTERSECT_VOLUME_STACK);
ShaderDataTinyStorage stack_sd_storage;
ccl_private ShaderData *stack_sd = AS_SHADER_DATA(&stack_sd_storage);
Ray volume_ray ccl_optional_struct_init;
integrator_state_read_ray(state, &volume_ray);
/* Trace ray in random direction. Any direction works, Z up is a guess to get the
* fewest hits. */
volume_ray.D = make_float3(0.0f, 0.0f, 1.0f);
volume_ray.tmin = 0.0f;
volume_ray.tmax = FLT_MAX;
volume_ray.self.object = OBJECT_NONE;
volume_ray.self.prim = PRIM_NONE;
volume_ray.self.light_object = OBJECT_NONE;
volume_ray.self.light_prim = PRIM_NONE;
int stack_index = 0;
int enclosed_index = 0;
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const PathRayVisibility visibility = SHADOW_CATCHER_PATH_VISIBILITY(path_flag,
PATH_RAY_VISIBILITY_CAMERA);
/* Initialize volume stack with background volume For shadow catcher the
* background volume is always assumed to be CG. */
if (kernel_data.background.volume_shader != SHADER_NONE) {
if (!(path_flag & PATH_RAY_SHADOW_CATCHER_PASS)) {
INTEGRATOR_STATE_ARRAY_WRITE(
state, volume_stack, stack_index, object) = kernel_data.background.object_index;
INTEGRATOR_STATE_ARRAY_WRITE(
state, volume_stack, stack_index, shader) = kernel_data.background.volume_shader;
stack_index++;
}
}
/* Store to avoid global fetches on every intersection step. */
const uint volume_stack_size = kernel_data.volume_stack_size;
# ifdef __VOLUME_RECORD_ALL__
Intersection hits[2 * MAX_VOLUME_STACK_SIZE + 1];
const uint num_hits = scene_intersect_volume(
kg, &volume_ray, hits, 2 * volume_stack_size, visibility);
if (num_hits > 0) {
int enclosed_volumes[MAX_VOLUME_STACK_SIZE];
Intersection *isect = hits;
qsort(hits, num_hits, sizeof(Intersection), intersections_compare);
for (uint hit = 0; hit < num_hits; ++hit, ++isect) {
shader_setup_from_ray(kg, stack_sd, &volume_ray, isect);
if (stack_sd->flag & SD_BACKFACING) {
bool need_add = true;
for (int i = 0; i < enclosed_index && need_add; ++i) {
/* If ray exited the volume and never entered to that volume
* it means that camera is inside such a volume.
*/
if (enclosed_volumes[i] == stack_sd->object) {
need_add = false;
}
}
for (int i = 0; i < stack_index && need_add; ++i) {
/* Don't add intersections twice. */
const VolumeStack entry = integrator_state_read_volume_stack(state, i);
if (entry.object == stack_sd->object) {
need_add = false;
break;
}
}
if (need_add && stack_index < volume_stack_size - 1) {
const VolumeStack new_entry = {stack_sd->object, stack_sd->shader};
integrator_state_write_volume_stack(state, stack_index, new_entry);
++stack_index;
}
}
else {
/* If ray from camera enters the volume, this volume shouldn't
* be added to the stack on exit.
*/
enclosed_volumes[enclosed_index++] = stack_sd->object;
}
}
}
# else
/* CUDA does not support definition of a variable size arrays, so use the maximum possible. */
int enclosed_volumes[MAX_VOLUME_STACK_SIZE];
int step = 0;
while (stack_index < volume_stack_size - 1 && enclosed_index < MAX_VOLUME_STACK_SIZE - 1 &&
step < 2 * volume_stack_size)
{
Intersection isect;
if (!scene_intersect_volume(kg, &volume_ray, &isect, visibility)) {
break;
}
shader_setup_from_ray(kg, stack_sd, &volume_ray, &isect);
if (stack_sd->flag & SD_BACKFACING) {
/* If ray exited the volume and never entered to that volume
* it means that camera is inside such a volume.
*/
bool need_add = true;
for (int i = 0; i < enclosed_index && need_add; ++i) {
/* If ray exited the volume and never entered to that volume
* it means that camera is inside such a volume.
*/
if (enclosed_volumes[i] == stack_sd->object) {
need_add = false;
}
}
for (int i = 0; i < stack_index && need_add; ++i) {
/* Don't add intersections twice. */
VolumeStack entry = integrator_state_read_volume_stack(state, i);
if (entry.object == stack_sd->object) {
need_add = false;
break;
}
}
if (need_add) {
const VolumeStack new_entry = {stack_sd->object, stack_sd->shader};
integrator_state_write_volume_stack(state, stack_index, new_entry);
++stack_index;
}
}
else {
/* If ray from camera enters the volume, this volume shouldn't
* be added to the stack on exit.
*/
enclosed_volumes[enclosed_index++] = stack_sd->object;
}
/* Move ray forward. */
volume_ray.tmin = intersection_t_offset(isect.t);
volume_ray.self.object = isect.object;
volume_ray.self.prim = isect.prim;
++step;
}
# endif
/* Write terminator. */
const VolumeStack new_entry = {OBJECT_NONE, SHADER_NONE};
integrator_state_write_volume_stack(state, stack_index, new_entry);
#endif
}
ccl_device void integrator_intersect_volume_stack(KernelGlobals kg, IntegratorState state)
{
#ifdef __VOLUME__
integrator_volume_stack_init(kg, state);
# ifdef __SHADOW_CATCHER__
if (INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_PASS) {
/* Volume stack re-init for shadow catcher, continue with shading of hit. */
integrator_intersect_next_kernel_after_shadow_catcher_volume<
DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK>(kg, state);
}
else
# endif
{
/* Volume stack init for camera rays, continue with intersection of camera ray. */
integrator_path_next(state,
DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK,
DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST);
}
#endif
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,121 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/integrator/intersect_closest.h"
#include "kernel/integrator/intersect_dedicated_light.h"
#include "kernel/integrator/intersect_mnee.h"
#include "kernel/integrator/intersect_shadow.h"
#include "kernel/integrator/intersect_subsurface.h"
#include "kernel/integrator/intersect_volume_stack.h"
#include "kernel/integrator/shade_background.h"
#include "kernel/integrator/shade_dedicated_light.h"
#include "kernel/integrator/shade_light.h"
#include "kernel/integrator/shade_shadow.h"
#include "kernel/integrator/shade_surface.h"
#include "kernel/integrator/shade_volume.h"
CCL_NAMESPACE_BEGIN
ccl_device void integrator_megakernel(KernelGlobals kg,
IntegratorState state,
ccl_global float *ccl_restrict render_buffer)
{
/* Each kernel indicates the next kernel to execute, so here we simply
* have to check what that kernel is and execute it. */
while (true) {
/* Handle any shadow paths before we potentially create more shadow paths. */
const uint32_t shadow_queued_kernel = INTEGRATOR_STATE(
&state->shadow, shadow_path, queued_kernel);
if (shadow_queued_kernel) {
switch (shadow_queued_kernel) {
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW:
integrator_intersect_shadow(kg, &state->shadow);
continue;
case DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW:
integrator_shade_shadow(kg, &state->shadow, render_buffer);
continue;
case DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_NEE:
integrator_shade_light_nee(kg, &state->shadow, render_buffer);
continue;
case DEVICE_KERNEL_INTEGRATOR_SHADOW_PATH_MNEE_PENDING:
/* Not a real kernel, only a state to keep it alive until
* shade_surface uses this shadow path. */
break;
default:
kernel_assert(0);
break;
}
}
/* Handle any AO paths before we potentially create more AO paths. */
const uint32_t ao_queued_kernel = INTEGRATOR_STATE(&state->ao, shadow_path, queued_kernel);
if (ao_queued_kernel) {
switch (ao_queued_kernel) {
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW:
integrator_intersect_shadow(kg, &state->ao);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW:
integrator_shade_shadow(kg, &state->ao, render_buffer);
break;
default:
kernel_assert(0);
break;
}
continue;
}
/* Then handle regular path kernels. */
const uint32_t queued_kernel = INTEGRATOR_STATE(state, path, queued_kernel);
if (queued_kernel) {
switch (queued_kernel) {
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST:
integrator_intersect_closest(kg, state, render_buffer);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND:
integrator_shade_background(kg, state, render_buffer);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE:
integrator_shade_surface(kg, state, render_buffer);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME:
integrator_shade_volume(kg, state, render_buffer);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_VOLUME_RAY_MARCHING:
integrator_shade_volume_ray_marching(kg, state, render_buffer);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE:
integrator_shade_surface_raytrace(kg, state, render_buffer);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_FORWARD:
integrator_shade_light_forward(kg, state, render_buffer);
break;
case DEVICE_KERNEL_INTEGRATOR_SHADE_DEDICATED_LIGHT:
integrator_shade_dedicated_light(kg, state, render_buffer);
break;
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE:
integrator_intersect_subsurface(kg, state);
break;
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_VOLUME_STACK:
integrator_intersect_volume_stack(kg, state);
break;
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_DEDICATED_LIGHT:
integrator_intersect_dedicated_light(kg, state);
break;
case DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE:
integrator_intersect_mnee(kg, state);
break;
default:
kernel_assert(0);
break;
}
continue;
}
break;
}
}
CCL_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,431 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/integrator/state.h"
#include "kernel/sample/pattern.h"
CCL_NAMESPACE_BEGIN
/* Initialize queues, so that this path is considered terminated.
* Used for early outputs in the camera ray initialization, as well as initialization of split
* states for shadow catcher. */
ccl_device_inline void path_state_init_queues(IntegratorState state)
{
INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = 0;
#ifndef __KERNEL_GPU__
INTEGRATOR_STATE_WRITE(&state->shadow, shadow_path, queued_kernel) = 0;
INTEGRATOR_STATE_WRITE(&state->ao, shadow_path, queued_kernel) = 0;
#endif
}
/* Minimalistic initialization of the path state, which is needed for early outputs in the
* integrator initialization to work. */
ccl_device_inline void path_state_init(IntegratorState state,
const ccl_global KernelWorkTile *ccl_restrict tile,
const int x,
const int y)
{
const uint render_pixel_index = (uint)tile->offset + x + y * tile->stride;
INTEGRATOR_STATE_WRITE(state, path, render_pixel_index) = render_pixel_index;
path_state_init_queues(state);
}
/* Initialize the rest of the path state needed to continue the path integration. */
ccl_device_inline void path_state_init_integrator(KernelGlobals kg,
IntegratorState state,
const int sample,
const uint rng_pixel,
const Spectrum throughput)
{
INTEGRATOR_STATE_WRITE(state, path, sample) = sample;
INTEGRATOR_STATE_WRITE(state, path, bounce) = 0;
INTEGRATOR_STATE_WRITE(state, path, diffuse_bounce) = 0;
INTEGRATOR_STATE_WRITE(state, path, glossy_bounce) = 0;
INTEGRATOR_STATE_WRITE(state, path, transmission_bounce) = 0;
INTEGRATOR_STATE_WRITE(state, path, transparent_bounce) = 0;
INTEGRATOR_STATE_WRITE(state, path, volume_bounce) = 0;
INTEGRATOR_STATE_WRITE(state, path, volume_bounds_bounce) = 0;
if ((kernel_data.kernel_features & KERNEL_FEATURE_NODE_PORTAL)) {
INTEGRATOR_STATE_WRITE(state, path, portal_bounce) = 0;
}
INTEGRATOR_STATE_WRITE(state, path, rng_pixel) = rng_pixel;
INTEGRATOR_STATE_WRITE(state, path, rng_offset) = PRNG_BOUNCE_NUM;
INTEGRATOR_STATE_WRITE(state, path, visibility) = PATH_RAY_VISIBILITY_CAMERA;
INTEGRATOR_STATE_WRITE(state, path, flag) = PATH_RAY_MIS_SKIP | PATH_RAY_TRANSPARENT_BACKGROUND;
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = 0.0f;
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = FLT_MAX;
INTEGRATOR_STATE_WRITE(state, path, continuation_probability) = 1.0f;
INTEGRATOR_STATE_WRITE(state, path, throughput) = throughput;
INTEGRATOR_STATE_WRITE(state, path, optical_depth) = 0.0f;
#if defined(__PATH_GUIDING__)
if ((kernel_data.kernel_features & KERNEL_FEATURE_PATH_GUIDING)) {
INTEGRATOR_STATE_WRITE(state, path, unguided_throughput) = 1.0f;
INTEGRATOR_STATE_WRITE(state, guiding, path_segment) = nullptr;
INTEGRATOR_STATE_WRITE(state, guiding, use_surface_guiding) = false;
INTEGRATOR_STATE_WRITE(state, guiding, sample_surface_guiding_rand) = 0.5f;
INTEGRATOR_STATE_WRITE(state, guiding, surface_guiding_sampling_prob) = 0.0f;
INTEGRATOR_STATE_WRITE(state, guiding, bssrdf_sampling_prob) = 0.0f;
INTEGRATOR_STATE_WRITE(state, guiding, use_volume_guiding) = false;
INTEGRATOR_STATE_WRITE(state, guiding, sample_volume_guiding_rand) = 0.5f;
INTEGRATOR_STATE_WRITE(state, guiding, volume_guiding_sampling_prob) = 0.0f;
}
#endif
#ifdef __MNEE__
INTEGRATOR_STATE_WRITE(state, path, mnee) = 0;
#endif
INTEGRATOR_STATE_WRITE(state, isect, object) = OBJECT_NONE;
INTEGRATOR_STATE_WRITE(state, isect, prim) = PRIM_NONE;
INTEGRATOR_STATE_WRITE(state, isect, type) = PRIMITIVE_NONE;
if (kernel_data.kernel_features & KERNEL_FEATURE_VOLUME) {
INTEGRATOR_STATE_ARRAY_WRITE(
state, volume_stack, 0, object) = kernel_data.background.object_index;
INTEGRATOR_STATE_ARRAY_WRITE(
state, volume_stack, 0, shader) = kernel_data.background.volume_shader;
INTEGRATOR_STATE_ARRAY_WRITE(state, volume_stack, 1, object) = OBJECT_NONE;
INTEGRATOR_STATE_ARRAY_WRITE(state, volume_stack, 1, shader) = SHADER_NONE;
}
#ifdef __DENOISING_FEATURES__
if (kernel_data.kernel_features & KERNEL_FEATURE_DENOISING) {
INTEGRATOR_STATE_WRITE(state, path, flag) |= PATH_RAY_DENOISING_FEATURES;
INTEGRATOR_STATE_WRITE(state, path, denoising_feature_throughput) = one_spectrum();
}
#endif
#ifdef __LIGHT_LINKING__
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING) {
INTEGRATOR_STATE_WRITE(state, path, mis_ray_object) = kernel_data.background.object_index;
}
#endif
}
ccl_device_inline void path_state_next(KernelGlobals kg,
IntegratorState state,
const int label,
const int shader_flag)
{
PathRayVisibility visibility = INTEGRATOR_STATE(state, path, visibility);
uint32_t flag = INTEGRATOR_STATE(state, path, flag);
/* ray through transparent keeps same flags from previous ray and is
* not counted as a regular bounce, transparent has separate max */
if (label & (LABEL_TRANSPARENT | LABEL_RAY_PORTAL)) {
const int transparent_bounce = INTEGRATOR_STATE(state, path, transparent_bounce) + 1;
flag |= PATH_RAY_TRANSPARENT;
if (transparent_bounce >= kernel_data.integrator.transparent_max_bounce) {
/* FIXME: `transparent_max_bounce` could be 0, but `transparent_bounce` is at least 1 when we
* enter this path. */
flag |= PATH_RAY_TERMINATE_ON_NEXT_SURFACE;
}
if (shader_flag & SD_RAY_PORTAL) {
flag |= PATH_RAY_MIS_SKIP;
INTEGRATOR_STATE_WRITE(
state, path, portal_bounce) = INTEGRATOR_STATE(state, path, portal_bounce) + 1;
}
INTEGRATOR_STATE_WRITE(state, path, flag) = flag;
INTEGRATOR_STATE_WRITE(state, path, transparent_bounce) = transparent_bounce;
/* Random number generator next bounce. */
INTEGRATOR_STATE_WRITE(state, path, rng_offset) += PRNG_BOUNCE_NUM;
return;
}
const int bounce = INTEGRATOR_STATE(state, path, bounce) + 1;
if (bounce >= kernel_data.integrator.max_bounce) {
flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
}
visibility = PATH_RAY_VISIBILITY_NONE;
flag &= ~(PATH_RAY_REFLECT | PATH_RAY_SINGULAR | PATH_RAY_TRANSPARENT |
PATH_RAY_IMPORTANCE_BAKE | PATH_RAY_MIS_SKIP | PATH_RAY_MIS_HAD_TRANSMISSION);
#ifdef __VOLUME__
if (label & LABEL_VOLUME_SCATTER) {
/* volume scatter */
visibility |= PATH_RAY_VISIBILITY_VOLUME_SCATTER;
flag |= PATH_RAY_MIS_HAD_TRANSMISSION;
flag &= ~PATH_RAY_TRANSPARENT_BACKGROUND;
if (!(flag & PATH_RAY_ANY_PASS)) {
flag |= PATH_RAY_VOLUME_PASS;
}
const int volume_bounce = INTEGRATOR_STATE(state, path, volume_bounce) + 1;
INTEGRATOR_STATE_WRITE(state, path, volume_bounce) = volume_bounce;
if (volume_bounce >= kernel_data.integrator.max_volume_bounce) {
flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
}
if (bounce == 1) {
flag &= ~PATH_RAY_VOLUME_PRIMARY_TRANSMIT;
}
}
else
#endif
{
/* surface reflection/transmission */
if (label & LABEL_REFLECT) {
flag |= PATH_RAY_REFLECT;
flag &= ~PATH_RAY_TRANSPARENT_BACKGROUND;
if (label & LABEL_DIFFUSE) {
const int diffuse_bounce = INTEGRATOR_STATE(state, path, diffuse_bounce) + 1;
INTEGRATOR_STATE_WRITE(state, path, diffuse_bounce) = diffuse_bounce;
if (diffuse_bounce >= kernel_data.integrator.max_diffuse_bounce) {
flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
}
}
else {
const int glossy_bounce = INTEGRATOR_STATE(state, path, glossy_bounce) + 1;
INTEGRATOR_STATE_WRITE(state, path, glossy_bounce) = glossy_bounce;
if (glossy_bounce >= kernel_data.integrator.max_glossy_bounce) {
flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
}
}
}
else {
kernel_assert(label & LABEL_TRANSMIT);
visibility |= PATH_RAY_VISIBILITY_TRANSMIT;
if (!(label & LABEL_TRANSMIT_TRANSPARENT)) {
flag &= ~PATH_RAY_TRANSPARENT_BACKGROUND;
}
const int transmission_bounce = INTEGRATOR_STATE(state, path, transmission_bounce) + 1;
INTEGRATOR_STATE_WRITE(state, path, transmission_bounce) = transmission_bounce;
if (transmission_bounce >= kernel_data.integrator.max_transmission_bounce) {
flag |= PATH_RAY_TERMINATE_AFTER_TRANSPARENT;
}
}
/* diffuse/glossy/singular */
if (label & LABEL_DIFFUSE) {
visibility |= PATH_RAY_VISIBILITY_DIFFUSE;
flag |= PATH_RAY_DIFFUSE_ANCESTOR;
}
else if (label & LABEL_GLOSSY) {
visibility |= PATH_RAY_VISIBILITY_GLOSSY;
}
else {
kernel_assert(label & LABEL_SINGULAR);
visibility |= PATH_RAY_VISIBILITY_GLOSSY;
flag |= PATH_RAY_SINGULAR | PATH_RAY_MIS_SKIP;
}
/* Flag for consistent MIS weights with light tree. */
if (shader_flag & SD_BSDF_HAS_TRANSMISSION) {
flag |= PATH_RAY_MIS_HAD_TRANSMISSION;
}
/* Render pass categories. */
if (!(flag & PATH_RAY_ANY_PASS) && !(flag & PATH_RAY_TRANSPARENT_BACKGROUND)) {
flag |= PATH_RAY_SURFACE_PASS;
}
}
INTEGRATOR_STATE_WRITE(state, path, visibility) = visibility;
INTEGRATOR_STATE_WRITE(state, path, flag) = flag;
INTEGRATOR_STATE_WRITE(state, path, bounce) = bounce;
/* Random number generator next bounce. */
INTEGRATOR_STATE_WRITE(state, path, rng_offset) += PRNG_BOUNCE_NUM;
}
#ifdef __VOLUME__
ccl_device_inline bool path_state_volume_next(IntegratorState state)
{
/* For volume bounding meshes we pass through without counting transparent
* bounces, only sanity check in case self intersection gets us stuck. */
const uint32_t volume_bounds_bounce = INTEGRATOR_STATE(state, path, volume_bounds_bounce) + 1;
INTEGRATOR_STATE_WRITE(state, path, volume_bounds_bounce) = volume_bounds_bounce;
if (volume_bounds_bounce > VOLUME_BOUNDS_MAX) {
return false;
}
/* Random number generator next bounce. */
INTEGRATOR_STATE_WRITE(state, path, rng_offset) += PRNG_BOUNCE_NUM;
return true;
}
#endif
ccl_device_inline PathRayVisibility path_state_ray_visibility(ConstIntegratorState state)
{
PathRayVisibility visibility = INTEGRATOR_STATE(state, path, visibility);
/* For visibility, diffuse/glossy are for reflection only. */
if (visibility & PATH_RAY_VISIBILITY_TRANSMIT) {
visibility &= ~(PATH_RAY_VISIBILITY_DIFFUSE | PATH_RAY_VISIBILITY_GLOSSY);
}
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
visibility = SHADOW_CATCHER_PATH_VISIBILITY(path_flag, visibility);
return visibility;
}
ccl_device_inline float path_state_continuation_probability(KernelGlobals kg,
ConstIntegratorState state,
const uint32_t path_flag)
{
if (path_flag & PATH_RAY_TRANSPARENT) {
const int transparent_bounce = INTEGRATOR_STATE(state, path, transparent_bounce);
/* Do at least specified number of bounces without RR. */
if (transparent_bounce <= kernel_data.integrator.transparent_min_bounce) {
return 1.0f;
}
}
else {
const int bounce = INTEGRATOR_STATE(state, path, bounce);
/* Do at least specified number of bounces without RR. */
if (bounce <= kernel_data.integrator.min_bounce) {
return 1.0f;
}
}
/* Probabilistic termination: use `sqrt()` to roughly match typical view
* transform and do path termination a bit later on average. */
Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
if ((kernel_data.kernel_features & KERNEL_FEATURE_PATH_GUIDING)) {
throughput *= INTEGRATOR_STATE(state, path, unguided_throughput);
}
#endif
return min(sqrtf(reduce_max(fabs(throughput))), 1.0f);
}
ccl_device_inline bool path_state_ao_bounce(KernelGlobals kg, ConstIntegratorState state)
{
if (!kernel_data.integrator.ao_bounces) {
return false;
}
const int bounce = INTEGRATOR_STATE(state, path, bounce) -
INTEGRATOR_STATE(state, path, transmission_bounce) -
(INTEGRATOR_STATE(state, path, glossy_bounce) > 0) + 1;
return (bounce > kernel_data.integrator.ao_bounces);
}
/* Random Number Sampling Utility Functions
*
* For each random number in each step of the path we must have a unique
* dimension to avoid using the same sequence twice.
*
* For branches in the path we must be careful not to reuse the same number
* in a sequence and offset accordingly.
*/
/* RNG State loaded onto stack. */
struct RNGState {
uint rng_pixel;
uint rng_offset;
int sample;
};
ccl_device_inline void path_state_rng_load(ConstIntegratorState state,
ccl_private RNGState *rng_state)
{
rng_state->rng_pixel = INTEGRATOR_STATE(state, path, rng_pixel);
rng_state->rng_offset = INTEGRATOR_STATE(state, path, rng_offset);
rng_state->sample = INTEGRATOR_STATE(state, path, sample);
}
ccl_device_inline void shadow_path_state_rng_load(ConstIntegratorShadowState state,
ccl_private RNGState *rng_state)
{
rng_state->rng_pixel = INTEGRATOR_STATE(state, shadow_path, rng_pixel);
rng_state->rng_offset = INTEGRATOR_STATE(state, shadow_path, rng_offset);
rng_state->sample = INTEGRATOR_STATE(state, shadow_path, sample);
}
ccl_device_inline void path_state_rng_scramble(ccl_private RNGState *rng_state, const int seed)
{
/* To get an uncorrelated sequence of samples (e.g. for subsurface random walk), just change
* the dimension offset since all implemented samplers can generate unlimited numbers of
* dimensions anyway. The only thing to ensure is that the offset is divisible by 4. */
rng_state->rng_offset = hash_hp_seeded_uint(rng_state->rng_offset, seed) & ~0x3;
}
ccl_device_inline float path_state_rng_1D(KernelGlobals kg,
const ccl_private RNGState *rng_state,
const int dimension)
{
return path_rng_1D(
kg, rng_state->rng_pixel, rng_state->sample, rng_state->rng_offset + dimension);
}
ccl_device_inline float2 path_state_rng_2D(KernelGlobals kg,
const ccl_private RNGState *rng_state,
const int dimension)
{
return path_rng_2D(
kg, rng_state->rng_pixel, rng_state->sample, rng_state->rng_offset + dimension);
}
ccl_device_inline float3 path_state_rng_3D(KernelGlobals kg,
const ccl_private RNGState *rng_state,
const int dimension)
{
return path_rng_3D(
kg, rng_state->rng_pixel, rng_state->sample, rng_state->rng_offset + dimension);
}
ccl_device_inline float path_branched_rng_1D(KernelGlobals kg,
const ccl_private RNGState *rng_state,
const int branch,
const int num_branches,
const int dimension)
{
return path_rng_1D(kg,
rng_state->rng_pixel,
rng_state->sample * num_branches + branch,
rng_state->rng_offset + dimension);
}
ccl_device_inline float2 path_branched_rng_2D(KernelGlobals kg,
const ccl_private RNGState *rng_state,
const int branch,
const int num_branches,
const int dimension)
{
return path_rng_2D(kg,
rng_state->rng_pixel,
rng_state->sample * num_branches + branch,
rng_state->rng_offset + dimension);
}
ccl_device_inline float3 path_branched_rng_3D(KernelGlobals kg,
const ccl_private RNGState *rng_state,
const int branch,
const int num_branches,
const int dimension)
{
return path_rng_3D(kg,
rng_state->rng_pixel,
rng_state->sample * num_branches + branch,
rng_state->rng_offset + dimension);
}
/* Utility functions to get light termination value,
* since it might not be needed in many cases.
*/
ccl_device_inline float path_state_rng_light_termination(KernelGlobals kg,
const ccl_private RNGState *state)
{
if (kernel_data.integrator.light_inv_rr_threshold > 0.0f) {
return path_state_rng_1D(kg, state, PRNG_LIGHT_TERMINATE);
}
return 0.0f;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,281 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/film/data_passes.h"
#include "kernel/film/denoising_passes.h"
#include "kernel/film/light_passes.h"
#include "kernel/integrator/guiding.h"
#include "kernel/integrator/intersect_closest.h"
#include "kernel/integrator/state_flow.h"
#include "kernel/integrator/surface_shader.h"
#include "kernel/light/light.h"
#include "kernel/light/sample.h"
#include "kernel/geom/object.h"
#include "kernel/geom/shader_data.h"
#include "kernel/types.h"
CCL_NAMESPACE_BEGIN
ccl_device void integrator_shade_background_cache_miss_set_resume_offset(IntegratorState state,
const int resume_offset)
{
/* Abuse prim field that is not used by shade_background. */
INTEGRATOR_STATE_WRITE(state, isect, prim) = resume_offset;
}
ccl_device int integrator_shade_background_cache_miss_get_resume_offset(IntegratorState state)
{
/* Abuse prim field that is not used by shade_background. */
const int resume_offset = INTEGRATOR_STATE_WRITE(state, isect, prim);
return (resume_offset == PRIM_NONE) ? 0 : resume_offset;
}
ccl_device Spectrum integrator_eval_background_shader(KernelGlobals kg,
IntegratorState state,
ccl_global float *ccl_restrict render_buffer,
ccl_private ShaderEvalResult &result)
{
const int shader = kernel_data.background.surface_shader;
const PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
/* Use visibility flag to skip lights. */
if (!is_light_shader_visible_to_path(shader, path_visibility, path_flag)) {
result = SHADER_EVAL_EMPTY;
return zero_spectrum();
}
/* Use fast constant background color if available. */
Spectrum L = zero_spectrum();
if (surface_shader_constant_emission(kg, shader, &L)) {
result = SHADER_EVAL_OK;
return L;
}
/* Evaluate background shader. */
/* TODO: does aliasing like this break automatic SoA in CUDA?
* Should we instead store closures separate from ShaderData? */
ShaderDataTinyStorage emission_sd_storage;
ccl_private ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage);
/* Clamp indirect evaluations to the importance map. Camera rays are not affected
* by the importance map and don't involve NEE, so don't need this. */
float ray_dD = INTEGRATOR_STATE(state, ray, dD);
if (!(path_visibility & PATH_RAY_VISIBILITY_CAMERA)) {
ray_dD = background_light_clamp_dD(kg, ray_dD);
}
PROFILING_INIT_FOR_SHADER(kg, PROFILING_SHADE_LIGHT_SETUP);
shader_setup_from_background(kg,
emission_sd,
INTEGRATOR_STATE(state, ray, P),
INTEGRATOR_STATE(state, ray, D),
ray_dD,
INTEGRATOR_STATE(state, ray, time));
PROFILING_SHADER(emission_sd->object, emission_sd->shader);
PROFILING_EVENT(PROFILING_SHADE_LIGHT_EVAL);
surface_shader_eval<KERNEL_FEATURE_NODE_MASK_SURFACE_BACKGROUND>(
kg, state, emission_sd, render_buffer, path_visibility, path_flag | PATH_RAY_EMISSION);
result = (emission_sd->flag & SD_CACHE_MISS) ? SHADER_EVAL_CACHE_MISS : SHADER_EVAL_OK;
return surface_shader_background(emission_sd);
}
ccl_device_inline ShaderEvalResult integrate_background(
KernelGlobals kg, IntegratorState state, ccl_global float *ccl_restrict render_buffer)
{
/* Accumulate transparency for transparent background. We can skip background
* shader evaluation unless a background pass is used. */
bool eval_background = true;
float transparent = 0.0f;
const PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const bool is_transparent_background_ray = kernel_data.background.transparent &&
(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND);
if (is_transparent_background_ray) {
transparent = average(INTEGRATOR_STATE(state, path, throughput));
#ifdef __PASSES__
eval_background = (kernel_data.film.light_pass_flag & PASSMASK(BACKGROUND));
#else
eval_background = false;
#endif
}
#ifdef __MNEE__
if (INTEGRATOR_STATE(state, path, mnee) & PATH_MNEE_CULL_LIGHT_CONNECTION) {
if (kernel_data.background.use_mis) {
for (int lamp = 0; lamp < kernel_data.integrator.num_lights; lamp++) {
/* This path should have been resolved with mnee, it will
* generate a firefly for small lights since it is improbable. */
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp);
if (klight->type == LIGHT_BACKGROUND && klight->use_caustics) {
eval_background = false;
break;
}
}
}
}
#endif /* __MNEE__ */
/* Evaluate background shader. */
Spectrum L = zero_spectrum();
if (eval_background) {
ShaderEvalResult result = SHADER_EVAL_EMPTY;
L = integrator_eval_background_shader(kg, state, render_buffer, result);
if (result == SHADER_EVAL_CACHE_MISS) {
integrator_shade_background_cache_miss_set_resume_offset(state,
kernel_data.integrator.num_lights);
return SHADER_EVAL_CACHE_MISS;
}
/* When using the ao bounces approximation, adjust background
* shader intensity with ao factor. */
if (path_state_ao_bounce(kg, state)) {
L *= kernel_data.integrator.ao_bounces_factor;
}
/* Background MIS weights. */
const float mis_weight = light_sample_mis_weight_forward_background(
kg, state, path_visibility, path_flag);
guiding_record_background(kg, state, L, mis_weight);
L *= mis_weight;
}
/* Write to render buffer. */
film_write_background(kg, state, L, transparent, is_transparent_background_ray, render_buffer);
film_write_data_passes_background(kg, state, render_buffer);
#ifdef __DENOISING_FEATURES__
film_write_denoising_features_background(kg, state, render_buffer);
#endif
return SHADER_EVAL_OK;
}
ccl_device_inline ShaderEvalResult integrate_sun_lights(
KernelGlobals kg, IntegratorState state, ccl_global float *ccl_restrict render_buffer)
{
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
const float ray_time = INTEGRATOR_STATE(state, ray, time);
const int lamp_offset = integrator_shade_background_cache_miss_get_resume_offset(state);
for (int lamp = lamp_offset; lamp < kernel_data.integrator.num_lights; lamp++) {
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, lamp);
if (klight->type != LIGHT_SUN || !(klight->shader_id & SHADER_USE_MIS)) {
continue;
}
LightEval light_eval = sun_light_eval_from_intersection(klight, ray_D);
if (light_eval.eval_fac == 0.0f) {
continue;
}
/* Use visibility flag to skip lights. */
#ifdef __PASSES__
const PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (!is_light_shader_visible_to_path(klight->shader_id, path_visibility, path_flag)) {
continue;
}
#endif
#ifdef __LIGHT_LINKING__
if (!(path_visibility & PATH_RAY_VISIBILITY_CAMERA) &&
!light_link_object_match(kg, light_link_receiver_forward(kg, state), klight->object_id))
{
continue;
}
#endif
#ifdef __SHADOW_LINKING__
if (kernel_data_fetch(objects, klight->object_id).shadow_set_membership != LIGHT_LINK_MASK_ALL)
{
continue;
}
#endif
#ifdef __MNEE__
if (INTEGRATOR_STATE(state, path, mnee) & PATH_MNEE_CULL_LIGHT_CONNECTION) {
/* This path should have been resolved with mnee, it will
* generate a firefly for small lights since it is improbable. */
if (klight->use_caustics) {
continue;
}
}
#endif /* __MNEE__ */
/* Evaluate light shader. */
Spectrum shader_eval;
const ShaderEvalResult eval_result = light_sample_shader_eval_forward(
kg, state, lamp, zero_float3(), ray_D, FLT_MAX, ray_time, shader_eval);
if (eval_result == SHADER_EVAL_CACHE_MISS) {
integrator_shade_background_cache_miss_set_resume_offset(state, lamp);
return SHADER_EVAL_CACHE_MISS;
}
const float3 eval = shader_eval * light_eval.eval_fac;
if (is_zero(eval)) {
continue;
}
/* MIS weighting. */
const float mis_weight = light_sample_mis_weight_forward_distant(
kg, state, path_visibility, path_flag, klight->object_id, light_eval.pdf);
/* Write to render buffer. */
guiding_record_background(kg, state, eval, mis_weight);
film_write_surface_emission(
kg, state, eval, mis_weight, render_buffer, object_lightgroup(kg, klight->object_id));
}
return SHADER_EVAL_OK;
}
ccl_device void integrator_shade_background(KernelGlobals kg,
IntegratorState state,
ccl_global float *ccl_restrict render_buffer)
{
PROFILING_INIT(kg, PROFILING_SHADE_LIGHT_SETUP);
/* TODO: unify these in a single loop to only have a single shader evaluation call. */
ShaderEvalResult result = integrate_sun_lights(kg, state, render_buffer);
if (result == SHADER_EVAL_CACHE_MISS) {
integrator_path_cache_miss(state, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND);
return;
}
result = integrate_background(kg, state, render_buffer);
if (result == SHADER_EVAL_CACHE_MISS) {
integrator_path_cache_miss(state, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND);
return;
}
#ifdef __SHADOW_CATCHER__
if (INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SHADOW_CATCHER_BACKGROUND) {
/* Special case for shadow catcher where we want to fill the background pass
* behind the shadow catcher but also continue tracing the path. */
INTEGRATOR_STATE_WRITE(state, path, flag) &= ~PATH_RAY_SHADOW_CATCHER_BACKGROUND;
integrator_intersect_next_kernel_after_shadow_catcher_background<
DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND>(kg, state);
return;
}
#endif
integrator_path_terminate(kg, state, render_buffer, DEVICE_KERNEL_INTEGRATOR_SHADE_BACKGROUND);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,243 @@
/* SPDX-FileCopyrightText: 2011-2023 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/integrator/state_flow.h"
#include "kernel/light/light.h"
#include "kernel/light/sample.h"
#include "kernel/light/sun.h"
#include "kernel/integrator/shade_surface.h"
CCL_NAMESPACE_BEGIN
#ifdef __SHADOW_LINKING__
ccl_device_inline LightEval
shadow_linking_light_eval_from_intersection(KernelGlobals kg,
const ccl_private Intersection &ccl_restrict isect,
const ccl_private Ray &ccl_restrict ray,
const float3 N,
const uint32_t path_flag)
{
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, isect.prim);
const LightType type = LightType(klight->type);
return (type == LIGHT_SUN) ?
sun_light_eval_from_intersection(klight, ray.D) :
light_eval_from_intersection(kg, &isect, ray.P, ray.D, N, path_flag);
}
ccl_device_inline float shadow_linking_light_sample_mis_weight(
KernelGlobals kg,
IntegratorState state,
const PathRayVisibility path_visibility,
const uint32_t path_flag,
const int light_id,
const int object_id,
const float light_sample_pdf,
const float3 P)
{
if (kernel_data_fetch(lights, light_id).type == LIGHT_SUN) {
return light_sample_mis_weight_forward_distant(
kg, state, path_visibility, path_flag, object_id, light_sample_pdf);
}
return light_sample_mis_weight_forward_lamp(
kg, state, path_visibility, path_flag, object_id, light_sample_pdf, P);
}
/* Setup ray for the shadow path.
* Expects that the current state of the ray is the one calculated by the surface bounce, and the
* intersection corresponds to a point on an emitter. */
ccl_device void shadow_linking_setup_ray_from_intersection(
IntegratorState state,
ccl_private Ray *ccl_restrict ray,
const ccl_private Intersection *ccl_restrict isect)
{
/* The ray->tmin follows the value configured at the surface bounce.
* it is the same for the continued main path and for this shadow ray. There is no need to push
* it forward here. */
ray->tmax = isect->t;
/* Use the same self intersection primitives as the main path.
* Those are copied to the dedicated storage from the main intersection after the surface bounce,
* but before the main intersection is re-used to find light to trace a ray to. */
ray->self.object = INTEGRATOR_STATE(state, shadow_link, last_isect_object);
ray->self.prim = INTEGRATOR_STATE(state, shadow_link, last_isect_prim);
ray->self.light_object = isect->object;
ray->self.light_prim = isect->prim;
}
ccl_device bool shadow_linking_shade_light(KernelGlobals kg,
IntegratorState state,
ccl_private Ray &ccl_restrict ray,
ccl_private Intersection &ccl_restrict isect,
ccl_private float &ccl_restrict light_weight,
ccl_private float &mis_weight,
ccl_private int &ccl_restrict light_group,
ccl_private int &ccl_restrict shader_id)
{
const PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
const LightEval light_eval = shadow_linking_light_eval_from_intersection(
kg, isect, ray, N, path_flag);
if (light_eval.eval_fac == 0.0f) {
/* No light to be sampled, so no direct light contribution either. */
return SHADER_EVAL_EMPTY;
}
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, isect.prim);
if (!is_light_shader_visible_to_path(klight->shader_id, path_visibility, path_flag)) {
return false;
}
/* MIS weighting. */
mis_weight = shadow_linking_light_sample_mis_weight(
kg, state, path_visibility, path_flag, isect.prim, isect.object, light_eval.pdf, ray.P);
light_weight = light_eval.eval_fac * mis_weight *
INTEGRATOR_STATE(state, shadow_link, dedicated_light_weight);
light_group = object_lightgroup(kg, klight->object_id);
shader_id = klight->shader_id;
return SHADER_EVAL_OK;
}
ccl_device bool shadow_linking_shade_surface_emission(KernelGlobals kg,
IntegratorState state,
ccl_private float &ccl_restrict light_weight,
ccl_private float &mis_weight,
ccl_private int &ccl_restrict light_group,
ccl_private int &ccl_restrict shader_id)
{
ShaderDataTinyStorage emission_sd_storage;
ccl_private ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage);
const PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
integrate_surface_shader_setup(kg, state, emission_sd);
# ifdef __VOLUME__
if (emission_sd->flag & SD_HAS_ONLY_VOLUME) {
return SHADER_EVAL_EMPTY;
}
# endif
mis_weight = light_sample_mis_weight_forward_surface(
kg, state, path_visibility, path_flag, emission_sd);
light_weight = mis_weight * INTEGRATOR_STATE(state, shadow_link, dedicated_light_weight);
light_group = object_lightgroup(kg, emission_sd->object);
shader_id = emission_sd->shader;
return SHADER_EVAL_OK;
}
ccl_device void shadow_linking_shade(KernelGlobals kg, IntegratorState state)
{
/* Read intersection from integrator state into local memory. */
Intersection isect ccl_optional_struct_init;
integrator_state_read_isect(state, &isect);
/* Read ray from integrator state into local memory. */
Ray ray ccl_optional_struct_init;
integrator_state_read_ray(state, &ray);
float light_weight = 0.0f;
float mis_weight = 1.0f;
int light_group = LIGHTGROUP_NONE;
int shader_id = SHADER_NONE;
if (isect.type == PRIMITIVE_LAMP) {
if (!shadow_linking_shade_light(
kg, state, ray, isect, light_weight, mis_weight, light_group, shader_id))
{
return;
}
}
else {
if (!shadow_linking_shade_surface_emission(
kg, state, light_weight, mis_weight, light_group, shader_id))
{
return;
}
}
/* Evaluate constant part of light shader, rest will optionally be done in another kernel. */
Spectrum light_eval;
const bool is_constant_light_shader = light_sample_shader_eval_nee_constant(
kg, shader_id, isect.prim, isect.type == PRIMITIVE_LAMP, light_eval);
light_eval *= light_weight;
if (is_zero(light_eval)) {
return;
}
shadow_linking_setup_ray_from_intersection(state, &ray, &isect);
/* Branch off shadow kernel. */
IntegratorShadowState shadow_state = integrate_direct_light_shadow_init_common(
kg, state, &ray, light_eval, light_group, 0, is_constant_light_shader);
/* The light is accumulated from the shade_surface kernel, which will make the clamping decision
* based on the actual value of the bounce. For the dedicated shadow ray we want to follow the
* main path clamping rules, which subtracts one from the bounds before accumulation. */
INTEGRATOR_STATE_WRITE(
shadow_state, shadow_path, bounce) = INTEGRATOR_STATE(shadow_state, shadow_path, bounce) - 1;
/* No need to update the volume stack as the surface bounce already performed enter-exit check.
*/
const uint32_t shadow_flag = INTEGRATOR_STATE(state, path, flag);
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
/* The diffuse and glossy pass weights are written into the main path as part of the path
* configuration at a surface bounce. */
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, pass_diffuse_weight) = INTEGRATOR_STATE(
state, path, pass_diffuse_weight);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, pass_glossy_weight) = INTEGRATOR_STATE(
state, path, pass_glossy_weight);
}
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, visibility) = INTEGRATOR_STATE(
state, path, visibility);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, flag) = shadow_flag |
PATH_RAY_SHADOW_FOR_LIGHT_LINKING;
# if defined(__PATH_GUIDING__)
if (kernel_data.integrator.train_guiding) {
INTEGRATOR_STATE(shadow_state, shadow_path, guiding_light_linking_mis_weight) = mis_weight;
}
# endif
}
#endif /* __SHADOW_LINKING__ */
ccl_device void integrator_shade_dedicated_light(KernelGlobals kg,
IntegratorState state,
ccl_global float *ccl_restrict /*render_buffer*/)
{
PROFILING_INIT(kg, PROFILING_SHADE_DEDICATED_LIGHT);
#ifdef __SHADOW_LINKING__
shadow_linking_shade(kg, state);
/* Restore self-intersection check primitives in the main state before returning to the
* intersect_closest() state. */
shadow_linking_restore_last_primitives(state);
#else
kernel_assert(!"integrator_intersect_dedicated_light is not supposed to be scheduled");
#endif
integrator_shade_surface_next_kernel<DEVICE_KERNEL_INTEGRATOR_SHADE_DEDICATED_LIGHT>(state);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,249 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/film/light_passes.h"
#include "kernel/integrator/path_state.h"
#include "kernel/integrator/state_flow.h"
#include "kernel/light/light.h"
#include "kernel/light/sample.h"
#include "kernel/geom/object.h"
#include "kernel/types.h"
CCL_NAMESPACE_BEGIN
ccl_device_inline ShaderEvalResult integrate_light_forward(
KernelGlobals kg, IntegratorState state, ccl_global float *ccl_restrict render_buffer)
{
/* Setup light sample. */
Intersection isect ccl_optional_struct_init;
integrator_state_read_isect(state, &isect);
guiding_record_light_surface_segment(kg, state, &isect);
const float3 ray_P = INTEGRATOR_STATE(state, ray, P);
const float3 ray_D = INTEGRATOR_STATE(state, ray, D);
const float ray_time = INTEGRATOR_STATE(state, ray, time);
const PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const float3 N = INTEGRATOR_STATE(state, path, mis_origin_n);
/* Advance ray to new start distance. */
INTEGRATOR_STATE_WRITE(state, ray, tmin) = intersection_t_offset(isect.t);
const LightEval light_eval = light_eval_from_intersection(
kg, &isect, ray_P, ray_D, N, path_flag);
if (light_eval.eval_fac == 0.0f) {
return SHADER_EVAL_EMPTY;
}
/* Use visibility flag to skip lights. */
#ifdef __PASSES__
{
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, isect.prim);
if (!is_light_shader_visible_to_path(klight->shader_id, path_visibility, path_flag)) {
return SHADER_EVAL_EMPTY;
}
}
#endif
/* Evaluate light shader. */
Spectrum shader_eval;
const ShaderEvalResult eval_result = light_sample_shader_eval_forward(
kg, state, isect.prim, ray_P, ray_D, isect.t, ray_time, shader_eval);
if (eval_result == SHADER_EVAL_CACHE_MISS) {
return SHADER_EVAL_CACHE_MISS;
}
const float3 eval = shader_eval * light_eval.eval_fac;
if (is_zero(eval)) {
return SHADER_EVAL_EMPTY;
}
/* MIS weighting. */
const float mis_weight = light_sample_mis_weight_forward_lamp(
kg, state, path_visibility, path_flag, isect.object, light_eval.pdf, ray_P);
/* Write to render buffer. */
guiding_record_surface_emission(kg, state, eval, mis_weight);
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, isect.prim);
film_write_surface_emission(
kg, state, eval, mis_weight, render_buffer, object_lightgroup(kg, klight->object_id));
return SHADER_EVAL_OK;
}
/* Evaluate light shader at intersection in forward path tracing. */
ccl_device void integrator_shade_light_forward(KernelGlobals kg,
IntegratorState state,
ccl_global float *ccl_restrict render_buffer)
{
PROFILING_INIT(kg, PROFILING_SHADE_LIGHT_SETUP);
const ShaderEvalResult result = integrate_light_forward(kg, state, render_buffer);
if (result == SHADER_EVAL_CACHE_MISS) {
integrator_path_cache_miss(state, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_FORWARD);
return;
}
/* TODO: we could get stuck in an infinite loop if there are precision issues
* and the same light is hit again.
*
* As a workaround count this as a transparent bounce. It makes some sense
* to interpret lights as transparent surfaces (and support making them opaque),
* but this needs to be revisited. */
const int transparent_bounce = INTEGRATOR_STATE(state, path, transparent_bounce) + 1;
INTEGRATOR_STATE_WRITE(state, path, transparent_bounce) = transparent_bounce;
if (transparent_bounce >= kernel_data.integrator.transparent_max_bounce) {
integrator_path_terminate(
kg, state, render_buffer, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_FORWARD);
return;
}
integrator_path_next(state,
DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_FORWARD,
DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST);
/* TODO: in some cases we could continue directly to SHADE_BACKGROUND, but
* probably that optimization is probably not practical if we add lights to
* scene geometry. */
}
ccl_device ShaderEvalResult integrate_light_nee(KernelGlobals kg, IntegratorShadowState state)
{
/* Read intersection and ray. */
Ray ray ccl_optional_struct_init;
integrator_state_read_shadow_ray(state, &ray);
integrator_state_read_shadow_ray_self(state, &ray);
Intersection isect = {};
isect.object = ray.self.light_object;
isect.prim = ray.self.light_prim;
isect.type = kernel_data_fetch(objects, isect.object).primitive_type;
isect.t = ray.tmax;
kernel_assert(isect.object != OBJECT_NONE);
kernel_assert(isect.prim != PRIM_NONE);
float3 eval = zero_spectrum();
bool is_background = false;
/* Setup shader data */
ShaderDataCausticsStorage emission_sd_storage;
ccl_private ShaderData *emission_sd = AS_SHADER_DATA(&emission_sd_storage);
PROFILING_INIT_FOR_SHADER(kg, PROFILING_SHADE_LIGHT_SETUP);
if (isect.type == PRIMITIVE_LAMP) {
/* Lights. */
const ccl_global KernelLight *klight = &kernel_data_fetch(lights, isect.prim);
const LightType light_type = LightType(klight->type);
if (light_type == LIGHT_BACKGROUND) {
/* Background light. */
#ifdef __RAY_DIFFERENTIALS__
const float ray_dD = background_light_clamp_dD(kg, ray.dD);
#else
const float ray_dD = 0.0f;
#endif
shader_setup_from_background(kg, emission_sd, ray.P, ray.D, ray_dD, ray.time);
is_background = true;
}
else {
/* Other light types.
* Compute Ng and UV on demand so we don't have to store it in integrator state. */
const float3 P = (ray.tmax == FLT_MAX) ? -ray.D : ray.P + ray.tmax * ray.D;
float3 Ng = zero_float3();
float2 uv = zero_float2();
light_normal_uv_from_position(kg, klight, P, ray.D, Ng, uv);
shader_setup_from_sample(kg,
emission_sd,
P,
Ng,
-ray.D,
klight->shader_id,
isect.object,
isect.prim,
uv.x,
uv.y,
ray.tmax,
ray.time,
false,
true);
}
}
else {
/* Triangles.
* Compute UV on demand so we don't have to store it in integrator state. */
const float2 uv = triangle_light_uv(kg, isect.object, isect.prim, ray.time, ray.P, ray.D);
isect.u = uv.x;
isect.v = uv.y;
shader_setup_from_ray(kg, emission_sd, &ray, &isect);
}
/* Evaluate shader. */
PROFILING_SHADER(emission_sd->object, emission_sd->shader);
PROFILING_EVENT(PROFILING_SHADE_LIGHT_EVAL);
/* No proper path flag, we're evaluating this for all closures. that's
* weak but we'd have to do multiple evaluations otherwise. */
surface_shader_eval<KERNEL_FEATURE_NODE_MASK_SURFACE_LIGHT>(
kg, state, emission_sd, nullptr, PATH_RAY_VISIBILITY_NONE, PATH_RAY_EMISSION);
if (emission_sd->flag & SD_CACHE_MISS) {
return SHADER_EVAL_CACHE_MISS;
}
/* Evaluate emission closures. */
eval = (is_background) ? surface_shader_background(emission_sd) :
surface_shader_emission(emission_sd);
/* Probabilistic light termination.
* Light threshold is only used without light tree. */
if (!(kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_TREE)) {
RNGState rng_state;
shadow_path_state_rng_load(state, &rng_state);
const float rand_terminate = path_state_rng_light_termination(kg, &rng_state);
const float bsdf_eval_average = INTEGRATOR_STATE(state, shadow_path, bsdf_eval_average);
if (light_sample_terminate(kg, eval, bsdf_eval_average, rand_terminate)) {
return SHADER_EVAL_EMPTY;
}
}
else if (is_zero(eval)) {
return SHADER_EVAL_EMPTY;
}
/* Update throughput. */
INTEGRATOR_STATE(state, shadow_path, throughput) *= eval;
return SHADER_EVAL_OK;
}
/* Evaluate light shader for next event estimation, after shade_surface and shade_volume and before
* shadow ray intersection. Only when the light has non-constant emission. */
ccl_device void integrator_shade_light_nee(KernelGlobals kg,
IntegratorShadowState state,
ccl_global float *ccl_restrict /*render_buffer*/)
{
PROFILING_INIT(kg, PROFILING_SHADE_LIGHT_SETUP);
const ShaderEvalResult result = integrate_light_nee(kg, state);
if (result == SHADER_EVAL_CACHE_MISS) {
integrator_shadow_path_cache_miss(state, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_NEE);
}
else if (result == SHADER_EVAL_EMPTY) {
integrator_shadow_path_terminate(state, DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_NEE);
}
else {
integrator_shadow_path_next(state,
DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_NEE,
DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW);
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,298 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/integrator/guiding.h"
#include "kernel/integrator/shade_volume.h"
#include "kernel/integrator/surface_shader.h"
#include "kernel/integrator/volume_stack.h"
#include "kernel/geom/shader_data.h"
#include "kernel/light/light.h"
CCL_NAMESPACE_BEGIN
enum TransparentShadowEvalResult {
TRANSPARENT_SHADOW_EVAL_CONTINUE = 0,
TRANSPARENT_SHADOW_EVAL_OPAQUE = 1,
TRANSPARENT_SHADOW_EVAL_CACHE_MISS = 2,
};
#ifdef __KERNEL_GPU__
/* Assume we can pack num hits into 12 bits, so that we can also store resume hits
* and skip volume in the 16 bit num_hits. */
# define SHADOW_HIT_COUNT_BITS_GPU 12
# define SHADOW_HIT_COUNT_MASK_GPU ((1 << SHADOW_HIT_COUNT_BITS_GPU) - 1)
# define SHADOW_RESUME_BITS_GPU 3
# define SHADOW_RESUME_MASK_GPU ((1 << SHADOW_RESUME_BITS_GPU) - 1)
/* +1 is for the extra loop iteration for the final volume segment. */
static_assert(INTEGRATOR_SHADOW_ISECT_SIZE_GPU + 1 < (1 << SHADOW_RESUME_BITS_GPU),
"INTEGRATOR_SHADOW_ISECT_SIZE_GPU too large for resume_hit bits");
#endif
ccl_device_inline uint shadow_num_hits_get(const uint packed_num_hits)
{
#ifdef __KERNEL_GPU__
return packed_num_hits & SHADOW_HIT_COUNT_MASK_GPU;
#else
return packed_num_hits;
#endif
}
ccl_device_inline uint shadow_resume_hit_get(const uint packed_num_hits)
{
#ifdef __KERNEL_GPU__
return (packed_num_hits >> SHADOW_HIT_COUNT_BITS_GPU) & SHADOW_RESUME_MASK_GPU;
#else
(void)packed_num_hits;
return 0;
#endif
}
ccl_device_inline bool shadow_skip_volume_get(const uint packed_num_hits)
{
#ifdef __KERNEL_GPU__
return (packed_num_hits >> (SHADOW_HIT_COUNT_BITS_GPU + SHADOW_RESUME_BITS_GPU)) & 1;
#else
(void)packed_num_hits;
return false;
#endif
}
ccl_device_inline uint shadow_num_hits_pack(const uint num_hits,
const uint resume_hit,
const bool skip_volume)
{
#ifdef __KERNEL_GPU__
return (num_hits & SHADOW_HIT_COUNT_MASK_GPU) |
((resume_hit & SHADOW_RESUME_MASK_GPU) << SHADOW_HIT_COUNT_BITS_GPU) |
((skip_volume ? 1u : 0u) << (SHADOW_HIT_COUNT_BITS_GPU + SHADOW_RESUME_BITS_GPU));
#else
/* Cache miss resume not supported on CPU. */
kernel_assert(resume_hit == 0 && !skip_volume);
(void)resume_hit;
(void)skip_volume;
return num_hits;
#endif
}
ccl_device_inline bool shadow_intersections_has_remaining(const uint packed_num_hits)
{
return shadow_num_hits_get(packed_num_hits) >= INTEGRATOR_SHADOW_ISECT_SIZE;
}
#ifdef __TRANSPARENT_SHADOWS__
ccl_device_inline Spectrum
integrate_transparent_surface_shadow(KernelGlobals kg,
IntegratorShadowState state,
const int hit,
ccl_private ShaderEvalResult &result)
{
PROFILING_INIT(kg, PROFILING_SHADE_SHADOW_SURFACE);
/* TODO: does aliasing like this break automatic SoA in CUDA?
* Should we instead store closures separate from ShaderData?
*
* TODO: is it better to declare this outside the loop or keep it local
* so the compiler can see there is no dependency between iterations? */
ShaderDataTinyStorage shadow_sd_storage;
ccl_private ShaderData *shadow_sd = AS_SHADER_DATA(&shadow_sd_storage);
/* Setup shader data at surface. */
Intersection isect ccl_optional_struct_init;
integrator_state_read_shadow_isect(state, &isect, hit);
Ray ray ccl_optional_struct_init;
integrator_state_read_shadow_ray(state, &ray);
shader_setup_from_ray(kg, shadow_sd, &ray, &isect);
/* Evaluate shader. */
if (!(shadow_sd->flag & SD_HAS_ONLY_VOLUME)) {
surface_shader_eval<KERNEL_FEATURE_NODE_MASK_SURFACE_SHADOW>(
kg, state, shadow_sd, nullptr, PATH_RAY_VISIBILITY_SHADOW, PATH_RAY_FLAG_NONE);
if (shadow_sd->flag & SD_CACHE_MISS) {
result = SHADER_EVAL_CACHE_MISS;
return zero_spectrum();
}
}
else {
INTEGRATOR_STATE_WRITE(state, shadow_path, volume_bounds_bounce) += 1;
}
# ifdef __VOLUME__
/* Exit/enter volume. */
volume_stack_enter_exit<true>(kg, state, shadow_sd);
# endif
/* Disable transparent shadows for ray portals */
if (shadow_sd->flag & SD_RAY_PORTAL) {
result = SHADER_EVAL_EMPTY;
return zero_spectrum();
}
/* Compute transparency from closures. */
result = SHADER_EVAL_OK;
return surface_shader_transparency(shadow_sd);
}
# ifdef __VOLUME__
ccl_device_inline bool integrate_transparent_volume_shadow(KernelGlobals kg,
IntegratorShadowState state,
const int hit,
const int num_recorded_hits,
ccl_private Spectrum *ccl_restrict
throughput)
{
PROFILING_INIT(kg, PROFILING_SHADE_SHADOW_VOLUME);
/* TODO: deduplicate with surface, or does it not matter for memory usage? */
ShaderDataTinyStorage shadow_sd_storage;
ccl_private ShaderData *shadow_sd = AS_SHADER_DATA(&shadow_sd_storage);
/* Setup shader data. */
Ray ray ccl_optional_struct_init;
integrator_state_read_shadow_ray(state, &ray);
ray.self.object = OBJECT_NONE;
ray.self.prim = PRIM_NONE;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
/* Modify ray position and length to match current segment. */
ray.tmin = (hit == 0) ? ray.tmin : INTEGRATOR_STATE_ARRAY(state, shadow_isect, hit - 1, t);
ray.tmax = (hit < num_recorded_hits) ? INTEGRATOR_STATE_ARRAY(state, shadow_isect, hit, t) :
ray.tmax;
/* `object` is only needed for light tree with light linking, it is irrelevant for shadow. */
shader_setup_from_volume(shadow_sd, &ray, OBJECT_NONE);
if (kernel_data.integrator.volume_ray_marching) {
const float step_size = volume_stack_step_size<true>(kg, state);
volume_shadow_ray_marching(kg, state, &ray, shadow_sd, throughput, step_size);
}
else {
volume_shadow_null_scattering(kg, state, &ray, shadow_sd, throughput);
}
return shadow_sd->flag & SD_CACHE_MISS;
}
# endif
ccl_device_inline TransparentShadowEvalResult integrate_transparent_shadow(
KernelGlobals kg, IntegratorShadowState state, const uint packed_num_hits)
{
/* Accumulate shadow for transparent surfaces. */
const uint num_hits = shadow_num_hits_get(packed_num_hits);
const uint num_recorded_hits = min(num_hits, (uint)INTEGRATOR_SHADOW_ISECT_SIZE);
/* Resume state from previous cache miss. */
const uint resume_hit = shadow_resume_hit_get(packed_num_hits);
const bool resume_skip_volume = shadow_skip_volume_get(packed_num_hits);
/* Plus one to account for world volume, which has no boundary to hit but casts shadows. */
for (uint hit = resume_hit; hit < num_recorded_hits + 1; hit++) {
/* Skip volume if resuming after volume completed but surface had cache miss. */
const bool skip_volume = (hit == resume_hit) && resume_skip_volume;
/* Volume shaders. */
if (!skip_volume &&
(hit < num_recorded_hits || !shadow_intersections_has_remaining(packed_num_hits)))
{
# ifdef __VOLUME__
if (!integrator_state_shadow_volume_stack_is_empty(kg, state)) {
Spectrum throughput = INTEGRATOR_STATE(state, shadow_path, throughput);
const bool cache_miss = integrate_transparent_volume_shadow(
kg, state, hit, num_recorded_hits, &throughput);
if (is_zero(throughput)) {
return TRANSPARENT_SHADOW_EVAL_OPAQUE;
}
if (cache_miss) {
/* Store resume state: restart at this hit, redo volume. */
INTEGRATOR_STATE_WRITE(state, shadow_path, packed_num_hits) = shadow_num_hits_pack(
num_hits, hit, false);
return TRANSPARENT_SHADOW_EVAL_CACHE_MISS;
}
INTEGRATOR_STATE_WRITE(state, shadow_path, throughput) = throughput;
}
# endif
}
/* Surface shaders. */
if (hit < num_recorded_hits) {
ShaderEvalResult result = SHADER_EVAL_EMPTY;
const Spectrum shadow = integrate_transparent_surface_shadow(kg, state, hit, result);
if (result == SHADER_EVAL_CACHE_MISS) {
/* Store resume state: restart at this hit, skip volume. */
INTEGRATOR_STATE_WRITE(state, shadow_path, packed_num_hits) = shadow_num_hits_pack(
num_hits, hit, true);
return TRANSPARENT_SHADOW_EVAL_CACHE_MISS;
}
const Spectrum throughput = INTEGRATOR_STATE(state, shadow_path, throughput) * shadow;
if (is_zero(throughput)) {
return TRANSPARENT_SHADOW_EVAL_OPAQUE;
}
INTEGRATOR_STATE_WRITE(state, shadow_path, throughput) = throughput;
INTEGRATOR_STATE_WRITE(state, shadow_path, transparent_bounce) += 1;
INTEGRATOR_STATE_WRITE(state, shadow_path, rng_offset) += PRNG_BOUNCE_NUM;
}
if (INTEGRATOR_STATE(state, shadow_path, volume_bounds_bounce) > VOLUME_BOUNDS_MAX) {
return TRANSPARENT_SHADOW_EVAL_OPAQUE;
}
/* Note we do not need to check max_transparent_bounce here, the number
* of intersections is already limited and made opaque in the
* INTERSECT_SHADOW kernel. */
}
if (shadow_intersections_has_remaining(packed_num_hits)) {
/* There are more hits that we could not recorded due to memory usage,
* adjust ray to intersect again from the last hit. */
const float last_hit_t = INTEGRATOR_STATE_ARRAY(state, shadow_isect, num_recorded_hits - 1, t);
INTEGRATOR_STATE_WRITE(state, shadow_ray, tmin) = intersection_t_offset(last_hit_t);
}
return TRANSPARENT_SHADOW_EVAL_CONTINUE;
}
#endif /* __TRANSPARENT_SHADOWS__ */
ccl_device void integrator_shade_shadow(KernelGlobals kg,
IntegratorShadowState state,
ccl_global float *ccl_restrict render_buffer)
{
PROFILING_INIT(kg, PROFILING_SHADE_SHADOW_SETUP);
const uint packed_num_hits = INTEGRATOR_STATE(state, shadow_path, packed_num_hits);
#ifdef __TRANSPARENT_SHADOWS__
/* Evaluate transparent shadows. */
const TransparentShadowEvalResult result = integrate_transparent_shadow(
kg, state, packed_num_hits);
if (result == TRANSPARENT_SHADOW_EVAL_CACHE_MISS) {
integrator_shadow_path_cache_miss(state, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW);
return;
}
if (result == TRANSPARENT_SHADOW_EVAL_OPAQUE) {
integrator_shadow_path_terminate(state, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW);
return;
}
#endif
if (shadow_intersections_has_remaining(packed_num_hits)) {
/* More intersections to find, continue shadow ray. */
integrator_shadow_path_next(
state, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW);
return;
}
guiding_record_direct_light(kg, state);
film_write_direct_light(kg, state, render_buffer);
integrator_shadow_path_terminate(state, DEVICE_KERNEL_INTEGRATOR_SHADE_SHADOW);
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,960 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/integrator/path_state.h"
#include "kernel/integrator/surface_shader.h"
#include "kernel/film/data_passes.h"
#include "kernel/film/denoising_passes.h"
#include "kernel/film/light_passes.h"
#include "kernel/light/sample.h"
#include "kernel/geom/motion_triangle.h"
#include "kernel/geom/triangle.h"
#include "kernel/integrator/guiding.h"
#include "kernel/integrator/shadow_linking.h"
#include "kernel/integrator/subsurface.h"
#include "kernel/integrator/volume_stack.h"
#include "kernel/types.h"
#include "util/math_intersect.h"
CCL_NAMESPACE_BEGIN
ccl_device_forceinline void integrate_surface_shader_setup(KernelGlobals kg,
ConstIntegratorState state,
ccl_private ShaderData *sd)
{
Intersection isect ccl_optional_struct_init;
integrator_state_read_isect(state, &isect);
Ray ray ccl_optional_struct_init;
integrator_state_read_ray(state, &ray);
shader_setup_from_ray(kg, sd, &ray, &isect);
}
ccl_device_forceinline float3 integrate_surface_ray_offset(KernelGlobals kg,
const ccl_private ShaderData *sd,
const float3 ray_P,
const float3 ray_D)
{
/* No ray offset needed for other primitive types. */
if (!(sd->type & PRIMITIVE_TRIANGLE)) {
return ray_P;
}
/* Self intersection tests already account for the case where a ray hits the
* same primitive. However precision issues can still cause neighboring
* triangles to be hit. Here we test if the ray-triangle intersection with
* the same primitive would miss, implying that a neighboring triangle would
* be hit instead.
*
* This relies on triangle intersection to be watertight, and the object inverse
* object transform to match the one used by ray intersection exactly.
*
* Potential improvements:
* - It appears this happens when either barycentric coordinates are small,
* or dot(sd->Ng, ray_D) is small. Detect such cases and skip test?
* - Instead of ray offset, can we tweak P to lie within the triangle?
*/
/* TODO: Investigate if there are better ray offsetting algorithms for each BVH.
* Cycles and Custom BVH triangle tests aren't numerically identical, meaning
* this method isn't ideal for them. */
float3 verts[3];
if (sd->type == PRIMITIVE_TRIANGLE) {
triangle_vertices(kg, sd->object, sd->prim, verts);
}
else {
kernel_assert(sd->type == PRIMITIVE_MOTION_TRIANGLE);
motion_triangle_vertices(kg, sd->object, sd->prim, sd->time, verts);
}
float3 local_ray_P = ray_P;
float3 local_ray_D = ray_D;
if (!(sd->object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
const Transform itfm = object_get_inverse_transform(kg, sd);
local_ray_P = transform_point(&itfm, local_ray_P);
local_ray_D = transform_direction(&itfm, local_ray_D);
}
if (ray_triangle_intersect_self(local_ray_P, local_ray_D, verts)) {
return ray_P;
}
return ray_offset(ray_P, sd->Ng);
}
ccl_device_forceinline bool integrate_surface_holdout(KernelGlobals kg,
ConstIntegratorState state,
ccl_private ShaderData *sd,
ccl_global float *ccl_restrict render_buffer)
{
/* Write holdout transparency to render buffer and stop if fully holdout. */
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (((sd->flag & SD_HOLDOUT) || (sd->object_flag & SD_OBJECT_HOLDOUT_MASK)) &&
(path_flag & PATH_RAY_TRANSPARENT_BACKGROUND))
{
const Spectrum holdout_weight = surface_shader_apply_holdout(sd);
const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
const float transparent = average(holdout_weight * throughput);
film_write_holdout(kg, state, path_flag, transparent, render_buffer);
if (isequal(holdout_weight, one_spectrum())) {
return false;
}
}
return true;
}
ccl_device_forceinline void integrate_surface_emission(KernelGlobals kg,
IntegratorState state,
const ccl_private ShaderData *sd,
ccl_global float *ccl_restrict
render_buffer)
{
const PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
#ifdef __LIGHT_LINKING__
if (!(path_visibility & PATH_RAY_VISIBILITY_CAMERA) &&
!light_link_object_match(kg, light_link_receiver_forward(kg, state), sd->object))
{
return;
}
#endif
#ifdef __SHADOW_LINKING__
/* Indirect emission of shadow-linked emissive surfaces is done via shadow rays to dedicated
* light sources. */
if (kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_LINKING) {
if (!(path_visibility & PATH_RAY_VISIBILITY_CAMERA) &&
kernel_data_fetch(objects, sd->object).shadow_set_membership != LIGHT_LINK_MASK_ALL)
{
return;
}
}
#endif
/* Evaluate emissive closure. */
const Spectrum L = surface_shader_emission(sd);
const float mis_weight = light_sample_mis_weight_forward_surface(
kg, state, path_visibility, path_flag, sd);
guiding_record_surface_emission(kg, state, L, mis_weight);
film_write_surface_emission(
kg, state, L, mis_weight, render_buffer, object_lightgroup(kg, sd->object));
}
ccl_device int integrate_surface_ray_portal(KernelGlobals kg,
IntegratorState state,
ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc)
{
const ccl_private RayPortalClosure *pc = (const ccl_private RayPortalClosure *)sc;
float sum_sample_weight = 0.0f;
for (int i = 0; i < sd->num_closure; i++) {
const ccl_private ShaderClosure *sc = &sd->closure[i];
if (CLOSURE_IS_BSDF_OR_BSSRDF(sc->type)) {
sum_sample_weight += sc->sample_weight;
}
}
if (sum_sample_weight <= 0.0f) {
return LABEL_NONE;
}
if (len_squared(sd->P - pc->P) > 1e-9f) {
/* if the ray origin is changed, unset the current object,
* so we can potentially hit the same polygon again */
INTEGRATOR_STATE_WRITE(state, isect, object) = OBJECT_NONE;
INTEGRATOR_STATE_WRITE(state, ray, P) = pc->P;
}
else {
INTEGRATOR_STATE_WRITE(state, ray, P) = integrate_surface_ray_offset(kg, sd, pc->P, pc->D);
}
INTEGRATOR_STATE_WRITE(state, ray, D) = pc->D;
INTEGRATOR_STATE_WRITE(state, ray, tmin) = 0.0f;
INTEGRATOR_STATE_WRITE(state, ray, tmax) = FLT_MAX;
#ifdef __RAY_DIFFERENTIALS__
INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP);
#endif
const float pick_pdf = pc->sample_weight / sum_sample_weight;
INTEGRATOR_STATE_WRITE(state, path, throughput) *= pc->weight / pick_pdf;
const int label = LABEL_TRANSMIT | LABEL_RAY_PORTAL;
path_state_next(kg, state, label, sd->flag);
return label;
}
/* Branch off a shadow path and initialize common part of it.
* THe common is between the surface shading and configuration of a special shadow ray for the
* shadow linking. */
ccl_device_inline IntegratorShadowState
integrate_direct_light_shadow_init_common(KernelGlobals kg,
IntegratorState state,
const ccl_private Ray *ccl_restrict ray,
const Spectrum bsdf_spectrum,
const int light_group,
const int mnee_vertex_count,
const bool constant_light_shader)
{
const DeviceKernel next_kernel = (constant_light_shader) ?
DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW :
DEVICE_KERNEL_INTEGRATOR_SHADE_LIGHT_NEE;
/* Branch off shadow kernel. */
IntegratorShadowState shadow_state;
#ifdef __MNEE__
if (mnee_vertex_count > 0) {
/* Reuse shadow path that was already allocated by intersect_mnee. */
shadow_state = integrator_state_get_mnee_shadow_state(state);
integrator_shadow_path_next(
shadow_state, DEVICE_KERNEL_INTEGRATOR_SHADOW_PATH_MNEE_PENDING, next_kernel);
}
else
#endif
{
shadow_state = integrator_shadow_path_init(kg, state, next_kernel, false);
}
#ifdef __VOLUME__
/* Copy volume stack and enter/exit volume. */
integrator_state_copy_volume_stack_to_shadow(kg, shadow_state, state);
#endif
/* Write shadow ray and associated state to global memory. */
integrator_state_write_shadow_ray(shadow_state, ray);
integrator_state_write_shadow_ray_self(shadow_state, ray);
/* Copy state from main path to shadow path. */
const Spectrum unlit_throughput = INTEGRATOR_STATE(state, path, throughput);
const Spectrum throughput = unlit_throughput * bsdf_spectrum;
if (!(kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_TREE)) {
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, bsdf_eval_average) = average(bsdf_spectrum);
}
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, render_pixel_index) = INTEGRATOR_STATE(
state, path, render_pixel_index);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_offset) = INTEGRATOR_STATE(
state, path, rng_offset);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_pixel) = INTEGRATOR_STATE(
state, path, rng_pixel);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, sample) = INTEGRATOR_STATE(
state, path, sample);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, transparent_bounce) = INTEGRATOR_STATE(
state, path, transparent_bounce);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, volume_bounds_bounce) = INTEGRATOR_STATE(
state, path, volume_bounds_bounce);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, glossy_bounce) = INTEGRATOR_STATE(
state, path, glossy_bounce);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, throughput) = throughput;
if ((kernel_data.kernel_features & KERNEL_FEATURE_NODE_PORTAL)) {
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, portal_bounce) = INTEGRATOR_STATE(
state, path, portal_bounce);
}
#ifdef __MNEE__
if (mnee_vertex_count > 0) {
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, transmission_bounce) =
INTEGRATOR_STATE(state, path, transmission_bounce) + mnee_vertex_count - 1;
INTEGRATOR_STATE_WRITE(shadow_state,
shadow_path,
diffuse_bounce) = INTEGRATOR_STATE(state, path, diffuse_bounce) + 1;
INTEGRATOR_STATE_WRITE(shadow_state,
shadow_path,
bounce) = INTEGRATOR_STATE(state, path, bounce) + mnee_vertex_count;
}
else
#endif
{
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, transmission_bounce) = INTEGRATOR_STATE(
state, path, transmission_bounce);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, diffuse_bounce) = INTEGRATOR_STATE(
state, path, diffuse_bounce);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, bounce) = INTEGRATOR_STATE(
state, path, bounce);
}
/* Write Light-group, +1 as light-group is int but we need to encode into a uint8_t. */
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, lightgroup) = light_group + 1;
#if defined(__PATH_GUIDING__)
if ((kernel_data.kernel_features & KERNEL_FEATURE_PATH_GUIDING)) {
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unlit_throughput) = unlit_throughput;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, path_segment) = INTEGRATOR_STATE(
state, guiding, path_segment);
INTEGRATOR_STATE(shadow_state, shadow_path, guiding_light_linking_mis_weight) = 0.0f;
}
#endif
return shadow_state;
}
/* Path tracing: sample point on light and evaluate light shader, then
* queue shadow ray to be traced. */
template<uint node_feature_mask>
#if defined(__KERNEL_GPU__)
ccl_device_forceinline
#else
/* MSVC has very long compilation time (x20) if we force inline this function */
ccl_device
#endif
ShaderEvalResult
integrate_surface_direct_light(KernelGlobals kg,
IntegratorState state,
ccl_private ShaderData *sd,
const ccl_private RNGState *rng_state)
{
/* Test if there is a light or BSDF that needs direct light. */
if (!(kernel_data.integrator.use_direct_light && (sd->flag & SD_BSDF_HAS_EVAL))) {
return SHADER_EVAL_EMPTY;
}
LightSample ls ccl_optional_struct_init;
int mnee_vertex_count = 0; // NOLINT
#ifdef __MNEE__
if ((kernel_data.kernel_features & KERNEL_FEATURE_MNEE) &&
(INTEGRATOR_STATE(state, path, mnee) & PATH_MNEE_SAMPLED))
{
/* MNEE already sampled a light and caustics casters. */
integrator_state_read_mnee(state, &ls, &mnee_vertex_count);
}
else
#endif
{
/* Sample position on a light. */
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
const uint bounce = INTEGRATOR_STATE(state, path, bounce);
const float3 rand_light = path_state_rng_3D(kg, rng_state, PRNG_LIGHT);
if (!light_sample_from_position(kg,
rand_light,
sd->time,
sd->P,
sd->N,
light_link_receiver_nee(kg, sd),
sd->flag,
bounce,
path_flag,
&ls))
{
return SHADER_EVAL_EMPTY;
}
}
kernel_assert(ls.pdf != 0.0f);
const bool is_transmission = dot(ls.D, sd->N) < 0.0f;
if (ls.prim != PRIM_NONE && ls.prim == sd->prim && ls.object == sd->object) {
/* Skip self intersection if light direction lies in the same hemisphere as the geometric
* normal. */
if (dot(ls.D, is_transmission ? -sd->Ng : sd->Ng) > 0.0f) {
return SHADER_EVAL_EMPTY;
}
}
#ifdef __MNEE__
/* On a caustic caster, a caustic light's contribution is delivered to receivers by
* MNEE and does not need to be computed again here. */
if (kernel_data.kernel_features & KERNEL_FEATURE_MNEE) {
if (mnee_vertex_count == 0 && is_transmission &&
(sd->object_flag & SD_OBJECT_CAUSTICS_CASTER) && ls.type != LIGHT_TRIANGLE &&
kernel_data_fetch(lights, ls.prim).use_caustics)
{
return SHADER_EVAL_EMPTY;
}
}
#endif
/* Evaluate constant part of light shader, rest will optionally be done in another kernel. */
Spectrum light_shader_eval ccl_optional_struct_init;
const bool is_constant_light_shader = light_sample_shader_eval_nee_constant(
kg, ls.shader, ls.prim, ls.type != LIGHT_TRIANGLE, light_shader_eval);
/* Evaluate BSDF. */
BsdfEval bsdf_eval ccl_optional_struct_init;
float avg_roughness_squared = 0.0f;
const float bsdf_pdf = surface_shader_bsdf_eval(
kg, state, sd, ls.D, &bsdf_eval, ls.shader, avg_roughness_squared);
Ray ray ccl_optional_struct_init;
#ifdef __MNEE__
if (mnee_vertex_count > 0) {
light_shader_eval *= integrator_state_read_mnee_throughput(state);
bsdf_eval_mul(&bsdf_eval, light_shader_eval);
if (bsdf_eval_is_zero(&bsdf_eval)) {
return SHADER_EVAL_EMPTY;
}
integrator_state_read_mnee_ray(state, &ls, &ray);
}
else
#endif /* __MNEE__ */
{
const float mis_weight = light_sample_mis_weight_nee(kg, ls.pdf, bsdf_pdf);
bsdf_eval_mul(&bsdf_eval, light_shader_eval * ls.eval_fac / ls.pdf * mis_weight);
/* Path termination for constant light shader. */
if (is_constant_light_shader && !(kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_TREE)) {
const float terminate = path_state_rng_light_termination(kg, rng_state);
if (light_sample_terminate(kg, &bsdf_eval, terminate)) {
return SHADER_EVAL_EMPTY;
}
}
/* For non-constant light shader, probabilistic termination happens in
* SHADE_LIGHT_NEE when the full contribution is known. */
else if (bsdf_eval_is_zero(&bsdf_eval)) {
return SHADER_EVAL_EMPTY;
}
/* Create shadow ray. */
light_sample_to_surface_shadow_ray(kg, sd, &ls, &ray);
#ifdef __RAY_DIFFERENTIALS__
/* Widen ray differences, with same logic as forward sampling to ensure
* both MIS strategies converge to the same result. */
ray.dD = bsdf_widen_dD(INTEGRATOR_STATE(state, ray, dD), avg_roughness_squared);
#endif
}
if (ray.self.object != OBJECT_NONE) {
ray.P = integrate_surface_ray_offset(kg, sd, ray.P, ray.D);
}
/* Branch off shadow kernel. */
IntegratorShadowState shadow_state = integrate_direct_light_shadow_init_common(
kg,
state,
&ray,
bsdf_eval_sum(&bsdf_eval),
ls.group,
mnee_vertex_count,
is_constant_light_shader);
if (is_transmission) {
#ifdef __VOLUME__
volume_stack_enter_exit<true>(kg, shadow_state, sd);
#endif
}
uint32_t shadow_flag = INTEGRATOR_STATE(state, path, flag);
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
PackedSpectrum pass_diffuse_weight;
PackedSpectrum pass_glossy_weight;
if (shadow_flag & PATH_RAY_ANY_PASS) {
/* Indirect bounce, use weights from earlier surface or volume bounce. */
pass_diffuse_weight = INTEGRATOR_STATE(state, path, pass_diffuse_weight);
pass_glossy_weight = INTEGRATOR_STATE(state, path, pass_glossy_weight);
}
else {
/* Direct light, use BSDFs at this bounce. */
shadow_flag |= PATH_RAY_SURFACE_PASS;
pass_diffuse_weight = PackedSpectrum(bsdf_eval_pass_diffuse_weight(&bsdf_eval));
pass_glossy_weight = PackedSpectrum(bsdf_eval_pass_glossy_weight(&bsdf_eval));
}
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, pass_diffuse_weight) = pass_diffuse_weight;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, pass_glossy_weight) = pass_glossy_weight;
}
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, visibility) = INTEGRATOR_STATE(
state, path, visibility);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, flag) = shadow_flag;
return SHADER_EVAL_OK;
}
/* Path tracing: bounce off or through surface with new direction. */
ccl_device_forceinline int integrate_surface_bsdf_bssrdf_bounce(
KernelGlobals kg,
IntegratorState state,
ccl_private ShaderData *sd,
const ccl_private RNGState *rng_state)
{
/* Sample BSDF or BSSRDF. */
if (!(sd->flag & (SD_BSDF | SD_BSSRDF))) {
return LABEL_NONE;
}
float3 rand_bsdf = path_state_rng_3D(kg, rng_state, PRNG_SURFACE_BSDF);
const ccl_private ShaderClosure *sc = surface_shader_bsdf_bssrdf_pick(sd, &rand_bsdf);
#ifdef __SUBSURFACE__
/* BSSRDF closure, we schedule subsurface intersection kernel. */
if (CLOSURE_IS_BSSRDF(sc->type)) {
return subsurface_bounce(kg, state, sd, sc);
}
#endif
if (CLOSURE_IS_RAY_PORTAL(sc->type)) {
return integrate_surface_ray_portal(kg, state, sd, sc);
}
/* BSDF closure, sample direction. */
float bsdf_pdf = 0.0f;
float unguided_bsdf_pdf = 0.0f;
BsdfEval bsdf_eval ccl_optional_struct_init;
float3 bsdf_wo ccl_optional_struct_init;
int label;
float2 bsdf_sampled_roughness = make_float2(1.0f, 1.0f);
float bsdf_eta = 1.0f;
float mis_pdf = 1.0f;
float bsdf_avg_roughness_squared = 0.0f;
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
if (kernel_data.integrator.use_surface_guiding &&
(kernel_data.kernel_features & KERNEL_FEATURE_PATH_GUIDING))
{
label = surface_shader_bsdf_guided_sample_closure(kg,
state,
sd,
sc,
rand_bsdf,
&bsdf_eval,
&bsdf_wo,
&bsdf_pdf,
&mis_pdf,
&unguided_bsdf_pdf,
&bsdf_sampled_roughness,
&bsdf_eta,
rng_state,
bsdf_avg_roughness_squared);
if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) {
return LABEL_NONE;
}
INTEGRATOR_STATE_WRITE(state, path, unguided_throughput) *= bsdf_pdf / unguided_bsdf_pdf;
}
else
#endif
{
label = surface_shader_bsdf_sample_closure(kg,
sd,
sc,
rand_bsdf,
&bsdf_eval,
&bsdf_wo,
&bsdf_pdf,
&bsdf_sampled_roughness,
&bsdf_eta,
bsdf_avg_roughness_squared);
if (bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval)) {
return LABEL_NONE;
}
mis_pdf = bsdf_pdf;
unguided_bsdf_pdf = bsdf_pdf;
}
if (label & LABEL_TRANSPARENT) {
/* Only need to modify start distance for transparent. */
INTEGRATOR_STATE_WRITE(state, ray, tmin) = intersection_t_offset(sd->ray_length);
}
else {
/* Setup ray with changed origin and direction. */
const float3 D = normalize(bsdf_wo);
INTEGRATOR_STATE_WRITE(state, ray, P) = integrate_surface_ray_offset(kg, sd, sd->P, D);
INTEGRATOR_STATE_WRITE(state, ray, D) = D;
INTEGRATOR_STATE_WRITE(state, ray, tmin) = 0.0f;
INTEGRATOR_STATE_WRITE(state, ray, tmax) = FLT_MAX;
#ifdef __RAY_DIFFERENTIALS__
INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP);
/* Widen ray differences, with same logic as NEE sampling to ensure
* both MIS strategies converge to the same result. */
const float dD = bsdf_widen_dD(INTEGRATOR_STATE(state, ray, dD), bsdf_avg_roughness_squared);
INTEGRATOR_STATE_WRITE(state, ray, dD) = dD;
#endif
}
/* Update throughput. */
const Spectrum bsdf_weight = bsdf_eval_sum(&bsdf_eval) / bsdf_pdf;
INTEGRATOR_STATE_WRITE(state, path, throughput) *= bsdf_weight;
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
if (INTEGRATOR_STATE(state, path, bounce) == 0) {
INTEGRATOR_STATE_WRITE(state, path, pass_diffuse_weight) = bsdf_eval_pass_diffuse_weight(
&bsdf_eval);
INTEGRATOR_STATE_WRITE(state, path, pass_glossy_weight) = bsdf_eval_pass_glossy_weight(
&bsdf_eval);
}
}
/* Update path state */
if (!(label & LABEL_TRANSPARENT)) {
const float min_ray_pdf = INTEGRATOR_STATE(state, path, min_ray_pdf);
INTEGRATOR_STATE_WRITE(state, path, mis_ray_pdf) = mis_pdf;
INTEGRATOR_STATE_WRITE(state, path, mis_origin_n) = sd->N;
INTEGRATOR_STATE_WRITE(state, path, min_ray_pdf) = fminf(unguided_bsdf_pdf, min_ray_pdf);
#ifdef __LIGHT_LINKING__
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_LINKING) {
INTEGRATOR_STATE_WRITE(state, path, mis_ray_object) = sd->object;
}
#endif
}
path_state_next(kg, state, label, sd->flag);
guiding_record_surface_bounce(kg,
state,
bsdf_weight,
bsdf_pdf,
sd->N,
normalize(bsdf_wo),
bsdf_sampled_roughness,
bsdf_eta);
return label;
}
#ifdef __VOLUME__
ccl_device_forceinline int integrate_surface_volume_only_bounce(IntegratorState state,
ccl_private ShaderData *sd)
{
if (!path_state_volume_next(state)) {
return LABEL_NONE;
}
/* Only modify start distance. */
INTEGRATOR_STATE_WRITE(state, ray, tmin) = intersection_t_offset(sd->ray_length);
return LABEL_TRANSMIT | LABEL_TRANSPARENT;
}
#endif
ccl_device_forceinline bool integrate_surface_terminate(IntegratorState state,
const uint32_t path_flag)
{
const float continuation_probability = (path_flag & PATH_RAY_TERMINATE_ON_NEXT_SURFACE) ?
0.0f :
INTEGRATOR_STATE(
state, path, continuation_probability);
if (continuation_probability == 0.0f) {
return true;
}
if (continuation_probability != 1.0f) {
INTEGRATOR_STATE_WRITE(state, path, throughput) /= continuation_probability;
}
return false;
}
#if defined(__AO__)
ccl_device_forceinline void integrate_surface_ao(KernelGlobals kg,
IntegratorState state,
const ccl_private ShaderData *ccl_restrict sd,
const ccl_private RNGState *ccl_restrict
rng_state)
{
const PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (!(kernel_data.kernel_features & KERNEL_FEATURE_AO_ADDITIVE) &&
!(path_visibility & PATH_RAY_VISIBILITY_CAMERA))
{
return;
}
/* Skip AO for paths that were split off for shadow catchers to avoid double-counting. */
if (path_flag & PATH_RAY_SHADOW_CATCHER_PASS) {
return;
}
const float2 rand_bsdf = path_state_rng_2D(kg, rng_state, PRNG_SURFACE_BSDF);
float3 ao_N;
const Spectrum ao_weight = surface_shader_ao(
sd, kernel_data.integrator.ao_additive_factor, &ao_N);
float3 ao_D;
float ao_pdf;
sample_cos_hemisphere(ao_N, rand_bsdf, &ao_D, &ao_pdf);
bool skip_self = true;
Ray ray ccl_optional_struct_init;
ray.P = shadow_ray_offset(kg, sd, ao_D, &skip_self);
ray.D = ao_D;
if (skip_self) {
ray.P = integrate_surface_ray_offset(kg, sd, ray.P, ray.D);
}
ray.tmin = 0.0f;
ray.tmax = kernel_data.integrator.ao_bounces_distance;
ray.time = sd->time;
ray.self.object = (skip_self) ? sd->object : OBJECT_NONE;
ray.self.prim = (skip_self) ? sd->prim : PRIM_NONE;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
ray.dP = differential_zero_compact();
ray.dD = differential_zero_compact();
/* Branch off shadow kernel. */
IntegratorShadowState shadow_state = integrator_shadow_path_init(
kg, state, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SHADOW, true);
# ifdef __VOLUME__
/* Copy volume stack and enter/exit volume. */
integrator_state_copy_volume_stack_to_shadow(kg, shadow_state, state);
# endif
/* Write shadow ray and associated state to global memory. */
integrator_state_write_shadow_ray(shadow_state, &ray);
integrator_state_write_shadow_ray_self(shadow_state, &ray);
/* Copy state from main path to shadow path. */
const uint16_t bounce = INTEGRATOR_STATE(state, path, bounce);
const uint16_t transparent_bounce = INTEGRATOR_STATE(state, path, transparent_bounce);
const uint32_t shadow_flag = INTEGRATOR_STATE(state, path, flag) | PATH_RAY_SHADOW_FOR_AO;
const Spectrum throughput = INTEGRATOR_STATE(state, path, throughput) * surface_shader_alpha(sd);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, render_pixel_index) = INTEGRATOR_STATE(
state, path, render_pixel_index);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_offset) = INTEGRATOR_STATE(
state, path, rng_offset);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, rng_pixel) = INTEGRATOR_STATE(
state, path, rng_pixel);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, sample) = INTEGRATOR_STATE(
state, path, sample);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, visibility) = INTEGRATOR_STATE(
state, path, visibility);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, flag) = shadow_flag;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, bounce) = bounce;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, transparent_bounce) = transparent_bounce;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, volume_bounds_bounce) = INTEGRATOR_STATE(
state, path, volume_bounds_bounce);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, throughput) = throughput;
if (kernel_data.kernel_features & KERNEL_FEATURE_AO_ADDITIVE) {
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, unshadowed_throughput) = ao_weight;
}
}
#endif /* defined(__AO__) */
template<uint node_feature_mask>
ccl_device int integrate_surface(KernelGlobals kg,
IntegratorState state,
ccl_global float *ccl_restrict render_buffer)
{
PROFILING_INIT_FOR_SHADER(kg, PROFILING_SHADE_SURFACE_SETUP);
/* Setup shader data. */
ShaderData sd;
integrate_surface_shader_setup(kg, state, &sd);
PROFILING_SHADER(sd.object, sd.shader);
int continue_path_label = 0;
const PathRayVisibility path_visibility = INTEGRATOR_STATE(state, path, visibility);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
/* Skip most work for volume bounding surface. */
#ifdef __VOLUME__
if (!(sd.flag & SD_HAS_ONLY_VOLUME)) {
#endif
#ifdef __SUBSURFACE__
/* Can skip shader evaluation for BSSRDF exit point without bump mapping. */
if (!(path_flag & PATH_RAY_SUBSURFACE) || ((sd.flag & SD_HAS_BSSRDF_BUMP)))
#endif
{
/* Evaluate shader. */
PROFILING_EVENT(PROFILING_SHADE_SURFACE_EVAL);
surface_shader_eval<node_feature_mask>(
kg, state, &sd, render_buffer, path_visibility, path_flag);
}
if (sd.flag & SD_CACHE_MISS) {
return LABEL_CACHE_MISS;
}
/* After shader evaluation, in case of texture cache miss. */
guiding_record_surface_segment(kg, state, &sd);
#ifdef __SUBSURFACE__
if (path_flag & PATH_RAY_SUBSURFACE) {
/* When coming from inside subsurface scattering, setup a diffuse
* closure to perform lighting at the exit point. */
subsurface_shader_data_setup(kg, &sd);
INTEGRATOR_STATE_WRITE(state, path, flag) &= ~PATH_RAY_SUBSURFACE;
}
else
#endif
{
/* Filter closures. */
surface_shader_prepare_closures(kg, state, &sd, path_visibility);
/* Evaluate holdout. */
if (!integrate_surface_holdout(kg, state, &sd, render_buffer)) {
return LABEL_NONE;
}
/* Write emission. */
if (sd.flag & SD_EMISSION) {
integrate_surface_emission(kg, state, &sd, render_buffer);
}
/* Perform path termination. Most paths have already been terminated in
* the intersect_closest kernel, this is just for emission and for dividing
* throughput by the probability at the right moment.
*
* Also ensure we don't do it twice for SSS at both the entry and exit point. */
if (integrate_surface_terminate(state, path_flag)) {
return LABEL_NONE;
}
/* Write render passes. */
#ifdef __PASSES__
PROFILING_EVENT(PROFILING_SHADE_SURFACE_PASSES);
film_write_data_passes(kg, state, &sd, render_buffer);
#endif
#ifdef __DENOISING_FEATURES__
film_write_denoising_features_surface(kg, state, &sd, render_buffer);
#endif
}
/* Load random number state. */
RNGState rng_state;
path_state_rng_load(state, &rng_state);
#if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
if (kernel_data.kernel_features & KERNEL_FEATURE_PATH_GUIDING) {
surface_shader_prepare_guiding(kg, state, &sd, &rng_state);
guiding_write_debug_passes(kg, state, &sd, render_buffer);
}
#endif
/* Direct light. */
PROFILING_EVENT(PROFILING_SHADE_SURFACE_DIRECT_LIGHT);
const ShaderEvalResult result = integrate_surface_direct_light<node_feature_mask>(
kg, state, &sd, &rng_state);
if (result == SHADER_EVAL_CACHE_MISS) {
return LABEL_CACHE_MISS;
}
#if defined(__AO__)
/* Ambient occlusion pass. */
if (kernel_data.kernel_features & KERNEL_FEATURE_AO) {
PROFILING_EVENT(PROFILING_SHADE_SURFACE_AO);
integrate_surface_ao(kg, state, &sd, &rng_state);
}
#endif
PROFILING_EVENT(PROFILING_SHADE_SURFACE_INDIRECT_LIGHT);
continue_path_label = integrate_surface_bsdf_bssrdf_bounce(kg, state, &sd, &rng_state);
#ifdef __VOLUME__
}
else {
if (integrate_surface_terminate(state, path_flag)) {
return LABEL_NONE;
}
# ifdef __DENOISING_FEATURES__
film_write_denoising_features_surface_volume(kg, state, &sd, render_buffer);
# endif
PROFILING_EVENT(PROFILING_SHADE_SURFACE_INDIRECT_LIGHT);
continue_path_label = integrate_surface_volume_only_bounce(state, &sd);
}
if (continue_path_label & LABEL_TRANSMIT) {
/* Enter/Exit volume. */
volume_stack_enter_exit<false>(kg, state, &sd);
}
#endif
return continue_path_label;
}
template<DeviceKernel current_kernel>
ccl_device_forceinline void integrator_shade_surface_next_kernel(IntegratorState state)
{
if (INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SUBSURFACE) {
integrator_path_next(state, current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE);
}
else {
kernel_assert(INTEGRATOR_STATE(state, ray, tmax) != 0.0f);
integrator_path_next(state, current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_CLOSEST);
}
}
template<uint node_feature_mask = KERNEL_FEATURE_NODE_MASK_SURFACE & ~KERNEL_FEATURE_NODE_RAYTRACE,
DeviceKernel current_kernel = DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE>
ccl_device_forceinline void integrator_shade_surface(KernelGlobals kg,
IntegratorState state,
ccl_global float *ccl_restrict render_buffer)
{
const int continue_path_label = integrate_surface<node_feature_mask>(kg, state, render_buffer);
if (continue_path_label == LABEL_CACHE_MISS) {
integrator_path_cache_miss_sorted(state, current_kernel);
return;
}
#ifdef __MNEE__
/* Cleanup MNEE flag and shadow path if it was not reused for shadow trace. */
if ((kernel_data.kernel_features & KERNEL_FEATURE_MNEE) &&
(INTEGRATOR_STATE(state, path, mnee) & PATH_MNEE_SAMPLED))
{
INTEGRATOR_STATE_WRITE(state, path, mnee) &= ~PATH_MNEE_SAMPLED;
const IntegratorShadowState shadow_state = integrator_state_get_mnee_shadow_state(state);
if (INTEGRATOR_STATE(shadow_state, shadow_path, queued_kernel) ==
DEVICE_KERNEL_INTEGRATOR_SHADOW_PATH_MNEE_PENDING)
{
integrator_shadow_path_terminate(shadow_state,
DEVICE_KERNEL_INTEGRATOR_SHADOW_PATH_MNEE_PENDING);
}
}
#endif
if (continue_path_label == LABEL_NONE) {
integrator_path_terminate(kg, state, render_buffer, current_kernel);
return;
}
#ifdef __SHADOW_LINKING__
/* No need to cast shadow linking rays at a transparent bounce: the lights will be accumulated
* via the main path in this case. BSSRDF bounces continue with intersect_subsurface. */
if ((continue_path_label & (LABEL_TRANSPARENT | LABEL_SUBSURFACE_SCATTER)) == 0) {
if (shadow_linking_schedule_intersection_kernel<current_kernel>(kg, state)) {
return;
}
}
#endif
integrator_shade_surface_next_kernel<current_kernel>(state);
}
ccl_device_forceinline void integrator_shade_surface_raytrace(
KernelGlobals kg, IntegratorState state, ccl_global float *ccl_restrict render_buffer)
{
integrator_shade_surface<KERNEL_FEATURE_NODE_MASK_SURFACE,
DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE>(
kg, state, render_buffer);
}
CCL_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/integrator/state_flow.h"
CCL_NAMESPACE_BEGIN
/* Check whether current surface bounce is where path is to be split for the shadow catcher. */
ccl_device_inline bool kernel_shadow_catcher_is_path_split_bounce(KernelGlobals kg,
IntegratorState state,
const uint object_flag)
{
#ifdef __SHADOW_CATCHER__
if (!kernel_data.integrator.has_shadow_catcher) {
return false;
}
/* Check the flag first, avoiding fetches form global memory. */
if ((object_flag & SD_OBJECT_SHADOW_CATCHER) == 0) {
return false;
}
if (object_flag & SD_OBJECT_HOLDOUT_MASK) {
return false;
}
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if ((path_flag & PATH_RAY_TRANSPARENT_BACKGROUND) == 0) {
/* Split only on primary rays, secondary bounces are to treat shadow catcher as a regular
* object. */
return false;
}
if (path_flag & PATH_RAY_SHADOW_CATCHER_HIT) {
return false;
}
return true;
#else
(void)object_flag;
return false;
#endif
}
/* Check whether the current path can still split. */
ccl_device_inline bool kernel_shadow_catcher_path_can_split(ConstIntegratorState state)
{
if (integrator_path_is_terminated(state)) {
return false;
}
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (path_flag & PATH_RAY_SHADOW_CATCHER_HIT) {
/* Shadow catcher was already hit and the state was split. No further split is allowed. */
return false;
}
return (path_flag & PATH_RAY_TRANSPARENT_BACKGROUND) != 0;
}
#ifdef __SHADOW_CATCHER__
ccl_device_forceinline bool kernel_shadow_catcher_is_matte_path(const uint32_t path_flag)
{
return (path_flag & PATH_RAY_SHADOW_CATCHER_HIT) == 0;
}
ccl_device_forceinline bool kernel_shadow_catcher_is_object_pass(const uint32_t path_flag)
{
return path_flag & PATH_RAY_SHADOW_CATCHER_PASS;
}
#endif /* __SHADOW_CATCHER__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,69 @@
/* SPDX-FileCopyrightText: 2011-2023 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/integrator/state_flow.h"
CCL_NAMESPACE_BEGIN
#ifdef __SHADOW_LINKING__
/* Check whether special shadow rays for shadow linking are needed in the current scene
* configuration. */
ccl_device_forceinline bool shadow_linking_scene_need_shadow_ray(KernelGlobals kg)
{
if (!(kernel_data.kernel_features & KERNEL_FEATURE_SHADOW_LINKING)) {
/* No shadow linking in the scene, so no need to trace any extra rays. */
return false;
}
/* The distant lights might be using shadow linking, and they are not counted as
* kernel_data.integrator.use_light_mis.
* So there is a potential to avoid extra rays from being traced, but it requires more granular
* flags set in the integrator. */
return true;
}
/* Shadow linking re-used the main path intersection to store information about the light to which
* the extra ray is to be traced (this intersection communicates light between the shadow blocker
* intersection and shading kernels).
* These utilities makes a copy of the fields from the main intersection which are needed by the
* intersect_closest kernel after the surface bounce. */
ccl_device_forceinline void shadow_linking_store_last_primitives(IntegratorState state)
{
INTEGRATOR_STATE_WRITE(state, shadow_link, last_isect_prim) = INTEGRATOR_STATE(
state, isect, prim);
INTEGRATOR_STATE_WRITE(state, shadow_link, last_isect_object) = INTEGRATOR_STATE(
state, isect, object);
}
ccl_device_forceinline void shadow_linking_restore_last_primitives(IntegratorState state)
{
INTEGRATOR_STATE_WRITE(state, isect, prim) = INTEGRATOR_STATE(
state, shadow_link, last_isect_prim);
INTEGRATOR_STATE_WRITE(state, isect, object) = INTEGRATOR_STATE(
state, shadow_link, last_isect_object);
}
/* Schedule shadow linking intersection kernel if it is needed.
* Returns true if the shadow linking specific kernel has been scheduled, false otherwise. */
template<DeviceKernel current_kernel>
ccl_device_inline bool shadow_linking_schedule_intersection_kernel(KernelGlobals kg,
IntegratorState state)
{
if (!shadow_linking_scene_need_shadow_ray(kg)) {
return false;
}
integrator_path_next(state, current_kernel, DEVICE_KERNEL_INTEGRATOR_INTERSECT_DEDICATED_LIGHT);
return true;
}
#endif /* __SHADOW_LINKING__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,112 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/********************************* Shadow Path State **************************/
KERNEL_STRUCT_BEGIN(shadow_path)
/* Index of a pixel within the device render buffer. */
KERNEL_STRUCT_MEMBER(shadow_path, uint32_t, render_pixel_index, KERNEL_FEATURE_PATH_TRACING)
/* Current sample number. */
KERNEL_STRUCT_MEMBER(shadow_path, uint32_t, sample, KERNEL_FEATURE_PATH_TRACING)
/* Random number generator per-pixel info. */
KERNEL_STRUCT_MEMBER(shadow_path, uint32_t, rng_pixel, KERNEL_FEATURE_PATH_TRACING)
/* Random number dimension offset. */
KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, rng_offset, KERNEL_FEATURE_PATH_TRACING)
/* Current ray bounce depth. */
KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current transparent ray bounce depth. */
KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, transparent_bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current diffuse ray bounce depth. */
KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, diffuse_bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current glossy ray bounce depth. */
KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, glossy_bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current transmission ray bounce depth. */
KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, transmission_bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current volume bounds ray bounce depth. */
KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, volume_bounds_bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current portal ray bounce depth. */
KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, portal_bounce, KERNEL_FEATURE_NODE_PORTAL)
/* DeviceKernel bit indicating queued kernels. */
KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, queued_kernel, KERNEL_FEATURE_PATH_TRACING)
/* enum PathRayVisibilityFlag, limited to bits from PATH_RAY_VISIBILITY_ALL */
KERNEL_STRUCT_MEMBER(shadow_path, uint8_t, visibility, KERNEL_FEATURE_PATH_TRACING)
/* enum PathRayFlag */
KERNEL_STRUCT_MEMBER(shadow_path, uint32_t, flag, KERNEL_FEATURE_PATH_TRACING)
/* Throughput. */
KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, throughput, KERNEL_FEATURE_PATH_TRACING)
/* Throughput for shadow pass. */
KERNEL_STRUCT_MEMBER(shadow_path,
PackedSpectrum,
unshadowed_throughput,
KERNEL_FEATURE_AO_ADDITIVE)
/* Ratio of throughput to distinguish diffuse / glossy / transmission render passes. */
KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, pass_diffuse_weight, KERNEL_FEATURE_LIGHT_PASSES)
KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, pass_glossy_weight, KERNEL_FEATURE_LIGHT_PASSES)
/* Packed number of intersections found by ray-tracing, and on GPU also the resume hit index
* and skip_volume flag for cache miss handling.
* Note that this is the total number of intersections for the shadow ray.
* The number of recorded intersections in the shadow_isect array might be different as it contains
* up INTEGRATOR_SHADOW_ISECT_SIZE closest intersections. */
KERNEL_STRUCT_MEMBER(shadow_path, uint16_t, packed_num_hits, KERNEL_FEATURE_PATH_TRACING)
/* Light group. */
KERNEL_STRUCT_MEMBER(shadow_path, uint8_t, lightgroup, KERNEL_FEATURE_PATH_TRACING)
/* Path guiding. */
KERNEL_STRUCT_MEMBER(shadow_path, PackedSpectrum, unlit_throughput, KERNEL_FEATURE_PATH_GUIDING)
#if defined(__PATH_GUIDING__)
KERNEL_STRUCT_MEMBER(shadow_path,
openpgl::cpp::PathSegment *,
path_segment,
KERNEL_FEATURE_PATH_GUIDING)
#else
KERNEL_STRUCT_MEMBER(shadow_path, uint64_t, path_segment, KERNEL_FEATURE_PATH_GUIDING)
#endif
KERNEL_STRUCT_MEMBER(shadow_path,
float,
guiding_light_linking_mis_weight,
KERNEL_FEATURE_PATH_GUIDING)
/* Only need when path tracing without the light tree. Stored as a single float to save
* space, as we do not expect to make it a big difference. */
KERNEL_STRUCT_MEMBER(shadow_path,
float,
bsdf_eval_average,
KernelFeatureRequest(KERNEL_FEATURE_PATH_TRACING, KERNEL_FEATURE_LIGHT_TREE))
KERNEL_STRUCT_END(shadow_path)
/********************************** Shadow Ray *******************************/
KERNEL_STRUCT_BEGIN_PACKED(shadow_ray, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(shadow_ray, packed_float3, P, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(shadow_ray, packed_float3, D, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(shadow_ray, float, tmin, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(shadow_ray, float, tmax, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(shadow_ray, float, time, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(shadow_ray, float, dP, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(shadow_ray, float, dD, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(shadow_ray, int, self_light_object, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(shadow_ray, int, self_light_prim, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_END(shadow_ray)
/*********************** Shadow Intersection result **************************/
/* Result from scene intersection.
* It contains INTEGRATOR_SHADOW_ISECT_SIZE closest intersections of the shadow ray. */
KERNEL_STRUCT_BEGIN(shadow_isect)
KERNEL_STRUCT_ARRAY_MEMBER(shadow_isect, float, t, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_ARRAY_MEMBER(shadow_isect, float, u, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_ARRAY_MEMBER(shadow_isect, float, v, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_ARRAY_MEMBER(shadow_isect, int, prim, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_ARRAY_MEMBER(shadow_isect, int, object, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_ARRAY_MEMBER(shadow_isect, int, type, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_END_ARRAY(shadow_isect,
INTEGRATOR_SHADOW_ISECT_SIZE_CPU,
INTEGRATOR_SHADOW_ISECT_SIZE_GPU)
/**************************** Shadow Volume Stack *****************************/
KERNEL_STRUCT_BEGIN(shadow_volume_stack)
KERNEL_STRUCT_ARRAY_MEMBER(shadow_volume_stack, int, object, KERNEL_FEATURE_VOLUME)
KERNEL_STRUCT_ARRAY_MEMBER(shadow_volume_stack, int, shader, KERNEL_FEATURE_VOLUME)
KERNEL_STRUCT_END_ARRAY(shadow_volume_stack,
KERNEL_STRUCT_VOLUME_STACK_SIZE,
KERNEL_STRUCT_VOLUME_STACK_SIZE)

View File

@@ -0,0 +1,286 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Integrator State
*
* This file defines the data structures that define the state of a path. Any state that is
* preserved and passed between kernel executions is part of this.
*
* The size of this state must be kept as small as possible, to reduce cache misses and keep memory
* usage under control on GPUs that may execute millions of kernels.
*
* Memory may be allocated and passed along in different ways depending on the device. There may
* be a scalar layout, or AoS or SoA layout for batches. The state may be passed along as a pointer
* to every kernel, or the pointer may exist at program scope or in constant memory. To abstract
* these differences between devices and experiment with different layouts, macros are used.
*
* Use IntegratorState to pass a reference to the integrator state for the current path. These are
* defined differently on the CPU and GPU. Use ConstIntegratorState instead of const
* IntegratorState for passing state as read-only, to avoid oddities in typedef behavior.
*
* INTEGRATOR_STATE(state, x, y): read nested struct member x.y of IntegratorState
* INTEGRATOR_STATE_WRITE(state, x, y): write to nested struct member x.y of IntegratorState
*
* INTEGRATOR_STATE_ARRAY(state, x, index, y): read x[index].y
* INTEGRATOR_STATE_ARRAY_WRITE(state, x, index, y): write x[index].y
*/
#include "kernel/types.h"
#include "util/types.h"
#if defined(__PATH_GUIDING__)
# include "util/guiding.h" // IWYU pragma: keep
#endif
#pragma once
CCL_NAMESPACE_BEGIN
/* Data structures */
/* Integrator State
*
* CPU rendering path state with AoS layout. */
struct IntegratorShadowStateCPU {
#define KERNEL_STRUCT_BEGIN(name) struct {
#define KERNEL_STRUCT_BEGIN_PACKED(parent_struct, feature) struct {
#define KERNEL_STRUCT_MEMBER(parent_struct, type, name, feature) type name;
#define KERNEL_STRUCT_MEMBER_PACKED KERNEL_STRUCT_MEMBER
#define KERNEL_STRUCT_ARRAY_MEMBER KERNEL_STRUCT_MEMBER
#define KERNEL_STRUCT_END(name) \
} \
name;
#define KERNEL_STRUCT_END_ARRAY(name, cpu_size, gpu_size) \
} \
name[cpu_size];
#define KERNEL_STRUCT_VOLUME_STACK_SIZE MAX_VOLUME_STACK_SIZE
#include "kernel/integrator/shadow_state_template.h"
#undef KERNEL_STRUCT_BEGIN
#undef KERNEL_STRUCT_BEGIN_PACKED
#undef KERNEL_STRUCT_MEMBER
#undef KERNEL_STRUCT_MEMBER_PACKED
#undef KERNEL_STRUCT_ARRAY_MEMBER
#undef KERNEL_STRUCT_END
#undef KERNEL_STRUCT_END_ARRAY
};
struct IntegratorStateCPU {
#define KERNEL_STRUCT_BEGIN(name) struct {
#define KERNEL_STRUCT_BEGIN_PACKED(parent_struct, feature) struct {
#define KERNEL_STRUCT_MEMBER(parent_struct, type, name, feature) type name;
#define KERNEL_STRUCT_MEMBER_PACKED KERNEL_STRUCT_MEMBER
#define KERNEL_STRUCT_ARRAY_MEMBER KERNEL_STRUCT_MEMBER
#define KERNEL_STRUCT_END(name) \
} \
name;
#define KERNEL_STRUCT_END_ARRAY(name, cpu_size, gpu_size) \
} \
name[cpu_size];
#define KERNEL_STRUCT_VOLUME_STACK_SIZE MAX_VOLUME_STACK_SIZE
#include "kernel/integrator/state_template.h"
#undef KERNEL_STRUCT_BEGIN
#undef KERNEL_STRUCT_BEGIN_PACKED
#undef KERNEL_STRUCT_MEMBER
#undef KERNEL_STRUCT_MEMBER_PACKED
#undef KERNEL_STRUCT_ARRAY_MEMBER
#undef KERNEL_STRUCT_END
#undef KERNEL_STRUCT_END_ARRAY
#undef KERNEL_STRUCT_VOLUME_STACK_SIZE
IntegratorShadowStateCPU shadow;
IntegratorShadowStateCPU ao;
};
/* Path Queue
*
* Keep track of which kernels are queued to be executed next in the path
* for GPU rendering. */
struct IntegratorQueueCounter {
int num_queued[DEVICE_GPU_KERNEL_INTEGRATOR_NUM];
int cache_miss;
};
#if defined(__INTEGRATOR_GPU_PACKED_STATE__) && defined(__KERNEL_GPU__)
/* Generate wrapper structs for all integrator state fields. This allows us to access state
* uniformly, regardless of whether it stored in a packed struct or separate arrays. */
# define KERNEL_STRUCT_BEGIN(name)
# define KERNEL_STRUCT_MEMBER(parent_struct, type, name, feature) \
struct Wrapped_##parent_struct##_##name { \
type name; \
};
# define KERNEL_STRUCT_MEMBER_PACKED KERNEL_STRUCT_MEMBER
# define KERNEL_STRUCT_BEGIN_PACKED(parent_struct, feature) \
KERNEL_STRUCT_BEGIN(parent_struct) \
KERNEL_STRUCT_MEMBER(parent_struct, packed_##parent_struct, packed, feature)
# define KERNEL_STRUCT_ARRAY_MEMBER KERNEL_STRUCT_MEMBER
# define KERNEL_STRUCT_END(name)
# define KERNEL_STRUCT_END_ARRAY(name, cpu_size, gpu_size)
# define KERNEL_STRUCT_VOLUME_STACK_SIZE MAX_VOLUME_STACK_SIZE
# include "kernel/integrator/shadow_state_template.h"
# include "kernel/integrator/state_template.h"
# undef KERNEL_STRUCT_BEGIN
# undef KERNEL_STRUCT_BEGIN_PACKED
# undef KERNEL_STRUCT_MEMBER
# undef KERNEL_STRUCT_MEMBER_PACKED
# undef KERNEL_STRUCT_ARRAY_MEMBER
# undef KERNEL_STRUCT_END
# undef KERNEL_STRUCT_END_ARRAY
# undef KERNEL_STRUCT_VOLUME_STACK_SIZE
#endif
/* Integrator State GPU
*
* GPU rendering path state with SoA layout. */
struct IntegratorStateGPU {
#define KERNEL_STRUCT_BEGIN(name) struct {
#ifdef __INTEGRATOR_GPU_PACKED_STATE__
# ifdef __KERNEL_GPU__
/* If we've opted in to packed layouts, generate member functions that return a pointer to a
* wrapper type so we can access state using uniform syntax. */
# define KERNEL_STRUCT_MEMBER(parent_struct, type, name, feature) \
ccl_global Wrapped_##parent_struct##_##name *name; \
ccl_device_inline ccl_global Wrapped_##parent_struct##_##name *name##_fn() ccl_constant \
{ \
return (ccl_global Wrapped_##parent_struct##_##name *)name; \
}
# define KERNEL_STRUCT_MEMBER_PACKED(parent_struct, type, name, feature) \
ccl_device_inline ccl_global packed_##parent_struct *name##_fn() ccl_constant \
{ \
return (ccl_global packed_##parent_struct *)packed; \
}
# else
# define KERNEL_STRUCT_MEMBER(parent_struct, type, name, feature) ccl_global type *name;
# define KERNEL_STRUCT_MEMBER_PACKED(parent_struct, type, name, feature)
# endif
# define KERNEL_STRUCT_BEGIN_PACKED(parent_struct, feature) \
KERNEL_STRUCT_BEGIN(parent_struct) \
KERNEL_STRUCT_MEMBER(parent_struct, packed_##parent_struct, packed, feature)
#else
# define KERNEL_STRUCT_MEMBER(parent_struct, type, name, feature) ccl_global type *name;
# define KERNEL_STRUCT_MEMBER_PACKED KERNEL_STRUCT_MEMBER
# define KERNEL_STRUCT_BEGIN_PACKED(parent_struct, feature) KERNEL_STRUCT_BEGIN(parent_struct)
#endif
#define KERNEL_STRUCT_ARRAY_MEMBER KERNEL_STRUCT_MEMBER
#define KERNEL_STRUCT_END(name) \
} \
name;
#define KERNEL_STRUCT_END_ARRAY(name, cpu_size, gpu_size) \
} \
name[gpu_size];
#define KERNEL_STRUCT_VOLUME_STACK_SIZE MAX_VOLUME_STACK_SIZE
#include "kernel/integrator/state_template.h"
#include "kernel/integrator/shadow_state_template.h"
#undef KERNEL_STRUCT_BEGIN
#undef KERNEL_STRUCT_BEGIN_PACKED
#undef KERNEL_STRUCT_MEMBER
#undef KERNEL_STRUCT_MEMBER_PACKED
#undef KERNEL_STRUCT_ARRAY_MEMBER
#undef KERNEL_STRUCT_END
#undef KERNEL_STRUCT_END_ARRAY
#undef KERNEL_STRUCT_VOLUME_STACK_SIZE
/* Count number of queued kernels. */
ccl_global IntegratorQueueCounter *queue_counter;
/* Count number of kernels queued for specific shaders. */
ccl_global int *sort_key_counter[DEVICE_GPU_KERNEL_INTEGRATOR_NUM];
/* Index of shadow path which will be used by a next shadow path. */
ccl_global int *next_shadow_path_index;
/* Index of main path which will be used by a next shadow catcher split. */
ccl_global int *next_main_path_index;
/* Partition/key offsets used when writing sorted active indices. */
ccl_global int *sort_partition_key_offsets;
/* Divisor used to partition active indices by locality when sorting by material. */
uint sort_partition_divisor;
};
/* Abstraction
*
* Macros to access data structures on different devices.
*
* Note that there is a special access function for the shadow catcher state. This access is to
* happen from a kernel which operates on a "main" path. Attempt to use shadow catcher accessors
* from a kernel which operates on a shadow catcher state will cause bad memory access. */
#ifndef __KERNEL_GPU__
/* Scalar access on CPU. */
using IntegratorState = IntegratorStateCPU *;
using ConstIntegratorState = const IntegratorStateCPU *;
using IntegratorShadowState = IntegratorShadowStateCPU *;
using ConstIntegratorShadowState = const IntegratorShadowStateCPU *;
struct IntegratorBakeState {};
using ConstIntegratorBakeState = IntegratorBakeState;
# define INTEGRATOR_STATE(state, nested_struct, member) ((state)->nested_struct.member)
# define INTEGRATOR_STATE_WRITE(state, nested_struct, member) ((state)->nested_struct.member)
# define INTEGRATOR_STATE_ARRAY(state, nested_struct, array_index, member) \
((state)->nested_struct[array_index].member)
# define INTEGRATOR_STATE_ARRAY_WRITE(state, nested_struct, array_index, member) \
((state)->nested_struct[array_index].member)
#else /* !__KERNEL_GPU__ */
/* Array access on GPU with Structure-of-Arrays. */
using IntegratorState = int;
using ConstIntegratorState = int;
/* Shadow state is wrapped in a struct to support function overloading and templates. */
struct IntegratorShadowState {
ccl_device_inline_method IntegratorShadowState() {}
ccl_device_inline_method IntegratorShadowState(int state) : state(state) {}
ccl_device_inline_method operator int() const
{
return state;
}
int state;
};
using ConstIntegratorShadowState = IntegratorShadowState;
struct IntegratorBakeState {};
using ConstIntegratorBakeState = IntegratorBakeState;
# ifdef __INTEGRATOR_GPU_PACKED_STATE__
/* If we've opted in to packed layouts, we use the generated accessor functions (member##_fn) to
* resolve different layouts (packed vs separate). */
# define INTEGRATOR_STATE(state, nested_struct, member) \
kernel_integrator_state.nested_struct.member##_fn()[state].member
# define INTEGRATOR_STATE_ARRAY(state, nested_struct, array_index, member) \
kernel_integrator_state.nested_struct[array_index].member##_fn()[state].member
# else
# define INTEGRATOR_STATE(state, nested_struct, member) \
kernel_integrator_state.nested_struct.member[state]
# define INTEGRATOR_STATE_ARRAY(state, nested_struct, array_index, member) \
kernel_integrator_state.nested_struct[array_index].member[state]
# endif
# define INTEGRATOR_STATE_WRITE(state, nested_struct, member) \
INTEGRATOR_STATE(state, nested_struct, member)
# define INTEGRATOR_STATE_ARRAY_WRITE(state, nested_struct, array_index, member) \
INTEGRATOR_STATE_ARRAY(state, nested_struct, array_index, member)
#endif /* !__KERNEL_GPU__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,304 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/types.h"
#include "kernel/film/write.h"
#include "kernel/integrator/state.h"
#ifdef __KERNEL_GPU__
# include "util/atomic.h"
#endif
CCL_NAMESPACE_BEGIN
/* Control Flow
*
* Utilities for control flow between kernels. The implementation is different between CPU and
* GPU devices. For the latter part of the logic is handled on the host side with wavefronts.
*
* There is a main path for regular path tracing camera for path tracing. Shadows for next
* event estimation branch off from this into their own path, that may be computed in
* parallel while the main path continues. Additionally, shading kernels are sorted using
* a key for coherence.
*
* Each kernel on the main path must call one of these functions. These may not be called
* multiple times from the same kernel.
*
* integrator_path_init(state, next_kernel)
* integrator_path_next(state, current_kernel, next_kernel)
* integrator_path_terminate(state, current_kernel)
*
* For the shadow path similar functions are used, and again each shadow kernel must call
* one of them, and only once.
*/
ccl_device_forceinline bool integrator_path_is_terminated(ConstIntegratorState state)
{
return INTEGRATOR_STATE(state, path, queued_kernel) == 0;
}
ccl_device_forceinline bool integrator_shadow_path_is_terminated(ConstIntegratorShadowState state)
{
return INTEGRATOR_STATE(state, shadow_path, queued_kernel) == 0;
}
ccl_device_inline void write_optical_depth(KernelGlobals kg,
IntegratorState state,
ccl_global float *ccl_restrict render_buffer)
{
if (!render_buffer) {
return;
}
if (INTEGRATOR_STATE(state, path, flag) & PATH_RAY_VOLUME_PRIMARY_TRANSMIT) {
kernel_assert(kernel_data.film.pass_volume_majorant != PASS_UNUSED);
const float optical_depth = INTEGRATOR_STATE(state, path, optical_depth);
ccl_global float *buffer = film_pass_pixel_render_buffer(kg, state, render_buffer);
film_write_pass_float(buffer + kernel_data.film.pass_volume_majorant, optical_depth);
film_write_pass_float(buffer + kernel_data.film.pass_volume_majorant_sample_count, 1.0f);
}
}
#ifdef __KERNEL_GPU__
ccl_device_forceinline void integrator_path_init(IntegratorState state,
const DeviceKernel next_kernel)
{
atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], 1);
INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel;
}
ccl_device_forceinline void integrator_path_next(IntegratorState state,
const DeviceKernel current_kernel,
const DeviceKernel next_kernel)
{
atomic_fetch_and_sub_uint32(&kernel_integrator_state.queue_counter->num_queued[current_kernel],
1);
atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], 1);
INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel;
}
ccl_device_forceinline void integrator_path_terminate(KernelGlobals kg,
IntegratorState state,
ccl_global float *ccl_restrict render_buffer,
const DeviceKernel current_kernel)
{
write_optical_depth(kg, state, render_buffer);
atomic_fetch_and_sub_uint32(&kernel_integrator_state.queue_counter->num_queued[current_kernel],
1);
INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = 0;
}
ccl_device_forceinline void integrator_path_cache_miss(IntegratorState state,
const DeviceKernel /*current_kernel*/)
{
/* Queued kernel and counter is unmodified, so it will be re-executed. */
kernel_integrator_state.queue_counter->cache_miss = true;
}
ccl_device_forceinline void integrator_path_cache_miss_sorted(IntegratorState state,
const DeviceKernel current_kernel)
{
/* Queued kernel and counter is unmodified, so it will be re-executed. */
kernel_integrator_state.queue_counter->cache_miss = true;
# if !defined(__KERNEL_LOCAL_ATOMIC_SORT__)
const int key_ = INTEGRATOR_STATE_WRITE(state, path, shader_sort_key);
atomic_fetch_and_add_uint32(&kernel_integrator_state.sort_key_counter[current_kernel][key_], 1);
# endif
}
ccl_device_forceinline IntegratorShadowState integrator_shadow_path_init(
KernelGlobals kg, IntegratorState state, const DeviceKernel next_kernel, const bool is_ao)
{
IntegratorShadowState shadow_state = atomic_fetch_and_add_uint32(
&kernel_integrator_state.next_shadow_path_index[0], 1);
atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], 1);
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, queued_kernel) = next_kernel;
# if defined(__PATH_GUIDING__)
if ((kernel_data.kernel_features & KERNEL_FEATURE_PATH_GUIDING)) {
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, path_segment) = nullptr;
}
# endif
return shadow_state;
}
ccl_device_forceinline void integrator_shadow_path_next(IntegratorShadowState state,
const DeviceKernel current_kernel,
const DeviceKernel next_kernel)
{
atomic_fetch_and_sub_uint32(&kernel_integrator_state.queue_counter->num_queued[current_kernel],
1);
atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], 1);
INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = next_kernel;
}
ccl_device_forceinline void integrator_shadow_path_terminate(IntegratorShadowState state,
const DeviceKernel current_kernel)
{
atomic_fetch_and_sub_uint32(&kernel_integrator_state.queue_counter->num_queued[current_kernel],
1);
INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = 0;
}
ccl_device_forceinline void integrator_shadow_path_cache_miss(
IntegratorShadowState state, const DeviceKernel /*current_kernel*/)
{
/* Queued kernel and counter is unmodified, so it will be re-executed. */
kernel_integrator_state.queue_counter->cache_miss = true;
}
/* Sort first by truncated state index (for good locality), then by key (for good coherence). */
# define INTEGRATOR_SORT_KEY(key, state) \
(key + kernel_data.max_shaders * (state / kernel_integrator_state.sort_partition_divisor))
ccl_device_forceinline void integrator_path_init_sorted(KernelGlobals kg,
IntegratorState state,
const DeviceKernel next_kernel,
const uint32_t key)
{
const int key_ = INTEGRATOR_SORT_KEY(key, state);
atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], 1);
INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel;
INTEGRATOR_STATE_WRITE(state, path, shader_sort_key) = key_;
# if defined(__KERNEL_LOCAL_ATOMIC_SORT__)
if (!kernel_integrator_state.sort_key_counter[next_kernel]) {
return;
}
# endif
atomic_fetch_and_add_uint32(&kernel_integrator_state.sort_key_counter[next_kernel][key_], 1);
}
ccl_device_forceinline void integrator_path_next_sorted(KernelGlobals kg,
IntegratorState state,
const DeviceKernel current_kernel,
const DeviceKernel next_kernel,
const uint32_t key)
{
const int key_ = INTEGRATOR_SORT_KEY(key, state);
atomic_fetch_and_sub_uint32(&kernel_integrator_state.queue_counter->num_queued[current_kernel],
1);
atomic_fetch_and_add_uint32(&kernel_integrator_state.queue_counter->num_queued[next_kernel], 1);
INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel;
INTEGRATOR_STATE_WRITE(state, path, shader_sort_key) = key_;
# if defined(__KERNEL_LOCAL_ATOMIC_SORT__)
if (!kernel_integrator_state.sort_key_counter[next_kernel]) {
return;
}
# endif
atomic_fetch_and_add_uint32(&kernel_integrator_state.sort_key_counter[next_kernel][key_], 1);
}
#else
ccl_device_forceinline void integrator_path_init(IntegratorState state,
const DeviceKernel next_kernel)
{
INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel;
}
ccl_device_forceinline void integrator_path_init_sorted(KernelGlobals /*kg*/,
IntegratorState state,
const DeviceKernel next_kernel,
const uint32_t key)
{
INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel;
(void)key;
}
ccl_device_forceinline void integrator_path_next(IntegratorState state,
const DeviceKernel current_kernel,
const DeviceKernel next_kernel)
{
INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel;
(void)current_kernel;
}
ccl_device_forceinline void integrator_path_terminate(KernelGlobals kg,
IntegratorState state,
ccl_global float *ccl_restrict render_buffer,
const DeviceKernel current_kernel)
{
write_optical_depth(kg, state, render_buffer);
INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = 0;
(void)current_kernel;
}
ccl_device_forceinline void integrator_path_cache_miss(IntegratorState /*state*/,
const DeviceKernel /*current_kernel*/)
{
assert(!"CPU kernel does not use texture cache miss mechanism");
}
ccl_device_forceinline void integrator_path_cache_miss_sorted(
IntegratorState /*state*/, const DeviceKernel /*current_kernel*/)
{
assert(!"CPU kernel does not use texture cache miss mechanism");
}
ccl_device_forceinline void integrator_path_next_sorted(KernelGlobals /*kg*/,
IntegratorState state,
const DeviceKernel current_kernel,
const DeviceKernel next_kernel,
const uint32_t key)
{
INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = next_kernel;
(void)key;
(void)current_kernel;
}
ccl_device_forceinline IntegratorShadowState
integrator_shadow_path_init(ccl_attr_maybe_unused KernelGlobals kg,
IntegratorState state,
const DeviceKernel next_kernel,
const bool is_ao)
{
IntegratorShadowState shadow_state = (is_ao) ? &state->ao : &state->shadow;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, queued_kernel) = next_kernel;
# if defined(__PATH_GUIDING__)
if ((kernel_data.kernel_features & KERNEL_FEATURE_PATH_GUIDING)) {
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, path_segment) = nullptr;
}
# else
(void)kg;
# endif
return shadow_state;
}
ccl_device_forceinline void integrator_shadow_path_next(IntegratorShadowState state,
const DeviceKernel current_kernel,
const DeviceKernel next_kernel)
{
INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = next_kernel;
(void)current_kernel;
}
ccl_device_forceinline void integrator_shadow_path_terminate(IntegratorShadowState state,
const DeviceKernel current_kernel)
{
INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = 0;
(void)current_kernel;
}
ccl_device_forceinline void integrator_shadow_path_cache_miss(
IntegratorShadowState /*state*/, const DeviceKernel /*current_kernel*/)
{
assert(!"CPU kernel does not use texture cache miss mechanism");
}
#endif
CCL_NAMESPACE_END

View File

@@ -0,0 +1,156 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/************************************ Path State *****************************/
KERNEL_STRUCT_BEGIN(path)
/* Index of a pixel within the device render buffer where this path will write its result.
* To get an actual offset within the buffer the value needs to be multiplied by the
* `kernel_data.film.pass_stride`.
*
* The multiplication is delayed for later, so that state can use 32bit integer. */
KERNEL_STRUCT_MEMBER(path, uint32_t, render_pixel_index, KERNEL_FEATURE_PATH_TRACING)
/* Current sample number. */
KERNEL_STRUCT_MEMBER(path, uint32_t, sample, KERNEL_FEATURE_PATH_TRACING)
/* Current ray bounce depth. */
KERNEL_STRUCT_MEMBER(path, uint16_t, bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current transparent ray bounce depth. */
KERNEL_STRUCT_MEMBER(path, uint16_t, transparent_bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current diffuse ray bounce depth. */
KERNEL_STRUCT_MEMBER(path, uint16_t, diffuse_bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current glossy ray bounce depth. */
KERNEL_STRUCT_MEMBER(path, uint16_t, glossy_bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current transmission ray bounce depth. */
KERNEL_STRUCT_MEMBER(path, uint16_t, transmission_bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current volume ray bounce depth. */
KERNEL_STRUCT_MEMBER(path, uint16_t, volume_bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current volume bounds ray bounce depth. */
KERNEL_STRUCT_MEMBER(path, uint16_t, volume_bounds_bounce, KERNEL_FEATURE_PATH_TRACING)
/* Current portal ray bounce depth. */
KERNEL_STRUCT_MEMBER(path, uint16_t, portal_bounce, KERNEL_FEATURE_NODE_PORTAL)
/* DeviceKernel bit indicating queued kernels. */
KERNEL_STRUCT_MEMBER(path, uint16_t, queued_kernel, KERNEL_FEATURE_PATH_TRACING)
/* Random number generator per-pixel info. */
KERNEL_STRUCT_MEMBER(path, uint32_t, rng_pixel, KERNEL_FEATURE_PATH_TRACING)
/* Random number dimension offset. */
KERNEL_STRUCT_MEMBER(path, uint16_t, rng_offset, KERNEL_FEATURE_PATH_TRACING)
/* enum PathRayVisibilityFlag, limited to bits from PATH_RAY_VISIBILITY_ALL */
KERNEL_STRUCT_MEMBER(path, uint8_t, visibility, KERNEL_FEATURE_PATH_TRACING)
/* enum PathRayFlag */
KERNEL_STRUCT_MEMBER(path, uint32_t, flag, KERNEL_FEATURE_PATH_TRACING)
/* enum PathRayMNEE */
KERNEL_STRUCT_MEMBER(path, uint8_t, mnee, KERNEL_FEATURE_PATH_TRACING)
/* Index of shadow state path used for storing MNEE state. */
KERNEL_STRUCT_MEMBER(path, int, mnee_shadow_state, KERNEL_FEATURE_MNEE)
/* Majorant volume optical depth. */
KERNEL_STRUCT_MEMBER(path, float, optical_depth, KERNEL_FEATURE_PATH_TRACING)
/* Multiple importance sampling
* The PDF of BSDF sampling at the last scatter point, which is at ray distance
* zero and distance. Note that transparency and volume attenuation increase
* the ray tmin but keep P unmodified so that this works. */
KERNEL_STRUCT_MEMBER(path, float, mis_ray_pdf, KERNEL_FEATURE_PATH_TRACING)
/* Object at last scatter point for light linking. */
KERNEL_STRUCT_MEMBER(path, int, mis_ray_object, KERNEL_FEATURE_LIGHT_LINKING)
/* Normal at last scatter point for light tree. */
KERNEL_STRUCT_MEMBER(path, packed_float3, mis_origin_n, KERNEL_FEATURE_PATH_TRACING)
/* Filter glossy. */
KERNEL_STRUCT_MEMBER(path, float, min_ray_pdf, KERNEL_FEATURE_PATH_TRACING)
/* Continuation probability for path termination. */
KERNEL_STRUCT_MEMBER(path, float, continuation_probability, KERNEL_FEATURE_PATH_TRACING)
/* Throughput. */
KERNEL_STRUCT_MEMBER(path, PackedSpectrum, throughput, KERNEL_FEATURE_PATH_TRACING)
/* Factor to multiple with throughput to get remove any guiding PDFS.
* Such throughput without guiding PDFS is used for Russian roulette termination. */
KERNEL_STRUCT_MEMBER(path, float, unguided_throughput, KERNEL_FEATURE_PATH_GUIDING)
/* Ratio of throughput to distinguish diffuse / glossy / transmission render passes. */
KERNEL_STRUCT_MEMBER(path, PackedSpectrum, pass_diffuse_weight, KERNEL_FEATURE_LIGHT_PASSES)
KERNEL_STRUCT_MEMBER(path, PackedSpectrum, pass_glossy_weight, KERNEL_FEATURE_LIGHT_PASSES)
/* Denoising. */
KERNEL_STRUCT_MEMBER(path, PackedSpectrum, denoising_feature_throughput, KERNEL_FEATURE_DENOISING)
/* Shader sorting. */
/* TODO: compress as uint16? or leave out entirely and recompute key in sorting code? */
KERNEL_STRUCT_MEMBER(path, uint32_t, shader_sort_key, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_END(path)
/************************************** Ray ***********************************/
KERNEL_STRUCT_BEGIN_PACKED(ray, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(ray, packed_float3, P, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(ray, float, dP, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(ray, packed_float3, D, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(ray, float, dD, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(ray, float, tmin, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(ray, float, tmax, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(ray, float, time, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER(ray, float, previous_dt, KERNEL_FEATURE_LIGHT_TREE)
KERNEL_STRUCT_END(ray)
/*************************** Intersection result ******************************/
/* Result from scene intersection. */
KERNEL_STRUCT_BEGIN_PACKED(isect, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(isect, float, t, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(isect, float, u, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(isect, float, v, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(isect, int, prim, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(isect, int, object, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_MEMBER_PACKED(isect, int, type, KERNEL_FEATURE_PATH_TRACING)
KERNEL_STRUCT_END(isect)
/*************** Subsurface closure state for subsurface kernel ***************/
KERNEL_STRUCT_BEGIN_PACKED(subsurface, KERNEL_FEATURE_SUBSURFACE)
KERNEL_STRUCT_MEMBER_PACKED(subsurface, PackedSpectrum, albedo, KERNEL_FEATURE_SUBSURFACE)
KERNEL_STRUCT_MEMBER_PACKED(subsurface, PackedSpectrum, radius, KERNEL_FEATURE_SUBSURFACE)
KERNEL_STRUCT_MEMBER_PACKED(subsurface, float, anisotropy, KERNEL_FEATURE_SUBSURFACE)
KERNEL_STRUCT_MEMBER_PACKED(subsurface, packed_float3, N, KERNEL_FEATURE_SUBSURFACE)
KERNEL_STRUCT_END(subsurface)
/********************************** Volume Stack ******************************/
KERNEL_STRUCT_BEGIN(volume_stack)
KERNEL_STRUCT_ARRAY_MEMBER(volume_stack, int, object, KERNEL_FEATURE_VOLUME)
KERNEL_STRUCT_ARRAY_MEMBER(volume_stack, int, shader, KERNEL_FEATURE_VOLUME)
KERNEL_STRUCT_END_ARRAY(volume_stack,
KERNEL_STRUCT_VOLUME_STACK_SIZE,
KERNEL_STRUCT_VOLUME_STACK_SIZE)
/************************************ Path Guiding *****************************/
KERNEL_STRUCT_BEGIN(guiding)
#if defined(__PATH_GUIDING__)
/* Current path segment of the random walk/path. */
KERNEL_STRUCT_MEMBER(guiding,
openpgl::cpp::PathSegment *,
path_segment,
KERNEL_FEATURE_PATH_GUIDING)
#else
/* Current path segment of the random walk/path. */
KERNEL_STRUCT_MEMBER(guiding, uint64_t, path_segment, KERNEL_FEATURE_PATH_GUIDING)
#endif
/* If surface guiding is enabled */
KERNEL_STRUCT_MEMBER(guiding, bool, use_surface_guiding, KERNEL_FEATURE_PATH_GUIDING)
/* Random number used for additional guiding decisions (e.g., cache query, selection to use guiding
* or BSDF sampling) */
KERNEL_STRUCT_MEMBER(guiding, float, sample_surface_guiding_rand, KERNEL_FEATURE_PATH_GUIDING)
/* The probability to use surface guiding (i.e., diffuse sampling prob * guiding prob). */
KERNEL_STRUCT_MEMBER(guiding, float, surface_guiding_sampling_prob, KERNEL_FEATURE_PATH_GUIDING)
/* Probability of sampling a BSSRDF closure instead of a BSDF closure. */
KERNEL_STRUCT_MEMBER(guiding, float, bssrdf_sampling_prob, KERNEL_FEATURE_PATH_GUIDING)
/* If volume guiding is enabled */
KERNEL_STRUCT_MEMBER(guiding, bool, use_volume_guiding, KERNEL_FEATURE_PATH_GUIDING)
/* Random number used for additional guiding decisions (e.g., cache query, selection to use guiding
* or BSDF sampling) */
KERNEL_STRUCT_MEMBER(guiding, float, sample_volume_guiding_rand, KERNEL_FEATURE_PATH_GUIDING)
/* The probability to use surface guiding (i.e., diffuse sampling prob * guiding prob). */
KERNEL_STRUCT_MEMBER(guiding, float, volume_guiding_sampling_prob, KERNEL_FEATURE_PATH_GUIDING)
KERNEL_STRUCT_END(guiding)
/******************************* Shadow linking *******************************/
KERNEL_STRUCT_BEGIN(shadow_link)
KERNEL_STRUCT_MEMBER(shadow_link, float, dedicated_light_weight, KERNEL_FEATURE_SHADOW_LINKING)
/* Copy of primitive and object from the last main path intersection. */
KERNEL_STRUCT_MEMBER(shadow_link, int, last_isect_prim, KERNEL_FEATURE_SHADOW_LINKING)
KERNEL_STRUCT_MEMBER(shadow_link, int, last_isect_object, KERNEL_FEATURE_SHADOW_LINKING)
KERNEL_STRUCT_END(shadow_link)

View File

@@ -0,0 +1,745 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/globals.h"
#include "kernel/integrator/state.h"
#include "kernel/light/common.h"
#include "kernel/sample/lcg.h"
#include "kernel/util/differential.h"
#if defined(__KERNEL_GPU__)
# include "util/atomic.h"
#endif
CCL_NAMESPACE_BEGIN
/* Ray */
ccl_device_forceinline void integrator_state_write_ray(IntegratorState state,
const ccl_private Ray *ccl_restrict ray)
{
#if defined(__INTEGRATOR_GPU_PACKED_STATE__) && defined(__KERNEL_GPU__)
static_assert(sizeof(ray->P) == sizeof(float4), "Bad assumption about float3 padding");
/* dP and dP are packed based on the assumption that float3 is padded to 16 bytes.
* This assumption hold trues on Metal, but not CUDA.
*/
((ccl_private float4 &)ray->P).w = ray->dP;
((ccl_private float4 &)ray->D).w = ray->dD;
INTEGRATOR_STATE_WRITE(state, ray, packed) = (ccl_private packed_ray &)*ray;
/* Ensure that we can correctly cast between Ray and the generated packed_ray struct. */
static_assert(offsetof(packed_ray, P) == offsetof(Ray, P),
"Generated packed_ray struct is misaligned with Ray struct");
static_assert(offsetof(packed_ray, D) == offsetof(Ray, D),
"Generated packed_ray struct is misaligned with Ray struct");
static_assert(offsetof(packed_ray, tmin) == offsetof(Ray, tmin),
"Generated packed_ray struct is misaligned with Ray struct");
static_assert(offsetof(packed_ray, tmax) == offsetof(Ray, tmax),
"Generated packed_ray struct is misaligned with Ray struct");
static_assert(offsetof(packed_ray, time) == offsetof(Ray, time),
"Generated packed_ray struct is misaligned with Ray struct");
static_assert(offsetof(packed_ray, dP) == 12 + offsetof(Ray, P),
"Generated packed_ray struct is misaligned with Ray struct");
static_assert(offsetof(packed_ray, dD) == 12 + offsetof(Ray, D),
"Generated packed_ray struct is misaligned with Ray struct");
#else
INTEGRATOR_STATE_WRITE(state, ray, P) = ray->P;
INTEGRATOR_STATE_WRITE(state, ray, D) = ray->D;
INTEGRATOR_STATE_WRITE(state, ray, tmin) = ray->tmin;
INTEGRATOR_STATE_WRITE(state, ray, tmax) = ray->tmax;
INTEGRATOR_STATE_WRITE(state, ray, time) = ray->time;
INTEGRATOR_STATE_WRITE(state, ray, dP) = ray->dP;
INTEGRATOR_STATE_WRITE(state, ray, dD) = ray->dD;
#endif
}
ccl_device_forceinline void integrator_state_read_ray(ConstIntegratorState state,
ccl_private Ray *ccl_restrict ray)
{
#if defined(__INTEGRATOR_GPU_PACKED_STATE__) && defined(__KERNEL_GPU__)
*((ccl_private packed_ray *)ray) = INTEGRATOR_STATE(state, ray, packed);
ray->dP = ((ccl_private float4 &)ray->P).w;
ray->dD = ((ccl_private float4 &)ray->D).w;
#else
ray->P = INTEGRATOR_STATE(state, ray, P);
ray->D = INTEGRATOR_STATE(state, ray, D);
ray->tmin = INTEGRATOR_STATE(state, ray, tmin);
ray->tmax = INTEGRATOR_STATE(state, ray, tmax);
ray->time = INTEGRATOR_STATE(state, ray, time);
ray->dP = INTEGRATOR_STATE(state, ray, dP);
ray->dD = INTEGRATOR_STATE(state, ray, dD);
#endif
}
/* Shadow Ray */
ccl_device_forceinline void integrator_state_write_shadow_ray(
IntegratorShadowState state, const ccl_private Ray *ccl_restrict ray)
{
INTEGRATOR_STATE_WRITE(state, shadow_ray, P) = ray->P;
INTEGRATOR_STATE_WRITE(state, shadow_ray, D) = ray->D;
INTEGRATOR_STATE_WRITE(state, shadow_ray, tmin) = ray->tmin;
INTEGRATOR_STATE_WRITE(state, shadow_ray, tmax) = ray->tmax;
INTEGRATOR_STATE_WRITE(state, shadow_ray, time) = ray->time;
INTEGRATOR_STATE_WRITE(state, shadow_ray, dP) = ray->dP;
INTEGRATOR_STATE_WRITE(state, shadow_ray, dD) = ray->dD;
}
ccl_device_forceinline void integrator_state_read_shadow_ray(ConstIntegratorShadowState state,
ccl_private Ray *ccl_restrict ray)
{
ray->P = INTEGRATOR_STATE(state, shadow_ray, P);
ray->D = INTEGRATOR_STATE(state, shadow_ray, D);
ray->tmin = INTEGRATOR_STATE(state, shadow_ray, tmin);
ray->tmax = INTEGRATOR_STATE(state, shadow_ray, tmax);
ray->time = INTEGRATOR_STATE(state, shadow_ray, time);
ray->dP = INTEGRATOR_STATE(state, shadow_ray, dP);
ray->dD = INTEGRATOR_STATE(state, shadow_ray, dD);
}
ccl_device_forceinline void integrator_state_write_shadow_ray_self(
IntegratorShadowState state, const ccl_private Ray *ccl_restrict ray)
{
/* There is a bit of implicit knowledge about the way how the kernels are invoked and what the
* state is actually storing. Special logic here is needed because the intersect_shadow kernel
* might be called multiple times. This happens when the total number of intersections by the
* ray (shadow_path.packed_num_hits) exceeds INTEGRATOR_SHADOW_ISECT_SIZE.
*
* Writing of the shadow_ray.self to the state happens only during the shadow ray setup, and
* the shadow_isect array gets overwritten by the intersect_shadow kernel. It is important to
* preserve the exact values of the light_object and light_prim for all invocations of the
* intersect_shadow kernel. Hence they are written to dedicated fields in the state.
*
* The self.object and self.prim are kept at the latest handled intersection: during shadow path
* branch-off it matches the main ray.self. For the consecutive calls of the intersect_shadow
* kernels it comes from the furthest intersection (the last element of the shadow_isect). So we
* use INTEGRATOR_SHADOW_ISECT_SIZE - 1 index for both writing and reading. This utilizes
* knowledge that intersect_shadow kernel is only called for either initial intersection, or when
* the number of ray intersections exceeds the shadow_isect size.
*
* This should help avoiding situations when the same intersection is recorded multiple times
* throughout separate invocations of the intersect_shadow kernel. However, it is still not
* fully reliable as there might be more than INTEGRATOR_SHADOW_ISECT_SIZE intersections at the
* same ray->t. There is no reliable way to deal with such situation, and offsetting ray from
* the shade_shadow kernel which will avoid potential false-positive detection of light being
* fully blocked at the expense of potentially ignoring some intersections. If the offset is
* used then preserving self.object and self.prim might not be as useful, but it definitely does
* not harm. */
INTEGRATOR_STATE_ARRAY_WRITE(
state, shadow_isect, INTEGRATOR_SHADOW_ISECT_SIZE - 1, object) = ray->self.object;
INTEGRATOR_STATE_ARRAY_WRITE(
state, shadow_isect, INTEGRATOR_SHADOW_ISECT_SIZE - 1, prim) = ray->self.prim;
INTEGRATOR_STATE_WRITE(state, shadow_ray, self_light_object) = ray->self.light_object;
INTEGRATOR_STATE_WRITE(state, shadow_ray, self_light_prim) = ray->self.light_prim;
}
ccl_device_forceinline void integrator_state_read_shadow_ray_self(
ConstIntegratorShadowState state, ccl_private Ray *ccl_restrict ray)
{
ray->self.object = INTEGRATOR_STATE_ARRAY(
state, shadow_isect, INTEGRATOR_SHADOW_ISECT_SIZE - 1, object);
ray->self.prim = INTEGRATOR_STATE_ARRAY(
state, shadow_isect, INTEGRATOR_SHADOW_ISECT_SIZE - 1, prim);
ray->self.light_object = INTEGRATOR_STATE(state, shadow_ray, self_light_object);
ray->self.light_prim = INTEGRATOR_STATE(state, shadow_ray, self_light_prim);
}
/* Intersection */
ccl_device_forceinline void integrator_state_write_isect(
IntegratorState state, const ccl_private Intersection *ccl_restrict isect)
{
#if defined(__INTEGRATOR_GPU_PACKED_STATE__) && defined(__KERNEL_GPU__)
INTEGRATOR_STATE_WRITE(state, isect, packed) = (ccl_private packed_isect &)*isect;
/* Ensure that we can correctly cast between Intersection and the generated packed_isect struct.
*/
static_assert(offsetof(packed_isect, t) == offsetof(Intersection, t),
"Generated packed_isect struct is misaligned with Intersection struct");
static_assert(offsetof(packed_isect, u) == offsetof(Intersection, u),
"Generated packed_isect struct is misaligned with Intersection struct");
static_assert(offsetof(packed_isect, v) == offsetof(Intersection, v),
"Generated packed_isect struct is misaligned with Intersection struct");
static_assert(offsetof(packed_isect, object) == offsetof(Intersection, object),
"Generated packed_isect struct is misaligned with Intersection struct");
static_assert(offsetof(packed_isect, prim) == offsetof(Intersection, prim),
"Generated packed_isect struct is misaligned with Intersection struct");
static_assert(offsetof(packed_isect, type) == offsetof(Intersection, type),
"Generated packed_isect struct is misaligned with Intersection struct");
#else
INTEGRATOR_STATE_WRITE(state, isect, t) = isect->t;
INTEGRATOR_STATE_WRITE(state, isect, u) = isect->u;
INTEGRATOR_STATE_WRITE(state, isect, v) = isect->v;
INTEGRATOR_STATE_WRITE(state, isect, object) = isect->object;
INTEGRATOR_STATE_WRITE(state, isect, prim) = isect->prim;
INTEGRATOR_STATE_WRITE(state, isect, type) = isect->type;
#endif
}
ccl_device_forceinline void integrator_state_read_isect(
ConstIntegratorState state, ccl_private Intersection *ccl_restrict isect)
{
#if defined(__INTEGRATOR_GPU_PACKED_STATE__) && defined(__KERNEL_GPU__)
*((ccl_private packed_isect *)isect) = INTEGRATOR_STATE(state, isect, packed);
#else
isect->prim = INTEGRATOR_STATE(state, isect, prim);
isect->object = INTEGRATOR_STATE(state, isect, object);
isect->type = INTEGRATOR_STATE(state, isect, type);
isect->u = INTEGRATOR_STATE(state, isect, u);
isect->v = INTEGRATOR_STATE(state, isect, v);
isect->t = INTEGRATOR_STATE(state, isect, t);
#endif
}
#ifdef __VOLUME__
ccl_device_forceinline VolumeStack integrator_state_read_volume_stack(ConstIntegratorState state,
const int i)
{
VolumeStack entry = {INTEGRATOR_STATE_ARRAY(state, volume_stack, i, object),
INTEGRATOR_STATE_ARRAY(state, volume_stack, i, shader)};
return entry;
}
ccl_device_forceinline void integrator_state_write_volume_stack(IntegratorState state,
const int i,
VolumeStack entry)
{
INTEGRATOR_STATE_ARRAY_WRITE(state, volume_stack, i, object) = entry.object;
INTEGRATOR_STATE_ARRAY_WRITE(state, volume_stack, i, shader) = entry.shader;
}
ccl_device_forceinline bool integrator_state_volume_stack_is_empty(KernelGlobals kg,
ConstIntegratorState state)
{
return (kernel_data.kernel_features & KERNEL_FEATURE_VOLUME) ?
INTEGRATOR_STATE_ARRAY(state, volume_stack, 0, shader) == SHADER_NONE :
true;
}
ccl_device_forceinline void integrator_state_copy_volume_stack_to_shadow(
KernelGlobals kg, IntegratorShadowState shadow_state, ConstIntegratorState state)
{
if (kernel_data.kernel_features & KERNEL_FEATURE_VOLUME) {
int index = 0;
int shader;
do {
shader = INTEGRATOR_STATE_ARRAY(state, volume_stack, index, shader);
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_volume_stack, index, object) =
INTEGRATOR_STATE_ARRAY(state, volume_stack, index, object);
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_volume_stack, index, shader) = shader;
++index;
} while (shader != SHADER_NONE);
}
}
ccl_device_forceinline void integrator_state_copy_volume_stack(KernelGlobals kg,
IntegratorState to_state,
ConstIntegratorState state)
{
if (kernel_data.kernel_features & KERNEL_FEATURE_VOLUME) {
int index = 0;
int shader;
do {
shader = INTEGRATOR_STATE_ARRAY(state, volume_stack, index, shader);
INTEGRATOR_STATE_ARRAY_WRITE(to_state, volume_stack, index, object) = INTEGRATOR_STATE_ARRAY(
state, volume_stack, index, object);
INTEGRATOR_STATE_ARRAY_WRITE(to_state, volume_stack, index, shader) = shader;
++index;
} while (shader != SHADER_NONE);
}
}
ccl_device_forceinline VolumeStack
integrator_state_read_shadow_volume_stack(ConstIntegratorShadowState state, const int i)
{
VolumeStack entry = {INTEGRATOR_STATE_ARRAY(state, shadow_volume_stack, i, object),
INTEGRATOR_STATE_ARRAY(state, shadow_volume_stack, i, shader)};
return entry;
}
ccl_device_forceinline bool integrator_state_shadow_volume_stack_is_empty(
KernelGlobals kg, ConstIntegratorShadowState state)
{
return (kernel_data.kernel_features & KERNEL_FEATURE_VOLUME) ?
INTEGRATOR_STATE_ARRAY(state, shadow_volume_stack, 0, shader) == SHADER_NONE :
true;
}
ccl_device_forceinline void integrator_state_write_shadow_volume_stack(IntegratorShadowState state,
const int i,
VolumeStack entry)
{
INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_volume_stack, i, object) = entry.object;
INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_volume_stack, i, shader) = entry.shader;
}
#endif /* __VOLUME__ */
/* Shadow Intersection */
ccl_device_forceinline void integrator_state_write_shadow_isect(
IntegratorShadowState state,
const ccl_private Intersection *ccl_restrict isect,
const int index)
{
INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, index, t) = isect->t;
INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, index, u) = isect->u;
INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, index, v) = isect->v;
INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, index, object) = isect->object;
INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, index, prim) = isect->prim;
INTEGRATOR_STATE_ARRAY_WRITE(state, shadow_isect, index, type) = isect->type;
}
ccl_device_forceinline void integrator_state_read_shadow_isect(
ConstIntegratorShadowState state,
ccl_private Intersection *ccl_restrict isect,
const int index)
{
isect->prim = INTEGRATOR_STATE_ARRAY(state, shadow_isect, index, prim);
isect->object = INTEGRATOR_STATE_ARRAY(state, shadow_isect, index, object);
isect->type = INTEGRATOR_STATE_ARRAY(state, shadow_isect, index, type);
isect->u = INTEGRATOR_STATE_ARRAY(state, shadow_isect, index, u);
isect->v = INTEGRATOR_STATE_ARRAY(state, shadow_isect, index, v);
isect->t = INTEGRATOR_STATE_ARRAY(state, shadow_isect, index, t);
}
/* MNEE state.
*
* This is packed into the shadow_state to avoid increasing overall path state size. */
#ifdef __MNEE__
ccl_device_forceinline IntegratorShadowState
integrator_state_get_mnee_shadow_state(ConstIntegratorState state)
{
# ifdef __KERNEL_GPU__
return IntegratorShadowState(INTEGRATOR_STATE(state, path, mnee_shadow_state));
# else
return &(((IntegratorStateCPU *)state)->shadow);
# endif
}
# ifdef __KERNEL_GPU__
/* The MNEE shadow slot stores a reference to its owning main path so shadow path
* sorting can maintain the correct index. */
ccl_device_forceinline void integrator_state_write_mnee_shadow_owner(
IntegratorShadowState shadow_state, IntegratorState state)
{
static_assert(INTEGRATOR_SHADOW_ISECT_SIZE >= 3);
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 2, prim) = (int)state;
}
ccl_device_forceinline IntegratorState
integrator_state_read_mnee_shadow_owner(ConstIntegratorShadowState shadow_state)
{
static_assert(INTEGRATOR_SHADOW_ISECT_SIZE >= 3);
return IntegratorState(INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 2, prim));
}
# endif
ccl_device_forceinline void integrator_state_write_mnee(IntegratorState state,
IntegratorShadowState shadow_state,
const ccl_private LightSample *ls,
const ccl_private Ray *ray,
const int mnee_vertex_count,
const Spectrum mnee_throughput,
const float3 mnee_wo)
{
static_assert(INTEGRATOR_SHADOW_ISECT_SIZE >= 2);
# ifdef __KERNEL_GPU__
INTEGRATOR_STATE_WRITE(state, path, mnee_shadow_state) = (int)shadow_state;
integrator_state_write_mnee_shadow_owner(shadow_state, state);
# endif
/* Light sample. */
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 0, t) = ls->P.x;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 0, u) = ls->P.y;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 0, v) = ls->P.z;
/* When the integrate_surface_direct_light() reads the MNEE state it should read mnee_wo as the
* light sample direction. */
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 1, t) = mnee_wo.x;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 1, u) = mnee_wo.y;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 1, v) = mnee_wo.z;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_ray, tmin) = ls->t;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_ray, tmax) = ls->pdf;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_ray, time) = ls->eval_fac;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_ray, self_light_object) = ls->object;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_ray, self_light_prim) = ls->prim;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 0, object) = ls->shader;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 0, prim) = ls->group + 1;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 0, type) = (int)ls->type;
/* Ray. */
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 1, prim) = mnee_vertex_count;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_path, throughput) = mnee_throughput;
/* The ray direction becomes the original light sample's direction for the shadow ray tracing. */
INTEGRATOR_STATE_WRITE(shadow_state, shadow_ray, D) = ls->D;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_ray, P) = ray->P;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_ray, dP) = ray->dP;
INTEGRATOR_STATE_WRITE(shadow_state, shadow_ray, dD) = ray->dD;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 1, object) = ray->self.object;
INTEGRATOR_STATE_ARRAY_WRITE(shadow_state, shadow_isect, 1, type) = ray->self.prim;
INTEGRATOR_STATE_WRITE(state, path, mnee) |= PATH_MNEE_SAMPLED;
}
ccl_device_forceinline void integrator_state_read_mnee(ConstIntegratorState state,
ccl_private LightSample *ls,
ccl_private int *mnee_vertex_count)
{
static_assert(INTEGRATOR_SHADOW_ISECT_SIZE >= 2);
ConstIntegratorShadowState shadow_state = integrator_state_get_mnee_shadow_state(state);
ls->P = make_float3(INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 0, t),
INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 0, u),
INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 0, v));
ls->D = make_float3(INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 1, t),
INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 1, u),
INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 1, v));
ls->t = INTEGRATOR_STATE(shadow_state, shadow_ray, tmin);
ls->pdf = INTEGRATOR_STATE(shadow_state, shadow_ray, tmax);
ls->eval_fac = INTEGRATOR_STATE(shadow_state, shadow_ray, time);
ls->object = INTEGRATOR_STATE(shadow_state, shadow_ray, self_light_object);
ls->prim = INTEGRATOR_STATE(shadow_state, shadow_ray, self_light_prim);
ls->shader = INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 0, object);
ls->group = INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 0, prim) - 1;
ls->type = (LightType)INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 0, type);
ls->pdf_selection = 0.0f;
ls->emitter_id = EMITTER_NONE;
*mnee_vertex_count = INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 1, prim);
}
ccl_device_forceinline void integrator_state_read_mnee_ray(ConstIntegratorState state,
const ccl_private LightSample *ls,
ccl_private Ray *ray)
{
static_assert(INTEGRATOR_SHADOW_ISECT_SIZE >= 2);
ConstIntegratorShadowState shadow_state = integrator_state_get_mnee_shadow_state(state);
ray->P = INTEGRATOR_STATE(shadow_state, shadow_ray, P);
if (ls->t == FLT_MAX) {
/* Distant light. */
ray->D = INTEGRATOR_STATE(shadow_state, shadow_ray, D);
ray->tmax = ls->t;
}
else {
/* Other lights. */
ray->D = ls->P - ray->P;
ray->D = safe_normalize_len(ray->D, &ray->tmax);
}
ray->tmin = ((ls->shader & SHADER_CAST_SHADOW) == 0) ? FLT_MAX : 0.0f;
ray->time = INTEGRATOR_STATE(state, ray, time);
ray->dP = INTEGRATOR_STATE(shadow_state, shadow_ray, dP);
ray->dD = INTEGRATOR_STATE(shadow_state, shadow_ray, dD);
ray->self.object = INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 1, object);
ray->self.prim = INTEGRATOR_STATE_ARRAY(shadow_state, shadow_isect, 1, type);
ray->self.light_object = ls->object;
ray->self.light_prim = ls->prim;
}
ccl_device_forceinline Spectrum integrator_state_read_mnee_throughput(ConstIntegratorState state)
{
ConstIntegratorShadowState shadow_state = integrator_state_get_mnee_shadow_state(state);
return INTEGRATOR_STATE(shadow_state, shadow_path, throughput);
}
#endif /* __MNEE__ */
#if defined(__KERNEL_GPU__)
ccl_device_inline void integrator_state_copy_only(KernelGlobals kg,
ConstIntegratorState to_state,
ConstIntegratorState state)
{
int index;
/* Rely on the compiler to optimize out unused assignments and `while(false)`'s. */
# define KERNEL_STRUCT_BEGIN(name) \
index = 0; \
do {
# define KERNEL_STRUCT_MEMBER(parent_struct, type, name, feature) \
if (kernel_integrator_state.parent_struct.name != nullptr) { \
kernel_integrator_state.parent_struct.name[to_state] = \
kernel_integrator_state.parent_struct.name[state]; \
}
# ifdef __INTEGRATOR_GPU_PACKED_STATE__
# define KERNEL_STRUCT_BEGIN_PACKED(parent_struct, feature) \
KERNEL_STRUCT_BEGIN(parent_struct) \
KERNEL_STRUCT_MEMBER(parent_struct, packed_##parent_struct, packed, feature)
# define KERNEL_STRUCT_MEMBER_PACKED(parent_struct, type, name, feature)
# else
# define KERNEL_STRUCT_MEMBER_PACKED KERNEL_STRUCT_MEMBER
# define KERNEL_STRUCT_BEGIN_PACKED(parent_struct, feature) KERNEL_STRUCT_BEGIN(parent_struct)
# endif
# define KERNEL_STRUCT_ARRAY_MEMBER(parent_struct, type, name, feature) \
if (kernel_integrator_state.parent_struct[index].name != nullptr) { \
kernel_integrator_state.parent_struct[index].name[to_state] = \
kernel_integrator_state.parent_struct[index].name[state]; \
}
# define KERNEL_STRUCT_END(name) \
} \
while (false) \
;
# define KERNEL_STRUCT_END_ARRAY(name, cpu_array_size, gpu_array_size) \
++index; \
} \
while (index < gpu_array_size) \
;
# define KERNEL_STRUCT_VOLUME_STACK_SIZE kernel_data.volume_stack_size
# include "kernel/integrator/state_template.h"
# undef KERNEL_STRUCT_BEGIN
# undef KERNEL_STRUCT_BEGIN_PACKED
# undef KERNEL_STRUCT_MEMBER
# undef KERNEL_STRUCT_MEMBER_PACKED
# undef KERNEL_STRUCT_ARRAY_MEMBER
# undef KERNEL_STRUCT_END
# undef KERNEL_STRUCT_END_ARRAY
# undef KERNEL_STRUCT_VOLUME_STACK_SIZE
}
ccl_device_inline void integrator_state_move(KernelGlobals kg,
ConstIntegratorState to_state,
ConstIntegratorState state)
{
integrator_state_copy_only(kg, to_state, state);
INTEGRATOR_STATE_WRITE(state, path, queued_kernel) = 0;
# ifdef __MNEE__
if (INTEGRATOR_STATE(to_state, path, mnee) & PATH_MNEE_SAMPLED) {
const IntegratorShadowState slot = INTEGRATOR_STATE(to_state, path, mnee_shadow_state);
integrator_state_write_mnee_shadow_owner(slot, to_state);
}
# endif
}
ccl_device_inline void integrator_shadow_state_copy_only(KernelGlobals kg,
ConstIntegratorShadowState to_state,
ConstIntegratorShadowState state)
{
int index;
/* Rely on the compiler to optimize out unused assignments and `while(false)`'s. */
# define KERNEL_STRUCT_BEGIN(name) \
index = 0; \
do {
# define KERNEL_STRUCT_MEMBER(parent_struct, type, name, feature) \
if (kernel_integrator_state.parent_struct.name != nullptr) { \
kernel_integrator_state.parent_struct.name[to_state] = \
kernel_integrator_state.parent_struct.name[state]; \
}
# ifdef __INTEGRATOR_GPU_PACKED_STATE__
# define KERNEL_STRUCT_BEGIN_PACKED(parent_struct, feature) \
KERNEL_STRUCT_BEGIN(parent_struct) \
KERNEL_STRUCT_MEMBER(parent_struct, type, packed, feature)
# define KERNEL_STRUCT_MEMBER_PACKED(parent_struct, type, name, feature)
# else
# define KERNEL_STRUCT_MEMBER_PACKED KERNEL_STRUCT_MEMBER
# define KERNEL_STRUCT_BEGIN_PACKED(parent_struct, feature) KERNEL_STRUCT_BEGIN(parent_struct)
# endif
# define KERNEL_STRUCT_ARRAY_MEMBER(parent_struct, type, name, feature) \
if (kernel_integrator_state.parent_struct[index].name != nullptr) { \
kernel_integrator_state.parent_struct[index].name[to_state] = \
kernel_integrator_state.parent_struct[index].name[state]; \
}
# define KERNEL_STRUCT_END(name) \
} \
while (false) \
;
# define KERNEL_STRUCT_END_ARRAY(name, cpu_array_size, gpu_array_size) \
++index; \
} \
while (index < gpu_array_size) \
;
# define KERNEL_STRUCT_VOLUME_STACK_SIZE kernel_data.volume_stack_size
# include "kernel/integrator/shadow_state_template.h"
# undef KERNEL_STRUCT_BEGIN
# undef KERNEL_STRUCT_BEGIN_PACKED
# undef KERNEL_STRUCT_MEMBER
# undef KERNEL_STRUCT_MEMBER_PACKED
# undef KERNEL_STRUCT_ARRAY_MEMBER
# undef KERNEL_STRUCT_END
# undef KERNEL_STRUCT_END_ARRAY
# undef KERNEL_STRUCT_VOLUME_STACK_SIZE
}
ccl_device_inline void integrator_shadow_state_move(KernelGlobals kg,
ConstIntegratorState to_state,
ConstIntegratorState state)
{
integrator_shadow_state_copy_only(kg, to_state, state);
INTEGRATOR_STATE_WRITE(state, shadow_path, queued_kernel) = 0;
# ifdef __MNEE__
if (INTEGRATOR_STATE(to_state, shadow_path, queued_kernel) ==
DEVICE_KERNEL_INTEGRATOR_SHADOW_PATH_MNEE_PENDING)
{
const IntegratorState main_state = integrator_state_read_mnee_shadow_owner(to_state);
INTEGRATOR_STATE_WRITE(main_state, path, mnee_shadow_state) = (int)to_state;
}
# endif
}
#endif
/* NOTE: Leaves kernel scheduling information untouched. Use INIT semantic for one of the paths
* after this function. */
ccl_device_inline IntegratorState integrator_state_shadow_catcher_split(KernelGlobals kg,
IntegratorState state)
{
#if defined(__KERNEL_GPU__)
ConstIntegratorState to_state = atomic_fetch_and_add_uint32(
&kernel_integrator_state.next_main_path_index[0], 1);
integrator_state_copy_only(kg, to_state, state);
#else
IntegratorStateCPU *ccl_restrict to_state = state + 1;
/* Only copy the required subset for performance. */
to_state->path = state->path;
to_state->ray = state->ray;
to_state->isect = state->isect;
# ifdef __VOLUME__
integrator_state_copy_volume_stack(kg, to_state, state);
# endif
#endif
return to_state;
}
ccl_device_inline int integrator_state_bounce(ConstIntegratorState state,
const uint32_t /*path_flag*/)
{
return INTEGRATOR_STATE(state, path, bounce);
}
ccl_device_inline int integrator_state_bounce(ConstIntegratorShadowState state,
const uint32_t /*path_flag*/)
{
return INTEGRATOR_STATE(state, shadow_path, bounce);
}
ccl_device_inline int integrator_state_diffuse_bounce(ConstIntegratorState state,
const uint32_t /*path_flag*/)
{
return INTEGRATOR_STATE(state, path, diffuse_bounce);
}
ccl_device_inline int integrator_state_diffuse_bounce(ConstIntegratorShadowState state,
const uint32_t /*path_flag*/)
{
return INTEGRATOR_STATE(state, shadow_path, diffuse_bounce);
}
ccl_device_inline int integrator_state_glossy_bounce(ConstIntegratorState state,
const uint32_t /*path_flag*/)
{
return INTEGRATOR_STATE(state, path, glossy_bounce);
}
ccl_device_inline int integrator_state_glossy_bounce(ConstIntegratorShadowState state,
const uint32_t /*path_flag*/)
{
return INTEGRATOR_STATE(state, shadow_path, glossy_bounce);
}
ccl_device_inline int integrator_state_transmission_bounce(ConstIntegratorState state,
const uint32_t /*path_flag*/)
{
return INTEGRATOR_STATE(state, path, transmission_bounce);
}
ccl_device_inline int integrator_state_transmission_bounce(ConstIntegratorShadowState state,
const uint32_t /*path_flag*/)
{
return INTEGRATOR_STATE(state, shadow_path, transmission_bounce);
}
ccl_device_inline int integrator_state_transparent_bounce(ConstIntegratorState state,
const uint32_t /*path_flag*/)
{
return INTEGRATOR_STATE(state, path, transparent_bounce);
}
ccl_device_inline int integrator_state_transparent_bounce(ConstIntegratorShadowState state,
const uint32_t /*path_flag*/)
{
return INTEGRATOR_STATE(state, shadow_path, transparent_bounce);
}
ccl_device_inline int integrator_state_portal_bounce(KernelGlobals kg,
ConstIntegratorState state,
const uint32_t /*path_flag*/)
{
return (kernel_data.kernel_features & KERNEL_FEATURE_NODE_PORTAL) ?
INTEGRATOR_STATE(state, path, portal_bounce) :
0;
}
ccl_device_inline int integrator_state_portal_bounce(KernelGlobals kg,
ConstIntegratorShadowState state,
const uint32_t /*path_flag*/)
{
return (kernel_data.kernel_features & KERNEL_FEATURE_NODE_PORTAL) ?
INTEGRATOR_STATE(state, shadow_path, portal_bounce) :
0;
}
ccl_device_inline uint integrator_state_lcg_init(ConstIntegratorShadowState state, const uint hash)
{
return lcg_state_init(INTEGRATOR_STATE(state, shadow_path, rng_pixel),
INTEGRATOR_STATE(state, shadow_path, rng_offset),
INTEGRATOR_STATE(state, shadow_path, sample),
hash);
}
ccl_device_inline uint integrator_state_lcg_init(ConstIntegratorState state, const uint hash)
{
return lcg_state_init(INTEGRATOR_STATE(state, path, rng_pixel),
INTEGRATOR_STATE(state, path, rng_offset),
INTEGRATOR_STATE(state, path, sample),
hash);
}
ccl_device_inline uint integrator_state_lcg_init(ConstIntegratorBakeState /*state*/,
const uint /*hash*/)
{
return 0;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,249 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "kernel/closure/alloc.h"
#include "kernel/closure/bsdf_diffuse.h"
#include "kernel/closure/bssrdf.h"
#include "kernel/integrator/intersect_volume_stack.h"
#include "kernel/integrator/path_state.h"
#include "kernel/integrator/subsurface_disk.h"
#include "kernel/integrator/subsurface_random_walk.h"
#include "kernel/integrator/surface_shader.h"
CCL_NAMESPACE_BEGIN
#ifdef __SUBSURFACE__
ccl_device_inline bool subsurface_entry_bounce(KernelGlobals kg,
const ccl_private Bssrdf *bssrdf,
ccl_private ShaderData *sd,
ccl_private RNGState *rng_state,
ccl_private float3 *wo)
{
float2 rand_bsdf = path_state_rng_2D(kg, rng_state, PRNG_SUBSURFACE_BSDF);
if (bssrdf->type == CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID) {
/* CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID has a 50% chance to sample a diffuse entry bounce.
* Also, for the refractive entry, it uses a fixed roughness of 1.0. */
if (rand_bsdf.x < 0.5f) {
rand_bsdf.x *= 2.0f;
float pdf;
sample_cos_hemisphere(-bssrdf->N, rand_bsdf, wo, &pdf);
return true;
}
rand_bsdf.x = 2.0f * (rand_bsdf.x - 0.5f);
}
const float cos_NI = dot(bssrdf->N, sd->wi);
if (cos_NI <= 0.0f) {
return false;
}
float3 X;
float3 Y;
const float3 Z = bssrdf->N;
make_orthonormals(Z, &X, &Y);
const float alpha = bssrdf->alpha;
const float neta = 1.0f / bssrdf->ior;
/* Sample microfacet normal by transforming to/from local coordinates. */
const float3 local_I = make_float3(dot(X, sd->wi), dot(Y, sd->wi), cos_NI);
const float3 local_H = microfacet_ggx_sample_vndf(local_I, alpha, alpha, rand_bsdf);
const float3 H = to_global(local_H, X, Y, Z);
const float cos_HI = dot(H, sd->wi);
const float arg = 1.0f - (sqr(neta) * (1.0f - sqr(cos_HI)));
/* We clamp subsurface IOR to be above 1, so there should never be TIR. */
kernel_assert(arg >= 0.0f);
const float dnp = max(sqrtf(arg), 1e-7f);
const float nK = (neta * cos_HI) - dnp;
*wo = -(neta * sd->wi) + (nK * H);
return true;
/* NOTE: For a proper refractive GGX interface, we should be computing lambdaI and lambdaO
* and multiplying the throughput by BSDF/pdf, which for VNDF sampling works out to
* `(1 + lambdaI) / (1 + lambdaI + lambdaO)`.
* However, this causes darkening due to the single-scattering approximation, which we'd
* then have to correct with a lookup table.
* Since we only really care about the directional distribution here, it's much easier to
* just skip all that instead. */
}
ccl_device int subsurface_bounce(KernelGlobals kg,
IntegratorState state,
ccl_private ShaderData *sd,
const ccl_private ShaderClosure *sc)
{
/* Setup path state for intersect_subsurface kernel. */
const ccl_private Bssrdf *bssrdf = (const ccl_private Bssrdf *)sc;
/* Setup ray into surface. */
INTEGRATOR_STATE_WRITE(state, ray, P) = sd->P;
INTEGRATOR_STATE_WRITE(state, ray, tmin) = 0.0f;
INTEGRATOR_STATE_WRITE(state, ray, tmax) = FLT_MAX;
INTEGRATOR_STATE_WRITE(state, ray, dP) = differential_make_compact(sd->dP);
INTEGRATOR_STATE_WRITE(state, ray, dD) = differential_zero_compact();
/* Advance random number offset for bounce. */
INTEGRATOR_STATE_WRITE(state, path, rng_offset) += PRNG_BOUNCE_NUM;
/* Compute weight, optionally including Fresnel from entry point. */
const Spectrum weight = surface_shader_bssrdf_sample_weight(sd, sc);
INTEGRATOR_STATE_WRITE(state, path, throughput) *= weight;
const PathRayVisibility path_visibility = (INTEGRATOR_STATE(state, path, visibility) &
~PATH_RAY_VISIBILITY_CAMERA);
uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
if (sc->type == CLOSURE_BSSRDF_BURLEY_ID) {
/* We should never have two consecutive BSSRDF bounces, the second one should
* be converted to a diffuse BSDF to avoid this. */
kernel_assert(!(INTEGRATOR_STATE(state, path, flag) & PATH_RAY_DIFFUSE_ANCESTOR));
path_flag |= PATH_RAY_SUBSURFACE_DISK;
INTEGRATOR_STATE_WRITE(state, subsurface, N) = sd->Ng;
}
else {
path_flag |= PATH_RAY_SUBSURFACE_RANDOM_WALK;
/* Sample entry bounce into the material. */
RNGState rng_state;
path_state_rng_load(state, &rng_state);
float3 wo;
if (!subsurface_entry_bounce(kg, bssrdf, sd, &rng_state, &wo) || dot(sd->Ng, wo) >= 0.0f) {
/* Sampling failed, give up on this bounce. */
return LABEL_NONE;
}
INTEGRATOR_STATE_WRITE(state, ray, D) = wo;
INTEGRATOR_STATE_WRITE(state, subsurface, N) = sd->N;
}
if (sd->flag & SD_BACKFACING) {
path_flag |= PATH_RAY_SUBSURFACE_BACKFACING;
}
INTEGRATOR_STATE_WRITE(state, path, visibility) = path_visibility;
INTEGRATOR_STATE_WRITE(state, path, flag) = path_flag;
if (kernel_data.kernel_features & KERNEL_FEATURE_LIGHT_PASSES) {
if (INTEGRATOR_STATE(state, path, bounce) == 0) {
INTEGRATOR_STATE_WRITE(state, path, pass_diffuse_weight) = one_spectrum();
INTEGRATOR_STATE_WRITE(state, path, pass_glossy_weight) = zero_spectrum();
}
}
/* Pass BSSRDF parameters. */
INTEGRATOR_STATE_WRITE(state, subsurface, albedo) = bssrdf->albedo;
INTEGRATOR_STATE_WRITE(state, subsurface, radius) = bssrdf->radius;
/* Encode the bssrdf type in anisotropy. */
INTEGRATOR_STATE_WRITE(state, subsurface, anisotropy) = (bssrdf->type ==
CLOSURE_BSSRDF_RANDOM_WALK_ID) ?
bssrdf->anisotropy :
bssrdf->anisotropy + 2.0f;
/* Path guiding. */
guiding_record_bssrdf_weight(kg, state, weight, bssrdf->albedo);
return LABEL_SUBSURFACE_SCATTER;
}
ccl_device void subsurface_shader_data_setup(KernelGlobals kg, ccl_private ShaderData *sd)
{
/* Get bump mapped normal from shader evaluation at exit point. */
float3 N = sd->N;
if (sd->flag & SD_HAS_BSSRDF_BUMP) {
N = surface_shader_bssrdf_normal(sd);
}
/* Setup diffuse BSDF at the exit point. This replaces shader_eval_surface. */
sd->flag &= ~SD_CLOSURE_FLAGS;
sd->num_closure = 0;
sd->num_closure_left = kernel_data.max_closures;
const Spectrum weight = one_spectrum();
bsdf_diffuse_setup(sd, N, weight);
}
ccl_device_inline bool subsurface_scatter(KernelGlobals kg, IntegratorState state)
{
RNGState rng_state;
path_state_rng_load(state, &rng_state);
Ray ray ccl_optional_struct_init;
LocalIntersection ss_isect ccl_optional_struct_init;
if (INTEGRATOR_STATE(state, path, flag) & PATH_RAY_SUBSURFACE_RANDOM_WALK) {
if (!subsurface_random_walk(kg, state, rng_state, ray, ss_isect)) {
return false;
}
}
else {
if (!subsurface_disk(kg, state, rng_state, ray, ss_isect)) {
return false;
}
}
# ifdef __VOLUME__
/* Update volume stack if needed. */
if (kernel_data.integrator.use_volumes) {
const int object = ss_isect.hits[0].object;
const uint object_flag = kernel_data_fetch(object_flag, object);
if (object_flag & SD_OBJECT_INTERSECTS_VOLUME) {
const float3 P = INTEGRATOR_STATE(state, ray, P);
integrator_volume_stack_update_for_subsurface(kg, state, P, ray.P);
}
}
# endif /* __VOLUME__ */
/* Pretend ray is coming from the outside towards the exit point. This ensures
* correct front/back facing normals.
* TODO: find a more elegant solution? */
ray.P += ray.D * ray.tmax * 2.0f;
ray.D = -ray.D;
integrator_state_write_isect(state, &ss_isect.hits[0]);
integrator_state_write_ray(state, &ray);
/* Advance random number offset for bounce. */
INTEGRATOR_STATE_WRITE(state, path, rng_offset) += PRNG_BOUNCE_NUM;
const int shader = intersection_get_shader(kg, &ss_isect.hits[0]);
const int shader_flags = kernel_data_fetch(shaders, shader).flags;
const uint object_flags = intersection_get_object_flags(kg, &ss_isect.hits[0]);
const bool use_caustics = kernel_data.integrator.use_caustics &&
(object_flags & SD_OBJECT_CAUSTICS_RECEIVER);
const bool use_raytrace_kernel = (shader_flags & SD_HAS_RAYTRACE);
if (use_caustics) {
integrator_path_next(state,
DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE,
DEVICE_KERNEL_INTEGRATOR_INTERSECT_MNEE);
}
else if (use_raytrace_kernel) {
integrator_path_next_sorted(kg,
state,
DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE,
DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_RAYTRACE,
shader);
}
else {
integrator_path_next_sorted(kg,
state,
DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE,
DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE,
shader);
}
return true;
}
#endif /* __SUBSURFACE__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,220 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "kernel/bvh/bvh.h"
#include "kernel/closure/bssrdf.h"
#include "kernel/geom/object.h"
#include "kernel/integrator/guiding.h"
#include "kernel/integrator/path_state.h"
#include "kernel/util/differential.h"
CCL_NAMESPACE_BEGIN
#ifdef __SUBSURFACE__
/* BSSRDF using disk based importance sampling.
*
* BSSRDF Importance Sampling, SIGGRAPH 2013
* http://library.imageworks.com/pdfs/imageworks-library-BSSRDF-sampling.pdf
*/
ccl_device_inline Spectrum subsurface_disk_eval(const Spectrum radius,
const float disk_r,
const float r)
{
const Spectrum eval = bssrdf_eval(radius, r);
const float pdf = bssrdf_pdf(radius, disk_r);
return (pdf > 0.0f) ? eval / pdf : zero_spectrum();
}
/* Subsurface scattering step, from a point on the surface to other
* nearby points on the same object. */
ccl_device_inline bool subsurface_disk(KernelGlobals kg,
IntegratorState state,
RNGState rng_state,
ccl_private Ray &ray,
ccl_private LocalIntersection &ss_isect)
{
float2 rand_disk = path_state_rng_2D(kg, &rng_state, PRNG_SUBSURFACE_DISK);
/* Read shading point info from integrator state. */
const float3 P = INTEGRATOR_STATE(state, ray, P);
const float ray_dP = INTEGRATOR_STATE(state, ray, dP);
const float time = INTEGRATOR_STATE(state, ray, time);
const float3 Ng = INTEGRATOR_STATE(state, subsurface, N);
const int object = INTEGRATOR_STATE(state, isect, object);
const uint32_t path_flag = INTEGRATOR_STATE(state, path, flag);
/* Read subsurface scattering parameters. */
const Spectrum radius = INTEGRATOR_STATE(state, subsurface, radius);
/* Pick random axis in local frame and point on disk. */
float3 disk_N;
float3 disk_T;
float3 disk_B;
float pick_pdf_N;
float pick_pdf_T;
float pick_pdf_B;
disk_N = Ng;
make_orthonormals(disk_N, &disk_T, &disk_B);
if (rand_disk.y < 0.5f) {
pick_pdf_N = 0.5f;
pick_pdf_T = 0.25f;
pick_pdf_B = 0.25f;
rand_disk.y *= 2.0f;
}
else if (rand_disk.y < 0.75f) {
const float3 tmp = disk_N;
disk_N = disk_T;
disk_T = tmp;
pick_pdf_N = 0.25f;
pick_pdf_T = 0.5f;
pick_pdf_B = 0.25f;
rand_disk.y = (rand_disk.y - 0.5f) * 4.0f;
}
else {
const float3 tmp = disk_N;
disk_N = disk_B;
disk_B = tmp;
pick_pdf_N = 0.25f;
pick_pdf_T = 0.25f;
pick_pdf_B = 0.5f;
rand_disk.y = (rand_disk.y - 0.75f) * 4.0f;
}
/* Sample point on disk. */
const float phi = M_2PI_F * rand_disk.y;
float disk_height;
float disk_r;
bssrdf_sample(radius, rand_disk.x, &disk_r, &disk_height);
const float3 disk_P = to_global(polar_to_cartesian(disk_r, phi), disk_T, disk_B);
/* Create ray. */
ray.P = P + disk_N * disk_height + disk_P;
ray.D = -disk_N;
ray.tmin = 0.0f;
ray.tmax = 2.0f * disk_height;
ray.dP = ray_dP;
ray.dD = differential_zero_compact();
ray.time = time;
ray.self.object = OBJECT_NONE;
ray.self.prim = PRIM_NONE;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
/* Intersect with the same object. if multiple intersections are found it
* will use at most BSSRDF_MAX_HITS hits, a random subset of all hits. */
uint lcg_state = lcg_state_init(
rng_state.rng_pixel, rng_state.rng_offset, rng_state.sample, 0x68bc21eb);
const int max_hits = BSSRDF_MAX_HITS;
scene_intersect_local(kg, &ray, &ss_isect, object, &lcg_state, max_hits);
const int num_eval_hits = min(ss_isect.num_hits, max_hits);
if (num_eval_hits == 0) {
return false;
}
/* Sort for consistent renders between CPU and GPU, independent of the BVH
* traversal algorithm. */
sort_intersections_and_normals(ss_isect.hits, ss_isect.Ng, num_eval_hits);
Spectrum weights[BSSRDF_MAX_HITS]; /* TODO: zero? */
float sum_weights = 0.0f;
for (int hit = 0; hit < num_eval_hits; hit++) {
/* Get geometric normal. */
const int object = ss_isect.hits[hit].object;
const uint object_flag = kernel_data_fetch(object_flag, object);
float3 hit_Ng = ss_isect.Ng[hit];
if (path_flag & PATH_RAY_SUBSURFACE_BACKFACING) {
hit_Ng = -hit_Ng;
}
if (object_negative_scale_applied(object_flag)) {
hit_Ng = -hit_Ng;
}
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
/* Transform normal to world space. */
Transform itfm;
object_fetch_transform_motion_test(kg, object, time, &itfm);
hit_Ng = normalize(transform_direction_transposed(&itfm, hit_Ng));
}
/* Quickly retrieve P and Ng without setting up ShaderData. */
const float3 hit_P = ray.P + ray.D * ss_isect.hits[hit].t;
/* Probability densities for local frame axes. */
const float pdf_N = pick_pdf_N * fabsf(dot(disk_N, hit_Ng));
const float pdf_T = pick_pdf_T * fabsf(dot(disk_T, hit_Ng));
const float pdf_B = pick_pdf_B * fabsf(dot(disk_B, hit_Ng));
/* Multiple importance sample between 3 axes, power heuristic
* found to be slightly better than balance heuristic. pdf_N
* in the MIS weight and denominator cancelled out. */
float w = pdf_N / (sqr(pdf_N) + sqr(pdf_T) + sqr(pdf_B));
if (ss_isect.num_hits > max_hits) {
w *= ss_isect.num_hits / (float)max_hits;
}
/* Real distance to sampled point. */
const float r = len(hit_P - P);
/* Evaluate profiles. */
const Spectrum weight = subsurface_disk_eval(radius, disk_r, r) * w;
/* Store result. */
ss_isect.Ng[hit] = hit_Ng;
weights[hit] = weight;
sum_weights += average(fabs(weight));
}
if (sum_weights == 0.0f) {
return false;
}
/* Use importance resampling, sampling one of the hits proportional to weight. */
const float rand_resample = path_state_rng_1D(kg, &rng_state, PRNG_SUBSURFACE_DISK_RESAMPLE);
const float r = rand_resample * sum_weights;
float partial_sum = 0.0f;
for (int hit = 0; hit < num_eval_hits; hit++) {
const Spectrum weight = weights[hit];
const float sample_weight = average(fabs(weight));
const float next_sum = partial_sum + sample_weight;
if (r < next_sum) {
/* Return exit point. */
const Spectrum resampled_weight = weight * sum_weights / sample_weight;
INTEGRATOR_STATE_WRITE(state, path, throughput) *= resampled_weight;
ss_isect.hits[0] = ss_isect.hits[hit];
ss_isect.Ng[0] = ss_isect.Ng[hit];
ray.P = ray.P + ray.D * ss_isect.hits[hit].t;
ray.D = ss_isect.Ng[hit];
ray.tmin = 0.0f;
ray.tmax = 1.0f;
guiding_record_bssrdf_bounce(
kg, state, 1.0f, Ng, -Ng, resampled_weight, INTEGRATOR_STATE(state, subsurface, albedo));
return true;
}
partial_sum = next_sum;
}
return false;
}
#endif /* __SUBSURFACE__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,502 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "kernel/bvh/bvh.h"
#include "kernel/closure/volume.h"
#include "kernel/integrator/guiding.h"
#include "kernel/integrator/path_state.h"
#include "util/color.h"
CCL_NAMESPACE_BEGIN
#ifdef __SUBSURFACE__
/* Random walk subsurface scattering.
*
* "Practical and Controllable Subsurface Scattering for Production Path
* Tracing". Matt Jen-Yuan Chiang, Peter Kutz, Brent Burley. SIGGRAPH 2016. */
/* Support for anisotropy from:
* "Path Traced Subsurface Scattering using Anisotropic Phase Functions
* and Non-Exponential Free Flights".
* Magnus Wrenninge, Ryusuke Villemin, Christophe Hery.
* https://graphics.pixar.com/library/PathTracedSubsurface/ */
ccl_device void subsurface_random_walk_remap(const float albedo,
const float d,
const float g,
ccl_private float *sigma_t,
ccl_private float *alpha)
{
/* Compute attenuation and scattering coefficients from albedo. */
const float g2 = g * g;
const float g3 = g2 * g;
const float g4 = g3 * g;
const float g5 = g4 * g;
const float g6 = g5 * g;
const float g7 = g6 * g;
const float A = 1.8260523782f + -1.28451056436f * g + -1.79904629312f * g2 +
9.19393289202f * g3 + -22.8215585862f * g4 + 32.0234874259f * g5 +
-23.6264803333f * g6 + 7.21067002658f * g7;
const float B = 4.98511194385f +
0.127355959438f *
expf(31.1491581433f * g + -201.847017512f * g2 + 841.576016723f * g3 +
-2018.09288505f * g4 + 2731.71560286f * g5 + -1935.41424244f * g6 +
559.009054474f * g7);
const float C = 1.09686102424f + -0.394704063468f * g + 1.05258115941f * g2 +
-8.83963712726f * g3 + 28.8643230661f * g4 + -46.8802913581f * g5 +
38.5402837518f * g6 + -12.7181042538f * g7;
const float D = 0.496310210422f + 0.360146581622f * g + -2.15139309747f * g2 +
17.8896899217f * g3 + -55.2984010333f * g4 + 82.065982243f * g5 +
-58.5106008578f * g6 + 15.8478295021f * g7;
const float E = 4.23190299701f +
0.00310603949088f *
expf(76.7316253952f * g + -594.356773233f * g2 + 2448.8834203f * g3 +
-5576.68528998f * g4 + 7116.60171912f * g5 + -4763.54467887f * g6 +
1303.5318055f * g7);
const float F = 2.40602999408f + -2.51814844609f * g + 9.18494908356f * g2 +
-79.2191708682f * g3 + 259.082868209f * g4 + -403.613804597f * g5 +
302.85712436f * g6 + -87.4370473567f * g7;
const float blend = powf(albedo, 0.25f);
*alpha = (1.0f - blend) * A * powf(atanf(B * albedo), C) +
blend * D * powf(atanf(E * albedo), F);
*alpha = clamp(*alpha, 0.0f, 0.999999f); // because of numerical precision
const float sigma_t_prime = 1.0f / fmaxf(d, 1e-16f);
*sigma_t = sigma_t_prime / (1.0f - g);
}
/* Mapping from subsurface color and anisotropy to single scatter albedo, taken from
* "A Hitchhiker's Guide to Multiple Scattering" by Eugene d'Eon, v0.3.2 Eq (53.7),
* https://eugenedeon.com/hitchhikers
*/
ccl_device void subsurface_random_walk_remap(const Spectrum color,
const Spectrum radius,
const float g,
ccl_private Spectrum *sigma_t,
ccl_private Spectrum *alpha)
{
const Spectrum s_sq = sqr(4.20863f * color -
sqrt(9.59217f + 41.6808f * color + 17.7126f * sqr(color)) + 4.09712f);
*alpha = safe_divide(1.0f - s_sq, 1.0f - g * s_sq);
/* Clamp to avoid numerical issues. */
*alpha = clamp(*alpha, zero_spectrum(), make_spectrum(0.999999f));
*sigma_t = reciprocal(max(radius, make_spectrum(1e-16f)));
}
ccl_device void subsurface_random_walk_coefficients(const Spectrum albedo,
const Spectrum radius,
const float anisotropy,
const bool van_de_hulst,
ccl_private Spectrum *sigma_t,
ccl_private Spectrum *alpha,
ccl_private Spectrum *throughput)
{
if (van_de_hulst) {
subsurface_random_walk_remap(albedo, radius, anisotropy, sigma_t, alpha);
}
else {
FOREACH_SPECTRUM_CHANNEL (i) {
subsurface_random_walk_remap(GET_SPECTRUM_CHANNEL(albedo, i),
GET_SPECTRUM_CHANNEL(radius, i),
anisotropy,
&GET_SPECTRUM_CHANNEL(*sigma_t, i),
&GET_SPECTRUM_CHANNEL(*alpha, i));
}
}
/* Throughput already contains closure weight at this point, which includes the
* albedo, as well as closure mixing and Fresnel weights. Divide out the albedo
* which will be added through scattering. */
*throughput = safe_divide_color(*throughput, albedo);
/* With low albedo values (like 0.025) we get diffusion_length 1.0 and
* infinite phase functions. To avoid a sharp discontinuity as we go from
* such values to 0.0, increase alpha and reduce the throughput to compensate. */
const float min_alpha = 0.2f;
FOREACH_SPECTRUM_CHANNEL (i) {
if (GET_SPECTRUM_CHANNEL(*alpha, i) < min_alpha) {
GET_SPECTRUM_CHANNEL(*throughput, i) *= GET_SPECTRUM_CHANNEL(*alpha, i) / min_alpha;
GET_SPECTRUM_CHANNEL(*alpha, i) = min_alpha;
}
}
}
/* References for Dwivedi sampling:
*
* [1] "A Zero-variance-based Sampling Scheme for Monte Carlo Subsurface Scattering"
* by Jaroslav Křivánek and Eugene d'Eon (SIGGRAPH 2014)
* https://cgg.mff.cuni.cz/~jaroslav/papers/2014-zerovar/
*
* [2] "Improving the Dwivedi Sampling Scheme"
* by Johannes Meng, Johannes Hanika, and Carsten Dachsbacher (EGSR 2016)
* https://cg.ivd.kit.edu/1951.php
*
* [3] "Zero-Variance Theory for Efficient Subsurface Scattering"
* by Eugene d'Eon and Jaroslav Křivánek (SIGGRAPH 2020)
* https://iliyan.com/publications/RenderingCourse2020
*/
ccl_device_forceinline float eval_phase_dwivedi(const float v,
const float phase_log,
const float cos_theta)
{
/* Eq. 9 from [2] using precomputed log((v + 1) / (v - 1)) */
return 1.0f / ((v - cos_theta) * phase_log);
}
ccl_device_forceinline float sample_phase_dwivedi(const float v,
const float phase_log,
const float rand)
{
/* Based on Eq. 10 from [2]: `v - (v + 1) * pow((v - 1) / (v + 1), rand)`
* Since we're already pre-computing `phase_log = log((v + 1) / (v - 1))` for the evaluation,
* we can implement the power function like this. */
return v - (v + 1.0f) * expf(-rand * phase_log);
}
ccl_device_forceinline float diffusion_length_dwivedi(const float alpha)
{
/* Eq. 67 from [3] */
return 1.0f / sqrtf(1.0f - powf(alpha, 2.44294f - 0.0215813f * alpha + 0.578637f / alpha));
}
ccl_device_forceinline float3 direction_from_cosine(const float3 D,
const float cos_theta,
const float randv)
{
const float phi = M_2PI_F * randv;
const float3 dir = spherical_cos_to_direction(cos_theta, phi);
float3 T;
float3 B;
make_orthonormals(D, &T, &B);
return to_global(dir, T, B, D);
}
ccl_device_forceinline Spectrum subsurface_random_walk_pdf(Spectrum sigma_t,
const float t,
bool hit,
ccl_private Spectrum *transmittance)
{
const Spectrum T = volume_color_transmittance(sigma_t, t);
if (transmittance) {
*transmittance = T;
}
return hit ? T : sigma_t * T;
}
/* Define the below variable to get the similarity code active,
* and the value represents the cutoff level */
# define SUBSURFACE_RANDOM_WALK_SIMILARITY_LEVEL 9
ccl_device_inline bool subsurface_random_walk(KernelGlobals kg,
IntegratorState state,
RNGState rng_state,
ccl_private Ray &ray,
ccl_private LocalIntersection &ss_isect)
{
const float3 P = INTEGRATOR_STATE(state, ray, P);
const float3 D = INTEGRATOR_STATE(state, ray, D);
const float ray_dP = INTEGRATOR_STATE(state, ray, dP);
const float time = INTEGRATOR_STATE(state, ray, time);
const float3 N = INTEGRATOR_STATE(state, subsurface, N);
const int object = INTEGRATOR_STATE(state, isect, object);
const int prim = INTEGRATOR_STATE(state, isect, prim);
/* Setup ray. */
ray.P = P;
ray.D = D;
ray.tmin = 0.0f;
ray.tmax = FLT_MAX;
ray.time = time;
ray.dP = ray_dP;
ray.dD = differential_zero_compact();
ray.self.object = object;
ray.self.prim = prim;
ray.self.light_object = OBJECT_NONE;
ray.self.light_prim = PRIM_NONE;
/* Convert subsurface to volume coefficients.
* The single-scattering albedo is named alpha to avoid confusion with the surface albedo. */
const Spectrum albedo = INTEGRATOR_STATE(state, subsurface, albedo);
const Spectrum radius = INTEGRATOR_STATE(state, subsurface, radius);
float anisotropy = INTEGRATOR_STATE(state, subsurface, anisotropy);
bool van_de_hulst;
if (anisotropy >= 1.0f) {
/* Legacy random walk was mapped from (-1, 1) to (1, 3) when stored in integrator state.
* Remapp to the original value. */
anisotropy -= 2.0f;
/* Legacy mapping doesn't support negative range, use Van de Hulst instead. */
van_de_hulst = anisotropy < 0.0f;
}
else {
/* Use Van de Hulst mapping for the new random walk model. */
van_de_hulst = true;
}
Spectrum sigma_t;
Spectrum alpha;
Spectrum throughput = INTEGRATOR_STATE(state, path, throughput);
subsurface_random_walk_coefficients(
albedo, radius, anisotropy, van_de_hulst, &sigma_t, &alpha, &throughput);
const Spectrum sigma_s = sigma_t * alpha;
/* Theoretically it should be better to use the exact alpha for the channel we're sampling at
* each bounce, but in practice there doesn't seem to be a noticeable difference in exchange
* for making the code significantly more complex and slower (if direction sampling depends on
* the sampled channel, we need to compute its PDF per-channel and consider it for MIS later on).
*
* Since the strength of the guided sampling increases as alpha gets lower, using a value that
* is too low results in fireflies while one that's too high just gives a bit more noise.
* Therefore, the code here uses the highest of the three albedos to be safe. */
const float diffusion_length = diffusion_length_dwivedi(reduce_max(alpha));
if (diffusion_length == 1.0f) {
/* With specific values of alpha the length might become 1, which in asymptotic makes phase to
* be infinite. After first bounce it will cause throughput to be 0. Do early output, avoiding
* numerical issues and extra unneeded work. */
return false;
}
/* Precompute term for phase sampling. */
const float phase_log = logf((diffusion_length + 1.0f) / (diffusion_length - 1.0f));
/* Modify state for RNGs, decorrelated from other paths. */
path_state_rng_scramble(&rng_state, 0xdeadbeef);
/* Random walk until we hit the surface again. */
bool hit = false;
bool have_opposite_interface = false;
float opposite_distance = 0.0f;
/* TODO: Disable for `alpha > 0.999` or so? */
/* Our heuristic, a compromise between guiding and classic. */
const float guided_fraction = 1.0f - fmaxf(0.5f, powf(fabsf(anisotropy), 0.125f));
# ifdef SUBSURFACE_RANDOM_WALK_SIMILARITY_LEVEL
const Spectrum sigma_s_star = sigma_s * (1.0f - anisotropy);
const Spectrum sigma_t_star = sigma_t - sigma_s + sigma_s_star;
const Spectrum sigma_t_org = sigma_t;
const Spectrum sigma_s_org = sigma_s;
const float anisotropy_org = anisotropy;
const float guided_fraction_org = guided_fraction;
# endif
for (int bounce = 0; bounce < BSSRDF_MAX_BOUNCES; bounce++) {
/* Advance random number offset. */
rng_state.rng_offset += PRNG_BOUNCE_NUM;
# ifdef SUBSURFACE_RANDOM_WALK_SIMILARITY_LEVEL
// shadow with local variables according to depth
float anisotropy;
float guided_fraction;
Spectrum sigma_s;
Spectrum sigma_t;
if (bounce <= SUBSURFACE_RANDOM_WALK_SIMILARITY_LEVEL) {
anisotropy = anisotropy_org;
guided_fraction = guided_fraction_org;
sigma_t = sigma_t_org;
sigma_s = sigma_s_org;
}
else {
anisotropy = 0.0f;
guided_fraction = 0.75f; // back to isotropic heuristic from Blender
sigma_t = sigma_t_star;
sigma_s = sigma_s_star;
}
# endif
/* Sample color channel, use MIS with balance heuristic. */
float rchannel = path_state_rng_1D(kg, &rng_state, PRNG_SUBSURFACE_COLOR_CHANNEL);
Spectrum channel_pdf;
const int channel = volume_sample_channel(alpha, throughput, &rchannel, &channel_pdf);
float sample_sigma_t = volume_channel_get(sigma_t, channel);
const float randt = path_state_rng_1D(kg, &rng_state, PRNG_SUBSURFACE_SCATTER_DISTANCE);
/* We need the result of the ray-cast to compute the full guided PDF, so just remember the
* relevant terms to avoid recomputing them later. */
float backward_fraction = 0.0f;
float forward_pdf_factor = 0.0f;
float forward_stretching = 1.0f;
float backward_pdf_factor = 0.0f;
float backward_stretching = 1.0f;
/* For the initial ray, we already know the direction, so just do classic distance sampling. */
if (bounce > 0) {
/* Decide whether we should use guided or classic sampling. */
const bool guided = (path_state_rng_1D(kg, &rng_state, PRNG_SUBSURFACE_GUIDE_STRATEGY) <
guided_fraction);
/* Determine if we want to sample away from the incoming interface.
* This only happens if we found a nearby opposite interface, and the probability for it
* depends on how close we are to it already.
* This probability term comes from the recorded presentation of [3]. */
bool guide_backward = false;
if (have_opposite_interface) {
/* Compute distance of the random walk between the tangent plane at the starting point
* and the assumed opposite interface (the parallel plane that contains the point we
* found in our ray query for the opposite side). */
const float x = clamp(dot(ray.P - P, -N), 0.0f, opposite_distance);
backward_fraction = 1.0f /
(1.0f + expf((opposite_distance - 2.0f * x) / diffusion_length));
guide_backward = path_state_rng_1D(kg, &rng_state, PRNG_SUBSURFACE_GUIDE_DIRECTION) <
backward_fraction;
}
/* Sample scattering direction. */
const float2 rand_scatter = path_state_rng_2D(kg, &rng_state, PRNG_SUBSURFACE_BSDF);
float cos_theta;
float hg_pdf;
if (guided) {
cos_theta = sample_phase_dwivedi(diffusion_length, phase_log, rand_scatter.x);
/* The backwards guiding distribution is just mirrored along `sd->N`, so swapping the
* sign here is enough to sample from that instead. */
if (guide_backward) {
cos_theta = -cos_theta;
}
const float3 newD = direction_from_cosine(N, cos_theta, rand_scatter.y);
hg_pdf = phase_henyey_greenstein(dot(ray.D, newD), anisotropy);
ray.D = newD;
}
else {
const float3 newD = phase_henyey_greenstein_sample(
ray.D, anisotropy, rand_scatter, &hg_pdf);
cos_theta = dot(newD, N);
ray.D = newD;
}
/* Compute PDF factor caused by phase sampling (as the ratio of guided / classic).
* Since phase sampling is channel-independent, we can get away with applying a factor
* to the guided PDF, which implicitly means pulling out the classic PDF term and letting
* it cancel with an equivalent term in the numerator of the full estimator.
* For the backward PDF, we again reuse the same probability distribution with a sign swap.
*/
forward_pdf_factor = M_1_2PI_F * eval_phase_dwivedi(diffusion_length, phase_log, cos_theta) /
hg_pdf;
backward_pdf_factor = M_1_2PI_F *
eval_phase_dwivedi(diffusion_length, phase_log, -cos_theta) / hg_pdf;
/* Prepare distance sampling.
* For the backwards case, this also needs the sign swapped since now directions against
* `sd->N` (and therefore with negative cos_theta) are preferred. */
forward_stretching = (1.0f - cos_theta / diffusion_length);
backward_stretching = (1.0f + cos_theta / diffusion_length);
if (guided) {
sample_sigma_t *= guide_backward ? backward_stretching : forward_stretching;
}
}
/* Sample distance along ray. */
float t = -logf(1.0f - randt) / sample_sigma_t;
/* On the first bounce, we use the ray-cast to check if the opposite side is nearby.
* If yes, we will later use backwards guided sampling in order to have a decent
* chance of connecting to it.
* TODO: Maybe use less than 10 times the mean free path? */
if (bounce == 0) {
ray.tmax = max(t, 10.0f / (reduce_min(sigma_t)));
}
else {
ray.tmax = t;
/* After the first bounce the object can intersect the same surface again */
ray.self.object = OBJECT_NONE;
ray.self.prim = PRIM_NONE;
}
scene_intersect_local<true>(kg, &ray, &ss_isect, object, nullptr, 1);
hit = (ss_isect.num_hits > 0);
if (hit) {
ray.tmax = ss_isect.hits[0].t;
}
if (bounce == 0) {
/* Check if we hit the opposite side. */
if (hit) {
have_opposite_interface = true;
opposite_distance = dot(ray.P + ray.tmax * ray.D - P, -N);
}
/* Apart from the opposite side check, we were supposed to only trace up to distance t,
* so check if there would have been a hit in that case. */
hit = ray.tmax < t;
}
/* Use the distance to the exit point for the throughput update if we found one. */
if (hit) {
t = ray.tmax;
}
/* Advance to new scatter location. */
ray.P += t * ray.D;
Spectrum transmittance;
Spectrum pdf = subsurface_random_walk_pdf(sigma_t, t, hit, &transmittance);
if (bounce > 0) {
/* Compute PDF just like we do for classic sampling, but with the stretched sigma_t. */
Spectrum guided_pdf = subsurface_random_walk_pdf(
forward_stretching * sigma_t, t, hit, nullptr);
if (have_opposite_interface) {
/* First step of MIS: Depending on geometry we might have two methods for guided
* sampling, so perform MIS between them. */
const Spectrum back_pdf = subsurface_random_walk_pdf(
backward_stretching * sigma_t, t, hit, nullptr);
guided_pdf = mix(
guided_pdf * forward_pdf_factor, back_pdf * backward_pdf_factor, backward_fraction);
}
else {
/* Just include phase sampling factor otherwise. */
guided_pdf *= forward_pdf_factor;
}
/* Now we apply the MIS balance heuristic between the classic and guided sampling. */
pdf = mix(pdf, guided_pdf, guided_fraction);
}
/* Finally, we're applying MIS again to combine the three color channels.
* Altogether, the MIS computation combines up to nine different estimators:
* {classic, guided, backward_guided} x {r, g, b} */
throughput *= (hit ? transmittance : sigma_s * transmittance) / dot(channel_pdf, pdf);
if (hit) {
/* If we hit the surface, we are done. */
break;
}
if (reduce_max(throughput) < VOLUME_THROUGHPUT_EPSILON) {
/* Avoid unnecessary work and precision issue when throughput gets really small. */
break;
}
}
if (hit) {
kernel_assert(isfinite_safe(throughput));
/* TODO(lukas): Which PDF should we report here? Entry bounce? The random walk? Just 1.0? */
guiding_record_bssrdf_bounce(
kg,
state,
1.0f,
N,
D,
safe_divide_color(throughput, INTEGRATOR_STATE(state, path, throughput)),
albedo);
INTEGRATOR_STATE_WRITE(state, path, throughput) = throughput;
}
return hit;
}
#endif /* __SUBSURFACE__ */
CCL_NAMESPACE_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,542 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Volume shader evaluation and sampling. */
#pragma once
#include "kernel/closure/volume.h"
#include "kernel/geom/attribute.h"
#include "kernel/geom/shader_data.h"
#ifdef __SVM__
# include "kernel/svm/svm.h"
#endif
#ifdef __OSL__
# include "kernel/osl/osl.h"
#endif
#include "kernel/film/light_passes.h"
#include "kernel/integrator/guiding.h"
#include "kernel/integrator/volume_stack.h"
CCL_NAMESPACE_BEGIN
#ifdef __VOLUME__
/* Merging */
ccl_device_inline void volume_shader_merge_closures(ccl_private ShaderData *sd)
{
/* Merge identical closures to save closure space with stacked volumes. */
for (int i = 0; i < sd->num_closure; i++) {
ccl_private ShaderClosure *sci = &sd->closure[i];
if (!CLOSURE_IS_VOLUME_SCATTER(sci->type)) {
continue;
}
for (int j = i + 1; j < sd->num_closure; j++) {
ccl_private ShaderClosure *scj = &sd->closure[j];
if (!volume_phase_equal(sci, scj)) {
continue;
}
sci->weight += scj->weight;
sci->sample_weight += scj->sample_weight;
const int size = sd->num_closure - (j + 1);
if (size > 0) {
for (int k = 0; k < size; k++) {
scj[k] = scj[k + 1];
}
}
sd->num_closure--;
kernel_assert(sd->num_closure >= 0);
j--;
}
}
}
ccl_device_inline void volume_shader_copy_phases(ccl_private ShaderVolumePhases *ccl_restrict
phases,
const ccl_private ShaderData *ccl_restrict sd)
{
phases->num_closure = 0;
for (int i = 0; i < sd->num_closure; i++) {
const ccl_private ShaderClosure *from_sc = &sd->closure[i];
if (CLOSURE_IS_VOLUME_SCATTER(from_sc->type)) {
/* ShaderVolumeClosure is a subset of ShaderClosure, so this is fine for all volume scatter
* closures. */
phases->closure[phases->num_closure++] = *((const ccl_private ShaderVolumeClosure *)from_sc);
if (phases->num_closure >= MAX_VOLUME_CLOSURE) {
break;
}
}
}
}
/* Guiding */
# if defined(__PATH_GUIDING__)
ccl_device_inline void volume_shader_prepare_guiding(KernelGlobals kg,
IntegratorState state,
float rand_phase_guiding,
const float3 P,
const float3 D,
ccl_private ShaderVolumePhases *phases)
{
/* Have any phase functions to guide? */
const int num_phases = phases->num_closure;
if (!kernel_data.integrator.use_volume_guiding || num_phases == 0) {
INTEGRATOR_STATE_WRITE(state, guiding, use_volume_guiding) = false;
return;
}
const float volume_guiding_probability = kernel_data.integrator.volume_guiding_probability;
/* If we have more than one phase function we select one random based on its
* sample weight to calculate the product distribution for guiding. */
int phase_id = 0;
float phase_weight = 1.0f;
if (num_phases > 1) {
/* Pick a phase closure based on sample weights. */
float sum = 0.0f;
for (phase_id = 0; phase_id < num_phases; phase_id++) {
const ccl_private ShaderVolumeClosure *svc = &phases->closure[phase_id];
sum += svc->sample_weight;
}
const float r = rand_phase_guiding * sum;
float partial_sum = 0.0f;
for (phase_id = 0; phase_id < num_phases; phase_id++) {
const ccl_private ShaderVolumeClosure *svc = &phases->closure[phase_id];
const float next_sum = partial_sum + svc->sample_weight;
if (r <= next_sum) {
/* Rescale to reuse. */
rand_phase_guiding = (r - partial_sum) / svc->sample_weight;
phase_weight = svc->sample_weight / sum;
break;
}
partial_sum = next_sum;
}
/* Adjust the sample weight of the component used for guiding. */
phases->closure[phase_id].sample_weight *= volume_guiding_probability;
}
/* Init guiding for selected phase function. */
const ccl_private ShaderVolumeClosure *svc = &phases->closure[phase_id];
const float phase_g = volume_phase_get_g(svc);
if (!guiding_phase_init(kg, P, D, phase_g, rand_phase_guiding)) {
INTEGRATOR_STATE_WRITE(state, guiding, use_volume_guiding) = false;
return;
}
INTEGRATOR_STATE_WRITE(state, guiding, use_volume_guiding) = true;
INTEGRATOR_STATE_WRITE(state, guiding, sample_volume_guiding_rand) = rand_phase_guiding;
INTEGRATOR_STATE_WRITE(
state, guiding, volume_guiding_sampling_prob) = volume_guiding_probability * phase_weight;
kernel_assert(INTEGRATOR_STATE(state, guiding, volume_guiding_sampling_prob) > 0.0f &&
INTEGRATOR_STATE(state, guiding, volume_guiding_sampling_prob) <= 1.0f);
}
# endif
/* Phase Evaluation & Sampling */
/* Randomly sample a volume phase function proportional to ShaderClosure.sample_weight. */
/* TODO: this isn't quite correct, we don't weight anisotropy properly depending on color channels,
* even if this is perhaps not a common case */
const ccl_device_inline ccl_private ShaderVolumeClosure *volume_shader_phase_pick(
const ccl_private ShaderVolumePhases *phases, ccl_private float2 *rand_phase)
{
int sampled = 0;
if (phases->num_closure > 1) {
/* Pick a phase closure based on sample weights. */
/* For reservoir sampling, always accept the first in the stream. */
float sum = phases->closure[0].sample_weight;
for (int i = 1; i < phases->num_closure; i++) {
const float sample_weight = phases->closure[i].sample_weight;
sum += sample_weight;
const float thresh = sample_weight / sum;
/* Rescale random number to reuse for volume phase direction sample. */
if (rand_phase->x < thresh) {
sampled = i;
rand_phase->x /= thresh;
}
else {
rand_phase->x = (rand_phase->x - thresh) / (1.0f - thresh);
}
}
}
return &phases->closure[sampled];
}
ccl_device_inline float _volume_shader_phase_eval_mis(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumePhases *phases,
const float3 wo,
ccl_private BsdfEval *result_eval,
float sum_pdf,
float sum_sample_weight)
{
for (int i = 0; i < phases->num_closure; i++) {
const ccl_private ShaderVolumeClosure *svc = &phases->closure[i];
float phase_pdf = 0.0f;
const Spectrum eval = volume_phase_eval(sd, svc, wo, &phase_pdf);
if (phase_pdf != 0.0f) {
bsdf_eval_accum(result_eval, eval * svc->sample_weight);
sum_pdf += phase_pdf * svc->sample_weight;
}
sum_sample_weight += svc->sample_weight;
}
bsdf_eval_mul(result_eval, 1.0f / sum_sample_weight);
return (sum_sample_weight > 0.0f) ? sum_pdf / sum_sample_weight : 0.0f;
}
ccl_device float volume_shader_phase_eval(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float3 wo,
ccl_private BsdfEval *phase_eval)
{
float phase_pdf = 0.0f;
const Spectrum eval = volume_phase_eval(sd, svc, wo, &phase_pdf);
if (phase_pdf != 0.0f) {
bsdf_eval_accum(phase_eval, eval);
}
return phase_pdf;
}
ccl_device float volume_shader_phase_eval(ccl_attr_maybe_unused KernelGlobals kg,
ccl_attr_maybe_unused IntegratorState state,
const ccl_private ShaderData *sd,
const ccl_private ShaderVolumePhases *phases,
const float3 wo,
ccl_private BsdfEval *phase_eval,
const uint light_shader_flags)
{
bsdf_eval_init(phase_eval, zero_spectrum());
float pdf = _volume_shader_phase_eval_mis(sd, phases, wo, phase_eval, 0.0f, 0.0f);
# if defined(__PATH_GUIDING__) && PATH_GUIDING_LEVEL >= 4
if ((kernel_data.kernel_features & KERNEL_FEATURE_PATH_GUIDING)) {
if (INTEGRATOR_STATE(state, guiding, use_volume_guiding)) {
const float guiding_sampling_prob = INTEGRATOR_STATE(
state, guiding, volume_guiding_sampling_prob);
const float guide_pdf = guiding_phase_pdf(kg, wo);
pdf = (guiding_sampling_prob * guide_pdf) + (1.0f - guiding_sampling_prob) * pdf;
}
}
# endif
/* If the light does not use MIS, then it is only sampled via NEE, so the probability of hitting
* the light using BSDF sampling is zero. */
if (!(light_shader_flags & SHADER_USE_MIS)) {
pdf = 0.0f;
}
return pdf;
}
# if defined(__PATH_GUIDING__)
ccl_device int volume_shader_phase_guided_sample(KernelGlobals kg,
IntegratorState state,
const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float2 rand_phase,
ccl_private BsdfEval *phase_eval,
ccl_private float3 *wo,
ccl_private float *phase_pdf,
ccl_private float *unguided_phase_pdf,
ccl_private float *sampled_roughness)
{
const bool use_volume_guiding = INTEGRATOR_STATE(state, guiding, use_volume_guiding);
const float guiding_sampling_prob = INTEGRATOR_STATE(
state, guiding, volume_guiding_sampling_prob);
/* Decide between sampling guiding distribution and phase. */
float rand_phase_guiding = INTEGRATOR_STATE(state, guiding, sample_volume_guiding_rand);
bool sample_guiding = false;
if (use_volume_guiding && rand_phase_guiding < guiding_sampling_prob) {
sample_guiding = true;
rand_phase_guiding /= guiding_sampling_prob;
}
else {
rand_phase_guiding -= guiding_sampling_prob;
rand_phase_guiding /= (1.0f - guiding_sampling_prob);
}
/* Initialize to zero. */
int label = LABEL_NONE;
Spectrum eval = zero_spectrum();
*unguided_phase_pdf = 0.0f;
float guide_pdf = 0.0f;
*sampled_roughness = 1.0f - fabsf(volume_phase_get_g(svc));
bsdf_eval_init(phase_eval, zero_spectrum());
if (sample_guiding) {
/* Sample guiding distribution. */
guide_pdf = guiding_phase_sample(kg, rand_phase, wo);
*phase_pdf = 0.0f;
if (guide_pdf != 0.0f) {
*unguided_phase_pdf = volume_shader_phase_eval(sd, svc, *wo, phase_eval);
*phase_pdf = (guiding_sampling_prob * guide_pdf) +
((1.0f - guiding_sampling_prob) * (*unguided_phase_pdf));
label = LABEL_VOLUME_SCATTER;
}
}
else {
/* Sample phase. */
*phase_pdf = 0.0f;
label = volume_phase_sample(sd, svc, rand_phase, &eval, wo, unguided_phase_pdf);
if (*unguided_phase_pdf != 0.0f) {
bsdf_eval_init(phase_eval, eval);
*phase_pdf = *unguided_phase_pdf;
if (use_volume_guiding) {
guide_pdf = guiding_phase_pdf(kg, *wo);
*phase_pdf *= 1.0f - guiding_sampling_prob;
*phase_pdf += guiding_sampling_prob * guide_pdf;
}
kernel_assert(reduce_min(bsdf_eval_sum(phase_eval)) >= 0.0f);
}
else {
bsdf_eval_init(phase_eval, zero_spectrum());
}
kernel_assert(reduce_min(bsdf_eval_sum(phase_eval)) >= 0.0f);
}
return label;
}
# endif
ccl_device int volume_shader_phase_sample(const ccl_private ShaderData *sd,
const ccl_private ShaderVolumeClosure *svc,
const float2 rand_phase,
ccl_private BsdfEval *phase_eval,
ccl_private float3 *wo,
ccl_private float *pdf,
ccl_private float *sampled_roughness)
{
*sampled_roughness = 1.0f - fabsf(volume_phase_get_g(svc));
Spectrum eval = zero_spectrum();
*pdf = 0.0f;
const int label = volume_phase_sample(sd, svc, rand_phase, &eval, wo, pdf);
if (*pdf != 0.0f) {
bsdf_eval_init(phase_eval, eval);
}
return label;
}
/* Motion Blur */
# ifdef __OBJECT_MOTION__
ccl_device_inline void volume_shader_motion_blur(KernelGlobals kg,
ccl_private ShaderData *ccl_restrict sd)
{
if ((sd->object_flag & SD_OBJECT_HAS_VOLUME_MOTION) == 0) {
return;
}
const AttributeDescriptor v_desc = find_attribute(kg, sd, ATTR_STD_VOLUME_VELOCITY);
kernel_assert(is_attribute_found(v_desc));
const float3 P = sd->P;
const float velocity_scale = kernel_data_fetch(objects, sd->object).velocity_scale;
const float time_offset = kernel_data.cam.motion_position == MOTION_POSITION_CENTER ? 0.5f :
0.0f;
const float time = kernel_data.cam.motion_position == MOTION_POSITION_END ?
(1.0f - kernel_data.cam.shuttertime) + sd->time :
sd->time;
/* Use a 1st order semi-lagrangian advection scheme to estimate what volume quantity
* existed, or will exist, at the given time:
*
* `phi(x, T) = phi(x - (T - t) * u(x, T), t)`
*
* where
*
* x : position
* T : super-sampled time (or ray time)
* t : current time of the simulation (in rendering we assume this is center frame with
* relative time = 0)
* phi : the volume quantity
* u : the velocity field
*
* But first we need to determine the velocity field `u(x, T)`, which we can estimate also
* using semi-lagrangian advection.
*
* `u(x, T) = u(x - (T - t) * u(x, T), t)`
*
* This is the typical way to model self-advection in fluid dynamics, however, we do not
* account for other forces affecting the velocity during simulation (pressure, buoyancy,
* etc.): this gives a linear interpolation when fluid are mostly "curvy". For better
* results, a higher order interpolation scheme can be used (at the cost of more lookups),
* or an interpolation of the velocity fields for the previous and next frames could also
* be used to estimate `u(x, T)` (which will cost more memory and lookups).
*
* References:
* "Eulerian Motion Blur", Kim and Ko, 2007
* "Production Volume Rendering", Wreninge et al., 2012
*/
/* Always use linear interpolation for velocity. */
const int cubic_flag = sd->flag & SD_VOLUME_CUBIC;
sd->flag &= ~SD_VOLUME_CUBIC;
/* Find velocity. */
float3 velocity = primitive_volume_attribute<float3>(kg, sd, v_desc, false);
object_dir_transform(kg, sd, &velocity);
/* Find advected P. */
sd->P = P - (time - time_offset) * velocity_scale * velocity;
/* Find advected velocity. */
velocity = primitive_volume_attribute<float3>(kg, sd, v_desc, false);
object_dir_transform(kg, sd, &velocity);
/* Find advected P. */
sd->P = P - (time - time_offset) * velocity_scale * velocity;
/* Restore flag. */
sd->flag |= cubic_flag;
}
# endif
/* Volume Evaluation */
template<const bool shadow, const uint node_feature_mask, typename ConstIntegratorGenericState>
ccl_device_inline bool volume_shader_eval_entry(KernelGlobals kg,
ConstIntegratorGenericState state,
ccl_private ShaderData *ccl_restrict sd,
const ccl_private VolumeStack &entry,
const PathRayVisibility path_visibility,
const uint32_t path_flag)
{
if (entry.shader == SHADER_NONE) {
return false;
}
/* Setup shader-data from stack. It's mostly setup already in shader_setup_from_volume, this
* switching should be quick. */
sd->object = entry.object;
sd->shader = entry.shader;
sd->flag &= ~SD_SHADER_FLAGS;
sd->flag |= kernel_data_fetch(shaders, (sd->shader & SHADER_MASK)).flags;
sd->object_flag &= ~SD_OBJECT_FLAGS;
if (sd->object != OBJECT_NONE) {
sd->object_flag |= kernel_data_fetch(object_flag, sd->object);
if (shadow && !(kernel_data_fetch(objects, sd->object).visibility & path_visibility)) {
/* If volume is invisible to shadow ray, the hit is not registered, but the volume is still
* in the stack. Skip the volume in such cases. */
/* NOTE: `SHADOW_CATCHER_PATH_VISIBILITY()` is omitted because `path_visibility` is just
* `PATH_RAY_VISIBILITY_SHADOW` when evaluating shadows. */
return true;
}
# ifdef __OBJECT_MOTION__
/* TODO: this is inefficient for motion blur, we should be caching matrices instead of
* recomputing them each step. */
shader_setup_object_transforms(kg, sd, sd->time);
volume_shader_motion_blur(kg, sd);
# endif
}
/* Evaluate shader. */
# ifdef __OSL__
if (kernel_data.kernel_features & KERNEL_FEATURE_OSL_SHADING) {
osl_eval_nodes<SHADER_TYPE_VOLUME>(kg, state, sd, path_visibility, path_flag);
}
else
# endif
{
# ifdef __SVM__
svm_eval_nodes<node_feature_mask, SHADER_TYPE_VOLUME>(
kg, state, sd, nullptr, path_visibility, path_flag);
# endif
}
return true;
}
template<const bool shadow, typename ConstIntegratorGenericState>
ccl_device_inline void volume_shader_eval(KernelGlobals kg,
ConstIntegratorGenericState state,
ccl_private ShaderData *ccl_restrict sd,
const PathRayVisibility path_visibility,
const uint32_t path_flag)
{
/* If path is being terminated, we are tracing a shadow ray or evaluating
* emission, then we don't need to store closures. The emission and shadow
* shader data also do not have a closure array to save GPU memory. */
int max_closures;
if ((path_visibility & PATH_RAY_VISIBILITY_SHADOW) ||
(path_flag & (PATH_RAY_TERMINATE | PATH_RAY_EMISSION)))
{
max_closures = 0;
}
else {
max_closures = kernel_data.max_closures;
}
/* reset closures once at the start, we will be accumulating the closures
* for all volumes in the stack into a single array of closures */
sd->num_closure = 0;
sd->num_closure_left = max_closures;
sd->flag = SD_IS_VOLUME_SHADER_EVAL | (sd->flag & SD_CACHE_MISS);
sd->object_flag = 0;
for (int i = 0;; i++) {
const VolumeStack entry = volume_stack_read<shadow>(state, i);
if (!volume_shader_eval_entry<shadow, KERNEL_FEATURE_NODE_MASK_VOLUME>(
kg, state, sd, entry, path_visibility, path_flag))
{
/* Stack fully processed. */
return;
}
/* Merge closures to avoid exceeding number of closures limit. */
if (!shadow) {
if (i > 0) {
volume_shader_merge_closures(sd);
}
}
}
}
#endif /* __VOLUME__ */
CCL_NAMESPACE_END

View File

@@ -0,0 +1,258 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
CCL_NAMESPACE_BEGIN
#ifdef __VOLUME__
/* Volume Stack
*
* This is an array of object/shared ID's that the current segment of the path
* is inside of. */
template<const bool shadow, typename IntegratorGenericState>
ccl_device_forceinline VolumeStack volume_stack_read(const IntegratorGenericState state,
const int i)
{
if constexpr (shadow) {
return integrator_state_read_shadow_volume_stack(state, i);
}
else {
return integrator_state_read_volume_stack(state, i);
}
# ifdef __KERNEL_GPU__
/* Silence false positive warning with some GPU compilers. */
VolumeStack stack = {};
return stack;
# endif
}
template<const bool shadow, typename IntegratorGenericState>
ccl_device_forceinline void volume_stack_write(IntegratorGenericState state,
const int i,
const VolumeStack entry)
{
if constexpr (shadow) {
integrator_state_write_shadow_volume_stack(state, i, entry);
}
else {
integrator_state_write_volume_stack(state, i, entry);
}
}
template<const bool shadow, typename IntegratorGenericState>
ccl_device void volume_stack_enter_exit(KernelGlobals kg,
IntegratorGenericState state,
const ccl_private ShaderData *sd)
{
# ifdef __KERNEL_USE_DATA_CONSTANTS__
/* If we're using data constants, this fetch disappears.
* On Apple GPUs, scenes without volumetric features can render 1 or 2% faster by dead-stripping
* this function. */
if (!(kernel_data.kernel_features & KERNEL_FEATURE_VOLUME)) {
return;
}
# endif
/* todo: we should have some way for objects to indicate if they want the
* world shader to work inside them. excluding it by default is problematic
* because non-volume objects can't be assumed to be closed manifolds */
if (!(sd->flag & SD_HAS_VOLUME)) {
return;
}
if (sd->flag & SD_BACKFACING) {
/* Exit volume object: remove from stack. */
for (int i = 0;; i++) {
VolumeStack entry = volume_stack_read<shadow>(state, i);
if (entry.shader == SHADER_NONE) {
break;
}
if (entry.object == sd->object && entry.shader == sd->shader) {
/* Shift back next stack entries. */
do {
entry = volume_stack_read<shadow>(state, i + 1);
volume_stack_write<shadow>(state, i, entry);
i++;
} while (entry.shader != SHADER_NONE);
return;
}
}
}
else {
/* Enter volume object: add to stack. */
uint i;
for (i = 0;; i++) {
const VolumeStack entry = volume_stack_read<shadow>(state, i);
if (entry.shader == SHADER_NONE) {
break;
}
/* Already in the stack? then we have nothing to do. */
if (entry.object == sd->object && entry.shader == sd->shader) {
return;
}
}
/* If we exceed the stack limit, ignore. */
if (i >= kernel_data.volume_stack_size - 1) {
return;
}
/* Add to the end of the stack. */
const VolumeStack new_entry = {sd->object, sd->shader};
const VolumeStack empty_entry = {OBJECT_NONE, SHADER_NONE};
volume_stack_write<shadow>(state, i, new_entry);
volume_stack_write<shadow>(state, i + 1, empty_entry);
}
}
/* Clean stack after the last bounce.
*
* It is expected that all volumes are closed manifolds, so at the time when ray
* hits nothing (for example, it is a last bounce which goes to environment) the
* only expected volume in the stack is the world's one. All the rest volume
* entries should have been exited already.
*
* This isn't always true because of ray intersection precision issues, which
* could lead us to an infinite non-world volume in the stack, causing render
* artifacts.
*
* Use this function after the last bounce to get rid of all volumes apart from
* the world's one after the last bounce to avoid render artifacts.
*/
ccl_device_inline void volume_stack_clean(KernelGlobals kg, IntegratorState state)
{
if (kernel_data.background.volume_shader != SHADER_NONE) {
/* Keep the world's volume in stack. */
INTEGRATOR_STATE_ARRAY_WRITE(state, volume_stack, 1, shader) = SHADER_NONE;
}
else {
INTEGRATOR_STATE_ARRAY_WRITE(state, volume_stack, 0, shader) = SHADER_NONE;
}
}
/* Check if the volume is homogeneous by checking if the shader flag is set or if volume attributes
* are needed. */
ccl_device_inline bool volume_is_homogeneous(KernelGlobals kg,
const ccl_private VolumeStack &entry)
{
const int shader_flag = kernel_data_fetch(shaders, (entry.shader & SHADER_MASK)).flags;
if (shader_flag & SD_HETEROGENEOUS_VOLUME) {
return false;
}
if (shader_flag & SD_NEED_VOLUME_ATTRIBUTES) {
const int object = entry.object;
if (object == kernel_data.background.object_index) {
/* Volume attributes for world is not supported. */
return true;
}
const uint object_flag = kernel_data_fetch(object_flag, object);
if (object_flag & SD_OBJECT_HAS_VOLUME_ATTRIBUTES) {
/* If both the shader and the object needs volume attributes, the volume is heterogeneous. */
return false;
}
}
return true;
}
template<const bool shadow, typename IntegratorGenericState>
ccl_device_inline bool volume_is_homogeneous(KernelGlobals kg, const IntegratorGenericState state)
{
for (int i = 0;; i++) {
const VolumeStack entry = volume_stack_read<shadow>(state, i);
if (entry.shader == SHADER_NONE) {
return true;
}
if (!volume_is_homogeneous(kg, entry)) {
return false;
}
}
kernel_assert(false);
return false;
}
template<const bool shadow, typename IntegratorGenericState>
ccl_device float volume_stack_step_size(KernelGlobals kg, const IntegratorGenericState state)
{
kernel_assert(kernel_data.integrator.volume_ray_marching);
float step_size = FLT_MAX;
for (int i = 0;; i++) {
const VolumeStack entry = volume_stack_read<shadow>(state, i);
if (entry.shader == SHADER_NONE) {
break;
}
if (!volume_is_homogeneous(kg, entry)) {
const float object_step_size = kernel_data_fetch(volume_step_size, entry.object);
step_size = fminf(object_step_size, step_size);
}
}
return step_size;
}
enum VolumeSampleMethod {
VOLUME_SAMPLE_NONE = 0,
VOLUME_SAMPLE_DISTANCE = (1 << 0),
VOLUME_SAMPLE_EQUIANGULAR = (1 << 1),
VOLUME_SAMPLE_MIS = (VOLUME_SAMPLE_DISTANCE | VOLUME_SAMPLE_EQUIANGULAR),
};
ccl_device VolumeSampleMethod volume_stack_sample_method(KernelGlobals kg, IntegratorState state)
{
VolumeSampleMethod method = VOLUME_SAMPLE_NONE;
for (int i = 0;; i++) {
VolumeStack entry = integrator_state_read_volume_stack(state, i);
if (entry.shader == SHADER_NONE) {
break;
}
int shader_flag = kernel_data_fetch(shaders, (entry.shader & SHADER_MASK)).flags;
if (shader_flag & SD_VOLUME_MIS) {
/* Multiple importance sampling. */
return VOLUME_SAMPLE_MIS;
}
else if (shader_flag & SD_VOLUME_EQUIANGULAR) {
/* Distance + equiangular sampling -> multiple importance sampling. */
if (method == VOLUME_SAMPLE_DISTANCE) {
return VOLUME_SAMPLE_MIS;
}
/* Only equiangular sampling. */
method = VOLUME_SAMPLE_EQUIANGULAR;
}
else {
/* Distance + equiangular sampling -> multiple importance sampling. */
if (method == VOLUME_SAMPLE_EQUIANGULAR) {
return VOLUME_SAMPLE_MIS;
}
/* Distance sampling only. */
method = VOLUME_SAMPLE_DISTANCE;
}
}
return method;
}
#endif /* __VOLUME__ */
CCL_NAMESPACE_END