Cycles Volume Render: support for rendering of homogeneous volume with absorption.

This is the simplest possible volume rendering case, constant density inside
the volume and no scattering or emission. My plan is to tweak, verify and commit
more volume rendering effects one by one, doing it all at once makes it
difficult to verify correctness and track down bugs.

Documentation is here:
http://wiki.blender.org/index.php/Doc:2.6/Manual/Render/Cycles/Materials/Volume

Currently this hooks into path tracing in 3 ways, which should get us pretty
far until we add more advanced light sampling. These 3 hooks are repeated in
the path tracing, branched path tracing and transparent shadow code:

* Determine active volume shader at start of the path
* Change active volume shader on transmission through a surface
* Light attenuation over line segments between camera, surfaces and background

This is work by "storm", Stuart Broadfoot, Thomas Dinges and myself.
This commit is contained in:
Brecht Van Lommel 2013-12-28 16:56:19 +01:00
parent 133f770ab3
commit e369a5c485
15 changed files with 532 additions and 134 deletions

@ -577,6 +577,12 @@ class CyclesWorldSettings(bpy.types.PropertyGroup):
min=1, max=10000, min=1, max=10000,
default=4, default=4,
) )
cls.homogeneous_volume = BoolProperty(
name="Homogeneous Volume",
description="When using volume rendering, assume volume has the same density everywhere, "
"for faster rendering",
default=False,
)
@classmethod @classmethod
def unregister(cls): def unregister(cls):

