Cycles Volume Render: heterogeneous (textured) volumes support.

Volumes can now have textured colors and density. There is a Volume Sampling
panel in the Render properties with these settings:

* Step size: distance between volume shader samples when rendering the volume.
  Lower values give more accurate and detailed results but also increased render
  time.
* Max steps: maximum number of steps through the volume before giving up, to
  protect from extremely long render times with big objects or small step sizes.

This is much more compute intensive than homogeneous volume, so when you are not
using a texture you should enable the Homogeneous Volume option in the material
or world for faster rendering.

One important missing feature is that Generated texture coordinates are not yet
working in volumes, and they are the default coordinates for nearly all texture
nodes. So until that works you need to plug in object texture coordinates or a
world space position.

This is work by "storm", Stuart Broadfoot, Thomas Dinges and myself.
This commit is contained in:
Brecht Van Lommel 2013-12-29 22:19:38 +01:00
parent af128c4c96
commit 889d77e6f6
14 changed files with 296 additions and 56 deletions

@ -302,6 +302,20 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
default=True,
)
cls.volume_step_size = FloatProperty(
name="Step Size",
description="Distance between volume shader samples when rendering the volume. Lower values give more accurate and detailed results but also increased render time.",
default=0.1,
min=0.0000001, max=100000.0
)
cls.volume_max_steps = IntProperty(
name="Max Steps",
description="Maximum number of steps through the volume before giving up, to protect from extremely long render times with big objects or small step sizes.",
default=1024,
min=2, max=65536
)
cls.film_exposure = FloatProperty(
name="Exposure",
description="Image brightness scale",
@ -509,8 +523,8 @@ class CyclesMaterialSettings(bpy.types.PropertyGroup):
)
cls.homogeneous_volume = BoolProperty(
name="Homogeneous Volume",
description="When using volume rendering, assume volume has the same density everywhere, "
"for faster rendering",
description="When using volume rendering, assume volume has the same density everywhere"
"(not using any textures), for faster rendering",
default=False,
)
@ -579,8 +593,8 @@ class CyclesWorldSettings(bpy.types.PropertyGroup):
)
cls.homogeneous_volume = BoolProperty(
name="Homogeneous Volume",
description="When using volume rendering, assume volume has the same density everywhere, "
"for faster rendering",
description="When using volume rendering, assume volume has the same density everywhere"
"(not using any textures), for faster rendering",
default=False,
)

@ -152,6 +152,21 @@ class CyclesRender_PT_sampling(CyclesButtonsPanel, Panel):
draw_samples_info(layout, cscene)
class CyclesRender_PT_volume_sampling(CyclesButtonsPanel, Panel):
bl_label = "Volume Sampling"
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
scene = context.scene
cscene = scene.cycles
split = layout.split()
split.prop(cscene, "volume_step_size")
split.prop(cscene, "volume_max_steps")
class CyclesRender_PT_light_paths(CyclesButtonsPanel, Panel):
bl_label = "Light Paths"
bl_options = {'DEFAULT_CLOSED'}

