Multi-bounce was mainly disabled for disk sampling where the probability of hitting something is relatively low even with high albedo, but this is not so much an issue with random walk. This reduces darkening artifacts at the cost of some extra render time. The difference is mainly visible when using a high radius. Pull Request: https://projects.blender.org/blender/blender/pulls/140665
252 lines
8.9 KiB
C
252 lines
8.9 KiB
C
/* 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;
|
|
|
|
uint32_t path_flag = (INTEGRATOR_STATE(state, path, flag) & ~PATH_RAY_CAMERA);
|
|
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, 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;
|
|
INTEGRATOR_STATE_WRITE(state, subsurface, anisotropy) = bssrdf->anisotropy;
|
|
|
|
/* 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();
|
|
|
|
ccl_private DiffuseBsdf *bsdf = (ccl_private DiffuseBsdf *)bsdf_alloc(
|
|
sd, sizeof(DiffuseBsdf), weight);
|
|
|
|
if (bsdf) {
|
|
bsdf->N = N;
|
|
sd->flag |= bsdf_diffuse_setup(bsdf);
|
|
}
|
|
}
|
|
|
|
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 int 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 int 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);
|
|
const bool use_raytrace_kernel = (shader_flags & SD_HAS_RAYTRACE);
|
|
|
|
if (use_caustics) {
|
|
integrator_path_next_sorted(kg,
|
|
state,
|
|
DEVICE_KERNEL_INTEGRATOR_INTERSECT_SUBSURFACE,
|
|
DEVICE_KERNEL_INTEGRATOR_SHADE_SURFACE_MNEE,
|
|
shader);
|
|
}
|
|
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
|