268 lines
6.9 KiB
C++
268 lines
6.9 KiB
C++
// Copyright (C) 2020 Jérôme Leclercq
|
|
// This file is part of the "Nazara Engine - Graphics module"
|
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
|
|
|
#include <Nazara/Graphics/Light.hpp>
|
|
#include <Nazara/Core/Error.hpp>
|
|
#include <Nazara/Graphics/AbstractRenderQueue.hpp>
|
|
#include <Nazara/Graphics/Enums.hpp>
|
|
#include <Nazara/Math/Algorithm.hpp>
|
|
#include <Nazara/Math/Sphere.hpp>
|
|
#include <Nazara/Graphics/Debug.hpp>
|
|
|
|
///TODO: Use of UBOs
|
|
///TODO: Scale ?
|
|
|
|
namespace Nz
|
|
{
|
|
/*!
|
|
* \ingroup graphics
|
|
* \class Nz::Light
|
|
* \brief Graphics class that represents a light
|
|
*/
|
|
|
|
/*!
|
|
* \brief Constructs a Light object with a type
|
|
*
|
|
* \param type Type of the light
|
|
*/
|
|
|
|
Light::Light(LightType type) :
|
|
m_type(type),
|
|
m_shadowMapFormat(PixelFormat_Depth16),
|
|
m_shadowMapSize(512, 512),
|
|
m_shadowCastingEnabled(false),
|
|
m_shadowMapUpdated(false)
|
|
{
|
|
SetAmbientFactor((type == LightType_Directional) ? 0.2f : 0.f);
|
|
SetAttenuation(0.9f);
|
|
SetColor(Color::White);
|
|
SetDiffuseFactor(1.f);
|
|
SetInnerAngle(15.f);
|
|
SetOuterAngle(45.f);
|
|
SetRadius(5.f);
|
|
}
|
|
|
|
/*!
|
|
* \brief Adds this light to the render queue
|
|
*
|
|
* \param renderQueue Queue to be added
|
|
* \param transformMatrix Matrix transformation for this light
|
|
*
|
|
* \remark Produces a NazaraError if type is invalid
|
|
*/
|
|
|
|
void Light::AddToRenderQueue(AbstractRenderQueue* renderQueue, const Matrix4f& transformMatrix) const
|
|
{
|
|
static Matrix4f biasMatrix(0.5f, 0.f, 0.f, 0.f,
|
|
0.f, 0.5f, 0.f, 0.f,
|
|
0.f, 0.f, 0.5f, 0.f,
|
|
0.5f, 0.5f, 0.5f, 1.f);
|
|
|
|
switch (m_type)
|
|
{
|
|
case LightType_Directional:
|
|
{
|
|
AbstractRenderQueue::DirectionalLight light;
|
|
light.ambientFactor = m_ambientFactor;
|
|
light.color = m_color;
|
|
light.diffuseFactor = m_diffuseFactor;
|
|
light.direction = transformMatrix.Transform(Vector3f::Forward(), 0.f);
|
|
light.shadowMap = m_shadowMap.Get();
|
|
light.transformMatrix = Matrix4f::ViewMatrix(transformMatrix.GetRotation() * Vector3f::Forward() * 100.f, transformMatrix.GetRotation()) * Matrix4f::Ortho(0.f, 100.f, 100.f, 0.f, 1.f, 100.f) * biasMatrix;
|
|
|
|
renderQueue->AddDirectionalLight(light);
|
|
break;
|
|
}
|
|
|
|
case LightType_Point:
|
|
{
|
|
AbstractRenderQueue::PointLight light;
|
|
light.ambientFactor = m_ambientFactor;
|
|
light.attenuation = m_attenuation;
|
|
light.color = m_color;
|
|
light.diffuseFactor = m_diffuseFactor;
|
|
light.invRadius = m_invRadius;
|
|
light.position = transformMatrix.GetTranslation();
|
|
light.radius = m_radius;
|
|
light.shadowMap = m_shadowMap.Get();
|
|
|
|
renderQueue->AddPointLight(light);
|
|
break;
|
|
}
|
|
|
|
case LightType_Spot:
|
|
{
|
|
AbstractRenderQueue::SpotLight light;
|
|
light.ambientFactor = m_ambientFactor;
|
|
light.attenuation = m_attenuation;
|
|
light.color = m_color;
|
|
light.diffuseFactor = m_diffuseFactor;
|
|
light.direction = transformMatrix.Transform(Vector3f::Forward(), 0.f);
|
|
light.innerAngleCosine = m_innerAngleCosine;
|
|
light.invRadius = m_invRadius;
|
|
light.outerAngleCosine = m_outerAngleCosine;
|
|
light.outerAngleTangent = m_outerAngleTangent;
|
|
light.position = transformMatrix.GetTranslation();
|
|
light.radius = m_radius;
|
|
light.shadowMap = m_shadowMap.Get();
|
|
light.transformMatrix = Matrix4f::ViewMatrix(transformMatrix.GetTranslation(), transformMatrix.GetRotation()) * Matrix4f::Perspective(m_outerAngle*2.f, 1.f, 0.1f, m_radius) * biasMatrix;
|
|
|
|
renderQueue->AddSpotLight(light);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
NazaraError("Invalid light type (0x" + String::Number(m_type, 16) + ')');
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Clones this light
|
|
* \return Pointer to newly allocated Light
|
|
*/
|
|
|
|
Light* Light::Clone() const
|
|
{
|
|
return new Light(*this);
|
|
}
|
|
|
|
/*!
|
|
* \brief Creates a default light
|
|
* \return Pointer to newly allocated light
|
|
*/
|
|
|
|
Light* Light::Create() const
|
|
{
|
|
return new Light;
|
|
}
|
|
|
|
/*!
|
|
* \brief Culls the light if not in the frustum
|
|
* \return true If light is in the frustum
|
|
*
|
|
* \param frustum Symbolizing the field of view
|
|
* \param transformMatrix Matrix transformation for our object
|
|
*
|
|
* \remark Produces a NazaraError if type is invalid
|
|
*/
|
|
|
|
bool Light::Cull(const Frustumf& frustum, const Matrix4f& transformMatrix) const
|
|
{
|
|
switch (m_type)
|
|
{
|
|
case LightType_Directional:
|
|
return true; // Always visible
|
|
|
|
case LightType_Point:
|
|
return frustum.Contains(Spheref(transformMatrix.GetTranslation(), m_radius)); // A sphere test is much faster (and precise)
|
|
|
|
case LightType_Spot:
|
|
return frustum.Contains(m_boundingVolume);
|
|
}
|
|
|
|
NazaraError("Invalid light type (0x" + String::Number(m_type, 16) + ')');
|
|
return false;
|
|
}
|
|
|
|
/*!
|
|
* \brief Updates the bounding volume by a matrix
|
|
*
|
|
* \param transformMatrix Matrix transformation for our bounding volume
|
|
*
|
|
* \remark Produces a NazaraError if type is invalid
|
|
*/
|
|
|
|
void Light::UpdateBoundingVolume(const Matrix4f& transformMatrix)
|
|
{
|
|
switch (m_type)
|
|
{
|
|
case LightType_Directional:
|
|
break; // Nothing to do (bounding volume should be infinite)
|
|
|
|
case LightType_Point:
|
|
m_boundingVolume.Update(transformMatrix.GetTranslation()); // The bounding volume only needs to be shifted
|
|
break;
|
|
|
|
case LightType_Spot:
|
|
m_boundingVolume.Update(transformMatrix);
|
|
break;
|
|
|
|
default:
|
|
NazaraError("Invalid light type (0x" + String::Number(m_type, 16) + ')');
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* \brief Makes the bounding volume of this light
|
|
*
|
|
* \remark Produces a NazaraError if type is invalid
|
|
*/
|
|
|
|
void Light::MakeBoundingVolume() const
|
|
{
|
|
switch (m_type)
|
|
{
|
|
case LightType_Directional:
|
|
m_boundingVolume.MakeInfinite();
|
|
break;
|
|
|
|
case LightType_Point:
|
|
{
|
|
Vector3f radius(m_radius * float(M_SQRT3));
|
|
m_boundingVolume.Set(-radius, radius);
|
|
break;
|
|
}
|
|
|
|
case LightType_Spot:
|
|
{
|
|
// We make a box center in the origin
|
|
Boxf box(Vector3f::Zero());
|
|
|
|
// We compute the other points
|
|
Vector3f base(Vector3f::Forward() * m_radius);
|
|
|
|
// Now we need the radius of the projected circle depending on the distance
|
|
// Tangent = Opposite/Adjacent <=> Opposite = Adjacent * Tangent
|
|
float radius = m_radius * m_outerAngleTangent;
|
|
Vector3f lExtend = Vector3f::Left() * radius;
|
|
Vector3f uExtend = Vector3f::Up() * radius;
|
|
|
|
// And we add the four extremities of our pyramid
|
|
box.ExtendTo(base + lExtend + uExtend);
|
|
box.ExtendTo(base + lExtend - uExtend);
|
|
box.ExtendTo(base - lExtend + uExtend);
|
|
box.ExtendTo(base - lExtend - uExtend);
|
|
|
|
m_boundingVolume.Set(box);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
NazaraError("Invalid light type (0x" + String::Number(m_type, 16) + ')');
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Updates the shadow map
|
|
*/
|
|
|
|
void Light::UpdateShadowMap() const
|
|
{
|
|
if (m_shadowCastingEnabled)
|
|
{
|
|
if (!m_shadowMap)
|
|
m_shadowMap = Texture::New();
|
|
|
|
m_shadowMap->Create((m_type == LightType_Point) ? ImageType_Cubemap : ImageType_2D, m_shadowMapFormat, m_shadowMapSize.x, m_shadowMapSize.y);
|
|
}
|
|
else
|
|
m_shadowMap.Reset();
|
|
|
|
m_shadowMapUpdated = true;
|
|
}
|
|
}
|