Lights: Add temperature property

Similar to other renderers, this adds a temperature property to set the
light color using blackbody emission. This can be more convenient than
using nodes, and can improve interop with other software.

This is supported in Cycles, EEVEE, Hydra, USD, COLLADA and FBX.

Pull Request: https://projects.blender.org/blender/blender/pulls/134303
This commit is contained in:
Eqkoss / T1NT1N 2025-02-21 21:37:48 +01:00 committed by Brecht Van Lommel
parent 7e0dad0580
commit a12bce039f
17 changed files with 201 additions and 25 deletions

View File

@ -1533,8 +1533,30 @@ class CYCLES_LIGHT_PT_light(CyclesButtonsPanel, Panel):
layout.row().prop(light, "type")
col = layout.column()
heading = col.column(align=True, heading="Temperature")
row = heading.column(align=True).row(align=True)
row.prop(light, "use_temperature", text="")
# Don't show color preview for now, it is grayed out so the color
# is not accurate. Would not a change in the UI code to allow
# non-editable colors to be displayed as is.
if False: # light.use_temperature:
sub = row.split(factor=0.7, align=True)
sub.active = light.use_temperature
sub.prop(light, "temperature", text="")
sub.prop(light, "temperature_color", text="")
else:
sub = row.row()
sub.active = light.use_temperature
sub.prop(light, "temperature", text="")
col.prop(light, "color")
if light.use_temperature:
col.prop(light, "color", text="Tint")
else:
col.prop(light, "color", text="Color")
layout.separator()
col = layout.column()
col.prop(light, "energy")
col.prop(light, "exposure")
@ -1556,6 +1578,7 @@ class CYCLES_LIGHT_PT_light(CyclesButtonsPanel, Panel):
sub.prop(light, "size", text="Size X")
sub.prop(light, "size_y", text="Y")
class CYCLES_LIGHT_PT_settings(CyclesButtonsPanel, Panel):
bl_label = "Settings"
bl_context = "data"

View File

@ -4,6 +4,10 @@
#include "scene/light.h"
#include "DNA_light_types.h"
#include "IMB_colormanagement.hh"
#include "blender/sync.h"
#include "blender/util.h"
#include "scene/object.h"
@ -74,9 +78,14 @@ void BlenderSync::sync_light(BObjectInfo &b_ob_info, Light *light)
}
}
/* strength */
const float3 strength = get_float3(b_light.color()) *
(BL::PointLight(b_light).energy() * exp2f(b_light.exposure()));
/* Color and strength. */
float3 light_color = get_float3(b_light.color());
if (b_light.use_temperature()) {
light_color *= get_float3(b_light.temperature_color());
}
const float3 strength = light_color * BL::PointLight(b_light).energy() *
exp2f(b_light.exposure());
light->set_strength(strength);
/* shadow */

View File

@ -167,8 +167,19 @@ class STORM_HYDRA_LIGHT_PT_light(Panel):
layout.use_property_decorate = False
main_col = layout.column()
heading = main_col.column(align=True, heading="Temperature")
row = heading.column(align=True).row(align=True)
row.prop(light, "use_temperature", text="")
sub = row.row()
sub.active = light.use_temperature
sub.prop(light, "temperature", text="")
main_col.prop(light, "color")
if light.use_temperature:
main_col.prop(light, "color", text="Tint")
else:
main_col.prop(light, "color", text="Color")
main_col = layout.column()
main_col.prop(light, "energy")
main_col.prop(light, "exposure")
main_col.separator()

View File

@ -603,12 +603,18 @@ def fbx_data_light_elements(root, lamp, scene_data):
elem_data_single_int32(light, b"GeometryVersion", FBX_GEOMETRY_VERSION) # Sic...
intensity = lamp.energy * 100.0 * pow(2.0, lamp.exposure)
color = lamp.color
if lamp.use_temperature:
temperature_color = lamp.temperature_color
color[0] *= temperature_color[0]
color[1] *= temperature_color[1]
color[2] *= temperature_color[2]
tmpl = elem_props_template_init(scene_data.templates, b"Light")
props = elem_properties(light)
elem_props_template_set(tmpl, props, "p_enum", b"LightType", FBX_LIGHT_TYPES[lamp.type])
elem_props_template_set(tmpl, props, "p_bool", b"CastLight", do_light)
elem_props_template_set(tmpl, props, "p_color", b"Color", lamp.color)
elem_props_template_set(tmpl, props, "p_color", b"Color", color)
elem_props_template_set(tmpl, props, "p_number", b"Intensity", intensity)
elem_props_template_set(tmpl, props, "p_enum", b"DecayType", FBX_LIGHT_DECAY_TYPES['INVERSE_SQUARE'])
elem_props_template_set(tmpl, props, "p_double", b"DecayStart", 25.0 * gscale) # 25 is old Blender default

