WIP: Cycles: Implement BSDF layering #110444
|
@ -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>();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue