2023-06-14 16:52:36 +10:00
|
|
|
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
2011-04-27 11:58:34 +00:00
|
|
|
|
2021-10-24 14:19:19 +02:00
|
|
|
#include "scene/shader_graph.h"
|
|
|
|
|
#include "scene/attribute.h"
|
|
|
|
|
#include "scene/constant_fold.h"
|
|
|
|
|
#include "scene/scene.h"
|
|
|
|
|
#include "scene/shader.h"
|
|
|
|
|
#include "scene/shader_nodes.h"
|
2011-04-27 11:58:34 +00:00
|
|
|
|
2021-10-24 14:19:19 +02:00
|
|
|
#include "util/algorithm.h"
|
2024-12-26 19:41:25 +01:00
|
|
|
|
2021-10-24 14:19:19 +02:00
|
|
|
#include "util/log.h"
|
|
|
|
|
#include "util/md5.h"
|
|
|
|
|
#include "util/queue.h"
|
2011-04-27 11:58:34 +00:00
|
|
|
|
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
|
|
2015-12-02 16:19:39 +05:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
bool check_node_inputs_has_links(const ShaderNode *node)
|
|
|
|
|
{
|
2024-12-26 19:41:25 +01:00
|
|
|
for (const ShaderInput *in : node->inputs) {
|
2015-12-02 16:19:39 +05:00
|
|
|
if (in->link) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool check_node_inputs_traversed(const ShaderNode *node, const ShaderNodeSet &done)
|
|
|
|
|
{
|
2024-12-26 19:41:25 +01:00
|
|
|
for (const ShaderInput *in : node->inputs) {
|
2015-12-02 16:19:39 +05:00
|
|
|
if (in->link) {
|
|
|
|
|
if (done.find(in->link->parent) == done.end()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} /* namespace */
|
|
|
|
|
|
2019-12-04 19:57:28 +01:00
|
|
|
/* Sockets */
|
|
|
|
|
|
|
|
|
|
void ShaderInput::disconnect()
|
|
|
|
|
{
|
|
|
|
|
if (link) {
|
|
|
|
|
link->links.erase(remove(link->links.begin(), link->links.end(), this), link->links.end());
|
|
|
|
|
}
|
2024-12-26 17:53:55 +01:00
|
|
|
link = nullptr;
|
2019-12-04 19:57:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ShaderOutput::disconnect()
|
|
|
|
|
{
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *sock : links) {
|
2024-12-26 17:53:55 +01:00
|
|
|
sock->link = nullptr;
|
2019-12-04 19:57:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
links.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* Node */
|
|
|
|
|
|
2016-05-07 19:48:28 +02:00
|
|
|
ShaderNode::ShaderNode(const NodeType *type) : Node(type)
|
2011-04-27 11:58:34 +00:00
|
|
|
{
|
2016-05-07 19:48:28 +02:00
|
|
|
create_inputs_outputs(type);
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-30 02:20:36 +01:00
|
|
|
ShaderNode::ShaderNode(const ShaderNode &other)
|
|
|
|
|
: Node(other.type), bump(other.bump), special_type(other.special_type)
|
2011-04-27 11:58:34 +00:00
|
|
|
{
|
2024-12-30 02:20:36 +01:00
|
|
|
/* Inputs and outputs are recreated, no links to other nodes will remain. */
|
|
|
|
|
name = other.name;
|
|
|
|
|
create_inputs_outputs(type);
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
2016-05-07 19:48:28 +02:00
|
|
|
void ShaderNode::create_inputs_outputs(const NodeType *type)
|
|
|
|
|
{
|
2024-12-26 19:41:25 +01:00
|
|
|
for (const SocketType &socket : type->inputs) {
|
2016-05-07 19:48:28 +02:00
|
|
|
if (socket.flags & SocketType::LINKABLE) {
|
2024-12-30 02:20:36 +01:00
|
|
|
inputs.push_back(make_unique<ShaderInput>(socket, this));
|
2016-05-07 19:48:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-26 19:41:25 +01:00
|
|
|
for (const SocketType &socket : type->outputs) {
|
2024-12-30 02:20:36 +01:00
|
|
|
outputs.push_back(make_unique<ShaderOutput>(socket, this));
|
2016-05-07 19:48:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
ShaderInput *ShaderNode::input(const char *name)
|
|
|
|
|
{
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *socket : inputs) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (socket->name() == name) {
|
2011-04-27 11:58:34 +00:00
|
|
|
return socket;
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2015-12-15 20:04:00 +05:00
|
|
|
}
|
2015-12-15 21:01:56 +05:00
|
|
|
|
2024-12-26 17:53:55 +01:00
|
|
|
return nullptr;
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ShaderOutput *ShaderNode::output(const char *name)
|
|
|
|
|
{
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderOutput *socket : outputs) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (socket->name() == name) {
|
2011-04-27 11:58:34 +00:00
|
|
|
return socket;
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2024-12-26 19:41:25 +01:00
|
|
|
}
|
2015-12-15 21:01:56 +05:00
|
|
|
|
2024-12-26 17:53:55 +01:00
|
|
|
return nullptr;
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
2016-05-08 01:32:09 +02:00
|
|
|
ShaderInput *ShaderNode::input(ustring name)
|
2011-04-27 11:58:34 +00:00
|
|
|
{
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *socket : inputs) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (socket->name() == name) {
|
2016-05-08 01:32:09 +02:00
|
|
|
return socket;
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2016-05-08 01:32:09 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-26 17:53:55 +01:00
|
|
|
return nullptr;
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
2016-05-08 01:32:09 +02:00
|
|
|
ShaderOutput *ShaderNode::output(ustring name)
|
|
|
|
|
{
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderOutput *socket : outputs) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (socket->name() == name) {
|
2016-05-08 01:32:09 +02:00
|
|
|
return socket;
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2024-12-26 19:41:25 +01:00
|
|
|
}
|
2016-05-08 01:32:09 +02:00
|
|
|
|
2024-12-26 17:53:55 +01:00
|
|
|
return nullptr;
|
2016-05-08 01:32:09 +02:00
|
|
|
}
|
|
|
|
|
|
2019-05-12 14:39:30 +02:00
|
|
|
void ShaderNode::remove_input(ShaderInput *input)
|
|
|
|
|
{
|
2024-12-26 17:53:55 +01:00
|
|
|
assert(input->link == nullptr);
|
2024-12-30 02:20:36 +01:00
|
|
|
inputs.erase(input);
|
2019-05-12 14:39:30 +02:00
|
|
|
}
|
|
|
|
|
|
2013-12-31 17:30:34 +01:00
|
|
|
void ShaderNode::attributes(Shader *shader, AttributeRequestSet *attributes)
|
2011-04-27 11:58:34 +00:00
|
|
|
{
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *input : inputs) {
|
2011-10-12 16:01:37 +00:00
|
|
|
if (!input->link) {
|
2016-05-08 01:32:09 +02:00
|
|
|
if (input->flags() & SocketType::LINK_TEXTURE_GENERATED) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (shader->has_surface_link()) {
|
2013-12-31 17:33:55 +01:00
|
|
|
attributes->add(ATTR_STD_GENERATED);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
|
|
|
|
if (shader->has_volume) {
|
2013-12-31 17:33:55 +01:00
|
|
|
attributes->add(ATTR_STD_GENERATED_TRANSFORM);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2013-12-31 17:33:55 +01:00
|
|
|
}
|
2016-05-08 01:32:09 +02:00
|
|
|
else if (input->flags() & SocketType::LINK_TEXTURE_UV) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (shader->has_surface_link()) {
|
2013-12-31 17:33:55 +01:00
|
|
|
attributes->add(ATTR_STD_UV);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2013-12-31 17:33:55 +01:00
|
|
|
}
|
2011-10-12 16:01:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
2016-05-07 19:48:28 +02:00
|
|
|
bool ShaderNode::equals(const ShaderNode &other)
|
|
|
|
|
{
|
2016-10-24 12:26:12 +02:00
|
|
|
if (type != other.type || bump != other.bump) {
|
2016-05-07 19:48:28 +02:00
|
|
|
return false;
|
2016-10-24 12:26:12 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2016-05-07 19:48:28 +02:00
|
|
|
assert(inputs.size() == other.inputs.size());
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2016-05-07 19:48:28 +02:00
|
|
|
/* Compare unlinkable sockets */
|
2024-12-26 19:41:25 +01:00
|
|
|
for (const SocketType &socket : type->inputs) {
|
2016-05-07 19:48:28 +02:00
|
|
|
if (!(socket.flags & SocketType::LINKABLE)) {
|
|
|
|
|
if (!Node::equals_value(other, socket)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2016-05-07 19:48:28 +02:00
|
|
|
/* Compare linkable input sockets */
|
|
|
|
|
for (int i = 0; i < inputs.size(); ++i) {
|
2024-12-29 17:32:00 +01:00
|
|
|
ShaderInput *input_a = inputs[i];
|
|
|
|
|
ShaderInput *input_b = other.inputs[i];
|
2024-12-26 17:53:55 +01:00
|
|
|
if (input_a->link == nullptr && input_b->link == nullptr) {
|
2016-05-07 19:48:28 +02:00
|
|
|
/* Unconnected inputs are expected to have the same value. */
|
|
|
|
|
if (!Node::equals_value(other, input_a->socket_type)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-12-26 17:53:55 +01:00
|
|
|
else if (input_a->link != nullptr && input_b->link != nullptr) {
|
2016-05-07 19:48:28 +02:00
|
|
|
/* Expect links are to come from the same exact socket. */
|
|
|
|
|
if (input_a->link != input_b->link) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* One socket has a link and another has not, inputs can't be
|
|
|
|
|
* considered equal.
|
|
|
|
|
*/
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2016-05-07 19:48:28 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* Graph */
|
|
|
|
|
|
|
|
|
|
ShaderGraph::ShaderGraph()
|
|
|
|
|
{
|
|
|
|
|
finalized = false;
|
2016-12-14 23:47:39 +01:00
|
|
|
simplified = false;
|
2013-05-10 15:10:54 +00:00
|
|
|
num_node_ids = 0;
|
2024-12-30 02:20:36 +01:00
|
|
|
create_node<OutputNode>();
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ShaderGraph::~ShaderGraph()
|
|
|
|
|
{
|
2016-01-05 21:18:02 +05:00
|
|
|
clear_nodes();
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-30 02:20:36 +01:00
|
|
|
void ShaderGraph::add_node(unique_ptr<ShaderNode> &&node)
|
2011-04-27 11:58:34 +00:00
|
|
|
{
|
|
|
|
|
assert(!finalized);
|
2016-12-14 23:47:39 +01:00
|
|
|
simplified = false;
|
|
|
|
|
|
2024-12-30 02:20:36 +01:00
|
|
|
node->set_owner(this);
|
2013-05-10 15:10:54 +00:00
|
|
|
node->id = num_node_ids++;
|
2024-12-30 02:20:36 +01:00
|
|
|
nodes.push_back(std::move(node));
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
2013-12-31 17:30:34 +01:00
|
|
|
OutputNode *ShaderGraph::output()
|
2011-04-27 11:58:34 +00:00
|
|
|
{
|
2024-12-30 02:20:36 +01:00
|
|
|
return static_cast<OutputNode *>(nodes[0]);
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ShaderGraph::connect(ShaderOutput *from, ShaderInput *to)
|
|
|
|
|
{
|
|
|
|
|
assert(!finalized);
|
|
|
|
|
assert(from && to);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
if (to->link) {
|
2012-10-04 20:12:16 +00:00
|
|
|
fprintf(stderr, "Cycles shader graph connect: input already connected.\n");
|
2011-04-27 11:58:34 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2016-05-08 01:32:09 +02:00
|
|
|
if (from->type() != to->type()) {
|
2018-06-13 16:29:06 +02:00
|
|
|
/* can't do automatic conversion from closure */
|
|
|
|
|
if (from->type() == SocketType::CLOSURE) {
|
2012-10-04 20:12:16 +00:00
|
|
|
fprintf(stderr,
|
|
|
|
|
"Cycles shader graph connect: can only connect closure to closure "
|
2013-06-05 15:54:39 +00:00
|
|
|
"(%s.%s to %s.%s).\n",
|
2016-05-08 01:32:09 +02:00
|
|
|
from->parent->name.c_str(),
|
|
|
|
|
from->name().c_str(),
|
|
|
|
|
to->parent->name.c_str(),
|
|
|
|
|
to->name().c_str());
|
2011-04-27 11:58:34 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* add automatic conversion node in case of type mismatch */
|
2018-06-13 16:29:06 +02:00
|
|
|
ShaderNode *convert;
|
2018-06-14 22:16:42 +02:00
|
|
|
ShaderInput *convert_in;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-08-24 14:36:18 +02:00
|
|
|
if (to->type() == SocketType::CLOSURE) {
|
Cycles: introduce an ownership system to protect nodes from unwanted deletions.
Problem: the Blender synchronization process creates and tags nodes for usage. It does
this by directly adding and removing nodes from the scene data. If some node is not tagged
as used at the end of a synchronization, it then deletes the node from the scene. This poses
a problem when it comes to supporting procedural nodes who can create other nodes not known
by the Blender synchonization system, which will remove them.
Nodes now have a NodeOwner, which is set after creation. Those owners for now are the Scene
for scene level nodes and ShaderGraph for shader nodes. Instead of creating and deleting
nodes using `new` and `delete` explicitely, we now use `create_node` and `delete_node` methods
found on the owners. `delete_node` will assert that the owner is the right one.
Whenever a scene level node is created or deleted, the appropriate node manager is tagged for
an update, freeing this responsability from BlenderSync or other software exporters.
Concerning BlenderSync, the `id_maps` do not explicitely manipulate scene data anymore, they
only keep track of which nodes are used, employing the scene to create and delete them. To
achieve this, the ParticleSystem is now a Node, although it does not have any sockets.
This is part of T79131.
Reviewed By: #cycles, brecht
Maniphest Tasks: T79131
Differential Revision: https://developer.blender.org/D8540
2020-08-30 23:20:51 +02:00
|
|
|
EmissionNode *emission = create_node<EmissionNode>();
|
2023-03-16 14:44:50 +01:00
|
|
|
emission->from_auto_conversion = true;
|
2021-02-17 01:47:18 +01:00
|
|
|
emission->set_color(one_float3());
|
2020-11-04 11:17:38 +01:00
|
|
|
emission->set_strength(1.0f);
|
2024-12-30 02:20:36 +01:00
|
|
|
convert = emission;
|
2021-02-05 16:23:34 +11:00
|
|
|
/* Connect float inputs to Strength to save an additional Value->Color conversion. */
|
2018-06-14 22:16:42 +02:00
|
|
|
if (from->type() == SocketType::FLOAT) {
|
|
|
|
|
convert_in = convert->input("Strength");
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
convert_in = convert->input("Color");
|
2018-06-13 16:29:06 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2018-06-13 16:29:06 +02:00
|
|
|
else {
|
2024-12-30 02:20:36 +01:00
|
|
|
convert = create_node<ConvertNode>(from->type(), to->type(), true);
|
2018-06-14 22:16:42 +02:00
|
|
|
convert_in = convert->inputs[0];
|
2018-06-13 16:29:06 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-14 22:16:42 +02:00
|
|
|
connect(from, convert_in);
|
2011-04-27 11:58:34 +00:00
|
|
|
connect(convert->outputs[0], to);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* types match, just connect */
|
|
|
|
|
to->link = from;
|
|
|
|
|
from->links.push_back(to);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-16 13:16:54 +02:00
|
|
|
void ShaderGraph::disconnect(ShaderOutput *from)
|
|
|
|
|
{
|
|
|
|
|
assert(!finalized);
|
2016-12-14 23:47:39 +01:00
|
|
|
simplified = false;
|
2016-07-16 13:16:54 +02:00
|
|
|
|
2019-12-04 19:57:28 +01:00
|
|
|
from->disconnect();
|
2016-07-16 13:16:54 +02:00
|
|
|
}
|
|
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
void ShaderGraph::disconnect(ShaderInput *to)
|
|
|
|
|
{
|
|
|
|
|
assert(!finalized);
|
|
|
|
|
assert(to->link);
|
2016-12-14 23:47:39 +01:00
|
|
|
simplified = false;
|
2011-04-27 11:58:34 +00:00
|
|
|
|
2019-12-04 19:57:28 +01:00
|
|
|
to->disconnect();
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
2019-05-12 14:39:30 +02:00
|
|
|
void ShaderGraph::relink(ShaderInput *from, ShaderInput *to)
|
|
|
|
|
{
|
|
|
|
|
ShaderOutput *out = from->link;
|
|
|
|
|
if (out) {
|
|
|
|
|
disconnect(from);
|
|
|
|
|
connect(out, to);
|
|
|
|
|
}
|
|
|
|
|
to->parent->copy_value(to->socket_type, *(from->parent), from->socket_type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ShaderGraph::relink(ShaderOutput *from, ShaderOutput *to)
|
|
|
|
|
{
|
|
|
|
|
/* Copy because disconnect modifies this list. */
|
2024-12-29 17:32:00 +01:00
|
|
|
const vector<ShaderInput *> outputs = from->links;
|
2019-05-12 14:39:30 +02:00
|
|
|
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *sock : outputs) {
|
2019-05-12 14:39:30 +02:00
|
|
|
disconnect(sock);
|
2023-09-17 09:01:48 +10:00
|
|
|
if (to) {
|
2019-05-12 14:39:30 +02:00
|
|
|
connect(to, sock);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-05-12 14:39:30 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-02 00:05:16 +02:00
|
|
|
void ShaderGraph::relink(ShaderNode *node, ShaderOutput *from, ShaderOutput *to)
|
2015-01-22 09:58:11 +01:00
|
|
|
{
|
2016-12-14 23:47:39 +01:00
|
|
|
simplified = false;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2016-05-02 00:05:16 +02:00
|
|
|
/* Copy because disconnect modifies this list */
|
2024-12-29 17:32:00 +01:00
|
|
|
const vector<ShaderInput *> outputs = from->links;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2016-05-02 00:05:16 +02:00
|
|
|
/* Bypass node by moving all links from "from" to "to" */
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *sock : node->inputs) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (sock->link) {
|
2015-01-22 09:58:11 +01:00
|
|
|
disconnect(sock);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2015-01-22 09:58:11 +01:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *sock : outputs) {
|
2015-01-22 09:58:11 +01:00
|
|
|
disconnect(sock);
|
2023-09-17 09:01:48 +10:00
|
|
|
if (to) {
|
2016-05-02 00:05:16 +02:00
|
|
|
connect(to, sock);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2015-01-22 09:58:11 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-14 23:47:39 +01:00
|
|
|
void ShaderGraph::simplify(Scene *scene)
|
|
|
|
|
{
|
|
|
|
|
if (!simplified) {
|
2019-05-12 14:39:30 +02:00
|
|
|
expand();
|
2016-12-14 23:47:39 +01:00
|
|
|
default_inputs(scene->shader_manager->use_osl());
|
|
|
|
|
clean(scene);
|
|
|
|
|
refine_bump_nodes();
|
|
|
|
|
|
|
|
|
|
simplified = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 12:36:31 +02:00
|
|
|
void ShaderGraph::finalize(Scene *scene, bool do_bump, bool bump_in_object_space)
|
2011-04-27 11:58:34 +00:00
|
|
|
{
|
|
|
|
|
/* before compiling, the shader graph may undergo a number of modifications.
|
|
|
|
|
* currently we set default geometry shader inputs, and create automatic bump
|
|
|
|
|
* from displacement. a graph can be finalized only once, and should not be
|
|
|
|
|
* modified afterwards. */
|
|
|
|
|
|
|
|
|
|
if (!finalized) {
|
2016-12-14 23:47:39 +01:00
|
|
|
simplify(scene);
|
2012-10-10 15:56:43 +00:00
|
|
|
|
2023-09-17 09:01:48 +10:00
|
|
|
if (do_bump) {
|
2016-09-02 00:41:04 -04:00
|
|
|
bump_from_displacement(bump_in_object_space);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2011-04-27 11:58:34 +00:00
|
|
|
|
2014-04-21 15:53:20 +02:00
|
|
|
ShaderInput *surface_in = output()->input("Surface");
|
|
|
|
|
ShaderInput *volume_in = output()->input("Volume");
|
2012-11-26 21:59:41 +00:00
|
|
|
|
2014-04-21 15:53:20 +02:00
|
|
|
/* todo: make this work when surface and volume closures are tangled up */
|
2012-11-26 21:59:41 +00:00
|
|
|
|
2023-09-17 09:01:48 +10:00
|
|
|
if (surface_in->link) {
|
2024-12-26 17:53:55 +01:00
|
|
|
transform_multi_closure(surface_in->link->parent, nullptr, false);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
|
|
|
|
if (volume_in->link) {
|
2024-12-26 17:53:55 +01:00
|
|
|
transform_multi_closure(volume_in->link->parent, nullptr, true);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2012-11-26 21:59:41 +00:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
finalized = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-25 13:07:29 +05:00
|
|
|
void ShaderGraph::find_dependencies(ShaderNodeSet &dependencies, ShaderInput *input)
|
2011-04-27 11:58:34 +00:00
|
|
|
{
|
2013-08-13 08:43:31 +00:00
|
|
|
/* find all nodes that this input depends on directly and indirectly */
|
2024-12-26 17:53:55 +01:00
|
|
|
ShaderNode *node = (input->link) ? input->link->parent : nullptr;
|
2011-04-27 11:58:34 +00:00
|
|
|
|
2024-12-26 17:53:55 +01:00
|
|
|
if (node != nullptr && dependencies.find(node) == dependencies.end()) {
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *in : node->inputs) {
|
2011-04-27 11:58:34 +00:00
|
|
|
find_dependencies(dependencies, in);
|
2024-12-26 19:41:25 +01:00
|
|
|
}
|
2011-04-27 11:58:34 +00:00
|
|
|
|
|
|
|
|
dependencies.insert(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-05 21:18:02 +05:00
|
|
|
void ShaderGraph::clear_nodes()
|
|
|
|
|
{
|
|
|
|
|
nodes.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-25 13:07:29 +05:00
|
|
|
void ShaderGraph::copy_nodes(ShaderNodeSet &nodes, ShaderNodeMap &nnodemap)
|
2011-04-27 11:58:34 +00:00
|
|
|
{
|
|
|
|
|
/* copy a set of nodes, and the links between them. the assumption is
|
|
|
|
|
* made that all nodes that inputs are linked to are in the set too. */
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* copy nodes */
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes) {
|
Cycles: introduce an ownership system to protect nodes from unwanted deletions.
Problem: the Blender synchronization process creates and tags nodes for usage. It does
this by directly adding and removing nodes from the scene data. If some node is not tagged
as used at the end of a synchronization, it then deletes the node from the scene. This poses
a problem when it comes to supporting procedural nodes who can create other nodes not known
by the Blender synchonization system, which will remove them.
Nodes now have a NodeOwner, which is set after creation. Those owners for now are the Scene
for scene level nodes and ShaderGraph for shader nodes. Instead of creating and deleting
nodes using `new` and `delete` explicitely, we now use `create_node` and `delete_node` methods
found on the owners. `delete_node` will assert that the owner is the right one.
Whenever a scene level node is created or deleted, the appropriate node manager is tagged for
an update, freeing this responsability from BlenderSync or other software exporters.
Concerning BlenderSync, the `id_maps` do not explicitely manipulate scene data anymore, they
only keep track of which nodes are used, employing the scene to create and delete them. To
achieve this, the ParticleSystem is now a Node, although it does not have any sockets.
This is part of T79131.
Reviewed By: #cycles, brecht
Maniphest Tasks: T79131
Differential Revision: https://developer.blender.org/D8540
2020-08-30 23:20:51 +02:00
|
|
|
ShaderNode *nnode = node->clone(this);
|
2011-04-27 11:58:34 +00:00
|
|
|
nnodemap[node] = nnode;
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* recreate links */
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes) {
|
|
|
|
|
for (ShaderInput *input : node->inputs) {
|
2011-04-27 11:58:34 +00:00
|
|
|
if (input->link) {
|
|
|
|
|
/* find new input and output */
|
|
|
|
|
ShaderNode *nfrom = nnodemap[input->link->parent];
|
|
|
|
|
ShaderNode *nto = nnodemap[input->parent];
|
2016-05-08 01:32:09 +02:00
|
|
|
ShaderOutput *noutput = nfrom->output(input->link->name());
|
|
|
|
|
ShaderInput *ninput = nto->input(input->name());
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* connect */
|
|
|
|
|
connect(noutput, ninput);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-12-15 21:56:27 +05:00
|
|
|
|
2015-11-18 18:47:56 +01:00
|
|
|
/* Graph simplification */
|
|
|
|
|
/* ******************** */
|
2011-04-27 11:58:34 +00:00
|
|
|
|
2017-03-21 17:18:25 +01:00
|
|
|
/* Remove proxy nodes.
|
|
|
|
|
*
|
2016-05-02 00:05:16 +02:00
|
|
|
* These only exists temporarily when exporting groups, and we must remove them
|
|
|
|
|
* early so that node->attributes() and default links do not see them.
|
2015-11-18 18:47:56 +01:00
|
|
|
*/
|
2016-05-02 00:05:16 +02:00
|
|
|
void ShaderGraph::remove_proxy_nodes()
|
2011-12-18 15:34:06 +00:00
|
|
|
{
|
2013-05-10 15:10:54 +00:00
|
|
|
vector<bool> removed(num_node_ids, false);
|
2013-05-10 11:31:57 +00:00
|
|
|
bool any_node_removed = false;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes) {
|
2013-05-10 11:31:57 +00:00
|
|
|
if (node->special_type == SHADER_SPECIAL_TYPE_PROXY) {
|
2016-05-02 00:05:16 +02:00
|
|
|
ConvertNode *proxy = static_cast<ConvertNode *>(node);
|
2011-12-18 15:34:06 +00:00
|
|
|
ShaderInput *input = proxy->inputs[0];
|
|
|
|
|
ShaderOutput *output = proxy->outputs[0];
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-12-18 15:34:06 +00:00
|
|
|
/* bypass the proxy node */
|
2016-05-02 00:05:16 +02:00
|
|
|
if (input->link) {
|
|
|
|
|
relink(proxy, output, input->link);
|
2011-12-18 15:34:06 +00:00
|
|
|
}
|
|
|
|
|
else {
|
2016-05-02 00:05:16 +02:00
|
|
|
/* Copy because disconnect modifies this list */
|
2024-12-29 17:32:00 +01:00
|
|
|
const vector<ShaderInput *> links(output->links);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *to : links) {
|
2020-08-05 11:49:31 +10:00
|
|
|
/* Remove any auto-convert nodes too if they lead to
|
|
|
|
|
* sockets with an automatically set default value. */
|
2013-06-23 19:24:32 +00:00
|
|
|
ShaderNode *tonode = to->parent;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2013-06-23 19:24:32 +00:00
|
|
|
if (tonode->special_type == SHADER_SPECIAL_TYPE_AUTOCONVERT) {
|
|
|
|
|
bool all_links_removed = true;
|
2024-12-29 17:32:00 +01:00
|
|
|
const vector<ShaderInput *> links = tonode->outputs[0]->links;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *autoin : links) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (autoin->flags() & SocketType::DEFAULT_LINK_MASK) {
|
2013-06-23 19:24:32 +00:00
|
|
|
disconnect(autoin);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
|
|
|
|
else {
|
2016-05-08 01:32:09 +02:00
|
|
|
all_links_removed = false;
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2013-06-23 19:24:32 +00:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-09-17 09:01:48 +10:00
|
|
|
if (all_links_removed) {
|
2013-06-23 19:24:32 +00:00
|
|
|
removed[tonode->id] = true;
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2013-06-23 19:24:32 +00:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-12-18 15:34:06 +00:00
|
|
|
disconnect(to);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-12-18 15:34:06 +00:00
|
|
|
/* transfer the default input value to the target socket */
|
2016-05-07 19:48:28 +02:00
|
|
|
tonode->copy_value(to->socket_type, *proxy, input->socket_type);
|
2011-12-18 15:34:06 +00:00
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-12-18 15:34:06 +00:00
|
|
|
removed[proxy->id] = true;
|
2013-05-10 11:31:57 +00:00
|
|
|
any_node_removed = true;
|
2011-12-18 15:34:06 +00:00
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2013-05-10 11:31:57 +00:00
|
|
|
/* remove nodes */
|
2014-09-19 13:21:25 +02:00
|
|
|
if (any_node_removed) {
|
2024-12-30 02:20:36 +01:00
|
|
|
unique_ptr_vector<ShaderNode> newnodes;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-30 02:20:36 +01:00
|
|
|
for (size_t i = 0; i < nodes.size(); i++) {
|
|
|
|
|
unique_ptr<ShaderNode> node = nodes.steal(i);
|
2023-09-17 09:01:48 +10:00
|
|
|
if (!removed[node->id]) {
|
2024-12-30 02:20:36 +01:00
|
|
|
newnodes.push_back(std::move(node));
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2013-05-10 11:31:57 +00:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-30 02:20:36 +01:00
|
|
|
nodes = std::move(newnodes);
|
2013-05-10 11:31:57 +00:00
|
|
|
}
|
2011-12-18 15:34:06 +00:00
|
|
|
}
|
|
|
|
|
|
2017-03-21 17:18:25 +01:00
|
|
|
/* Constant folding.
|
|
|
|
|
*
|
2015-11-25 13:52:39 +01:00
|
|
|
* Try to constant fold some nodes, and pipe result directly to
|
|
|
|
|
* the input socket of connected nodes.
|
|
|
|
|
*/
|
2018-06-14 17:48:19 +02:00
|
|
|
void ShaderGraph::constant_fold(Scene *scene)
|
2015-11-25 13:52:39 +01:00
|
|
|
{
|
2024-12-29 17:32:00 +01:00
|
|
|
ShaderNodeSet done;
|
|
|
|
|
ShaderNodeSet scheduled;
|
2015-12-02 16:19:39 +05:00
|
|
|
queue<ShaderNode *> traverse_queue;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-29 17:32:00 +01:00
|
|
|
const bool has_displacement = (output()->input("Displacement")->link != nullptr);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2015-12-02 16:19:39 +05:00
|
|
|
/* Schedule nodes which doesn't have any dependencies. */
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes) {
|
2015-12-02 16:19:39 +05:00
|
|
|
if (!check_node_inputs_has_links(node)) {
|
|
|
|
|
traverse_queue.push(node);
|
|
|
|
|
scheduled.insert(node);
|
2015-11-25 13:52:39 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2015-12-02 16:19:39 +05:00
|
|
|
while (!traverse_queue.empty()) {
|
|
|
|
|
ShaderNode *node = traverse_queue.front();
|
|
|
|
|
traverse_queue.pop();
|
|
|
|
|
done.insert(node);
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderOutput *output : node->outputs) {
|
2024-12-26 17:53:59 +01:00
|
|
|
if (output->links.empty()) {
|
2016-05-02 00:05:16 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2015-12-02 16:19:39 +05:00
|
|
|
/* Schedule node which was depending on the value,
|
|
|
|
|
* when possible. Do it before disconnect.
|
|
|
|
|
*/
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *input : output->links) {
|
2015-12-02 16:19:39 +05:00
|
|
|
if (scheduled.find(input->parent) != scheduled.end()) {
|
2015-12-06 23:47:38 +01:00
|
|
|
/* Node might not be optimized yet but scheduled already
|
2015-12-02 16:19:39 +05:00
|
|
|
* by other dependencies. No need to re-schedule it.
|
|
|
|
|
*/
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* Schedule node if its inputs are fully done. */
|
|
|
|
|
if (check_node_inputs_traversed(input->parent, done)) {
|
|
|
|
|
traverse_queue.push(input->parent);
|
|
|
|
|
scheduled.insert(input->parent);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* Optimize current node. */
|
2024-12-29 17:32:00 +01:00
|
|
|
const ConstantFolder folder(this, node, output, scene);
|
2016-07-16 13:16:54 +02:00
|
|
|
node->constant_fold(folder);
|
2015-11-25 13:52:39 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2016-09-11 13:39:12 -04:00
|
|
|
/* Folding might have removed all nodes connected to the displacement output
|
|
|
|
|
* even tho there is displacement to be applied, so add in a value node if
|
|
|
|
|
* that happens to ensure there is still a valid graph for displacement.
|
|
|
|
|
*/
|
|
|
|
|
if (has_displacement && !output()->input("Displacement")->link) {
|
2024-12-30 02:20:36 +01:00
|
|
|
ColorNode *value = create_node<ColorNode>();
|
2020-11-04 11:17:38 +01:00
|
|
|
value->set_value(output()->get_displacement());
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-01-20 02:01:07 +01:00
|
|
|
connect(value->output("Color"), output()->input("Displacement"));
|
2016-09-11 13:39:12 -04:00
|
|
|
}
|
2015-11-25 13:52:39 +01:00
|
|
|
}
|
|
|
|
|
|
2017-03-21 17:18:25 +01:00
|
|
|
/* Simplification. */
|
2015-11-25 13:52:39 +01:00
|
|
|
void ShaderGraph::simplify_settings(Scene *scene)
|
2015-11-18 18:47:56 +01:00
|
|
|
{
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes) {
|
2015-11-25 13:52:39 +01:00
|
|
|
node->simplify_settings(scene);
|
2015-11-18 18:47:56 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-21 17:18:25 +01:00
|
|
|
/* Deduplicate nodes with same settings. */
|
2015-12-15 21:56:27 +05:00
|
|
|
void ShaderGraph::deduplicate_nodes()
|
|
|
|
|
{
|
|
|
|
|
/* NOTES:
|
|
|
|
|
* - Deduplication happens for nodes which has same exact settings and same
|
|
|
|
|
* exact input links configuration (either connected to same output or has
|
|
|
|
|
* the same exact default value).
|
|
|
|
|
* - Deduplication happens in the bottom-top manner, so we know for fact that
|
|
|
|
|
* all traversed nodes are either can not be deduplicated at all or were
|
|
|
|
|
* already deduplicated.
|
|
|
|
|
*/
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-29 17:32:00 +01:00
|
|
|
ShaderNodeSet scheduled;
|
|
|
|
|
ShaderNodeSet done;
|
2016-06-19 16:51:10 +03:00
|
|
|
map<ustring, ShaderNodeSet> candidates;
|
2015-12-15 21:56:27 +05:00
|
|
|
queue<ShaderNode *> traverse_queue;
|
2016-08-02 19:26:57 +03:00
|
|
|
int num_deduplicated = 0;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2015-12-15 21:56:27 +05:00
|
|
|
/* Schedule nodes which doesn't have any dependencies. */
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes) {
|
2015-12-15 21:56:27 +05:00
|
|
|
if (!check_node_inputs_has_links(node)) {
|
|
|
|
|
traverse_queue.push(node);
|
|
|
|
|
scheduled.insert(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2015-12-15 21:56:27 +05:00
|
|
|
while (!traverse_queue.empty()) {
|
|
|
|
|
ShaderNode *node = traverse_queue.front();
|
|
|
|
|
traverse_queue.pop();
|
2016-06-19 16:51:10 +03:00
|
|
|
done.insert(node);
|
2015-12-15 21:56:27 +05:00
|
|
|
/* Schedule the nodes which were depending on the current node. */
|
2016-08-02 19:26:57 +03:00
|
|
|
bool has_output_links = false;
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderOutput *output : node->outputs) {
|
|
|
|
|
for (ShaderInput *input : output->links) {
|
2016-08-02 19:26:57 +03:00
|
|
|
has_output_links = true;
|
2015-12-15 21:56:27 +05:00
|
|
|
if (scheduled.find(input->parent) != scheduled.end()) {
|
|
|
|
|
/* Node might not be optimized yet but scheduled already
|
|
|
|
|
* by other dependencies. No need to re-schedule it.
|
|
|
|
|
*/
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
/* Schedule node if its inputs are fully done. */
|
2016-06-19 16:51:10 +03:00
|
|
|
if (check_node_inputs_traversed(input->parent, done)) {
|
2015-12-15 21:56:27 +05:00
|
|
|
traverse_queue.push(input->parent);
|
|
|
|
|
scheduled.insert(input->parent);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-08-02 19:26:57 +03:00
|
|
|
/* Only need to care about nodes that are actually used */
|
|
|
|
|
if (!has_output_links) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2015-12-15 21:56:27 +05:00
|
|
|
/* Try to merge this node with another one. */
|
2024-12-26 17:53:55 +01:00
|
|
|
ShaderNode *merge_with = nullptr;
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *other_node : candidates[node->type->name]) {
|
2016-10-24 12:26:12 +02:00
|
|
|
if (node != other_node && node->equals(*other_node)) {
|
2016-06-19 16:51:10 +03:00
|
|
|
merge_with = other_node;
|
|
|
|
|
break;
|
2015-12-15 21:56:27 +05:00
|
|
|
}
|
2016-06-19 16:51:10 +03:00
|
|
|
}
|
|
|
|
|
/* If found an equivalent, merge; otherwise keep node for later merges */
|
2024-12-26 17:53:55 +01:00
|
|
|
if (merge_with != nullptr) {
|
2016-06-19 16:51:10 +03:00
|
|
|
for (int i = 0; i < node->outputs.size(); ++i) {
|
|
|
|
|
relink(node, node->outputs[i], merge_with->outputs[i]);
|
|
|
|
|
}
|
2016-08-02 19:26:57 +03:00
|
|
|
num_deduplicated++;
|
2016-06-19 16:51:10 +03:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
candidates[node->type->name].insert(node);
|
2015-12-15 21:56:27 +05:00
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2016-08-02 19:26:57 +03:00
|
|
|
if (num_deduplicated > 0) {
|
2022-06-16 19:39:13 +02:00
|
|
|
VLOG_DEBUG << "Deduplicated " << num_deduplicated << " nodes.";
|
2016-08-02 19:26:57 +03:00
|
|
|
}
|
2015-12-15 21:56:27 +05:00
|
|
|
}
|
|
|
|
|
|
2017-03-21 17:18:25 +01:00
|
|
|
/* Check whether volume output has meaningful nodes, otherwise
|
|
|
|
|
* disconnect the output.
|
|
|
|
|
*/
|
|
|
|
|
void ShaderGraph::verify_volume_output()
|
|
|
|
|
{
|
|
|
|
|
/* Check whether we can optimize the whole volume graph out. */
|
|
|
|
|
ShaderInput *volume_in = output()->input("Volume");
|
2024-12-26 17:53:55 +01:00
|
|
|
if (volume_in->link == nullptr) {
|
2017-03-21 17:18:25 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
bool has_valid_volume = false;
|
|
|
|
|
ShaderNodeSet scheduled;
|
|
|
|
|
queue<ShaderNode *> traverse_queue;
|
|
|
|
|
/* Schedule volume output. */
|
|
|
|
|
traverse_queue.push(volume_in->link->parent);
|
|
|
|
|
scheduled.insert(volume_in->link->parent);
|
|
|
|
|
/* Traverse down the tree. */
|
|
|
|
|
while (!traverse_queue.empty()) {
|
|
|
|
|
ShaderNode *node = traverse_queue.front();
|
|
|
|
|
traverse_queue.pop();
|
|
|
|
|
/* Node is fully valid for volume, can't optimize anything out. */
|
|
|
|
|
if (node->has_volume_support()) {
|
|
|
|
|
has_valid_volume = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *input : node->inputs) {
|
2024-12-26 17:53:55 +01:00
|
|
|
if (input->link == nullptr) {
|
2017-03-21 17:18:25 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (scheduled.find(input->link->parent) != scheduled.end()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
traverse_queue.push(input->link->parent);
|
|
|
|
|
scheduled.insert(input->link->parent);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!has_valid_volume) {
|
2022-06-16 19:39:13 +02:00
|
|
|
VLOG_DEBUG << "Disconnect meaningless volume output.";
|
2017-03-21 17:18:25 +01:00
|
|
|
disconnect(volume_in->link);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
void ShaderGraph::break_cycles(ShaderNode *node, vector<bool> &visited, vector<bool> &on_stack)
|
|
|
|
|
{
|
|
|
|
|
visited[node->id] = true;
|
|
|
|
|
on_stack[node->id] = true;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *input : node->inputs) {
|
2011-04-27 11:58:34 +00:00
|
|
|
if (input->link) {
|
|
|
|
|
ShaderNode *depnode = input->link->parent;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
if (on_stack[depnode->id]) {
|
|
|
|
|
/* break cycle */
|
|
|
|
|
disconnect(input);
|
2012-10-04 20:12:16 +00:00
|
|
|
fprintf(stderr, "Cycles shader graph: detected cycle in graph, connection removed.\n");
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
else if (!visited[depnode->id]) {
|
|
|
|
|
/* visit dependencies */
|
|
|
|
|
break_cycles(depnode, visited, on_stack);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
on_stack[node->id] = false;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-24 20:19:48 +01:00
|
|
|
void ShaderGraph::compute_displacement_hash()
|
|
|
|
|
{
|
|
|
|
|
/* Compute hash of all nodes linked to displacement, to detect if we need
|
|
|
|
|
* to recompute displacement when shader nodes change. */
|
|
|
|
|
ShaderInput *displacement_in = output()->input("Displacement");
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-01-24 20:19:48 +01:00
|
|
|
if (!displacement_in->link) {
|
|
|
|
|
displacement_hash = "";
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-01-24 20:19:48 +01:00
|
|
|
ShaderNodeSet nodes_displace;
|
|
|
|
|
find_dependencies(nodes_displace, displacement_in);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-01-24 20:19:48 +01:00
|
|
|
MD5Hash md5;
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes_displace) {
|
2018-01-24 20:19:48 +01:00
|
|
|
node->hash(md5);
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *input : node->inputs) {
|
2018-01-24 20:19:48 +01:00
|
|
|
int link_id = (input->link) ? input->link->parent->id : 0;
|
|
|
|
|
md5.append((uint8_t *)&link_id, sizeof(link_id));
|
2020-11-05 19:17:18 +01:00
|
|
|
md5.append((input->link) ? input->link->name().c_str() : "");
|
2018-01-24 20:19:48 +01:00
|
|
|
}
|
2019-04-21 02:18:19 +02:00
|
|
|
|
|
|
|
|
if (node->special_type == SHADER_SPECIAL_TYPE_OSL) {
|
|
|
|
|
/* Hash takes into account socket values, to detect changes
|
|
|
|
|
* in the code of the node we need an exception. */
|
|
|
|
|
OSLNode *oslnode = static_cast<OSLNode *>(node);
|
|
|
|
|
md5.append(oslnode->bytecode_hash);
|
|
|
|
|
}
|
2018-01-24 20:19:48 +01:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-01-24 20:19:48 +01:00
|
|
|
displacement_hash = md5.get_hex();
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-20 18:18:27 +05:00
|
|
|
void ShaderGraph::clean(Scene *scene)
|
2011-04-27 11:58:34 +00:00
|
|
|
{
|
2016-05-02 00:05:16 +02:00
|
|
|
/* Graph simplification */
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2017-03-21 17:18:25 +01:00
|
|
|
/* NOTE: Remove proxy nodes was already done. */
|
2018-06-14 17:48:19 +02:00
|
|
|
constant_fold(scene);
|
2015-11-25 13:52:39 +01:00
|
|
|
simplify_settings(scene);
|
2015-12-15 21:56:27 +05:00
|
|
|
deduplicate_nodes();
|
2017-03-21 17:18:25 +01:00
|
|
|
verify_volume_output();
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* we do two things here: find cycles and break them, and remove unused
|
2012-06-09 17:22:52 +00:00
|
|
|
* nodes that don't feed into the output. how cycles are broken is
|
|
|
|
|
* undefined, they are invalid input, the important thing is to not crash */
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2013-05-10 15:10:54 +00:00
|
|
|
vector<bool> visited(num_node_ids, false);
|
|
|
|
|
vector<bool> on_stack(num_node_ids, false);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* break cycles */
|
|
|
|
|
break_cycles(output(), visited, on_stack);
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes) {
|
2019-12-04 19:57:28 +01:00
|
|
|
if (node->special_type == SHADER_SPECIAL_TYPE_OUTPUT_AOV) {
|
|
|
|
|
break_cycles(node, visited, on_stack);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-09-04 13:29:07 +00:00
|
|
|
/* disconnect unused nodes */
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes) {
|
2012-09-04 13:29:07 +00:00
|
|
|
if (!visited[node->id]) {
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *to : node->inputs) {
|
2012-09-04 13:29:07 +00:00
|
|
|
ShaderOutput *from = to->link;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2013-05-10 11:31:57 +00:00
|
|
|
if (from) {
|
2024-12-26 17:53:55 +01:00
|
|
|
to->link = nullptr;
|
2012-09-04 13:29:07 +00:00
|
|
|
from->links.erase(remove(from->links.begin(), from->links.end(), to), from->links.end());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* remove unused nodes */
|
2024-12-30 02:20:36 +01:00
|
|
|
unique_ptr_vector<ShaderNode> newnodes;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-30 02:20:36 +01:00
|
|
|
for (size_t i = 0; i < nodes.size(); i++) {
|
|
|
|
|
unique_ptr<ShaderNode> node = nodes.steal(i);
|
2023-09-17 09:01:48 +10:00
|
|
|
if (visited[node->id]) {
|
2024-12-30 02:20:36 +01:00
|
|
|
newnodes.push_back(std::move(node));
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-30 02:20:36 +01:00
|
|
|
nodes = std::move(newnodes);
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
2019-05-12 14:39:30 +02:00
|
|
|
void ShaderGraph::expand()
|
|
|
|
|
{
|
2024-12-30 02:20:36 +01:00
|
|
|
/* Call expand on all nodes, to generate additional nodes.
|
|
|
|
|
* No range based for loop because we modify the vector, and want to expand
|
|
|
|
|
* newly generated nodes too. */
|
|
|
|
|
for (size_t i = 0; i < nodes.size(); i++) {
|
|
|
|
|
ShaderNode *node = nodes[i];
|
2019-05-12 14:39:30 +02:00
|
|
|
node->expand(this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
void ShaderGraph::default_inputs(bool do_osl)
|
|
|
|
|
{
|
|
|
|
|
/* nodes can specify default texture coordinates, for now we give
|
|
|
|
|
* everything the position by default, except for the sky texture */
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-26 17:53:55 +01:00
|
|
|
GeometryNode *geom = nullptr;
|
|
|
|
|
TextureCoordinateNode *texco = nullptr;
|
|
|
|
|
VectorTransformNode *normal_transform = nullptr;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-30 02:20:36 +01:00
|
|
|
for (size_t i = 0; i < nodes.size(); i++) {
|
|
|
|
|
ShaderNode *node = nodes[i];
|
|
|
|
|
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *input : node->inputs) {
|
2016-05-08 01:32:09 +02:00
|
|
|
if (!input->link && (!(input->flags() & SocketType::OSL_INTERNAL) || do_osl)) {
|
|
|
|
|
if (input->flags() & SocketType::LINK_TEXTURE_GENERATED) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (!texco) {
|
Cycles: introduce an ownership system to protect nodes from unwanted deletions.
Problem: the Blender synchronization process creates and tags nodes for usage. It does
this by directly adding and removing nodes from the scene data. If some node is not tagged
as used at the end of a synchronization, it then deletes the node from the scene. This poses
a problem when it comes to supporting procedural nodes who can create other nodes not known
by the Blender synchonization system, which will remove them.
Nodes now have a NodeOwner, which is set after creation. Those owners for now are the Scene
for scene level nodes and ShaderGraph for shader nodes. Instead of creating and deleting
nodes using `new` and `delete` explicitely, we now use `create_node` and `delete_node` methods
found on the owners. `delete_node` will assert that the owner is the right one.
Whenever a scene level node is created or deleted, the appropriate node manager is tagged for
an update, freeing this responsability from BlenderSync or other software exporters.
Concerning BlenderSync, the `id_maps` do not explicitely manipulate scene data anymore, they
only keep track of which nodes are used, employing the scene to create and delete them. To
achieve this, the ParticleSystem is now a Node, although it does not have any sockets.
This is part of T79131.
Reviewed By: #cycles, brecht
Maniphest Tasks: T79131
Differential Revision: https://developer.blender.org/D8540
2020-08-30 23:20:51 +02:00
|
|
|
texco = create_node<TextureCoordinateNode>();
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
connect(texco->output("Generated"), input);
|
|
|
|
|
}
|
Cycles: Add Support for IES files as textures for light strength
This patch adds support for IES files, a file format that is commonly used to store the directional intensity distribution of light sources.
The new IES node is supposed to be plugged into the Strength input of the Emission node of the lamp.
Since people generating IES files do not really seem to care about the standard, the parser is flexible enough to accept all test files I have tried.
Some common weirdnesses are distributing values over multiple lines that should go into one line, using commas instead of spaces as delimiters and adding various useless stuff at the end of the file.
The user interface of the node is similar to the script node, the user can either select an internal Text or load a file.
Internally, IES files are handled similar to Image textures: They are stored in slots by the LightManager and each unique IES is assigned to one slot.
The local coordinate system of the lamp is used, so that the direction of the light can be changed. For UI reasons, it's usually best to add an area light,
rotate it and then change its type, since especially the point light does not immediately show its local coordinate system in the viewport.
Reviewers: #cycles, dingto, sergey, brecht
Reviewed By: #cycles, dingto, brecht
Subscribers: OgDEV, crazyrobinhood, secundar, cardboard, pisuke, intrah, swerner, micah_denn, harvester, gottfried, disnel, campbellbarton, duarteframos, Lapineige, brecht, juicyfruit, dingto, marek, rickyblender, bliblubli, lockal, sergey
Differential Revision: https://developer.blender.org/D1543
2018-05-27 00:46:37 +02:00
|
|
|
if (input->flags() & SocketType::LINK_TEXTURE_NORMAL) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (!texco) {
|
Cycles: introduce an ownership system to protect nodes from unwanted deletions.
Problem: the Blender synchronization process creates and tags nodes for usage. It does
this by directly adding and removing nodes from the scene data. If some node is not tagged
as used at the end of a synchronization, it then deletes the node from the scene. This poses
a problem when it comes to supporting procedural nodes who can create other nodes not known
by the Blender synchonization system, which will remove them.
Nodes now have a NodeOwner, which is set after creation. Those owners for now are the Scene
for scene level nodes and ShaderGraph for shader nodes. Instead of creating and deleting
nodes using `new` and `delete` explicitely, we now use `create_node` and `delete_node` methods
found on the owners. `delete_node` will assert that the owner is the right one.
Whenever a scene level node is created or deleted, the appropriate node manager is tagged for
an update, freeing this responsability from BlenderSync or other software exporters.
Concerning BlenderSync, the `id_maps` do not explicitely manipulate scene data anymore, they
only keep track of which nodes are used, employing the scene to create and delete them. To
achieve this, the ParticleSystem is now a Node, although it does not have any sockets.
This is part of T79131.
Reviewed By: #cycles, brecht
Maniphest Tasks: T79131
Differential Revision: https://developer.blender.org/D8540
2020-08-30 23:20:51 +02:00
|
|
|
texco = create_node<TextureCoordinateNode>();
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
Cycles: Add Support for IES files as textures for light strength
This patch adds support for IES files, a file format that is commonly used to store the directional intensity distribution of light sources.
The new IES node is supposed to be plugged into the Strength input of the Emission node of the lamp.
Since people generating IES files do not really seem to care about the standard, the parser is flexible enough to accept all test files I have tried.
Some common weirdnesses are distributing values over multiple lines that should go into one line, using commas instead of spaces as delimiters and adding various useless stuff at the end of the file.
The user interface of the node is similar to the script node, the user can either select an internal Text or load a file.
Internally, IES files are handled similar to Image textures: They are stored in slots by the LightManager and each unique IES is assigned to one slot.
The local coordinate system of the lamp is used, so that the direction of the light can be changed. For UI reasons, it's usually best to add an area light,
rotate it and then change its type, since especially the point light does not immediately show its local coordinate system in the viewport.
Reviewers: #cycles, dingto, sergey, brecht
Reviewed By: #cycles, dingto, brecht
Subscribers: OgDEV, crazyrobinhood, secundar, cardboard, pisuke, intrah, swerner, micah_denn, harvester, gottfried, disnel, campbellbarton, duarteframos, Lapineige, brecht, juicyfruit, dingto, marek, rickyblender, bliblubli, lockal, sergey
Differential Revision: https://developer.blender.org/D1543
2018-05-27 00:46:37 +02:00
|
|
|
connect(texco->output("Normal"), input);
|
|
|
|
|
}
|
2016-05-08 01:32:09 +02:00
|
|
|
else if (input->flags() & SocketType::LINK_TEXTURE_UV) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (!texco) {
|
Cycles: introduce an ownership system to protect nodes from unwanted deletions.
Problem: the Blender synchronization process creates and tags nodes for usage. It does
this by directly adding and removing nodes from the scene data. If some node is not tagged
as used at the end of a synchronization, it then deletes the node from the scene. This poses
a problem when it comes to supporting procedural nodes who can create other nodes not known
by the Blender synchonization system, which will remove them.
Nodes now have a NodeOwner, which is set after creation. Those owners for now are the Scene
for scene level nodes and ShaderGraph for shader nodes. Instead of creating and deleting
nodes using `new` and `delete` explicitely, we now use `create_node` and `delete_node` methods
found on the owners. `delete_node` will assert that the owner is the right one.
Whenever a scene level node is created or deleted, the appropriate node manager is tagged for
an update, freeing this responsability from BlenderSync or other software exporters.
Concerning BlenderSync, the `id_maps` do not explicitely manipulate scene data anymore, they
only keep track of which nodes are used, employing the scene to create and delete them. To
achieve this, the ParticleSystem is now a Node, although it does not have any sockets.
This is part of T79131.
Reviewed By: #cycles, brecht
Maniphest Tasks: T79131
Differential Revision: https://developer.blender.org/D8540
2020-08-30 23:20:51 +02:00
|
|
|
texco = create_node<TextureCoordinateNode>();
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-10-12 16:01:37 +00:00
|
|
|
connect(texco->output("UV"), input);
|
|
|
|
|
}
|
2023-10-18 20:09:37 +02:00
|
|
|
else if (input->flags() & SocketType::LINK_TEXTURE_INCOMING) {
|
|
|
|
|
if (!geom) {
|
|
|
|
|
geom = create_node<GeometryNode>();
|
|
|
|
|
}
|
|
|
|
|
if (!normal_transform) {
|
|
|
|
|
normal_transform = create_node<VectorTransformNode>();
|
|
|
|
|
normal_transform->set_transform_type(NODE_VECTOR_TRANSFORM_TYPE_NORMAL);
|
|
|
|
|
normal_transform->set_convert_from(NODE_VECTOR_TRANSFORM_CONVERT_SPACE_WORLD);
|
|
|
|
|
normal_transform->set_convert_to(NODE_VECTOR_TRANSFORM_CONVERT_SPACE_OBJECT);
|
|
|
|
|
connect(geom->output("Incoming"), normal_transform->input("Vector"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
connect(normal_transform->output("Vector"), input);
|
|
|
|
|
}
|
2016-05-08 01:32:09 +02:00
|
|
|
else if (input->flags() & SocketType::LINK_INCOMING) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (!geom) {
|
Cycles: introduce an ownership system to protect nodes from unwanted deletions.
Problem: the Blender synchronization process creates and tags nodes for usage. It does
this by directly adding and removing nodes from the scene data. If some node is not tagged
as used at the end of a synchronization, it then deletes the node from the scene. This poses
a problem when it comes to supporting procedural nodes who can create other nodes not known
by the Blender synchonization system, which will remove them.
Nodes now have a NodeOwner, which is set after creation. Those owners for now are the Scene
for scene level nodes and ShaderGraph for shader nodes. Instead of creating and deleting
nodes using `new` and `delete` explicitely, we now use `create_node` and `delete_node` methods
found on the owners. `delete_node` will assert that the owner is the right one.
Whenever a scene level node is created or deleted, the appropriate node manager is tagged for
an update, freeing this responsability from BlenderSync or other software exporters.
Concerning BlenderSync, the `id_maps` do not explicitely manipulate scene data anymore, they
only keep track of which nodes are used, employing the scene to create and delete them. To
achieve this, the ParticleSystem is now a Node, although it does not have any sockets.
This is part of T79131.
Reviewed By: #cycles, brecht
Maniphest Tasks: T79131
Differential Revision: https://developer.blender.org/D8540
2020-08-30 23:20:51 +02:00
|
|
|
geom = create_node<GeometryNode>();
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
connect(geom->output("Incoming"), input);
|
|
|
|
|
}
|
2016-05-08 01:32:09 +02:00
|
|
|
else if (input->flags() & SocketType::LINK_NORMAL) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (!geom) {
|
Cycles: introduce an ownership system to protect nodes from unwanted deletions.
Problem: the Blender synchronization process creates and tags nodes for usage. It does
this by directly adding and removing nodes from the scene data. If some node is not tagged
as used at the end of a synchronization, it then deletes the node from the scene. This poses
a problem when it comes to supporting procedural nodes who can create other nodes not known
by the Blender synchonization system, which will remove them.
Nodes now have a NodeOwner, which is set after creation. Those owners for now are the Scene
for scene level nodes and ShaderGraph for shader nodes. Instead of creating and deleting
nodes using `new` and `delete` explicitely, we now use `create_node` and `delete_node` methods
found on the owners. `delete_node` will assert that the owner is the right one.
Whenever a scene level node is created or deleted, the appropriate node manager is tagged for
an update, freeing this responsability from BlenderSync or other software exporters.
Concerning BlenderSync, the `id_maps` do not explicitely manipulate scene data anymore, they
only keep track of which nodes are used, employing the scene to create and delete them. To
achieve this, the ParticleSystem is now a Node, although it does not have any sockets.
This is part of T79131.
Reviewed By: #cycles, brecht
Maniphest Tasks: T79131
Differential Revision: https://developer.blender.org/D8540
2020-08-30 23:20:51 +02:00
|
|
|
geom = create_node<GeometryNode>();
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
connect(geom->output("Normal"), input);
|
|
|
|
|
}
|
2016-05-08 01:32:09 +02:00
|
|
|
else if (input->flags() & SocketType::LINK_POSITION) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (!geom) {
|
Cycles: introduce an ownership system to protect nodes from unwanted deletions.
Problem: the Blender synchronization process creates and tags nodes for usage. It does
this by directly adding and removing nodes from the scene data. If some node is not tagged
as used at the end of a synchronization, it then deletes the node from the scene. This poses
a problem when it comes to supporting procedural nodes who can create other nodes not known
by the Blender synchonization system, which will remove them.
Nodes now have a NodeOwner, which is set after creation. Those owners for now are the Scene
for scene level nodes and ShaderGraph for shader nodes. Instead of creating and deleting
nodes using `new` and `delete` explicitely, we now use `create_node` and `delete_node` methods
found on the owners. `delete_node` will assert that the owner is the right one.
Whenever a scene level node is created or deleted, the appropriate node manager is tagged for
an update, freeing this responsability from BlenderSync or other software exporters.
Concerning BlenderSync, the `id_maps` do not explicitely manipulate scene data anymore, they
only keep track of which nodes are used, employing the scene to create and delete them. To
achieve this, the ParticleSystem is now a Node, although it does not have any sockets.
This is part of T79131.
Reviewed By: #cycles, brecht
Maniphest Tasks: T79131
Differential Revision: https://developer.blender.org/D8540
2020-08-30 23:20:51 +02:00
|
|
|
geom = create_node<GeometryNode>();
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
connect(geom->output("Position"), input);
|
|
|
|
|
}
|
2016-05-08 01:32:09 +02:00
|
|
|
else if (input->flags() & SocketType::LINK_TANGENT) {
|
2023-09-17 09:01:48 +10:00
|
|
|
if (!geom) {
|
Cycles: introduce an ownership system to protect nodes from unwanted deletions.
Problem: the Blender synchronization process creates and tags nodes for usage. It does
this by directly adding and removing nodes from the scene data. If some node is not tagged
as used at the end of a synchronization, it then deletes the node from the scene. This poses
a problem when it comes to supporting procedural nodes who can create other nodes not known
by the Blender synchonization system, which will remove them.
Nodes now have a NodeOwner, which is set after creation. Those owners for now are the Scene
for scene level nodes and ShaderGraph for shader nodes. Instead of creating and deleting
nodes using `new` and `delete` explicitely, we now use `create_node` and `delete_node` methods
found on the owners. `delete_node` will assert that the owner is the right one.
Whenever a scene level node is created or deleted, the appropriate node manager is tagged for
an update, freeing this responsability from BlenderSync or other software exporters.
Concerning BlenderSync, the `id_maps` do not explicitely manipulate scene data anymore, they
only keep track of which nodes are used, employing the scene to create and delete them. To
achieve this, the ParticleSystem is now a Node, although it does not have any sockets.
This is part of T79131.
Reviewed By: #cycles, brecht
Maniphest Tasks: T79131
Differential Revision: https://developer.blender.org/D8540
2020-08-30 23:20:51 +02:00
|
|
|
geom = create_node<GeometryNode>();
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-10-10 13:02:20 +00:00
|
|
|
connect(geom->output("Tangent"), input);
|
|
|
|
|
}
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-10 15:56:43 +00:00
|
|
|
void ShaderGraph::refine_bump_nodes()
|
|
|
|
|
{
|
2022-06-01 15:19:33 +10:00
|
|
|
/* We transverse the node graph looking for bump nodes, when we find them,
|
2012-10-10 15:56:43 +00:00
|
|
|
* like in bump_from_displacement(), we copy the sub-graph defined from "bump"
|
|
|
|
|
* input to the inputs "center","dx" and "dy" What is in "bump" input is moved
|
|
|
|
|
* to "center" input. */
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-30 02:20:36 +01:00
|
|
|
/* No range based for loop because we modify the vector. */
|
|
|
|
|
for (int i = 0; i < nodes.size(); i++) {
|
|
|
|
|
ShaderNode *node = nodes[i];
|
|
|
|
|
|
2016-05-02 00:05:16 +02:00
|
|
|
if (node->special_type == SHADER_SPECIAL_TYPE_BUMP && node->input("Height")->link) {
|
2012-10-10 15:56:43 +00:00
|
|
|
ShaderInput *bump_input = node->input("Height");
|
2015-11-25 13:07:29 +05:00
|
|
|
ShaderNodeSet nodes_bump;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2022-06-01 15:19:33 +10:00
|
|
|
/* Make 2 extra copies of the subgraph defined in Bump input. */
|
2015-11-25 13:07:29 +05:00
|
|
|
ShaderNodeMap nodes_dx;
|
|
|
|
|
ShaderNodeMap nodes_dy;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2022-06-01 15:19:33 +10:00
|
|
|
/* Find dependencies for the given input. */
|
2015-11-25 13:07:29 +05:00
|
|
|
find_dependencies(nodes_bump, bump_input);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-10-10 15:56:43 +00:00
|
|
|
copy_nodes(nodes_bump, nodes_dx);
|
|
|
|
|
copy_nodes(nodes_bump, nodes_dy);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2022-06-01 15:19:33 +10:00
|
|
|
/* Mark nodes to indicate they are use for bump computation, so
|
|
|
|
|
* that any texture coordinates are shifted by dx/dy when sampling. */
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes_bump) {
|
2012-10-10 15:56:43 +00:00
|
|
|
node->bump = SHADER_BUMP_CENTER;
|
2024-12-26 19:41:25 +01:00
|
|
|
}
|
2024-12-29 17:32:00 +01:00
|
|
|
for (const NodePair &pair : nodes_dx) {
|
2012-10-10 15:56:43 +00:00
|
|
|
pair.second->bump = SHADER_BUMP_DX;
|
2024-12-26 19:41:25 +01:00
|
|
|
}
|
2024-12-29 17:32:00 +01:00
|
|
|
for (const NodePair &pair : nodes_dy) {
|
2012-10-10 15:56:43 +00:00
|
|
|
pair.second->bump = SHADER_BUMP_DY;
|
2024-12-26 19:41:25 +01:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-10-10 15:56:43 +00:00
|
|
|
ShaderOutput *out = bump_input->link;
|
2016-05-08 01:32:09 +02:00
|
|
|
ShaderOutput *out_dx = nodes_dx[out->parent]->output(out->name());
|
|
|
|
|
ShaderOutput *out_dy = nodes_dy[out->parent]->output(out->name());
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-10-10 15:56:43 +00:00
|
|
|
connect(out_dx, node->input("SampleX"));
|
|
|
|
|
connect(out_dy, node->input("SampleY"));
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-07-02 12:58:25 +10:00
|
|
|
/* Connect what is connected is bump to sample-center input. */
|
2012-10-10 15:56:43 +00:00
|
|
|
connect(out, node->input("SampleCenter"));
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-07-02 12:58:25 +10:00
|
|
|
/* Bump input is just for connectivity purpose for the graph input,
|
|
|
|
|
* we re-connected this input to sample-center, so lets disconnect it
|
|
|
|
|
* from bump input. */
|
2012-10-10 15:56:43 +00:00
|
|
|
disconnect(bump_input);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-02 00:41:04 -04:00
|
|
|
void ShaderGraph::bump_from_displacement(bool use_object_space)
|
2011-04-27 11:58:34 +00:00
|
|
|
{
|
|
|
|
|
/* generate bump mapping automatically from displacement. bump mapping is
|
|
|
|
|
* done using a 3-tap filter, computing the displacement at the center,
|
|
|
|
|
* and two other positions shifted by ray differentials.
|
|
|
|
|
*
|
|
|
|
|
* since the input to displacement is a node graph, we need to ensure that
|
|
|
|
|
* all texture coordinates use are shift by the ray differentials. for this
|
|
|
|
|
* reason we make 3 copies of the node subgraph defining the displacement,
|
|
|
|
|
* with each different geometry and texture coordinate nodes that generate
|
|
|
|
|
* different shifted coordinates.
|
|
|
|
|
*
|
|
|
|
|
* these 3 displacement values are then fed into the bump node, which will
|
2014-10-29 14:11:19 +01:00
|
|
|
* output the perturbed normal. */
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
ShaderInput *displacement_in = output()->input("Displacement");
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-09-17 09:01:48 +10:00
|
|
|
if (!displacement_in->link) {
|
2011-04-27 11:58:34 +00:00
|
|
|
return;
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* find dependencies for the given input */
|
2015-11-25 13:07:29 +05:00
|
|
|
ShaderNodeSet nodes_displace;
|
2011-04-27 11:58:34 +00:00
|
|
|
find_dependencies(nodes_displace, displacement_in);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* copy nodes for 3 bump samples */
|
2015-11-25 13:07:29 +05:00
|
|
|
ShaderNodeMap nodes_center;
|
|
|
|
|
ShaderNodeMap nodes_dx;
|
|
|
|
|
ShaderNodeMap nodes_dy;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
copy_nodes(nodes_displace, nodes_center);
|
|
|
|
|
copy_nodes(nodes_displace, nodes_dx);
|
|
|
|
|
copy_nodes(nodes_displace, nodes_dy);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* mark nodes to indicate they are use for bump computation, so
|
2012-06-09 17:22:52 +00:00
|
|
|
* that any texture coordinates are shifted by dx/dy when sampling */
|
2024-12-29 17:32:00 +01:00
|
|
|
for (const NodePair &pair : nodes_center) {
|
2011-04-27 11:58:34 +00:00
|
|
|
pair.second->bump = SHADER_BUMP_CENTER;
|
2024-12-26 19:41:25 +01:00
|
|
|
}
|
2024-12-29 17:32:00 +01:00
|
|
|
for (const NodePair &pair : nodes_dx) {
|
2011-04-27 11:58:34 +00:00
|
|
|
pair.second->bump = SHADER_BUMP_DX;
|
2024-12-26 19:41:25 +01:00
|
|
|
}
|
2024-12-29 17:32:00 +01:00
|
|
|
for (const NodePair &pair : nodes_dy) {
|
2011-04-27 11:58:34 +00:00
|
|
|
pair.second->bump = SHADER_BUMP_DY;
|
2024-12-26 19:41:25 +01:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2019-08-01 13:53:25 +10:00
|
|
|
/* add set normal node and connect the bump normal output to the set normal
|
2012-10-10 15:56:43 +00:00
|
|
|
* output, so it can finally set the shader normal, note we are only doing
|
|
|
|
|
* this for bump from displacement, this will be the only bump allowed to
|
|
|
|
|
* overwrite the shader normal */
|
2024-12-30 02:20:36 +01:00
|
|
|
ShaderNode *set_normal = create_node<SetNormalNode>();
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
/* add bump node and connect copied graphs to it */
|
2024-12-30 02:20:36 +01:00
|
|
|
BumpNode *bump = create_node<BumpNode>();
|
2020-11-04 11:17:38 +01:00
|
|
|
bump->set_use_object_space(use_object_space);
|
|
|
|
|
bump->set_distance(1.0f);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
ShaderOutput *out = displacement_in->link;
|
2016-05-08 01:32:09 +02:00
|
|
|
ShaderOutput *out_center = nodes_center[out->parent]->output(out->name());
|
|
|
|
|
ShaderOutput *out_dx = nodes_dx[out->parent]->output(out->name());
|
|
|
|
|
ShaderOutput *out_dy = nodes_dy[out->parent]->output(out->name());
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-01-20 02:01:07 +01:00
|
|
|
/* convert displacement vector to height */
|
2024-12-30 02:20:36 +01:00
|
|
|
VectorMathNode *dot_center = create_node<VectorMathNode>();
|
|
|
|
|
VectorMathNode *dot_dx = create_node<VectorMathNode>();
|
|
|
|
|
VectorMathNode *dot_dy = create_node<VectorMathNode>();
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-11-04 11:17:38 +01:00
|
|
|
dot_center->set_math_type(NODE_VECTOR_MATH_DOT_PRODUCT);
|
|
|
|
|
dot_dx->set_math_type(NODE_VECTOR_MATH_DOT_PRODUCT);
|
|
|
|
|
dot_dy->set_math_type(NODE_VECTOR_MATH_DOT_PRODUCT);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-30 02:20:36 +01:00
|
|
|
GeometryNode *geom = create_node<GeometryNode>();
|
2024-06-14 16:45:21 +02:00
|
|
|
connect(geom->output("Normal"), bump->input("Normal"));
|
2018-01-20 02:01:07 +01:00
|
|
|
connect(geom->output("Normal"), dot_center->input("Vector2"));
|
|
|
|
|
connect(geom->output("Normal"), dot_dx->input("Vector2"));
|
|
|
|
|
connect(geom->output("Normal"), dot_dy->input("Vector2"));
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-01-20 02:01:07 +01:00
|
|
|
connect(out_center, dot_center->input("Vector1"));
|
|
|
|
|
connect(out_dx, dot_dx->input("Vector1"));
|
|
|
|
|
connect(out_dy, dot_dy->input("Vector1"));
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-01-20 02:01:07 +01:00
|
|
|
connect(dot_center->output("Value"), bump->input("SampleCenter"));
|
|
|
|
|
connect(dot_dx->output("Value"), bump->input("SampleX"));
|
|
|
|
|
connect(dot_dy->output("Value"), bump->input("SampleY"));
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-10-10 15:56:43 +00:00
|
|
|
/* connect the bump out to the set normal in: */
|
|
|
|
|
connect(bump->output("Normal"), set_normal->input("Direction"));
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2016-08-14 11:44:25 -04:00
|
|
|
/* connect to output node */
|
|
|
|
|
connect(set_normal->output("Normal"), output()->input("Normal"));
|
2011-04-27 11:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
2012-11-26 21:59:41 +00:00
|
|
|
void ShaderGraph::transform_multi_closure(ShaderNode *node, ShaderOutput *weight_out, bool volume)
|
|
|
|
|
{
|
|
|
|
|
/* for SVM in multi closure mode, this transforms the shader mix/add part of
|
|
|
|
|
* the graph into nodes that feed weights into closure nodes. this is too
|
|
|
|
|
* avoid building a closure tree and then flattening it, and instead write it
|
|
|
|
|
* directly to an array */
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2016-05-08 00:41:01 +02:00
|
|
|
if (node->special_type == SHADER_SPECIAL_TYPE_COMBINE_CLOSURE) {
|
2012-11-26 21:59:41 +00:00
|
|
|
ShaderInput *fin = node->input("Fac");
|
|
|
|
|
ShaderInput *cl1in = node->input("Closure1");
|
|
|
|
|
ShaderInput *cl2in = node->input("Closure2");
|
2024-12-29 17:32:00 +01:00
|
|
|
ShaderOutput *weight1_out;
|
|
|
|
|
ShaderOutput *weight2_out;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-11-26 21:59:41 +00:00
|
|
|
if (fin) {
|
|
|
|
|
/* mix closure: add node to mix closure weights */
|
Cycles: introduce an ownership system to protect nodes from unwanted deletions.
Problem: the Blender synchronization process creates and tags nodes for usage. It does
this by directly adding and removing nodes from the scene data. If some node is not tagged
as used at the end of a synchronization, it then deletes the node from the scene. This poses
a problem when it comes to supporting procedural nodes who can create other nodes not known
by the Blender synchonization system, which will remove them.
Nodes now have a NodeOwner, which is set after creation. Those owners for now are the Scene
for scene level nodes and ShaderGraph for shader nodes. Instead of creating and deleting
nodes using `new` and `delete` explicitely, we now use `create_node` and `delete_node` methods
found on the owners. `delete_node` will assert that the owner is the right one.
Whenever a scene level node is created or deleted, the appropriate node manager is tagged for
an update, freeing this responsability from BlenderSync or other software exporters.
Concerning BlenderSync, the `id_maps` do not explicitely manipulate scene data anymore, they
only keep track of which nodes are used, employing the scene to create and delete them. To
achieve this, the ParticleSystem is now a Node, although it does not have any sockets.
This is part of T79131.
Reviewed By: #cycles, brecht
Maniphest Tasks: T79131
Differential Revision: https://developer.blender.org/D8540
2020-08-30 23:20:51 +02:00
|
|
|
MixClosureWeightNode *mix_node = create_node<MixClosureWeightNode>();
|
2018-07-06 10:17:58 +02:00
|
|
|
ShaderInput *fac_in = mix_node->input("Fac");
|
|
|
|
|
ShaderInput *weight_in = mix_node->input("Weight");
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-09-17 09:01:48 +10:00
|
|
|
if (fin->link) {
|
2012-11-26 21:59:41 +00:00
|
|
|
connect(fin->link, fac_in);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
|
|
|
|
else {
|
2020-11-04 11:17:38 +01:00
|
|
|
mix_node->set_fac(node->get_float(fin->socket_type));
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-09-17 09:01:48 +10:00
|
|
|
if (weight_out) {
|
2012-11-26 21:59:41 +00:00
|
|
|
connect(weight_out, weight_in);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-11-26 21:59:41 +00:00
|
|
|
weight1_out = mix_node->output("Weight1");
|
|
|
|
|
weight2_out = mix_node->output("Weight2");
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* add closure: just pass on any weights */
|
|
|
|
|
weight1_out = weight_out;
|
|
|
|
|
weight2_out = weight_out;
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-09-17 09:01:48 +10:00
|
|
|
if (cl1in->link) {
|
2012-11-26 21:59:41 +00:00
|
|
|
transform_multi_closure(cl1in->link->parent, weight1_out, volume);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
|
|
|
|
if (cl2in->link) {
|
2012-11-26 21:59:41 +00:00
|
|
|
transform_multi_closure(cl2in->link->parent, weight2_out, volume);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2012-11-26 21:59:41 +00:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
ShaderInput *weight_in = node->input((volume) ? "VolumeMixWeight" : "SurfaceMixWeight");
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-11-26 21:59:41 +00:00
|
|
|
/* not a closure node? */
|
2023-09-17 09:01:48 +10:00
|
|
|
if (!weight_in) {
|
2012-11-26 21:59:41 +00:00
|
|
|
return;
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-11-26 21:59:41 +00:00
|
|
|
/* already has a weight connected to it? add weights */
|
2024-12-29 17:32:00 +01:00
|
|
|
const float weight_value = node->get_float(weight_in->socket_type);
|
2016-05-07 19:48:28 +02:00
|
|
|
if (weight_in->link || weight_value != 0.0f) {
|
Cycles: introduce an ownership system to protect nodes from unwanted deletions.
Problem: the Blender synchronization process creates and tags nodes for usage. It does
this by directly adding and removing nodes from the scene data. If some node is not tagged
as used at the end of a synchronization, it then deletes the node from the scene. This poses
a problem when it comes to supporting procedural nodes who can create other nodes not known
by the Blender synchonization system, which will remove them.
Nodes now have a NodeOwner, which is set after creation. Those owners for now are the Scene
for scene level nodes and ShaderGraph for shader nodes. Instead of creating and deleting
nodes using `new` and `delete` explicitely, we now use `create_node` and `delete_node` methods
found on the owners. `delete_node` will assert that the owner is the right one.
Whenever a scene level node is created or deleted, the appropriate node manager is tagged for
an update, freeing this responsability from BlenderSync or other software exporters.
Concerning BlenderSync, the `id_maps` do not explicitely manipulate scene data anymore, they
only keep track of which nodes are used, employing the scene to create and delete them. To
achieve this, the ParticleSystem is now a Node, although it does not have any sockets.
This is part of T79131.
Reviewed By: #cycles, brecht
Maniphest Tasks: T79131
Differential Revision: https://developer.blender.org/D8540
2020-08-30 23:20:51 +02:00
|
|
|
MathNode *math_node = create_node<MathNode>();
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-09-17 09:01:48 +10:00
|
|
|
if (weight_in->link) {
|
2016-05-07 19:48:28 +02:00
|
|
|
connect(weight_in->link, math_node->input("Value1"));
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
|
|
|
|
else {
|
2020-11-04 11:17:38 +01:00
|
|
|
math_node->set_value1(weight_value);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-09-17 09:01:48 +10:00
|
|
|
if (weight_out) {
|
2016-05-07 19:48:28 +02:00
|
|
|
connect(weight_out, math_node->input("Value2"));
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
|
|
|
|
else {
|
2020-11-04 11:17:38 +01:00
|
|
|
math_node->set_value2(1.0f);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-11-26 21:59:41 +00:00
|
|
|
weight_out = math_node->output("Value");
|
2023-09-17 09:01:48 +10:00
|
|
|
if (weight_in->link) {
|
2013-01-23 13:13:20 +00:00
|
|
|
disconnect(weight_in);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2012-11-26 21:59:41 +00:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2012-11-26 21:59:41 +00:00
|
|
|
/* connected to closure mix weight */
|
2023-09-17 09:01:48 +10:00
|
|
|
if (weight_out) {
|
2012-11-26 21:59:41 +00:00
|
|
|
connect(weight_out, weight_in);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
|
|
|
|
else {
|
2016-05-07 19:48:28 +02:00
|
|
|
node->set(weight_in->socket_type, weight_value + 1.0f);
|
2023-09-17 09:01:48 +10:00
|
|
|
}
|
2012-11-26 21:59:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-09 19:11:37 +05:00
|
|
|
int ShaderGraph::get_num_closures()
|
|
|
|
|
{
|
|
|
|
|
int num_closures = 0;
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes) {
|
2024-12-29 17:32:00 +01:00
|
|
|
const ClosureType closure_type = node->get_closure_type();
|
2016-05-23 14:09:27 +02:00
|
|
|
if (closure_type == CLOSURE_NONE_ID) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-12-26 17:53:59 +01:00
|
|
|
if (CLOSURE_IS_BSSRDF(closure_type)) {
|
2016-05-23 14:09:27 +02:00
|
|
|
num_closures += 3;
|
|
|
|
|
}
|
2016-07-25 03:03:23 +02:00
|
|
|
else if (CLOSURE_IS_BSDF_MULTISCATTER(closure_type)) {
|
|
|
|
|
num_closures += 2;
|
|
|
|
|
}
|
2017-04-18 11:43:09 +02:00
|
|
|
else if (CLOSURE_IS_PRINCIPLED(closure_type)) {
|
2023-10-09 16:13:44 +02:00
|
|
|
num_closures += 12;
|
2017-04-18 11:43:09 +02:00
|
|
|
}
|
2017-08-23 12:32:48 +02:00
|
|
|
else if (CLOSURE_IS_VOLUME(closure_type)) {
|
2024-12-30 02:20:36 +01:00
|
|
|
/* TODO(sergey): Verify this is still needed, since we have special minimized volume
|
|
|
|
|
* storage for the volume steps. */
|
2021-10-05 15:05:12 +02:00
|
|
|
num_closures += MAX_VOLUME_STACK_SIZE;
|
2017-08-23 12:32:48 +02:00
|
|
|
}
|
2024-08-27 17:20:46 +02:00
|
|
|
else if (closure_type == CLOSURE_BSDF_PHYSICAL_CONDUCTOR ||
|
|
|
|
|
closure_type == CLOSURE_BSDF_F82_CONDUCTOR ||
|
|
|
|
|
closure_type == CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID ||
|
2023-10-09 19:17:15 +02:00
|
|
|
closure_type == CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID ||
|
|
|
|
|
closure_type == CLOSURE_BSDF_HAIR_CHIANG_ID ||
|
|
|
|
|
closure_type == CLOSURE_BSDF_HAIR_HUANG_ID)
|
|
|
|
|
{
|
Cycles: new Principled Hair BSDF variant with elliptical cross-section support
Implements the paper [A Microfacet-based Hair Scattering
Model](https://onlinelibrary.wiley.com/doi/full/10.1111/cgf.14588) by
Weizhen Huang, Matthias B. Hullin and Johannes Hanika.
### Features:
- This is a far-field model, as opposed to the previous near-field
Principled Hair BSDF model. The hair is expected to be less noisy, but
lower roughness values takes longer to render due to numerical
integration along the hair width. The hair also appears to be flat when
viewed up-close.
- The longitudinal width of the scattering lobe differs along the
azimuth, providing a higher contrast compared to the evenly spread
scattering in the near-field Principled Hair BSDF model. For a more
detailed comparison, please refer to the original paper.
- Supports elliptical cross-sections, adding more realism as human hairs
are usually elliptical. The orientation of the cross-section is aligned
with the curve normal, which can be adjusted using geometry nodes.
Default is minimal twist. During sampling, light rays that hit outside
the hair width will continue propogating as if the material is
transparent.
- There is non-physical modulation factors for the first three
lobes (Reflection, Transmission, Secondary Reflection).
### Missing:
- A good default for cross-section orientation. There was an
attempt (9039f76928) to default the orientation to align with the curve
normal in the mathematical sense, but the stability (when animated) is
unclear and it would be a hassle to generalise to all curve types. After
the model is in main, we could experiment with the geometry nodes team
to see what works the best as a default.
Co-authored-by: Lukas Stockner <lukas.stockner@freenet.de>
Pull Request: https://projects.blender.org/blender/blender/pulls/105600
2023-08-18 12:46:13 +02:00
|
|
|
num_closures += 2;
|
2018-07-18 11:14:43 +02:00
|
|
|
}
|
2016-05-23 14:09:27 +02:00
|
|
|
else {
|
|
|
|
|
++num_closures;
|
2015-05-09 19:11:37 +05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return num_closures;
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-19 15:21:32 +06:00
|
|
|
void ShaderGraph::dump_graph(const char *filename)
|
|
|
|
|
{
|
|
|
|
|
FILE *fd = fopen(filename, "w");
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-26 17:53:55 +01:00
|
|
|
if (fd == nullptr) {
|
2014-09-19 15:21:32 +06:00
|
|
|
printf("Error opening file for dumping the graph: %s\n", filename);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2014-11-20 18:30:13 +05:00
|
|
|
fprintf(fd, "digraph shader_graph {\n");
|
2014-09-19 15:21:32 +06:00
|
|
|
fprintf(fd, "ranksep=1.5\n");
|
2015-03-17 21:15:17 +05:00
|
|
|
fprintf(fd, "rankdir=LR\n");
|
2014-09-19 15:21:32 +06:00
|
|
|
fprintf(fd, "splines=false\n");
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes) {
|
2014-09-19 15:21:32 +06:00
|
|
|
fprintf(fd, "// NODE: %p\n", node);
|
2015-03-17 21:15:17 +05:00
|
|
|
fprintf(fd, "\"%p\" [shape=record,label=\"{", node);
|
2024-12-26 17:53:59 +01:00
|
|
|
if (!node->inputs.empty()) {
|
2015-03-17 21:15:17 +05:00
|
|
|
fprintf(fd, "{");
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderInput *socket : node->inputs) {
|
2015-03-17 21:15:17 +05:00
|
|
|
if (socket != node->inputs[0]) {
|
|
|
|
|
fprintf(fd, "|");
|
|
|
|
|
}
|
2016-05-08 01:32:09 +02:00
|
|
|
fprintf(fd, "<IN_%p>%s", socket, socket->name().c_str());
|
2015-03-17 21:15:17 +05:00
|
|
|
}
|
|
|
|
|
fprintf(fd, "}|");
|
|
|
|
|
}
|
|
|
|
|
fprintf(fd, "%s", node->name.c_str());
|
|
|
|
|
if (node->bump == SHADER_BUMP_CENTER) {
|
|
|
|
|
fprintf(fd, " (bump:center)");
|
|
|
|
|
}
|
|
|
|
|
else if (node->bump == SHADER_BUMP_DX) {
|
|
|
|
|
fprintf(fd, " (bump:dx)");
|
|
|
|
|
}
|
|
|
|
|
else if (node->bump == SHADER_BUMP_DY) {
|
|
|
|
|
fprintf(fd, " (bump:dy)");
|
|
|
|
|
}
|
2024-12-26 17:53:59 +01:00
|
|
|
if (!node->outputs.empty()) {
|
2015-03-17 21:15:17 +05:00
|
|
|
fprintf(fd, "|{");
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderOutput *socket : node->outputs) {
|
2015-03-17 21:15:17 +05:00
|
|
|
if (socket != node->outputs[0]) {
|
|
|
|
|
fprintf(fd, "|");
|
|
|
|
|
}
|
2016-05-08 01:32:09 +02:00
|
|
|
fprintf(fd, "<OUT_%p>%s", socket, socket->name().c_str());
|
2015-03-17 21:15:17 +05:00
|
|
|
}
|
|
|
|
|
fprintf(fd, "}");
|
|
|
|
|
}
|
|
|
|
|
fprintf(fd, "}\"]");
|
2014-09-19 15:21:32 +06:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-12-26 19:41:25 +01:00
|
|
|
for (ShaderNode *node : nodes) {
|
|
|
|
|
for (ShaderOutput *output : node->outputs) {
|
|
|
|
|
for (ShaderInput *input : output->links) {
|
2014-09-19 15:21:32 +06:00
|
|
|
fprintf(fd,
|
2015-03-17 21:15:17 +05:00
|
|
|
"// CONNECTION: OUT_%p->IN_%p (%s:%s)\n",
|
2014-09-19 15:21:32 +06:00
|
|
|
output,
|
|
|
|
|
input,
|
2016-05-08 01:32:09 +02:00
|
|
|
output->name().c_str(),
|
|
|
|
|
input->name().c_str());
|
2014-09-19 15:21:32 +06:00
|
|
|
fprintf(fd,
|
2015-04-08 01:13:16 +05:00
|
|
|
"\"%p\":\"OUT_%p\":e -> \"%p\":\"IN_%p\":w [label=\"\"]\n",
|
2014-09-19 15:21:32 +06:00
|
|
|
output->parent,
|
2015-03-17 21:15:17 +05:00
|
|
|
output,
|
2014-09-19 15:21:32 +06:00
|
|
|
input->parent,
|
2015-03-17 21:15:17 +05:00
|
|
|
input);
|
2014-09-19 15:21:32 +06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2014-09-19 15:21:32 +06:00
|
|
|
fprintf(fd, "}\n");
|
|
|
|
|
fclose(fd);
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 11:58:34 +00:00
|
|
|
CCL_NAMESPACE_END
|