@ -780,9 +780,8 @@ class CyclesWorld_PT_volume(CyclesButtonsPanel, Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
# world = context.world world = context.world
# world and world.node_tree and CyclesButtonsPanel.poll(context) return world and world.node_tree and CyclesButtonsPanel.poll(context)
return False
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
@ -790,6 +789,8 @@ class CyclesWorld_PT_volume(CyclesButtonsPanel, Panel):
world = context.world world = context.world
panel_node_draw(layout, world, 'OUTPUT_WORLD', 'Volume') panel_node_draw(layout, world, 'OUTPUT_WORLD', 'Volume')
layout.prop(world.cycles, "homogeneous_volume")
class CyclesWorld_PT_ambient_occlusion(CyclesButtonsPanel, Panel): class CyclesWorld_PT_ambient_occlusion(CyclesButtonsPanel, Panel):
bl_label = "Ambient Occlusion" bl_label = "Ambient Occlusion"
@ -926,9 +927,8 @@ class CyclesMaterial_PT_volume(CyclesButtonsPanel, Panel):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
# mat = context.material mat = context.material
# mat and mat.node_tree and CyclesButtonsPanel.poll(context) return mat and mat.node_tree and CyclesButtonsPanel.poll(context)
return False
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout

@ -956,6 +956,9 @@ void BlenderSync::sync_world(bool update_all)
out = graph->output(); out = graph->output();
graph->connect(closure->output("Background"), out->input("Surface")); 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");
} }
if(b_world) { if(b_world) {

@ -51,6 +51,7 @@ set(SRC_HEADERS
kernel_textures.h kernel_textures.h
kernel_triangle.h kernel_triangle.h
kernel_types.h kernel_types.h
kernel_volume.h
) )
set(SRC_CLOSURE_HEADERS set(SRC_CLOSURE_HEADERS

@ -40,6 +40,10 @@
#include "kernel_subsurface.h" #include "kernel_subsurface.h"
#endif #endif
#ifdef __VOLUME__
#include "kernel_volume.h"
#endif
#include "kernel_shadow.h" #include "kernel_shadow.h"
CCL_NAMESPACE_BEGIN CCL_NAMESPACE_BEGIN
@ -87,6 +91,15 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, RNG *rng, int sample, Ra
} }
#endif #endif
#ifdef __VOLUME__
/* volume attenuation */
if(state.volume_shader != SHADER_NO_ID) {
Ray segment_ray = ray;
segment_ray.t = (hit)? isect.t: FLT_MAX;
throughput *= kernel_volume_get_shadow_attenuation(kg, &state, &segment_ray, state.volume_shader);
}
#endif
if(!hit) { if(!hit) {
#ifdef __BACKGROUND__ #ifdef __BACKGROUND__
/* sample background shader */ /* sample background shader */
@ -235,9 +248,7 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, RNG *rng, int sample, Ra
#endif #endif
/* no BSDF? we can stop here */ /* no BSDF? we can stop here */
if(!(sd.flag & SD_BSDF)) if(sd.flag & SD_BSDF) {
break;
/* sample BSDF */ /* sample BSDF */
float bsdf_pdf; float bsdf_pdf;
BsdfEval bsdf_eval; BsdfEval bsdf_eval;
@ -276,6 +287,34 @@ ccl_device void kernel_path_indirect(KernelGlobals *kg, RNG *rng, int sample, Ra
ray.dP = sd.dP; ray.dP = sd.dP;
ray.dD = bsdf_domega_in; ray.dD = bsdf_domega_in;
#endif #endif
#ifdef __VOLUME__
/* enter/exit volume */
if(label & LABEL_TRANSMIT)
kernel_volume_enter_exit(kg, &sd, &state.volume_shader);
#endif
}
#ifdef __VOLUME__
else if(sd.flag & SD_HAS_ONLY_VOLUME) {
/* no surface shader but have a volume shader? act transparent */
/* update path state, count as transparent */
path_state_next(kg, &state, LABEL_TRANSPARENT);
/* setup ray position, direction stays unchanged */
ray.P = ray_offset(sd.P, -sd.Ng);
#ifdef __RAY_DIFFERENTIALS__
ray.dP = sd.dP;
#endif
/* enter/exit volume */
kernel_volume_enter_exit(kg, &sd, &state.volume_shader);
}
#endif
else {
/* no bsdf or volume? we're done */
break;
}
} }
} }
@ -324,9 +363,7 @@ ccl_device_inline bool kernel_path_integrate_lighting(KernelGlobals *kg, RNG *rn
#endif #endif
/* no BSDF? we can stop here */ /* no BSDF? we can stop here */
if(!(sd->flag & SD_BSDF)) if(sd->flag & SD_BSDF) {
return false;
/* sample BSDF */ /* sample BSDF */
float bsdf_pdf; float bsdf_pdf;
BsdfEval bsdf_eval; BsdfEval bsdf_eval;
@ -371,8 +408,36 @@ ccl_device_inline bool kernel_path_integrate_lighting(KernelGlobals *kg, RNG *rn
ray->dD = bsdf_domega_in; ray->dD = bsdf_domega_in;
#endif #endif
#ifdef __VOLUME__
/* enter/exit volume */
if(label & LABEL_TRANSMIT)
kernel_volume_enter_exit(kg, sd, &state->volume_shader);
#endif
return true; return true;
} }
#ifdef __VOLUME__
else if(sd->flag & SD_HAS_ONLY_VOLUME) {
/* no surface shader but have a volume shader? act transparent */
/* update path state, count as transparent */
path_state_next(kg, state, LABEL_TRANSPARENT);
/* setup ray position, direction stays unchanged */
ray->P = ray_offset(sd->P, -sd->Ng);
#ifdef __RAY_DIFFERENTIALS__
ray->dP = sd->dP;
#endif
/* enter/exit volume */
kernel_volume_enter_exit(kg, sd, &state->volume_shader);
return true;
}
#endif
else {
/* no bsdf or volume? */
return false;
}
}
#endif #endif
@ -398,7 +463,7 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
int num_samples = 0; int num_samples = 0;
#endif #endif
path_state_init(&state); path_state_init(kg, &state);
/* path iteration */ /* path iteration */
for(;; rng_offset += PRNG_BOUNCE_NUM) { for(;; rng_offset += PRNG_BOUNCE_NUM) {
@ -448,6 +513,15 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
} }
#endif #endif
#ifdef __VOLUME__
/* volume attenuation */
if(state.volume_shader != SHADER_NO_ID) {
Ray segment_ray = ray;
segment_ray.t = (hit)? isect.t: FLT_MAX;
throughput *= kernel_volume_get_shadow_attenuation(kg, &state, &segment_ray, state.volume_shader);
}
#endif
if(!hit) { if(!hit) {
/* eval background shader if nothing hit */ /* eval background shader if nothing hit */
if(kernel_data.background.transparent && (state.flag & PATH_RAY_CAMERA)) { if(kernel_data.background.transparent && (state.flag & PATH_RAY_CAMERA)) {
@ -652,10 +726,7 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
} }
#endif #endif
/* no BSDF? we can stop here */ if(sd.flag & SD_BSDF) {
if(!(sd.flag & SD_BSDF))
break;
/* sample BSDF */ /* sample BSDF */
float bsdf_pdf; float bsdf_pdf;
BsdfEval bsdf_eval; BsdfEval bsdf_eval;
@ -690,15 +761,45 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
ray.P = ray_offset(sd.P, (label & LABEL_TRANSMIT)? -sd.Ng: sd.Ng); ray.P = ray_offset(sd.P, (label & LABEL_TRANSMIT)? -sd.Ng: sd.Ng);
ray.D = bsdf_omega_in; ray.D = bsdf_omega_in;
if(state.bounce == 0)
ray.t -= sd.ray_length; /* clipping works through transparent */
else
ray.t = FLT_MAX;
#ifdef __RAY_DIFFERENTIALS__ #ifdef __RAY_DIFFERENTIALS__
ray.dP = sd.dP; ray.dP = sd.dP;
ray.dD = bsdf_domega_in; ray.dD = bsdf_domega_in;
#endif #endif
#ifdef __VOLUME__
/* enter/exit volume */
if(label & LABEL_TRANSMIT)
kernel_volume_enter_exit(kg, &sd, &state.volume_shader);
#endif
}
#ifdef __VOLUME__
else if(sd.flag & SD_HAS_ONLY_VOLUME) {
/* no surface shader but have a volume shader? act transparent */
/* update path state, count as transparent */
path_state_next(kg, &state, LABEL_TRANSPARENT);
/* setup ray position, direction stays unchanged */
ray.P = ray_offset(sd.P, -sd.Ng);
#ifdef __RAY_DIFFERENTIALS__
ray.dP = sd.dP;
#endif
/* enter/exit volume */
kernel_volume_enter_exit(kg, &sd, &state.volume_shader);
}
#endif
else {
/* no bsdf or volume? we're done */
break;
}
/* adjust ray distance for clipping */
if(state.bounce == 0)
ray.t -= sd.ray_length; /* clipping works through transparent */
else
ray.t = FLT_MAX;
} }
float3 L_sum = path_radiance_sum(kg, &L); float3 L_sum = path_radiance_sum(kg, &L);
@ -853,6 +954,12 @@ ccl_device_noinline void kernel_branched_path_integrate_lighting(KernelGlobals *
bsdf_ray.time = sd->time; bsdf_ray.time = sd->time;
#endif #endif
#ifdef __VOLUME__
/* enter/exit volume */
if(label & LABEL_TRANSMIT)
kernel_volume_enter_exit(kg, sd, &ps.volume_shader);
#endif
kernel_path_indirect(kg, rng, sample*num_samples + j, bsdf_ray, buffer, kernel_path_indirect(kg, rng, sample*num_samples + j, bsdf_ray, buffer,
tp*num_samples_inv, num_samples, aa_samples*num_samples, tp*num_samples_inv, num_samples, aa_samples*num_samples,
min_ray_pdf, bsdf_pdf, ps, rng_offset+PRNG_BOUNCE_NUM, L); min_ray_pdf, bsdf_pdf, ps, rng_offset+PRNG_BOUNCE_NUM, L);
@ -883,7 +990,7 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in
int aa_samples = 0; int aa_samples = 0;
#endif #endif
path_state_init(&state); path_state_init(kg, &state);
for(;; rng_offset += PRNG_BOUNCE_NUM) { for(;; rng_offset += PRNG_BOUNCE_NUM) {
/* intersect scene */ /* intersect scene */
@ -905,10 +1012,21 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in
lcg_state = lcg_init(*rng + rng_offset + sample*0x51633e2d); lcg_state = lcg_init(*rng + rng_offset + sample*0x51633e2d);
} }
if(!scene_intersect(kg, &ray, visibility, &isect, &lcg_state, difl, extmax)) { bool hit = scene_intersect(kg, &ray, visibility, &isect, &lcg_state, difl, extmax);
#else #else
if(!scene_intersect(kg, &ray, visibility, &isect)) { bool hit = scene_intersect(kg, &ray, visibility, &isect);
#endif #endif
#ifdef __VOLUME__
/* volume attenuation */
if(state.volume_shader != SHADER_NO_ID) {
Ray segment_ray = ray;
segment_ray.t = (hit)? isect.t: FLT_MAX;
throughput *= kernel_volume_get_shadow_attenuation(kg, &state, &segment_ray, state.volume_shader);
}
#endif
if(!hit) {
/* eval background shader if nothing hit */ /* eval background shader if nothing hit */
if(kernel_data.background.transparent) { if(kernel_data.background.transparent) {
L_transparent += average(throughput); L_transparent += average(throughput);
@ -1062,6 +1180,7 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in
} }
#endif #endif
if(!(sd.flag & SD_HAS_ONLY_VOLUME)) {
/* lighting */ /* lighting */
kernel_branched_path_integrate_lighting(kg, rng, sample, aa_samples, kernel_branched_path_integrate_lighting(kg, rng, sample, aa_samples,
&sd, throughput, 1.0f, ray_pdf, ray_pdf, state, rng_offset, &L, buffer); &sd, throughput, 1.0f, ray_pdf, ray_pdf, state, rng_offset, &L, buffer);
@ -1071,10 +1190,16 @@ ccl_device float4 kernel_branched_path_integrate(KernelGlobals *kg, RNG *rng, in
if(is_zero(throughput)) if(is_zero(throughput))
break; break;
}
path_state_next(kg, &state, LABEL_TRANSPARENT); path_state_next(kg, &state, LABEL_TRANSPARENT);
ray.P = ray_offset(sd.P, -sd.Ng); ray.P = ray_offset(sd.P, -sd.Ng);
ray.t -= sd.ray_length; /* clipping works through transparent */ ray.t -= sd.ray_length; /* clipping works through transparent */
#ifdef __VOLUME__
/* enter/exit volume */
kernel_volume_enter_exit(kg, &sd, &state.volume_shader);
#endif
} }
float3 L_sum = path_radiance_sum(kg, &L); float3 L_sum = path_radiance_sum(kg, &L);

@ -24,9 +24,13 @@ typedef struct PathState {
int glossy_bounce; int glossy_bounce;
int transmission_bounce; int transmission_bounce;
int transparent_bounce; int transparent_bounce;
#ifdef __VOLUME__
int volume_shader;
#endif
} PathState; } PathState;
ccl_device_inline void path_state_init(PathState *state) ccl_device_inline void path_state_init(KernelGlobals *kg, PathState *state)
{ {
state->flag = PATH_RAY_CAMERA|PATH_RAY_SINGULAR|PATH_RAY_MIS_SKIP; state->flag = PATH_RAY_CAMERA|PATH_RAY_SINGULAR|PATH_RAY_MIS_SKIP;
state->bounce = 0; state->bounce = 0;
@ -34,6 +38,11 @@ ccl_device_inline void path_state_init(PathState *state)
state->glossy_bounce = 0; state->glossy_bounce = 0;
state->transmission_bounce = 0; state->transmission_bounce = 0;
state->transparent_bounce = 0; state->transparent_bounce = 0;
#ifdef __VOLUME__
/* todo: this assumes camera is always in air, need to detect when it isn't */
state->volume_shader = kernel_data.background.volume_shader;
#endif
} }
ccl_device_inline void path_state_next(KernelGlobals *kg, PathState *state, int label) ccl_device_inline void path_state_next(KernelGlobals *kg, PathState *state, int label)

@ -388,9 +388,53 @@ ccl_device_inline void shader_setup_from_background(KernelGlobals *kg, ShaderDat
sd->object = ~0; sd->object = ~0;
#endif #endif
sd->prim = ~0; sd->prim = ~0;
#ifdef __UV__
sd->u = 0.0f;
sd->v = 0.0f;
#endif
#ifdef __DPDU__
/* dPdu/dPdv */
sd->dPdu = make_float3(0.0f, 0.0f, 0.0f);
sd->dPdv = make_float3(0.0f, 0.0f, 0.0f);
#endif
#ifdef __RAY_DIFFERENTIALS__
/* differentials */
sd->dP = ray->dD;
differential_incoming(&sd->dI, sd->dP);
sd->du.dx = 0.0f;
sd->du.dy = 0.0f;
sd->dv.dx = 0.0f;
sd->dv.dy = 0.0f;
#endif
}
/* ShaderData setup from point inside volume */
ccl_device_inline void shader_setup_from_volume(KernelGlobals *kg, ShaderData *sd, const Ray *ray, int volume_shader, int bounce)
{
/* vectors */
sd->P = ray->P;
sd->N = -ray->D;
sd->Ng = -ray->D;
sd->I = -ray->D;
sd->shader = volume_shader;
sd->flag = kernel_tex_fetch(__shader_flag, (sd->shader & SHADER_MASK)*2);
#ifdef __OBJECT_MOTION__
sd->time = ray->time;
#endif
sd->ray_length = 0.0f; /* todo: can we set this to some useful value? */
sd->ray_depth = bounce;
#ifdef __INSTANCING__
sd->object = ~0; /* todo: fill this for texture coordinates */
#endif
sd->prim = ~0;
#ifdef __HAIR__ #ifdef __HAIR__
sd->segment = ~0; sd->segment = ~0;
#endif #endif
#ifdef __UV__ #ifdef __UV__
sd->u = 0.0f; sd->u = 0.0f;
sd->v = 0.0f; sd->v = 0.0f;

@ -45,6 +45,10 @@ ccl_device_inline bool shadow_blocked(KernelGlobals *kg, PathState *state, Ray *
float3 Pend = ray->P + ray->D*ray->t; float3 Pend = ray->P + ray->D*ray->t;
int bounce = state->transparent_bounce; int bounce = state->transparent_bounce;
#ifdef __VOLUME__
int volume_shader = state->volume_shader;
#endif
for(;;) { for(;;) {
if(bounce >= kernel_data.integrator.transparent_max_bounce) { if(bounce >= kernel_data.integrator.transparent_max_bounce) {
return true; return true;
@ -67,6 +71,13 @@ ccl_device_inline bool shadow_blocked(KernelGlobals *kg, PathState *state, Ray *
#else #else
if(!scene_intersect(kg, ray, PATH_RAY_SHADOW_TRANSPARENT, &isect)) { if(!scene_intersect(kg, ray, PATH_RAY_SHADOW_TRANSPARENT, &isect)) {
#endif #endif
#ifdef __VOLUME__
/* attenuation for last line segment towards light */
if(volume_shader != SHADER_NO_ID)
throughput *= kernel_volume_get_shadow_attenuation(kg, state, ray, volume_shader);
#endif
*shadow *= throughput; *shadow *= throughput;
return false; return false;
} }
@ -74,20 +85,48 @@ ccl_device_inline bool shadow_blocked(KernelGlobals *kg, PathState *state, Ray *
if(!shader_transparent_shadow(kg, &isect)) if(!shader_transparent_shadow(kg, &isect))
return true; return true;
#ifdef __VOLUME__
/* attenuation between last surface and next surface */
if(volume_shader != SHADER_NO_ID) {
Ray segment_ray = *ray;
segment_ray.t = isect.t;
throughput *= kernel_volume_get_shadow_attenuation(kg, state, &segment_ray, volume_shader);
}
#endif
/* setup shader data at surface */
ShaderData sd; ShaderData sd;
shader_setup_from_ray(kg, &sd, &isect, ray, state->bounce+1); shader_setup_from_ray(kg, &sd, &isect, ray, state->bounce+1);
/* attenuation from transparent surface */
if(!(sd.flag & SD_HAS_ONLY_VOLUME)) {
shader_eval_surface(kg, &sd, 0.0f, PATH_RAY_SHADOW, SHADER_CONTEXT_SHADOW); shader_eval_surface(kg, &sd, 0.0f, PATH_RAY_SHADOW, SHADER_CONTEXT_SHADOW);
throughput *= shader_bsdf_transparency(kg, &sd); throughput *= shader_bsdf_transparency(kg, &sd);
}
/* move ray forward */
ray->P = ray_offset(sd.P, -sd.Ng); ray->P = ray_offset(sd.P, -sd.Ng);
if(ray->t != FLT_MAX) if(ray->t != FLT_MAX)
ray->D = normalize_len(Pend - ray->P, &ray->t); ray->D = normalize_len(Pend - ray->P, &ray->t);
#ifdef __VOLUME__
/* exit/enter volume */
if(sd.flag & SD_BACKFACING)
volume_shader = kernel_data.background.volume_shader;
else
volume_shader = (sd.flag & SD_HAS_VOLUME)? sd.shader: SHADER_NO_ID;
#endif
bounce++; bounce++;
} }
} }
} }
#ifdef __VOLUME__
else if(!result && state->volume_shader != SHADER_NO_ID) {
/* apply attenuation from current volume shader */
*shadow *= kernel_volume_get_shadow_attenuation(kg, state, ray, state->volume_shader);
}
#endif
#endif #endif
return result; return result;

@ -58,12 +58,14 @@ CCL_NAMESPACE_BEGIN
#endif #endif
#define __SUBSURFACE__ #define __SUBSURFACE__
#define __CMJ__ #define __CMJ__
#define __VOLUME__
#endif #endif
#ifdef __KERNEL_CUDA__ #ifdef __KERNEL_CUDA__
#define __KERNEL_SHADING__ #define __KERNEL_SHADING__
#define __KERNEL_ADV_SHADING__ #define __KERNEL_ADV_SHADING__
#define __BRANCHED_PATH__ #define __BRANCHED_PATH__
//#define __VOLUME__
#endif #endif
#ifdef __KERNEL_OPENCL__ #ifdef __KERNEL_OPENCL__
@ -478,7 +480,8 @@ typedef enum ShaderContext {
SHADER_CONTEXT_EMISSION = 2, SHADER_CONTEXT_EMISSION = 2,
SHADER_CONTEXT_SHADOW = 3, SHADER_CONTEXT_SHADOW = 3,
SHADER_CONTEXT_SSS = 4, SHADER_CONTEXT_SSS = 4,
SHADER_CONTEXT_NUM = 5 SHADER_CONTEXT_VOLUME = 5,
SHADER_CONTEXT_NUM = 6
} ShaderContext; } ShaderContext;
/* Shader Data /* Shader Data

@ -0,0 +1,124 @@
/*
* Copyright 2011-2013 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
CCL_NAMESPACE_BEGIN
/* Volume shader properties
*
* extinction coefficient = absorption coefficient + scattering coefficient
* sigma_t = sigma_a + sigma_s */
ccl_device float3 volume_shader_get_extinction_coefficient(ShaderData *sd)
{
float3 sigma_t = make_float3(0.0f, 0.0f, 0.0f);
for(int i = 0; i < sd->num_closure; i++) {
const ShaderClosure *sc = &sd->closure[i];
if(CLOSURE_IS_VOLUME(sc->type))
sigma_t += sc->weight;
}
return sigma_t;
}
ccl_device float3 volume_shader_get_scattering_coefficient(ShaderData *sd)
{
float3 sigma_s = make_float3(0.0f, 0.0f, 0.0f);
for(int i = 0; i < sd->num_closure; i++) {
const ShaderClosure *sc = &sd->closure[i];
if(CLOSURE_IS_VOLUME(sc->type) && sc->type != CLOSURE_VOLUME_ABSORPTION_ID)
sigma_s += sc->weight;
}
return sigma_s;
}
ccl_device float3 volume_shader_get_absorption_coefficient(ShaderData *sd)
{
float3 sigma_a = make_float3(0.0f, 0.0f, 0.0f);
for(int i = 0; i < sd->num_closure; i++) {
const ShaderClosure *sc = &sd->closure[i];
if(sc->type == CLOSURE_VOLUME_ABSORPTION_ID)
sigma_a += sc->weight;
}
return sigma_a;
}
/* evaluate shader to get extinction coefficient at P */
ccl_device float3 volume_extinction_sample(KernelGlobals *kg, ShaderData *sd, int path_flag, ShaderContext ctx, float3 P)
{
sd->P = P;
shader_eval_volume(kg, sd, 0.0f, path_flag, ctx);
return volume_shader_get_extinction_coefficient(sd);
}
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 segment_ray, with the
* assumption that there are surfaces blocking light between the endpoints */
ccl_device float3 kernel_volume_get_shadow_attenuation(KernelGlobals *kg, PathState *state, Ray *segment_ray, int shader)
{
ShaderData sd;
shader_setup_from_volume(kg, &sd, segment_ray, shader, state->bounce);
/* do we have a volume shader? */
if(!(sd.flag & SD_HAS_VOLUME))
return make_float3(1.0f, 1.0f, 1.0f);
/* single shader evaluation at the start */
ShaderContext ctx = SHADER_CONTEXT_SHADOW;
int path_flag = PATH_RAY_SHADOW;
float3 attenuation;
//if(sd.flag & SD_HOMOGENEOUS_VOLUME) {
/* homogenous volume: assume shader evaluation at the starts gives
* the extinction coefficient for the entire line segment */
/* todo: could this use sigma_t_cache? */
float3 sigma_t = volume_extinction_sample(kg, &sd, path_flag, ctx, segment_ray->P);
attenuation = volume_color_attenuation(sigma_t, segment_ray->t);
//}
return attenuation;
}
/* Volume Stack */
/* todo: this assumes no overlapping volumes, needs to become a stack */
ccl_device void kernel_volume_enter_exit(KernelGlobals *kg, ShaderData *sd, int *volume_shader)
{
if(sd->flag & SD_BACKFACING)
*volume_shader = kernel_data.background.volume_shader;
else
*volume_shader = (sd->flag & SD_HAS_VOLUME)? sd->shader: SHADER_NO_ID;
}
CCL_NAMESPACE_END

@ -93,6 +93,29 @@ ccl_device_inline ShaderClosure *svm_node_closure_get_bsdf(ShaderData *sd, float
#endif #endif
} }
ccl_device_inline ShaderClosure *svm_node_closure_get_absorption(ShaderData *sd, float mix_weight)
{
#ifdef __MULTI_CLOSURE__
ShaderClosure *sc = &sd->closure[sd->num_closure];
float3 weight = (make_float3(1.0f, 1.0f, 1.0f) - sc->weight) * mix_weight;
float sample_weight = fabsf(average(weight));
if(sample_weight > CLOSURE_WEIGHT_CUTOFF && sd->num_closure < MAX_CLOSURE) {
sc->weight = weight;
sc->sample_weight = sample_weight;
sd->num_closure++;
#ifdef __OSL__
sc->prim = NULL;
#endif
return sc;
}
return NULL;
#else
return &sd->closure;
#endif
}
ccl_device void svm_node_closure_bsdf(KernelGlobals *kg, ShaderData *sd, float *stack, uint4 node, float randb, int path_flag, int *offset) ccl_device void svm_node_closure_bsdf(KernelGlobals *kg, ShaderData *sd, float *stack, uint4 node, float randb, int path_flag, int *offset)
{ {
uint type, param1_offset, param2_offset; uint type, param1_offset, param2_offset;
@ -478,7 +501,7 @@ ccl_device void svm_node_closure_volume(KernelGlobals *kg, ShaderData *sd, float
switch(type) { switch(type) {
case CLOSURE_VOLUME_ABSORPTION_ID: { case CLOSURE_VOLUME_ABSORPTION_ID: {
ShaderClosure *sc = svm_node_closure_get_bsdf(sd, mix_weight * density); ShaderClosure *sc = svm_node_closure_get_absorption(sd, mix_weight * density);
if(sc) { if(sc) {
sd->flag |= volume_absorption_setup(sc); sd->flag |= volume_absorption_setup(sc);

@ -133,7 +133,6 @@ void Scene::device_update(Device *device_, Progress& progress)
/* The order of updates is important, because there's dependencies between /* The order of updates is important, because there's dependencies between
* the different managers, using data computed by previous managers. * the different managers, using data computed by previous managers.
* *
* - Background generates shader graph compiled by shader manager.
* - Image manager uploads images used by shaders. * - Image manager uploads images used by shaders.
* - Camera may be used for adapative subdivison. * - Camera may be used for adapative subdivison.
* - Displacement shader must have all shader data available. * - Displacement shader must have all shader data available.
@ -142,11 +141,6 @@ void Scene::device_update(Device *device_, Progress& progress)
image_manager->set_pack_images(device->info.pack_images); image_manager->set_pack_images(device->info.pack_images);
progress.set_status("Updating Background");
background->device_update(device, &dscene, this);
if(progress.get_cancel()) return;
progress.set_status("Updating Shaders"); progress.set_status("Updating Shaders");
shader_manager->device_update(device, &dscene, this, progress); shader_manager->device_update(device, &dscene, this, progress);
@ -157,6 +151,11 @@ void Scene::device_update(Device *device_, Progress& progress)
if(progress.get_cancel()) return; if(progress.get_cancel()) return;
progress.set_status("Updating Background");
background->device_update(device, &dscene, this);
if(progress.get_cancel()) return;
progress.set_status("Updating Camera"); progress.set_status("Updating Camera");
camera->device_update(device, &dscene, this); camera->device_update(device, &dscene, this);

@ -193,6 +193,7 @@ shader_node_categories = [
NodeItem("ShaderNodeBackground"), NodeItem("ShaderNodeBackground"),
NodeItem("ShaderNodeAmbientOcclusion"), NodeItem("ShaderNodeAmbientOcclusion"),
NodeItem("ShaderNodeHoldout"), NodeItem("ShaderNodeHoldout"),
NodeItem("ShaderNodeVolumeAbsorption"),
]), ]),
ShaderNewNodeCategory("SH_NEW_TEXTURE", "Texture", items=[ ShaderNewNodeCategory("SH_NEW_TEXTURE", "Texture", items=[
NodeItem("ShaderNodeTexImage"), NodeItem("ShaderNodeTexImage"),

@ -3499,7 +3499,7 @@ static void registerShaderNodes(void)
register_node_type_sh_bsdf_hair(); register_node_type_sh_bsdf_hair();
register_node_type_sh_emission(); register_node_type_sh_emission();
register_node_type_sh_holdout(); register_node_type_sh_holdout();
//register_node_type_sh_volume_absorption(); register_node_type_sh_volume_absorption();
//register_node_type_sh_volume_scatter(); //register_node_type_sh_volume_scatter();
register_node_type_sh_subsurface_scattering(); register_node_type_sh_subsurface_scattering();
register_node_type_sh_mix_shader(); register_node_type_sh_mix_shader();

@ -921,6 +921,21 @@ static void node_shader_buts_subsurface(uiLayout *layout, bContext *C, PointerRN
uiItemR(layout, ptr, "falloff", 0, "", ICON_NONE); uiItemR(layout, ptr, "falloff", 0, "", ICON_NONE);
} }
static void node_shader_buts_volume(uiLayout *layout, bContext *C, PointerRNA *UNUSED(ptr))
{
/* SSS does not work on GPU yet */
PointerRNA scene = CTX_data_pointer_get(C, "scene");
if (scene.data) {
PointerRNA cscene = RNA_pointer_get(&scene, "cycles");
if (cscene.data && RNA_enum_get(&cscene, "device") == 1)
uiItemL(layout, IFACE_("Volumes not supported on GPU"), ICON_ERROR);
else
uiItemL(layout, IFACE_("Volumes are work in progress"), ICON_ERROR);
}
}
static void node_shader_buts_toon(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr) static void node_shader_buts_toon(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{ {
uiItemR(layout, ptr, "component", 0, "", ICON_NONE); uiItemR(layout, ptr, "component", 0, "", ICON_NONE);
@ -1064,6 +1079,12 @@ static void node_shader_set_butfunc(bNodeType *ntype)
case SH_NODE_SUBSURFACE_SCATTERING: case SH_NODE_SUBSURFACE_SCATTERING:
ntype->draw_buttons = node_shader_buts_subsurface; ntype->draw_buttons = node_shader_buts_subsurface;
break; break;
case SH_NODE_VOLUME_SCATTER:
ntype->draw_buttons = node_shader_buts_volume;
break;
case SH_NODE_VOLUME_ABSORPTION:
ntype->draw_buttons = node_shader_buts_volume;
break;
case SH_NODE_BSDF_TOON: case SH_NODE_BSDF_TOON:
ntype->draw_buttons = node_shader_buts_toon; ntype->draw_buttons = node_shader_buts_toon;
break; break;