1993 lines
77 KiB
C++
1993 lines
77 KiB
C++
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#include "scene/shader.h"
|
|
#include "kernel/svm/types.h"
|
|
#include "scene/background.h"
|
|
#include "scene/integrator.h"
|
|
#include "scene/light.h"
|
|
#include "scene/osl.h"
|
|
#include "scene/scene.h"
|
|
#include "scene/shader_graph.h"
|
|
#include "scene/shader_nodes.h"
|
|
|
|
#include "blender/image.h"
|
|
#include "blender/sync.h"
|
|
#include "blender/util.h"
|
|
|
|
#include "util/log.h"
|
|
#include "util/set.h"
|
|
#include "util/string.h"
|
|
#include "util/task.h"
|
|
|
|
#include "BLI_listbase.h"
|
|
|
|
#include "BKE_duplilist.hh"
|
|
#include "BKE_node.hh"
|
|
#include "BKE_node_runtime.hh"
|
|
|
|
#include "NOD_shader.h"
|
|
#include "NOD_shader_nodes_inline.hh"
|
|
#include "NOD_shader_raycast.hh"
|
|
|
|
#include "DEG_depsgraph_query.hh"
|
|
|
|
#include "DNA_light_types.h"
|
|
#include "DNA_material_types.h"
|
|
#include "DNA_world_types.h"
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
using PtrInputMap = unordered_multimap<void *, ShaderInput *>;
|
|
using PtrOutputMap = map<void *, ShaderOutput *>;
|
|
using ProxyMap = map<string, ConvertNode *>;
|
|
|
|
/* Find */
|
|
|
|
void BlenderSync::find_shader(const blender::ID *id,
|
|
array<Node *> &used_shaders,
|
|
Shader *default_shader)
|
|
{
|
|
Shader *synced_shader = (id) ? shader_map.find(id) : nullptr;
|
|
Shader *shader = (synced_shader) ? synced_shader : default_shader;
|
|
|
|
used_shaders.push_back_slow(shader);
|
|
shader->tag_used(scene);
|
|
}
|
|
|
|
/* RNA translation utilities */
|
|
|
|
static VolumeSampling get_volume_sampling(blender::PointerRNA &ptr)
|
|
{
|
|
return (VolumeSampling)get_enum(
|
|
ptr, "volume_sampling", VOLUME_NUM_SAMPLING, VOLUME_SAMPLING_DISTANCE);
|
|
}
|
|
|
|
static VolumeInterpolation get_volume_interpolation(blender::PointerRNA &ptr)
|
|
{
|
|
return (VolumeInterpolation)get_enum(
|
|
ptr, "volume_interpolation", VOLUME_NUM_INTERPOLATION, VOLUME_INTERPOLATION_LINEAR);
|
|
}
|
|
|
|
static EmissionSampling get_emission_sampling(blender::PointerRNA &ptr)
|
|
{
|
|
return (EmissionSampling)get_enum(
|
|
ptr, "emission_sampling", EMISSION_SAMPLING_NUM, EMISSION_SAMPLING_AUTO);
|
|
}
|
|
|
|
static int validate_enum_value(const int value, const int num_values, const int default_value)
|
|
{
|
|
if (value >= num_values) {
|
|
return default_value;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static DisplacementMethod get_displacement_method(blender::Material &b_mat)
|
|
{
|
|
const int value = b_mat.displacement_method;
|
|
return (DisplacementMethod)validate_enum_value(value, DISPLACE_NUM_METHODS, DISPLACE_BUMP);
|
|
}
|
|
|
|
template<typename NodeType> static InterpolationType get_image_interpolation(NodeType &b_node)
|
|
{
|
|
const int value = b_node.interpolation;
|
|
return (InterpolationType)validate_enum_value(
|
|
value, INTERPOLATION_NUM_TYPES, INTERPOLATION_LINEAR);
|
|
}
|
|
|
|
template<typename NodeType> static ExtensionType get_image_extension(NodeType &b_node)
|
|
{
|
|
const int value = b_node.extension;
|
|
return (ExtensionType)validate_enum_value(value, EXTENSION_NUM_TYPES, EXTENSION_REPEAT);
|
|
}
|
|
|
|
static ImageAlphaType get_image_alpha_type(blender::Image &b_image)
|
|
{
|
|
const int value = b_image.alpha_mode;
|
|
return (ImageAlphaType)validate_enum_value(value, IMAGE_ALPHA_NUM_TYPES, IMAGE_ALPHA_AUTO);
|
|
}
|
|
|
|
/* Attribute name translation utilities */
|
|
|
|
/* Since Eevee needs to know whether the attribute is uniform or varying
|
|
* at the time it compiles the shader for the material, Blender had to
|
|
* introduce different namespaces (types) in its attribute node. However,
|
|
* Cycles already has object attributes that form a uniform namespace with
|
|
* the more common varying attributes. Without completely reworking the
|
|
* attribute handling in Cycles to introduce separate namespaces (this could
|
|
* be especially hard for OSL which directly uses the name string), the
|
|
* space identifier has to be added to the attribute name as a prefix.
|
|
*
|
|
* The prefixes include a control character to ensure the user specified
|
|
* name can't accidentally include a special prefix.
|
|
*/
|
|
|
|
static const string_view object_attr_prefix("\x01object:");
|
|
static const string_view instancer_attr_prefix("\x01instancer:");
|
|
static const string_view view_layer_attr_prefix("\x01layer:");
|
|
|
|
static ustring blender_attribute_name_add_type(const string &name, int type)
|
|
{
|
|
switch (type) {
|
|
case blender::SHD_ATTRIBUTE_OBJECT:
|
|
return ustring::concat(object_attr_prefix, name);
|
|
case blender::SHD_ATTRIBUTE_INSTANCER:
|
|
return ustring::concat(instancer_attr_prefix, name);
|
|
case blender::SHD_ATTRIBUTE_VIEW_LAYER:
|
|
return ustring::concat(view_layer_attr_prefix, name);
|
|
default:
|
|
return ustring(name);
|
|
}
|
|
}
|
|
|
|
int blender_attribute_name_split_type(ustring name, string *r_real_name)
|
|
{
|
|
const string_view sname(name);
|
|
|
|
if (sname.substr(0, object_attr_prefix.size()) == object_attr_prefix) {
|
|
*r_real_name = sname.substr(object_attr_prefix.size());
|
|
return blender::SHD_ATTRIBUTE_OBJECT;
|
|
}
|
|
|
|
if (sname.substr(0, instancer_attr_prefix.size()) == instancer_attr_prefix) {
|
|
*r_real_name = sname.substr(instancer_attr_prefix.size());
|
|
return blender::SHD_ATTRIBUTE_INSTANCER;
|
|
}
|
|
|
|
if (sname.substr(0, view_layer_attr_prefix.size()) == view_layer_attr_prefix) {
|
|
*r_real_name = sname.substr(view_layer_attr_prefix.size());
|
|
return blender::SHD_ATTRIBUTE_VIEW_LAYER;
|
|
}
|
|
|
|
return blender::SHD_ATTRIBUTE_GEOMETRY;
|
|
}
|
|
|
|
/* Graph */
|
|
|
|
static ustring get_node_input_string(const blender::bNode &b_node, const string &name)
|
|
{
|
|
const blender::bNodeSocket *b_sock = b_node.input_by_identifier(blender::UString(name));
|
|
BLI_assert(b_sock->type == blender::SOCK_STRING);
|
|
const auto &default_value = *b_sock->default_value_typed<blender::bNodeSocketValueString>();
|
|
return ustring(default_value.value);
|
|
}
|
|
|
|
static float3 get_node_output_rgba(blender::bNode &b_node, const string &name)
|
|
{
|
|
blender::bNodeSocket *b_sock = b_node.output_by_identifier(blender::UString(name));
|
|
BLI_assert(b_sock->type == blender::SOCK_RGBA);
|
|
const auto &default_value = *b_sock->default_value_typed<blender::bNodeSocketValueRGBA>();
|
|
return make_float3(default_value.value[0], default_value.value[1], default_value.value[2]);
|
|
}
|
|
|
|
static float get_node_output_value(blender::bNode &b_node, const string &name)
|
|
{
|
|
blender::bNodeSocket *b_sock = b_node.output_by_identifier(blender::UString(name));
|
|
BLI_assert(b_sock->type == blender::SOCK_FLOAT);
|
|
const auto &default_value = *b_sock->default_value_typed<blender::bNodeSocketValueFloat>();
|
|
return default_value.value;
|
|
}
|
|
|
|
static float3 get_node_output_vector(blender::bNode &b_node, const string &name)
|
|
{
|
|
blender::bNodeSocket *b_sock = b_node.output_by_identifier(blender::UString(name));
|
|
BLI_assert(b_sock->type == blender::SOCK_VECTOR);
|
|
const auto &default_value = *b_sock->default_value_typed<blender::bNodeSocketValueVector>();
|
|
return make_float3(default_value.value[0], default_value.value[1], default_value.value[2]);
|
|
}
|
|
|
|
static SocketType::Type convert_socket_type(const blender::bNodeSocket &b_socket)
|
|
{
|
|
switch (b_socket.type) {
|
|
case blender::SOCK_FLOAT:
|
|
return SocketType::FLOAT;
|
|
case blender::SOCK_BOOLEAN:
|
|
case blender::SOCK_INT:
|
|
return SocketType::INT;
|
|
case blender::SOCK_VECTOR:
|
|
return SocketType::VECTOR;
|
|
case blender::SOCK_RGBA:
|
|
return SocketType::COLOR;
|
|
case blender::SOCK_STRING:
|
|
return SocketType::STRING;
|
|
case blender::SOCK_SHADER:
|
|
return SocketType::CLOSURE;
|
|
|
|
default:
|
|
return SocketType::UNDEFINED;
|
|
}
|
|
}
|
|
|
|
static void set_default_value(ShaderInput *input,
|
|
blender::bNodeSocket &b_sock,
|
|
blender::Main &b_data,
|
|
blender::ID &b_id)
|
|
{
|
|
Node *node = input->parent;
|
|
const SocketType &socket = input->socket_type;
|
|
|
|
/* copy values for non linked inputs */
|
|
switch (input->type()) {
|
|
case SocketType::FLOAT: {
|
|
const auto &default_value = *b_sock.default_value_typed<blender::bNodeSocketValueFloat>();
|
|
node->set(socket, default_value.value);
|
|
break;
|
|
}
|
|
case SocketType::INT: {
|
|
if (b_sock.type == blender::SOCK_BOOLEAN) {
|
|
const auto &default_value =
|
|
*b_sock.default_value_typed<blender::bNodeSocketValueBoolean>();
|
|
/* Make sure to call the int overload of set() since this is an integer socket as far as
|
|
* Cycles is concerned. */
|
|
node->set(socket, default_value.value ? 1 : 0);
|
|
}
|
|
else {
|
|
const auto &default_value = *b_sock.default_value_typed<blender::bNodeSocketValueInt>();
|
|
node->set(socket, default_value.value);
|
|
}
|
|
break;
|
|
}
|
|
case SocketType::COLOR: {
|
|
const auto &default_value = *b_sock.default_value_typed<blender::bNodeSocketValueRGBA>();
|
|
const float *value = default_value.value;
|
|
node->set(socket, make_float3(value[0], value[1], value[2]));
|
|
break;
|
|
}
|
|
case SocketType::NORMAL:
|
|
case SocketType::POINT:
|
|
case SocketType::VECTOR: {
|
|
const auto &default_value = *b_sock.default_value_typed<blender::bNodeSocketValueVector>();
|
|
const float *value = default_value.value;
|
|
node->set(socket, make_float3(value[0], value[1], value[2]));
|
|
break;
|
|
}
|
|
case SocketType::STRING: {
|
|
const auto &default_value = *b_sock.default_value_typed<blender::bNodeSocketValueString>();
|
|
node->set(socket,
|
|
(ustring)blender_absolute_path(b_data, &b_id, default_value.value).c_str());
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void get_tex_mapping(TextureNode *mapping, const blender::TexMapping *b_mapping)
|
|
{
|
|
if (!b_mapping) {
|
|
return;
|
|
}
|
|
|
|
mapping->set_tex_mapping_translation(
|
|
make_float3(b_mapping->loc[0], b_mapping->loc[1], b_mapping->loc[2]));
|
|
mapping->set_tex_mapping_rotation(
|
|
make_float3(b_mapping->rot[0], b_mapping->rot[1], b_mapping->rot[2]));
|
|
mapping->set_tex_mapping_scale(
|
|
make_float3(b_mapping->size[0], b_mapping->size[1], b_mapping->size[2]));
|
|
mapping->set_tex_mapping_type((TextureMapping::Type)b_mapping->type);
|
|
|
|
mapping->set_tex_mapping_x_mapping((TextureMapping::Mapping)b_mapping->projx);
|
|
mapping->set_tex_mapping_y_mapping((TextureMapping::Mapping)b_mapping->projy);
|
|
mapping->set_tex_mapping_z_mapping((TextureMapping::Mapping)b_mapping->projz);
|
|
}
|
|
|
|
static bool is_image_animated(blender::eImageSource b_image_source,
|
|
blender::ImageUser &b_image_user)
|
|
{
|
|
return (b_image_source == blender::IMA_SRC_MOVIE ||
|
|
b_image_source == blender::IMA_SRC_SEQUENCE) &&
|
|
(b_image_user.flag & blender::IMA_ANIM_ALWAYS) != 0;
|
|
}
|
|
|
|
static std::optional<RaycastNode::AttributeOutputType> raycast_get_attribute_output_type(
|
|
const blender::eCustomDataType data_type)
|
|
{
|
|
switch (data_type) {
|
|
case blender::CD_PROP_FLOAT:
|
|
return RaycastNode::ATTR_OUTPUT_FLOAT;
|
|
case blender::CD_PROP_FLOAT3:
|
|
case blender::CD_PROP_COLOR:
|
|
return RaycastNode::ATTR_OUTPUT_FLOAT3;
|
|
default:
|
|
break;
|
|
}
|
|
LOG_DFATAL << "Unhandled data type " << int(data_type);
|
|
return std::nullopt;
|
|
}
|
|
|
|
static void raycast_add_output_attribute_sockets(RaycastNode *raycast,
|
|
const blender::bNode &b_node)
|
|
{
|
|
auto *storage = static_cast<blender::NodeShaderRaycast *>(b_node.storage);
|
|
for (const blender::NodeRaycastSampleAttributeItem &item :
|
|
blender::Span(storage->sample_attribute_items, storage->sample_attribute_items_num))
|
|
{
|
|
using blender::nodes::RaycastSampleAttributeItemsAccessor;
|
|
|
|
const std::optional<RaycastNode::AttributeOutputType> attribute_output_type =
|
|
raycast_get_attribute_output_type(blender::eCustomDataType(item.data_type));
|
|
if (!attribute_output_type) {
|
|
continue;
|
|
}
|
|
|
|
const string input_identifier(
|
|
RaycastSampleAttributeItemsAccessor::input_socket_identifier_for_item(item));
|
|
const ustring output_identifier(
|
|
RaycastSampleAttributeItemsAccessor::output_socket_identifier_for_item(item));
|
|
|
|
const ustring attribute_name = get_node_input_string(b_node, input_identifier);
|
|
|
|
raycast->add_output_attribute_socket(
|
|
attribute_name, *attribute_output_type, output_identifier);
|
|
|
|
/* For color attributes additionally add the corresponding Alpha socket.
|
|
* This is because colors are RGB, access to Alpha needs special handling. */
|
|
if (item.data_type == blender::CD_PROP_COLOR) {
|
|
const ustring alpha_output_identifier(
|
|
RaycastSampleAttributeItemsAccessor::output_socket_identifier_for_item_alpha(item));
|
|
raycast->add_output_attribute_socket(
|
|
attribute_name, RaycastNode::ATTR_OUTPUT_FLOAT_ALPHA, alpha_output_identifier);
|
|
}
|
|
}
|
|
}
|
|
|
|
static ShaderNode *add_node(Scene *scene,
|
|
blender::RenderEngine &b_engine,
|
|
blender::Main &b_data,
|
|
blender::Scene &b_scene,
|
|
ShaderGraph *graph,
|
|
blender::bNodeTree &b_ntree,
|
|
blender::bNode &b_node)
|
|
{
|
|
using blender::operator""_ustr;
|
|
ShaderNode *node = nullptr;
|
|
|
|
/* existing blender nodes */
|
|
if (b_node.is_type("ShaderNodeRGBCurve"_ustr)) {
|
|
const auto &mapping = *static_cast<blender::CurveMapping *>(b_node.storage);
|
|
RGBCurvesNode *curves = graph->create_node<RGBCurvesNode>();
|
|
array<packed_float3> curve_mapping_curves;
|
|
float min_x;
|
|
float max_x;
|
|
curvemapping_color_to_array(mapping, curve_mapping_curves, RAMP_TABLE_SIZE, true);
|
|
curvemapping_minmax(mapping, 4, &min_x, &max_x);
|
|
curves->set_min_x(min_x);
|
|
curves->set_max_x(max_x);
|
|
curves->set_curves(curve_mapping_curves);
|
|
curves->set_extrapolate((mapping.flag & blender::CUMA_EXTEND_EXTRAPOLATE) != 0);
|
|
node = curves;
|
|
}
|
|
if (b_node.is_type("ShaderNodeVectorCurve"_ustr)) {
|
|
const auto &mapping = *static_cast<blender::CurveMapping *>(b_node.storage);
|
|
VectorCurvesNode *curves = graph->create_node<VectorCurvesNode>();
|
|
array<packed_float3> curve_mapping_curves;
|
|
float min_x;
|
|
float max_x;
|
|
curvemapping_color_to_array(mapping, curve_mapping_curves, RAMP_TABLE_SIZE, false);
|
|
curvemapping_minmax(mapping, 3, &min_x, &max_x);
|
|
curves->set_min_x(min_x);
|
|
curves->set_max_x(max_x);
|
|
curves->set_curves(curve_mapping_curves);
|
|
curves->set_extrapolate((mapping.flag & blender::CUMA_EXTEND_EXTRAPOLATE) != 0);
|
|
node = curves;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeFloatCurve"_ustr)) {
|
|
const auto &mapping = *static_cast<blender::CurveMapping *>(b_node.storage);
|
|
FloatCurveNode *curve = graph->create_node<FloatCurveNode>();
|
|
array<float> curve_mapping_curve;
|
|
float min_x;
|
|
float max_x;
|
|
curvemapping_float_to_array(mapping, curve_mapping_curve, RAMP_TABLE_SIZE);
|
|
curvemapping_minmax(mapping, 1, &min_x, &max_x);
|
|
curve->set_min_x(min_x);
|
|
curve->set_max_x(max_x);
|
|
curve->set_curve(curve_mapping_curve);
|
|
curve->set_extrapolate((mapping.flag & blender::CUMA_EXTEND_EXTRAPOLATE) != 0);
|
|
node = curve;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeValToRGB"_ustr)) {
|
|
RGBRampNode *ramp = graph->create_node<RGBRampNode>();
|
|
const auto &b_color_ramp = *static_cast<blender::ColorBand *>(b_node.storage);
|
|
array<packed_float3> ramp_values;
|
|
array<float> ramp_alpha;
|
|
colorramp_to_array(b_color_ramp, ramp_values, ramp_alpha, RAMP_TABLE_SIZE);
|
|
ramp->set_ramp(ramp_values);
|
|
ramp->set_ramp_alpha(ramp_alpha);
|
|
ramp->set_interpolate(b_color_ramp.ipotype != blender::COLBAND_INTERP_CONSTANT);
|
|
node = ramp;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeRGB"_ustr)) {
|
|
ColorNode *color = graph->create_node<ColorNode>();
|
|
color->set_value(get_node_output_rgba(b_node, "Color"));
|
|
node = color;
|
|
}
|
|
else if (b_node.is_type("FunctionNodeInputVector"_ustr)) {
|
|
ColorNode *value = graph->create_node<ColorNode>();
|
|
const auto &storage = *static_cast<const blender::NodeInputVector *>(b_node.storage);
|
|
value->set_value(make_float3(storage.vector[0], storage.vector[1], storage.vector[2]));
|
|
node = value;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeValue"_ustr)) {
|
|
ValueNode *value = graph->create_node<ValueNode>();
|
|
value->set_value(get_node_output_value(b_node, "Value"));
|
|
node = value;
|
|
}
|
|
else if (b_node.is_type("FunctionNodeInputBool"_ustr)) {
|
|
ValueNode *value = graph->create_node<ValueNode>();
|
|
const auto &storage = *static_cast<const blender::NodeInputBool *>(b_node.storage);
|
|
value->set_value(bool(storage.boolean));
|
|
node = value;
|
|
}
|
|
else if (b_node.is_type("FunctionNodeInputInt"_ustr)) {
|
|
ValueNode *value = graph->create_node<ValueNode>();
|
|
const auto &storage = *static_cast<const blender::NodeInputInt *>(b_node.storage);
|
|
value->set_value(storage.integer);
|
|
node = value;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeCameraData"_ustr)) {
|
|
node = graph->create_node<CameraNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeInvert"_ustr)) {
|
|
node = graph->create_node<InvertNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeGamma"_ustr)) {
|
|
node = graph->create_node<GammaNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBrightContrast"_ustr)) {
|
|
node = graph->create_node<BrightContrastNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeMixRGB"_ustr)) {
|
|
MixNode *mix = graph->create_node<MixNode>();
|
|
mix->set_mix_type((NodeMix)b_node.custom1);
|
|
mix->set_use_clamp(b_node.custom2 & blender::SHD_MIXRGB_CLAMP);
|
|
node = mix;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeMix"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeShaderMix *>(b_node.storage);
|
|
if (storage.data_type == blender::SOCK_VECTOR) {
|
|
if (storage.factor_mode == blender::NODE_MIX_MODE_UNIFORM) {
|
|
MixVectorNode *mix_node = graph->create_node<MixVectorNode>();
|
|
mix_node->set_use_clamp(storage.clamp_factor);
|
|
node = mix_node;
|
|
}
|
|
else {
|
|
MixVectorNonUniformNode *mix_node = graph->create_node<MixVectorNonUniformNode>();
|
|
mix_node->set_use_clamp(storage.clamp_factor);
|
|
node = mix_node;
|
|
}
|
|
}
|
|
else if (storage.data_type == blender::SOCK_RGBA) {
|
|
MixColorNode *mix_node = graph->create_node<MixColorNode>();
|
|
mix_node->set_blend_type((NodeMix)storage.blend_type);
|
|
mix_node->set_use_clamp(storage.clamp_factor);
|
|
mix_node->set_use_clamp_result(storage.clamp_result);
|
|
node = mix_node;
|
|
}
|
|
else {
|
|
MixFloatNode *mix_node = graph->create_node<MixFloatNode>();
|
|
mix_node->set_use_clamp(storage.clamp_factor);
|
|
node = mix_node;
|
|
}
|
|
}
|
|
else if (b_node.is_type("ShaderNodeSeparateColor"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeCombSepColor *>(b_node.storage);
|
|
SeparateColorNode *separate_node = graph->create_node<SeparateColorNode>();
|
|
separate_node->set_color_type((NodeCombSepColorType)storage.mode);
|
|
node = separate_node;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeCombineColor"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeCombSepColor *>(b_node.storage);
|
|
CombineColorNode *combine_node = graph->create_node<CombineColorNode>();
|
|
combine_node->set_color_type((NodeCombSepColorType)storage.mode);
|
|
node = combine_node;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeSeparateXYZ"_ustr)) {
|
|
node = graph->create_node<SeparateXYZNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeCombineXYZ"_ustr)) {
|
|
node = graph->create_node<CombineXYZNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeHueSaturation"_ustr)) {
|
|
node = graph->create_node<HSVNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeRGBToBW"_ustr)) {
|
|
node = graph->create_node<RGBToBWNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeMapRange"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeMapRange *>(b_node.storage);
|
|
if (storage.data_type == blender::CD_PROP_FLOAT3) {
|
|
VectorMapRangeNode *vector_map_range_node = graph->create_node<VectorMapRangeNode>();
|
|
vector_map_range_node->set_use_clamp(storage.clamp);
|
|
vector_map_range_node->set_range_type((NodeMapRangeType)storage.interpolation_type);
|
|
node = vector_map_range_node;
|
|
}
|
|
else {
|
|
MapRangeNode *map_range_node = graph->create_node<MapRangeNode>();
|
|
map_range_node->set_clamp(storage.clamp);
|
|
map_range_node->set_range_type((NodeMapRangeType)storage.interpolation_type);
|
|
node = map_range_node;
|
|
}
|
|
}
|
|
else if (b_node.is_type("ShaderNodeClamp"_ustr)) {
|
|
ClampNode *clamp_node = graph->create_node<ClampNode>();
|
|
clamp_node->set_clamp_type((NodeClampType)b_node.custom1);
|
|
node = clamp_node;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeMath"_ustr)) {
|
|
MathNode *math_node = graph->create_node<MathNode>();
|
|
math_node->set_math_type((NodeMathType)b_node.custom1);
|
|
math_node->set_use_clamp(b_node.custom2);
|
|
node = math_node;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeVectorMath"_ustr)) {
|
|
VectorMathNode *vector_math_node = graph->create_node<VectorMathNode>();
|
|
vector_math_node->set_math_type((NodeVectorMathType)b_node.custom1);
|
|
node = vector_math_node;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeVectorRotate"_ustr)) {
|
|
VectorRotateNode *vector_rotate_node = graph->create_node<VectorRotateNode>();
|
|
vector_rotate_node->set_rotate_type((NodeVectorRotateType)b_node.custom1);
|
|
vector_rotate_node->set_invert(b_node.custom2);
|
|
node = vector_rotate_node;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeVectorTransform"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeShaderVectTransform *>(b_node.storage);
|
|
VectorTransformNode *vtransform = graph->create_node<VectorTransformNode>();
|
|
vtransform->set_transform_type((NodeVectorTransformType)storage.type);
|
|
vtransform->set_convert_from((NodeVectorTransformConvertSpace)storage.convert_from);
|
|
vtransform->set_convert_to((NodeVectorTransformConvertSpace)storage.convert_to);
|
|
node = vtransform;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeNormal"_ustr)) {
|
|
NormalNode *norm = graph->create_node<NormalNode>();
|
|
norm->set_direction(get_node_output_vector(b_node, "Normal"));
|
|
node = norm;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeMapping"_ustr)) {
|
|
MappingNode *mapping = graph->create_node<MappingNode>();
|
|
mapping->set_mapping_type((NodeMappingType)b_node.custom1);
|
|
node = mapping;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeFresnel"_ustr)) {
|
|
node = graph->create_node<FresnelNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeLayerWeight"_ustr)) {
|
|
node = graph->create_node<LayerWeightNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeAddShader"_ustr)) {
|
|
node = graph->create_node<AddClosureNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeMixShader"_ustr)) {
|
|
node = graph->create_node<MixClosureNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeAttribute"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeShaderAttribute *>(b_node.storage);
|
|
AttributeNode *attr = graph->create_node<AttributeNode>();
|
|
attr->set_attribute(blender_attribute_name_add_type(storage.name, storage.type));
|
|
node = attr;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBackground"_ustr)) {
|
|
node = graph->create_node<BackgroundNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeHoldout"_ustr)) {
|
|
node = graph->create_node<HoldoutNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfDiffuse"_ustr)) {
|
|
node = graph->create_node<DiffuseBsdfNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeSubsurfaceScattering"_ustr)) {
|
|
SubsurfaceScatteringNode *subsurface = graph->create_node<SubsurfaceScatteringNode>();
|
|
|
|
switch (b_node.custom1) {
|
|
case blender::SHD_SUBSURFACE_BURLEY:
|
|
subsurface->set_method(CLOSURE_BSSRDF_BURLEY_ID);
|
|
break;
|
|
case blender::SHD_SUBSURFACE_RANDOM_WALK:
|
|
subsurface->set_method(CLOSURE_BSSRDF_RANDOM_WALK_ID);
|
|
break;
|
|
case blender::SHD_SUBSURFACE_RANDOM_WALK_SKIN:
|
|
subsurface->set_method(CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID);
|
|
break;
|
|
case blender::SHD_SUBSURFACE_RANDOM_WALK_LEGACY:
|
|
subsurface->set_method(CLOSURE_BSSRDF_RANDOM_WALK_LEGACY_ID);
|
|
break;
|
|
}
|
|
|
|
node = subsurface;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfMetallic"_ustr)) {
|
|
MetallicBsdfNode *metal = graph->create_node<MetallicBsdfNode>();
|
|
|
|
switch (b_node.custom1) {
|
|
case blender::SHD_GLOSSY_BECKMANN:
|
|
metal->set_distribution(CLOSURE_BSDF_MICROFACET_BECKMANN_ID);
|
|
break;
|
|
case blender::SHD_GLOSSY_GGX:
|
|
metal->set_distribution(CLOSURE_BSDF_MICROFACET_GGX_ID);
|
|
break;
|
|
case blender::SHD_GLOSSY_MULTI_GGX:
|
|
metal->set_distribution(CLOSURE_BSDF_MICROFACET_MULTI_GGX_ID);
|
|
break;
|
|
}
|
|
|
|
switch (b_node.custom2) {
|
|
case blender::SHD_PHYSICAL_CONDUCTOR:
|
|
metal->set_fresnel_type(CLOSURE_BSDF_PHYSICAL_CONDUCTOR);
|
|
break;
|
|
case blender::SHD_CONDUCTOR_F82:
|
|
metal->set_fresnel_type(CLOSURE_BSDF_F82_CONDUCTOR);
|
|
break;
|
|
}
|
|
node = metal;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfAnisotropic"_ustr)) {
|
|
GlossyBsdfNode *glossy = graph->create_node<GlossyBsdfNode>();
|
|
|
|
switch (b_node.custom1) {
|
|
case blender::SHD_GLOSSY_BECKMANN:
|
|
glossy->set_distribution(CLOSURE_BSDF_MICROFACET_BECKMANN_ID);
|
|
break;
|
|
case blender::SHD_GLOSSY_GGX:
|
|
glossy->set_distribution(CLOSURE_BSDF_MICROFACET_GGX_ID);
|
|
break;
|
|
case blender::SHD_GLOSSY_ASHIKHMIN_SHIRLEY:
|
|
glossy->set_distribution(CLOSURE_BSDF_ASHIKHMIN_SHIRLEY_ID);
|
|
break;
|
|
case blender::SHD_GLOSSY_MULTI_GGX:
|
|
glossy->set_distribution(CLOSURE_BSDF_MICROFACET_MULTI_GGX_ID);
|
|
break;
|
|
}
|
|
node = glossy;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfGlass"_ustr)) {
|
|
GlassBsdfNode *glass = graph->create_node<GlassBsdfNode>();
|
|
switch (b_node.custom1) {
|
|
case blender::SHD_GLOSSY_BECKMANN:
|
|
glass->set_distribution(CLOSURE_BSDF_MICROFACET_BECKMANN_GLASS_ID);
|
|
break;
|
|
case blender::SHD_GLOSSY_GGX:
|
|
glass->set_distribution(CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID);
|
|
break;
|
|
case blender::SHD_GLOSSY_MULTI_GGX:
|
|
glass->set_distribution(CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID);
|
|
break;
|
|
}
|
|
node = glass;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfRefraction"_ustr)) {
|
|
RefractionBsdfNode *refraction = graph->create_node<RefractionBsdfNode>();
|
|
switch (b_node.custom1) {
|
|
case blender::SHD_GLOSSY_BECKMANN:
|
|
refraction->set_distribution(CLOSURE_BSDF_MICROFACET_BECKMANN_REFRACTION_ID);
|
|
break;
|
|
case blender::SHD_GLOSSY_GGX:
|
|
refraction->set_distribution(CLOSURE_BSDF_MICROFACET_GGX_REFRACTION_ID);
|
|
break;
|
|
}
|
|
node = refraction;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfToon"_ustr)) {
|
|
ToonBsdfNode *toon = graph->create_node<ToonBsdfNode>();
|
|
switch (b_node.custom1) {
|
|
case blender::SHD_TOON_DIFFUSE:
|
|
toon->set_component(CLOSURE_BSDF_DIFFUSE_TOON_ID);
|
|
break;
|
|
case blender::SHD_TOON_GLOSSY:
|
|
toon->set_component(CLOSURE_BSDF_GLOSSY_TOON_ID);
|
|
break;
|
|
}
|
|
node = toon;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfHair"_ustr)) {
|
|
HairBsdfNode *hair = graph->create_node<HairBsdfNode>();
|
|
switch (b_node.custom1) {
|
|
case blender::SHD_HAIR_REFLECTION:
|
|
hair->set_component(CLOSURE_BSDF_HAIR_REFLECTION_ID);
|
|
break;
|
|
case blender::SHD_HAIR_TRANSMISSION:
|
|
hair->set_component(CLOSURE_BSDF_HAIR_TRANSMISSION_ID);
|
|
break;
|
|
}
|
|
node = hair;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfHairPrincipled"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeShaderHairPrincipled *>(b_node.storage);
|
|
PrincipledHairBsdfNode *principled_hair = graph->create_node<PrincipledHairBsdfNode>();
|
|
principled_hair->set_model((NodePrincipledHairModel)validate_enum_value(
|
|
storage.model, NODE_PRINCIPLED_HAIR_MODEL_NUM, NODE_PRINCIPLED_HAIR_HUANG));
|
|
principled_hair->set_parametrization((NodePrincipledHairParametrization)validate_enum_value(
|
|
storage.parametrization,
|
|
NODE_PRINCIPLED_HAIR_PARAMETRIZATION_NUM,
|
|
NODE_PRINCIPLED_HAIR_REFLECTANCE));
|
|
node = principled_hair;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfPrincipled"_ustr)) {
|
|
PrincipledBsdfNode *principled = graph->create_node<PrincipledBsdfNode>();
|
|
switch (b_node.custom1) {
|
|
case blender::SHD_GLOSSY_GGX:
|
|
principled->set_distribution(CLOSURE_BSDF_MICROFACET_GGX_GLASS_ID);
|
|
break;
|
|
case blender::SHD_GLOSSY_MULTI_GGX:
|
|
principled->set_distribution(CLOSURE_BSDF_MICROFACET_MULTI_GGX_GLASS_ID);
|
|
break;
|
|
}
|
|
switch (b_node.custom2) {
|
|
case blender::SHD_SUBSURFACE_BURLEY:
|
|
principled->set_subsurface_method(CLOSURE_BSSRDF_BURLEY_ID);
|
|
break;
|
|
case blender::SHD_SUBSURFACE_RANDOM_WALK:
|
|
principled->set_subsurface_method(CLOSURE_BSSRDF_RANDOM_WALK_ID);
|
|
break;
|
|
case blender::SHD_SUBSURFACE_RANDOM_WALK_SKIN:
|
|
principled->set_subsurface_method(CLOSURE_BSSRDF_RANDOM_WALK_SKIN_ID);
|
|
break;
|
|
case blender::SHD_SUBSURFACE_RANDOM_WALK_LEGACY:
|
|
principled->set_subsurface_method(CLOSURE_BSSRDF_RANDOM_WALK_LEGACY_ID);
|
|
break;
|
|
}
|
|
node = principled;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfTranslucent"_ustr)) {
|
|
node = graph->create_node<TranslucentBsdfNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfTransparent"_ustr)) {
|
|
node = graph->create_node<TransparentBsdfNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfRayPortal"_ustr)) {
|
|
node = graph->create_node<RayPortalBsdfNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBsdfSheen"_ustr)) {
|
|
SheenBsdfNode *sheen = graph->create_node<SheenBsdfNode>();
|
|
switch (b_node.custom1) {
|
|
case SHD_SHEEN_ASHIKHMIN:
|
|
sheen->set_distribution(CLOSURE_BSDF_ASHIKHMIN_VELVET_ID);
|
|
break;
|
|
case SHD_SHEEN_MICROFIBER:
|
|
sheen->set_distribution(CLOSURE_BSDF_SHEEN_ID);
|
|
break;
|
|
}
|
|
node = sheen;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeEmission"_ustr)) {
|
|
node = graph->create_node<EmissionNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeAmbientOcclusion"_ustr)) {
|
|
AmbientOcclusionNode *ao = graph->create_node<AmbientOcclusionNode>();
|
|
ao->set_samples(b_node.custom1);
|
|
ao->set_inside(b_node.custom2 & blender::SHD_AO_INSIDE);
|
|
ao->set_only_local(b_node.custom2 & blender::SHD_AO_LOCAL);
|
|
node = ao;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeVolumeScatter"_ustr)) {
|
|
ScatterVolumeNode *scatter = graph->create_node<ScatterVolumeNode>();
|
|
switch (b_node.custom1) {
|
|
case blender::SHD_PHASE_HENYEY_GREENSTEIN:
|
|
scatter->set_phase(CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID);
|
|
break;
|
|
case blender::SHD_PHASE_FOURNIER_FORAND:
|
|
scatter->set_phase(CLOSURE_VOLUME_FOURNIER_FORAND_ID);
|
|
break;
|
|
case blender::SHD_PHASE_DRAINE:
|
|
scatter->set_phase(CLOSURE_VOLUME_DRAINE_ID);
|
|
break;
|
|
case blender::SHD_PHASE_RAYLEIGH:
|
|
scatter->set_phase(CLOSURE_VOLUME_RAYLEIGH_ID);
|
|
break;
|
|
case blender::SHD_PHASE_MIE:
|
|
scatter->set_phase(CLOSURE_VOLUME_MIE_ID);
|
|
break;
|
|
}
|
|
node = scatter;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeVolumeAbsorption"_ustr)) {
|
|
node = graph->create_node<AbsorptionVolumeNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeVolumeCoefficients"_ustr)) {
|
|
VolumeCoefficientsNode *coeffs = graph->create_node<VolumeCoefficientsNode>();
|
|
switch (b_node.custom1) {
|
|
case blender::SHD_PHASE_HENYEY_GREENSTEIN:
|
|
coeffs->set_phase(CLOSURE_VOLUME_HENYEY_GREENSTEIN_ID);
|
|
break;
|
|
case blender::SHD_PHASE_FOURNIER_FORAND:
|
|
coeffs->set_phase(CLOSURE_VOLUME_FOURNIER_FORAND_ID);
|
|
break;
|
|
case blender::SHD_PHASE_DRAINE:
|
|
coeffs->set_phase(CLOSURE_VOLUME_DRAINE_ID);
|
|
break;
|
|
case blender::SHD_PHASE_RAYLEIGH:
|
|
coeffs->set_phase(CLOSURE_VOLUME_RAYLEIGH_ID);
|
|
break;
|
|
case blender::SHD_PHASE_MIE:
|
|
coeffs->set_phase(CLOSURE_VOLUME_MIE_ID);
|
|
break;
|
|
}
|
|
node = coeffs;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeVolumePrincipled"_ustr)) {
|
|
PrincipledVolumeNode *principled = graph->create_node<PrincipledVolumeNode>();
|
|
node = principled;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeNewGeometry"_ustr)) {
|
|
node = graph->create_node<GeometryNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeWireframe"_ustr)) {
|
|
WireframeNode *wire = graph->create_node<WireframeNode>();
|
|
wire->set_use_pixel_size(b_node.custom1);
|
|
node = wire;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeWavelength"_ustr)) {
|
|
node = graph->create_node<WavelengthNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBlackbody"_ustr)) {
|
|
node = graph->create_node<BlackbodyNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeLightPath"_ustr)) {
|
|
node = graph->create_node<LightPathNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeLightFalloff"_ustr)) {
|
|
node = graph->create_node<LightFalloffNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeObjectInfo"_ustr)) {
|
|
node = graph->create_node<ObjectInfoNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeParticleInfo"_ustr)) {
|
|
node = graph->create_node<ParticleInfoNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeHairInfo"_ustr)) {
|
|
node = graph->create_node<HairInfoNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodePointInfo"_ustr)) {
|
|
node = graph->create_node<PointInfoNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeVolumeInfo"_ustr)) {
|
|
node = graph->create_node<VolumeInfoNode>();
|
|
}
|
|
else if (b_node.is_type("ShaderNodeVertexColor"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeShaderVertexColor *>(b_node.storage);
|
|
VertexColorNode *vertex_color_node = graph->create_node<VertexColorNode>();
|
|
vertex_color_node->set_layer_name(ustring(storage.layer_name));
|
|
node = vertex_color_node;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBump"_ustr)) {
|
|
BumpNode *bump = graph->create_node<BumpNode>();
|
|
bump->set_invert(b_node.custom1);
|
|
node = bump;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeScript"_ustr)) {
|
|
#ifdef WITH_OSL
|
|
const auto &storage = *static_cast<blender::NodeShaderScript *>(b_node.storage);
|
|
if (scene->shader_manager->use_osl()) {
|
|
const string bytecode_hash = storage.bytecode_hash;
|
|
if (!bytecode_hash.empty()) {
|
|
node = OSLShaderManager::osl_node(graph, scene, "", bytecode_hash, storage.bytecode);
|
|
}
|
|
else {
|
|
const string absolute_filepath = blender_absolute_path(
|
|
b_data, &b_ntree.id, storage.filepath);
|
|
node = OSLShaderManager::osl_node(graph, scene, absolute_filepath, "");
|
|
}
|
|
}
|
|
#else
|
|
(void)b_data;
|
|
(void)b_ntree;
|
|
#endif
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexImage"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeTexImage *>(b_node.storage);
|
|
blender::Image *b_image = blender::id_cast<blender::Image *>(b_node.id);
|
|
blender::ImageUser &b_image_user = const_cast<blender::ImageUser &>(storage.iuser);
|
|
ImageTextureNode *image = graph->create_node<ImageTextureNode>();
|
|
|
|
image->set_interpolation(get_image_interpolation(storage));
|
|
image->set_extension(get_image_extension(storage));
|
|
image->set_projection((NodeImageProjection)storage.projection);
|
|
image->set_projection_blend(storage.projection_blend);
|
|
const blender::TexMapping &b_texture_mapping = storage.base.tex_mapping;
|
|
get_tex_mapping(image, &b_texture_mapping);
|
|
|
|
if (b_image) {
|
|
const blender::eImageSource b_image_source = blender::eImageSource(b_image->source);
|
|
blender::PointerRNA image_rna_ptr = RNA_id_pointer_create(&b_image->id);
|
|
image->set_colorspace(ustring(b_image->colorspace_settings.name));
|
|
image->set_animated(is_image_animated(b_image_source, b_image_user));
|
|
image->set_alpha_type(get_image_alpha_type(*b_image));
|
|
|
|
if (b_image_source == blender::IMA_SRC_TILED) {
|
|
array<int> tiles;
|
|
for (blender::ImageTile &b_tile : b_image->tiles) {
|
|
tiles.push_back_slow(b_tile.tile_number);
|
|
}
|
|
image->set_tiles(tiles);
|
|
}
|
|
|
|
/* builtin images will use callback-based reading because
|
|
* they could only be loaded correct from blender side
|
|
*/
|
|
const bool is_builtin = image_is_builtin(*b_image, b_engine);
|
|
|
|
if (is_builtin) {
|
|
/* for builtin images we're using image datablock name to find an image to
|
|
* read pixels from later
|
|
*
|
|
* also store frame number as well, so there's no differences in handling
|
|
* builtin names for packed images and movies
|
|
*/
|
|
const int scene_frame = b_scene.r.cfra;
|
|
const int image_frame = image_user_frame_number(b_image_user, *b_image, scene_frame);
|
|
if (b_image_source != blender::IMA_SRC_TILED) {
|
|
image->handle = scene->image_manager->add_image(
|
|
make_unique<BlenderImageLoader>(b_image,
|
|
&b_image_user,
|
|
image_frame,
|
|
0,
|
|
(b_engine.flag & blender::RE_ENGINE_PREVIEW) != 0),
|
|
image->image_params());
|
|
}
|
|
else {
|
|
vector<unique_ptr<ImageLoader>> loaders;
|
|
loaders.reserve(image->get_tiles().size());
|
|
for (const int tile_number : image->get_tiles()) {
|
|
loaders.push_back(make_unique<BlenderImageLoader>(
|
|
b_image,
|
|
&b_image_user,
|
|
image_frame,
|
|
tile_number,
|
|
(b_engine.flag & blender::RE_ENGINE_PREVIEW) != 0));
|
|
}
|
|
|
|
image->handle = scene->image_manager->add_image(std::move(loaders),
|
|
image->image_params());
|
|
}
|
|
}
|
|
else {
|
|
const ustring filename = ustring(
|
|
image_user_file_path(b_data, b_image_user, *b_image, b_scene.r.cfra));
|
|
image->set_filename(filename);
|
|
}
|
|
}
|
|
node = image;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexEnvironment"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeTexEnvironment *>(b_node.storage);
|
|
blender::Image *b_image = blender::id_cast<blender::Image *>(b_node.id);
|
|
blender::ImageUser &b_image_user = const_cast<blender::ImageUser &>(storage.iuser);
|
|
EnvironmentTextureNode *env = graph->create_node<EnvironmentTextureNode>();
|
|
|
|
env->set_interpolation(get_image_interpolation(storage));
|
|
env->set_projection((NodeEnvironmentProjection)storage.projection);
|
|
const blender::TexMapping &b_texture_mapping = storage.base.tex_mapping;
|
|
get_tex_mapping(env, &b_texture_mapping);
|
|
|
|
if (b_image) {
|
|
const blender::eImageSource b_image_source = blender::eImageSource(b_image->source);
|
|
blender::PointerRNA image_rna_ptr = RNA_id_pointer_create(&b_image->id);
|
|
env->set_colorspace(ustring(b_image->colorspace_settings.name));
|
|
env->set_animated(is_image_animated(b_image_source, b_image_user));
|
|
env->set_alpha_type(get_image_alpha_type(*b_image));
|
|
|
|
const bool is_builtin = image_is_builtin(*b_image, b_engine);
|
|
|
|
if (is_builtin) {
|
|
const int scene_frame = b_scene.r.cfra;
|
|
const int image_frame = image_user_frame_number(b_image_user, *b_image, scene_frame);
|
|
env->handle = scene->image_manager->add_image(
|
|
make_unique<BlenderImageLoader>(b_image,
|
|
&b_image_user,
|
|
image_frame,
|
|
0,
|
|
(b_engine.flag & blender::RE_ENGINE_PREVIEW) != 0),
|
|
env->image_params());
|
|
}
|
|
else {
|
|
env->set_filename(
|
|
ustring(image_user_file_path(b_data, b_image_user, *b_image, b_scene.r.cfra)));
|
|
}
|
|
}
|
|
node = env;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexGradient"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeTexGradient *>(b_node.storage);
|
|
GradientTextureNode *gradient = graph->create_node<GradientTextureNode>();
|
|
gradient->set_gradient_type((NodeGradientType)storage.gradient_type);
|
|
const blender::TexMapping &b_texture_mapping = storage.base.tex_mapping;
|
|
get_tex_mapping(gradient, &b_texture_mapping);
|
|
node = gradient;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexVoronoi"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeTexVoronoi *>(b_node.storage);
|
|
VoronoiTextureNode *voronoi = graph->create_node<VoronoiTextureNode>();
|
|
voronoi->set_dimensions(storage.dimensions);
|
|
voronoi->set_feature((NodeVoronoiFeature)storage.feature);
|
|
voronoi->set_metric((NodeVoronoiDistanceMetric)storage.distance);
|
|
voronoi->set_use_normalize(storage.normalize);
|
|
const blender::TexMapping &b_texture_mapping = storage.base.tex_mapping;
|
|
get_tex_mapping(voronoi, &b_texture_mapping);
|
|
node = voronoi;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexMagic"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeTexMagic *>(b_node.storage);
|
|
MagicTextureNode *magic = graph->create_node<MagicTextureNode>();
|
|
magic->set_depth(storage.depth);
|
|
const blender::TexMapping &b_texture_mapping = storage.base.tex_mapping;
|
|
get_tex_mapping(magic, &b_texture_mapping);
|
|
node = magic;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexWave"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeTexWave *>(b_node.storage);
|
|
WaveTextureNode *wave = graph->create_node<WaveTextureNode>();
|
|
wave->set_wave_type((NodeWaveType)storage.wave_type);
|
|
wave->set_bands_direction((NodeWaveBandsDirection)storage.bands_direction);
|
|
wave->set_rings_direction((NodeWaveRingsDirection)storage.rings_direction);
|
|
wave->set_profile((NodeWaveProfile)storage.wave_profile);
|
|
const blender::TexMapping &b_texture_mapping = storage.base.tex_mapping;
|
|
get_tex_mapping(wave, &b_texture_mapping);
|
|
node = wave;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexChecker"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeTexChecker *>(b_node.storage);
|
|
CheckerTextureNode *checker = graph->create_node<CheckerTextureNode>();
|
|
const blender::TexMapping &b_texture_mapping = storage.base.tex_mapping;
|
|
get_tex_mapping(checker, &b_texture_mapping);
|
|
node = checker;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexBrick"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeTexBrick *>(b_node.storage);
|
|
BrickTextureNode *brick = graph->create_node<BrickTextureNode>();
|
|
brick->set_offset(storage.offset);
|
|
brick->set_offset_frequency(storage.offset_freq);
|
|
brick->set_squash(storage.squash);
|
|
brick->set_squash_frequency(storage.squash_freq);
|
|
const blender::TexMapping &b_texture_mapping = storage.base.tex_mapping;
|
|
get_tex_mapping(brick, &b_texture_mapping);
|
|
node = brick;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexNoise"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeTexNoise *>(b_node.storage);
|
|
NoiseTextureNode *noise = graph->create_node<NoiseTextureNode>();
|
|
noise->set_dimensions(storage.dimensions);
|
|
noise->set_type((NodeNoiseType)storage.type);
|
|
noise->set_use_normalize(storage.normalize);
|
|
const blender::TexMapping &b_texture_mapping = storage.base.tex_mapping;
|
|
get_tex_mapping(noise, &b_texture_mapping);
|
|
node = noise;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexGabor"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeTexGabor *>(b_node.storage);
|
|
GaborTextureNode *gabor = graph->create_node<GaborTextureNode>();
|
|
gabor->set_type((NodeGaborType)storage.type);
|
|
const blender::TexMapping &b_texture_mapping = storage.base.tex_mapping;
|
|
get_tex_mapping(gabor, &b_texture_mapping);
|
|
node = gabor;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexCoord"_ustr)) {
|
|
TextureCoordinateNode *tex_coord = graph->create_node<TextureCoordinateNode>();
|
|
tex_coord->set_from_dupli(b_node.custom1);
|
|
if (const blender::ID *b_object = b_node.id) {
|
|
tex_coord->set_use_transform(true);
|
|
tex_coord->set_ob_tfm(
|
|
get_transform(blender::id_cast<const blender::Object *>(b_object)->object_to_world()));
|
|
}
|
|
node = tex_coord;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexSky"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeTexSky *>(b_node.storage);
|
|
SkyTextureNode *sky = graph->create_node<SkyTextureNode>();
|
|
sky->set_sky_type((NodeSkyType)storage.sky_model);
|
|
sky->set_sun_direction(normalize(make_float3(
|
|
storage.sun_direction[0], storage.sun_direction[1], storage.sun_direction[2])));
|
|
sky->set_turbidity(storage.turbidity);
|
|
sky->set_ground_albedo(storage.ground_albedo);
|
|
sky->set_sun_disc(storage.sun_disc);
|
|
sky->set_sun_size(storage.sun_size);
|
|
sky->set_sun_intensity(storage.sun_intensity);
|
|
sky->set_sun_elevation(storage.sun_elevation);
|
|
sky->set_sun_rotation(storage.sun_rotation);
|
|
sky->set_altitude(storage.altitude);
|
|
sky->set_air_density(storage.air_density);
|
|
sky->set_aerosol_density(storage.aerosol_density);
|
|
sky->set_ozone_density(storage.ozone_density);
|
|
const blender::TexMapping &b_texture_mapping = storage.base.tex_mapping;
|
|
get_tex_mapping(sky, &b_texture_mapping);
|
|
node = sky;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexIES"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeShaderTexIES *>(b_node.storage);
|
|
IESLightNode *ies = graph->create_node<IESLightNode>();
|
|
switch (storage.mode) {
|
|
case blender::NODE_IES_EXTERNAL:
|
|
ies->set_filename(ustring(blender_absolute_path(b_data, &b_ntree.id, storage.filepath)));
|
|
break;
|
|
case blender::NODE_IES_INTERNAL:
|
|
ustring ies_content = ustring(get_text_datablock_content(b_node.id));
|
|
ies->set_ies(ies_content);
|
|
break;
|
|
}
|
|
node = ies;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTexWhiteNoise"_ustr)) {
|
|
WhiteNoiseTextureNode *white_noise_node = graph->create_node<WhiteNoiseTextureNode>();
|
|
white_noise_node->set_dimensions(b_node.custom1);
|
|
node = white_noise_node;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeNormalMap"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeShaderNormalMap *>(b_node.storage);
|
|
NormalMapNode *nmap = graph->create_node<NormalMapNode>();
|
|
nmap->set_space(NodeNormalMapSpace(storage.space));
|
|
nmap->set_attribute(ustring(storage.uv_map));
|
|
nmap->set_convention(NodeNormalMapConvention(storage.convention));
|
|
nmap->set_base(NodeNormalMapBase(storage.base));
|
|
node = nmap;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeRadialTiling"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeRadialTiling *>(b_node.storage);
|
|
RadialTilingNode *radial_tiling = graph->create_node<RadialTilingNode>();
|
|
radial_tiling->set_use_normalize(storage.normalize);
|
|
node = radial_tiling;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeTangent"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeShaderTangent *>(b_node.storage);
|
|
TangentNode *tangent = graph->create_node<TangentNode>();
|
|
tangent->set_direction_type((NodeTangentDirectionType)storage.direction_type);
|
|
tangent->set_axis((NodeTangentAxis)storage.axis);
|
|
tangent->set_attribute(ustring(storage.uv_map));
|
|
node = tangent;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeUVMap"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeShaderUVMap *>(b_node.storage);
|
|
UVMapNode *uvm = graph->create_node<UVMapNode>();
|
|
uvm->set_attribute(ustring(storage.uv_map));
|
|
uvm->set_from_dupli(b_node.custom1);
|
|
node = uvm;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeBevel"_ustr)) {
|
|
BevelNode *bevel = graph->create_node<BevelNode>();
|
|
bevel->set_samples(b_node.custom1);
|
|
node = bevel;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeDisplacement"_ustr)) {
|
|
DisplacementNode *disp = graph->create_node<DisplacementNode>();
|
|
disp->set_space((NodeNormalMapSpace)b_node.custom1);
|
|
node = disp;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeVectorDisplacement"_ustr)) {
|
|
VectorDisplacementNode *disp = graph->create_node<VectorDisplacementNode>();
|
|
disp->set_space((NodeNormalMapSpace)b_node.custom1);
|
|
disp->set_attribute(ustring(""));
|
|
node = disp;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeOutputAOV"_ustr)) {
|
|
const auto &storage = *static_cast<blender::NodeShaderOutputAOV *>(b_node.storage);
|
|
OutputAOVNode *aov = graph->create_node<OutputAOVNode>();
|
|
aov->set_name(ustring(storage.name));
|
|
node = aov;
|
|
}
|
|
else if (b_node.is_type("ShaderNodeRaycast"_ustr)) {
|
|
RaycastNode *raycast = graph->create_node<RaycastNode>();
|
|
raycast->set_only_local(b_node.custom1);
|
|
raycast_add_output_attribute_sockets(raycast, b_node);
|
|
node = raycast;
|
|
}
|
|
else if (b_node.is_type("GeometryNodeInputSceneTime"_ustr)) {
|
|
node = graph->create_node<SceneTimeNode>();
|
|
}
|
|
|
|
if (node) {
|
|
node->name = b_node.name;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
static bool node_use_modified_socket_name(ShaderNode *node)
|
|
{
|
|
if (node->special_type == SHADER_SPECIAL_TYPE_OSL) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static ShaderInput *node_find_input_by_name(const blender::bNode &b_node,
|
|
ShaderNode *node,
|
|
blender::bNodeSocket &b_socket)
|
|
{
|
|
using blender::operator""_ustr;
|
|
string name = b_socket.identifier;
|
|
ShaderInput *input = node->input(name.c_str());
|
|
|
|
if (!input && node_use_modified_socket_name(node)) {
|
|
/* Different internal name for shader. */
|
|
if (string_startswith(name, "Shader")) {
|
|
string_replace(name, "Shader", "Closure");
|
|
}
|
|
|
|
/* Map mix node internal name for shader. */
|
|
if (b_node.is_type("ShaderNodeMix"_ustr)) {
|
|
if (string_endswith(name, "Factor_Float")) {
|
|
string_replace(name, "Factor_Float", "Factor");
|
|
}
|
|
else if (string_endswith(name, "Factor_Vector")) {
|
|
string_replace(name, "Factor_Vector", "Factor");
|
|
}
|
|
else if (string_endswith(name, "A_Float")) {
|
|
string_replace(name, "A_Float", "A");
|
|
}
|
|
else if (string_endswith(name, "B_Float")) {
|
|
string_replace(name, "B_Float", "B");
|
|
}
|
|
else if (string_endswith(name, "A_Color")) {
|
|
string_replace(name, "A_Color", "A");
|
|
}
|
|
else if (string_endswith(name, "B_Color")) {
|
|
string_replace(name, "B_Color", "B");
|
|
}
|
|
else if (string_endswith(name, "A_Vector")) {
|
|
string_replace(name, "A_Vector", "A");
|
|
}
|
|
else if (string_endswith(name, "B_Vector")) {
|
|
string_replace(name, "B_Vector", "B");
|
|
}
|
|
}
|
|
|
|
input = node->input(name.c_str());
|
|
|
|
if (!input) {
|
|
/* Different internal numbering of two sockets with same name.
|
|
* Note that the Blender convention for unique socket names changed
|
|
* from . to _ at some point, so we check both to handle old files. */
|
|
if (string_endswith(name, "_001")) {
|
|
string_replace(name, "_001", "2");
|
|
}
|
|
else if (string_endswith(name, ".001")) {
|
|
string_replace(name, ".001", "2");
|
|
}
|
|
else if (string_endswith(name, "_002")) {
|
|
string_replace(name, "_002", "3");
|
|
}
|
|
else if (string_endswith(name, ".002")) {
|
|
string_replace(name, ".002", "3");
|
|
}
|
|
else {
|
|
name += "1";
|
|
}
|
|
|
|
input = node->input(name.c_str());
|
|
}
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
static ShaderOutput *node_find_output_by_name(blender::bNode &b_node,
|
|
ShaderNode *node,
|
|
blender::bNodeSocket &b_socket)
|
|
{
|
|
using blender::operator""_ustr;
|
|
string name = b_socket.identifier;
|
|
ShaderOutput *output = node->output(name.c_str());
|
|
|
|
if (!output && node_use_modified_socket_name(node)) {
|
|
/* Different internal name for shader. */
|
|
if (name == "Shader") {
|
|
name = "Closure";
|
|
output = node->output(name.c_str());
|
|
}
|
|
/* Map internal name for shader. */
|
|
if (b_node.is_type("ShaderNodeMix"_ustr)) {
|
|
if (string_endswith(name, "Result_Float")) {
|
|
string_replace(name, "Result_Float", "Result");
|
|
output = node->output(name.c_str());
|
|
}
|
|
else if (string_endswith(name, "Result_Color")) {
|
|
string_replace(name, "Result_Color", "Result");
|
|
output = node->output(name.c_str());
|
|
}
|
|
else if (string_endswith(name, "Result_Vector")) {
|
|
string_replace(name, "Result_Vector", "Result");
|
|
output = node->output(name.c_str());
|
|
}
|
|
}
|
|
else if (b_node.is_type("FunctionNodeInputVector"_ustr)) {
|
|
/* FunctionNodeInputVector has an output called "Vector", and it uses ColorNode Cycles node
|
|
* that has an output called "Color". */
|
|
if (name == "Vector") {
|
|
name = "Color";
|
|
output = node->output(name.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
static void add_nodes(Scene *scene,
|
|
blender::RenderEngine &b_engine,
|
|
blender::Main &b_data,
|
|
blender::Scene &b_scene,
|
|
ShaderGraph *graph,
|
|
blender::bNodeTree &b_ntree,
|
|
const ProxyMap &proxy_input_map,
|
|
const ProxyMap &proxy_output_map);
|
|
|
|
static void add_nodes_inlined(Scene *scene,
|
|
blender::RenderEngine &b_engine,
|
|
blender::Main &b_data,
|
|
blender::Scene &b_scene,
|
|
ShaderGraph *graph,
|
|
blender::bNodeTree &b_ntree,
|
|
const ProxyMap &proxy_input_map,
|
|
const ProxyMap &proxy_output_map)
|
|
{
|
|
using blender::operator""_ustr;
|
|
/* add nodes */
|
|
PtrInputMap input_map;
|
|
PtrOutputMap output_map;
|
|
|
|
/* find the node to use for output if there are multiple */
|
|
const blender::bNode *output_node = ntreeShaderOutputNode(&b_ntree, blender::SHD_OUTPUT_CYCLES);
|
|
|
|
/* add nodes */
|
|
for (blender::bNode *b_node : b_ntree.all_nodes()) {
|
|
if (b_node->is_muted() || b_node->is_reroute()) {
|
|
/* replace muted node with internal links */
|
|
for (blender::bNodeLink &b_link : b_node->runtime->internal_links) {
|
|
blender::bNodeSocket *to_socket = b_link.tosock;
|
|
const SocketType::Type to_socket_type = convert_socket_type(*to_socket);
|
|
if (to_socket_type == SocketType::UNDEFINED) {
|
|
continue;
|
|
}
|
|
|
|
ConvertNode *proxy = graph->create_node<ConvertNode>(to_socket_type, to_socket_type, true);
|
|
|
|
/* Muted nodes can result in multiple Cycles input sockets mapping to the same Blender
|
|
* input socket, so this needs to be a multimap. */
|
|
input_map.emplace(b_link.fromsock, proxy->inputs[0]);
|
|
output_map[b_link.tosock] = proxy->outputs[0];
|
|
}
|
|
}
|
|
else if (b_node->is_group()) {
|
|
blender::bNodeTree *b_group_ntree = blender::id_cast<blender::bNodeTree *>(b_node->id);
|
|
|
|
ProxyMap group_proxy_input_map;
|
|
ProxyMap group_proxy_output_map;
|
|
|
|
/* Add a proxy node for each socket
|
|
* Do this even if the node group has no internal tree,
|
|
* so that links have something to connect to and assert won't fail.
|
|
*/
|
|
for (blender::bNodeSocket *b_input : b_node->input_sockets()) {
|
|
const SocketType::Type input_type = convert_socket_type(*b_input);
|
|
if (input_type == SocketType::UNDEFINED) {
|
|
continue;
|
|
}
|
|
|
|
ConvertNode *proxy = graph->create_node<ConvertNode>(input_type, input_type, true);
|
|
|
|
/* register the proxy node for internal binding */
|
|
group_proxy_input_map[b_input->identifier] = proxy;
|
|
|
|
input_map.emplace(b_input, proxy->inputs[0]);
|
|
|
|
set_default_value(proxy->inputs[0], *b_input, b_data, b_ntree.id);
|
|
}
|
|
for (blender::bNodeSocket *b_output : b_node->output_sockets()) {
|
|
const SocketType::Type output_type = convert_socket_type(*b_output);
|
|
if (output_type == SocketType::UNDEFINED) {
|
|
continue;
|
|
}
|
|
|
|
ConvertNode *proxy = graph->create_node<ConvertNode>(output_type, output_type, true);
|
|
|
|
/* register the proxy node for internal binding */
|
|
group_proxy_output_map[b_output->identifier] = proxy;
|
|
|
|
output_map[b_output] = proxy->outputs[0];
|
|
}
|
|
|
|
if (b_group_ntree) {
|
|
add_nodes(scene,
|
|
b_engine,
|
|
b_data,
|
|
b_scene,
|
|
graph,
|
|
*b_group_ntree,
|
|
group_proxy_input_map,
|
|
group_proxy_output_map);
|
|
}
|
|
}
|
|
else if (b_node->is_type("NodeGroupInput"_ustr)) {
|
|
/* map each socket to a proxy node */
|
|
for (blender::bNodeSocket *b_output : b_node->output_sockets()) {
|
|
const ProxyMap::const_iterator proxy_it = proxy_input_map.find(b_output->identifier);
|
|
if (proxy_it != proxy_input_map.end()) {
|
|
ConvertNode *proxy = proxy_it->second;
|
|
|
|
output_map[b_output] = proxy->outputs[0];
|
|
}
|
|
}
|
|
}
|
|
else if (b_node->is_type("NodeGroupOutput"_ustr)) {
|
|
/* only the active group output is used */
|
|
if (b_node->flag & blender::NODE_DO_OUTPUT) {
|
|
/* map each socket to a proxy node */
|
|
for (blender::bNodeSocket *b_input : b_node->input_sockets()) {
|
|
const ProxyMap::const_iterator proxy_it = proxy_output_map.find(b_input->identifier);
|
|
if (proxy_it != proxy_output_map.end()) {
|
|
ConvertNode *proxy = proxy_it->second;
|
|
|
|
input_map.emplace(b_input, proxy->inputs[0]);
|
|
|
|
set_default_value(proxy->inputs[0], *b_input, b_data, b_ntree.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* TODO: All the previous cases can be removed? */
|
|
else {
|
|
ShaderNode *node = nullptr;
|
|
|
|
if (b_node == output_node) {
|
|
node = graph->output();
|
|
}
|
|
else {
|
|
node = add_node(scene, b_engine, b_data, b_scene, graph, b_ntree, *b_node);
|
|
}
|
|
|
|
if (node) {
|
|
/* map node sockets for linking */
|
|
for (blender::bNodeSocket *b_input : b_node->input_sockets()) {
|
|
if (!b_input->is_available()) {
|
|
/* Skip unavailable sockets. */
|
|
continue;
|
|
}
|
|
ShaderInput *input = node_find_input_by_name(*b_node, node, *b_input);
|
|
if (!input) {
|
|
/* XXX should not happen, report error? */
|
|
continue;
|
|
}
|
|
input_map.emplace(b_input, input);
|
|
|
|
set_default_value(input, *b_input, b_data, b_ntree.id);
|
|
}
|
|
for (blender::bNodeSocket *b_output : b_node->output_sockets()) {
|
|
if (!b_output->is_available()) {
|
|
/* Skip unavailable sockets. */
|
|
continue;
|
|
}
|
|
ShaderOutput *output = node_find_output_by_name(*b_node, node, *b_output);
|
|
if (!output) {
|
|
/* XXX should not happen, report error? */
|
|
continue;
|
|
}
|
|
output_map[b_output] = output;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* connect nodes */
|
|
for (blender::bNodeLink *b_link : b_ntree.all_links()) {
|
|
/* Ignore invalid links to avoid unwanted cycles created in graph.
|
|
* Also ignore links with unavailable sockets. */
|
|
if (!((b_link->flag & blender::NODE_LINK_VALID) != 0 && b_link->fromsock->is_available() &&
|
|
b_link->tosock->is_available()) ||
|
|
b_link->is_muted())
|
|
{
|
|
continue;
|
|
}
|
|
/* get blender link data */
|
|
blender::bNodeSocket *b_from_sock = b_link->fromsock;
|
|
blender::bNodeSocket *b_to_sock = b_link->tosock;
|
|
|
|
ShaderOutput *output = nullptr;
|
|
const PtrOutputMap::iterator output_it = output_map.find(b_from_sock);
|
|
if (output_it != output_map.end()) {
|
|
output = output_it->second;
|
|
}
|
|
|
|
/* either socket may be nullptr when the node was not exported, typically
|
|
* because the node type is not supported */
|
|
if (output != nullptr) {
|
|
ShaderOutput *output = output_it->second;
|
|
auto inputs = input_map.equal_range(b_to_sock);
|
|
for (PtrInputMap::iterator input_it = inputs.first; input_it != inputs.second; ++input_it) {
|
|
ShaderInput *input = input_it->second;
|
|
if (input != nullptr) {
|
|
graph->connect(output, input);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void add_nodes(Scene *scene,
|
|
blender::RenderEngine &b_engine,
|
|
blender::Main &b_data,
|
|
blender::Scene &b_scene,
|
|
ShaderGraph *graph,
|
|
blender::bNodeTree &b_ntree,
|
|
const ProxyMap &proxy_input_map,
|
|
const ProxyMap &proxy_output_map)
|
|
{
|
|
blender::bNodeTree *localtree = blender::bke::node_tree_add_tree(
|
|
nullptr, (blender::StringRef(b_ntree.id.name) + " Inlined").c_str(), b_ntree.idname);
|
|
blender::nodes::InlineShaderNodeTreeParams inline_params;
|
|
inline_params.allow_preserving_repeat_zones = false;
|
|
inline_params.target_engine_ = blender::SHD_OUTPUT_CYCLES;
|
|
blender::nodes::inline_shader_node_tree(b_ntree, *localtree, inline_params);
|
|
|
|
add_nodes_inlined(
|
|
scene, b_engine, b_data, b_scene, graph, *localtree, proxy_input_map, proxy_output_map);
|
|
|
|
BKE_id_free(nullptr, &localtree->id);
|
|
}
|
|
|
|
static void add_nodes(Scene *scene,
|
|
blender::RenderEngine &b_engine,
|
|
blender::Main &b_data,
|
|
blender::Scene &b_scene,
|
|
ShaderGraph *graph,
|
|
blender::bNodeTree &b_ntree)
|
|
{
|
|
static const ProxyMap empty_proxy_map;
|
|
add_nodes(scene, b_engine, b_data, b_scene, graph, b_ntree, empty_proxy_map, empty_proxy_map);
|
|
}
|
|
|
|
/* Look up and constant fold all references to View Layer attributes. */
|
|
void BlenderSync::resolve_view_layer_attributes(Shader *shader,
|
|
ShaderGraph *graph,
|
|
blender::Depsgraph &b_depsgraph)
|
|
{
|
|
bool updated = false;
|
|
|
|
for (ShaderNode *node : graph->nodes) {
|
|
if (node->is_a(AttributeNode::get_node_type())) {
|
|
AttributeNode *attr_node = static_cast<AttributeNode *>(node);
|
|
|
|
std::string real_name;
|
|
const int type = blender_attribute_name_split_type(attr_node->get_attribute(), &real_name);
|
|
|
|
if (type == blender::SHD_ATTRIBUTE_VIEW_LAYER) {
|
|
/* Look up the value. */
|
|
const blender::ViewLayer *b_layer = DEG_get_evaluated_view_layer(&b_depsgraph);
|
|
const blender::Scene *b_scene = DEG_get_evaluated_scene(&b_depsgraph);
|
|
float4 value;
|
|
|
|
BKE_view_layer_find_rgba_attribute(b_scene, b_layer, real_name.c_str(), &value.x);
|
|
|
|
/* Replace all outgoing links, using appropriate output types. */
|
|
const float val_avg = (value.x + value.y + value.z) / 3.0f;
|
|
|
|
for (ShaderOutput *output : node->outputs) {
|
|
float val_float;
|
|
float3 val_float3;
|
|
|
|
if (output->type() == SocketType::FLOAT) {
|
|
val_float = (output->name() == "Alpha") ? value.w : val_avg;
|
|
val_float3 = make_float3(val_float);
|
|
}
|
|
else {
|
|
val_float = val_avg;
|
|
val_float3 = make_float3(value);
|
|
}
|
|
|
|
for (ShaderInput *sock : output->links) {
|
|
if (sock->type() == SocketType::FLOAT) {
|
|
sock->set(val_float);
|
|
}
|
|
else if (SocketType::is_float3(sock->type())) {
|
|
sock->set(val_float3);
|
|
}
|
|
|
|
sock->constant_folded_in = true;
|
|
}
|
|
|
|
graph->disconnect(output);
|
|
}
|
|
|
|
/* Clear the attribute name to avoid further attempts to look up. */
|
|
attr_node->set_attribute(ustring());
|
|
updated = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (updated) {
|
|
shader_map.set_flag(shader, SHADER_WITH_LAYER_ATTRS);
|
|
}
|
|
else {
|
|
shader_map.clear_flag(shader, SHADER_WITH_LAYER_ATTRS);
|
|
}
|
|
}
|
|
|
|
bool BlenderSync::scene_attr_needs_recalc(Shader *shader, blender::Depsgraph &b_depsgraph)
|
|
{
|
|
if (shader && shader_map.test_flag(shader, SHADER_WITH_LAYER_ATTRS)) {
|
|
blender::Scene *scene = DEG_get_evaluated_scene(&b_depsgraph);
|
|
|
|
return shader_map.check_recalc(&scene->id) ||
|
|
shader_map.check_recalc(reinterpret_cast<blender::ID *>(scene->world)) ||
|
|
shader_map.check_recalc(reinterpret_cast<blender::ID *>(scene->camera));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Sync Materials */
|
|
|
|
void BlenderSync::sync_materials(blender::Depsgraph &b_depsgraph,
|
|
bool update_all,
|
|
bool update_time)
|
|
{
|
|
shader_map.set_default(scene->default_surface);
|
|
|
|
TaskPool pool;
|
|
set<Shader *> updated_shaders;
|
|
|
|
/* When consecutive view layers to be rendered have different AOVs or if those AOVs are merely
|
|
* reordered, the AOV offsets in the shaders that have AOV output nodes must be updated via
|
|
* OutputAOVNode::simplify_settings() further down the line.
|
|
* This tracking ensures that the inputs of the AOV output nodes are connected when needed,
|
|
* disconnected when not, and that the offsets, which also depend on the data types, are correct.
|
|
* Storing the new AOV data must take place even if no shaders are affected so the new data
|
|
* is available as the old data when the next view layer is rendered, but the check could be
|
|
* deferred.
|
|
*/
|
|
blender::Vector<std::pair<std::string, int>> new_shader_view_layer_aovs;
|
|
/* Store info on the new AOVs. */
|
|
blender::ViewLayer *const b_view_layer = DEG_get_evaluated_view_layer(&b_depsgraph);
|
|
for (blender::ViewLayerAOV &b_aov : b_view_layer->aovs) {
|
|
if ((b_aov.flag & blender::AOV_CONFLICT) != 0) {
|
|
continue;
|
|
}
|
|
new_shader_view_layer_aovs.append({b_aov.name, b_aov.type});
|
|
}
|
|
const bool aovs_changed_between_view_layers = new_shader_view_layer_aovs !=
|
|
shader_view_layer_aovs;
|
|
|
|
blender::DEGIDIterData data{};
|
|
data.graph = &b_depsgraph;
|
|
ITER_BEGIN (blender::DEG_iterator_ids_begin,
|
|
blender::DEG_iterator_ids_next,
|
|
blender::DEG_iterator_ids_end,
|
|
&data,
|
|
blender::ID *,
|
|
b_id)
|
|
{
|
|
if (GS(b_id->name) != blender::ID_MA) {
|
|
continue;
|
|
}
|
|
|
|
blender::Material &b_mat = blender::id_cast<blender::Material &>(*b_id);
|
|
Shader *shader;
|
|
|
|
/* test if we need to sync */
|
|
if (shader_map.add_or_update(&shader, &b_mat.id) || update_all ||
|
|
scene_attr_needs_recalc(shader, b_depsgraph) || aovs_changed_between_view_layers ||
|
|
(shader->has_time_dependency && update_time))
|
|
{
|
|
unique_ptr<ShaderGraph> graph = make_unique<ShaderGraph>();
|
|
|
|
shader->name = BKE_id_name(b_mat.id);
|
|
shader->set_pass_id(b_mat.index);
|
|
|
|
/* create nodes */
|
|
if (b_mat.nodetree) {
|
|
add_nodes(scene, *b_engine, *b_data, *b_scene, graph.get(), *b_mat.nodetree);
|
|
}
|
|
else {
|
|
DiffuseBsdfNode *diffuse = graph->create_node<DiffuseBsdfNode>();
|
|
diffuse->set_color(make_float3(b_mat.r, b_mat.g, b_mat.b));
|
|
|
|
ShaderNode *out = graph->output();
|
|
graph->connect(diffuse->output("BSDF"), out->input("Surface"));
|
|
}
|
|
|
|
resolve_view_layer_attributes(shader, graph.get(), b_depsgraph);
|
|
|
|
/* settings */
|
|
blender::PointerRNA mat_rna_ptr = RNA_id_pointer_create(&b_mat.id);
|
|
blender::PointerRNA cmat = RNA_pointer_get(&mat_rna_ptr, "cycles");
|
|
shader->set_emission_sampling_method(get_emission_sampling(cmat));
|
|
shader->set_use_transparent_shadow(b_mat.blend_flag & blender::MA_BL_TRANSPARENT_SHADOW);
|
|
shader->set_use_bump_map_correction(get_boolean(cmat, "use_bump_map_correction"));
|
|
shader->set_volume_sampling_method(get_volume_sampling(cmat));
|
|
shader->set_volume_interpolation_method(get_volume_interpolation(cmat));
|
|
shader->set_volume_step_rate(get_float(cmat, "volume_step_rate"));
|
|
shader->set_displacement_method(get_displacement_method(b_mat));
|
|
|
|
shader->set_graph(std::move(graph));
|
|
|
|
/* By simplifying the shader graph as soon as possible, some
|
|
* redundant shader nodes might be removed which prevents loading
|
|
* unnecessary attributes later.
|
|
*
|
|
* However, since graph simplification also accounts for mix
|
|
* weight, this would cause frequent expensive resyncs in interactive
|
|
* sessions, so for those sessions optimization is only performed
|
|
* right before compiling.
|
|
*/
|
|
if (!preview) {
|
|
pool.push([graph = shader->graph.get(), scene = scene] { graph->simplify(scene); });
|
|
/* NOTE: Update shaders out of the threads since those routines
|
|
* are accessing and writing to a global context.
|
|
*/
|
|
updated_shaders.insert(shader);
|
|
}
|
|
else {
|
|
/* NOTE: Update tagging can access links which are being
|
|
* optimized out.
|
|
*/
|
|
shader->tag_update(scene);
|
|
}
|
|
}
|
|
}
|
|
ITER_END;
|
|
|
|
pool.wait_work();
|
|
|
|
/* Info on the new AOVs becomes info on the old AOVs. */
|
|
shader_view_layer_aovs = std::move(new_shader_view_layer_aovs);
|
|
|
|
for (Shader *shader : updated_shaders) {
|
|
shader->tag_update(scene);
|
|
}
|
|
}
|
|
|
|
/* Sync World */
|
|
|
|
void BlenderSync::sync_world(blender::Depsgraph &b_depsgraph,
|
|
blender::bScreen *b_screen,
|
|
blender::View3D *b_v3d,
|
|
bool update_all,
|
|
bool update_time)
|
|
{
|
|
Background *background = scene->background;
|
|
Integrator *integrator = scene->integrator;
|
|
blender::PointerRNA scene_rna_ptr = RNA_id_pointer_create(&b_scene->id);
|
|
blender::PointerRNA cscene = RNA_pointer_get(&scene_rna_ptr, "cycles");
|
|
|
|
blender::World *b_world = view_layer.world_override ? view_layer.world_override : b_scene->world;
|
|
|
|
const BlenderViewportParameters new_viewport_parameters(b_screen, b_v3d, use_developer_ui);
|
|
|
|
Shader *shader = scene->default_background;
|
|
|
|
if (world_recalc || update_all || b_world != world_map ||
|
|
viewport_parameters.shader_modified(new_viewport_parameters) ||
|
|
scene_attr_needs_recalc(shader, b_depsgraph) || (shader->has_time_dependency && update_time))
|
|
{
|
|
unique_ptr<ShaderGraph> graph = make_unique<ShaderGraph>();
|
|
|
|
/* create nodes */
|
|
if (new_viewport_parameters.use_scene_world && b_world && b_world->nodetree) {
|
|
add_nodes(scene, *b_engine, *b_data, *b_scene, graph.get(), *b_world->nodetree);
|
|
|
|
/* volume */
|
|
blender::PointerRNA world_rna_ptr = RNA_id_pointer_create(&b_world->id);
|
|
blender::PointerRNA cworld = RNA_pointer_get(&world_rna_ptr, "cycles");
|
|
shader->set_volume_sampling_method(get_volume_sampling(cworld));
|
|
shader->set_volume_interpolation_method(get_volume_interpolation(cworld));
|
|
shader->set_volume_step_rate(get_float(cworld, "volume_step_size"));
|
|
}
|
|
else if (new_viewport_parameters.use_scene_world && b_world) {
|
|
BackgroundNode *background = graph->create_node<BackgroundNode>();
|
|
background->set_color(make_float3(b_world->horr, b_world->horg, b_world->horb));
|
|
|
|
ShaderNode *out = graph->output();
|
|
graph->connect(background->output("Background"), out->input("Surface"));
|
|
}
|
|
else if (!new_viewport_parameters.use_scene_world) {
|
|
float3 world_color;
|
|
if (b_world) {
|
|
world_color = make_float3(b_world->horr, b_world->horg, b_world->horb);
|
|
}
|
|
else {
|
|
world_color = zero_float3();
|
|
}
|
|
|
|
BackgroundNode *background = graph->create_node<BackgroundNode>();
|
|
LightPathNode *light_path = graph->create_node<LightPathNode>();
|
|
|
|
MixNode *mix_scene_with_background = graph->create_node<MixNode>();
|
|
mix_scene_with_background->set_color2(world_color);
|
|
|
|
EnvironmentTextureNode *texture_environment = graph->create_node<EnvironmentTextureNode>();
|
|
texture_environment->set_tex_mapping_type(TextureMapping::VECTOR);
|
|
float3 rotation_z = texture_environment->get_tex_mapping_rotation();
|
|
rotation_z[2] = new_viewport_parameters.studiolight_rotate_z;
|
|
texture_environment->set_tex_mapping_rotation(rotation_z);
|
|
texture_environment->set_filename(new_viewport_parameters.studiolight_path);
|
|
|
|
MixNode *mix_intensity = graph->create_node<MixNode>();
|
|
mix_intensity->set_mix_type(NODE_MIX_MUL);
|
|
mix_intensity->set_fac(1.0f);
|
|
mix_intensity->set_color2(make_float3(new_viewport_parameters.studiolight_intensity,
|
|
new_viewport_parameters.studiolight_intensity,
|
|
new_viewport_parameters.studiolight_intensity));
|
|
|
|
TextureCoordinateNode *texture_coordinate = graph->create_node<TextureCoordinateNode>();
|
|
|
|
MixNode *mix_background_with_environment = graph->create_node<MixNode>();
|
|
mix_background_with_environment->set_fac(
|
|
new_viewport_parameters.studiolight_background_alpha);
|
|
mix_background_with_environment->set_color1(world_color);
|
|
|
|
ShaderNode *out = graph->output();
|
|
|
|
graph->connect(texture_coordinate->output("Generated"),
|
|
texture_environment->input("Vector"));
|
|
graph->connect(texture_environment->output("Color"), mix_intensity->input("Color1"));
|
|
graph->connect(light_path->output("Is Camera Ray"), mix_scene_with_background->input("Fac"));
|
|
graph->connect(mix_intensity->output("Color"), mix_scene_with_background->input("Color1"));
|
|
graph->connect(mix_intensity->output("Color"),
|
|
mix_background_with_environment->input("Color2"));
|
|
graph->connect(mix_background_with_environment->output("Color"),
|
|
mix_scene_with_background->input("Color2"));
|
|
graph->connect(mix_scene_with_background->output("Color"), background->input("Color"));
|
|
graph->connect(background->output("Background"), out->input("Surface"));
|
|
}
|
|
|
|
/* Visibility */
|
|
if (b_world) {
|
|
blender::PointerRNA world_rna_ptr = RNA_id_pointer_create(&b_world->id);
|
|
blender::PointerRNA cvisibility = RNA_pointer_get(&world_rna_ptr, "cycles_visibility");
|
|
PathRayVisibility visibility = PATH_RAY_VISIBILITY_NONE;
|
|
|
|
visibility |= get_boolean(cvisibility, "camera") ? PATH_RAY_VISIBILITY_CAMERA :
|
|
PATH_RAY_VISIBILITY_NONE;
|
|
visibility |= get_boolean(cvisibility, "diffuse") ? PATH_RAY_VISIBILITY_DIFFUSE :
|
|
PATH_RAY_VISIBILITY_NONE;
|
|
visibility |= get_boolean(cvisibility, "glossy") ? PATH_RAY_VISIBILITY_GLOSSY :
|
|
PATH_RAY_VISIBILITY_NONE;
|
|
visibility |= get_boolean(cvisibility, "transmission") ? PATH_RAY_VISIBILITY_TRANSMIT :
|
|
PATH_RAY_VISIBILITY_NONE;
|
|
visibility |= get_boolean(cvisibility, "scatter") ? PATH_RAY_VISIBILITY_VOLUME_SCATTER :
|
|
PATH_RAY_VISIBILITY_NONE;
|
|
|
|
background->set_visibility(visibility);
|
|
}
|
|
|
|
resolve_view_layer_attributes(shader, graph.get(), b_depsgraph);
|
|
|
|
shader->set_graph(std::move(graph));
|
|
shader->tag_update(scene);
|
|
}
|
|
|
|
/* Fast GI */
|
|
if (b_world) {
|
|
enum { FAST_GI_METHOD_REPLACE = 0, FAST_GI_METHOD_ADD = 1, FAST_GI_METHOD_NUM };
|
|
|
|
const bool use_fast_gi = get_boolean(cscene, "use_fast_gi");
|
|
if (use_fast_gi) {
|
|
const int fast_gi_method = get_enum(
|
|
cscene, "fast_gi_method", FAST_GI_METHOD_NUM, FAST_GI_METHOD_REPLACE);
|
|
integrator->set_ao_factor((fast_gi_method == FAST_GI_METHOD_REPLACE) ? b_world->aoenergy :
|
|
0.0f);
|
|
integrator->set_ao_additive_factor(
|
|
(fast_gi_method == FAST_GI_METHOD_ADD) ? b_world->aoenergy : 0.0f);
|
|
}
|
|
else {
|
|
integrator->set_ao_factor(0.0f);
|
|
integrator->set_ao_additive_factor(0.0f);
|
|
}
|
|
|
|
integrator->set_ao_distance(b_world->aodist);
|
|
}
|
|
else {
|
|
integrator->set_ao_factor(0.0f);
|
|
integrator->set_ao_additive_factor(0.0f);
|
|
integrator->set_ao_distance(10.0f);
|
|
}
|
|
|
|
background->set_transparent((b_scene->r.alphamode & blender::R_ALPHAPREMUL) != 0);
|
|
|
|
if (background->get_transparent()) {
|
|
background->set_transparent_glass(get_boolean(cscene, "film_transparent_glass"));
|
|
background->set_transparent_roughness_threshold(
|
|
get_float(cscene, "film_transparent_roughness"));
|
|
}
|
|
else {
|
|
background->set_transparent_glass(false);
|
|
background->set_transparent_roughness_threshold(0.0f);
|
|
}
|
|
|
|
background->set_use_shader(view_layer.use_background_shader ||
|
|
viewport_parameters.use_custom_shader());
|
|
|
|
background->set_lightgroup(
|
|
ustring((b_world && b_world->lightgroup) ? b_world->lightgroup->name : ""));
|
|
|
|
background->tag_update(scene);
|
|
}
|
|
|
|
/* Sync Lights */
|
|
|
|
void BlenderSync::sync_lights(blender::Depsgraph &b_depsgraph, bool update_all, bool update_time)
|
|
{
|
|
shader_map.set_default(scene->default_light);
|
|
|
|
blender::DEGIDIterData data{};
|
|
data.graph = &b_depsgraph;
|
|
ITER_BEGIN (blender::DEG_iterator_ids_begin,
|
|
blender::DEG_iterator_ids_next,
|
|
blender::DEG_iterator_ids_end,
|
|
&data,
|
|
blender::ID *,
|
|
b_id)
|
|
{
|
|
if (GS(b_id->name) != blender::ID_LA) {
|
|
continue;
|
|
}
|
|
|
|
blender::Light &b_light = blender::id_cast<blender::Light &>(*b_id);
|
|
Shader *shader;
|
|
|
|
/* test if we need to sync */
|
|
if (shader_map.add_or_update(&shader, &b_light.id) || update_all ||
|
|
scene_attr_needs_recalc(shader, b_depsgraph) ||
|
|
(shader->has_time_dependency && update_time))
|
|
{
|
|
unique_ptr<ShaderGraph> graph = make_unique<ShaderGraph>();
|
|
|
|
/* create nodes */
|
|
if (b_light.nodetree) {
|
|
shader->name = BKE_id_name(b_light.id);
|
|
|
|
add_nodes(scene, *b_engine, *b_data, *b_scene, graph.get(), *b_light.nodetree);
|
|
}
|
|
else {
|
|
EmissionNode *emission = graph->create_node<EmissionNode>();
|
|
emission->set_color(one_float3());
|
|
emission->set_strength(1.0f);
|
|
|
|
ShaderNode *out = graph->output();
|
|
graph->connect(emission->output("Emission"), out->input("Surface"));
|
|
}
|
|
|
|
resolve_view_layer_attributes(shader, graph.get(), b_depsgraph);
|
|
|
|
shader->set_graph(std::move(graph));
|
|
shader->tag_update(scene);
|
|
}
|
|
}
|
|
ITER_END;
|
|
}
|
|
|
|
void BlenderSync::sync_shaders(blender::Depsgraph &b_depsgraph,
|
|
blender::bScreen *b_screen,
|
|
blender::View3D *b_v3d,
|
|
bool update_all,
|
|
bool update_time)
|
|
{
|
|
shader_map.pre_sync();
|
|
|
|
sync_world(b_depsgraph, b_screen, b_v3d, update_all, update_time);
|
|
sync_lights(b_depsgraph, update_all, update_time);
|
|
sync_materials(b_depsgraph, update_all, update_time);
|
|
}
|
|
|
|
CCL_NAMESPACE_END
|