Files
sbox-public/engine/Sandbox.Engine/Systems/SceneSystem/Lights/SceneLight.cs
Sam Pavlovic ea81bd18c1 Screenspace Shadows (#5099)
* Screen space shadows

* Pass screenspace shadow to directional light

* Simplify screenspaceshadows

* cleanup, format

* Guard against compositing screenspace shadows to translucent or compute

* Add validity check for ShadowMaskTextureIndex getter and setter

* Add Screen Space Shadows to dependency index

* Remove debug shader for screen space shadows

* No need for this, access directly from scenecamera

* Dont heap alloc

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Dont heap alloc here either

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* move stackalloc out of the loop

* Rename license file

* Implicitly add screen space shadows component to directional light, make SSS component hidden until supported in local lights

* Update shaders

* Move ScreenSpaceShadows from a component to just a class driven by directionallight

* ScreenSpaceShadows class internal, we don't want to expose any of this to the public.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-07 10:42:38 +01:00

248 lines
5.7 KiB
C#

using NativeEngine;
namespace Sandbox;
/// <summary>
/// Base class for light scene objects for use with a <see cref="SceneWorld"/>.
/// </summary>
[Expose]
public class SceneLight : SceneObject
{
internal CSceneLightObject lightNative;
internal SceneLight() { }
internal SceneLight( HandleCreationData _ ) { }
/// <summary>
/// Color and brightness of the light
/// </summary>
public Color LightColor
{
get { return lightNative.GetColor(); }
set { lightNative.SetColor( value ); }
}
/// <summary>
/// Radius of the light in units
/// </summary>
public float Radius
{
get { return lightNative.GetRadius(); }
set
{
if ( Radius == value ) return;
lightNative.SetRadius( value );
}
}
/// <summary>
/// The light attenuation constant term
/// </summary>
public float ConstantAttenuation
{
get { return lightNative.GetConstantAttn(); }
set { lightNative.SetConstantAttn( value ); }
}
/// <summary>
/// The light attenuation linear term
/// </summary>
public float LinearAttenuation
{
get { return lightNative.GetLinearAttn(); }
set { lightNative.SetLinearAttn( value ); }
}
/// <summary>
/// The light attenuation quadratic term
/// </summary>
public float QuadraticAttenuation
{
// Note: to make these numbers sane I'm doing some calculation here
get { return lightNative.GetQuadraticAttn() * 10000.0f; }
set { lightNative.SetQuadraticAttn( value / 10000.0f ); }
}
/// <summary>
/// Get or set the resolution of the shadow map. If this is zero the engine will decide what it should use.
/// </summary>
public int ShadowTextureResolution
{
get { return lightNative.GetShadowTextureResolution(); }
set { lightNative.SetShadowTextureResolution( value ); }
}
/// <summary>
/// Enable or disable shadow rendering
/// </summary>
public bool ShadowsEnabled
{
get { return lightNative.GetShadows(); }
set { lightNative.SetShadows( value ); }
}
private Texture _lightCookie;
/// <summary>
/// Access the LightCookie - which is a texture that gets drawn over the light
/// </summary>
public Texture LightCookie
{
get => _lightCookie ??= Texture.FromNative( lightNative.GetLightCookie() );
set
{
_lightCookie = value;
lightNative.SetLightCookie( value == null ? default : value.native );
}
}
/// <summary>
/// Should this light contribute diffuse lighting?
/// </summary>
public bool RenderDiffuse
{
get => _renderDiffuse;
set
{
_renderDiffuse = value;
lightNative.SetRenderDiffuse( value );
}
}
private bool _renderDiffuse = true;
/// <summary>
/// Should this light contribute specular highlights?
/// </summary>
public bool RenderSpecular
{
get => _renderSpecular;
set
{
_renderSpecular = value;
lightNative.SetRenderSpecular( value );
}
}
private bool _renderSpecular = true;
/// <summary>
/// Should this light contribute transmissive lighting (light passing through surfaces)?
/// </summary>
public bool RenderTransmissive
{
get => _renderTransmissive;
set
{
_renderTransmissive = value;
lightNative.SetRenderTransmissive( value );
}
}
private bool _renderTransmissive = true;
public enum FogLightingMode
{
None,
Baked,
Dynamic,
DynamicNoShadows
}
public enum LightShape
{
Sphere,
Capsule,
Rectangle
}
public LightShape Shape
{
get => (LightShape)lightNative.GetLightShape();
set => lightNative.SetLightShape( (LightSourceShape_t)value );
}
public Vector2 ShapeSize
{
set
{
lightNative.SetLightSourceDim0( value.x );
lightNative.SetLightSourceDim1( value.y );
}
}
public FogLightingMode FogLighting
{
get => (FogLightingMode)lightNative.GetFogLightingMode();
set => lightNative.SetFogLightingMode( (int)value );
}
public float FogStrength
{
get => lightNative.GetFogContributionStength();
set => lightNative.SetFogContributionStength( value );
}
/// <summary>
/// Stupid fucking shit
/// </summary>
internal Vector3 WorldDirection => lightNative.GetWorldDirection();
public float ShadowBias { get; set; } = 0.0005f;
public float ShadowHardness { get; set; } = 0.0f;
/// <summary>
/// Bindless indices of the screen-space shadow masks for this light, keyed by managed camera id
/// (masks are generated per rendering view). Written from threaded procedural-layer jobs while
/// other views' light binning reads it, hence the concurrent dictionary.
/// </summary>
internal readonly System.Collections.Concurrent.ConcurrentDictionary<int, uint> ShadowMaskTextureIndices = new();
internal override void OnTransformChanged( in Transform tx )
{
base.OnTransformChanged( tx );
lightNative.SetWorldDirection( tx.Rotation );
lightNative.SetWorldPosition( tx.Position );
}
[Obsolete( "Use ScenePointLight (or stop fucking using SceneObjects at all)" )]
public SceneLight( SceneWorld sceneWorld, Vector3 position, float radius, Color color )
{
Assert.IsValid( sceneWorld );
using ( var h = IHandle.MakeNextHandle( this ) )
{
CSceneSystem.CreatePointLight( sceneWorld );
}
Position = position;
Radius = radius;
LightColor = color;
QuadraticAttenuation = 1.0f;
}
[Obsolete( "Use ScenePointLight (or stop fucking using SceneObjects at all)" )]
public SceneLight( SceneWorld sceneWorld ) : this( sceneWorld, Vector3.Zero, 100, Color.White * 10.0f )
{
}
internal override void OnNativeInit( CSceneObject ptr )
{
base.OnNativeInit( ptr );
lightNative = (CSceneLightObject)ptr;
}
internal override void OnNativeDestroy()
{
// Return any cached shadow map to the pool before this object becomes
// eligible for GC. Without this the ConditionalWeakTable silently drops
// the entry on collection and the shadow texture is orphaned.
Rendering.ShadowMapper.OnLightRemoved( this );
lightNative = IntPtr.Zero;
base.OnNativeDestroy();
}
}