Cycles: random walk subsurface scattering.

It is basically brute force volume scattering within the mesh, but part
of the SSS code for faster performance. The main difference with actual
volume scattering is that we assume the boundaries are diffuse and that
all lighting is coming through this boundary from outside the volume.

This gives much more accurate results for thin features and low density.
Some challenges remain however:

* Significantly more noisy than BSSRDF. Adding Dwivedi sampling may help
  here, but it's unclear still how much it helps in real world cases.
* Due to this being a volumetric method, geometry like eyes or mouth can
  darken the skin on the outside. We may be able to reduce this effect,
  or users can compensate for it by reducing the scattering radius in
  such areas.
* Sharp corners are quite bright. This matches actual volume rendering
  and results in some other renderers, but maybe not so much real world
  objects.

Differential Revision: https://developer.blender.org/D3054
This commit is contained in:
Brecht Van Lommel 2018-01-21 14:04:22 +01:00
parent 3ab5ef7b4f
commit 0df9b2c715
22 changed files with 348 additions and 73 deletions

View File

@ -433,6 +433,9 @@ static ShaderNode *add_node(Scene *scene,
case BL::ShaderNodeSubsurfaceScattering::falloff_BURLEY:
subsurface->falloff = CLOSURE_BSSRDF_BURLEY_ID;
break;
case BL::ShaderNodeSubsurfaceScattering::falloff_RANDOM_WALK:
subsurface->falloff = CLOSURE_BSSRDF_RANDOM_WALK_ID;
break;
}
node = subsurface;

View File

@ -30,9 +30,7 @@
#include "kernel/closure/bsdf_principled_diffuse.h"
#include "kernel/closure/bsdf_principled_sheen.h"
#include "kernel/closure/bssrdf.h"
#ifdef __VOLUME__
# include "kernel/closure/volume.h"
#endif
#include "kernel/closure/volume.h"
CCL_NAMESPACE_BEGIN

View File

@ -408,7 +408,8 @@ ccl_device int bssrdf_setup(ShaderData *sd, Bssrdf *bssrdf, ClosureType type)
bssrdf->sharpness = saturate(bssrdf->sharpness);
if(type == CLOSURE_BSSRDF_BURLEY_ID ||
type == CLOSURE_BSSRDF_PRINCIPLED_ID)
type == CLOSURE_BSSRDF_PRINCIPLED_ID ||
type == CLOSURE_BSSRDF_RANDOM_WALK_ID)
{
bssrdf_burley_setup(bssrdf);
}

View File

