Files
o3de/Code/CryEngine/Cry3DEngine/SkyLightNishita.cpp
T
2021-03-08 14:30:57 -08:00

730 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.
#include "Cry3DEngine_precompiled.h"
#define IGNORE_ASSERTS
#if defined(_DEBUG) && defined(IGNORE_ASSERTS)
# undef assert
# define assert(cond) ((void)0)
#endif
#include "SkyLightNishita.h"
#include <math.h>
// constant definitions (all heights & radii given in km or km^-1 )
const f64 c_maxAtmosphereHeight(100.0);
const f64 c_earthRadius(6368.0);
const f32 c_earthRadiusf(6368.0f);
const f64 c_avgDensityHeightMieInv(1.0 / 1.2);
const f64 c_avgDensityHeightRayleighInv(1.0 / 7.994);
const f64 c_opticalDepthWhenHittingEarth(1e10);
const f64 c_pi(3.1415926535897932384626433832795);
const f32 c_pif(3.1415926535897932384626433832795f);
// Machine epsilon is too small to catch rounding error asserts here. We use a large enough number to prevent rounding errors, but small
// enough to still catch invalid conditions (10^-6).
static const float floatDiffFactor = 0.000001f;
// constants for optical LUT serialization
const uint32 c_lutFileTag(0x4C594B53); // "SKYL"
const uint32 c_lutFileVersion(0x00010002);
const char c_lutFileName[] = "engineassets/sky/optical.lut";
static inline f64 MapSaveExpArg(f64 arg)
{
const f64 c_saveExpArgRange((f64)650.0); // -650.0 to 650 range is safe not to introduce fp over-/underflows
return((arg < -c_saveExpArgRange) ? -c_saveExpArgRange : (arg > c_saveExpArgRange) ? c_saveExpArgRange : arg);
}
static inline f64 exp_precise(f64 arg)
{
return(exp(MapSaveExpArg((f64) arg)));
}
namespace
{
union eco
{
f64 d;
struct
{
int32 i, j;
} n;
};
}
static inline f64 exp_fast(f64 arg)
{
const f64 eco_m(1048576L / 0.693147180559945309417232121458177);
const f64 eco_a(1072693248L - 60801L);
#if defined(_CPU_X86) || defined(_CPU_AMD64) || defined(_CPU_ARM)// for little endian (tested on Win32 / Win64)
eco e;
# ifdef _DEBUG
e.d = 1.0;
assert(e.n.j - 1072693248L || e.n.i == 0); // check IEEE-754 conformance
# endif
e.n.j = (int32) (eco_m * MapSaveExpArg(arg) + eco_a);
return((f64)e.d);
#elif defined(_CPU_G5)
eco e;
# ifdef _DEBUG
e.d = 1.0;
assert(e.n.i == 1072693248L || e.n.j == 0); // check IEEE-754 conformance
# endif
e.n.i = (int32) (eco_m * MapSaveExpArg(arg) + eco_a);
return((f64)e.d);
#else // fall back to default exp_sky() implementation for untested/unsupported target platforms
# pragma message( "Optimized exp_fast() not available for this platform!" )
# pragma message( "If your target CPU is IEEE-754 conformant then please specify it in either the little or big endian branch (see SkyLightNishita.cpp::exp_fast())." )
return(exp(arg));
#endif
}
static inline f64 OpticalScaleFunction(const f64& height, const f64& avgDensityHeightInv)
{
assert(height >= 0.0);
assert(avgDensityHeightInv > 0.0 && avgDensityHeightInv <= 1.0);
return(exp_precise(-height * avgDensityHeightInv));
}
static inline f64 IntegrateOpticalDepthInternal(const Vec3d& start, const f64& startScale,
const Vec3d& end, const f64& endScale, const f64& avgDensityHeightInv, const f64& error)
{
assert(_finite(startScale) && _finite(endScale));
Vec3d mid(0.5 * (start + end));
f64 midScale(OpticalScaleFunction(mid.GetLength() - c_earthRadius, avgDensityHeightInv));
if (fabs(startScale - midScale) <= error && fabs(midScale - endScale) <= error)
{
// integrate section this via simpson rule and stop recursing
const f64 c_oneSixth(1.0 / 6.0);
return((startScale + 4.0 * midScale + endScale) * c_oneSixth * (end - start).GetLength());
}
else
{
// refine section via recursing down left and right branch
return(IntegrateOpticalDepthInternal(start, startScale, mid, midScale, avgDensityHeightInv, error) +
IntegrateOpticalDepthInternal(mid, midScale, end, endScale, avgDensityHeightInv, error));
}
}
CSkyLightNishita::CSkyLightNishita()
: m_opticalDepthLUT()
, m_opticalScaleLUT()
, m_phaseLUT()
, m_Km(0.0f)
, m_Kr(0.0f)
, m_sunIntensity(20.0f, 20.0f, 20.0f)
, m_g(0.0f)
, m_invRGBWaveLength4(1.0f, 1.0f, 1.0f)
, m_sunDir(0.0f, 0.707106f, 0.707106f)
, m_inScatteringStepSize(1)
{
SetRGBWaveLengths(Vec3(650.0f, 570.0f, 475.0f));
SetSunDirection(Vec3(0.0f, 0.707106f, 0.707106f));
SetAtmosphericConditions(Vec3(20.0f, 20.0f, 20.0f), 0.001f, 0.00025f, -0.99f);
ILog* pLog(C3DEngine::GetLog());
if (false == LoadOpticalLUTs())
{
if (0 != pLog)
{
PrintMessage("Sky light: Optical lookup tables couldn't be loaded off disc. Recomputation needed!");
}
ComputeOpticalLUTs();
}
else
{
if (0 != pLog)
{
PrintMessage("Sky light: Optical lookup tables loaded off disc.");
}
}
}
CSkyLightNishita::~CSkyLightNishita()
{
}
CSkyLightNishita::SOpticalDepthLUTEntry CSkyLightNishita::LookupBilerpedOpticalDepthLUTEntry(
const SOpticalDepthLUTEntry* const __restrict cpOptDepthLUT,
uint32 heightIndex, const f32 cosVertAngle) const
{
uint32 vertAngleIndex;
f32 vertAngleIndexFrc;
f32 saveCosVertAngle(clamp_tpl(cosVertAngle, -1.0f, 1.0f));
f32 _index((f32) (cOLUT_AngularSteps - 1) * (-saveCosVertAngle * 0.5f + 0.5f));
vertAngleIndex = (uint32) _index;
vertAngleIndexFrc = _index - floorf(_index);
if (vertAngleIndex >= cOLUT_AngularSteps - 1)
{
return(cpOptDepthLUT[ OpticalLUTIndex(heightIndex, vertAngleIndex) ]);
}
else
{
uint32 index(OpticalLUTIndex(heightIndex, vertAngleIndex));
const SOpticalDepthLUTEntry& a(cpOptDepthLUT[ index ]);
const SOpticalDepthLUTEntry& b(cpOptDepthLUT[ index + 1 ]);
SOpticalDepthLUTEntry res;
res.mie = a.mie + vertAngleIndexFrc * (b.mie - a.mie);
res.rayleigh = a.rayleigh + vertAngleIndexFrc * (b.rayleigh - a.rayleigh);
return(res);
}
}
CSkyLightNishita::SPhaseLUTEntry CSkyLightNishita::LookupBilerpedPhaseLUTEntry(const f32 cosPhaseAngle) const
{
uint32 index;
f32 indexFrc;
MapCosPhaseAngleToIndex(cosPhaseAngle, index, indexFrc);
if (index >= cPLUT_AngularSteps - 1)
{
return(m_phaseLUT[ cPLUT_AngularSteps - 1 ]);
}
else
{
const SPhaseLUTEntry& a(m_phaseLUT[ index + 0 ]);
const SPhaseLUTEntry& b(m_phaseLUT[ index + 1 ]);
SPhaseLUTEntry res;
res.mie = a.mie + indexFrc * (b.mie - a.mie);
res.rayleigh = a.rayleigh + indexFrc * (b.rayleigh - a.rayleigh);
return(res);
}
}
void CSkyLightNishita::SamplePartialInScatteringAtHeight(const SOpticalScaleLUTEntry& osAtHeight,
const f32 outScatteringConstMie, const Vec3& outScatteringConstRayleigh, const SOpticalDepthLUTEntry& odAtHeightSky,
const SOpticalDepthLUTEntry& odAtViewerSky, const SOpticalDepthLUTEntry& odAtHeightSun,
Vec3& partialInScatteringMie, Vec3& partialInScatteringRayleigh) const
{
assert(odAtHeightSky.mie >= 0.0 && (odAtHeightSky.mie - floatDiffFactor) <= odAtViewerSky.mie);
assert(odAtHeightSun.mie >= 0.0);
assert(odAtHeightSky.rayleigh >= 0.0 && (odAtHeightSky.rayleigh - floatDiffFactor) <= odAtViewerSky.rayleigh);
assert(odAtHeightSun.rayleigh >= 0.0);
// mie out-scattering
f32 sampleExpArgMie(outScatteringConstMie * (-odAtHeightSun.mie - (odAtViewerSky.mie - odAtHeightSky.mie)));
// rayleigh out-scattering
Vec3 sampleExpArgRayleigh(outScatteringConstRayleigh * (-odAtHeightSun.rayleigh - (odAtViewerSky.rayleigh - odAtHeightSky.rayleigh)));
// partial in-scattering sampling result
Vec3 sampleExpArg(Vec3(sampleExpArgMie, sampleExpArgMie, sampleExpArgMie) + sampleExpArgRayleigh);
Vec3 sampleRes((float)exp_fast(sampleExpArg.x), (float)exp_fast(sampleExpArg.y), (float)exp_fast(sampleExpArg.z));
partialInScatteringMie = osAtHeight.mie * sampleRes;
partialInScatteringRayleigh = osAtHeight.rayleigh * sampleRes;
}
void CSkyLightNishita::ComputeInScatteringNoPremul(const f32 outScatteringConstMie, const Vec3& outScatteringConstRayleigh, const Vec3& skyDir,
Vec3& inScatteringMieNoPremul, Vec3& inScatteringRayleighNoPremul) const
{
// start integration along the "skyDir" from the viewer's point of view
const Vec3 c_up(0.0f, 0.0f, 1.0f);
const Vec3 viewer(c_up * c_earthRadiusf);
Vec3 curRayPos(viewer);
// to be reused by ray-sphere intersection code in loop below
f32 B(2.0f * viewer.Dot(skyDir));
f32 Bsq(B * B);
f32 Cpart(viewer.Dot(viewer));
// calculate optical depth at viewer
const SOpticalDepthLUTEntry* const __restrict cpOptDepthLUT = &m_opticalDepthLUT[0];
const Vec3& cSunDir(m_sunDir);
SOpticalDepthLUTEntry odAtViewerSky(LookupBilerpedOpticalDepthLUTEntry(cpOptDepthLUT, 0, skyDir.Dot(c_up)));
SOpticalDepthLUTEntry odAtViewerSun(LookupBilerpedOpticalDepthLUTEntry(cpOptDepthLUT, 0, cSunDir.Dot(c_up)));
// sample partial in-scattering term at viewer
Vec3 curSampleMie, curSampleRayleigh;
const SOpticalScaleLUTEntry* const __restrict cpOptScaleLUT = &m_opticalScaleLUT[0];
SamplePartialInScatteringAtHeight(cpOptScaleLUT[0], outScatteringConstMie, outScatteringConstRayleigh,
odAtViewerSky, odAtViewerSky, odAtViewerSun, curSampleMie, curSampleRayleigh);
// integrate along "skyDir" over all height segments we've precalculated in the optical lookup table
inScatteringMieNoPremul = Vec3(0.0f, 0.0f, 0.0f);
inScatteringRayleighNoPremul = Vec3(0.0f, 0.0f, 0.0f);
const int32 cInScatteringStepSize(m_inScatteringStepSize);
for (int a(1); a < cOLUT_HeightSteps; a += cInScatteringStepSize)
{
// calculate intersection with current "atmosphere shell"
const SOpticalScaleLUTEntry& crOpticalScaleLUTEntry = cpOptScaleLUT[a];
SOpticalScaleLUTEntry osAtHeight(crOpticalScaleLUTEntry);
f32 C(Cpart - (c_earthRadiusf + osAtHeight.atmosphereLayerHeight) * (c_earthRadiusf + osAtHeight.atmosphereLayerHeight));
f32 det(Bsq - 4.0f * C);
assert(det >= 0.0f && (0.5f * (-B - sqrtf(det)) <= 0.0f) && (((int)(0.5f * (-B + sqrtf(det)))*100.0) / 100.0f >= 0.0f));
f32 t(0.5f * (-B + sqrtf(det)));
Vec3 newRayPos(viewer + t * skyDir);
// calculate optical depth at new position
// since atmosphere bends we need to determine a new up vector to properly index the optical LUT
Vec3 newUp(newRayPos.GetNormalized());
SOpticalDepthLUTEntry odAtHeightSky(LookupBilerpedOpticalDepthLUTEntry(cpOptDepthLUT, a, skyDir.Dot(newUp)));
SOpticalDepthLUTEntry odAtHeightSun(LookupBilerpedOpticalDepthLUTEntry(cpOptDepthLUT, a, cSunDir.Dot(newUp)));
// when optimized in clang, values seem to drift a bit and under certain edge conditions
// raise asserts in SamplePartialInScatteringAtHeight function
if (odAtHeightSky.mie > odAtViewerSky.mie)
{
odAtHeightSky.mie = odAtViewerSky.mie;
}
if (odAtHeightSky.rayleigh > odAtViewerSky.rayleigh)
{
odAtHeightSky.rayleigh = odAtViewerSky.rayleigh;
}
// sample partial in-scattering term at new position
Vec3 newSampleMie, newSampleRayleigh;
SamplePartialInScatteringAtHeight(osAtHeight, outScatteringConstMie, outScatteringConstRayleigh,
odAtHeightSky, odAtViewerSky, odAtHeightSun, newSampleMie, newSampleRayleigh);
// integrate via trapezoid rule
f32 weight((newRayPos - curRayPos).GetLength() * 0.5f);
inScatteringMieNoPremul += (curSampleMie + newSampleMie) * weight;
inScatteringRayleighNoPremul += (curSampleRayleigh + newSampleRayleigh) * weight;
// update sampling data
curRayPos = newRayPos;
curSampleMie = newSampleMie;
curSampleRayleigh = newSampleRayleigh;
}
}
void CSkyLightNishita::ComputeSkyColor(const Vec3& skyDir, Vec3* pInScattering, Vec3* pInScatteringMieNoPremul,
Vec3* pInScatteringRayleighNoPremul, Vec3* pInScatteringRayleigh) const
{
//// get high precision normalized sky direction
//Vec3 _skyDir( skyDir );
//assert( _skyDir.GetLengthSquared() > 0.0 );
//_skyDir.Normalize();
assert(fabsf(skyDir.GetLengthSquared() - 1.0f) < 1e-4f);
SPhaseLUTEntry phaseLUTEntry(LookupBilerpedPhaseLUTEntry(-skyDir.Dot(m_sunDir)));
// initialize constants for mie scattering
f32 phaseForPhiGMie(phaseLUTEntry.mie);
f32 outScatteringConstMie(4.0f * c_pif * m_Km);
Vec3 inScatteringConstMie(m_sunIntensity * m_Km * phaseForPhiGMie);
// initialize constants for rayleigh scattering
f32 phaseForPhiGRayleigh(phaseLUTEntry.rayleigh);
Vec3 outScatteringConstRayleigh(4.0f * (float)c_pi * m_Kr * m_invRGBWaveLength4);
Vec3 inScatteringConstRayleigh((m_sunIntensity * m_Kr * phaseForPhiGRayleigh).CompMul(m_invRGBWaveLength4));
// compute in-scattering
Vec3 inScatteringMieNoPremul, inScatteringRayleighNoPremul;
ComputeInScatteringNoPremul(outScatteringConstMie, outScatteringConstRayleigh, skyDir, inScatteringMieNoPremul, inScatteringRayleighNoPremul);
assert(inScatteringMieNoPremul.x >= 0.0f && inScatteringMieNoPremul.y >= 0.0f && inScatteringMieNoPremul.z >= 0.0f);
assert(inScatteringRayleighNoPremul.x >= 0.0f && inScatteringRayleighNoPremul.y >= 0.0f && inScatteringRayleighNoPremul.z >= 0.0f);
// return color
if (pInScattering)
{
*pInScattering = Vec3(inScatteringMieNoPremul.CompMul(inScatteringConstMie) + inScatteringRayleighNoPremul.CompMul(inScatteringConstRayleigh));
}
if (pInScatteringMieNoPremul)
{
*pInScatteringMieNoPremul = Vec3(inScatteringMieNoPremul);
}
if (pInScatteringRayleighNoPremul)
{
*pInScatteringRayleighNoPremul = Vec3(inScatteringRayleighNoPremul);
}
if (pInScatteringRayleigh)
{
*pInScatteringRayleigh = Vec3(inScatteringRayleighNoPremul.CompMul(inScatteringConstRayleigh));
}
}
void CSkyLightNishita::SetInScatteringIntegralStepSize(int32 stepSize)
{
stepSize = stepSize < 1 ? 1 : stepSize > 2 ? 2 : stepSize;
m_inScatteringStepSize = stepSize;
}
int32 CSkyLightNishita::GetInScatteringIntegralStepSize() const
{
return(m_inScatteringStepSize);
}
Vec4 CSkyLightNishita::GetPartialMieInScatteringConst() const
{
Vec3 res(m_sunIntensity * m_Km);
return(Vec4(res.x, res.y, res.z, 0.0f));
}
Vec4 CSkyLightNishita::GetPartialRayleighInScatteringConst() const
{
Vec3 res((m_sunIntensity * m_Kr).CompMul(m_invRGBWaveLength4));
return(Vec4(res.x, res.y, res.z, 0.0f));
}
Vec3 CSkyLightNishita::GetSunDirection() const
{
return(Vec3(m_sunDir.x, m_sunDir.y, m_sunDir.z));
}
Vec4 CSkyLightNishita::GetPhaseFunctionConsts() const
{
//f32 g2( m_g * m_g );
//f32 miePart( 1.5f * ( 1.0f - g2 ) / ( 2.0f + g2 ) );
//return( Vec4( m_g, m_g * m_g, miePart, 0.0f ) );
f32 g2(m_g * m_g);
f32 miePart(1.5f * (1.0f - g2) / (2.0f + g2));
f32 miePartPow(powf(miePart, -2.0f / 3.0f));
return(Vec4(miePartPow * -2.0f * m_g, miePartPow * (1.0f + g2), 0.0f, 0.0f));
}
f64 CSkyLightNishita::IntegrateOpticalDepth(const Vec3d& start, const Vec3d& end, const f64& avgDensityHeightInv, const f64& error) const
{
f64 startScale(OpticalScaleFunction(start.GetLength() - c_earthRadius, avgDensityHeightInv));
f64 endScale(OpticalScaleFunction(end.GetLength() - c_earthRadius, avgDensityHeightInv));
return(IntegrateOpticalDepthInternal(start, startScale, end, endScale, avgDensityHeightInv, error));
}
bool CSkyLightNishita::ComputeOpticalDepth(const Vec3d& cameraLookDir, const f64& cameraHeight, const f64& avgDensityHeightInv, float& depth) const
{
// init camera position
Vec3d cameraPos(0.0, cameraHeight + c_earthRadius, 0.0);
// check if ray hits earth
// compute B, and C of quadratic function (A=1, as looking direction is normalized)
f64 B(2.0 * cameraPos.Dot(cameraLookDir));
f64 Bsq(B * B);
f64 Cpart(cameraPos.Dot(cameraPos));
f64 C(Cpart - c_earthRadius * c_earthRadius);
f64 det(Bsq - 4.0 * C);
bool hitsEarth(det >= 0.0 && ((0.5 * (-B - sqrt(det)) > 1e-4) || (0.5 * (-B + sqrt(det)) > 1e-4)));
if (false != hitsEarth)
{
depth = (float)c_opticalDepthWhenHittingEarth;
return(false);
}
// find intersection with atmosphere top
C = Cpart - (c_maxAtmosphereHeight + c_earthRadius) * (c_maxAtmosphereHeight + c_earthRadius);
det = Bsq - 4.0 * C;
assert(det >= 0.0); // ray defined outside the atmosphere
f64 t(0.5 * (-B + sqrt(det)));
assert(t >= -1e-4);
if (t < 0.0)
{
t = 0.0;
}
// integrate depth along ray from camera to atmosphere top
f64 _depth(0.0);
int numInitialSamples((int) t);
numInitialSamples = (numInitialSamples < 2) ? 2 : numInitialSamples;
Vec3d lastCameraPos(cameraPos);
for (int i(1); i < numInitialSamples; ++i)
{
Vec3d curCameraPos(cameraPos + cameraLookDir * (t * ((float) i / (float) numInitialSamples)));
_depth += IntegrateOpticalDepth(lastCameraPos, curCameraPos, avgDensityHeightInv, 1e-1);
lastCameraPos = curCameraPos;
}
assert(_depth >= 0.0 && _depth < 1e25);
assert(0 != _finite(_depth));
depth = (float) _depth;
return(true);
}
void CSkyLightNishita::ComputeOpticalLUTs()
{
LOADING_TIME_PROFILE_SECTION(GetISystem());
ILog* pLog(C3DEngine::GetLog());
if (0 != pLog)
{
PrintMessage("Sky light: Computing optical lookup tables (this might take a while)... ");
}
// reset tables
m_opticalDepthLUT.resize(0);
m_opticalDepthLUT.reserve(cOLUT_HeightSteps * cOLUT_AngularSteps);
m_opticalScaleLUT.resize(0);
m_opticalScaleLUT.reserve(cOLUT_HeightSteps);
// compute LUTs
for (int a(0); a < cOLUT_HeightSteps; ++a)
{
f64 height(MapIndexToHeight(a));
// compute optical depth
for (int i(0); i < cOLUT_AngularSteps; ++i)
{
// init looking direction of camera
f64 cosVertAngle(MapIndexToCosVertAngle(i));
Vec3d cameraLookDir(sqrt(1.0 - cosVertAngle * cosVertAngle), cosVertAngle, 0.0);
// compute optical depth
SOpticalDepthLUTEntry e;
bool b0(ComputeOpticalDepth(cameraLookDir, height, c_avgDensityHeightMieInv, e.mie));
bool b1(ComputeOpticalDepth(cameraLookDir, height, c_avgDensityHeightRayleighInv, e.rayleigh));
assert(b0 == b1);
// blend out previous values once camera ray hits earth
if (false == b0 && false == b1 && i > 0)
{
e = m_opticalDepthLUT.back();
e.mie = (f32) ((f32)0.5 * (e.mie + c_opticalDepthWhenHittingEarth));
e.rayleigh = (f32) ((f32)0.5 * (e.rayleigh + c_opticalDepthWhenHittingEarth));
}
// store result
m_opticalDepthLUT.push_back(e);
}
{
// compute optical scale
SOpticalScaleLUTEntry e;
e.atmosphereLayerHeight = (f32) height;
e.mie = (f32) OpticalScaleFunction(height, c_avgDensityHeightMieInv);
e.rayleigh = (f32) OpticalScaleFunction(height, c_avgDensityHeightRayleighInv);
m_opticalScaleLUT.push_back(e);
}
}
// save LUTs for next time
SaveOpticalLUTs();
if (0 != pLog)
{
PrintMessage(" ... done.\n");
}
}
void CSkyLightNishita::ComputePhaseLUT()
{
//ILog* pLog( C3DEngine::GetLog() );
//if( 0 != pLog )
// PrintMessage( "Sky light: Computing phase lookup table... " );
// reset tables
m_phaseLUT.resize(0);
m_phaseLUT.reserve(cPLUT_AngularSteps);
// compute coefficients
f32 g(m_g);
f32 g2(g * g);
f32 miePart(1.5f * (1.0f - g2) / (2.0f + g2));
// calculate entries
for (int i(0); i < cPLUT_AngularSteps; ++i)
{
f32 cosine(MapIndexToCosPhaseAngle(i));
f32 cosine2(cosine * cosine);
//f32 t = 1.0f + g2 - 2.0f * g * cosine;
//if (fabsf(t) < 1e-5f)
//{
// PrintMessage( "Sky light: g = %.10f", g );
// PrintMessage( "Sky light: g2 = %.10f", g2 );
// PrintMessage( "Sky light: cosine = %.10f", cosine );
// PrintMessage( "Sky light: cosine2 = %.10f", cosine2 );
// PrintMessage( "Sky light: t = %.10f", t );
//}
f32 miePhase(miePart * (1.0f + cosine2) / powf(1.0f + g2 - 2.0f * g * cosine, 1.5f));
f32 rayleighPhase(0.75f * (1.0f + cosine2));
SPhaseLUTEntry e;
e.mie = (float) miePhase;
e.rayleigh = (float) rayleighPhase;
m_phaseLUT.push_back(e);
}
//if( 0 != pLog )
// PrintMessage( " ... done.\n" );
}
f64 CSkyLightNishita::MapIndexToHeight(uint32 index) const
{
// a function that maps well to mie and rayleigh at the same time
// that is, a lot of indices will map below the average density height for mie & rayleigh scattering
assert(index < cOLUT_HeightSteps);
f64 x((f64)index / (cOLUT_HeightSteps - 1));
return(c_maxAtmosphereHeight * exp_precise(10.0 * (x - 1.0)) * x);
}
f64 CSkyLightNishita::MapIndexToCosVertAngle(uint32 index) const
{
assert(index < cOLUT_AngularSteps);
return(1.0 - 2.0 * ((f64)index / (cOLUT_AngularSteps - 1)));
}
f32 CSkyLightNishita::MapIndexToCosPhaseAngle(uint32 index) const
{
assert(index < cPLUT_AngularSteps);
return(1.0f - 2.0f * ((f32)index / ((f32)cPLUT_AngularSteps - 1)));
}
void CSkyLightNishita::MapCosPhaseAngleToIndex(const f32 cosPhaseAngle, uint32& index, f32& indexFrc) const
{
//assert( -1 <= cosPhaseAngle && 1 >= cosPhaseAngle );
f32 saveCosPhaseAngle(clamp_tpl(cosPhaseAngle, -1.0f, 1.0f));
f32 _index((f32) (cPLUT_AngularSteps - 1) * (-saveCosPhaseAngle * 0.5f + 0.5f));
index = (uint32) _index;
indexFrc = _index - floorf(_index);
}
uint32 CSkyLightNishita::OpticalLUTIndex(uint32 heightIndex, uint32 cosVertAngleIndex) const
{
assert(heightIndex < cOLUT_HeightSteps && cosVertAngleIndex < cOLUT_AngularSteps);
return(heightIndex * cOLUT_AngularSteps + cosVertAngleIndex);
}
bool CSkyLightNishita::LoadOpticalLUTs()
{
auto pPak(C3DEngine::GetPak());
if (0 != pPak)
{
AZ::IO::HandleType fileHandle = pPak->FOpen(c_lutFileName, "rb");
if (fileHandle != AZ::IO::InvalidHandle)
{
size_t itemsRead(0);
// read in file tag
uint32 fileTag(0);
itemsRead = pPak->FRead(&fileTag, 1, fileHandle);
if (itemsRead != 1 || fileTag != c_lutFileTag)
{
// file tag mismatch
pPak->FClose(fileHandle);
return(false);
}
// read in file format version
uint32 fileVersion(0);
itemsRead = pPak->FRead(&fileVersion, 1, fileHandle);
if (itemsRead != 1 || fileVersion != c_lutFileVersion)
{
// file version mismatch
pPak->FClose(fileHandle);
return(false);
}
// read in optical depth LUT
m_opticalDepthLUT.resize(cOLUT_HeightSteps * cOLUT_AngularSteps);
itemsRead = pPak->FRead(&m_opticalDepthLUT[0], m_opticalDepthLUT.size(), fileHandle);
if (itemsRead != m_opticalDepthLUT.size())
{
pPak->FClose(fileHandle);
return(false);
}
// read in optical scale LUT
m_opticalScaleLUT.resize(cOLUT_HeightSteps);
itemsRead = pPak->FRead(&m_opticalScaleLUT[0], m_opticalScaleLUT.size(), fileHandle);
if (itemsRead != m_opticalScaleLUT.size())
{
pPak->FClose(fileHandle);
return(false);
}
// check if we read entire file
uint64_t curPos(pPak->FTell(fileHandle));
pPak->FSeek(fileHandle, 0, SEEK_END);
uint64_t endPos(pPak->FTell(fileHandle));
if (curPos != endPos)
{
pPak->FClose(fileHandle);
return(false);
}
// LUT successfully read
pPak->FClose(fileHandle);
return(true);
}
}
return(false);
}
void CSkyLightNishita::SaveOpticalLUTs() const
{
// only save on little endian PCs so the load function can do proper endian swapping
#if defined(_CPU_X86) || defined(_CPU_AMD64)
auto pPak(C3DEngine::GetPak());
if (0 != pPak)
{
AZ::IO::HandleType fileHandle = pPak->FOpen(c_lutFileName, "wb");
if (fileHandle != AZ::IO::InvalidHandle)
{
// write out file tag
pPak->FWrite(&c_lutFileTag, 1, sizeof(c_lutFileTag), fileHandle);
// write out file format version
pPak->FWrite(&c_lutFileVersion, 1, sizeof(c_lutFileVersion), fileHandle);
// write out optical depth LUT
assert(m_opticalDepthLUT.size() == cOLUT_HeightSteps * cOLUT_AngularSteps);
pPak->FWrite(&m_opticalDepthLUT[0], 1, sizeof(SOpticalDepthLUTEntry) * m_opticalDepthLUT.size(), fileHandle);
// write out optical scale LUT
assert(m_opticalScaleLUT.size() == cOLUT_HeightSteps);
pPak->FWrite(&m_opticalScaleLUT[0], 1, sizeof(SOpticalScaleLUTEntry) * m_opticalScaleLUT.size(), fileHandle);
// close file
pPak->FClose(fileHandle);
}
}
#endif
}