From 0315eae536b987d8b133b2bb16219c2b3267071c Mon Sep 17 00:00:00 2001 From: Alexandre Cardaillac Date: Wed, 2 Oct 2024 11:05:28 +0200 Subject: [PATCH] Cycles: Add more scattering phase functions Previously, Cycles only supported the Henyey-Greenstein phase function for volume scattering. While HG is flexible and works for a wide range of effects, sometimes a more physically accurate phase function may be needed for realism. Therefore, this adds three new phase functions to the code: Rayleigh: For particles with a size below the wavelength of light, mostly athmospheric scattering. Fournier-Forand: For realistic underwater scattering. Draine: Fairly specific on its own (mostly for interstellar dust), but useful for the next entry. Mie: Approximates Mie scattering in water droplets using a mix of Draine and HG phase functions. These phase functions can be combined using Mix nodes as usual. Co-authored-by: Lukas Stockner Pull Request: https://projects.blender.org/blender/blender/pulls/123532 --- intern/cycles/blender/shader.cpp | 21 +- intern/cycles/kernel/CMakeLists.txt | 5 + intern/cycles/kernel/closure/volume.h | 186 +++++++------- intern/cycles/kernel/closure/volume_draine.h | 60 +++++ .../kernel/closure/volume_fournier_forand.h | 71 ++++++ .../kernel/closure/volume_henyey_greenstein.h | 62 +++++ .../cycles/kernel/closure/volume_rayleigh.h | 49 ++++ intern/cycles/kernel/closure/volume_util.h | 238 ++++++++++++++++++ .../integrator/subsurface_random_walk.h | 4 +- .../cycles/kernel/integrator/volume_shader.h | 33 +-- intern/cycles/kernel/osl/closures_setup.h | 58 +++++ intern/cycles/kernel/osl/closures_template.h | 13 + .../osl/shaders/node_scatter_volume.osl | 33 ++- intern/cycles/kernel/osl/shaders/stdcycles.h | 3 + intern/cycles/kernel/svm/closure.h | 81 ++++-- intern/cycles/kernel/svm/types.h | 13 +- intern/cycles/kernel/types.h | 21 +- intern/cycles/scene/shader_nodes.cpp | 75 +++++- intern/cycles/scene/shader_nodes.h | 10 +- intern/cycles/util/math.h | 6 + intern/cycles/util/math_fast.h | 13 + source/blender/editors/space_node/drawnode.cc | 8 + .../gpu_shader_material_volume_scatter.glsl | 11 +- source/blender/makesdna/DNA_node_types.h | 9 + .../blender/makesrna/intern/rna_nodetree.cc | 42 ++++ source/blender/nodes/NOD_static_types.h | 2 +- .../nodes/node_shader_volume_scatter.cc | 72 +++++- 27 files changed, 1027 insertions(+), 172 deletions(-) create mode 100644 intern/cycles/kernel/closure/volume_draine.h create mode 100644 intern/cycles/kernel/closure/volume_fournier_forand.h create mode 100644 intern/cycles/kernel/closure/volume_henyey_greenstein.h create mode 100644 intern/cycles/kernel/closure/volume_rayleigh.h create mode 100644 intern/cycles/kernel/closure/volume_util.h diff --git a/intern/cycles/blender/shader.cpp b/intern/cycles/blender/shader.cpp index 20de3ab2495..8d8ac7e67ac 100644 --- a/intern/cycles/blender/shader.cpp +++ b/intern/cycles/blender/shader.cpp @@ -721,7 +721,26 @@ static ShaderNode *add_node(Scene *scene, node = ao; } else if (b_node.is_a(&RNA_ShaderNodeVolumeScatter)) { - node = graph->create_node(); + BL::ShaderNodeVolumeScatter b_scatter_node(b_node); + ScatterVolumeNode *scatter = graph->create_node(); + switch (b_scatter_node.phase()) { + case BL::ShaderNodeVolumeScatter::phase_HENYEY_GREENSTEIN: + scatter->set_phase(CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID); + break; + case BL::ShaderNodeVolumeScatter::phase_FOURNIER_FORAND: + scatter->set_phase(CLOSURE_VOLUME_FOURNIER_FORAND_ID); + break; + case BL::ShaderNodeVolumeScatter::phase_DRAINE: + scatter->set_phase(CLOSURE_VOLUME_DRAINE_ID); + break; + case BL::ShaderNodeVolumeScatter::phase_RAYLEIGH: + scatter->set_phase(CLOSURE_VOLUME_RAYLEIGH_ID); + break; + case BL::ShaderNodeVolumeScatter::phase_MIE: + scatter->set_phase(CLOSURE_VOLUME_MIE_ID); + break; + } + node = scatter; } else if (b_node.is_a(&RNA_ShaderNodeVolumeAbsorption)) { node = graph->create_node(); diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index 4951da8e93a..69126f9b21a 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -161,6 +161,11 @@ set(SRC_KERNEL_CLOSURE_HEADERS closure/bssrdf.h closure/emissive.h closure/volume.h + closure/volume_util.h + closure/volume_henyey_greenstein.h + closure/volume_rayleigh.h + closure/volume_fournier_forand.h + closure/volume_draine.h closure/bsdf_principled_hair_chiang.h closure/bsdf_principled_hair_huang.h ) diff --git a/intern/cycles/kernel/closure/volume.h b/intern/cycles/kernel/closure/volume.h index 4463eb81f24..7e6dc3c1f2f 100644 --- a/intern/cycles/kernel/closure/volume.h +++ b/intern/cycles/kernel/closure/volume.h @@ -4,6 +4,13 @@ #pragma once +#include "kernel/closure/volume_util.h" + +#include "kernel/closure/volume_draine.h" +#include "kernel/closure/volume_fournier_forand.h" +#include "kernel/closure/volume_henyey_greenstein.h" +#include "kernel/closure/volume_rayleigh.h" + CCL_NAMESPACE_BEGIN /* VOLUME EXTINCTION */ @@ -19,110 +26,27 @@ ccl_device void volume_extinction_setup(ccl_private ShaderData *sd, Spectrum wei } } -/* HENYEY-GREENSTEIN CLOSURE */ - -typedef struct HenyeyGreensteinVolume { - SHADER_CLOSURE_BASE; - - float g; -} HenyeyGreensteinVolume; - -static_assert(sizeof(ShaderClosure) >= sizeof(HenyeyGreensteinVolume), - "HenyeyGreensteinVolume is too large!"); - -/* 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) -{ - 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(ccl_private HenyeyGreensteinVolume *volume) -{ - volume->type = CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID; - - /* clamp anisotropy to avoid delta function */ - volume->g = signf(volume->g) * min(fabsf(volume->g), 1.0f - 1e-3f); - - return SD_SCATTER; -} - -ccl_device Spectrum volume_henyey_greenstein_eval_phase(ccl_private const ShaderVolumeClosure *svc, - const float3 wi, - float3 wo, - ccl_private float *pdf) -{ - float g = svc->g; - - /* note that wi points towards the viewer */ - if (fabsf(g) < 1e-3f) { - *pdf = M_1_PI_F * 0.25f; - } - else { - float cos_theta = dot(-wi, wo); - *pdf = single_peaked_henyey_greenstein(cos_theta, g); - } - - return make_spectrum(*pdf); -} - -ccl_device float3 henyey_greenstrein_sample(float3 D, float g, float2 rand, ccl_private float *pdf) -{ - /* match pdf for small g */ - float cos_theta; - bool isotropic = fabsf(g) < 1e-3f; - - if (isotropic) { - cos_theta = (1.0f - 2.0f * rand.x); - if (pdf) { - *pdf = M_1_PI_F * 0.25f; - } - } - else { - float k = (1.0f - g * g) / (1.0f - g + 2.0f * g * rand.x); - cos_theta = (1.0f + g * g - k * k) / (2.0f * g); - if (pdf) { - *pdf = single_peaked_henyey_greenstein(cos_theta, g); - } - } - - float sin_theta = sin_from_cos(cos_theta); - float phi = M_2PI_F * rand.y; - float3 dir = make_float3(sin_theta * cosf(phi), sin_theta * sinf(phi), cos_theta); - - float3 T, B; - make_orthonormals(D, &T, &B); - dir = dir.x * T + dir.y * B + dir.z * D; - - return dir; -} - -ccl_device int volume_henyey_greenstein_sample(ccl_private const ShaderVolumeClosure *svc, - float3 wi, - float2 rand, - ccl_private Spectrum *eval, - ccl_private float3 *wo, - ccl_private float *pdf) -{ - float g = svc->g; - - /* note that wi points towards the viewer and so is used negated */ - *wo = henyey_greenstrein_sample(-wi, g, rand, pdf); - *eval = make_spectrum(*pdf); /* perfect importance sampling */ - - return LABEL_VOLUME_SCATTER; -} - -/* VOLUME CLOSURE */ +/* VOLUME SCATTERING */ ccl_device Spectrum volume_phase_eval(ccl_private const ShaderData *sd, ccl_private const ShaderVolumeClosure *svc, float3 wo, ccl_private float *pdf) { - return volume_henyey_greenstein_eval_phase(svc, sd->wi, wo, pdf); + switch (svc->type) { + case CLOSURE_VOLUME_FOURNIER_FORAND_ID: + return volume_fournier_forand_eval(sd, svc, wo, pdf); + case CLOSURE_VOLUME_RAYLEIGH_ID: + return volume_rayleigh_eval(sd, wo, pdf); + case CLOSURE_VOLUME_DRAINE_ID: + return volume_draine_eval(sd, svc, wo, pdf); + case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: + return volume_henyey_greenstein_eval(sd, svc, wo, pdf); + default: + kernel_assert(false); + *pdf = 0.0f; + return zero_spectrum(); + } } ccl_device int volume_phase_sample(ccl_private const ShaderData *sd, @@ -132,7 +56,71 @@ ccl_device int volume_phase_sample(ccl_private const ShaderData *sd, ccl_private float3 *wo, ccl_private float *pdf) { - return volume_henyey_greenstein_sample(svc, sd->wi, rand, eval, wo, pdf); + switch (svc->type) { + case CLOSURE_VOLUME_FOURNIER_FORAND_ID: + return volume_fournier_forand_sample(sd, svc, rand, eval, wo, pdf); + case CLOSURE_VOLUME_RAYLEIGH_ID: + return volume_rayleigh_sample(sd, rand, eval, wo, pdf); + case CLOSURE_VOLUME_DRAINE_ID: + return volume_draine_sample(sd, svc, rand, eval, wo, pdf); + case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: + return volume_henyey_greenstein_sample(sd, svc, rand, eval, wo, pdf); + default: + kernel_assert(false); + *pdf = 0.0f; + return 0; + } +} + +ccl_device bool volume_phase_equal(ccl_private const ShaderClosure *c1, + ccl_private const ShaderClosure *c2) +{ + if (c1->type != c2->type) { + return false; + } + switch (c1->type) { + case CLOSURE_VOLUME_FOURNIER_FORAND_ID: { + ccl_private FournierForandVolume *v1 = (ccl_private FournierForandVolume *)c1; + ccl_private FournierForandVolume *v2 = (ccl_private FournierForandVolume *)c2; + return v1->c1 == v2->c1 && v1->c2 == v2->c2 && v1->c3 == v2->c3; + } + case CLOSURE_VOLUME_RAYLEIGH_ID: + return true; + case CLOSURE_VOLUME_DRAINE_ID: { + ccl_private DraineVolume *v1 = (ccl_private DraineVolume *)c1; + ccl_private DraineVolume *v2 = (ccl_private DraineVolume *)c2; + return v1->g == v2->g && v1->alpha == v2->alpha; + } + case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: { + ccl_private HenyeyGreensteinVolume *v1 = (ccl_private HenyeyGreensteinVolume *)c1; + ccl_private HenyeyGreensteinVolume *v2 = (ccl_private HenyeyGreensteinVolume *)c2; + return v1->g == v2->g; + } + default: + return false; + } + return false; +} + +/* Approximate phase functions as Henyey-Greenstein for volume guiding. + * TODO: This is not ideal, we should use RIS guiding for non-HG phase functions. */ +ccl_device float volume_phase_get_g(ccl_private const ShaderVolumeClosure *svc) +{ + switch (svc->type) { + case CLOSURE_VOLUME_FOURNIER_FORAND_ID: + /* TODO */ + return 1.0f; + case CLOSURE_VOLUME_RAYLEIGH_ID: + /* Approximate as isotropic */ + return 0.0f; + case CLOSURE_VOLUME_DRAINE_ID: + /* Approximate as HG, TODO */ + return ((ccl_private DraineVolume *)svc)->g; + case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: + return ((ccl_private HenyeyGreensteinVolume *)svc)->g; + default: + return 0.0f; + } } /* Volume sampling utilities. */ diff --git a/intern/cycles/kernel/closure/volume_draine.h b/intern/cycles/kernel/closure/volume_draine.h new file mode 100644 index 00000000000..ad9b6a668aa --- /dev/null +++ b/intern/cycles/kernel/closure/volume_draine.h @@ -0,0 +1,60 @@ +/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation + * + * SPDX-License-Identifier: Apache-2.0 */ + +#pragma once + +#include "kernel/closure/volume_util.h" + +CCL_NAMESPACE_BEGIN + +/* DRAINE CLOSURE */ + +typedef struct DraineVolume { + SHADER_CLOSURE_VOLUME_BASE; + + float g; + float alpha; +} DraineVolume; +static_assert(sizeof(ShaderVolumeClosure) >= sizeof(DraineVolume), "DraineVolume is too large!"); + +ccl_device int volume_draine_setup(ccl_private DraineVolume *volume) +{ + volume->type = CLOSURE_VOLUME_DRAINE_ID; + /* clamp anisotropy */ + volume->g = signf(volume->g) * min(fabsf(volume->g), 1.0f - 1e-3f); + + return SD_SCATTER; +} + +ccl_device Spectrum volume_draine_eval(ccl_private const ShaderData *sd, + ccl_private const ShaderVolumeClosure *svc, + float3 wo, + ccl_private float *pdf) +{ + ccl_private const DraineVolume *volume = (ccl_private const DraineVolume *)svc; + + /* note that wi points towards the viewer */ + float cos_theta = dot(-sd->wi, wo); + *pdf = phase_draine(cos_theta, volume->g, volume->alpha); + + return make_spectrum(*pdf); +} + +ccl_device int volume_draine_sample(ccl_private const ShaderData *sd, + ccl_private const ShaderVolumeClosure *svc, + float2 rand, + ccl_private Spectrum *eval, + ccl_private float3 *wo, + ccl_private float *pdf) +{ + ccl_private const DraineVolume *volume = (ccl_private const DraineVolume *)svc; + + /* note that wi points towards the viewer and so is used negated */ + *wo = phase_draine_sample(-sd->wi, volume->g, volume->alpha, rand, pdf); + *eval = make_spectrum(*pdf); /* perfect importance sampling */ + + return LABEL_VOLUME_SCATTER; +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/closure/volume_fournier_forand.h b/intern/cycles/kernel/closure/volume_fournier_forand.h new file mode 100644 index 00000000000..ce208487091 --- /dev/null +++ b/intern/cycles/kernel/closure/volume_fournier_forand.h @@ -0,0 +1,71 @@ +/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation + * + * SPDX-License-Identifier: Apache-2.0 */ + +#pragma once + +#include "kernel/closure/volume_util.h" + +CCL_NAMESPACE_BEGIN + +/* FOURNIER-FORAND CLOSURE */ + +typedef struct FournierForandVolume { + SHADER_CLOSURE_VOLUME_BASE; + + /* Precomputed coefficients, based on B and IOR */ + float c1, c2, c3; +} FournierForandVolume; +static_assert(sizeof(ShaderVolumeClosure) >= sizeof(FournierForandVolume), + "FournierForandVolume is too large!"); + +ccl_device int volume_fournier_forand_setup(ccl_private FournierForandVolume *volume, + float B, + float IOR) +{ + volume->type = CLOSURE_VOLUME_FOURNIER_FORAND_ID; + + /* clamp backscatter fraction to avoid delta function */ + B = min(fabsf(B), 0.5f - 1e-3f); + IOR = max(IOR, 1.0f + 1e-3f); + float3 coeffs = phase_fournier_forand_coeffs(B, IOR); + volume->c1 = coeffs.x; + volume->c2 = coeffs.y; + volume->c3 = coeffs.z; + + return SD_SCATTER; +} + +ccl_device Spectrum volume_fournier_forand_eval(ccl_private const ShaderData *sd, + ccl_private const ShaderVolumeClosure *svc, + float3 wo, + ccl_private float *pdf) +{ + ccl_private const FournierForandVolume *volume = (ccl_private const FournierForandVolume *)svc; + const float3 coeffs = make_float3(volume->c1, volume->c2, volume->c3); + + /* note that wi points towards the viewer */ + float cos_theta = dot(-sd->wi, wo); + *pdf = phase_fournier_forand(cos_theta, coeffs); + + return make_spectrum(*pdf); +} + +ccl_device int volume_fournier_forand_sample(ccl_private const ShaderData *sd, + ccl_private const ShaderVolumeClosure *svc, + float2 rand, + ccl_private Spectrum *eval, + ccl_private float3 *wo, + ccl_private float *pdf) +{ + ccl_private const FournierForandVolume *volume = (ccl_private const FournierForandVolume *)svc; + const float3 coeffs = make_float3(volume->c1, volume->c2, volume->c3); + + /* note that wi points towards the viewer and so is used negated */ + *wo = phase_fournier_forand_sample(-sd->wi, coeffs, rand, pdf); + *eval = make_spectrum(*pdf); /* perfect importance sampling */ + + return LABEL_VOLUME_SCATTER; +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/closure/volume_henyey_greenstein.h b/intern/cycles/kernel/closure/volume_henyey_greenstein.h new file mode 100644 index 00000000000..b07e8963bc6 --- /dev/null +++ b/intern/cycles/kernel/closure/volume_henyey_greenstein.h @@ -0,0 +1,62 @@ +/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation + * + * SPDX-License-Identifier: Apache-2.0 */ + +#pragma once + +#include "kernel/closure/volume_util.h" + +CCL_NAMESPACE_BEGIN + +/* HENYEY-GREENSTEIN CLOSURE */ + +typedef struct HenyeyGreensteinVolume { + SHADER_CLOSURE_VOLUME_BASE; + + float g; +} HenyeyGreensteinVolume; +static_assert(sizeof(ShaderVolumeClosure) >= sizeof(HenyeyGreensteinVolume), + "HenyeyGreensteinVolume is too large!"); + +ccl_device int volume_henyey_greenstein_setup(ccl_private HenyeyGreensteinVolume *volume) +{ + volume->type = CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID; + /* clamp anisotropy to avoid delta function */ + volume->g = signf(volume->g) * min(fabsf(volume->g), 1.0f - 1e-3f); + + return SD_SCATTER; +} + +ccl_device Spectrum volume_henyey_greenstein_eval(ccl_private const ShaderData *sd, + ccl_private const ShaderVolumeClosure *svc, + float3 wo, + ccl_private float *pdf) +{ + ccl_private const HenyeyGreensteinVolume *volume = (ccl_private const HenyeyGreensteinVolume *) + svc; + + /* note that wi points towards the viewer */ + float cos_theta = dot(-sd->wi, wo); + *pdf = phase_henyey_greenstein(cos_theta, volume->g); + + return make_spectrum(*pdf); +} + +ccl_device int volume_henyey_greenstein_sample(ccl_private const ShaderData *sd, + ccl_private const ShaderVolumeClosure *svc, + float2 rand, + ccl_private Spectrum *eval, + ccl_private float3 *wo, + ccl_private float *pdf) +{ + ccl_private const HenyeyGreensteinVolume *volume = (ccl_private const HenyeyGreensteinVolume *) + svc; + + /* note that wi points towards the viewer and so is used negated */ + *wo = phase_henyey_greenstein_sample(-sd->wi, volume->g, rand, pdf); + *eval = make_spectrum(*pdf); /* perfect importance sampling */ + + return LABEL_VOLUME_SCATTER; +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/closure/volume_rayleigh.h b/intern/cycles/kernel/closure/volume_rayleigh.h new file mode 100644 index 00000000000..b984a609b31 --- /dev/null +++ b/intern/cycles/kernel/closure/volume_rayleigh.h @@ -0,0 +1,49 @@ +/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation + * + * SPDX-License-Identifier: Apache-2.0 */ + +#pragma once + +#include "kernel/closure/volume_util.h" + +CCL_NAMESPACE_BEGIN + +/* RAYLEIGH CLOSURE */ + +typedef struct RayleighVolume { + SHADER_CLOSURE_VOLUME_BASE; +} RayleighVolume; +static_assert(sizeof(ShaderVolumeClosure) >= sizeof(RayleighVolume), + "RayleighVolume is too large!"); + +ccl_device int volume_rayleigh_setup(ccl_private RayleighVolume *volume) +{ + volume->type = CLOSURE_VOLUME_RAYLEIGH_ID; + return SD_SCATTER; +} + +ccl_device Spectrum volume_rayleigh_eval(ccl_private const ShaderData *sd, + float3 wo, + ccl_private float *pdf) +{ + /* note that wi points towards the viewer */ + float cos_theta = dot(-sd->wi, wo); + *pdf = phase_rayleigh(cos_theta); + + return make_spectrum(*pdf); +} + +ccl_device int volume_rayleigh_sample(ccl_private const ShaderData *sd, + float2 rand, + ccl_private Spectrum *eval, + ccl_private float3 *wo, + ccl_private float *pdf) +{ + /* note that wi points towards the viewer and so is used negated */ + *wo = phase_rayleigh_sample(-sd->wi, rand, pdf); + *eval = make_spectrum(*pdf); /* perfect importance sampling */ + + return LABEL_VOLUME_SCATTER; +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/closure/volume_util.h b/intern/cycles/kernel/closure/volume_util.h new file mode 100644 index 00000000000..d807cb47759 --- /dev/null +++ b/intern/cycles/kernel/closure/volume_util.h @@ -0,0 +1,238 @@ +/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation + * + * SPDX-License-Identifier: Apache-2.0 */ + +#pragma once + +CCL_NAMESPACE_BEGIN + +/* Given a random number, sample a direction that makes an angle of theta with direction D. */ +ccl_device float3 phase_sample_direction(float3 D, float cos_theta, float rand) +{ + float sin_theta = sin_from_cos(cos_theta); + float phi = M_2PI_F * rand; + float3 dir = make_float3(sin_theta * cosf(phi), sin_theta * sinf(phi), cos_theta); + + float3 T, B; + make_orthonormals(D, &T, &B); + dir = dir.x * T + dir.y * B + dir.z * D; + + return dir; +} + +/* 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 phase_henyey_greenstein(float cos_theta, float g) +{ + if (fabsf(g) < 1e-3f) { + return M_1_4PI_F; + } + const float fac = 1 + g * (g - 2 * cos_theta); + return (1 - sqr(g)) / (M_4PI_F * fac * safe_sqrtf(fac)); +} + +ccl_device float3 phase_henyey_greenstein_sample(float3 D, + float g, + float2 rand, + ccl_private float *pdf) +{ + float cos_theta = 1 - 2 * rand.x; + if (fabsf(g) >= 1e-3f) { + const float k = (1 - sqr(g)) / (1 - g * cos_theta); + cos_theta = (1 + sqr(g) - sqr(k)) / (2 * g); + } + + *pdf = phase_henyey_greenstein(cos_theta, g); + + return phase_sample_direction(D, cos_theta, rand.y); +} + +/* Given cosine between rays, return probability density that a photon bounces to that direction + * according to the constant Rayleigh phase function. + * See https://doi.org/10.1364/JOSAA.28.002436 for details. */ +ccl_device float phase_rayleigh(float cos_theta) +{ + return (0.1875f * M_1_PI_F) * (1.0f + sqr(cos_theta)); +} + +ccl_device float3 phase_rayleigh_sample(float3 D, float2 rand, ccl_private float *pdf) +{ + const float a = 2 - 4 * rand.x; + /* Metal doesn't have cbrtf, but since we compute u - 1/u anyways, we can just as well + * use the inverse cube root for which there is a simple Quake-style fast implementation.*/ + const float inv_u = -fast_inv_cbrtf(sqrtf(1 + sqr(a)) - a); + const float cos_theta = 1 / inv_u - inv_u; + *pdf = phase_rayleigh(cos_theta); + + return phase_sample_direction(D, cos_theta, rand.y); +} + +/* Given cosine between rays, return probability density that a photon bounces to that direction + * according to the Draine phase function. This is a generalisation of the Henyey-Greenstein + * function which bridges the cases of HG and Rayleigh scattering. The parameter g mainly controls + * the first moment , and alpha the second moment of the exact phase + * function. alpha=0 reduces to HG function, g=0, alpha=1 reduces to Rayleigh function, alpha=1 + * reduces to Cornette-Shanks function. + * See https://doi.org/10.1086/379118 for details. */ +ccl_device float phase_draine(float cos_theta, float g, float alpha) +{ + /* Check special cases. */ + if (fabsf(g) < 1e-3f && alpha > 0.999f) { + return phase_rayleigh(cos_theta); + } + else if (fabsf(alpha) < 1e-3f) { + return phase_henyey_greenstein(cos_theta, g); + } + + const float g2 = sqr(g); + const float fac = 1 + g2 - 2 * g * cos_theta; + return ((1 - g2) * (1 + alpha * sqr(cos_theta))) / + ((1 + (alpha * (1 + 2 * g2)) * (1 / 3.0f)) * M_4PI_F * fac * sqrtf(fac)); +} + +/* Adapted from the hlsl code provided in https://research.nvidia.com/labs/rtr/approximate-mie/ */ +ccl_device float phase_draine_sample_cos(float g, float alpha, float rand) +{ + const float g2 = sqr(g), g3 = g * g2, g4 = sqr(g2), g6 = g2 * g4; + const float pgp1_2 = sqr(1 + g2); + const float T1a = alpha * (g4 - 1), T1a3 = sqr(T1a) * T1a; + const float T2 = -1296 * (g2 - 1) * (alpha - alpha * g2) * T1a * (4 * g2 + alpha * pgp1_2); + const float T9 = 2 + g2 + g3 * (1 + 2 * g2) * (2 * rand - 1); + const float T3 = 3 * g2 * (1 + g * (2 * rand - 1)) + alpha * T9; + const float T4a = 432 * T1a3 + T2 + 432 * (alpha * (1 - g2)) * sqr(T3); + const float T10 = alpha * (2 * g4 - g2 - g6); + const float T4b = 144 * T10, T4b3 = sqr(T4b) * T4b; + const float T4 = T4a + sqrtf(-4 * T4b3 + sqr(T4a)); + const float inv_T4p3 = fast_inv_cbrtf(T4); + const float T8 = 48 * M_CBRT2_F * T10; + const float T6 = (2 * T1a + T8 * inv_T4p3 + 1 / (3 * M_CBRT2_F * inv_T4p3)) / (alpha * (1 - g2)); + const float T5 = 6 * (1 + g2) + T6; + const float T7 = 6 * (1 + g2) - (8 * T3) / (alpha * (g2 - 1) * sqrtf(T5)) - T6; + return (1 + g2 - 0.25f * sqr(sqrtf(T7) - sqrtf(T5))) / (2 * g); +} + +ccl_device float3 +phase_draine_sample(float3 D, float g, float alpha, float2 rand, ccl_private float *pdf) +{ + /* Check special cases. */ + if (fabsf(g) < 1e-3f && alpha > 0.999f) { + return phase_rayleigh_sample(D, rand, pdf); + } + else if (fabsf(alpha) < 1e-3f) { + return phase_henyey_greenstein_sample(D, g, rand, pdf); + } + + const float cos_theta = phase_draine_sample_cos(g, alpha, rand.x); + *pdf = phase_draine(cos_theta, g, alpha); + + return phase_sample_direction(D, cos_theta, rand.y); +} + +ccl_device float phase_fournier_forand_delta(float n, float sin_htheta_sqr) +{ + float u = 4 * sin_htheta_sqr; + return u / (3 * sqr(n - 1)); +} + +ccl_device_inline float3 phase_fournier_forand_coeffs(float B, float IOR) +{ + const float d90 = phase_fournier_forand_delta(IOR, 0.5f); + const float d180 = phase_fournier_forand_delta(IOR, 1.0f); + const float v = -logf(2 * B * (d90 - 1) + 1) / logf(d90); + return make_float3(IOR, v, (powf(d180, -v) - 1) / (d180 - 1)); +} + +/* Given cosine between rays, return probability density that a photon bounces to that direction + * according to the Fournier-Forand phase function. The n parameter is the particle index of + * refraction and controls how much of the light is refracted. B is the particle backscatter + * fraction, B = b_b / b. + * See https://doi.org/10.1117/12.366488 for details. */ +ccl_device_inline float phase_fournier_forand_impl( + float cos_theta, float delta, float pow_delta_v, float v, float sin_htheta_sqr, float pf_coeff) +{ + const float m_delta = 1 - delta, m_pow_delta_v = 1 - pow_delta_v; + + float pf; + if (fabsf(m_delta) < 1e-3f) { + /* Special case (first-order Taylor expansion) to avoid singularity at delta near 1.0 */ + pf = v * ((v - 1) - (v + 1) / sin_htheta_sqr) * (1 / (8 * M_PI_F)); + pf += v * (v + 1) * m_delta * (2 * (v - 1) - (2 * v + 1) / sin_htheta_sqr) * + (1 / (24 * M_PI_F)); + } + else { + pf = (v * m_delta - m_pow_delta_v + (delta * m_pow_delta_v - v * m_delta) / sin_htheta_sqr) / + (M_4PI_F * sqr(m_delta) * pow_delta_v); + } + pf += pf_coeff * (3 * sqr(cos_theta) - 1); + return pf; +} + +ccl_device float phase_fournier_forand(float cos_theta, float3 coeffs) +{ + if (fabsf(cos_theta) >= 1.0f) { + return 0.0f; + } + + const float n = coeffs.x, v = coeffs.y; + const float pf_coeff = coeffs.z * (1.0f / (16.0f * M_PI_F)); + const float sin_htheta_sqr = 0.5f * (1 - cos_theta); /* sin^2(theta / 2)*/ + const float delta = phase_fournier_forand_delta(n, sin_htheta_sqr); + + return phase_fournier_forand_impl(cos_theta, delta, powf(delta, v), v, sin_htheta_sqr, pf_coeff); +} + +ccl_device float phase_fournier_forand_newton(float rand, float3 coeffs) +{ + const float n = coeffs.x, v = coeffs.y; + const float cdf_coeff = coeffs.z * (1.0f / 8.0f); + const float pf_coeff = coeffs.z * (1.0f / (16.0f * M_PI_F)); + + float cos_theta = 0.64278760968f; /* Initial guess: 50 degrees */ + for (int it = 0; it < 20; it++) { + const float sin_htheta_sqr = 0.5f * (1 - cos_theta); /* sin^2(theta / 2)*/ + const float delta = phase_fournier_forand_delta(n, sin_htheta_sqr); + const float pow_delta_v = powf(delta, v); + const float m_delta = 1 - delta, m_pow_delta_v = 1 - pow_delta_v; + + /* Evaluate CDF and phase functions */ + float cdf; + if (fabsf(m_delta) < 1e-3f) { + /* Special case (first-order Taylor expansion) to avoid singularity at delta near 1.0 */ + cdf = 1 + v * (1 - sin_htheta_sqr) * (1 - 0.5f * (v + 1) * m_delta); + } + else { + cdf = (1 - pow_delta_v * delta - m_pow_delta_v * sin_htheta_sqr) / (m_delta * pow_delta_v); + } + cdf += cdf_coeff * cos_theta * (1 - sqr(cos_theta)); + const float pf = phase_fournier_forand_impl( + cos_theta, delta, pow_delta_v, v, sin_htheta_sqr, pf_coeff); + + /* Perform Newton iteration step */ + float new_cos_theta = cos_theta + M_1_2PI_F * (cdf - rand) / pf; + + /* Don't step off past 1.0, approach the peak slowly */ + if (new_cos_theta >= 1.0f) { + new_cos_theta = max(mix(cos_theta, 1.0f, 0.5f), 0.99f); + } + if (fabsf(cos_theta - new_cos_theta) < 1e-6f || new_cos_theta == 1.0f) { + return new_cos_theta; + } + cos_theta = new_cos_theta; + } + /* Reached iteration limit, so give up and use what we have. */ + return cos_theta; +} + +ccl_device float3 phase_fournier_forand_sample(float3 D, + float3 coeffs, + float2 rand, + ccl_private float *pdf) +{ + float cos_theta = phase_fournier_forand_newton(rand.x, coeffs); + *pdf = phase_fournier_forand(cos_theta, coeffs); + + return phase_sample_direction(D, cos_theta, rand.y); +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/integrator/subsurface_random_walk.h b/intern/cycles/kernel/integrator/subsurface_random_walk.h index 8b711f5e1d1..eb85fb23d2f 100644 --- a/intern/cycles/kernel/integrator/subsurface_random_walk.h +++ b/intern/cycles/kernel/integrator/subsurface_random_walk.h @@ -315,11 +315,11 @@ ccl_device_inline bool subsurface_random_walk(KernelGlobals kg, cos_theta = -cos_theta; } float3 newD = direction_from_cosine(N, cos_theta, rand_scatter.y); - hg_pdf = single_peaked_henyey_greenstein(dot(ray.D, newD), anisotropy); + hg_pdf = phase_henyey_greenstein(dot(ray.D, newD), anisotropy); ray.D = newD; } else { - float3 newD = henyey_greenstrein_sample(ray.D, anisotropy, rand_scatter, &hg_pdf); + float3 newD = phase_henyey_greenstein_sample(ray.D, anisotropy, rand_scatter, &hg_pdf); cos_theta = dot(newD, N); ray.D = newD; } diff --git a/intern/cycles/kernel/integrator/volume_shader.h b/intern/cycles/kernel/integrator/volume_shader.h index 9f86e74aedb..415a1330b1f 100644 --- a/intern/cycles/kernel/integrator/volume_shader.h +++ b/intern/cycles/kernel/integrator/volume_shader.h @@ -30,21 +30,13 @@ ccl_device_inline void volume_shader_merge_closures(ccl_private ShaderData *sd) for (int i = 0; i < sd->num_closure; i++) { ccl_private ShaderClosure *sci = &sd->closure[i]; - if (sci->type != CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID) { + if (!CLOSURE_IS_VOLUME_SCATTER(sci->type)) { continue; } for (int j = i + 1; j < sd->num_closure; j++) { ccl_private ShaderClosure *scj = &sd->closure[j]; - if (sci->type != scj->type) { - continue; - } - - ccl_private const HenyeyGreensteinVolume *hgi = (ccl_private const HenyeyGreensteinVolume *) - sci; - ccl_private const HenyeyGreensteinVolume *hgj = (ccl_private const HenyeyGreensteinVolume *) - scj; - if (!(hgi->g == hgj->g)) { + if (!volume_phase_equal(sci, scj)) { continue; } @@ -73,16 +65,10 @@ ccl_device_inline void volume_shader_copy_phases(ccl_private ShaderVolumePhases for (int i = 0; i < sd->num_closure; i++) { ccl_private const ShaderClosure *from_sc = &sd->closure[i]; - ccl_private const HenyeyGreensteinVolume *from_hg = - (ccl_private const HenyeyGreensteinVolume *)from_sc; - - if (from_sc->type == CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID) { - ccl_private ShaderVolumeClosure *to_sc = &phases->closure[phases->num_closure]; - - to_sc->weight = from_sc->weight; - to_sc->sample_weight = from_sc->sample_weight; - to_sc->g = from_hg->g; - phases->num_closure++; + if (CLOSURE_IS_VOLUME_SCATTER(from_sc->type)) { + /* ShaderVolumeClosure is a subset of ShaderClosure, so this is fine for all volume scatter + * closures. */ + phases->closure[phases->num_closure++] = *((ccl_private const ShaderVolumeClosure *)from_sc); if (phases->num_closure >= MAX_VOLUME_CLOSURE) { break; } @@ -147,7 +133,8 @@ ccl_device_inline void volume_shader_prepare_guiding(KernelGlobals kg, /* Init guiding for selected phase function. */ ccl_private const ShaderVolumeClosure *svc = &phases->closure[phase_id]; - if (!guiding_phase_init(kg, state, P, D, svc->g, rand_phase_guiding)) { + const float phase_g = volume_phase_get_g(svc); + if (!guiding_phase_init(kg, state, P, D, phase_g, rand_phase_guiding)) { state->guiding.use_volume_guiding = false; return; } @@ -301,7 +288,7 @@ ccl_device int volume_shader_phase_guided_sample(KernelGlobals kg, *unguided_phase_pdf = 0.0f; float guide_pdf = 0.0f; - *sampled_roughness = 1.0f - fabsf(svc->g); + *sampled_roughness = 1.0f - fabsf(volume_phase_get_g(svc)); bsdf_eval_init(phase_eval, zero_spectrum()); @@ -355,7 +342,7 @@ ccl_device int volume_shader_phase_sample(KernelGlobals kg, ccl_private float *pdf, ccl_private float *sampled_roughness) { - *sampled_roughness = 1.0f - fabsf(svc->g); + *sampled_roughness = 1.0f - fabsf(volume_phase_get_g(svc)); Spectrum eval = zero_spectrum(); *pdf = 0.0f; diff --git a/intern/cycles/kernel/osl/closures_setup.h b/intern/cycles/kernel/osl/closures_setup.h index 411f5ad0415..82ff2ca3adb 100644 --- a/intern/cycles/kernel/osl/closures_setup.h +++ b/intern/cycles/kernel/osl/closures_setup.h @@ -1086,4 +1086,62 @@ ccl_device void osl_closure_henyey_greenstein_setup( sd->flag |= volume_henyey_greenstein_setup(volume); } +ccl_device void osl_closure_fournier_forand_setup( + KernelGlobals kg, + ccl_private ShaderData *sd, + uint32_t path_flag, + float3 weight, + ccl_private const VolumeFournierForandClosure *closure, + float3 *layer_albedo) +{ + volume_extinction_setup(sd, rgb_to_spectrum(weight)); + + ccl_private FournierForandVolume *volume = (ccl_private FournierForandVolume *)bsdf_alloc( + sd, sizeof(FournierForandVolume), rgb_to_spectrum(weight)); + if (!volume) { + return; + } + + sd->flag |= volume_fournier_forand_setup(volume, closure->B, closure->IOR); +} + +ccl_device void osl_closure_draine_setup(KernelGlobals kg, + ccl_private ShaderData *sd, + uint32_t path_flag, + float3 weight, + ccl_private const VolumeDraineClosure *closure, + float3 *layer_albedo) +{ + volume_extinction_setup(sd, rgb_to_spectrum(weight)); + + ccl_private DraineVolume *volume = (ccl_private DraineVolume *)bsdf_alloc( + sd, sizeof(DraineVolume), rgb_to_spectrum(weight)); + if (!volume) { + return; + } + + volume->g = closure->g; + volume->alpha = closure->alpha; + + sd->flag |= volume_draine_setup(volume); +} + +ccl_device void osl_closure_rayleigh_setup(KernelGlobals kg, + ccl_private ShaderData *sd, + uint32_t path_flag, + float3 weight, + ccl_private const VolumeRayleighClosure *closure, + float3 *layer_albedo) +{ + volume_extinction_setup(sd, rgb_to_spectrum(weight)); + + ccl_private RayleighVolume *volume = (ccl_private RayleighVolume *)bsdf_alloc( + sd, sizeof(RayleighVolume), rgb_to_spectrum(weight)); + if (!volume) { + return; + } + + sd->flag |= volume_rayleigh_setup(volume); +} + CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/osl/closures_template.h b/intern/cycles/kernel/osl/closures_template.h index 11aac73ac27..64f3b4507dc 100644 --- a/intern/cycles/kernel/osl/closures_template.h +++ b/intern/cycles/kernel/osl/closures_template.h @@ -223,6 +223,19 @@ OSL_CLOSURE_STRUCT_BEGIN(VolumeHenyeyGreenstein, henyey_greenstein) OSL_CLOSURE_STRUCT_MEMBER(VolumeHenyeyGreenstein, FLOAT, float, g, NULL) OSL_CLOSURE_STRUCT_END(VolumeHenyeyGreenstein, henyey_greenstein) +OSL_CLOSURE_STRUCT_BEGIN(VolumeFournierForand, fournier_forand) + OSL_CLOSURE_STRUCT_MEMBER(VolumeFournierForand, FLOAT, float, B, NULL) + OSL_CLOSURE_STRUCT_MEMBER(VolumeFournierForand, FLOAT, float, IOR, NULL) +OSL_CLOSURE_STRUCT_END(VolumeFournierForand, fournier_forand) + +OSL_CLOSURE_STRUCT_BEGIN(VolumeDraine, draine) + OSL_CLOSURE_STRUCT_MEMBER(VolumeDraine, FLOAT, float, g, NULL) + OSL_CLOSURE_STRUCT_MEMBER(VolumeDraine, FLOAT, float, alpha, NULL) +OSL_CLOSURE_STRUCT_END(VolumeDraine, draine) + +OSL_CLOSURE_STRUCT_BEGIN(VolumeRayleigh, rayleigh) +OSL_CLOSURE_STRUCT_END(VolumeRayleigh, rayleigh) + #undef OSL_CLOSURE_STRUCT_BEGIN #undef OSL_CLOSURE_STRUCT_END #undef OSL_CLOSURE_STRUCT_MEMBER diff --git a/intern/cycles/kernel/osl/shaders/node_scatter_volume.osl b/intern/cycles/kernel/osl/shaders/node_scatter_volume.osl index 7eef8830df4..726a29d6fd9 100644 --- a/intern/cycles/kernel/osl/shaders/node_scatter_volume.osl +++ b/intern/cycles/kernel/osl/shaders/node_scatter_volume.osl @@ -4,10 +4,39 @@ #include "stdcycles.h" -shader node_scatter_volume(color Color = color(0.8, 0.8, 0.8), +shader node_scatter_volume(string phase = "Henyey-Greenstein", + color Color = color(0.8, 0.8, 0.8), float Density = 1.0, float Anisotropy = 0.0, + float IOR = 1.33, + float Backscatter = 0.1, + float Alpha = 0.5, + float Diameter = 20.0, output closure color Volume = 0) { - Volume = (Color * max(Density, 0.0)) * henyey_greenstein(Anisotropy); + closure color scatter = 0; + if (phase == "Fournier-Forand") { + scatter = fournier_forand(Backscatter, IOR); + } + else if (phase == "Draine") { + scatter = draine(Anisotropy, Alpha); + } + else if (phase == "Rayleigh") { + scatter = rayleigh(); + } + else if (phase == "Mie") { + /* Approxmiation of Mie phase function for water droplets using a mix of Draine and H-G. + * See kernel/svm/closure.h for details. */ + float d = max(Diameter, 2.0); + float aniso_hg = exp(-0.0990567 / (d - 1.67154)); + float aniso_d = exp(-2.20679 / (d + 3.91029) - 0.428934); + float alpha = exp(3.62489 - 8.29288 / (d + 5.52825)); + float mixture = exp(-0.599085 / (d - 0.641583) - 0.665888); + scatter = mix(henyey_greenstein(aniso_hg), draine(aniso_d, alpha), mixture); + } + else { + scatter = henyey_greenstein(Anisotropy); + } + + Volume = (Color * max(Density, 0.0)) * scatter; } diff --git a/intern/cycles/kernel/osl/shaders/stdcycles.h b/intern/cycles/kernel/osl/shaders/stdcycles.h index 35faf1419dd..a1f72973aa1 100644 --- a/intern/cycles/kernel/osl/shaders/stdcycles.h +++ b/intern/cycles/kernel/osl/shaders/stdcycles.h @@ -61,6 +61,9 @@ closure color hair_huang(normal N, // Volume closure color henyey_greenstein(float g) BUILTIN; +closure color fournier_forand(float B, float IOR) BUILTIN; +closure color draine(float g, float alpha) BUILTIN; +closure color rayleigh() BUILTIN; closure color absorption() BUILTIN; // Ray Portal diff --git a/intern/cycles/kernel/svm/closure.h b/intern/cycles/kernel/svm/closure.h index 2cbdde1516a..a8c135ac54e 100644 --- a/intern/cycles/kernel/svm/closure.h +++ b/intern/cycles/kernel/svm/closure.h @@ -1006,13 +1006,10 @@ ccl_device_noinline void svm_node_closure_volume(KernelGlobals kg, return; } - uint type, density_offset, anisotropy_offset; - - uint mix_weight_offset; - svm_unpack_node_uchar4(node.y, &type, &density_offset, &anisotropy_offset, &mix_weight_offset); + uint type, density_offset, param1_offset, mix_weight_offset; + svm_unpack_node_uchar4(node.y, &type, &density_offset, ¶m1_offset, &mix_weight_offset); float mix_weight = (stack_valid(mix_weight_offset) ? stack_load_float(stack, mix_weight_offset) : 1.0f); - if (mix_weight == 0.0f) { return; } @@ -1031,16 +1028,70 @@ ccl_device_noinline void svm_node_closure_volume(KernelGlobals kg, weight *= density; /* Add closure for volume scattering. */ - if (type == CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID) { - ccl_private HenyeyGreensteinVolume *volume = (ccl_private HenyeyGreensteinVolume *)bsdf_alloc( - sd, sizeof(HenyeyGreensteinVolume), weight); - - if (volume) { - float anisotropy = (stack_valid(anisotropy_offset)) ? - stack_load_float(stack, anisotropy_offset) : - __uint_as_float(node.w); - volume->g = anisotropy; /* g */ - sd->flag |= volume_henyey_greenstein_setup(volume); + if (CLOSURE_IS_VOLUME_SCATTER(type)) { + switch (type) { + case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: { + ccl_private HenyeyGreensteinVolume *volume = (ccl_private HenyeyGreensteinVolume *) + bsdf_alloc(sd, sizeof(HenyeyGreensteinVolume), weight); + if (volume) { + volume->g = stack_valid(param1_offset) ? stack_load_float(stack, param1_offset) : + __uint_as_float(node.w); + sd->flag |= volume_henyey_greenstein_setup(volume); + } + } break; + case CLOSURE_VOLUME_FOURNIER_FORAND_ID: { + ccl_private FournierForandVolume *volume = (ccl_private FournierForandVolume *)bsdf_alloc( + sd, sizeof(FournierForandVolume), weight); + if (volume) { + const float IOR = stack_load_float(stack, param1_offset); + const float B = stack_load_float(stack, node.w); + sd->flag |= volume_fournier_forand_setup(volume, B, IOR); + } + } break; + case CLOSURE_VOLUME_RAYLEIGH_ID: { + ccl_private RayleighVolume *volume = (ccl_private RayleighVolume *)bsdf_alloc( + sd, sizeof(RayleighVolume), weight); + if (volume) { + sd->flag |= volume_rayleigh_setup(volume); + } + break; + } + case CLOSURE_VOLUME_DRAINE_ID: { + ccl_private DraineVolume *volume = (ccl_private DraineVolume *)bsdf_alloc( + sd, sizeof(DraineVolume), weight); + if (volume) { + volume->g = stack_load_float(stack, param1_offset); + volume->alpha = stack_load_float(stack, node.w); + sd->flag |= volume_draine_setup(volume); + } + } break; + case CLOSURE_VOLUME_MIE_ID: { + /* We approximate the Mie phase function for water droplets using a mix of Draine and H-G + * following "An Approximate Mie Scattering Function for Fog and Cloud Rendering", Johannes + * Jendersie and Eugene d'Eon, https://research.nvidia.com/labs/rtr/approximate-mie. + * + * The numerical fit here (eq. 4-7 in the paper) is intended for 5 1.67154, so we clamp it to 2 to be safe. */ + const float d = max(2.0f, + stack_valid(param1_offset) ? stack_load_float(stack, param1_offset) : + __uint_as_float(node.w)); + const float mixture = fast_expf(-0.599085f / (d - 0.641583f) - 0.665888f); + ccl_private HenyeyGreensteinVolume *hg = (ccl_private HenyeyGreensteinVolume *)bsdf_alloc( + sd, sizeof(HenyeyGreensteinVolume), weight * (1.0f - mixture)); + if (hg) { + hg->g = fast_expf(-0.0990567f / (d - 1.67154f)); + sd->flag |= volume_henyey_greenstein_setup(hg); + } + ccl_private DraineVolume *draine = (ccl_private DraineVolume *)bsdf_alloc( + sd, sizeof(DraineVolume), weight * mixture); + if (draine) { + draine->g = fast_expf(-2.20679f / (d + 3.91029f) - 0.428934f); + draine->alpha = fast_expf(3.62489f - 8.29288f / (d + 5.52825f)); + sd->flag |= volume_draine_setup(draine); + } + } break; } } diff --git a/intern/cycles/kernel/svm/types.h b/intern/cycles/kernel/svm/types.h index 4814e00c026..271afbc8251 100644 --- a/intern/cycles/kernel/svm/types.h +++ b/intern/cycles/kernel/svm/types.h @@ -470,6 +470,10 @@ typedef enum ClosureType { CLOSURE_VOLUME_ID, CLOSURE_VOLUME_ABSORPTION_ID, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID, + CLOSURE_VOLUME_MIE_ID, /* virtual closure */ + CLOSURE_VOLUME_FOURNIER_FORAND_ID, + CLOSURE_VOLUME_RAYLEIGH_ID, + CLOSURE_VOLUME_DRAINE_ID, CLOSURE_BSDF_PRINCIPLED_ID, @@ -502,12 +506,13 @@ typedef enum ClosureType { (type != CLOSURE_NONE_ID && type <= CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID) #define CLOSURE_IS_BSSRDF(type) \ (type >= CLOSURE_BSSRDF_BURLEY_ID && type <= CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID) -#define CLOSURE_IS_VOLUME(type) \ - (type >= CLOSURE_VOLUME_ID && type <= CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID) -#define CLOSURE_IS_VOLUME_SCATTER(type) (type == CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID) +#define CLOSURE_IS_VOLUME(type) (type >= CLOSURE_VOLUME_ID && type <= CLOSURE_VOLUME_DRAINE_ID) +#define CLOSURE_IS_VOLUME_SCATTER(type) \ + (type >= CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID && type <= CLOSURE_VOLUME_DRAINE_ID) #define CLOSURE_IS_VOLUME_ABSORPTION(type) (type == CLOSURE_VOLUME_ABSORPTION_ID) #define CLOSURE_IS_HOLDOUT(type) (type == CLOSURE_HOLDOUT_ID) -#define CLOSURE_IS_PHASE(type) (type == CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID) +#define CLOSURE_IS_PHASE(type) \ + (type >= CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID && type <= CLOSURE_VOLUME_DRAINE_ID) #define CLOSURE_IS_REFRACTION(type) \ (type >= CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID && \ type <= CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID) diff --git a/intern/cycles/kernel/types.h b/intern/cycles/kernel/types.h index e2d8f2a72e0..56dad1b0548 100644 --- a/intern/cycles/kernel/types.h +++ b/intern/cycles/kernel/types.h @@ -977,6 +977,15 @@ typedef struct AttributeMap { float sample_weight; \ float3 N +/* To save some space, volume closures (phase functions) don't store a normal. + * They are still allocated as ShaderClosures first, but get assigned to + * slots of type ShaderVolumeClosure later, so make sure to keep the layout + * in sync. */ +#define SHADER_CLOSURE_VOLUME_BASE \ + Spectrum weight; \ + ClosureType type; \ + float sample_weight + typedef struct ccl_align(16) ShaderClosure { SHADER_CLOSURE_BASE; @@ -1221,12 +1230,18 @@ ShaderDataCausticsStorage; /* Compact volume closures storage. * - * Used for decoupled direct/indirect light closure storage. */ - + * Used for decoupled direct/indirect light closure storage. + * + * This shares its basic layout with SHADER_CLOSURE_VOLUME_BASE and ShaderClosure, + * just without the normal and with less space for closure-specific parameters. + * That way, we can just cast ShaderClosure* to ShaderVolumeClosure* and assign it. + */ typedef struct ShaderVolumeClosure { Spectrum weight; + ClosureType type; float sample_weight; - float g; + /* Space for closure-specific parameters. */ + float param[3]; } ShaderVolumeClosure; typedef struct ShaderVolumePhases { diff --git a/intern/cycles/scene/shader_nodes.cpp b/intern/cycles/scene/shader_nodes.cpp index 229ab67aff6..d4f298cd8ac 100644 --- a/intern/cycles/scene/shader_nodes.cpp +++ b/intern/cycles/scene/shader_nodes.cpp @@ -3400,7 +3400,10 @@ VolumeNode::VolumeNode(const NodeType *node_type) : ShaderNode(node_type) closure = CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID; } -void VolumeNode::compile(SVMCompiler &compiler, ShaderInput *param1, ShaderInput *param2) +void VolumeNode::compile(SVMCompiler &compiler, + ShaderInput *density, + ShaderInput *param1, + ShaderInput *param2) { ShaderInput *color_in = input("Color"); @@ -3411,19 +3414,32 @@ void VolumeNode::compile(SVMCompiler &compiler, ShaderInput *param1, ShaderInput compiler.add_node(NODE_CLOSURE_SET_WEIGHT, color); } - compiler.add_node( - NODE_CLOSURE_VOLUME, - compiler.encode_uchar4(closure, - (param1) ? compiler.stack_assign(param1) : SVM_STACK_INVALID, - (param2) ? compiler.stack_assign(param2) : SVM_STACK_INVALID, - compiler.closure_mix_weight_offset()), - __float_as_int((param1) ? get_float(param1->socket_type) : 0.0f), - __float_as_int((param2) ? get_float(param2->socket_type) : 0.0f)); + /* Density and mix weight need to be stored the same way for all volume closures since there's + * a shortcut code path if we only need the extinction value. */ + uint density_ofs = (density) ? compiler.stack_assign_if_linked(density) : SVM_STACK_INVALID; + uint mix_weight_ofs = compiler.closure_mix_weight_offset(); + + if (param2 == nullptr) { + /* More efficient packing if we don't need the second parameter. */ + uint param1_ofs = (param1) ? compiler.stack_assign_if_linked(param1) : SVM_STACK_INVALID; + compiler.add_node(NODE_CLOSURE_VOLUME, + compiler.encode_uchar4(closure, density_ofs, param1_ofs, mix_weight_ofs), + __float_as_int((density) ? get_float(density->socket_type) : 0.0f), + __float_as_int((param1) ? get_float(param1->socket_type) : 0.0f)); + } + else { + uint param1_ofs = (param1) ? compiler.stack_assign(param1) : SVM_STACK_INVALID; + uint param2_ofs = (param2) ? compiler.stack_assign(param2) : SVM_STACK_INVALID; + compiler.add_node(NODE_CLOSURE_VOLUME, + compiler.encode_uchar4(closure, density_ofs, param1_ofs, mix_weight_ofs), + __float_as_int((density) ? get_float(density->socket_type) : 0.0f), + param2_ofs); + } } void VolumeNode::compile(SVMCompiler &compiler) { - compile(compiler, NULL, NULL); + compile(compiler, nullptr, nullptr, nullptr); } void VolumeNode::compile(OSLCompiler & /*compiler*/) @@ -3453,7 +3469,7 @@ AbsorptionVolumeNode::AbsorptionVolumeNode() : VolumeNode(get_node_type()) void AbsorptionVolumeNode::compile(SVMCompiler &compiler) { - VolumeNode::compile(compiler, input("Density"), NULL); + VolumeNode::compile(compiler, input("Density")); } void AbsorptionVolumeNode::compile(OSLCompiler &compiler) @@ -3470,6 +3486,19 @@ NODE_DEFINE(ScatterVolumeNode) SOCKET_IN_COLOR(color, "Color", make_float3(0.8f, 0.8f, 0.8f)); SOCKET_IN_FLOAT(density, "Density", 1.0f); SOCKET_IN_FLOAT(anisotropy, "Anisotropy", 0.0f); + SOCKET_IN_FLOAT(IOR, "IOR", 1.33f); + SOCKET_IN_FLOAT(backscatter, "Backscatter", 0.1f); + SOCKET_IN_FLOAT(alpha, "Alpha", 0.5f); + SOCKET_IN_FLOAT(diameter, "Diameter", 20.0f); + + static NodeEnum phase_enum; + phase_enum.insert("Henyey-Greenstein", CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID); + phase_enum.insert("Fournier-Forand", CLOSURE_VOLUME_FOURNIER_FORAND_ID); + phase_enum.insert("Draine", CLOSURE_VOLUME_DRAINE_ID); + phase_enum.insert("Rayleigh", CLOSURE_VOLUME_RAYLEIGH_ID); + phase_enum.insert("Mie", CLOSURE_VOLUME_MIE_ID); + SOCKET_ENUM(phase, "Phase", phase_enum, CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID); + SOCKET_IN_FLOAT(volume_mix_weight, "VolumeMixWeight", 0.0f, SocketType::SVM_INTERNAL); SOCKET_OUT_CLOSURE(volume, "Volume"); @@ -3484,11 +3513,33 @@ ScatterVolumeNode::ScatterVolumeNode() : VolumeNode(get_node_type()) void ScatterVolumeNode::compile(SVMCompiler &compiler) { - VolumeNode::compile(compiler, input("Density"), input("Anisotropy")); + closure = phase; + + switch (phase) { + case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID: + VolumeNode::compile(compiler, input("Density"), input("Anisotropy")); + break; + case CLOSURE_VOLUME_FOURNIER_FORAND_ID: + VolumeNode::compile(compiler, input("Density"), input("IOR"), input("Backscatter")); + break; + case CLOSURE_VOLUME_RAYLEIGH_ID: + VolumeNode::compile(compiler, input("Density")); + break; + case CLOSURE_VOLUME_DRAINE_ID: + VolumeNode::compile(compiler, input("Density"), input("Anisotropy"), input("Alpha")); + break; + case CLOSURE_VOLUME_MIE_ID: + VolumeNode::compile(compiler, input("Density"), input("Diameter")); + break; + default: + assert(false); + break; + } } void ScatterVolumeNode::compile(OSLCompiler &compiler) { + compiler.parameter(this, "phase"); compiler.add(this, "node_scatter_volume"); } diff --git a/intern/cycles/scene/shader_nodes.h b/intern/cycles/scene/shader_nodes.h index 879f6d1c204..45157d156d2 100644 --- a/intern/cycles/scene/shader_nodes.h +++ b/intern/cycles/scene/shader_nodes.h @@ -796,7 +796,10 @@ class VolumeNode : public ShaderNode { VolumeNode(const NodeType *node_type); SHADER_NODE_BASE_CLASS(VolumeNode) - void compile(SVMCompiler &compiler, ShaderInput *param1, ShaderInput *param2); + void compile(SVMCompiler &compiler, + ShaderInput *density, + ShaderInput *param1 = nullptr, + ShaderInput *param2 = nullptr); virtual int get_feature() { return ShaderNode::get_feature() | KERNEL_FEATURE_NODE_VOLUME; @@ -835,6 +838,11 @@ class ScatterVolumeNode : public VolumeNode { SHADER_NODE_CLASS(ScatterVolumeNode) NODE_SOCKET_API(float, anisotropy) + NODE_SOCKET_API(float, IOR) + NODE_SOCKET_API(float, backscatter) + NODE_SOCKET_API(float, alpha) + NODE_SOCKET_API(float, diameter) + NODE_SOCKET_API(ClosureType, phase) }; class PrincipledVolumeNode : public VolumeNode { diff --git a/intern/cycles/util/math.h b/intern/cycles/util/math.h index 59bf82e7a7f..077993c1ea9 100644 --- a/intern/cycles/util/math.h +++ b/intern/cycles/util/math.h @@ -49,6 +49,9 @@ CCL_NAMESPACE_BEGIN #ifndef M_1_2PI_F # define M_1_2PI_F (0.1591549430918953f) /* 1/(2*pi) */ #endif +#ifndef M_1_4PI_F +# define M_1_4PI_F (0.0795774715459476f) /* 1/(4*pi) */ +#endif #ifndef M_SQRT_PI_8_F # define M_SQRT_PI_8_F (0.6266570686577501f) /* sqrt(pi/8) */ #endif @@ -71,6 +74,9 @@ CCL_NAMESPACE_BEGIN #ifndef M_SQRT2_F # define M_SQRT2_F (1.4142135623730950f) /* sqrt(2) */ #endif +#ifndef M_CBRT2_F +# define M_CBRT2_F 1.2599210498948732f /* cbrt(2) */ +#endif #ifndef M_SQRT1_2F # define M_SQRT1_2F 0.70710678118654752440f /* sqrt(1/2) */ #endif diff --git a/intern/cycles/util/math_fast.h b/intern/cycles/util/math_fast.h index cec5e5391fb..822148fa1ad 100644 --- a/intern/cycles/util/math_fast.h +++ b/intern/cycles/util/math_fast.h @@ -634,6 +634,19 @@ ccl_device_inline float fast_ierff(float x) return p * x; } +/* Fast inverse cube root for positive x, with two Newton iterations to improve accuracy. */ +ccl_device_inline float fast_inv_cbrtf(float x) +{ + util_assert(x >= 0.0f); + + /* Constant is roughly cbrt(2^127), but tweaked a bit to balance the error across the entire + * range. The exact value is not critical. */ + float y = __int_as_float(0x54a24242 - __float_as_int(x) / 3); + y = (2.0f / 3) * y + 1 / (3 * y * y * x); + y = (2.0f / 3) * y + 1 / (3 * y * y * x); + return y; +} + CCL_NAMESPACE_END #endif /* __UTIL_FAST_MATH__ */ diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index c44557def43..788b3ac5d7f 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -445,6 +445,11 @@ static void node_buts_output_shader(uiLayout *layout, bContext * /*C*/, PointerR uiItemR(layout, ptr, "target", DEFAULT_FLAGS, "", ICON_NONE); } +static void node_shader_buts_scatter(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + uiItemR(layout, ptr, "phase", DEFAULT_FLAGS, "", ICON_NONE); +} + /* only once called */ static void node_shader_set_butfunc(blender::bke::bNodeType *ntype) { @@ -501,6 +506,9 @@ static void node_shader_set_butfunc(blender::bke::bNodeType *ntype) case SH_NODE_OUTPUT_WORLD: ntype->draw_buttons = node_buts_output_shader; break; + case SH_NODE_VOLUME_SCATTER: + ntype->draw_buttons = node_shader_buts_scatter; + break; } } diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_volume_scatter.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_volume_scatter.glsl index 29a9d0bdf6d..f39fbdf81b9 100644 --- a/source/blender/gpu/shaders/material/gpu_shader_material_volume_scatter.glsl +++ b/source/blender/gpu/shaders/material/gpu_shader_material_volume_scatter.glsl @@ -2,8 +2,15 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ -void node_volume_scatter( - vec4 color, float density, float anisotropy, float weight, out Closure result) +void node_volume_scatter(vec4 color, + float density, + float anisotropy, + float IOR, + float Backscatter, + float alpha, + float diameter, + float weight, + out Closure result) { color = max(color, vec4(0.0)); density = max(density, 0.0); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 1122ea54723..f8d669a763c 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -2849,6 +2849,15 @@ enum { SHD_POINTDENSITY_COLOR_VERTNOR = 2, }; +/* Scattering phase functions */ +enum { + SHD_PHASE_HENYEY_GREENSTEIN = 0, + SHD_PHASE_FOURNIER_FORAND = 1, + SHD_PHASE_DRAINE = 2, + SHD_PHASE_RAYLEIGH = 3, + SHD_PHASE_MIE = 4, +}; + /* Output shader node */ typedef enum NodeShaderOutputTarget { diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index b5deddaa73b..f2a81ebc735 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -4476,6 +4476,37 @@ static const EnumPropertyItem prop_image_extension[] = { {0, nullptr, 0, nullptr, nullptr}, }; +static const EnumPropertyItem node_scatter_phase_items[] = { + {SHD_PHASE_HENYEY_GREENSTEIN, + "HENYEY_GREENSTEIN", + 0, + "Henyey-Greenstein", + "Henyey-Greenstein, default phase function for the scattering of light"}, + {SHD_PHASE_FOURNIER_FORAND, + "FOURNIER_FORAND", + 0, + "Fournier-Forand", + "Fournier-Forand phase function, used for the scattering of light in underwater " + "environments"}, + {SHD_PHASE_DRAINE, + "DRAINE", + 0, + "Draine", + "Draine phase functions, mostly used for the scattering of light in interstellar dust"}, + {SHD_PHASE_RAYLEIGH, + "RAYLEIGH", + 0, + "Rayleigh", + "Rayleigh phase function, mostly used for particles smaller than the wavelength of light, " + "such as scattering of sunlight in earth's atmosphere"}, + {SHD_PHASE_MIE, + "MIE", + 0, + "Mie", + "Approximation of Mie scattering in water droplets, used for scattering in clouds and fog"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + /* -- Common nodes ---------------------------------------------------------- */ static void def_group_input(StructRNA * /*srna*/) {} @@ -5927,6 +5958,17 @@ static void def_refraction(StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_scatter(StructRNA *srna) +{ + PropertyRNA *prop; + + prop = RNA_def_property(srna, "phase", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "custom1"); + RNA_def_property_enum_items(prop, node_scatter_phase_items); + RNA_def_property_ui_text(prop, "Phase", "Phase function for the scattered light"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + static void def_toon(StructRNA *srna) { PropertyRNA *prop; diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index dd45b4d1b49..4a151b77565 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -76,7 +76,7 @@ DefNode(ShaderNode, SH_NODE_BSDF_HAIR, def_hair, "BSD DefNode(ShaderNode, SH_NODE_BSDF_HAIR_PRINCIPLED, def_hair_principled, "BSDF_HAIR_PRINCIPLED", BsdfHairPrincipled, "Principled Hair BSDF", "Physically-based, easy-to-use shader for rendering hair and fur") DefNode(ShaderNode, SH_NODE_SUBSURFACE_SCATTERING, def_sh_subsurface, "SUBSURFACE_SCATTERING",SubsurfaceScattering,"Subsurface Scattering","Subsurface multiple scattering shader to simulate light entering the surface and bouncing internally.\nTypically used for materials such as skin, wax, marble or milk") DefNode(ShaderNode, SH_NODE_VOLUME_ABSORPTION, 0, "VOLUME_ABSORPTION", VolumeAbsorption, "Volume Absorption", "Absorb light as it passes through the volume") -DefNode(ShaderNode, SH_NODE_VOLUME_SCATTER, 0, "VOLUME_SCATTER", VolumeScatter, "Volume Scatter", "Scatter light as it passes through the volume, often used to add fog to a scene") +DefNode(ShaderNode, SH_NODE_VOLUME_SCATTER, def_scatter, "VOLUME_SCATTER", VolumeScatter, "Volume Scatter", "Scatter light as it passes through the volume, often used to add fog to a scene") DefNode(ShaderNode, SH_NODE_VOLUME_PRINCIPLED, 0, "PRINCIPLED_VOLUME", VolumePrincipled, "Principled Volume", "Combine all volume shading components into a single easy to use node") DefNode(ShaderNode, SH_NODE_EMISSION, 0, "EMISSION", Emission, "Emission", "Lambertian emission shader") DefNode(ShaderNode, SH_NODE_NEW_GEOMETRY, 0, "NEW_GEOMETRY", NewGeometry, "Geometry", "Retrieve geometric information about the current shading point") diff --git a/source/blender/nodes/shader/nodes/node_shader_volume_scatter.cc b/source/blender/nodes/shader/nodes/node_shader_volume_scatter.cc index b292530ecaf..64a30b7fd88 100644 --- a/source/blender/nodes/shader/nodes/node_shader_volume_scatter.cc +++ b/source/blender/nodes/shader/nodes/node_shader_volume_scatter.cc @@ -4,6 +4,13 @@ #include "node_shader_util.hh" +#include "BLI_string.h" + +#include "UI_interface.hh" +#include "UI_resources.hh" + +#include "BKE_node_runtime.hh" + namespace blender::nodes::node_shader_volume_scatter_cc { static void node_declare(NodeDeclarationBuilder &b) @@ -16,16 +23,62 @@ static void node_declare(NodeDeclarationBuilder &b) .default_value(0.0f) .min(-1.0f) .max(1.0f) - .subtype(PROP_FACTOR); + .subtype(PROP_FACTOR) + .description( + "Directionality of the scattering. Zero is isotropic, negative is backward, " + "positive is forward"); + b.add_input("IOR") + .default_value(1.33f) + .min(1.0f) + .max(2.0f) + .subtype(PROP_FACTOR) + .description("Index Of Refraction of the scattering particles"); + b.add_input("Backscatter") + .default_value(0.1f) + .min(0.0f) + .max(0.5f) + .subtype(PROP_FACTOR) + .description("Fraction of light that is scattered backwards"); + b.add_input("Alpha").default_value(0.5f).min(0.0f).max(500.0f); + b.add_input("Diameter") + .default_value(20.0f) + .min(5.0f) + .max(50.0f) + .description("Diameter of the water droplets, in micrometers"); b.add_input("Weight").available(false); b.add_output("Volume").translation_context(BLT_I18NCONTEXT_ID_ID); } -#define socket_not_zero(sock) (in[sock].link || (clamp_f(in[sock].vec[0], 0.0f, 1.0f) > 1e-5f)) -#define socket_not_black(sock) \ - (in[sock].link || (clamp_f(in[sock].vec[0], 0.0f, 1.0f) > 1e-5f && \ - clamp_f(in[sock].vec[1], 0.0f, 1.0f) > 1e-5f && \ - clamp_f(in[sock].vec[2], 0.0f, 1.0f) > 1e-5f)) +static void node_shader_buts_scatter(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + uiItemR(layout, ptr, "phase", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); +} + +static void node_shader_init_scatter(bNodeTree * /*ntree*/, bNode *node) +{ + node->custom1 = SHD_PHASE_HENYEY_GREENSTEIN; +} + +static void node_shader_update_scatter(bNodeTree *ntree, bNode *node) +{ + const int phase_function = node->custom1; + + LISTBASE_FOREACH (bNodeSocket *, sock, &node->inputs) { + if (STR_ELEM(sock->name, "IOR", "Backscatter")) { + bke::node_set_socket_availability(ntree, sock, phase_function == SHD_PHASE_FOURNIER_FORAND); + } + else if (STR_ELEM(sock->name, "Anisotropy")) { + bke::node_set_socket_availability( + ntree, sock, ELEM(phase_function, SHD_PHASE_HENYEY_GREENSTEIN, SHD_PHASE_DRAINE)); + } + else if (STR_ELEM(sock->name, "Alpha")) { + bke::node_set_socket_availability(ntree, sock, phase_function == SHD_PHASE_DRAINE); + } + else if (STR_ELEM(sock->name, "Diameter")) { + bke::node_set_socket_availability(ntree, sock, phase_function == SHD_PHASE_MIE); + } + } +} static int node_shader_gpu_volume_scatter(GPUMaterial *mat, bNode *node, @@ -33,7 +86,7 @@ static int node_shader_gpu_volume_scatter(GPUMaterial *mat, GPUNodeStack *in, GPUNodeStack *out) { - if (socket_not_zero(SOCK_DENSITY_ID) && socket_not_black(SOCK_COLOR_ID)) { + if (node_socket_not_zero(in[SOCK_DENSITY_ID]) && node_socket_not_black(in[SOCK_COLOR_ID])) { /* Consider there is absorption phenomenon when there is scattering since * `extinction = scattering + absorption`. */ GPU_material_flag_set(mat, GPU_MATFLAG_VOLUME_SCATTER | GPU_MATFLAG_VOLUME_ABSORPTION); @@ -55,7 +108,12 @@ void register_node_type_sh_volume_scatter() sh_node_type_base(&ntype, SH_NODE_VOLUME_SCATTER, "Volume Scatter", NODE_CLASS_SHADER); ntype.declare = file_ns::node_declare; + ntype.add_ui_poll = object_shader_nodes_poll; + ntype.draw_buttons = file_ns::node_shader_buts_scatter; + blender::bke::node_type_size_preset(&ntype, blender::bke::eNodeSizePreset::Middle); + ntype.initfunc = file_ns::node_shader_init_scatter; ntype.gpu_fn = file_ns::node_shader_gpu_volume_scatter; + ntype.updatefunc = file_ns::node_shader_update_scatter; blender::bke::node_register_type(&ntype); }