@ -83,35 +83,45 @@ ccl_device float3 volume_henyey_greenstein_eval_phase(const ShaderClosure *sc, c
return make_float3(*pdf, *pdf, *pdf);
}
ccl_device float3 henyey_greenstrein_sample(float3 D, float g, float randu, float randv, float *pdf)
{
/* match pdf for small g */
float cos_theta;
bool isotropic = fabsf(g) < 1e-3f;
if(isotropic) {
cos_theta = (1.0f - 2.0f * randu);
if(pdf) {
*pdf = M_1_PI_F * 0.25f;
}
}
else {
float k = (1.0f - g * g) / (1.0f - g + 2.0f * g * randu);
cos_theta = (1.0f + g * g - k * k) / (2.0f * g);
if(pdf) {
*pdf = single_peaked_henyey_greenstein(cos_theta, g);
}
}
float sin_theta = safe_sqrtf(1.0f - cos_theta * cos_theta);
float phi = M_2PI_F * randv;
float3 dir = make_float3(sin_theta * cosf(phi), sin_theta * sinf(phi), cos_theta);
float3 T, B;
make_orthonormals(D, &T, &B);
dir = dir.x * T + dir.y * B + dir.z * D;
return dir;
}
ccl_device int volume_henyey_greenstein_sample(const ShaderClosure *sc, float3 I, float3 dIdx, float3 dIdy, float randu, float randv,
float3 *eval, float3 *omega_in, float3 *domega_in_dx, float3 *domega_in_dy, float *pdf)
{
const HenyeyGreensteinVolume *volume = (const HenyeyGreensteinVolume*)sc;
float g = volume->g;
float cos_phi, sin_phi, cos_theta;
/* match pdf for small g */
if(fabsf(g) < 1e-3f) {
cos_theta = (1.0f - 2.0f * randu);
*pdf = M_1_PI_F * 0.25f;
}
else {
float k = (1.0f - g * g) / (1.0f - g + 2.0f * g * randu);
cos_theta = (1.0f + g * g - k * k) / (2.0f * g);
*pdf = single_peaked_henyey_greenstein(cos_theta, g);
}
float sin_theta = safe_sqrtf(1.0f - cos_theta * cos_theta);
float phi = M_2PI_F * randv;
cos_phi = cosf(phi);
sin_phi = sinf(phi);
/* note that I points towards the viewer and so is used negated */
float3 T, B;
make_orthonormals(-I, &T, &B);
*omega_in = sin_theta * cos_phi * T + sin_theta * sin_phi * B + cos_theta * (-I);
*omega_in = henyey_greenstrein_sample(-I, g, randu, randv, pdf);
*eval = make_float3(*pdf, *pdf, *pdf); /* perfect importance sampling */
#ifdef __RAY_DIFFERENTIALS__

View File

@ -248,23 +248,30 @@ ccl_device_inline void motion_triangle_intersect_local(
motion_triangle_vertices(kg, local_object, prim, time, verts);
/* Ray-triangle intersection, unoptimized. */
float t, u, v;
if(ray_triangle_intersect(P,
dir,
tmax,
if(!ray_triangle_intersect(P,
dir,
tmax,
#if defined(__KERNEL_SSE2__) && defined(__KERNEL_SSE__)
(ssef*)verts,
(ssef*)verts,
#else
verts[0], verts[1], verts[2],
verts[0], verts[1], verts[2],
#endif
&u, &v, &t))
&u, &v, &t))
{
return;
}
int hit;
if(lcg_state) {
/* Record up to max_hits intersections. */
for(int i = min(max_hits, local_isect->num_hits) - 1; i >= 0; --i) {
if(local_isect->hits[i].t == t) {
return;
}
}
local_isect->num_hits++;
int hit;
if(local_isect->num_hits <= max_hits) {
hit = local_isect->num_hits - 1;
}
@ -277,18 +284,29 @@ ccl_device_inline void motion_triangle_intersect_local(
if(hit >= max_hits)
return;
}
/* Record intersection. */
Intersection *isect = &local_isect->hits[hit];
isect->t = t;
isect->u = u;
isect->v = v;
isect->prim = prim_addr;
isect->object = object;
isect->type = PRIMITIVE_MOTION_TRIANGLE;
/* Record geometric normal. */
local_isect->Ng[hit] = normalize(cross(verts[1] - verts[0],
verts[2] - verts[0]));
}
else {
/* Record closest intersection only. */
if(local_isect->num_hits && t > local_isect->hits[0].t) {
return;
}
hit = 0;
local_isect->num_hits = 1;
}
/* Record intersection. */
Intersection *isect = &local_isect->hits[hit];
isect->t = t;
isect->u = u;
isect->v = v;
isect->prim = prim_addr;
isect->object = object;
isect->type = PRIMITIVE_MOTION_TRIANGLE;
/* Record geometric normal. */
local_isect->Ng[hit] = normalize(cross(verts[1] - verts[0],
verts[2] - verts[0]));
}
#endif /* __BVH_LOCAL__ */

View File

@ -118,28 +118,40 @@ ccl_device_inline void triangle_intersect_local(
return;
}
for(int i = min(max_hits, local_isect->num_hits) - 1; i >= 0; --i) {
if(local_isect->hits[i].t == t) {
return;
int hit;
if(lcg_state) {
/* Record up to max_hits intersections. */
for(int i = min(max_hits, local_isect->num_hits) - 1; i >= 0; --i) {
if(local_isect->hits[i].t == t) {
return;
}
}
local_isect->num_hits++;
if(local_isect->num_hits <= max_hits) {
hit = local_isect->num_hits - 1;
}
else {
/* reservoir sampling: if we are at the maximum number of
* hits, randomly replace element or skip it */
hit = lcg_step_uint(lcg_state) % local_isect->num_hits;
if(hit >= max_hits)
return;
}
}
local_isect->num_hits++;
int hit;
if(local_isect->num_hits <= max_hits) {
hit = local_isect->num_hits - 1;
}
else {
/* reservoir sampling: if we are at the maximum number of
* hits, randomly replace element or skip it */
hit = lcg_step_uint(lcg_state) % local_isect->num_hits;
if(hit >= max_hits)
/* Record closest intersection only. */
if(local_isect->num_hits && t > local_isect->hits[0].t) {
return;
}
hit = 0;
local_isect->num_hits = 1;
}
/* record intersection */
/* Record intersection. */
Intersection *isect = &local_isect->hits[hit];
isect->prim = prim_addr;
isect->object = object;

View File

@ -32,12 +32,12 @@
#include "kernel/kernel_light.h"
#include "kernel/kernel_passes.h"
#ifdef __SUBSURFACE__
# include "kernel/kernel_subsurface.h"
#if defined(__VOLUME__) || defined(__SUBSURFACE__)
# include "kernel/kernel_volume.h"
#endif
#ifdef __VOLUME__
# include "kernel/kernel_volume.h"
#ifdef __SUBSURFACE__
# include "kernel/kernel_subsurface.h"
#endif
#include "kernel/kernel_path_state.h"

View File

@ -350,6 +350,7 @@ ccl_device void kernel_branched_path_subsurface_scatter(KernelGlobals *kg,
int num_hits = subsurface_scatter_multi_intersect(kg,
&ss_isect,
sd,
&hit_state,
sc,
&lcg_state,
bssrdf_u, bssrdf_v,

View File

@ -51,6 +51,7 @@ bool kernel_path_subsurface_scatter(
int num_hits = subsurface_scatter_multi_intersect(kg,
&ss_isect,
sd,
state,
sc,
&lcg_state,
bssrdf_u, bssrdf_v,

View File

@ -20,7 +20,6 @@ CCL_NAMESPACE_BEGIN
*
* BSSRDF Importance Sampling, SIGGRAPH 2013
* http://library.imageworks.com/pdfs/imageworks-library-BSSRDF-sampling.pdf
*
*/
ccl_device_inline float3 subsurface_scatter_eval(ShaderData *sd,
@ -41,7 +40,7 @@ ccl_device_inline float3 subsurface_scatter_eval(ShaderData *sd,
for(int i = 0; i < sd->num_closure; i++) {
sc = &sd->closure[i];
if(CLOSURE_IS_BSSRDF(sc->type)) {
if(CLOSURE_IS_DISK_BSSRDF(sc->type)) {
sample_weight_sum += sc->sample_weight;
}
}
@ -52,7 +51,7 @@ ccl_device_inline float3 subsurface_scatter_eval(ShaderData *sd,
for(int i = 0; i < sd->num_closure; i++) {
sc = &sd->closure[i];
if(CLOSURE_IS_BSSRDF(sc->type)) {
if(CLOSURE_IS_DISK_BSSRDF(sc->type)) {
/* in case of branched path integrate we sample all bssrdf's once,
* for path trace we pick one, so adjust pdf for that */
float sample_weight = (all)? 1.0f: sc->sample_weight * sample_weight_inv;
@ -166,7 +165,7 @@ ccl_device void subsurface_color_bump_blur(KernelGlobals *kg,
/* Subsurface scattering step, from a point on the surface to other
* nearby points on the same object.
*/
ccl_device_inline int subsurface_scatter_multi_intersect(
ccl_device_inline int subsurface_scatter_disk(
KernelGlobals *kg,
LocalIntersection *ss_isect,
ShaderData *sd,
@ -433,5 +432,202 @@ ccl_device void subsurface_scatter_step(KernelGlobals *kg, ShaderData *sd, ccl_a
subsurface_scatter_setup_diffuse_bsdf(kg, sd, sc, eval, (ss_isect.num_hits > 0), N);
}
/* Random walk subsurface scattering.
*
* "Practical and Controllable Subsurface Scattering for Production Path
* Tracing". Matt Jen-Yuan Chiang, Peter Kutz, Brent Burley. SIGGRAPH 2016. */
ccl_device void subsurface_random_walk_remap(
const float A,
const float d,
float *sigma_t,
float *sigma_s)
{
/* Compute attenuation and scattering coefficients from albedo. */
const float a = 1.0f - expf(A * (-5.09406f + A * (2.61188f - A * 4.31805f)));
const float s = 1.9f - A + 3.5f * sqr(A - 0.8f);
*sigma_t = 1.0f / fmaxf(d * s, 1e-16f);
*sigma_s = *sigma_t * a;
}
ccl_device void subsurface_random_walk_coefficients(
const ShaderClosure *sc,
float3 *sigma_t,
float3 *sigma_s,
float3 *weight)
{
const Bssrdf *bssrdf = (const Bssrdf*)sc;
const float3 A = bssrdf->albedo;
const float3 d = bssrdf->radius;
float sigma_t_x, sigma_t_y, sigma_t_z;
float sigma_s_x, sigma_s_y, sigma_s_z;
subsurface_random_walk_remap(A.x, d.x, &sigma_t_x, &sigma_s_x);
subsurface_random_walk_remap(A.y, d.y, &sigma_t_y, &sigma_s_y);
subsurface_random_walk_remap(A.z, d.z, &sigma_t_z, &sigma_s_z);
*sigma_t = make_float3(sigma_t_x, sigma_t_y, sigma_t_z);
*sigma_s = make_float3(sigma_s_x, sigma_s_y, sigma_s_z);
/* Closure mixing and Fresnel weights separate from albedo. */
*weight = safe_divide_color(bssrdf->weight, A);
}
ccl_device_noinline bool subsurface_random_walk(
KernelGlobals *kg,
LocalIntersection *ss_isect,
ShaderData *sd,
ccl_addr_space PathState *state,
const ShaderClosure *sc,
const float bssrdf_u,
const float bssrdf_v)
{
/* Sample diffuse surface scatter into the object. */
float3 D;
float pdf;
sample_cos_hemisphere(-sd->N, bssrdf_u, bssrdf_v, &D, &pdf);
if(dot(-sd->Ng, D) <= 0.0f) {
return 0;
}
/* Convert subsurface to volume coefficients. */
float3 sigma_t, sigma_s;
float3 throughput = make_float3(1.0f, 1.0f, 1.0f);
subsurface_random_walk_coefficients(sc, &sigma_t, &sigma_s, &throughput);
/* Setup ray. */
#ifdef __SPLIT_KERNEL__
Ray ray_object = ss_isect->ray;
Ray *ray = &ray_object;
#else
Ray *ray = &ss_isect->ray;
#endif
ray->P = ray_offset(sd->P, -sd->Ng);
ray->D = D;
ray->t = FLT_MAX;
ray->time = sd->time;
/* Modify state for RNGs, decorrelated from other paths. */
uint prev_rng_offset = state->rng_offset;
uint prev_rng_hash = state->rng_hash;
state->rng_hash = cmj_hash(state->rng_hash + state->rng_offset, 0xdeadbeef);
/* Random walk until we hit the surface again. */
bool hit = false;
for(int bounce = 0; bounce < BSSRDF_MAX_BOUNCES; bounce++) {
/* Advance random number offset. */
state->rng_offset += PRNG_BOUNCE_NUM;
if(bounce > 0) {
/* Sample scattering direction. */
const float anisotropy = 0.0f;
float scatter_u, scatter_v;
path_state_rng_2D(kg, state, PRNG_BSDF_U, &scatter_u, &scatter_v);
ray->D = henyey_greenstrein_sample(ray->D, anisotropy, scatter_u, scatter_v, NULL);
}
/* Sample color channel, use MIS with balance heuristic. */
float rphase = path_state_rng_1D(kg, state, PRNG_PHASE_CHANNEL);
float3 albedo = safe_divide_color(sigma_s, sigma_t);
float3 channel_pdf;
int channel = kernel_volume_sample_channel(albedo, throughput, rphase, &channel_pdf);
/* Distance sampling. */
float rdist = path_state_rng_1D(kg, state, PRNG_SCATTER_DISTANCE);
float sample_sigma_t = kernel_volume_channel_get(sigma_t, channel);
float t = -logf(1.0f - rdist)/sample_sigma_t;
ray->t = t;
scene_intersect_local(kg, *ray, ss_isect, sd->object, NULL, 1);
hit = (ss_isect->num_hits > 0);
if(hit) {
/* Compute world space distance to surface hit. */
float3 D = ray->D;
object_inverse_dir_transform(kg, sd, &D);
D = normalize(D) * ss_isect->hits[0].t;
object_dir_transform(kg, sd, &D);
t = len(D);
}
/* Advance to new scatter location. */
ray->P += t * ray->D;
/* Update throughput. */
float3 transmittance = volume_color_transmittance(sigma_t, t);
float pdf = dot(channel_pdf, (hit)? transmittance: sigma_t * transmittance);
throughput *= ((hit)? transmittance: sigma_s * transmittance) / pdf;
if(hit) {
/* If we hit the surface, we are done. */
break;
}
/* Russian roulette. */
float terminate = path_state_rng_1D(kg, state, PRNG_TERMINATE);
float probability = min(max3(fabs(throughput)), 1.0f);
if(terminate >= probability) {
break;
}
throughput /= probability;
}
kernel_assert(isfinite_safe(throughput.x) &&
isfinite_safe(throughput.y) &&
isfinite_safe(throughput.z));
state->rng_offset = prev_rng_offset;
state->rng_hash = prev_rng_hash;
/* Return number of hits in ss_isect. */
if(!hit) {
return 0;
}
/* TODO: gain back performance lost from merging with disk BSSRDF. We
* only need to return on hit so this indirect ray push/pop overhead
* is not actually needed, but it does keep the code simpler. */
ss_isect->weight[0] = throughput;
#ifdef __SPLIT_KERNEL__
ss_isect->ray = *ray;
#endif
return 1;
}
ccl_device_inline int subsurface_scatter_multi_intersect(
KernelGlobals *kg,
LocalIntersection *ss_isect,
ShaderData *sd,
ccl_addr_space PathState *state,
const ShaderClosure *sc,
uint *lcg_state,
float bssrdf_u,
float bssrdf_v,
bool all)
{
if(CLOSURE_IS_DISK_BSSRDF(sc->type)) {
return subsurface_scatter_disk(kg,
ss_isect,
sd,
sc,
lcg_state,
bssrdf_u,
bssrdf_v,
all);
}
else {
return subsurface_random_walk(kg,
ss_isect,
sd,
state,
sc,
bssrdf_u,
bssrdf_v);
}
}
CCL_NAMESPACE_END

View File

@ -46,6 +46,7 @@ CCL_NAMESPACE_BEGIN
#define BSSRDF_MIN_RADIUS 1e-8f
#define BSSRDF_MAX_HITS 4
#define BSSRDF_MAX_BOUNCES 256
#define LOCAL_MAX_HITS 4
#define BECKMANN_TABLE_SIZE 256

View File

@ -35,6 +35,8 @@ typedef struct VolumeShaderCoefficients {
float3 emission;
} VolumeShaderCoefficients;
#ifdef __VOLUME__
/* evaluate shader to get extinction coefficient at P */
ccl_device_inline bool volume_shader_extinction_sample(KernelGlobals *kg,
ShaderData *sd,
@ -92,6 +94,8 @@ ccl_device_inline bool volume_shader_sample(KernelGlobals *kg,
return true;
}
#endif /* __VOLUME__ */
ccl_device float3 volume_color_transmittance(float3 sigma, float t)
{
return make_float3(expf(-sigma.x * t), expf(-sigma.y * t), expf(-sigma.z * t));
@ -102,6 +106,8 @@ ccl_device float kernel_volume_channel_get(float3 value, int channel)
return (channel == 0)? value.x: ((channel == 1)? value.y: value.z);
}
#ifdef __VOLUME__
ccl_device bool volume_stack_is_heterogeneous(KernelGlobals *kg, ccl_addr_space VolumeStack *stack)
{
for(int i = 0; stack[i].shader != SHADER_NONE; i++) {
@ -239,6 +245,8 @@ ccl_device_noinline void kernel_volume_shadow(KernelGlobals *kg,
kernel_volume_shadow_homogeneous(kg, state, ray, shadow_sd, throughput);
}
#endif /* __VOLUME__ */
/* Equi-angular sampling as in:
* "Importance Sampling Techniques for Path Tracing in Participating Media" */
@ -369,6 +377,8 @@ ccl_device int kernel_volume_sample_channel(float3 albedo, float3 throughput, fl
}
}
#ifdef __VOLUME__
/* homogeneous volume: assume shader evaluation at the start gives
* the volume shading coefficient for the entire line segment */
ccl_device VolumeIntegrateResult kernel_volume_integrate_homogeneous(
@ -1346,4 +1356,6 @@ ccl_device_inline void kernel_volume_clean_stack(KernelGlobals *kg,
}
}
#endif /* __VOLUME__ */
CCL_NAMESPACE_END

View File

@ -52,6 +52,7 @@ static ustring u_cubic("cubic");
static ustring u_gaussian("gaussian");
static ustring u_burley("burley");
static ustring u_principled("principled");
static ustring u_random_walk("random_walk");
class CBSSRDFClosure : public CClosurePrimitive {
public:
@ -79,6 +80,9 @@ public:
else if (method == u_principled) {
alloc(sd, path_flag, weight, CLOSURE_BSSRDF_PRINCIPLED_ID);
}
else if (method == u_random_walk) {
alloc(sd, path_flag, weight, CLOSURE_BSSRDF_RANDOM_WALK_ID);
}
}
void alloc(ShaderData *sd, int path_flag, float3 weight, ClosureType type)

View File

@ -30,7 +30,9 @@ shader node_subsurface_scattering(
BSSRDF = Color * bssrdf("gaussian", Normal, Scale * Radius, Color, "texture_blur", TextureBlur);
else if (falloff == "cubic")
BSSRDF = Color * bssrdf("cubic", Normal, Scale * Radius, Color, "texture_blur", TextureBlur, "sharpness", Sharpness);
else
else if (falloff == "burley")
BSSRDF = Color * bssrdf("burley", Normal, Scale * Radius, Color, "texture_blur", TextureBlur);
else
BSSRDF = Color * bssrdf("random_walk", Normal, Scale * Radius, Color, "texture_blur", TextureBlur);
}

View File

@ -85,6 +85,7 @@ ccl_device_noinline bool kernel_split_branched_path_subsurface_indirect_light_it
branched_state->num_hits = subsurface_scatter_multi_intersect(kg,
&ss_isect_private,
sd,
hit_state,
sc,
&lcg_state,
bssrdf_u, bssrdf_v,

View File

@ -764,7 +764,8 @@ ccl_device void svm_node_closure_bsdf(KernelGlobals *kg, ShaderData *sd, float *
#ifdef __SUBSURFACE__
case CLOSURE_BSSRDF_CUBIC_ID:
case CLOSURE_BSSRDF_GAUSSIAN_ID:
case CLOSURE_BSSRDF_BURLEY_ID: {
case CLOSURE_BSSRDF_BURLEY_ID:
case CLOSURE_BSSRDF_RANDOM_WALK_ID: {
float3 weight = sd->svm_closure_weight * mix_weight;
Bssrdf *bssrdf = bssrdf_alloc(sd, weight);

View File

@ -446,6 +446,7 @@ typedef enum ClosureType {
CLOSURE_BSSRDF_GAUSSIAN_ID,
CLOSURE_BSSRDF_PRINCIPLED_ID,
CLOSURE_BSSRDF_BURLEY_ID,
CLOSURE_BSSRDF_RANDOM_WALK_ID,
/* Other */
CLOSURE_HOLDOUT_ID,
@ -477,8 +478,9 @@ typedef enum ClosureType {
#define CLOSURE_IS_BSDF_MICROFACET(type) ((type >= CLOSURE_BSDF_MICROFACET_GGX_ID && type <= CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ANISO_ID) ||\
(type >= CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID && type <= CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID) ||\
(type == CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_FRESNEL_ID))
#define CLOSURE_IS_BSDF_OR_BSSRDF(type) (type <= CLOSURE_BSSRDF_BURLEY_ID)
#define CLOSURE_IS_BSSRDF(type) (type >= CLOSURE_BSSRDF_CUBIC_ID && type <= CLOSURE_BSSRDF_BURLEY_ID)
#define CLOSURE_IS_BSDF_OR_BSSRDF(type) (type <= CLOSURE_BSSRDF_RANDOM_WALK_ID)
#define CLOSURE_IS_BSSRDF(type) (type >= CLOSURE_BSSRDF_CUBIC_ID && type <= CLOSURE_BSSRDF_RANDOM_WALK_ID)
#define CLOSURE_IS_DISK_BSSRDF(type) (type >= CLOSURE_BSSRDF_CUBIC_ID && type <= CLOSURE_BSSRDF_BURLEY_ID)
#define CLOSURE_IS_VOLUME(type) (type >= CLOSURE_VOLUME_ID && type <= CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID)
#define CLOSURE_IS_VOLUME_SCATTER(type) (type == CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID)
#define CLOSURE_IS_VOLUME_ABSORPTION(type) (type == CLOSURE_VOLUME_ABSORPTION_ID)

View File

@ -187,7 +187,10 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
max_samples = max(max_samples, volume_samples);
}
max_samples *= (max_bounce + transparent_max_bounce + 3 + BSSRDF_MAX_HITS);
uint total_bounces = max_bounce + transparent_max_bounce + 3 +
max(BSSRDF_MAX_HITS, BSSRDF_MAX_BOUNCES);
max_samples *= total_bounces;
int dimensions = PRNG_BASE_NUM + max_samples*PRNG_BOUNCE_NUM;
dimensions = min(dimensions, SOBOL_MAX_DIMENSIONS);

View File

@ -2525,6 +2525,7 @@ NODE_DEFINE(SubsurfaceScatteringNode)
falloff_enum.insert("cubic", CLOSURE_BSSRDF_CUBIC_ID);
falloff_enum.insert("gaussian", CLOSURE_BSSRDF_GAUSSIAN_ID);
falloff_enum.insert("burley", CLOSURE_BSSRDF_BURLEY_ID);
falloff_enum.insert("random_walk", CLOSURE_BSSRDF_RANDOM_WALK_ID);
SOCKET_ENUM(falloff, "Falloff", falloff_enum, CLOSURE_BSSRDF_BURLEY_ID);
SOCKET_IN_FLOAT(scale, "Scale", 0.01f);
SOCKET_IN_VECTOR(radius, "Radius", make_float3(0.1f, 0.1f, 0.1f));

View File

@ -59,6 +59,7 @@ ccl_device_inline float3 mix(const float3& a, const float3& b, float t);
ccl_device_inline float3 rcp(const float3& a);
#endif /* !__KERNEL_OPENCL__ */
ccl_device_inline float min3(float3 a);
ccl_device_inline float max3(float3 a);
ccl_device_inline float len(const float3 a);
ccl_device_inline float len_squared(const float3 a);
@ -285,6 +286,11 @@ ccl_device_inline float3 rcp(const float3& a)
}
#endif /* !__KERNEL_OPENCL__ */
ccl_device_inline float min3(float3 a)
{
return min(min(a.x, a.y), a.z);
}
ccl_device_inline float max3(float3 a)
{
return max(max(a.x, a.y), a.z);

View File

@ -1074,6 +1074,7 @@ enum {
SHD_SUBSURFACE_CUBIC = 1,
SHD_SUBSURFACE_GAUSSIAN = 2,
SHD_SUBSURFACE_BURLEY = 3,
SHD_SUBSURFACE_RANDOM_WALK = 4,
};
/* blur node */

View File

@ -4444,6 +4444,7 @@ static void def_sh_subsurface(StructRNA *srna)
{SHD_SUBSURFACE_CUBIC, "CUBIC", 0, "Cubic", "Simple cubic falloff function"},
{SHD_SUBSURFACE_GAUSSIAN, "GAUSSIAN", 0, "Gaussian", "Normal distribution, multiple can be combined to fit more complex profiles"},
{SHD_SUBSURFACE_BURLEY, "BURLEY", 0, "Christensen-Burley", "Approximation to physically based volume scattering"},
{SHD_SUBSURFACE_RANDOM_WALK, "RANDOM_WALK", 0, "Random Walk", "Volumetric approximation to physically based volume scattering"},
{0, NULL, 0, NULL, NULL}
};