Add Chromium-only Blender WebEngine parity work
This commit is contained in:
@@ -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
|
||||
752
blender-5.2.0/intern/cycles/kernel/integrator/guiding.h
Normal file
752
blender-5.2.0/intern/cycles/kernel/integrator/guiding.h
Normal 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
|
||||
343
blender-5.2.0/intern/cycles/kernel/integrator/init_from_bake.h
Normal file
343
blender-5.2.0/intern/cycles/kernel/integrator/init_from_bake.h
Normal 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
|
||||
145
blender-5.2.0/intern/cycles/kernel/integrator/init_from_camera.h
Normal file
145
blender-5.2.0/intern/cycles/kernel/integrator/init_from_camera.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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, ¤t_isect);
|
||||
if (!hit) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Only record primitives that potentially have emission.
|
||||
* TODO: optimize with a dedicated ray visibility flag, which could then also be
|
||||
* used once lights are in the BVH as geometry? */
|
||||
const int shader = intersection_get_shader(kg, ¤t_isect);
|
||||
const int shader_flags = kernel_data_fetch(shaders, shader).flags;
|
||||
if (light_link_object_match(kg, object_receiver, current_isect.object) &&
|
||||
(shader_flags & SD_HAS_EMISSION))
|
||||
{
|
||||
const uint64_t set_membership =
|
||||
kernel_data_fetch(objects, current_isect.object).shadow_set_membership;
|
||||
if (set_membership != LIGHT_LINK_MASK_ALL) {
|
||||
++num_hits;
|
||||
|
||||
if ((linked_isect->prim == PRIM_NONE) || (lcg_step_float(lcg_state) < 1.0f / num_hits)) {
|
||||
*linked_isect = current_isect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Contribution from the lights past the default opaque blocker is accumulated
|
||||
* using the main path. */
|
||||
if (!(shader_flags & (SD_HAS_ONLY_VOLUME | SD_HAS_TRANSPARENT_SHADOW))) {
|
||||
const uint blocker_set = kernel_data_fetch(objects, current_isect.object).blocker_shadow_set;
|
||||
if (blocker_set == 0) {
|
||||
ray->tmax = current_isect.t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Lights past the maximum allowed transparency bounce do not contribute any light, so
|
||||
* consider them as fully blocked and only consider lights prior to this intersection. */
|
||||
if (shader_flags & SD_HAS_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
|
||||
119
blender-5.2.0/intern/cycles/kernel/integrator/intersect_mnee.h
Normal file
119
blender-5.2.0/intern/cycles/kernel/integrator/intersect_mnee.h
Normal 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
|
||||
186
blender-5.2.0/intern/cycles/kernel/integrator/intersect_shadow.h
Normal file
186
blender-5.2.0/intern/cycles/kernel/integrator/intersect_shadow.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
121
blender-5.2.0/intern/cycles/kernel/integrator/megakernel.h
Normal file
121
blender-5.2.0/intern/cycles/kernel/integrator/megakernel.h
Normal 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
|
||||
1140
blender-5.2.0/intern/cycles/kernel/integrator/mnee.h
Normal file
1140
blender-5.2.0/intern/cycles/kernel/integrator/mnee.h
Normal file
File diff suppressed because it is too large
Load Diff
431
blender-5.2.0/intern/cycles/kernel/integrator/path_state.h
Normal file
431
blender-5.2.0/intern/cycles/kernel/integrator/path_state.h
Normal 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
|
||||
281
blender-5.2.0/intern/cycles/kernel/integrator/shade_background.h
Normal file
281
blender-5.2.0/intern/cycles/kernel/integrator/shade_background.h
Normal 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
|
||||
@@ -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
|
||||
249
blender-5.2.0/intern/cycles/kernel/integrator/shade_light.h
Normal file
249
blender-5.2.0/intern/cycles/kernel/integrator/shade_light.h
Normal 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
|
||||
298
blender-5.2.0/intern/cycles/kernel/integrator/shade_shadow.h
Normal file
298
blender-5.2.0/intern/cycles/kernel/integrator/shade_shadow.h
Normal 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
|
||||
960
blender-5.2.0/intern/cycles/kernel/integrator/shade_surface.h
Normal file
960
blender-5.2.0/intern/cycles/kernel/integrator/shade_surface.h
Normal 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
|
||||
3006
blender-5.2.0/intern/cycles/kernel/integrator/shade_volume.h
Normal file
3006
blender-5.2.0/intern/cycles/kernel/integrator/shade_volume.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
286
blender-5.2.0/intern/cycles/kernel/integrator/state.h
Normal file
286
blender-5.2.0/intern/cycles/kernel/integrator/state.h
Normal 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
|
||||
304
blender-5.2.0/intern/cycles/kernel/integrator/state_flow.h
Normal file
304
blender-5.2.0/intern/cycles/kernel/integrator/state_flow.h
Normal 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
|
||||
156
blender-5.2.0/intern/cycles/kernel/integrator/state_template.h
Normal file
156
blender-5.2.0/intern/cycles/kernel/integrator/state_template.h
Normal 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)
|
||||
745
blender-5.2.0/intern/cycles/kernel/integrator/state_util.h
Normal file
745
blender-5.2.0/intern/cycles/kernel/integrator/state_util.h
Normal 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
|
||||
249
blender-5.2.0/intern/cycles/kernel/integrator/subsurface.h
Normal file
249
blender-5.2.0/intern/cycles/kernel/integrator/subsurface.h
Normal 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
|
||||
220
blender-5.2.0/intern/cycles/kernel/integrator/subsurface_disk.h
Normal file
220
blender-5.2.0/intern/cycles/kernel/integrator/subsurface_disk.h
Normal 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
|
||||
@@ -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
|
||||
1299
blender-5.2.0/intern/cycles/kernel/integrator/surface_shader.h
Normal file
1299
blender-5.2.0/intern/cycles/kernel/integrator/surface_shader.h
Normal file
File diff suppressed because it is too large
Load Diff
542
blender-5.2.0/intern/cycles/kernel/integrator/volume_shader.h
Normal file
542
blender-5.2.0/intern/cycles/kernel/integrator/volume_shader.h
Normal 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
|
||||
258
blender-5.2.0/intern/cycles/kernel/integrator/volume_stack.h
Normal file
258
blender-5.2.0/intern/cycles/kernel/integrator/volume_stack.h
Normal 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
|
||||
Reference in New Issue
Block a user