blender/intern/cycles/kernel/kernel_volume.h
Brecht Van Lommel fe222643b4 Cycles Volume Render: add volume emission support.
This is done using the existing Emission node and closure (we may add a volume
emission node, not clear yet if it will be needed).

Volume emission only supports indirect light sampling which means it's not very
efficient to make small or far away bright light sources. Using direct light
sampling and MIS would be tricky and probably won't be added anytime soon. Other
renderers don't support this either as far as I know, lamps and ray visibility
tricks may be used instead.
2013-12-28 23:20:53 +01:00

221 lines
6.6 KiB
C

/*
* 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 */
typedef struct VolumeShaderSample {
float3 sigma_a;
float3 sigma_s;
float3 emission;
} VolumeShaderSample;
/* evaluate shader to get extinction coefficient at P */
ccl_device bool volume_shader_extinction_sample(KernelGlobals *kg, ShaderData *sd, VolumeStack *stack, int path_flag, ShaderContext ctx, float3 P, float3 *extinction)
{
sd->P = P;
shader_eval_volume(kg, sd, stack, 0.0f, path_flag, ctx);
if(!(sd->flag & SD_VOLUME))
return false;
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;
}
*extinction = sigma_t;
return true;
}
/* evaluate shader to get absorption, scattering and emission at P */
ccl_device bool volume_shader_sample(KernelGlobals *kg, ShaderData *sd, VolumeStack *stack, int path_flag, ShaderContext ctx, float3 P, VolumeShaderSample *sample)
{
sd->P = P;
shader_eval_volume(kg, sd, stack, 0.0f, path_flag, ctx);
if(!(sd->flag & (SD_VOLUME|SD_EMISSION)))
return false;
sample->sigma_a = make_float3(0.0f, 0.0f, 0.0f);
sample->sigma_s = make_float3(0.0f, 0.0f, 0.0f);
sample->emission = 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)
sample->sigma_a += sc->weight;
else if(sc->type == CLOSURE_EMISSION_ID)
sample->emission += sc->weight;
else if(CLOSURE_IS_VOLUME(sc->type))
sample->sigma_s += sc->weight;
}
return true;
}
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 no surfaces blocking light between the endpoints */
ccl_device void kernel_volume_get_shadow_attenuation(KernelGlobals *kg, PathState *state, Ray *segment_ray, float3 *throughput)
{
ShaderData sd;
shader_setup_from_volume(kg, &sd, segment_ray, state->bounce);
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, segment_ray->P, &sigma_t))
return;
*throughput *= volume_color_attenuation(sigma_t, segment_ray->t);
}
/* Volumetric Path */
/* get the volume attenuation and emission over line segment defined by
* segment_ray, with the assumption that there are no surfaces blocking light
* between the endpoints */
ccl_device void kernel_volume_integrate(KernelGlobals *kg, PathState *state, Ray *segment_ray, PathRadiance *L, float3 *throughput)
{
ShaderData sd;
shader_setup_from_volume(kg, &sd, segment_ray, state->bounce);
ShaderContext ctx = SHADER_CONTEXT_VOLUME;
int path_flag = PATH_RAY_SHADOW;
VolumeShaderSample sample;
/* 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, segment_ray->P, &sample))
return;
int closure_flag = sd.flag;
float t = segment_ray->t;
/* compute attenuation from absorption (+ scattering for now) */
float3 sigma_t, attenuation;
if(closure_flag & SD_VOLUME) {
sigma_t = sample.sigma_a + sample.sigma_s;
attenuation = volume_color_attenuation(sigma_t, t);
}
/* integrate emission attenuated by absorption
* integral E * exp(-sigma_t * t) from 0 to t = E * (1 - exp(-sigma_t * t))/sigma_t
* this goes to E * t as sigma_t goes to zero
*
* todo: we should use an epsilon to avoid precision issues near zero sigma_t */
if(closure_flag & SD_EMISSION) {
float3 emission = sample.emission;
if(closure_flag & SD_VOLUME) {
emission.x *= (sigma_t.x > 0.0f)? (1.0f - attenuation.x)/sigma_t.x: t;
emission.y *= (sigma_t.y > 0.0f)? (1.0f - attenuation.y)/sigma_t.y: t;
emission.z *= (sigma_t.z > 0.0f)? (1.0f - attenuation.z)/sigma_t.z: t;
}
else
emission *= t;
path_radiance_accum_emission(L, *throughput, emission, state->bounce);
}
/* modify throughput */
if(closure_flag & SD_VOLUME)
*throughput *= attenuation;
}
/* Volume Stack */
ccl_device void kernel_volume_stack_init(KernelGlobals *kg, VolumeStack *stack)
{
/* todo: this assumes camera is always in air, need to detect when it isn't */
if(kernel_data.background.volume_shader == SHADER_NO_ID) {
stack[0].shader = SHADER_NO_ID;
}
else {
stack[0].shader = kernel_data.background.volume_shader;
stack[0].object = ~0;
stack[1].shader = SHADER_NO_ID;
}
}
ccl_device void kernel_volume_stack_enter_exit(KernelGlobals *kg, ShaderData *sd, VolumeStack *stack)
{
/* todo: we should have some way for objects to indicate if they want the
* world shader to work inside them. excluding it by default is problematic
* because non-volume objects can't be assumed to be closed manifolds */
if(!(sd->flag & SD_HAS_VOLUME))
return;
if(sd->flag & SD_BACKFACING) {
/* exit volume object: remove from stack */
for(int i = 0; stack[i].shader != SHADER_NO_ID; i++) {
if(stack[i].object == sd->object) {
/* shift back next stack entries */
do {
stack[i] = stack[i+1];
i++;
}
while(stack[i].shader != SHADER_NO_ID);
return;
}
}
}
else {
/* enter volume object: add to stack */
int i;
for(i = 0; stack[i].shader != SHADER_NO_ID; i++) {
/* already in the stack? then we have nothing to do */
if(stack[i].object == sd->object)
return;
}
/* if we exceed the stack limit, ignore */
if(i >= VOLUME_STACK_SIZE-1)
return;
/* add to the end of the stack */
stack[i].shader = sd->shader;
stack[i].object = sd->object;
stack[i+1].shader = SHADER_NO_ID;
}
}
CCL_NAMESPACE_END