1
1

Compare commits

...

57 Commits

Author SHA1 Message Date
fe7d12bba5 Cycles: remove disabled kernel_volume_get_final_homogeneous_extinction code.
I'm not quite sure why this would be needed. It gives per color channel sigma,
which we should have so we can compute per channel attentuation without noise
instead of the single float now.

However I think this should be handled at a deeper level by making sigma a
float3 everywhere instead of doing some kind of correction for this afterwards.
2013-12-26 16:14:37 +01:00
daf01126e6 Cycles Volume: add appropriate shader contexts for OSL.
This is needed to avoid using same memory block twice when the closures from
a shader evaluation are in use at the same time as the closures from another
evaluation.
2013-12-26 15:56:42 +01:00
34f4f7a2ea Cycles Volume: remove some unnecessary rngs from shadow computation. 2013-12-26 15:56:42 +01:00
76cf4a4d04 * New Volume Absorption node was missing in Node Editor menu. 2013-12-26 15:25:23 +01:00
de479f51b6 Cycles Volume: implement volume absorption node.
This is the transparent volume node renamed, currently it is basically the
same as a scatter volume node with anisotropy 1, so it scatters light perfectly
forward.

This also does some tweaks to the henyey-greenstein closure code to avoid
division by zero and to make eval/sample consistent in some corner cases, and
some other code cleanup related to volume shaders.
2013-12-26 14:58:18 +01:00
bdf527bfec Merge branch 'master' into soc-2013-dingto 2013-12-26 11:18:52 +01:00
35a683bfe4 Cycles Volume: materials without a surface shader now act transparent when
there is a volume shader.

This is the behavior you would expect I think and makes setup easier. More
optimizations are possible in this case to avoid surface shading altogether, but
it's probably not so noticeable altogether as this will early out and not get to
any expensive computations as far as I know.

Note that these still count as transparent bounces even if there is no BSDF. Not
entirely happy with that, but there should be some way to limit the depth?
2013-12-08 13:00:50 +01:00
9bea4db0d8 Cycles Volume: remove use volumetrics option.
In general it should not be needed to turn on such features, cycles should
auto-detect if there is a volume shader and just use it. It would be useful
at some point to have global options to turn off effects, but we better group
those options somewhere then.
2013-12-08 13:00:49 +01:00
ea9de15d0f Cycles Volume: rename isotropic to scatter volume, g to anisotropy, scattering to volume bounces. 2013-12-08 13:00:49 +01:00
16d223c5e7 Merge branch 'master' into soc-2013-dingto 2013-12-08 13:00:44 +01:00
3301b38116 Merge branch 'master' into soc-2013-dingto 2013-11-21 08:09:27 +01:00
8f6358182e Merge branch 'master' into soc-2013-dingto
Solved conflicts with __device to ccl_device change, was actually quite easy.
2013-11-18 09:00:30 +01:00
1031e0ba4b Merged revision(s) 60839-61042 from trunk/blender into soc-2013-dingto. 2013-11-01 09:00:48 +00:00
85ec8768ab Merged revision(s) 60651-60838 from trunk/blender into soc-2013-dingto. 2013-10-18 07:38:08 +00:00
47cd4d41ab Merged revision(s) 60459-60650 from trunk/blender into soc-2013-dingto. 2013-10-09 20:56:38 +00:00
cf17355ecc Volume:
* Style and code cleanup for volume.h closure file.
* Revert rename of henyey-greenstein closure to isotropic, the actual node name is still unsure, but the closure name should be called what it is.
2013-10-09 18:08:05 +00:00
53ddaa5acd Volume:
* Remove the "start" variable from the sampler code.
This was hard coded to 0.0f and therefore had no effect on the calculations.
2013-10-03 22:36:13 +00:00
d4f7e1524e Volume:
* More style and code cleanup.
2013-10-03 22:04:05 +00:00
56b9d24184 Volume:
* Some renaming and move common epsilons to #defines.
2013-10-03 21:27:26 +00:00
84efe953ad Volume:
* Some code cleanup for shadow_blocked_new, renamed to shadow_blocked_volume() now.
* Also re-order the parameters, so the first 4 match the shadow_blocked() function.
2013-10-03 21:04:11 +00:00
f89a66ab06 Merged revision(s) 60385-60458 from trunk/blender into soc-2013-dingto. 2013-09-30 23:00:47 +00:00
2d9a23a7f8 Merged revision(s) 60321-60384 from trunk/blender into soc-2013-dingto. 2013-09-26 22:27:51 +00:00
39ba7447ba Volume:
* Comment some unused variables to silence a few compile warnings.
2013-09-23 00:55:22 +00:00
f54e3e79b1 Merged revision(s) 60246-60320 from trunk/blender into soc-2013-dingto. 2013-09-23 00:31:14 +00:00
df19fe9162 Branch maintenance:
* Bring the brach in sync with hair bsdf and ao_alpha code from trunk, somehow didn't work in r60244.
2013-09-23 00:16:40 +00:00
b28fbdd329 Merged revision(s) 60244-60245 from trunk/blender into soc-2013-dingto.
Note: 60243 was merged in r60244 already, log info was wrong.
2013-09-19 23:57:34 +00:00
2024d8ee34 Merged revision(s) 60149-60242 from trunk/blender into soc-2013-dingto.
Resolved conflicts in kernel_path.h, osl_closures.cpp and stdosl.h.
2013-09-19 23:43:30 +00:00
6ef3ac8c00 Merged revision(s) 60078-60148 from trunk/blender into soc-2013-dingto. 2013-09-15 11:56:17 +00:00
3a27f6bacd Volume:
* Further header cleanup for sampler functions, remove unused RNG parameters.
2013-09-13 00:47:53 +00:00
f122ffd476 Volume:
* Cleanup function headers. 
* Remove some unused variables, also remove another redundant "bounce" variable in shadow_blocked_new().
2013-09-12 22:29:20 +00:00
7136f6abad Volume:
* Some style cleanup, mainly for if/else brackets.
* Remove redundant bounce parameter in kernel_path_trace_volume(), we already pass state.
2013-09-12 21:40:08 +00:00
89aae3afdd Merged revision(s) 60000-60077 from trunk/blender into soc-2013-dingto. 2013-09-12 15:30:37 +00:00
966f32a205 Volume:
* Comment out some unused code.
* Remove some redundant ifdefs in shadow_blocked_new().
2013-09-12 15:23:09 +00:00
c07459e357 Volume:
* direct_emission() and indirect_primitive_emission() were inlined for some reason, change back to noinlined (as in trunk). 
* Put most volume kernel code behind __VOLUME__ now.
2013-09-12 15:01:00 +00:00
dd6ebf8ba1 Merged revision(s) 59995-59999 from trunk/blender into soc-2013-dingto.
Yeah I know. :P
2013-09-10 12:22:21 +00:00
f5ec195be9 Merged revision(s) 59930-59994 from trunk/blender into soc-2013-dingto. 2013-09-10 09:13:29 +00:00
Lukas Toenne
e7c2bf0b52 Fix for OSL volume closure flattening, this was not copying settings from the OSL closures. 2013-09-10 07:42:42 +00:00
Lukas Toenne
649d345fdd Removed invalid code from volume closure, the OSL distinction has to happen on a different level and doesn't make any sense. The function body works in both SVM and OSL case. 2013-09-10 07:42:41 +00:00
Lukas Toenne
a88a02964d Reinstated the OSLShader API functions for volumes, fixed the function signature. 2013-09-10 07:42:40 +00:00
f9c2563d05 Fix for last commit, forgot to rename the shader in 2 locations. :/ 2013-09-10 00:36:14 +00:00
21e669c047 Volume:
* Rename OSL closure and file to reflect recent changes.
2013-09-09 23:46:55 +00:00
3d49b320f3 Volume:
Cleanup and refactor of closure code in volume.h.
* Remove unused code (the brute force henyey-greenstein eval and sample function and double peaked functions). Maybe we need something here in the future, but the current code just became too confusing. 

* Remove the CLOSURE_BSDF_DOUBLE_PEAKED_HENYEY_GREENSTEIN_ID, instead use CLOSURE_VOLUME_ISOTROPIC_ID now. 
The final naming of the node/closure is still unsure, but better to use one consistent naming scheme. When the "g" parameter is 0, the closure is Isotropic anyway, so maybe it's ok, we'll see.

* Now we use 2 counterparts to bsdf_eval() and bsdf_sample():
volume_eval_phase() and volume_sample() 

ToDo:
* Reflect these changes for OSL.
2013-09-09 23:01:49 +00:00
Lukas Toenne
145931d7b9 OSL implementation of the Henyey-Greenstein volume closure. A lot of cleanup included. 2013-09-09 20:13:39 +00:00
40df16dc51 Volume:
* Fix some render errors (no caustics etc.), the patch accidentally removed some lines in kernel_path.h.
* Remove unused bsdf_eval_add() function.
2013-09-09 14:17:02 +00:00
d91f700fed * Put some more volume code behind __VOLUME__.
* Compiling on the GPU works and renders fine (sm_21 here), if you want to test uncomment the line in kernel_types.h.
2013-09-09 01:34:39 +00:00
e011f35456 Cycles / Volume Rendering:
* Initial and experimental implementation of volumetric rendering. 

At this stage it is already usable, although there are a lot of limitations still:
- No Volume rendering in Branched Path integrator. 
- No Smoke volume rendering
- No OSL support
- UI and data structures are WIP, be careful when saving files with this. 
- ...

Credits:
* Based on the original patch by "storm". 
* Cleanup and improvements by Stuart Broadfoot. 
* A crash fix by Lukas Tönne.
Thanks a lot guys!

* Also some cleanup and refactor by myself. 

Some WIP docs and infos: http://wiki.blender.org/index.php/User:DingTo/Volume
2013-09-09 01:02:09 +00:00
1bb2892562 Branch maintenance:
* Revert r59183 and r57612 of my branch. Those changes were experimental. In case we need it, it's still in SVN history. 
* Also fix a mismatch in gpu_shader_material.glsl.

Trunk and my branch are now equal, except splash.png. Compared with Meld, thanks to Campbell for the suggestion. :)
2013-09-09 00:22:00 +00:00
fc8fa1593b Merged revision(s) 59906-59929 from trunk/blender into soc-2013-dingto. 2013-09-08 23:57:17 +00:00
0fb6924273 Merged revision(s) 59751-59905 from trunk/blender into soc-2013-dingto.
* Resolved a conflict in intern/cycles/render/object.cpp.
2013-09-07 00:52:16 +00:00
932dd5cef6 Merged revision(s) 59605-59750 from trunk/blender into soc-2013-dingto. 2013-09-02 19:35:58 +00:00
836da36db3 Merged revision(s) 59491-59602, 59604 from trunk/blender into soc-2013-dingto. 2013-08-28 14:48:04 +00:00
3c56be794e Cycles / Sky Model:
* Rename Albedo to Ground Albedo, and improve tooltip. 
* Soft UI Range for Turbidity, between 1 and 10, higher values can produce weird results. I checked and other engines clamp this to 10 as well.
* Some code cleanup.
2013-08-28 13:28:26 +00:00
8aa6d3bc2a Merged revision(s) 59438-59490 from trunk/blender into soc-2013-dingto. 2013-08-25 12:23:22 +00:00
8d8a53dcc2 Merged revision(s) 59247-59437 from trunk/blender into soc-2013-dingto. 2013-08-23 19:13:32 +00:00
aedbe54394 Cycles / Sky Model:
* Remove the KernelSunSky struct, which limited us to 1 Sky texture for SVM. Now we can use more than 1 Sky shader, which allows some artificial effects like 2 Sun/Sky's or so.

* New and old model are now working in OSL as well.

Patch by Lukas Tönne, with some small tweaks by me. Thanks!

ToDo:
* Code cleanup!
2013-08-18 23:54:56 +00:00
fb6c20df07 Merged revision(s) 59185-59246 from trunk/blender into soc-2013-dingto. 2013-08-18 20:17:18 +00:00
8b82c86170 Cycles / Sky Model:
* Old and new model can now be selected from a drop down menu.
* Old blend files load fine and use the old model, when we create a new Sky Texture node it defaults to the new model. 

* Fixed Strength issues with new model and some code cleanup there.

ToDo:
* OSL implementation for thee new model. 
* Code cleanup. Maybe outsource the whole precalc into its own cpp file?
2013-08-17 21:24:46 +00:00
46 changed files with 1756 additions and 155 deletions

View File

