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 <lukas@lukasstockner.de> Pull Request: https://projects.blender.org/blender/blender/pulls/123532
This commit is contained in:
committed by
Lukas Stockner
parent
db490e90fe
commit
0315eae536
@@ -721,7 +721,26 @@ static ShaderNode *add_node(Scene *scene,
|
||||
node = ao;
|
||||
}
|
||||
else if (b_node.is_a(&RNA_ShaderNodeVolumeScatter)) {
|
||||
node = graph->create_node<ScatterVolumeNode>();
|
||||
BL::ShaderNodeVolumeScatter b_scatter_node(b_node);
|
||||
ScatterVolumeNode *scatter = graph->create_node<ScatterVolumeNode>();
|
||||
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<AbsorptionVolumeNode>();
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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. */
|
||||
|
||||
60
intern/cycles/kernel/closure/volume_draine.h
Normal file
60
intern/cycles/kernel/closure/volume_draine.h
Normal file
@@ -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
|
||||
71
intern/cycles/kernel/closure/volume_fournier_forand.h
Normal file
71
intern/cycles/kernel/closure/volume_fournier_forand.h
Normal file
@@ -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
|
||||
62
intern/cycles/kernel/closure/volume_henyey_greenstein.h
Normal file
62
intern/cycles/kernel/closure/volume_henyey_greenstein.h
Normal file
@@ -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
|
||||
49
intern/cycles/kernel/closure/volume_rayleigh.h
Normal file
49
intern/cycles/kernel/closure/volume_rayleigh.h
Normal file
@@ -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
|
||||
238
intern/cycles/kernel/closure/volume_util.h
Normal file
238
intern/cycles/kernel/closure/volume_util.h
Normal file
@@ -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 <cos theta>, and alpha the second moment <cos2 theta> 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<d<50.
|
||||
* Generally, we try to allow exceeding the soft limits when reasonable. Here, the only
|
||||
* real limit is that the values need to stay within -1<g<1, 0<a and 0<mixture<1.
|
||||
* This results in the condition d > 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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<decl::Float>("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<decl::Float>("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<decl::Float>("Alpha").default_value(0.5f).min(0.0f).max(500.0f);
|
||||
b.add_input<decl::Float>("Diameter")
|
||||
.default_value(20.0f)
|
||||
.min(5.0f)
|
||||
.max(50.0f)
|
||||
.description("Diameter of the water droplets, in micrometers");
|
||||
b.add_input<decl::Float>("Weight").available(false);
|
||||
b.add_output<decl::Shader>("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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user