Lights: Add normalize property

When enabled, this normalize the strength by the light area, to keep
the total output the same regardless of shape or size. This is the
existing behavior.

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

For add-ons, an API function to compute the area is added for conversion,
in case there is no native support for normalization.

area = light.area(matrix_world=ob.matrix_world)

Co-authored-by: Brecht Van Lommel <brecht@blender.org>

Pull Request: https://projects.blender.org/blender/blender/pulls/136958
This commit is contained in:
Eqkoss / T1NT1N
2025-02-21 21:37:48 +01:00
committed by Brecht Van Lommel
parent a428815716
commit bb9d5cdaad
42 changed files with 173 additions and 84 deletions

View File

@@ -1559,6 +1559,7 @@ class CYCLES_LIGHT_PT_light(CyclesButtonsPanel, Panel):
col = layout.column()
col.prop(light, "energy")
col.prop(light, "exposure")
col.prop(light, "normalize")
layout.separator()

View File

@@ -88,6 +88,9 @@ void BlenderSync::sync_light(BObjectInfo &b_ob_info, Light *light)
exp2f(b_light.exposure());
light->set_strength(strength);
/* normalize */
light->set_normalize(b_light.normalize());
/* shadow */
PointerRNA clight = RNA_pointer_get(&b_light.ptr, "cycles");
light->set_cast_shadow(b_light.use_shadow());

View File

@@ -182,6 +182,7 @@ class STORM_HYDRA_LIGHT_PT_light(Panel):
main_col = layout.column()
main_col.prop(light, "energy")
main_col.prop(light, "exposure")
main_col.prop(light, "normalize")
main_col.separator()
if light.type == 'POINT':

View File

@@ -115,6 +115,7 @@ class DATA_PT_EEVEE_light(DataButtonsPanel, Panel):
col = layout.column()
col.prop(light, "energy")
col.prop(light, "exposure")
col.prop(light, "normalize")
layout.separator()

View File

@@ -10,6 +10,7 @@
*/
#include "BLI_compiler_attrs.h"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
struct Depsgraph;
@@ -22,3 +23,4 @@ void BKE_light_eval(Depsgraph *depsgraph, Light *la);
float BKE_light_power(const Light &light);
blender::float3 BKE_light_color(const Light &light);
float BKE_light_area(const Light &light, const blender::float4x4 &object_to_world);

View File

@@ -19,6 +19,9 @@
#include "DNA_node_types.h"
#include "DNA_scene_types.h"
#include "BLI_math_base.hh"
#include "BLI_math_matrix.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_utildefines.h"
#include "BKE_icons.h"
@@ -219,3 +222,43 @@ blender::float3 BKE_light_color(const Light &light)
return color;
}
float BKE_light_area(const Light &light, const blender::float4x4 &object_to_world)
{
/* Make illumination power constant. */
switch (light.type) {
case LA_AREA: {
/* Rectangle area. */
const blender::float3x3 scalemat = object_to_world.view<3, 3>();
const blender::float3 scale = blender::math::to_scale(scalemat);
const float size_x = light.area_size * scale.x;
const float size_y = (ELEM(light.area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE) ?
light.area_sizey :
light.area_size) *
scale.y;
float area = size_x * size_y;
/* Scale for smaller area of the ellipse compared to the surrounding rectangle. */
if (ELEM(light.area_shape, LA_AREA_DISK, LA_AREA_ELLIPSE)) {
area *= float(M_PI / 4.0f);
}
return area;
}
case LA_LOCAL:
case LA_SPOT: {
/* Sphere area. For legacy reasons object scale is not taken into account
* here, even though logically it should be. */
const float radius = light.radius;
return (radius > 0.0f) ? float(4.0f * M_PI) * blender::math::square(radius) : 4.0f;
}
case LA_SUN: {
/* Sun disk area. */
const float angle = light.sun_angle / 2.0f;
return (angle > 0.0f) ? float(M_PI) * blender::math::square(sinf(angle)) : 1.0f;
}
}
BLI_assert_unreachable();
return 1.0f;
}

View File

@@ -15,6 +15,7 @@
#include "eevee_light.hh"
#include "DNA_defaults.h"
#include "DNA_light_types.h"
#include "DNA_sdna_type_ids.hh"
#include "BKE_light.h"
@@ -69,6 +70,9 @@ void Light::sync(ShadowModule &shadows,
}
this->color = BKE_light_power(*la) * BKE_light_color(*la);
if (la->mode & LA_UNNORMALIZED) {
this->color *= BKE_light_area(*la, object_to_world);
}
float3 scale;
object_to_world.view<3, 3>() = normalize_and_get_size(object_to_world.view<3, 3>(), scale);

View File

@@ -11,6 +11,7 @@
#include "COLLADASWColor.h"
#include "COLLADASWLight.h"
#include "DNA_light_types.h"
#include "LightExporter.h"
#include "collada_internal.h"
@@ -48,7 +49,10 @@ 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 blender::float3 color = BKE_light_power(*la) * BKE_light_color(*la);
blender::float3 color = BKE_light_power(*la) * BKE_light_color(*la);
if (la->mode & LA_UNNORMALIZED) {
color *= BKE_light_area(*la, ob->runtime->object_to_world);
}
COLLADASW::Color col(color[0], color[1], color[2]);
/* sun */

View File

@@ -95,7 +95,7 @@ void LightData::init()
data_[pxr::HdLightTokens->exposure] = light->exposure;
data_[pxr::HdLightTokens->diffuse] = light->diff_fac;
data_[pxr::HdLightTokens->specular] = light->spec_fac;
data_[pxr::HdLightTokens->normalize] = true;
data_[pxr::HdLightTokens->normalize] = (light->mode & LA_UNNORMALIZED) == 0;
prim_type_ = prim_type(light);

