290 lines
11 KiB
C++
290 lines
11 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/Graphics/RenderTools.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))
|
|
{
|
|
RenderSpotLightData 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.ShadowsUpdateRate = ShadowsUpdateRate;
|
|
data.ShadowsUpdateRateAtDistance = ShadowsUpdateRateAtDistance;
|
|
data.ShadowFrame = _invalidateShadowFrame;
|
|
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();
|
|
data.ScreenSize = Math::Min(1.0f, Math::Sqrt(RenderTools::ComputeBoundsScreenRadiusSquared(position, (float)_sphere.Radius, renderContext.View)));
|
|
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);
|
|
}
|