EEVEE: Add support for camera light visibility

Ref #119100

Adds support for `visible_camera` in EEVEE.

Pull Request: https://projects.blender.org/blender/blender/pulls/158416
This commit is contained in:
Le Gia Bao 2026-05-14 19:50:58 +02:00 • committed by Clément Foucault
parent d0c8ae7db3
commit f07bcb85cb
23 changed files with 268 additions and 27 deletions

View file

@ -437,6 +437,7 @@ class OBJECT_PT_visibility(ObjectButtonsPanel, Panel):
if ob.type in {'LIGHT'}:
layout.separator()
col = layout.column(heading="Ray Visibility")
col.prop(ob, "visible_camera", text="Camera", toggle=False)
col.prop(ob, "visible_diffuse", text="Diffuse", toggle=False)
col.prop(ob, "visible_glossy", text="Glossy", toggle=False)
col.prop(ob, "visible_transmission", text="Transmission", toggle=False)

View file

@ -445,6 +445,7 @@ set(GLSL_SRC
engines/eevee/shaders/eevee_hiz_update_comp.glsl
engines/eevee/shaders/eevee_fast_gi.bsl.hh
engines/eevee/shaders/eevee_light_culling.bsl.hh
engines/eevee/shaders/eevee_light_shape_display.bsl.hh
engines/eevee/shaders/eevee_light_shadow_setup.bsl.hh
engines/eevee/shaders/eevee_light_data.bsl.hh
engines/eevee/shaders/eevee_light_eval.bsl.hh

View file

@ -103,6 +103,7 @@ void Light::sync(ShadowModule &shadows,
this->lod_min = shadow_lod_min_get(la);
this->filter_radius = la->shadow_filter_radius;
this->shadow_jitter = (la->mode & LA_SHADOW_JITTER) != 0;
this->visible_camera = (visibility_flag & OB_HIDE_CAMERA) == 0;
if (la->mode & LA_SHADOW) {
shadow_ensure(shadows);
@ -376,6 +377,7 @@ void LightModule::add_world_sun_light(const ObjectKey &key, bool use_diffuse, bo
int visibility_flag = 0;
SET_FLAG_FROM_TEST(visibility_flag, !use_diffuse, OB_HIDE_DIFFUSE);
SET_FLAG_FROM_TEST(visibility_flag, !use_glossy, OB_HIDE_GLOSSY);
visibility_flag |= OB_HIDE_CAMERA;
Light &light = light_map_.lookup_or_add_default(key);
light.used = true;
@ -535,9 +537,27 @@ void LightModule::end_sync()
culling_pass_sync();
update_pass_sync();
shape_display_pass_sync();
debug_pass_sync();
}
void LightModule::shape_display_pass_sync()
{
shape_display_ps_.init();
if (lights_len_ == 0) {
return;
}
shape_display_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_BLEND_ADD |
DRW_STATE_CLIP_CONTROL_UNIT_RANGE | inst_.film.depth.test_state);
shape_display_ps_.shader_set(inst_.shaders.static_shader_get(LIGHT_SHAPE_DISPLAY));
shape_display_ps_.bind_resources(inst_.uniform_data);
shape_display_ps_.bind_resources(inst_.volume.result);
shape_display_ps_.bind_resources(inst_.lights);
shape_display_ps_.draw_procedural(GPU_PRIM_TRIS, 1, lights_len_ * 6);
}
void LightModule::culling_pass_sync()
{
uint safe_lights_len = max_ii(lights_len_, 1);
@ -654,6 +674,12 @@ void LightModule::debug_draw(View &view, gpu::FrameBuffer *view_fb)
}
}
void LightModule::shape_display_draw(View &view, gpu::FrameBuffer *view_fb)
{
GPU_framebuffer_bind(view_fb);
inst_.manager->submit(shape_display_ps_, view);
}
/** \} */
} // namespace blender::eevee

View file