View File

@ -89,7 +89,30 @@ class DATA_PT_EEVEE_light(DataButtonsPanel, Panel):
layout.row().prop(light, "type")
col = layout.column()
col.prop(light, "color")
heading = col.column(align=True, heading="Temperature")
row = heading.column(align=True).row(align=True)
row.prop(light, "use_temperature", text="")
# Don't show color preview for now, it is grayed out so the color
# is not accurate. Would not a change in the UI code to allow
# non-editable colors to be displayed as is.
if False: # light.use_temperature:
sub = row.split(factor=0.7, align=True)
sub.active = light.use_temperature
sub.prop(light, "temperature", text="")
sub.prop(light, "temperature_color", text="")
else:
sub = row.row()
sub.active = light.use_temperature
sub.prop(light, "temperature", text="")
if light.use_temperature:
col.prop(light, "color", text="Tint")
else:
col.prop(light, "color", text="Color")
layout.separator()
col = layout.column()
col.prop(light, "energy")
col.prop(light, "exposure")

View File

@ -10,11 +10,15 @@
*/
#include "BLI_compiler_attrs.h"
#include "BLI_math_vector_types.hh"
struct Depsgraph;
struct Light;
struct Main;
struct Light *BKE_light_add(struct Main *bmain, const char *name) ATTR_WARN_UNUSED_RESULT;
Light *BKE_light_add(Main *bmain, const char *name) ATTR_WARN_UNUSED_RESULT;
void BKE_light_eval(struct Depsgraph *depsgraph, struct Light *la);
void BKE_light_eval(Depsgraph *depsgraph, Light *la);
float BKE_light_power(const Light &light);
blender::float3 BKE_light_color(const Light &light);

View File

@ -33,6 +33,8 @@
#include "DEG_depsgraph.hh"
#include "IMB_colormanagement.hh"
#include "BLO_read_write.hh"
static void light_init_data(ID *id)
@ -199,3 +201,21 @@ void BKE_light_eval(Depsgraph *depsgraph, Light *la)
{
DEG_debug_print_eval(depsgraph, __func__, la->id.name, la);
}
float BKE_light_power(const Light &light)
{
return light.energy * exp2f(light.exposure);
}
blender::float3 BKE_light_color(const Light &light)
{
blender::float3 color(&light.r);
if (light.mode & LA_USE_TEMPERATURE) {
float temperature_color[4];
IMB_colormanagement_blackbody_temperature_to_rgb(temperature_color, light.temperature);
color *= blender::float3(temperature_color);
}
return color;
}

View File

@ -13,6 +13,7 @@
#include "DNA_anim_types.h"
#include "DNA_brush_types.h"
#include "DNA_defaults.h"
#include "DNA_light_types.h"
#include "DNA_mesh_types.h"
#include "DNA_sequence_types.h"
@ -5557,6 +5558,14 @@ void blo_do_versions_450(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 76)) {
LISTBASE_FOREACH (Light *, light, &bmain->lights) {
if (light->temperature == 0.0f) {
light->temperature = 6500.0f;
}
}
}
/* Always run this versioning (keep at the bottom of the function). Meshes are written with the
* legacy format which always needs to be converted to the new format on file load. To be moved
* to a subversion check in 5.0. */

View File

