/* SPDX-FileCopyrightText: 2022 NVIDIA Corporation * SPDX-FileCopyrightText: 2022 Blender Foundation * * SPDX-License-Identifier: Apache-2.0 */ #include "hydra/light.h" #include "hydra/session.h" #include "hydra/util.h" #include "kernel/types.h" #include "scene/light.h" #include "scene/object.h" #include "scene/scene.h" #include "scene/shader.h" #include "scene/shader_graph.h" #include "scene/shader_nodes.h" #include "util/hash.h" #include "util/transform.h" #include #include #include #include #include HDCYCLES_NAMESPACE_OPEN_SCOPE extern Transform convert_transform(const GfMatrix4d &matrix); // clang-format off TF_DEFINE_PRIVATE_TOKENS(_tokens, (visibleInPrimaryRay) (treatAsPoint) (falloff) ); // clang-format on HdCyclesLight::HdCyclesLight(const SdfPath &sprimId, const TfToken &lightType) : HdLight(sprimId), _lightType(lightType) { } HdCyclesLight::~HdCyclesLight() = default; 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); const SdfPath &id = GetId(); const HdSceneIndexPrim prim = GetPrim(sceneDelegate, id); const HdContainerDataSourceHandle &primDs = prim.dataSource; const HdContainerDataSourceHandle lightDs = HdLightSchema::GetFromParent(primDs).GetContainer(); if (*dirtyBits & DirtyBits::DirtyTransform) { const float metersPerUnit = static_cast(renderParam)->GetStageMetersPerUnit(); GfMatrix4d xform(1.0); if (auto matrixDs = HdXformSchema::GetFromParent(primDs).GetMatrix()) { xform = matrixDs->GetTypedValue(0.0f); } const Transform tfm = transform_scale(make_float3(metersPerUnit)) * convert_transform(xform); _object->set_tfm(tfm); } if (*dirtyBits & DirtyBits::DirtyParams) { float3 strength = make_float3(1.0f, 1.0f, 1.0f); { const GfVec3f color = GetTypedValue( lightDs, HdLightTokens->color, GfVec3f(1.0f, 1.0f, 1.0f)); strength = make_float3(color[0], color[1], color[2]); } strength *= exp2(GetTypedValue(lightDs, HdLightTokens->exposure, 0.0f)); strength *= GetTypedValue(lightDs, HdLightTokens->intensity, 1.0f); if (_lightType == HdPrimTypeTokens->distantLight) { /* Unclear why, but approximately matches Karma. */ strength *= 4.0f; } else { /* Convert from intensity to radiant flux. */ strength *= M_PI_F; } _light->set_normalize(GetTypedValue(lightDs, HdLightTokens->normalize, false)); if (auto ds = GetTypedDataSource(lightDs, _tokens->visibleInPrimaryRay)) { if (ds->GetTypedValue(0.0f)) { _object->set_visibility(_object->get_visibility() | PATH_RAY_VISIBILITY_CAMERA); } else { _object->set_visibility(_object->get_visibility() & ~PATH_RAY_VISIBILITY_CAMERA); } } if (auto ds = GetTypedDataSource(lightDs, HdLightTokens->shadowEnable)) { _light->set_cast_shadow(ds->GetTypedValue(0.0f)); } if (_lightType == HdPrimTypeTokens->distantLight) { if (auto ds = GetTypedDataSource(lightDs, HdLightTokens->angle)) { static_cast(_light)->set_angle(GfDegreesToRadians(ds->GetTypedValue(0.0f))); } } else if (_lightType == HdPrimTypeTokens->diskLight) { AreaLight *area_light = static_cast(_light); if (auto ds = GetTypedDataSource(lightDs, HdLightTokens->radius)) { const float size = ds->GetTypedValue(0.0f) * 2.0f; area_light->set_sizeu(size); area_light->set_sizev(size); } } else if (_lightType == HdPrimTypeTokens->rectLight) { AreaLight *area_light = static_cast(_light); if (auto ds = GetTypedDataSource(lightDs, HdLightTokens->width)) { area_light->set_sizeu(ds->GetTypedValue(0.0f)); } if (auto ds = GetTypedDataSource(lightDs, HdLightTokens->height)) { area_light->set_sizev(ds->GetTypedValue(0.0f)); } } else if (_lightType == HdPrimTypeTokens->sphereLight) { SpotLight *spot_light = static_cast(_light); const bool treatAsPoint = GetTypedValue(lightDs, _tokens->treatAsPoint, false); if (treatAsPoint) { spot_light->set_radius(0.0f); } else if (auto ds = GetTypedDataSource(lightDs, HdLightTokens->radius)) { spot_light->set_radius(ds->GetTypedValue(0.0f)); } bool shaping = false; if (auto ds = GetTypedDataSource(lightDs, HdLightTokens->shapingConeAngle)) { spot_light->set_angle(GfDegreesToRadians(ds->GetTypedValue(0.0f)) * 2.0f); shaping = true; } if (auto ds = GetTypedDataSource(lightDs, HdLightTokens->shapingConeSoftness)) { spot_light->set_smooth(ds->GetTypedValue(0.0f)); shaping = true; } _light->set_light_type(shaping ? LIGHT_SPOT : LIGHT_POINT); } bool visible = true; if (auto ds = HdVisibilitySchema::GetFromParent(primDs).GetVisibility()) { visible = ds->GetTypedValue(0.0f); } // 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(lightDs); } // Need to update shader graph when transform changes in case transform was baked into it else if (_object->tfm_is_modified() && (_lightType == HdPrimTypeTokens->domeLight || _light->get_shader()->has_surface_spatial_varying)) { PopulateShaderGraph(lightDs); } if (_light->is_modified()) { _light->tag_update(lock.scene); } *dirtyBits = DirtyBits::Clean; } void HdCyclesLight::PopulateShaderGraph(const HdContainerDataSourceHandle &lightContainer) { unique_ptr graph = make_unique(); ShaderNode *outputNode = nullptr; if (_lightType == HdPrimTypeTokens->domeLight) { BackgroundNode *bgNode = graph->create_node(); // Bake strength into shader graph, since only the shader is used for background lights bgNode->set_color(_light->get_strength()); graph->connect(bgNode->output("Background"), graph->output()->input("Surface")); outputNode = bgNode; } else if (lightContainer) { if (auto ds = HdStringDataSource::Cast(lightContainer->Get(_tokens->falloff))) { const std::string strVal = ds->GetTypedValue(0.0f); if (strVal == "Constant" || strVal == "Linear" || strVal == "Quadratic") { LightFalloffNode *lfoNode = graph->create_node(); lfoNode->set_strength(1.f); graph->connect(lfoNode->output(strVal.c_str()), graph->output()->input("Surface")); outputNode = lfoNode; } } } if (outputNode == nullptr) { EmissionNode *emissionNode = graph->create_node(); emissionNode->set_color(one_float3()); emissionNode->set_strength(1.0f); graph->connect(emissionNode->output("Emission"), graph->output()->input("Surface")); outputNode = emissionNode; } bool hasSpatialVarying = false; bool hasColorTemperature = false; if (lightContainer) { const bool enableColorTemperature = GetTypedValue( lightContainer, HdLightTokens->enableColorTemperature, false); if (enableColorTemperature) { if (auto ds = HdFloatDataSource::Cast(lightContainer->Get(HdLightTokens->colorTemperature))) { BlackbodyNode *blackbodyNode = graph->create_node(); blackbodyNode->set_temperature(ds->GetTypedValue(0.0f)); if (_lightType == HdPrimTypeTokens->domeLight) { VectorMathNode *mathNode = graph->create_node(); mathNode->set_math_type(NODE_VECTOR_MATH_MULTIPLY); mathNode->set_vector2(_light->get_strength()); 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; } } if (auto ds = HdAssetPathDataSource::Cast(lightContainer->Get(HdLightTokens->shapingIesFile))) { const SdfAssetPath assetPath = ds->GetTypedValue(0.0f); std::string filename = assetPath.GetResolvedPath(); if (filename.empty()) { filename = assetPath.GetAssetPath(); } if (!filename.empty()) { TextureCoordinateNode *coordNode = graph->create_node(); coordNode->set_ob_tfm(_object->get_tfm()); coordNode->set_use_transform(true); IESLightNode *iesNode = graph->create_node(); iesNode->set_filename(ustring(filename)); graph->connect(coordNode->output("Normal"), iesNode->input("Vector")); graph->connect(iesNode->output("Fac"), outputNode->input("Strength")); hasSpatialVarying = true; } } if (auto ds = HdAssetPathDataSource::Cast(lightContainer->Get(HdLightTokens->textureFile))) { const SdfAssetPath assetPath = ds->GetTypedValue(0.0f); std::string filename = assetPath.GetResolvedPath(); if (filename.empty()) { filename = assetPath.GetAssetPath(); } if (!filename.empty()) { ImageSlotTextureNode *textureNode = nullptr; if (_lightType == HdPrimTypeTokens->domeLight) { Transform tfm = _object->get_tfm(); transform_set_column(&tfm, 3, zero_float3()); // Remove translation TextureCoordinateNode *coordNode = graph->create_node(); coordNode->set_ob_tfm(tfm); coordNode->set_use_transform(true); textureNode = graph->create_node(); static_cast(textureNode)->set_filename(ustring(filename)); graph->connect(coordNode->output("Object"), textureNode->input("Vector")); hasSpatialVarying = true; } else { GeometryNode *coordNode = graph->create_node(); textureNode = graph->create_node(); static_cast(textureNode)->set_filename(ustring(filename)); graph->connect(coordNode->output("Parametric"), textureNode->input("Vector")); } if (hasColorTemperature) { VectorMathNode *mathNode = graph->create_node(); mathNode->set_math_type(NODE_VECTOR_MATH_MULTIPLY); 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(); mathNode->set_math_type(NODE_VECTOR_MATH_MULTIPLY); mathNode->set_vector2(_light->get_strength()); 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(std::move(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); const bool keep_nodes = static_cast(renderParam)->keep_nodes; if (!keep_nodes) { lock.scene->delete_node(_light); lock.scene->delete_node(_object); } _light = nullptr; _object = nullptr; } void HdCyclesLight::Initialize(HdRenderParam *renderParam) { if (_light) { return; } const SceneLock lock(renderParam); _object = lock.scene->create_node(); _object->name = GetId().GetString(); if (_lightType == HdPrimTypeTokens->domeLight) { _light = lock.scene->create_node(); } else if (_lightType == HdPrimTypeTokens->distantLight) { _light = lock.scene->create_node(); } else if (_lightType == HdPrimTypeTokens->diskLight) { _light = lock.scene->create_node(); static_cast(_light)->set_ellipse(true); } else if (_lightType == HdPrimTypeTokens->rectLight) { _light = lock.scene->create_node(); static_cast(_light)->set_ellipse(false); } else if (_lightType == HdPrimTypeTokens->sphereLight) { /* We can't know in advance if this is spot light or point light, so we set to derived class * SpotLight and change the type later. */ _light = lock.scene->create_node(); } _light->set_use_mis(true); _light->name = GetId().GetString(); _object->set_geometry(_light); _object->set_random_id(hash_uint2(hash_string(_light->name.c_str()), 0)); _object->set_visibility(PATH_RAY_VISIBILITY_ALL & ~PATH_RAY_VISIBILITY_CAMERA); Shader *const shader = lock.scene->create_node(); array used_shaders; used_shaders.push_back_slow(shader); _light->set_used_shaders(used_shaders); // Create default shader graph PopulateShaderGraph(HdContainerDataSourceHandle()); } HDCYCLES_NAMESPACE_CLOSE_SCOPE