View File

@@ -43,8 +43,6 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
return;
}
float light_surface_area = 1.0f;
if (prim_.IsA<pxr::UsdLuxDiskLight>()) {
/* Disk area light. */
blight->type = LA_AREA;
@@ -59,9 +57,6 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
}
}
}
const float radius = 0.5f * blight->area_size;
light_surface_area = radius * radius * M_PI;
}
else if (prim_.IsA<pxr::UsdLuxRectLight>()) {
/* Rectangular area light. */
@@ -84,8 +79,6 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
}
}
}
light_surface_area = blight->area_size * blight->area_sizey;
}
else if (prim_.IsA<pxr::UsdLuxSphereLight>()) {
/* Point and spot light. */
@@ -108,8 +101,6 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
}
}
light_surface_area = 4.0f * M_PI * blight->radius * blight->radius;
pxr::UsdLuxShapingAPI shaping_api = pxr::UsdLuxShapingAPI(prim_);
if (shaping_api && shaping_api.GetShapingConeAngleAttr().IsAuthored()) {
blight->type = LA_SPOT;
@@ -208,15 +199,14 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
}
}
/* Normalize: Blender lights are always normalized, so inverse correct for it
* TODO: take into account object transform, or natively support this as a
* setting on lights in Blender. */
bool normalize = false;
/* Normalize */
if (pxr::UsdAttribute normalize_attr = light_api.GetNormalizeAttr()) {
normalize_attr.Get(&normalize, motionSampleTime);
}
if (!normalize) {
blight->energy *= light_surface_area;
bool normalize = false;
if (normalize_attr.Get(&normalize, motionSampleTime)) {
if (!normalize) {
blight->mode |= LA_UNNORMALIZED;
}
}
}
USDXformReader::read_object_data(bmain, motionSampleTime);

View File

@@ -169,8 +169,10 @@ void USDLightWriter::do_write(HierarchyContext &context)
light->spec_fac,
timecode,
usd_value_writer_);
set_attribute(
usd_light_api.CreateNormalizeAttr(pxr::VtValue(), true), true, timecode, usd_value_writer_);
set_attribute(usd_light_api.CreateNormalizeAttr(pxr::VtValue(), true),
(light->mode & LA_UNNORMALIZED) == 0,
timecode,
usd_value_writer_);
pxr::UsdPrim prim = usd_light_api.GetPrim();
write_id_properties(prim, light->id, timecode);

View File

@@ -145,6 +145,7 @@ enum {
LA_SHAD_RES_ABSOLUTE = 1 << 22,
LA_SHADOW_JITTER = 1 << 23,
LA_USE_TEMPERATURE = 1 << 24,
LA_UNNORMALIZED = 1 << 25,
};
/** #Light::falloff_type */

View File

@@ -25,7 +25,10 @@
# include "MEM_guardedalloc.h"
# include "BLI_math_matrix_types.hh"
# include "BKE_context.hh"
# include "BKE_light.h"
# include "BKE_main.hh"
# include "BKE_texture.h"
@@ -84,12 +87,23 @@ 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);
if (la->mode & LA_USE_TEMPERATURE) {
float rgb[4];
IMB_colormanagement_blackbody_temperature_to_rgb(rgb, la->temperature);
color[0] = rgb[0];
color[1] = rgb[1];
color[2] = rgb[2];
color[0] = rgb[0];
color[1] = rgb[1];
color[2] = rgb[2];
}
else {
copy_v3_fl(color, 1.0f);
}
}
static float rna_Light_area(Light *light, const float matrix_world[16])
{
blender::float4x4 mat(matrix_world);
return BKE_light_area(*light, mat);
}
#else
@@ -105,6 +119,19 @@ const EnumPropertyItem rna_enum_light_type_items[] = {
{0, nullptr, 0, nullptr, nullptr},
};
static void rna_def_light_api(StructRNA *srna)
{
FunctionRNA *func = RNA_def_function(srna, "area", "rna_Light_area");
RNA_def_function_ui_description(func,
"Compute light area based on type and shape. The normalize "
"option divides light intensity by this area");
PropertyRNA *parm = RNA_def_property(func, "matrix_world", PROP_FLOAT, PROP_MATRIX);
RNA_def_property_multi_array(parm, 2, rna_matrix_dimsize_4x4);
RNA_def_property_ui_text(parm, "", "Object to world space transformation matrix");
parm = RNA_def_property(func, "area", PROP_FLOAT, PROP_NONE);
RNA_def_function_return(func, parm);
}
static void rna_def_light(BlenderRNA *brna)
{
StructRNA *srna;
@@ -209,6 +236,15 @@ static void rna_def_light(BlenderRNA *brna)
"Scales the power of the light exponentially, multiplying the intensity by 2^exposure");
RNA_def_property_update(prop, 0, "rna_Light_update");
prop = RNA_def_property(srna, "normalize", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(prop, nullptr, "mode", LA_UNNORMALIZED);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop,
"Normalize",
"Normalize intensity by light area, for consistent total light "
"output regardless of size and shape");
RNA_def_property_update(prop, 0, "rna_Light_draw_update");
/* nodes */
prop = RNA_def_property(srna, "node_tree", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, nullptr, "nodetree");
@@ -225,6 +261,7 @@ static void rna_def_light(BlenderRNA *brna)
/* common */
rna_def_animdata_common(srna);
rna_def_light_api(srna);
}
static void rna_def_light_energy(StructRNA *srna, const short light_type)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.