diff options
author | Patrick Mours <pmours@nvidia.com> | 2022-03-23 18:07:43 +0300 |
---|---|---|
committer | Patrick Mours <pmours@nvidia.com> | 2022-03-23 18:39:05 +0300 |
commit | d350976ba06d4ef93aa53fc4cd9da57be46ae924 (patch) | |
tree | 808f38e6b0c663c7a50f1d98619c96ca0bd82018 /intern/cycles/hydra/light.cpp | |
parent | 827e9ccb29dd50d155972444d608c35cf4698d6a (diff) |
Cycles: Add Hydra render delegate
This patch adds a Hydra render delegate to Cycles, allowing Cycles to be used for rendering
in applications that provide a Hydra viewport. The implementation was written from scratch
against Cycles X, for integration into the Blender repository to make it possible to continue
developing it in step with the rest of Cycles. For this purpose it follows the style of the rest of
the Cycles code and can be built with a CMake option
(`WITH_CYCLES_HYDRA_RENDER_DELEGATE=1`) similar to the existing standalone version
of Cycles.
Since Hydra render delegates need to be built against the exact USD version and other
dependencies as the target application is using, this is intended to be built separate from
Blender (`WITH_BLENDER=0` CMake option) and with support for library versions different
from what Blender is using. As such the CMake build scripts for Windows had to be modified
slightly, so that the Cycles Hydra render delegate can e.g. be built with MSVC 2017 again
even though Blender requires MSVC 2019 now, and it's possible to specify custom paths to
the USD SDK etc. The codebase supports building against the latest USD release 22.03 and all
the way back to USD 20.08 (with some limitations).
Reviewed By: brecht, LazyDodo
Differential Revision: https://developer.blender.org/D14398
Diffstat (limited to 'intern/cycles/hydra/light.cpp')
-rw-r--r-- | intern/cycles/hydra/light.cpp | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/intern/cycles/hydra/light.cpp b/intern/cycles/hydra/light.cpp new file mode 100644 index 00000000000..b691da0d6a6 --- /dev/null +++ b/intern/cycles/hydra/light.cpp @@ -0,0 +1,399 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2022 NVIDIA Corporation + * Copyright 2022 Blender Foundation */ + +#include "hydra/light.h" +#include "hydra/session.h" +#include "scene/light.h" +#include "scene/scene.h" +#include "scene/shader.h" +#include "scene/shader_graph.h" +#include "scene/shader_nodes.h" +#include "util/hash.h" + +#include <pxr/imaging/hd/sceneDelegate.h> +#include <pxr/usd/sdf/assetPath.h> + +HDCYCLES_NAMESPACE_OPEN_SCOPE + +extern Transform convert_transform(const GfMatrix4d &matrix); + +// clang-format off +TF_DEFINE_PRIVATE_TOKENS(_tokens, + (visibleInPrimaryRay) +); +// clang-format on + +HdCyclesLight::HdCyclesLight(const SdfPath &sprimId, const TfToken &lightType) + : HdLight(sprimId), _lightType(lightType) +{ +} + +HdCyclesLight::~HdCyclesLight() +{ +} + +HdDirtyBits HdCyclesLight::GetInitialDirtyBitsMask() const +{ + return DirtyBits::DirtyTransform | DirtyBits::DirtyParams; +} + +void HdCyclesLight::Sync(HdSceneDelegate *sceneDelegate, + HdRenderParam *renderParam, + HdDirtyBits *dirtyBits) +{ + if (*dirtyBits == DirtyBits::Clean) { + return; + } + + Initialize(renderParam); + + const SceneLock lock(renderParam); + + VtValue value; + const SdfPath &id = GetId(); + + if (*dirtyBits & DirtyBits::DirtyTransform) { +#if PXR_VERSION >= 2011 + const Transform tfm = convert_transform(sceneDelegate->GetTransform(id)); +#else + const Transform tfm = convert_transform( + sceneDelegate->GetLightParamValue(id, HdTokens->transform).Get<GfMatrix4d>()); +#endif + _light->set_tfm(tfm); + + _light->set_co(transform_get_column(&tfm, 3)); + _light->set_dir(-transform_get_column(&tfm, 2)); + + if (_lightType == HdPrimTypeTokens->diskLight || _lightType == HdPrimTypeTokens->rectLight) { + _light->set_axisu(transform_get_column(&tfm, 0)); + _light->set_axisv(transform_get_column(&tfm, 1)); + } + } + + if (*dirtyBits & DirtyBits::DirtyParams) { + float3 strength = make_float3(1.0f, 1.0f, 1.0f); + + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->color); + if (!value.IsEmpty()) { + const auto color = value.Get<GfVec3f>(); + strength = make_float3(color[0], color[1], color[2]); + } + + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->exposure); + if (!value.IsEmpty()) { + strength *= exp2(value.Get<float>()); + } + + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->intensity); + if (!value.IsEmpty()) { + strength *= value.Get<float>(); + } + + // Cycles lights are normalized by default, so need to scale intensity if Hydra light is not + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->normalize); + const bool normalize = value.IsHolding<bool>() && value.UncheckedGet<bool>(); + + value = sceneDelegate->GetLightParamValue(id, _tokens->visibleInPrimaryRay); + if (!value.IsEmpty()) { + _light->set_use_camera(value.Get<bool>()); + } + + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->shadowEnable); + if (!value.IsEmpty()) { + _light->set_cast_shadow(value.Get<bool>()); + } + + if (_lightType == HdPrimTypeTokens->distantLight) { + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->angle); + if (!value.IsEmpty()) { + _light->set_angle(GfDegreesToRadians(value.Get<float>())); + } + } + else if (_lightType == HdPrimTypeTokens->diskLight) { + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->radius); + if (!value.IsEmpty()) { + const float size = value.Get<float>() * 2.0f; + _light->set_sizeu(size); + _light->set_sizev(size); + } + + if (!normalize) { + const float radius = _light->get_sizeu() * 0.5f; + strength *= M_PI * radius * radius; + } + } + else if (_lightType == HdPrimTypeTokens->rectLight) { + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->width); + if (!value.IsEmpty()) { + _light->set_sizeu(value.Get<float>()); + } + + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->height); + if (!value.IsEmpty()) { + _light->set_sizev(value.Get<float>()); + } + + if (!normalize) { + strength *= _light->get_sizeu() * _light->get_sizeu(); + } + } + else if (_lightType == HdPrimTypeTokens->sphereLight) { + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->radius); + if (!value.IsEmpty()) { + _light->set_size(value.Get<float>()); + } + + bool shaping = false; + + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->shapingConeAngle); + if (!value.IsEmpty()) { + _light->set_spot_angle(GfDegreesToRadians(value.Get<float>()) * 2.0f); + shaping = true; + } + + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->shapingConeSoftness); + if (!value.IsEmpty()) { + _light->set_spot_smooth(value.Get<float>()); + shaping = true; + } + + _light->set_light_type(shaping ? LIGHT_SPOT : LIGHT_POINT); + + if (!normalize) { + const float radius = _light->get_size(); + strength *= M_PI * radius * radius * 4.0f; + } + } + + const bool visible = sceneDelegate->GetVisible(id); + // Disable invisible lights by zeroing the strength + // So 'LightManager::test_enabled_lights' updates the enabled flag correctly + if (!visible) { + strength = zero_float3(); + } + + _light->set_strength(strength); + _light->set_is_enabled(visible); + + PopulateShaderGraph(sceneDelegate); + } + // Need to update shader graph when transform changes in case transform was baked into it + else if (_light->tfm_is_modified() && (_lightType == HdPrimTypeTokens->domeLight || + _light->get_shader()->has_surface_spatial_varying)) { + PopulateShaderGraph(sceneDelegate); + } + + if (_light->is_modified()) { + _light->tag_update(lock.scene); + } + + *dirtyBits = DirtyBits::Clean; +} + +void HdCyclesLight::PopulateShaderGraph(HdSceneDelegate *sceneDelegate) +{ + auto graph = new ShaderGraph(); + ShaderNode *outputNode = nullptr; + + if (_lightType == HdPrimTypeTokens->domeLight) { + BackgroundNode *bgNode = graph->create_node<BackgroundNode>(); + // Bake strength into shader graph, since only the shader is used for background lights + bgNode->set_color(_light->get_strength()); + graph->add(bgNode); + + graph->connect(bgNode->output("Background"), graph->output()->input("Surface")); + + outputNode = bgNode; + } + else { + EmissionNode *emissionNode = graph->create_node<EmissionNode>(); + emissionNode->set_color(one_float3()); + emissionNode->set_strength(1.0f); + graph->add(emissionNode); + + graph->connect(emissionNode->output("Emission"), graph->output()->input("Surface")); + + outputNode = emissionNode; + } + + VtValue value; + const SdfPath &id = GetId(); + bool hasSpatialVarying = false; + bool hasColorTemperature = false; + + if (sceneDelegate != nullptr) { + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->enableColorTemperature); + const bool enableColorTemperature = value.IsHolding<bool>() && value.UncheckedGet<bool>(); + + if (enableColorTemperature) { + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->colorTemperature); + if (value.IsHolding<float>()) { + BlackbodyNode *blackbodyNode = graph->create_node<BlackbodyNode>(); + blackbodyNode->set_temperature(value.UncheckedGet<float>()); + graph->add(blackbodyNode); + + if (_lightType == HdPrimTypeTokens->domeLight) { + VectorMathNode *mathNode = graph->create_node<VectorMathNode>(); + mathNode->set_math_type(NODE_VECTOR_MATH_MULTIPLY); + mathNode->set_vector2(_light->get_strength()); + graph->add(mathNode); + + graph->connect(blackbodyNode->output("Color"), mathNode->input("Vector1")); + graph->connect(mathNode->output("Vector"), outputNode->input("Color")); + } + else { + graph->connect(blackbodyNode->output("Color"), outputNode->input("Color")); + } + + hasColorTemperature = true; + } + } + + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->shapingIesFile); + if (value.IsHolding<SdfAssetPath>()) { + std::string filename = value.UncheckedGet<SdfAssetPath>().GetResolvedPath(); + if (filename.empty()) { + filename = value.UncheckedGet<SdfAssetPath>().GetAssetPath(); + } + + TextureCoordinateNode *coordNode = graph->create_node<TextureCoordinateNode>(); + coordNode->set_ob_tfm(_light->get_tfm()); + coordNode->set_use_transform(true); + graph->add(coordNode); + + IESLightNode *iesNode = graph->create_node<IESLightNode>(); + iesNode->set_filename(ustring(filename)); + + graph->connect(coordNode->output("Normal"), iesNode->input("Vector")); + graph->connect(iesNode->output("Fac"), outputNode->input("Strength")); + + hasSpatialVarying = true; + } + + value = sceneDelegate->GetLightParamValue(id, HdLightTokens->textureFile); + if (value.IsHolding<SdfAssetPath>()) { + std::string filename = value.UncheckedGet<SdfAssetPath>().GetResolvedPath(); + if (filename.empty()) { + filename = value.UncheckedGet<SdfAssetPath>().GetAssetPath(); + } + + ImageSlotTextureNode *textureNode = nullptr; + if (_lightType == HdPrimTypeTokens->domeLight) { + Transform tfm = _light->get_tfm(); + transform_set_column(&tfm, 3, zero_float3()); // Remove translation + + TextureCoordinateNode *coordNode = graph->create_node<TextureCoordinateNode>(); + coordNode->set_ob_tfm(tfm); + coordNode->set_use_transform(true); + graph->add(coordNode); + + textureNode = graph->create_node<EnvironmentTextureNode>(); + static_cast<EnvironmentTextureNode *>(textureNode)->set_filename(ustring(filename)); + graph->add(textureNode); + + graph->connect(coordNode->output("Object"), textureNode->input("Vector")); + + hasSpatialVarying = true; + } + else { + GeometryNode *coordNode = graph->create_node<GeometryNode>(); + graph->add(coordNode); + + textureNode = graph->create_node<ImageTextureNode>(); + static_cast<ImageTextureNode *>(textureNode)->set_filename(ustring(filename)); + graph->add(textureNode); + + graph->connect(coordNode->output("Parametric"), textureNode->input("Vector")); + } + + if (hasColorTemperature) { + VectorMathNode *mathNode = graph->create_node<VectorMathNode>(); + mathNode->set_math_type(NODE_VECTOR_MATH_MULTIPLY); + graph->add(mathNode); + + graph->connect(textureNode->output("Color"), mathNode->input("Vector1")); + ShaderInput *const outputNodeInput = outputNode->input("Color"); + graph->connect(outputNodeInput->link, mathNode->input("Vector2")); + graph->disconnect(outputNodeInput); + graph->connect(mathNode->output("Vector"), outputNodeInput); + } + else if (_lightType == HdPrimTypeTokens->domeLight) { + VectorMathNode *mathNode = graph->create_node<VectorMathNode>(); + mathNode->set_math_type(NODE_VECTOR_MATH_MULTIPLY); + mathNode->set_vector2(_light->get_strength()); + graph->add(mathNode); + + graph->connect(textureNode->output("Color"), mathNode->input("Vector1")); + graph->connect(mathNode->output("Vector"), outputNode->input("Color")); + } + else { + graph->connect(textureNode->output("Color"), outputNode->input("Color")); + } + } + } + + Shader *const shader = _light->get_shader(); + shader->set_graph(graph); + shader->tag_update((Scene *)_light->get_owner()); + + shader->has_surface_spatial_varying = hasSpatialVarying; +} + +void HdCyclesLight::Finalize(HdRenderParam *renderParam) +{ + if (!_light) { + return; + } + + const SceneLock lock(renderParam); + + lock.scene->delete_node(_light); + _light = nullptr; +} + +void HdCyclesLight::Initialize(HdRenderParam *renderParam) +{ + if (_light) { + return; + } + + const SceneLock lock(renderParam); + + _light = lock.scene->create_node<Light>(); + _light->name = GetId().GetString(); + + _light->set_random_id(hash_uint2(hash_string(_light->name.c_str()), 0)); + + if (_lightType == HdPrimTypeTokens->domeLight) { + _light->set_light_type(LIGHT_BACKGROUND); + } + else if (_lightType == HdPrimTypeTokens->distantLight) { + _light->set_light_type(LIGHT_DISTANT); + } + else if (_lightType == HdPrimTypeTokens->diskLight) { + _light->set_light_type(LIGHT_AREA); + _light->set_round(true); + _light->set_size(1.0f); + } + else if (_lightType == HdPrimTypeTokens->rectLight) { + _light->set_light_type(LIGHT_AREA); + _light->set_round(false); + _light->set_size(1.0f); + } + else if (_lightType == HdPrimTypeTokens->sphereLight) { + _light->set_light_type(LIGHT_POINT); + _light->set_size(1.0f); + } + + _light->set_use_mis(true); + _light->set_use_camera(false); + + Shader *const shader = lock.scene->create_node<Shader>(); + _light->set_shader(shader); + + // Create default shader graph + PopulateShaderGraph(nullptr); +} + +HDCYCLES_NAMESPACE_CLOSE_SCOPE |