@ -14,11 +14,11 @@
#include "eevee_light.hh"
#include "BLI_math_rotation.h"
#include "DNA_defaults.h"
#include "DNA_sdna_type_ids.hh"
#include "BKE_light.h"
namespace blender::eevee {
/* Convert by putting the least significant bits in the first component. */
@ -68,7 +68,7 @@ void Light::sync(ShadowModule &shadows,
shadow_discard_safe(shadows);
}
this->color = float3(&la->r) * (la->energy * exp2f(la->exposure));
this->color = BKE_light_power(*la) * BKE_light_color(*la);
float3 scale;
object_to_world.view<3, 3>() = normalize_and_get_size(object_to_world.view<3, 3>(), scale);

View File

@ -14,6 +14,8 @@
#include "LightExporter.h"
#include "collada_internal.h"
#include "BKE_light.h"
template<class Functor>
void forEachLightObjectInExportSet(Scene *sce, Functor &f, LinkNode *export_set)
{
@ -46,8 +48,8 @@ void LightsExporter::operator()(Object *ob)
Light *la = (Light *)ob->data;
std::string la_id(get_light_id(ob));
std::string la_name(id_name(la));
const float energy = la->energy * exp2f(la->exposure);
COLLADASW::Color col(la->r * energy, la->g * energy, la->b * energy);
const blender::float3 color = BKE_light_power(*la) * BKE_light_color(*la);
COLLADASW::Color col(color[0], color[1], color[2]);
/* sun */
if (la->type == LA_SUN) {

View File

@ -10,6 +10,8 @@
#include "DNA_light_types.h"
#include "IMB_colormanagement.hh"
#include "BLI_math_rotation.h"
#include "hydra_scene_delegate.hh"
@ -84,9 +86,13 @@ void LightData::init()
intensity = light->energy / M_PI;
}
pxr::GfVec3f color(light->r, light->g, light->b);
data_[pxr::HdLightTokens->color] = color; // We multiply the Temperature by 1
data_[pxr::HdLightTokens->enableColorTemperature] = (light->mode & LA_USE_TEMPERATURE) != 0;
data_[pxr::HdLightTokens->colorTemperature] = light->temperature;
data_[pxr::HdLightTokens->intensity] = intensity;
data_[pxr::HdLightTokens->exposure] = light->exposure;
data_[pxr::HdLightTokens->color] = pxr::GfVec3f(light->r, light->g, light->b);
data_[pxr::HdLightTokens->diffuse] = light->diff_fac;
data_[pxr::HdLightTokens->specular] = light->spec_fac;
data_[pxr::HdLightTokens->normalize] = true;

View File

@ -9,6 +9,8 @@
#include "BKE_light.h"
#include "BKE_object.hh"
#include "IMB_colormanagement.hh"
#include "DNA_light_types.h"
#include "DNA_object_types.h"
@ -175,6 +177,23 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
}
}
/* Temperature */
if (pxr::UsdAttribute enable_temperature_attr = light_api.GetEnableColorTemperatureAttr()) {
bool enable_temperature = false;
if (enable_temperature_attr.Get(&enable_temperature, motionSampleTime)) {
if (enable_temperature) {
blight->mode |= LA_USE_TEMPERATURE;
}
}
}
if (pxr::UsdAttribute color_temperature_attr = light_api.GetColorTemperatureAttr()) {
float color_temperature = 6500.0f;
if (color_temperature_attr.Get(&color_temperature, motionSampleTime)) {
blight->temperature = color_temperature;
}
}
/* Diffuse and Specular. */
if (pxr::UsdAttribute diff_attr = light_api.GetDiffuseAttr()) {
float diff_fac = 1.0f;
@ -200,11 +219,6 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
blight->energy *= light_surface_area;
}
/* TODO:
* bool GetEnableColorTemperatureAttr
* float GetColorTemperatureAttr
*/
USDXformReader::read_object_data(bmain, motionSampleTime);
}

View File

