blender/intern/cycles/kernel/kernel_volume.h
Brecht Van Lommel 2b39214c4d Cycles Volume Render: add support for overlapping volume objects.
This works pretty much as you would expect, overlapping volume objects gives
a more dense volume. What did change is that world volume shaders are now
active everywhere, they are no longer excluded inside objects.

This may not be desirable and we need to think of better control over this.
In some cases you clearly want it to happen, for example if you are rendering
a fire in a foggy environment. In other cases like the inside of a house you
may not want any fog, but it doesn't seem possible in general for the renderer
to automatically determine what is inside or outside of the house.

This is implemented using a simple fixed size array of shader/object ID pairs,
limited to max 15 overlapping objects. The closures from all shaders are put
into a single closure array, exactly the same as if an add shader was used to
combine them.
2013-12-28 20:12:11 +01:00

174 lines
4.8 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 */
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, VolumeStack *stack, int path_flag, ShaderContext ctx, float3 P)
{
sd->P = P;
shader_eval_volume(kg, sd, stack, 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)
{
ShaderData sd;
shader_setup_from_volume(kg, &sd, segment_ray, state->volume_stack[0].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, state->volume_stack, path_flag, ctx, segment_ray->P);
attenuation = volume_color_attenuation(sigma_t, segment_ray->t);
//}
return 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