Files
FlaxEngine/Source/Engine/Level/Actors/SpotLight.cpp
2024-02-26 19:00:48 +01:00

286 lines
10 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "SpotLight.h"
#include "Engine/Graphics/RenderView.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Content/Assets/IESProfile.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Level/Scene/SceneRendering.h"
SpotLight::SpotLight(const SpawnParams& params)
: LightWithShadow(params)
, _radius(1000.0f)
, _outerConeAngle(43.0f)
, _innerConeAngle(10.0f)
{
CastVolumetricShadow = false;
ShadowsDistance = 2000.0f;
ShadowsFadeDistance = 100.0f;
ShadowsDepthBias = 0.5f;
_direction = Vector3::Forward;
_cosOuterCone = Math::Cos(_outerConeAngle * DegreesToRadians);
_cosInnerCone = Math::Cos(_innerConeAngle * DegreesToRadians);
_invCosConeDifference = 1.0f / (_cosInnerCone - _cosOuterCone);
const float boundsRadius = Math::Sqrt(1.25f * _radius * _radius - _radius * _radius * _cosOuterCone);
_sphere = BoundingSphere(GetPosition() + 0.5f * GetDirection() * _radius, boundsRadius);
BoundingBox::FromSphere(_sphere, _box);
}
float SpotLight::ComputeBrightness() const
{
float result = Brightness;
if (IESTexture)
{
if (UseIESBrightness)
{
result = IESTexture->Brightness * IESBrightnessScale;
}
result *= IESTexture->TextureMultiplier;
}
//if (UseInverseSquaredFalloff)
// result *= 16.0f;
return result;
}
float SpotLight::GetScaledRadius() const
{
return _radius * _transform.Scale.MaxValue();
}
void SpotLight::SetRadius(float value)
{
value = Math::Max(0.0f, value);
if (Math::NearEqual(value, _radius))
return;
_radius = value;
UpdateBounds();
}
void SpotLight::SetOuterConeAngle(float value)
{
// Clamp value
value = Math::Clamp(value, 0.0f, 89.0f);
// Check if value will change
if (!Math::NearEqual(value, _outerConeAngle))
{
// Change values
_innerConeAngle = Math::Min(_innerConeAngle, value - ZeroTolerance);
_outerConeAngle = value;
UpdateBounds();
}
}
void SpotLight::SetInnerConeAngle(float value)
{
// Clamp value
value = Math::Clamp(value, 0.0f, 89.0f);
// Check if value will change
if (!Math::NearEqual(value, _innerConeAngle))
{
// Change values
_innerConeAngle = value;
_outerConeAngle = Math::Max(_outerConeAngle + ZeroTolerance, value);
UpdateBounds();
}
}
void SpotLight::UpdateBounds()
{
// Cache light direction
Vector3::Transform(Vector3::Forward, _transform.Orientation, _direction);
_direction.Normalize();
// Cache cone angles
_cosOuterCone = Math::Cos(_outerConeAngle * DegreesToRadians);
_cosInnerCone = Math::Cos(_innerConeAngle * DegreesToRadians);
_invCosConeDifference = 1.0f / Math::Max(_cosInnerCone - _cosOuterCone, 0.0001f);
// Cache bounds
// Note: we use the law of cosines to find the distance to the furthest edge of the spotlight cone from a position that is halfway down the spotlight direction
const float radius = GetScaledRadius();
const float boundsRadius = Math::Sqrt(1.25f * radius * radius - radius * radius * _cosOuterCone);
_sphere = BoundingSphere(GetPosition() + 0.5f * GetDirection() * radius, boundsRadius);
BoundingBox::FromSphere(_sphere, _box);
if (_sceneRenderingKey != -1)
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey);
}
void SpotLight::OnTransformChanged()
{
// Base
LightWithShadow::OnTransformChanged();
UpdateBounds();
}
void SpotLight::Draw(RenderContext& renderContext)
{
float brightness = ComputeBrightness();
AdjustBrightness(renderContext.View, brightness);
const Float3 position = GetPosition() - renderContext.View.Origin;
const float radius = GetScaledRadius();
const float outerConeAngle = GetOuterConeAngle();
if (EnumHasAnyFlags(renderContext.View.Flags, ViewFlags::SpotLights)
&& EnumHasAnyFlags(renderContext.View.Pass, DrawPass::GBuffer)
&& brightness > ZeroTolerance
&& radius > ZeroTolerance
&& outerConeAngle > ZeroTolerance
&& (ViewDistance < ZeroTolerance || Vector3::DistanceSquared(renderContext.View.Position, position) < ViewDistance * ViewDistance))
{
RendererSpotLightData data;
data.Position = position;
data.MinRoughness = MinRoughness;
data.ShadowsDistance = ShadowsDistance;
data.Color = Color.ToFloat3() * (Color.A * brightness);
data.ShadowsStrength = ShadowsStrength;
data.Direction = _direction;
data.ShadowsFadeDistance = ShadowsFadeDistance;
data.ShadowsNormalOffsetScale = ShadowsNormalOffsetScale;
data.ShadowsDepthBias = ShadowsDepthBias;
data.ShadowsSharpness = ShadowsSharpness;
data.VolumetricScatteringIntensity = VolumetricScatteringIntensity;
data.CastVolumetricShadow = CastVolumetricShadow;
data.RenderedVolumetricFog = 0;
data.ShadowsMode = ShadowsMode;
data.Radius = radius;
data.FallOffExponent = FallOffExponent;
data.UseInverseSquaredFalloff = UseInverseSquaredFalloff;
data.SourceRadius = SourceRadius;
data.CosOuterCone = _cosOuterCone;
data.InvCosConeDifference = _invCosConeDifference;
data.ContactShadowsLength = ContactShadowsLength;
data.IndirectLightingIntensity = IndirectLightingIntensity;
data.IESTexture = IESTexture ? IESTexture->GetTexture() : nullptr;
Float3::Transform(Float3::Up, GetOrientation(), data.UpVector);
data.OuterConeAngle = outerConeAngle;
data.StaticFlags = GetStaticFlags();
data.ID = GetID();
renderContext.List->SpotLights.Add(data);
}
}
#if USE_EDITOR
#include "Engine/Debug/DebugDraw.h"
void SpotLight::OnDebugDraw()
{
if (SourceRadius > ZeroTolerance)
{
// Draw source tube
DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(GetPosition(), SourceRadius), Color::Orange, 0, true);
}
// Base
LightWithShadow::OnDebugDraw();
}
void SpotLight::OnDebugDrawSelected()
{
const auto color = Color::Yellow;
Vector3 right = _transform.GetRight();
Vector3 up = _transform.GetUp();
Vector3 forward = GetDirection();
float radius = GetScaledRadius();
float discRadius = radius * Math::Tan(_outerConeAngle * DegreesToRadians);
float falloffDiscRadius = radius * Math::Tan(_innerConeAngle * DegreesToRadians);
Vector3 position = GetPosition();
DEBUG_DRAW_LINE(position, position + forward * radius + up * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - up * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + right * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - right * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + up * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - up * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + right * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - right * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_CIRCLE(position + forward * radius, forward, discRadius, color, 0, true);
DEBUG_DRAW_CIRCLE(position + forward * radius, forward, falloffDiscRadius, color * 0.6f, 0, true);
// Base
LightWithShadow::OnDebugDrawSelected();
}
void SpotLight::DrawLightsDebug(RenderView& view)
{
const BoundingSphere sphere(_sphere.Center - view.Origin, _sphere.Radius);
if (!view.CullingFrustum.Intersects(sphere) || !EnumHasAnyFlags(view.Flags, ViewFlags::SpotLights))
return;
const auto color = Color::Yellow;
Vector3 right = _transform.GetRight();
Vector3 up = _transform.GetUp();
Vector3 forward = GetDirection();
float radius = GetScaledRadius();
float discRadius = radius * Math::Tan(_outerConeAngle * DegreesToRadians);
float falloffDiscRadius = radius * Math::Tan(_innerConeAngle * DegreesToRadians);
Vector3 position = GetPosition();
DEBUG_DRAW_LINE(position, position + forward * radius + up * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - up * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + right * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - right * discRadius, color, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + up * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - up * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius + right * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_LINE(position, position + forward * radius - right * falloffDiscRadius, color * 0.6f, 0, true);
DEBUG_DRAW_CIRCLE(position + forward * radius, forward, discRadius, color, 0, true);
DEBUG_DRAW_CIRCLE(position + forward * radius, forward, falloffDiscRadius, color * 0.6f, 0, true);
}
#endif
void SpotLight::Serialize(SerializeStream& stream, const void* otherObj)
{
// Base
LightWithShadow::Serialize(stream, otherObj);
SERIALIZE_GET_OTHER_OBJ(SpotLight);
SERIALIZE_MEMBER(Radius, _radius);
SERIALIZE_MEMBER(OuterAngle, _outerConeAngle);
SERIALIZE_MEMBER(InnerAngle, _innerConeAngle);
SERIALIZE(IESTexture);
SERIALIZE(SourceRadius);
SERIALIZE(FallOffExponent);
SERIALIZE(UseInverseSquaredFalloff);
SERIALIZE(UseIESBrightness);
SERIALIZE(IESBrightnessScale);
}
void SpotLight::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
// Base
LightWithShadow::Deserialize(stream, modifier);
DESERIALIZE_MEMBER(Radius, _radius);
DESERIALIZE_MEMBER(OuterAngle, _outerConeAngle);
DESERIALIZE_MEMBER(InnerAngle, _innerConeAngle);
DESERIALIZE(IESTexture);
DESERIALIZE(SourceRadius);
DESERIALIZE(FallOffExponent);
DESERIALIZE(UseInverseSquaredFalloff);
DESERIALIZE(UseIESBrightness);
DESERIALIZE(IESBrightnessScale);
}
bool SpotLight::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
{
return CollisionsHelper::RayIntersectsSphere(ray, _sphere, distance, normal);
}