1003 lines
29 KiB
C++
1003 lines
29 KiB
C++
// scattertable.cpp
|
|
//
|
|
// Copyright (C) 2010, Chris Laurel <claurel@shatters.net>
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// A utility for generating atmospheric transmittance and scattering
|
|
// tables for use in real time 3D rendering.
|
|
//
|
|
// Using a 3D texture to store precomputed inscattering values was described
|
|
// in:
|
|
// Schafhitzel T., Falk M., Ertl T.: "Real-time rendering of planets with
|
|
// atmospheres." In WSCG International Conference in Central Europe on
|
|
// Computer Graphics, Visualization and Computer Vision (2007).
|
|
//
|
|
// The approach in Schafhitzel et al was extended in Bruneton E., Neyret F.:
|
|
// "Precomputed Atmospheric Scattering." Eurographics Symposium on
|
|
// Rendering 2008. Bruneton and Neyret made three key improvements:
|
|
// * Extended the tables to 4D in order to incorporate the angle between
|
|
// viewer and the sun (resulting in the 'twilight wedge' phenomenon)
|
|
// * Added extra steps in the table generation to simulate the effects
|
|
// of multiple scattering
|
|
// * Optimized the parameterizations of height, view angle, and sun angle
|
|
// in order to reduce artifacts and use less storage. Further improvements
|
|
// to the parameterization were made in GLSL and code available on the
|
|
// web at http://evasion.inrialpes.fr/~Eric.Bruneton/
|
|
//
|
|
// The inscatter table produced by the scattertable utility currently only
|
|
// incorporates single scattering and is 3D, not 4D. The parameterization of
|
|
// view angle is an optimized version of the 'steep sigmoid' function
|
|
// used by Bruneton (which involved an expensive inverse trig operation
|
|
// in the shader.)
|
|
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <cstdlib>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <cassert>
|
|
#include <Eigen/Core>
|
|
#include <Eigen/Array>
|
|
|
|
using namespace Eigen;
|
|
using namespace std;
|
|
|
|
|
|
const unsigned int HeightSamples = 32;
|
|
const unsigned int ViewAngleSamples = 256;
|
|
const unsigned int SunAngleSamples = 32;
|
|
|
|
// Values settable via the command line
|
|
static unsigned int ScatteringIntegrationSteps = 25;
|
|
|
|
typedef map<string, double> ParameterSet;
|
|
|
|
static const Vector3f RGBWavelengths(680.0f, 550.0f, 440.0f);
|
|
|
|
/** @param lambda - wavelength in nm
|
|
* @param n - index of refraction
|
|
* @param N - particles per meter^3
|
|
*/
|
|
static float RayleighScatteringCoeff(float lambda, float n, float N)
|
|
{
|
|
float lambdaM = lambda * 1.0e-9f;
|
|
return (8.0f * pow(float(M_PI), 3.0f) * pow(n * n - 1.0f, 2.0f)) / (3.0f * N * pow(lambdaM, 4.0f));
|
|
}
|
|
|
|
static Vector3f RayleighScatteringCoeff(float n, float N)
|
|
{
|
|
return Vector3f(RayleighScatteringCoeff(RGBWavelengths.x(), n, N),
|
|
RayleighScatteringCoeff(RGBWavelengths.y(), n, N),
|
|
RayleighScatteringCoeff(RGBWavelengths.z(), n, N));
|
|
}
|
|
|
|
|
|
class Atmosphere
|
|
{
|
|
public:
|
|
Atmosphere() = default;
|
|
|
|
float shellRadius() const
|
|
{
|
|
return planetRadius + max(mieScaleHeight, rayleighScaleHeight) * 8.0f;
|
|
}
|
|
|
|
Vector3f* computeTransmittanceTable() const;
|
|
Vector4f* computeInscatterTable() const;
|
|
|
|
public:
|
|
float planetRadius;
|
|
float rayleighScaleHeight;
|
|
float mieScaleHeight;
|
|
Vector3f rayleighCoeff;
|
|
float mieCoeff;
|
|
Vector3f absorptionCoeff;
|
|
float mieAsymmetry;
|
|
};
|
|
|
|
|
|
// Check to see if a floating point value is a NaN. Useful for debugging.
|
|
static inline bool isNaN(float x)
|
|
{
|
|
return x != x;
|
|
}
|
|
|
|
|
|
static inline float sign(float x)
|
|
{
|
|
if (x < 0.0f)
|
|
return -1.0f;
|
|
if (x > 0.0f)
|
|
return 1.0f;
|
|
else
|
|
return 0.0f;
|
|
}
|
|
|
|
|
|
// Based on approximation from E. Bruneton and F. Neyret,
|
|
// - r is distance of eye from planet center
|
|
// - mu is the cosine of the view angle (view angle dot zenith direction)
|
|
// - pathLength is the distance that the ray travels through the atmosphere
|
|
// - H is the scale height
|
|
// - R is the planet radius
|
|
float opticalDepth(float r, float mu, float l, float H, float R)
|
|
{
|
|
float a = sqrt(r * 0.5 / H);
|
|
float bx = a * mu;
|
|
float by = a * (mu + l / r);
|
|
float sbx = sign(bx);
|
|
float sby = sign(by);
|
|
float x = sby > sbx ? exp(bx * bx) : 0.0f;
|
|
float yx = sbx / (2.3193f * abs(bx) + sqrt(1.52f * bx * bx + 4.0f));
|
|
float yy = sby / (2.3193f * abs(by) + sqrt(1.52f * by * by + 4.0f)) *
|
|
exp(-l / H * (l / (2.0f * r) + mu));
|
|
|
|
return sqrt(6.2831f * H * r) * exp((R - r) / H) * (x + yx - yy);
|
|
}
|
|
|
|
|
|
Vector3f transmittance(float r, float mu, float l, const Atmosphere& atm)
|
|
{
|
|
float depthR = opticalDepth(r, mu, l, atm.rayleighScaleHeight, atm.planetRadius);
|
|
float depthM = opticalDepth(r, mu, l, atm.mieScaleHeight, atm.planetRadius);
|
|
return (-depthR * atm.rayleighCoeff
|
|
- depthM * Vector3f::Constant(atm.mieCoeff)
|
|
- depthM * atm.absorptionCoeff).cwise().exp();
|
|
}
|
|
|
|
|
|
// Parameters:
|
|
// h - height of the viewpoint above the planet surface
|
|
// mu - cosine of the angle between the view direction and zenith
|
|
// muS - cosine of the angle between the sun direction and zenith
|
|
|
|
// Map a unorm to the cosine of the view angle
|
|
static inline float toMu(float u)
|
|
{
|
|
float x = u * 2.0f - 1.0f;
|
|
float signX = x < 0.0f ? 1.0f : -1.0f;
|
|
return (x * (0.1f - 0.15f * signX) - 0.165f) / (signX * x + 1.1f);
|
|
}
|
|
|
|
// Map a unorm to the cosine of the sun angle
|
|
static inline float toMuS(float u)
|
|
{
|
|
// Original version from Bruneton paper:
|
|
//return (-1.0f / 3.0f) * (log(1.0f - u * (1.0f - exp(-3.6f))) + 0.6f);
|
|
|
|
// Modifier version has a wider range, allowing more negative angles. This
|
|
// eliminates the faint but persistent illumination that appears even
|
|
// when the sun is far below the horizon. Even the adjusted function may
|
|
// still not be adequate when very large scale heights are used.
|
|
return (-1.0f / 2.0f) * (log(1.0f - u * (1.0f - exp(-2.6f))) + 0.6f);
|
|
}
|
|
|
|
|
|
Vector3f*
|
|
Atmosphere::computeTransmittanceTable() const
|
|
{
|
|
float Rg = planetRadius;
|
|
float Rg2 = Rg * Rg;
|
|
float Rt = shellRadius();
|
|
float Rt2 = Rt * Rt;
|
|
|
|
unsigned int sampleCount = HeightSamples * ViewAngleSamples;
|
|
Vector3f* transmittanceTable = new Vector3f[sampleCount];
|
|
|
|
// Avoid numerical precision problems by choosing a first viewer
|
|
// position just *above* the planet surface.
|
|
float baseHeight = Rg * 1.0e-6f;
|
|
|
|
for (unsigned int i = 0; i < HeightSamples; ++i)
|
|
{
|
|
float v = float(i) / float(HeightSamples);
|
|
float h = v * v * (Rt - Rg) + baseHeight;
|
|
float r = Rg + h;
|
|
float r2 = r * r;
|
|
|
|
for (unsigned int j = 0; j < ViewAngleSamples; ++j)
|
|
{
|
|
float u = float(j) / float(ViewAngleSamples - 1);
|
|
float mu = max(-1.0f, min(1.0f, toMu(u)));
|
|
float cosTheta = mu;
|
|
float sinTheta2 = 1.0f - cosTheta * cosTheta;
|
|
|
|
float pathLength;
|
|
float d = Rg2 - r2 * sinTheta2;
|
|
if (d > 0.0f && -r * cosTheta - sqrt(d) > 0.0f)
|
|
{
|
|
pathLength = -r * cosTheta - sqrt(Rg2 - r2 * sinTheta2);
|
|
}
|
|
else
|
|
{
|
|
pathLength = -r * cosTheta + sqrt(Rt2 - r2 * sinTheta2);
|
|
}
|
|
|
|
unsigned int index = i * ViewAngleSamples + j;
|
|
transmittanceTable[index] = transmittance(r, mu, pathLength, *this);
|
|
|
|
// Warning messages
|
|
if (isNaN(transmittanceTable[index].x()))
|
|
{
|
|
cout << "NaN in transmittance table at (" << j << ", " << i << ")\n";
|
|
cout << transmittanceTable[index].x() << endl;
|
|
cout << "r=" << r << ", mu=" << mu << ", l=" << pathLength << endl;
|
|
exit(1);
|
|
}
|
|
|
|
if (transmittanceTable[index].x() > 1.0f)
|
|
{
|
|
cout << "Non-physical transmittance " << transmittanceTable[index].x() << endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
return transmittanceTable;
|
|
}
|
|
|
|
|
|
Vector4f*
|
|
Atmosphere::computeInscatterTable() const
|
|
{
|
|
// Rg - "ground radius"
|
|
// Rt - "transparent radius", i.e. radius of the atmosphere at some point
|
|
// where it is visually undetectable.
|
|
|
|
float Rg = planetRadius;
|
|
float Rg2 = Rg * Rg;
|
|
float Rt = shellRadius();
|
|
float Rt2 = Rt * Rt;
|
|
|
|
// Avoid numerical precision problems by choosing a first viewer
|
|
// position just *above* the planet surface.
|
|
float baseHeight = Rg * 1.0e-6f;
|
|
|
|
unsigned int sampleCount = HeightSamples * ViewAngleSamples * SunAngleSamples;
|
|
Vector4f* inscatter = new Vector4f[sampleCount];
|
|
|
|
for (unsigned int i = 0; i < HeightSamples; ++i)
|
|
{
|
|
float w = float(i) / float(HeightSamples);
|
|
float h = w * w * (Rt - Rg) + baseHeight;
|
|
float r = Rg + h;
|
|
float r2 = r * r;
|
|
|
|
cout << "layer " << i << ", height=" << h << "km\n";
|
|
|
|
Vector2f eye(0.0f, r);
|
|
|
|
for (unsigned int j = 0; j < ViewAngleSamples; ++j)
|
|
{
|
|
float v = float(j) / float(ViewAngleSamples - 1);
|
|
float mu = max(-1.0f, min(1.0f, toMu(v)));
|
|
float cosTheta = mu;
|
|
float sinTheta2 = 1.0f - cosTheta * cosTheta;
|
|
float sinTheta = sqrt(sinTheta2);
|
|
Vector2f view(sinTheta, cosTheta);
|
|
|
|
float pathLength;
|
|
float d = Rg2 - r2 * sinTheta2;
|
|
if (d > 0.0f && -r * cosTheta - sqrt(d) > 0.0f)
|
|
{
|
|
// Ray hits the planet
|
|
pathLength = -r * cosTheta - sqrt(Rg2 - r2 * sinTheta2);
|
|
}
|
|
else
|
|
{
|
|
// Ray hits the sky
|
|
pathLength = -r * cosTheta + sqrt(Rt2 - r2 * sinTheta2);
|
|
}
|
|
|
|
for (unsigned int k = 0; k < SunAngleSamples; ++k)
|
|
{
|
|
float w = float(k) / float(SunAngleSamples - 1);
|
|
float muS = toMuS(w);
|
|
float cosPhi = muS;
|
|
float sinPhi = sqrt(max(0.0f, 1.0f - cosPhi * cosPhi));
|
|
Vector2f sun(sinPhi, cosPhi);
|
|
|
|
float stepLength = pathLength / float(ScatteringIntegrationSteps);
|
|
Vector2f step = view * stepLength;
|
|
|
|
Vector3f rayleigh = Vector3f::Zero();
|
|
float mie = 0.0f;
|
|
|
|
for (unsigned int m = 0; m < ScatteringIntegrationSteps; ++m)
|
|
{
|
|
Vector2f x = eye + step * m;
|
|
float distanceToViewer = stepLength * m;
|
|
float rx2 = x.squaredNorm();
|
|
float rx = sqrt(rx2);
|
|
|
|
// Compute the transmittance along the path to the viewer
|
|
Vector3f viewPathTransmittance = transmittance(r, mu, distanceToViewer, *this);
|
|
|
|
// Compute the cosine and sine of the angle between the
|
|
// sun direction and zenith at the current sample.
|
|
float c = x.dot(sun) / rx;
|
|
float s2 = 1.0f - c * c;
|
|
|
|
// Compute the transmittance along the path to the sun
|
|
// and the total transmittance t.
|
|
Vector3f t;
|
|
if (Rg2 - rx2 * s2 < 0.0f || -rx * c - sqrt(Rg2 - rx2 * s2) < 0.0f)
|
|
{
|
|
// Compute the distance through the atmosphere
|
|
// in the direction of the sun
|
|
float sunPathLength = -rx * c + sqrt(Rt2 - rx2 * s2);
|
|
Vector3f sunPathTransmittance = transmittance(rx, c, sunPathLength, *this);
|
|
|
|
t = viewPathTransmittance.cwise() * sunPathTransmittance;
|
|
}
|
|
else
|
|
{
|
|
// Ray to sun intersects the planet; no inscattered
|
|
// light at this point.
|
|
t = Vector3f::Zero();
|
|
}
|
|
|
|
// Accumulate Rayleigh and Mie scattering
|
|
float hx = rx - Rg;
|
|
rayleigh += (exp(-hx / rayleighScaleHeight) * stepLength) * t;
|
|
mie += exp(-hx / mieScaleHeight) * stepLength * t.x();
|
|
}
|
|
|
|
unsigned int index = (i * ViewAngleSamples + j) * SunAngleSamples + k;
|
|
inscatter[index] << rayleigh.cwise() * rayleighCoeff,
|
|
mie * mieCoeff;
|
|
if (i == HeightSamples - 1 && k == 0)
|
|
{
|
|
cout << acos(muS) * 180.0/M_PI << ", "
|
|
<< acos(mu) * 180.0/M_PI << ", "
|
|
<< inscatter[index].transpose() << endl;
|
|
}
|
|
|
|
#if 0
|
|
// Emit warnings about NaNs in scatter table
|
|
if (isNaN(rayleigh.x()))
|
|
{
|
|
cout << "NaN in inscatter table at (" << k << ", " << j << ", " << i << ")\n";
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
return inscatter;
|
|
}
|
|
|
|
|
|
|
|
void usage()
|
|
{
|
|
cerr << "Usage: scattertable [options] <config file>\n";
|
|
cerr << " --output <filename> (or -o) : set filename of output image\n";
|
|
cerr << " (default is out.atm)\n";
|
|
cerr << " --scattersteps <value> (or -s)\n";
|
|
cerr << " set the number of integration steps for scattering\n";
|
|
}
|
|
|
|
|
|
/*
|
|
* Theory:
|
|
* Atmospheres are assumed to be composed of two different populations of
|
|
* particles: Rayleigh scattering and Mie scattering. The density
|
|
* of each population decreases exponentially with height above the planet
|
|
* surface to a degree determined by a scale height:
|
|
*
|
|
* density(height) = e^(-height/scaleHeight)
|
|
*
|
|
* Rayleigh scattering is wavelength dependent, with a fixed phase function.
|
|
*
|
|
* Mie scattering is wavelength independent, with a phase function determined
|
|
* by a single parameter g (the asymmetry parameter). Mie scattering aerosols
|
|
* may also be assigned wavelength dependent absorption coefficients.
|
|
*/
|
|
|
|
Vector3f computeRayleighCoeffs(const Vector3f& wavelengths)
|
|
{
|
|
return wavelengths.cwise().pow(-4.0f);
|
|
}
|
|
|
|
|
|
void SetAtmosphereParameters(Atmosphere& atm, ParameterSet& params)
|
|
{
|
|
atm.rayleighScaleHeight = params["RayleighScaleHeight"];
|
|
atm.rayleighCoeff.x() = params["RayleighRed"];
|
|
atm.rayleighCoeff.y() = params["RayleighGreen"];
|
|
atm.rayleighCoeff.z() = params["RayleighBlue"];
|
|
|
|
atm.mieScaleHeight = params["MieScaleHeight"];
|
|
atm.mieCoeff = params["Mie"];
|
|
|
|
atm.absorptionCoeff.x() = params["AbsorbRed"];
|
|
atm.absorptionCoeff.y() = params["AbsorbGreen"];
|
|
atm.absorptionCoeff.z() = params["AbsorbBlue"];
|
|
|
|
atm.planetRadius = params["Radius"];
|
|
}
|
|
|
|
|
|
void SetDefaultParameters(ParameterSet& params)
|
|
{
|
|
// Compute default rayleigh coefficients index of refraction and
|
|
// molecular densities for Earth's atmosphere.
|
|
Vector3f rayleighCoeff = RayleighScatteringCoeff(1.00027712f, 2.5470e25f);
|
|
float km = 1000.0f;
|
|
|
|
params["RayleighScaleHeight"] = 7.94;
|
|
params["RayleighRed"] = rayleighCoeff.x() * km;
|
|
params["RayleighGreen"] = rayleighCoeff.y() * km;
|
|
params["RayleighBlue"] = rayleighCoeff.z() * km;
|
|
|
|
params["MieScaleHeight"] = 1.2;
|
|
params["Mie"] = 2.1e-6f * km;
|
|
|
|
params["AbsorbRed"] = 0.0;
|
|
params["AbsorbGreen"] = 0.0;
|
|
params["AbsorbBlue"] = 0.0;
|
|
|
|
params["Radius"] = 6378.0;
|
|
}
|
|
|
|
|
|
bool LoadParameterSet(ParameterSet& params, const string& filename)
|
|
{
|
|
ifstream in(filename);
|
|
|
|
if (!in.good())
|
|
{
|
|
cerr << "Error opening config file " << filename << endl;
|
|
return false;
|
|
}
|
|
|
|
while (in.good())
|
|
{
|
|
string name;
|
|
in >> name;
|
|
|
|
double numValue = 0.0;
|
|
in >> numValue;
|
|
if (in.good())
|
|
{
|
|
params[name] = numValue;
|
|
}
|
|
}
|
|
|
|
if (in.bad())
|
|
{
|
|
cerr << "Error in scene config file " << filename << endl;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
string ConfigFileName;
|
|
string OutputFileName("out.atm");
|
|
|
|
bool parseCommandLine(int argc, char* argv[])
|
|
{
|
|
int i = 1;
|
|
int fileCount = 0;
|
|
|
|
while (i < argc)
|
|
{
|
|
if (argv[i][0] == '-')
|
|
{
|
|
if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--scattersteps"))
|
|
{
|
|
if (i == argc - 1)
|
|
return false;
|
|
|
|
if (sscanf(argv[i + 1], " %u", &ScatteringIntegrationSteps) != 1)
|
|
return false;
|
|
i++;
|
|
}
|
|
else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output"))
|
|
{
|
|
if (i == argc - 1)
|
|
return false;
|
|
|
|
OutputFileName = string(argv[i + 1]);
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
if (fileCount == 0)
|
|
{
|
|
// input filename first
|
|
ConfigFileName = string(argv[i]);
|
|
fileCount++;
|
|
}
|
|
else
|
|
{
|
|
// more than one filenames on the command line is an error
|
|
return false;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
union Uint16
|
|
{
|
|
char bytes[2];
|
|
uint16_t u;
|
|
};
|
|
|
|
union Uint32
|
|
{
|
|
char bytes[4];
|
|
uint32_t u;
|
|
};
|
|
|
|
union Float
|
|
{
|
|
char bytes[4];
|
|
float f;
|
|
};
|
|
|
|
union FloatInt
|
|
{
|
|
float f;
|
|
uint32_t u;
|
|
};
|
|
|
|
|
|
// Convert a single precision floating point value to half precision
|
|
uint16_t floatToHalf(float f)
|
|
{
|
|
FloatInt fi;
|
|
fi.f = f;
|
|
|
|
uint16_t half = 0;
|
|
|
|
auto signBit = uint16_t((fi.u & 0x80000000) >> 16);
|
|
|
|
if (f > 65504.0f)
|
|
{
|
|
// overflow
|
|
return 0x7c00;
|
|
}
|
|
if (f < -65504.0f)
|
|
{
|
|
// overflow
|
|
return 0xfc00;
|
|
}
|
|
|
|
int exponent = int((fi.u >> 23) & 0xff) - 127 + 15;
|
|
uint32_t significand = fi.u & 0x007fffff;
|
|
|
|
if (exponent < -9)
|
|
{
|
|
// Value is too small even to represent as a subnormal
|
|
return signBit;
|
|
}
|
|
if (exponent <= 0)
|
|
{
|
|
// Convert to a subnormal
|
|
return signBit + (significand >> (13 - exponent));
|
|
}
|
|
else if (exponent + 127 - 15 == 0xff)
|
|
{
|
|
// Special values: infinities and NaNs
|
|
if (significand == 0)
|
|
{
|
|
// Infinity
|
|
return signBit + 0x7c00;
|
|
}
|
|
|
|
|
|
// NaN - preserve bits, but make sure that we don't
|
|
// make the significand zero, as that would indicate
|
|
// an infinity, not a NaN
|
|
auto nanBits = uint16_t(significand >> 13);
|
|
if (nanBits == 0)
|
|
{
|
|
nanBits = 1;
|
|
}
|
|
return signBit + 0x7c00 + nanBits;
|
|
|
|
}
|
|
else if (exponent > 30)
|
|
{
|
|
// Overflow; return infinity
|
|
return signBit + 0x7c00;
|
|
}
|
|
else
|
|
{
|
|
// Normal value
|
|
return signBit + (exponent << 10) +
|
|
((significand + 0x00001000) >> 13); // round
|
|
}
|
|
}
|
|
|
|
|
|
struct DDSPixelFormat
|
|
{
|
|
DDSPixelFormat() = default;
|
|
|
|
uint32_t dwSize{0};
|
|
uint32_t dwFlags{0};
|
|
uint32_t dwFourCC{0};
|
|
uint32_t dwRGBBitCount{0};
|
|
uint32_t dwRBitMask{0};
|
|
uint32_t dwGBitMask{0};
|
|
uint32_t dwBBitMask{0};
|
|
uint32_t dwABitMask{0};
|
|
};
|
|
|
|
|
|
// Header for Microsoft DDS file format
|
|
struct DDSHeader
|
|
{
|
|
DDSHeader()
|
|
{
|
|
for (unsigned int & i : dwReserved1)
|
|
{
|
|
i = 0;
|
|
}
|
|
}
|
|
|
|
static const uint32_t CAPS_COMPLEX = 0x000008;
|
|
static const uint32_t CAPS_MIPMAP = 0x400000;
|
|
static const uint32_t CAPS_TEXTURE = 0x001000;
|
|
|
|
static const uint32_t CAPS2_VOLUME = 0x200000;
|
|
static const uint32_t CAPS2_CUBEMAP = 0x00000200;
|
|
static const uint32_t CAPS2_CUBEMAP_POSITIVEX = 0x00000400;
|
|
static const uint32_t CAPS2_CUBEMAP_NEGATIVEX = 0x00000800;
|
|
static const uint32_t CAPS2_CUBEMAP_POSITIVEY = 0x00001000;
|
|
static const uint32_t CAPS2_CUBEMAP_NEGATIVEY = 0x00002000;
|
|
static const uint32_t CAPS2_CUBEMAP_POSITIVEZ = 0x00004000;
|
|
static const uint32_t CAPS2_CUBEMAP_NEGATIVEZ = 0x00008000;
|
|
|
|
static const uint32_t DDSD_CAPS = 0x1;
|
|
static const uint32_t DDSD_HEIGHT = 0x2;
|
|
static const uint32_t DDSD_WIDTH = 0x4;
|
|
static const uint32_t DDSD_PITCH = 0x8;
|
|
static const uint32_t DDSD_PIXELFORMAT = 0x1000;
|
|
static const uint32_t DDSD_MIPMAPCOUNT = 0x20000;
|
|
static const uint32_t DDSD_LINEARSIZE = 0x80000;
|
|
static const uint32_t DDSD_DEPTH = 0x800000;
|
|
|
|
static const uint32_t D3DFMT_A16B16G16R16 = 36;
|
|
static const uint32_t D3DFMT_A16B16G16R16F = 113;
|
|
static const uint32_t D3DFMT_DXT1 = 0x31545844;
|
|
static const uint32_t D3DFMT_DXT3 = 0x33545844;
|
|
static const uint32_t D3DFMT_DXT5 = 0x35545844;
|
|
|
|
static const uint32_t FOURCC = 0x04;
|
|
|
|
uint32_t dwSize{sizeof(DDSHeader)};
|
|
uint32_t dwFlags{DDSD_PIXELFORMAT};
|
|
uint32_t dwHeight{0};
|
|
uint32_t dwWidth{0};
|
|
uint32_t dwLinearSize{0};
|
|
uint32_t dwDepth{0};
|
|
uint32_t dwMipMapCount{0};
|
|
uint32_t dwReserved1[11];
|
|
DDSPixelFormat ddpf;
|
|
uint32_t dwCaps{0};
|
|
uint32_t dwCaps2{0};
|
|
uint32_t dwCaps3{0};
|
|
uint32_t dwCaps4{0};
|
|
uint32_t dwReserved2{0};
|
|
|
|
void setTexture()
|
|
{
|
|
dwCaps |= CAPS_TEXTURE;
|
|
}
|
|
|
|
void setFourCC(uint32_t fcc)
|
|
{
|
|
dwFlags |= FOURCC;
|
|
ddpf.dwFourCC = fcc;
|
|
}
|
|
|
|
void setMipMapLevels(uint32_t levels)
|
|
{
|
|
dwCaps |= (CAPS_COMPLEX | CAPS_MIPMAP);
|
|
dwFlags |= DDSD_MIPMAPCOUNT;
|
|
dwMipMapCount = levels;
|
|
}
|
|
|
|
void setDimensions(uint32_t width, uint32_t height)
|
|
{
|
|
dwFlags |= (DDSD_WIDTH | DDSD_HEIGHT);
|
|
dwWidth = width;
|
|
dwHeight = height;
|
|
}
|
|
|
|
void setVolumeDimensions(uint32_t width, uint32_t height, uint32_t depth)
|
|
{
|
|
dwCaps |= CAPS_COMPLEX;
|
|
dwFlags |= (DDSD_WIDTH | DDSD_HEIGHT | DDSD_DEPTH);
|
|
dwWidth = width;
|
|
dwHeight = height;
|
|
dwDepth = depth;
|
|
}
|
|
};
|
|
|
|
static bool ByteSwapRequired = false;
|
|
static bool IsLittleEndian()
|
|
{
|
|
Uint32_t endiannessTest;
|
|
endiannessTest.u = 0x01020304;
|
|
return endiannessTest.bytes[0] == 0x04;
|
|
}
|
|
|
|
|
|
// Write out a 16-bit unsigned integer in little-endian order
|
|
static void WriteUint16(ostream& out, uint16_t u)
|
|
{
|
|
static_assert(sizeof(u) == 2, "");
|
|
|
|
Uint16_t ub;
|
|
ub.u = u;
|
|
if (ByteSwapRequired)
|
|
{
|
|
swap(ub.bytes[0], ub.bytes[1]);
|
|
}
|
|
|
|
out.write(ub.bytes, sizeof(ub.bytes));
|
|
}
|
|
|
|
|
|
// Write out a 32-bit unsigned integer in little-endian order
|
|
static void WriteUint32(ostream& out, uint32_t u)
|
|
{
|
|
static_assert(sizeof(u) == 4, "");
|
|
|
|
Uint32_t ub;
|
|
ub.u = u;
|
|
if (ByteSwapRequired)
|
|
{
|
|
swap(ub.bytes[0], ub.bytes[3]);
|
|
swap(ub.bytes[1], ub.bytes[2]);
|
|
}
|
|
|
|
out.write(ub.bytes, sizeof(ub.bytes));
|
|
}
|
|
|
|
|
|
// Write out a single precision floating point number
|
|
static void WriteFloat(ostream& out, float f)
|
|
{
|
|
static_assert(sizeof(f) == 4, "");
|
|
|
|
Float ub;
|
|
ub.f = f;
|
|
if (ByteSwapRequired)
|
|
{
|
|
swap(ub.bytes[0], ub.bytes[3]);
|
|
swap(ub.bytes[1], ub.bytes[2]);
|
|
}
|
|
|
|
out.write(ub.bytes, sizeof(ub.bytes));
|
|
}
|
|
|
|
|
|
// Convert a single precision floating point value to half precision
|
|
// and write it out.
|
|
static void WriteHalfFloat(ostream& out, float f)
|
|
{
|
|
WriteUint16(out, floatToHalf(f));
|
|
}
|
|
|
|
|
|
void WriteDDSHeader(ostream& out, const DDSHeader& dds)
|
|
{
|
|
WriteUint32(out, dds.dwSize);
|
|
WriteUint32(out, dds.dwFlags);
|
|
WriteUint32(out, dds.dwHeight);
|
|
WriteUint32(out, dds.dwWidth);
|
|
WriteUint32(out, dds.dwLinearSize);
|
|
WriteUint32(out, dds.dwDepth);
|
|
WriteUint32(out, dds.dwMipMapCount);
|
|
for (unsigned int i : dds.dwReserved1)
|
|
{
|
|
WriteUint32(out, i);
|
|
}
|
|
|
|
WriteUint32(out, dds.ddpf.dwSize);
|
|
WriteUint32(out, dds.ddpf.dwFlags);
|
|
WriteUint32(out, dds.ddpf.dwFourCC);
|
|
WriteUint32(out, dds.ddpf.dwRGBBitCount);
|
|
WriteUint32(out, dds.ddpf.dwRBitMask);
|
|
WriteUint32(out, dds.ddpf.dwGBitMask);
|
|
WriteUint32(out, dds.ddpf.dwBBitMask);
|
|
WriteUint32(out, dds.ddpf.dwABitMask);
|
|
|
|
WriteUint32(out, dds.dwCaps);
|
|
WriteUint32(out, dds.dwCaps2);
|
|
WriteUint32(out, dds.dwCaps3);
|
|
WriteUint32(out, dds.dwCaps4);
|
|
WriteUint32(out, dds.dwReserved2);
|
|
}
|
|
|
|
|
|
static void WriteInscatterTableDDS(ostream& out, Vector4f* inscatterTable)
|
|
{
|
|
DDSHeader dds;
|
|
dds.setTexture();
|
|
dds.setFourCC(DDSHeader::D3DFMT_A16B16G16R16F);
|
|
dds.setVolumeDimensions(SunAngleSamples, ViewAngleSamples, HeightSamples);
|
|
//dds.setMipMapLevels();
|
|
|
|
WriteDDSHeader(out, dds);
|
|
|
|
unsigned int sampleCount = SunAngleSamples * ViewAngleSamples * HeightSamples;
|
|
for (unsigned int i = 0; i < sampleCount; ++i)
|
|
{
|
|
const Vector4f& v = inscatterTable[i];
|
|
WriteHalfFloat(out, v.x());
|
|
WriteHalfFloat(out, v.y());
|
|
WriteHalfFloat(out, v.z());
|
|
WriteHalfFloat(out, v.w());
|
|
}
|
|
}
|
|
|
|
|
|
static void WriteTransmittanceTableDDS(ostream& out, Vector3f* transmittanceTable)
|
|
{
|
|
DDSHeader dds;
|
|
dds.setTexture();
|
|
dds.setFourCC(DDSHeader::D3DFMT_A16B16G16R16F);
|
|
dds.setDimensions(ViewAngleSamples, HeightSamples);
|
|
//dds.setMipMapLevels();
|
|
|
|
WriteDDSHeader(out, dds);
|
|
|
|
unsigned int sampleCount = ViewAngleSamples * HeightSamples;
|
|
for (unsigned int i = 0; i < sampleCount; ++i)
|
|
{
|
|
const Vector3f& v = transmittanceTable[i];
|
|
WriteHalfFloat(out, v.x());
|
|
WriteHalfFloat(out, v.y());
|
|
WriteHalfFloat(out, v.z());
|
|
WriteHalfFloat(out, 0.0f);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#if TEST_FLOAT_TO_HALF
|
|
int main(void)
|
|
{
|
|
while (cin.good())
|
|
{
|
|
float f = 0.0f;
|
|
cin >> f;
|
|
cout << hex << floatToHalf(f) << endl;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
bool commandLineOK = parseCommandLine(argc, argv);
|
|
if (!commandLineOK || ConfigFileName.empty())
|
|
{
|
|
usage();
|
|
exit(1);
|
|
}
|
|
|
|
ParameterSet params;
|
|
SetDefaultParameters(params);
|
|
if (!LoadParameterSet(params, ConfigFileName))
|
|
{
|
|
exit(1);
|
|
}
|
|
|
|
Atmosphere atmosphere;
|
|
SetAtmosphereParameters(atmosphere, params);
|
|
|
|
cout << "Planet radius: " << atmosphere.planetRadius << "km\n";
|
|
cout << "Rayleigh scale height: " << atmosphere.rayleighScaleHeight << "km\n";
|
|
cout << "Rayleigh coeff: " << atmosphere.rayleighCoeff.transpose() << "m^-1\n";
|
|
|
|
cout << "Mie scale height: " << atmosphere.mieScaleHeight << "km\n";
|
|
cout << "Mie coeff: " << atmosphere.mieCoeff << "m^-1\n";
|
|
cout << "Absorption coeff: " << atmosphere.absorptionCoeff.transpose() << "m^-1\n";
|
|
cout << "Using " << ScatteringIntegrationSteps << " integration steps.\n";
|
|
|
|
cout << "Generating transmittance table (" << ViewAngleSamples << "x"
|
|
<< HeightSamples << ")...\n";
|
|
Vector3f* transmittanceTable = atmosphere.computeTransmittanceTable();
|
|
|
|
cout << "Generating inscatter table (" << SunAngleSamples << "x"
|
|
<< ViewAngleSamples << "x" << HeightSamples << ")...\n";
|
|
|
|
Vector4f* inscatterTable = atmosphere.computeInscatterTable();
|
|
|
|
ByteSwapRequired = !IsLittleEndian();
|
|
|
|
#if 0
|
|
// Write tables in a single file
|
|
ofstream out(OutputFileName, ostream::binary);
|
|
|
|
// Header
|
|
out.write("atmscatr", 8);
|
|
|
|
// Version
|
|
WriteUint32(out, 1u);
|
|
|
|
// Scattering parameters
|
|
WriteFloat(out, atmosphere.rayleighScaleHeight);
|
|
WriteFloat(out, atmosphere.rayleighCoeff.x());
|
|
WriteFloat(out, atmosphere.rayleighCoeff.y());
|
|
WriteFloat(out, atmosphere.rayleighCoeff.z());
|
|
WriteFloat(out, atmosphere.mieScaleHeight);
|
|
WriteFloat(out, atmosphere.mieCoeff);
|
|
WriteFloat(out, atmosphere.mieAsymmetry);
|
|
WriteFloat(out, atmosphere.absorptionCoeff.x());
|
|
WriteFloat(out, atmosphere.absorptionCoeff.y());
|
|
WriteFloat(out, atmosphere.absorptionCoeff.z());
|
|
WriteFloat(out, atmosphere.planetRadius);
|
|
|
|
// Transmittance table dimensions
|
|
WriteUint32(out, ViewAngleSamples);
|
|
WriteUint32(out, HeightSamples);
|
|
|
|
// Inscatter table dimensions
|
|
WriteUint32(out, SunAngleSamples);
|
|
WriteUint32(out, ViewAngleSamples);
|
|
WriteUint32(out, HeightSamples);
|
|
|
|
unsigned int transmittanceTableSamples = ViewAngleSamples * HeightSamples;
|
|
unsigned int inscatterTableSamples = SunAngleSamples * ViewAngleSamples *
|
|
HeightSamples;
|
|
|
|
for (unsigned int i = 0; i < transmittanceTableSamples; ++i)
|
|
{
|
|
Vector3f v = transmittanceTable[i];
|
|
WriteFloat(out, v.x());
|
|
WriteFloat(out, v.y());
|
|
WriteFloat(out, v.z());
|
|
}
|
|
|
|
for (unsigned int i = 0; i < inscatterTableSamples; ++i)
|
|
{
|
|
Vector4f v = inscatterTable[i];
|
|
WriteFloat(out, v.x());
|
|
WriteFloat(out, v.y());
|
|
WriteFloat(out, v.z());
|
|
WriteFloat(out, v.w());
|
|
}
|
|
|
|
out.close();
|
|
#else
|
|
// Write tables as separate DDS files
|
|
ofstream transmittanceOut("transmittance.dds", ostream::binary);
|
|
WriteTransmittanceTableDDS(transmittanceOut, transmittanceTable);
|
|
transmittanceOut.close();
|
|
|
|
ofstream inscatterOut("inscatter.dds", ostream::binary);
|
|
WriteInscatterTableDDS(inscatterOut, inscatterTable);
|
|
inscatterOut.close();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|