@ -146,10 +146,21 @@ void USDLightWriter::do_write(HierarchyContext &context)
light->exposure,
timecode,
usd_value_writer_);
set_attribute(usd_light_api.CreateColorAttr(pxr::VtValue(), true),
pxr::GfVec3f(light->r, light->g, light->b),
timecode,
usd_value_writer_);
set_attribute(usd_light_api.CreateEnableColorTemperatureAttr(
pxr::VtValue(), (light->mode & LA_USE_TEMPERATURE) != 0),
true,
timecode,
usd_value_writer_);
set_attribute(usd_light_api.CreateColorTemperatureAttr(pxr::VtValue(), true),
light->temperature,
timecode,
usd_value_writer_);
set_attribute(usd_light_api.CreateDiffuseAttr(pxr::VtValue(), true),
light->diff_fac,
timecode,

View File

@ -19,6 +19,7 @@
.r = 1.0f, \
.g = 1.0f, \
.b = 1.0f, \
.temperature = 6500.0f, \
.energy = 10.0f, \
.energy_deprecated = 10.0f, \
.spotsize = DEG2RADF(45.0f), \

View File

@ -34,11 +34,11 @@ typedef struct Light {
short type, flag;
int mode;
/* Color and energy. */
/* Color, temperature and energy. */
float r, g, b;
float temperature;
float energy;
float exposure;
float _pad3;
/* Point light. */
float radius;
@ -144,6 +144,7 @@ enum {
/** Use absolute resolution clamping instead of relative. */
LA_SHAD_RES_ABSOLUTE = 1 << 22,
LA_SHADOW_JITTER = 1 << 23,
LA_USE_TEMPERATURE = 1 << 24,
};
/** #Light::falloff_type */

View File

@ -1714,7 +1714,7 @@ void RNA_def_property_ui_range(
DefRNA.error = true;
}
if (step < 0 || step > 100) {
if (step < 0 || step > 1000) {
CLOG_ERROR(&LOG, "\"%s.%s\", step outside range.", srna->identifier, prop->identifier);
DefRNA.error = true;
}

View File

@ -14,10 +14,13 @@
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "RNA_types.hh"
#include "rna_internal.hh"
#include "DNA_light_types.h"
#include "IMB_colormanagement.hh"
#ifdef RNA_RUNTIME
# include "MEM_guardedalloc.h"
@ -77,6 +80,18 @@ static void rna_Light_use_nodes_update(bContext *C, PointerRNA *ptr)
rna_Light_update(CTX_data_main(C), CTX_data_scene(C), ptr);
}
static void rna_Light_temperature_color_get(PointerRNA *ptr, float *color)
{
Light *la = (Light *)ptr->data;
float rgb[4];
IMB_colormanagement_blackbody_temperature_to_rgb(rgb, la->temperature);
color[0] = rgb[0];
color[1] = rgb[1];
color[2] = rgb[2];
}
#else
/* NOTE(@dingto): Don't define icons here,
@ -109,6 +124,13 @@ static void rna_def_light(BlenderRNA *brna)
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_LIGHT);
RNA_def_property_update(prop, 0, "rna_Light_draw_update");
prop = RNA_def_property(srna, "use_temperature", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "mode", LA_USE_TEMPERATURE);
RNA_def_property_ui_text(
prop, "Use Temperature", "Use blackbody temperature to define a natural light color");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_LIGHT);
RNA_def_property_update(prop, 0, "rna_Light_draw_update");
prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR);
RNA_def_property_float_sdna(prop, nullptr, "r");
RNA_def_property_array(prop, 3);
@ -116,6 +138,20 @@ static void rna_def_light(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Color", "Light color");
RNA_def_property_update(prop, 0, "rna_Light_draw_update");
prop = RNA_def_property(srna, "temperature", PROP_FLOAT, PROP_TEMPERATURE);
RNA_def_property_float_sdna(prop, nullptr, "temperature");
RNA_def_property_range(prop, 800.0f, 20000.0f);
RNA_def_property_ui_range(prop, 800.0f, 20000.0f, 400.0f, 1);
RNA_def_property_ui_text(prop, "Temperature", "Light color temperature in Kelvin");
RNA_def_property_update(prop, 0, "rna_Light_update");
prop = RNA_def_property(srna, "temperature_color", PROP_FLOAT, PROP_COLOR);
RNA_def_property_array(prop, 3);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_float_funcs(prop, "rna_Light_temperature_color_get", nullptr, nullptr);
RNA_def_property_ui_text(prop, "Temperature Color", "Color from Temperature");
RNA_def_property_update(prop, 0, "rna_Light_draw_update");
prop = RNA_def_property(srna, "specular_factor", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, nullptr, "spec_fac");
RNA_def_property_range(prop, 0.0f, FLT_MAX);
@ -211,7 +247,7 @@ static void rna_def_light_energy(StructRNA *srna, const short light_type)
/* Lights with a location have radiometric ppower in Watts,
* which is sensitive to scene unit scale. */
prop = RNA_def_property(srna, "energy", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_range(prop, 0.0f, 1000000.0f, 10, 5);
RNA_def_property_ui_range(prop, 0.0f, 1000000.0f, 10, 3);
RNA_def_property_ui_text(
prop,
"Power",
@ -225,7 +261,7 @@ static void rna_def_light_energy(StructRNA *srna, const short light_type)
/* Lights with a location have radiometric power in Watts,
* which is sensitive to scene unit scale. */
prop = RNA_def_property(srna, "energy", PROP_FLOAT, PROP_NONE);
RNA_def_property_ui_range(prop, 0.0f, 1000000.0f, 10, 5);
RNA_def_property_ui_range(prop, 0.0f, 1000000.0f, 10, 3);
RNA_def_property_ui_text(prop,
"Power",
"Light energy emitted over the entire area of the light in all "