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.
400 lines
16 KiB
C++
400 lines
16 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.
|
|
|
|
#include "Cry3DEngine_precompiled.h"
|
|
#include "FogVolumeRenderNode.h"
|
|
#include "VisAreas.h"
|
|
#include "CREFogVolume.h"
|
|
#include "Cry_Geo.h"
|
|
#include "ObjMan.h"
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
inline static float expf_s(float arg)
|
|
{
|
|
return expf(clamp_tpl(arg, -80.0f, 80.0f));
|
|
}
|
|
|
|
AABB UnprojectAABB2D(const AABB& aabb, const CCamera& camera)
|
|
{
|
|
Vec3 results[4];
|
|
camera.Unproject(Vec3(aabb.min.x, aabb.min.y, 1), results[0]);
|
|
camera.Unproject(Vec3(aabb.max.x, aabb.min.y, 1), results[1]);
|
|
camera.Unproject(Vec3(aabb.max.x, aabb.max.y, 1), results[2]);
|
|
camera.Unproject(Vec3(aabb.min.x, aabb.max.y, 1), results[3]);
|
|
|
|
AABB newAABB(AABB::RESET);
|
|
newAABB.Add(results[0]);
|
|
newAABB.Add(results[1]);
|
|
newAABB.Add(results[2]);
|
|
newAABB.Add(results[3]);
|
|
return newAABB;
|
|
}
|
|
|
|
AABB GetProjectedQuadFromAABB(const AABB& aabb, const CCamera& camera)
|
|
{
|
|
// Get all 8 points of the AABB
|
|
|
|
Vec3 aabbPoints[] =
|
|
{
|
|
Vec3(aabb.max.x, aabb.max.y, aabb.max.z),
|
|
Vec3(aabb.max.x, aabb.min.y, aabb.max.z),
|
|
Vec3(aabb.min.x, aabb.min.y, aabb.max.z),
|
|
Vec3(aabb.min.x, aabb.max.y, aabb.max.z),
|
|
Vec3(aabb.max.x, aabb.max.y, aabb.min.z),
|
|
Vec3(aabb.max.x, aabb.min.y, aabb.min.z),
|
|
Vec3(aabb.min.x, aabb.min.y, aabb.min.z),
|
|
Vec3(aabb.min.x, aabb.max.y, aabb.min.z),
|
|
};
|
|
const int numAABBPoints = AZ_ARRAY_SIZE(aabbPoints);
|
|
|
|
AZ_Assert(numAABBPoints==8,"you should have 8 points in the aabb");
|
|
|
|
// Project each AABB point and construct another AABB.
|
|
AABB quadResult(AABB::RESET);
|
|
for (int i = 0; i < numAABBPoints; ++i)
|
|
{
|
|
Vec3 projectedPoint;
|
|
camera.Project(aabbPoints[i], projectedPoint);
|
|
quadResult.Add(projectedPoint);
|
|
}
|
|
return quadResult;
|
|
}
|
|
|
|
|
|
// To know if aabb are aligned with camera, we test if projected quads overlap.
|
|
bool CFogVolumeRenderNode::OverlapProjectedAABB(const AABB& aabb0, const AABB& aabb1, const CCamera& camera)
|
|
{
|
|
// quads are AABB2D.
|
|
AABB quad0 = GetProjectedQuadFromAABB(aabb0, camera);
|
|
AABB quad1 = GetProjectedQuadFromAABB(aabb1, camera);
|
|
bool bOverlap = Overlap::AABB_AABB2D(quad0, quad1);
|
|
return bOverlap;
|
|
}
|
|
|
|
void AverageFogVolume(SFogVolumeData& fogVolData, const CFogVolumeRenderNode* pFogVol)
|
|
{
|
|
AABB& avgAABBoxOut = fogVolData.avgAABBox;
|
|
avgAABBoxOut.Add(pFogVol->GetBBox());
|
|
|
|
// ratio is used to approximate fog volumes contribution depending of the importance of their size.
|
|
float ratio = pFogVol->GetBBox().GetRadius() / avgAABBoxOut.GetRadius();
|
|
fogVolData.m_heightFallOffBasePoint = Lerp(fogVolData.m_heightFallOffBasePoint,pFogVol->GetHeightFallOffBasePoint(), ratio);
|
|
fogVolData.m_heightFallOffDirScaled = Lerp(fogVolData.m_heightFallOffDirScaled,pFogVol->GetHeightFallOffDirScaled(), ratio);
|
|
fogVolData.m_densityOffset = Lerp(fogVolData.m_densityOffset, pFogVol->GetDensityoffset(), ratio);
|
|
fogVolData.m_globalDensity = Lerp(fogVolData.m_globalDensity, pFogVol->GetGlobalDensity(), ratio);
|
|
fogVolData.m_volumeType |= pFogVol->GetVolumeType();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// TraceFogVolumes :
|
|
// if object intersects/is aligned with fog volumes then register these fog volumes,
|
|
// check if object is in front of the fog volume and compute average color.
|
|
// if highVertexShadingQuality average fog volume box.
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void CFogVolumeRenderNode::TraceFogVolumes(const Vec3& objPosition, const AABB& objAABB, SFogVolumeData& fogVolData, const SRenderingPassInfo& passInfo, bool fogVolumeShadingQuality)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
PrefetchLine(&s_tracableFogVolumeArea, 0);
|
|
|
|
// init default result
|
|
ColorF localFogColor = ColorF(0.0f, 0.0f, 0.0f, 0.0f);
|
|
|
|
// We will "accumulate" fog volumes contribution the same way that fog color.
|
|
|
|
// trace is needed when volumetric fog is off.
|
|
if (GetCVars()->e_Fog && GetCVars()->e_FogVolumes && (GetCVars()->e_VolumetricFog == 0))
|
|
{
|
|
const Vec3 worldPos = objPosition;
|
|
|
|
// init view ray
|
|
Vec3 camPos(s_tracableFogVolumeArea.GetCenter());
|
|
Lineseg lineseg(camPos, objPosition);
|
|
|
|
#ifdef _DEBUG
|
|
const SCachedFogVolume* prev(0);
|
|
#endif
|
|
|
|
// loop over all traceable fog volumes
|
|
CachedFogVolumes::const_iterator itEnd(s_cachedFogVolumes.end());
|
|
for (CachedFogVolumes::const_iterator it(s_cachedFogVolumes.begin()); it != itEnd; ++it)
|
|
{
|
|
// get current fog volume
|
|
const CFogVolumeRenderNode* pFogVol((*it).m_pFogVol);
|
|
|
|
bool isAligned = false;
|
|
bool isFrontOfBoxCenter = false;
|
|
// only trace visible fog volumes
|
|
if (!(pFogVol->GetRndFlags() & ERF_HIDDEN))
|
|
{
|
|
bool projectedAABBIntersect = false;
|
|
bool isInside = Overlap::Point_AABB(objPosition, pFogVol->m_WSBBox);
|
|
|
|
if (fogVolumeShadingQuality)
|
|
{
|
|
projectedAABBIntersect = OverlapProjectedAABB(pFogVol->m_WSBBox, objAABB, passInfo.GetCamera());
|
|
isInside = isInside || Overlap::AABB_AABB(objAABB, pFogVol->m_WSBBox);
|
|
}
|
|
|
|
// check if view ray intersects with bounding box of current fog volume
|
|
isAligned = Overlap::Lineseg_AABB(lineseg, pFogVol->m_WSBBox);
|
|
if (projectedAABBIntersect || isAligned)
|
|
{
|
|
// compute contribution of current fog volume
|
|
ColorF color(0,0,0);
|
|
|
|
// Get distance camera to fog volume.
|
|
const CCamera& cam(passInfo.GetCamera());
|
|
Vec3 cameraToObject(objPosition - cam.GetPosition());
|
|
Vec3 cameraToFogVolume(pFogVol->m_WSBBox.GetCenter() - cam.GetPosition());
|
|
isFrontOfBoxCenter = (cameraToFogVolume.GetLengthSquared() > cameraToObject.GetLengthSquared());
|
|
// if particle is in front of the box center and not inside the box, then do not accumulate fog color.
|
|
if (isFrontOfBoxCenter && !isInside)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (fogVolumeShadingQuality)
|
|
{
|
|
// Accumulate this fogVolData
|
|
AverageFogVolume(fogVolData, pFogVol);
|
|
color = pFogVol->GetFogColor();
|
|
}
|
|
else
|
|
{
|
|
if (0 == pFogVol->m_volumeType)
|
|
{
|
|
pFogVol->GetVolumetricFogColorEllipsoid(worldPos, passInfo, color);
|
|
}
|
|
else
|
|
{
|
|
pFogVol->GetVolumetricFogColorBox(worldPos, passInfo, color);
|
|
}
|
|
|
|
color.a = 1.0f - color.a; // 0 = transparent, 1 = opaque
|
|
}
|
|
|
|
|
|
|
|
// blend fog colors
|
|
localFogColor.r = Lerp(localFogColor.r, color.r, color.a);
|
|
localFogColor.g = Lerp(localFogColor.g, color.g, color.a);
|
|
localFogColor.b = Lerp(localFogColor.b, color.b, color.a);
|
|
localFogColor.a = Lerp(localFogColor.a, 1.0f, color.a);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
if (prev)
|
|
{
|
|
assert(prev->m_distToCenterSq >= (*it).m_distToCenterSq);
|
|
prev = &(*it);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
const float fDivisor = (float)fsel(-localFogColor.a, 1.0f, localFogColor.a);
|
|
const float fMultiplier = (float)fsel(-localFogColor.a, 0.0f, 1.0f / fDivisor);
|
|
|
|
localFogColor.r *= fMultiplier;
|
|
localFogColor.g *= fMultiplier;
|
|
localFogColor.b *= fMultiplier;
|
|
}
|
|
|
|
localFogColor.a = 1.0f - localFogColor.a;
|
|
fogVolData.fogColor = localFogColor;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void CFogVolumeRenderNode::GetVolumetricFogColorEllipsoid(const Vec3& worldPos, const SRenderingPassInfo& passInfo, ColorF& resultColor) const
|
|
{
|
|
const CCamera& cam(passInfo.GetCamera());
|
|
Vec3 camPos(cam.GetPosition());
|
|
Vec3 camDir(cam.GetViewdir());
|
|
Vec3 cameraLookDir(worldPos - camPos);
|
|
|
|
resultColor = ColorF(1.0f, 1.0f, 1.0f, 1.0f);
|
|
|
|
if (cameraLookDir.GetLengthSquared() > 1e-4f)
|
|
{
|
|
// setup ray tracing in OS
|
|
Vec3 cameraPosInOSx2(m_matWSInv.TransformPoint(camPos) * 2.0f);
|
|
Vec3 cameraLookDirInOS(m_matWSInv.TransformVector(cameraLookDir));
|
|
|
|
float tI(sqrtf(cameraLookDirInOS.Dot(cameraLookDirInOS)));
|
|
float invOfScaledCamDirLength(1.0f / tI);
|
|
cameraLookDirInOS *= invOfScaledCamDirLength;
|
|
|
|
// calc coefficients for ellipsoid parametrization (just a simple unit-sphere in its own space)
|
|
float B(cameraPosInOSx2.Dot(cameraLookDirInOS));
|
|
float Bsq(B * B);
|
|
float C(cameraPosInOSx2.Dot(cameraPosInOSx2) - 4.0f);
|
|
|
|
// solve quadratic equation
|
|
float discr(Bsq - C);
|
|
if (discr >= 0.0)
|
|
{
|
|
float discrSqrt = sqrtf(discr);
|
|
|
|
// ray hit
|
|
Vec3 cameraPosInWS(camPos);
|
|
Vec3 cameraLookDirInWS((worldPos - camPos) * invOfScaledCamDirLength);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
float tS(max(0.5f * (-B - discrSqrt), 0.0f)); // clamp to zero so front ray-ellipsoid intersection is NOT behind camera
|
|
float tE(max(0.5f * (-B + discrSqrt), 0.0f)); // clamp to zero so back ray-ellipsoid intersection is NOT behind camera
|
|
//float tI( ( worldPos - camPos ).Dot( camDir ) / cameraLookDirInWS.Dot( camDir ) );
|
|
tI = max(tS, min(tI, tE)); // clamp to range [tS, tE]
|
|
|
|
Vec3 front(tS * cameraLookDirInWS + cameraPosInWS);
|
|
Vec3 dist((tI - tS) * cameraLookDirInWS);
|
|
float distLength(dist.GetLength());
|
|
float fogInt(distLength * expf_s(-(front - m_heightFallOffBasePoint).Dot(m_heightFallOffDirScaled)));
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
float heightDiff(dist.Dot(m_heightFallOffDirScaled));
|
|
if (fabsf(heightDiff) > 0.001f)
|
|
{
|
|
fogInt *= (1.0f - expf_s(-heightDiff)) / heightDiff;
|
|
}
|
|
|
|
float softArg(clamp_tpl(discr * m_cachedSoftEdgesLerp.x + m_cachedSoftEdgesLerp.y, 0.0f, 1.0f));
|
|
fogInt *= softArg * (2.0f - softArg);
|
|
|
|
float fog(expf_s(-m_globalDensity * fogInt));
|
|
|
|
resultColor = ColorF(m_cachedFogColor.r, m_cachedFogColor.g, m_cachedFogColor.b, min(fog, 1.0f));
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void CFogVolumeRenderNode::GetVolumetricFogColorBox(const Vec3& worldPos, const SRenderingPassInfo& passInfo, ColorF& resultColor) const
|
|
{
|
|
const CCamera& cam(passInfo.GetCamera());
|
|
Vec3 camPos(cam.GetPosition());
|
|
Vec3 cameraLookDir(worldPos - camPos);
|
|
|
|
resultColor = ColorF(1.0f, 1.0f, 1.0f, 1.0f);
|
|
|
|
if (cameraLookDir.GetLengthSquared() > 1e-4f)
|
|
{
|
|
// setup ray tracing in OS
|
|
Vec3 cameraPosInOS(m_matWSInv.TransformPoint(camPos));
|
|
Vec3 cameraLookDirInOS(m_matWSInv.TransformVector(cameraLookDir));
|
|
|
|
float tI(sqrtf(cameraLookDirInOS.Dot(cameraLookDirInOS)));
|
|
float invOfScaledCamDirLength(1.0f / tI);
|
|
cameraLookDirInOS *= invOfScaledCamDirLength;
|
|
|
|
const float fMax = std::numeric_limits<float>::max();
|
|
float tS(0), tE(fMax);
|
|
|
|
//TODO:
|
|
// May be worth profiling use of a loop here, iterating over elements of vector;
|
|
// might save on i-cache, but suspect we'll lose instruction interleaving and hit
|
|
// more register dependency issues.
|
|
|
|
//These fsels mean that the result is ignored if cameraLookDirInOS.x is 0.0f,
|
|
// avoiding a floating point compare. Avoiding the fcmp is ~15% faster
|
|
const float fXSelect = -fabsf(cameraLookDirInOS.x);
|
|
const float fXDivisor = (float)fsel(fXSelect, 1.0f, cameraLookDirInOS.x);
|
|
const float fXMultiplier = (float)fsel(fXSelect, 0.0f, 1.0f);
|
|
const float fXInvMultiplier = 1.0f - fXMultiplier;
|
|
|
|
//Accurate to 255/256ths on console
|
|
float invCameraDirInOSx = fres(fXDivisor); //(1.0f / fXDivisor);
|
|
|
|
float tPosPlane((1 - cameraPosInOS.x) * invCameraDirInOSx);
|
|
float tNegPlane((-1 - cameraPosInOS.x) * invCameraDirInOSx);
|
|
|
|
float tFrontFace = (float)fsel(-cameraLookDirInOS.x, tPosPlane, tNegPlane);
|
|
float tBackFace = (float)fsel(-cameraLookDirInOS.x, tNegPlane, tPosPlane);
|
|
|
|
tS = max(tS, tFrontFace * fXMultiplier);
|
|
tE = min(tE, (tBackFace * fXMultiplier) + (fXInvMultiplier * fMax));
|
|
|
|
|
|
|
|
const float fYSelect = -fabsf(cameraLookDirInOS.y);
|
|
const float fYDivisor = (float)fsel(fYSelect, 1.0f, cameraLookDirInOS.y);
|
|
const float fYMultiplier = (float)fsel(fYSelect, 0.0f, 1.0f);
|
|
const float fYInvMultiplier = 1.0f - fYMultiplier;
|
|
|
|
//Accurate to 255/256ths on console
|
|
float invCameraDirInOSy = fres(fYDivisor); //(1.0f / fYDivisor);
|
|
|
|
tPosPlane = ((1 - cameraPosInOS.y) * invCameraDirInOSy);
|
|
tNegPlane = ((-1 - cameraPosInOS.y) * invCameraDirInOSy);
|
|
|
|
tFrontFace = (float)fsel(-cameraLookDirInOS.y, tPosPlane, tNegPlane);
|
|
tBackFace = (float)fsel(-cameraLookDirInOS.y, tNegPlane, tPosPlane);
|
|
|
|
tS = max(tS, tFrontFace * fYMultiplier);
|
|
tE = min(tE, (tBackFace * fYMultiplier) + (fYInvMultiplier * fMax));
|
|
|
|
|
|
|
|
const float fZSelect = -fabsf(cameraLookDirInOS.z);
|
|
const float fZDivisor = (float)fsel(fZSelect, 1.0f, cameraLookDirInOS.z);
|
|
const float fZMultiplier = (float)fsel(fZSelect, 0.0f, 1.0f);
|
|
const float fZInvMultiplier = 1.0f - fZMultiplier;
|
|
|
|
//Accurate to 255/256ths on console
|
|
float invCameraDirInOSz = fres(fZDivisor); //(1.0f / fZDivisor);
|
|
|
|
tPosPlane = ((1 - cameraPosInOS.z) * invCameraDirInOSz);
|
|
tNegPlane = ((-1 - cameraPosInOS.z) * invCameraDirInOSz);
|
|
|
|
tFrontFace = (float)fsel(-cameraLookDirInOS.z, tPosPlane, tNegPlane);
|
|
tBackFace = (float)fsel(-cameraLookDirInOS.z, tNegPlane, tPosPlane);
|
|
|
|
tS = max(tS, tFrontFace * fZMultiplier);
|
|
tE = min(tE, (tBackFace * fZMultiplier) + (fZInvMultiplier * fMax));
|
|
|
|
tE = max(tE, 0.0f);
|
|
|
|
if (tS <= tE)
|
|
{
|
|
Vec3 cameraPosInWS(camPos);
|
|
Vec3 cameraLookDirInWS((worldPos - camPos) * invOfScaledCamDirLength);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
tI = max(tS, min(tI, tE)); // clamp to range [tS, tE]
|
|
|
|
Vec3 front(tS * cameraLookDirInWS + cameraPosInWS);
|
|
Vec3 dist((tI - tS) * cameraLookDirInWS);
|
|
float distLength(dist.GetLength());
|
|
float fogInt(distLength * expf_s(-(front - m_heightFallOffBasePoint).Dot(m_heightFallOffDirScaled)));
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
float heightDiff(dist.Dot(m_heightFallOffDirScaled));
|
|
|
|
//heightDiff = fabsf( heightDiff ) > 0.001f ? heightDiff : 0.001f
|
|
heightDiff = (float)fsel((-fabsf(heightDiff) + 0.001f), 0.001f, heightDiff);
|
|
|
|
fogInt *= (1.0f - expf_s(-heightDiff)) * fres(heightDiff);
|
|
|
|
float fog(expf_s(-m_globalDensity * fogInt));
|
|
|
|
resultColor = ColorF(m_cachedFogColor.r, m_cachedFogColor.g, m_cachedFogColor.b, min(fog, 1.0f));
|
|
}
|
|
}
|
|
}
|