- Use uppercase NOTE: tags. - Correct bNote -> bNode. - Use colon after parameters. - Use doxy-style doc-strings.
257 lines
9.1 KiB
C
257 lines
9.1 KiB
C
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#pragma once
|
|
|
|
#include "kernel/camera/projection.h"
|
|
|
|
#include "kernel/bvh/bvh.h"
|
|
|
|
#include "kernel/closure/alloc.h"
|
|
#include "kernel/closure/bsdf_diffuse.h"
|
|
#include "kernel/closure/bssrdf.h"
|
|
#include "kernel/closure/volume.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,
|
|
ccl_private const 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, Y, 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 = X * local_H.x + Y * local_H.y + Z * local_H.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,
|
|
ccl_private const ShaderClosure *sc)
|
|
{
|
|
/* 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));
|
|
|
|
/* Setup path state for intersect_subsurface kernel. */
|
|
ccl_private const Bssrdf *bssrdf = (ccl_private const 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. */
|
|
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) {
|
|
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,
|
|
IntegratorState state,
|
|
ccl_private ShaderData *sd,
|
|
const uint32_t path_flag)
|
|
{
|
|
/* 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) {
|
|
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
|