@ -921,7 +921,7 @@ void BlenderSync::sync_materials(bool update_all)
PointerRNA cmat = RNA_pointer_get(&b_mat->ptr, "cycles");
shader->use_mis = get_boolean(cmat, "sample_as_light");
shader->use_transparent_shadow = get_boolean(cmat, "use_transparent_shadow");
shader->homogeneous_volume = get_boolean(cmat, "homogeneous_volume");
shader->heterogeneous_volume = !get_boolean(cmat, "homogeneous_volume");
shader->set_graph(graph);
shader->tag_update(scene);
@ -958,7 +958,7 @@ void BlenderSync::sync_world(bool update_all)
graph->connect(closure->output("Background"), out->input("Surface"));
PointerRNA cworld = RNA_pointer_get(&b_world.ptr, "cycles");
shader->homogeneous_volume = get_boolean(cworld, "homogeneous_volume");
shader->heterogeneous_volume = !get_boolean(cworld, "homogeneous_volume");
}
if(b_world) {

@ -171,6 +171,9 @@ void BlenderSync::sync_integrator()
integrator->transparent_min_bounce = get_int(cscene, "transparent_min_bounces");
integrator->transparent_shadows = get_boolean(cscene, "use_transparent_shadows");
integrator->volume_max_steps = get_int(cscene, "volume_max_steps");
integrator->volume_step_size = get_float(cscene, "volume_step_size");
integrator->no_caustics = get_boolean(cscene, "no_caustics");
integrator->filter_glossy = get_float(cscene, "blur_glossy");

@ -94,8 +94,9 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, RNG *rng, int sample, Ra
#ifdef __VOLUME__
/* volume attenuation, emission, scatter */
if(state.volume_stack[0].shader != SHADER_NO_ID) {
ray.t = (hit)? isect.t: FLT_MAX;
kernel_volume_integrate(kg, &state, &ray, L, &throughput);
Ray volume_ray = ray;
volume_ray.t = (hit)? isect.t: FLT_MAX;
kernel_volume_integrate(kg, &state, &volume_ray, L, &throughput);
}
#endif
@ -462,7 +463,7 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
int num_samples = 0;
#endif
path_state_init(kg, &state);
path_state_init(kg, &state, rng, sample);
/* path iteration */
for(;; rng_offset += PRNG_BOUNCE_NUM) {
@ -515,8 +516,9 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
#ifdef __VOLUME__
/* volume attenuation, emission, scatter */
if(state.volume_stack[0].shader != SHADER_NO_ID) {
ray.t = (hit)? isect.t: FLT_MAX;
kernel_volume_integrate(kg, &state, &ray, &L, &throughput);
Ray volume_ray = ray;
volume_ray.t = (hit)? isect.t: FLT_MAX;
kernel_volume_integrate(kg, &state, &volume_ray, &L, &throughput);
}
#endif
@ -988,7 +990,7 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in
int aa_samples = 0;
#endif
path_state_init(kg, &state);
path_state_init(kg, &state, rng, sample);
for(;; rng_offset += PRNG_BOUNCE_NUM) {
/* intersect scene */
@ -1018,8 +1020,9 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in
#ifdef __VOLUME__
/* volume attenuation, emission, scatter */
if(state.volume_stack[0].shader != SHADER_NO_ID) {
ray.t = (hit)? isect.t: FLT_MAX;
kernel_volume_integrate(kg, &state, &ray, &L, &throughput);
Ray volume_ray = ray;
volume_ray.t = (hit)? isect.t: FLT_MAX;
kernel_volume_integrate(kg, &state, &volume_ray, &L, &throughput);
}
#endif

@ -16,7 +16,7 @@
CCL_NAMESPACE_BEGIN
ccl_device_inline void path_state_init(KernelGlobals *kg, PathState *state)
ccl_device_inline void path_state_init(KernelGlobals *kg, PathState *state, RNG *rng, int sample)
{
state->flag = PATH_RAY_CAMERA|PATH_RAY_SINGULAR|PATH_RAY_MIS_SKIP;
state->bounce = 0;
@ -26,7 +26,15 @@ ccl_device_inline void path_state_init(KernelGlobals *kg, PathState *state)
state->transparent_bounce = 0;
#ifdef __VOLUME__
if(kernel_data.integrator.use_volumes) {
/* initialize volume stack with volume we are inside of */
kernel_volume_stack_init(kg, state->volume_stack);
/* seed RNG for cases where we can't use stratified samples */
state->rng_congruential = lcg_init(*rng + sample*0x51633e2d);
}
else {
state->volume_stack[0].shader = SHADER_NO_ID;
}
#endif
}

@ -18,8 +18,6 @@
CCL_NAMESPACE_BEGIN
typedef uint RNG;
#ifdef __SOBOL__
/* skip initial numbers that are not as well distributed, especially the
@ -192,10 +190,6 @@ ccl_device void path_rng_end(KernelGlobals *kg, ccl_global uint *rng_state, RNG
/* Linear Congruential Generator */
ccl_device float path_rng(KernelGlobals *kg, RNG& rng, int sample, int dimension)
{
}
ccl_device_inline float path_rng_1D(KernelGlobals *kg, RNG& rng, int sample, int num_samples, int dimension)
{
/* implicit mod 2^32 */

@ -74,7 +74,7 @@ ccl_device_inline bool shadow_blocked(KernelGlobals *kg, PathState *state, Ray *
#ifdef __VOLUME__
/* attenuation for last line segment towards light */
if(ps.volume_stack[0].shader != SHADER_NO_ID)
kernel_volume_get_shadow_attenuation(kg, &ps, ray, &throughput);
kernel_volume_shadow(kg, &ps, ray, &throughput);
#endif
*shadow *= throughput;
@ -89,7 +89,7 @@ ccl_device_inline bool shadow_blocked(KernelGlobals *kg, PathState *state, Ray *
if(ps.volume_stack[0].shader != SHADER_NO_ID) {
Ray segment_ray = *ray;
segment_ray.t = isect.t;
kernel_volume_get_shadow_attenuation(kg, &ps, &segment_ray, &throughput);
kernel_volume_shadow(kg, &ps, &segment_ray, &throughput);
}
#endif
@ -120,7 +120,7 @@ ccl_device_inline bool shadow_blocked(KernelGlobals *kg, PathState *state, Ray *
#ifdef __VOLUME__
else if(!result && state->volume_stack[0].shader != SHADER_NO_ID) {
/* apply attenuation from current volume shader */
kernel_volume_get_shadow_attenuation(kg, state, ray, shadow);
kernel_volume_shadow(kg, state, ray, shadow);
}
#endif
#endif

@ -153,6 +153,10 @@ CCL_NAMESPACE_BEGIN
#error "OpenCL: mismatch between advanced shading flags in device_opencl.cpp and kernel_types.h"
#endif
/* Random Numbers */
typedef uint RNG;
/* Shader Evaluation */
typedef enum ShaderEvalType {
@ -511,10 +515,10 @@ enum ShaderDataFlag {
SD_HAS_TRANSPARENT_SHADOW = 2048, /* has transparent shadow */
SD_HAS_VOLUME = 4096, /* has volume shader */
SD_HAS_ONLY_VOLUME = 8192, /* has only volume shader, no surface */
SD_HOMOGENEOUS_VOLUME = 16384, /* has homogeneous volume */
SD_HETEROGENEOUS_VOLUME = 16384, /* has heterogeneous volume */
SD_HAS_BSSRDF_BUMP = 32768, /* bssrdf normal uses bump */
SD_SHADER_FLAGS = (SD_USE_MIS|SD_HAS_TRANSPARENT_SHADOW|SD_HAS_VOLUME|SD_HAS_ONLY_VOLUME|SD_HOMOGENEOUS_VOLUME|SD_HAS_BSSRDF_BUMP),
SD_SHADER_FLAGS = (SD_USE_MIS|SD_HAS_TRANSPARENT_SHADOW|SD_HAS_VOLUME|SD_HAS_ONLY_VOLUME|SD_HETEROGENEOUS_VOLUME|SD_HAS_BSSRDF_BUMP),
/* object flags */
SD_HOLDOUT_MASK = 65536, /* holdout for camera rays */
@ -625,6 +629,7 @@ typedef struct PathState {
int transparent_bounce;
#ifdef __VOLUME__
RNG rng_congruential;
VolumeStack volume_stack[VOLUME_STACK_SIZE];
#endif
} PathState;
@ -806,6 +811,9 @@ typedef struct KernelIntegrator {
/* volume render */
int use_volumes;
int volume_max_steps;
float volume_step_size;
int pad1, pad2;
} KernelIntegrator;
typedef struct KernelBVH {

@ -88,54 +88,124 @@ ccl_device float3 volume_color_attenuation(float3 sigma, float t)
return make_float3(expf(-sigma.x * t), expf(-sigma.y * t), expf(-sigma.z * t));
}
/* Volumetric Shadows */
/* get the volume attenuation over line segment defined by ray, with the
* assumption that there are no surfaces blocking light between the endpoints */
ccl_device void kernel_volume_get_shadow_attenuation(KernelGlobals *kg, PathState *state, Ray *ray, float3 *throughput)
ccl_device bool volume_stack_is_heterogeneous(KernelGlobals *kg, VolumeStack *stack)
{
ShaderData sd;
shader_setup_from_volume(kg, &sd, ray, state->bounce);
for(int i = 0; stack[i].shader != SHADER_NO_ID; i++) {
int shader_flag = kernel_tex_fetch(__shader_flag, (stack[i].shader & SHADER_MASK)*2);
if(shader_flag & SD_HETEROGENEOUS_VOLUME)
return true;
}
return false;
}
/* Volumetric Shadows
*
* These functions are used to attenuate shadow rays to lights. Both absorption
* and scattering will block light, represented by the extinction coefficient. */
/* homogenous volume: assume shader evaluation at the starts gives
* the extinction coefficient for the entire line segment */
ccl_device void kernel_volume_shadow_homogeneous(KernelGlobals *kg, PathState *state, Ray *ray, ShaderData *sd, float3 *throughput)
{
ShaderContext ctx = SHADER_CONTEXT_SHADOW;
int path_flag = PATH_RAY_SHADOW;
float3 sigma_t;
/* homogenous volume: assume shader evaluation at the starts gives
* the extinction coefficient for the entire line segment */
if(!volume_shader_extinction_sample(kg, &sd, state->volume_stack, path_flag, ctx, ray->P, &sigma_t))
return;
if(volume_shader_extinction_sample(kg, sd, state->volume_stack, path_flag, ctx, ray->P, &sigma_t))
*throughput *= volume_color_attenuation(sigma_t, ray->t);
}
/* Volumetric Path */
/* heterogeneous volume: integrate stepping through the volume until we
* reach the end, get absorbed entirely, or run out of iterations */
ccl_device void kernel_volume_shadow_heterogeneous(KernelGlobals *kg, PathState *state, Ray *ray, ShaderData *sd, float3 *throughput)
{
ShaderContext ctx = SHADER_CONTEXT_SHADOW;
int path_flag = PATH_RAY_SHADOW;
float3 tp = *throughput;
const float tp_eps = 1e-10f; /* todo: this is likely not the right value */
/* get the volume attenuation and emission over line segment defined by
* ray, with the assumption that there are no surfaces blocking light
* between the endpoints */
ccl_device VolumeIntegrateResult kernel_volume_integrate(KernelGlobals *kg, PathState *state, Ray *ray, PathRadiance *L, float3 *throughput)
/* prepare for stepping */
int max_steps = kernel_data.integrator.volume_max_steps;
float step = kernel_data.integrator.volume_step_size;
float random_jitter_offset = lcg_step_float(&state->rng_congruential) * step;
/* compute extinction at the start */
float t = 0.0f;
float3 P = ray->P;
float3 sigma_t;
if(!volume_shader_extinction_sample(kg, sd, state->volume_stack, path_flag, ctx, P, &sigma_t))
sigma_t = make_float3(0.0f, 0.0f, 0.0f);
for(int i = 0; i < max_steps; i++) {
/* advance to new position */
float new_t = min(ray->t, t + random_jitter_offset + i * step);
float3 new_P = ray->P + ray->D * new_t;
float3 new_sigma_t;
/* compute attenuation over segment */
if(volume_shader_extinction_sample(kg, sd, state->volume_stack, path_flag, ctx, new_P, &new_sigma_t)) {
/* todo: we could avoid computing expf() for each step by summing,
* because exp(a)*exp(b) = exp(a+b), but we still want a quick
* tp_eps check too */
tp *= volume_color_attenuation(0.5f*(sigma_t + new_sigma_t), new_t - t);
/* stop if nearly all light blocked */
if(tp.x < tp_eps && tp.y < tp_eps && tp.z < tp_eps)
break;
sigma_t = new_sigma_t;
}
else {
/* skip empty space */
sigma_t = make_float3(0.0f, 0.0f, 0.0f);
}
/* stop if at the end of the volume */
t = new_t;
if(t == ray->t)
break;
}
*throughput = tp;
}
/* get the volume attenuation over line segment defined by ray, with the
* assumption that there are no surfaces blocking light between the endpoints */
ccl_device void kernel_volume_shadow(KernelGlobals *kg, PathState *state, Ray *ray, float3 *throughput)
{
ShaderData sd;
shader_setup_from_volume(kg, &sd, ray, state->bounce);
if(volume_stack_is_heterogeneous(kg, state->volume_stack))
kernel_volume_shadow_heterogeneous(kg, state, ray, &sd, throughput);
else
kernel_volume_shadow_homogeneous(kg, state, ray, &sd, throughput);
}
/* Volumetric Path */
/* homogenous volume: assume shader evaluation at the starts gives
* the volume shading coefficient for the entire line segment */
ccl_device VolumeIntegrateResult kernel_volume_integrate_homogeneous(KernelGlobals *kg, PathState *state, Ray *ray, ShaderData *sd, PathRadiance *L, float3 *throughput)
{
ShaderContext ctx = SHADER_CONTEXT_VOLUME;
int path_flag = PATH_RAY_SHADOW;
VolumeShaderCoefficients coeff;
/* homogenous volume: assume shader evaluation at the starts gives
* the extinction coefficient for the entire line segment */
if(!volume_shader_sample(kg, &sd, state->volume_stack, path_flag, ctx, ray->P, &coeff))
if(!volume_shader_sample(kg, sd, state->volume_stack, path_flag, ctx, ray->P, &coeff))
return VOLUME_PATH_MISSED;
/* todo: in principle the SD_EMISSION, SD_ABSORPTION and SD_SCATTER flags
* should ensure that one of the components is > 0 and so no division by
* zero occurs, however this needs to be double-checked and tested */
int closure_flag = sd.flag;
int closure_flag = sd->flag;
float t = ray->t;
/* compute attenuation from absorption (+ scattering for now) */
/* compute attenuation from absorption */
float3 attenuation;
if(closure_flag & SD_ABSORPTION)
@ -169,7 +239,121 @@ ccl_device VolumeIntegrateResult kernel_volume_integrate(KernelGlobals *kg, Path
return VOLUME_PATH_ATTENUATED;
}
/* Volume Stack */
/* heterogeneous volume: integrate stepping through the volume until we
* reach the end, get absorbed entirely, or run out of iterations */
ccl_device VolumeIntegrateResult kernel_volume_integrate_heterogeneous(KernelGlobals *kg, PathState *state, Ray *ray, ShaderData *sd, PathRadiance *L, float3 *throughput)
{
ShaderContext ctx = SHADER_CONTEXT_VOLUME;
int path_flag = PATH_RAY_SHADOW;
VolumeShaderCoefficients coeff;
float3 tp = *throughput;
const float tp_eps = 1e-10f; /* todo: this is likely not the right value */
/* prepare for stepping */
int max_steps = kernel_data.integrator.volume_max_steps;
float step = kernel_data.integrator.volume_step_size;
float random_jitter_offset = lcg_step_float(&state->rng_congruential) * step;
/* compute coefficients at the start */
float t = 0.0f;
float3 P = ray->P;
if(!volume_shader_sample(kg, sd, state->volume_stack, path_flag, ctx, P, &coeff)) {
coeff.sigma_a = make_float3(0.0f, 0.0f, 0.0f);
coeff.sigma_s = make_float3(0.0f, 0.0f, 0.0f);
coeff.emission = make_float3(0.0f, 0.0f, 0.0f);
}
for(int i = 0; i < max_steps; i++) {
/* advance to new position */
float new_t = min(ray->t, t + random_jitter_offset + i * step);
float3 new_P = ray->P + ray->D * new_t;
VolumeShaderCoefficients new_coeff;
/* compute attenuation over segment */
if(volume_shader_sample(kg, sd, state->volume_stack, path_flag, ctx, new_P, &new_coeff)) {
int closure_flag = sd->flag;
float dt = new_t - t;
/* compute attenuation from absorption */
float3 attenuation, sigma_a;
if(closure_flag & SD_ABSORPTION) {
/* todo: we could avoid computing expf() for each step by summing,
* because exp(a)*exp(b) = exp(a+b), but we still want a quick
* tp_eps check too */
sigma_a = 0.5f*(coeff.sigma_a + new_coeff.sigma_a);
attenuation = volume_color_attenuation(sigma_a, dt);
}
/* integrate emission attenuated by absorption
* integral E * exp(-sigma_a * t) from 0 to t = E * (1 - exp(-sigma_a * t))/sigma_a
* this goes to E * t as sigma_a goes to zero
*
* todo: we should use an epsilon to avoid precision issues near zero sigma_a */
if(closure_flag & SD_EMISSION) {
float3 emission = 0.5f*(coeff.emission + new_coeff.emission);
if(closure_flag & SD_ABSORPTION) {
emission.x *= (sigma_a.x > 0.0f)? (1.0f - attenuation.x)/sigma_a.x: dt;
emission.y *= (sigma_a.y > 0.0f)? (1.0f - attenuation.y)/sigma_a.y: dt;
emission.z *= (sigma_a.z > 0.0f)? (1.0f - attenuation.z)/sigma_a.z: dt;
}
else
emission *= t;
path_radiance_accum_emission(L, tp, emission, state->bounce);
}
/* modify throughput */
if(closure_flag & SD_ABSORPTION) {
tp *= attenuation;
/* stop if nearly all light blocked */
if(tp.x < tp_eps && tp.y < tp_eps && tp.z < tp_eps) {
tp = make_float3(0.0f, 0.0f, 0.0f);
break;
}
}
coeff = new_coeff;
}
else {
/* skip empty space */
coeff.sigma_a = make_float3(0.0f, 0.0f, 0.0f);
coeff.sigma_s = make_float3(0.0f, 0.0f, 0.0f);
coeff.emission = make_float3(0.0f, 0.0f, 0.0f);
}
/* stop if at the end of the volume */
t = new_t;
if(t == ray->t)
break;
}
*throughput = tp;
return VOLUME_PATH_ATTENUATED;
}
/* get the volume attenuation and emission over line segment defined by
* ray, with the assumption that there are no surfaces blocking light
* between the endpoints */
ccl_device VolumeIntegrateResult kernel_volume_integrate(KernelGlobals *kg, PathState *state, Ray *ray, PathRadiance *L, float3 *throughput)
{
ShaderData sd;
shader_setup_from_volume(kg, &sd, ray, state->bounce);
if(volume_stack_is_heterogeneous(kg, state->volume_stack))
return kernel_volume_integrate_heterogeneous(kg, state, ray, &sd, L, throughput);
else
return kernel_volume_integrate_homogeneous(kg, state, ray, &sd, L, throughput);
}
/* Volume Stack
*
* This is an array of object/shared ID's that the current segment of the path
* is inside of. */
ccl_device void kernel_volume_stack_init(KernelGlobals *kg, VolumeStack *stack)
{

@ -40,6 +40,9 @@ Integrator::Integrator()
transparent_probalistic = true;
transparent_shadows = false;
volume_max_steps = 1024;
volume_step_size = 0.1;
no_caustics = false;
filter_glossy = 0.0f;
seed = 0;
@ -93,6 +96,9 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
kintegrator->transparent_shadows = transparent_shadows;
kintegrator->volume_max_steps = volume_max_steps;
kintegrator->volume_step_size = volume_step_size;
kintegrator->no_caustics = no_caustics;
kintegrator->filter_glossy = (filter_glossy == 0.0f)? FLT_MAX: 1.0f/filter_glossy;
@ -158,6 +164,8 @@ bool Integrator::modified(const Integrator& integrator)
transparent_max_bounce == integrator.transparent_max_bounce &&
transparent_probalistic == integrator.transparent_probalistic &&
transparent_shadows == integrator.transparent_shadows &&
volume_max_steps == integrator.volume_max_steps &&
volume_step_size == integrator.volume_step_size &&
no_caustics == integrator.no_caustics &&
filter_glossy == integrator.filter_glossy &&
layer_flag == integrator.layer_flag &&

@ -40,6 +40,9 @@ public:
bool transparent_probalistic;
bool transparent_shadows;
int volume_max_steps;
float volume_step_size;
bool no_caustics;
float filter_glossy;

@ -43,7 +43,7 @@ Shader::Shader()
use_mis = true;
use_transparent_shadow = true;
homogeneous_volume = false;
heterogeneous_volume = true;
has_surface = false;
has_surface_transparent = false;
@ -240,8 +240,8 @@ void ShaderManager::device_update_common(Device *device, DeviceScene *dscene, Sc
* the case with camera inside volumes too */
flag |= SD_HAS_TRANSPARENT_SHADOW;
}
if(shader->homogeneous_volume)
flag |= SD_HOMOGENEOUS_VOLUME;
if(shader->heterogeneous_volume)
flag |= SD_HETEROGENEOUS_VOLUME;
if(shader->has_bssrdf_bump)
flag |= SD_HAS_BSSRDF_BUMP;
if(shader->has_converter_blackbody)

@ -62,7 +62,7 @@ public:
/* sampling */
bool use_mis;
bool use_transparent_shadow;
bool homogeneous_volume;
bool heterogeneous_volume;
/* synchronization */
bool need_update;