diff --git a/source/blender/io/usd/hydra/world.cc b/source/blender/io/usd/hydra/world.cc index 98bd330b78f..037ac1b9f4c 100644 --- a/source/blender/io/usd/hydra/world.cc +++ b/source/blender/io/usd/hydra/world.cc @@ -7,27 +7,23 @@ #include #include +#include #include #include #include #include #include -#include "DNA_node_types.h" #include "DNA_scene_types.h" #include "DNA_world_types.h" -#include "BLI_math_rotation.h" +#include "BLI_math_constants.h" -#include "BKE_node.hh" -#include "BKE_node_legacy_types.hh" -#include "BKE_node_runtime.hh" #include "BKE_studiolight.h" -#include "NOD_shader.h" - #include "hydra_scene_delegate.hh" #include "image.hh" +#include "usd_light_convert.hh" /* TODO: add custom `tftoken` "transparency"? */ @@ -45,71 +41,39 @@ void WorldData::init() { data_.clear(); + pxr::GfVec3f color(1.0f, 1.0f, 1.0f); float intensity = 1.0f; pxr::SdfAssetPath texture_file; if (scene_delegate_->shading_settings.use_scene_world) { const World *world = scene_delegate_->scene->world; - pxr::GfVec3f color(1.0f, 1.0f, 1.0f); ID_LOG("%s", world->id.name); - world->nodetree->ensure_topology_cache(); + usd::WorldToDomeLight res; + usd::world_material_to_dome_light(scene_delegate_->scene, res); - /* TODO: Create nodes parsing system */ - bNode *output_node = ntreeShaderOutputNode(world->nodetree, SHD_OUTPUT_ALL); - if (!output_node) { - return; - } - const Span input_sockets = output_node->input_sockets(); - bNodeSocket *input_socket = nullptr; + if (res.image) { + const std::string file_path = cache_or_get_image_file( + scene_delegate_->bmain, scene_delegate_->scene, res.image, res.iuser); + if (!file_path.empty()) { + texture_file = pxr::SdfAssetPath(file_path, file_path); + } - for (auto *socket : input_sockets) { - if (STREQ(socket->name, "Surface")) { - input_socket = socket; - break; + if (res.mult_found) { + color = pxr::GfVec3f(res.color_mult); } } - if (!input_socket) { - return; + else if (res.color_found) { + const std::string File_path = blender::io::usd::cache_image_color(res.color); + texture_file = pxr::SdfAssetPath(File_path, File_path); + intensity = res.intensity; } - if (input_socket->directly_linked_links().is_empty()) { - return; - } - bNodeLink const *link = input_socket->directly_linked_links()[0]; - - bNode *input_node = link->fromnode; - if (input_node->type_legacy != SH_NODE_BACKGROUND) { - return; + else { + intensity = 0.0f; + color = pxr::GfVec3f(0.0f, 0.0f, 0.0f); } - const bNodeSocket &color_input = *input_node->input_by_identifier("Color"); - const bNodeSocket &strength_input = *input_node->input_by_identifier("Strength"); - - float const *strength = strength_input.default_value_typed(); - float const *input_color = color_input.default_value_typed(); - intensity = strength[1]; - color = pxr::GfVec3f(input_color[0], input_color[1], input_color[2]); - - if (!color_input.directly_linked_links().is_empty()) { - bNode *color_input_node = color_input.directly_linked_links()[0]->fromnode; - if (ELEM(color_input_node->type_legacy, SH_NODE_TEX_IMAGE, SH_NODE_TEX_ENVIRONMENT)) { - NodeTexImage *tex = static_cast(color_input_node->storage); - Image *image = (Image *)color_input_node->id; - if (image) { - std::string image_path = cache_or_get_image_file( - scene_delegate_->bmain, scene_delegate_->scene, image, &tex->iuser); - if (!image_path.empty()) { - texture_file = pxr::SdfAssetPath(image_path, image_path); - } - } - } - } - - if (texture_file.GetAssetPath().empty()) { - float fill_color[4] = {color[0], color[1], color[2], 1.0f}; - std::string image_path = blender::io::usd::cache_image_color(fill_color); - texture_file = pxr::SdfAssetPath(image_path, image_path); - } + transform = res.transform; } else { ID_LOG("studiolight: %s", scene_delegate_->shading_settings.studiolight_name.c_str()); @@ -119,18 +83,20 @@ void WorldData::init() STUDIOLIGHT_ORIENTATIONS_MATERIAL_MODE); if (sl != nullptr && sl->flag & STUDIOLIGHT_TYPE_WORLD) { texture_file = pxr::SdfAssetPath(sl->filepath, sl->filepath); - /* coefficient to follow Cycles result */ + /* Coefficient to follow Cycles result */ intensity = scene_delegate_->shading_settings.studiolight_intensity / 2; } + + transform = pxr::GfMatrix4d().SetRotate( + pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, -1.0), + RAD2DEGF(scene_delegate_->shading_settings.studiolight_rotation))); } data_[pxr::UsdLuxTokens->orientToStageUpAxis] = true; data_[pxr::HdLightTokens->intensity] = intensity; data_[pxr::HdLightTokens->exposure] = 0.0f; - data_[pxr::HdLightTokens->color] = pxr::GfVec3f(1.0f, 1.0f, 1.0f); + data_[pxr::HdLightTokens->color] = color; data_[pxr::HdLightTokens->textureFile] = texture_file; - - write_transform(); } void WorldData::update() @@ -154,15 +120,4 @@ void WorldData::update() } } -void WorldData::write_transform() -{ - transform = pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), 90.0)) * - pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, 1.0), 90.0)); - if (!scene_delegate_->shading_settings.use_scene_world) { - transform *= pxr::GfMatrix4d().SetRotate( - pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, -1.0), - RAD2DEGF(scene_delegate_->shading_settings.studiolight_rotation))); - } -} - } // namespace blender::io::hydra diff --git a/source/blender/io/usd/hydra/world.hh b/source/blender/io/usd/hydra/world.hh index ebd2c1fc1c3..09f5cc2d56b 100644 --- a/source/blender/io/usd/hydra/world.hh +++ b/source/blender/io/usd/hydra/world.hh @@ -20,9 +20,6 @@ class WorldData : public LightData { void init() override; void update() override; - - protected: - void write_transform() override; }; } // namespace blender::io::hydra diff --git a/source/blender/io/usd/intern/usd_light_convert.cc b/source/blender/io/usd/intern/usd_light_convert.cc index 7a0d872d0b4..046c16a4ddf 100644 --- a/source/blender/io/usd/intern/usd_light_convert.cc +++ b/source/blender/io/usd/intern/usd_light_convert.cc @@ -53,26 +53,11 @@ static const pxr::TfToken pole_axis_z("Z", pxr::TfToken::Immortal); namespace { -/** - * Helper struct for retrieving shader information when traversing a world material - * node chain, provided as user data for #bke::node_chain_iterator(). - */ -struct WorldNtreeSearchResults { +struct WorldNtreeSearchPayload { const blender::io::usd::USDExportParams ¶ms; pxr::UsdStageRefPtr stage; - std::string file_path; - - float world_intensity = 0.0f; - float world_color[3]{}; - float mapping_rot[3]{}; - float color_mult[3]{}; - - bool background_found = false; - bool env_tex_found = false; - bool mult_found = false; - - WorldNtreeSearchResults(const blender::io::usd::USDExportParams &in_params, + WorldNtreeSearchPayload(const blender::io::usd::USDExportParams &in_params, pxr::UsdStageRefPtr in_stage) : params(in_params), stage(in_stage) { @@ -153,76 +138,6 @@ static bNode *append_node(bNode *dst_node, return src_node; } -/** - * Callback function for iterating over a shader node chain to retrieve data - * necessary for converting a world material to a USD dome light. It also - * handles copying textures, if required. - */ -static bool node_search(bNode *fromnode, - bNode * /*tonode*/, - void *userdata, - const bool /*reversed*/) -{ - if (!(userdata && fromnode)) { - return true; - } - - WorldNtreeSearchResults *res = reinterpret_cast(userdata); - - if (!res->background_found && fromnode->type_legacy == SH_NODE_BACKGROUND) { - /* Get light color and intensity */ - const bNodeSocketValueRGBA *color_data = bke::node_find_socket(*fromnode, SOCK_IN, "Color") - ->default_value_typed(); - const bNodeSocketValueFloat *strength_data = - bke::node_find_socket(*fromnode, SOCK_IN, "Strength") - ->default_value_typed(); - - res->background_found = true; - res->world_intensity = strength_data->value; - res->world_color[0] = color_data->value[0]; - res->world_color[1] = color_data->value[1]; - res->world_color[2] = color_data->value[2]; - } - else if (!res->env_tex_found && fromnode->type_legacy == SH_NODE_TEX_ENVIRONMENT) { - /* Get env tex path. */ - - res->file_path = get_tex_image_asset_filepath(fromnode, res->stage, res->params); - - if (!res->file_path.empty()) { - res->env_tex_found = true; - if (res->params.export_textures) { - export_texture(fromnode, res->stage, res->params.overwrite_textures); - } - } - } - else if (!res->env_tex_found && !res->mult_found && fromnode->type_legacy == SH_NODE_VECTOR_MATH) - { - - if (fromnode->custom1 == NODE_VECTOR_MATH_MULTIPLY) { - res->mult_found = true; - - bNodeSocket *vec_sock = bke::node_find_socket(*fromnode, SOCK_IN, "Vector"); - if (vec_sock) { - vec_sock = vec_sock->next; - } - - if (vec_sock) { - copy_v3_v3(res->color_mult, ((bNodeSocketValueVector *)vec_sock->default_value)->value); - } - } - } - else if (res->env_tex_found && fromnode->type_legacy == SH_NODE_MAPPING) { - copy_v3_fl(res->mapping_rot, 0.0f); - if (bNodeSocket *socket = bke::node_find_socket(*fromnode, SOCK_IN, "Rotation")) { - const bNodeSocketValueVector *rot_value = static_cast( - socket->default_value); - copy_v3_v3(res->mapping_rot, rot_value->value); - } - } - - return true; -} - void world_material_to_dome_light(const USDExportParams ¶ms, const Scene *scene, pxr::UsdStageRefPtr stage) @@ -231,58 +146,57 @@ void world_material_to_dome_light(const USDExportParams ¶ms, return; } - WorldNtreeSearchResults res(params, stage); + WorldToDomeLight res; + world_material_to_dome_light(scene, res); - if (scene->world->nodetree) { - /* Find the world output. */ - bNode *output = nullptr; - const bNodeTree *ntree = scene->world->nodetree; - ntree->ensure_topology_cache(); - const Span bsdf_nodes = ntree->nodes_by_type("ShaderNodeOutputWorld"); - for (const bNode *node : bsdf_nodes) { - if (node->flag & NODE_DO_OUTPUT) { - output = const_cast(node); - break; - } - } - - if (!output) { - /* No output, no valid network to convert. */ - return; - } - - bke::node_chain_iterator(scene->world->nodetree, output, node_search, &res, true); - } - else { - res.world_intensity = 1.0f; - zero_v3(res.world_color); - res.background_found = false; - } - - if (!(res.background_found || res.env_tex_found)) { + if (!(res.color_found || res.image)) { /* No nodes to convert */ return; } + std::string image_filepath; + if (res.image) { + /* Compute image filepath and export if needed. */ + image_filepath = get_tex_image_asset_filepath(res.image, stage, params); + if (image_filepath.empty()) { + return; + } + if (params.export_textures) { + export_texture(res.image, stage, params.overwrite_textures); + } + } + /* Create USD dome light. */ - pxr::SdfPath env_light_path = get_unique_path(stage, params.root_prim_path + "/env_light"); - pxr::UsdLuxDomeLight dome_light = pxr::UsdLuxDomeLight::Define(stage, env_light_path); - if (!res.env_tex_found) { - /* Like the Hydra delegate, if no texture is found export a solid - * color texture as a stand-in so that Hydra renderers don't - * throw errors. */ + if (res.image) { + /* Use existing image texture file. */ + dome_light.CreateTextureFileAttr().Set(pxr::SdfAssetPath(image_filepath)); - float fill_color[4] = {res.world_color[0], res.world_color[1], res.world_color[2], 1.0f}; + /* Set optional color multiplication. */ + if (res.mult_found) { + pxr::GfVec3f color_val(res.color_mult[0], res.color_mult[1], res.color_mult[2]); + dome_light.CreateColorAttr().Set(color_val); + } - std::string source_path = cache_image_color(fill_color); + /* Set transform. */ + pxr::GfVec3d angles = res.transform.DecomposeRotation( + pxr::GfVec3d::ZAxis(), pxr::GfVec3d::YAxis(), pxr::GfVec3d::XAxis()); + pxr::GfVec3f rot_vec(angles[2], angles[1], angles[0]); + pxr::UsdGeomXformCommonAPI xform_api(dome_light); + xform_api.SetRotate(rot_vec, pxr::UsdGeomXformCommonAPI::RotationOrderXYZ); + } + else if (res.color_found) { + /* If no texture is found export a solid color texture as a stand-in so that Hydra + * renderers don't throw errors. */ + dome_light.CreateIntensityAttr().Set(res.intensity); + + std::string source_path = cache_image_color(res.color); const std::string base_path = stage->GetRootLayer()->GetRealPath(); - /* It'll be short, coming from cache_image_color. */ - char file_path[64]; - BLI_path_split_file_part(source_path.c_str(), file_path, 64); + char file_path[FILE_MAX]; + BLI_path_split_file_part(source_path.c_str(), file_path, FILE_MAX); char dest_path[FILE_MAX]; BLI_path_split_dir_part(base_path.c_str(), dest_path, FILE_MAX); @@ -295,52 +209,11 @@ void world_material_to_dome_light(const USDExportParams ¶ms, CLOG_WARN(&LOG, "USD Export: Couldn't write world color image to %s", dest_path); } else { - res.env_tex_found = true; BLI_path_join(dest_path, FILE_MAX, ".", "textures", file_path); - res.file_path = dest_path; + image_filepath = dest_path; + dome_light.CreateTextureFileAttr().Set(pxr::SdfAssetPath(image_filepath)); } } - - if (res.env_tex_found) { - pxr::SdfAssetPath path(res.file_path); - dome_light.CreateTextureFileAttr().Set(path); - - if (res.mult_found) { - pxr::GfVec3f color_val(res.color_mult[0], res.color_mult[1], res.color_mult[2]); - dome_light.CreateColorAttr().Set(color_val); - } - } - else { - pxr::GfVec3f color_val(res.world_color[0], res.world_color[1], res.world_color[2]); - dome_light.CreateColorAttr().Set(color_val); - } - - if (res.background_found) { - dome_light.CreateIntensityAttr().Set(res.world_intensity); - } - - /* We always set a default rotation on the light since res.mapping_rot defaults to zeros. */ - - /* Convert radians to degrees. */ - mul_v3_fl(res.mapping_rot, 180.0f / M_PI); - - pxr::GfMatrix4d xf = - pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), 90.0)) * - pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, 1.0), 90.0)) * - pxr::GfMatrix4d().SetRotate( - pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, 1.0), -res.mapping_rot[2])) * - pxr::GfMatrix4d().SetRotate( - pxr::GfRotation(pxr::GfVec3d(0.0, 1.0, 0.0), -res.mapping_rot[1])) * - pxr::GfMatrix4d().SetRotate( - pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), -res.mapping_rot[0])); - - pxr::GfVec3d angles = xf.DecomposeRotation( - pxr::GfVec3d::ZAxis(), pxr::GfVec3d::YAxis(), pxr::GfVec3d::XAxis()); - - pxr::GfVec3f rot_vec(angles[2], angles[1], angles[0]); - - pxr::UsdGeomXformCommonAPI xform_api(dome_light); - xform_api.SetRotate(rot_vec, pxr::UsdGeomXformCommonAPI::RotationOrderXYZ); } /* Import the dome light as a world material. */ @@ -531,4 +404,79 @@ void dome_light_to_world_material(const USDImportParams ¶ms, BKE_ntree_update_after_single_tree_change(*bmain, *ntree); } +static bool node_search(bNode *fromnode, bNode * /*tonode*/, void *userdata, bool /*reversed*/) +{ + if (!(userdata && fromnode)) { + return true; + } + + WorldToDomeLight &res = *static_cast(userdata); + + if (!res.color_found && fromnode->type_legacy == SH_NODE_BACKGROUND) { + /* Get light color and intensity */ + const bNodeSocketValueRGBA *color_data = bke::node_find_socket(*fromnode, SOCK_IN, "Color") + ->default_value_typed(); + const bNodeSocketValueFloat *strength_data = + bke::node_find_socket(*fromnode, SOCK_IN, "Strength") + ->default_value_typed(); + + res.color_found = true; + res.intensity = strength_data->value; + res.color[0] = color_data->value[0]; + res.color[1] = color_data->value[1]; + res.color[2] = color_data->value[2]; + res.color[3] = 1.0f; + } + else if (!res.image && fromnode->type_legacy == SH_NODE_TEX_ENVIRONMENT) { + NodeTexImage *tex = static_cast(fromnode->storage); + res.image = reinterpret_cast(fromnode->id); + res.iuser = &tex->iuser; + } + else if (!res.image && !res.mult_found && fromnode->type_legacy == SH_NODE_VECTOR_MATH) { + if (fromnode->custom1 == NODE_VECTOR_MATH_MULTIPLY) { + res.mult_found = true; + + bNodeSocket *vec_sock = bke::node_find_socket(*fromnode, SOCK_IN, "Vector"); + if (vec_sock) { + vec_sock = vec_sock->next; + } + + if (vec_sock) { + copy_v3_v3(res.color_mult, ((bNodeSocketValueVector *)vec_sock->default_value)->value); + } + } + } + else if (res.image && fromnode->type_legacy == SH_NODE_MAPPING) { + if (bNodeSocket *socket = bke::node_find_socket(*fromnode, SOCK_IN, "Rotation")) { + const bNodeSocketValueVector *rot_value = static_cast( + socket->default_value); + /* Convert radians to degrees. */ + pxr::GfVec3f rot(rot_value->value); + mul_v3_fl(rot.data(), 180.0f / M_PI); + res.transform = + pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), 90.0)) * + pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, 1.0), 90.0)) * + pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 0.0, 1.0), -rot[2])) * + pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(0.0, 1.0, 0.0), -rot[1])) * + pxr::GfMatrix4d().SetRotate(pxr::GfRotation(pxr::GfVec3d(1.0, 0.0, 0.0), -rot[0])); + } + } + return true; +} + +void world_material_to_dome_light(const Scene *scene, WorldToDomeLight &res) +{ + /* Find the world output. */ + scene->world->nodetree->ensure_topology_cache(); + const Span bsdf_nodes = scene->world->nodetree->nodes_by_type( + "ShaderNodeOutputWorld"); + + for (const bNode *node : bsdf_nodes) { + if (node->flag & NODE_DO_OUTPUT) { + bke::node_chain_iterator(scene->world->nodetree, node, node_search, &res, true); + break; + } + } +} + } // namespace blender::io::usd diff --git a/source/blender/io/usd/intern/usd_light_convert.hh b/source/blender/io/usd/intern/usd_light_convert.hh index cf5f9ee62f6..a1050c7a550 100644 --- a/source/blender/io/usd/intern/usd_light_convert.hh +++ b/source/blender/io/usd/intern/usd_light_convert.hh @@ -6,6 +6,11 @@ #include #include +struct bNode; +struct bNodeTree; + +struct Image; +struct ImageUser; struct Main; struct Scene; @@ -41,4 +46,25 @@ void dome_light_to_world_material(const USDImportParams ¶ms, const pxr::UsdPrim &prim, const pxr::UsdTimeCode time = 0.0); +/** + * Helper struct for converting world shader nodes to a dome light, used by both + * USD and Hydra. */ +struct WorldToDomeLight { + /* Image and its transform. */ + Image *image = nullptr; + ImageUser *iuser = nullptr; + pxr::GfMatrix4d transform = pxr::GfMatrix4d(1.0); + + /* Multiply image by color. */ + bool mult_found = false; + float color_mult[4]{}; + + /* Fixed color. */ + bool color_found = false; + float intensity = 0.0f; + float color[4]{}; +}; + +void world_material_to_dome_light(const Scene *scene, WorldToDomeLight &res); + } // namespace blender::io::usd diff --git a/tests/files/render/light/storm_hydra_renders/multiple_lights_in_volume.png b/tests/files/render/light/storm_hydra_renders/multiple_lights_in_volume.png index 58e37a2b598..04b2db636dd 100644 --- a/tests/files/render/light/storm_hydra_renders/multiple_lights_in_volume.png +++ b/tests/files/render/light/storm_hydra_renders/multiple_lights_in_volume.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ade76348621bfdd9fa1ce64e33cd7da758cd09630b368bf92d4e6cac4c8c0ab -size 1091 +oid sha256:d7f618192325eb1ab7c0d9f001ab1a2ba246e0c2379036fe1f2f082a615a3a10 +size 896