@@ -455,8 +455,8 @@ static void xml_read_shader_graph(const XMLReadState& state, Shader *shader, pug
else if(string_iequals(node.name(), "transparent_volume")) {
snode = new TransparentVolumeNode();
}
else if(string_iequals(node.name(), "isotropic_volume")) {
snode = new IsotropicVolumeNode();
else if(string_iequals(node.name(), "scatter_volume")) {
snode = new ScatterVolumeNode();
}
else if(string_iequals(node.name(), "geometry")) {
snode = new GeometryNode();

View File

@@ -37,6 +37,7 @@ class AddPresetIntegrator(AddPresetBase, Operator):
"cycles.diffuse_bounces",
"cycles.glossy_bounces",
"cycles.transmission_bounces",
"cycles.volume_bounces",
"cycles.transparent_min_bounces",
"cycles.transparent_max_bounces"
]

View File

@@ -91,6 +91,18 @@ enum_tile_order = (
('TOP_TO_BOTTOM', "Top to Bottom", "Render from top to bottom"),
('BOTTOM_TO_TOP', "Bottom to Top", "Render from bottom to top"),
)
enum_volumetric_sampling_algorithm = (
('VOLUMETRIC_MARCHING', "Ray Marching", "Use ray marching algorithm"),
('VOLUMETRIC_WOODCOCK', "Woodcock", "Use Woodcock algorithm"),
('VOLUMETRIC_MARCHING_EXP', "Ray Marching(Exp)", "Use ray marching algorithm (experimental)"),
('VOLUMETRIC_WOODCOCK_EXP', "Woodcock(Exp)", "Use Woodcock algorithm (experimental)"),
)
enum_homogeneous_volumetric_sampling_algorithm = (
('VOLUMETRIC_IMPORTANCE_SAMPLING', "Importance Sampling", "Use perfect importance sampling"),
('VOLUMETRIC_EQUIANGULAR', "Equi-angular light(Exp)", "Use angular sampling based on lighting (experimental)"),
)
enum_use_layer_samples = (
('USE', "Use", "Per render layer number of samples override scene samples"),
@@ -280,6 +292,12 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
min=0, max=1024,
default=12,
)
cls.volume_bounces = IntProperty(
name="Volume Bounces",
description="Maximum number of volumetric scattering events",
min=0, max=1024,
default=128,
)
cls.transparent_min_bounces = IntProperty(
name="Transparent Min Bounces",
@@ -409,6 +427,41 @@ class CyclesRenderSettings(bpy.types.PropertyGroup):
default=False,
)
cls.volume_sampling_algorithm = EnumProperty(
name="Sampling algorithm",
description="Choose volumetric sampling algorithm for inhomogeneous volumes",
items=enum_volumetric_sampling_algorithm,
default='VOLUMETRIC_MARCHING',
)
cls.volume_homogeneous_sampling = EnumProperty(
name="Homogeneous Sampling",
description="Choose volumetric sampling algorithm for homogeneous volumes",
items=enum_homogeneous_volumetric_sampling_algorithm,
default='VOLUMETRIC_IMPORTANCE_SAMPLING',
)
cls.volume_max_iterations = IntProperty(
name="Max Iterations",
description="Number of iterations before we give up, protect for very slow deep volume marching",
default=200,
min=10, max=8192
)
cls.volume_cell_step = FloatProperty(
name="Cell Step",
description="cell size when probing for density, work same as 3d texture resolution",
default=0.1,
min=0.0000001, max=100000.0
)
cls.volume_woodcock_max_density = FloatProperty(
name="Max Woodcock Density",
description="Max density in volume, band aid, I need to calculate it from texture",
default=0.95,
min=0.0000001, max=100000.0
)
@classmethod
def unregister(cls):
del bpy.types.Scene.cycles
@@ -577,6 +630,12 @@ class CyclesWorldSettings(bpy.types.PropertyGroup):
min=1, max=10000,
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
def unregister(cls):

View File

@@ -193,7 +193,30 @@ class CyclesRender_PT_light_paths(CyclesButtonsPanel, Panel):
sub.prop(cscene, "diffuse_bounces", text="Diffuse")
sub.prop(cscene, "glossy_bounces", text="Glossy")
sub.prop(cscene, "transmission_bounces", text="Transmission")
sub.prop(cscene, "volume_bounces", text="Volume")
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
col = layout.column()
col.prop(cscene, "volume_sampling_algorithm", text="Inhomogeneous")
col.prop(cscene, "volume_homogeneous_sampling", text="Homogeneous")
layout.separator()
col = layout.column()
row = col.row()
row.prop(cscene, "volume_max_iterations")
row.prop(cscene, "volume_cell_step")
if cscene.volume_sampling_algorithm == 'VOLUMETRIC_WOODCOCK_EXP' or cscene.volume_sampling_algorithm == 'VOLUMETRIC_WOODCOCK':
col.prop(cscene, "volume_woodcock_max_density")
class CyclesRender_PT_motion_blur(CyclesButtonsPanel, Panel):
bl_label = "Motion Blur"
@@ -780,15 +803,16 @@ class CyclesWorld_PT_volume(CyclesButtonsPanel, Panel):
@classmethod
def poll(cls, context):
# world = context.world
# world and world.node_tree and CyclesButtonsPanel.poll(context)
return False
world = context.world
return world and world.node_tree and CyclesButtonsPanel.poll(context)
def draw(self, context):
layout = self.layout
world = context.world
panel_node_draw(layout, world, 'OUTPUT_WORLD', 'Volume')
layout.prop(world.cycles, "homogeneous_volume")
class CyclesWorld_PT_ambient_occlusion(CyclesButtonsPanel, Panel):
@@ -926,9 +950,8 @@ class CyclesMaterial_PT_volume(CyclesButtonsPanel, Panel):
@classmethod
def poll(cls, context):
# mat = context.material
# mat and mat.node_tree and CyclesButtonsPanel.poll(context)
return False
mat = context.material
return mat and mat.node_tree and CyclesButtonsPanel.poll(context)
def draw(self, context):
layout = self.layout

View File

@@ -422,11 +422,11 @@ static ShaderNode *add_node(Scene *scene, BL::BlendData b_data, BL::Scene b_scen
else if (b_node.is_a(&RNA_ShaderNodeAmbientOcclusion)) {
node = new AmbientOcclusionNode();
}
else if (b_node.is_a(&RNA_ShaderNodeVolumeIsotropic)) {
node = new IsotropicVolumeNode();
else if (b_node.is_a(&RNA_ShaderNodeVolumeScatter)) {
node = new ScatterVolumeNode();
}
else if (b_node.is_a(&RNA_ShaderNodeVolumeTransparent)) {
node = new TransparentVolumeNode();
else if (b_node.is_a(&RNA_ShaderNodeVolumeAbsorption)) {
node = new AbsorptionVolumeNode();
}
else if (b_node.is_a(&RNA_ShaderNodeNewGeometry)) {
node = new GeometryNode();
@@ -958,6 +958,10 @@ void BlenderSync::sync_world(bool update_all)
graph->connect(closure->output("Background"), out->input("Surface"));
}
/* settings */
PointerRNA wmat = RNA_pointer_get(&b_world.ptr, "cycles");
shader->homogeneous_volume = get_boolean(wmat, "homogeneous_volume");
if(b_world) {
/* AO */
BL::WorldLighting b_light = b_world.light_settings();

View File

@@ -166,12 +166,19 @@ void BlenderSync::sync_integrator()
integrator->max_diffuse_bounce = get_int(cscene, "diffuse_bounces");
integrator->max_glossy_bounce = get_int(cscene, "glossy_bounces");
integrator->max_transmission_bounce = get_int(cscene, "transmission_bounces");
integrator->max_volume_bounce = get_int(cscene, "volume_bounces");
integrator->transparent_max_bounce = get_int(cscene, "transparent_max_bounces");
integrator->transparent_min_bounce = get_int(cscene, "transparent_min_bounces");
integrator->transparent_shadows = get_boolean(cscene, "use_transparent_shadows");
integrator->no_caustics = get_boolean(cscene, "no_caustics");
integrator->volume_sampling_algorithm = get_enum(cscene, "volume_sampling_algorithm");
integrator->volume_homogeneous_sampling = get_enum(cscene, "volume_homogeneous_sampling");
integrator->volume_max_iterations = get_int(cscene, "volume_max_iterations");
integrator->volume_cell_step = get_float(cscene, "volume_cell_step");
integrator->volume_woodcock_max_density = get_float(cscene, "volume_woodcock_max_density");
integrator->filter_glossy = get_float(cscene, "blur_glossy");
integrator->seed = get_int(cscene, "seed");

View File

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

View File

@@ -33,6 +33,10 @@
#include "../closure/bssrdf.h"
#endif
#ifdef __VOLUME__
#include "../closure/volume.h"
#endif
CCL_NAMESPACE_BEGIN
ccl_device int bsdf_sample(KernelGlobals *kg, const ShaderData *sd, const ShaderClosure *sc, float randu, float randv, float3 *eval, float3 *omega_in, differential3 *domega_in, float *pdf)
@@ -141,6 +145,13 @@ ccl_device float3 bsdf_eval(KernelGlobals *kg, const ShaderData *sd, const Shade
return OSLShader::bsdf_eval(sd, sc, omega_in, *pdf);
#endif
#ifdef __VOLUME__
/* need this to keep logic in kernel_emission.h direct light in case of volume particle */
/* todo: restructure code so this is handled better */
if (CLOSURE_IS_VOLUME(sc->type))
return volume_eval_phase(sc, sd->I, omega_in, pdf);
#endif
if(dot(sd->Ng, omega_in) >= 0.0f) {
switch(sc->type) {
case CLOSURE_BSDF_DIFFUSE_ID:

View File

@@ -14,53 +14,132 @@
* limitations under the License
*/
#ifndef __VOLUME_H__
#define __VOLUME_H__
CCL_NAMESPACE_BEGIN
/* note: the interfaces here are just as an example, need to figure
* out the right functions and parameters to use */
/* HENYEY-GREENSTEIN CLOSURE */
/* ISOTROPIC VOLUME CLOSURE */
ccl_device int volume_isotropic_setup(ShaderClosure *sc, float density)
/* Given cosine between rays, return probability density that a photon bounces
* to that direction. The g parameter controls how different it is from the
* uniform sphere. g=0 uniform diffuse-like, g=1 close to sharp single ray. */
ccl_device float single_peaked_henyey_greenstein(float cos_theta, float g)
{
sc->type = CLOSURE_VOLUME_ISOTROPIC_ID;
sc->data0 = density;
if(fabsf(g) < 1e-3f)
return M_1_PI_F * 0.25f;
return ((1.0f - g * g) / safe_powf(1.0f + g * g - 2.0f * g * cos_theta, 1.5f)) * (M_1_PI_F * 0.25f);
};
ccl_device int volume_henyey_greenstein_setup(ShaderClosure *sc)
{
sc->type = CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID;
/* positive density */
sc->data0 = max(sc->data0, 0.0f);
/* clamp anisotropy to avoid delta function */
sc->data1 = signf(sc->data1) * min(fabsf(sc->data1), 1.0f - 1e-3f);
return SD_BSDF|SD_BSDF_HAS_EVAL;
}
ccl_device float3 volume_henyey_greenstein_eval_phase(const ShaderClosure *sc, const float3 I, float3 omega_in, float *pdf)
{
float g = sc->data1;
/* note that I points towards the viewer */
float cos_theta = dot(-I, omega_in);
*pdf = single_peaked_henyey_greenstein(cos_theta, g);
return make_float3(*pdf, *pdf, *pdf);
}
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)
{
float g = sc->data1;
float cos_phi, sin_phi, cos_theta;
/* match pdf for small g */
if(fabsf(g) < 1e-3f) {
cos_theta = (1.0f - 2.0f * randu);
}
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);
}
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);
*pdf = single_peaked_henyey_greenstein(cos_theta, g);
*eval = make_float3(*pdf, *pdf, *pdf); /* perfect importance sampling */
#ifdef __RAY_DIFFERENTIALS__
/* todo: implement ray differential estimation */
*domega_in_dx = make_float3(0.0f, 0.0f, 0.0f);
*domega_in_dy = make_float3(0.0f, 0.0f, 0.0f);
#endif
/* todo: do we need a separate light path state for volume scatter? */
return LABEL_DIFFUSE;
}
/* ABSORPTION VOLUME CLOSURE */
ccl_device int volume_absorption_setup(ShaderClosure *sc)
{
sc->type = CLOSURE_VOLUME_ABSORPTION_ID;
/* positive density */
sc->data0 = max(sc->data0, 0.0f);
return SD_VOLUME;
}
ccl_device float3 volume_isotropic_eval_phase(const ShaderClosure *sc, const float3 omega_in, const float3 omega_out)
ccl_device float3 volume_absorption_eval_phase(const ShaderClosure *sc, const float3 I, float3 omega_in, float *pdf)
{
return make_float3(1.0f, 1.0f, 1.0f);
/* eval to zero for delta functions */
return make_float3(0.0f, 0.0f, 0.0f);
}
/* TRANSPARENT VOLUME CLOSURE */
ccl_device int volume_transparent_setup(ShaderClosure *sc, float density)
ccl_device int volume_absorption_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)
{
sc->type = CLOSURE_VOLUME_TRANSPARENT_ID;
sc->data0 = density;
*omega_in = -I;
#ifdef __RAY_DIFFERENTIALS__
*domega_in_dx = -dIdx;
*domega_in_dy = -dIdy;
#endif
return SD_VOLUME;
}
*pdf = 1.0f;
*eval = make_float3(1.0f, 1.0f, 1.0f);
ccl_device float3 volume_transparent_eval_phase(const ShaderClosure *sc, const float3 omega_in, const float3 omega_out)
{
return make_float3(1.0f, 1.0f, 1.0f);
return LABEL_TRANSMIT|LABEL_TRANSPARENT;
}
/* VOLUME CLOSURE */
ccl_device float3 volume_eval_phase(KernelGlobals *kg, const ShaderClosure *sc, const float3 omega_in, const float3 omega_out)
ccl_device float3 volume_eval_phase(const ShaderClosure *sc, const float3 I, float3 omega_in, float *pdf)
{
float3 eval;
switch(sc->type) {
case CLOSURE_VOLUME_ISOTROPIC_ID:
eval = volume_isotropic_eval_phase(sc, omega_in, omega_out);
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID:
eval = volume_henyey_greenstein_eval_phase(sc, I, omega_in, pdf);
break;
case CLOSURE_VOLUME_TRANSPARENT_ID:
eval = volume_transparent_eval_phase(sc, omega_in, omega_out);
case CLOSURE_VOLUME_ABSORPTION_ID:
eval = volume_absorption_eval_phase(sc, I, omega_in, pdf);
break;
default:
eval = make_float3(0.0f, 0.0f, 0.0f);
@@ -70,5 +149,27 @@ ccl_device float3 volume_eval_phase(KernelGlobals *kg, const ShaderClosure *sc,
return eval;
}
ccl_device int volume_sample(const ShaderData *sd, const ShaderClosure *sc, float randu,
float randv, float3 *eval, float3 *omega_in, differential3 *domega_in, float *pdf)
{
int label;
switch(sc->type) {
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID:
label = volume_henyey_greenstein_sample(sc, sd->I, sd->dI.dx, sd->dI.dy, randu, randv, eval, omega_in, &domega_in->dx, &domega_in->dy, pdf);
break;
case CLOSURE_VOLUME_ABSORPTION_ID:
label = volume_absorption_sample(sc, sd->I, sd->dI.dx, sd->dI.dy, randu, randv, eval, omega_in, &domega_in->dx, &domega_in->dy, pdf);
break;
default:
*eval = make_float3(0.0f, 0.0f, 0.0f);
label = LABEL_NONE;
break;
}
return label;
}
CCL_NAMESPACE_END
#endif

View File

@@ -35,7 +35,7 @@ ccl_device_inline void bsdf_eval_init(BsdfEval *eval, ClosureType type, float3 v
if(type == CLOSURE_BSDF_TRANSPARENT_ID)
eval->transparent = value;
else if(CLOSURE_IS_BSDF_DIFFUSE(type))
else if(CLOSURE_IS_BSDF_DIFFUSE(type) || CLOSURE_IS_VOLUME(type))
eval->diffuse = value;
else if(CLOSURE_IS_BSDF_GLOSSY(type))
eval->glossy = value;
@@ -55,7 +55,7 @@ ccl_device_inline void bsdf_eval_accum(BsdfEval *eval, ClosureType type, float3
{
#ifdef __PASSES__
if(eval->use_light_pass) {
if(CLOSURE_IS_BSDF_DIFFUSE(type))
if(CLOSURE_IS_BSDF_DIFFUSE(type) || CLOSURE_IS_VOLUME(type))
eval->diffuse += value;
else if(CLOSURE_IS_BSDF_GLOSSY(type))
eval->glossy += value;

View File

@@ -70,6 +70,45 @@ ccl_device_noinline float3 direct_emissive_eval(KernelGlobals *kg, float rando,
return eval;
}
#ifdef __VOLUME__
/* ToDo: Remove these 2 duplicate functions */
ccl_device float sigma_from_value_(float value, float geom_factor)
{
#if 0
// const float att_magic_eps = 1e-7f;
const float att_magic_eps = 1e-15f;
float attenuation = 1-value;
// protect infinity nan from too big density materials
if( attenuation < att_magic_eps) attenuation = att_magic_eps;
return (-logf( attenuation )/geom_factor);
#else
return value * geom_factor;
#endif
}
ccl_device float get_sigma_sample_(KernelGlobals *kg, ShaderData *sd, float randv, int path_flag, float3 p)
{
ShaderData vsd = *sd;
vsd.P = p;
#ifdef __MULTI_CLOSURE__
int sampled = 0;
// if(vsd.num_closure > 1)
const ShaderClosure *sc = &sd->closure[sampled];
shader_eval_volume(kg, &vsd, randv, path_flag, SHADER_CONTEXT_MAIN);
float v = sc->data0;
#else
shader_eval_volume(kg, &vsd, randv, path_flag, SHADER_CONTEXT_MAIN);
float v = sd->closure.data0;
#endif
return sigma_from_value_(v, 1.0f);
}
#endif
ccl_device_noinline bool direct_emission(KernelGlobals *kg, ShaderData *sd, int lindex,
float randt, float rando, float randu, float randv, Ray *ray, BsdfEval *eval,
bool *is_lamp, int bounce)
@@ -107,7 +146,25 @@ ccl_device_noinline bool direct_emission(KernelGlobals *kg, ShaderData *sd, int
if(ls.shader & SHADER_USE_MIS) {
/* multiple importance sampling */
#ifdef __VOLUME__
// with respect to volume pdf
// W = (pdf_dir*pdf_dir) / (pdf_indirect*pdf_indirect + pdf_dir*pdf_dir)
// pdf_dir = pre_hit_bsdf_pdf * 1.0 * 1.0f / (triangle_area) ?
// ------------angle_pdf------------------ -- volume sphere space pdf----- --light surf orient-- --light surface density--
// pdf_indirect = pre_hit_bsdf_pdf * post_hit_bsdf_pdf * 1.0f/ (4.0f * M_PI_F * t*t) * pre_hit_bsdf_pdf * 1.0f / (triangle_area)
// W = post_hit_bsdf_pdf * 1.0f/ (4.0f * M_PI_F * t*t) * pre_hit_bsdf_pdf
float volume_pdf = 1.0f;
if(( kernel_data.integrator.use_volumetrics ) && (sd->flag & SD_HAS_VOLUME) && (sd->flag & SD_HOMOGENEOUS_VOLUME))
{
float sigma = get_sigma_sample_(kg, sd, 0, 0, make_float3(0.0f, 0.0f, 0.0f));
volume_pdf = sigma * exp(-ls.t * sigma);
}
// float mis_weight = power_heuristic(pdf / volume_pdf, bsdf_pdf);
float mis_weight = power_heuristic(ls.pdf, bsdf_pdf * volume_pdf);
#else
float mis_weight = power_heuristic(ls.pdf, bsdf_pdf);
#endif
light_eval *= mis_weight;
}
@@ -160,7 +217,7 @@ ccl_device_noinline bool direct_emission(KernelGlobals *kg, ShaderData *sd, int
/* Indirect Primitive Emission */
ccl_device_noinline float3 indirect_primitive_emission(KernelGlobals *kg, ShaderData *sd, float t, int path_flag, float bsdf_pdf)
ccl_device_noinline float3 indirect_primitive_emission(KernelGlobals *kg, ShaderData *sd, float t, int path_flag, float bsdf_pdf, float volume_pdf = 0.0f)
{
/* evaluate emissive closure */
float3 L = shader_emissive_eval(kg, sd);
@@ -173,8 +230,11 @@ ccl_device_noinline float3 indirect_primitive_emission(KernelGlobals *kg, Shader
/* multiple importance sampling, get triangle light pdf,
* and compute weight with respect to BSDF pdf */
float pdf = triangle_light_pdf(kg, sd->Ng, sd->I, t);
#ifdef __VOLUME__
float mis_weight = power_heuristic(bsdf_pdf * volume_pdf, pdf);
#else
float mis_weight = power_heuristic(bsdf_pdf, pdf);
#endif
return L*mis_weight;
}

View File

@@ -40,6 +40,10 @@
#include "kernel_subsurface.h"
#endif
#ifdef __VOLUME__
#include "kernel_volume.h"
#endif
CCL_NAMESPACE_BEGIN
ccl_device_inline bool shadow_blocked(KernelGlobals *kg, PathState *state, Ray *ray, float3 *shadow)
@@ -468,6 +472,17 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
#endif
PathState state;
int rng_offset = PRNG_BASE_NUM;
#ifdef __VOLUME__
float3 volume_eval = make_float3(1.0f, 1.0f, 1.0f);
float volume_pdf = 1.0f;
float omega_cache = 0.0f;
uint rng_congruential = lcg_init(*rng + sample*0x51633e2d);
int media_volume_shader = kernel_data.background.shader; // assume camera always in air, need proper CSG stack based solution.
//media_volume_shader = get_media_volume_shader(kg, ray.P);
//media_volume_shader = gmsdr(kg, ray.P);
#endif
#ifdef __CMJ__
int num_samples = kernel_data.integrator.aa_samples;
#else
@@ -524,6 +539,20 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
}
#endif
#ifdef __VOLUME__
if(state.volume_bounce < kernel_data.integrator.max_volume_bounce) {
int vol_result = kernel_path_trace_volume(kg, rng, rng_offset, &rng_congruential, sample, &ray, &isect, isect.t,
&state, media_volume_shader, &throughput, &L, buffer, &ray_pdf, &volume_eval, &volume_pdf, &omega_cache);
if (vol_result == VOLUME_PATH_CONTINUE) {
state.volume_bounce++;
continue;
}
else if (vol_result == VOLUME_PATH_TERMINATED)
break;
}
#endif
if(!hit) {
/* eval background shader if nothing hit */
if(kernel_data.background.transparent && (state.flag & PATH_RAY_CAMERA)) {
@@ -544,6 +573,10 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
break;
}
#ifdef __VOLUME__
omega_cache = 0.0f;
#endif
/* setup shading */
ShaderData sd;
shader_setup_from_ray(kg, &sd, &isect, &ray, state.bounce);
@@ -588,7 +621,11 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
/* emission */
if(sd.flag & SD_EMISSION) {
/* todo: is isect.t wrong here for transparent surfaces? */
#ifdef __VOLUME__
float3 emission = indirect_primitive_emission(kg, &sd, isect.t, state.flag, ray_pdf, volume_pdf);
#else
float3 emission = indirect_primitive_emission(kg, &sd, isect.t, state.flag, ray_pdf);
#endif
path_radiance_accum_emission(&L, throughput, emission, state.bounce);
}
#endif
@@ -639,7 +676,12 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
light_ray.dP = sd.dP;
light_ray.dD = differential3_zero();
#ifdef __VOLUME__
float tmp_volume_pdf;
if(!shadow_blocked_volume(kg, &state, &light_ray, &ao_shadow, &rng_congruential, sample, media_volume_shader, &tmp_volume_pdf))
#else
if(!shadow_blocked(kg, &state, &light_ray, &ao_shadow))
#endif
path_radiance_accum_ao(&L, throughput, ao_alpha, ao_bsdf, ao_shadow, state.bounce);
}
}
@@ -718,8 +760,12 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
if(direct_emission(kg, &sd, -1, light_t, light_o, light_u, light_v, &light_ray, &L_light, &is_lamp, state.bounce)) {
/* trace shadow ray */
float3 shadow;
#ifdef __VOLUME__
float tmp_volume_pdf;
if(!shadow_blocked_volume(kg, &state, &light_ray, &shadow, &rng_congruential, sample, media_volume_shader, &tmp_volume_pdf)) {
#else
if(!shadow_blocked(kg, &state, &light_ray, &shadow)) {
#endif
/* accumulate */
path_radiance_accum_light(&L, throughput, &L_light, shadow, 1.0f, state.bounce, is_lamp);
}
@@ -728,52 +774,81 @@ ccl_device float4 kernel_path_integrate(KernelGlobals *kg, RNG *rng, int sample,
}
#endif
/* no BSDF? we can stop here */
if(!(sd.flag & SD_BSDF))
break;
if(sd.flag & SD_BSDF) {
/* sample BSDF */
float bsdf_pdf;
BsdfEval bsdf_eval;
float3 bsdf_omega_in;
differential3 bsdf_domega_in;
float bsdf_u, bsdf_v;
path_rng_2D(kg, rng, sample, num_samples, rng_offset + PRNG_BSDF_U, &bsdf_u, &bsdf_v);
int label;
/* sample BSDF */
float bsdf_pdf;
BsdfEval bsdf_eval;
float3 bsdf_omega_in;
differential3 bsdf_domega_in;
float bsdf_u, bsdf_v;
path_rng_2D(kg, rng, sample, num_samples, rng_offset + PRNG_BSDF_U, &bsdf_u, &bsdf_v);
int label;
label = shader_bsdf_sample(kg, &sd, bsdf_u, bsdf_v, &bsdf_eval,
&bsdf_omega_in, &bsdf_domega_in, &bsdf_pdf);
label = shader_bsdf_sample(kg, &sd, bsdf_u, bsdf_v, &bsdf_eval,
&bsdf_omega_in, &bsdf_domega_in, &bsdf_pdf);
if(bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval))
break;
if(bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval))
break;
/* modify throughput */
path_radiance_bsdf_bounce(&L, &throughput, &bsdf_eval, bsdf_pdf, state.bounce, label);
/* set labels */
if(!(label & LABEL_TRANSPARENT)) {
ray_pdf = bsdf_pdf;
#ifdef __LAMP_MIS__
ray_t = 0.0f;
/* modify throughput */
#ifdef __VOLUME__
bsdf_eval_mul(&bsdf_eval, volume_eval/volume_pdf);
#endif
min_ray_pdf = fminf(bsdf_pdf, min_ray_pdf);
path_radiance_bsdf_bounce(&L, &throughput, &bsdf_eval, bsdf_pdf, state.bounce, label);
/* set labels */
if(!(label & LABEL_TRANSPARENT)) {
ray_pdf = bsdf_pdf;
#ifdef __LAMP_MIS__
ray_t = 0.0f;
#endif
min_ray_pdf = fminf(bsdf_pdf, min_ray_pdf);
}
/* update path state */
path_state_next(kg, &state, label);
/* setup ray */
ray.P = ray_offset(sd.P, (label & LABEL_TRANSMIT)? -sd.Ng: sd.Ng);
ray.D = bsdf_omega_in;
#ifdef __RAY_DIFFERENTIALS__
ray.dP = sd.dP;
ray.dD = bsdf_domega_in;
#endif
}
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
}
else {
/* no bsdf or volume? we're done */
break;
}
/* update path state */
path_state_next(kg, &state, label);
/* setup ray */
ray.P = ray_offset(sd.P, (label & LABEL_TRANSMIT)? -sd.Ng: sd.Ng);
ray.D = bsdf_omega_in;
#ifdef __VOLUME__
ray_pdf *= volume_pdf;
#endif
/* adjust ray distance for clipping */
if(state.bounce == 0)
ray.t -= sd.ray_length; /* clipping works through transparent */
else
ray.t = FLT_MAX;
#ifdef __RAY_DIFFERENTIALS__
ray.dP = sd.dP;
ray.dD = bsdf_domega_in;
#ifdef __VOLUME__
if (sd.flag & SD_BACKFACING)
media_volume_shader = kernel_data.background.shader;
else
media_volume_shader = sd.shader;
#endif
}

View File

@@ -24,6 +24,7 @@ typedef struct PathState {
int glossy_bounce;
int transmission_bounce;
int transparent_bounce;
int volume_bounce;
} PathState;
ccl_device_inline void path_state_init(PathState *state)
@@ -34,6 +35,7 @@ ccl_device_inline void path_state_init(PathState *state)
state->glossy_bounce = 0;
state->transmission_bounce = 0;
state->transparent_bounce = 0;
state->volume_bounce = 0;
}
ccl_device_inline void path_state_next(KernelGlobals *kg, PathState *state, int label)
@@ -116,7 +118,8 @@ ccl_device_inline float path_state_terminate_probability(KernelGlobals *kg, Path
if((state->bounce >= kernel_data.integrator.max_bounce) ||
(state->diffuse_bounce >= kernel_data.integrator.max_diffuse_bounce) ||
(state->glossy_bounce >= kernel_data.integrator.max_glossy_bounce) ||
(state->transmission_bounce >= kernel_data.integrator.max_transmission_bounce))
(state->transmission_bounce >= kernel_data.integrator.max_transmission_bounce) ||
(state->volume_bounce >= kernel_data.integrator.max_volume_bounce))
{
return 0.0f;
}

View File

@@ -100,6 +100,29 @@ ccl_device uint sobol_lookup(const uint m, const uint frame, const uint ex, cons
return index;
}
ccl_device_inline float path_rng(KernelGlobals *kg, RNG *rng, int sample, int dimension)
{
#ifdef __SOBOL_FULL_SCREEN__
uint result = sobol_dimension(kg, *rng, dimension);
float r = (float)result * (1.0f/(float)0xFFFFFFFF);
return r;
#else
/* compute sobol sequence value using direction vectors */
uint result = sobol_dimension(kg, sample + SOBOL_SKIP, dimension);
float r = (float)result * (1.0f/(float)0xFFFFFFFF);
/* Cranly-Patterson rotation using rng seed */
float shift;
if(dimension & 1)
shift = (*rng >> 16) * (1.0f/(float)0xFFFF);
else
shift = (*rng & 0xFFFF) * (1.0f/(float)0xFFFF);
return r + shift - floorf(r + shift);
#endif
}
ccl_device_inline float path_rng_1D(KernelGlobals *kg, RNG *rng, int sample, int num_samples, int dimension)
{
#ifdef __CMJ__
@@ -194,6 +217,9 @@ ccl_device void path_rng_end(KernelGlobals *kg, ccl_global uint *rng_state, RNG
ccl_device float path_rng(KernelGlobals *kg, RNG& rng, int sample, int dimension)
{
/* implicit mod 2^32 */
rng = (1103515245*(rng) + 12345);
return (float)rng * (1.0f/(float)0xFFFFFFFF);
}
ccl_device_inline float path_rng_1D(KernelGlobals *kg, RNG& rng, int sample, int num_samples, int dimension)

View File

@@ -384,6 +384,48 @@ ccl_device_inline void shader_setup_from_background(KernelGlobals *kg, ShaderDat
sd->ray_length = 0.0f;
sd->ray_depth = bounce;
#ifdef __INSTANCING__
sd->object = ~0;
#endif
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 ray into background or triangle, assume input ray.t +INF or intersection point distance */
ccl_device_inline void shader_setup_from_volume(KernelGlobals *kg, ShaderData *sd, const Ray *ray, int media_shader)
{
/* vectors */
sd->P = ray->P;
sd->N = -ray->D;
sd->Ng = -ray->D;
sd->I = -ray->D;
sd->shader = media_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;
#ifdef __INSTANCING__
sd->object = ~0;
#endif
@@ -430,7 +472,7 @@ ccl_device_inline void _shader_bsdf_multi_eval(KernelGlobals *kg, const ShaderDa
const ShaderClosure *sc = &sd->closure[i];
if(CLOSURE_IS_BSDF(sc->type)) {
if(CLOSURE_IS_BSDF(sc->type) || CLOSURE_IS_VOLUME(sc->type)) {
float bsdf_pdf = 0.0f;
float3 eval = bsdf_eval(kg, sd, sc, omega_in, &bsdf_pdf);
@@ -446,6 +488,30 @@ ccl_device_inline void _shader_bsdf_multi_eval(KernelGlobals *kg, const ShaderDa
*pdf = (sum_sample_weight > 0.0f)? sum_pdf/sum_sample_weight: 0.0f;
}
ccl_device_inline void _shader_volume_bsdf_multi_eval( KernelGlobals *kg, const ShaderData *sd, const float3 omega_in, float *pdf,
int skip_bsdf, BsdfEval *result_eval, float sum_pdf, float sum_sample_weight)
{
for(int i = 0; i< sd->num_closure; i++) {
if(i == skip_bsdf)
continue;
const ShaderClosure *sc = &sd->closure[i];
if( CLOSURE_IS_VOLUME(sc->type)) {
float bsdf_pdf = 0.0f;
float3 eval = bsdf_eval(kg, sd, sc, omega_in, &bsdf_pdf);
if(bsdf_pdf != 0.0f) {
bsdf_eval_accum(result_eval, sc->type, eval*sc->weight);
sum_pdf += bsdf_pdf*sc->sample_weight;
}
sum_sample_weight += sc->sample_weight;
}
}
*pdf = (sum_sample_weight > 0.0f)? sum_pdf/sum_sample_weight: 0.0f;
}
#endif
ccl_device void shader_bsdf_eval(KernelGlobals *kg, const ShaderData *sd,
@@ -459,7 +525,13 @@ ccl_device void shader_bsdf_eval(KernelGlobals *kg, const ShaderData *sd,
const ShaderClosure *sc = &sd->closure;
*pdf = 0.0f;
#if 0
*eval = bsdf_eval(kg, sd, sc, omega_in, pdf)*sc->weight;
#else
float3 tmp_eval = bsdf_eval(kg, sd, sc, omega_in, pdf);
bsdf_eval_init(eval, NBUILTIN_CLOSURES, tmp_eval*sc->weight, kernel_data.film.use_light_pass);
#endif
#endif
}
@@ -521,8 +593,17 @@ ccl_device int shader_bsdf_sample(KernelGlobals *kg, const ShaderData *sd,
#else
/* sample the single closure that we picked */
*pdf = 0.0f;
#if 0
int label = bsdf_sample(kg, sd, &sd->closure, randu, randv, bsdf_eval, omega_in, domega_in, pdf);
*bsdf_eval *= sd->closure.weight;
#else
const ShaderClosure *sc = &sd->closure;
int label;
float3 eval;
label = bsdf_sample(kg, sd, sc, randu, randv, &eval, omega_in, domega_in, pdf);
bsdf_eval_init(bsdf_eval, sc->type, eval*sc->weight, kernel_data.film.use_light_pass);
#endif
return label;
#endif
}
@@ -863,6 +944,7 @@ ccl_device float3 shader_eval_background(KernelGlobals *kg, ShaderData *sd, int
/* Volume */
#if 0 /* XXX unused */
ccl_device float3 shader_volume_eval_phase(KernelGlobals *kg, ShaderData *sd,
float3 omega_in, float3 omega_out)
{
@@ -881,19 +963,95 @@ ccl_device float3 shader_volume_eval_phase(KernelGlobals *kg, ShaderData *sd,
return volume_eval_phase(kg, &sd->closure, omega_in, omega_out);
#endif
}
#endif
/* Volume Evaluation */
ccl_device void shader_eval_volume(KernelGlobals *kg, ShaderData *sd,
float randb, int path_flag, ShaderContext ctx)
{
#ifdef __SVM__
#ifdef __OSL__
if (kg->osl)
OSLShader::eval_volume(kg, sd, randb, path_flag, ctx);
else
#endif
{
#ifdef __SVM__
svm_eval_nodes(kg, sd, SHADER_TYPE_VOLUME, randb, path_flag);
#endif
}
}
ccl_device int shader_volume_bsdf_sample(KernelGlobals *kg, const ShaderData *sd,
float randu, float randv, BsdfEval *bsdf_eval,
float3 *omega_in, differential3 *domega_in, float *pdf)
{
#ifdef __MULTI_CLOSURE__
int sampled = 0;
if(sd->num_closure > 1) {
/* pick a BSDF closure based on sample weights */
float sum = 0.0f;
for(sampled = 0; sampled < sd->num_closure; sampled++) {
const ShaderClosure *sc = &sd->closure[sampled];
if(CLOSURE_IS_VOLUME(sc->type))
sum += sc->sample_weight;
}
float r = sd->randb_closure*sum;
sum = 0.0f;
for(sampled = 0; sampled < sd->num_closure; sampled++) {
const ShaderClosure *sc = &sd->closure[sampled];
if(CLOSURE_IS_VOLUME(sc->type)) {
sum += sd->closure[sampled].sample_weight;
if(r <= sum)
break;
}
}
if(sampled == sd->num_closure) {
*pdf = 0.0f;
return LABEL_NONE;
}
}
const ShaderClosure *sc = &sd->closure[sampled];
int label;
float3 eval = make_float3(0.0f, 0.0f, 0.0f);
*pdf = 0.0f;
label = volume_sample(sd, sc, randu, randv, &eval, omega_in, domega_in, pdf);
if(*pdf != 0.0f) {
bsdf_eval_init(bsdf_eval, sc->type, eval*sc->weight, kernel_data.film.use_light_pass);
if(sd->num_closure > 1) {
float sweight = sc->sample_weight;
_shader_volume_bsdf_multi_eval(kg, sd, *omega_in, pdf, sampled, bsdf_eval, *pdf*sweight, sweight);
}
}
return label;
#else
/* sample the single closure that we picked */
*pdf = 0.0f;
#if 0
int label = volume_sample(sd, &sd->closure, randu, randv, bsdf_eval, omega_in, domega_in, pdf);
*bsdf_eval *= sd->closure.weight;
#else
const ShaderClosure *sc = &sd->closure;
int label;
float3 eval = make_float3(0.0f, 0.0f, 0.0f);
label = volume_sample(sd, sc, randu, randv, &eval, omega_in, domega_in, pdf);
bsdf_eval_init(bsdf_eval, sc->type, eval*sc->weight, kernel_data.film.use_light_pass);
#endif
return label;
#endif
}

View File

@@ -56,12 +56,14 @@ CCL_NAMESPACE_BEGIN
#endif
#define __SUBSURFACE__
#define __CMJ__
#define __VOLUME__
#endif
#ifdef __KERNEL_CUDA__
#define __KERNEL_SHADING__
#define __KERNEL_ADV_SHADING__
#define __BRANCHED_PATH__
//#define __VOLUME__
#endif
#ifdef __KERNEL_OPENCL__
@@ -180,7 +182,18 @@ enum PathTraceDimension {
PRNG_LIGHT_V = 5,
PRNG_LIGHT_F = 6,
PRNG_TERMINATE = 7,
PRNG_BOUNCE_NUM = 8
PRNG_VOLUME_DENSITY = 8,
PRNG_VOLUME_DISTANCE = 9,
// not need, reuse PRNG_BSDF_UV because we never blend particle and surface
PRNG_VOLUME_BRDF_U = PRNG_BSDF_U,
PRNG_VOLUME_BRDF_V = PRNG_BSDF_V,
PRNG_VOLUME_BRDF = PRNG_BSDF,
PRNG_VOLUME_SHADOW_DENSITY = 10,
PRNG_VOLUME_SHADOW_DISTANCE = 11,
PRNG_VOLUME_TERMINATE = PRNG_TERMINATE,
PRNG_BOUNCE_NUM = 12
};
enum SamplingPattern {
@@ -224,18 +237,12 @@ enum PathRayFlag {
typedef enum ClosureLabel {
LABEL_NONE = 0,
LABEL_CAMERA = 1,
LABEL_LIGHT = 2,
LABEL_BACKGROUND = 4,
LABEL_TRANSMIT = 8,
LABEL_REFLECT = 16,
LABEL_VOLUME = 32,
LABEL_OBJECT = 64,
LABEL_DIFFUSE = 128,
LABEL_GLOSSY = 256,
LABEL_SINGULAR = 512,
LABEL_TRANSPARENT = 1024,
LABEL_STOP = 2048
LABEL_TRANSMIT = 1,
LABEL_REFLECT = 2,
LABEL_DIFFUSE = 4,
LABEL_GLOSSY = 8,
LABEL_SINGULAR = 16,
LABEL_TRANSPARENT = 32,
} ClosureLabel;
/* Render Passes */
@@ -480,7 +487,8 @@ typedef enum ShaderContext {
SHADER_CONTEXT_EMISSION = 2,
SHADER_CONTEXT_SHADOW = 3,
SHADER_CONTEXT_SSS = 4,
SHADER_CONTEXT_NUM = 5
SHADER_CONTEXT_VOLUME = 5,
SHADER_CONTEXT_NUM = 6
} ShaderContext;
/* Shader Data
@@ -506,13 +514,14 @@ enum ShaderDataFlag {
SD_USE_MIS = 512, /* direct light sample */
SD_HAS_TRANSPARENT_SHADOW = 1024, /* has transparent shadow */
SD_HAS_VOLUME = 2048, /* has volume shader */
SD_HOMOGENEOUS_VOLUME = 4096, /* has homogeneous volume */
SD_HAS_BSSRDF_BUMP = 8192, /* bssrdf normal uses bump */
SD_HAS_ONLY_VOLUME = 4096, /* has only volume shader, no surface */
SD_HOMOGENEOUS_VOLUME = 8192, /* has homogeneous volume */
SD_HAS_BSSRDF_BUMP = 16384, /* bssrdf normal uses bump */
/* object flags */
SD_HOLDOUT_MASK = 16384, /* holdout for camera rays */
SD_OBJECT_MOTION = 32768, /* has object motion blur */
SD_TRANSFORM_APPLIED = 65536 /* vertices have transform applied */
SD_HOLDOUT_MASK = 32768, /* holdout for camera rays */
SD_OBJECT_MOTION = 65536, /* has object motion blur */
SD_TRANSFORM_APPLIED = 131072 /* vertices have transform applied */
};
struct KernelGlobals;
@@ -734,6 +743,7 @@ typedef struct KernelIntegrator {
int max_diffuse_bounce;
int max_glossy_bounce;
int max_transmission_bounce;
int max_volume_bounce;
/* transparent */
int transparent_min_bounce;
@@ -744,6 +754,14 @@ typedef struct KernelIntegrator {
int no_caustics;
float filter_glossy;
/* volumetric */
int use_volumetrics;
int volume_sampling_algorithm; // when homogeneous = false, 0 = cell marching, 1 = woodcock delta tracking
int volume_homogeneous_sampling;
int volume_max_iterations;
float volume_cell_step;
float volume_woodcock_max_density; // set it by hands, guess max density in volume, sorry no auto generation for now
/* seed */
int seed;

View File

@@ -0,0 +1,821 @@
/*
* 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
#ifdef __VOLUME__
#define rand_congruential() (lcg_step_float(rng_congruential))
#define VOLUME_PATH_TERMINATED 0
#define VOLUME_PATH_CONTINUE 1
#define VOLUME_PATH_PARTICLE_MISS 2
/* Epsilon defines */
#define SIGMA_MAGIC_EPS 1e-15f
#define DISTANCE_MAGIC_EPS 1e-4f
#define RAND_MAGIC_EPS 0.00001f
// probability to hit volume if far intersection exist, 50% by default
// help to speedup noise clear when tiny object or low density.
//#define __VOLUME_USE_GUARANTEE_HIT_PROB 1
#define VOLUME_GUARANTEE_HIT_PROB 0.5f
ccl_device float sigma_from_value(float value, float geom_factor)
{
/* return "sigma" that required to get "value" attenuation at "geom_factor" distance of media.
to make input value resemble "alpha color" in 2d grapics , "value"=0 mean ransparent, 1 = opaque, so there is another a=1-v step.*/
#if 0
const float att_magic_eps = 1e-15f; // 1e-7f?
float attenuation = 1-value;
// protect infinity nan from too big density materials
if(attenuation < att_magic_eps)
attenuation = att_magic_eps;
return (-logf(attenuation) / geom_factor);
#else
return value * geom_factor;
#endif
}
ccl_device float get_sigma_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);
/* todo: this assumes global density and is broken, density is per closure! */
int sampled = 0;
const ShaderClosure *sc = &sd->closure[sampled];
float v = sc->data0;
return sigma_from_value(v, 1.0f);
}
ccl_device int get_media_volume_shader(KernelGlobals *kg, float3 P, int bounce)
{
/* check all objects that intersect random ray from given point, assume we have perfect geometry (all meshes closed, correct faces direct
we can calculate current volume material, assuming background as start, and reassign when we cross face */
if(!kernel_data.integrator.use_volumetrics)
return kernel_data.background.shader;
Ray ray;
ray.P = P;
// ray.D = make_float3(0.0f, 0.0f, 1.0f);
ray.D = normalize(make_float3(0.1f, 0.5f, 0.9f)); //a bit more wild. Use random dir maybe ?
ray.t = FLT_MAX;
Intersection isect;
int stack = 0;
// while (scene_intersect(kg, &ray, PATH_RAY_SHADOW, &isect))
#ifdef __HAIR__
while(scene_intersect(kg, &ray, 0, &isect, NULL, 0.0f, 0.0f))
#else
while(scene_intersect(kg, &ray, 0, &isect))
#endif
{
ShaderData sd;
shader_setup_from_ray(kg, &sd, &isect, &ray, bounce);
shader_eval_surface(kg, &sd, 0.0f, 0, SHADER_CONTEXT_MAIN); // not needed ?
if(sd.flag & SD_BACKFACING) {
stack--;
if(stack <= 0 && (sd.flag & SD_HAS_VOLUME))
return sd.shader; // we are inside of object, as first triangle hit is from inside
}
else
stack++; //we are outside, push stack to skip closed objects
// ray.P = ray_offset(sd.P, -sd.Ng);
ray.P = ray_offset(sd.P, (sd.flag & SD_BACKFACING)? sd.Ng : -sd.Ng);
ray.t = FLT_MAX;
}
return kernel_data.background.shader;
}
/* used */
/* Volumetric sampling */
ccl_device int kernel_volumetric_woodcock_sampler(KernelGlobals *kg, RNG *rng_congruential, ShaderData *sd,
Ray ray, int path_flag, ShaderContext ctx, float end, float *new_t, float *pdf)
{
/* Google "woodcock delta tracker" algorithm, must be preprocessed to guess max density in volume,
* better keep it as close to density as possible or we got lot of tiny steps and spend millenniums
* marching single volume ray segment. 0.95 is good default. */
float magic_eps = 1e-4f;
int max_iter = kernel_data.integrator.volume_max_iterations;
//float max_prob = kernel_data.integrator.volume_woodcock_max_density;
//float max_sigma_t = sigma_from_value(max_prob, 1.0f);
float max_sigma_t = 0.0f;
float step = end / 10.0f; // uses 10 segments for maximum - needs parameter
for(float s = 0.0f ; s < end ; s+= step)
max_sigma_t = max(max_sigma_t , get_sigma_sample(kg, sd, path_flag, ctx, ray.P + ray.D * s));
int i = 0;
float t = 0;
float sigma_factor = 1.0f;
*pdf = 1.0f;
if((end < magic_eps) || (max_sigma_t == 0.0f))
return 0;
do {
float r = rand_congruential();
t += -logf(r) / max_sigma_t;
// *pdf *= sigma_factor; // pdf that previous position was transparent pseudo-particle, obviously 1.0 for first loop step
// *pdf *= max_sigma_t * r; // pdf of particle collision, based on conventional freefly homogeneous distance equation
}
while((sigma_factor = (get_sigma_sample(kg, sd, path_flag, ctx, ray.P + ray.D * t) / max_sigma_t)) < rand_congruential() &&
t < (end - magic_eps) &&
i++ < max_iter);
if(t < (end - magic_eps) && i <= max_iter) {
*new_t = t;
sd->P = ray.P + ray.D * t;
// *pdf *= sigma_factor; // fixme: is it necessary ?
return 1;
}
// Assume rest of media up to end is homogeneous, it helps when using woodcock in outdoor scenes that tend to have continuous density.
if((i > max_iter) && (t < (end - magic_eps))) {
float sigma = get_sigma_sample(kg, sd, path_flag, ctx, ray.P + ray.D * t);
if(sigma < magic_eps)
return 0;
float r = rand_congruential();
t += -logf(r) / sigma;
*pdf *= sigma * r;
if(t < (end - magic_eps)) {
// double check current sigma, just to be sure we do not register event for null media.
if(get_sigma_sample(kg, sd, path_flag, ctx, ray.P + ray.D * t) > magic_eps) {
*new_t = t;
sd->P = ray.P + ray.D * t;
return 1;
}
}
}
return 0;
}
ccl_device int kernel_volumetric_woodcock_sampler2(KernelGlobals *kg, RNG *rng_congruential, ShaderData *sd,
Ray ray, int path_flag, ShaderContext ctx, float end, float *new_t, float *pdf)
{
/* Google "woodcock delta tracker" algorithm, must be preprocessed to guess max density in volume,
* better keep it as close to density as possible or we got lot of tiny steps and spend millenniums
* marching single volume ray segment. 0.95 is good default. */
float magic_eps = 1e-4f;
int max_iter = kernel_data.integrator.volume_max_iterations;
float max_prob = kernel_data.integrator.volume_woodcock_max_density;
float max_sigma_t = sigma_from_value(max_prob, 1.0f);
int i = 0;
float t = 0;
float sigma_factor = 1.0f;
*pdf = 1.0f;
if(end < magic_eps)
return 0;
if(max_sigma_t == 0.0f)
return 0;
do {
float r = rand_congruential();
t += -logf(r) / max_sigma_t;
// *pdf *= sigma_factor; // pdf that previous position was transparent pseudo-particle, obviously 1.0 for first loop step
// *pdf *= max_sigma_t * r; // pdf of particle collision, based on conventional freefly homogeneous distance equation
}
while((sigma_factor = (get_sigma_sample(kg, sd, path_flag, ctx, ray.P + ray.D * t) / max_sigma_t)) < rand_congruential() &&
(t < (end - magic_eps)) &&
i++ < max_iter);
if(t < (end - magic_eps) && i <= max_iter) {
*new_t = t;
sd->P = ray.P + ray.D * t;
// *pdf *= sigma_factor; // fixme: is it necessary ?
return 1;
}
// *new_t = end;
#if 1
// last chance trick, we cannot iterate infinity, but we can force to homogeneous last step after max_iter,
// assume rest of media up to end is homogeneous, it help to use woodcock even in outdoor scenes that tend to have continuous density
// even if vary a bit in close distance. of course it make sampling biased (not respect actual density).
if((i > max_iter) && (t < (end - magic_eps))) {
float sigma = get_sigma_sample(kg, sd, path_flag, ctx, ray.P + ray.D * t);
if(sigma < magic_eps)
return 0;
// t += -logf( rand_congruential()) / sigma;
float r = rand_congruential();
t += -logf(r) / sigma;
*pdf *= sigma * r;
if(t < (end - magic_eps)) {
// double check current sigma, just to be sure we do not register event for null media.
if(get_sigma_sample(kg, sd, path_flag, ctx, ray.P + ray.D * t) > magic_eps) {
*new_t = t;
sd->P = ray.P + ray.D * t;
return 1;
}
}
}
#endif
return 0;
}
ccl_device int kernel_volumetric_marching_sampler(KernelGlobals *kg, RNG *rng_congruential, ShaderData *sd,
Ray ray, int path_flag, ShaderContext ctx, float end, float *new_t, float *pdf)
{
int max_steps = kernel_data.integrator.volume_max_iterations;
//float step = end != FLT_MAX ? end / max_steps : kernel_data.integrator.volume_cell_step;
float step = kernel_data.integrator.volume_cell_step;
int cell_count = 0;
float current_cell_near_boundary_distance = 0.0f;
float random_jitter_offset = rand_congruential() * step;
*pdf = 1.0f;
float t = 0.0f;
float integral = 0.0f;
float randsamp = rand_congruential();
float previous_cell_average_sigma = 0.0f;
float current_cell_average_sigma = 0.0f;
float root = -logf(randsamp);
float intstep = 0.0f;
do {
current_cell_near_boundary_distance += step;
t = current_cell_near_boundary_distance + random_jitter_offset;
previous_cell_average_sigma = current_cell_average_sigma;
current_cell_average_sigma = get_sigma_sample(kg, sd, path_flag, ctx, ray.P + ray.D * t);
intstep = (previous_cell_average_sigma + current_cell_average_sigma) * step * 0.5f;
integral += intstep;
cell_count++;
}
while((integral < root) && (cell_count < max_steps) && (t < end));
if((cell_count >= max_steps) || (t > end)) {
return 0;
}
t = current_cell_near_boundary_distance - ((integral - root) / intstep) * step;
//*pdf = randsamp * current_cell_average_sigma;
*new_t = t;
sd->P = ray.P + ray.D * *new_t;
return 1;
}
ccl_device int kernel_volumetric_marching_sampler2(KernelGlobals *kg, RNG *rng_congruential, ShaderData *sd,
Ray ray, int path_flag, ShaderContext ctx, float end, float *new_t, float *pdf)
{
float step = kernel_data.integrator.volume_cell_step;
int max_steps = min(kernel_data.integrator.volume_max_iterations, ceil_to_int(end / step));
int cell_count = 0;
float current_cell_near_boundary_distance;
float random_jitter_offset = rand_congruential() * step;
float t = 0.0f;
do {
current_cell_near_boundary_distance = step * (float)cell_count;
float current_cell_average_sigma = get_sigma_sample(kg, sd, path_flag, ctx, ray.P + ray.D * (current_cell_near_boundary_distance + random_jitter_offset));
if(current_cell_average_sigma < SIGMA_MAGIC_EPS)
t = end + step;
else
t = -logf( rand_congruential()) / current_cell_average_sigma;
cell_count++;
}
while((t > step) && (cell_count < max_steps));
*pdf = 1.0f;
if((cell_count >= max_steps) && ((current_cell_near_boundary_distance + t) > end))
return 0;
*new_t = current_cell_near_boundary_distance + t;
sd->P = ray.P + ray.D * *new_t;
return 1;
}
ccl_device int kernel_volumetric_homogeneous_sampler(KernelGlobals *kg, float randv, float randp, ShaderData *sd,
Ray ray, int path_flag, ShaderContext ctx, float end, float *new_t, float *pdf, float *eval, float *omega_cache)
{
/* return pdf of perfect importance volume sampling at given distance
only for homogeneous case, of course.
TODO: cache sigma to avoid complex shader call (very CPU/GPU expensive) */
float distance = end;
float sigma;
*pdf = 1.0f; /* pdf used for importance sampling of homogeneous data, it just sigma if x=log(1-rand())/sigma used as sampling distance */
*eval = 1.0f;
if((distance < DISTANCE_MAGIC_EPS) || (randv < RAND_MAGIC_EPS)) {
/* tiny volume and preventing log (0), *new_t = end */
return 0;
}
if (omega_cache) {
if(*omega_cache == 0.0f) {
*omega_cache = get_sigma_sample(kg, sd, path_flag, ctx, ray.P);
}
sigma = *omega_cache;
}
else
sigma = get_sigma_sample(kg, sd, path_flag, ctx, ray.P);
if(sigma < SIGMA_MAGIC_EPS) {
/* Very transparent volume - Protect div by 0, *new_t = end; */
return 0;
}
#ifdef __VOLUME_USE_GUARANTEE_HIT_PROB
/* split randv by VOLUME_GUARANTEE_HIT_PROB */
if (randv > VOLUME_GUARANTEE_HIT_PROB) {
// miss
*pdf = VOLUME_GUARANTEE_HIT_PROB;
return 0;
}
else {
// assume we hit media particle, need adjust randv
randv = 1.0f - randv / VOLUME_GUARANTEE_HIT_PROB;
//*pdf = sigma * randv * VOLUME_GUARANTEE_HIT_PROB;
}
#endif
float sample_distance = -logf(randv) / sigma;
if(sample_distance > end) { // nothing hit in between [start(0.0), end]
//*eval = sigma * exp(-distance * sigma);
*eval = sigma * randv;
*pdf = sigma * randv;
return 0;
}
// we hit particle!
*new_t = sample_distance;
*pdf = sigma * randv;
*eval = sigma * randv;
sd->P = ray.P + ray.D * sample_distance;
return 1;
}
ccl_device int kernel_volumetric_equiangular_sampler(KernelGlobals *kg, RNG *rng_congruential, float randv, float randp,
ShaderData *sd, Ray ray, int path_flag, ShaderContext ctx, float end, float *new_t, float *pdf, float *eval, float *omega_cache)
{
float distance = end;
float sigma;
*pdf = 1.0f; /* pdf used for importance sampling of homogeneous data, it just sigma if x=log(1-rand())/sigma used as sampling distance */
*eval = 1.0f;
if((distance < DISTANCE_MAGIC_EPS) || (randv < RAND_MAGIC_EPS)) {
/* tiny volume and preventing log (0), *new_t = end */
return 0;
}
/* sample a light and position on int */
float light_t = rand_congruential();
float light_u = rand_congruential();
float light_v = rand_congruential();
LightSample ls;
light_sample(kg, light_t, light_u, light_v, ray.time, ray.P, &ls);
if(ls.pdf == 0.0f)
return 0;
if (omega_cache) {
if(*omega_cache == 0.0f) {
*omega_cache = get_sigma_sample(kg, sd, path_flag, ctx, ray.P);
}
sigma = *omega_cache;
}
else
sigma = get_sigma_sample(kg, sd, path_flag, ctx, ray.P);
if(sigma < SIGMA_MAGIC_EPS) {
/* Very transparent volume - Protect div by 0, *new_t = end; */
return 0;
}
float sample_distance = dot((ls.P - ray.P) , ray.D);
float D = sqrtf(len_squared(ls.P - ray.P) - sample_distance * sample_distance);
float atheta = atan(sample_distance / D);
//float endtheta = atan((end - sample_distance) / D);
float t = D * tan((randv * M_PI_2_F) - (1 - randv) * atheta);
sample_distance += t;
*pdf = D / ((M_PI_2_F + atheta) * (D * D + t * t));
*eval = *pdf;
if(sample_distance > end)
return 0;
else
/* we hit particle */
*new_t = sample_distance;
sd->P = ray.P + ray.D * sample_distance;
return 1;
}
ccl_device int kernel_volumetric_sample(KernelGlobals *kg, RNG *rng_congruential, int pass, float randv, float randp,
ShaderData *sd, Ray ray, float distance, float *particle_isect_t, int path_flag, ShaderContext ctx,
float *pdf, float *eval, float3 *throughput, float *omega_cache = NULL)
{
/* sample point on volumetric ray (return false - no hit, true - hit : fill new hit t value on path [start, end] */
if((sd->flag & SD_HAS_VOLUME) == 0 || (distance < DISTANCE_MAGIC_EPS))
return 0; /* empty volume shader slot or escape from bottle when scattering in solid */
*pdf = 1.0f;
*eval = 1.0f;
*particle_isect_t = 0.0f;
if(sd->flag & SD_HOMOGENEOUS_VOLUME) {
/* homogeneous media */
if(kernel_data.integrator.volume_homogeneous_sampling == 1 && kernel_data.integrator.num_all_lights) {
bool ok = kernel_volumetric_equiangular_sampler(kg, rng_congruential, randv, randp, sd, ray, path_flag, ctx, distance, particle_isect_t, pdf, eval, omega_cache);
return ok;
}
else {
bool ok = kernel_volumetric_homogeneous_sampler(kg, randv, randp, sd, ray, path_flag, ctx, distance, particle_isect_t, pdf, eval, omega_cache);
return ok;
}
}
else {
if(kernel_data.integrator.volume_sampling_algorithm == 3) {
/* Woodcock delta tracking */
bool ok = kernel_volumetric_woodcock_sampler(kg, rng_congruential, sd, ray, path_flag, ctx, distance, particle_isect_t, pdf);
*eval = *pdf;
return ok;
}
else if(kernel_data.integrator.volume_sampling_algorithm == 2){
/* Volume marching. Move particles through one region at a time, until collision occurs */
bool ok = kernel_volumetric_marching_sampler(kg, rng_congruential, sd, ray, path_flag, ctx, distance, particle_isect_t, pdf);
*eval = *pdf;
return ok;
}
else if(kernel_data.integrator.volume_sampling_algorithm == 1){
/* Woodcock delta tracking */
bool ok = kernel_volumetric_woodcock_sampler2(kg, rng_congruential, sd, ray, path_flag, ctx, distance, particle_isect_t, pdf);
*eval = *pdf;
return ok;
}
else {
/* Volume marching. Move particles through one region at a time, until collision occurs */
bool ok = kernel_volumetric_marching_sampler2(kg, rng_congruential, sd, ray, path_flag, ctx, distance, particle_isect_t, pdf);
*eval = *pdf;
return ok;
}
}
}
/* Volumetric shadows */
ccl_device float3 kernel_volume_get_shadow_attenuation(KernelGlobals *kg, RNG *rng_congruential, int sample,
Ray *light_ray, int media_volume_shader, float *volume_pdf)
{
// helper for shadow probes, optimised for homogeneous volume, variable density other return 0 or 1.
// assume there are no objects inside light_ray, so it mus be preceded by shdow_blocked() or scene_intersect().
float3 attenuation = make_float3(1.0f, 1.0f, 1.0f);
*volume_pdf = 1.0f;
if(!kernel_data.integrator.use_volumetrics)
return attenuation;
ShaderData tsd;
shader_setup_from_volume(kg, &tsd, light_ray, media_volume_shader);
if((tsd.flag & SD_HAS_VOLUME) != 0) { // check for empty volume shader
float tparticle_isect_t;
float tpdf;
float teval;
float3 tthroughput = make_float3(1.0f, 1.0f, 1.0f);
if(tsd.flag & SD_HOMOGENEOUS_VOLUME) {
// special case
float sigma = get_sigma_sample(kg, &tsd, PATH_RAY_SHADOW, SHADER_CONTEXT_SHADOW, light_ray->P);
if (sigma < 0.0f) sigma = 0.0f;
// sigma = 1.0f;
#if 0
// get transition probability
BsdfEval eval;
float3 omega_in = -tsd.I;
float transition_pdf;
shader_bsdf_eval(kg, &tsd, omega_in, &eval, &transition_pdf);
// sigma /= 1.0f / 4 * M_PI; //diffusion?
sigma /= transition_pdf; //diffusion?
#endif
float magic_eps = 0.00001f;
// if( light_ray->t < magic_eps)
if( light_ray->t < magic_eps || (sigma < 0.00001f))
attenuation = make_float3(1.0f, 1.0f, 1.0f);
else {
*volume_pdf = sigma * exp(-light_ray->t * sigma);
/* todo: sigma should become a float3 with per color channel
* density returned by get_sigma_sample */
attenuation.x = exp(-light_ray->t * sigma);
attenuation.y = exp(-light_ray->t * sigma);
attenuation.z = exp(-light_ray->t * sigma);
}
}
else {
/* todo: these are actually unused in heterogeneous volumes, we can
* reorganize this code so it's more clear which numbers are used */
float trandv = 0.0f;
float trandp = 0.0f;
if(!kernel_volumetric_sample(kg, rng_congruential, sample, trandv, trandp, &tsd, *light_ray, light_ray->t, &tparticle_isect_t, PATH_RAY_SHADOW, SHADER_CONTEXT_SHADOW, &tpdf, &teval, &tthroughput))
attenuation = make_float3(1.0f, 1.0f, 1.0f);
else {
*volume_pdf = 0.0f;
attenuation = make_float3(0.0f, 0.0f, 0.0f);
}
}
}
else
attenuation = make_float3(1.0f, 1.0f, 1.0f);
return attenuation;
}
ccl_device_inline bool shadow_blocked_volume(KernelGlobals *kg, PathState *state, Ray *ray, float3 *shadow,
RNG *rng_congruential, int sample, int media_volume_shader, float *volume_pdf)
{
*shadow = make_float3(1.0f, 1.0f, 1.0f);
*volume_pdf = 1.0f;
if(ray->t == 0.0f)
return false;
float tmp_volume_pdf;
uint visibility = path_state_ray_visibility(kg, state);
visibility |= PATH_RAY_SHADOW_OPAQUE;
Intersection isect;
#ifdef __HAIR__
// bool result = scene_intersect(kg, ray, PATH_RAY_SHADOW_OPAQUE, &isect, NULL, 0.0f, 0.0f);
bool result = scene_intersect(kg, ray, visibility, &isect, NULL, 0.0f, 0.0f);
#else
// bool result = scene_intersect(kg, ray, PATH_RAY_SHADOW_OPAQUE, &isect);
bool result = scene_intersect(kg, ray, visibility, &isect);
#endif
#ifdef __TRANSPARENT_SHADOWS__
if(result && kernel_data.integrator.transparent_shadows) {
/* transparent shadows work in such a way to try to minimize overhead
* in cases where we don't need them. after a regular shadow ray is
* cast we check if the hit primitive was potentially transparent, and
* only in that case start marching. this gives on extra ray cast for
* the cases were we do want transparency.
*
* also note that for this to work correct, multi close sampling must
* be used, since we don't pass a random number to shader_eval_surface */
if(shader_transparent_shadow(kg, &isect)) {
float3 throughput = make_float3(1.0f, 1.0f, 1.0f);
float3 Pend = ray->P + ray->D*ray->t;
int bounce = state->transparent_bounce;
for(;;) {
if(bounce >= kernel_data.integrator.transparent_max_bounce) {
*shadow = make_float3(1.0f, 1.0f, 1.0f);
*volume_pdf = 1.0f;
return true;
}
else if(bounce >= kernel_data.integrator.transparent_min_bounce) {
/* todo: get random number somewhere for probabilistic terminate */
#if 0
float probability = average(throughput);
float terminate = 0.0f;
if(terminate >= probability)
return true;
throughput /= probability;
#endif
}
#ifdef __HAIR__
// if(!scene_intersect(kg, ray, PATH_RAY_SHADOW_TRANSPARENT, &isect, NULL, 0.0f, 0.0f)) {
if(!scene_intersect(kg, ray, visibility, &isect, NULL, 0.0f, 0.0f)) {
#else
// if(!scene_intersect(kg, ray, PATH_RAY_SHADOW_TRANSPARENT, &isect)) {
if(!scene_intersect(kg, ray, visibility, &isect)) {
#endif
float3 attenuation = kernel_volume_get_shadow_attenuation(kg, rng_congruential, sample, ray, media_volume_shader, &tmp_volume_pdf);
throughput *= attenuation;
*shadow *= throughput;
*volume_pdf *= tmp_volume_pdf;
return false;
}
if(!shader_transparent_shadow(kg, &isect)) {
// *shadow = make_float3(1.0f, 1.0f, 1.0f);
*shadow = make_float3(0.0f, 0.0f, 0.0f); // black
*volume_pdf = 1.0f;
return true;
}
Ray v_ray = *ray;
v_ray.t = isect.t;
float3 attenuation = kernel_volume_get_shadow_attenuation(kg, rng_congruential, sample, &v_ray, media_volume_shader, &tmp_volume_pdf);
*volume_pdf *= tmp_volume_pdf;
throughput *= attenuation;
ShaderData sd;
shader_setup_from_ray(kg, &sd, &isect, ray, state->bounce);
if(!(sd.flag & SD_HAS_ONLY_VOLUME)) {
shader_eval_surface(kg, &sd, 0.0f, PATH_RAY_SHADOW, SHADER_CONTEXT_SHADOW);
throughput *= shader_bsdf_transparency(kg, &sd);
}
ray->P = ray_offset(sd.P, -sd.Ng);
if(ray->t != FLT_MAX)
ray->D = normalize_len(Pend - ray->P, &ray->t);
bounce++;
if(media_volume_shader == kernel_data.background.shader)
media_volume_shader = sd.shader;
else
media_volume_shader = kernel_data.background.shader;
}
}
}
#endif
if(!result) {
float3 attenuation = kernel_volume_get_shadow_attenuation(kg, rng_congruential, sample, ray, media_volume_shader, &tmp_volume_pdf);
*shadow *= attenuation;
*volume_pdf *= tmp_volume_pdf;
}
return result;
}
/* volumetric tracing */
ccl_device int kernel_path_trace_volume(KernelGlobals *kg, RNG *rng, int rng_offset, RNG *rng_congruential, int sample,
Ray *ray, Intersection *isect, float isect_t, PathState *state, int media_volume_shader, float3 *throughput,
PathRadiance *L, ccl_global float *buffer, float *ray_pdf, float3 *volume_eval, float *volume_pdf, float *omega_cache)
{
// we sampling volume using different algorithms, respect importance sampling
*volume_pdf = 1.0f;
*volume_eval = make_float3( *volume_pdf, *volume_pdf, *volume_pdf);
if(!kernel_data.integrator.use_volumetrics)
return VOLUME_PATH_PARTICLE_MISS;
ShaderData vsd;
shader_setup_from_volume(kg, &vsd, ray, media_volume_shader);
if((vsd.flag & SD_HAS_VOLUME) == 0)
return VOLUME_PATH_PARTICLE_MISS; // null volume slot, assume transparent.
float randv = path_rng(kg, rng, sample, rng_offset + PRNG_VOLUME_DISTANCE);
float randp = path_rng(kg, rng, sample, rng_offset + PRNG_VOLUME_DENSITY);
float particle_isect_t;
float pdf;
float eval;
if(kernel_volumetric_sample(kg, rng_congruential, sample, randv, randp, &vsd,
*ray, isect_t, &particle_isect_t, state->flag, SHADER_CONTEXT_VOLUME, &pdf, &eval, throughput, omega_cache)) {
*volume_pdf = pdf;
*volume_eval = make_float3( eval, eval, eval);
//if (vsd.flag & SD_HOMOGENEOUS_VOLUME)
// *volume_eval = make_float3( *volume_pdf, *volume_pdf, *volume_pdf); // perfect importance sampling for homogeneous
kernel_write_data_passes(kg, buffer, L, &vsd, sample, state->flag, *throughput);
#ifdef __EMISSION__
/* emission */
if(vsd.flag & SD_EMISSION) {
// float3 emission = indirect_emission(kg, &vsd, particle_isect_t, state->flag, *ray_pdf) / pdf;
// float3 emission = indirect_emission(kg, &vsd, particle_isect_t, state->flag, *ray_pdf, *volume_pdf) / pdf;
// float3 emission = indirect_emission(kg, &vsd, particle_isect_t, state->flag, *ray_pdf, *volume_pdf);
float3 emission = indirect_primitive_emission(kg, &vsd, particle_isect_t, state->flag, *ray_pdf, *volume_pdf);
path_radiance_accum_emission(L, *throughput, emission, state->bounce);
}
#endif
/* path termination. this is a strange place to put the termination, it's
mainly due to the mixed in MIS that we use. gives too many unneeded
shader evaluations, only need emission if we are going to terminate */
float probability = path_state_terminate_probability(kg, state, *throughput);
float terminate = path_rng(kg, rng, sample, rng_offset + PRNG_VOLUME_TERMINATE);
if(terminate >= probability)
return VOLUME_PATH_TERMINATED;
*throughput /= probability;
#ifdef __EMISSION__
if(kernel_data.integrator.use_direct_light) {
/* sample illumination from lights to find path contribution */
if(vsd.flag & SD_BSDF_HAS_EVAL) {
float light_t = path_rng(kg, rng, sample, rng_offset + PRNG_LIGHT);
float light_o = path_rng(kg, rng, sample, rng_offset + PRNG_LIGHT_F);
float light_u = path_rng(kg, rng, sample, rng_offset + PRNG_LIGHT_U);
float light_v = path_rng(kg, rng, sample, rng_offset + PRNG_LIGHT_V);
Ray light_ray;
BsdfEval L_light;
// int lamp;
bool is_lamp;
#ifdef __OBJECT_MOTION__
light_ray.time = vsd.time;
#endif
#ifdef __MULTI_LIGHT__ /* ToDo: Fix, Branched Path trace feature */
/* index -1 means randomly sample from distribution */
int i = (kernel_data.integrator.num_distribution)? -1: 0;
for(; i < kernel_data.integrator.num_all_lights; i++) {
#else
const int i = -1;
#endif
if(direct_emission(kg, &vsd, i, light_t, light_o, light_u, light_v, &light_ray, &L_light, &is_lamp, state->bounce)) {
/* trace shadow ray */
float3 shadow;
float tmp_volume_pdf;
if(!shadow_blocked_volume(kg, state, &light_ray, &shadow, rng_congruential, sample, media_volume_shader, &tmp_volume_pdf)) {
/* accumulate */
// bool is_lamp = (lamp != ~0);
path_radiance_accum_light(L, *throughput, &L_light, shadow, 1.0f, state->bounce, is_lamp);
}
}
#ifdef __MULTI_LIGHT__
}
#endif
}
}
#endif
/* sample BRDF */
float bsdf_pdf;
BsdfEval bsdf_eval;
float3 bsdf_omega_in;
differential3 bsdf_domega_in;
float bsdf_u = path_rng(kg, rng, sample, rng_offset + PRNG_VOLUME_BRDF_U);
float bsdf_v = path_rng(kg, rng, sample, rng_offset + PRNG_VOLUME_BRDF_V);
int label;
label = shader_volume_bsdf_sample(kg, &vsd, bsdf_u, bsdf_v, &bsdf_eval,
&bsdf_omega_in, &bsdf_domega_in, &bsdf_pdf);
if(bsdf_pdf == 0.0f || bsdf_eval_is_zero(&bsdf_eval) || pdf == 0.0f)
return VOLUME_PATH_TERMINATED;
/* modify throughput */
bsdf_eval_mul(&bsdf_eval, (*volume_eval)/(*volume_pdf));
path_radiance_bsdf_bounce(L, throughput, &bsdf_eval, bsdf_pdf, state->bounce, label);
/* set labels */
#if defined(__EMISSION__) || defined(__BACKGROUND__)
*ray_pdf = bsdf_pdf * (*volume_pdf);
#endif
/* update path state */
path_state_next(kg, state, label);
/* setup ray */
// ray.P = ray_offset(sd.P, (label & LABEL_TRANSMIT)? -vsd.Ng: vsd.Ng);
ray->P = vsd.P;
ray->D = bsdf_omega_in;
ray->t = FLT_MAX;
#ifdef __RAY_DIFFERENTIALS__
ray->dP = vsd.dP;
ray->dD = bsdf_domega_in;
#endif
return VOLUME_PATH_CONTINUE;
}
*volume_pdf = pdf;
//*volume_eval = make_float3(1.0f, 1.0f, 1.0f);
*volume_eval = make_float3( eval, eval, eval);
//*volume_eval = make_float3( *volume_pdf, *volume_pdf, *volume_pdf); // perfect importance sampling for homogeneous
return VOLUME_PATH_PARTICLE_MISS;
}
#endif
CCL_NAMESPACE_END

View File

@@ -55,6 +55,7 @@
#include "closure/bsdf_westin.h"
#include "closure/bsdf_toon.h"
#include "closure/bsdf_hair.h"
#include "closure/volume.h"
CCL_NAMESPACE_BEGIN
@@ -169,6 +170,15 @@ BSDF_CLOSURE_CLASS_BEGIN(HairTransmission, hair_transmission, hair_transmission,
#endif
BSDF_CLOSURE_CLASS_END(HairTransmission, hair_transmission)
VOLUME_CLOSURE_CLASS_BEGIN(VolumeHenyeyGreenstein, henyey_greenstein, LABEL_DIFFUSE)
CLOSURE_FLOAT_PARAM(VolumeHenyeyGreensteinClosure, sc.data0),
CLOSURE_FLOAT_PARAM(VolumeHenyeyGreensteinClosure, sc.data1),
VOLUME_CLOSURE_CLASS_END(VolumeHenyeyGreenstein, henyey_greenstein)
VOLUME_CLOSURE_CLASS_BEGIN(VolumeAbsorption, absorption, LABEL_SINGULAR)
CLOSURE_FLOAT_PARAM(VolumeAbsorptionClosure, sc.data0),
VOLUME_CLOSURE_CLASS_END(VolumeAbsorption, absorption)
/* Registration */
static void register_closure(OSL::ShadingSystem *ss, const char *name, int id, OSL::ClosureParam *params, OSL::PrepareClosureFunc prepare)
@@ -248,6 +258,11 @@ void OSLShader::register_closures(OSLShadingSystem *ss_)
bsdf_hair_reflection_params(), bsdf_hair_reflection_prepare);
register_closure(ss, "hair_transmission", id++,
bsdf_hair_transmission_params(), bsdf_hair_transmission_prepare);
register_closure(ss, "henyey_greenstein", id++,
volume_henyey_greenstein_params(), volume_henyey_greenstein_prepare);
register_closure(ss, "absorption_volume", id++,
volume_absorption_params(), volume_absorption_prepare);
}
CCL_NAMESPACE_END

View File

@@ -54,6 +54,7 @@ OSL::ClosureParam *closure_bssrdf_cubic_params();
OSL::ClosureParam *closure_bssrdf_gaussian_params();
OSL::ClosureParam *closure_bssrdf_cubic_extended_params();
OSL::ClosureParam *closure_bssrdf_gaussian_extended_params();
OSL::ClosureParam *closure_henyey_greenstein_volume_params();
void closure_emission_prepare(OSL::RendererServices *, int id, void *data);
void closure_background_prepare(OSL::RendererServices *, int id, void *data);
@@ -65,6 +66,7 @@ void closure_westin_backscatter_prepare(OSL::RendererServices *, int id, void *d
void closure_westin_sheen_prepare(OSL::RendererServices *, int id, void *data);
void closure_bssrdf_cubic_prepare(OSL::RendererServices *, int id, void *data);
void closure_bssrdf_gaussian_prepare(OSL::RendererServices *, int id, void *data);
void closure_henyey_greenstein_volume_prepare(OSL::RendererServices *, int id, void *data);
#define CCLOSURE_PREPARE(name, classname) \
void name(RendererServices *, int id, void *data) \
@@ -186,6 +188,76 @@ static ClosureParam *bsdf_##lower##_params() \
\
CCLOSURE_PREPARE_STATIC(bsdf_##lower##_prepare, Upper##Closure)
/* Volume */
class CVolumeClosure : public CClosurePrimitive {
public:
ShaderClosure sc;
CVolumeClosure(int scattering) : CClosurePrimitive(Volume),
m_scattering_label(scattering), m_shaderdata_flag(0)
{ memset(&sc, 0, sizeof(sc)); }
~CVolumeClosure() { }
int scattering() const { return m_scattering_label; }
int shaderdata_flag() const { return m_shaderdata_flag; }
virtual float3 eval_phase(const float3 &omega_out, const float3 &omega_in, float& pdf) const = 0;
virtual int sample(const float3 &omega_out, const float3 &domega_out_dx, const float3 &domega_out_dy,
float randu, float randv,
float3 &omega_in, float3 &domega_in_dx, float3 &domega_in_dy,
float &pdf, float3 &eval) const = 0;
protected:
int m_scattering_label;
int m_shaderdata_flag;
};
#define VOLUME_CLOSURE_CLASS_BEGIN(Upper, lower, TYPE) \
\
class Upper##Closure : public CVolumeClosure { \
public: \
Upper##Closure() : CVolumeClosure(TYPE) {} \
\
void setup() \
{ \
sc.prim = NULL; \
m_shaderdata_flag = volume_##lower##_setup(&sc); \
} \
\
float3 eval_phase(const float3 &omega_out, const float3 &omega_in, float& pdf) const \
{ \
return volume_##lower##_eval_phase(&sc, omega_out, omega_in, &pdf); \
} \
\
int sample(const float3 &omega_out, const float3 &domega_out_dx, const float3 &domega_out_dy, \
float randu, float randv, \
float3 &omega_in, float3 &domega_in_dx, float3 &domega_in_dy, \
float &pdf, float3 &eval) const \
{ \
return volume_##lower##_sample(&sc, omega_out, domega_out_dx, domega_out_dy, \
randu, randv, &eval, &omega_in, &domega_in_dx, &domega_in_dy, &pdf); \
} \
\
}; \
\
static ClosureParam *volume_##lower##_params() \
{ \
static ClosureParam params[] = {
/* parameters */
#define VOLUME_CLOSURE_CLASS_END(Upper, lower) \
CLOSURE_STRING_KEYPARAM("label"), \
CLOSURE_FINISH_PARAM(Upper##Closure) \
}; \
return params; \
} \
\
CCLOSURE_PREPARE_STATIC(volume_##lower##_prepare, Upper##Closure)
CCL_NAMESPACE_END
#endif /* __OSL_CLOSURES_H__ */

View File

@@ -401,18 +401,23 @@ static void flatten_volume_closure_tree(ShaderData *sd,
switch (prim->category) {
case CClosurePrimitive::Volume: {
CVolumeClosure *volume = (CVolumeClosure *)prim;
/* sample weight */
float sample_weight = fabsf(average(weight));
sc.sample_weight = sample_weight;
sc.type = CLOSURE_VOLUME_ID;
sc.data0 = 0.0f;
sc.data1 = 0.0f;
sc.prim = NULL;
sc.type = volume->sc.type;
sc.N = volume->sc.N;
sc.T = volume->sc.T;
sc.data0 = volume->sc.data0;
sc.data1 = volume->sc.data1;
sc.prim = volume->sc.prim;
/* add */
if(sc.sample_weight > 1e-5f && sd->num_closure < MAX_CLOSURE)
if(sc.sample_weight > 1e-5f && sd->num_closure < MAX_CLOSURE) {
sd->closure[sd->num_closure++] = sc;
sd->flag |= volume->shaderdata_flag();
}
break;
}
case CClosurePrimitive::Holdout:
@@ -451,6 +456,10 @@ void OSLShader::eval_volume(KernelGlobals *kg, ShaderData *sd, float randb, int
if (kg->osl->volume_state[shader])
ss->execute(*octx, *(kg->osl->volume_state[shader]), *globals);
/* flatten closure tree */
sd->num_closure = 0;
sd->randb_closure = randb;
if (globals->Ci)
flatten_volume_closure_tree(sd, globals->Ci);

View File

@@ -29,6 +29,8 @@ set(SRC_OSL
node_glossy_bsdf.osl
node_gradient_texture.osl
node_hair_info.osl
node_scatter_volume.osl
node_absorption_volume.osl
node_holdout.osl
node_hsv.osl
node_image_texture.osl

View File

@@ -0,0 +1,26 @@
/*
* 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
*/
#include "stdosl.h"
shader node_absorption_volume(
color Color = color(0.8, 0.8, 0.8),
float Density = 1.0,
output closure color Volume = 0)
{
Volume = Color * absorption_volume(Density);
}

View File

@@ -0,0 +1,27 @@
/*
* 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
*/
#include "stdosl.h"
shader node_scatter_volume(
color Color = color(0.8, 0.8, 0.8),
float Density = 1.0,
float Anisotropy = 0.0,
output closure color Volume = 0)
{
Volume = Color * henyey_greenstein(Density, Anisotropy);
}

View File

@@ -497,9 +497,14 @@ closure color ambient_occlusion() BUILTIN;
closure color bssrdf_cubic(normal N, vector radius, float texture_blur, float sharpness) BUILTIN;
closure color bssrdf_gaussian(normal N, vector radius, float texture_blur) BUILTIN;
// Hair
closure color hair_reflection(normal N, float roughnessu, float roughnessv, vector T, float offset) BUILTIN;
closure color hair_transmission(normal N, float roughnessu, float roughnessv, vector T, float offset) BUILTIN;
// Volume
closure color henyey_greenstein(float density, float g) BUILTIN;
closure color absorption_volume(float density) BUILTIN;
// Backwards compatibility
closure color bssrdf_cubic(normal N, vector radius) BUILTIN;
closure color bssrdf_gaussian(normal N, vector radius) BUILTIN;

View File

@@ -473,24 +473,28 @@ ccl_device void svm_node_closure_volume(KernelGlobals *kg, ShaderData *sd, float
#endif
float param1 = (stack_valid(param1_offset))? stack_load_float(stack, param1_offset): __uint_as_float(node.z);
//float param2 = (stack_valid(param2_offset))? stack_load_float(stack, param2_offset): __uint_as_float(node.w);
float param2 = (stack_valid(param2_offset))? stack_load_float(stack, param2_offset): __uint_as_float(node.w);
switch(type) {
case CLOSURE_VOLUME_TRANSPARENT_ID: {
case CLOSURE_VOLUME_ABSORPTION_ID: {
ShaderClosure *sc = svm_node_closure_get_bsdf(sd, mix_weight);
if(sc) {
float density = param1;
sd->flag |= volume_transparent_setup(sc, density);
sc->data0 = density;
sd->flag |= volume_absorption_setup(sc);
}
break;
}
case CLOSURE_VOLUME_ISOTROPIC_ID: {
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: {
ShaderClosure *sc = svm_node_closure_get_bsdf(sd, mix_weight);
if(sc) {
float density = param1;
sd->flag |= volume_isotropic_setup(sc, density);
float g = param2;
sc->data0 = density;
sc->data1 = g;
sd->flag |= volume_henyey_greenstein_setup(sc);
}
break;
}

View File

@@ -389,8 +389,8 @@ typedef enum ClosureType {
/* Volume */
CLOSURE_VOLUME_ID,
CLOSURE_VOLUME_TRANSPARENT_ID,
CLOSURE_VOLUME_ISOTROPIC_ID,
CLOSURE_VOLUME_ABSORPTION_ID,
CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID,
NBUILTIN_CLOSURES
} ClosureType;
@@ -402,7 +402,7 @@ typedef enum ClosureType {
#define CLOSURE_IS_BSDF_TRANSMISSION(type) (type >= CLOSURE_BSDF_TRANSMISSION_ID && type <= CLOSURE_BSDF_HAIR_TRANSMISSION_ID)
#define CLOSURE_IS_BSDF_BSSRDF(type) (type == CLOSURE_BSDF_BSSRDF_ID)
#define CLOSURE_IS_BSSRDF(type) (type >= CLOSURE_BSSRDF_CUBIC_ID && type <= CLOSURE_BSSRDF_GAUSSIAN_ID)
#define CLOSURE_IS_VOLUME(type) (type >= CLOSURE_VOLUME_ID && type <= CLOSURE_VOLUME_ISOTROPIC_ID)
#define CLOSURE_IS_VOLUME(type) (type >= CLOSURE_VOLUME_ID && type <= CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID)
#define CLOSURE_IS_EMISSION(type) (type == CLOSURE_EMISSION_ID)
#define CLOSURE_IS_HOLDOUT(type) (type == CLOSURE_HOLDOUT_ID)
#define CLOSURE_IS_BACKGROUND(type) (type == CLOSURE_BACKGROUND_ID)

View File

@@ -33,6 +33,7 @@ Integrator::Integrator()
max_diffuse_bounce = max_bounce;
max_glossy_bounce = max_bounce;
max_transmission_bounce = max_bounce;
max_volume_bounce = max_bounce;
probalistic_termination = true;
transparent_min_bounce = min_bounce;
@@ -41,6 +42,13 @@ Integrator::Integrator()
transparent_shadows = false;
no_caustics = false;
volume_sampling_algorithm = 0;
volume_homogeneous_sampling = 0;
volume_max_iterations = 200;
volume_cell_step = 0.1;
volume_woodcock_max_density = 0.95;
filter_glossy = 0.0f;
seed = 0;
layer_flag = ~0;
@@ -84,6 +92,7 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
kintegrator->max_diffuse_bounce = max_diffuse_bounce + 1;
kintegrator->max_glossy_bounce = max_glossy_bounce + 1;
kintegrator->max_transmission_bounce = max_transmission_bounce + 1;
kintegrator->max_volume_bounce = max_volume_bounce + 1;
kintegrator->transparent_max_bounce = transparent_max_bounce + 1;
if(transparent_probalistic)
@@ -94,6 +103,12 @@ void Integrator::device_update(Device *device, DeviceScene *dscene, Scene *scene
kintegrator->transparent_shadows = transparent_shadows;
kintegrator->no_caustics = no_caustics;
kintegrator->volume_sampling_algorithm = volume_sampling_algorithm;
kintegrator->volume_homogeneous_sampling = volume_homogeneous_sampling;
kintegrator->volume_max_iterations = volume_max_iterations;
kintegrator->volume_cell_step = volume_cell_step;
kintegrator->volume_woodcock_max_density = volume_woodcock_max_density;
kintegrator->filter_glossy = (filter_glossy == 0.0f)? FLT_MAX: 1.0f/filter_glossy;
kintegrator->seed = hash_int(seed);
@@ -153,6 +168,7 @@ bool Integrator::modified(const Integrator& integrator)
max_diffuse_bounce == integrator.max_diffuse_bounce &&
max_glossy_bounce == integrator.max_glossy_bounce &&
max_transmission_bounce == integrator.max_transmission_bounce &&
max_volume_bounce == integrator.max_volume_bounce &&
probalistic_termination == integrator.probalistic_termination &&
transparent_min_bounce == integrator.transparent_min_bounce &&
transparent_max_bounce == integrator.transparent_max_bounce &&
@@ -161,6 +177,11 @@ bool Integrator::modified(const Integrator& integrator)
no_caustics == integrator.no_caustics &&
filter_glossy == integrator.filter_glossy &&
layer_flag == integrator.layer_flag &&
volume_sampling_algorithm == integrator.volume_sampling_algorithm &&
volume_homogeneous_sampling == integrator.volume_homogeneous_sampling &&
volume_max_iterations == integrator.volume_max_iterations &&
volume_cell_step == integrator.volume_cell_step &&
volume_woodcock_max_density == integrator.volume_woodcock_max_density &&
seed == integrator.seed &&
sample_clamp == integrator.sample_clamp &&
method == integrator.method &&

View File

@@ -33,6 +33,7 @@ public:
int max_diffuse_bounce;
int max_glossy_bounce;
int max_transmission_bounce;
int max_volume_bounce;
bool probalistic_termination;
int transparent_min_bounce;
@@ -41,6 +42,13 @@ public:
bool transparent_shadows;
bool no_caustics;
int volume_sampling_algorithm; // when homogeneous = false, 0 = cell marching, 1 = woodcock delta tracking
int volume_homogeneous_sampling;
int volume_max_iterations;
float volume_cell_step;
float volume_woodcock_max_density; // set it by hands, guess max density in volume, sorry no auto generation for now
float filter_glossy;
int seed;

View File

@@ -1951,7 +1951,7 @@ void AmbientOcclusionNode::compile(OSLCompiler& compiler)
VolumeNode::VolumeNode()
: ShaderNode("volume")
{
closure = CLOSURE_VOLUME_ISOTROPIC_ID;
closure = CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID;
add_input("Color", SHADER_SOCKET_COLOR, make_float3(0.8f, 0.8f, 0.8f));
add_input("Density", SHADER_SOCKET_FLOAT, 1.0f);
@@ -1995,38 +1995,40 @@ void VolumeNode::compile(OSLCompiler& compiler)
assert(0);
}
/* Transparent Volume Closure */
/* Absorption Volume Closure */
TransparentVolumeNode::TransparentVolumeNode()
AbsorptionVolumeNode::AbsorptionVolumeNode()
{
closure = CLOSURE_VOLUME_TRANSPARENT_ID;
closure = CLOSURE_VOLUME_ABSORPTION_ID;
}
void TransparentVolumeNode::compile(SVMCompiler& compiler)
void AbsorptionVolumeNode::compile(SVMCompiler& compiler)
{
VolumeNode::compile(compiler, input("Density"), NULL);
}
void TransparentVolumeNode::compile(OSLCompiler& compiler)
void AbsorptionVolumeNode::compile(OSLCompiler& compiler)
{
compiler.add(this, "node_transparent_volume");
compiler.add(this, "node_absorption_volume");
}
/* Isotropic Volume Closure */
/* Scatter Volume Closure */
IsotropicVolumeNode::IsotropicVolumeNode()
ScatterVolumeNode::ScatterVolumeNode()
{
closure = CLOSURE_VOLUME_ISOTROPIC_ID;
closure = CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID;
add_input("Anisotropy", SHADER_SOCKET_FLOAT, 0.0f);
}
void IsotropicVolumeNode::compile(SVMCompiler& compiler)
void ScatterVolumeNode::compile(SVMCompiler& compiler)
{
VolumeNode::compile(compiler, input("Density"), NULL);
VolumeNode::compile(compiler, input("Density"), input("Anisotropy"));
}
void IsotropicVolumeNode::compile(OSLCompiler& compiler)
void ScatterVolumeNode::compile(OSLCompiler& compiler)
{
compiler.add(this, "node_isotropic_volume");
compiler.add(this, "node_scatter_volume");
}
/* Hair BSDF Closure */

View File

@@ -316,14 +316,14 @@ public:
ClosureType closure;
};
class TransparentVolumeNode : public VolumeNode {
class AbsorptionVolumeNode : public VolumeNode {
public:
SHADER_NODE_CLASS(TransparentVolumeNode)
SHADER_NODE_CLASS(AbsorptionVolumeNode)
};
class IsotropicVolumeNode : public VolumeNode {
class ScatterVolumeNode : public VolumeNode {
public:
SHADER_NODE_CLASS(IsotropicVolumeNode)
SHADER_NODE_CLASS(ScatterVolumeNode)
};
class HairBsdfNode : public BsdfNode {

View File

@@ -179,6 +179,7 @@ public:
int default_surface;
int default_light;
int default_background;
int default_volume;
int default_empty;
/* device */

View File

@@ -218,6 +218,7 @@ void ShaderManager::device_update_common(Device *device, DeviceScene *dscene, Sc
uint *shader_flag = dscene->shader_flag.resize(shader_flag_size);
uint i = 0;
bool has_converter_blackbody = false;
bool has_volumetrics = false;
foreach(Shader *shader, scene->shaders) {
uint flag = 0;
@@ -226,8 +227,19 @@ void ShaderManager::device_update_common(Device *device, DeviceScene *dscene, Sc
flag |= SD_USE_MIS;
if(shader->has_surface_transparent && shader->use_transparent_shadow)
flag |= SD_HAS_TRANSPARENT_SHADOW;
if(shader->has_volume)
if(shader->has_volume) {
flag |= SD_HAS_VOLUME;
has_volumetrics = true;
/* in this case we can assume transparent surface */
if(!shader->has_surface)
flag |= SD_HAS_ONLY_VOLUME;
/* todo: this could check more fine grained, to skip useless volumes
* enclosed inside an opaque bsdf, although we still need to handle
* the case with camera inside volumes too */
flag |= SD_HAS_TRANSPARENT_SHADOW;
}
if(shader->homogeneous_volume)
flag |= SD_HOMOGENEOUS_VOLUME;
if(shader->has_bssrdf_bump)
@@ -263,6 +275,9 @@ void ShaderManager::device_update_common(Device *device, DeviceScene *dscene, Sc
blackbody_table_offset = TABLE_OFFSET_INVALID;
}
/* volumetrics */
KernelIntegrator *kintegrator = &dscene->data.integrator;
kintegrator->use_volumetrics = has_volumetrics;
}
void ShaderManager::device_free_common(Device *device, DeviceScene *dscene, Scene *scene)
@@ -299,6 +314,25 @@ void ShaderManager::add_default(Scene *scene)
scene->default_surface = scene->shaders.size() - 1;
}
/* default volume */
{
graph = new ShaderGraph();
closure = graph->add(new ScatterVolumeNode());
closure->input("Color")->value = make_float3(0.8f, 0.8f, 0.8f);
closure->input("Density")->value.x = 1.0f;
closure->input("Anisotropy")->value.x = 0.0f;
out = graph->output();
graph->connect(closure->output("Volume"), out->input("Volume"));
shader = new Shader();
shader->name = "default_volume";
shader->graph = graph;
scene->shaders.push_back(shader);
scene->default_volume = scene->shaders.size() - 1;
}
/* default light */
{
graph = new ShaderGraph();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View File

@@ -7,5 +7,6 @@ cycles.no_caustics = True
cycles.diffuse_bounces = 0
cycles.glossy_bounces = 1
cycles.transmission_bounces = 2
cycles.volume_bounces = 0
cycles.transparent_min_bounces = 8
cycles.transparent_max_bounces = 8

View File

@@ -7,5 +7,6 @@ cycles.no_caustics = False
cycles.diffuse_bounces = 128
cycles.glossy_bounces = 128
cycles.transmission_bounces = 128
cycles.volume_bounces = 128
cycles.transparent_min_bounces = 8
cycles.transparent_max_bounces = 128

View File

@@ -7,5 +7,6 @@ cycles.no_caustics = True
cycles.diffuse_bounces = 1
cycles.glossy_bounces = 4
cycles.transmission_bounces = 8
cycles.volume_bounces = 1
cycles.transparent_min_bounces = 8
cycles.transparent_max_bounces = 8

View File

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

View File

@@ -717,8 +717,8 @@ struct ShadeResult;
#define SH_NODE_OUTPUT_TEXTURE 158
#define SH_NODE_HOLDOUT 159
#define SH_NODE_LAYER_WEIGHT 160
#define SH_NODE_VOLUME_TRANSPARENT 161
#define SH_NODE_VOLUME_ISOTROPIC 162
#define SH_NODE_VOLUME_ABSORPTION 161
#define SH_NODE_VOLUME_SCATTER 162
#define SH_NODE_GAMMA 163
#define SH_NODE_TEX_CHECKER 164
#define SH_NODE_BRIGHTCONTRAST 165

View File

@@ -3494,8 +3494,8 @@ static void registerShaderNodes(void)
register_node_type_sh_bsdf_hair();
register_node_type_sh_emission();
register_node_type_sh_holdout();
//register_node_type_sh_volume_transparent();
//register_node_type_sh_volume_isotropic();
register_node_type_sh_volume_absorption();
register_node_type_sh_volume_scatter();
register_node_type_sh_subsurface_scattering();
register_node_type_sh_mix_shader();
register_node_type_sh_add_shader();

View File

@@ -197,8 +197,8 @@ set(SRC
shader/nodes/node_shader_tex_sky.c
shader/nodes/node_shader_tex_voronoi.c
shader/nodes/node_shader_tex_wave.c
shader/nodes/node_shader_volume_isotropic.c
shader/nodes/node_shader_volume_transparent.c
shader/nodes/node_shader_volume_scatter.c
shader/nodes/node_shader_volume_absorption.c
shader/node_shader_tree.c
shader/node_shader_util.c

View File

@@ -105,8 +105,8 @@ void register_node_type_sh_bsdf_toon(void);
void register_node_type_sh_bsdf_anisotropic(void);
void register_node_type_sh_emission(void);
void register_node_type_sh_holdout(void);
void register_node_type_sh_volume_transparent(void);
void register_node_type_sh_volume_isotropic(void);
void register_node_type_sh_volume_absorption(void);
void register_node_type_sh_volume_scatter(void);
void register_node_type_sh_bsdf_hair(void);
void register_node_type_sh_subsurface_scattering(void);
void register_node_type_sh_mix_shader(void);

View File

@@ -88,8 +88,8 @@ DefNode( ShaderNode, SH_NODE_BSDF_VELVET, 0, "BS
DefNode( ShaderNode, SH_NODE_BSDF_TOON, def_toon, "BSDF_TOON", BsdfToon, "Toon BSDF", "" )
DefNode( ShaderNode, SH_NODE_BSDF_HAIR, def_hair, "BSDF_HAIR", BsdfHair, "Hair BSDF", "" )
DefNode( ShaderNode, SH_NODE_SUBSURFACE_SCATTERING, def_sh_subsurface, "SUBSURFACE_SCATTERING",SubsurfaceScattering,"Subsurface Scattering","")
DefNode( ShaderNode, SH_NODE_VOLUME_TRANSPARENT, 0, "VOLUME_TRANSPARENT", VolumeTransparent,"Transparent Volume","" )
DefNode( ShaderNode, SH_NODE_VOLUME_ISOTROPIC, 0, "VOLUME_ISOTROPIC", VolumeIsotropic, "Isotropic Volume", "" )
DefNode( ShaderNode, SH_NODE_VOLUME_ABSORPTION, 0, "VOLUME_ABSORPTION", VolumeAbsorption, "Volume Absorption", "" )
DefNode( ShaderNode, SH_NODE_VOLUME_SCATTER, 0, "VOLUME_SCATTER", VolumeScatter, "Volume Scatter", "" )
DefNode( ShaderNode, SH_NODE_EMISSION, 0, "EMISSION", Emission, "Emission", "" )
DefNode( ShaderNode, SH_NODE_NEW_GEOMETRY, 0, "NEW_GEOMETRY", NewGeometry, "Geometry", "" )
DefNode( ShaderNode, SH_NODE_LIGHT_PATH, 0, "LIGHT_PATH", LightPath, "Light Path", "" )

View File

@@ -29,33 +29,33 @@
/* **************** OUTPUT ******************** */
static bNodeSocketTemplate sh_node_volume_transparent_in[] = {
static bNodeSocketTemplate sh_node_volume_absorption_in[] = {
{ SOCK_RGBA, 1, N_("Color"), 0.8f, 0.8f, 0.8f, 1.0f, 0.0f, 1.0f},
{ SOCK_FLOAT, 1, N_("Density"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1000.0f},
{ -1, 0, "" }
};
static bNodeSocketTemplate sh_node_volume_transparent_out[] = {
static bNodeSocketTemplate sh_node_volume_absorption_out[] = {
{ SOCK_SHADER, 0, N_("Volume")},
{ -1, 0, "" }
};
static int node_shader_gpu_volume_transparent(GPUMaterial *UNUSED(mat), bNode *UNUSED(node), bNodeExecData *UNUSED(execdata), GPUNodeStack *UNUSED(in), GPUNodeStack *UNUSED(out))
static int node_shader_gpu_volume_absorption(GPUMaterial *UNUSED(mat), bNode *UNUSED(node), bNodeExecData *UNUSED(execdata), GPUNodeStack *UNUSED(in), GPUNodeStack *UNUSED(out))
{
return 0;
}
/* node type definition */
void register_node_type_sh_volume_transparent(void)
void register_node_type_sh_volume_absorption(void)
{
static bNodeType ntype;
sh_node_type_base(&ntype, SH_NODE_VOLUME_TRANSPARENT, "Transparent Volume", NODE_CLASS_SHADER, 0);
sh_node_type_base(&ntype, SH_NODE_VOLUME_ABSORPTION, "Volume Absorption", NODE_CLASS_SHADER, 0);
node_type_compatibility(&ntype, NODE_NEW_SHADING);
node_type_socket_templates(&ntype, sh_node_volume_transparent_in, sh_node_volume_transparent_out);
node_type_socket_templates(&ntype, sh_node_volume_absorption_in, sh_node_volume_absorption_out);
node_type_init(&ntype, NULL);
node_type_storage(&ntype, "", NULL, NULL);
node_type_gpu(&ntype, node_shader_gpu_volume_transparent);
node_type_gpu(&ntype, node_shader_gpu_volume_absorption);
nodeRegisterType(&ntype);
}

View File

@@ -29,33 +29,35 @@
/* **************** OUTPUT ******************** */
static bNodeSocketTemplate sh_node_volume_isotropic_in[] = {
static bNodeSocketTemplate sh_node_volume_scatter_in[] = {
{ SOCK_RGBA, 1, N_("Color"), 0.8f, 0.8f, 0.8f, 1.0f, 0.0f, 1.0f},
{ SOCK_FLOAT, 1, N_("Density"), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1000.0f},
{ SOCK_FLOAT, 1, N_("Anisotropy"),0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f},
{ -1, 0, "" }
};
static bNodeSocketTemplate sh_node_volume_isotropic_out[] = {
static bNodeSocketTemplate sh_node_volume_scatter_out[] = {
{ SOCK_SHADER, 0, N_("Volume")},
{ -1, 0, "" }
};
static int node_shader_gpu_volume_isotropic(GPUMaterial *UNUSED(mat), bNode *UNUSED(node), bNodeExecData *UNUSED(execdata), GPUNodeStack *UNUSED(in), GPUNodeStack *UNUSED(out))
static int node_shader_gpu_volume_scatter(GPUMaterial *UNUSED(mat), bNode *UNUSED(node), bNodeExecData *UNUSED(execdata), GPUNodeStack *UNUSED(in), GPUNodeStack *UNUSED(out))
{
return 0;
}
/* node type definition */
void register_node_type_sh_volume_isotropic(void)
void register_node_type_sh_volume_scatter(void)
{
static bNodeType ntype;
sh_node_type_base(&ntype, SH_NODE_VOLUME_ISOTROPIC, "Isotropic Volume", NODE_CLASS_SHADER, 0);
sh_node_type_base(&ntype, SH_NODE_VOLUME_SCATTER, "Volume Scatter", NODE_CLASS_SHADER, 0);
node_type_compatibility(&ntype, NODE_NEW_SHADING);
node_type_socket_templates(&ntype, sh_node_volume_isotropic_in, sh_node_volume_isotropic_out);
node_type_socket_templates(&ntype, sh_node_volume_scatter_in, sh_node_volume_scatter_out);
node_type_init(&ntype, NULL);
node_type_storage(&ntype, "", NULL, NULL);
node_type_gpu(&ntype, node_shader_gpu_volume_isotropic);
node_type_gpu(&ntype, node_shader_gpu_volume_scatter);
nodeRegisterType(&ntype);
}