Shader: New Volume Coefficients Shader

Add a new shader node to control volume coefficients (scattering,
absorption and emission) directly, making it easier to model existing
volumes with measured data.

Pull Request: https://projects.blender.org/blender/blender/pulls/136287
This commit is contained in:
Alexandre-Cardaillac
2025-05-08 19:19:35 +02:00
committed by Weizhen Huang
parent d018c12b61
commit 921c2b9d61
26 changed files with 614 additions and 128 deletions

View File

@@ -747,6 +747,28 @@ static ShaderNode *add_node(Scene *scene,
else if (b_node.is_a(&RNA_ShaderNodeVolumeAbsorption)) {
node = graph->create_node<AbsorptionVolumeNode>();
}
else if (b_node.is_a(&RNA_ShaderNodeVolumeCoefficients)) {
BL::ShaderNodeVolumeCoefficients b_coeffs_node(b_node);
VolumeCoefficientsNode *coeffs = graph->create_node<VolumeCoefficientsNode>();
switch (b_coeffs_node.phase()) {
case BL::ShaderNodeVolumeCoefficients::phase_HENYEY_GREENSTEIN:
coeffs->set_phase(CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID);
break;
case BL::ShaderNodeVolumeCoefficients::phase_FOURNIER_FORAND:
coeffs->set_phase(CLOSURE_VOLUME_FOURNIER_FORAND_ID);
break;
case BL::ShaderNodeVolumeCoefficients::phase_DRAINE:
coeffs->set_phase(CLOSURE_VOLUME_DRAINE_ID);
break;
case BL::ShaderNodeVolumeCoefficients::phase_RAYLEIGH:
coeffs->set_phase(CLOSURE_VOLUME_RAYLEIGH_ID);
break;
case BL::ShaderNodeVolumeCoefficients::phase_MIE:
coeffs->set_phase(CLOSURE_VOLUME_MIE_ID);
break;
}
node = coeffs;
}
else if (b_node.is_a(&RNA_ShaderNodeVolumePrincipled)) {
PrincipledVolumeNode *principled = graph->create_node<PrincipledVolumeNode>();
node = principled;

View File

@@ -43,6 +43,7 @@ set(SRC_OSL
node_point_info.osl
node_scatter_volume.osl
node_absorption_volume.osl
node_volume_coefficients.osl
node_principled_volume.osl
node_holdout.osl
node_hsv.osl

View File

@@ -0,0 +1,74 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "stdcycles.h"
struct MieParameters {
float g_HG;
float g_D;
float alpha;
float mixture;
};
MieParameters phase_mie_fitted_parameters(float Diameter)
{
float d = max(Diameter, 0.0);
if (d <= 0.1) {
/* Eq (11 - 14). */
return {13.8 * d * d, 1.1456 * d * sin(9.29044 * d), 250.0, 0.252977 - 312.983 * pow(d, 4.3)};
}
if (d < 1.5) {
/* Eq (15 - 18). */
float log_d = log(d);
float a = (log_d - 0.238604) * (log_d + 1.00667);
float b = 0.507522 - 0.15677 * log_d;
float c = 1.19692 * cos(a / b) + 1.37932 * log_d + 0.0625835;
return {0.862 - 0.143 * log_d * log_d,
0.379685 * cos(c) + 0.344213,
250.0,
0.146209 * cos(3.38707 * log_d + 2.11193) + 0.316072 + 0.0778917 * log_d};
}
if (d < 5.0) {
/* Eq (19 - 22). */
float log_d = log(d);
float temp = cos(5.68947 * (log(log_d) - 0.0292149));
return {0.0604931 * log(log_d) + 0.940256,
0.500411 - (0.081287 / (-2.0 * log_d + tan(log_d) + 1.27551)),
7.30354 * log_d + 6.31675,
0.026914 * (log_d - temp) + 0.3764};
}
/* Eq (7 - 10). */
return {exp(-0.0990567 / (d - 1.67154)),
exp(-2.20679 / (d + 3.91029) - 0.428934),
exp(3.62489 - 8.29288 / (d + 5.52825)),
exp(-0.599085 / (d - 0.641583) - 0.665888)};
}
closure color
scatter(string phase, float Anisotropy, float IOR, float Backscatter, float Alpha, float Diameter)
{
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") {
/* Approximation of Mie phase function for water droplets using a mix of Draine and H-G.
* See `kernel/svm/closure.h` for details. */
MieParameters param = phase_mie_fitted_parameters(Diameter);
scatter = mix(henyey_greenstein(param.g_HG), draine(param.g_D, param.alpha), param.mixture);
}
else {
scatter = henyey_greenstein(Anisotropy);
}
return scatter;
}

View File

@@ -2,51 +2,7 @@
*
* SPDX-License-Identifier: Apache-2.0 */
#include "stdcycles.h"
struct MieParameters {
float g_HG;
float g_D;
float alpha;
float mixture;
};
MieParameters phase_mie_fitted_parameters(float Diameter)
{
float d = max(Diameter, 0.0);
if (d <= 0.1) {
/* Eq (11 - 14). */
return {13.8 * d * d, 1.1456 * d * sin(9.29044 * d), 250.0, 0.252977 - 312.983 * pow(d, 4.3)};
}
if (d < 1.5) {
/* Eq (15 - 18). */
float log_d = log(d);
float a = (log_d - 0.238604) * (log_d + 1.00667);
float b = 0.507522 - 0.15677 * log_d;
float c = 1.19692 * cos(a / b) + 1.37932 * log_d + 0.0625835;
return {0.862 - 0.143 * log_d * log_d,
0.379685 * cos(c) + 0.344213,
250.0,
0.146209 * cos(3.38707 * log_d + 2.11193) + 0.316072 + 0.0778917 * log_d};
}
if (d < 5.0) {
/* Eq (19 - 22). */
float log_d = log(d);
float temp = cos(5.68947 * (log(log_d) - 0.0292149));
return {0.0604931 * log(log_d) + 0.940256,
0.500411 - (0.081287 / (-2.0 * log_d + tan(log_d) + 1.27551)),
7.30354 * log_d + 6.31675,
0.026914 * (log_d - temp) + 0.3764};
}
/* Eq (7 - 10). */
return {exp(-0.0990567 / (d - 1.67154)),
exp(-2.20679 / (d + 3.91029) - 0.428934),
exp(3.62489 - 8.29288 / (d + 5.52825)),
exp(-0.599085 / (d - 0.641583) - 0.665888)};
}
#include "node_scatter.h"
shader node_scatter_volume(string phase = "Henyey-Greenstein",
color Color = color(0.8, 0.8, 0.8),
@@ -58,25 +14,7 @@ shader node_scatter_volume(string phase = "Henyey-Greenstein",
float Diameter = 20.0,
output closure color Volume = 0)
{
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") {
/* Approximation of Mie phase function for water droplets using a mix of Draine and H-G.
* See `kernel/svm/closure.h` for details. */
MieParameters param = phase_mie_fitted_parameters(Diameter);
scatter = mix(henyey_greenstein(param.g_HG), draine(param.g_D, param.alpha), param.mixture);
}
else {
scatter = henyey_greenstein(Anisotropy);
}
closure color scatter_closure = scatter(phase, Anisotropy, IOR, Backscatter, Alpha, Diameter);
Volume = (Color * max(Density, 0.0)) * scatter;
Volume = (Color * max(Density, 0.0)) * scatter_closure;
}

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "node_scatter.h"
shader node_volume_coefficients(string phase = "Henyey-Greenstein",
vector AbsorptionCoefficients = vector(1.0, 1.0, 1.0),
vector ScatterCoefficients = vector(1.0, 1.0, 1.0),
float Anisotropy = 0.0,
float IOR = 1.33,
float Backscatter = 0.1,
float Alpha = 0.5,
float Diameter = 20.0,
vector EmissionCoefficients = vector(0.0, 0.0, 0.0),
output closure color Volume = 0)
{
closure color scatter_closure = scatter(phase, Anisotropy, IOR, Backscatter, Alpha, Diameter);
/* Add scattering and absorption closures. */
Volume = color(ScatterCoefficients) * scatter_closure +
color(AbsorptionCoefficients) * absorption();
/* Compute emission. */
Volume += color(EmissionCoefficients) * emission();
}

View File

@@ -1053,6 +1053,78 @@ ccl_device
return offset;
}
ccl_device_inline void svm_alloc_closure_volume_scatter(ccl_private ShaderData *sd,
ccl_private float *stack,
Spectrum weight,
const uint type,
const uint param1_offset,
const uint param_extra)
{
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(param_extra);
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, param_extra);
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, param_extra);
sd->flag |= volume_draine_setup(volume);
}
} break;
case CLOSURE_VOLUME_MIE_ID: {
const float d = stack_valid(param1_offset) ? stack_load_float(stack, param1_offset) :
__uint_as_float(param_extra);
float g_HG;
float g_D;
float alpha;
float mixture;
phase_mie_fitted_parameters(d, &g_HG, &g_D, &alpha, &mixture);
ccl_private HenyeyGreensteinVolume *hg = (ccl_private HenyeyGreensteinVolume *)bsdf_alloc(
sd, sizeof(HenyeyGreensteinVolume), weight * (1.0f - mixture));
if (hg) {
hg->g = g_HG;
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 = g_D;
draine->alpha = alpha;
sd->flag |= volume_draine_setup(draine);
}
} break;
default: {
kernel_assert(0);
break;
}
}
}
template<ShaderType shader_type>
ccl_device_noinline void svm_node_closure_volume(KernelGlobals kg,
ccl_private ShaderData *sd,
@@ -1093,69 +1165,7 @@ ccl_device_noinline void svm_node_closure_volume(KernelGlobals kg,
/* Add closure for volume scattering. */
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: {
const float d = stack_valid(param1_offset) ? stack_load_float(stack, param1_offset) :
__uint_as_float(node.w);
float g_HG;
float g_D;
float alpha;
float mixture;
phase_mie_fitted_parameters(d, &g_HG, &g_D, &alpha, &mixture);
ccl_private HenyeyGreensteinVolume *hg = (ccl_private HenyeyGreensteinVolume *)bsdf_alloc(
sd, sizeof(HenyeyGreensteinVolume), weight * (1.0f - mixture));
if (hg) {
hg->g = g_HG;
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 = g_D;
draine->alpha = alpha;
sd->flag |= volume_draine_setup(draine);
}
} break;
default: {
kernel_assert(0);
break;
}
}
svm_alloc_closure_volume_scatter(sd, stack, weight, type, param1_offset, node.w);
}
/* Sum total extinction weight. */
@@ -1163,6 +1173,62 @@ ccl_device_noinline void svm_node_closure_volume(KernelGlobals kg,
#endif
}
template<ShaderType shader_type>
ccl_device_noinline void svm_node_volume_coefficients(KernelGlobals kg,
ccl_private ShaderData *sd,
ccl_private float *stack,
Spectrum scatter_coeffs,
const uint4 node,
const uint32_t path_flag)
{
#ifdef __VOLUME__
/* Only sum extinction for volumes, variable is shared with surface transparency. */
if (shader_type != SHADER_TYPE_VOLUME) {
return;
}
uint type;
uint empty_offset;
uint param1_offset;
uint mix_weight_offset;
svm_unpack_node_uchar4(node.y, &type, &empty_offset, &param1_offset, &mix_weight_offset);
const float mix_weight = (stack_valid(mix_weight_offset) ?
stack_load_float(stack, mix_weight_offset) :
1.0f);
if (mix_weight == 0.0f) {
return;
}
/* Compute scattering coefficient. */
const float weight = mix_weight * object_volume_density(kg, sd->object);
/* Add closure for volume scattering. */
if (!is_zero(scatter_coeffs) && CLOSURE_IS_VOLUME_SCATTER(type)) {
svm_alloc_closure_volume_scatter(
sd, stack, weight * scatter_coeffs, type, param1_offset, node.z);
}
uint absorption_coeffs_offset;
uint emission_coeffs_offset;
svm_unpack_node_uchar4(
node.w, &absorption_coeffs_offset, &emission_coeffs_offset, &empty_offset, &empty_offset);
const float3 absorption_coeffs = stack_load_float3(stack, absorption_coeffs_offset);
volume_extinction_setup(sd, weight * (scatter_coeffs + absorption_coeffs));
const float3 emission_coeffs = stack_load_float3(stack, emission_coeffs_offset);
/* Compute emission. */
if (path_flag & PATH_RAY_SHADOW) {
/* Don't need emission for shadows. */
return;
}
if (is_zero(emission_coeffs)) {
return;
}
emission_setup(sd, weight * emission_coeffs);
#endif
}
template<ShaderType shader_type>
ccl_device_noinline int svm_node_principled_volume(KernelGlobals kg,
ccl_private ShaderData *sd,

View File

@@ -50,6 +50,7 @@ SHADER_NODE_TYPE(NODE_CLOSURE_HOLDOUT)
SHADER_NODE_TYPE(NODE_FRESNEL)
SHADER_NODE_TYPE(NODE_LAYER_WEIGHT)
SHADER_NODE_TYPE(NODE_CLOSURE_VOLUME)
SHADER_NODE_TYPE(NODE_VOLUME_COEFFICIENTS)
SHADER_NODE_TYPE(NODE_PRINCIPLED_VOLUME)
SHADER_NODE_TYPE(NODE_MATH)
SHADER_NODE_TYPE(NODE_VECTOR_MATH)

View File

@@ -296,6 +296,12 @@ ccl_device void svm_eval_nodes(KernelGlobals kg,
svm_node_closure_volume<type>(kg, sd, stack, closure_weight, node);
}
break;
SVM_CASE(NODE_VOLUME_COEFFICIENTS)
IF_KERNEL_NODES_FEATURE(VOLUME)
{
svm_node_volume_coefficients<type>(kg, sd, stack, closure_weight, node, path_flag);
}
break;
SVM_CASE(NODE_PRINCIPLED_VOLUME)
IF_KERNEL_NODES_FEATURE(VOLUME)
{

View File

@@ -3513,6 +3513,11 @@ NODE_DEFINE(ScatterVolumeNode)
return type;
}
ScatterVolumeNode::ScatterVolumeNode(const NodeType *node_type) : VolumeNode(node_type)
{
closure = CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID;
}
ScatterVolumeNode::ScatterVolumeNode() : VolumeNode(get_node_type())
{
closure = CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID;
@@ -3550,6 +3555,111 @@ void ScatterVolumeNode::compile(OSLCompiler &compiler)
compiler.add(this, "node_scatter_volume");
}
/* Volume Coefficients Closure */
NODE_DEFINE(VolumeCoefficientsNode)
{
NodeType *type = NodeType::add("volume_coefficients", create, NodeType::SHADER);
SOCKET_IN_VECTOR(scatter_coeffs, "Scatter Coefficients", make_float3(1.0f, 1.0f, 1.0f));
SOCKET_IN_VECTOR(absorption_coeffs, "Absorption Coefficients", make_float3(1.0f, 1.0f, 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);
SOCKET_IN_VECTOR(emission_coeffs, "Emission Coefficients", make_float3(0.0f, 0.0f, 0.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");
return type;
}
VolumeCoefficientsNode::VolumeCoefficientsNode() : ScatterVolumeNode(get_node_type())
{
closure = CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID;
}
void VolumeCoefficientsNode::compile(SVMCompiler &compiler)
{
closure = phase;
ShaderInput *param1 = nullptr;
ShaderInput *param2 = nullptr;
switch (phase) {
case CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID:
param1 = input("Anisotropy");
break;
case CLOSURE_VOLUME_FOURNIER_FORAND_ID:
param1 = input("IOR");
param2 = input("Backscatter");
break;
case CLOSURE_VOLUME_RAYLEIGH_ID:
break;
case CLOSURE_VOLUME_DRAINE_ID:
param1 = input("Anisotropy");
param2 = input("Alpha");
break;
case CLOSURE_VOLUME_MIE_ID:
param1 = input("Diameter");
break;
default:
assert(false);
break;
}
ShaderInput *coeffs_in = input("Scatter Coefficients");
ShaderInput *absorption_coeffs_in = input("Absorption Coefficients");
ShaderInput *emission_coeffs_in = input("Emission Coefficients");
if (coeffs_in->link) {
compiler.add_node(NODE_CLOSURE_WEIGHT, compiler.stack_assign(coeffs_in));
}
else {
compiler.add_node(NODE_CLOSURE_SET_WEIGHT, scatter_coeffs);
}
const uint mix_weight_ofs = compiler.closure_mix_weight_offset();
if (param2 == nullptr) {
/* More efficient packing if we don't need the second parameter. */
const uint param1_ofs = (param1) ? compiler.stack_assign_if_linked(param1) : SVM_STACK_INVALID;
compiler.add_node(NODE_VOLUME_COEFFICIENTS,
compiler.encode_uchar4(closure, 0, param1_ofs, mix_weight_ofs),
__float_as_int((param1) ? get_float(param1->socket_type) : 0.0f),
compiler.encode_uchar4(compiler.stack_assign(absorption_coeffs_in),
compiler.stack_assign(emission_coeffs_in),
0,
0));
}
else {
const uint param1_ofs = (param1) ? compiler.stack_assign(param1) : SVM_STACK_INVALID;
const uint param2_ofs = (param2) ? compiler.stack_assign(param2) : SVM_STACK_INVALID;
compiler.add_node(NODE_VOLUME_COEFFICIENTS,
compiler.encode_uchar4(closure, 0, param1_ofs, mix_weight_ofs),
param2_ofs,
compiler.encode_uchar4(compiler.stack_assign(absorption_coeffs_in),
compiler.stack_assign(emission_coeffs_in),
0,
0));
}
}
void VolumeCoefficientsNode::compile(OSLCompiler &compiler)
{
compiler.parameter(this, "phase");
compiler.add(this, "node_volume_coefficients");
}
/* Principled Volume Closure */
NODE_DEFINE(PrincipledVolumeNode)

View File

@@ -836,6 +836,7 @@ class AbsorptionVolumeNode : public VolumeNode {
class ScatterVolumeNode : public VolumeNode {
public:
ScatterVolumeNode(const NodeType *node_type);
SHADER_NODE_CLASS(ScatterVolumeNode)
NODE_SOCKET_API(float, anisotropy)
@@ -846,6 +847,15 @@ class ScatterVolumeNode : public VolumeNode {
NODE_SOCKET_API(ClosureType, phase)
};
class VolumeCoefficientsNode : public ScatterVolumeNode {
public:
SHADER_NODE_CLASS(VolumeCoefficientsNode)
NODE_SOCKET_API(float3, scatter_coeffs)
NODE_SOCKET_API(float3, absorption_coeffs)
NODE_SOCKET_API(float3, emission_coeffs)
};
class PrincipledVolumeNode : public VolumeNode {
public:
SHADER_NODE_CLASS(PrincipledVolumeNode)

View File

@@ -232,6 +232,10 @@ class NODE_MT_category_shader_shader(Menu):
layout,
"ShaderNodeVolumeScatter",
)
node_add_menu.add_node_type(
layout,
"ShaderNodeVolumeCoefficients",
)
node_add_menu.draw_assets_for_catalog(layout, self.bl_label)

View File

@@ -133,6 +133,7 @@
#define SH_NODE_BSDF_RAY_PORTAL 714
#define SH_NODE_TEX_GABOR 715
#define SH_NODE_BSDF_METALLIC 716
#define SH_NODE_VOLUME_COEFFICIENTS 717
/** \} */

View File

@@ -629,6 +629,7 @@ set(GLSL_SRC
shaders/material/gpu_shader_material_volume_absorption.glsl
shaders/material/gpu_shader_material_volume_principled.glsl
shaders/material/gpu_shader_material_volume_scatter.glsl
shaders/material/gpu_shader_material_volume_coefficients.glsl
shaders/material/gpu_shader_material_voronoi.glsl
shaders/material/gpu_shader_material_wireframe.glsl
shaders/material/gpu_shader_material_world_normals.glsl

View File

@@ -0,0 +1,32 @@
/* SPDX-FileCopyrightText: 2019-2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_material_blackbody.glsl"
void node_volume_coefficients(float weight,
float3 AbsorptionCoefficients,
float3 ScatterCoefficients,
float Anisotropy,
float IOR,
float Backscatter,
float Alpha,
float Diameter,
float3 EmissionCoefficients,
out Closure result)
{
ClosureVolumeScatter volume_scatter_data;
volume_scatter_data.weight = weight;
volume_scatter_data.scattering = ScatterCoefficients;
volume_scatter_data.anisotropy = Anisotropy;
ClosureVolumeAbsorption volume_absorption_data;
volume_absorption_data.weight = weight;
volume_absorption_data.absorption = AbsorptionCoefficients;
ClosureEmission emission_data;
emission_data.weight = weight;
emission_data.emission = EmissionCoefficients;
result = closure_eval(volume_scatter_data, volume_absorption_data, emission_data);
}

View File

@@ -6862,6 +6862,17 @@ static void def_scatter(BlenderRNA * /*brna*/, StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
static void def_volume_coefficients(BlenderRNA * /*brna*/, 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(BlenderRNA * /*brna*/, StructRNA *srna)
{
PropertyRNA *prop;
@@ -13617,6 +13628,7 @@ static void rna_def_nodes(BlenderRNA *brna)
define("ShaderNode", "ShaderNodeVolumeInfo");
define("ShaderNode", "ShaderNodeVolumePrincipled");
define("ShaderNode", "ShaderNodeVolumeScatter", def_scatter);
define("ShaderNode", "ShaderNodeVolumeCoefficients", def_volume_coefficients);
define("ShaderNode", "ShaderNodeWavelength");
define("ShaderNode", "ShaderNodeWireframe", def_sh_tex_wireframe);

View File

@@ -110,6 +110,7 @@ set(SRC
nodes/node_shader_volume_info.cc
nodes/node_shader_volume_principled.cc
nodes/node_shader_volume_scatter.cc
nodes/node_shader_volume_coefficients.cc
nodes/node_shader_wavelength.cc
nodes/node_shader_wireframe.cc

View File

@@ -110,6 +110,7 @@ void register_shader_nodes()
register_node_type_sh_volume_info();
register_node_type_sh_volume_principled();
register_node_type_sh_volume_scatter();
register_node_type_sh_volume_coefficients();
register_node_type_sh_wavelength();
register_node_type_sh_wireframe();
}

View File

@@ -109,5 +109,6 @@ void register_node_type_sh_volume_absorption();
void register_node_type_sh_volume_info();
void register_node_type_sh_volume_principled();
void register_node_type_sh_volume_scatter();
void register_node_type_sh_volume_coefficients();
void register_node_type_sh_wavelength();
void register_node_type_sh_wireframe();

View File

@@ -969,6 +969,7 @@ static void ntree_shader_weight_tree_invert(bNodeTree *ntree, bNode *output_node
case SH_NODE_VOLUME_ABSORPTION:
case SH_NODE_VOLUME_PRINCIPLED:
case SH_NODE_VOLUME_SCATTER:
case SH_NODE_VOLUME_COEFFICIENTS:
fromsock = ntree_shader_node_find_input(fromnode, "Weight");
/* Make "weight" sockets available so that links to it are available as well and are
* not ignored in other places. */
@@ -1035,6 +1036,7 @@ static bool closure_node_filter(const bNode *node)
case SH_NODE_VOLUME_ABSORPTION:
case SH_NODE_VOLUME_PRINCIPLED:
case SH_NODE_VOLUME_SCATTER:
case SH_NODE_VOLUME_COEFFICIENTS:
return true;
default:
return false;

View File

@@ -0,0 +1,159 @@
/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_shader_util.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "BKE_node_runtime.hh"
namespace blender::nodes::node_shader_volume_coefficients_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.use_custom_socket_order();
b.add_output<decl::Shader>("Volume").translation_context(BLT_I18NCONTEXT_ID_ID);
b.add_input<decl::Float>("Weight").available(false);
#define SOCK_WEIGHT_ID 0
PanelDeclarationBuilder &abs = b.add_panel("Absorption").default_closed(false);
abs.add_input<decl::Vector>("Absorption Coefficients")
.default_value({1.0f, 1.0f, 1.0f})
.min(0.0f)
.max(1000.0f)
.description(
"Probability density per color channel that light is absorbed per unit distance "
"traveled in the medium");
#define SOCK_ABSORPTION_COEFFICIENTS_ID 1
PanelDeclarationBuilder &sca = b.add_panel("Scatter").default_closed(false);
sca.add_layout([](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) {
uiItemR(layout, ptr, "phase", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
});
sca.add_input<decl::Vector>("Scatter Coefficients")
.default_value({1.0f, 1.0f, 1.0f})
.min(0.0f)
.max(1000.0f)
.description(
"Probability density per color channel of an out-scattering event occurring per unit "
"distance");
#define SOCK_SCATTER_COEFFICIENTS_ID 2
sca.add_input<decl::Float>("Anisotropy")
.default_value(0.0f)
.min(-1.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.description(
"Directionality of the scattering. Zero is isotropic, negative is backward, "
"positive is forward");
#define SOCK_SCATTER_ANISOTROPY_ID 3
sca.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");
#define SOCK_SCATTER_IOR_ID 4
sca.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");
#define SOCK_SCATTER_BACKSCATTER_ID 5
sca.add_input<decl::Float>("Alpha").default_value(0.5f).min(0.0f).max(500.0f);
#define SOCK_SCATTER_ALPHA_ID 6
sca.add_input<decl::Float>("Diameter")
.default_value(20.0f)
.min(0.0f)
.max(50.0f)
.description("Diameter of the water droplets, in micrometers");
#define SOCK_SCATTER_DIAMETER_ID 7
PanelDeclarationBuilder &emi = b.add_panel("Emission").default_closed(false);
emi.add_input<decl::Vector>("Emission Coefficients")
.default_value({0.0f, 0.0f, 0.0f})
.min(0.0f)
.max(1000.0f)
.description("Emitted radiance per color channel that is added to a ray per unit distance");
#define SOCK_EMISSION_COEFFICIENTS_ID 8
}
static void node_shader_init_coefficients(bNodeTree * /*ntree*/, bNode *node)
{
node->custom1 = SHD_PHASE_HENYEY_GREENSTEIN;
}
static void node_shader_update_coefficients(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_coefficients(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
if (node_socket_not_black(in[SOCK_SCATTER_COEFFICIENTS_ID])) {
GPU_material_flag_set(mat, GPU_MATFLAG_VOLUME_SCATTER | GPU_MATFLAG_VOLUME_ABSORPTION);
}
if (node_socket_not_black(in[SOCK_ABSORPTION_COEFFICIENTS_ID])) {
GPU_material_flag_set(mat, GPU_MATFLAG_VOLUME_ABSORPTION);
}
return GPU_stack_link(mat, node, "node_volume_coefficients", in, out);
}
#undef SOCK_WEIGHT_ID
#undef SOCK_ABSORPTION_COEFFICIENTS_ID
#undef SOCK_SCATTER_COEFFICIENTS_ID
#undef SOCK_SCATTER_ANISOTROPY_ID
#undef SOCK_SCATTER_IOR_ID
#undef SOCK_SCATTER_BACKSCATTER_ID
#undef SOCK_SCATTER_ALPHA_ID
#undef SOCK_SCATTER_DIAMETER_ID
#undef SOCK_EMISSION_COEFFICIENTS_ID
} // namespace blender::nodes::node_shader_volume_coefficients_cc
/* node type definition */
void register_node_type_sh_volume_coefficients()
{
namespace file_ns = blender::nodes::node_shader_volume_coefficients_cc;
static blender::bke::bNodeType ntype;
sh_node_type_base(&ntype, "ShaderNodeVolumeCoefficients", SH_NODE_VOLUME_COEFFICIENTS);
ntype.ui_name = "Volume Coefficients";
ntype.ui_description =
"Model all three physical processes in a volume, represented by their coefficients";
ntype.enum_name_legacy = "VOLUME_COEFFICIENTS";
ntype.nclass = NODE_CLASS_SHADER;
ntype.declare = file_ns::node_declare;
ntype.add_ui_poll = object_shader_nodes_poll;
blender::bke::node_type_size_preset(ntype, blender::bke::eNodeSizePreset::Large);
ntype.initfunc = file_ns::node_shader_init_coefficients;
ntype.gpu_fn = file_ns::node_shader_gpu_volume_coefficients;
ntype.updatefunc = file_ns::node_shader_update_coefficients;
blender::bke::node_register_type(ntype);
}

BIN
tests/files/render/volume/cycles_renders/implicit_volume.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
tests/files/render/volume/implicit_volume.blend (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.