WIP: Cycles: Implement BSDF layering #110444

Closed
Lukas Stockner wants to merge 3 commits from LukasStockner/blender:layering into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
19 changed files with 292 additions and 2 deletions
Showing only changes of commit 68ea681777 - Show all commits

View File

@ -511,6 +511,9 @@ static ShaderNode *add_node(Scene *scene,
else if (b_node.is_a(&RNA_ShaderNodeMixShader)) {
node = graph->create_node<MixClosureNode>();
}
else if (b_node.is_a(&RNA_ShaderNodeLayerShader)) {
node = graph->create_node<LayerClosureNode>();
}
else if (b_node.is_a(&RNA_ShaderNodeAttribute)) {
BL::ShaderNodeAttribute b_attr_node(b_node);
AttributeNode *attr = graph->create_node<AttributeNode>();

View File

@ -48,6 +48,7 @@ set(SRC_OSL
node_ies_light.osl
node_image_texture.osl
node_invert.osl
node_layer_closure.osl
node_layer_weight.osl
node_light_falloff.osl
node_light_path.osl

View File

@ -0,0 +1,9 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
shader node_mix_closure(closure color Base = 0,
closure color Top = 0,
output closure color Closure = 0)
{
Closure = layer(Top, Base);
}

View File

@ -59,6 +59,9 @@ ccl_device_noinline void svm_node_closure_bsdf(KernelGlobals kg,
return;
}
const bool need_albedo = (svm->layer_albedo_offset != SVM_STACK_INVALID);
float3 albedo = one_float3();
float3 N = stack_valid(data_node.x) ? safe_normalize(stack_load_float3(svm, data_node.x)) :
sd->N;
@ -453,6 +456,11 @@ ccl_device_noinline void svm_node_closure_bsdf(KernelGlobals kg,
}
}
if (need_albedo) {
albedo = bsdf->energy_scale *
bsdf_albedo(sd, (ccl_private ShaderClosure *)bsdf, true, false);
}
break;
}
case CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID:
@ -552,6 +560,10 @@ ccl_device_noinline void svm_node_closure_bsdf(KernelGlobals kg,
bsdf->roughness = param1;
sd->flag |= bsdf_sheen_setup(kg, sd, bsdf);
if (need_albedo) {
albedo = bsdf->weight;
}
}
break;
}
@ -757,6 +769,12 @@ ccl_device_noinline void svm_node_closure_bsdf(KernelGlobals kg,
default:
break;
}
/* Accumulate albedo for layering. */
if (need_albedo) {
albedo += stack_load_float3(svm, svm->layer_albedo_offset);
stack_store_float3(svm, svm->layer_albedo_offset, albedo);
}
}
template<ShaderType shader_type>
@ -1040,6 +1058,33 @@ ccl_device_noinline void svm_node_mix_closure(ccl_private ShaderData *sd,
stack_store_float(svm, weight2_offset, in_weight * weight);
}
ccl_device_noinline void svm_node_layer_closure_accumulate(ccl_private ShaderData *sd,
ccl_private SVMState *svm,
uint4 node)
{
uint weight_in_offset = node.y, albedo_out_offset = node.z, weight_out_offset = node.w;
float weight = stack_load_float(svm, weight_in_offset);
stack_store_float(svm, weight_out_offset, weight);
stack_store_float3(svm, albedo_out_offset, make_float3(0.0f, 0.0f, 0.0f));
kernel_assert(svm->layer_albedo_offset == SVM_STACK_INVALID);
svm->layer_albedo_offset = albedo_out_offset;
}
ccl_device_noinline void svm_node_layer_closure(ccl_private ShaderData *sd,
ccl_private SVMState *svm,
uint4 node)
{
uint weight_in_offset = node.y, albedo_in_offset = node.z, weight_out_offset = node.w;
float weight = stack_load_float(svm, weight_in_offset);
float3 albedo = stack_load_float3(svm, albedo_in_offset);
weight *= saturatef(1.0f - reduce_max(albedo / weight));
stack_store_float(svm, weight_out_offset, weight);
kernel_assert(svm->layer_albedo_offset == albedo_in_offset);
svm->layer_albedo_offset = SVM_STACK_INVALID;
}
/* (Bump) normal */
ccl_device void svm_node_set_normal(KernelGlobals kg,

View File

@ -18,6 +18,8 @@ SHADER_NODE_TYPE(NODE_CLOSURE_SET_WEIGHT)
SHADER_NODE_TYPE(NODE_CLOSURE_WEIGHT)
SHADER_NODE_TYPE(NODE_EMISSION_WEIGHT)
SHADER_NODE_TYPE(NODE_MIX_CLOSURE)
SHADER_NODE_TYPE(NODE_LAYER_CLOSURE_ACCUMULATE)
SHADER_NODE_TYPE(NODE_LAYER_CLOSURE)
SHADER_NODE_TYPE(NODE_JUMP_IF_ZERO)
SHADER_NODE_TYPE(NODE_JUMP_IF_ONE)
SHADER_NODE_TYPE(NODE_GEOMETRY)
@ -111,5 +113,7 @@ SHADER_NODE_TYPE(NODE_MIX_VECTOR_NON_UNIFORM)
/* Padding for struct alignment. */
SHADER_NODE_TYPE(NODE_PAD1)
SHADER_NODE_TYPE(NODE_PAD2)
SHADER_NODE_TYPE(NODE_PAD3)
#undef SHADER_NODE_TYPE

View File

@ -33,6 +33,7 @@ typedef struct SVMState {
float stack[SVM_STACK_SIZE];
int offset;
float3 closure_weight;
int layer_albedo_offset;
} SVMState;
/* Stack */
@ -230,6 +231,7 @@ ccl_device void svm_eval_nodes(KernelGlobals kg,
{
SVMState svm;
svm.offset = sd->shader & SHADER_MASK;
svm.layer_albedo_offset = SVM_STACK_INVALID;
while (1) {
uint4 node = read_node(kg, &svm);
@ -279,6 +281,12 @@ ccl_device void svm_eval_nodes(KernelGlobals kg,
SVM_CASE(NODE_MIX_CLOSURE)
svm_node_mix_closure(sd, &svm, node);
break;
SVM_CASE(NODE_LAYER_CLOSURE_ACCUMULATE)
svm_node_layer_closure_accumulate(sd, &svm, node);
break;
SVM_CASE(NODE_LAYER_CLOSURE)
svm_node_layer_closure(sd, &svm, node);
break;
SVM_CASE(NODE_JUMP_IF_ZERO)
if (stack_load_float(&svm, node.z) <= 0.0f)
svm.offset += node.y;

View File

@ -192,6 +192,7 @@ static float3 output_estimate_emission(ShaderOutput *output, bool &is_constant)
return estimate1 + estimate2;
}
/* TODO Layer */
else if (node->type == MixClosureNode::get_node_type()) {
/* Mix Closure. */
ShaderInput *fac_in = node->input("Fac");

View File

@ -1043,7 +1043,46 @@ void ShaderGraph::transform_multi_closure(ShaderNode *node, ShaderOutput *weight
* avoid building a closure tree and then flattening it, and instead write it
* directly to an array */
if (node->special_type == SHADER_SPECIAL_TYPE_COMBINE_CLOSURE) {
if (node->type == LayerClosureNode::get_node_type()) {
/* The layer closure works by first executing the top layer subtree, then computing
* the base weight from the albedo of all top layer closures, and then executing
* the base layer subtree with that weight.
* To implement that in SVM, we need the following order of nodes:
* - LayerClosureAccumulateNode, which allocates space on the stack to store the
* accumulated albedo and sets a flag telling closure nodes to write to it
* - Top Layer subtree (using the layer node's input weight)
* - LayerClosureWeightNode, which reads the accumulated albedo and the layer node's
* input weight and outputs the base layer subtree weight
* - Base Layer subtree (using the output from above)
* In order to get this ordering, we can make the SVM compiler recurse into the top
* layer before the base layer, taking care of that. The LayerClosureWeightNode will
* automatically be compiled before the base layer, since it's a dependency.
* To ensure that the LayerClosureAccumulateNode is compiled before the top layer,
* we make it a fake dependency by connecting the top layer weight input to an output
* of the Accumulate node. In reality, this just passes through the input Weight, but
* it's enough to make the ordering work. */
ShaderInput *base_in = node->input("Base");
ShaderInput *top_in = node->input("Top");
LayerClosureAccumulateNode *accumulate_node = create_node<LayerClosureAccumulateNode>();
add(accumulate_node);
LayerClosureWeightNode *weight_node = create_node<LayerClosureWeightNode>();
add(weight_node);
connect(accumulate_node->output("Albedo"), weight_node->input("Albedo"));
if (weight_out) {
connect(weight_out, weight_node->input("Weight"));
connect(weight_out, accumulate_node->input("Weight"));
}
if (base_in->link)
transform_multi_closure(base_in->link->parent, weight_node->output("BaseWeight"), volume);
if (top_in->link)
transform_multi_closure(top_in->link->parent, accumulate_node->output("TopWeight"), volume);
}
else if (node->special_type == SHADER_SPECIAL_TYPE_COMBINE_CLOSURE) {
ShaderInput *fin = node->input("Fac");
ShaderInput *cl1in = node->input("Closure1");
ShaderInput *cl2in = node->input("Closure2");

View File

@ -4728,6 +4728,107 @@ void MixClosureWeightNode::compile(OSLCompiler & /*compiler*/)
assert(0);
}
/* Layer Closure */
NODE_DEFINE(LayerClosureNode)
{
NodeType *type = NodeType::add("layer_closure", create, NodeType::SHADER);
SOCKET_IN_CLOSURE(base, "Base");
SOCKET_IN_CLOSURE(top, "Top");
SOCKET_OUT_CLOSURE(closure, "Closure");
return type;
}
LayerClosureNode::LayerClosureNode() : ShaderNode(get_node_type()) {}
void LayerClosureNode::compile(SVMCompiler & /*compiler*/)
{
/* handled in the SVM compiler */
}
void LayerClosureNode::compile(OSLCompiler &compiler)
{
compiler.add(this, "node_layer_closure");
}
void LayerClosureNode::constant_fold(const ConstantFolder &folder)
{
ShaderInput *base_in = input("Base");
ShaderInput *top_in = input("Top");
/* remove useless layer closures nodes */
if (!base_in->link) {
folder.bypass_or_discard(top_in);
}
else if (!top_in->link) {
folder.bypass_or_discard(base_in);
}
/* We could theoretically also check for top layers that are known to be opaque
* and skip the base in that case, but that's quite ugly to get right. */
}
NODE_DEFINE(LayerClosureAccumulateNode)
{
NodeType *type = NodeType::add("layer_closure_accumulate", create, NodeType::SHADER);
SOCKET_IN_FLOAT(weight, "Weight", 1.0f);
SOCKET_OUT_COLOR(albedo, "Albedo");
SOCKET_OUT_FLOAT(weight_top, "TopWeight");
return type;
}
LayerClosureAccumulateNode::LayerClosureAccumulateNode() : ShaderNode(get_node_type()) {}
void LayerClosureAccumulateNode::compile(SVMCompiler &compiler)
{
ShaderInput *weight_in = input("Weight");
ShaderOutput *albedo_out = output("Albedo");
ShaderOutput *weight_top_out = output("TopWeight");
compiler.add_node(NODE_LAYER_CLOSURE_ACCUMULATE,
compiler.stack_assign(weight_in),
compiler.stack_assign(albedo_out),
compiler.stack_assign(weight_top_out));
}
void LayerClosureAccumulateNode::compile(OSLCompiler & /*compiler*/)
{
assert(0);
}
NODE_DEFINE(LayerClosureWeightNode)
{
NodeType *type = NodeType::add("layer_closure_weight", create, NodeType::SHADER);
SOCKET_IN_FLOAT(weight, "Weight", 1.0f);
SOCKET_IN_COLOR(albedo, "Albedo", one_float3());
SOCKET_OUT_FLOAT(weight_base, "BaseWeight");
return type;
}
LayerClosureWeightNode::LayerClosureWeightNode() : ShaderNode(get_node_type()) {}
void LayerClosureWeightNode::compile(SVMCompiler &compiler)
{
ShaderInput *weight_in = input("Weight");
ShaderInput *albedo_in = input("Albedo");
ShaderOutput *weight_base_out = output("BaseWeight");
compiler.add_node(NODE_LAYER_CLOSURE,
compiler.stack_assign(weight_in),
compiler.stack_assign(albedo_in),
compiler.stack_assign(weight_base_out));
}
void LayerClosureWeightNode::compile(OSLCompiler & /*compiler*/)
{
assert(0);
}
/* Invert */
NODE_DEFINE(InvertNode)

View File

@ -1059,6 +1059,27 @@ class MixClosureWeightNode : public ShaderNode {
NODE_SOCKET_API(float, fac)
};
class LayerClosureNode : public ShaderNode {
public:
SHADER_NODE_CLASS(LayerClosureNode)
void constant_fold(const ConstantFolder &folder);
};
class LayerClosureAccumulateNode : public ShaderNode {
public:
SHADER_NODE_CLASS(LayerClosureAccumulateNode)
NODE_SOCKET_API(float, weight)
};
class LayerClosureWeightNode : public ShaderNode {
public:
SHADER_NODE_CLASS(LayerClosureWeightNode)
NODE_SOCKET_API(float, weight)
NODE_SOCKET_API(float3, albedo)
};
class InvertNode : public ShaderNode {
public:
SHADER_NODE_CLASS(InvertNode)

View File

@ -567,7 +567,16 @@ void SVMCompiler::generate_multi_closure(ShaderNode *root_node,
state->closure_done.insert(node);
if (node->special_type == SHADER_SPECIAL_TYPE_COMBINE_CLOSURE) {
if (node->type == LayerClosureNode::get_node_type()) {
ShaderInput *base_in = node->input("Base");
ShaderInput *top_in = node->input("Top");
if (top_in->link)
generate_multi_closure(root_node, top_in->link->parent, state);
if (base_in->link)
generate_multi_closure(root_node, base_in->link->parent, state);
}
else if (node->special_type == SHADER_SPECIAL_TYPE_COMBINE_CLOSURE) {
/* weighting is already taken care of in ShaderGraph::transform_multi_closure */
ShaderInput *cl1in = node->input("Closure1");
ShaderInput *cl2in = node->input("Closure2");

View File

@ -187,6 +187,7 @@ shader_node_categories = [
ShaderNodeCategory("SH_NEW_SHADER", "Shader", items=[
NodeItem("ShaderNodeMixShader", poll=eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeAddShader", poll=eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeLayerShader", poll=eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfDiffuse", poll=object_eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfPrincipled", poll=object_eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfGlossy", poll=object_eevee_cycles_shader_nodes_poll),

View File

@ -1023,6 +1023,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define SH_NODE_COMBINE_COLOR 711
#define SH_NODE_SEPARATE_COLOR 712
#define SH_NODE_MIX 713
#define SH_NODE_LAYER_SHADER 714
/** \} */

View File

@ -57,6 +57,7 @@ DefNode(ShaderNode, SH_NODE_FRESNEL, 0, "FRE
DefNode(ShaderNode, SH_NODE_LAYER_WEIGHT, 0, "LAYER_WEIGHT", LayerWeight, "Layer Weight", "Produce a blending factor depending on the angle between the surface normal and the view direction.\nTypically used for layering shaders with the Mix Shader node")
DefNode(ShaderNode, SH_NODE_MIX_SHADER, 0, "MIX_SHADER", MixShader, "Mix Shader", "Mix two shaders together. Typically used for material layering")
DefNode(ShaderNode, SH_NODE_ADD_SHADER, 0, "ADD_SHADER", AddShader, "Add Shader", "Add two Shaders together")
DefNode(ShaderNode, SH_NODE_LAYER_SHADER, 0, "LAYER_SHADER", LayerShader, "Layer Shader", "Layer one Shader over another while conserving energy")
DefNode(ShaderNode, SH_NODE_ATTRIBUTE, def_sh_attribute, "ATTRIBUTE", Attribute, "Attribute", "Retrieve attributes attached to objects or geometry")
DefNode(ShaderNode, SH_NODE_AMBIENT_OCCLUSION, def_sh_ambient_occlusion,"AMBIENT_OCCLUSION", AmbientOcclusion, "Ambient Occlusion", "Compute how much the hemisphere above the shading point is occluded, for example to add weathering effects to corners.\nNote: For Cycles, this may slow down renders significantly")
DefNode(ShaderNode, SH_NODE_BACKGROUND, 0, "BACKGROUND", Background, "Background", "Add background light emission.\nNote: This node should only be used for the world surface output")

View File

@ -54,6 +54,7 @@ set(SRC
nodes/node_shader_hueSatVal.cc
nodes/node_shader_ies_light.cc
nodes/node_shader_invert.cc
nodes/node_shader_layer_shader.cc
nodes/node_shader_layer_weight.cc
nodes/node_shader_light_falloff.cc
nodes/node_shader_light_path.cc

View File

@ -50,6 +50,7 @@ void register_shader_nodes()
register_node_type_sh_holdout();
register_node_type_sh_hue_sat();
register_node_type_sh_invert();
register_node_type_sh_layer_shader();
register_node_type_sh_layer_weight();
register_node_type_sh_light_falloff();
register_node_type_sh_light_path();

View File

@ -48,6 +48,7 @@ void register_node_type_sh_hair_info();
void register_node_type_sh_holdout();
void register_node_type_sh_hue_sat();
void register_node_type_sh_invert();
void register_node_type_sh_layer_shader();
void register_node_type_sh_layer_weight();
void register_node_type_sh_light_falloff();
void register_node_type_sh_light_path();

View File

@ -674,6 +674,8 @@ static void ntree_weight_tree_merge_weight(bNodeTree *ntree,
*tosock = addsock_out;
}
/* TODO */
static bool ntree_weight_tree_tag_nodes(bNode *fromnode, bNode *tonode, void *userdata)
{
int *node_count = (int *)userdata;
@ -939,6 +941,7 @@ static bool closure_node_filter(const bNode *node)
switch (node->type) {
case SH_NODE_ADD_SHADER:
case SH_NODE_MIX_SHADER:
case SH_NODE_LAYER_SHADER:
case SH_NODE_BACKGROUND:
case SH_NODE_BSDF_DIFFUSE:
case SH_NODE_BSDF_GLASS:

View File

@ -0,0 +1,40 @@
/* SPDX-FileCopyrightText: 2005 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "node_shader_util.hh"
namespace blender::nodes::node_shader_layer_shader_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Shader>("Base");
b.add_input<decl::Shader>("Top");
b.add_output<decl::Shader>("Shader");
}
static int node_shader_gpu_layer_shader(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
/* TODO */
return GPU_stack_link(mat, node, "node_add_shader", in, out);
}
} // namespace blender::nodes::node_shader_layer_shader_cc
/* node type definition */
void register_node_type_sh_layer_shader()
{
namespace file_ns = blender::nodes::node_shader_layer_shader_cc;
static bNodeType ntype;
sh_node_type_base(&ntype, SH_NODE_LAYER_SHADER, "Layer Shader", NODE_CLASS_SHADER);
ntype.declare = file_ns::node_declare;
ntype.gpu_fn = file_ns::node_shader_gpu_layer_shader;
nodeRegisterType(&ntype);
}