You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
734 lines
26 KiB
C++
734 lines
26 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
// Original file Copyright Crytek GMBH or its affiliates, used under license.
|
|
|
|
// Description : Light sources manager
|
|
|
|
|
|
#include "Cry3DEngine_precompiled.h"
|
|
|
|
#include "3dEngine.h"
|
|
#include "ObjMan.h"
|
|
#include "VisAreas.h"
|
|
#include "AABBSV.h"
|
|
#include "LightEntity.h"
|
|
#include "ObjectsTree.h"
|
|
#include "ClipVolumeManager.h"
|
|
|
|
ILightSource* C3DEngine::CreateLightSource()
|
|
{
|
|
// construct new object
|
|
CLightEntity* pLightEntity = new CLightEntity();
|
|
|
|
m_lstStaticLights.Add(pLightEntity);
|
|
|
|
return pLightEntity;
|
|
}
|
|
|
|
void C3DEngine::DeleteLightSource(ILightSource* pLightSource)
|
|
{
|
|
if (m_lstStaticLights.Delete((CLightEntity*)pLightSource) || pLightSource == m_pSun)
|
|
{
|
|
if (pLightSource == m_pSun)
|
|
{
|
|
m_pSun = NULL;
|
|
}
|
|
|
|
delete pLightSource;
|
|
}
|
|
else
|
|
{
|
|
assert(!"Light object not found");
|
|
}
|
|
}
|
|
|
|
void CLightEntity::Release(bool)
|
|
{
|
|
Get3DEngine()->UnRegisterEntityDirect(this);
|
|
Get3DEngine()->DeleteLightSource(this);
|
|
}
|
|
|
|
void CLightEntity::SetLightProperties(const CDLight& light)
|
|
{
|
|
C3DEngine* engine = Get3DEngine();
|
|
|
|
m_light = light;
|
|
|
|
m_bShadowCaster = (m_light.m_Flags & DLF_CASTSHADOW_MAPS) != 0;
|
|
|
|
m_light.m_fBaseRadius = m_light.m_fRadius;
|
|
m_light.m_fLightFrustumAngle = CLAMP(m_light.m_fLightFrustumAngle, 0.f, (LIGHT_PROJECTOR_MAX_FOV / 2.f));
|
|
|
|
if (!(m_light.m_Flags & (DLF_PROJECT | DLF_AREA_LIGHT)))
|
|
{
|
|
m_light.m_fLightFrustumAngle = 90.f / 2.f;
|
|
}
|
|
|
|
m_light.m_pOwner = this;
|
|
|
|
if (m_light.m_Flags & DLF_ATTACH_TO_SUN)
|
|
{
|
|
m_dwRndFlags |= ERF_RENDER_ALWAYS | ERF_HUD;
|
|
}
|
|
|
|
engine->GetLightEntities()->Delete((ILightSource*)this);
|
|
|
|
PodArray<ILightSource*>& lightEntities = *engine->GetLightEntities();
|
|
|
|
//on consoles we force all lights (except sun) to be deferred
|
|
if (GetCVars()->e_DynamicLightsForceDeferred && !(m_light.m_Flags & (DLF_SUN | DLF_POST_3D_RENDERER)))
|
|
{
|
|
m_light.m_Flags |= DLF_DEFERRED_LIGHT;
|
|
}
|
|
|
|
if (light.m_Flags & DLF_DEFERRED_LIGHT)
|
|
{
|
|
lightEntities.Add((ILightSource*)this);
|
|
}
|
|
else
|
|
{
|
|
lightEntities.InsertBefore((ILightSource*)this, 0);
|
|
}
|
|
}
|
|
|
|
void C3DEngine::ResetCasterCombinationsCache()
|
|
{
|
|
for (int nSunInUse = 0; nSunInUse < 2; nSunInUse++)
|
|
{
|
|
// clear user counters
|
|
for (ShadowFrustumListsCacheUsers::iterator it = m_FrustumsCacheUsers[nSunInUse].begin(); it != m_FrustumsCacheUsers[nSunInUse].end(); ++it)
|
|
{
|
|
it->second = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void C3DEngine::DeleteAllStaticLightSources()
|
|
{
|
|
for (int i = 0; i < m_lstStaticLights.Count(); i++)
|
|
{
|
|
delete m_lstStaticLights[i];
|
|
}
|
|
m_lstStaticLights.Reset();
|
|
|
|
m_pSun = NULL;
|
|
}
|
|
|
|
void C3DEngine::InitShadowFrustums(const SRenderingPassInfo& passInfo)
|
|
{
|
|
assert(passInfo.IsGeneralPass());
|
|
FUNCTION_PROFILER_3DENGINE_LEGACYONLY;
|
|
AZ_TRACE_METHOD();
|
|
|
|
if (m_pSun)
|
|
{
|
|
CDLight* pLight = &m_pSun->GetLightProperties();
|
|
CLightEntity* pLightEntity = (CLightEntity*)pLight->m_pOwner;
|
|
|
|
if (passInfo.RenderShadows() && (pLight->m_Flags & DLF_CASTSHADOW_MAPS) && pLight->m_Id >= 0)
|
|
{
|
|
pLightEntity->UpdateGSMLightSourceShadowFrustum(passInfo);
|
|
|
|
if (pLightEntity->m_pShadowMapInfo)
|
|
{
|
|
pLight->m_pShadowMapFrustums = pLightEntity->m_pShadowMapInfo->pGSM;
|
|
}
|
|
}
|
|
|
|
_smart_ptr<IMaterial> pMat = pLightEntity->GetMaterial();
|
|
if (pMat)
|
|
{
|
|
pLight->m_Shader = pMat->GetShaderItem();
|
|
}
|
|
|
|
// update copy of light ion the renderer
|
|
if (pLight->m_Id >= 0)
|
|
{
|
|
CDLight* pRndLight = NULL;
|
|
GetRenderer()->EF_Query(EFQ_LightSource, pLight->m_Id, pRndLight);
|
|
assert(pLight->m_Id == pRndLight->m_Id);
|
|
pRndLight->m_pShadowMapFrustums = pLight->m_pShadowMapFrustums;
|
|
pRndLight->m_Shader = pLight->m_Shader;
|
|
pRndLight->m_Flags = pLight->m_Flags;
|
|
}
|
|
|
|
// add per object shadow frustums
|
|
m_nCustomShadowFrustumCount = 0;
|
|
if (passInfo.RenderShadows() && GetCVars()->e_ShadowsPerObject > 0)
|
|
{
|
|
const uint nFrustumCount = m_lstPerObjectShadows.size();
|
|
if (nFrustumCount > m_lstCustomShadowFrustums.size())
|
|
{
|
|
m_lstCustomShadowFrustums.resize(nFrustumCount);
|
|
}
|
|
|
|
for (uint i = 0; i < nFrustumCount; ++i)
|
|
{
|
|
if (m_lstPerObjectShadows[i].pCaster)
|
|
{
|
|
ShadowMapFrustum* pFr = &m_lstCustomShadowFrustums[i];
|
|
pFr->m_eFrustumType = ShadowMapFrustum::e_PerObject;
|
|
|
|
CLightEntity::ProcessPerObjectFrustum(pFr, &m_lstPerObjectShadows[i], m_pSun, passInfo);
|
|
++m_nCustomShadowFrustumCount;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (passInfo.RenderShadows())
|
|
{
|
|
ResetCasterCombinationsCache();
|
|
}
|
|
}
|
|
|
|
void C3DEngine::AddPerObjectShadow(IShadowCaster* pCaster, float fConstBias, float fSlopeBias, float fJitter, const Vec3& vBBoxScale, uint nTexSize)
|
|
{
|
|
SPerObjectShadow* pOS = GetPerObjectShadow(pCaster);
|
|
if (!pOS)
|
|
{
|
|
pOS = &m_lstPerObjectShadows.AddNew();
|
|
}
|
|
|
|
pOS->pCaster = pCaster;
|
|
pOS->fConstBias = fConstBias;
|
|
pOS->fSlopeBias = fSlopeBias;
|
|
pOS->fJitter = fJitter;
|
|
pOS->vBBoxScale = vBBoxScale;
|
|
pOS->nTexSize = nTexSize;
|
|
}
|
|
|
|
void C3DEngine::RemovePerObjectShadow(IShadowCaster* pCaster)
|
|
{
|
|
SPerObjectShadow* pOS = GetPerObjectShadow(pCaster);
|
|
if (pOS)
|
|
{
|
|
FRAME_PROFILER("C3DEngine::RemovePerObjectShadow", GetSystem(), PROFILE_3DENGINE);
|
|
|
|
size_t nIndex = (size_t)(pOS - m_lstPerObjectShadows.begin());
|
|
m_lstPerObjectShadows.Delete(nIndex);
|
|
}
|
|
}
|
|
|
|
struct SPerObjectShadow* C3DEngine::GetPerObjectShadow(IShadowCaster* pCaster)
|
|
{
|
|
for (int i = 0; i < m_lstPerObjectShadows.Count(); ++i)
|
|
{
|
|
if (m_lstPerObjectShadows[i].pCaster == pCaster)
|
|
{
|
|
return &m_lstPerObjectShadows[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void C3DEngine::GetCustomShadowMapFrustums(ShadowMapFrustum*& arrFrustums, int& nFrustumCount)
|
|
{
|
|
arrFrustums = m_lstCustomShadowFrustums.begin();
|
|
nFrustumCount = m_nCustomShadowFrustumCount;
|
|
}
|
|
|
|
// delete pLight->m_pProjCamera;
|
|
//pLight->m_pProjCamera=0;
|
|
//if(pLight->m_pShader)
|
|
// SAFE_RELEASE(pLight->m_pShader);
|
|
namespace
|
|
{
|
|
static inline bool CmpCastShadowFlag(const CDLight* p1, const CDLight* p2)
|
|
{
|
|
// move sun first
|
|
if ((p1->m_Flags & DLF_SUN) > (p2->m_Flags & DLF_SUN))
|
|
{
|
|
return true;
|
|
}
|
|
else if ((p1->m_Flags & DLF_SUN) < (p2->m_Flags & DLF_SUN))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// move shadow casters first
|
|
if ((p1->m_Flags & DLF_CASTSHADOW_MAPS) > (p2->m_Flags & DLF_CASTSHADOW_MAPS))
|
|
{
|
|
return true;
|
|
}
|
|
else if ((p1->m_Flags & DLF_CASTSHADOW_MAPS) < (p2->m_Flags & DLF_CASTSHADOW_MAPS))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// get some sorting consistency for shadow casters
|
|
if (p1->m_pOwner > p2->m_pOwner)
|
|
{
|
|
return true;
|
|
}
|
|
else if (p1->m_pOwner < p2->m_pOwner)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void C3DEngine::SubmitSun(const SRenderingPassInfo& passInfo)
|
|
{
|
|
assert(passInfo.IsGeneralPass());
|
|
FUNCTION_PROFILER_3DENGINE_LEGACYONLY;
|
|
AZ_TRACE_METHOD();
|
|
|
|
if (m_pSun)
|
|
{
|
|
CDLight* light = &m_pSun->GetLightProperties();
|
|
|
|
GetRenderer()->EF_ADDDlight(light, passInfo);
|
|
}
|
|
}
|
|
|
|
void C3DEngine::RemoveEntityLightSources(IRenderNode* pEntity)
|
|
{
|
|
for (int i = 0; i < m_lstStaticLights.Count(); i++)
|
|
{
|
|
if (m_lstStaticLights[i] == pEntity)
|
|
{
|
|
m_lstStaticLights.Delete(i);
|
|
if (pEntity == m_pSun)
|
|
{
|
|
m_pSun = NULL;
|
|
}
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
ILightSource* C3DEngine::GetSunEntity()
|
|
{
|
|
return m_pSun;
|
|
}
|
|
|
|
void C3DEngine::OnCasterDeleted(IShadowCaster* pCaster)
|
|
{
|
|
FUNCTION_PROFILER(gEnv->pSystem, PROFILE_3DENGINE);
|
|
{ // make sure pointer to object will not be used somewhere in the renderer
|
|
if (m_pSun)
|
|
{
|
|
m_pSun->OnCasterDeleted(pCaster);
|
|
}
|
|
|
|
if (GetRenderer()->GetActiveGPUCount() > 1)
|
|
{
|
|
if (ShadowFrustumMGPUCache* pFrustumCache = GetRenderer()->GetShadowFrustumMGPUCache())
|
|
{
|
|
pFrustumCache->DeleteFromCache(pCaster);
|
|
}
|
|
}
|
|
|
|
// remove from per object shadows list
|
|
RemovePerObjectShadow(pCaster);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void CLightVolumesMgr::Init()
|
|
{
|
|
m_bUpdateLightVolumes = false;
|
|
for (int i = 0; i < RT_COMMAND_BUF_COUNT; ++i)
|
|
{
|
|
m_pLightVolumes[i].reserve(LV_MAX_COUNT);
|
|
m_pLightVolsInfo[i].reserve(LV_MAX_COUNT);
|
|
}
|
|
memset(m_nWorldCells, 0, sizeof(m_nWorldCells));
|
|
memset(m_pWorldLightCells, 0, sizeof(m_pWorldLightCells));
|
|
}
|
|
|
|
void CLightVolumesMgr::Reset()
|
|
{
|
|
for (int i = 0; i < RT_COMMAND_BUF_COUNT; ++i)
|
|
{
|
|
stl::free_container(m_pLightVolumes[i]);
|
|
}
|
|
|
|
m_bUpdateLightVolumes = false;
|
|
memset(m_nWorldCells, 0, sizeof(m_nWorldCells));
|
|
memset(m_pWorldLightCells, 0, sizeof(m_pWorldLightCells));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
uint16 CLightVolumesMgr::RegisterVolume(const Vec3& vPos, f32 fRadius, uint8 nClipVolumeRef, const SRenderingPassInfo& passInfo)
|
|
{
|
|
DynArray<SLightVolInfo*>& lightVolsInfo = m_pLightVolsInfo[passInfo.ThreadID()];
|
|
|
|
IF ((m_bUpdateLightVolumes && (lightVolsInfo.size() < LV_MAX_COUNT)) && fRadius < 256.0f, 1)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
int32 nPosx = (int32)(floorf(vPos.x * LV_CELL_RSIZEX));
|
|
int32 nPosy = (int32)(floorf(vPos.y * LV_CELL_RSIZEY));
|
|
int32 nPosz = (int32)(floorf(vPos.z * LV_CELL_RSIZEZ));
|
|
|
|
// Check if world cell has any light volume, else add new one
|
|
uint16 nHashIndex = GetWorldHashBucketKey(nPosx, nPosy, nPosz);
|
|
uint16* pCurrentVolumeID = &m_nWorldCells[nHashIndex];
|
|
|
|
while (*pCurrentVolumeID != 0)
|
|
{
|
|
SLightVolInfo& sVolInfo = *lightVolsInfo[*pCurrentVolumeID - 1];
|
|
|
|
int32 nVolumePosx = (int32)(floorf(sVolInfo.vVolume.x * LV_CELL_RSIZEX));
|
|
int32 nVolumePosy = (int32)(floorf(sVolInfo.vVolume.y * LV_CELL_RSIZEY));
|
|
int32 nVolumePosz = (int32)(floorf(sVolInfo.vVolume.z * LV_CELL_RSIZEZ));
|
|
|
|
if (nPosx == nVolumePosx &&
|
|
nPosy == nVolumePosy &&
|
|
nPosz == nVolumePosz &&
|
|
nClipVolumeRef == sVolInfo.nClipVolumeID)
|
|
{
|
|
return (uint16) * pCurrentVolumeID;
|
|
}
|
|
|
|
pCurrentVolumeID = &sVolInfo.nNextVolume;
|
|
}
|
|
|
|
// create new volume
|
|
SLightVolInfo* pLightVolInfo = new SLightVolInfo(vPos, fRadius, nClipVolumeRef);
|
|
lightVolsInfo.push_back(pLightVolInfo);
|
|
*pCurrentVolumeID = lightVolsInfo.size();
|
|
|
|
return *pCurrentVolumeID;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void CLightVolumesMgr::RegisterLight(const CDLight& pDL, uint32 nLightID, [[maybe_unused]] const SRenderingPassInfo& passInfo)
|
|
{
|
|
IF ((m_bUpdateLightVolumes && !(pDL.m_Flags & LV_DLF_LIGHTVOLUMES_MASK)), 1)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
const f32 fColCheck = (f32) fsel(pDL.m_Color.r + pDL.m_Color.g + pDL.m_Color.b - 0.333f, 1.0f, 0.0f); //light color > threshold
|
|
const f32 fRadCheck = (f32) fsel(pDL.m_fRadius - 0.5f, 1.0f, 0.0f); //light radius > threshold
|
|
if (fColCheck * fRadCheck)
|
|
{
|
|
//if the radius is large than certain value, all the the world light cells will be lighted anyway. So we just add the light to all the cells
|
|
//the input radius restriction will be added too
|
|
if(floorf(pDL.m_fRadius*LV_LIGHT_CELL_R_SIZE) > LV_LIGHTS_WORLD_BUCKET_SIZE)
|
|
{
|
|
for (int32 idx = 0; idx < LV_LIGHTS_WORLD_BUCKET_SIZE; idx++)
|
|
{
|
|
SLightCell& lightCell = m_pWorldLightCells[idx];
|
|
CryPrefetch(&lightCell);
|
|
if (lightCell.nLightCount < LV_LIGHTS_MAX_COUNT)
|
|
{
|
|
lightCell.nLightID[lightCell.nLightCount] = nLightID;
|
|
lightCell.nLightCount += 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 nMiny = (int32)(floorf((pDL.m_Origin.y - pDL.m_fRadius) * LV_LIGHT_CELL_R_SIZE));
|
|
int32 nMaxy = (int32)(floorf((pDL.m_Origin.y + pDL.m_fRadius) * LV_LIGHT_CELL_R_SIZE));
|
|
int32 nMinx = (int32)(floorf((pDL.m_Origin.x - pDL.m_fRadius) * LV_LIGHT_CELL_R_SIZE));
|
|
int32 nMaxx = (int32)(floorf((pDL.m_Origin.x + pDL.m_fRadius) * LV_LIGHT_CELL_R_SIZE));
|
|
|
|
// Register light into all cells touched by light radius
|
|
for (int32 y = nMiny, ymax = nMaxy; y <= ymax; ++y)
|
|
{
|
|
for (int32 x = nMinx, xmax = nMaxx; x <= xmax; ++x)
|
|
{
|
|
SLightCell& lightCell = m_pWorldLightCells[GetWorldHashBucketKey(x, y, 1, LV_LIGHTS_WORLD_BUCKET_SIZE)];
|
|
CryPrefetch(&lightCell);
|
|
if (lightCell.nLightCount < LV_LIGHTS_MAX_COUNT)
|
|
{
|
|
//only if the las light added to the cell wasn't the same light
|
|
if (!(lightCell.nLightCount > 0 && lightCell.nLightID[lightCell.nLightCount - 1] == nLightID))
|
|
{
|
|
lightCell.nLightID[lightCell.nLightCount] = nLightID;
|
|
lightCell.nLightCount += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void CLightVolumesMgr::AddLight(const SRenderLight& pLight, const SLightVolInfo* __restrict pVolInfo, SLightVolume& pVolume)
|
|
{
|
|
// Check for clip volume
|
|
if (pLight.m_nStencilRef[0] == pVolInfo->nClipVolumeID || pLight.m_nStencilRef[1] == pVolInfo->nClipVolumeID ||
|
|
pLight.m_nStencilRef[0] == CClipVolumeManager::AffectsEverythingStencilRef)
|
|
{
|
|
const Vec4* __restrict vLight = (Vec4*) &pLight.m_Origin.x;
|
|
const Vec4& vVolume = pVolInfo->vVolume;
|
|
const f32 fDimCheck = (f32) fsel(vLight->w - vVolume.w * 0.1f, 1.0f, 0.0f); //light radius not more than 10x smaller than volume radius
|
|
const f32 fOverlapCheck = (f32) fsel(sqr(vVolume.x - vLight->x) + sqr(vVolume.y - vLight->y) + sqr(vVolume.z - vLight->z) - sqr(vVolume.w + vLight->w), 0.0f, 1.0f);// touches volumes
|
|
if (fDimCheck * fOverlapCheck)
|
|
{
|
|
float fAttenuationBulbSize = pLight.m_fAttenuationBulbSize;
|
|
Vec3 lightColor = *((Vec3*)&pLight.m_Color);
|
|
|
|
// Adjust light intensity so that the intended brightness is reached 1 meter from the light's surface
|
|
IF (!(pLight.m_Flags & (DLF_AREA_LIGHT | DLF_AMBIENT)), 1)
|
|
{
|
|
fAttenuationBulbSize = max(fAttenuationBulbSize, 0.001f);
|
|
|
|
// Solve I * 1 / (1 + d/lightsize)^2 = 1
|
|
float intensityMul = 1.0f + 1.0f / fAttenuationBulbSize;
|
|
intensityMul *= intensityMul;
|
|
lightColor *= intensityMul;
|
|
}
|
|
|
|
pVolume.pData.push_back();
|
|
SLightVolume::SLightData& lightData = pVolume.pData[pVolume.pData.size() - 1];
|
|
lightData.vPos = *vLight;
|
|
lightData.vColor = Vec4(lightColor, fAttenuationBulbSize);
|
|
lightData.vParams = Vec4(0.f, 0.f, 0.f, 0.f);
|
|
|
|
IF (pLight.m_Flags & DLF_PROJECT, 1)
|
|
{
|
|
lightData.vParams = Vec4(pLight.m_ObjMatrix.GetColumn0(), cos_tpl(DEG2RAD(pLight.m_fLightFrustumAngle)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void CLightVolumesMgr::Update(const SRenderingPassInfo& passInfo)
|
|
{
|
|
uint32 nThreadID = passInfo.ThreadID();
|
|
DynArray<SLightVolInfo*>& lightVolsInfo = m_pLightVolsInfo[nThreadID];
|
|
|
|
if (!m_bUpdateLightVolumes || lightVolsInfo.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
TArray<SRenderLight>* pLights = GetRenderer()->EF_GetDeferredLights(passInfo);
|
|
const uint32 nLightCount = pLights->size();
|
|
|
|
uint32 nLightVols = lightVolsInfo.size();
|
|
LightVolumeVector& lightVols = m_pLightVolumes[nThreadID];
|
|
uint32 existingLightVolsCount = 0; //This is 0, that just means we will be overwriting all existing light volumes
|
|
|
|
//If this is a recursive pass (not the first time that this is called this frame), we're just going to be adding on new light volumes to the existing collection
|
|
if (passInfo.IsRecursivePass())
|
|
{
|
|
existingLightVolsCount = lightVols.size();
|
|
|
|
//If no new light volumes have been added, don't bother updating
|
|
if (nLightVols == existingLightVolsCount)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
lightVols.resize(nLightVols);
|
|
|
|
if (!nLightCount)
|
|
{
|
|
//Start out existingLightVolsCount to avoid clearing out existing light volumes when we don't need to
|
|
for (uint32 v = existingLightVolsCount; v < nLightVols; ++v)
|
|
{
|
|
lightVols[v].pData.resize(0);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const int MAX_NUM_LIGHTS_FOR_LIGHT_VOLUME_UPDATE = 1024;
|
|
|
|
if (nLightCount > MAX_NUM_LIGHTS_FOR_LIGHT_VOLUME_UPDATE)
|
|
{
|
|
CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_WARNING, "More lights in the scene (%d) than supported by the Light Volume Update function (%d). Extra lights will be ignored.",
|
|
nLightCount, MAX_NUM_LIGHTS_FOR_LIGHT_VOLUME_UPDATE);
|
|
}
|
|
|
|
//This can be a uint8 array because nLightVols should never be greater than 256(LV_MAX_COUNT)
|
|
assert(LV_MAX_COUNT <= 256);
|
|
uint8 lightProcessedStateArray[MAX_NUM_LIGHTS_FOR_LIGHT_VOLUME_UPDATE];
|
|
|
|
//Start at the number of light volumes that already exist so that we don't end up re-updating light volumes unnecessarily.
|
|
for (uint32 v = existingLightVolsCount; v < nLightVols; ++v)
|
|
{
|
|
const Vec4* __restrict vBVol = &lightVolsInfo[v]->vVolume;
|
|
int32 nMiny = (int32)(floorf((vBVol->y - vBVol->w) * LV_LIGHT_CELL_R_SIZE));
|
|
int32 nMaxy = (int32)(floorf((vBVol->y + vBVol->w) * LV_LIGHT_CELL_R_SIZE));
|
|
int32 nMinx = (int32)(floorf((vBVol->x - vBVol->w) * LV_LIGHT_CELL_R_SIZE));
|
|
int32 nMaxx = (int32)(floorf((vBVol->x + vBVol->w) * LV_LIGHT_CELL_R_SIZE));
|
|
|
|
lightVols[v].pData.resize(0);
|
|
|
|
// Loop through active light cells touching bounding volume (~avg 2 cells)
|
|
for (int32 y = nMiny, ymax = nMaxy; y <= ymax; ++y)
|
|
{
|
|
for (int32 x = nMinx, xmax = nMaxx; x <= xmax; ++x)
|
|
{
|
|
const SLightCell& lightCell = m_pWorldLightCells[GetWorldHashBucketKey(x, y, 1, LV_LIGHTS_WORLD_BUCKET_SIZE)];
|
|
CryPrefetch(&lightCell);
|
|
|
|
const SRenderLight& pFirstDL = (*pLights)[lightCell.nLightID[0]];
|
|
CryPrefetch(&pFirstDL);
|
|
CryPrefetch(&pFirstDL.m_ObjMatrix);
|
|
for (uint32 l = 0; (l < lightCell.nLightCount) & (lightVols[v].pData.size() < LIGHTVOLUME_MAXLIGHTS); ++l)
|
|
{
|
|
const int32 nLightId = lightCell.nLightID[l];
|
|
|
|
//Only allow IDs < MAX_NUM_LIGHTS_FOR_LIGHT_VOLUME_UPDATE to continue or else we'll overflow access to
|
|
//lightProcessedStateArray[MAX_NUM_LIGHTS_FOR_LIGHT_VOLUME_UPDATE]. Skipping the extra lights shouldn't really matter
|
|
//since A) folks won't be using that many lights, and B) for the case of light emitting particles, they tend to be grouped
|
|
//so that the individual contributions tend to bleed together anyway.
|
|
if (nLightId >= MAX_NUM_LIGHTS_FOR_LIGHT_VOLUME_UPDATE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (static_cast<uint32>(nLightId) < nLightCount)
|
|
{
|
|
const SRenderLight& pDL = (*pLights)[nLightId];
|
|
const int32 nNextLightId = lightCell.nLightID[(l + 1) & (LIGHTVOLUME_MAXLIGHTS - 1)];
|
|
const SRenderLight& pNextDL = (*pLights)[nNextLightId];
|
|
CryPrefetch(&pNextDL);
|
|
CryPrefetch(&pNextDL.m_ObjMatrix);
|
|
|
|
IF(lightProcessedStateArray[nLightId] != v + 1, 1)
|
|
{
|
|
lightProcessedStateArray[nLightId] = v + 1;
|
|
AddLight(pDL, &*lightVolsInfo[v], lightVols[v]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void CLightVolumesMgr::Clear(const SRenderingPassInfo& passInfo)
|
|
{
|
|
DynArray<SLightVolInfo*>& lightVolsInfo = m_pLightVolsInfo[passInfo.ThreadID()];
|
|
|
|
m_bUpdateLightVolumes = false;
|
|
if (GetCVars()->e_LightVolumes && passInfo.IsGeneralPass() && GetCVars()->e_DynamicLights)
|
|
{
|
|
memset(m_nWorldCells, 0, sizeof(m_nWorldCells));
|
|
memset(m_pWorldLightCells, 0, sizeof(m_pWorldLightCells));
|
|
|
|
//Clean up volume info data
|
|
for (size_t i = 0; i < lightVolsInfo.size(); ++i)
|
|
{
|
|
delete lightVolsInfo[i];
|
|
}
|
|
|
|
m_pLightVolsInfo[passInfo.ThreadID()].clear();
|
|
m_bUpdateLightVolumes = (GetCVars()->e_LightVolumes == 1) ? true : false;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void CLightVolumesMgr::GetLightVolumes(threadID nThreadID, SLightVolume*& pLightVols, uint32& nNumVols)
|
|
{
|
|
pLightVols = 0;
|
|
nNumVols = 0;
|
|
if (GetCVars()->e_LightVolumes == 1 && GetCVars()->e_DynamicLights && !m_pLightVolumes[nThreadID].empty())
|
|
{
|
|
pLightVols = &m_pLightVolumes[nThreadID][0];
|
|
nNumVols = m_pLightVolumes[nThreadID].size();
|
|
}
|
|
}
|
|
|
|
|
|
void C3DEngine::GetLightVolumes(threadID nThreadID, SLightVolume*& pLightVols, uint32& nNumVols)
|
|
{
|
|
m_LightVolumesMgr.GetLightVolumes(nThreadID, pLightVols, nNumVols);
|
|
}
|
|
|
|
uint16 C3DEngine::RegisterVolumeForLighting(const Vec3& vPos, f32 fRadius, uint8 nClipVolumeRef, const SRenderingPassInfo& passInfo)
|
|
{
|
|
return m_LightVolumesMgr.RegisterVolume(vPos, fRadius, nClipVolumeRef, passInfo);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
#ifndef _RELEASE
|
|
void CLightVolumesMgr::DrawDebug(const SRenderingPassInfo& passInfo)
|
|
{
|
|
DynArray<SLightVolInfo*>& lightVolsInfo = m_pLightVolsInfo[passInfo.ThreadID()];
|
|
|
|
IRenderer* pRenderer = GetRenderer();
|
|
IRenderAuxGeom* pAuxGeom = GetRenderer()->GetIRenderAuxGeom();
|
|
if (!pAuxGeom || !passInfo.IsGeneralPass())
|
|
{
|
|
return;
|
|
}
|
|
|
|
ColorF cWhite = ColorF(1, 1, 1, 1);
|
|
ColorF cBad = ColorF(1.0f, 0.0, 0.0f, 1.0f);
|
|
ColorF cWarning = ColorF(1.0f, 1.0, 0.0f, 1.0f);
|
|
ColorF cGood = ColorF(0.0f, 0.5, 1.0f, 1.0f);
|
|
ColorF cSingleCell = ColorF(0.0f, 1.0, 0.0f, 1.0f);
|
|
|
|
const uint32 nLightVols = lightVolsInfo.size();
|
|
LightVolumeVector& lightVols = m_pLightVolumes[passInfo.ThreadID()];
|
|
const Vec3 vCamPos = passInfo.GetCamera().GetPosition();
|
|
|
|
float fYLine = 8.0f, fYStep = 20.0f;
|
|
GetRenderer()->Draw2dLabel(8.0f, fYLine += fYStep, 2.0f, (float*)&cWhite.r, false, "Light Volumes Info (count %d)", nLightVols);
|
|
|
|
for (uint32 v = 0; v < nLightVols; ++v) // draw each light volume
|
|
{
|
|
SLightVolume& lv = lightVols[v];
|
|
SLightVolInfo& lvInfo = *lightVolsInfo[v];
|
|
|
|
ColorF& cCol = (lv.pData.size() >= 10) ? cBad : ((lv.pData.size() >= 5) ? cWarning : cGood);
|
|
const Vec3 vPos = Vec3(lvInfo.vVolume.x, lvInfo.vVolume.y, lvInfo.vVolume.z);
|
|
const float fCamDistSq = (vPos - vCamPos).len2();
|
|
cCol.a = max(0.25f, min(1.0f, 1024.0f / (fCamDistSq + 1e-6f)));
|
|
|
|
pRenderer->DrawLabelEx(vPos, 1.3f, (float*)&cCol.r, true, true, "Id: %d\nPos: %.2f %.2f %.2f\nRadius: %.2f\nLights: %d\nOutLights: %d",
|
|
v, vPos.x, vPos.y, vPos.z, lvInfo.vVolume.w, lv.pData.size(), (*(int32*)&lvInfo.vVolume.w) & (1 << 31) ? 1 : 0);
|
|
|
|
if (GetCVars()->e_LightVolumesDebug == 2)
|
|
{
|
|
const float fSideSize = 0.707f * sqrtf(lvInfo.vVolume.w * lvInfo.vVolume.w * 2);
|
|
pAuxGeom->DrawAABB(AABB(vPos - Vec3(fSideSize), vPos + Vec3(fSideSize)), false, cCol, eBBD_Faceted);
|
|
}
|
|
|
|
if (GetCVars()->e_LightVolumesDebug == 3)
|
|
{
|
|
cBad.a = 1.0f;
|
|
const Vec3 vCellPos = Vec3(floorf((lvInfo.vVolume.x) * LV_CELL_RSIZEX) * LV_CELL_SIZEX,
|
|
floorf((lvInfo.vVolume.y) * LV_CELL_RSIZEY) * LV_CELL_SIZEY,
|
|
floorf((lvInfo.vVolume.z) * LV_CELL_RSIZEZ) * LV_CELL_SIZEZ);
|
|
|
|
const Vec3 vMin = vCellPos;
|
|
const Vec3 vMax = vMin + Vec3(LV_CELL_SIZEX, LV_CELL_SIZEY, LV_CELL_SIZEZ);
|
|
pAuxGeom->DrawAABB(AABB(vMin, vMax), false, cBad, eBBD_Faceted);
|
|
}
|
|
}
|
|
}
|
|
#endif
|