Cycles: Detect volume attribute nodes that can use stochastic sampling

Detect which volume attributes nodes have a linear mapping to their usage
as density / color / temperature in volume shader nodes, and use stochastic
sampling for them.

Pull Request: https://projects.blender.org/blender/blender/pulls/132908
This commit is contained in:
Brecht Van Lommel
2025-06-12 18:55:59 +02:00
parent 646dc7fe4d
commit 13ab5067ce
11 changed files with 354 additions and 34 deletions

View File

@@ -657,7 +657,7 @@ inline bool get_object_attribute_impl(const ThreadKernelGlobalsCPU *kg,
T dy = make_zero<T>();
#ifdef __VOLUME__
if (primitive_is_volume_attribute(sd)) {
v = primitive_volume_attribute<T>(kg, sd, desc, false);
v = primitive_volume_attribute<T>(kg, sd, desc, true);
}
else
#endif
@@ -1345,11 +1345,10 @@ bool OSLRenderServices::texture3d(OSLUStringHash filename,
switch (texture_type) {
case OSLTextureHandle::SVM: {
/* Packed texture. */
ShaderData *sd = globals->sd;
const int slot = handle->svm_slots[0].y;
const float3 P_float3 = make_float3(P.x, P.y, P.z);
float4 rgba = kernel_tex_image_interp_3d(
kernel_globals, slot, P_float3, INTERPOLATION_NONE, lcg_step_float(&sd->lcg_state));
kernel_globals, slot, P_float3, INTERPOLATION_NONE, -1.0f);
result[0] = rgba[0];
if (nchannels > 1) {

View File

@@ -764,7 +764,7 @@ ccl_device_inline bool get_object_attribute_impl(KernelGlobals kg,
T dy = make_zero<T>();
#ifdef __VOLUME__
if (primitive_is_volume_attribute(sd)) {
v = primitive_volume_attribute<T>(kg, sd, desc, false);
v = primitive_volume_attribute<T>(kg, sd, desc, true);
}
else
#endif
@@ -1147,9 +1147,7 @@ ccl_device_extern bool rs_texture3d(ccl_private ShaderGlobals *sg,
switch (type) {
case OSL_TEXTURE_HANDLE_TYPE_SVM: {
ccl_private ShaderData *const sd = static_cast<ccl_private ShaderData *>(sg->renderstate);
const float4 rgba = kernel_tex_image_interp_3d(
nullptr, slot, *P, INTERPOLATION_NONE, lcg_step_float(&sd->lcg_state));
const float4 rgba = kernel_tex_image_interp_3d(nullptr, slot, *P, INTERPOLATION_NONE, -1.0f);
if (nchannels > 0) {
result[0] = rgba.x;
}

View File

@@ -65,7 +65,8 @@ ccl_device_noinline void svm_node_attr(KernelGlobals kg,
/* Volumes
* NOTE: moving this into its own node type might help improve performance. */
if (primitive_is_volume_attribute(sd)) {
const float4 value = volume_attribute_float4(kg, sd, desc, false);
const bool stochastic_sample = node.w;
const float4 value = volume_attribute_float4(kg, sd, desc, stochastic_sample);
if (type == NODE_ATTR_OUTPUT_FLOAT) {
const float f = volume_attribute_value<float>(value);

View File

@@ -663,43 +663,68 @@ void ShaderGraph::deduplicate_nodes()
}
}
/* Check whether volume output has meaningful nodes, otherwise
* disconnect the output.
*/
void ShaderGraph::verify_volume_output()
/* Does two optimizations:
* - Check whether volume output has meaningful nodes, otherwise disconnect the output.
* - Tag volume attribute nodes as supporting stochastic sampling. */
void ShaderGraph::optimize_volume_output()
{
/* Check whether we can optimize the whole volume graph out. */
ShaderInput *volume_in = output()->input("Volume");
if (volume_in->link == nullptr) {
return;
}
bool has_valid_volume = false;
ShaderNodeSet scheduled;
queue<ShaderNode *> traverse_queue;
using ShaderNodeAndNonLinear = std::pair<ShaderNode *, bool>;
set<ShaderNodeAndNonLinear, ShaderNodeIDAndBoolComparator> scheduled;
queue<ShaderNodeAndNonLinear> traverse_queue;
/* Schedule volume output. */
traverse_queue.push(volume_in->link->parent);
scheduled.insert(volume_in->link->parent);
traverse_queue.emplace(volume_in->link->parent, false);
scheduled.insert({volume_in->link->parent, false});
/* Traverse down the tree. */
while (!traverse_queue.empty()) {
ShaderNode *node = traverse_queue.front();
auto [node, nonlinear] = traverse_queue.front();
traverse_queue.pop();
/* Node is fully valid for volume, can't optimize anything out. */
/* Disable stochastic sampling on node if its contribution is nonlinear.
* This defaults to true in the class, so we only need to disable it. */
if (nonlinear && node->type == AttributeNode::get_node_type()) {
static_cast<AttributeNode *>(node)->stochastic_sample = false;
}
nonlinear = nonlinear || !node->is_linear_operation();
/* Node is fully valid for volume, won't be able to optimize it out. */
if (node->has_volume_support()) {
has_valid_volume = true;
break;
}
for (ShaderInput *input : node->inputs) {
if (input->link == nullptr) {
continue;
}
if (scheduled.find(input->link->parent) != scheduled.end()) {
ShaderNode *input_node = input->link->parent;
if (scheduled.find({input_node, nonlinear}) != scheduled.end()) {
continue;
}
traverse_queue.push(input->link->parent);
scheduled.insert(input->link->parent);
traverse_queue.emplace(input_node, nonlinear);
scheduled.insert({input_node, nonlinear});
}
}
if (LOG_IS_ON(DEBUG)) {
for (ShaderNode *node : nodes) {
if (node->type == AttributeNode::get_node_type() &&
static_cast<AttributeNode *>(node)->stochastic_sample)
{
LOG(DEBUG) << "Volume attribute node " << node->name << " uses stochastic sampling";
}
}
}
if (!has_valid_volume) {
/* We can remove the entire volume shader. */
LOG(DEBUG) << "Disconnect meaningless volume output.";
disconnect(volume_in->link);
}
@@ -771,7 +796,7 @@ void ShaderGraph::clean(Scene *scene)
constant_fold(scene);
simplify_settings(scene);
deduplicate_nodes();
verify_volume_output();
optimize_volume_output();
/* we do two things here: find cycles and break them, and remove unused
* nodes that don't feed into the output. how cycles are broken is

View File

@@ -205,6 +205,12 @@ class ShaderNode : public Node {
{
return false;
}
/* True if the node only multiplies or adds a constant values. */
virtual bool is_linear_operation()
{
return false;
}
unique_ptr_vector<ShaderInput> inputs;
unique_ptr_vector<ShaderOutput> outputs;
@@ -283,6 +289,15 @@ class ShaderNodeIDComparator {
}
};
class ShaderNodeIDAndBoolComparator {
public:
bool operator()(const std::pair<ShaderNode *, bool> p1,
const std::pair<ShaderNode *, bool> p2) const
{
return p1.first->id < p2.first->id || p1.second < p2.second;
}
};
using ShaderNodeSet = set<ShaderNode *, ShaderNodeIDComparator>;
using ShaderNodeMap = map<ShaderNode *, ShaderNode *, ShaderNodeIDComparator>;
@@ -369,7 +384,7 @@ class ShaderGraph : public NodeOwner {
void constant_fold(Scene *scene);
void simplify_settings(Scene *scene);
void deduplicate_nodes();
void verify_volume_output();
void optimize_volume_output();
};
CCL_NAMESPACE_END

View File

@@ -5063,6 +5063,20 @@ void MixNode::constant_fold(const ConstantFolder &folder)
}
}
bool MixNode::is_linear_operation()
{
switch (mix_type) {
case NODE_MIX_BLEND:
case NODE_MIX_ADD:
case NODE_MIX_MUL:
case NODE_MIX_SUB:
break;
default:
return false;
}
return use_clamp == false && input("Factor")->link == nullptr;
}
/* Mix Color */
NODE_DEFINE(MixColorNode)
@@ -5143,6 +5157,20 @@ void MixColorNode::constant_fold(const ConstantFolder &folder)
}
}
bool MixColorNode::is_linear_operation()
{
switch (blend_type) {
case NODE_MIX_BLEND:
case NODE_MIX_ADD:
case NODE_MIX_MUL:
case NODE_MIX_SUB:
break;
default:
return false;
}
return use_clamp == false && use_clamp_result == false && input("Factor")->link == nullptr;
}
/* Mix Float */
NODE_DEFINE(MixFloatNode)
@@ -5197,6 +5225,11 @@ void MixFloatNode::constant_fold(const ConstantFolder &folder)
}
}
bool MixFloatNode::is_linear_operation()
{
return use_clamp == false && input("Factor")->link == nullptr;
}
/* Mix Vector */
NODE_DEFINE(MixVectorNode)
@@ -5251,6 +5284,11 @@ void MixVectorNode::constant_fold(const ConstantFolder &folder)
}
}
bool MixVectorNode::is_linear_operation()
{
return use_clamp == false && input("Factor")->link == nullptr;
}
/* Mix Vector Non Uniform */
NODE_DEFINE(MixVectorNonUniformNode)
@@ -5302,6 +5340,11 @@ void MixVectorNonUniformNode::constant_fold(const ConstantFolder &folder)
}
}
bool MixVectorNonUniformNode::is_linear_operation()
{
return use_clamp == false && input("Factor")->link == nullptr;
}
/* Combine Color */
NODE_DEFINE(CombineColorNode)
@@ -5699,6 +5742,9 @@ void AttributeNode::compile(SVMCompiler &compiler)
ShaderOutput *alpha_out = output("Alpha");
ShaderNodeType attr_node = NODE_ATTR;
const int attr = compiler.attribute_standard(attribute);
const uint bump_filter_or_stochastic = (compiler.output_type() == SHADER_TYPE_VOLUME) ?
stochastic_sample :
__float_as_uint(bump_filter_width);
if (bump == SHADER_BUMP_DX) {
attr_node = NODE_ATTR_BUMP_DX;
@@ -5713,14 +5759,14 @@ void AttributeNode::compile(SVMCompiler &compiler)
attr_node,
attr,
compiler.encode_uchar4(compiler.stack_assign(color_out), NODE_ATTR_OUTPUT_FLOAT3),
__float_as_uint(bump_filter_width));
bump_filter_or_stochastic);
}
if (!vector_out->links.empty()) {
compiler.add_node(
attr_node,
attr,
compiler.encode_uchar4(compiler.stack_assign(vector_out), NODE_ATTR_OUTPUT_FLOAT3),
__float_as_uint(bump_filter_width));
bump_filter_or_stochastic);
}
}
@@ -5729,7 +5775,7 @@ void AttributeNode::compile(SVMCompiler &compiler)
attr_node,
attr,
compiler.encode_uchar4(compiler.stack_assign(fac_out), NODE_ATTR_OUTPUT_FLOAT),
__float_as_uint(bump_filter_width));
bump_filter_or_stochastic);
}
if (!alpha_out->links.empty()) {
@@ -5737,7 +5783,7 @@ void AttributeNode::compile(SVMCompiler &compiler)
attr_node,
attr,
compiler.encode_uchar4(compiler.stack_assign(alpha_out), NODE_ATTR_OUTPUT_FLOAT_ALPHA),
__float_as_uint(bump_filter_width));
bump_filter_or_stochastic);
}
}
@@ -6091,6 +6137,20 @@ void MapRangeNode::expand(ShaderGraph *graph)
}
}
bool MapRangeNode::is_linear_operation()
{
if (range_type != NODE_MAP_RANGE_LINEAR) {
return false;
}
ShaderInput *from_min_in = input("To Min");
ShaderInput *from_max_in = input("To Max");
ShaderInput *to_min_in = input("To Min");
ShaderInput *to_max_in = input("To Max");
return from_min_in->link == nullptr && from_max_in->link == nullptr &&
to_min_in->link == nullptr && to_max_in->link == nullptr;
}
void MapRangeNode::compile(SVMCompiler &compiler)
{
ShaderInput *value_in = input("Value");
@@ -6159,6 +6219,20 @@ VectorMapRangeNode::VectorMapRangeNode() : ShaderNode(get_node_type()) {}
void VectorMapRangeNode::expand(ShaderGraph * /*graph*/) {}
bool VectorMapRangeNode::is_linear_operation()
{
if (range_type != NODE_MAP_RANGE_LINEAR) {
return false;
}
ShaderInput *from_min_in = input("From_Min_FLOAT3");
ShaderInput *from_max_in = input("From_Max_FLOAT3");
ShaderInput *to_min_in = input("To_Min_FLOAT3");
ShaderInput *to_max_in = input("To_Max_FLOAT3");
return from_min_in->link == nullptr && from_max_in->link == nullptr &&
to_min_in->link == nullptr && to_max_in->link == nullptr;
}
void VectorMapRangeNode::compile(SVMCompiler &compiler)
{
ShaderInput *vector_in = input("Vector");
@@ -6391,6 +6465,27 @@ void MathNode::constant_fold(const ConstantFolder &folder)
}
}
bool MathNode::is_linear_operation()
{
switch (math_type) {
case NODE_MATH_ADD:
case NODE_MATH_SUBTRACT:
case NODE_MATH_MULTIPLY:
case NODE_MATH_MULTIPLY_ADD:
break;
case NODE_MATH_DIVIDE:
return input("Value2")->link == nullptr;
default:
return false;
}
int num_variable_inputs = 0;
for (ShaderInput *input : inputs) {
num_variable_inputs += (input->link) ? 1 : 0;
}
return num_variable_inputs <= 1;
}
void MathNode::compile(SVMCompiler &compiler)
{
ShaderInput *value1_in = input("Value1");
@@ -6491,6 +6586,27 @@ void VectorMathNode::constant_fold(const ConstantFolder &folder)
}
}
bool VectorMathNode::is_linear_operation()
{
switch (math_type) {
case NODE_VECTOR_MATH_ADD:
case NODE_VECTOR_MATH_SUBTRACT:
case NODE_VECTOR_MATH_MULTIPLY:
case NODE_VECTOR_MATH_MULTIPLY_ADD:
break;
case NODE_VECTOR_MATH_DIVIDE:
return input("Vector2")->link == nullptr;
default:
return false;
}
int num_variable_inputs = 0;
for (ShaderInput *input : inputs) {
num_variable_inputs += (input->link) ? 1 : 0;
}
return num_variable_inputs <= 1;
}
void VectorMathNode::compile(SVMCompiler &compiler)
{
ShaderInput *vector1_in = input("Vector1");

View File

@@ -5,6 +5,7 @@
#pragma once
#include "graph/node.h"
#include "kernel/svm/types.h"
#include "scene/image.h"
#include "scene/shader_graph.h"
@@ -190,6 +191,11 @@ class OutputNode : public ShaderNode {
{
return false;
}
bool is_linear_operation() override
{
return true;
}
};
class OutputAOVNode : public ShaderNode {
@@ -208,6 +214,11 @@ class OutputAOVNode : public ShaderNode {
return false;
}
bool is_linear_operation() override
{
return true;
}
int offset;
bool is_color;
};
@@ -386,6 +397,10 @@ class RGBToBWNode : public ShaderNode {
public:
SHADER_NODE_CLASS(RGBToBWNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override
{
return true;
}
NODE_SOCKET_API(float3, color)
};
@@ -398,6 +413,11 @@ class ConvertNode : public ShaderNode {
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override
{
return true;
}
private:
SocketType::Type from, to;
@@ -422,6 +442,10 @@ class BsdfBaseNode : public ShaderNode {
public:
BsdfBaseNode(const NodeType *node_type);
bool is_linear_operation() override
{
return true;
}
bool has_spatial_varying() override
{
return true;
@@ -467,6 +491,10 @@ class BsdfNode : public BsdfBaseNode {
class DiffuseBsdfNode : public BsdfNode {
public:
SHADER_NODE_CLASS(DiffuseBsdfNode)
bool is_linear_operation() override
{
return true;
}
NODE_SOCKET_API(float, roughness)
};
@@ -690,6 +718,10 @@ class EmissionNode : public ShaderNode {
{
return true;
}
bool is_linear_operation() override
{
return true;
}
int get_feature() override
{
@@ -708,6 +740,10 @@ class BackgroundNode : public ShaderNode {
public:
SHADER_NODE_CLASS(BackgroundNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override
{
return true;
}
int get_feature() override
{
@@ -726,6 +762,10 @@ class HoldoutNode : public ShaderNode {
{
return CLOSURE_HOLDOUT_ID;
}
bool is_linear_operation() override
{
return true;
}
NODE_SOCKET_API(float, surface_mix_weight)
NODE_SOCKET_API(float, volume_mix_weight)
@@ -757,6 +797,10 @@ class VolumeNode : public ShaderNode {
public:
VolumeNode(const NodeType *node_type);
SHADER_NODE_BASE_CLASS(VolumeNode)
bool is_linear_operation() override
{
return true;
}
void compile(SVMCompiler &compiler,
ShaderInput *density,
@@ -1058,6 +1102,10 @@ class ValueNode : public ShaderNode {
SHADER_NODE_CLASS(ValueNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override
{
return true;
}
NODE_SOCKET_API(float, value)
};
@@ -1067,6 +1115,10 @@ class ColorNode : public ShaderNode {
SHADER_NODE_CLASS(ColorNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override
{
return true;
}
NODE_SOCKET_API(float3, value)
};
@@ -1075,12 +1127,20 @@ class AddClosureNode : public ShaderNode {
public:
SHADER_NODE_CLASS(AddClosureNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override
{
return true;
}
};
class MixClosureNode : public ShaderNode {
public:
SHADER_NODE_CLASS(MixClosureNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override
{
return true;
}
NODE_SOCKET_API(float, fac)
};
@@ -1088,6 +1148,10 @@ class MixClosureNode : public ShaderNode {
class MixClosureWeightNode : public ShaderNode {
public:
SHADER_NODE_CLASS(MixClosureWeightNode)
bool is_linear_operation() override
{
return true;
}
NODE_SOCKET_API(float, weight)
NODE_SOCKET_API(float, fac)
@@ -1100,12 +1164,18 @@ class InvertNode : public ShaderNode {
NODE_SOCKET_API(float, fac)
NODE_SOCKET_API(float3, color)
bool is_linear_operation() override
{
return true;
}
};
class MixNode : public ShaderNode {
public:
SHADER_NODE_CLASS(MixNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override;
NODE_SOCKET_API(NodeMix, mix_type)
NODE_SOCKET_API(bool, use_clamp)
@@ -1118,6 +1188,7 @@ class MixColorNode : public ShaderNode {
public:
SHADER_NODE_CLASS(MixColorNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override;
NODE_SOCKET_API(float3, a)
NODE_SOCKET_API(float3, b)
@@ -1131,6 +1202,7 @@ class MixFloatNode : public ShaderNode {
public:
SHADER_NODE_CLASS(MixFloatNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override;
NODE_SOCKET_API(float, a)
NODE_SOCKET_API(float, b)
@@ -1142,6 +1214,7 @@ class MixVectorNode : public ShaderNode {
public:
SHADER_NODE_CLASS(MixVectorNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override;
NODE_SOCKET_API(float3, a)
NODE_SOCKET_API(float3, b)
@@ -1153,6 +1226,7 @@ class MixVectorNonUniformNode : public ShaderNode {
public:
SHADER_NODE_CLASS(MixVectorNonUniformNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override;
NODE_SOCKET_API(float3, a)
NODE_SOCKET_API(float3, b)
@@ -1164,6 +1238,10 @@ class CombineColorNode : public ShaderNode {
public:
SHADER_NODE_CLASS(CombineColorNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override
{
return true;
}
NODE_SOCKET_API(NodeCombSepColorType, color_type)
NODE_SOCKET_API(float, r)
@@ -1175,6 +1253,10 @@ class CombineXYZNode : public ShaderNode {
public:
SHADER_NODE_CLASS(CombineXYZNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override
{
return true;
}
NODE_SOCKET_API(float, x)
NODE_SOCKET_API(float, y)
@@ -1204,6 +1286,10 @@ class SeparateColorNode : public ShaderNode {
public:
SHADER_NODE_CLASS(SeparateColorNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override
{
return true;
}
NODE_SOCKET_API(NodeCombSepColorType, color_type)
NODE_SOCKET_API(float3, color)
@@ -1213,6 +1299,10 @@ class SeparateXYZNode : public ShaderNode {
public:
SHADER_NODE_CLASS(SeparateXYZNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override
{
return true;
}
NODE_SOCKET_API(float3, vector)
};
@@ -1242,6 +1332,8 @@ class AttributeNode : public ShaderNode {
}
NODE_SOCKET_API(ustring, attribute)
bool stochastic_sample = true;
};
class CameraNode : public ShaderNode {
@@ -1308,6 +1400,7 @@ class VectorMapRangeNode : public ShaderNode {
public:
SHADER_NODE_CLASS(VectorMapRangeNode)
void expand(ShaderGraph *graph) override;
bool is_linear_operation() override;
NODE_SOCKET_API(float3, vector)
NODE_SOCKET_API(float3, from_min)
@@ -1323,6 +1416,7 @@ class MapRangeNode : public ShaderNode {
public:
SHADER_NODE_CLASS(MapRangeNode)
void expand(ShaderGraph *graph) override;
bool is_linear_operation() override;
NODE_SOCKET_API(float, value)
NODE_SOCKET_API(float, from_min)
@@ -1349,6 +1443,7 @@ class MathNode : public ShaderNode {
SHADER_NODE_CLASS(MathNode)
void expand(ShaderGraph *graph) override;
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override;
NODE_SOCKET_API(float, value1)
NODE_SOCKET_API(float, value2)
@@ -1360,6 +1455,10 @@ class MathNode : public ShaderNode {
class NormalNode : public ShaderNode {
public:
SHADER_NODE_CLASS(NormalNode)
bool is_linear_operation() override
{
return true;
}
NODE_SOCKET_API(float3, direction)
NODE_SOCKET_API(float3, normal)
@@ -1369,6 +1468,7 @@ class VectorMathNode : public ShaderNode {
public:
SHADER_NODE_CLASS(VectorMathNode)
void constant_fold(const ConstantFolder &folder) override;
bool is_linear_operation() override;
NODE_SOCKET_API(float3, vector1)
NODE_SOCKET_API(float3, vector2)

View File

@@ -116,6 +116,11 @@ class ShaderGraphBuilder {
return (*this).add_connection(from, "Output::Surface");
}
ShaderGraphBuilder &output_volume_closure(const string &from)
{
return (*this).add_connection(from, "Output::Volume");
}
ShaderGraphBuilder &output_color(const string &from)
{
return (*this)
@@ -1518,4 +1523,65 @@ TEST_F(RenderGraph, constant_fold_convert_color_float_color)
log.invalid_info_message("Folding convert_float_to_color::");
}
/*
* Tests:
* - Stochastic sampling with math multiply node.
*/
TEST_F(RenderGraph, stochastic_sample_math_multiply)
{
builder.add_attribute("Attribute")
.add_node(ShaderNodeBuilder<MathNode>(graph, "MathMultiply")
.set_param("math_type", NODE_MATH_MULTIPLY))
.add_node(ShaderNodeBuilder<ScatterVolumeNode>(graph, "ScatterVolume"))
.add_connection("Attribute::Fac", "MathMultiply::Value1")
.add_connection("MathMultiply::Value", "ScatterVolume::Density")
.output_volume_closure("ScatterVolume::Volume");
graph.finalize(scene.get());
log.correct_info_message("Volume attribute node Attribute uses stochastic sampling");
}
/*
* Tests:
* - No stochastic sampling with math power node.
*/
TEST_F(RenderGraph, not_stochastic_sample_math_power)
{
builder.add_attribute("Attribute")
.add_node(
ShaderNodeBuilder<MathNode>(graph, "MathPower").set_param("math_type", NODE_MATH_POWER))
.add_node(ShaderNodeBuilder<ScatterVolumeNode>(graph, "ScatterVolume"))
.add_connection("Attribute::Fac", "MathPower::Value1")
.add_connection("MathPower::Value", "ScatterVolume::Density")
.output_volume_closure("ScatterVolume::Volume");
graph.finalize(scene.get());
log.invalid_info_message("Volume attribute node Attribute uses stochastic sampling");
}
/*
* Tests:
* - Stochastic sampling temperature with map range, principled volume and mix closure.
*/
TEST_F(RenderGraph, stochastic_sample_principled_volume_mix)
{
builder.add_attribute("Attribute")
.add_node(ShaderNodeBuilder<MapRangeNode>(graph, "MapRange"))
.add_node(ShaderNodeBuilder<MixClosureNode>(graph, "MixClosure").set("Fac", 0.5f))
.add_node(ShaderNodeBuilder<PrincipledVolumeNode>(graph, "PrincipledVolume1"))
.add_node(ShaderNodeBuilder<PrincipledVolumeNode>(graph, "PrincipledVolume2"))
.add_connection("Attribute::Color", "MapRange::Value")
.add_connection("MapRange::Result", "PrincipledVolume1::Temperature")
.add_connection("Attribute::Fac", "PrincipledVolume2::Density")
.add_connection("PrincipledVolume1::Volume", "MixClosure::Closure1")
.add_connection("PrincipledVolume2::Volume", "MixClosure::Closure2")
.output_volume_closure("MixClosure::Closure");
graph.finalize(scene.get());
log.correct_info_message("Volume attribute node Attribute uses stochastic sampling");
}
CCL_NAMESPACE_END

Binary file not shown.

Binary file not shown.

Binary file not shown.