@ -175,6 +175,8 @@ class LightModule {
/** Update light on the GPU after culling. Ran for each sample. */
PassSimple update_ps_ = {"LightUpdate"};
/** Draw camera-visible light shapes. */
PassSimple shape_display_ps_ = {"Light.ShapeDisplay"};
/** Debug Culling visualization. */
PassSimple debug_draw_ps_ = {"LightCulling.Debug"};
@ -192,6 +194,7 @@ class LightModule {
*/
void set_view(View &view, const int2 extent);
void shape_display_draw(View &view, gpu::FrameBuffer *view_fb);
void debug_draw(View &view, gpu::FrameBuffer *view_fb);
template<typename PassType> void bind_resources(PassType &pass)
@ -205,6 +208,7 @@ class LightModule {
private:
void culling_pass_sync();
void update_pass_sync();
void shape_display_pass_sync();
void debug_pass_sync();
void add_world_sun_light(const ObjectKey &key, bool use_diffuse, bool use_glossy);

View file

@ -201,7 +201,8 @@ struct [[host_shared]] LightData {
float lod_min;
/* True if the light uses jittered soft shadows. */
bool32_t shadow_jitter;
float _pad2;
/* True if the light shape should be visible to camera rays. */
bool32_t visible_camera;
uint2 light_set_membership;
/** Used by shadow sync. */
/* TODO(fclem): this should be part of #eevee::Light struct. But for some reason it gets cleared

View file

@ -181,6 +181,7 @@ ShaderGroups ShaderModule::static_shaders_load(const ShaderGroups request_bits,
LIGHT_CULLING_SORT,
LIGHT_CULLING_TILE,
LIGHT_CULLING_ZBIN,
LIGHT_SHAPE_DISPLAY,
LIGHT_SHADOW_SETUP};
request(LIGHT_CULLING_SHADERS, AS_SPAN(shader_list));
}
@ -449,6 +450,8 @@ const char *ShaderModule::static_shader_create_info_name_get(eShaderType shader_
return "eevee_light_culling_tile";
case LIGHT_CULLING_ZBIN:
return "eevee_light_culling_zbin";
case LIGHT_SHAPE_DISPLAY:
return "eevee_light_shape_display";
case LIGHT_SHADOW_SETUP:
return "eevee_light_shadow_setup";
case RAY_DENOISE_SPATIAL:

View file

@ -91,6 +91,7 @@ enum eShaderType {
LIGHT_CULLING_SORT,
LIGHT_CULLING_TILE,
LIGHT_CULLING_ZBIN,
LIGHT_SHAPE_DISPLAY,
LIGHT_SHADOW_SETUP,
LIGHTPROBE_IRRADIANCE_BOUNDS,

View file

@ -169,6 +169,8 @@ void ShadingView::render()
inst_.pipelines.forward.render(
render_view_, rbufs.depth_tx, prepass_fb_, transparent_fb_, combined_fb_, extent_);
inst_.lights.shape_display_draw(render_view_, combined_fb_);
inst_.lights.debug_draw(render_view_, combined_fb_);
inst_.hiz_buffer.debug_draw(render_view_, combined_fb_);
inst_.shadows.debug_draw(render_view_, combined_fb_);

View file

@ -17,6 +17,7 @@
#include "eevee_light_eval.bsl.hh" /* IWYU pragma: export */
#include "eevee_light_iter.bsl.hh" /* IWYU pragma: export */
#include "eevee_light_shadow_setup.bsl.hh" /* IWYU pragma: export */
#include "eevee_light_shape_display.bsl.hh" /* IWYU pragma: export */
#include "eevee_lightprobe_display.bsl.hh" /* IWYU pragma: export */
#include "eevee_ltc_lib.bsl.hh" /* IWYU pragma: export */
#include "eevee_ltc_lut_lib.bsl.hh" /* IWYU pragma: export */

View file

@ -0,0 +1,201 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "draw_view_infos.hh"
#include "infos/eevee_light_infos.hh"
#include "infos/eevee_volume_resolved_infos.hh"
FRAGMENT_SHADER_CREATE_INFO(draw_view)
FRAGMENT_SHADER_CREATE_INFO(eevee_light_data)
FRAGMENT_SHADER_CREATE_INFO(eevee_volume_lib)
#include "draw_view_lib.glsl"
#include "eevee_light_lib.glsl"
#include "eevee_reverse_z_lib.bsl.hh"
#include "eevee_volume_lib.bsl.hh"
#include "gpu_shader_math_base_lib.glsl"
#include "gpu_shader_math_constants_lib.glsl"
#include "gpu_shader_math_matrix_transform_lib.glsl"
namespace eevee::light {
struct ShapeDisplayResources {
[[legacy_info]] ShaderCreateInfo draw_view;
[[legacy_info]] ShaderCreateInfo eevee_light_data;
[[legacy_info]] ShaderCreateInfo eevee_volume_lib;
};
struct ShapeDisplayVertOut {
[[smooth]] float2 lP;
[[smooth]] float3 P;
[[flat]] float3 radiance;
[[flat]] int light_index;
[[flat]] uint light_type;
};
struct ShapeDisplayFragOut {
[[frag_color(0)]] float4 out_color;
};
float shape_display_light_radiance_get(LightData light)
{
if (is_sun_light(light.type)) {
float radius = light.sun().shape_radius;
return M_1_PI * (1.0f + 1.0f / max(radius * radius, 1e-20f));
}
if (is_area_light(light.type)) {
float area = light.area().size.x * light.area().size.y * 4.0f;
if (light.type == LIGHT_ELLIPSE) {
area *= M_PI * 0.25f;
}
return 1.0f / max(M_PI * area, 1e-20f);
}
float radius = light.local().local.shape_radius;
float area = 4.0f * M_PI * radius * radius;
return 1.0f / max(M_PI * area, 1e-20f);
}
float3 shape_display_light_position_get(LightData light, float2 quad_pos)
{
if (is_area_light(light.type)) {
return transform_point(light.object_to_world, float3(quad_pos * light.area().size, 0.0f));
}
float radius = light.local().local.shape_radius;
float3 center = light_position_get(light);
float3 view_right = drw_view().viewinv[0].xyz;
float3 view_up = drw_view().viewinv[1].xyz;
float3 L = center - drw_view_position();
float dist = length(L);
if (dist > 1e-8f) {
L /= dist;
make_orthonormal_basis(L, view_right, view_up);
}
if (is_sphere_light(light.type)) {
if (dist > radius) {
radius = light_sphere_disk_radius(radius, dist);
}
else {
radius = drw_view_far();
}
}
return center + (view_right * quad_pos.x + view_up * quad_pos.y) * radius;
}
float3 shape_display_far_plane_position_get(float2 ndc_pos)
{
return drw_point_ndc_to_world(float3(ndc_pos, 1.0f));
}
float2 shape_display_quad_position_get(int vertex_id)
{
int quad_vertex_id = vertex_id % 6;
float x = (quad_vertex_id == 0 || quad_vertex_id == 2 || quad_vertex_id == 5) ? -1.0f : 1.0f;
float y = (quad_vertex_id == 0 || quad_vertex_id == 1 || quad_vertex_id == 3) ? -1.0f : 1.0f;
return float2(x, y);
}
[[vertex]] [[clip_control]]
void shape_display_vert([[resource_table]] const ShapeDisplayResources & /*srt*/,
[[vertex_id]] const int vertex_id,
[[out]] ShapeDisplayVertOut &v_out,
[[position]] float4 &out_position)
{
v_out.lP = shape_display_quad_position_get(vertex_id);
v_out.P = float3(0.0f);
v_out.radiance = float3(0.0f);
v_out.light_index = 0;
v_out.light_type = uint(LIGHT_RECT);
out_position = float4(0.0f, 0.0f, 0.0f, 1.0f);
int draw_index = vertex_id / 6;
int visible_count = int(light_cull_buf.visible_count + light_cull_buf.sun_lights_len);
if (draw_index >= visible_count) {
return;
}
int light_index;
if (draw_index < int(light_cull_buf.visible_count)) {
light_index = draw_index;
}
else {
int offset = draw_index - int(light_cull_buf.visible_count);
light_index = int(light_cull_buf.local_lights_len) + offset;
}
LightData light = light_buf[light_index];
if (!light.visible_camera) {
return;
}
v_out.light_type = uint(light.type);
v_out.light_index = light_index;
v_out.radiance = light.color * shape_display_light_radiance_get(light);
if (is_sun_light(light.type)) {
v_out.P = shape_display_far_plane_position_get(v_out.lP);
out_position = reverse_z::transform(float4(v_out.lP, 1.0f, 1.0f));
return;
}
v_out.P = shape_display_light_position_get(light, v_out.lP);
out_position = reverse_z::transform(drw_point_world_to_homogenous(v_out.P));
}
[[fragment]]
void shape_display_frag([[resource_table]] const ShapeDisplayResources & /*srt*/,
[[frag_coord]] const float4 frag_co,
[[in]] const ShapeDisplayVertOut &v_out,
[[out]] ShapeDisplayFragOut &frag_out)
{
eLightType light_type = eLightType(v_out.light_type);
LightData light = light_buf[v_out.light_index];
float3 P = v_out.P;
if (is_sun_light(light_type)) {
float3 V = -drw_world_incident_vector(P);
float3 sun_direction = light.sun().direction;
float sun_cos = cos_from_tan(light.sun().shape_radius);
if (dot(V, sun_direction) < sun_cos) {
gpu_discard_fragment();
return;
}
}
else {
if (is_area_light(light_type) && dot(drw_world_incident_vector(P), light_z_axis(light)) > 0.0f)
{
gpu_discard_fragment();
return;
}
bool is_circle = light_type == LIGHT_ELLIPSE || is_point_light(light_type);
if (is_circle && dot(v_out.lP, v_out.lP) > 1.0f) {
gpu_discard_fragment();
return;
}
}
float depth = reverse_z::read(frag_co.z);
float2 uvs = frag_co.xy * uniform_buf.volumes.main_view_extent_inv;
float3 radiance = v_out.radiance;
if (is_spot_light(light_type)) {
radiance *= light_spot_attenuation(light, -drw_world_incident_vector(P));
}
VolumeResolveSample vol = volume_resolve(
float3(uvs, depth), volume_transmittance_tx, volume_scattering_tx);
frag_out.out_color = float4(radiance * vol.transmittance, 1.0f);
}
PipelineGraphic shape_display(shape_display_vert, shape_display_frag);
} // namespace eevee::light

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8c403104659e659032d1214a6431277b5f65584330a0a741cce8b5c0a9bbf3b2
size 21399
oid sha256:5ec41f722897d0ef6d0edc1efbd364b55b7d57e7098c2ac714842aae9bc58265
size 21482

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9abcdd0f48a74e7b4da7b7f9ebc234755605665f2f6aad7033381cad33927b3d
size 27852
oid sha256:a7f44182dda3bb5ada6428290d3c5e01c21c011b87390bb15d62c40fe6063566
size 27962

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6524a94b41bf9270dc60d8184ecfdbcd4b54d845019db6671213aecfa644d5f2
size 12940
oid sha256:c977e74ae7d26b2794e15fa602f7acc0255c66c1ef32f68bd01ef4db60ce3524
size 12902

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8c90f2719bf3a900b19650040a6162878f1fb648121244fe4e65de2dd321a318
size 17086
oid sha256:b40096d0cf2ff3aa3001162b41398556a381abe366b099c522ae5f8ae7779114
size 17741

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9c184ca262078d82938773b6864ae327bd3658cc0352b8e7531a772182a76a7d
size 7488
oid sha256:478883c8f9e6a53b27706cc28e2b320ddacf3bc3c0a0488c0c26e3cd3bb39cfd
size 6382

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:749da7dd876bc17706200cc5f27dee02342eced95603e9da2b88843f1866fbd9
size 18790
oid sha256:becaa2961d6c4715d1e3468921581c92d0038fe51cd0b9660339c6bfabf0d68b
size 18198

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c9e73537f23761d15ea48b3746639b82501fa94f5772ef3b12e6c5f1c1b43cc0
size 16261
oid sha256:61e39049a9226424675f4536fda00043581d958ecb606f591d0dd54f0c8e60fb
size 16394

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:884416f11a9df1d27f78c4cfac14474fd83e06ce1e9d5995f5677dd8177a148b
size 16272
oid sha256:4805d2a850f1e3b52e381ece3b64947b2b0a3549ca8e10b01e5ba1b196323b69
size 16405

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3445164d7b646bfa424fa8b09d023f01016e10bc1901a3965d3045eafa37758c
size 56563
oid sha256:e3900daf1f642f948b5ae22717281ef0ce0d81d2a871619e23348fdf3eff6c96
size 57614

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f22dd680524511b722cd07d23f36ea19b96f51b3d6e508672ee5260e2361f2f1
size 55786
oid sha256:11d316146b99d7c9cb07937fd151ddaba6574d28528a571e9a70fe02326097f4
size 57177

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f7a18e76cc75f228d73edccc976b276ccbe8aa16f4fb0f96cf1c53bbbe963bc5
size 20444
oid sha256:52a40c3e2a0d609213563cef00a1d1c1b2ffadbd3aca3234018d5c9e02e94b4f
size 20412

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ba68ea54078ca615b2a8cabde91a05af05601c8273f0e7908238a30d5011e941
size 5710
oid sha256:cfeede17acf1194a7d2dd5f62d79353c589b1e2b9ac674d70c2098ed1059726c
size 5949

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b3565479322ad95f1f310a872094e495e6992ab050b592586455214e773c203b
size 17744
oid sha256:b727d62f514ad8362d5db5ce6a44b3053f7189f8fab4fb12aa898e2370e87f90
size 17912