Browse Source

Added atmospheric scattering and sun position calculator

-Added dynamic sky with sun position and accurate sky color by precomputed atmospheric scattering based on "Bruneton, E. and Neyret, F., Precomputed Atmospheric Scattering", which is split into two rendering passes, one for the sky and one for the ground/objects (for atmospheric fog). Upon engine start-up, some of the calculations are performed on the GPU and put into look-up textures to speed up the rendering.
-Added an accurate solar position calculation based on the algorithm by "Blanco-Muriel et al. 3 from the Plataforma Solar de Almerýa (PSA)". It encompasses latitude, longitude (viewer position on Earth), date and time to calculate sun position of an accuracy to within 0.5 arc minutes.
-Modified the gaussian blur to make multiple blur passes.
Paulcodedev 7 years ago
parent
commit
6cc12bb050
54 changed files with 7477 additions and 409 deletions
  1. 19 15
      Praxis3D/Data/Maps/default_lite.pmap
  2. 966 0
      Praxis3D/Data/Shaders/atmosphericScatteringPass_ground.frag
  3. 18 0
      Praxis3D/Data/Shaders/atmosphericScatteringPass_ground.vert
  4. 718 0
      Praxis3D/Data/Shaders/atmosphericScatteringPass_ground_simple.frag
  5. 646 0
      Praxis3D/Data/Shaders/atmosphericScatteringPass_sky.frag
  6. 18 0
      Praxis3D/Data/Shaders/atmosphericScatteringPass_sky.vert
  7. 20 3
      Praxis3D/Data/Shaders/finalPass.frag
  8. 1 1
      Praxis3D/Data/Shaders/finalPass.vert
  9. 17 4
      Praxis3D/Data/Shaders/gaussianBlurHorizontal.frag
  10. 5 3
      Praxis3D/Data/Shaders/gaussianBlurVertical.frag
  11. 1 0
      Praxis3D/Data/Shaders/geometryPass.vert
  12. 15 8
      Praxis3D/Data/Shaders/hdrMappingPass.frag
  13. 3 3
      Praxis3D/Data/Shaders/hdrMappingPass.vert
  14. 434 0
      Praxis3D/Data/Shaders/lightPass - Copy.frag
  15. 22 0
      Praxis3D/Data/Shaders/lightPass - Copy.vert
  16. 122 223
      Praxis3D/Data/Shaders/lightPass.frag
  17. 0 1
      Praxis3D/Data/Shaders/postProcessPass.frag
  18. 4 2
      Praxis3D/Data/config.ini
  19. 9 0
      Praxis3D/Praxis3D.vcxproj
  20. 27 0
      Praxis3D/Praxis3D.vcxproj.filters
  21. 174 0
      Praxis3D/Source/AtmScatteringConstants.h
  22. 1404 0
      Praxis3D/Source/AtmScatteringModel.cpp
  23. 345 0
      Praxis3D/Source/AtmScatteringModel.h
  24. 192 0
      Praxis3D/Source/AtmScatteringPass.cpp
  25. 250 0
      Praxis3D/Source/AtmScatteringPass.h
  26. 92 0
      Praxis3D/Source/AtmScatteringShaderDefinitions.h
  27. 810 0
      Praxis3D/Source/AtmScatteringShaderFunctions.h
  28. 162 0
      Praxis3D/Source/AtmScatteringShaderPass.h
  29. 23 0
      Praxis3D/Source/BlurPass.h
  30. 19 0
      Praxis3D/Source/CameraScript.h
  31. 27 7
      Praxis3D/Source/CommonDefinitions.h
  32. 12 0
      Praxis3D/Source/Config.cpp
  33. 40 2
      Praxis3D/Source/Config.h
  34. 3 0
      Praxis3D/Source/GeometryPass.h
  35. 165 0
      Praxis3D/Source/GraphicsDataSets.h
  36. 5 2
      Praxis3D/Source/LightingGraphicsObjects.h
  37. 8 7
      Praxis3D/Source/LightingPass.h
  38. 11 0
      Praxis3D/Source/Math.cpp
  39. 19 5
      Praxis3D/Source/Math.h
  40. 4 0
      Praxis3D/Source/RenderPassBase.h
  41. 1 1
      Praxis3D/Source/RendererBackend.cpp
  42. 15 13
      Praxis3D/Source/RendererBackend.h
  43. 55 2
      Praxis3D/Source/RendererFrontend.cpp
  44. 28 19
      Praxis3D/Source/RendererFrontend.h
  45. 6 0
      Praxis3D/Source/RendererScene.cpp
  46. 38 3
      Praxis3D/Source/ScriptingScene.cpp
  47. 2 0
      Praxis3D/Source/ScriptingScene.h
  48. 45 33
      Praxis3D/Source/ShaderLoader.cpp
  49. 11 0
      Praxis3D/Source/ShaderUniformUpdater.cpp
  50. 91 5
      Praxis3D/Source/ShaderUniforms.h
  51. 16 14
      Praxis3D/Source/SkyPass.h
  52. 250 32
      Praxis3D/Source/SolarTimeScript.h
  53. 83 0
      Praxis3D/Source/SunScript.h
  54. 6 1
      Praxis3D/Source/UniformData.h

+ 19 - 15
Praxis3D/Data/Maps/default_lite.pmap

@@ -21,7 +21,7 @@
 					"Name": "DirectionalLight 1",
 					"Color": "1.0f, 1.0f, 1.0f",
 					"Direction": "0.006461f, -0.707092f, -0.707092f",
-					"Intensity": "0.1f"
+					"Intensity": "10.0f"
 				},
 				{
 					"Type": "EnvironmentMapStatic",
@@ -763,7 +763,7 @@
 					"Rotation": "0.0f, 0.0f, 0.0f",
 					"OffsetPosition": "0.0f, 0.0f, 0.0f",
 					"OffsetRotation": "0.0f, 0.0f, 0.0f",
-					"Scale": "25000.0f, 25000.0f, 25000.0f",
+					"Scale": "2500.0f, 2500.0f, 2500.0f",
 					"AlphaThreshold": "0.0f",
 					"HeightScale": "0.0f",
 					"Lighting": "1",
@@ -849,7 +849,7 @@
 					"Name": "PointLight 1",
 					"Attenuation": "0.0f, 0.0f, 1.0f",
 					"Color": "1.0f, 1.0f, 1.0f",
-					"Intensity": "100.0f",
+					"Intensity": "10.0f",
 					"OffsetPosition": "0.0f, 0.0f, 0.0f",
 					"Position": "12.971f, 5.0f, -68.7851f"
 				},
@@ -858,7 +858,7 @@
 					"Name": "PointLight 2",
 					"Attenuation": "0.0f, 0.0f, 1.0f",
 					"Color": "1.0f, 1.0f, 1.0f",
-					"Intensity": "100.0f",
+					"Intensity": "10.0f",
 					"OffsetPosition": "0.0f, 0.0f, 0.0f",
 					"Position": "9.12898f, 7.98369f, 9.59357f"
 				},
@@ -867,7 +867,7 @@
 					"Name": "PointLight 3",
 					"Attenuation": "0.0f, 0.0f, 1.0f",
 					"Color": "1.0f, 1.0f, 1.0f",
-					"Intensity": "100.0f",
+					"Intensity": "10.0f",
 					"OffsetPosition": "0.0f, 0.0f, 0.0f",
 					"Position": "-6.94746f, 8.59811f, 7.54629f"
 				},
@@ -876,7 +876,7 @@
 					"Name": "PointLight 45",
 					"Attenuation": "0.0f, 0.0f, 1.0f",
 					"Color": "1.0f, 1.0f, 1.0f",
-					"Intensity": "100.0f",
+					"Intensity": "10.0f",
 					"OffsetPosition": "0.0f, 0.0f, 0.0f",
 					"Position": "-100.0f, 10.0f, 20.0f"
 				},
@@ -896,7 +896,7 @@
 					"CutoffAngle": "60.0f",
 					"Color": "1.0f, 1.0f, 1.0f",
 					"Direction": "-1.0f, 0.0f, 0.0f",
-					"Intensity": "500.0f",
+					"Intensity": "300.0f",
 					"OffsetPosition": "0.0f, 0.0f, 0.0f",
 					"OffsetRotation": "0.0f, 0.0f, 0.0f",
 					"Position": "60.0f, 3.0f, 60.0f"
@@ -932,7 +932,7 @@
 					"CutoffAngle": "60.0f",
 					"Color": "1.0f, 1.0f, 1.0f",
 					"Direction": "0.707107f, 0.0f, 0.707107f",
-					"Intensity": "500.0f",
+					"Intensity": "300.0f",
 					"OffsetPosition": "0.0f, 0.0f, 0.0f",
 					"OffsetRotation": "0.0f, 0.0f, 0.0f",
 					"Position": "-20.0f, 3.0f, -20.0f"
@@ -952,9 +952,9 @@
 					"Position": "-90.6949f, 7.47164f, 11.0953f",
 					"Angle": "26.727f, -0.14598f",
 					"Speed": "2.0f",
-					"SprintSpeed": "50.0f",
+					"SprintSpeed": "500.0f",
 					"LowerLimit": "0.0f",
-					"UpperLimit": "500.0f",
+					"UpperLimit": "500000.0f",
 					"Keybindings": 
 					{
 						"ForwardKey": "26",
@@ -1018,12 +1018,16 @@
 				{
 					"Type": "SolarTimeScript",
 					"Name": "Solar Time Script 1",
-					"Hours": "12",
+					"Hours": "18",
+					"Minutes": "0",
 					"Seconds": "0.0f",
-					"Latitude": "54.0f",
-					"Longitude": "54.0f",
-					"DayOfYear": "1",
-					"TimeMultiplier": "10.0f",
+					"Year": "2018",
+					"Month": "5",
+					"Day": "14",
+					"TimeZone": "3",
+					"Latitude": "54.7f",
+					"Longitude": "25.3f",
+					"TimeMultiplier": "300.0f",
 					"OffsetPosition": "25.0f"
 				}
 			]

+ 966 - 0
Praxis3D/Data/Shaders/atmosphericScatteringPass_ground.frag

@@ -0,0 +1,966 @@
+#version 430 core
+
+/*const float kLengthUnitInMeters = 1000.0;
+const float PI = 3.14159265;
+const vec3 kSphereCenter = vec3(0.0, 0.0, 1000.0) / kLengthUnitInMeters;
+const float kSphereRadius = 1000.0 / kLengthUnitInMeters;
+const vec3 kSphereAlbedo = vec3(0.8);
+const vec3 kGroundAlbedo = vec3(0.0, 0.0, 0.04);
+
+#ifdef USE_LUMINANCE
+#define GetSolarRadiance GetSolarLuminance
+#define GetSkyRadiance GetSkyLuminance
+#define GetSkyRadianceToPoint GetSkyLuminanceToPoint
+#define GetSunAndSkyIrradiance GetSunAndSkyIlluminance
+#endif*/
+
+#define TEMPLATE(x)
+#define TEMPLATE_ARGUMENT(x)
+#define assert(x)
+
+in vec3 viewRay;
+
+//layout(location = 0) out vec4 emissiveBuffer;
+layout(location = 0) out vec4 colorBuffer;
+
+uniform float exposure;
+
+uniform ivec2 screenSize;
+uniform vec3 cameraPosVec;
+
+uniform sampler2D atmIrradianceTexture;
+uniform sampler3D atmScatteringTexture;
+uniform sampler3D atmSingleMieTexture;
+uniform sampler2D atmTransmitTexture;
+uniform sampler2D inputColorMap;
+uniform sampler2D positionMap;
+uniform sampler2D normalMap;
+
+const float kLengthUnitInMeters = 1000.0;
+//const vec3 kSphereCenter = vec3(0.0, 0.0, 1000.0) / kLengthUnitInMeters;
+const vec3 kSphereCenter = vec3(0.0, 1000.0, 0.0) / kLengthUnitInMeters;
+const float kSphereRadius = 1000.0 / kLengthUnitInMeters;
+const vec3 kSphereAlbedo = vec3(0.8);
+const vec3 kGroundAlbedo = vec3(0.0, 0.0, 0.04);
+
+const float m = 1.0;
+const float nm = 1.0;
+const float rad = 1.0;
+const float sr = 1.0;
+const float watt = 1.0;
+const float lm = 1.0;
+const float PI = 3.14159265358979323846;
+const float km = 1000.0 * m;
+const float m2 = m * m;
+const float m3 = m * m * m;
+const float pi = PI * rad;
+const float deg = pi / 180.0;
+const float watt_per_square_meter = watt / m2;
+const float watt_per_square_meter_per_sr = watt / (m2 * sr);
+const float watt_per_square_meter_per_nm = watt / (m2 * nm);
+const float watt_per_square_meter_per_sr_per_nm = watt / (m2 * sr * nm);
+const float watt_per_cubic_meter_per_sr_per_nm = watt / (m3 * sr * nm);
+const float cd = lm / sr;
+const float kcd = 1000.0 * cd;
+const float cd_per_square_meter = cd / m2;
+const float kcd_per_square_meter = kcd / m2;
+const vec3 SKY_SPECTRAL_RADIANCE_TO_LUMINANCE = vec3(114974.916437,71305.954816,65310.548555);
+const vec3 SUN_SPECTRAL_RADIANCE_TO_LUMINANCE = vec3(98242.786222,69954.398112,66475.012354);
+const int TRANSMITTANCE_TEXTURE_WIDTH = 256;
+const int TRANSMITTANCE_TEXTURE_HEIGHT = 64;
+const int SCATTERING_TEXTURE_R_SIZE = 32;
+const int SCATTERING_TEXTURE_MU_SIZE = 128;
+const int SCATTERING_TEXTURE_MU_S_SIZE = 32;
+const int SCATTERING_TEXTURE_NU_SIZE = 8;
+const int IRRADIANCE_TEXTURE_WIDTH = 64;
+const int IRRADIANCE_TEXTURE_HEIGHT = 16;
+
+struct DirectionalLight
+{
+    vec3 m_color;
+    vec3 m_direction;
+    float m_intensity;
+};
+// An atmosphere layer of width 'width', and whose density is defined as
+//   'exp_term' * exp('exp_scale' * h) + 'linear_term' * h + 'constant_term',
+// clamped to [0,1], and where h is the altitude.
+struct DensityProfileLayer 
+{
+	float width;
+	float exp_term;
+	float exp_scale;
+	float linear_term;
+	float constant_term;
+};
+
+// An atmosphere density profile made of several layers on top of each other
+// (from bottom to top). The width of the last layer is ignored, i.e. it always
+// extend to the top atmosphere boundary. The profile values vary between 0
+// (null density) to 1 (maximum density).
+struct DensityProfile 
+{
+	DensityProfileLayer layers[2];
+};
+
+struct AtmosphereParameters 
+{
+	// The solar irradiance at the top of the atmosphere.
+	vec3 solar_irradiance;
+	// The sun's angular radius. Warning: the implementation uses approximations
+	// that are valid only if this angle is smaller than 0.1 radians.
+	float sun_angular_radius;
+	// The density profile of air molecules, i.e. a function from altitude to
+	// dimensionless values between 0 (null density) and 1 (maximum density).
+	DensityProfile rayleigh_density;
+	// The density profile of aerosols, i.e. a function from altitude to
+	// dimensionless values between 0 (null density) and 1 (maximum density).
+	DensityProfile mie_density;
+	// The density profile of air molecules that absorb light (e.g. ozone), i.e.
+	// a function from altitude to dimensionless values between 0 (null density)
+	// and 1 (maximum density).
+	DensityProfile absorption_density;
+	// The scattering coefficient of air molecules at the altitude where their
+	// density is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The scattering coefficient at altitude h is equal to
+	// 'rayleigh_scattering' times 'rayleigh_density' at this altitude.
+	vec3 rayleigh_scattering;
+	// The distance between the planet center and the bottom of the atmosphere.
+	float bottom_radius;
+	// The scattering coefficient of aerosols at the altitude where their density
+	// is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The scattering coefficient at altitude h is equal to
+	// 'mie_scattering' times 'mie_density' at this altitude.
+	vec3 mie_scattering;
+	// The distance between the planet center and the top of the atmosphere.
+	float top_radius;
+	// The extinction coefficient of aerosols at the altitude where their density
+	// is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The extinction coefficient at altitude h is equal to
+	// 'mie_extinction' times 'mie_density' at this altitude.
+	vec3 mie_extinction;
+	// The asymetry parameter for the Cornette-Shanks phase function for the
+	// aerosols.
+	float mie_phase_function_g;
+	// The extinction coefficient of molecules that absorb light (e.g. ozone) at
+	// the altitude where their density is maximum, as a function of wavelength.
+	// The extinction coefficient at altitude h is equal to
+	// 'absorption_extinction' times 'absorption_density' at this altitude.
+	vec3 absorption_extinction;
+	// The cosine of the maximum Sun zenith angle for which atmospheric scattering
+	// must be precomputed (for maximum precision, use the smallest Sun zenith
+	// angle yielding negligible sky light radiance values. For instance, for the
+	// Earth case, 102 degrees is a good choice - yielding mu_s_min = -0.2).
+	float mu_s_min;
+	// The average albedo of the ground.
+	vec3 ground_albedo;
+};
+
+struct AtmScatteringParameters
+{
+	vec3 m_whitePoint;
+	vec3 m_earthCenter;
+	vec2 m_sunSize;
+	AtmosphereParameters m_atmosphereParam;
+};
+
+uniform DirectionalLight directionalLight;
+
+layout (std140) uniform AtmScatParametersBuffer
+{
+	AtmScatteringParameters atmScatteringParam;
+};
+
+/*struct AtmosphereParameters 
+{
+	// The sun's angular radius. Warning: the implementation uses approximations
+	// that are valid only if this angle is smaller than 0.1 radians.
+	float sun_angular_radius;
+	// The distance between the planet center and the bottom of the atmosphere.
+	float bottom_radius;
+	// The solar irradiance at the top of the atmosphere.
+	vec3 solar_irradiance;
+	// The distance between the planet center and the top of the atmosphere.
+	float top_radius;
+	// The density profile of air molecules, i.e. a function from altitude to
+	// dimensionless values between 0 (null density) and 1 (maximum density).
+	DensityProfile rayleigh_density;
+	// The scattering coefficient of air molecules at the altitude where their
+	// density is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The scattering coefficient at altitude h is equal to
+	// 'rayleigh_scattering' times 'rayleigh_density' at this altitude.
+	vec3 rayleigh_scattering;
+	// The density profile of aerosols, i.e. a function from altitude to
+	// dimensionless values between 0 (null density) and 1 (maximum density).
+	DensityProfile mie_density;
+	// The scattering coefficient of aerosols at the altitude where their density
+	// is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The scattering coefficient at altitude h is equal to
+	// 'mie_scattering' times 'mie_density' at this altitude.
+	vec3 mie_scattering;
+	// The extinction coefficient of aerosols at the altitude where their density
+	// is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The extinction coefficient at altitude h is equal to
+	// 'mie_extinction' times 'mie_density' at this altitude.
+	vec3 mie_extinction;
+	// The asymetry parameter for the Cornette-Shanks phase function for the
+	// aerosols.
+	float mie_phase_function_g;
+	// The density profile of air molecules that absorb light (e.g. ozone), i.e.
+	// a function from altitude to dimensionless values between 0 (null density)
+	// and 1 (maximum density).
+	DensityProfile absorption_density;
+	// The extinction coefficient of molecules that absorb light (e.g. ozone) at
+	// the altitude where their density is maximum, as a function of wavelength.
+	// The extinction coefficient at altitude h is equal to
+	// 'absorption_extinction' times 'absorption_density' at this altitude.
+	vec3 absorption_extinction;
+	// The average albedo of the ground.
+	vec3 ground_albedo;
+	// The cosine of the maximum Sun zenith angle for which atmospheric scattering
+	// must be precomputed (for maximum precision, use the smallest Sun zenith
+	// angle yielding negligible sky light radiance values. For instance, for the
+	// Earth case, 102 degrees is a good choice - yielding mu_s_min = -0.2).
+	float mu_s_min;
+};*/
+/*
+const AtmosphereParameters atmosphereParam = AtmosphereParameters(
+	vec3(1.474000,1.850400,1.911980),
+	0.004675,
+	6360.000000,
+	6420.000000,
+	DensityProfile(DensityProfileLayer[2](DensityProfileLayer(0.000000,0.000000,0.000000,0.000000,0.000000),DensityProfileLayer(0.000000,1.000000,-0.125000,0.000000,0.000000))),
+	vec3(0.005802,0.013558,0.033100),
+	DensityProfile(DensityProfileLayer[2](DensityProfileLayer(0.000000,0.000000,0.000000,0.000000,0.000000),DensityProfileLayer(0.000000,1.000000,-0.833333,0.000000,0.000000))),
+	vec3(0.003996,0.003996,0.003996),
+	vec3(0.004440,0.004440,0.004440),
+	0.800000,
+	DensityProfile(DensityProfileLayer[2](DensityProfileLayer(25.000000,0.000000,0.000000,0.066667,-0.666667),DensityProfileLayer(0.000000,0.000000,0.000000,-0.066667,2.666667))),
+	vec3(0.000650,0.001881,0.000085),
+	vec3(0.100000,0.100000,0.100000),
+	-0.207912
+	);*/
+	
+vec2 calcTexCoord(void)
+{
+	return gl_FragCoord.xy / screenSize;
+}
+float ClampCosine(float p_mu) 
+{
+	return clamp(p_mu, float(-1.0), float(1.0));
+}
+
+float ClampDistance(float p_d) 
+{
+	return max(p_d, 0.0 * m);
+}
+
+float ClampRadius(const AtmosphereParameters p_atmospherePar, float p_r) 
+{
+	return clamp(p_r, p_atmospherePar.bottom_radius, p_atmospherePar.top_radius);
+}
+
+float SafeSqrt(float p_a) 
+{
+	return sqrt(max(p_a, 0.0 * m2));
+}
+
+float GetTextureCoordFromUnitRange(float p_x, int p_textureSize) 
+{
+	return 0.5 / float(p_textureSize) + p_x * (1.0 - 1.0 / float(p_textureSize));
+}
+
+float RayleighPhaseFunction(float p_nu) 
+{
+	float k = 3.0 / (16.0 * PI * sr);
+	return k * (1.0 + p_nu * p_nu);
+}
+
+float MiePhaseFunction(float p_g, float p_nu) 
+{
+	float k = 3.0 / (8.0 * PI * sr) * (1.0 - p_g * p_g) / (2.0 + p_g * p_g);
+	return k * (1.0 + p_nu * p_nu) / pow(1.0 + p_g * p_g - 2.0 * p_g * p_nu, 1.5);
+}
+
+float DistanceToTopAtmosphereBoundary(const AtmosphereParameters p_atmospherePar, float p_r, float p_mu) 
+{
+	assert(p_r <= p_atmospherePar.top_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	float discriminant = p_r * p_r * (p_mu * p_mu - 1.0) + p_atmospherePar.top_radius * p_atmospherePar.top_radius;
+	return ClampDistance(-p_r * p_mu + SafeSqrt(discriminant));
+}
+
+vec2 GetTransmittanceTextureUvFromRMu(const AtmosphereParameters p_atmospherePar, float p_r, float p_mu) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	// Distance to top atmosphere boundary for a horizontal ray at ground level.
+	float H = sqrt(p_atmospherePar.top_radius * p_atmospherePar.top_radius - p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius);
+	// Distance to the horizon.
+	float rho = SafeSqrt(p_r * p_r - p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius);
+	// Distance to the top atmosphere boundary for the ray (r,mu), and its minimum
+	// and maximum values over all mu - obtained for (r,1) and (r,mu_horizon).
+	float d = DistanceToTopAtmosphereBoundary(p_atmospherePar, p_r, p_mu);
+	float d_min = p_atmospherePar.top_radius - p_r;
+	float d_max = rho + H;
+	float x_mu = (d - d_min) / (d_max - d_min);
+	float x_r = rho / H;
+	return vec2(GetTextureCoordFromUnitRange(x_mu, TRANSMITTANCE_TEXTURE_WIDTH), GetTextureCoordFromUnitRange(x_r, TRANSMITTANCE_TEXTURE_HEIGHT));
+}
+
+vec2 GetIrradianceTextureUvFromRMuS(const AtmosphereParameters p_atmospherePar, float p_r, float p_muS) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	assert(p_muS >= -1.0 && p_muS <= 1.0);
+	float x_r = (p_r - p_atmospherePar.bottom_radius) / (p_atmospherePar.top_radius - p_atmospherePar.bottom_radius);
+	float x_mu_s = p_muS * 0.5 + 0.5;
+	return vec2(GetTextureCoordFromUnitRange(x_mu_s, IRRADIANCE_TEXTURE_WIDTH), GetTextureCoordFromUnitRange(x_r, IRRADIANCE_TEXTURE_HEIGHT));
+}
+
+vec3 GetIrradiance(const AtmosphereParameters p_atmospherePar, sampler2D p_atmIrradianceTex, float p_r, float p_muS) 
+{
+	vec2 uv = GetIrradianceTextureUvFromRMuS(p_atmospherePar, p_r, p_muS);
+	return vec3(texture(p_atmIrradianceTex, uv).xyz);
+}
+
+vec3 GetTransmittanceToTopAtmosphereBoundary(const AtmosphereParameters p_atmospherePar, sampler2D p_atmTransmittanceTex, float p_r, float p_muS) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	vec2 uv = GetTransmittanceTextureUvFromRMu(p_atmospherePar, p_r, p_muS);
+	return vec3(texture(p_atmTransmittanceTex, uv));
+}
+
+vec3 GetTransmittanceToSun(const AtmosphereParameters p_atmospherePar, sampler2D p_atmTransmittanceTex, float p_r, float p_muS) 
+{
+	float sin_theta_h = p_atmospherePar.bottom_radius / p_r;
+	float cos_theta_h = -sqrt(max(1.0 - sin_theta_h * sin_theta_h, 0.0));
+	return GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, p_r, p_muS) *
+		smoothstep(-sin_theta_h * p_atmospherePar.sun_angular_radius / rad, sin_theta_h * p_atmospherePar.sun_angular_radius / rad, p_muS - cos_theta_h);
+}
+
+vec3 GetSunAndSkyIrradiance(const AtmosphereParameters p_atmospherePar, in vec3 p_point, in vec3 p_normal, in vec3 p_sunDirection, out vec3 p_skyIrradiance) 
+{
+	float r = length(p_point);
+	float mu_s = dot(p_point, p_sunDirection) / r;
+
+	// Indirect irradiance (approximated if the surface is not horizontal).
+	p_skyIrradiance = GetIrradiance(p_atmospherePar, atmIrradianceTexture, r, mu_s) * (1.0 + dot(p_normal, p_point) / r) * 0.5;
+
+	// Direct irradiance.
+	return p_atmospherePar.solar_irradiance * GetTransmittanceToSun(p_atmospherePar, atmTransmitTexture, r, mu_s) * max(dot(p_normal, p_sunDirection), 0.0);
+}
+
+void GetSphereShadowInOut(in vec3 p_cameraPos, in vec3 p_viewDirection, in vec3 p_sunDirection, out float d_in, out float d_out) 
+{
+	vec3 pos = p_cameraPos - kSphereCenter;
+	float pos_dot_sun = dot(pos, p_sunDirection);
+	float view_dot_sun = dot(p_viewDirection, p_sunDirection);
+	float k = atmScatteringParam.m_sunSize.x;
+	float l = 1.0 + k * k;
+	float a = 1.0 - l * view_dot_sun * view_dot_sun;
+	float b = dot(pos, p_viewDirection) - l * pos_dot_sun * view_dot_sun - k * kSphereRadius * view_dot_sun;
+	float c = dot(pos, pos) - l * pos_dot_sun * pos_dot_sun - 2.0 * k * kSphereRadius * pos_dot_sun - kSphereRadius * kSphereRadius;
+	float discriminant = b * b - a * c;
+	if(discriminant > 0.0) 
+	{
+		d_in = max(0.0, (-b - sqrt(discriminant)) / a);
+		d_out = (-b + sqrt(discriminant)) / a;
+		// The values of d for which delta is equal to 0 and kSphereRadius / k.
+		float d_base = -pos_dot_sun / view_dot_sun;
+		float d_apex = -(pos_dot_sun + kSphereRadius / k) / view_dot_sun;
+		if(view_dot_sun > 0.0) 
+		{
+			d_in = max(d_in, d_apex);
+			d_out = a > 0.0 ? min(d_out, d_base) : d_base;
+		} 
+		else 
+		{
+			d_in = a > 0.0 ? max(d_in, d_base) : d_base;
+			d_out = min(d_out, d_apex);
+		}
+	} 
+	else 
+	{
+		d_in = 0.0;
+		d_out = 0.0;
+	}
+}
+
+bool RayIntersectsGround(const AtmosphereParameters p_atmospherePar, float p_r, float p_mu) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	return p_mu < 0.0 && p_r * p_r * (p_mu * p_mu - 1.0) + p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius >= 0.0 * m2;
+}
+
+vec3 GetTransmittance(const AtmosphereParameters p_atmospherePar, sampler2D p_atmTransmittanceTex,
+    float p_r, float p_mu, float p_d, bool p_ray_r_mu_intersects_ground) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	assert(p_d >= 0.0 * m);
+
+	float r_d = ClampRadius(p_atmospherePar, sqrt(p_d * p_d + 2.0 * p_r * p_mu * p_d + p_r * p_r));
+	float mu_d = ClampCosine((p_r * p_mu + p_d) / r_d);
+
+	if(p_ray_r_mu_intersects_ground)
+	{
+		return min(GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, r_d, -mu_d) /
+			GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, p_r, -p_mu), 
+			vec3(1.0));
+	} 
+	else
+	{
+		return min(GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, p_r, p_mu) /
+			GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, r_d, mu_d),
+			vec3(1.0));
+	}
+}
+
+vec4 GetScatteringTextureUvwzFromRMuMuSNu(const AtmosphereParameters p_atmospherePar, float p_r, float p_mu, float p_muS, float p_nu, bool p_ray_r_mu_intersects_ground) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	assert(p_muS >= -1.0 && p_muS <= 1.0);
+	assert(p_nu >= -1.0 && p_nu <= 1.0);
+
+	// Distance to top atmosphere boundary for a horizontal ray at ground level.
+	float H = sqrt(p_atmospherePar.top_radius * p_atmospherePar.top_radius - p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius);
+	// Distance to the horizon.
+	float rho = SafeSqrt(p_r * p_r - p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius);
+	float u_r = GetTextureCoordFromUnitRange(rho / H, SCATTERING_TEXTURE_R_SIZE);
+
+	// Discriminant of the quadratic equation for the intersections of the ray
+	// (r,mu) with the ground (see RayIntersectsGround).
+	float r_mu = p_r * p_mu;
+	float discriminant = r_mu * r_mu - p_r * p_r + p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius;
+	float u_mu;
+	p_ray_r_mu_intersects_ground = true;
+	if(p_ray_r_mu_intersects_ground)
+	{
+		// Distance to the ground for the ray (r,mu), and its minimum and maximum
+		// values over all mu - obtained for (r,-1) and (r,mu_horizon).
+		float d = -r_mu - SafeSqrt(discriminant);
+		float d_min = p_r - p_atmospherePar.bottom_radius;
+		float d_max = rho;
+		u_mu = 0.5 - 0.5 * GetTextureCoordFromUnitRange(d_max == d_min ? 0.0 : (d - d_min) / (d_max - d_min), SCATTERING_TEXTURE_MU_SIZE / 2);
+	} 
+	else 
+	{
+		// Distance to the top atmosphere boundary for the ray (r,mu), and its
+		// minimum and maximum values over all mu - obtained for (r,1) and
+		// (r,mu_horizon).
+		float d = -r_mu + SafeSqrt(discriminant + H * H);
+		float d_min = p_atmospherePar.top_radius - p_r;
+		float d_max = rho + H;
+		u_mu = 0.5 + 0.5 * GetTextureCoordFromUnitRange((d - d_min) / (d_max - d_min), SCATTERING_TEXTURE_MU_SIZE / 2);
+	}
+
+	float d = DistanceToTopAtmosphereBoundary(p_atmospherePar, p_atmospherePar.bottom_radius, p_muS);
+	float d_min = p_atmospherePar.top_radius - p_atmospherePar.bottom_radius;
+	float d_max = H;
+	float a = (d - d_min) / (d_max - d_min);
+	float A = -2.0 * p_atmospherePar.mu_s_min * p_atmospherePar.bottom_radius / (d_max - d_min);
+	float u_mu_s = GetTextureCoordFromUnitRange(max(1.0 - a / A, 0.0) / (1.0 + a), SCATTERING_TEXTURE_MU_S_SIZE);
+
+	float u_nu = (p_nu + 1.0) / 2.0;
+	return vec4(u_nu, u_mu_s, u_mu, u_r);
+}
+
+vec3 GetCombinedScattering(
+    const AtmosphereParameters p_atmospherePar,
+    sampler3D p_atmScatteringTex,
+    sampler3D p_atmSingleMieScatteringTex,
+    float p_r, 
+	float p_mu, 
+	float p_muS, 
+	float p_nu,
+    bool p_ray_r_mu_intersects_ground,
+    out vec3 p_single_mie_scattering) 
+	{
+	vec4 uvwz = GetScatteringTextureUvwzFromRMuMuSNu(p_atmospherePar, p_r, p_mu, p_muS, p_nu, p_ray_r_mu_intersects_ground);
+	float tex_coord_x = uvwz.x * float(SCATTERING_TEXTURE_NU_SIZE - 1);
+	float tex_x = floor(tex_coord_x);
+	float lerp = tex_coord_x - tex_x;
+	vec3 uvw0 = vec3((tex_x + uvwz.y) / float(SCATTERING_TEXTURE_NU_SIZE), uvwz.z, uvwz.w);
+	vec3 uvw1 = vec3((tex_x + 1.0 + uvwz.y) / float(SCATTERING_TEXTURE_NU_SIZE), uvwz.z, uvwz.w);
+	
+#ifdef COMBINED_SCATTERING_TEXTURES
+	vec4 combined_scattering =texture(p_atmScatteringTex, uvw0) * (1.0 - lerp) + texture(p_atmScatteringTex, uvw1) * lerp;
+	vec3 scattering = vec3(combined_scattering);
+	p_single_mie_scattering = GetExtrapolatedSingleMieScattering(p_atmospherePar, combined_scattering);
+#else
+	vec3 scattering = vec3(texture(p_atmScatteringTex, uvw0) * (1.0 - lerp) + texture(p_atmScatteringTex, uvw1) * lerp);
+	p_single_mie_scattering = vec3(texture(p_atmSingleMieScatteringTex, uvw0) * (1.0 - lerp) + texture(p_atmSingleMieScatteringTex, uvw1) * lerp);
+#endif
+
+	return scattering;
+}
+
+vec3 GetSkyRadianceToPoint(
+    const AtmosphereParameters p_atmospherePar,
+    sampler2D p_atmTransmittanceTex,
+    sampler3D p_atmScatteringTex,
+    sampler3D p_atmSingleMieScatteringTex,
+    vec3 p_cameraPos, 
+	in vec3 p_point, 
+	in float p_shadowLength,
+    in vec3 p_sunDirection, 
+	out vec3 p_transmittance) 
+	{
+	// Compute the distance to the top atmosphere boundary along the view ray,
+	// assuming the viewer is in space (or NaN if the view ray does not intersect
+	// the atmosphere).
+	vec3 view_ray = normalize(vec3(0.0, -1.0, 0.0));// normalize(p_point - p_cameraPos);
+	float r = length(p_cameraPos);
+	float rmu = dot(p_cameraPos, view_ray);
+	float distance_to_top_atmosphere_boundary = -rmu -sqrt(rmu * rmu - r * r + p_atmospherePar.top_radius * p_atmospherePar.top_radius);
+	// If the viewer is in space and the view ray intersects the atmosphere, move
+	// the viewer to the top atmosphere boundary (along the view ray):
+	if(distance_to_top_atmosphere_boundary > 0.0 * m) 
+	{
+		p_cameraPos = p_cameraPos + view_ray * distance_to_top_atmosphere_boundary;
+		r = p_atmospherePar.top_radius;
+		rmu += distance_to_top_atmosphere_boundary;
+	}
+
+	float viewDot = abs(dot(viewRay, vec3(0.0, 1.0, 0.0)));
+	
+	//if(viewDot < 0.01)
+	//	return vec3(0.0);
+	
+	// Compute the r, mu, mu_s and nu parameters for the first texture lookup.
+	float mu = rmu / r;
+	float mu_s = dot(p_cameraPos, p_sunDirection) / r;
+	float nu = dot(view_ray, p_sunDirection);
+	float d = length(p_point - p_cameraPos);
+	bool ray_r_mu_intersects_ground = RayIntersectsGround(p_atmospherePar, r, mu);
+	
+	float treshold = 0.004;
+	const float Rg = 6360.0;
+	
+	float muHoriz = -sqrt(1.0 - (Rg / r) * (Rg / r));
+	if(abs(mu - muHoriz) < treshold)
+	{
+		//if((mu - muHoriz) > 0.0)
+		//	mu += treshold * 5.0;
+		//else
+		//	mu -= treshold * 5.0;
+		//return vec3(abs(mu - muHoriz));
+	}
+	
+	
+	if(mu < treshold && mu > -treshold)
+	{
+		//if(mu > 0)
+		//	mu = treshold;
+		//else
+		//	mu = -treshold;
+	}
+	//return vec3(mu);
+	
+	p_transmittance = GetTransmittance(p_atmospherePar, p_atmTransmittanceTex, r, mu, d, ray_r_mu_intersects_ground);
+
+	vec3 single_mie_scattering;
+	vec3 scattering = GetCombinedScattering(
+		p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+		r, mu, mu_s, nu, ray_r_mu_intersects_ground,
+		single_mie_scattering);
+	
+	const float Rt = 6420.0;
+	const float RL = 6421.0;
+	
+	vec3 x = p_cameraPos;
+	float t = p_atmospherePar.top_radius;
+	vec3 x0 = x + t * view_ray;
+	float r0 = length(x0);
+	float rMu0 = dot(x0, view_ray);
+	float mu0 = rMu0 / r0;
+	float muS0 = dot(x0, p_sunDirection) / r0;
+			
+	const float EPS = 0.004;
+	//float muHoriz = -sqrt(1.0 - (Rg / r) * (Rg / r));
+	if (abs(mu - muHoriz) < EPS) 
+	{
+		float a = ((mu - muHoriz) + EPS) / (2.0 * EPS);
+
+		mu = muHoriz - EPS;
+		r0 = sqrt(r * r + t * t + 2.0 * r * t * mu);
+		mu0 = (r * mu + t) / r0;
+		//vec4 inScatter0 = texture4D(inscatterSampler, r, mu, muS, nu);
+		//vec4 inScatter1 = texture4D(inscatterSampler, r0, mu0, muS0, nu);
+		vec3 inScatter0 = GetCombinedScattering(
+			p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+			r, mu, mu_s, nu, ray_r_mu_intersects_ground,
+			single_mie_scattering);
+		vec3 inScatter1 = GetCombinedScattering(
+			p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+			r0, mu0, muS0, nu, ray_r_mu_intersects_ground,
+			single_mie_scattering);
+		vec3 inScatterA = max(inScatter0 - p_transmittance.rgb * inScatter1, 0.0);
+
+		mu = muHoriz + EPS;
+		r0 = sqrt(r * r + t * t + 2.0 * r * t * mu);
+		mu0 = (r * mu + t) / r0;
+		//inScatter0 = texture4D(inscatterSampler, r, mu, muS, nu);
+		//inScatter1 = texture4D(inscatterSampler, r0, mu0, muS0, nu);
+		inScatter0 = GetCombinedScattering(
+			p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+			r, mu, mu_s, nu, ray_r_mu_intersects_ground,
+			single_mie_scattering);
+		inScatter1 = GetCombinedScattering(
+			p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+			r0, mu0, muS0, nu, ray_r_mu_intersects_ground,
+			single_mie_scattering);
+		vec3 inScatterB = max(inScatter0 - p_transmittance.rgb * inScatter1, 0.0);
+
+		scattering = mix(inScatterA, inScatterB, a);
+		//return vec3(0.3, 0.0, 0.0);
+	}
+	
+	// Compute the r, mu, mu_s and nu parameters for the second texture lookup.
+	// If shadow_length is not 0 (case of light shafts), we want to ignore the
+	// scattering along the last shadow_length meters of the view ray, which we
+	// do by subtracting shadow_length from d (this way scattering_p is equal to
+	// the S|x_s=x_0-lv term in Eq. (17) of our paper).
+	d = max(d - p_shadowLength, 0.0 * m);
+	float r_p = ClampRadius(p_atmospherePar, sqrt(d * d + 2.0 * r * mu * d + r * r));
+	float mu_p = (r * mu + d) / r_p;
+	float mu_s_p = (r * mu_s + d * nu) / r_p;
+	
+	vec3 single_mie_scattering_p;
+	vec3 scattering_p = GetCombinedScattering(
+		p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+		r_p, mu_p, mu_s_p, nu, ray_r_mu_intersects_ground,
+		single_mie_scattering_p);
+
+	// Combine the lookup results to get the scattering between camera and point.
+	vec3 shadow_transmittance = p_transmittance;
+	if(p_shadowLength > 0.0 * m) 
+	{
+		// This is the T(x,x_s) term in Eq. (17) of our paper, for light shafts.
+		shadow_transmittance = GetTransmittance(p_atmospherePar, p_atmTransmittanceTex, r, mu, d, ray_r_mu_intersects_ground);
+	}
+	scattering = scattering - shadow_transmittance * scattering_p;
+	single_mie_scattering = single_mie_scattering - shadow_transmittance * single_mie_scattering_p;
+#ifdef COMBINED_SCATTERING_TEXTURES
+	single_mie_scattering = GetExtrapolatedSingleMieScattering(p_atmospherePar, vec4(scattering, single_mie_scattering.r));
+#endif
+
+	// Hack to avoid rendering artifacts when the sun is below the horizon.
+	single_mie_scattering = single_mie_scattering * smoothstep(float(0.0), float(0.01), mu_s);
+
+	
+	//return vec3(scattering_p);
+	return scattering * RayleighPhaseFunction(nu) + 
+	single_mie_scattering * MiePhaseFunction(p_atmospherePar.mie_phase_function_g, nu) * smoothstep(0.00, 0.02, mu_s);
+}
+
+vec3 GetSkyRadiance(
+    const AtmosphereParameters p_atmospherePar,
+    sampler2D p_atmTransmittanceTex,
+    sampler3D p_atmScatteringTex,
+    sampler3D p_atmSingleMieScatteringTex,
+	vec3 p_fragPos,
+    vec3 p_camera, 
+	in vec3 p_viewRay, 
+	float p_shadowLength,
+    in vec3 p_sunDirection, 
+	out vec3 p_transmittance)
+{
+	// Compute the distance to the top atmosphere boundary along the view ray,
+	// assuming the viewer is in space (or NaN if the view ray does not intersect
+	// the atmosphere).
+	float r = length(p_camera - p_fragPos);//length(p_camera);
+	float rmu = dot(p_camera, p_viewRay);
+	float distance_to_top_atmosphere_boundary = -rmu - sqrt(rmu * rmu - r * r + p_atmospherePar.top_radius * p_atmospherePar.top_radius);
+	// If the viewer is in space and the view ray intersects the atmosphere, move
+	// the viewer to the top atmosphere boundary (along the view ray):
+	if(distance_to_top_atmosphere_boundary > 0.0 * m)
+	{
+	//	p_camera = p_camera + p_viewRay * distance_to_top_atmosphere_boundary;
+		p_camera = p_fragPos;
+		r = p_atmospherePar.top_radius;
+		rmu += distance_to_top_atmosphere_boundary;
+		return vec3(0.0);
+	} 
+	/*else if(r > p_atmospherePar.top_radius) 
+	{
+		// If the view ray does not intersect the atmosphere, simply return 0.
+		p_transmittance = vec3(1.0);
+		return vec3(0.0 * watt_per_square_meter_per_sr_per_nm);
+	}*/
+	// Compute the r, mu, mu_s and nu parameters needed for the texture lookups.
+	float mu = rmu / r;
+	float mu_s = dot(p_camera, p_sunDirection) / r;
+	float nu = dot(p_viewRay, p_sunDirection);
+	bool ray_r_mu_intersects_ground = RayIntersectsGround(p_atmospherePar, r, mu);
+	//ray_r_mu_intersects_ground = true;
+	
+	p_transmittance = ray_r_mu_intersects_ground ? vec3(0.0) : GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, r, mu);
+	vec3 single_mie_scattering;
+	vec3 scattering;
+	//if(p_shadowLength == 0.0 * m)
+	//{
+		scattering = GetCombinedScattering(
+			p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+			r, mu, mu_s, nu, ray_r_mu_intersects_ground,
+			single_mie_scattering);
+	//}
+	/*else
+	{
+		// Case of light shafts (shadow_length is the total length noted l in our
+		// paper): we omit the scattering between the camera and the point at
+		// distance l, by implementing Eq. (18) of the paper (shadow_transmittance
+		// is the T(x,x_s) term, scattering is the S|x_s=x+lv term).
+		float d = p_shadowLength;
+		float r_p = ClampRadius(p_atmospherePar, sqrt(d * d + 2.0 * r * mu * d + r * r));
+		float mu_p = (r * mu + d) / r_p;
+		float mu_s_p = (r * mu_s + d * nu) / r_p;
+
+		scattering = GetCombinedScattering(
+			p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+			r_p, mu_p, mu_s_p, nu, ray_r_mu_intersects_ground,
+			single_mie_scattering);
+		vec3 shadow_transmittance =
+			GetTransmittance(p_atmospherePar, p_atmTransmittanceTex,
+			r, mu, p_shadowLength, ray_r_mu_intersects_ground);
+		scattering = scattering * shadow_transmittance;
+		single_mie_scattering = single_mie_scattering * shadow_transmittance;
+	}*/
+	
+	return scattering * RayleighPhaseFunction(nu) + single_mie_scattering * MiePhaseFunction(p_atmospherePar.mie_phase_function_g, nu);
+}
+
+float GetSunVisibility(vec3 p_point, vec3 p_sunDirection)
+{
+	vec3 p = p_point - kSphereCenter;
+	float p_dot_v = dot(p, p_sunDirection);
+	float p_dot_p = dot(p, p);
+	float ray_sphere_center_squared_distance = p_dot_p - p_dot_v * p_dot_v;
+	float distance_to_intersection = -p_dot_v - sqrt(kSphereRadius * kSphereRadius - ray_sphere_center_squared_distance);
+	
+	if(distance_to_intersection > 0.0)
+	{
+		// Compute the distance between the view ray and the sphere, and the
+		// corresponding (tangent of the) subtended angle. Finally, use this to
+		// compute an approximate sun visibility.
+		float ray_sphere_distance = kSphereRadius - sqrt(ray_sphere_center_squared_distance);
+		float ray_sphere_angular_distance = -ray_sphere_distance / p_dot_v;
+		return smoothstep(1.0, 0.0, ray_sphere_angular_distance / atmScatteringParam.m_sunSize.x);
+	}
+	
+	return 1.0;
+}
+
+float GetSkyVisibility(vec3 p_point)
+{
+	vec3 p = p_point - kSphereCenter;
+	float p_dot_p = dot(p, p);
+	return 1.0 + p.z / sqrt(p_dot_p) * kSphereRadius * kSphereRadius / p_dot_p;
+} 
+vec3 GetSolarRadiance(const AtmosphereParameters p_atmospherePar)
+{
+	return p_atmospherePar.solar_irradiance / (PI * p_atmospherePar.sun_angular_radius * p_atmospherePar.sun_angular_radius);
+}
+
+void main() 
+{
+	// Calculate screen-space texture coordinates, for buffer access
+	vec2 texCoord = calcTexCoord();
+	// Get the current fragment color
+	vec3 fragmentColor = texture(inputColorMap, texCoord).xyz;
+	// Get pixel's position in world space
+	vec3 worldPos = texture(positionMap, texCoord).xyz;
+	// Get normal (in world space) and normalize it to minimize floating point approximation errors
+	vec3 normal = normalize(texture(normalMap, texCoord).xyz);
+	
+	float distanceToFrag = length(cameraPosVec - worldPos);
+	vec3 cameraPosition = cameraPosVec / kLengthUnitInMeters;
+	vec3 sun_direction = directionalLight.m_direction;
+	worldPos = worldPos / kLengthUnitInMeters;
+	
+	// Normalized view direction vector.
+	vec3 view_direction = normalize(viewRay);
+	
+	// Tangent of the angle subtended by this fragment.
+	float fragment_angular_size = length(dFdx(viewRay) + dFdy(viewRay)) / length(viewRay);
+
+	vec3 sphere_radiance;
+	float shadow_in;
+	float shadow_out;
+	GetSphereShadowInOut(cameraPosition, view_direction, sun_direction, shadow_in, shadow_out);
+
+	// Hack to fade out light shafts when the Sun is very close to the horizon.
+	//float lightshaft_fadein_hack = smoothstep(0.02, 0.04, dot(normalize(cameraPosition - earth_center), sun_direction));
+	/*{
+		// Compute the distance between the view ray line and the sphere center,
+		// and the distance between the camera and the intersection of the view
+		// ray with the sphere (or NaN if there is no intersection).
+		vec3 p = cameraPosition - kSphereCenter;
+		float p_dot_v = dot(p, view_direction);
+		float p_dot_p = dot(p, p);
+		float ray_sphere_center_squared_distance = p_dot_p - p_dot_v * p_dot_v;
+		float distance_to_intersection = -p_dot_v - sqrt(kSphereRadius * kSphereRadius - ray_sphere_center_squared_distance);
+*/
+	// Compute the radiance reflected by the sphere, if the ray intersects it.
+	/*float sphere_alpha = 0.0;
+	vec3 sphere_radiance = vec3(0.0);
+	if(distance_to_intersection > 0.0) */
+	/*
+		// Compute the distance between the view ray and the sphere, and the
+		// corresponding (tangent of the) subtended angle. Finally, use this to
+		// compute the approximate analytic antialiasing factor sphere_alpha.
+		//float ray_sphere_distance = kSphereRadius - sqrt(ray_sphere_center_squared_distance);
+		//float ray_sphere_angular_distance = -ray_sphere_distance / p_dot_v;
+		//sphere_alpha = min(ray_sphere_angular_distance / fragment_angular_size, 1.0);
+		
+		vec3 point = cameraPosition + worldPos;// cameraPosition + view_direction * distance_to_intersection;
+		//vec3 normal = normalize(point - kSphereCenter);
+
+		// Compute the radiance reflected by the sphere.
+		vec3 sky_irradiance;
+		vec3 sun_irradiance = GetSunAndSkyIrradiance(atmScatteringParam.m_atmosphereParam, 
+			point - atmScatteringParam.m_earthCenter, 
+			normal, 
+			sun_direction, 
+			sky_irradiance);
+			
+		sphere_radiance = kSphereAlbedo * (1.0 / PI) * (sun_irradiance + sky_irradiance);
+		
+		float shadow_length = 0.0;//= max(0.0, min(shadow_out, distance_to_intersection) - shadow_in);// * lightshaft_fadein_hack;
+		vec3 transmittance;
+		vec3 in_scatter = GetSkyRadianceToPoint(atmScatteringParam.m_atmosphereParam, 
+			atmTransmitTexture, 
+			atmScatteringTexture, 
+			atmSingleMieTexture, 
+			cameraPosition - atmScatteringParam.m_earthCenter, 
+			point - atmScatteringParam.m_earthCenter, 
+			shadow_length, 
+			sun_direction, 
+			transmittance); 
+			
+		sphere_radiance = sphere_radiance * transmittance + in_scatter;
+		//sphere_radiance = in_scatter;
+	}
+	vec3 color = sphere_radiance;*/
+	vec3 color = vec3(0.0);
+	
+	// Compute the distance between the view ray line and the Earth center,
+	// and the distance between the camera and the intersection of the view
+	// ray with the ground (or NaN if there is no intersection).
+	/*vec3 p = cameraPosition - atmScatteringParam.m_earthCenter;
+	float p_dot_v = dot(p, view_direction);
+	float p_dot_p = dot(p, p);
+	float ray_earth_center_squared_distance = p_dot_p - p_dot_v * p_dot_v;
+	float distance_to_intersection = -p_dot_v - sqrt(atmScatteringParam.m_earthCenter.z * atmScatteringParam.m_earthCenter.z - ray_earth_center_squared_distance);
+
+	// Compute the radiance reflected by the ground, if the ray intersects it.
+	float ground_alpha = 0.0;
+	vec3 ground_radiance = vec3(0.0);
+	if(distance_to_intersection > 0.0)
+	{
+		vec3 point = cameraPosition + view_direction * distance_to_intersection;
+		vec3 normal = normalize(point - atmScatteringParam.m_earthCenter);
+
+		// Compute the radiance reflected by the ground.
+		vec3 sky_irradiance;
+		vec3 sun_irradiance = GetSunAndSkyIrradiance(atmScatteringParam.m_atmosphereParam, point - atmScatteringParam.m_earthCenter, normal, sun_direction, sky_irradiance);
+		ground_radiance = kGroundAlbedo * (1.0 / PI) * (sun_irradiance * GetSunVisibility(point, sun_direction) + sky_irradiance * GetSkyVisibility(point));
+
+		float shadow_length = 0.0;//max(0.0, min(shadow_out, distance_to_intersection) - shadow_in) * lightshaft_fadein_hack;
+		vec3 transmittance;
+		vec3 in_scatter = GetSkyRadianceToPoint(atmScatteringParam.m_atmosphereParam, atmTransmitTexture, atmScatteringTexture, atmSingleMieTexture, cameraPosition - atmScatteringParam.m_earthCenter, point - atmScatteringParam.m_earthCenter, shadow_length, sun_direction, transmittance);
+		ground_radiance = ground_radiance * transmittance + in_scatter;
+		ground_alpha = 1.0;
+	}*/
+	
+	// Compute the distance between the view ray line and the sphere center,
+	// and the distance between the camera and the intersection of the view
+	// ray with the sphere (or NaN if there is no intersection).
+	vec3 p = cameraPosition - kSphereCenter;
+	float p_dot_v = dot(p, view_direction);
+	float p_dot_p = dot(p, p);
+	float ray_sphere_center_squared_distance = p_dot_p - p_dot_v * p_dot_v;
+	float distance_to_intersection = -p_dot_v - sqrt(kSphereRadius * kSphereRadius - ray_sphere_center_squared_distance);
+	float lightshaft_fadein_hack = 0.0;
+	
+	// Compute the radiance reflected by the sphere, if the ray intersects it.
+	float sphere_alpha = 0.0;
+	//vec3 sphere_radiance = vec3(0.0);
+	//if(distance_to_intersection > 0.0) 
+	{
+		// Compute the distance between the view ray and the sphere, and the
+		// corresponding (tangent of the) subtended angle. Finally, use this to
+		// compute the approximate analytic antialiasing factor sphere_alpha.
+		float ray_sphere_distance = kSphereRadius - sqrt(ray_sphere_center_squared_distance);
+		float ray_sphere_angular_distance = -ray_sphere_distance / p_dot_v;
+		sphere_alpha = min(ray_sphere_angular_distance / fragment_angular_size, 1.0);
+		
+		vec3 point = worldPos;// cameraPosition + view_direction * distance_to_intersection;
+		//vec3 normal = normalize(point - kSphereCenter);
+
+		// Compute the radiance reflected by the sphere.
+		vec3 sky_irradiance;
+		vec3 sun_irradiance = GetSunAndSkyIrradiance(atmScatteringParam.m_atmosphereParam, 
+		point - atmScatteringParam.m_earthCenter, 
+		normal, 
+		sun_direction, 
+		sky_irradiance);
+		
+		sphere_radiance = vec3(0.0);//fragmentColor;// kSphereAlbedo * (1.0 / PI) * (sun_irradiance + sky_irradiance);
+		
+		float shadow_length = 0.0;//max(0.0, min(shadow_out, distance_to_intersection) - shadow_in) * lightshaft_fadein_hack;
+		vec3 transmittance;
+		vec3 in_scatter = GetSkyRadianceToPoint(
+			atmScatteringParam.m_atmosphereParam, 
+			atmTransmitTexture, 
+			atmScatteringTexture, 
+			atmSingleMieTexture, 
+			cameraPosition - atmScatteringParam.m_earthCenter, 
+			point - atmScatteringParam.m_earthCenter, 
+			shadow_length, 
+			sun_direction, 
+			transmittance); 
+		
+		sphere_radiance = sphere_radiance * transmittance + in_scatter;
+		
+		float fogFactor = clamp((distanceToFrag / 100.0), 0.0, 1.0);
+		
+		sphere_radiance = max(in_scatter, vec3(0.0));
+		sphere_radiance = mix(vec3(0.0), sphere_radiance, fogFactor);
+		//sphere_radiance = vec3(fogFactor);
+	}
+	
+	vec3 worldPos2 = worldPos;
+	worldPos2.y = max(worldPos2.y, cameraPosition.y);
+	
+	// Compute the radiance of the sky.
+	float shadow_length = 0.0;//max(0.0, shadow_out - shadow_in) * lightshaft_fadein_hack;
+	vec3 transmittance;
+	vec3 radiance = GetSkyRadiance(atmScatteringParam.m_atmosphereParam, 
+		atmTransmitTexture, 
+		atmScatteringTexture, 
+		atmSingleMieTexture, 
+		worldPos, 
+		cameraPosition - atmScatteringParam.m_earthCenter, 
+		view_direction, 
+		shadow_length, 
+		sun_direction, 
+		transmittance);
+
+	//color = ground_radiance;
+	radiance = sphere_radiance;
+	
+	// If the view ray intersects the Sun, add the Sun radiance.
+	//if(dot(view_direction, sun_direction) > atmScatteringParam.m_sunSize.y)
+	//{
+	//	radiance = radiance + transmittance * GetSolarRadiance(atmScatteringParam.m_atmosphereParam);
+	//}
+	//radiance = mix(radiance, ground_radiance, ground_alpha);
+	//radiance = mix(radiance, sphere_radiance, sphere_alpha);
+	//color = vec4(pow(vec3(1.0) - exp(-radiance / white_point * exposure), vec3(1.0 / 2.2)), 1.0);
+	//color = vec4(vec3(1.0) - exp(-radiance / atmScatteringParam.m_whitePoint * directionalLight.m_intensity), 1.0);
+	//colorBuffer = vec4(radiance / atmScatteringParam.m_whitePoint * directionalLight.m_intensity * 1.0, 1.0);
+	color = radiance / atmScatteringParam.m_whitePoint * directionalLight.m_intensity * 10.0;
+	colorBuffer = vec4(fragmentColor + color, 1.0);
+}

+ 18 - 0
Praxis3D/Data/Shaders/atmosphericScatteringPass_ground.vert

@@ -0,0 +1,18 @@
+#version 430 core
+
+uniform mat4 transposeViewMat;
+uniform mat4 atmScatProjMat;
+
+out vec3 viewRay;
+
+void main(void) 
+{	
+	// Determine texture coordinates
+	vec2 texCoord = vec2((gl_VertexID == 2) ?  2.0 :  0.0, (gl_VertexID == 1) ?  2.0 :  0.0);
+	
+	// Calculate the position, so that the triangle fills the whole screen
+	vec4 vertexPos = vec4(texCoord * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 1.0, 1.0);
+		
+	viewRay = (((transposeViewMat)) * vec4((atmScatProjMat * vertexPos).xyz, 0.0)).xyz;
+	gl_Position = vertexPos;
+}

+ 718 - 0
Praxis3D/Data/Shaders/atmosphericScatteringPass_ground_simple.frag

@@ -0,0 +1,718 @@
+#version 430 core
+
+/*const float kLengthUnitInMeters = 1000.0;
+const float PI = 3.14159265;
+const vec3 kSphereCenter = vec3(0.0, 0.0, 1000.0) / kLengthUnitInMeters;
+const float kSphereRadius = 1000.0 / kLengthUnitInMeters;
+const vec3 kSphereAlbedo = vec3(0.8);
+const vec3 kGroundAlbedo = vec3(0.0, 0.0, 0.04);
+
+#ifdef USE_LUMINANCE
+#define GetSolarRadiance GetSolarLuminance
+#define GetSkyRadiance GetSkyLuminance
+#define GetSkyRadianceToPoint GetSkyLuminanceToPoint
+#define GetSunAndSkyIrradiance GetSunAndSkyIlluminance
+#endif*/
+
+#define TEMPLATE(x)
+#define TEMPLATE_ARGUMENT(x)
+#define assert(x)
+
+in vec3 viewRay;
+
+//layout(location = 0) out vec4 emissiveBuffer;
+layout(location = 0) out vec4 colorBuffer;
+
+uniform float exposure;
+
+uniform ivec2 screenSize;
+uniform vec3 cameraPosVec;
+
+uniform sampler2D atmIrradianceTexture;
+uniform sampler3D atmScatteringTexture;
+uniform sampler3D atmSingleMieTexture;
+uniform sampler2D atmTransmitTexture;
+uniform sampler2D inputColorMap;
+uniform sampler2D positionMap;
+uniform sampler2D normalMap;
+
+const float kLengthUnitInMeters = 1000.0;
+//const vec3 kSphereCenter = vec3(0.0, 0.0, 1000.0) / kLengthUnitInMeters;
+const vec3 kSphereCenter = vec3(0.0, 1000.0, 0.0) / kLengthUnitInMeters;
+const float kSphereRadius = 1000.0 / kLengthUnitInMeters;
+const vec3 kSphereAlbedo = vec3(0.8);
+const vec3 kGroundAlbedo = vec3(0.0, 0.0, 0.04);
+
+const float m = 1.0;
+const float nm = 1.0;
+const float rad = 1.0;
+const float sr = 1.0;
+const float watt = 1.0;
+const float lm = 1.0;
+const float PI = 3.14159265358979323846;
+const float km = 1000.0 * m;
+const float m2 = m * m;
+const float m3 = m * m * m;
+const float pi = PI * rad;
+const float deg = pi / 180.0;
+const float watt_per_square_meter = watt / m2;
+const float watt_per_square_meter_per_sr = watt / (m2 * sr);
+const float watt_per_square_meter_per_nm = watt / (m2 * nm);
+const float watt_per_square_meter_per_sr_per_nm = watt / (m2 * sr * nm);
+const float watt_per_cubic_meter_per_sr_per_nm = watt / (m3 * sr * nm);
+const float cd = lm / sr;
+const float kcd = 1000.0 * cd;
+const float cd_per_square_meter = cd / m2;
+const float kcd_per_square_meter = kcd / m2;
+const vec3 SKY_SPECTRAL_RADIANCE_TO_LUMINANCE = vec3(114974.916437,71305.954816,65310.548555);
+const vec3 SUN_SPECTRAL_RADIANCE_TO_LUMINANCE = vec3(98242.786222,69954.398112,66475.012354);
+const int TRANSMITTANCE_TEXTURE_WIDTH = 256;
+const int TRANSMITTANCE_TEXTURE_HEIGHT = 64;
+const int SCATTERING_TEXTURE_R_SIZE = 32;
+const int SCATTERING_TEXTURE_MU_SIZE = 128;
+const int SCATTERING_TEXTURE_MU_S_SIZE = 32;
+const int SCATTERING_TEXTURE_NU_SIZE = 8;
+const int IRRADIANCE_TEXTURE_WIDTH = 64;
+const int IRRADIANCE_TEXTURE_HEIGHT = 16;
+
+struct DirectionalLight
+{
+    vec3 m_color;
+    vec3 m_direction;
+    float m_intensity;
+};
+// An atmosphere layer of width 'width', and whose density is defined as
+//   'exp_term' * exp('exp_scale' * h) + 'linear_term' * h + 'constant_term',
+// clamped to [0,1], and where h is the altitude.
+struct DensityProfileLayer 
+{
+	float width;
+	float exp_term;
+	float exp_scale;
+	float linear_term;
+	float constant_term;
+};
+
+// An atmosphere density profile made of several layers on top of each other
+// (from bottom to top). The width of the last layer is ignored, i.e. it always
+// extend to the top atmosphere boundary. The profile values vary between 0
+// (null density) to 1 (maximum density).
+struct DensityProfile 
+{
+	DensityProfileLayer layers[2];
+};
+
+struct AtmosphereParameters 
+{
+	// The solar irradiance at the top of the atmosphere.
+	vec3 solar_irradiance;
+	// The sun's angular radius. Warning: the implementation uses approximations
+	// that are valid only if this angle is smaller than 0.1 radians.
+	float sun_angular_radius;
+	// The density profile of air molecules, i.e. a function from altitude to
+	// dimensionless values between 0 (null density) and 1 (maximum density).
+	DensityProfile rayleigh_density;
+	// The density profile of aerosols, i.e. a function from altitude to
+	// dimensionless values between 0 (null density) and 1 (maximum density).
+	DensityProfile mie_density;
+	// The density profile of air molecules that absorb light (e.g. ozone), i.e.
+	// a function from altitude to dimensionless values between 0 (null density)
+	// and 1 (maximum density).
+	DensityProfile absorption_density;
+	// The scattering coefficient of air molecules at the altitude where their
+	// density is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The scattering coefficient at altitude h is equal to
+	// 'rayleigh_scattering' times 'rayleigh_density' at this altitude.
+	vec3 rayleigh_scattering;
+	// The distance between the planet center and the bottom of the atmosphere.
+	float bottom_radius;
+	// The scattering coefficient of aerosols at the altitude where their density
+	// is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The scattering coefficient at altitude h is equal to
+	// 'mie_scattering' times 'mie_density' at this altitude.
+	vec3 mie_scattering;
+	// The distance between the planet center and the top of the atmosphere.
+	float top_radius;
+	// The extinction coefficient of aerosols at the altitude where their density
+	// is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The extinction coefficient at altitude h is equal to
+	// 'mie_extinction' times 'mie_density' at this altitude.
+	vec3 mie_extinction;
+	// The asymetry parameter for the Cornette-Shanks phase function for the
+	// aerosols.
+	float mie_phase_function_g;
+	// The extinction coefficient of molecules that absorb light (e.g. ozone) at
+	// the altitude where their density is maximum, as a function of wavelength.
+	// The extinction coefficient at altitude h is equal to
+	// 'absorption_extinction' times 'absorption_density' at this altitude.
+	vec3 absorption_extinction;
+	// The cosine of the maximum Sun zenith angle for which atmospheric scattering
+	// must be precomputed (for maximum precision, use the smallest Sun zenith
+	// angle yielding negligible sky light radiance values. For instance, for the
+	// Earth case, 102 degrees is a good choice - yielding mu_s_min = -0.2).
+	float mu_s_min;
+	// The average albedo of the ground.
+	vec3 ground_albedo;
+};
+
+struct AtmScatteringParameters
+{
+	vec3 m_whitePoint;
+	vec3 m_earthCenter;
+	vec2 m_sunSize;
+	AtmosphereParameters m_atmosphereParam;
+};
+
+uniform DirectionalLight directionalLight;
+
+layout (std140) uniform AtmScatParametersBuffer
+{
+	AtmScatteringParameters atmScatteringParam;
+};
+
+/*struct AtmosphereParameters 
+{
+	// The sun's angular radius. Warning: the implementation uses approximations
+	// that are valid only if this angle is smaller than 0.1 radians.
+	float sun_angular_radius;
+	// The distance between the planet center and the bottom of the atmosphere.
+	float bottom_radius;
+	// The solar irradiance at the top of the atmosphere.
+	vec3 solar_irradiance;
+	// The distance between the planet center and the top of the atmosphere.
+	float top_radius;
+	// The density profile of air molecules, i.e. a function from altitude to
+	// dimensionless values between 0 (null density) and 1 (maximum density).
+	DensityProfile rayleigh_density;
+	// The scattering coefficient of air molecules at the altitude where their
+	// density is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The scattering coefficient at altitude h is equal to
+	// 'rayleigh_scattering' times 'rayleigh_density' at this altitude.
+	vec3 rayleigh_scattering;
+	// The density profile of aerosols, i.e. a function from altitude to
+	// dimensionless values between 0 (null density) and 1 (maximum density).
+	DensityProfile mie_density;
+	// The scattering coefficient of aerosols at the altitude where their density
+	// is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The scattering coefficient at altitude h is equal to
+	// 'mie_scattering' times 'mie_density' at this altitude.
+	vec3 mie_scattering;
+	// The extinction coefficient of aerosols at the altitude where their density
+	// is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The extinction coefficient at altitude h is equal to
+	// 'mie_extinction' times 'mie_density' at this altitude.
+	vec3 mie_extinction;
+	// The asymetry parameter for the Cornette-Shanks phase function for the
+	// aerosols.
+	float mie_phase_function_g;
+	// The density profile of air molecules that absorb light (e.g. ozone), i.e.
+	// a function from altitude to dimensionless values between 0 (null density)
+	// and 1 (maximum density).
+	DensityProfile absorption_density;
+	// The extinction coefficient of molecules that absorb light (e.g. ozone) at
+	// the altitude where their density is maximum, as a function of wavelength.
+	// The extinction coefficient at altitude h is equal to
+	// 'absorption_extinction' times 'absorption_density' at this altitude.
+	vec3 absorption_extinction;
+	// The average albedo of the ground.
+	vec3 ground_albedo;
+	// The cosine of the maximum Sun zenith angle for which atmospheric scattering
+	// must be precomputed (for maximum precision, use the smallest Sun zenith
+	// angle yielding negligible sky light radiance values. For instance, for the
+	// Earth case, 102 degrees is a good choice - yielding mu_s_min = -0.2).
+	float mu_s_min;
+};*/
+/*
+const AtmosphereParameters atmosphereParam = AtmosphereParameters(
+	vec3(1.474000,1.850400,1.911980),
+	0.004675,
+	6360.000000,
+	6420.000000,
+	DensityProfile(DensityProfileLayer[2](DensityProfileLayer(0.000000,0.000000,0.000000,0.000000,0.000000),DensityProfileLayer(0.000000,1.000000,-0.125000,0.000000,0.000000))),
+	vec3(0.005802,0.013558,0.033100),
+	DensityProfile(DensityProfileLayer[2](DensityProfileLayer(0.000000,0.000000,0.000000,0.000000,0.000000),DensityProfileLayer(0.000000,1.000000,-0.833333,0.000000,0.000000))),
+	vec3(0.003996,0.003996,0.003996),
+	vec3(0.004440,0.004440,0.004440),
+	0.800000,
+	DensityProfile(DensityProfileLayer[2](DensityProfileLayer(25.000000,0.000000,0.000000,0.066667,-0.666667),DensityProfileLayer(0.000000,0.000000,0.000000,-0.066667,2.666667))),
+	vec3(0.000650,0.001881,0.000085),
+	vec3(0.100000,0.100000,0.100000),
+	-0.207912
+	);*/
+	
+vec2 calcTexCoord(void)
+{
+	return gl_FragCoord.xy / screenSize;
+}
+float ClampCosine(float p_mu) 
+{
+	return clamp(p_mu, float(-1.0), float(1.0));
+}
+
+float ClampDistance(float p_d) 
+{
+	return max(p_d, 0.0 * m);
+}
+
+float ClampRadius(const AtmosphereParameters p_atmospherePar, float p_r) 
+{
+	return clamp(p_r, p_atmospherePar.bottom_radius, p_atmospherePar.top_radius);
+}
+
+float SafeSqrt(float p_a) 
+{
+	return sqrt(max(p_a, 0.0 * m2));
+}
+
+float GetTextureCoordFromUnitRange(float p_x, int p_textureSize) 
+{
+	return 0.5 / float(p_textureSize) + p_x * (1.0 - 1.0 / float(p_textureSize));
+}
+
+float RayleighPhaseFunction(float p_nu) 
+{
+	float k = 3.0 / (16.0 * PI * sr);
+	return k * (1.0 + p_nu * p_nu);
+}
+
+float MiePhaseFunction(float p_g, float p_nu) 
+{
+	float k = 3.0 / (8.0 * PI * sr) * (1.0 - p_g * p_g) / (2.0 + p_g * p_g);
+	return k * (1.0 + p_nu * p_nu) / pow(1.0 + p_g * p_g - 2.0 * p_g * p_nu, 1.5);
+}
+
+float DistanceToTopAtmosphereBoundary(const AtmosphereParameters p_atmospherePar, float p_r, float p_mu) 
+{
+	assert(p_r <= p_atmospherePar.top_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	float discriminant = p_r * p_r * (p_mu * p_mu - 1.0) + p_atmospherePar.top_radius * p_atmospherePar.top_radius;
+	return ClampDistance(-p_r * p_mu + SafeSqrt(discriminant));
+}
+
+vec2 GetTransmittanceTextureUvFromRMu(const AtmosphereParameters p_atmospherePar, float p_r, float p_mu) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	// Distance to top atmosphere boundary for a horizontal ray at ground level.
+	float H = sqrt(p_atmospherePar.top_radius * p_atmospherePar.top_radius - p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius);
+	// Distance to the horizon.
+	float rho = SafeSqrt(p_r * p_r - p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius);
+	// Distance to the top atmosphere boundary for the ray (r,mu), and its minimum
+	// and maximum values over all mu - obtained for (r,1) and (r,mu_horizon).
+	float d = DistanceToTopAtmosphereBoundary(p_atmospherePar, p_r, p_mu);
+	float d_min = p_atmospherePar.top_radius - p_r;
+	float d_max = rho + H;
+	float x_mu = (d - d_min) / (d_max - d_min);
+	float x_r = rho / H;
+	return vec2(GetTextureCoordFromUnitRange(x_mu, TRANSMITTANCE_TEXTURE_WIDTH), GetTextureCoordFromUnitRange(x_r, TRANSMITTANCE_TEXTURE_HEIGHT));
+}
+
+vec2 GetIrradianceTextureUvFromRMuS(const AtmosphereParameters p_atmospherePar, float p_r, float p_muS) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	assert(p_muS >= -1.0 && p_muS <= 1.0);
+	float x_r = (p_r - p_atmospherePar.bottom_radius) / (p_atmospherePar.top_radius - p_atmospherePar.bottom_radius);
+	float x_mu_s = p_muS * 0.5 + 0.5;
+	return vec2(GetTextureCoordFromUnitRange(x_mu_s, IRRADIANCE_TEXTURE_WIDTH), GetTextureCoordFromUnitRange(x_r, IRRADIANCE_TEXTURE_HEIGHT));
+}
+
+vec3 GetIrradiance(const AtmosphereParameters p_atmospherePar, sampler2D p_atmIrradianceTex, float p_r, float p_muS) 
+{
+	vec2 uv = GetIrradianceTextureUvFromRMuS(p_atmospherePar, p_r, p_muS);
+	return vec3(texture(p_atmIrradianceTex, uv).xyz);
+}
+
+vec3 GetTransmittanceToTopAtmosphereBoundary(const AtmosphereParameters p_atmospherePar, sampler2D p_atmTransmittanceTex, float p_r, float p_muS) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	vec2 uv = GetTransmittanceTextureUvFromRMu(p_atmospherePar, p_r, p_muS);
+	return vec3(texture(p_atmTransmittanceTex, uv));
+}
+
+vec3 GetTransmittanceToSun(const AtmosphereParameters p_atmospherePar, sampler2D p_atmTransmittanceTex, float p_r, float p_muS) 
+{
+	float sin_theta_h = p_atmospherePar.bottom_radius / p_r;
+	float cos_theta_h = -sqrt(max(1.0 - sin_theta_h * sin_theta_h, 0.0));
+	return GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, p_r, p_muS) *
+		smoothstep(-sin_theta_h * p_atmospherePar.sun_angular_radius / rad, sin_theta_h * p_atmospherePar.sun_angular_radius / rad, p_muS - cos_theta_h);
+}
+
+vec3 GetSunAndSkyIrradiance(const AtmosphereParameters p_atmospherePar, in vec3 p_point, in vec3 p_normal, in vec3 p_sunDirection, out vec3 p_skyIrradiance) 
+{
+	float r = length(p_point);
+	float mu_s = dot(p_point, p_sunDirection) / r;
+
+	// Indirect irradiance (approximated if the surface is not horizontal).
+	p_skyIrradiance = GetIrradiance(p_atmospherePar, atmIrradianceTexture, r, mu_s) * (1.0 + dot(p_normal, p_point) / r) * 0.5;
+
+	// Direct irradiance.
+	return p_atmospherePar.solar_irradiance * GetTransmittanceToSun(p_atmospherePar, atmTransmitTexture, r, mu_s) * max(dot(p_normal, p_sunDirection), 0.0);
+}
+
+void GetSphereShadowInOut(in vec3 p_cameraPos, in vec3 p_viewDirection, in vec3 p_sunDirection, out float d_in, out float d_out) 
+{
+	vec3 pos = p_cameraPos - kSphereCenter;
+	float pos_dot_sun = dot(pos, p_sunDirection);
+	float view_dot_sun = dot(p_viewDirection, p_sunDirection);
+	float k = atmScatteringParam.m_sunSize.x;
+	float l = 1.0 + k * k;
+	float a = 1.0 - l * view_dot_sun * view_dot_sun;
+	float b = dot(pos, p_viewDirection) - l * pos_dot_sun * view_dot_sun - k * kSphereRadius * view_dot_sun;
+	float c = dot(pos, pos) - l * pos_dot_sun * pos_dot_sun - 2.0 * k * kSphereRadius * pos_dot_sun - kSphereRadius * kSphereRadius;
+	float discriminant = b * b - a * c;
+	if(discriminant > 0.0) 
+	{
+		d_in = max(0.0, (-b - sqrt(discriminant)) / a);
+		d_out = (-b + sqrt(discriminant)) / a;
+		// The values of d for which delta is equal to 0 and kSphereRadius / k.
+		float d_base = -pos_dot_sun / view_dot_sun;
+		float d_apex = -(pos_dot_sun + kSphereRadius / k) / view_dot_sun;
+		if(view_dot_sun > 0.0) 
+		{
+			d_in = max(d_in, d_apex);
+			d_out = a > 0.0 ? min(d_out, d_base) : d_base;
+		} 
+		else 
+		{
+			d_in = a > 0.0 ? max(d_in, d_base) : d_base;
+			d_out = min(d_out, d_apex);
+		}
+	} 
+	else 
+	{
+		d_in = 0.0;
+		d_out = 0.0;
+	}
+}
+
+bool RayIntersectsGround(const AtmosphereParameters p_atmospherePar, float p_r, float p_mu) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	return p_mu < 0.0 && p_r * p_r * (p_mu * p_mu - 1.0) + p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius >= 0.0 * m2;
+}
+
+vec3 GetTransmittance(const AtmosphereParameters p_atmospherePar, sampler2D p_atmTransmittanceTex,
+    float p_r, float p_mu, float p_d, bool p_ray_r_mu_intersects_ground) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	assert(p_d >= 0.0 * m);
+
+	float r_d = ClampRadius(p_atmospherePar, sqrt(p_d * p_d + 2.0 * p_r * p_mu * p_d + p_r * p_r));
+	float mu_d = ClampCosine((p_r * p_mu + p_d) / r_d);
+
+	if(p_ray_r_mu_intersects_ground)
+	{
+		return min(GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, r_d, -mu_d) /
+			GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, p_r, -p_mu), 
+			vec3(1.0));
+	} 
+	else
+	{
+		return min(GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, p_r, p_mu) /
+			GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, r_d, mu_d),
+			vec3(1.0));
+	}
+}
+
+vec4 GetScatteringTextureUvwzFromRMuMuSNu(const AtmosphereParameters p_atmospherePar, float p_r, float p_mu, float p_muS, float p_nu, bool p_ray_r_mu_intersects_ground) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	assert(p_muS >= -1.0 && p_muS <= 1.0);
+	assert(p_nu >= -1.0 && p_nu <= 1.0);
+
+	// Distance to top atmosphere boundary for a horizontal ray at ground level.
+	float H = sqrt(p_atmospherePar.top_radius * p_atmospherePar.top_radius - p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius);
+	// Distance to the horizon.
+	float rho = SafeSqrt(p_r * p_r - p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius);
+	float u_r = GetTextureCoordFromUnitRange(rho / H, SCATTERING_TEXTURE_R_SIZE);
+
+	// Discriminant of the quadratic equation for the intersections of the ray
+	// (r,mu) with the ground (see RayIntersectsGround).
+	float r_mu = p_r * p_mu;
+	float discriminant = r_mu * r_mu - p_r * p_r + p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius;
+	float u_mu;
+	p_ray_r_mu_intersects_ground = true;
+	if(p_ray_r_mu_intersects_ground)
+	{
+		// Distance to the ground for the ray (r,mu), and its minimum and maximum
+		// values over all mu - obtained for (r,-1) and (r,mu_horizon).
+		float d = -r_mu - SafeSqrt(discriminant);
+		float d_min = p_r - p_atmospherePar.bottom_radius;
+		float d_max = rho;
+		u_mu = 0.5 - 0.5 * GetTextureCoordFromUnitRange(d_max == d_min ? 0.0 : (d - d_min) / (d_max - d_min), SCATTERING_TEXTURE_MU_SIZE / 2);
+	} 
+	else 
+	{
+		// Distance to the top atmosphere boundary for the ray (r,mu), and its
+		// minimum and maximum values over all mu - obtained for (r,1) and
+		// (r,mu_horizon).
+		float d = -r_mu + SafeSqrt(discriminant + H * H);
+		float d_min = p_atmospherePar.top_radius - p_r;
+		float d_max = rho + H;
+		u_mu = 0.5 + 0.5 * GetTextureCoordFromUnitRange((d - d_min) / (d_max - d_min), SCATTERING_TEXTURE_MU_SIZE / 2);
+	}
+
+	float d = DistanceToTopAtmosphereBoundary(p_atmospherePar, p_atmospherePar.bottom_radius, p_muS);
+	float d_min = p_atmospherePar.top_radius - p_atmospherePar.bottom_radius;
+	float d_max = H;
+	float a = (d - d_min) / (d_max - d_min);
+	float A = -2.0 * p_atmospherePar.mu_s_min * p_atmospherePar.bottom_radius / (d_max - d_min);
+	float u_mu_s = GetTextureCoordFromUnitRange(max(1.0 - a / A, 0.0) / (1.0 + a), SCATTERING_TEXTURE_MU_S_SIZE);
+
+	float u_nu = (p_nu + 1.0) / 2.0;
+	return vec4(u_nu, u_mu_s, u_mu, u_r);
+}
+
+vec3 GetCombinedScattering(
+    const AtmosphereParameters p_atmospherePar,
+    sampler3D p_atmScatteringTex,
+    sampler3D p_atmSingleMieScatteringTex,
+    float p_r, 
+	float p_mu, 
+	float p_muS, 
+	float p_nu,
+    bool p_ray_r_mu_intersects_ground,
+    out vec3 p_single_mie_scattering) 
+	{
+	vec4 uvwz = GetScatteringTextureUvwzFromRMuMuSNu(p_atmospherePar, p_r, p_mu, p_muS, p_nu, p_ray_r_mu_intersects_ground);
+	float tex_coord_x = uvwz.x * float(SCATTERING_TEXTURE_NU_SIZE - 1);
+	float tex_x = floor(tex_coord_x);
+	float lerp = tex_coord_x - tex_x;
+	vec3 uvw0 = vec3((tex_x + uvwz.y) / float(SCATTERING_TEXTURE_NU_SIZE), uvwz.z, uvwz.w);
+	vec3 uvw1 = vec3((tex_x + 1.0 + uvwz.y) / float(SCATTERING_TEXTURE_NU_SIZE), uvwz.z, uvwz.w);
+	
+#ifdef COMBINED_SCATTERING_TEXTURES
+	vec4 combined_scattering =texture(p_atmScatteringTex, uvw0) * (1.0 - lerp) + texture(p_atmScatteringTex, uvw1) * lerp;
+	vec3 scattering = vec3(combined_scattering);
+	p_single_mie_scattering = GetExtrapolatedSingleMieScattering(p_atmospherePar, combined_scattering);
+#else
+	vec3 scattering = vec3(texture(p_atmScatteringTex, uvw0) * (1.0 - lerp) + texture(p_atmScatteringTex, uvw1) * lerp);
+	p_single_mie_scattering = vec3(texture(p_atmSingleMieScatteringTex, uvw0) * (1.0 - lerp) + texture(p_atmSingleMieScatteringTex, uvw1) * lerp);
+#endif
+
+	return scattering;
+}
+
+vec3 GetSkyRadianceToPoint(
+    const AtmosphereParameters p_atmospherePar,
+    sampler2D p_atmTransmittanceTex,
+    sampler3D p_atmScatteringTex,
+    sampler3D p_atmSingleMieScatteringTex,
+    vec3 p_cameraPos, 
+	in vec3 p_point, 
+	//in float p_shadowLength,
+    in vec3 p_sunDirection, 
+	out vec3 p_transmittance) 
+	{
+	// Compute the distance to the top atmosphere boundary along the view ray,
+	// assuming the viewer is in space (or NaN if the view ray does not intersect
+	// the atmosphere).
+	vec3 view_ray = normalize(p_point - p_cameraPos);
+	float r = length(p_cameraPos);
+	float rmu = dot(p_cameraPos, view_ray);
+	float distance_to_top_atmosphere_boundary = -rmu -sqrt(rmu * rmu - r * r + p_atmospherePar.top_radius * p_atmospherePar.top_radius);
+	// If the viewer is in space and the view ray intersects the atmosphere, move
+	// the viewer to the top atmosphere boundary (along the view ray):
+	if(distance_to_top_atmosphere_boundary > 0.0 * m) 
+	{
+		p_cameraPos = p_cameraPos + view_ray * distance_to_top_atmosphere_boundary;
+		r = p_atmospherePar.top_radius;
+		rmu += distance_to_top_atmosphere_boundary;
+	}
+
+	// Compute the r, mu, mu_s and nu parameters for the first texture lookup.
+	float mu = rmu / r;
+	float mu_s = dot(p_cameraPos, p_sunDirection) / r;
+	float nu = dot(view_ray, p_sunDirection);
+	float d = length(p_point - p_cameraPos);
+	bool ray_r_mu_intersects_ground = RayIntersectsGround(p_atmospherePar, r, mu);
+
+	p_transmittance = GetTransmittance(p_atmospherePar, p_atmTransmittanceTex, r, mu, d, ray_r_mu_intersects_ground);
+
+	vec3 single_mie_scattering;
+	vec3 scattering = GetCombinedScattering(
+		p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+		r, mu, mu_s, nu, ray_r_mu_intersects_ground,
+		single_mie_scattering);
+
+	// Compute the r, mu, mu_s and nu parameters for the second texture lookup.
+	// If shadow_length is not 0 (case of light shafts), we want to ignore the
+	// scattering along the last shadow_length meters of the view ray, which we
+	// do by subtracting shadow_length from d (this way scattering_p is equal to
+	// the S|x_s=x_0-lv term in Eq. (17) of our paper).
+	//d = max(d - p_shadowLength, 0.0 * m);
+	float r_p = ClampRadius(p_atmospherePar, sqrt(d * d + 2.0 * r * mu * d + r * r));
+	float mu_p = (r * mu + d) / r_p;
+	float mu_s_p = (r * mu_s + d * nu) / r_p;
+
+	vec3 single_mie_scattering_p;
+	vec3 scattering_p = GetCombinedScattering(
+		p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+		r_p, mu_p, mu_s_p, nu, ray_r_mu_intersects_ground,
+		single_mie_scattering_p);
+
+	// Combine the lookup results to get the scattering between camera and point.
+	vec3 shadow_transmittance = p_transmittance;
+	//if(p_shadowLength > 0.0 * m) 
+	//{
+		// This is the T(x,x_s) term in Eq. (17) of our paper, for light shafts.
+	//	shadow_transmittance = GetTransmittance(p_atmospherePar, p_atmTransmittanceTex, r, mu, d, ray_r_mu_intersects_ground);
+	//}
+	scattering = scattering - shadow_transmittance * scattering_p;
+	single_mie_scattering = single_mie_scattering - shadow_transmittance * single_mie_scattering_p;
+#ifdef COMBINED_SCATTERING_TEXTURES
+	single_mie_scattering = GetExtrapolatedSingleMieScattering(p_atmospherePar, vec4(scattering, single_mie_scattering.r));
+#endif
+
+	// Hack to avoid rendering artifacts when the sun is below the horizon.
+	single_mie_scattering = single_mie_scattering * smoothstep(float(0.0), float(0.01), mu_s);
+
+	return scattering * RayleighPhaseFunction(nu) + single_mie_scattering * MiePhaseFunction(p_atmospherePar.mie_phase_function_g, nu);
+}
+
+vec3 GetSkyRadiance(
+    const AtmosphereParameters p_atmospherePar,
+    sampler2D p_atmTransmittanceTex,
+    sampler3D p_atmScatteringTex,
+    sampler3D p_atmSingleMieScatteringTex,
+	vec3 p_fragPos,
+    vec3 p_camera, 
+	in vec3 p_viewRay, 
+	float p_shadowLength,
+    in vec3 p_sunDirection, 
+	out vec3 p_transmittance)
+{
+	// Compute the distance to the top atmosphere boundary along the view ray,
+	// assuming the viewer is in space (or NaN if the view ray does not intersect
+	// the atmosphere).
+	float r = length(p_camera - p_fragPos);//length(p_camera);
+	float rmu = dot(p_camera, p_viewRay);
+	float distance_to_top_atmosphere_boundary = -rmu - sqrt(rmu * rmu - r * r + p_atmospherePar.top_radius * p_atmospherePar.top_radius);
+	// If the viewer is in space and the view ray intersects the atmosphere, move
+	// the viewer to the top atmosphere boundary (along the view ray):
+	if(distance_to_top_atmosphere_boundary > 0.0 * m)
+	{
+	//	p_camera = p_camera + p_viewRay * distance_to_top_atmosphere_boundary;
+		p_camera = p_fragPos;
+		r = p_atmospherePar.top_radius;
+		rmu += distance_to_top_atmosphere_boundary;
+		return vec3(0.0);
+	} 
+	/*else if(r > p_atmospherePar.top_radius) 
+	{
+		// If the view ray does not intersect the atmosphere, simply return 0.
+		p_transmittance = vec3(1.0);
+		return vec3(0.0 * watt_per_square_meter_per_sr_per_nm);
+	}*/
+	// Compute the r, mu, mu_s and nu parameters needed for the texture lookups.
+	float mu = rmu / r;
+	float mu_s = dot(p_camera, p_sunDirection) / r;
+	float nu = dot(p_viewRay, p_sunDirection);
+	bool ray_r_mu_intersects_ground = RayIntersectsGround(p_atmospherePar, r, mu);
+	//ray_r_mu_intersects_ground = true;
+	
+	p_transmittance = ray_r_mu_intersects_ground ? vec3(0.0) : GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, r, mu);
+	vec3 single_mie_scattering;
+	vec3 scattering;
+	//if(p_shadowLength == 0.0 * m)
+	//{
+		scattering = GetCombinedScattering(
+			p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+			r, mu, mu_s, nu, ray_r_mu_intersects_ground,
+			single_mie_scattering);
+	//}
+	/*else
+	{
+		// Case of light shafts (shadow_length is the total length noted l in our
+		// paper): we omit the scattering between the camera and the point at
+		// distance l, by implementing Eq. (18) of the paper (shadow_transmittance
+		// is the T(x,x_s) term, scattering is the S|x_s=x+lv term).
+		float d = p_shadowLength;
+		float r_p = ClampRadius(p_atmospherePar, sqrt(d * d + 2.0 * r * mu * d + r * r));
+		float mu_p = (r * mu + d) / r_p;
+		float mu_s_p = (r * mu_s + d * nu) / r_p;
+
+		scattering = GetCombinedScattering(
+			p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+			r_p, mu_p, mu_s_p, nu, ray_r_mu_intersects_ground,
+			single_mie_scattering);
+		vec3 shadow_transmittance =
+			GetTransmittance(p_atmospherePar, p_atmTransmittanceTex,
+			r, mu, p_shadowLength, ray_r_mu_intersects_ground);
+		scattering = scattering * shadow_transmittance;
+		single_mie_scattering = single_mie_scattering * shadow_transmittance;
+	}*/
+	
+	return scattering * RayleighPhaseFunction(nu) + single_mie_scattering * MiePhaseFunction(p_atmospherePar.mie_phase_function_g, nu);
+}
+
+float GetSunVisibility(vec3 p_point, vec3 p_sunDirection)
+{
+	vec3 p = p_point - kSphereCenter;
+	float p_dot_v = dot(p, p_sunDirection);
+	float p_dot_p = dot(p, p);
+	float ray_sphere_center_squared_distance = p_dot_p - p_dot_v * p_dot_v;
+	float distance_to_intersection = -p_dot_v - sqrt(kSphereRadius * kSphereRadius - ray_sphere_center_squared_distance);
+	
+	if(distance_to_intersection > 0.0)
+	{
+		// Compute the distance between the view ray and the sphere, and the
+		// corresponding (tangent of the) subtended angle. Finally, use this to
+		// compute an approximate sun visibility.
+		float ray_sphere_distance = kSphereRadius - sqrt(ray_sphere_center_squared_distance);
+		float ray_sphere_angular_distance = -ray_sphere_distance / p_dot_v;
+		return smoothstep(1.0, 0.0, ray_sphere_angular_distance / atmScatteringParam.m_sunSize.x);
+	}
+	
+	return 1.0;
+}
+
+float GetSkyVisibility(vec3 p_point)
+{
+	vec3 p = p_point - kSphereCenter;
+	float p_dot_p = dot(p, p);
+	return 1.0 + p.z / sqrt(p_dot_p) * kSphereRadius * kSphereRadius / p_dot_p;
+} 
+vec3 GetSolarRadiance(const AtmosphereParameters p_atmospherePar)
+{
+	return p_atmospherePar.solar_irradiance / (PI * p_atmospherePar.sun_angular_radius * p_atmospherePar.sun_angular_radius);
+}
+
+void main() 
+{
+	// Calculate screen-space texture coordinates, for buffer access
+	vec2 texCoord = calcTexCoord();
+	// Get the current fragment color
+	vec3 fragmentColor = texture(inputColorMap, texCoord).xyz;
+	// Get pixel's position in world space
+	vec3 worldPos = texture(positionMap, texCoord).xyz;
+	// Get normal (in world space) and normalize it to minimize floating point approximation errors
+	vec3 normal = normalize(texture(normalMap, texCoord).xyz);
+	
+	float distanceToFrag = length(cameraPosVec - worldPos) / kLengthUnitInMeters;
+	vec3 cameraPosition = cameraPosVec / kLengthUnitInMeters;
+	worldPos = worldPos / kLengthUnitInMeters;
+	
+	vec3 transmittance = vec3(0.0);
+	
+	// Hack to acquire believable looking atmospheric fog without the artifacts at the horizon view directionalLight
+	// All fog (in-scattering) is calculated as if the view direction is from the top-down view
+	vec3 newPointPos = vec3(0.0, worldPos.y, 0.0);
+	vec3 newCameraPos = vec3(0.0, worldPos.y + distanceToFrag, 0.0);
+	
+	vec3 inScatter = GetSkyRadianceToPoint(
+		atmScatteringParam.m_atmosphereParam,
+		atmTransmitTexture,
+		atmScatteringTexture,
+		atmSingleMieTexture,
+		newCameraPos - atmScatteringParam.m_earthCenter,
+		newPointPos - atmScatteringParam.m_earthCenter,
+		directionalLight.m_direction,
+		transmittance);
+		
+	inScatter = inScatter / atmScatteringParam.m_whitePoint * directionalLight.m_intensity * 1.0;
+	colorBuffer = vec4(fragmentColor + inScatter, 1.0);
+}

+ 646 - 0
Praxis3D/Data/Shaders/atmosphericScatteringPass_sky.frag

@@ -0,0 +1,646 @@
+#version 430 core
+
+/*const float kLengthUnitInMeters = 1000.0;
+const float PI = 3.14159265;
+const vec3 kSphereCenter = vec3(0.0, 0.0, 1000.0) / kLengthUnitInMeters;
+const float kSphereRadius = 1000.0 / kLengthUnitInMeters;
+const vec3 kSphereAlbedo = vec3(0.8);
+const vec3 kGroundAlbedo = vec3(0.0, 0.0, 0.04);
+
+#ifdef USE_LUMINANCE
+#define GetSolarRadiance GetSolarLuminance
+#define GetSkyRadiance GetSkyLuminance
+#define GetSkyRadianceToPoint GetSkyLuminanceToPoint
+#define GetSunAndSkyIrradiance GetSunAndSkyIlluminance
+#endif*/
+
+#define TEMPLATE(x)
+#define TEMPLATE_ARGUMENT(x)
+#define assert(x)
+
+in vec3 viewRay;
+
+out vec4 color;
+
+uniform float exposure;
+
+uniform ivec2 screenSize;
+uniform vec3 cameraPosVec;
+
+uniform sampler2D atmIrradianceTexture;
+uniform sampler3D atmScatteringTexture;
+uniform sampler3D atmSingleMieTexture;
+uniform sampler2D atmTransmitTexture;
+
+const float kLengthUnitInMeters = 1000.0;
+const vec3 kGroundAlbedo = vec3(0.0, 0.0, 0.04);
+
+const float m = 1.0;
+const float nm = 1.0;
+const float rad = 1.0;
+const float sr = 1.0;
+const float watt = 1.0;
+const float lm = 1.0;
+const float PI = 3.14159265358979323846;
+const float km = 1000.0 * m;
+const float m2 = m * m;
+const float m3 = m * m * m;
+const float pi = PI * rad;
+const float deg = pi / 180.0;
+const float watt_per_square_meter = watt / m2;
+const float watt_per_square_meter_per_sr = watt / (m2 * sr);
+const float watt_per_square_meter_per_nm = watt / (m2 * nm);
+const float watt_per_square_meter_per_sr_per_nm = watt / (m2 * sr * nm);
+const float watt_per_cubic_meter_per_sr_per_nm = watt / (m3 * sr * nm);
+const float cd = lm / sr;
+const float kcd = 1000.0 * cd;
+const float cd_per_square_meter = cd / m2;
+const float kcd_per_square_meter = kcd / m2;
+const vec3 SKY_SPECTRAL_RADIANCE_TO_LUMINANCE = vec3(114974.916437,71305.954816,65310.548555);
+const vec3 SUN_SPECTRAL_RADIANCE_TO_LUMINANCE = vec3(98242.786222,69954.398112,66475.012354);
+const int TRANSMITTANCE_TEXTURE_WIDTH = 256;
+const int TRANSMITTANCE_TEXTURE_HEIGHT = 64;
+const int SCATTERING_TEXTURE_R_SIZE = 32;
+const int SCATTERING_TEXTURE_MU_SIZE = 128;
+const int SCATTERING_TEXTURE_MU_S_SIZE = 32;
+const int SCATTERING_TEXTURE_NU_SIZE = 8;
+const int IRRADIANCE_TEXTURE_WIDTH = 64;
+const int IRRADIANCE_TEXTURE_HEIGHT = 16;
+
+struct DirectionalLight
+{
+    vec3 m_color;
+    vec3 m_direction;
+    float m_intensity;
+};
+
+// An atmosphere layer of width 'width', and whose density is defined as
+//   'exp_term' * exp('exp_scale' * h) + 'linear_term' * h + 'constant_term',
+// clamped to [0,1], and where h is the altitude.
+struct DensityProfileLayer 
+{
+	float width;
+	float exp_term;
+	float exp_scale;
+	float linear_term;
+	float constant_term;
+};
+
+// An atmosphere density profile made of several layers on top of each other
+// (from bottom to top). The width of the last layer is ignored, i.e. it always
+// extend to the top atmosphere boundary. The profile values vary between 0
+// (null density) to 1 (maximum density).
+struct DensityProfile 
+{
+	DensityProfileLayer layers[2];
+};
+
+struct AtmosphereParameters 
+{
+	// The solar irradiance at the top of the atmosphere.
+	vec3 solar_irradiance;
+	// The sun's angular radius. Warning: the implementation uses approximations
+	// that are valid only if this angle is smaller than 0.1 radians.
+	float sun_angular_radius;
+	// The density profile of air molecules, i.e. a function from altitude to
+	// dimensionless values between 0 (null density) and 1 (maximum density).
+	DensityProfile rayleigh_density;
+	// The density profile of aerosols, i.e. a function from altitude to
+	// dimensionless values between 0 (null density) and 1 (maximum density).
+	DensityProfile mie_density;
+	// The density profile of air molecules that absorb light (e.g. ozone), i.e.
+	// a function from altitude to dimensionless values between 0 (null density)
+	// and 1 (maximum density).
+	DensityProfile absorption_density;
+	// The scattering coefficient of air molecules at the altitude where their
+	// density is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The scattering coefficient at altitude h is equal to
+	// 'rayleigh_scattering' times 'rayleigh_density' at this altitude.
+	vec3 rayleigh_scattering;
+	// The distance between the planet center and the bottom of the atmosphere.
+	float bottom_radius;
+	// The scattering coefficient of aerosols at the altitude where their density
+	// is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The scattering coefficient at altitude h is equal to
+	// 'mie_scattering' times 'mie_density' at this altitude.
+	vec3 mie_scattering;
+	// The distance between the planet center and the top of the atmosphere.
+	float top_radius;
+	// The extinction coefficient of aerosols at the altitude where their density
+	// is maximum (usually the bottom of the atmosphere), as a function of
+	// wavelength. The extinction coefficient at altitude h is equal to
+	// 'mie_extinction' times 'mie_density' at this altitude.
+	vec3 mie_extinction;
+	// The asymetry parameter for the Cornette-Shanks phase function for the
+	// aerosols.
+	float mie_phase_function_g;
+	// The extinction coefficient of molecules that absorb light (e.g. ozone) at
+	// the altitude where their density is maximum, as a function of wavelength.
+	// The extinction coefficient at altitude h is equal to
+	// 'absorption_extinction' times 'absorption_density' at this altitude.
+	vec3 absorption_extinction;
+	// The cosine of the maximum Sun zenith angle for which atmospheric scattering
+	// must be precomputed (for maximum precision, use the smallest Sun zenith
+	// angle yielding negligible sky light radiance values. For instance, for the
+	// Earth case, 102 degrees is a good choice - yielding mu_s_min = -0.2).
+	float mu_s_min;
+	// The average albedo of the ground.
+	vec3 ground_albedo;
+};
+
+struct AtmScatteringParameters
+{
+	vec3 m_whitePoint;
+	vec3 m_earthCenter;
+	vec2 m_sunSize;
+	AtmosphereParameters m_atmosphereParam;
+};
+
+uniform DirectionalLight directionalLight;
+
+layout (std140) uniform AtmScatParametersBuffer
+{
+	AtmScatteringParameters atmScatteringParam;
+};
+	
+vec2 calcTexCoord(void)
+{
+	return gl_FragCoord.xy / screenSize;
+}
+float ClampCosine(float p_mu) 
+{
+	return clamp(p_mu, float(-1.0), float(1.0));
+}
+
+float ClampDistance(float p_d) 
+{
+	return max(p_d, 0.0 * m);
+}
+
+float ClampRadius(const AtmosphereParameters p_atmospherePar, float p_r) 
+{
+	return clamp(p_r, p_atmospherePar.bottom_radius, p_atmospherePar.top_radius);
+}
+
+float SafeSqrt(float p_a) 
+{
+	return sqrt(max(p_a, 0.0 * m2));
+}
+
+float GetTextureCoordFromUnitRange(float p_x, int p_textureSize) 
+{
+	return 0.5 / float(p_textureSize) + p_x * (1.0 - 1.0 / float(p_textureSize));
+}
+
+float RayleighPhaseFunction(float p_nu) 
+{
+	float k = 3.0 / (16.0 * PI * sr);
+	return k * (1.0 + p_nu * p_nu);
+}
+
+float MiePhaseFunction(float p_g, float p_nu) 
+{
+	float k = 3.0 / (8.0 * PI * sr) * (1.0 - p_g * p_g) / (2.0 + p_g * p_g);
+	return k * (1.0 + p_nu * p_nu) / pow(1.0 + p_g * p_g - 2.0 * p_g * p_nu, 1.5);
+}
+
+float DistanceToTopAtmosphereBoundary(const AtmosphereParameters p_atmospherePar, float p_r, float p_mu) 
+{
+	assert(p_r <= p_atmospherePar.top_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	float discriminant = p_r * p_r * (p_mu * p_mu - 1.0) + p_atmospherePar.top_radius * p_atmospherePar.top_radius;
+	return ClampDistance(-p_r * p_mu + SafeSqrt(discriminant));
+}
+
+vec2 GetTransmittanceTextureUvFromRMu(const AtmosphereParameters p_atmospherePar, float p_r, float p_mu) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	// Distance to top atmosphere boundary for a horizontal ray at ground level.
+	float H = sqrt(p_atmospherePar.top_radius * p_atmospherePar.top_radius - p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius);
+	// Distance to the horizon.
+	float rho = SafeSqrt(p_r * p_r - p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius);
+	// Distance to the top atmosphere boundary for the ray (r,mu), and its minimum
+	// and maximum values over all mu - obtained for (r,1) and (r,mu_horizon).
+	float d = DistanceToTopAtmosphereBoundary(p_atmospherePar, p_r, p_mu);
+	float d_min = p_atmospherePar.top_radius - p_r;
+	float d_max = rho + H;
+	float x_mu = (d - d_min) / (d_max - d_min);
+	float x_r = rho / H;
+	return vec2(GetTextureCoordFromUnitRange(x_mu, TRANSMITTANCE_TEXTURE_WIDTH), GetTextureCoordFromUnitRange(x_r, TRANSMITTANCE_TEXTURE_HEIGHT));
+}
+
+vec2 GetIrradianceTextureUvFromRMuS(const AtmosphereParameters p_atmospherePar, float p_r, float p_muS) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	assert(p_muS >= -1.0 && p_muS <= 1.0);
+	float x_r = (p_r - p_atmospherePar.bottom_radius) / (p_atmospherePar.top_radius - p_atmospherePar.bottom_radius);
+	float x_mu_s = p_muS * 0.5 + 0.5;
+	return vec2(GetTextureCoordFromUnitRange(x_mu_s, IRRADIANCE_TEXTURE_WIDTH), GetTextureCoordFromUnitRange(x_r, IRRADIANCE_TEXTURE_HEIGHT));
+}
+
+vec3 GetIrradiance(const AtmosphereParameters p_atmospherePar, sampler2D p_atmIrradianceTex, float p_r, float p_muS) 
+{
+	vec2 uv = GetIrradianceTextureUvFromRMuS(p_atmospherePar, p_r, p_muS);
+	return vec3(texture(p_atmIrradianceTex, uv).xyz);
+}
+
+vec3 GetTransmittanceToTopAtmosphereBoundary(const AtmosphereParameters p_atmospherePar, sampler2D p_atmTransmittanceTex, float p_r, float p_muS) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	vec2 uv = GetTransmittanceTextureUvFromRMu(p_atmospherePar, p_r, p_muS);
+	return vec3(texture(p_atmTransmittanceTex, uv));
+}
+
+vec3 GetTransmittanceToSun(const AtmosphereParameters p_atmospherePar, sampler2D p_atmTransmittanceTex, float p_r, float p_muS) 
+{
+	float sin_theta_h = p_atmospherePar.bottom_radius / p_r;
+	float cos_theta_h = -sqrt(max(1.0 - sin_theta_h * sin_theta_h, 0.0));
+	return GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, p_r, p_muS) *
+		smoothstep(-sin_theta_h * p_atmospherePar.sun_angular_radius / rad, sin_theta_h * p_atmospherePar.sun_angular_radius / rad, p_muS - cos_theta_h);
+}
+
+vec3 GetSunAndSkyIrradiance(const AtmosphereParameters p_atmospherePar, in vec3 p_point, in vec3 p_normal, in vec3 p_sunDirection, out vec3 p_skyIrradiance) 
+{
+	float r = length(p_point);
+	float mu_s = dot(p_point, p_sunDirection) / r;
+
+	// Indirect irradiance (approximated if the surface is not horizontal).
+	p_skyIrradiance = GetIrradiance(p_atmospherePar, atmIrradianceTexture, r, mu_s) * (1.0 + dot(p_normal, p_point) / r) * 0.5;
+
+	// Direct irradiance.
+	return p_atmospherePar.solar_irradiance * GetTransmittanceToSun(p_atmospherePar, atmTransmitTexture, r, mu_s) * max(dot(p_normal, p_sunDirection), 0.0);
+}
+
+bool RayIntersectsGround(const AtmosphereParameters p_atmospherePar, float p_r, float p_mu) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	return p_mu < 0.0 && p_r * p_r * (p_mu * p_mu - 1.0) + p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius >= 0.0 * m2;
+}
+
+vec3 GetTransmittance(const AtmosphereParameters p_atmospherePar, sampler2D p_atmTransmittanceTex,
+    float p_r, float p_mu, float p_d, bool p_ray_r_mu_intersects_ground) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	assert(p_d >= 0.0 * m);
+
+	float r_d = ClampRadius(p_atmospherePar, sqrt(p_d * p_d + 2.0 * p_r * p_mu * p_d + p_r * p_r));
+	float mu_d = ClampCosine((p_r * p_mu + p_d) / r_d);
+
+	if(p_ray_r_mu_intersects_ground)
+	{
+		return min(GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, r_d, -mu_d) /
+			GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, p_r, -p_mu), 
+			vec3(1.0));
+	} 
+	else
+	{
+		return min(GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, p_r, p_mu) /
+			GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, r_d, mu_d),
+			vec3(1.0));
+	}
+}
+
+vec4 GetScatteringTextureUvwzFromRMuMuSNu(const AtmosphereParameters p_atmospherePar, float p_r, float p_mu, float p_muS, float p_nu, bool p_ray_r_mu_intersects_ground) 
+{
+	assert(p_r >= p_atmospherePar.bottom_radius && p_r <= p_atmospherePar.top_radius);
+	assert(p_mu >= -1.0 && p_mu <= 1.0);
+	assert(p_muS >= -1.0 && p_muS <= 1.0);
+	assert(p_nu >= -1.0 && p_nu <= 1.0);
+
+	// Distance to top atmosphere boundary for a horizontal ray at ground level.
+	float H = sqrt(p_atmospherePar.top_radius * p_atmospherePar.top_radius - p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius);
+	// Distance to the horizon.
+	float rho = SafeSqrt(p_r * p_r - p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius);
+	float u_r = GetTextureCoordFromUnitRange(rho / H, SCATTERING_TEXTURE_R_SIZE);
+
+	// Discriminant of the quadratic equation for the intersections of the ray
+	// (r,mu) with the ground (see RayIntersectsGround).
+	float r_mu = p_r * p_mu;
+	float discriminant = r_mu * r_mu - p_r * p_r + p_atmospherePar.bottom_radius * p_atmospherePar.bottom_radius;
+	float u_mu;
+	if(p_ray_r_mu_intersects_ground)
+	{
+		// Distance to the ground for the ray (r,mu), and its minimum and maximum
+		// values over all mu - obtained for (r,-1) and (r,mu_horizon).
+		float d = -r_mu - SafeSqrt(discriminant);
+		float d_min = p_r - p_atmospherePar.bottom_radius;
+		float d_max = rho;
+		u_mu = 0.5 - 0.5 * GetTextureCoordFromUnitRange(d_max == d_min ? 0.0 : (d - d_min) / (d_max - d_min), SCATTERING_TEXTURE_MU_SIZE / 2);
+	} 
+	else 
+	{
+		// Distance to the top atmosphere boundary for the ray (r,mu), and its
+		// minimum and maximum values over all mu - obtained for (r,1) and
+		// (r,mu_horizon).
+		float d = -r_mu + SafeSqrt(discriminant + H * H);
+		float d_min = p_atmospherePar.top_radius - p_r;
+		float d_max = rho + H;
+		u_mu = 0.5 + 0.5 * GetTextureCoordFromUnitRange((d - d_min) / (d_max - d_min), SCATTERING_TEXTURE_MU_SIZE / 2);
+	}
+
+	float d = DistanceToTopAtmosphereBoundary(p_atmospherePar, p_atmospherePar.bottom_radius, p_muS);
+	float d_min = p_atmospherePar.top_radius - p_atmospherePar.bottom_radius;
+	float d_max = H;
+	float a = (d - d_min) / (d_max - d_min);
+	float A = -2.0 * p_atmospherePar.mu_s_min * p_atmospherePar.bottom_radius / (d_max - d_min);
+	float u_mu_s = GetTextureCoordFromUnitRange(max(1.0 - a / A, 0.0) / (1.0 + a), SCATTERING_TEXTURE_MU_S_SIZE);
+
+	float u_nu = (p_nu + 1.0) / 2.0;
+	return vec4(u_nu, u_mu_s, u_mu, u_r);
+}
+
+vec3 GetCombinedScattering(
+    const AtmosphereParameters p_atmospherePar,
+    sampler3D p_atmScatteringTex,
+    sampler3D p_atmSingleMieScatteringTex,
+    float p_r, 
+	float p_mu, 
+	float p_muS, 
+	float p_nu,
+    bool p_ray_r_mu_intersects_ground,
+    out vec3 p_single_mie_scattering) 
+	{
+	vec4 uvwz = GetScatteringTextureUvwzFromRMuMuSNu(p_atmospherePar, p_r, p_mu, p_muS, p_nu, p_ray_r_mu_intersects_ground);
+	float tex_coord_x = uvwz.x * float(SCATTERING_TEXTURE_NU_SIZE - 1);
+	float tex_x = floor(tex_coord_x);
+	float lerp = tex_coord_x - tex_x;
+	vec3 uvw0 = vec3((tex_x + uvwz.y) / float(SCATTERING_TEXTURE_NU_SIZE), uvwz.z, uvwz.w);
+	vec3 uvw1 = vec3((tex_x + 1.0 + uvwz.y) / float(SCATTERING_TEXTURE_NU_SIZE), uvwz.z, uvwz.w);
+	
+#ifdef COMBINED_SCATTERING_TEXTURES
+	vec4 combined_scattering =texture(p_atmScatteringTex, uvw0) * (1.0 - lerp) + texture(p_atmScatteringTex, uvw1) * lerp;
+	vec3 scattering = vec3(combined_scattering);
+	p_single_mie_scattering = GetExtrapolatedSingleMieScattering(p_atmospherePar, combined_scattering);
+#else
+	vec3 scattering = vec3(texture(p_atmScatteringTex, uvw0) * (1.0 - lerp) + texture(p_atmScatteringTex, uvw1) * lerp);
+	p_single_mie_scattering = vec3(texture(p_atmSingleMieScatteringTex, uvw0) * (1.0 - lerp) + texture(p_atmSingleMieScatteringTex, uvw1) * lerp);
+#endif
+
+	return scattering;
+}
+
+vec3 GetSkyRadianceToPoint(
+    const AtmosphereParameters p_atmospherePar,
+    sampler2D p_atmTransmittanceTex,
+    sampler3D p_atmScatteringTex,
+    sampler3D p_atmSingleMieScatteringTex,
+    vec3 p_cameraPos, 
+	in vec3 p_point, 
+	in float p_shadowLength,
+    in vec3 p_sunDirection, 
+	out vec3 p_transmittance) 
+	{
+	// Compute the distance to the top atmosphere boundary along the view ray,
+	// assuming the viewer is in space (or NaN if the view ray does not intersect
+	// the atmosphere).
+	vec3 view_ray = normalize(p_point - p_cameraPos);
+	float r = length(p_cameraPos);
+	float rmu = dot(p_cameraPos, view_ray);
+	float distance_to_top_atmosphere_boundary = -rmu -sqrt(rmu * rmu - r * r + p_atmospherePar.top_radius * p_atmospherePar.top_radius);
+	// If the viewer is in space and the view ray intersects the atmosphere, move
+	// the viewer to the top atmosphere boundary (along the view ray):
+	if(distance_to_top_atmosphere_boundary > 0.0 * m) 
+	{
+		p_cameraPos = p_cameraPos + view_ray * distance_to_top_atmosphere_boundary;
+		r = p_atmospherePar.top_radius;
+		rmu += distance_to_top_atmosphere_boundary;
+	}
+
+	// Compute the r, mu, mu_s and nu parameters for the first texture lookup.
+	float mu = rmu / r;
+	float mu_s = dot(p_cameraPos, p_sunDirection) / r;
+	float nu = dot(view_ray, p_sunDirection);
+	float d = length(p_point - p_cameraPos);
+	bool ray_r_mu_intersects_ground = RayIntersectsGround(p_atmospherePar, r, mu);
+
+	p_transmittance = GetTransmittance(p_atmospherePar, p_atmTransmittanceTex, r, mu, d, ray_r_mu_intersects_ground);
+
+	vec3 single_mie_scattering;
+	vec3 scattering = GetCombinedScattering(
+		p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+		r, mu, mu_s, nu, ray_r_mu_intersects_ground,
+		single_mie_scattering);
+
+	// Compute the r, mu, mu_s and nu parameters for the second texture lookup.
+	// If shadow_length is not 0 (case of light shafts), we want to ignore the
+	// scattering along the last shadow_length meters of the view ray, which we
+	// do by subtracting shadow_length from d (this way scattering_p is equal to
+	// the S|x_s=x_0-lv term in Eq. (17) of our paper).
+	d = max(d - p_shadowLength, 0.0 * m);
+	float r_p = ClampRadius(p_atmospherePar, sqrt(d * d + 2.0 * r * mu * d + r * r));
+	float mu_p = (r * mu + d) / r_p;
+	float mu_s_p = (r * mu_s + d * nu) / r_p;
+
+	vec3 single_mie_scattering_p;
+	vec3 scattering_p = GetCombinedScattering(
+		p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+		r_p, mu_p, mu_s_p, nu, ray_r_mu_intersects_ground,
+		single_mie_scattering_p);
+
+	// Combine the lookup results to get the scattering between camera and point.
+	vec3 shadow_transmittance = p_transmittance;
+	if(p_shadowLength > 0.0 * m) 
+	{
+		// This is the T(x,x_s) term in Eq. (17) of our paper, for light shafts.
+		shadow_transmittance = GetTransmittance(p_atmospherePar, p_atmTransmittanceTex, r, mu, d, ray_r_mu_intersects_ground);
+	}
+	scattering = scattering - shadow_transmittance * scattering_p;
+	single_mie_scattering = single_mie_scattering - shadow_transmittance * single_mie_scattering_p;
+#ifdef COMBINED_SCATTERING_TEXTURES
+	single_mie_scattering = GetExtrapolatedSingleMieScattering(p_atmospherePar, vec4(scattering, single_mie_scattering.r));
+#endif
+
+	// Hack to avoid rendering artifacts when the sun is below the horizon.
+	single_mie_scattering = single_mie_scattering * smoothstep(float(0.0), float(0.01), mu_s);
+
+	return scattering * RayleighPhaseFunction(nu) + single_mie_scattering * MiePhaseFunction(p_atmospherePar.mie_phase_function_g, nu);
+}
+
+vec3 GetSkyRadiance(
+    const AtmosphereParameters p_atmospherePar,
+    sampler2D p_atmTransmittanceTex,
+    sampler3D p_atmScatteringTex,
+    sampler3D p_atmSingleMieScatteringTex,
+    vec3 p_camera, 
+	in vec3 p_viewRay, 
+	float p_shadowLength,
+    in vec3 p_sunDirection, 
+	out vec3 p_transmittance)
+{
+	// Compute the distance to the top atmosphere boundary along the view ray,
+	// assuming the viewer is in space (or NaN if the view ray does not intersect
+	// the atmosphere).
+	float r = length(p_camera);
+	float rmu = dot(p_camera, p_viewRay);
+	float distance_to_top_atmosphere_boundary = -rmu - sqrt(rmu * rmu - r * r + p_atmospherePar.top_radius * p_atmospherePar.top_radius);
+	// If the viewer is in space and the view ray intersects the atmosphere, move
+	// the viewer to the top atmosphere boundary (along the view ray):
+	if(distance_to_top_atmosphere_boundary > 0.0 * m)
+	{
+		p_camera = p_camera + p_viewRay * distance_to_top_atmosphere_boundary;
+		r = p_atmospherePar.top_radius;
+		rmu += distance_to_top_atmosphere_boundary;
+	} 
+	else if(r > p_atmospherePar.top_radius) 
+	{
+		// If the view ray does not intersect the atmosphere, simply return 0.
+		p_transmittance = vec3(1.0);
+		return vec3(0.0 * watt_per_square_meter_per_sr_per_nm);
+	}
+	// Compute the r, mu, mu_s and nu parameters needed for the texture lookups.
+	float mu = rmu / r;
+	float mu_s = dot(p_camera, p_sunDirection) / r;
+	float nu = dot(p_viewRay, p_sunDirection);
+	bool ray_r_mu_intersects_ground = RayIntersectsGround(p_atmospherePar, r, mu);
+
+	p_transmittance = ray_r_mu_intersects_ground ? vec3(0.0) : GetTransmittanceToTopAtmosphereBoundary(p_atmospherePar, p_atmTransmittanceTex, r, mu);
+	vec3 single_mie_scattering;
+	vec3 scattering;
+	if(p_shadowLength == 0.0 * m)
+	{
+		scattering = GetCombinedScattering(
+			p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+			r, mu, mu_s, nu, ray_r_mu_intersects_ground,
+			single_mie_scattering);
+	}
+	else
+	{
+		// Case of light shafts (shadow_length is the total length noted l in our
+		// paper): we omit the scattering between the camera and the point at
+		// distance l, by implementing Eq. (18) of the paper (shadow_transmittance
+		// is the T(x,x_s) term, scattering is the S|x_s=x+lv term).
+		float d = p_shadowLength;
+		float r_p = ClampRadius(p_atmospherePar, sqrt(d * d + 2.0 * r * mu * d + r * r));
+		float mu_p = (r * mu + d) / r_p;
+		float mu_s_p = (r * mu_s + d * nu) / r_p;
+
+		scattering = GetCombinedScattering(
+			p_atmospherePar, p_atmScatteringTex, p_atmSingleMieScatteringTex,
+			r_p, mu_p, mu_s_p, nu, ray_r_mu_intersects_ground,
+			single_mie_scattering);
+		vec3 shadow_transmittance =
+			GetTransmittance(p_atmospherePar, p_atmTransmittanceTex,
+			r, mu, p_shadowLength, ray_r_mu_intersects_ground);
+		scattering = scattering * shadow_transmittance;
+		single_mie_scattering = single_mie_scattering * shadow_transmittance;
+	}
+	
+	return scattering * RayleighPhaseFunction(nu) + single_mie_scattering * MiePhaseFunction(p_atmospherePar.mie_phase_function_g, nu);
+}
+
+float GetSunVisibility(vec3 p_point, vec3 p_sunDirection)
+{
+	vec3 p = p_point;// - kSphereCenter;
+	float p_dot_v = dot(p, p_sunDirection);
+	float p_dot_p = dot(p, p);
+	float ray_sphere_center_squared_distance = p_dot_p - p_dot_v * p_dot_v;
+	//float distance_to_intersection = -p_dot_v - sqrt(kSphereRadius * kSphereRadius - ray_sphere_center_squared_distance);
+	
+	//if(distance_to_intersection > 0.0)
+	{
+		// Compute the distance between the view ray and the sphere, and the
+		// corresponding (tangent of the) subtended angle. Finally, use this to
+		// compute an approximate sun visibility.
+		//float ray_sphere_distance = kSphereRadius - sqrt(ray_sphere_center_squared_distance);
+		//float ray_sphere_angular_distance = -ray_sphere_distance / p_dot_v;
+		//return smoothstep(1.0, 0.0, ray_sphere_angular_distance / atmScatteringParam.m_sunSize.x);
+	}
+	
+	return 1.0;
+}
+
+float GetSkyVisibility(vec3 p_point)
+{
+	vec3 p = p_point;// - kSphereCenter;
+	float p_dot_p = dot(p, p);
+	return 1.0;// + p.z / sqrt(p_dot_p) * kSphereRadius * kSphereRadius / p_dot_p;
+} 
+vec3 GetSolarRadiance(const AtmosphereParameters p_atmospherePar)
+{
+	return p_atmospherePar.solar_irradiance / (PI * p_atmospherePar.sun_angular_radius * p_atmospherePar.sun_angular_radius);
+}
+
+void main() 
+{
+	//vec2 texCoord = calcTexCoord();
+	vec3 cameraPosition = cameraPosVec.xyz / kLengthUnitInMeters;
+	
+	// Normalized view direction vector.
+	vec3 viewDirection = normalize(viewRay);
+	
+	// Tangent of the angle subtended by this fragment.
+	float fragment_angular_size = length(dFdx(viewRay) + dFdy(viewRay)) / length(viewRay);
+	
+	float shadow_length = 0.0;
+	
+	// Compute the distance between the view ray line and the Earth center,
+	// and the distance between the camera and the intersection of the view
+	// ray with the ground (or NaN if there is no intersection).
+	vec3 p = cameraPosition - atmScatteringParam.m_earthCenter;
+	float p_dot_v = dot(p, viewDirection);
+	float p_dot_p = dot(p, p);
+	float ray_earth_center_squared_distance = p_dot_p - p_dot_v * p_dot_v;
+	float distance_to_intersection = -p_dot_v - sqrt(atmScatteringParam.m_earthCenter.z * atmScatteringParam.m_earthCenter.z - ray_earth_center_squared_distance);
+
+	// Compute the radiance reflected by the ground, if the ray intersects it.
+	float ground_alpha = 0.0;
+	vec3 ground_radiance = vec3(0.0);
+	if(distance_to_intersection > 0.0)
+	{
+		vec3 point = cameraPosition + viewDirection * distance_to_intersection;
+		vec3 normal = normalize(point - atmScatteringParam.m_earthCenter);
+
+		// Compute the radiance reflected by the ground.
+		vec3 sky_irradiance;
+		vec3 sun_irradiance = GetSunAndSkyIrradiance(
+			atmScatteringParam.m_atmosphereParam,
+			point - atmScatteringParam.m_earthCenter,
+			normal,
+			directionalLight.m_direction,
+			sky_irradiance);
+			
+		ground_radiance = kGroundAlbedo * (1.0 / PI) * (sun_irradiance * GetSunVisibility(point, directionalLight.m_direction) + sky_irradiance * GetSkyVisibility(point));
+
+		vec3 transmittance;
+		vec3 in_scatter = GetSkyRadianceToPoint(
+			atmScatteringParam.m_atmosphereParam,
+			atmTransmitTexture,
+			atmScatteringTexture,
+			atmSingleMieTexture,
+			cameraPosition - atmScatteringParam.m_earthCenter,
+			point - atmScatteringParam.m_earthCenter,
+			shadow_length,
+			directionalLight.m_direction,
+			transmittance);
+			
+		ground_radiance = ground_radiance * transmittance + in_scatter;
+		ground_alpha = 1.0;
+	}
+	
+	// Compute the radiance of the sky.
+	vec3 transmittance;
+	vec3 radiance = GetSkyRadiance(
+		atmScatteringParam.m_atmosphereParam,
+		atmTransmitTexture,
+		atmScatteringTexture,
+		atmSingleMieTexture,
+		cameraPosition - atmScatteringParam.m_earthCenter,
+		viewDirection,
+		shadow_length,
+		directionalLight.m_direction,
+		transmittance);
+
+	// If the view ray intersects the Sun, add the Sun radiance.
+	if(dot(viewDirection, directionalLight.m_direction) > atmScatteringParam.m_sunSize.y)
+	{
+		radiance = radiance + transmittance * GetSolarRadiance(atmScatteringParam.m_atmosphereParam);
+	}
+	
+	radiance = mix(radiance, ground_radiance, ground_alpha);
+	
+	//color = vec4(vec3(1.0) - exp(-radiance / atmScatteringParam.m_whitePoint * directionalLight.m_intensity), 1.0);
+	color = vec4(radiance / atmScatteringParam.m_whitePoint * directionalLight.m_intensity * 1.0, 1.0);
+}

+ 18 - 0
Praxis3D/Data/Shaders/atmosphericScatteringPass_sky.vert

@@ -0,0 +1,18 @@
+#version 430 core
+
+uniform mat4 transposeViewMat;
+uniform mat4 atmScatProjMat;
+
+out vec3 viewRay;
+
+void main(void) 
+{	
+	// Determine texture coordinates
+	vec2 texCoord = vec2((gl_VertexID == 2) ?  2.0 :  0.0, (gl_VertexID == 1) ?  2.0 :  0.0);
+	
+	// Calculate the position, so that the triangle fills the whole screen
+	vec4 vertexPos = vec4(texCoord * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 1.0, 1.0);
+		
+	viewRay = (((transposeViewMat)) * vec4((atmScatProjMat * vertexPos).xyz, 0.0)).xyz;
+	gl_Position = vertexPos;
+}

+ 20 - 3
Praxis3D/Data/Shaders/finalPass.frag

@@ -30,6 +30,20 @@ vec3 reinhardToneMapping(vec3 p_color)
 	
 // Filmic tone mapping using an algorithm created by John Hable for Uncharted 2
 vec3 filmicToneMapping(vec3 p_color)
+{
+	// http://www.gdcvault.com/play/1012459/Uncharted_2__HDR_Lighting
+	// http://filmicgames.com/archives/75 - the coefficients are from here
+	float A = 0.15; // Shoulder Strength
+	float B = 0.50; // Linear Strength
+	float C = 0.10; // Linear Angle
+	float D = 0.20; // Toe Strength
+	float E = 0.02; // Toe Numerator
+	float F = 0.30; // Toe Denominator
+	
+	return ((p_color * (A * p_color + C * B) + D * E) / (p_color * (A * p_color + B) + D * F)) - E / F; // E/F = Toe Angle
+}
+
+vec3 Uncharted2ToneMapping(vec3 color)
 {
 	float A = 0.15;
 	float B = 0.50;
@@ -37,8 +51,11 @@ vec3 filmicToneMapping(vec3 p_color)
 	float D = 0.20;
 	float E = 0.02;
 	float F = 0.30;
-
-	return ((p_color*(A*p_color+C*B)+D*E)/(p_color*(A*p_color+B)+D*F))-E/F;
+	float W = 11.2;
+	color = ((color * (A * color + C * B) + D * E) / (color * (A * color + B) + D * F)) - E / F;
+	float white = ((W * (A * W + C * B) + D * E) / (W * (A * W + B) + D * F)) - E / F;
+	color /= white;
+	return color;
 }
 
 vec2 calcTexCoord(void)
@@ -69,7 +86,7 @@ void main(void)
 	
 	#ifdef ENABLE_FILMIC_TONE_MAPPING
 	// Perform filmic tonemapping on the final color
-	fragmentColor = filmicToneMapping(fragmentColor);
+	fragmentColor = Uncharted2ToneMapping(fragmentColor);
 	#endif
 	
 	// Perform gamma correction as the last step of the fragment color

+ 1 - 1
Praxis3D/Data/Shaders/finalPass.vert

@@ -6,5 +6,5 @@ void main(void)
 	vec2 texCoord = vec2((gl_VertexID == 2) ?  2.0 :  0.0, (gl_VertexID == 1) ?  2.0 :  0.0);
 	
 	// Calculate the position, so that the triangle fills the whole screen
-	gl_Position = vec4(texCoord * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 1.0, 1.0);
+	gl_Position = vec4(texCoord * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.5, 1.0);
 }

+ 17 - 4
Praxis3D/Data/Shaders/gaussianBlurHorizontal.frag

@@ -10,6 +10,9 @@ uniform ivec2 screenSize;
 
 uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
  
+//uniform float offset[3] = float[]( 0.0, 1.3846153846, 3.2307692308 );
+//uniform float weight[3] = float[]( 0.2270270270, 0.3162162162, 0.0702702703 );
+
 vec2 calcTexCoord(void)
 {
     return gl_FragCoord.xy / screenSize;
@@ -24,15 +27,25 @@ void main(void)
 	
 	float blurOffset = 1.0;
 	
-	vec2 tex_offset = 1.0 / screenSize; // gets size of single texel
+	// Calculate the size of a single pixel
+	vec2 texCoordOffset = 1.0 / screenSize;
+	
     fragColor = texture(inputColorMap, texCoord).rgb * weight[0]; // current fragment's contribution
 	
 	for(int i = 1; i < 5; ++i)
 	{
-		fragColor += texture(inputColorMap, texCoord + vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
-		fragColor += texture(inputColorMap, texCoord - vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
+		fragColor += texture(inputColorMap, texCoord + vec2(texCoordOffset.x * i, 0.0)).rgb * weight[i];
+		fragColor += texture(inputColorMap, texCoord - vec2(texCoordOffset.x * i, 0.0)).rgb * weight[i];
 	}
-		
+	
+	//FragmentColor = texture2D( image, vec2(gl_FragCoord)/1024.0 ) * weight[0];
+   /* for(int i=1; i<3; i++) 
+	{
+        fragColor +=
+            texture2D( inputColorMap, (texCoord + vec2(0.0, offset[i])) / 900.0) * weight[i];
+        fragColor +=
+            texture2D( inputColorMap, (texCoord - vec2(0.0, offset[i])) / 900.0) * weight[i];
+    }*/
 	/*
     blurTexCoords[ 0] = texCoord + vec2(-0.028 * blurOffset, 0.0);
     blurTexCoords[ 1] = texCoord + vec2(-0.024 * blurOffset, 0.0);

+ 5 - 3
Praxis3D/Data/Shaders/gaussianBlurVertical.frag

@@ -26,13 +26,15 @@ void main(void)
 	
 	float blurOffset = 1.0;
 	
-	vec2 tex_offset = 1.0 / screenSize; // gets size of single texel
+	// Calculate the size of a single pixel
+	vec2 texCoordOffset = 1.0 / screenSize;
+	
     fragColor = texture(inputColorMap, texCoord).rgb * weight[0]; // current fragment's contribution
 	
 	for(int i = 1; i < 5; ++i)
 	{
-		fragColor += texture(inputColorMap, texCoord + vec2(0.0, tex_offset.y * i)).rgb * weight[i];
-		fragColor += texture(inputColorMap, texCoord - vec2(0.0, tex_offset.y * i)).rgb * weight[i];
+		fragColor += texture(inputColorMap, texCoord + vec2(0.0, texCoordOffset.y * i)).rgb * weight[i];
+		fragColor += texture(inputColorMap, texCoord - vec2(0.0, texCoordOffset.y * i)).rgb * weight[i];
 	}
 		
 	/*

+ 1 - 0
Praxis3D/Data/Shaders/geometryPass.vert

@@ -22,6 +22,7 @@ uniform mat4 modelMat;
 uniform mat4 viewMat;
 uniform mat4 modelViewMat;
 uniform mat4 projMat;
+uniform mat4 viewProjMat;
 uniform vec3 cameraPosVec;
 uniform float textureTilingFactor;
 uniform float heightScale;

+ 15 - 8
Praxis3D/Data/Shaders/hdrMappingPass.frag

@@ -1,8 +1,8 @@
 #version 430 core
 
 //#define AVG_INTENDED_BRIGHTNESS 0.2
-#define MIN_INTENDED_BRIGHTNESS 0.01
-#define MAX_INTENDED_BRIGHTNESS 10.0
+#define MIN_INTENDED_BRIGHTNESS 0.05
+#define MAX_INTENDED_BRIGHTNESS 5.0
 
 #define MAX_NUM_POINT_LIGHTS 20
 #define MAX_NUM_SPOT_LIGHTS 10
@@ -34,8 +34,8 @@ float getBrightestColor(vec3 p_color)
 	return max(p_color.x, max(p_color.y, p_color.z));
 }
 
-// Calculates a brightness value from a color
-float calcBrightness(vec3 p_color)
+// Calculates a brightness value (luma) from a color
+float calcLuma(vec3 p_color)
 {
 	return dot(p_color, vec3(0.2126, 0.7152, 0.0722));
 }
@@ -68,11 +68,18 @@ void main(void)
 	
 	// Adjust the fragment brightness based on average
 	fragmentColor = brightnessMapping(fragmentColor, avgBrightness);
+	emissiveColor = brightnessMapping(emissiveColor, avgBrightness);
 	
-	// If a fragment brightness exceeds a value of 1.0 after an HDR Mapping, 
-	// add it to the emissive buffer, as a bloom effect
-	if(calcBrightness(fragmentColor) > 1.0)
-		emissiveColor += fragmentColor;
+	// Calculate luminance of the fragment after HDR mapping
+	float luminance = max(0.0, calcLuma(fragmentColor) - 1.0);
+	
+	// Get bright fragments
+	vec3 fragmentBrightness = min(luminance * fragmentColor, fragmentColor);
+	
+	// Add bright fragments to the emissive buffer for bloom effect
+	emissiveColor += fragmentBrightness;
+	//fragmentColor = max(fragmentColor * (1.0 - luminance), vec3(0.0));
+	//fragmentColor = max(fragmentColor - fragmentBrightness, vec3(0.0));
 	
 	emissiveBuffer = vec4(emissiveColor, 1.0);
 	colorBuffer = vec4(fragmentColor, 1.0);

+ 3 - 3
Praxis3D/Data/Shaders/hdrMappingPass.vert

@@ -33,8 +33,8 @@ float calcBrightnessLinear(vec3 p_color)
 	return (p_color.x + p_color.y + p_color.z) / 3.0;
 }
 
-// Calculates a brightness value from a color
-float calcBrightness(vec3 p_color)
+// Calculates a brightness value (luma) from a color
+float calcLuma(vec3 p_color)
 {
 	return dot(p_color, vec3(0.2126, 0.7152, 0.0722));
 }
@@ -46,7 +46,7 @@ void main(void)
 	// Get maximum mipmap level (1x1) of a framebuffer
 	float exposureMipmapLevel = calcMaxMipmapLevel(screenSize);
 	// Get the current (previous frame) average brightness
-	float avgBrightnessPrevFrame = calcBrightness(textureLod(finalColorMap, vec2(0.0), exposureMipmapLevel).xyz);
+	float avgBrightnessPrevFrame = calcLuma(textureLod(finalColorMap, vec2(0.0), exposureMipmapLevel).xyz);
 	// Perform a linear interpolation between current and previous brightness based on delta time
 	screenBrightness = mix(screenBrightness, avgBrightnessPrevFrame, deltaTimeS / eyeAdaptionRate);
 	

+ 434 - 0
Praxis3D/Data/Shaders/lightPass - Copy.frag

@@ -0,0 +1,434 @@
+#version 430 core
+
+#define AVG_INTENDED_BRIGHTNESS 0.5
+#define MIN_INTENDED_BRIGHTNESS 0.005
+#define MAX_INTENDED_BRIGHTNESS 5.0
+
+#define MAX_NUM_POINT_LIGHTS 20
+#define MAX_NUM_SPOT_LIGHTS 10
+#define PI 3.1415926535
+
+layout(location = 0) out vec4 emissiveBuffer;
+layout(location = 1) out vec3 finalBuffer;
+
+in float avgBrightness;
+
+struct DirectionalLight
+{
+    vec3 m_color;
+    vec3 m_direction;
+    float m_intensity;
+};
+
+struct PointLight
+{
+    vec3 m_color;
+    vec3 m_position;
+	
+    float m_attenConstant;
+    float m_attenLinear;
+    float m_attenQuad;
+    float m_intensity;
+};
+struct SpotLight
+{
+    vec3 m_color;
+    vec3 m_position;
+    vec3 m_direction;
+	
+    float m_attenConstant;
+    float m_attenLinear;
+    float m_attenQuad;
+	
+    float m_intensity;
+    float m_cutoffAngle;
+};
+
+uniform sampler2D positionMap;
+uniform sampler2D diffuseMap;
+uniform sampler2D normalMap;
+uniform sampler2D emissiveMap;
+uniform sampler2D matPropertiesMap;
+
+uniform samplerCube staticEnvMap;
+
+uniform mat4 modelViewMat;
+uniform mat4 viewMat;
+uniform vec3 cameraPosVec;
+uniform ivec2 screenSize;
+uniform float gamma;
+
+uniform int numPointLights;
+uniform int numSpotLights;
+
+uniform DirectionalLight directionalLight;
+
+// Using uniform buffer objects to pass light arrays and std140 layout for consistent variable spacing inside the buffer.
+// Array size is fixed, but is updated partially, with only the lights that are being used, passing number of lights as uniform.
+layout (std140) uniform PointLights
+{
+	PointLight pointLights[MAX_NUM_POINT_LIGHTS];
+};
+layout (std140) uniform SpotLights
+{
+	SpotLight spotLights[MAX_NUM_SPOT_LIGHTS];
+};
+
+vec3 worldPos;
+vec3 normal;
+vec3 fragmentToEye;
+
+float getBrightestColor(vec3 p_color)
+{
+	return max(p_color.x, max(p_color.y, p_color.z));
+}
+
+// Calculates a brightness value from a color
+float calcBrightness(vec3 p_color)
+{
+	return dot(p_color, vec3(0.2126, 0.7152, 0.0722));
+}
+
+// Adjusts an RGB color based on the average brightness (exposure)
+// by making overall brightness match the average intended brightness (AVG_INTENDED_BRIGHTNESS)
+vec3 brightnessMapping(vec3 p_color, float p_exposure)
+{
+	return p_color * clamp(AVG_INTENDED_BRIGHTNESS / p_exposure, MIN_INTENDED_BRIGHTNESS, MAX_INTENDED_BRIGHTNESS);
+}
+
+float saturate(float p_value)
+{
+	return clamp(p_value, 0.0f, 1.0f);
+}
+
+float G1V(float p_dotNV, float p_k)
+{
+    return 1.0f / (p_dotNV * (1.0f - p_k) + p_k);
+}
+
+/*float MicroFacetDistr_GGX(vec3 p_normal, vec3 p_halfVec, float p_roughnessSqrt)
+{
+	float NdotH = dot(p_normal, p_halfVec);
+	if(NdotH > 0.0)
+	{
+		float NdotHSqrt = NdotH * NdotH;
+		float microfacetDstrb = NdotHSqrt * p_roughnessSqrt + (1.0 - NdotHSqrt);
+		return (NdotH * p_roughnessSqrt) / (3.14 * microfacetDstrb * microfacetDstrb);
+	}
+	else
+		return 0.0;
+}
+
+float GeometryAtten_GGX(vec3 p_fragToEye, vec3 p_normal, vec3 p_halfVec, float p_roughnessSqrt)
+{
+    float VdotH = clamp(dot(p_fragToEye, p_halfVec), 0.0, 1.0);
+    float VdotN = clamp(dot(p_fragToEye, p_normal), 0.0, 1.0);
+	
+	float geoFactor = (VdotH / VdotN);
+	if(geoFactor > 0.0)
+	{
+		float VdotHSqrt = VdotH * VdotH;
+		float geoAtt = (1.0 - VdotHSqrt) / VdotHSqrt;
+		return 2.0 / (1.0 + sqrt(1.0 + p_roughnessSqrt * geoAtt));
+	}
+	else
+		return 0.0;
+}
+
+vec3 Fresnel_Schlick(float p_cosT, vec3 p_F0)
+{
+	return F0 + (vec3(1.0) - F0) * pow(1.0 - p_cosT, 5.0);
+}*/
+
+vec3 computePBRLighting(vec3 lightPos, vec3 lightColor, vec3 position, vec3 N, vec3 V, vec3 albedo, float roughness, vec3 F0) 
+{
+
+	float alpha = roughness*roughness;
+	vec3 L = normalize(lightPos - position);
+	vec3 H = normalize (V + L);
+
+	float dotNL = clamp (dot (N, L), 0.0, 1.0);
+	float dotNV = clamp (dot (N, V), 0.0, 1.0);
+	float dotNH = clamp (dot (N, H), 0.0, 1.0);
+	float dotLH = clamp (dot (L, H), 0.0, 1.0);
+
+	float D, vis;
+	vec3 F;
+
+	// NDF : GGX
+	float alphaSqr = alpha*alpha;
+	float pi = 3.1415926535;
+	float denom = dotNH * dotNH *(alphaSqr - 1.0) + 1.0;
+	D = alphaSqr / (pi * denom * denom);
+
+	// Fresnel (Schlick)
+	float dotLH5 = pow (1.0 - dotLH, 5.0);
+	F = F0 + (1.0 - F0)*(dotLH5);
+
+	// Visibility term (G) : Smith with Schlick's approximation
+	float k = alpha / 2.0;
+	vis = G1V (dotNL, k) * G1V (dotNV, k);
+
+	vec3 specular = /*dotNL **/ D * F * vis;
+
+	vec3 ambient = vec3(.01);
+
+	float invPi = 0.31830988618;
+	vec3 diffuse = (albedo * invPi);
+
+
+	return (diffuse + specular) * lightColor * dotNL ;
+}
+
+vec3 LightingFuncGGX_REF(vec3 p_normal, vec3 p_viewDir, vec3 p_lightDir, float p_roughnessSqrt, vec3 p_F0)
+{
+	// Calculate half vector between view and light directions
+    vec3 halfVector = normalize(p_viewDir + p_lightDir);
+	
+	// Calculate required dot products
+    float dotNL = saturate(dot(p_normal, p_lightDir));
+    float dotNV = saturate(dot(p_normal, p_viewDir));
+    float dotNH = saturate(dot(p_normal, halfVector));
+    float dotLH = saturate(dot(p_lightDir, halfVector));
+
+	// Calculate microfacet distributions (based on roughness)
+	// Using GGX normal distribution function
+    float RoughnessPow4 = p_roughnessSqrt * p_roughnessSqrt;
+    float denom = dotNH * dotNH * (RoughnessPow4 - 1.0) + 1.0f;
+    float D = RoughnessPow4 / (PI * denom * denom);
+
+	// Calculate fresnel effect 
+	// Using Schlick's approximation
+    float dotLH5 = pow(1.0f - dotLH, 5);
+	vec3 F = p_F0 + (vec3(1.0f) - p_F0) * (dotLH5);
+
+	// Calculate geometric attenuation (or visibility term - self shadowing of microfacets)
+	// Using Smith with Schlick's approximation
+    float k2 = p_roughnessSqrt / 2.0f;
+    float G = G1V(dotNL, k2) * G1V(dotNV, k2);
+	
+	// Multiple all the terms together
+	return dotNL * D * F * G;
+}
+
+float SpecGGX(vec3 N, vec3 V, vec3 L, float roughness, float F0 )
+{
+	float SqrRoughness = roughness*roughness;
+
+	vec3 H = normalize(V + L);
+
+	float NdotL = clamp(dot(N,L),0.0,1.0);
+	float NdotV = clamp(dot(N,V),0.0,1.0);
+	float NdotH = clamp(dot(N,H),0.0,1.0);
+	float LdotH = clamp(dot(L,H),0.0,1.0);
+
+	float RoughnessPow4 = SqrRoughness * SqrRoughness;
+	float denom = NdotH * NdotH * (RoughnessPow4 - 1.0) + 1.0;
+	float D = RoughnessPow4 / (PI * denom * denom);
+
+	float LdotH5 = 1.0 - LdotH;
+    LdotH5 = LdotH5 * LdotH5 * LdotH5 * LdotH5 * LdotH5;
+	float F = F0 + (1.0 - F0) * (LdotH5);
+
+	float k = SqrRoughness/2.0;
+	float G = G1V(NdotL, k) * G1V(NdotV, k);
+
+	float specular = NdotL * D * F * G;
+    
+	return specular;
+}
+
+vec3 calcLightColor(vec3 p_normal, vec3 p_fragToEye, vec3 p_lightColor, vec3 p_lightDirection, vec3 p_F0, float p_diffuseAmount, float p_roughnessSqrt)
+{	
+	// Get specular and diffuse lighting
+	vec3 specularColor = LightingFuncGGX_REF(p_normal, p_fragToEye, p_lightDirection, p_roughnessSqrt, p_F0);
+    vec3 diffuseColor = vec3(clamp(dot(p_normal, p_lightDirection), 0.0, 1.0));
+	
+	// Add specular and diffuse together, and multiply it by the color of the light
+	return (specularColor + diffuseColor * p_diffuseAmount) * p_lightColor;
+}
+
+/*vec3 calcLightColorOld(vec3 p_lightColor, vec3 p_lightDirection)
+{
+    // Get angle between normal and light direction
+    //float NdotL = max(dot(normal, -p_lightDirection), 0.0);
+	float NdotL = clamp( dot( normal, -p_lightDirection ), 0.0, 1.0 );
+    
+    float specular = 0.0;
+    if(NdotL > 0.0)
+    {
+        // Calculate neccessary values
+        vec3 halfVector = normalize(fragmentToEye - p_lightDirection );
+		
+		float NdotH = clamp( dot( normal, halfVector ), 0.0, 1.0 );
+		float NdotV = clamp( dot( normal, fragmentToEye ), 0.0, 1.0 );
+		float VdotH = clamp( dot( fragmentToEye, halfVector ), 0.0, 1.0 );
+		        
+        // Calculate geometric attenuation (self shadowing of microfacets)
+        float NH2 = 2.0 * NdotH;
+        float g1 = (NH2 * NdotV) / VdotH;
+        float g2 = (NH2 * NdotL) / VdotH;
+        float geoAtt = min(1.0, min(g1, g2));
+     	
+		// Calculate microfacet distributions (roughness)
+		// Using Beckmann distribution function
+        float r1 = 1.0 / ( 4.0 * roughnessSqrt * pow(NdotH, 4.0));
+        float r2 = (NdotH * NdotH - 1.0) / (roughnessSqrt * NdotH * NdotH);
+        float microfacetDstrb = r1 * exp(r2);
+        
+		// Calculate fresnel effect (using Schlick's approximation)
+        float fresnel = pow(1.0 - VdotH, 5.0);
+        fresnel *= (1.0 - F0);
+        fresnel += F0;
+        
+		// Calculate specular component
+        specular = max((fresnel * geoAtt * microfacetDstrb) / (NdotV * NdotL * 3.14), 0.0);
+        //specular = (fresnel * geoAtt * microfacetDstrb) / (NdotV * NdotL * 3.14);
+		
+		//F0:"NdotL * (cSpecular * Rs + cDiffuse * (1-f0))"
+		
+		// Combine specular and diffuse components
+		return p_lightColor * NdotL * (k + specular * (1.0 - k));
+		//return p_lightColor * NdotL * specular;//(k + specular * (1.0 - k));
+    }
+	else
+	{
+		return vec3(0.0, 0.0, 0.0);
+	}
+}*/
+
+vec2 calcTexCoord(void)
+{
+    return gl_FragCoord.xy / screenSize;
+}
+
+void main(void) 
+{
+	// Calculate screen-space texture coordinates, for buffer access
+	vec2 texCoord = calcTexCoord();
+	
+	// Get the emissive texture color
+	//vec3 emissiveColor = texture(emissiveMap, texCoord).xyz;
+	vec3 emissiveColor = pow(texture(emissiveMap, texCoord).xyz, vec3(gamma));
+	
+	//if(getBrightestColor(emissiveColor) > 0.05)
+	//{
+	//	finalBuffer = emissiveColor;
+	//	discard;
+	//}
+	
+	// Get diffuse color (full-bright) from diffuse buffer and convert it to linear space
+	vec3 diffuseColor = pow(texture(diffuseMap, texCoord).xyz, vec3(gamma));
+	// Get pixel's position in world space
+	vec3 worldPos = texture(positionMap, texCoord).xyz;
+	// Get normal (in world space) and normalize it to minimize floating point approximation errors
+	vec3 normal = normalize(texture(normalMap, texCoord).xyz);
+	// Get material properties
+	vec4 matProperties = texture(matPropertiesMap, texCoord).xyzw;
+	
+	// Extract roughness and metalness values
+	float roughnessVar = matProperties.x;
+	float metalness = matProperties.y;
+	
+	// Squaring roughness gives better visual results
+	float roughnessSqrt = roughnessVar * roughnessVar;
+	
+	// Calculate view direction (fragment to eye vector)
+	fragmentToEye = normalize(cameraPosVec - worldPos);
+	
+	float ior = mix(1.5, 40.5, metalness);
+	ior = 0.04;//40.5;
+	
+	float F0 = abs((1.0 - ior) / (1.0 + ior));
+	F0 = F0 * F0;
+	//vec3 F0vec = mix(vec3(F0), diffuseColor, metalness);
+	vec3 F0vec = mix(vec3(0.04), diffuseColor, metalness);
+		
+	// Fresnel for the diffuse color
+    float NdotV = clamp(dot(normal, fragmentToEye), 0.0, 1.0);
+	NdotV = pow(1.0 - NdotV, 5.0);
+	float diffuseAmount = 1.0 - (metalness + (1.0 - metalness) * (NdotV));
+	
+	
+	// ior = from 1.2 to 10.0
+	
+	// Declare final color of the fragment and add directional light to it
+	vec3 finalLightColor = calcLightColor(normal, fragmentToEye, directionalLight.m_color, normalize(-directionalLight.m_direction), F0vec, diffuseAmount, roughnessSqrt) * directionalLight.m_intensity;
+	
+	//vec3 finalLightColor = calcLightColor(normal, fragmentToEye, vec3(1.0, 1.0, 1.0), normalize(vec3(8.0, 10.0, 5.0)), F0vec, diffuseAmount, roughnessSqrt) * 1.0;
+	
+	for(int i = 0; i < numPointLights; i++)
+	{		
+		// Get light direction, extract length from it and normalize for usage as direction vector
+		vec3 lightDirection =  pointLights[i].m_position - worldPos;
+		float lightDistance = length(lightDirection);
+		lightDirection = normalize(lightDirection);
+		
+		// Add up constant, linear and quadratic attenuation
+		float attenuation = pointLights[i].m_attenConstant + 
+							pointLights[i].m_attenLinear * lightDistance + 
+							pointLights[i].m_attenQuad * lightDistance * lightDistance;
+							
+		// Light color multiplied by intensity and divided by attenuation
+		finalLightColor += (calcLightColor(normal, fragmentToEye, pointLights[i].m_color, lightDirection, F0vec, diffuseAmount, roughnessSqrt) * pointLights[i].m_intensity) / attenuation;
+	}
+	
+	for(int i = 0; i < numSpotLights; i++)
+	{			
+		// Calculate direction from position of light to current pixel
+		vec3 lightToFragment = normalize(worldPos - spotLights[i].m_position);
+		
+		// Get dot product of light direction and direction of light to pixel, and use it as a factor for light strength
+		float spotLightFactor = dot(lightToFragment, spotLights[i].m_direction);
+		
+		// Early bail if pixel is outside of the cone of spot light
+		if(spotLightFactor > spotLights[i].m_cutoffAngle)
+		{
+			// Get light direction, extract length from it and normalize for usage as direction vector
+			vec3 lightDirection =  spotLights[i].m_position - worldPos;
+			float lightDistance = length(lightDirection);
+			lightDirection = normalize(lightDirection);
+			
+			// Add up constant, linear and quadratic attenuation
+			float attenuation = spotLights[i].m_attenConstant + 
+								spotLights[i].m_attenLinear * lightDistance + 
+								spotLights[i].m_attenQuad * lightDistance * lightDistance;
+			
+			// Light color multiplied by intensity
+			vec3 lightColor = (calcLightColor(normal, fragmentToEye, spotLights[i].m_color, lightDirection, F0vec, diffuseAmount, roughnessSqrt) * spotLights[i].m_intensity);
+			
+			// Light restriction from cone
+			float coneAttenuation = (1.0 - (1.0 - spotLightFactor) * 1.0 / (1.0 - spotLights[i].m_cutoffAngle));
+			
+			finalLightColor += (lightColor / attenuation) * coneAttenuation;
+		}
+	}
+	
+	//emissiveBuffer = vec4(emissive, 0.0);	
+	
+	// Multiply the diffuse color by the amount of light the current pixel receives
+	diffuseColor = diffuseColor * finalLightColor;
+	
+	// Adjust the fragment brightness based on average
+	diffuseColor = brightnessMapping(diffuseColor, avgBrightness);
+	
+	// If a fragment brightness exceeds a value of 1.0 after an HDR Mapping, 
+	// add it to the emissive buffer, as a bloom effect
+	//emissiveBuffer = vec4(max(diffuseColor - vec3(1.0), vec3(0.0)), 1.0);
+	
+	if(calcBrightness(diffuseColor) > 1.0)
+		emissiveColor += diffuseColor;
+	
+	emissiveBuffer = vec4(emissiveColor, 1.0);
+	finalBuffer = diffuseColor + emissiveColor;
+	//finalBuffer = vec3(0.0, 1.0, 0.0);//diffuseColor * finalLightColor;
+	//finalBuffer = simpleToneMapping(diffuseColor * finalLightColor, 2.2);
+	//finalBuffer = vec3(roughnessVar, roughnessVar, roughnessVar);
+	//finalBuffer = vec4(texture(staticEnvMap, vec3(0.0, 0.0, 0.0)).xyz, 1.0);
+	//finalBuffer = vec4(metallic, metallic, metallic, 1.0);
+	//finalBuffer = vec4(pow(mix(diffuseColor, vec3(0.0, 0.0, 0.0), 1.0 - metallic) * finalLightColor, vec3(1.0 / 2.2)), 1.0);
+	//finalBuffer = vec4(pow(finalLightColor, vec3(1.0 / 2.2)), 1.0);
+	//finalBuffer = vec4(pow(diffuseColor, vec3(1.0 / 2.2)), 1.0);
+	//finalBuffer = vec4(texture(normalMap, texCoord).xyz, 1.0);
+	//finalBuffer = vec4(roughnessVar, roughnessVar, roughnessVar, 1.0);
+}

+ 22 - 0
Praxis3D/Data/Shaders/lightPass - Copy.vert

@@ -0,0 +1,22 @@
+#version 430 core
+
+#define ENABLE_HDR
+
+layout(std430, binding = 0) buffer HDRBuffer
+{
+	float screenBrightness;
+};
+
+out float avgBrightness;
+
+void main(void) 
+{
+	// Send average screen brightness to fragment shader for HDR Mapping
+	avgBrightness = screenBrightness;
+
+	// Determine texture coordinates
+	vec2 texCoord = vec2((gl_VertexID == 2) ?  2.0 :  0.0, (gl_VertexID == 1) ?  2.0 :  0.0);
+	
+	// Calculate the position, so that the triangle fills the whole screen
+	gl_Position = vec4(texCoord * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0);
+}

+ 122 - 223
Praxis3D/Data/Shaders/lightPass.frag

@@ -1,13 +1,28 @@
 #version 430 core
 
+#define AVG_INTENDED_BRIGHTNESS 0.5
+#define MIN_INTENDED_BRIGHTNESS 0.001
+#define MAX_INTENDED_BRIGHTNESS 100.0
+
 #define MAX_NUM_POINT_LIGHTS 20
 #define MAX_NUM_SPOT_LIGHTS 10
 #define PI 3.1415926535
 
-layout(location = 0) out vec4 emissiveBuffer;
-layout(location = 1) out vec3 finalBuffer;
+const vec3 g_sunNoonColor = vec3(1.0, 1.0, 1.0);
+const vec3 g_sunSetColor = vec3(1.0, 0.6, 0.2);
+
+const float g_sunNoonIntensityMod = 1.0;
+const float g_sunSetIntensityMod = 0.0;
+
+layout(std430, binding = 0) buffer HDRBuffer
+{
+	float screenBrightness;
+};
 
-//in vec2 texCoord;
+//layout(location = 0) out vec4 emissiveBuffer;
+layout(location = 0) out vec4 colorBuffer;
+
+in float avgBrightness;
 
 struct DirectionalLight
 {
@@ -43,9 +58,10 @@ struct SpotLight
 uniform sampler2D positionMap;
 uniform sampler2D diffuseMap;
 uniform sampler2D normalMap;
+uniform sampler2D emissiveMap;
 uniform sampler2D matPropertiesMap;
 
-uniform samplerCube staticEnvMap;
+//uniform samplerCube staticEnvMap;
 
 uniform mat4 modelViewMat;
 uniform mat4 viewMat;
@@ -73,207 +89,102 @@ vec3 worldPos;
 vec3 normal;
 vec3 fragmentToEye;
 
-float saturate(float p_value)
+float getBrightestColor(vec3 p_color)
 {
-	return clamp(p_value, 0.0f, 1.0f);
+	return max(p_color.x, max(p_color.y, p_color.z));
 }
 
-float G1V(float p_dotNV, float p_k)
+// Calculates a brightness value from a color
+float calcBrightness(vec3 p_color)
 {
-    return 1.0f / (p_dotNV * (1.0f - p_k) + p_k);
+	return dot(p_color, vec3(0.2126, 0.7152, 0.0722));
 }
 
-/*float MicroFacetDistr_GGX(vec3 p_normal, vec3 p_halfVec, float p_roughnessSqrt)
+// Adjusts an RGB color based on the average brightness (exposure)
+// by making overall brightness match the average intended brightness (AVG_INTENDED_BRIGHTNESS)
+vec3 brightnessMapping(vec3 p_color, float p_exposure)
 {
-	float NdotH = dot(p_normal, p_halfVec);
-	if(NdotH > 0.0)
-	{
-		float NdotHSqrt = NdotH * NdotH;
-		float microfacetDstrb = NdotHSqrt * p_roughnessSqrt + (1.0 - NdotHSqrt);
-		return (NdotH * p_roughnessSqrt) / (3.14 * microfacetDstrb * microfacetDstrb);
-	}
-	else
-		return 0.0;
+	return p_color * clamp(AVG_INTENDED_BRIGHTNESS / p_exposure, MIN_INTENDED_BRIGHTNESS, MAX_INTENDED_BRIGHTNESS);
 }
 
-float GeometryAtten_GGX(vec3 p_fragToEye, vec3 p_normal, vec3 p_halfVec, float p_roughnessSqrt)
+float saturate(float p_value)
 {
-    float VdotH = clamp(dot(p_fragToEye, p_halfVec), 0.0, 1.0);
-    float VdotN = clamp(dot(p_fragToEye, p_normal), 0.0, 1.0);
-	
-	float geoFactor = (VdotH / VdotN);
-	if(geoFactor > 0.0)
-	{
-		float VdotHSqrt = VdotH * VdotH;
-		float geoAtt = (1.0 - VdotHSqrt) / VdotHSqrt;
-		return 2.0 / (1.0 + sqrt(1.0 + p_roughnessSqrt * geoAtt));
-	}
-	else
-		return 0.0;
+	return clamp(p_value, 0.0f, 1.0f);
 }
 
-vec3 Fresnel_Schlick(float p_cosT, vec3 p_F0)
-{
-	return F0 + (vec3(1.0) - F0) * pow(1.0 - p_cosT, 5.0);
-}*/
-
-vec3 computePBRLighting(vec3 lightPos, vec3 lightColor, vec3 position, vec3 N, vec3 V, vec3 albedo, float roughness, vec3 F0) 
+// Calculates microfacet distribution (based on roughness)
+// Using GGX normal distribution function
+float DistributionGGX(vec3 p_normal, vec3 p_halfVector, float p_roughness)
 {
-
-	float alpha = roughness*roughness;
-	vec3 L = normalize(lightPos - position);
-	vec3 H = normalize (V + L);
-
-	float dotNL = clamp (dot (N, L), 0.0, 1.0);
-	float dotNV = clamp (dot (N, V), 0.0, 1.0);
-	float dotNH = clamp (dot (N, H), 0.0, 1.0);
-	float dotLH = clamp (dot (L, H), 0.0, 1.0);
-
-	float D, vis;
-	vec3 F;
-
-	// NDF : GGX
-	float alphaSqr = alpha*alpha;
-	float pi = 3.1415926535;
-	float denom = dotNH * dotNH *(alphaSqr - 1.0) + 1.0;
-	D = alphaSqr / (pi * denom * denom);
-
-	// Fresnel (Schlick)
-	float dotLH5 = pow (1.0 - dotLH, 5.0);
-	F = F0 + (1.0 - F0)*(dotLH5);
-
-	// Visibility term (G) : Smith with Schlick's approximation
-	float k = alpha / 2.0;
-	vis = G1V (dotNL, k) * G1V (dotNV, k);
-
-	vec3 specular = /*dotNL **/ D * F * vis;
-
-	vec3 ambient = vec3(.01);
-
-	float invPi = 0.31830988618;
-	vec3 diffuse = (albedo * invPi);
-
-
-	return (diffuse + specular) * lightColor * dotNL ;
+	float roughnessSquaredSquared = p_roughness * p_roughness * p_roughness * p_roughness;
+    float NdotH = max(dot(p_normal, p_halfVector), 0.0);
+    float NdotH2 = NdotH * NdotH;
+	
+    float denominator = (NdotH2 * (roughnessSquaredSquared - 1.0) + 1.0);
+    denominator = PI * denominator * denominator;
+	
+    return roughnessSquaredSquared / denominator;
 }
 
-vec3 LightingFuncGGX_REF(vec3 p_normal, vec3 p_viewDir, vec3 p_lightDir, float p_roughnessSqrt, vec3 p_F0)
+
+// Geometry attenuation (Smith with Schlick's approximation)
+float GeometrySchlickGGX(float p_NdotV, float p_roughness)
 {
-	// Calculate half vector between view and light directions
-    vec3 halfVector = normalize(p_viewDir + p_lightDir);
+    float roughness = (p_roughness + 1.0);
+    float k = (roughness * roughness) / 8.0;
 	
-	// Calculate required dot products
-    float dotNL = saturate(dot(p_normal, p_lightDir));
-    float dotNV = saturate(dot(p_normal, p_viewDir));
-    float dotNH = saturate(dot(p_normal, halfVector));
-    float dotLH = saturate(dot(p_lightDir, halfVector));
-
-	// Calculate microfacet distributions (based on roughness)
-	// Using GGX normal distribution function
-    float RoughnessPow4 = p_roughnessSqrt * p_roughnessSqrt;
-    float denom = dotNH * dotNH * (RoughnessPow4 - 1.0) + 1.0f;
-    float D = RoughnessPow4 / (PI * denom * denom);
-
-	// Calculate fresnel effect 
-	// Using Schlick's approximation
-    float dotLH5 = pow(1.0f - dotLH, 5);
-	vec3 F = p_F0 + (vec3(1.0f) - p_F0) * (dotLH5);
+    return p_NdotV / (p_NdotV * (1.0 - k) + k);
+}
 
-	// Calculate geometric attenuation (or visibility term - self shadowing of microfacets)
-	// Using Smith with Schlick's approximation
-    float k2 = p_roughnessSqrt / 2.0f;
-    float G = G1V(dotNL, k2) * G1V(dotNV, k2);
+// Calculates geometric attenuation (or visibility term - self shadowing of microfacets)
+float GeometrySmith(vec3 p_normal, vec3 p_fragToEye, vec3 L, float p_roughness)
+{
+    float NdotV = max(dot(p_normal, p_fragToEye), 0.0);
+    float NdotL = max(dot(p_normal, L), 0.0);
+    float ggx2  = GeometrySchlickGGX(NdotV, p_roughness);
+    float ggx1  = GeometrySchlickGGX(NdotL, p_roughness);
 	
-	// Multiple all the terms together
-	return dotNL * D * F * G;
+    return ggx1 * ggx2;
 }
 
-float SpecGGX(vec3 N, vec3 V, vec3 L, float roughness, float F0 )
+// Calculates fresnel effect using Schlick's approximation
+vec3 fresnelSchlick(float p_cosTheta, vec3 p_F0)
 {
-	float SqrRoughness = roughness*roughness;
-
-	vec3 H = normalize(V + L);
-
-	float NdotL = clamp(dot(N,L),0.0,1.0);
-	float NdotV = clamp(dot(N,V),0.0,1.0);
-	float NdotH = clamp(dot(N,H),0.0,1.0);
-	float LdotH = clamp(dot(L,H),0.0,1.0);
-
-	float RoughnessPow4 = SqrRoughness * SqrRoughness;
-	float denom = NdotH * NdotH * (RoughnessPow4 - 1.0) + 1.0;
-	float D = RoughnessPow4 / (PI * denom * denom);
-
-	float LdotH5 = 1.0 - LdotH;
-    LdotH5 = LdotH5 * LdotH5 * LdotH5 * LdotH5 * LdotH5;
-	float F = F0 + (1.0 - F0) * (LdotH5);
-
-	float k = SqrRoughness/2.0;
-	float G = G1V(NdotL, k) * G1V(NdotV, k);
-
-	float specular = NdotL * D * F * G;
-    
-	return specular;
-}
+    return p_F0 + (1.0 - p_F0) * pow(1.0 - p_cosTheta, 5.0);
+} 
 
-vec3 calcLightColor(vec3 p_normal, vec3 p_fragToEye, vec3 p_lightColor, vec3 p_lightDirection, vec3 p_F0, float p_diffuseAmount, float p_roughnessSqrt)
+vec3 calcLightColor(vec3 p_albedoColor, vec3 p_normal, vec3 p_fragToEye, vec3 p_lightColor, vec3 p_lightDirection, float p_lightDistance, vec3 p_F0, float p_roughness, float p_metalic)
 {	
-	// Get specular and diffuse lighting
+	/*/ Get specular and diffuse lighting
 	vec3 specularColor = LightingFuncGGX_REF(p_normal, p_fragToEye, p_lightDirection, p_roughnessSqrt, p_F0);
     vec3 diffuseColor = vec3(clamp(dot(p_normal, p_lightDirection), 0.0, 1.0));
 	
 	// Add specular and diffuse together, and multiply it by the color of the light
-	return (specularColor + diffuseColor * p_diffuseAmount) * p_lightColor;
+	return (specularColor + diffuseColor * p_diffuseAmount) * p_lightColor;*/
+	
+	// Calculate per-light radiance
+	vec3 halfVector = normalize(p_fragToEye + p_lightDirection);
+	float attenuation = 1.0 / (p_lightDistance * p_lightDistance);
+	vec3 radiance = p_lightColor * attenuation;
+	
+	// Calculate Cook-Torrance BRDF
+	float NDF = DistributionGGX(p_normal, halfVector, p_roughness);
+	float G = GeometrySmith(p_normal, p_fragToEye, p_lightDirection, p_roughness);
+	vec3 F = fresnelSchlick(clamp(dot(halfVector, p_fragToEye), 0.0, 1.0), p_F0);
+	
+	vec3 kS = F;
+	vec3 kD = vec3(1.0) - kS;
+	kD *= 1.0 - p_metalic;
+	
+	vec3 numerator = NDF * G * F;
+	float denominator = 4.0 * max(dot(p_normal, p_fragToEye), 0.0) * max(dot(p_normal, p_lightDirection), 0.0);
+	vec3 specular = numerator / max(denominator, 0.001);
+	
+	// Combine diffuse, specular, radiance with albedo color and return it
+	float NdotL = max(dot(p_normal, p_lightDirection), 0.0);
+	return (kD * p_albedoColor / PI + specular) * radiance * NdotL;
 }
 
-/*vec3 calcLightColorOld(vec3 p_lightColor, vec3 p_lightDirection)
-{
-    // Get angle between normal and light direction
-    //float NdotL = max(dot(normal, -p_lightDirection), 0.0);
-	float NdotL = clamp( dot( normal, -p_lightDirection ), 0.0, 1.0 );
-    
-    float specular = 0.0;
-    if(NdotL > 0.0)
-    {
-        // Calculate neccessary values
-        vec3 halfVector = normalize(fragmentToEye - p_lightDirection );
-		
-		float NdotH = clamp( dot( normal, halfVector ), 0.0, 1.0 );
-		float NdotV = clamp( dot( normal, fragmentToEye ), 0.0, 1.0 );
-		float VdotH = clamp( dot( fragmentToEye, halfVector ), 0.0, 1.0 );
-		        
-        // Calculate geometric attenuation (self shadowing of microfacets)
-        float NH2 = 2.0 * NdotH;
-        float g1 = (NH2 * NdotV) / VdotH;
-        float g2 = (NH2 * NdotL) / VdotH;
-        float geoAtt = min(1.0, min(g1, g2));
-     	
-		// Calculate microfacet distributions (roughness)
-		// Using Beckmann distribution function
-        float r1 = 1.0 / ( 4.0 * roughnessSqrt * pow(NdotH, 4.0));
-        float r2 = (NdotH * NdotH - 1.0) / (roughnessSqrt * NdotH * NdotH);
-        float microfacetDstrb = r1 * exp(r2);
-        
-		// Calculate fresnel effect (using Schlick's approximation)
-        float fresnel = pow(1.0 - VdotH, 5.0);
-        fresnel *= (1.0 - F0);
-        fresnel += F0;
-        
-		// Calculate specular component
-        specular = max((fresnel * geoAtt * microfacetDstrb) / (NdotV * NdotL * 3.14), 0.0);
-        //specular = (fresnel * geoAtt * microfacetDstrb) / (NdotV * NdotL * 3.14);
-		
-		//F0:"NdotL * (cSpecular * Rs + cDiffuse * (1-f0))"
-		
-		// Combine specular and diffuse components
-		return p_lightColor * NdotL * (k + specular * (1.0 - k));
-		//return p_lightColor * NdotL * specular;//(k + specular * (1.0 - k));
-    }
-	else
-	{
-		return vec3(0.0, 0.0, 0.0);
-	}
-}*/
-
 vec2 calcTexCoord(void)
 {
     return gl_FragCoord.xy / screenSize;
@@ -283,7 +194,8 @@ void main(void)
 {
 	// Calculate screen-space texture coordinates, for buffer access
 	vec2 texCoord = calcTexCoord();
-	
+	// Get the emissive texture color and convert it to linear space
+	vec3 emissiveColor = pow(texture(emissiveMap, texCoord).xyz, vec3(gamma));
 	// Get diffuse color (full-bright) from diffuse buffer and convert it to linear space
 	vec3 diffuseColor = pow(texture(diffuseMap, texCoord).xyz, vec3(gamma));
 	// Get pixel's position in world space
@@ -292,35 +204,45 @@ void main(void)
 	vec3 normal = normalize(texture(normalMap, texCoord).xyz);
 	// Get material properties
 	vec4 matProperties = texture(matPropertiesMap, texCoord).xyzw;
-	
-	// Extract roughness and metalness values
-	float roughnessVar = matProperties.x;
-	float metalness = matProperties.y;
-	
 	// Calculate view direction (fragment to eye vector)
 	fragmentToEye = normalize(cameraPosVec - worldPos);
 	
-	float ior = mix(1.5, 40.5, metalness);
-	ior = 40.5;
+	// Extract roughness and metalness values
+	float roughnessSqrt = matProperties.x;
+	float metalic = matProperties.y;
 	
-	float F0 = abs((1.0 - ior) / (1.0 + ior));
-	F0 = F0 * F0;
-	vec3 F0vec = mix(vec3(F0), diffuseColor, metalness);
-		
-	// Fresnel for the diffuse color
-    float NdotV = clamp(dot(normal, fragmentToEye), 0.0, 1.0);
-	NdotV = pow(1.0 - NdotV, 5.0);
-	float diffuseAmount = 1.0 - (metalness + (1.0 - metalness) * (NdotV));
+	// Calculate F0, with minimum IOR as 0.04
+	vec3 f0 = mix(vec3(0.04), diffuseColor, metalic);
 	
-	float roughnessSqrt = roughnessVar * roughnessVar;
+	// Normalize direction light direction vector
+	vec3 dirLightDirection =  normalize(directionalLight.m_direction);
 	
-	// ior = from 1.2 to 10.0
+	// Get dot product between the up vector (perpendicular to the ground) and directional light direction
+	float dirLightFactor = dot(dirLightDirection, vec3(0.0, 1.0, 0.0)) + 0.001;
 	
-	// Declare final color of the fragment and add directional light to it
-	vec3 finalLightColor = calcLightColor(normal, fragmentToEye, directionalLight.m_color, normalize(-directionalLight.m_direction), F0vec, diffuseAmount, roughnessSqrt) * directionalLight.m_intensity;
+	// Initialize final color variable of this fragment
+	vec3 finalLightColor = vec3(0.0);
 	
-	//vec3 finalLightColor = calcLightColor(normal, fragmentToEye, vec3(1.0, 1.0, 1.0), normalize(vec3(8.0, 10.0, 5.0)), F0vec, diffuseAmount, roughnessSqrt) * 1.0;
+	// Calculate directional light only if it is pointing from above the horizon
+	if(dirLightFactor > 0.0)
+	{
+		float dirLightFactorSqrt = sqrt(dirLightFactor);
+		vec3 dirLightColor = mix(g_sunSetColor, g_sunNoonColor, dirLightFactorSqrt);
+		float dirLightIntensity = directionalLight.m_intensity * mix(g_sunSetIntensityMod, g_sunNoonIntensityMod, dirLightFactorSqrt);
 	
+		// Declare final color of the fragment and add directional light to it
+		finalLightColor += calcLightColor(
+			diffuseColor, 
+			normal, 
+			fragmentToEye, 
+			dirLightColor, 
+			dirLightDirection, 
+			1.0, 
+			f0, 
+			roughnessSqrt, 
+			metalic) * dirLightIntensity;// * min(1.0, (dirLightFactor /* 100.0*/) + 0.02);
+	}
+		
 	for(int i = 0; i < numPointLights; i++)
 	{		
 		// Get light direction, extract length from it and normalize for usage as direction vector
@@ -328,13 +250,8 @@ void main(void)
 		float lightDistance = length(lightDirection);
 		lightDirection = normalize(lightDirection);
 		
-		// Add up constant, linear and quadratic attenuation
-		float attenuation = pointLights[i].m_attenConstant + 
-							pointLights[i].m_attenLinear * lightDistance + 
-							pointLights[i].m_attenQuad * lightDistance * lightDistance;
-							
 		// Light color multiplied by intensity and divided by attenuation
-		finalLightColor += (calcLightColor(normal, fragmentToEye, pointLights[i].m_color, lightDirection, F0vec, diffuseAmount, roughnessSqrt) * pointLights[i].m_intensity) / attenuation;
+		finalLightColor += (calcLightColor(diffuseColor, normal, fragmentToEye, pointLights[i].m_color, lightDirection, lightDistance, f0, roughnessSqrt, metalic) * pointLights[i].m_intensity);
 	}
 	
 	for(int i = 0; i < numSpotLights; i++)
@@ -353,33 +270,15 @@ void main(void)
 			float lightDistance = length(lightDirection);
 			lightDirection = normalize(lightDirection);
 			
-			// Add up constant, linear and quadratic attenuation
-			float attenuation = spotLights[i].m_attenConstant + 
-								spotLights[i].m_attenLinear * lightDistance + 
-								spotLights[i].m_attenQuad * lightDistance * lightDistance;
-			
 			// Light color multiplied by intensity
-			vec3 lightColor = (calcLightColor(normal, fragmentToEye, spotLights[i].m_color, lightDirection, F0vec, diffuseAmount, roughnessSqrt) * spotLights[i].m_intensity);
+			vec3 lightColor = (calcLightColor(diffuseColor, normal, fragmentToEye, spotLights[i].m_color, lightDirection, lightDistance, f0, roughnessSqrt, metalic) * spotLights[i].m_intensity);
 			
 			// Light restriction from cone
 			float coneAttenuation = (1.0 - (1.0 - spotLightFactor) * 1.0 / (1.0 - spotLights[i].m_cutoffAngle));
 			
-			finalLightColor += (lightColor / attenuation) * coneAttenuation;
+			finalLightColor += lightColor * coneAttenuation;
 		}
 	}
-	emissiveBuffer = vec4(1.0, 0.0, 0.0, 0.0);
-	
-	// Multiply the diffuse color by the amount of light the current pixel receives and gamma correct it
 	
-	finalBuffer = diffuseColor * finalLightColor;//texture(diffuseMap, texCoord).xyz;
-	//finalBuffer = vec3(0.0, 1.0, 0.0);//diffuseColor * finalLightColor;
-	//finalBuffer = simpleToneMapping(diffuseColor * finalLightColor, 2.2);
-	//finalBuffer = vec3(roughnessVar, roughnessVar, roughnessVar);
-	//finalBuffer = vec4(texture(staticEnvMap, vec3(0.0, 0.0, 0.0)).xyz, 1.0);
-	//finalBuffer = vec4(metallic, metallic, metallic, 1.0);
-	//finalBuffer = vec4(pow(mix(diffuseColor, vec3(0.0, 0.0, 0.0), 1.0 - metallic) * finalLightColor, vec3(1.0 / 2.2)), 1.0);
-	//finalBuffer = vec4(pow(finalLightColor, vec3(1.0 / 2.2)), 1.0);
-	//finalBuffer = vec4(pow(diffuseColor, vec3(1.0 / 2.2)), 1.0);
-	//finalBuffer = vec4(texture(normalMap, texCoord).xyz, 1.0);
-	//finalBuffer = vec4(roughnessVar, roughnessVar, roughnessVar, 1.0);
+	colorBuffer = vec4(finalLightColor + emissiveColor, 1.0);
 }

+ 0 - 1
Praxis3D/Data/Shaders/postProcessPass.frag

@@ -3,7 +3,6 @@
 out vec4 outputColor;
 
 uniform ivec2 screenSize;
-
 uniform sampler2D inputColorMap;
 
 vec2 calcTexCoord(void)

+ 4 - 2
Praxis3D/Data/config.ini

@@ -1,6 +1,6 @@
 window_position_x 250
 window_position_y 60
-window_size_windowed_x 1500
+window_size_windowed_x 1600
 window_size_windowed_y 900
 window_size_fullscreen_x 1920
 window_size_fullscreen_y 1080
@@ -15,9 +15,11 @@ gl_texture_anisotropy 16
 camera_freelook_speed 25
 face_culling 0
 eye_adaption 1
+eye_adaption_intended_brightness 0.5
 mouse_captured 1
 mouse_warp_mode 0
 fov 80
 z_near 0.1
 z_far 40000
-default_map default_lite.pmap
+default_map default_lite.pmap
+atm_scattering_ground_frag_shader atmosphericScatteringPass_ground_simple.frag

+ 9 - 0
Praxis3D/Praxis3D.vcxproj

@@ -283,6 +283,8 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="main.cpp" />
+    <ClCompile Include="Source\AtmScatteringModel.cpp" />
+    <ClCompile Include="Source\AtmScatteringPass.cpp" />
     <ClCompile Include="Source\BaseGraphicsObjects.cpp" />
     <ClCompile Include="Source\ChangeController.cpp" />
     <ClCompile Include="Source\ClockLocator.cpp" />
@@ -333,6 +335,12 @@
   <ItemGroup>
     <ClInclude Include="resource.h" />
     <ClInclude Include="resource1.h" />
+    <ClInclude Include="Source\AtmScatteringConstants.h" />
+    <ClInclude Include="Source\AtmScatteringModel.h" />
+    <ClInclude Include="Source\AtmScatteringPass.h" />
+    <ClInclude Include="Source\AtmScatteringShaderDefinitions.h" />
+    <ClInclude Include="Source\AtmScatteringShaderFunctions.h" />
+    <ClInclude Include="Source\AtmScatteringShaderPass.h" />
     <ClInclude Include="Source\BaseGraphicsObjects.h" />
     <ClInclude Include="Source\BaseScriptObject.h" />
     <ClInclude Include="Source\BlurPass.h" />
@@ -402,6 +410,7 @@
     <ClInclude Include="Source\SkyPass.h" />
     <ClInclude Include="Source\SolarTimeScript.h" />
     <ClInclude Include="Source\SpinWait.h" />
+    <ClInclude Include="Source\SunScript.h" />
     <ClInclude Include="Source\System.h" />
     <ClInclude Include="Source\TaskManager.h" />
     <ClInclude Include="Source\TaskManagerLocator.h" />

+ 27 - 0
Praxis3D/Praxis3D.vcxproj.filters

@@ -258,6 +258,12 @@
     <ClCompile Include="Source\System.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="Source\AtmScatteringModel.cpp">
+      <Filter>Renderer\Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Source\AtmScatteringPass.cpp">
+      <Filter>Renderer\Render Passes\Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="Source\ErrorCodes.h">
@@ -509,6 +515,27 @@
     <ClInclude Include="Source\PostProcessPass.h">
       <Filter>Renderer\Render Passes\Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="Source\AtmScatteringModel.h">
+      <Filter>Renderer\Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Source\AtmScatteringConstants.h">
+      <Filter>Renderer\Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Source\AtmScatteringShaderFunctions.h">
+      <Filter>Shaders\Shader Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Source\AtmScatteringShaderDefinitions.h">
+      <Filter>Shaders\Shader Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Source\AtmScatteringPass.h">
+      <Filter>Renderer\Render Passes\Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Source\AtmScatteringShaderPass.h">
+      <Filter>Shaders\Shader Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Source\SunScript.h">
+      <Filter>Scripting\Objects\Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="Data\config.ini" />

+ 174 - 0
Praxis3D/Source/AtmScatteringConstants.h

@@ -0,0 +1,174 @@
+#pragma once
+
+/**
+* Copyright (c) 2017 Eric Bruneton
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in the
+*    documentation and/or other materials provided with the distribution.
+* 3. Neither the name of the copyright holders nor the names of its
+*    contributors may be used to endorse or promote products derived from
+*    this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+* THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*<h2>atmosphere/constants.h</h2>
+
+<p>This file defines the size of the precomputed texures used in our atmosphere
+model. It also provides tabulated values of the <a href=
+"https://en.wikipedia.org/wiki/CIE_1931_color_space#Color_matching_functions"
+>CIE color matching functions</a> and the conversion matrix from the <a href=
+"https://en.wikipedia.org/wiki/CIE_1931_color_space">XYZ</a> to the
+<a href="https://en.wikipedia.org/wiki/SRGB">sRGB</a> color spaces (which are
+needed to convert the spectral radiance samples computed by our algorithm to
+sRGB luminance values).
+*/
+
+namespace atmosphere {
+
+	constexpr int TRANSMITTANCE_TEXTURE_WIDTH = 256;
+	constexpr int TRANSMITTANCE_TEXTURE_HEIGHT = 64;
+
+	constexpr int SCATTERING_TEXTURE_R_SIZE = 32;
+	constexpr int SCATTERING_TEXTURE_MU_SIZE = 128;
+	constexpr int SCATTERING_TEXTURE_MU_S_SIZE = 32;
+	constexpr int SCATTERING_TEXTURE_NU_SIZE = 8;
+
+	constexpr int SCATTERING_TEXTURE_WIDTH =
+		SCATTERING_TEXTURE_NU_SIZE * SCATTERING_TEXTURE_MU_S_SIZE;
+	constexpr int SCATTERING_TEXTURE_HEIGHT = SCATTERING_TEXTURE_MU_SIZE;
+	constexpr int SCATTERING_TEXTURE_DEPTH = SCATTERING_TEXTURE_R_SIZE;
+
+	constexpr int IRRADIANCE_TEXTURE_WIDTH = 64;
+	constexpr int IRRADIANCE_TEXTURE_HEIGHT = 16;
+
+	// The conversion factor between watts and lumens.
+	constexpr double MAX_LUMINOUS_EFFICACY = 683.0;
+
+	// Values from "CIE (1931) 2-deg color matching functions", see
+	// "http://web.archive.org/web/20081228084047/
+	//    http://www.cvrl.org/database/data/cmfs/ciexyz31.txt".
+	constexpr double CIE_2_DEG_COLOR_MATCHING_FUNCTIONS[380] = {
+		360, 0.000129900000, 0.000003917000, 0.000606100000,
+		365, 0.000232100000, 0.000006965000, 0.001086000000,
+		370, 0.000414900000, 0.000012390000, 0.001946000000,
+		375, 0.000741600000, 0.000022020000, 0.003486000000,
+		380, 0.001368000000, 0.000039000000, 0.006450001000,
+		385, 0.002236000000, 0.000064000000, 0.010549990000,
+		390, 0.004243000000, 0.000120000000, 0.020050010000,
+		395, 0.007650000000, 0.000217000000, 0.036210000000,
+		400, 0.014310000000, 0.000396000000, 0.067850010000,
+		405, 0.023190000000, 0.000640000000, 0.110200000000,
+		410, 0.043510000000, 0.001210000000, 0.207400000000,
+		415, 0.077630000000, 0.002180000000, 0.371300000000,
+		420, 0.134380000000, 0.004000000000, 0.645600000000,
+		425, 0.214770000000, 0.007300000000, 1.039050100000,
+		430, 0.283900000000, 0.011600000000, 1.385600000000,
+		435, 0.328500000000, 0.016840000000, 1.622960000000,
+		440, 0.348280000000, 0.023000000000, 1.747060000000,
+		445, 0.348060000000, 0.029800000000, 1.782600000000,
+		450, 0.336200000000, 0.038000000000, 1.772110000000,
+		455, 0.318700000000, 0.048000000000, 1.744100000000,
+		460, 0.290800000000, 0.060000000000, 1.669200000000,
+		465, 0.251100000000, 0.073900000000, 1.528100000000,
+		470, 0.195360000000, 0.090980000000, 1.287640000000,
+		475, 0.142100000000, 0.112600000000, 1.041900000000,
+		480, 0.095640000000, 0.139020000000, 0.812950100000,
+		485, 0.057950010000, 0.169300000000, 0.616200000000,
+		490, 0.032010000000, 0.208020000000, 0.465180000000,
+		495, 0.014700000000, 0.258600000000, 0.353300000000,
+		500, 0.004900000000, 0.323000000000, 0.272000000000,
+		505, 0.002400000000, 0.407300000000, 0.212300000000,
+		510, 0.009300000000, 0.503000000000, 0.158200000000,
+		515, 0.029100000000, 0.608200000000, 0.111700000000,
+		520, 0.063270000000, 0.710000000000, 0.078249990000,
+		525, 0.109600000000, 0.793200000000, 0.057250010000,
+		530, 0.165500000000, 0.862000000000, 0.042160000000,
+		535, 0.225749900000, 0.914850100000, 0.029840000000,
+		540, 0.290400000000, 0.954000000000, 0.020300000000,
+		545, 0.359700000000, 0.980300000000, 0.013400000000,
+		550, 0.433449900000, 0.994950100000, 0.008749999000,
+		555, 0.512050100000, 1.000000000000, 0.005749999000,
+		560, 0.594500000000, 0.995000000000, 0.003900000000,
+		565, 0.678400000000, 0.978600000000, 0.002749999000,
+		570, 0.762100000000, 0.952000000000, 0.002100000000,
+		575, 0.842500000000, 0.915400000000, 0.001800000000,
+		580, 0.916300000000, 0.870000000000, 0.001650001000,
+		585, 0.978600000000, 0.816300000000, 0.001400000000,
+		590, 1.026300000000, 0.757000000000, 0.001100000000,
+		595, 1.056700000000, 0.694900000000, 0.001000000000,
+		600, 1.062200000000, 0.631000000000, 0.000800000000,
+		605, 1.045600000000, 0.566800000000, 0.000600000000,
+		610, 1.002600000000, 0.503000000000, 0.000340000000,
+		615, 0.938400000000, 0.441200000000, 0.000240000000,
+		620, 0.854449900000, 0.381000000000, 0.000190000000,
+		625, 0.751400000000, 0.321000000000, 0.000100000000,
+		630, 0.642400000000, 0.265000000000, 0.000049999990,
+		635, 0.541900000000, 0.217000000000, 0.000030000000,
+		640, 0.447900000000, 0.175000000000, 0.000020000000,
+		645, 0.360800000000, 0.138200000000, 0.000010000000,
+		650, 0.283500000000, 0.107000000000, 0.000000000000,
+		655, 0.218700000000, 0.081600000000, 0.000000000000,
+		660, 0.164900000000, 0.061000000000, 0.000000000000,
+		665, 0.121200000000, 0.044580000000, 0.000000000000,
+		670, 0.087400000000, 0.032000000000, 0.000000000000,
+		675, 0.063600000000, 0.023200000000, 0.000000000000,
+		680, 0.046770000000, 0.017000000000, 0.000000000000,
+		685, 0.032900000000, 0.011920000000, 0.000000000000,
+		690, 0.022700000000, 0.008210000000, 0.000000000000,
+		695, 0.015840000000, 0.005723000000, 0.000000000000,
+		700, 0.011359160000, 0.004102000000, 0.000000000000,
+		705, 0.008110916000, 0.002929000000, 0.000000000000,
+		710, 0.005790346000, 0.002091000000, 0.000000000000,
+		715, 0.004109457000, 0.001484000000, 0.000000000000,
+		720, 0.002899327000, 0.001047000000, 0.000000000000,
+		725, 0.002049190000, 0.000740000000, 0.000000000000,
+		730, 0.001439971000, 0.000520000000, 0.000000000000,
+		735, 0.000999949300, 0.000361100000, 0.000000000000,
+		740, 0.000690078600, 0.000249200000, 0.000000000000,
+		745, 0.000476021300, 0.000171900000, 0.000000000000,
+		750, 0.000332301100, 0.000120000000, 0.000000000000,
+		755, 0.000234826100, 0.000084800000, 0.000000000000,
+		760, 0.000166150500, 0.000060000000, 0.000000000000,
+		765, 0.000117413000, 0.000042400000, 0.000000000000,
+		770, 0.000083075270, 0.000030000000, 0.000000000000,
+		775, 0.000058706520, 0.000021200000, 0.000000000000,
+		780, 0.000041509940, 0.000014990000, 0.000000000000,
+		785, 0.000029353260, 0.000010600000, 0.000000000000,
+		790, 0.000020673830, 0.000007465700, 0.000000000000,
+		795, 0.000014559770, 0.000005257800, 0.000000000000,
+		800, 0.000010253980, 0.000003702900, 0.000000000000,
+		805, 0.000007221456, 0.000002607800, 0.000000000000,
+		810, 0.000005085868, 0.000001836600, 0.000000000000,
+		815, 0.000003581652, 0.000001293400, 0.000000000000,
+		820, 0.000002522525, 0.000000910930, 0.000000000000,
+		825, 0.000001776509, 0.000000641530, 0.000000000000,
+		830, 0.000001251141, 0.000000451810, 0.000000000000,
+	};
+
+	// The conversion matrix from XYZ to linear sRGB color spaces.
+	// Values from https://en.wikipedia.org/wiki/SRGB.
+	constexpr double XYZ_TO_SRGB[9] = {
+		+3.2406, -1.5372, -0.4986,
+		-0.9689, +1.8758, +0.0415,
+		+0.0557, -0.2040, +1.0570
+	};
+
+}  // namespace atmosphere

+ 1404 - 0
Praxis3D/Source/AtmScatteringModel.cpp

@@ -0,0 +1,1404 @@
+/**
+* Copyright (c) 2017 Eric Bruneton
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in the
+*    documentation and/or other materials provided with the distribution.
+* 3. Neither the name of the copyright holders nor the names of its
+*    contributors may be used to endorse or promote products derived from
+*    this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+* THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*<h2>atmosphere/model.cc</h2>
+
+<p>This file implements the <a href="model.h.html">API of our atmosphere
+model</a>. Its main role is to precompute the transmittance, scattering and
+irradiance textures. The GLSL functions to precompute them are provided in
+<a href="functions.glsl.html">functions.glsl</a>, but they are not sufficient.
+They must be used in fully functional shaders and programs, and these programs
+must be called in the correct order, with the correct input and output textures
+(via framebuffer objects), to precompute each scattering order in sequence, as
+described in Algorithm 4.1 of
+<a href="https://hal.inria.fr/inria-00288758/en">our paper</a>. This is the role
+of the following C++ code.
+*/
+
+#include <cassert>
+#include <cmath>
+#include <iostream>
+#include <memory>
+
+#include "AtmScatteringModel.h"
+#include "AtmScatteringShaderDefinitions.h"
+#include "AtmScatteringShaderFunctions.h"
+#include "RendererFrontend.h"
+
+/*
+<p>The rest of this file is organized in 3 parts:
+<ul>
+<li>the <a href="#shaders">first part</a> defines the shaders used to precompute
+the atmospheric textures,</li>
+<li>the <a href="#utilities">second part</a> provides utility classes and
+functions used to compile shaders, create textures, draw quads, etc,</li>
+<li>the <a href="#implementation">third part</a> provides the actual
+implementation of the <code>AtmScatteringModel</code> class, using the above tools.</li>
+</ul>
+
+<h3 id="shaders">Shader definitions</h3>
+
+<p>In order to precompute a texture we attach it to a framebuffer object (FBO)
+and we render a full quad in this FBO. For this we need a basic vertex shader:
+*/
+
+constexpr int TRANSMITTANCE_TEXTURE_WIDTH = 256;
+constexpr int TRANSMITTANCE_TEXTURE_HEIGHT = 64;
+
+constexpr int SCATTERING_TEXTURE_R_SIZE = 32;
+constexpr int SCATTERING_TEXTURE_MU_SIZE = 128;
+constexpr int SCATTERING_TEXTURE_MU_S_SIZE = 32;
+constexpr int SCATTERING_TEXTURE_NU_SIZE = 8;
+
+constexpr int SCATTERING_TEXTURE_WIDTH =
+SCATTERING_TEXTURE_NU_SIZE * SCATTERING_TEXTURE_MU_S_SIZE;
+constexpr int SCATTERING_TEXTURE_HEIGHT = SCATTERING_TEXTURE_MU_SIZE;
+constexpr int SCATTERING_TEXTURE_DEPTH = SCATTERING_TEXTURE_R_SIZE;
+
+constexpr int IRRADIANCE_TEXTURE_WIDTH = 64;
+constexpr int IRRADIANCE_TEXTURE_HEIGHT = 16;
+
+// The conversion factor between watts and lumens.
+constexpr double MAX_LUMINOUS_EFFICACY = 683.0;
+
+// Values from "CIE (1931) 2-deg color matching functions", see
+// "http://web.archive.org/web/20081228084047/
+//    http://www.cvrl.org/database/data/cmfs/ciexyz31.txt".
+constexpr double CIE_2_DEG_COLOR_MATCHING_FUNCTIONS[380] = {
+	360, 0.000129900000, 0.000003917000, 0.000606100000,
+	365, 0.000232100000, 0.000006965000, 0.001086000000,
+	370, 0.000414900000, 0.000012390000, 0.001946000000,
+	375, 0.000741600000, 0.000022020000, 0.003486000000,
+	380, 0.001368000000, 0.000039000000, 0.006450001000,
+	385, 0.002236000000, 0.000064000000, 0.010549990000,
+	390, 0.004243000000, 0.000120000000, 0.020050010000,
+	395, 0.007650000000, 0.000217000000, 0.036210000000,
+	400, 0.014310000000, 0.000396000000, 0.067850010000,
+	405, 0.023190000000, 0.000640000000, 0.110200000000,
+	410, 0.043510000000, 0.001210000000, 0.207400000000,
+	415, 0.077630000000, 0.002180000000, 0.371300000000,
+	420, 0.134380000000, 0.004000000000, 0.645600000000,
+	425, 0.214770000000, 0.007300000000, 1.039050100000,
+	430, 0.283900000000, 0.011600000000, 1.385600000000,
+	435, 0.328500000000, 0.016840000000, 1.622960000000,
+	440, 0.348280000000, 0.023000000000, 1.747060000000,
+	445, 0.348060000000, 0.029800000000, 1.782600000000,
+	450, 0.336200000000, 0.038000000000, 1.772110000000,
+	455, 0.318700000000, 0.048000000000, 1.744100000000,
+	460, 0.290800000000, 0.060000000000, 1.669200000000,
+	465, 0.251100000000, 0.073900000000, 1.528100000000,
+	470, 0.195360000000, 0.090980000000, 1.287640000000,
+	475, 0.142100000000, 0.112600000000, 1.041900000000,
+	480, 0.095640000000, 0.139020000000, 0.812950100000,
+	485, 0.057950010000, 0.169300000000, 0.616200000000,
+	490, 0.032010000000, 0.208020000000, 0.465180000000,
+	495, 0.014700000000, 0.258600000000, 0.353300000000,
+	500, 0.004900000000, 0.323000000000, 0.272000000000,
+	505, 0.002400000000, 0.407300000000, 0.212300000000,
+	510, 0.009300000000, 0.503000000000, 0.158200000000,
+	515, 0.029100000000, 0.608200000000, 0.111700000000,
+	520, 0.063270000000, 0.710000000000, 0.078249990000,
+	525, 0.109600000000, 0.793200000000, 0.057250010000,
+	530, 0.165500000000, 0.862000000000, 0.042160000000,
+	535, 0.225749900000, 0.914850100000, 0.029840000000,
+	540, 0.290400000000, 0.954000000000, 0.020300000000,
+	545, 0.359700000000, 0.980300000000, 0.013400000000,
+	550, 0.433449900000, 0.994950100000, 0.008749999000,
+	555, 0.512050100000, 1.000000000000, 0.005749999000,
+	560, 0.594500000000, 0.995000000000, 0.003900000000,
+	565, 0.678400000000, 0.978600000000, 0.002749999000,
+	570, 0.762100000000, 0.952000000000, 0.002100000000,
+	575, 0.842500000000, 0.915400000000, 0.001800000000,
+	580, 0.916300000000, 0.870000000000, 0.001650001000,
+	585, 0.978600000000, 0.816300000000, 0.001400000000,
+	590, 1.026300000000, 0.757000000000, 0.001100000000,
+	595, 1.056700000000, 0.694900000000, 0.001000000000,
+	600, 1.062200000000, 0.631000000000, 0.000800000000,
+	605, 1.045600000000, 0.566800000000, 0.000600000000,
+	610, 1.002600000000, 0.503000000000, 0.000340000000,
+	615, 0.938400000000, 0.441200000000, 0.000240000000,
+	620, 0.854449900000, 0.381000000000, 0.000190000000,
+	625, 0.751400000000, 0.321000000000, 0.000100000000,
+	630, 0.642400000000, 0.265000000000, 0.000049999990,
+	635, 0.541900000000, 0.217000000000, 0.000030000000,
+	640, 0.447900000000, 0.175000000000, 0.000020000000,
+	645, 0.360800000000, 0.138200000000, 0.000010000000,
+	650, 0.283500000000, 0.107000000000, 0.000000000000,
+	655, 0.218700000000, 0.081600000000, 0.000000000000,
+	660, 0.164900000000, 0.061000000000, 0.000000000000,
+	665, 0.121200000000, 0.044580000000, 0.000000000000,
+	670, 0.087400000000, 0.032000000000, 0.000000000000,
+	675, 0.063600000000, 0.023200000000, 0.000000000000,
+	680, 0.046770000000, 0.017000000000, 0.000000000000,
+	685, 0.032900000000, 0.011920000000, 0.000000000000,
+	690, 0.022700000000, 0.008210000000, 0.000000000000,
+	695, 0.015840000000, 0.005723000000, 0.000000000000,
+	700, 0.011359160000, 0.004102000000, 0.000000000000,
+	705, 0.008110916000, 0.002929000000, 0.000000000000,
+	710, 0.005790346000, 0.002091000000, 0.000000000000,
+	715, 0.004109457000, 0.001484000000, 0.000000000000,
+	720, 0.002899327000, 0.001047000000, 0.000000000000,
+	725, 0.002049190000, 0.000740000000, 0.000000000000,
+	730, 0.001439971000, 0.000520000000, 0.000000000000,
+	735, 0.000999949300, 0.000361100000, 0.000000000000,
+	740, 0.000690078600, 0.000249200000, 0.000000000000,
+	745, 0.000476021300, 0.000171900000, 0.000000000000,
+	750, 0.000332301100, 0.000120000000, 0.000000000000,
+	755, 0.000234826100, 0.000084800000, 0.000000000000,
+	760, 0.000166150500, 0.000060000000, 0.000000000000,
+	765, 0.000117413000, 0.000042400000, 0.000000000000,
+	770, 0.000083075270, 0.000030000000, 0.000000000000,
+	775, 0.000058706520, 0.000021200000, 0.000000000000,
+	780, 0.000041509940, 0.000014990000, 0.000000000000,
+	785, 0.000029353260, 0.000010600000, 0.000000000000,
+	790, 0.000020673830, 0.000007465700, 0.000000000000,
+	795, 0.000014559770, 0.000005257800, 0.000000000000,
+	800, 0.000010253980, 0.000003702900, 0.000000000000,
+	805, 0.000007221456, 0.000002607800, 0.000000000000,
+	810, 0.000005085868, 0.000001836600, 0.000000000000,
+	815, 0.000003581652, 0.000001293400, 0.000000000000,
+	820, 0.000002522525, 0.000000910930, 0.000000000000,
+	825, 0.000001776509, 0.000000641530, 0.000000000000,
+	830, 0.000001251141, 0.000000451810, 0.000000000000,
+};
+
+// The conversion matrix from XYZ to linear sRGB color spaces.
+// Values from https://en.wikipedia.org/wiki/SRGB.
+constexpr double XYZ_TO_SRGB[9] = {
+	+3.2406, -1.5372, -0.4986,
+	-0.9689, +1.8758, +0.0415,
+	+0.0557, -0.2040, +1.0570
+};
+
+
+namespace {
+
+	const char m_vertexShaderSource[] = R"(
+    #version 330
+    layout(location = 0) in vec2 vertex;
+    void main() {
+      gl_Position = vec4(vertex, 0.0, 1.0);
+    })";
+
+	/*
+	<p>a basic geometry shader (only for 3D textures, to specify in which layer we
+	want to write):
+	*/
+
+	const char kGeometryShader[] = R"(
+    #version 330
+    layout(triangles) in;
+    layout(triangle_strip, max_vertices = 3) out;
+    uniform int layer;
+    void main() {
+      gl_Position = gl_in[0].gl_Position;
+      gl_Layer = layer;
+      EmitVertex();
+      gl_Position = gl_in[1].gl_Position;
+      gl_Layer = layer;
+      EmitVertex();
+      gl_Position = gl_in[2].gl_Position;
+      gl_Layer = layer;
+      EmitVertex();
+      EndPrimitive();
+    })";
+
+	/*
+	<p>and a fragment shader, which depends on the texture we want to compute. This
+	is the role of the following shaders, which simply wrap the precomputation
+	functions from <a href="functions.glsl.html">functions.glsl</a> in complete
+	shaders (with a <code>main</code> function and a proper declaration of the
+	shader inputs and outputs). Note that these strings must be concatenated with
+	<code>definitions.glsl</code> and <code>functions.glsl</code> (provided as C++
+	string literals by the generated <code>.glsl.inc</code> files), as well as with
+	a definition of the <code>ATMOSPHERE</code> constant - containing the atmosphere
+	parameters, to really get a complete shader. Note also the
+	<code>luminance_from_radiance</code> uniforms: these are used in precomputed
+	illuminance mode to convert the radiance values computed by the
+	<code>functions.glsl</code> functions to luminance values (see the
+	<code>Init</code> method for more details).
+	*/
+	
+	const char kComputeTransmittanceShader[] = R"(
+    layout(location = 0) out vec3 transmittance;
+    void main() {
+      transmittance = ComputeTransmittanceToTopAtmosphereBoundaryTexture(
+          ATMOSPHERE, gl_FragCoord.xy);
+    })";
+
+	const char kComputeDirectIrradianceShader[] = R"(
+    layout(location = 0) out vec3 delta_irradiance;
+    layout(location = 1) out vec3 irradiance;
+    uniform sampler2D transmittance_texture;
+    void main() {
+      delta_irradiance = ComputeDirectIrradianceTexture(
+          ATMOSPHERE, transmittance_texture, gl_FragCoord.xy);
+      irradiance = vec3(0.0);
+    })";
+
+	const char kComputeSingleScatteringShader[] = R"(
+    layout(location = 0) out vec3 delta_rayleigh;
+    layout(location = 1) out vec3 delta_mie;
+    layout(location = 2) out vec4 scattering;
+    layout(location = 3) out vec3 single_mie_scattering;
+    uniform mat3 luminance_from_radiance;
+    uniform sampler2D transmittance_texture;
+    uniform int layer;
+    void main() {
+      ComputeSingleScatteringTexture(
+          ATMOSPHERE, transmittance_texture, vec3(gl_FragCoord.xy, layer + 0.5),
+          delta_rayleigh, delta_mie);
+      scattering = vec4(luminance_from_radiance * delta_rayleigh.rgb,
+          (luminance_from_radiance * delta_mie).r);
+      single_mie_scattering = luminance_from_radiance * delta_mie;
+    })";
+
+	const char kComputeScatteringDensityShader[] = R"(
+    layout(location = 0) out vec3 scattering_density;
+    uniform sampler2D transmittance_texture;
+    uniform sampler3D single_rayleigh_scattering_texture;
+    uniform sampler3D single_mie_scattering_texture;
+    uniform sampler3D multiple_scattering_texture;
+    uniform sampler2D irradiance_texture;
+    uniform int scattering_order;
+    uniform int layer;
+    void main() {
+      scattering_density = ComputeScatteringDensityTexture(
+          ATMOSPHERE, transmittance_texture, single_rayleigh_scattering_texture,
+          single_mie_scattering_texture, multiple_scattering_texture,
+          irradiance_texture, vec3(gl_FragCoord.xy, layer + 0.5),
+          scattering_order);
+    })";
+
+	const char kComputeIndirectIrradianceShader[] = R"(
+    layout(location = 0) out vec3 delta_irradiance;
+    layout(location = 1) out vec3 irradiance;
+    uniform mat3 luminance_from_radiance;
+    uniform sampler3D single_rayleigh_scattering_texture;
+    uniform sampler3D single_mie_scattering_texture;
+    uniform sampler3D multiple_scattering_texture;
+    uniform int scattering_order;
+    void main() {
+      delta_irradiance = ComputeIndirectIrradianceTexture(
+          ATMOSPHERE, single_rayleigh_scattering_texture,
+          single_mie_scattering_texture, multiple_scattering_texture,
+          gl_FragCoord.xy, scattering_order);
+      irradiance = luminance_from_radiance * delta_irradiance;
+    })";
+
+	const char kComputeMultipleScatteringShader[] = R"(
+    layout(location = 0) out vec3 delta_multiple_scattering;
+    layout(location = 1) out vec4 scattering;
+    uniform mat3 luminance_from_radiance;
+    uniform sampler2D transmittance_texture;
+    uniform sampler3D scattering_density_texture;
+    uniform int layer;
+    void main() {
+      float nu;
+      delta_multiple_scattering = ComputeMultipleScatteringTexture(
+          ATMOSPHERE, transmittance_texture, scattering_density_texture,
+          vec3(gl_FragCoord.xy, layer + 0.5), nu);
+      scattering = vec4(
+          luminance_from_radiance *
+              delta_multiple_scattering.rgb / RayleighPhaseFunction(nu),
+          0.0);
+    })";
+
+	/*
+	<p>We finally need a shader implementing the GLSL functions exposed in our API,
+	which can be done by calling the corresponding functions in
+	<a href="functions.glsl.html#rendering">functions.glsl</a>, with the precomputed
+	texture arguments taken from uniform variables (note also the
+	*<code>_RADIANCE_TO_LUMINANCE</code> conversion constants in the last functions:
+	they are computed in the <a href="#utilities">second part</a> below, and their
+	definitions are concatenated to this GLSL code to get a fully functional
+	shader).
+	*/
+
+	const char kAtmosphereShader[] = R"(
+    uniform sampler2D transmittance_texture;
+    uniform sampler3D scattering_texture;
+    uniform sampler3D single_mie_scattering_texture;
+    uniform sampler2D irradiance_texture;
+    #ifdef RADIANCE_API_ENABLED
+    RadianceSpectrum GetSolarRadiance() {
+      return ATMOSPHERE.solar_irradiance /
+          (PI * ATMOSPHERE.sun_angular_radius * ATMOSPHERE.sun_angular_radius);
+    }
+    RadianceSpectrum GetSkyRadiance(
+        Position camera, Direction view_ray, Length shadow_length,
+        Direction sun_direction, out DimensionlessSpectrum transmittance) {
+      return GetSkyRadiance(ATMOSPHERE, transmittance_texture,
+          scattering_texture, single_mie_scattering_texture,
+          camera, view_ray, shadow_length, sun_direction, transmittance);
+    }
+    RadianceSpectrum GetSkyRadianceToPoint(
+        Position camera, Position point, Length shadow_length,
+        Direction sun_direction, out DimensionlessSpectrum transmittance) {
+      return GetSkyRadianceToPoint(ATMOSPHERE, transmittance_texture,
+          scattering_texture, single_mie_scattering_texture,
+          camera, point, shadow_length, sun_direction, transmittance);
+    }
+    IrradianceSpectrum GetSunAndSkyIrradiance(
+       Position p, Direction normal, Direction sun_direction,
+       out IrradianceSpectrum sky_irradiance) {
+      return GetSunAndSkyIrradiance(ATMOSPHERE, transmittance_texture,
+          irradiance_texture, p, normal, sun_direction, sky_irradiance);
+    }
+    #endif
+    Luminance3 GetSolarLuminance() {
+      return ATMOSPHERE.solar_irradiance /
+          (PI * ATMOSPHERE.sun_angular_radius * ATMOSPHERE.sun_angular_radius) *
+          SUN_SPECTRAL_RADIANCE_TO_LUMINANCE;
+    }
+    Luminance3 GetSkyLuminance(
+        Position camera, Direction view_ray, Length shadow_length,
+        Direction sun_direction, out DimensionlessSpectrum transmittance) {
+      return GetSkyRadiance(ATMOSPHERE, transmittance_texture,
+          scattering_texture, single_mie_scattering_texture,
+          camera, view_ray, shadow_length, sun_direction, transmittance) *
+          SKY_SPECTRAL_RADIANCE_TO_LUMINANCE;
+    }
+    Luminance3 GetSkyLuminanceToPoint(
+        Position camera, Position point, Length shadow_length,
+        Direction sun_direction, out DimensionlessSpectrum transmittance) {
+      return GetSkyRadianceToPoint(ATMOSPHERE, transmittance_texture,
+          scattering_texture, single_mie_scattering_texture,
+          camera, point, shadow_length, sun_direction, transmittance) *
+          SKY_SPECTRAL_RADIANCE_TO_LUMINANCE;
+    }
+    Illuminance3 GetSunAndSkyIlluminance(
+       Position p, Direction normal, Direction sun_direction,
+       out IrradianceSpectrum sky_irradiance) {
+      IrradianceSpectrum sun_irradiance = GetSunAndSkyIrradiance(
+          ATMOSPHERE, transmittance_texture, irradiance_texture, p, normal,
+          sun_direction, sky_irradiance);
+      sky_irradiance *= SKY_SPECTRAL_RADIANCE_TO_LUMINANCE;
+      return sun_irradiance * SUN_SPECTRAL_RADIANCE_TO_LUMINANCE;
+    })";
+
+	/*<h3 id="utilities">Utility classes and functions</h3>
+
+	<p>To compile and link these shaders into programs, and to set their uniforms,
+	we use the following utility class:
+	*/
+
+	class Program {
+	public:
+		Program(
+			const std::string& vertex_shader_source,
+			const std::string& fragment_shader_source)
+			: Program(vertex_shader_source, "", fragment_shader_source)
+		{
+		}
+
+		Program(
+			const std::string& vertex_shader_source,
+			const std::string& geometry_shader_source,
+			const std::string& fragment_shader_source)
+		{
+			program_ = glCreateProgram();
+
+			const char* source;
+			source = vertex_shader_source.c_str();
+			GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
+			glShaderSource(vertex_shader, 1, &source, NULL);
+			glCompileShader(vertex_shader);
+			CheckShader(vertex_shader);
+			glAttachShader(program_, vertex_shader);
+
+			GLuint geometry_shader = 0;
+			if(!geometry_shader_source.empty())
+			{
+				source = geometry_shader_source.c_str();
+				geometry_shader = glCreateShader(GL_GEOMETRY_SHADER);
+				glShaderSource(geometry_shader, 1, &source, NULL);
+				glCompileShader(geometry_shader);
+				CheckShader(geometry_shader);
+				glAttachShader(program_, geometry_shader);
+			}
+
+			source = fragment_shader_source.c_str();
+			GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
+			glShaderSource(fragment_shader, 1, &source, NULL);
+			glCompileShader(fragment_shader);
+			CheckShader(fragment_shader);
+			glAttachShader(program_, fragment_shader);
+
+			glLinkProgram(program_);
+			CheckProgram(program_);
+
+			glDetachShader(program_, vertex_shader);
+			glDeleteShader(vertex_shader);
+			if(!geometry_shader_source.empty())
+			{
+				glDetachShader(program_, geometry_shader);
+				glDeleteShader(geometry_shader);
+			}
+			glDetachShader(program_, fragment_shader);
+			glDeleteShader(fragment_shader);
+		}
+
+		~Program()
+		{
+			glDeleteProgram(program_);
+		}
+
+		void Use() const
+		{
+			glUseProgram(program_);
+		}
+
+		void BindMat3(const std::string& uniform_name,
+			const std::array<float, 9>& value) const
+		{
+			glUniformMatrix3fv(glGetUniformLocation(program_, uniform_name.c_str()),
+				1, true /* transpose */, value.data());
+		}
+
+		void BindInt(const std::string& uniform_name, int value) const
+		{
+			glUniform1i(glGetUniformLocation(program_, uniform_name.c_str()), value);
+		}
+
+		void BindTexture2d(const std::string& sampler_uniform_name, GLuint texture,
+			GLuint texture_unit) const
+		{
+			glActiveTexture(GL_TEXTURE0 + texture_unit);
+			glBindTexture(GL_TEXTURE_2D, texture);
+			BindInt(sampler_uniform_name, texture_unit);
+		}
+
+		void BindTexture3d(const std::string& sampler_uniform_name, GLuint texture,
+			GLuint texture_unit) const
+		{
+			glActiveTexture(GL_TEXTURE0 + texture_unit);
+			glBindTexture(GL_TEXTURE_3D, texture);
+			BindInt(sampler_uniform_name, texture_unit);
+		}
+
+	private:
+		static void CheckShader(GLuint shader)
+		{
+			GLint compile_status;
+			glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
+			if(compile_status == GL_FALSE)
+			{
+				PrintShaderLog(shader);
+			}
+			assert(compile_status == GL_TRUE);
+		}
+
+		static void PrintShaderLog(GLuint shader)
+		{
+			GLint log_length;
+			glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
+			if(log_length > 0)
+			{
+				std::unique_ptr<char[]> log_data(new char[log_length]);
+				glGetShaderInfoLog(shader, log_length, &log_length, log_data.get());
+				std::cerr << "compile log = "
+					<< std::string(log_data.get(), log_length) << std::endl;
+			}
+		}
+
+		static void CheckProgram(GLuint program)
+		{
+			GLint link_status;
+			glGetProgramiv(program, GL_LINK_STATUS, &link_status);
+			if(link_status == GL_FALSE)
+			{
+				PrintProgramLog(program);
+			}
+			assert(link_status == GL_TRUE);
+			assert(glGetError() == 0);
+		}
+
+		static void PrintProgramLog(GLuint program)
+		{
+			GLint log_length;
+			glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
+			if(log_length > 0)
+			{
+				std::unique_ptr<char[]> log_data(new char[log_length]);
+				glGetProgramInfoLog(program, log_length, &log_length, log_data.get());
+				std::cerr << "link log = "
+					<< std::string(log_data.get(), log_length) << std::endl;
+			}
+		}
+
+		GLuint program_;
+	};
+
+	/*
+	<p>We also need functions to allocate the precomputed textures on GPU:
+	*/
+
+	GLuint NewTexture2d(int width, int height)
+	{
+		GLuint texture;
+		glGenTextures(1, &texture);
+		glActiveTexture(GL_TEXTURE0);
+		glBindTexture(GL_TEXTURE_2D, texture);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+		// 16F precision for the transmittance gives artifacts.
+		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0,
+			GL_RGBA, GL_FLOAT, NULL);
+		return texture;
+	}
+
+	GLuint NewTexture3d(int width, int height, int depth, GLenum format,
+		bool half_precision)
+	{
+		GLuint texture;
+		glGenTextures(1, &texture);
+		glActiveTexture(GL_TEXTURE0);
+		glBindTexture(GL_TEXTURE_3D, texture);
+		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+		GLenum internal_format = format == GL_RGBA ?
+			(half_precision ? GL_RGBA16F : GL_RGBA32F) :
+			(half_precision ? GL_RGB16F : GL_RGB32F);
+		glTexImage3D(GL_TEXTURE_3D, 0, internal_format, width, height, depth, 0,
+			format, GL_FLOAT, NULL);
+		return texture;
+	}
+
+	/*
+	<p>a function to test whether the RGB format is a supported renderbuffer color
+	format (the OpenGL 3.3 Core Profile specification requires support for the RGBA
+	formats, but not for the RGB ones):
+	*/
+
+	bool IsFramebufferRgbFormatSupported(bool half_precision)
+	{
+		GLuint test_fbo = 0;
+		glGenFramebuffers(1, &test_fbo);
+		glBindFramebuffer(GL_FRAMEBUFFER, test_fbo);
+		GLuint test_texture = 0;
+		glGenTextures(1, &test_texture);
+		glBindTexture(GL_TEXTURE_2D, test_texture);
+		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glTexImage2D(GL_TEXTURE_2D, 0, half_precision ? GL_RGB16F : GL_RGB32F,
+			1, 1, 0, GL_RGB, GL_FLOAT, NULL);
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+			GL_TEXTURE_2D, test_texture, 0);
+		bool rgb_format_supported =
+			glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
+		glDeleteTextures(1, &test_texture);
+		glDeleteFramebuffers(1, &test_fbo);
+		return rgb_format_supported;
+	}
+
+	/*
+	<p>and a function to draw a full screen quad in an offscreen framebuffer (with
+	blending separately enabled or disabled for each color attachment):
+	*/
+
+	void DrawQuad(const std::vector<bool>& enable_blend, GLuint quad_vao)
+	{
+		for(unsigned int i = 0; i < enable_blend.size(); ++i)
+		{
+			if(enable_blend[i])
+			{
+				glEnablei(GL_BLEND, i);
+			}
+		}
+
+		glBindVertexArray(quad_vao);
+		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+		glBindVertexArray(0);
+
+		for(unsigned int i = 0; i < enable_blend.size(); ++i)
+		{
+			glDisablei(GL_BLEND, i);
+		}
+	}
+
+	/*
+	<p>Finally, we need a utility function to compute the value of the conversion
+	constants *<code>_RADIANCE_TO_LUMINANCE</code>, used above to convert the
+	spectral results into luminance values. These are the constants k_r, k_g, k_b
+	described in Section 14.3 of <a href="https://arxiv.org/pdf/1612.04336.pdf">A
+	Qualitative and Quantitative Evaluation of 8 Clear Sky Models</a>.
+
+	<p>Computing their value requires an integral of a function times a CIE color
+	matching function. Thus, we first need functions to interpolate an arbitrary
+	function (specified by some samples), and a CIE color matching function
+	(specified by tabulated values), at an arbitrary wavelength. This is the purpose
+	of the following two functions:
+	*/
+
+	constexpr int kLambdaMin = 360;
+	constexpr int kLambdaMax = 830;
+
+	double CieColorMatchingFunctionTableValue(double wavelength, int column)
+	{
+		if(wavelength <= kLambdaMin || wavelength >= kLambdaMax)
+		{
+			return 0.0;
+		}
+		double u = (wavelength - kLambdaMin) / 5.0;
+		int row = static_cast<int>(std::floor(u));
+		assert(row >= 0 && row + 1 < 95);
+		assert(CIE_2_DEG_COLOR_MATCHING_FUNCTIONS[4 * row] <= wavelength &&
+			CIE_2_DEG_COLOR_MATCHING_FUNCTIONS[4 * (row + 1)] >= wavelength);
+		u -= row;
+		return CIE_2_DEG_COLOR_MATCHING_FUNCTIONS[4 * row + column] * (1.0 - u) +
+			CIE_2_DEG_COLOR_MATCHING_FUNCTIONS[4 * (row + 1) + column] * u;
+	}
+
+	double Interpolate(
+		const std::vector<double>& wavelengths,
+		const std::vector<double>& wavelength_function,
+		double wavelength)
+	{
+		assert(wavelength_function.size() == wavelengths.size());
+		if(wavelength < wavelengths[0])
+		{
+			return wavelength_function[0];
+		}
+		for(unsigned int i = 0; i < wavelengths.size() - 1; ++i)
+		{
+			if(wavelength < wavelengths[i + 1])
+			{
+				double u =
+					(wavelength - wavelengths[i]) / (wavelengths[i + 1] - wavelengths[i]);
+				return
+					wavelength_function[i] * (1.0 - u) + wavelength_function[i + 1] * u;
+			}
+		}
+		return wavelength_function[wavelength_function.size() - 1];
+	}
+
+	/*
+	<p>We can then implement a utility function to compute the "spectral radiance to
+	luminance" conversion constants (see Section 14.3 in <a
+	href="https://arxiv.org/pdf/1612.04336.pdf">A Qualitative and Quantitative
+	Evaluation of 8 Clear Sky Models</a> for their definitions):
+	*/
+
+	// The returned constants are in lumen.nm / watt.
+	void ComputeSpectralRadianceToLuminanceFactors(
+		const std::vector<double>& wavelengths,
+		const std::vector<double>& solar_irradiance,
+		double lambda_power, double* k_r, double* k_g, double* k_b)
+	{
+		*k_r = 0.0;
+		*k_g = 0.0;
+		*k_b = 0.0;
+		double solar_r = Interpolate(wavelengths, solar_irradiance, AtmScatteringModel::kLambdaR);
+		double solar_g = Interpolate(wavelengths, solar_irradiance, AtmScatteringModel::kLambdaG);
+		double solar_b = Interpolate(wavelengths, solar_irradiance, AtmScatteringModel::kLambdaB);
+		int dlambda = 1;
+		for(int lambda = kLambdaMin; lambda < kLambdaMax; lambda += dlambda)
+		{
+			double x_bar = CieColorMatchingFunctionTableValue(lambda, 1);
+			double y_bar = CieColorMatchingFunctionTableValue(lambda, 2);
+			double z_bar = CieColorMatchingFunctionTableValue(lambda, 3);
+			const double* xyz2srgb = XYZ_TO_SRGB;
+			double r_bar =
+				xyz2srgb[0] * x_bar + xyz2srgb[1] * y_bar + xyz2srgb[2] * z_bar;
+			double g_bar =
+				xyz2srgb[3] * x_bar + xyz2srgb[4] * y_bar + xyz2srgb[5] * z_bar;
+			double b_bar =
+				xyz2srgb[6] * x_bar + xyz2srgb[7] * y_bar + xyz2srgb[8] * z_bar;
+			double irradiance = Interpolate(wavelengths, solar_irradiance, lambda);
+			*k_r += r_bar * irradiance / solar_r *
+				pow(lambda / AtmScatteringModel::kLambdaR, lambda_power);
+			*k_g += g_bar * irradiance / solar_g *
+				pow(lambda / AtmScatteringModel::kLambdaG, lambda_power);
+			*k_b += b_bar * irradiance / solar_b *
+				pow(lambda / AtmScatteringModel::kLambdaB, lambda_power);
+		}
+		*k_r *= MAX_LUMINOUS_EFFICACY * dlambda;
+		*k_g *= MAX_LUMINOUS_EFFICACY * dlambda;
+		*k_b *= MAX_LUMINOUS_EFFICACY * dlambda;
+	}
+
+}  // anonymous namespace
+
+   /*<h3 id="implementation">AtmScatteringModel implementation</h3>
+
+   <p>Using the above utility functions and classes, we can now implement the
+   constructor of the <code>AtmScatteringModel</code> class. This constructor generates a piece
+   of GLSL code that defines an <code>ATMOSPHERE</code> constant containing the
+   atmosphere parameters (we use constants instead of uniforms to enable constant
+   folding and propagation optimizations in the GLSL compiler), concatenated with
+   <a href="functions.glsl.html">functions.glsl</a>, and with
+   <code>kAtmosphereShader</code>, to get the shader exposed by our API in
+   <code>GetShader</code>. It also allocates the precomputed textures (but does not
+   initialize them), as well as a vertex buffer object to render a full screen quad
+   (used to render into the precomputed textures).
+   */
+
+AtmScatteringModel::AtmScatteringModel(
+	const std::vector<double>& wavelengths,
+	const std::vector<double>& solar_irradiance,
+	const double sun_angular_radius,
+	double bottom_radius,
+	double top_radius,
+	const std::vector<DensityProfileLayer>& rayleigh_density,
+	const std::vector<double>& rayleigh_scattering,
+	const std::vector<DensityProfileLayer>& mie_density,
+	const std::vector<double>& mie_scattering,
+	const std::vector<double>& mie_extinction,
+	double mie_phase_function_g,
+	const std::vector<DensityProfileLayer>& absorption_density,
+	const std::vector<double>& absorption_extinction,
+	const std::vector<double>& ground_albedo,
+	double max_sun_zenith_angle,
+	double length_unit_in_meters,
+	unsigned int num_precomputed_wavelengths,
+	bool combine_scattering_textures,
+	bool half_precision) :
+	num_precomputed_wavelengths_(num_precomputed_wavelengths),
+	half_precision_(half_precision),
+	rgb_format_supported_(IsFramebufferRgbFormatSupported(half_precision))
+{
+	auto to_string = [&wavelengths](const std::vector<double>& v,
+		const vec3& lambdas, double scale) {
+		double r = Interpolate(wavelengths, v, lambdas[0]) * scale;
+		double g = Interpolate(wavelengths, v, lambdas[1]) * scale;
+		double b = Interpolate(wavelengths, v, lambdas[2]) * scale;
+		return "vec3(" + std::to_string(r) + "," + std::to_string(g) + "," +
+			std::to_string(b) + ")";
+	};
+	auto density_layer =
+		[length_unit_in_meters](const DensityProfileLayer& layer) {
+		return "DensityProfileLayer(" +
+			std::to_string(layer.m_width / length_unit_in_meters) + "," +
+			std::to_string(layer.m_expTerm) + "," +
+			std::to_string(layer.m_expScale * length_unit_in_meters) + "," +
+			std::to_string(layer.m_linearTerm * length_unit_in_meters) + "," +
+			std::to_string(layer.m_constantTerm) + ")";
+	};
+	auto density_profile =
+		[density_layer](std::vector<DensityProfileLayer> layers) {
+		constexpr int kLayerCount = 2;
+		while(layers.size() < kLayerCount)
+		{
+			layers.insert(layers.begin(), DensityProfileLayer());
+		}
+		std::string result = "DensityProfile(DensityProfileLayer[" +
+			std::to_string(kLayerCount) + "](";
+		for(int i = 0; i < kLayerCount; ++i)
+		{
+			result += density_layer(layers[i]);
+			result += i < kLayerCount - 1 ? "," : "))";
+		}
+		return result;
+	};
+
+	// Compute the values for the SKY_RADIANCE_TO_LUMINANCE constant. In theory
+	// this should be 1 in precomputed illuminance mode (because the precomputed
+	// textures already contain illuminance values). In practice, however, storing
+	// true illuminance values in half precision textures yields artefacts
+	// (because the values are too large), so we store illuminance values divided
+	// by MAX_LUMINOUS_EFFICACY instead. This is why, in precomputed illuminance
+	// mode, we set SKY_RADIANCE_TO_LUMINANCE to MAX_LUMINOUS_EFFICACY.
+	bool precompute_illuminance = num_precomputed_wavelengths > 3;
+	double sky_k_r, sky_k_g, sky_k_b;
+	if(precompute_illuminance)
+	{
+		sky_k_r = sky_k_g = sky_k_b = MAX_LUMINOUS_EFFICACY;
+	}
+	else
+	{
+		ComputeSpectralRadianceToLuminanceFactors(wavelengths, solar_irradiance,
+			-3 /* lambda_power */, &sky_k_r, &sky_k_g, &sky_k_b);
+	}
+	// Compute the values for the SUN_RADIANCE_TO_LUMINANCE constant.
+	double sun_k_r, sun_k_g, sun_k_b;
+	ComputeSpectralRadianceToLuminanceFactors(wavelengths, solar_irradiance,
+		0 /* lambda_power */, &sun_k_r, &sun_k_g, &sun_k_b);
+
+	// A lambda that creates a GLSL header containing our atmosphere computation
+	// functions, specialized for the given atmosphere parameters and for the 3
+	// wavelengths in 'lambdas'.
+	glsl_header_factory_ = [=](const vec3& lambdas) {
+		return
+			"#version 330\n"
+			"#define IN(x) const in x\n"
+			"#define OUT(x) out x\n"
+			"#define TEMPLATE(x)\n"
+			"#define TEMPLATE_ARGUMENT(x)\n"
+			"#define assert(x)\n"
+			"const int TRANSMITTANCE_TEXTURE_WIDTH = " +
+			std::to_string(TRANSMITTANCE_TEXTURE_WIDTH) + ";\n" +
+			"const int TRANSMITTANCE_TEXTURE_HEIGHT = " +
+			std::to_string(TRANSMITTANCE_TEXTURE_HEIGHT) + ";\n" +
+			"const int SCATTERING_TEXTURE_R_SIZE = " +
+			std::to_string(SCATTERING_TEXTURE_R_SIZE) + ";\n" +
+			"const int SCATTERING_TEXTURE_MU_SIZE = " +
+			std::to_string(SCATTERING_TEXTURE_MU_SIZE) + ";\n" +
+			"const int SCATTERING_TEXTURE_MU_S_SIZE = " +
+			std::to_string(SCATTERING_TEXTURE_MU_S_SIZE) + ";\n" +
+			"const int SCATTERING_TEXTURE_NU_SIZE = " +
+			std::to_string(SCATTERING_TEXTURE_NU_SIZE) + ";\n" +
+			"const int IRRADIANCE_TEXTURE_WIDTH = " +
+			std::to_string(IRRADIANCE_TEXTURE_WIDTH) + ";\n" +
+			"const int IRRADIANCE_TEXTURE_HEIGHT = " +
+			std::to_string(IRRADIANCE_TEXTURE_HEIGHT) + ";\n" +
+			(combine_scattering_textures ?
+				"#define COMBINED_SCATTERING_TEXTURES\n" : "") +
+			definitions_glsl +
+			"const AtmosphereParameters ATMOSPHERE = AtmosphereParameters(\n" +
+			to_string(solar_irradiance, lambdas, 1.0) + ",\n" +
+			std::to_string(sun_angular_radius) + ",\n" +
+			std::to_string(bottom_radius / length_unit_in_meters) + ",\n" +
+			std::to_string(top_radius / length_unit_in_meters) + ",\n" +
+			density_profile(rayleigh_density) + ",\n" +
+			to_string(
+				rayleigh_scattering, lambdas, length_unit_in_meters) + ",\n" +
+			density_profile(mie_density) + ",\n" +
+			to_string(mie_scattering, lambdas, length_unit_in_meters) + ",\n" +
+			to_string(mie_extinction, lambdas, length_unit_in_meters) + ",\n" +
+			std::to_string(mie_phase_function_g) + ",\n" +
+			density_profile(absorption_density) + ",\n" +
+			to_string(
+				absorption_extinction, lambdas, length_unit_in_meters) + ",\n" +
+			to_string(ground_albedo, lambdas, 1.0) + ",\n" +
+			std::to_string(cos(max_sun_zenith_angle)) + ");\n" +
+			"const vec3 SKY_SPECTRAL_RADIANCE_TO_LUMINANCE = vec3(" +
+			std::to_string(sky_k_r) + "," +
+			std::to_string(sky_k_g) + "," +
+			std::to_string(sky_k_b) + ");\n" +
+			"const vec3 SUN_SPECTRAL_RADIANCE_TO_LUMINANCE = vec3(" +
+			std::to_string(sun_k_r) + "," +
+			std::to_string(sun_k_g) + "," +
+			std::to_string(sun_k_b) + ");\n" +
+			functions_glsl;
+	};
+
+	// Allocate the precomputed textures, but don't precompute them yet.
+	transmittance_texture_ = NewTexture2d(
+		TRANSMITTANCE_TEXTURE_WIDTH, TRANSMITTANCE_TEXTURE_HEIGHT);
+	scattering_texture_ = NewTexture3d(
+		SCATTERING_TEXTURE_WIDTH,
+		SCATTERING_TEXTURE_HEIGHT,
+		SCATTERING_TEXTURE_DEPTH,
+		combine_scattering_textures || !rgb_format_supported_ ? GL_RGBA : GL_RGB,
+		half_precision);
+	if(combine_scattering_textures)
+	{
+		optional_single_mie_scattering_texture_ = 0;
+	}
+	else
+	{
+		optional_single_mie_scattering_texture_ = NewTexture3d(
+			SCATTERING_TEXTURE_WIDTH,
+			SCATTERING_TEXTURE_HEIGHT,
+			SCATTERING_TEXTURE_DEPTH,
+			rgb_format_supported_ ? GL_RGB : GL_RGBA,
+			half_precision);
+	}
+	irradiance_texture_ = NewTexture2d(
+		IRRADIANCE_TEXTURE_WIDTH, IRRADIANCE_TEXTURE_HEIGHT);
+
+	// Create and compile the shader providing our API.
+	std::string shader =
+		glsl_header_factory_({ kLambdaR, kLambdaG, kLambdaB }) +
+		(precompute_illuminance ? "" : "#define RADIANCE_API_ENABLED\n") +
+		kAtmosphereShader;
+	//std::cout << shader << std::endl;
+	const char* source = shader.c_str();
+	atmosphere_shader_ = glCreateShader(GL_FRAGMENT_SHADER);
+	glShaderSource(atmosphere_shader_, 1, &source, NULL);
+	glCompileShader(atmosphere_shader_);
+
+	// Create a full screen quad vertex array and vertex buffer objects.
+	glGenVertexArrays(1, &full_screen_quad_vao_);
+	glBindVertexArray(full_screen_quad_vao_);
+	glGenBuffers(1, &full_screen_quad_vbo_);
+	glBindBuffer(GL_ARRAY_BUFFER, full_screen_quad_vbo_);
+	const GLfloat vertices[] = {
+		-1.0, -1.0,
+		+1.0, -1.0,
+		-1.0, +1.0,
+		+1.0, +1.0,
+	};
+	constexpr int kCoordsPerVertex = 2;
+	glBufferData(GL_ARRAY_BUFFER, sizeof vertices, vertices, GL_STATIC_DRAW);
+	constexpr GLuint kAttribIndex = 0;
+	glVertexAttribPointer(kAttribIndex, kCoordsPerVertex, GL_FLOAT, false, 0, 0);
+	glEnableVertexAttribArray(kAttribIndex);
+	glBindVertexArray(0);
+}
+
+/*
+<p>The destructor is trivial:
+*/
+
+AtmScatteringModel::~AtmScatteringModel()
+{
+	glDeleteBuffers(1, &full_screen_quad_vbo_);
+	glDeleteVertexArrays(1, &full_screen_quad_vao_);
+	glDeleteTextures(1, &transmittance_texture_);
+	glDeleteTextures(1, &scattering_texture_);
+	if(optional_single_mie_scattering_texture_ != 0)
+	{
+		glDeleteTextures(1, &optional_single_mie_scattering_texture_);
+	}
+	glDeleteTextures(1, &irradiance_texture_);
+	glDeleteShader(atmosphere_shader_);
+}
+
+/*
+<p>The Init method precomputes the atmosphere textures. It first allocates the
+temporary resources it needs, then calls <code>Precompute</code> to do the
+actual precomputations, and finally destroys the temporary resources.
+
+<p>Note that there are two precomputation modes here, depending on whether we
+want to store precomputed irradiance or illuminance values:
+<ul>
+<li>In precomputed irradiance mode, we simply need to call
+<code>Precompute</code> with the 3 wavelengths for which we want to precompute
+irradiance, namely <code>kLambdaR</code>, <code>kLambdaG</code>,
+<code>kLambdaB</code> (with the identity matrix for
+<code>luminance_from_radiance</code>, since we don't want any conversion from
+radiance to luminance)</li>
+<li>In precomputed illuminance mode, we need to precompute irradiance for
+<code>num_precomputed_wavelengths_</code>, and then integrate the results,
+multiplied with the 3 CIE xyz color matching functions and the XYZ to sRGB
+matrix to get sRGB illuminance values.
+<p>A naive solution would be to allocate temporary textures for the
+intermediate irradiance results, then perform the integration from irradiance
+to illuminance and store the result in the final precomputed texture. In
+pseudo-code (and assuming one wavelength per texture instead of 3):
+<pre>
+create n temporary irradiance textures
+for each wavelength lambda in the n wavelengths:
+precompute irradiance at lambda into one of the temporary textures
+initializes the final illuminance texture with zeros
+for each wavelength lambda in the n wavelengths:
+accumulate in the final illuminance texture the product of the
+precomputed irradiance at lambda (read from the temporary textures)
+with the value of the 3 sRGB color matching functions at lambda (i.e.
+the product of the XYZ to sRGB matrix with the CIE xyz color matching
+functions).
+</pre>
+<p>However, this be would waste GPU memory. Instead, we can avoid allocating
+temporary irradiance textures, by merging the two above loops:
+<pre>
+for each wavelength lambda in the n wavelengths:
+accumulate in the final illuminance texture (or, for the first
+iteration, set this texture to) the product of the precomputed
+irradiance at lambda (computed on the fly) with the value of the 3
+sRGB color matching functions at lambda.
+</pre>
+<p>This is the method we use below, with 3 wavelengths per iteration instead
+of 1, using <code>Precompute</code> to compute 3 irradiances values per
+iteration, and <code>luminance_from_radiance</code> to multiply 3 irradiances
+with the values of the 3 sRGB color matching functions at 3 different
+wavelengths (yielding a 3x3 matrix).</li>
+</ul>
+
+<p>This yields the following implementation:
+*/
+
+void AtmScatteringModel::Init(unsigned int num_scattering_orders)
+{
+	// The precomputations require temporary textures, in particular to store the
+	// contribution of one scattering order, which is needed to compute the next
+	// order of scattering (the final precomputed textures store the sum of all
+	// the scattering orders). We allocate them here, and destroy them at the end
+	// of this method.
+
+
+
+	GLuint delta_irradiance_texture = NewTexture2d(
+		IRRADIANCE_TEXTURE_WIDTH, IRRADIANCE_TEXTURE_HEIGHT);
+	GLuint delta_rayleigh_scattering_texture = NewTexture3d(
+		SCATTERING_TEXTURE_WIDTH,
+		SCATTERING_TEXTURE_HEIGHT,
+		SCATTERING_TEXTURE_DEPTH,
+		rgb_format_supported_ ? GL_RGB : GL_RGBA,
+		half_precision_);
+	GLuint delta_mie_scattering_texture = NewTexture3d(
+		SCATTERING_TEXTURE_WIDTH,
+		SCATTERING_TEXTURE_HEIGHT,
+		SCATTERING_TEXTURE_DEPTH,
+		rgb_format_supported_ ? GL_RGB : GL_RGBA,
+		half_precision_);
+	GLuint delta_scattering_density_texture = NewTexture3d(
+		SCATTERING_TEXTURE_WIDTH,
+		SCATTERING_TEXTURE_HEIGHT,
+		SCATTERING_TEXTURE_DEPTH,
+		rgb_format_supported_ ? GL_RGB : GL_RGBA,
+		half_precision_);
+	// delta_multiple_scattering_texture is only needed to compute scattering
+	// order 3 or more, while delta_rayleigh_scattering_texture and
+	// delta_mie_scattering_texture are only needed to compute double scattering.
+	// Therefore, to save memory, we can store delta_rayleigh_scattering_texture
+	// and delta_multiple_scattering_texture in the same GPU texture.
+	GLuint delta_multiple_scattering_texture = delta_rayleigh_scattering_texture;
+
+	// The precomputations also require a temporary framebuffer object, created
+	// here (and destroyed at the end of this method).
+	GLuint fbo;
+	glGenFramebuffers(1, &fbo);
+	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+	// The actual precomputations depend on whether we want to store precomputed
+	// irradiance or illuminance values.
+	if(num_precomputed_wavelengths_ <= 3)
+	{
+		vec3 lambdas{ kLambdaR, kLambdaG, kLambdaB };
+		mat3 luminance_from_radiance{ 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 };
+		Precompute(fbo, delta_irradiance_texture, delta_rayleigh_scattering_texture,
+			delta_mie_scattering_texture, delta_scattering_density_texture,
+			delta_multiple_scattering_texture, lambdas, luminance_from_radiance,
+			false /* blend */, num_scattering_orders);
+	}
+	else
+	{
+		constexpr double kLambdaMin = 360.0;
+		constexpr double kLambdaMax = 830.0;
+		int num_iterations = (num_precomputed_wavelengths_ + 2) / 3;
+		double dlambda = (kLambdaMax - kLambdaMin) / (3 * num_iterations);
+		for(int i = 0; i < num_iterations; ++i)
+		{
+			vec3 lambdas{
+				kLambdaMin + (3 * i + 0.5) * dlambda,
+				kLambdaMin + (3 * i + 1.5) * dlambda,
+				kLambdaMin + (3 * i + 2.5) * dlambda
+			};
+			auto coeff = [dlambda](double lambda, int component) {
+				// Note that we don't include MAX_LUMINOUS_EFFICACY here, to avoid
+				// artefacts due to too large values when using half precision on GPU.
+				// We add this term back in kAtmosphereShader, via
+				// SKY_SPECTRAL_RADIANCE_TO_LUMINANCE (see also the comments in the
+				// AtmScatteringModel constructor).
+				double x = CieColorMatchingFunctionTableValue(lambda, 1);
+				double y = CieColorMatchingFunctionTableValue(lambda, 2);
+				double z = CieColorMatchingFunctionTableValue(lambda, 3);
+				return static_cast<float>((
+					XYZ_TO_SRGB[component * 3] * x +
+					XYZ_TO_SRGB[component * 3 + 1] * y +
+					XYZ_TO_SRGB[component * 3 + 2] * z) * dlambda);
+			};
+			mat3 luminance_from_radiance{
+				coeff(lambdas[0], 0), coeff(lambdas[1], 0), coeff(lambdas[2], 0),
+				coeff(lambdas[0], 1), coeff(lambdas[1], 1), coeff(lambdas[2], 1),
+				coeff(lambdas[0], 2), coeff(lambdas[1], 2), coeff(lambdas[2], 2)
+			};
+			Precompute(fbo, delta_irradiance_texture,
+				delta_rayleigh_scattering_texture, delta_mie_scattering_texture,
+				delta_scattering_density_texture, delta_multiple_scattering_texture,
+				lambdas, luminance_from_radiance, i > 0 /* blend */,
+				num_scattering_orders);
+		}
+
+		// After the above iterations, the transmittance texture contains the
+		// transmittance for the 3 wavelengths used at the last iteration. But we
+		// want the transmittance at kLambdaR, kLambdaG, kLambdaB instead, so we
+		// must recompute it here for these 3 wavelengths:
+		std::string header = glsl_header_factory_({ kLambdaR, kLambdaG, kLambdaB });
+		Program compute_transmittance(
+			m_vertexShaderSource, header + kComputeTransmittanceShader);
+		glFramebufferTexture(
+			GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, transmittance_texture_, 0);
+		glDrawBuffer(GL_COLOR_ATTACHMENT0);
+		glViewport(0, 0, TRANSMITTANCE_TEXTURE_WIDTH, TRANSMITTANCE_TEXTURE_HEIGHT);
+		compute_transmittance.Use();
+		DrawQuad({}, full_screen_quad_vao_);
+	}
+
+	// Delete the temporary resources allocated at the begining of this method.
+	glUseProgram(0);
+	glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	glDeleteFramebuffers(1, &fbo);
+	glDeleteTextures(1, &delta_scattering_density_texture);
+	glDeleteTextures(1, &delta_mie_scattering_texture);
+	glDeleteTextures(1, &delta_rayleigh_scattering_texture);
+	glDeleteTextures(1, &delta_irradiance_texture);
+	assert(glGetError() == 0);
+}
+
+/*
+<p>The <code>SetProgramUniforms</code> method is straightforward: it simply
+binds the precomputed textures to the specified texture units, and then sets
+the corresponding uniforms in the user provided program to the index of these
+texture units.
+*/
+
+void AtmScatteringModel::SetProgramUniforms(
+	unsigned int program,
+	unsigned int transmittance_texture_unit,
+	unsigned int scattering_texture_unit,
+	unsigned int irradiance_texture_unit,
+	unsigned int single_mie_scattering_texture_unit) const
+{
+	glActiveTexture(GL_TEXTURE0 + transmittance_texture_unit);
+	glBindTexture(GL_TEXTURE_2D, transmittance_texture_);
+	glUniform1i(glGetUniformLocation(program, "transmittance_texture"),
+		transmittance_texture_unit);
+
+	glActiveTexture(GL_TEXTURE0 + scattering_texture_unit);
+	glBindTexture(GL_TEXTURE_3D, scattering_texture_);
+	glUniform1i(glGetUniformLocation(program, "scattering_texture"),
+		scattering_texture_unit);
+
+	glActiveTexture(GL_TEXTURE0 + irradiance_texture_unit);
+	glBindTexture(GL_TEXTURE_2D, irradiance_texture_);
+	glUniform1i(glGetUniformLocation(program, "irradiance_texture"),
+		irradiance_texture_unit);
+
+	if(optional_single_mie_scattering_texture_ != 0)
+	{
+		glActiveTexture(GL_TEXTURE0 + single_mie_scattering_texture_unit);
+		glBindTexture(GL_TEXTURE_3D, optional_single_mie_scattering_texture_);
+		glUniform1i(glGetUniformLocation(program, "single_mie_scattering_texture"),
+			single_mie_scattering_texture_unit);
+	}
+}
+
+/*
+<p>The utility method <code>ConvertSpectrumToLinearSrgb</code> is implemented
+with a simple numerical integration of the given function, times the CIE color
+matching funtions (with an integration step of 1nm), followed by a matrix
+multiplication:
+*/
+
+void AtmScatteringModel::ConvertSpectrumToLinearSrgb(
+	const std::vector<double>& wavelengths,
+	const std::vector<double>& spectrum,
+	double* r, double* g, double* b)
+{
+	double x = 0.0;
+	double y = 0.0;
+	double z = 0.0;
+	const int dlambda = 1;
+	for(int lambda = kLambdaMin; lambda < kLambdaMax; lambda += dlambda)
+	{
+		double value = Interpolate(wavelengths, spectrum, lambda);
+		x += CieColorMatchingFunctionTableValue(lambda, 1) * value;
+		y += CieColorMatchingFunctionTableValue(lambda, 2) * value;
+		z += CieColorMatchingFunctionTableValue(lambda, 3) * value;
+	}
+	*r = MAX_LUMINOUS_EFFICACY *
+		(XYZ_TO_SRGB[0] * x + XYZ_TO_SRGB[1] * y + XYZ_TO_SRGB[2] * z) * dlambda;
+	*g = MAX_LUMINOUS_EFFICACY *
+		(XYZ_TO_SRGB[3] * x + XYZ_TO_SRGB[4] * y + XYZ_TO_SRGB[5] * z) * dlambda;
+	*b = MAX_LUMINOUS_EFFICACY *
+		(XYZ_TO_SRGB[6] * x + XYZ_TO_SRGB[7] * y + XYZ_TO_SRGB[8] * z) * dlambda;
+}
+
+/*
+<p>Finally, we provide the actual implementation of the precomputation algorithm
+described in Algorithm 4.1 of
+<a href="https://hal.inria.fr/inria-00288758/en">our paper</a>. Each step is
+explained by the inline comments below.
+*/
+void AtmScatteringModel::Precompute(
+	unsigned int fbo,
+	unsigned int delta_irradiance_texture,
+	unsigned int delta_rayleigh_scattering_texture,
+	unsigned int delta_mie_scattering_texture,
+	unsigned int delta_scattering_density_texture,
+	unsigned int delta_multiple_scattering_texture,
+	const vec3& lambdas,
+	const mat3& luminance_from_radiance,
+	bool blend,
+	unsigned int num_scattering_orders)
+{
+	// The precomputations require specific GLSL programs, for each precomputation
+	// step. We create and compile them here (they are automatically destroyed
+	// when this method returns, via the Program destructor).
+	std::string header = glsl_header_factory_(lambdas);
+	Program compute_transmittance(
+		m_vertexShaderSource, header + kComputeTransmittanceShader);
+	Program compute_direct_irradiance(
+		m_vertexShaderSource, header + kComputeDirectIrradianceShader);
+	Program compute_single_scattering(m_vertexShaderSource, kGeometryShader,
+		header + kComputeSingleScatteringShader);
+	Program compute_scattering_density(m_vertexShaderSource, kGeometryShader,
+		header + kComputeScatteringDensityShader);
+	Program compute_indirect_irradiance(
+		m_vertexShaderSource, header + kComputeIndirectIrradianceShader);
+	Program compute_multiple_scattering(m_vertexShaderSource, kGeometryShader,
+		header + kComputeMultipleScatteringShader);
+
+	const GLuint kDrawBuffers[4] = {
+		GL_COLOR_ATTACHMENT0,
+		GL_COLOR_ATTACHMENT1,
+		GL_COLOR_ATTACHMENT2,
+		GL_COLOR_ATTACHMENT3
+	};
+	glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
+	glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, GL_ONE);
+
+	// Compute the transmittance, and store it in transmittance_texture_.
+	glFramebufferTexture(
+		GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, transmittance_texture_, 0);
+	glDrawBuffer(GL_COLOR_ATTACHMENT0);
+	glViewport(0, 0, TRANSMITTANCE_TEXTURE_WIDTH, TRANSMITTANCE_TEXTURE_HEIGHT);
+	compute_transmittance.Use();
+	DrawQuad({}, full_screen_quad_vao_);
+
+	// Compute the direct irradiance, store it in delta_irradiance_texture and,
+	// depending on 'blend', either initialize irradiance_texture_ with zeros or
+	// leave it unchanged (we don't want the direct irradiance in
+	// irradiance_texture_, but only the irradiance from the sky).
+	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+		delta_irradiance_texture, 0);
+	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1,
+		irradiance_texture_, 0);
+	glDrawBuffers(2, kDrawBuffers);
+	glViewport(0, 0, IRRADIANCE_TEXTURE_WIDTH, IRRADIANCE_TEXTURE_HEIGHT);
+	compute_direct_irradiance.Use();
+	compute_direct_irradiance.BindTexture2d(
+		"transmittance_texture", transmittance_texture_, 0);
+	DrawQuad({ false, blend }, full_screen_quad_vao_);
+
+	// Compute the rayleigh and mie single scattering, store them in
+	// delta_rayleigh_scattering_texture and delta_mie_scattering_texture, and
+	// either store them or accumulate them in scattering_texture_ and
+	// optional_single_mie_scattering_texture_.
+	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+		delta_rayleigh_scattering_texture, 0);
+	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1,
+		delta_mie_scattering_texture, 0);
+	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2,
+		scattering_texture_, 0);
+	if(optional_single_mie_scattering_texture_ != 0)
+	{
+		glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3,
+			optional_single_mie_scattering_texture_, 0);
+		glDrawBuffers(4, kDrawBuffers);
+	}
+	else
+	{
+		glDrawBuffers(3, kDrawBuffers);
+	}
+	glViewport(0, 0, SCATTERING_TEXTURE_WIDTH, SCATTERING_TEXTURE_HEIGHT);
+	compute_single_scattering.Use();
+	compute_single_scattering.BindMat3(
+		"luminance_from_radiance", luminance_from_radiance);
+	compute_single_scattering.BindTexture2d(
+		"transmittance_texture", transmittance_texture_, 0);
+	for(unsigned int layer = 0; layer < SCATTERING_TEXTURE_DEPTH; ++layer)
+	{
+		compute_single_scattering.BindInt("layer", layer);
+		DrawQuad({ false, false, blend, blend }, full_screen_quad_vao_);
+	}
+
+	// Compute the 2nd, 3rd and 4th order of scattering, in sequence.
+	for(unsigned int scattering_order = 2;
+		scattering_order <= num_scattering_orders;
+		++scattering_order)
+	{
+		// Compute the scattering density, and store it in
+		// delta_scattering_density_texture.
+		glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+			delta_scattering_density_texture, 0);
+		glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, 0, 0);
+		glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, 0, 0);
+		glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, 0, 0);
+		glDrawBuffer(GL_COLOR_ATTACHMENT0);
+		glViewport(0, 0, SCATTERING_TEXTURE_WIDTH, SCATTERING_TEXTURE_HEIGHT);
+		compute_scattering_density.Use();
+		compute_scattering_density.BindTexture2d(
+			"transmittance_texture", transmittance_texture_, 0);
+		compute_scattering_density.BindTexture3d(
+			"single_rayleigh_scattering_texture",
+			delta_rayleigh_scattering_texture,
+			1);
+		compute_scattering_density.BindTexture3d(
+			"single_mie_scattering_texture", delta_mie_scattering_texture, 2);
+		compute_scattering_density.BindTexture3d(
+			"multiple_scattering_texture", delta_multiple_scattering_texture, 3);
+		compute_scattering_density.BindTexture2d(
+			"irradiance_texture", delta_irradiance_texture, 4);
+		compute_scattering_density.BindInt("scattering_order", scattering_order);
+		for(unsigned int layer = 0; layer < SCATTERING_TEXTURE_DEPTH; ++layer)
+		{
+			compute_scattering_density.BindInt("layer", layer);
+			DrawQuad({}, full_screen_quad_vao_);
+		}
+
+		// Compute the indirect irradiance, store it in delta_irradiance_texture and
+		// accumulate it in irradiance_texture_.
+		glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+			delta_irradiance_texture, 0);
+		glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1,
+			irradiance_texture_, 0);
+		glDrawBuffers(2, kDrawBuffers);
+		glViewport(0, 0, IRRADIANCE_TEXTURE_WIDTH, IRRADIANCE_TEXTURE_HEIGHT);
+		compute_indirect_irradiance.Use();
+		compute_indirect_irradiance.BindMat3(
+			"luminance_from_radiance", luminance_from_radiance);
+		compute_indirect_irradiance.BindTexture3d(
+			"single_rayleigh_scattering_texture",
+			delta_rayleigh_scattering_texture,
+			0);
+		compute_indirect_irradiance.BindTexture3d(
+			"single_mie_scattering_texture", delta_mie_scattering_texture, 1);
+		compute_indirect_irradiance.BindTexture3d(
+			"multiple_scattering_texture", delta_multiple_scattering_texture, 2);
+		compute_indirect_irradiance.BindInt("scattering_order",
+			scattering_order - 1);
+		DrawQuad({ false, true }, full_screen_quad_vao_);
+
+		// Compute the multiple scattering, store it in
+		// delta_multiple_scattering_texture, and accumulate it in
+		// scattering_texture_.
+		glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+			delta_multiple_scattering_texture, 0);
+		glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1,
+			scattering_texture_, 0);
+		glDrawBuffers(2, kDrawBuffers);
+		glViewport(0, 0, SCATTERING_TEXTURE_WIDTH, SCATTERING_TEXTURE_HEIGHT);
+		compute_multiple_scattering.Use();
+		compute_multiple_scattering.BindMat3(
+			"luminance_from_radiance", luminance_from_radiance);
+		compute_multiple_scattering.BindTexture2d(
+			"transmittance_texture", transmittance_texture_, 0);
+		compute_multiple_scattering.BindTexture3d(
+			"scattering_density_texture", delta_scattering_density_texture, 1);
+		for(unsigned int layer = 0; layer < SCATTERING_TEXTURE_DEPTH; ++layer)
+		{
+			compute_multiple_scattering.BindInt("layer", layer);
+			DrawQuad({ false, true }, full_screen_quad_vao_);
+		}
+	}
+	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, 0, 0);
+	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, 0, 0);
+	glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, 0, 0);
+}
+

+ 345 - 0
Praxis3D/Source/AtmScatteringModel.h

@@ -0,0 +1,345 @@
+#pragma once
+
+/**
+* Copyright (c) 2017 Eric Bruneton
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in the
+*    documentation and/or other materials provided with the distribution.
+* 3. Neither the name of the copyright holders nor the names of its
+*    contributors may be used to endorse or promote products derived from
+*    this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+* THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*<h2>atmosphere/model.h</h2>
+
+<p>This file defines the API to use our atmosphere model in OpenGL applications.
+To use it:
+<ul>
+<li>create a <code>AtmScatteringModel</code> instance with the desired atmosphere
+parameters.</li>
+<li>call <code>Init</code> to precompute the atmosphere textures,</li>
+<li>link <code>GetShader</code> with your shaders that need access to the
+atmosphere shading functions.</li>
+<li>for each GLSL program linked with <code>GetShader</code>, call
+<code>SetProgramUniforms</code> to bind the precomputed textures to this
+program (usually at each frame).</li>
+<li>delete your <code>AtmScatteringModel</code> when you no longer need its shader and
+precomputed textures (the destructor deletes these resources).</li>
+</ul>
+
+<p>The shader returned by <code>GetShader</code> provides the following
+functions (that you need to forward declare in your own shaders to be able to
+compile them separately):
+
+<pre class="prettyprint">
+// Returns the radiance of the Sun, outside the atmosphere.
+vec3 GetSolarRadiance();
+
+// Returns the sky radiance along the segment from 'camera' to the nearest
+// atmosphere boundary in direction 'view_ray', as well as the transmittance
+// along this segment.
+vec3 GetSkyRadiance(vec3 camera, vec3 view_ray, double shadow_length,
+vec3 sun_direction, out vec3 transmittance);
+
+// Returns the sky radiance along the segment from 'camera' to 'p', as well as
+// the transmittance along this segment.
+vec3 GetSkyRadianceToPoint(vec3 camera, vec3 p, double shadow_length,
+vec3 sun_direction, out vec3 transmittance);
+
+// Returns the sun and sky irradiance received on a surface patch located at 'p'
+// and whose normal vector is 'normal'.
+vec3 GetSunAndSkyIrradiance(vec3 p, vec3 normal, vec3 sun_direction,
+out vec3 sky_irradiance);
+
+// Returns the luminance of the Sun, outside the atmosphere.
+vec3 GetSolarLuminance();
+
+// Returns the sky luminance along the segment from 'camera' to the nearest
+// atmosphere boundary in direction 'view_ray', as well as the transmittance
+// along this segment.
+vec3 GetSkyLuminance(vec3 camera, vec3 view_ray, double shadow_length,
+vec3 sun_direction, out vec3 transmittance);
+
+// Returns the sky luminance along the segment from 'camera' to 'p', as well as
+// the transmittance along this segment.
+vec3 GetSkyLuminanceToPoint(vec3 camera, vec3 p, double shadow_length,
+vec3 sun_direction, out vec3 transmittance);
+
+// Returns the sun and sky illuminance received on a surface patch located at
+// 'p' and whose normal vector is 'normal'.
+vec3 GetSunAndSkyIlluminance(vec3 p, vec3 normal, vec3 sun_direction,
+out vec3 sky_illuminance);
+</pre>
+
+<p>where
+<ul>
+<li><code>camera</code> and <code>p</code> must be expressed in a reference
+frame where the planet center is at the origin, and measured in the unit passed
+to the constructor's <code>length_unit_in_meters</code> argument.
+<code>camera</code> can be in space, but <code>p</code> must be inside the
+atmosphere,</li>
+<li><code>view_ray</code>, <code>sun_direction</code> and <code>normal</code>
+are unit direction vectors expressed in the same reference frame (with
+<code>sun_direction</code> pointing <i>towards</i> the Sun),</li>
+<li><code>shadow_length</code> is the length along the segment which is in
+shadow, measured in the unit passed to the constructor's
+<code>length_unit_in_meters</code> argument.</li>
+</ul>
+
+<p>and where
+<ul>
+<li>the first 4 functions return spectral radiance and irradiance values
+(in $W.m^{-2}.sr^{-1}.nm^{-1}$ and $W.m^{-2}.nm^{-1}$), at the 3 wavelengths
+<code>kLambdaR</code>, <code>kLambdaG</code>, <code>kLambdaB</code> (in this
+order),</li>
+<li>the other functions return luminance and illuminance values (in
+$cd.m^{-2}$ and $lx$) in linear <a href="https://en.wikipedia.org/wiki/SRGB">
+sRGB</a> space (i.e. before adjustements for gamma correction),</li>
+<li>all the functions return the (unitless) transmittance of the atmosphere
+along the specified segment at the 3 wavelengths <code>kLambdaR</code>,
+<code>kLambdaG</code>, <code>kLambdaB</code> (in this order).</li>
+</ul>
+
+<p><b>Note</b> The precomputed atmosphere textures can store either irradiance
+or illuminance values (see the <code>num_precomputed_wavelengths</code>
+parameter):
+<ul>
+<li>when using irradiance values, the RGB channels of these textures contain
+spectral irradiance values, in $W.m^{-2}.nm^{-1}$, at the 3 wavelengths
+<code>kLambdaR</code>, <code>kLambdaG</code>, <code>kLambdaB</code> (in this
+order). The API functions returning radiance values return these precomputed
+values (times the phase functions), while the API functions returning
+luminance values use the approximation described in
+<a href="https://arxiv.org/pdf/1612.04336.pdf">A Qualitative and Quantitative
+Evaluation of 8 Clear Sky Models</a>, section 14.3, to convert 3 radiance
+values to linear sRGB luminance values.</li>
+<li>when using illuminance values, the RGB channels of these textures contain
+illuminance values, in $lx$, in linear sRGB space. These illuminance values
+are precomputed as described in
+<a href="http://www.oskee.wz.cz/stranka/uploads/SCCG10ElekKmoch.pdf">Real-time
+Spectral Scattering in Large-scale Natural Participating Media</a>, section
+4.4 (i.e. <code>num_precomputed_wavelengths</code> irradiance values are
+precomputed, and then converted to sRGB via a numerical integration of this
+spectrum with the CIE color matching functions). The API functions returning
+luminance values return these precomputed values (times the phase functions),
+while <i>the API functions returning radiance values are not provided</i>.
+</li>
+</ul>
+
+<p>The concrete API definition is the following:
+*/
+
+#include <array>
+#include <functional>
+#include <string>
+#include <vector>
+
+// An atmosphere layer of width 'width' (in m), and whose density is defined as
+//   'exp_term' * exp('exp_scale' * h) + 'linear_term' * h + 'constant_term',
+// clamped to [0,1], and where h is the altitude (in m). 'exp_term' and
+// 'constant_term' are unitless, while 'exp_scale' and 'linear_term' are in
+// m^-1.
+class DensityProfileLayer {
+public:
+	DensityProfileLayer() : DensityProfileLayer(0.0, 0.0, 0.0, 0.0, 0.0)
+	{
+	}
+	DensityProfileLayer(double p_width, double p_expTerm, double p_expScale,
+		double p_linearTerm, double p_constantTerm)
+		: m_width(p_width), m_expTerm(p_expTerm), m_expScale(p_expScale),
+		m_linearTerm(p_linearTerm), m_constantTerm(p_constantTerm)
+	{
+	}
+	double m_width;
+	double m_expTerm;
+	double m_expScale;
+	double m_linearTerm;
+	double m_constantTerm;
+};
+
+class AtmScatteringModel 
+{
+public:
+	AtmScatteringModel(
+		// The wavelength values, in nanometers, and sorted in increasing order, for
+		// which the solar_irradiance, rayleigh_scattering, mie_scattering,
+		// mie_extinction and ground_albedo samples are provided. If your shaders
+		// use luminance values (as opposed to radiance values, see above), use a
+		// large number of wavelengths (e.g. between 15 and 50) to get accurate
+		// results (this number of wavelengths has absolutely no impact on the
+		// shader performance).
+		const std::vector<double>& wavelengths,
+		// The solar irradiance at the top of the atmosphere, in W/m^2/nm. This
+		// vector must have the same size as the wavelengths parameter.
+		const std::vector<double>& solar_irradiance,
+		// The sun's angular radius, in radians. Warning: the implementation uses
+		// approximations that are valid only if this value is smaller than 0.1.
+		double sun_angular_radius,
+		// The distance between the planet center and the bottom of the atmosphere,
+		// in m.
+		double bottom_radius,
+		// The distance between the planet center and the top of the atmosphere,
+		// in m.
+		double top_radius,
+		// The density profile of air molecules, i.e. a function from altitude to
+		// dimensionless values between 0 (null density) and 1 (maximum density).
+		// Layers must be sorted from bottom to top. The width of the last layer is
+		// ignored, i.e. it always extend to the top atmosphere boundary. At most 2
+		// layers can be specified.
+		const std::vector<DensityProfileLayer>& rayleigh_density,
+		// The scattering coefficient of air molecules at the altitude where their
+		// density is maximum (usually the bottom of the atmosphere), as a function
+		// of wavelength, in m^-1. The scattering coefficient at altitude h is equal
+		// to 'rayleigh_scattering' times 'rayleigh_density' at this altitude. This
+		// vector must have the same size as the wavelengths parameter.
+		const std::vector<double>& rayleigh_scattering,
+		// The density profile of aerosols, i.e. a function from altitude to
+		// dimensionless values between 0 (null density) and 1 (maximum density).
+		// Layers must be sorted from bottom to top. The width of the last layer is
+		// ignored, i.e. it always extend to the top atmosphere boundary. At most 2
+		// layers can be specified.
+		const std::vector<DensityProfileLayer>& mie_density,
+		// The scattering coefficient of aerosols at the altitude where their
+		// density is maximum (usually the bottom of the atmosphere), as a function
+		// of wavelength, in m^-1. The scattering coefficient at altitude h is equal
+		// to 'mie_scattering' times 'mie_density' at this altitude. This vector
+		// must have the same size as the wavelengths parameter.
+		const std::vector<double>& mie_scattering,
+		// The extinction coefficient of aerosols at the altitude where their
+		// density is maximum (usually the bottom of the atmosphere), as a function
+		// of wavelength, in m^-1. The extinction coefficient at altitude h is equal
+		// to 'mie_extinction' times 'mie_density' at this altitude. This vector
+		// must have the same size as the wavelengths parameter.
+		const std::vector<double>& mie_extinction,
+		// The asymetry parameter for the Cornette-Shanks phase function for the
+		// aerosols.
+		double mie_phase_function_g,
+		// The density profile of air molecules that absorb light (e.g. ozone), i.e.
+		// a function from altitude to dimensionless values between 0 (null density)
+		// and 1 (maximum density). Layers must be sorted from bottom to top. The
+		// width of the last layer is ignored, i.e. it always extend to the top
+		// atmosphere boundary. At most 2 layers can be specified.
+		const std::vector<DensityProfileLayer>& absorption_density,
+		// The extinction coefficient of molecules that absorb light (e.g. ozone) at
+		// the altitude where their density is maximum, as a function of wavelength,
+		// in m^-1. The extinction coefficient at altitude h is equal to
+		// 'absorption_extinction' times 'absorption_density' at this altitude. This
+		// vector must have the same size as the wavelengths parameter.
+		const std::vector<double>& absorption_extinction,
+		// The average albedo of the ground, as a function of wavelength. This
+		// vector must have the same size as the wavelengths parameter.
+		const std::vector<double>& ground_albedo,
+		// The maximum Sun zenith angle for which atmospheric scattering must be
+		// precomputed, in radians (for maximum precision, use the smallest Sun
+		// zenith angle yielding negligible sky light radiance values. For instance,
+		// for the Earth case, 102 degrees is a good choice for most cases (120
+		// degrees is necessary for very high exposure values).
+		double max_sun_zenith_angle,
+		// The length unit used in your shaders and meshes. This is the length unit
+		// which must be used when calling the atmosphere model shader functions.
+		double length_unit_in_meters,
+		// The number of wavelengths for which atmospheric scattering must be
+		// precomputed (the temporary GPU memory used during precomputations, and
+		// the GPU memory used by the precomputed results, is independent of this
+		// number, but the <i>precomputation time is directly proportional to this
+		// number</i>):
+		// - if this number is less than or equal to 3, scattering is precomputed
+		// for 3 wavelengths, and stored as irradiance values. Then both the
+		// radiance-based and the luminance-based API functions are provided (see
+		// the above note).
+		// - otherwise, scattering is precomputed for this number of wavelengths
+		// (rounded up to a multiple of 3), integrated with the CIE color matching
+		// functions, and stored as illuminance values. Then only the
+		// luminance-based API functions are provided (see the above note).
+		unsigned int num_precomputed_wavelengths,
+		// Whether to pack the (red component of the) single Mie scattering with the
+		// Rayleigh and multiple scattering in a single texture, or to store the
+		// (3 components of the) single Mie scattering in a separate texture.
+		bool combine_scattering_textures,
+		// Whether to use half precision floats (16 bits) or single precision floats
+		// (32 bits) for the precomputed textures. Half precision is sufficient for
+		// most cases, except for very high exposure values.
+		bool half_precision);
+
+	~AtmScatteringModel();
+
+	void Init(unsigned int num_scattering_orders = 4);
+
+	unsigned int GetShader() const
+	{
+		return atmosphere_shader_;
+	}
+
+	void SetProgramUniforms(
+		unsigned int program,
+		unsigned int transmittance_texture_unit,
+		unsigned int scattering_texture_unit,
+		unsigned int irradiance_texture_unit,
+		unsigned int optional_single_mie_scattering_texture_unit = 0) const;
+
+	const inline unsigned int getIrradianceTexture() const		{ return irradiance_texture_; }
+	const inline unsigned int getScatteringTexture() const		{ return scattering_texture_; }
+	const inline unsigned int getSingleMieTexture() const		{ return optional_single_mie_scattering_texture_; }
+	const inline unsigned int getTransmittanceTexture() const	{ return transmittance_texture_; }
+
+	// Utility method to convert a function of the wavelength to linear sRGB.
+	// 'wavelengths' and 'spectrum' must have the same size. The integral of
+	// 'spectrum' times each CIE_2_DEG_COLOR_MATCHING_FUNCTIONS (and times
+	// MAX_LUMINOUS_EFFICACY) is computed to get XYZ values, which are then
+	// converted to linear sRGB with the XYZ_TO_SRGB matrix.
+	static void ConvertSpectrumToLinearSrgb(
+		const std::vector<double>& wavelengths,
+		const std::vector<double>& spectrum,
+		double* r, double* g, double* b);
+
+	static constexpr double kLambdaR = 680.0;
+	static constexpr double kLambdaG = 550.0;
+	static constexpr double kLambdaB = 440.0;
+
+private:
+	typedef std::array<double, 3> vec3;
+	typedef std::array<float, 9> mat3;
+
+	void Precompute(
+		unsigned int fbo,
+		unsigned int delta_irradiance_texture,
+		unsigned int delta_rayleigh_scattering_texture,
+		unsigned int delta_mie_scattering_texture,
+		unsigned int delta_scattering_density_texture,
+		unsigned int delta_multiple_scattering_texture,
+		const vec3& lambdas,
+		const mat3& luminance_from_radiance,
+		bool blend,
+		unsigned int num_scattering_orders);
+
+	unsigned int num_precomputed_wavelengths_;
+	bool half_precision_;
+	bool rgb_format_supported_;
+	std::function<std::string(const vec3&)> glsl_header_factory_;
+	unsigned int transmittance_texture_;
+	unsigned int scattering_texture_;
+	unsigned int optional_single_mie_scattering_texture_;
+	unsigned int irradiance_texture_;
+	unsigned int atmosphere_shader_;
+	unsigned int full_screen_quad_vao_;
+	unsigned int full_screen_quad_vbo_;
+};

+ 192 - 0
Praxis3D/Source/AtmScatteringPass.cpp

@@ -0,0 +1,192 @@
+
+#include "AtmScatteringPass.h"
+#include "AtmScatteringShaderPass.h"
+
+
+void AtmScatteringPass::InitModel()
+{
+	// Values from "Reference Solar Spectral Irradiance: ASTM G-173", ETR column
+	// (see http://rredc.nrel.gov/solar/spectra/am1.5/ASTMG173/ASTMG173.html),
+	// summed and averaged in each bin (e.g. the value for 360nm is the average
+	// of the ASTM G-173 values for all wavelengths between 360 and 370nm).
+	// Values in W.m^-2.
+	constexpr int kLambdaMin = 360;
+	constexpr int kLambdaMax = 830;
+	constexpr double kSolarIrradiance[48] = {
+		1.11776, 1.14259, 1.01249, 1.14716, 1.72765, 1.73054, 1.6887, 1.61253,
+		1.91198, 2.03474, 2.02042, 2.02212, 1.93377, 1.95809, 1.91686, 1.8298,
+		1.8685, 1.8931, 1.85149, 1.8504, 1.8341, 1.8345, 1.8147, 1.78158, 1.7533,
+		1.6965, 1.68194, 1.64654, 1.6048, 1.52143, 1.55622, 1.5113, 1.474, 1.4482,
+		1.41018, 1.36775, 1.34188, 1.31429, 1.28303, 1.26758, 1.2367, 1.2082,
+		1.18737, 1.14683, 1.12362, 1.1058, 1.07124, 1.04992
+	};
+	// Values from http://www.iup.uni-bremen.de/gruppen/molspec/databases/
+	// referencespectra/o3spectra2011/index.html for 233K, summed and averaged in
+	// each bin (e.g. the value for 360nm is the average of the original values
+	// for all wavelengths between 360 and 370nm). Values in m^2.
+	constexpr double kOzoneCrossSection[48] = {
+		1.18e-27, 2.182e-28, 2.818e-28, 6.636e-28, 1.527e-27, 2.763e-27, 5.52e-27,
+		8.451e-27, 1.582e-26, 2.316e-26, 3.669e-26, 4.924e-26, 7.752e-26, 9.016e-26,
+		1.48e-25, 1.602e-25, 2.139e-25, 2.755e-25, 3.091e-25, 3.5e-25, 4.266e-25,
+		4.672e-25, 4.398e-25, 4.701e-25, 5.019e-25, 4.305e-25, 3.74e-25, 3.215e-25,
+		2.662e-25, 2.238e-25, 1.852e-25, 1.473e-25, 1.209e-25, 9.423e-26, 7.455e-26,
+		6.566e-26, 5.105e-26, 4.15e-26, 4.228e-26, 3.237e-26, 2.451e-26, 2.801e-26,
+		2.534e-26, 1.624e-26, 1.465e-26, 2.078e-26, 1.383e-26, 7.105e-27
+	};
+	// From https://en.wikipedia.org/wiki/Dobson_unit, in molecules.m^-2.
+	constexpr double kDobsonUnit = 2.687e20;
+	// Maximum number density of ozone molecules, in m^-3 (computed so at to get
+	// 300 Dobson units of ozone - for this we divide 300 DU by the integral of
+	// the ozone density profile defined below, which is equal to 15km).
+	constexpr double kMaxOzoneNumberDensity = 300.0 * kDobsonUnit / 15000.0;
+	// Wavelength independent solar irradiance "spectrum" (not physically
+	// realistic, but was used in the original implementation).
+	constexpr double kConstantSolarIrradiance = 1.5;
+	constexpr double kBottomRadius = 6360000.0;
+	constexpr double kTopRadius = 6420000.0;
+	constexpr double kRayleigh = 1.24062e-6;
+	constexpr double kRayleighScaleHeight = 8000.0;
+	constexpr double kMieScaleHeight = 1200.0;
+	constexpr double kMieAngstromAlpha = 0.0;
+	constexpr double kMieAngstromBeta = 5.328e-3;
+	constexpr double kMieSingleScatteringAlbedo = 0.9;
+	constexpr double kMiePhaseFunctionG = 0.8;
+	constexpr double kGroundAlbedo = 0.1;
+	const double max_sun_zenith_angle =
+		(m_useHalfPrecision ? 102.0 : 120.0) / 180.0 * PI;
+
+	DensityProfileLayer
+		rayleigh_layer(0.0, 1.0, -1.0 / kRayleighScaleHeight, 0.0, 0.0);
+	DensityProfileLayer mie_layer(0.0, 1.0, -1.0 / kMieScaleHeight, 0.0, 0.0);
+	// Density profile increasing linearly from 0 to 1 between 10 and 25km, and
+	// decreasing linearly from 1 to 0 between 25 and 40km. This is an approximate
+	// profile from http://www.kln.ac.lk/science/Chemistry/Teaching_Resources/
+	// Documents/Introduction%20to%20atmospheric%20chemistry.pdf (page 10).
+	std::vector<DensityProfileLayer> ozone_density;
+	ozone_density.push_back(
+		DensityProfileLayer(25000.0, 0.0, 0.0, 1.0 / 15000.0, -2.0 / 3.0));
+	ozone_density.push_back(
+		DensityProfileLayer(0.0, 0.0, 0.0, -1.0 / 15000.0, 8.0 / 3.0));
+
+	std::vector<double> wavelengths;
+	std::vector<double> solar_irradiance;
+	std::vector<double> rayleigh_scattering;
+	std::vector<double> mie_scattering;
+	std::vector<double> mie_extinction;
+	std::vector<double> absorption_extinction;
+	std::vector<double> ground_albedo;
+	for(int l = kLambdaMin; l <= kLambdaMax; l += 10)
+	{
+		double lambda = static_cast<double>(l) * 1e-3;  // micro-meters
+		double mie =
+			kMieAngstromBeta / kMieScaleHeight * pow(lambda, -kMieAngstromAlpha);
+		wavelengths.push_back(l);
+		if(m_useConstantSolarSpectrum)
+		{
+			solar_irradiance.push_back(kConstantSolarIrradiance);
+		}
+		else
+		{
+			solar_irradiance.push_back(kSolarIrradiance[(l - kLambdaMin) / 10]);
+		}
+		rayleigh_scattering.push_back(kRayleigh * pow(lambda, -4));
+		mie_scattering.push_back(mie * kMieSingleScatteringAlbedo);
+		mie_extinction.push_back(mie);
+		absorption_extinction.push_back(m_useOzone ?
+			kMaxOzoneNumberDensity * kOzoneCrossSection[(l - kLambdaMin) / 10] :
+			0.0);
+		ground_albedo.push_back(kGroundAlbedo);
+	}
+
+	m_atmScatteringModel.reset(new AtmScatteringModel(wavelengths, solar_irradiance, kSunAngularRadius,
+		kBottomRadius, kTopRadius, { rayleigh_layer }, rayleigh_scattering,
+		{ mie_layer }, mie_scattering, mie_extinction, kMiePhaseFunctionG,
+		ozone_density, absorption_extinction, ground_albedo, max_sun_zenith_angle,
+		kLengthUnitInMeters, m_luminanceType == PRECOMPUTED ? 15 : 3,
+		m_useCombinedTextures, m_useHalfPrecision));
+	m_atmScatteringModel->Init();
+
+	double whitePointR = 1.0f;
+	double whitePointG = 1.0f;
+	double whitePointB = 1.0f;
+
+	if(m_useWhiteBalance)
+	{
+		AtmScatteringModel::ConvertSpectrumToLinearSrgb(wavelengths, solar_irradiance,
+			&whitePointR, &whitePointG, &whitePointB);
+		double whitePointAvg = (whitePointR + whitePointG + whitePointB) / 3.0;
+		whitePointR /= whitePointAvg;
+		whitePointG /= whitePointAvg;
+		whitePointB /= whitePointAvg;
+	}
+
+	m_atmScatteringParam.m_whitePoint = Math::Vec3f((float)whitePointR, (float)whitePointG, (float)whitePointB);
+
+	/*
+	<p>Then, it creates and compiles the vertex and fragment shaders used to render
+	our demo scene, and link them with the <code>AtmScatteringModel</code>'s atmosphere shader
+	to get the final scene rendering program:
+	*/
+	/*
+	GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
+	const char* const vertex_shader_source = m_vertexShaderSource.c_str();
+	glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
+	glCompileShader(vertex_shader);
+
+	const std::string fragment_shader_str =
+		"#version 330\n" +
+		std::string(m_luminanceType != NONE ? "#define USE_LUMINANCE\n" : "") +
+		"const float kLengthUnitInMeters = " +
+		std::to_string(kLengthUnitInMeters) + ";\n" +
+		demo_glsl;
+	const char* fragment_shader_source = fragment_shader_str.c_str();
+	GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
+	glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
+	glCompileShader(fragment_shader);
+
+	if(program_ != 0)
+	{
+		glDeleteProgram(program_);
+	}
+	program_ = glCreateProgram();
+	glAttachShader(program_, vertex_shader);
+	glAttachShader(program_, fragment_shader);
+	glAttachShader(program_, m_atmScatteringModel->GetShader());
+	glLinkProgram(program_);
+	glDetachShader(program_, vertex_shader);
+	glDetachShader(program_, fragment_shader);
+	glDetachShader(program_, m_atmScatteringModel->GetShader());
+	glDeleteShader(vertex_shader);
+	glDeleteShader(fragment_shader);
+
+	/*
+	<p>Finally, it sets the uniforms of this program that can be set once and for
+	all (in our case this includes the <code>AtmScatteringModel</code>'s texture uniforms,
+	because our demo app does not have any texture of its own):
+	*/
+	/*
+	glUseProgram(program_);
+	model_->SetProgramUniforms(program_, 0, 1, 2, 3);
+	double white_point_r = 1.0;
+	double white_point_g = 1.0;
+	double white_point_b = 1.0;
+	if(m_useWhiteBalance)
+	{
+		AtmScatteringModel::ConvertSpectrumToLinearSrgb(wavelengths, solar_irradiance,
+			&white_point_r, &white_point_g, &white_point_b);
+		double white_point = (white_point_r + white_point_g + white_point_b) / 3.0;
+		white_point_r /= white_point;
+		white_point_g /= white_point;
+		white_point_b /= white_point;
+	}
+	glUniform3f(glGetUniformLocation(program_, "white_point"),
+		white_point_r, white_point_g, white_point_b);
+	glUniform3f(glGetUniformLocation(program_, "earth_center"),
+		0.0, 0.0, -kBottomRadius / kLengthUnitInMeters);
+	glUniform2f(glGetUniformLocation(program_, "sun_size"),
+		tan(kSunAngularRadius),
+		cos(kSunAngularRadius));
+	*/
+	// This sets 'view_from_clip', which only depends on the window size.
+	//HandleReshapeEvent(glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));
+}

+ 250 - 0
Praxis3D/Source/AtmScatteringPass.h

@@ -0,0 +1,250 @@
+#pragma once
+
+#include "AtmScatteringModel.h"
+#include "GraphicsDataSets.h"
+#include "RenderPassBase.h"
+
+class AtmScatteringPass : public RenderPass
+{
+public:
+	AtmScatteringPass(RendererFrontend &p_renderer) :
+		RenderPass(p_renderer),
+		m_useConstantSolarSpectrum(false),
+		m_useOzone(true),
+		m_useCombinedTextures(true),
+		m_useHalfPrecision(true),
+		m_luminanceType(NONE),
+		m_useWhiteBalance(true),
+		kSunAngularRadius(0.00935 / 2.0),
+		kLengthUnitInMeters(1000.0),
+		m_atmParamBuffer(BufferType_Uniform, BufferUsageHint_DynamicDraw)
+	{
+		kSunSolidAngle = PI * kSunAngularRadius * kSunAngularRadius;
+
+		m_vertexShaderSource = R"(
+	#version 330
+	uniform mat4 modelMat;
+	uniform mat4 viewMat;
+	layout(location = 0) in vec4 vertex;
+	out vec3 view_ray;
+	void main() {
+		view_ray = (modelMat * vec4((viewMat * vertex).xyz, 0.0)).xyz;
+	gl_Position = vertex;
+	})";
+
+		m_atmosphereParam = AtmosphereParameters(
+			Math::Vec3f(1.474000f, 1.850400f, 1.911980f),
+			0.004675f,
+			DensityProfile(DensityProfLayer(0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f), DensityProfLayer(0.000000f, 1.000000f, -0.125000f, 0.000000f, 0.000000f)),
+			DensityProfile(DensityProfLayer(0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f), DensityProfLayer(0.000000f, 1.000000f, -0.833333f, 0.000000f, 0.000000f)),
+			DensityProfile(DensityProfLayer(25.000000f, 0.000000f, 0.000000f, 0.066667f, -0.666667f), DensityProfLayer(0.000000f, 0.000000f, 0.000000f, -0.066667f, 2.666667f)),
+			Math::Vec3f(0.005802f, 0.013558f, 0.033100f),
+			6360.000000f,
+			Math::Vec3f(0.003996f, 0.003996f, 0.003996f),
+			6420.000000f,
+			Math::Vec3f(0.004440f, 0.004440f, 0.004440f),
+			0.800000f,
+			Math::Vec3f(0.000650f, 0.001881f, 0.000085f),
+			-0.207912f,
+			Math::Vec3f(0.100000f, 0.100000f, 0.100000f));
+
+		m_atmScatteringParam = AtmScatteringParameters(
+			m_atmosphereParam,
+			Math::Vec3f(1.0f, 1.0f, 1.0f),
+			Math::Vec3f(0.0f, -6360000.0f / 1000.0f, 0.0f),
+			Math::Vec2f(tan(0.00935f / 2.0f), cos(0.00935f / 2.0f)));
+
+	}
+
+	~AtmScatteringPass()
+	{
+	}
+
+	ErrorCode init()
+	{
+		ErrorCode returnError = ErrorCode::Success;
+		ErrorCode shaderError;
+
+		m_name = "Atmospheric Scattering Rendering Pass";
+		
+		// Initialize atmospheric scattering model
+		InitModel();
+
+		// Create a property-set used to load sky pass shaders
+		PropertySet skyShaderProperties(Properties::Shaders);
+		skyShaderProperties.addProperty(Properties::VertexShader, Config::rendererVar().atm_scattering_sky_vert_shader);
+		skyShaderProperties.addProperty(Properties::FragmentShader, Config::rendererVar().atm_scattering_sky_frag_shader);
+		
+		// Create a property-set used to load ground pass shaders
+		PropertySet groundShaderProperties(Properties::Shaders);
+		groundShaderProperties.addProperty(Properties::VertexShader, Config::rendererVar().atm_scattering_ground_vert_shader);
+		groundShaderProperties.addProperty(Properties::FragmentShader, Config::rendererVar().atm_scattering_ground_frag_shader);
+		
+		// Create shaders
+		m_skyShader = Loaders::shader().load(skyShaderProperties);
+		m_groundShader = Loaders::shader().load(groundShaderProperties);
+
+		//		 _______________________________
+		//		|	  ATMOSPHERIC SCATTERING 	|
+		//		|________SKY PASS SHADER________|
+		shaderError = m_skyShader->loadToMemory();		// Load shader to memory
+		if(shaderError == ErrorCode::Success)			// Check if shader was loaded successfully
+			m_renderer.queueForLoading(*m_skyShader);	// Queue the shader to be loaded to GPU
+		else
+			returnError = shaderError;
+		
+		//		 _______________________________
+		//		|	  ATMOSPHERIC SCATTERING 	|
+		//		|_______GROUND PASS SHADER______|
+		shaderError = m_groundShader->loadToMemory();		// Load shader to memory
+		if(shaderError == ErrorCode::Success)				// Check if shader was loaded successfully
+			m_renderer.queueForLoading(*m_groundShader);	// Queue the shader to be loaded to GPU
+		else
+			returnError = shaderError;
+
+		// Set atmosphere parameters buffer shader binding index
+		m_atmParamBuffer.m_bindingIndex = UniformBufferBinding_AtmScatParam;
+
+		// Set atmosphere parameters buffer size and data
+		m_atmParamBuffer.m_size = sizeof(AtmScatteringParameters);
+		m_atmParamBuffer.m_data = (void*)(&m_atmScatteringParam);
+
+		// Queue atmosphere parameters buffer to be created
+		m_renderer.queueForLoading(m_atmParamBuffer);
+
+		return returnError;
+	}
+
+	void update(RenderPassData &p_renderPassData, const SceneObjects &p_sceneObjects, const float p_deltaTime)
+	{
+		glEnable(GL_DEPTH_TEST);
+
+		if(p_renderPassData.m_atmScatDoSkyPass)
+		{
+			//glDisable(GL_DEPTH_TEST);
+			//glEnable(GL_DEPTH_TEST);
+			glDepthFunc(GL_LEQUAL);
+			//glDepthMask(GL_FALSE);
+
+			// Bind output color texture for writing to
+			m_renderer.m_backend.getGeometryBuffer()->bindBufferForWriting(p_renderPassData.getColorOutputMap());
+
+			glUseProgram(m_skyShader->getShaderHandle());
+
+			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Irradiance);
+			glBindTexture(GL_TEXTURE_2D, m_atmScatteringModel->getIrradianceTexture());
+			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Scattering);
+			glBindTexture(GL_TEXTURE_3D, m_atmScatteringModel->getScatteringTexture());
+			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_SingleMie);
+			glBindTexture(GL_TEXTURE_3D, m_atmScatteringModel->getSingleMieTexture());
+			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Transmittance);
+			glBindTexture(GL_TEXTURE_2D, m_atmScatteringModel->getTransmittanceTexture());
+
+			// Set atmosphere parameters buffer size and data
+			//m_atmParamBuffer.m_updateSize = sizeof(AtmScatteringParameters);
+			//m_atmParamBuffer.m_data = (void*)(&m_atmScatteringParam);
+			//m_renderer.queueForUpdate(m_atmParamBuffer);
+
+			// Pass update commands so they are executed 
+			//m_renderer.passUpdateCommandsToBackend();
+
+			// Perform various visual effects in the post process shader
+			m_renderer.queueForDrawing(m_skyShader->getShaderHandle(), m_skyShader->getUniformUpdater(), p_sceneObjects.m_camera->getBaseObjectData().m_modelMat);
+			
+		}
+		else
+		{
+			glDepthFunc(GL_GREATER);
+			//glDisable(GL_DEPTH_TEST);
+			//glEnable(GL_DEPTH_TEST);
+			//glDepthFunc(GL_LEQUAL);
+			//glDepthMask(GL_FALSE);
+
+			// Bind output color texture for writing to
+			m_renderer.m_backend.getGeometryBuffer()->bindBufferForWriting(p_renderPassData.getColorOutputMap());
+
+			glUseProgram(m_groundShader->getShaderHandle());
+
+			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Irradiance);
+			glBindTexture(GL_TEXTURE_2D, m_atmScatteringModel->getIrradianceTexture());
+			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Scattering);
+			glBindTexture(GL_TEXTURE_3D, m_atmScatteringModel->getScatteringTexture());
+			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_SingleMie);
+			glBindTexture(GL_TEXTURE_3D, m_atmScatteringModel->getSingleMieTexture());
+			glActiveTexture(GL_TEXTURE0 + AtmScatteringTextureType::AtmScatteringTextureType_Transmittance);
+			glBindTexture(GL_TEXTURE_2D, m_atmScatteringModel->getTransmittanceTexture());
+			
+			
+			auto tex1 = m_atmScatteringModel->getIrradianceTexture();
+			auto tex2 = m_atmScatteringModel->getScatteringTexture();
+			auto tex3 = m_atmScatteringModel->getSingleMieTexture();
+			auto tex4 = m_atmScatteringModel->getTransmittanceTexture();
+
+			m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(p_renderPassData.getColorInputMap(), GeometryBuffer::GBufferInputTexture);
+			m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(GeometryBuffer::GBufferPosition, GeometryBuffer::GBufferPosition);
+			m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(GeometryBuffer::GBufferNormal, GeometryBuffer::GBufferNormal);
+			
+			// Set atmosphere parameters buffer size and data
+			//m_atmParamBuffer.m_updateSize = sizeof(AtmScatteringParameters);
+			//m_atmParamBuffer.m_data = (void*)(&m_atmScatteringParam);
+			//m_renderer.queueForUpdate(m_atmParamBuffer);
+
+			// Pass update commands so they are executed 
+			//m_renderer.passUpdateCommandsToBackend();
+
+			// Perform various visual effects in the post process shader
+			m_renderer.queueForDrawing(m_groundShader->getShaderHandle(), m_groundShader->getUniformUpdater(), p_sceneObjects.m_camera->getBaseObjectData().m_modelMat);
+		
+		}
+		
+		// Pass the draw command so it is executed
+		m_renderer.passScreenSpaceDrawCommandsToBackend();
+		
+		p_renderPassData.swapColorInputOutputMaps();
+
+		p_renderPassData.m_atmScatDoSkyPass = !p_renderPassData.m_atmScatDoSkyPass;
+	}
+
+private:
+	enum Luminance {
+		// Render the spectral radiance at kLambdaR, kLambdaG, kLambdaB.
+		NONE,
+		// Render the sRGB luminance, using an approximate (on the fly) conversion
+		// from 3 spectral radiance values only (see section 14.3 in <a href=
+		// "https://arxiv.org/pdf/1612.04336.pdf">A Qualitative and Quantitative
+		//  Evaluation of 8 Clear Sky Models</a>).
+		APPROXIMATE,
+		// Render the sRGB luminance, precomputed from 15 spectral radiance values
+		// (see section 4.4 in <a href=
+		// "http://www.oskee.wz.cz/stranka/uploads/SCCG10ElekKmoch.pdf">Real-time
+		//  Spectral Scattering in Large-scale Natural Participating Media</a>).
+		PRECOMPUTED
+	};
+
+	void InitModel();
+	
+	Luminance m_luminanceType;
+	bool m_useConstantSolarSpectrum;
+	bool m_useCombinedTextures;
+	bool m_useHalfPrecision;
+	bool m_useOzone;
+	bool m_useWhiteBalance;
+
+	std::unique_ptr<AtmScatteringModel> m_atmScatteringModel;
+
+	double kSunAngularRadius;
+	double kSunSolidAngle;
+	double kLengthUnitInMeters;
+
+	std::string m_vertexShaderSource;
+
+	//Math::Vec3f m_whitePoint;
+
+	ShaderLoader::ShaderProgram	*m_skyShader;
+	ShaderLoader::ShaderProgram	*m_groundShader;
+
+	RendererFrontend::ShaderBuffer m_atmParamBuffer;
+
+	AtmosphereParameters m_atmosphereParam;
+	AtmScatteringParameters m_atmScatteringParam;
+};

+ 92 - 0
Praxis3D/Source/AtmScatteringShaderDefinitions.h

@@ -0,0 +1,92 @@
+#pragma once
+
+const char* definitions_glsl = \
+"#define Length float\r\n"\
+"#define Wavelength float\r\n"\
+"#define Angle float\r\n"\
+"#define SolidAngle float\r\n"\
+"#define Power float\r\n"\
+"#define LuminousPower float\r\n"\
+"#define Number float\r\n"\
+"#define InverseLength float\r\n"\
+"#define Area float\r\n"\
+"#define Volume float\r\n"\
+"#define NumberDensity float\r\n"\
+"#define Irradiance float\r\n"\
+"#define Radiance float\r\n"\
+"#define SpectralPower float\r\n"\
+"#define SpectralIrradiance float\r\n"\
+"#define SpectralRadiance float\r\n"\
+"#define SpectralRadianceDensity float\r\n"\
+"#define ScatteringCoefficient float\r\n"\
+"#define InverseSolidAngle float\r\n"\
+"#define LuminousIntensity float\r\n"\
+"#define Luminance float\r\n"\
+"#define Illuminance float\r\n"\
+"#define AbstractSpectrum vec3\r\n"\
+"#define DimensionlessSpectrum vec3\r\n"\
+"#define PowerSpectrum vec3\r\n"\
+"#define IrradianceSpectrum vec3\r\n"\
+"#define RadianceSpectrum vec3\r\n"\
+"#define RadianceDensitySpectrum vec3\r\n"\
+"#define ScatteringSpectrum vec3\r\n"\
+"#define Position vec3\r\n"\
+"#define Direction vec3\r\n"\
+"#define Luminance3 vec3\r\n"\
+"#define Illuminance3 vec3\r\n"\
+"#define TransmittanceTexture sampler2D\r\n"\
+"#define AbstractScatteringTexture sampler3D\r\n"\
+"#define ReducedScatteringTexture sampler3D\r\n"\
+"#define ScatteringTexture sampler3D\r\n"\
+"#define ScatteringDensityTexture sampler3D\r\n"\
+"#define IrradianceTexture sampler2D\r\n"\
+"const Length m = 1.0;\r\n"\
+"const Wavelength nm = 1.0;\r\n"\
+"const Angle rad = 1.0;\r\n"\
+"const SolidAngle sr = 1.0;\r\n"\
+"const Power watt = 1.0;\r\n"\
+"const LuminousPower lm = 1.0;\r\n"\
+"const float PI = 3.14159265358979323846;\r\n"\
+"const Length km = 1000.0 * m;\r\n"\
+"const Area m2 = m * m;\r\n"\
+"const Volume m3 = m * m * m;\r\n"\
+"const Angle pi = PI * rad;\r\n"\
+"const Angle deg = pi / 180.0;\r\n"\
+"const Irradiance watt_per_square_meter = watt / m2;\r\n"\
+"const Radiance watt_per_square_meter_per_sr = watt / (m2 * sr);\r\n"\
+"const SpectralIrradiance watt_per_square_meter_per_nm = watt / (m2 * nm);\r\n"\
+"const SpectralRadiance watt_per_square_meter_per_sr_per_nm =\r\n"\
+"    watt / (m2 * sr * nm);\r\n"\
+"const SpectralRadianceDensity watt_per_cubic_meter_per_sr_per_nm =\r\n"\
+"    watt / (m3 * sr * nm);\r\n"\
+"const LuminousIntensity cd = lm / sr;\r\n"\
+"const LuminousIntensity kcd = 1000.0 * cd;\r\n"\
+"const Luminance cd_per_square_meter = cd / m2;\r\n"\
+"const Luminance kcd_per_square_meter = kcd / m2;\r\n"\
+"struct DensityProfileLayer {\r\n"\
+"  Length width;\r\n"\
+"  Number exp_term;\r\n"\
+"  InverseLength exp_scale;\r\n"\
+"  InverseLength linear_term;\r\n"\
+"  Number constant_term;\r\n"\
+"};\r\n"\
+"struct DensityProfile {\r\n"\
+"  DensityProfileLayer layers[2];\r\n"\
+"};\r\n"\
+"struct AtmosphereParameters {\r\n"\
+"  IrradianceSpectrum solar_irradiance;\r\n"\
+"  Angle sun_angular_radius;\r\n"\
+"  Length bottom_radius;\r\n"\
+"  Length top_radius;\r\n"\
+"  DensityProfile rayleigh_density;\r\n"\
+"  ScatteringSpectrum rayleigh_scattering;\r\n"\
+"  DensityProfile mie_density;\r\n"\
+"  ScatteringSpectrum mie_scattering;\r\n"\
+"  ScatteringSpectrum mie_extinction;\r\n"\
+"  Number mie_phase_function_g;\r\n"\
+"  DensityProfile absorption_density;\r\n"\
+"  ScatteringSpectrum absorption_extinction;\r\n"\
+"  DimensionlessSpectrum ground_albedo;\r\n"\
+"  Number mu_s_min;\r\n"\
+"};\r\n"\
+"";

+ 810 - 0
Praxis3D/Source/AtmScatteringShaderFunctions.h

@@ -0,0 +1,810 @@
+#pragma once
+
+const char* functions_glsl = \
+"Number ClampCosine(Number mu) {\r\n"\
+"  return clamp(mu, Number(-1.0), Number(1.0));\r\n"\
+"}\r\n"\
+"Length ClampDistance(Length d) {\r\n"\
+"  return max(d, 0.0 * m);\r\n"\
+"}\r\n"\
+"Length ClampRadius(IN(AtmosphereParameters) atmosphere, Length r) {\r\n"\
+"  return clamp(r, atmosphere.bottom_radius, atmosphere.top_radius);\r\n"\
+"}\r\n"\
+"Length SafeSqrt(Area a) {\r\n"\
+"  return sqrt(max(a, 0.0 * m2));\r\n"\
+"}\r\n"\
+"Length DistanceToTopAtmosphereBoundary(IN(AtmosphereParameters) atmosphere,\r\n"\
+"    Length r, Number mu) {\r\n"\
+"  assert(r <= atmosphere.top_radius);\r\n"\
+"  assert(mu >= -1.0 && mu <= 1.0);\r\n"\
+"  Area discriminant = r * r * (mu * mu - 1.0) +\r\n"\
+"      atmosphere.top_radius * atmosphere.top_radius;\r\n"\
+"  return ClampDistance(-r * mu + SafeSqrt(discriminant));\r\n"\
+"}\r\n"\
+"Length DistanceToBottomAtmosphereBoundary(IN(AtmosphereParameters) atmosphere,\r\n"\
+"    Length r, Number mu) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius);\r\n"\
+"  assert(mu >= -1.0 && mu <= 1.0);\r\n"\
+"  Area discriminant = r * r * (mu * mu - 1.0) +\r\n"\
+"      atmosphere.bottom_radius * atmosphere.bottom_radius;\r\n"\
+"  return ClampDistance(-r * mu - SafeSqrt(discriminant));\r\n"\
+"}\r\n"\
+"bool RayIntersectsGround(IN(AtmosphereParameters) atmosphere,\r\n"\
+"    Length r, Number mu) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius);\r\n"\
+"  assert(mu >= -1.0 && mu <= 1.0);\r\n"\
+"  return mu < 0.0 && r * r * (mu * mu - 1.0) +\r\n"\
+"      atmosphere.bottom_radius * atmosphere.bottom_radius >= 0.0 * m2;\r\n"\
+"}\r\n"\
+"Number GetLayerDensity(IN(DensityProfileLayer) layer, Length altitude) {\r\n"\
+"  Number density = layer.exp_term * exp(layer.exp_scale * altitude) +\r\n"\
+"      layer.linear_term * altitude + layer.constant_term;\r\n"\
+"  return clamp(density, Number(0.0), Number(1.0));\r\n"\
+"}\r\n"\
+"Number GetProfileDensity(IN(DensityProfile) profile, Length altitude) {\r\n"\
+"  return altitude < profile.layers[0].width ?\r\n"\
+"      GetLayerDensity(profile.layers[0], altitude) :\r\n"\
+"      GetLayerDensity(profile.layers[1], altitude);\r\n"\
+"}\r\n"\
+"Length ComputeOpticalLengthToTopAtmosphereBoundary(\r\n"\
+"    IN(AtmosphereParameters) atmosphere, IN(DensityProfile) profile,\r\n"\
+"    Length r, Number mu) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius);\r\n"\
+"  assert(mu >= -1.0 && mu <= 1.0);\r\n"\
+"  const int SAMPLE_COUNT = 500;\r\n"\
+"  Length dx =\r\n"\
+"      DistanceToTopAtmosphereBoundary(atmosphere, r, mu) / Number(SAMPLE_COUNT);\r\n"\
+"  Length result = 0.0 * m;\r\n"\
+"  for (int i = 0; i <= SAMPLE_COUNT; ++i) {\r\n"\
+"    Length d_i = Number(i) * dx;\r\n"\
+"    Length r_i = sqrt(d_i * d_i + 2.0 * r * mu * d_i + r * r);\r\n"\
+"    Number y_i = GetProfileDensity(profile, r_i - atmosphere.bottom_radius);\r\n"\
+"    Number weight_i = i == 0 || i == SAMPLE_COUNT ? 0.5 : 1.0;\r\n"\
+"    result += y_i * weight_i * dx;\r\n"\
+"  }\r\n"\
+"  return result;\r\n"\
+"}\r\n"\
+"DimensionlessSpectrum ComputeTransmittanceToTopAtmosphereBoundary(\r\n"\
+"    IN(AtmosphereParameters) atmosphere, Length r, Number mu) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius);\r\n"\
+"  assert(mu >= -1.0 && mu <= 1.0);\r\n"\
+"  return exp(-(\r\n"\
+"      atmosphere.rayleigh_scattering *\r\n"\
+"          ComputeOpticalLengthToTopAtmosphereBoundary(\r\n"\
+"              atmosphere, atmosphere.rayleigh_density, r, mu) +\r\n"\
+"      atmosphere.mie_extinction *\r\n"\
+"          ComputeOpticalLengthToTopAtmosphereBoundary(\r\n"\
+"              atmosphere, atmosphere.mie_density, r, mu) +\r\n"\
+"      atmosphere.absorption_extinction *\r\n"\
+"          ComputeOpticalLengthToTopAtmosphereBoundary(\r\n"\
+"              atmosphere, atmosphere.absorption_density, r, mu)));\r\n"\
+"}\r\n"\
+"Number GetTextureCoordFromUnitRange(Number x, int texture_size) {\r\n"\
+"  return 0.5 / Number(texture_size) + x * (1.0 - 1.0 / Number(texture_size));\r\n"\
+"}\r\n"\
+"Number GetUnitRangeFromTextureCoord(Number u, int texture_size) {\r\n"\
+"  return (u - 0.5 / Number(texture_size)) / (1.0 - 1.0 / Number(texture_size));\r\n"\
+"}\r\n"\
+"vec2 GetTransmittanceTextureUvFromRMu(IN(AtmosphereParameters) atmosphere,\r\n"\
+"    Length r, Number mu) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius);\r\n"\
+"  assert(mu >= -1.0 && mu <= 1.0);\r\n"\
+"  Length H = sqrt(atmosphere.top_radius * atmosphere.top_radius -\r\n"\
+"      atmosphere.bottom_radius * atmosphere.bottom_radius);\r\n"\
+"  Length rho =\r\n"\
+"      SafeSqrt(r * r - atmosphere.bottom_radius * atmosphere.bottom_radius);\r\n"\
+"  Length d = DistanceToTopAtmosphereBoundary(atmosphere, r, mu);\r\n"\
+"  Length d_min = atmosphere.top_radius - r;\r\n"\
+"  Length d_max = rho + H;\r\n"\
+"  Number x_mu = (d - d_min) / (d_max - d_min);\r\n"\
+"  Number x_r = rho / H;\r\n"\
+"  return vec2(GetTextureCoordFromUnitRange(x_mu, TRANSMITTANCE_TEXTURE_WIDTH),\r\n"\
+"              GetTextureCoordFromUnitRange(x_r, TRANSMITTANCE_TEXTURE_HEIGHT));\r\n"\
+"}\r\n"\
+"void GetRMuFromTransmittanceTextureUv(IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(vec2) uv, OUT(Length) r, OUT(Number) mu) {\r\n"\
+"  assert(uv.x >= 0.0 && uv.x <= 1.0);\r\n"\
+"  assert(uv.y >= 0.0 && uv.y <= 1.0);\r\n"\
+"  Number x_mu = GetUnitRangeFromTextureCoord(uv.x, TRANSMITTANCE_TEXTURE_WIDTH);\r\n"\
+"  Number x_r = GetUnitRangeFromTextureCoord(uv.y, TRANSMITTANCE_TEXTURE_HEIGHT);\r\n"\
+"  Length H = sqrt(atmosphere.top_radius * atmosphere.top_radius -\r\n"\
+"      atmosphere.bottom_radius * atmosphere.bottom_radius);\r\n"\
+"  Length rho = H * x_r;\r\n"\
+"  r = sqrt(rho * rho + atmosphere.bottom_radius * atmosphere.bottom_radius);\r\n"\
+"  Length d_min = atmosphere.top_radius - r;\r\n"\
+"  Length d_max = rho + H;\r\n"\
+"  Length d = d_min + x_mu * (d_max - d_min);\r\n"\
+"  mu = d == 0.0 * m ? Number(1.0) : (H * H - rho * rho - d * d) / (2.0 * r * d);\r\n"\
+"  mu = ClampCosine(mu);\r\n"\
+"}\r\n"\
+"DimensionlessSpectrum ComputeTransmittanceToTopAtmosphereBoundaryTexture(\r\n"\
+"    IN(AtmosphereParameters) atmosphere, IN(vec2) frag_coord) {\r\n"\
+"  const vec2 TRANSMITTANCE_TEXTURE_SIZE =\r\n"\
+"      vec2(TRANSMITTANCE_TEXTURE_WIDTH, TRANSMITTANCE_TEXTURE_HEIGHT);\r\n"\
+"  Length r;\r\n"\
+"  Number mu;\r\n"\
+"  GetRMuFromTransmittanceTextureUv(\r\n"\
+"      atmosphere, frag_coord / TRANSMITTANCE_TEXTURE_SIZE, r, mu);\r\n"\
+"  return ComputeTransmittanceToTopAtmosphereBoundary(atmosphere, r, mu);\r\n"\
+"}\r\n"\
+"DimensionlessSpectrum GetTransmittanceToTopAtmosphereBoundary(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    Length r, Number mu) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius);\r\n"\
+"  vec2 uv = GetTransmittanceTextureUvFromRMu(atmosphere, r, mu);\r\n"\
+"  return DimensionlessSpectrum(texture(transmittance_texture, uv));\r\n"\
+"}\r\n"\
+"DimensionlessSpectrum GetTransmittance(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    Length r, Number mu, Length d, bool ray_r_mu_intersects_ground) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius);\r\n"\
+"  assert(mu >= -1.0 && mu <= 1.0);\r\n"\
+"  assert(d >= 0.0 * m);\r\n"\
+"  Length r_d = ClampRadius(atmosphere, sqrt(d * d + 2.0 * r * mu * d + r * r));\r\n"\
+"  Number mu_d = ClampCosine((r * mu + d) / r_d);\r\n"\
+"  if (ray_r_mu_intersects_ground) {\r\n"\
+"    return min(\r\n"\
+"        GetTransmittanceToTopAtmosphereBoundary(\r\n"\
+"            atmosphere, transmittance_texture, r_d, -mu_d) /\r\n"\
+"        GetTransmittanceToTopAtmosphereBoundary(\r\n"\
+"            atmosphere, transmittance_texture, r, -mu),\r\n"\
+"        DimensionlessSpectrum(1.0));\r\n"\
+"  } else {\r\n"\
+"    return min(\r\n"\
+"        GetTransmittanceToTopAtmosphereBoundary(\r\n"\
+"            atmosphere, transmittance_texture, r, mu) /\r\n"\
+"        GetTransmittanceToTopAtmosphereBoundary(\r\n"\
+"            atmosphere, transmittance_texture, r_d, mu_d),\r\n"\
+"        DimensionlessSpectrum(1.0));\r\n"\
+"  }\r\n"\
+"}\r\n"\
+"DimensionlessSpectrum GetTransmittanceToSun(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    Length r, Number mu_s) {\r\n"\
+"  Number sin_theta_h = atmosphere.bottom_radius / r;\r\n"\
+"  Number cos_theta_h = -sqrt(max(1.0 - sin_theta_h * sin_theta_h, 0.0));\r\n"\
+"  return GetTransmittanceToTopAtmosphereBoundary(\r\n"\
+"          atmosphere, transmittance_texture, r, mu_s) *\r\n"\
+"      smoothstep(-sin_theta_h * atmosphere.sun_angular_radius / rad,\r\n"\
+"                 sin_theta_h * atmosphere.sun_angular_radius / rad,\r\n"\
+"                 mu_s - cos_theta_h);\r\n"\
+"}\r\n"\
+"void ComputeSingleScatteringIntegrand(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    Length r, Number mu, Number mu_s, Number nu, Length d,\r\n"\
+"    bool ray_r_mu_intersects_ground,\r\n"\
+"    OUT(DimensionlessSpectrum) rayleigh, OUT(DimensionlessSpectrum) mie) {\r\n"\
+"  Length r_d = ClampRadius(atmosphere, sqrt(d * d + 2.0 * r * mu * d + r * r));\r\n"\
+"  Number mu_s_d = ClampCosine((r * mu_s + d * nu) / r_d);\r\n"\
+"  DimensionlessSpectrum transmittance =\r\n"\
+"      GetTransmittance(\r\n"\
+"          atmosphere, transmittance_texture, r, mu, d,\r\n"\
+"          ray_r_mu_intersects_ground) *\r\n"\
+"      GetTransmittanceToSun(\r\n"\
+"          atmosphere, transmittance_texture, r_d, mu_s_d);\r\n"\
+"  rayleigh = transmittance * GetProfileDensity(\r\n"\
+"      atmosphere.rayleigh_density, r_d - atmosphere.bottom_radius);\r\n"\
+"  mie = transmittance * GetProfileDensity(\r\n"\
+"      atmosphere.mie_density, r_d - atmosphere.bottom_radius);\r\n"\
+"}\r\n"\
+"Length DistanceToNearestAtmosphereBoundary(IN(AtmosphereParameters) atmosphere,\r\n"\
+"    Length r, Number mu, bool ray_r_mu_intersects_ground) {\r\n"\
+"  if (ray_r_mu_intersects_ground) {\r\n"\
+"    return DistanceToBottomAtmosphereBoundary(atmosphere, r, mu);\r\n"\
+"  } else {\r\n"\
+"    return DistanceToTopAtmosphereBoundary(atmosphere, r, mu);\r\n"\
+"  }\r\n"\
+"}\r\n"\
+"void ComputeSingleScattering(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    Length r, Number mu, Number mu_s, Number nu,\r\n"\
+"    bool ray_r_mu_intersects_ground,\r\n"\
+"    OUT(IrradianceSpectrum) rayleigh, OUT(IrradianceSpectrum) mie) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius);\r\n"\
+"  assert(mu >= -1.0 && mu <= 1.0);\r\n"\
+"  assert(mu_s >= -1.0 && mu_s <= 1.0);\r\n"\
+"  assert(nu >= -1.0 && nu <= 1.0);\r\n"\
+"  const int SAMPLE_COUNT = 50;\r\n"\
+"  Length dx =\r\n"\
+"      DistanceToNearestAtmosphereBoundary(atmosphere, r, mu,\r\n"\
+"          ray_r_mu_intersects_ground) / Number(SAMPLE_COUNT);\r\n"\
+"  DimensionlessSpectrum rayleigh_sum = DimensionlessSpectrum(0.0);\r\n"\
+"  DimensionlessSpectrum mie_sum = DimensionlessSpectrum(0.0);\r\n"\
+"  for (int i = 0; i <= SAMPLE_COUNT; ++i) {\r\n"\
+"    Length d_i = Number(i) * dx;\r\n"\
+"    DimensionlessSpectrum rayleigh_i;\r\n"\
+"    DimensionlessSpectrum mie_i;\r\n"\
+"    ComputeSingleScatteringIntegrand(atmosphere, transmittance_texture,\r\n"\
+"        r, mu, mu_s, nu, d_i, ray_r_mu_intersects_ground, rayleigh_i, mie_i);\r\n"\
+"    Number weight_i = (i == 0 || i == SAMPLE_COUNT) ? 0.5 : 1.0;\r\n"\
+"    rayleigh_sum += rayleigh_i * weight_i;\r\n"\
+"    mie_sum += mie_i * weight_i;\r\n"\
+"  }\r\n"\
+"  rayleigh = rayleigh_sum * dx * atmosphere.solar_irradiance *\r\n"\
+"      atmosphere.rayleigh_scattering;\r\n"\
+"  mie = mie_sum * dx * atmosphere.solar_irradiance * atmosphere.mie_scattering;\r\n"\
+"}\r\n"\
+"InverseSolidAngle RayleighPhaseFunction(Number nu) {\r\n"\
+"  InverseSolidAngle k = 3.0 / (16.0 * PI * sr);\r\n"\
+"  return k * (1.0 + nu * nu);\r\n"\
+"}\r\n"\
+"InverseSolidAngle MiePhaseFunction(Number g, Number nu) {\r\n"\
+"  InverseSolidAngle k = 3.0 / (8.0 * PI * sr) * (1.0 - g * g) / (2.0 + g * g);\r\n"\
+"  return k * (1.0 + nu * nu) / pow(1.0 + g * g - 2.0 * g * nu, 1.5);\r\n"\
+"}\r\n"\
+"vec4 GetScatteringTextureUvwzFromRMuMuSNu(IN(AtmosphereParameters) atmosphere,\r\n"\
+"    Length r, Number mu, Number mu_s, Number nu,\r\n"\
+"    bool ray_r_mu_intersects_ground) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius);\r\n"\
+"  assert(mu >= -1.0 && mu <= 1.0);\r\n"\
+"  assert(mu_s >= -1.0 && mu_s <= 1.0);\r\n"\
+"  assert(nu >= -1.0 && nu <= 1.0);\r\n"\
+"  Length H = sqrt(atmosphere.top_radius * atmosphere.top_radius -\r\n"\
+"      atmosphere.bottom_radius * atmosphere.bottom_radius);\r\n"\
+"  Length rho =\r\n"\
+"      SafeSqrt(r * r - atmosphere.bottom_radius * atmosphere.bottom_radius);\r\n"\
+"  Number u_r = GetTextureCoordFromUnitRange(rho / H, SCATTERING_TEXTURE_R_SIZE);\r\n"\
+"  Length r_mu = r * mu;\r\n"\
+"  Area discriminant =\r\n"\
+"      r_mu * r_mu - r * r + atmosphere.bottom_radius * atmosphere.bottom_radius;\r\n"\
+"  Number u_mu;\r\n"\
+"  if (ray_r_mu_intersects_ground) {\r\n"\
+"    Length d = -r_mu - SafeSqrt(discriminant);\r\n"\
+"    Length d_min = r - atmosphere.bottom_radius;\r\n"\
+"    Length d_max = rho;\r\n"\
+"    u_mu = 0.5 - 0.5 * GetTextureCoordFromUnitRange(d_max == d_min ? 0.0 :\r\n"\
+"        (d - d_min) / (d_max - d_min), SCATTERING_TEXTURE_MU_SIZE / 2);\r\n"\
+"  } else {\r\n"\
+"    Length d = -r_mu + SafeSqrt(discriminant + H * H);\r\n"\
+"    Length d_min = atmosphere.top_radius - r;\r\n"\
+"    Length d_max = rho + H;\r\n"\
+"    u_mu = 0.5 + 0.5 * GetTextureCoordFromUnitRange(\r\n"\
+"        (d - d_min) / (d_max - d_min), SCATTERING_TEXTURE_MU_SIZE / 2);\r\n"\
+"  }\r\n"\
+"  Length d = DistanceToTopAtmosphereBoundary(\r\n"\
+"      atmosphere, atmosphere.bottom_radius, mu_s);\r\n"\
+"  Length d_min = atmosphere.top_radius - atmosphere.bottom_radius;\r\n"\
+"  Length d_max = H;\r\n"\
+"  Number a = (d - d_min) / (d_max - d_min);\r\n"\
+"  Number A =\r\n"\
+"      -2.0 * atmosphere.mu_s_min * atmosphere.bottom_radius / (d_max - d_min);\r\n"\
+"  Number u_mu_s = GetTextureCoordFromUnitRange(\r\n"\
+"      max(1.0 - a / A, 0.0) / (1.0 + a), SCATTERING_TEXTURE_MU_S_SIZE);\r\n"\
+"  Number u_nu = (nu + 1.0) / 2.0;\r\n"\
+"  return vec4(u_nu, u_mu_s, u_mu, u_r);\r\n"\
+"}\r\n"\
+"void GetRMuMuSNuFromScatteringTextureUvwz(IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(vec4) uvwz, OUT(Length) r, OUT(Number) mu, OUT(Number) mu_s,\r\n"\
+"    OUT(Number) nu, OUT(bool) ray_r_mu_intersects_ground) {\r\n"\
+"  assert(uvwz.x >= 0.0 && uvwz.x <= 1.0);\r\n"\
+"  assert(uvwz.y >= 0.0 && uvwz.y <= 1.0);\r\n"\
+"  assert(uvwz.z >= 0.0 && uvwz.z <= 1.0);\r\n"\
+"  assert(uvwz.w >= 0.0 && uvwz.w <= 1.0);\r\n"\
+"  Length H = sqrt(atmosphere.top_radius * atmosphere.top_radius -\r\n"\
+"      atmosphere.bottom_radius * atmosphere.bottom_radius);\r\n"\
+"  Length rho =\r\n"\
+"      H * GetUnitRangeFromTextureCoord(uvwz.w, SCATTERING_TEXTURE_R_SIZE);\r\n"\
+"  r = sqrt(rho * rho + atmosphere.bottom_radius * atmosphere.bottom_radius);\r\n"\
+"  if (uvwz.z < 0.5) {\r\n"\
+"    Length d_min = r - atmosphere.bottom_radius;\r\n"\
+"    Length d_max = rho;\r\n"\
+"    Length d = d_min + (d_max - d_min) * GetUnitRangeFromTextureCoord(\r\n"\
+"        1.0 - 2.0 * uvwz.z, SCATTERING_TEXTURE_MU_SIZE / 2);\r\n"\
+"    mu = d == 0.0 * m ? Number(-1.0) :\r\n"\
+"        ClampCosine(-(rho * rho + d * d) / (2.0 * r * d));\r\n"\
+"    ray_r_mu_intersects_ground = true;\r\n"\
+"  } else {\r\n"\
+"    Length d_min = atmosphere.top_radius - r;\r\n"\
+"    Length d_max = rho + H;\r\n"\
+"    Length d = d_min + (d_max - d_min) * GetUnitRangeFromTextureCoord(\r\n"\
+"        2.0 * uvwz.z - 1.0, SCATTERING_TEXTURE_MU_SIZE / 2);\r\n"\
+"    mu = d == 0.0 * m ? Number(1.0) :\r\n"\
+"        ClampCosine((H * H - rho * rho - d * d) / (2.0 * r * d));\r\n"\
+"    ray_r_mu_intersects_ground = false;\r\n"\
+"  }\r\n"\
+"  Number x_mu_s =\r\n"\
+"      GetUnitRangeFromTextureCoord(uvwz.y, SCATTERING_TEXTURE_MU_S_SIZE);\r\n"\
+"  Length d_min = atmosphere.top_radius - atmosphere.bottom_radius;\r\n"\
+"  Length d_max = H;\r\n"\
+"  Number A =\r\n"\
+"      -2.0 * atmosphere.mu_s_min * atmosphere.bottom_radius / (d_max - d_min);\r\n"\
+"  Number a = (A - x_mu_s * A) / (1.0 + x_mu_s * A);\r\n"\
+"  Length d = d_min + min(a, A) * (d_max - d_min);\r\n"\
+"  mu_s = d == 0.0 * m ? Number(1.0) :\r\n"\
+"     ClampCosine((H * H - d * d) / (2.0 * atmosphere.bottom_radius * d));\r\n"\
+"  nu = ClampCosine(uvwz.x * 2.0 - 1.0);\r\n"\
+"}\r\n"\
+"void GetRMuMuSNuFromScatteringTextureFragCoord(\r\n"\
+"    IN(AtmosphereParameters) atmosphere, IN(vec3) frag_coord,\r\n"\
+"    OUT(Length) r, OUT(Number) mu, OUT(Number) mu_s, OUT(Number) nu,\r\n"\
+"    OUT(bool) ray_r_mu_intersects_ground) {\r\n"\
+"  const vec4 SCATTERING_TEXTURE_SIZE = vec4(\r\n"\
+"      SCATTERING_TEXTURE_NU_SIZE - 1,\r\n"\
+"      SCATTERING_TEXTURE_MU_S_SIZE,\r\n"\
+"      SCATTERING_TEXTURE_MU_SIZE,\r\n"\
+"      SCATTERING_TEXTURE_R_SIZE);\r\n"\
+"  Number frag_coord_nu =\r\n"\
+"      floor(frag_coord.x / Number(SCATTERING_TEXTURE_MU_S_SIZE));\r\n"\
+"  Number frag_coord_mu_s =\r\n"\
+"      mod(frag_coord.x, Number(SCATTERING_TEXTURE_MU_S_SIZE));\r\n"\
+"  vec4 uvwz =\r\n"\
+"      vec4(frag_coord_nu, frag_coord_mu_s, frag_coord.y, frag_coord.z) /\r\n"\
+"          SCATTERING_TEXTURE_SIZE;\r\n"\
+"  GetRMuMuSNuFromScatteringTextureUvwz(\r\n"\
+"      atmosphere, uvwz, r, mu, mu_s, nu, ray_r_mu_intersects_ground);\r\n"\
+"  nu = clamp(nu, mu * mu_s - sqrt((1.0 - mu * mu) * (1.0 - mu_s * mu_s)),\r\n"\
+"      mu * mu_s + sqrt((1.0 - mu * mu) * (1.0 - mu_s * mu_s)));\r\n"\
+"}\r\n"\
+"void ComputeSingleScatteringTexture(IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture, IN(vec3) frag_coord,\r\n"\
+"    OUT(IrradianceSpectrum) rayleigh, OUT(IrradianceSpectrum) mie) {\r\n"\
+"  Length r;\r\n"\
+"  Number mu;\r\n"\
+"  Number mu_s;\r\n"\
+"  Number nu;\r\n"\
+"  bool ray_r_mu_intersects_ground;\r\n"\
+"  GetRMuMuSNuFromScatteringTextureFragCoord(atmosphere, frag_coord,\r\n"\
+"      r, mu, mu_s, nu, ray_r_mu_intersects_ground);\r\n"\
+"  ComputeSingleScattering(atmosphere, transmittance_texture,\r\n"\
+"      r, mu, mu_s, nu, ray_r_mu_intersects_ground, rayleigh, mie);\r\n"\
+"}\r\n"\
+"TEMPLATE(AbstractSpectrum)\r\n"\
+"AbstractSpectrum GetScattering(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(AbstractScatteringTexture TEMPLATE_ARGUMENT(AbstractSpectrum))\r\n"\
+"        scattering_texture,\r\n"\
+"    Length r, Number mu, Number mu_s, Number nu,\r\n"\
+"    bool ray_r_mu_intersects_ground) {\r\n"\
+"  vec4 uvwz = GetScatteringTextureUvwzFromRMuMuSNu(\r\n"\
+"      atmosphere, r, mu, mu_s, nu, ray_r_mu_intersects_ground);\r\n"\
+"  Number tex_coord_x = uvwz.x * Number(SCATTERING_TEXTURE_NU_SIZE - 1);\r\n"\
+"  Number tex_x = floor(tex_coord_x);\r\n"\
+"  Number lerp = tex_coord_x - tex_x;\r\n"\
+"  vec3 uvw0 = vec3((tex_x + uvwz.y) / Number(SCATTERING_TEXTURE_NU_SIZE),\r\n"\
+"      uvwz.z, uvwz.w);\r\n"\
+"  vec3 uvw1 = vec3((tex_x + 1.0 + uvwz.y) / Number(SCATTERING_TEXTURE_NU_SIZE),\r\n"\
+"      uvwz.z, uvwz.w);\r\n"\
+"  return AbstractSpectrum(texture(scattering_texture, uvw0) * (1.0 - lerp) +\r\n"\
+"      texture(scattering_texture, uvw1) * lerp);\r\n"\
+"}\r\n"\
+"RadianceSpectrum GetScattering(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(ReducedScatteringTexture) single_rayleigh_scattering_texture,\r\n"\
+"    IN(ReducedScatteringTexture) single_mie_scattering_texture,\r\n"\
+"    IN(ScatteringTexture) multiple_scattering_texture,\r\n"\
+"    Length r, Number mu, Number mu_s, Number nu,\r\n"\
+"    bool ray_r_mu_intersects_ground,\r\n"\
+"    int scattering_order) {\r\n"\
+"  if (scattering_order == 1) {\r\n"\
+"    IrradianceSpectrum rayleigh = GetScattering(\r\n"\
+"        atmosphere, single_rayleigh_scattering_texture, r, mu, mu_s, nu,\r\n"\
+"        ray_r_mu_intersects_ground);\r\n"\
+"    IrradianceSpectrum mie = GetScattering(\r\n"\
+"        atmosphere, single_mie_scattering_texture, r, mu, mu_s, nu,\r\n"\
+"        ray_r_mu_intersects_ground);\r\n"\
+"    return rayleigh * RayleighPhaseFunction(nu) +\r\n"\
+"        mie * MiePhaseFunction(atmosphere.mie_phase_function_g, nu);\r\n"\
+"  } else {\r\n"\
+"    return GetScattering(\r\n"\
+"        atmosphere, multiple_scattering_texture, r, mu, mu_s, nu,\r\n"\
+"        ray_r_mu_intersects_ground);\r\n"\
+"  }\r\n"\
+"}\r\n"\
+"IrradianceSpectrum GetIrradiance(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(IrradianceTexture) irradiance_texture,\r\n"\
+"    Length r, Number mu_s);\r\n"\
+"RadianceDensitySpectrum ComputeScatteringDensity(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    IN(ReducedScatteringTexture) single_rayleigh_scattering_texture,\r\n"\
+"    IN(ReducedScatteringTexture) single_mie_scattering_texture,\r\n"\
+"    IN(ScatteringTexture) multiple_scattering_texture,\r\n"\
+"    IN(IrradianceTexture) irradiance_texture,\r\n"\
+"    Length r, Number mu, Number mu_s, Number nu, int scattering_order) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius);\r\n"\
+"  assert(mu >= -1.0 && mu <= 1.0);\r\n"\
+"  assert(mu_s >= -1.0 && mu_s <= 1.0);\r\n"\
+"  assert(nu >= -1.0 && nu <= 1.0);\r\n"\
+"  assert(scattering_order >= 2);\r\n"\
+"  vec3 zenith_direction = vec3(0.0, 0.0, 1.0);\r\n"\
+"  vec3 omega = vec3(sqrt(1.0 - mu * mu), 0.0, mu);\r\n"\
+"  Number sun_dir_x = omega.x == 0.0 ? 0.0 : (nu - mu * mu_s) / omega.x;\r\n"\
+"  Number sun_dir_y = sqrt(max(1.0 - sun_dir_x * sun_dir_x - mu_s * mu_s, 0.0));\r\n"\
+"  vec3 omega_s = vec3(sun_dir_x, sun_dir_y, mu_s);\r\n"\
+"  const int SAMPLE_COUNT = 16;\r\n"\
+"  const Angle dphi = pi / Number(SAMPLE_COUNT);\r\n"\
+"  const Angle dtheta = pi / Number(SAMPLE_COUNT);\r\n"\
+"  RadianceDensitySpectrum rayleigh_mie =\r\n"\
+"      RadianceDensitySpectrum(0.0 * watt_per_cubic_meter_per_sr_per_nm);\r\n"\
+"  for (int l = 0; l < SAMPLE_COUNT; ++l) {\r\n"\
+"    Angle theta = (Number(l) + 0.5) * dtheta;\r\n"\
+"    Number cos_theta = cos(theta);\r\n"\
+"    Number sin_theta = sin(theta);\r\n"\
+"    bool ray_r_theta_intersects_ground =\r\n"\
+"        RayIntersectsGround(atmosphere, r, cos_theta);\r\n"\
+"    Length distance_to_ground = 0.0 * m;\r\n"\
+"    DimensionlessSpectrum transmittance_to_ground = DimensionlessSpectrum(0.0);\r\n"\
+"    DimensionlessSpectrum ground_albedo = DimensionlessSpectrum(0.0);\r\n"\
+"    if (ray_r_theta_intersects_ground) {\r\n"\
+"      distance_to_ground =\r\n"\
+"          DistanceToBottomAtmosphereBoundary(atmosphere, r, cos_theta);\r\n"\
+"      transmittance_to_ground =\r\n"\
+"          GetTransmittance(atmosphere, transmittance_texture, r, cos_theta,\r\n"\
+"              distance_to_ground, true /* ray_intersects_ground */);\r\n"\
+"      ground_albedo = atmosphere.ground_albedo;\r\n"\
+"    }\r\n"\
+"    for (int m = 0; m < 2 * SAMPLE_COUNT; ++m) {\r\n"\
+"      Angle phi = (Number(m) + 0.5) * dphi;\r\n"\
+"      vec3 omega_i =\r\n"\
+"          vec3(cos(phi) * sin_theta, sin(phi) * sin_theta, cos_theta);\r\n"\
+"      SolidAngle domega_i = (dtheta / rad) * (dphi / rad) * sin(theta) * sr;\r\n"\
+"      Number nu1 = dot(omega_s, omega_i);\r\n"\
+"      RadianceSpectrum incident_radiance = GetScattering(atmosphere,\r\n"\
+"          single_rayleigh_scattering_texture, single_mie_scattering_texture,\r\n"\
+"          multiple_scattering_texture, r, omega_i.z, mu_s, nu1,\r\n"\
+"          ray_r_theta_intersects_ground, scattering_order - 1);\r\n"\
+"      vec3 ground_normal =\r\n"\
+"          normalize(zenith_direction * r + omega_i * distance_to_ground);\r\n"\
+"      IrradianceSpectrum ground_irradiance = GetIrradiance(\r\n"\
+"          atmosphere, irradiance_texture, atmosphere.bottom_radius,\r\n"\
+"          dot(ground_normal, omega_s));\r\n"\
+"      incident_radiance += transmittance_to_ground *\r\n"\
+"          ground_albedo * (1.0 / (PI * sr)) * ground_irradiance;\r\n"\
+"      Number nu2 = dot(omega, omega_i);\r\n"\
+"      Number rayleigh_density = GetProfileDensity(\r\n"\
+"          atmosphere.rayleigh_density, r - atmosphere.bottom_radius);\r\n"\
+"      Number mie_density = GetProfileDensity(\r\n"\
+"          atmosphere.mie_density, r - atmosphere.bottom_radius);\r\n"\
+"      rayleigh_mie += incident_radiance * (\r\n"\
+"          atmosphere.rayleigh_scattering * rayleigh_density *\r\n"\
+"              RayleighPhaseFunction(nu2) +\r\n"\
+"          atmosphere.mie_scattering * mie_density *\r\n"\
+"              MiePhaseFunction(atmosphere.mie_phase_function_g, nu2)) *\r\n"\
+"          domega_i;\r\n"\
+"    }\r\n"\
+"  }\r\n"\
+"  return rayleigh_mie;\r\n"\
+"}\r\n"\
+"RadianceSpectrum ComputeMultipleScattering(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    IN(ScatteringDensityTexture) scattering_density_texture,\r\n"\
+"    Length r, Number mu, Number mu_s, Number nu,\r\n"\
+"    bool ray_r_mu_intersects_ground) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius);\r\n"\
+"  assert(mu >= -1.0 && mu <= 1.0);\r\n"\
+"  assert(mu_s >= -1.0 && mu_s <= 1.0);\r\n"\
+"  assert(nu >= -1.0 && nu <= 1.0);\r\n"\
+"  const int SAMPLE_COUNT = 50;\r\n"\
+"  Length dx =\r\n"\
+"      DistanceToNearestAtmosphereBoundary(\r\n"\
+"          atmosphere, r, mu, ray_r_mu_intersects_ground) /\r\n"\
+"              Number(SAMPLE_COUNT);\r\n"\
+"  RadianceSpectrum rayleigh_mie_sum =\r\n"\
+"      RadianceSpectrum(0.0 * watt_per_square_meter_per_sr_per_nm);\r\n"\
+"  for (int i = 0; i <= SAMPLE_COUNT; ++i) {\r\n"\
+"    Length d_i = Number(i) * dx;\r\n"\
+"    Length r_i =\r\n"\
+"        ClampRadius(atmosphere, sqrt(d_i * d_i + 2.0 * r * mu * d_i + r * r));\r\n"\
+"    Number mu_i = ClampCosine((r * mu + d_i) / r_i);\r\n"\
+"    Number mu_s_i = ClampCosine((r * mu_s + d_i * nu) / r_i);\r\n"\
+"    RadianceSpectrum rayleigh_mie_i =\r\n"\
+"        GetScattering(\r\n"\
+"            atmosphere, scattering_density_texture, r_i, mu_i, mu_s_i, nu,\r\n"\
+"            ray_r_mu_intersects_ground) *\r\n"\
+"        GetTransmittance(\r\n"\
+"            atmosphere, transmittance_texture, r, mu, d_i,\r\n"\
+"            ray_r_mu_intersects_ground) *\r\n"\
+"        dx;\r\n"\
+"    Number weight_i = (i == 0 || i == SAMPLE_COUNT) ? 0.5 : 1.0;\r\n"\
+"    rayleigh_mie_sum += rayleigh_mie_i * weight_i;\r\n"\
+"  }\r\n"\
+"  return rayleigh_mie_sum;\r\n"\
+"}\r\n"\
+"RadianceDensitySpectrum ComputeScatteringDensityTexture(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    IN(ReducedScatteringTexture) single_rayleigh_scattering_texture,\r\n"\
+"    IN(ReducedScatteringTexture) single_mie_scattering_texture,\r\n"\
+"    IN(ScatteringTexture) multiple_scattering_texture,\r\n"\
+"    IN(IrradianceTexture) irradiance_texture,\r\n"\
+"    IN(vec3) frag_coord, int scattering_order) {\r\n"\
+"  Length r;\r\n"\
+"  Number mu;\r\n"\
+"  Number mu_s;\r\n"\
+"  Number nu;\r\n"\
+"  bool ray_r_mu_intersects_ground;\r\n"\
+"  GetRMuMuSNuFromScatteringTextureFragCoord(atmosphere, frag_coord,\r\n"\
+"      r, mu, mu_s, nu, ray_r_mu_intersects_ground);\r\n"\
+"  return ComputeScatteringDensity(atmosphere, transmittance_texture,\r\n"\
+"      single_rayleigh_scattering_texture, single_mie_scattering_texture,\r\n"\
+"      multiple_scattering_texture, irradiance_texture, r, mu, mu_s, nu,\r\n"\
+"      scattering_order);\r\n"\
+"}\r\n"\
+"RadianceSpectrum ComputeMultipleScatteringTexture(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    IN(ScatteringDensityTexture) scattering_density_texture,\r\n"\
+"    IN(vec3) frag_coord, OUT(Number) nu) {\r\n"\
+"  Length r;\r\n"\
+"  Number mu;\r\n"\
+"  Number mu_s;\r\n"\
+"  bool ray_r_mu_intersects_ground;\r\n"\
+"  GetRMuMuSNuFromScatteringTextureFragCoord(atmosphere, frag_coord,\r\n"\
+"      r, mu, mu_s, nu, ray_r_mu_intersects_ground);\r\n"\
+"  return ComputeMultipleScattering(atmosphere, transmittance_texture,\r\n"\
+"      scattering_density_texture, r, mu, mu_s, nu,\r\n"\
+"      ray_r_mu_intersects_ground);\r\n"\
+"}\r\n"\
+"IrradianceSpectrum ComputeDirectIrradiance(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    Length r, Number mu_s) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius);\r\n"\
+"  assert(mu_s >= -1.0 && mu_s <= 1.0);\r\n"\
+"  Number alpha_s = atmosphere.sun_angular_radius / rad;\r\n"\
+"  Number average_cosine_factor =\r\n"\
+"    mu_s < -alpha_s ? 0.0 : (mu_s > alpha_s ? mu_s :\r\n"\
+"        (mu_s + alpha_s) * (mu_s + alpha_s) / (4.0 * alpha_s));\r\n"\
+"  return atmosphere.solar_irradiance *\r\n"\
+"      GetTransmittanceToTopAtmosphereBoundary(\r\n"\
+"          atmosphere, transmittance_texture, r, mu_s) * average_cosine_factor;\r\n"\
+"}\r\n"\
+"IrradianceSpectrum ComputeIndirectIrradiance(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(ReducedScatteringTexture) single_rayleigh_scattering_texture,\r\n"\
+"    IN(ReducedScatteringTexture) single_mie_scattering_texture,\r\n"\
+"    IN(ScatteringTexture) multiple_scattering_texture,\r\n"\
+"    Length r, Number mu_s, int scattering_order) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius);\r\n"\
+"  assert(mu_s >= -1.0 && mu_s <= 1.0);\r\n"\
+"  assert(scattering_order >= 1);\r\n"\
+"  const int SAMPLE_COUNT = 32;\r\n"\
+"  const Angle dphi = pi / Number(SAMPLE_COUNT);\r\n"\
+"  const Angle dtheta = pi / Number(SAMPLE_COUNT);\r\n"\
+"  IrradianceSpectrum result =\r\n"\
+"      IrradianceSpectrum(0.0 * watt_per_square_meter_per_nm);\r\n"\
+"  vec3 omega_s = vec3(sqrt(1.0 - mu_s * mu_s), 0.0, mu_s);\r\n"\
+"  for (int j = 0; j < SAMPLE_COUNT / 2; ++j) {\r\n"\
+"    Angle theta = (Number(j) + 0.5) * dtheta;\r\n"\
+"    for (int i = 0; i < 2 * SAMPLE_COUNT; ++i) {\r\n"\
+"      Angle phi = (Number(i) + 0.5) * dphi;\r\n"\
+"      vec3 omega =\r\n"\
+"          vec3(cos(phi) * sin(theta), sin(phi) * sin(theta), cos(theta));\r\n"\
+"      SolidAngle domega = (dtheta / rad) * (dphi / rad) * sin(theta) * sr;\r\n"\
+"      Number nu = dot(omega, omega_s);\r\n"\
+"      result += GetScattering(atmosphere, single_rayleigh_scattering_texture,\r\n"\
+"          single_mie_scattering_texture, multiple_scattering_texture,\r\n"\
+"          r, omega.z, mu_s, nu, false /* ray_r_theta_intersects_ground */,\r\n"\
+"          scattering_order) *\r\n"\
+"              omega.z * domega;\r\n"\
+"    }\r\n"\
+"  }\r\n"\
+"  return result;\r\n"\
+"}\r\n"\
+"vec2 GetIrradianceTextureUvFromRMuS(IN(AtmosphereParameters) atmosphere,\r\n"\
+"    Length r, Number mu_s) {\r\n"\
+"  assert(r >= atmosphere.bottom_radius && r <= atmosphere.top_radius);\r\n"\
+"  assert(mu_s >= -1.0 && mu_s <= 1.0);\r\n"\
+"  Number x_r = (r - atmosphere.bottom_radius) /\r\n"\
+"      (atmosphere.top_radius - atmosphere.bottom_radius);\r\n"\
+"  Number x_mu_s = mu_s * 0.5 + 0.5;\r\n"\
+"  return vec2(GetTextureCoordFromUnitRange(x_mu_s, IRRADIANCE_TEXTURE_WIDTH),\r\n"\
+"              GetTextureCoordFromUnitRange(x_r, IRRADIANCE_TEXTURE_HEIGHT));\r\n"\
+"}\r\n"\
+"void GetRMuSFromIrradianceTextureUv(IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(vec2) uv, OUT(Length) r, OUT(Number) mu_s) {\r\n"\
+"  assert(uv.x >= 0.0 && uv.x <= 1.0);\r\n"\
+"  assert(uv.y >= 0.0 && uv.y <= 1.0);\r\n"\
+"  Number x_mu_s = GetUnitRangeFromTextureCoord(uv.x, IRRADIANCE_TEXTURE_WIDTH);\r\n"\
+"  Number x_r = GetUnitRangeFromTextureCoord(uv.y, IRRADIANCE_TEXTURE_HEIGHT);\r\n"\
+"  r = atmosphere.bottom_radius +\r\n"\
+"      x_r * (atmosphere.top_radius - atmosphere.bottom_radius);\r\n"\
+"  mu_s = ClampCosine(2.0 * x_mu_s - 1.0);\r\n"\
+"}\r\n"\
+"const vec2 IRRADIANCE_TEXTURE_SIZE =\r\n"\
+"    vec2(IRRADIANCE_TEXTURE_WIDTH, IRRADIANCE_TEXTURE_HEIGHT);\r\n"\
+"IrradianceSpectrum ComputeDirectIrradianceTexture(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    IN(vec2) frag_coord) {\r\n"\
+"  Length r;\r\n"\
+"  Number mu_s;\r\n"\
+"  GetRMuSFromIrradianceTextureUv(\r\n"\
+"      atmosphere, frag_coord / IRRADIANCE_TEXTURE_SIZE, r, mu_s);\r\n"\
+"  return ComputeDirectIrradiance(atmosphere, transmittance_texture, r, mu_s);\r\n"\
+"}\r\n"\
+"IrradianceSpectrum ComputeIndirectIrradianceTexture(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(ReducedScatteringTexture) single_rayleigh_scattering_texture,\r\n"\
+"    IN(ReducedScatteringTexture) single_mie_scattering_texture,\r\n"\
+"    IN(ScatteringTexture) multiple_scattering_texture,\r\n"\
+"    IN(vec2) frag_coord, int scattering_order) {\r\n"\
+"  Length r;\r\n"\
+"  Number mu_s;\r\n"\
+"  GetRMuSFromIrradianceTextureUv(\r\n"\
+"      atmosphere, frag_coord / IRRADIANCE_TEXTURE_SIZE, r, mu_s);\r\n"\
+"  return ComputeIndirectIrradiance(atmosphere,\r\n"\
+"      single_rayleigh_scattering_texture, single_mie_scattering_texture,\r\n"\
+"      multiple_scattering_texture, r, mu_s, scattering_order);\r\n"\
+"}\r\n"\
+"IrradianceSpectrum GetIrradiance(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(IrradianceTexture) irradiance_texture,\r\n"\
+"    Length r, Number mu_s) {\r\n"\
+"  vec2 uv = GetIrradianceTextureUvFromRMuS(atmosphere, r, mu_s);\r\n"\
+"  return IrradianceSpectrum(texture(irradiance_texture, uv));\r\n"\
+"}\r\n"\
+"#ifdef COMBINED_SCATTERING_TEXTURES\r\n"\
+"vec3 GetExtrapolatedSingleMieScattering(\r\n"\
+"    IN(AtmosphereParameters) atmosphere, IN(vec4) scattering) {\r\n"\
+"  if (scattering.r == 0.0) {\r\n"\
+"    return vec3(0.0);\r\n"\
+"  }\r\n"\
+"  return scattering.rgb * scattering.a / scattering.r *\r\n"\
+"	    (atmosphere.rayleigh_scattering.r / atmosphere.mie_scattering.r) *\r\n"\
+"	    (atmosphere.mie_scattering / atmosphere.rayleigh_scattering);\r\n"\
+"}\r\n"\
+"#endif\r\n"\
+"IrradianceSpectrum GetCombinedScattering(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(ReducedScatteringTexture) scattering_texture,\r\n"\
+"    IN(ReducedScatteringTexture) single_mie_scattering_texture,\r\n"\
+"    Length r, Number mu, Number mu_s, Number nu,\r\n"\
+"    bool ray_r_mu_intersects_ground,\r\n"\
+"    OUT(IrradianceSpectrum) single_mie_scattering) {\r\n"\
+"  vec4 uvwz = GetScatteringTextureUvwzFromRMuMuSNu(\r\n"\
+"      atmosphere, r, mu, mu_s, nu, ray_r_mu_intersects_ground);\r\n"\
+"  Number tex_coord_x = uvwz.x * Number(SCATTERING_TEXTURE_NU_SIZE - 1);\r\n"\
+"  Number tex_x = floor(tex_coord_x);\r\n"\
+"  Number lerp = tex_coord_x - tex_x;\r\n"\
+"  vec3 uvw0 = vec3((tex_x + uvwz.y) / Number(SCATTERING_TEXTURE_NU_SIZE),\r\n"\
+"      uvwz.z, uvwz.w);\r\n"\
+"  vec3 uvw1 = vec3((tex_x + 1.0 + uvwz.y) / Number(SCATTERING_TEXTURE_NU_SIZE),\r\n"\
+"      uvwz.z, uvwz.w);\r\n"\
+"#ifdef COMBINED_SCATTERING_TEXTURES\r\n"\
+"  vec4 combined_scattering =\r\n"\
+"      texture(scattering_texture, uvw0) * (1.0 - lerp) +\r\n"\
+"      texture(scattering_texture, uvw1) * lerp;\r\n"\
+"  IrradianceSpectrum scattering = IrradianceSpectrum(combined_scattering);\r\n"\
+"  single_mie_scattering =\r\n"\
+"      GetExtrapolatedSingleMieScattering(atmosphere, combined_scattering);\r\n"\
+"#else\r\n"\
+"  IrradianceSpectrum scattering = IrradianceSpectrum(\r\n"\
+"      texture(scattering_texture, uvw0) * (1.0 - lerp) +\r\n"\
+"      texture(scattering_texture, uvw1) * lerp);\r\n"\
+"  single_mie_scattering = IrradianceSpectrum(\r\n"\
+"      texture(single_mie_scattering_texture, uvw0) * (1.0 - lerp) +\r\n"\
+"      texture(single_mie_scattering_texture, uvw1) * lerp);\r\n"\
+"#endif\r\n"\
+"  return scattering;\r\n"\
+"}\r\n"\
+"RadianceSpectrum GetSkyRadiance(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    IN(ReducedScatteringTexture) scattering_texture,\r\n"\
+"    IN(ReducedScatteringTexture) single_mie_scattering_texture,\r\n"\
+"    Position camera, IN(Direction) view_ray, Length shadow_length,\r\n"\
+"    IN(Direction) sun_direction, OUT(DimensionlessSpectrum) transmittance) {\r\n"\
+"  Length r = length(camera);\r\n"\
+"  Length rmu = dot(camera, view_ray);\r\n"\
+"  Length distance_to_top_atmosphere_boundary = -rmu -\r\n"\
+"      sqrt(rmu * rmu - r * r + atmosphere.top_radius * atmosphere.top_radius);\r\n"\
+"  if (distance_to_top_atmosphere_boundary > 0.0 * m) {\r\n"\
+"    camera = camera + view_ray * distance_to_top_atmosphere_boundary;\r\n"\
+"    r = atmosphere.top_radius;\r\n"\
+"    rmu += distance_to_top_atmosphere_boundary;\r\n"\
+"  } else if (r > atmosphere.top_radius) {\r\n"\
+"    transmittance = DimensionlessSpectrum(1.0);\r\n"\
+"    return RadianceSpectrum(0.0 * watt_per_square_meter_per_sr_per_nm);\r\n"\
+"  }\r\n"\
+"  Number mu = rmu / r;\r\n"\
+"  Number mu_s = dot(camera, sun_direction) / r;\r\n"\
+"  Number nu = dot(view_ray, sun_direction);\r\n"\
+"  bool ray_r_mu_intersects_ground = RayIntersectsGround(atmosphere, r, mu);\r\n"\
+"  transmittance = ray_r_mu_intersects_ground ? DimensionlessSpectrum(0.0) :\r\n"\
+"      GetTransmittanceToTopAtmosphereBoundary(\r\n"\
+"          atmosphere, transmittance_texture, r, mu);\r\n"\
+"  IrradianceSpectrum single_mie_scattering;\r\n"\
+"  IrradianceSpectrum scattering;\r\n"\
+"  if (shadow_length == 0.0 * m) {\r\n"\
+"    scattering = GetCombinedScattering(\r\n"\
+"        atmosphere, scattering_texture, single_mie_scattering_texture,\r\n"\
+"        r, mu, mu_s, nu, ray_r_mu_intersects_ground,\r\n"\
+"        single_mie_scattering);\r\n"\
+"  } else {\r\n"\
+"    Length d = shadow_length;\r\n"\
+"    Length r_p =\r\n"\
+"        ClampRadius(atmosphere, sqrt(d * d + 2.0 * r * mu * d + r * r));\r\n"\
+"    Number mu_p = (r * mu + d) / r_p;\r\n"\
+"    Number mu_s_p = (r * mu_s + d * nu) / r_p;\r\n"\
+"    scattering = GetCombinedScattering(\r\n"\
+"        atmosphere, scattering_texture, single_mie_scattering_texture,\r\n"\
+"        r_p, mu_p, mu_s_p, nu, ray_r_mu_intersects_ground,\r\n"\
+"        single_mie_scattering);\r\n"\
+"    DimensionlessSpectrum shadow_transmittance =\r\n"\
+"        GetTransmittance(atmosphere, transmittance_texture,\r\n"\
+"            r, mu, shadow_length, ray_r_mu_intersects_ground);\r\n"\
+"    scattering = scattering * shadow_transmittance;\r\n"\
+"    single_mie_scattering = single_mie_scattering * shadow_transmittance;\r\n"\
+"  }\r\n"\
+"  return scattering * RayleighPhaseFunction(nu) + single_mie_scattering *\r\n"\
+"      MiePhaseFunction(atmosphere.mie_phase_function_g, nu);\r\n"\
+"}\r\n"\
+"RadianceSpectrum GetSkyRadianceToPoint(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    IN(ReducedScatteringTexture) scattering_texture,\r\n"\
+"    IN(ReducedScatteringTexture) single_mie_scattering_texture,\r\n"\
+"    Position camera, IN(Position) point, Length shadow_length,\r\n"\
+"    IN(Direction) sun_direction, OUT(DimensionlessSpectrum) transmittance) {\r\n"\
+"  Direction view_ray = normalize(point - camera);\r\n"\
+"  Length r = length(camera);\r\n"\
+"  Length rmu = dot(camera, view_ray);\r\n"\
+"  Length distance_to_top_atmosphere_boundary = -rmu -\r\n"\
+"      sqrt(rmu * rmu - r * r + atmosphere.top_radius * atmosphere.top_radius);\r\n"\
+"  if (distance_to_top_atmosphere_boundary > 0.0 * m) {\r\n"\
+"    camera = camera + view_ray * distance_to_top_atmosphere_boundary;\r\n"\
+"    r = atmosphere.top_radius;\r\n"\
+"    rmu += distance_to_top_atmosphere_boundary;\r\n"\
+"  }\r\n"\
+"  Number mu = rmu / r;\r\n"\
+"  Number mu_s = dot(camera, sun_direction) / r;\r\n"\
+"  Number nu = dot(view_ray, sun_direction);\r\n"\
+"  Length d = length(point - camera);\r\n"\
+"  bool ray_r_mu_intersects_ground = RayIntersectsGround(atmosphere, r, mu);\r\n"\
+"  transmittance = GetTransmittance(atmosphere, transmittance_texture,\r\n"\
+"      r, mu, d, ray_r_mu_intersects_ground);\r\n"\
+"  IrradianceSpectrum single_mie_scattering;\r\n"\
+"  IrradianceSpectrum scattering = GetCombinedScattering(\r\n"\
+"      atmosphere, scattering_texture, single_mie_scattering_texture,\r\n"\
+"      r, mu, mu_s, nu, ray_r_mu_intersects_ground,\r\n"\
+"      single_mie_scattering);\r\n"\
+"  d = max(d - shadow_length, 0.0 * m);\r\n"\
+"  Length r_p = ClampRadius(atmosphere, sqrt(d * d + 2.0 * r * mu * d + r * r));\r\n"\
+"  Number mu_p = (r * mu + d) / r_p;\r\n"\
+"  Number mu_s_p = (r * mu_s + d * nu) / r_p;\r\n"\
+"  IrradianceSpectrum single_mie_scattering_p;\r\n"\
+"  IrradianceSpectrum scattering_p = GetCombinedScattering(\r\n"\
+"      atmosphere, scattering_texture, single_mie_scattering_texture,\r\n"\
+"      r_p, mu_p, mu_s_p, nu, ray_r_mu_intersects_ground,\r\n"\
+"      single_mie_scattering_p);\r\n"\
+"  DimensionlessSpectrum shadow_transmittance = transmittance;\r\n"\
+"  if (shadow_length > 0.0 * m) {\r\n"\
+"    shadow_transmittance = GetTransmittance(atmosphere, transmittance_texture,\r\n"\
+"        r, mu, d, ray_r_mu_intersects_ground);\r\n"\
+"  }\r\n"\
+"  scattering = scattering - shadow_transmittance * scattering_p;\r\n"\
+"  single_mie_scattering =\r\n"\
+"      single_mie_scattering - shadow_transmittance * single_mie_scattering_p;\r\n"\
+"#ifdef COMBINED_SCATTERING_TEXTURES\r\n"\
+"  single_mie_scattering = GetExtrapolatedSingleMieScattering(\r\n"\
+"      atmosphere, vec4(scattering, single_mie_scattering.r));\r\n"\
+"#endif\r\n"\
+"  single_mie_scattering = single_mie_scattering *\r\n"\
+"      smoothstep(Number(0.0), Number(0.01), mu_s);\r\n"\
+"  return scattering * RayleighPhaseFunction(nu) + single_mie_scattering *\r\n"\
+"      MiePhaseFunction(atmosphere.mie_phase_function_g, nu);\r\n"\
+"}\r\n"\
+"IrradianceSpectrum GetSunAndSkyIrradiance(\r\n"\
+"    IN(AtmosphereParameters) atmosphere,\r\n"\
+"    IN(TransmittanceTexture) transmittance_texture,\r\n"\
+"    IN(IrradianceTexture) irradiance_texture,\r\n"\
+"    IN(Position) point, IN(Direction) normal, IN(Direction) sun_direction,\r\n"\
+"    OUT(IrradianceSpectrum) sky_irradiance) {\r\n"\
+"  Length r = length(point);\r\n"\
+"  Number mu_s = dot(point, sun_direction) / r;\r\n"\
+"  sky_irradiance = GetIrradiance(atmosphere, irradiance_texture, r, mu_s) *\r\n"\
+"      (1.0 + dot(normal, point) / r) * 0.5;\r\n"\
+"  return atmosphere.solar_irradiance *\r\n"\
+"      GetTransmittanceToSun(\r\n"\
+"          atmosphere, transmittance_texture, r, mu_s) *\r\n"\
+"      max(dot(normal, sun_direction), 0.0);\r\n"\
+"}\r\n"\
+"";

+ 162 - 0
Praxis3D/Source/AtmScatteringShaderPass.h

@@ -0,0 +1,162 @@
+#pragma once
+
+const char* demo_glsl = \
+"uniform vec3 cameraPosVec;\r\n"\
+"uniform float exposure;\r\n"\
+"uniform vec3 white_point;\r\n"\
+"uniform vec3 earth_center;\r\n"\
+"uniform vec3 sun_direction;\r\n"\
+"uniform vec2 sun_size;\r\n"\
+"in vec3 view_ray;\r\n"\
+"out vec4 color;\r\n"\
+"const float PI = 3.14159265;\r\n"\
+"const vec3 kSphereCenter = vec3(0.0, 0.0, 1000.0) / kLengthUnitInMeters;\r\n"\
+"const float kSphereRadius = 1000.0 / kLengthUnitInMeters;\r\n"\
+"const vec3 kSphereAlbedo = vec3(0.8);\r\n"\
+"const vec3 kGroundAlbedo = vec3(0.0, 0.0, 0.04);\r\n"\
+"#ifdef USE_LUMINANCE\r\n"\
+"#define GetSolarRadiance GetSolarLuminance\r\n"\
+"#define GetSkyRadiance GetSkyLuminance\r\n"\
+"#define GetSkyRadianceToPoint GetSkyLuminanceToPoint\r\n"\
+"#define GetSunAndSkyIrradiance GetSunAndSkyIlluminance\r\n"\
+"#endif\r\n"\
+"vec3 GetSolarRadiance();\r\n"\
+"vec3 GetSkyRadiance(vec3 camera, vec3 view_ray, float shadow_length,\r\n"\
+"    vec3 sun_direction, out vec3 transmittance);\r\n"\
+"vec3 GetSkyRadianceToPoint(vec3 camera, vec3 point, float shadow_length,\r\n"\
+"    vec3 sun_direction, out vec3 transmittance);\r\n"\
+"vec3 GetSunAndSkyIrradiance(\r\n"\
+"    vec3 p, vec3 normal, vec3 sun_direction, out vec3 sky_irradiance);\r\n"\
+"float GetSunVisibility(vec3 point, vec3 sun_direction) {\r\n"\
+"  vec3 p = point - kSphereCenter;\r\n"\
+"  float p_dot_v = dot(p, sun_direction);\r\n"\
+"  float p_dot_p = dot(p, p);\r\n"\
+"  float ray_sphere_center_squared_distance = p_dot_p - p_dot_v * p_dot_v;\r\n"\
+"  float distance_to_intersection = -p_dot_v - sqrt(\r\n"\
+"      kSphereRadius * kSphereRadius - ray_sphere_center_squared_distance);\r\n"\
+"  if (distance_to_intersection > 0.0) {\r\n"\
+"    float ray_sphere_distance =\r\n"\
+"        kSphereRadius - sqrt(ray_sphere_center_squared_distance);\r\n"\
+"    float ray_sphere_angular_distance = -ray_sphere_distance / p_dot_v;\r\n"\
+"    return smoothstep(1.0, 0.0, ray_sphere_angular_distance / sun_size.x);\r\n"\
+"  }\r\n"\
+"  return 1.0;\r\n"\
+"}\r\n"\
+"float GetSkyVisibility(vec3 point) {\r\n"\
+"  vec3 p = point - kSphereCenter;\r\n"\
+"  float p_dot_p = dot(p, p);\r\n"\
+"  return\r\n"\
+"      1.0 + p.z / sqrt(p_dot_p) * kSphereRadius * kSphereRadius / p_dot_p;\r\n"\
+"}\r\n"\
+"void GetSphereShadowInOut(vec3 view_direction, vec3 sun_direction,\r\n"\
+"    out float d_in, out float d_out) {\r\n"\
+"  vec3 pos = cameraPosVec - kSphereCenter;\r\n"\
+"  float pos_dot_sun = dot(pos, sun_direction);\r\n"\
+"  float view_dot_sun = dot(view_direction, sun_direction);\r\n"\
+"  float k = sun_size.x;\r\n"\
+"  float l = 1.0 + k * k;\r\n"\
+"  float a = 1.0 - l * view_dot_sun * view_dot_sun;\r\n"\
+"  float b = dot(pos, view_direction) - l * pos_dot_sun * view_dot_sun -\r\n"\
+"      k * kSphereRadius * view_dot_sun;\r\n"\
+"  float c = dot(pos, pos) - l * pos_dot_sun * pos_dot_sun -\r\n"\
+"      2.0 * k * kSphereRadius * pos_dot_sun - kSphereRadius * kSphereRadius;\r\n"\
+"  float discriminant = b * b - a * c;\r\n"\
+"  if (discriminant > 0.0) {\r\n"\
+"    d_in = max(0.0, (-b - sqrt(discriminant)) / a);\r\n"\
+"    d_out = (-b + sqrt(discriminant)) / a;\r\n"\
+"    float d_base = -pos_dot_sun / view_dot_sun;\r\n"\
+"    float d_apex = -(pos_dot_sun + kSphereRadius / k) / view_dot_sun;\r\n"\
+"    if (view_dot_sun > 0.0) {\r\n"\
+"      d_in = max(d_in, d_apex);\r\n"\
+"      d_out = a > 0.0 ? min(d_out, d_base) : d_base;\r\n"\
+"    } else {\r\n"\
+"      d_in = a > 0.0 ? max(d_in, d_base) : d_base;\r\n"\
+"      d_out = min(d_out, d_apex);\r\n"\
+"    }\r\n"\
+"  } else {\r\n"\
+"    d_in = 0.0;\r\n"\
+"    d_out = 0.0;\r\n"\
+"  }\r\n"\
+"}\r\n"\
+"void main() {\r\n"\
+"  vec3 camera = cameraPosVec;\r\n"\
+"  vec3 view_direction = normalize(view_ray);\r\n"\
+"  float fragment_angular_size =\r\n"\
+"      length(dFdx(view_ray) + dFdy(view_ray)) / length(view_ray);\r\n"\
+"  float shadow_in;\r\n"\
+"  float shadow_out;\r\n"\
+"  GetSphereShadowInOut(view_direction, sun_direction, shadow_in, shadow_out);\r\n"\
+"  float lightshaft_fadein_hack = smoothstep(\r\n"\
+"      0.02, 0.04, dot(normalize(camera - earth_center), sun_direction));\r\n"\
+"  vec3 p = camera - kSphereCenter;\r\n"\
+"  float p_dot_v = dot(p, view_direction);\r\n"\
+"  float p_dot_p = dot(p, p);\r\n"\
+"  float ray_sphere_center_squared_distance = p_dot_p - p_dot_v * p_dot_v;\r\n"\
+"  float distance_to_intersection = -p_dot_v - sqrt(\r\n"\
+"      kSphereRadius * kSphereRadius - ray_sphere_center_squared_distance);\r\n"\
+"  float sphere_alpha = 0.0;\r\n"\
+"  vec3 sphere_radiance = vec3(0.0);\r\n"\
+"  if (distance_to_intersection > 0.0) {\r\n"\
+"    float ray_sphere_distance =\r\n"\
+"        kSphereRadius - sqrt(ray_sphere_center_squared_distance);\r\n"\
+"    float ray_sphere_angular_distance = -ray_sphere_distance / p_dot_v;\r\n"\
+"    sphere_alpha =\r\n"\
+"        min(ray_sphere_angular_distance / fragment_angular_size, 1.0);\r\n"\
+"    vec3 point = camera + view_direction * distance_to_intersection;\r\n"\
+"    vec3 normal = normalize(point - kSphereCenter);\r\n"\
+"    vec3 sky_irradiance;\r\n"\
+"    vec3 sun_irradiance = GetSunAndSkyIrradiance(\r\n"\
+"        point - earth_center, normal, sun_direction, sky_irradiance);\r\n"\
+"    sphere_radiance =\r\n"\
+"        kSphereAlbedo * (1.0 / PI) * (sun_irradiance + sky_irradiance);\r\n"\
+"    float shadow_length =\r\n"\
+"        max(0.0, min(shadow_out, distance_to_intersection) - shadow_in) *\r\n"\
+"        lightshaft_fadein_hack;\r\n"\
+"    vec3 transmittance;\r\n"\
+"    vec3 in_scatter = GetSkyRadianceToPoint(camera - earth_center,\r\n"\
+"        point - earth_center, shadow_length, sun_direction, transmittance);\r\n"\
+"    sphere_radiance = sphere_radiance * transmittance + in_scatter;\r\n"\
+"  }\r\n"\
+"  p = camera - earth_center;\r\n"\
+"  p_dot_v = dot(p, view_direction);\r\n"\
+"  p_dot_p = dot(p, p);\r\n"\
+"  float ray_earth_center_squared_distance = p_dot_p - p_dot_v * p_dot_v;\r\n"\
+"  distance_to_intersection = -p_dot_v - sqrt(\r\n"\
+"      earth_center.z * earth_center.z - ray_earth_center_squared_distance);\r\n"\
+"  float ground_alpha = 0.0;\r\n"\
+"  vec3 ground_radiance = vec3(0.0);\r\n"\
+"  if (distance_to_intersection > 0.0) {\r\n"\
+"    vec3 point = camera + view_direction * distance_to_intersection;\r\n"\
+"    vec3 normal = normalize(point - earth_center);\r\n"\
+"    vec3 sky_irradiance;\r\n"\
+"    vec3 sun_irradiance = GetSunAndSkyIrradiance(\r\n"\
+"        point - earth_center, normal, sun_direction, sky_irradiance);\r\n"\
+"    ground_radiance = kGroundAlbedo * (1.0 / PI) * (\r\n"\
+"        sun_irradiance * GetSunVisibility(point, sun_direction) +\r\n"\
+"        sky_irradiance * GetSkyVisibility(point));\r\n"\
+"    float shadow_length =\r\n"\
+"        max(0.0, min(shadow_out, distance_to_intersection) - shadow_in) *\r\n"\
+"        lightshaft_fadein_hack;\r\n"\
+"    vec3 transmittance;\r\n"\
+"    vec3 in_scatter = GetSkyRadianceToPoint(camera - earth_center,\r\n"\
+"        point - earth_center, shadow_length, sun_direction, transmittance);\r\n"\
+"    ground_radiance = ground_radiance * transmittance + in_scatter;\r\n"\
+"    ground_alpha = 1.0;\r\n"\
+"  }\r\n"\
+"  float shadow_length = max(0.0, shadow_out - shadow_in) *\r\n"\
+"      lightshaft_fadein_hack;\r\n"\
+"  vec3 transmittance;\r\n"\
+"  vec3 radiance = GetSkyRadiance(\r\n"\
+"      camera - earth_center, view_direction, shadow_length, sun_direction,\r\n"\
+"      transmittance);\r\n"\
+"  if (dot(view_direction, sun_direction) > sun_size.y) {\r\n"\
+"    radiance = radiance + transmittance * GetSolarRadiance();\r\n"\
+"  }\r\n"\
+"  radiance = mix(radiance, ground_radiance, ground_alpha);\r\n"\
+"  radiance = mix(radiance, sphere_radiance, sphere_alpha);\r\n"\
+"  color =\r\n"\
+"     vec4(sun_direction, 1.0);\r\n"\
+"}\r\n"\
+"";
+
+//"     vec4(pow(vec3(1.0) - exp(-radiance / white_point * exposure), vec3(1.0 / 2.2)), 1.0);\r\n"\

+ 23 - 0
Praxis3D/Source/BlurPass.h

@@ -70,6 +70,29 @@ public:
 		m_renderer.queueForDrawing(m_blurVerticalShader->getShaderHandle(), m_blurVerticalShader->getUniformUpdater(), p_sceneObjects.m_camera->getBaseObjectData().m_modelMat);
 		m_renderer.passScreenSpaceDrawCommandsToBackend();
 
+		for(int i = 0; i < 5; i++)
+		{
+			// Bind intermediate blur texture for reading so it can be accessed in the shaders
+			m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(p_renderPassData.getColorOutputMap(), GeometryBuffer::GBufferInputTexture);
+
+			// Bind emissive texture for writing to, so the second pass populates it with the final blur result
+			m_renderer.m_backend.getGeometryBuffer()->bindBufferForWriting(p_renderPassData.getEmissiveInputMap());
+
+			// Perform horizontal blur. Queue and render a full screen quad using a horizontal blur shader
+			m_renderer.queueForDrawing(m_blurHorizontalShader->getShaderHandle(), m_blurHorizontalShader->getUniformUpdater(), p_sceneObjects.m_camera->getBaseObjectData().m_modelMat);
+			m_renderer.passScreenSpaceDrawCommandsToBackend();
+
+			// Bind emissive texture for reading so it can be accessed in the shaders
+			m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(p_renderPassData.getEmissiveInputMap(), GeometryBuffer::GBufferInputTexture);
+
+			// Bind blur texture for writing to, so it can be used as an intermediate buffer between blur passes
+			m_renderer.m_backend.getGeometryBuffer()->bindBufferForWriting(p_renderPassData.getColorOutputMap());
+
+			// Perform verical blur. Queue and render a full screen quad using a vertical blur shader
+			m_renderer.queueForDrawing(m_blurVerticalShader->getShaderHandle(), m_blurVerticalShader->getUniformUpdater(), p_sceneObjects.m_camera->getBaseObjectData().m_modelMat);
+			m_renderer.passScreenSpaceDrawCommandsToBackend();
+		}
+
 
 		// Bind intermediate blur texture for reading so it can be accessed in the shaders
 		m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(p_renderPassData.getColorOutputMap(), GeometryBuffer::GBufferInputTexture);

+ 19 - 0
Praxis3D/Source/CameraScript.h

@@ -166,6 +166,25 @@ public:
 		m_upVector = Math::cross(m_horizontalVec, m_targetVec);
 		m_modelMatrix.initCamera(m_positionVec, m_targetVec + m_positionVec, m_upVector);
 
+		/*
+		float cos_z = cos(m_verticalAngle);
+		float sin_z = sin(m_verticalAngle);
+		float cos_a = cos(m_horizontalAngle);
+		float sin_a = sin(m_horizontalAngle);
+		float ux[3] = { -sin_a, cos_a, 0.0 };
+		float uy[3] = { -cos_z * cos_a, -cos_z * sin_a, sin_z };
+		float uz[3] = { sin_z * cos_a, sin_z * sin_a, cos_z };
+		float l = 9000.0 / 1000.0;
+
+		// Transform matrix from camera frame to world space (i.e. the inverse of a
+		// GL_MODELVIEW matrix).
+		m_modelMatrix = {
+			ux[0], uy[0], uz[0], uz[0] * l,
+			ux[1], uy[1], uz[1], uz[1] * l,
+			ux[2], uy[2], uz[2], uz[2] * l,
+			0.0, 0.0, 0.0, 1.0
+		};*/
+
 		// Set the target vector variable, so it can be retrieved later by listeners
 		m_targetVec = Math::Vec3f(0.0f);
 		m_targetVec.y = m_verticalAngle;

+ 27 - 7
Praxis3D/Source/CommonDefinitions.h

@@ -15,6 +15,12 @@ enum CommandType : unsigned int
 	CommandType_Load
 };
 
+/*
+enum AtmScatteringBufferBinding : unsigned int
+{
+	AtmScatteringBufferBinding_AtmScatParam = LightBufferBinding_Total,
+	AtmScatteringBufferBinding_Total
+};*/
 enum BufferType : unsigned int
 {
 	BufferType_Uniform			= GL_UNIFORM_BUFFER,
@@ -33,16 +39,13 @@ enum BufferUsageHint : unsigned int
 	BufferUsageHint_StaticDraw		= GL_STATIC_DRAW,
 	BufferUsageHint_DynamicDraw		= GL_DYNAMIC_DRAW,
 	BufferUsageHint_DynamicCopy		= GL_DYNAMIC_COPY
-};
+};/*
 enum LightBufferBinding : unsigned int
 {
 	LightBufferBinding_PointLight = 0,
-	LightBufferBinding_SpotLight
-};
-enum SSBOBinding : unsigned int
-{
-	SSBOBinding_HDR = 0
-};
+	LightBufferBinding_SpotLight,
+	LightBufferBinding_Total
+};*/
 enum LoadObjectType : unsigned int
 {
 	LoadObject_Buffer,
@@ -85,6 +88,13 @@ enum ModelBufferType : unsigned int
 	ModelBuffer_Index = ModelBuffer_NumTypesWithoutIndex,
 	ModelBuffer_NumAllTypes
 };
+enum AtmScatteringTextureType : unsigned int
+{
+	AtmScatteringTextureType_Irradiance = MaterialType_NumOfTypes_Extended,
+	AtmScatteringTextureType_Scattering,
+	AtmScatteringTextureType_SingleMie,
+	AtmScatteringTextureType_Transmittance
+};
 enum ShaderType : unsigned int
 {
 	// WARNING: do not change the order - enum entries are order-sensitive
@@ -96,6 +106,10 @@ enum ShaderType : unsigned int
 	ShaderType_TessEvaluation,
 	ShaderType_NumOfTypes
 };
+enum SSBOBinding : unsigned int
+{
+	SSBOBinding_HDR = 0
+};
 enum TextureFormat : int
 {
 	TextureFormat_Red	= GL_RED,
@@ -104,4 +118,10 @@ enum TextureFormat : int
 	TextureFormat_Alpha = GL_ALPHA,
 	TextureFormat_RGB	= GL_RGB,
 	TextureFormat_RGBA	= GL_RGBA
+};
+enum UniformBufferBinding : unsigned int
+{
+	UniformBufferBinding_PointLights,
+	UniformBufferBinding_SpotLights,
+	UniformBufferBinding_AtmScatParam
 };

+ 12 - 0
Praxis3D/Source/Config.cpp

@@ -199,6 +199,10 @@ void Config::init()
 	AddVariablePredef(m_filepathVar, texture_path);
 	
 	// Renderer variables
+	AddVariablePredef(m_rendererVar, atm_scattering_ground_vert_shader);
+	AddVariablePredef(m_rendererVar, atm_scattering_ground_frag_shader);
+	AddVariablePredef(m_rendererVar, atm_scattering_sky_vert_shader);
+	AddVariablePredef(m_rendererVar, atm_scattering_sky_frag_shader);
 	AddVariablePredef(m_rendererVar, dir_light_vert_shader);
 	AddVariablePredef(m_rendererVar, dir_light_frag_shader);
 	AddVariablePredef(m_rendererVar, point_light_vert_shader);
@@ -247,12 +251,14 @@ void Config::init()
 	AddVariablePredef(m_rendererVar, face_culling);
 
 	// Shader variables
+	AddVariablePredef(m_shaderVar, atmScatProjMatUniform);
 	AddVariablePredef(m_shaderVar, modelMatUniform);
 	AddVariablePredef(m_shaderVar, viewMatUniform);
 	AddVariablePredef(m_shaderVar, projectionMatUniform);
 	AddVariablePredef(m_shaderVar, viewProjectionMatUniform);
 	AddVariablePredef(m_shaderVar, modelViewMatUniform);
 	AddVariablePredef(m_shaderVar, modelViewProjectionMatUniform);
+	AddVariablePredef(m_shaderVar, transposeViewMatUniform);
 	AddVariablePredef(m_shaderVar, screenSizeUniform);
 	AddVariablePredef(m_shaderVar, deltaTimeMSUniform);
 	AddVariablePredef(m_shaderVar, deltaTimeSUniform);
@@ -298,6 +304,11 @@ void Config::init()
 	AddVariablePredef(m_shaderVar, emissiveTextureUniform);
 	AddVariablePredef(m_shaderVar, glossTextureUniform);
 	AddVariablePredef(m_shaderVar, heightTextureUniform);
+	AddVariablePredef(m_shaderVar, combinedTextureUniform);
+	AddVariablePredef(m_shaderVar, atmIrradianceTextureUniform);
+	AddVariablePredef(m_shaderVar, atmScatteringTextureUniform);
+	AddVariablePredef(m_shaderVar, atmSingleMieScatTextureUniform);
+	AddVariablePredef(m_shaderVar, atmTransmittanceTextureUniform);
 	AddVariablePredef(m_shaderVar, fogDensityUniform);
 	AddVariablePredef(m_shaderVar, fogColorUniform);
 	AddVariablePredef(m_shaderVar, billboardScaleUniform);
@@ -305,6 +316,7 @@ void Config::init()
 	AddVariablePredef(m_shaderVar, eyeAdaptionRateUniform);
 	AddVariablePredef(m_shaderVar, eyeAdaptionIntBrightnessUniform);
 	AddVariablePredef(m_shaderVar, HDRSSBuffer);
+	AddVariablePredef(m_shaderVar, atmScatParamBuffer);
 	AddVariablePredef(m_shaderVar, testMatUniform);
 	AddVariablePredef(m_shaderVar, testVecUniform);
 	AddVariablePredef(m_shaderVar, testFloatUniform);

+ 40 - 2
Praxis3D/Source/Config.h

@@ -183,7 +183,9 @@ namespace Properties
 	/* Scripting */ \
 	Code(Angle,) \
 	Code(Axis,) \
+	Code(Azimuth,) \
 	Code(BaseUIScript,) \
+	Code(Day,) \
 	Code(DayOfYear,) \
 	Code(DebugMoveScript,) \
 	Code(DebugRotateScript,) \
@@ -197,15 +199,20 @@ namespace Properties
 	Code(KeyCode,) \
 	Code(KeyName,) \
 	Code(Minutes,) \
+	Code(Month,) \
 	Code(Radius,) \
 	Code(Scripting,) \
 	Code(Seconds,) \
 	Code(SolarTimeScript,) \
 	Code(Speed,) \
 	Code(SprintSpeed,) \
+	Code(SunScript,) \
 	Code(TimeMultiplier,) \
+	Code(TimeZone,) \
+	Code(Year,) \
 	Code(UpperLimit,) \
 	Code(WorldEditScript,) \
+	Code(Zenith,) \
 	/* Window */ \
 	Code(Fullscreen,) \
 	Code(MouseCapture,) \
@@ -306,7 +313,9 @@ namespace Properties
 		GetString(Subject),
 		GetString(Angle),
 		GetString(Axis),
+		GetString(Azimuth),
 		GetString(BaseUIScript),
+		GetString(Day),
 		GetString(DayOfYear),
 		GetString(DebugMoveScript),
 		GetString(DebugRotateScript),
@@ -320,15 +329,20 @@ namespace Properties
 		GetString(KeyCode),
 		GetString(KeyName),
 		GetString(Minutes),
+		GetString(Month),
 		GetString(Radius),
 		GetString(Scripting),
 		GetString(Seconds),
 		GetString(SolarTimeScript),
 		GetString(Speed),
 		GetString(SprintSpeed),
+		GetString(SunScript),
 		GetString(TimeMultiplier),
+		GetString(TimeZone),
+		GetString(Year),
 		GetString(UpperLimit),
 		GetString(WorldEditScript),
+		GetString(Zenith),
 		GetString(Fullscreen),
 		GetString(MouseCapture),
 		GetString(VerticalSync),
@@ -560,7 +574,7 @@ public:
 			emissive_multiplier = 10.0f;
 			emissive_threshold = 0.01f;
 			eye_adaption_rate = 0.25f;
-			eye_adaption_intended_brightness = 0.2f;
+			eye_adaption_intended_brightness = 0.5f;
 			fog_color_x = 0.55f;
 			fog_color_y = 0.55f;
 			fog_color_z = 0.55f;
@@ -759,6 +773,10 @@ public:
 	{
 		RendererVariables()
 		{
+			atm_scattering_ground_vert_shader = "atmosphericScatteringPass_ground.vert";
+			atm_scattering_ground_frag_shader = "atmosphericScatteringPass_ground.frag";
+			atm_scattering_sky_vert_shader = "atmosphericScatteringPass_sky.vert";
+			atm_scattering_sky_frag_shader = "atmosphericScatteringPass_sky.frag";
 			dir_light_vert_shader = "dirLightPass.vert";
 			dir_light_frag_shader = "dirLightPass.frag";
 			point_light_vert_shader = "pointLightPass.vert";
@@ -808,7 +826,11 @@ public:
 			depth_test = true;
 			face_culling = true;
 		}
-
+		
+		std::string atm_scattering_ground_vert_shader;
+		std::string atm_scattering_ground_frag_shader;
+		std::string atm_scattering_sky_vert_shader;
+		std::string atm_scattering_sky_frag_shader;
 		std::string dir_light_vert_shader;
 		std::string dir_light_frag_shader;
 		std::string point_light_vert_shader;
@@ -862,12 +884,14 @@ public:
 	{
 		ShaderVariables()
 		{
+			atmScatProjMatUniform = "atmScatProjMat";
 			modelMatUniform = "modelMat";
 			viewMatUniform = "viewMat";
 			projectionMatUniform = "projMat";
 			viewProjectionMatUniform = "viewProjMat";
 			modelViewMatUniform = "modelViewMat";
 			modelViewProjectionMatUniform = "MVP";
+			transposeViewMatUniform = "transposeViewMat";
 			screenSizeUniform = "screenSize";
 			deltaTimeMSUniform = "deltaTimeMS";
 			deltaTimeSUniform = "deltaTimeS";
@@ -921,6 +945,11 @@ public:
 			heightTextureUniform = "heightTexture";
 			combinedTextureUniform = "combinedTexture";
 
+			atmIrradianceTextureUniform = "atmIrradianceTexture";
+			atmScatteringTextureUniform = "atmScatteringTexture";
+			atmSingleMieScatTextureUniform = "atmSingleMieTexture";
+			atmTransmittanceTextureUniform = "atmTransmitTexture";
+
 			dynamicEnvMapUniform = "dynamicEnvMap";
 			staticEnvMapUniform = "staticEnvMap";
 
@@ -932,18 +961,21 @@ public:
 			eyeAdaptionRateUniform = "eyeAdaptionRate";
 			eyeAdaptionIntBrightnessUniform = "eyeAdaptionIntBrightness";
 			HDRSSBuffer = "HDRBuffer";
+			atmScatParamBuffer = "AtmScatParametersBuffer";
 
 			testMatUniform = "testMat";
 			testVecUniform = "testVec";
 			testFloatUniform = "testFloat";
 		}
 
+		std::string atmScatProjMatUniform;
 		std::string modelMatUniform;
 		std::string viewMatUniform;
 		std::string projectionMatUniform;
 		std::string viewProjectionMatUniform;
 		std::string modelViewMatUniform;
 		std::string modelViewProjectionMatUniform;
+		std::string transposeViewMatUniform;
 		std::string screenSizeUniform;
 		std::string deltaTimeMSUniform;
 		std::string deltaTimeSUniform;
@@ -997,6 +1029,11 @@ public:
 		std::string heightTextureUniform;
 		std::string combinedTextureUniform;
 
+		std::string atmIrradianceTextureUniform;
+		std::string atmScatteringTextureUniform;
+		std::string atmSingleMieScatTextureUniform;
+		std::string atmTransmittanceTextureUniform;
+
 		std::string dynamicEnvMapUniform;
 		std::string staticEnvMapUniform;
 
@@ -1008,6 +1045,7 @@ public:
 		std::string eyeAdaptionRateUniform;
 		std::string eyeAdaptionIntBrightnessUniform;
 		std::string HDRSSBuffer;
+		std::string atmScatParamBuffer;
 
 		std::string testMatUniform;
 		std::string testVecUniform;

+ 3 - 0
Praxis3D/Source/GeometryPass.h

@@ -37,6 +37,9 @@ public:
 
 	void update(RenderPassData &p_renderPassData, const SceneObjects &p_sceneObjects, const float p_deltaTime)
 	{
+		glDepthMask(GL_TRUE);
+		glDepthFunc(GL_LEQUAL);
+
 		// Set input and output color maps for this frame
 		p_renderPassData.setColorInputMap(GeometryBuffer::GBufferIntermediate);
 		p_renderPassData.setColorOutputMap(GeometryBuffer::GBufferFinal);

+ 165 - 0
Praxis3D/Source/GraphicsDataSets.h

@@ -107,4 +107,169 @@ struct HDRDataSet
 	}
 
 	float m_screenBrightness;
+};
+
+// An atmosphere density layer
+struct DensityProfLayer
+{
+	DensityProfLayer() : DensityProfLayer(0.0f, 0.0f, 0.0f, 0.0f, 0.0f) { }
+	DensityProfLayer(float p_width, float p_expTerm, float p_expScale, float p_linearTerm, float p_constantTerm) :
+		m_width(p_width), m_expTerm(p_expTerm), m_expScale(p_expScale), m_linearTerm(p_linearTerm), m_constantTerm(p_constantTerm) { }
+
+	float m_width;
+	float m_expTerm;
+	float m_expScale;
+	float m_linearTerm;
+	float m_constantTerm;
+
+	Math::Vec3f m_padding1;
+};
+
+// An atmosphere density profile made of several layers on top of each other
+struct DensityProfile
+{
+	DensityProfile() { }
+
+	DensityProfile(DensityProfLayer p_layers[2])
+	{
+		m_layers[0] = p_layers[0];
+		m_layers[1] = p_layers[1];
+	}
+	DensityProfile(DensityProfLayer p_layer1, DensityProfLayer p_layer2)
+	{
+		m_layers[0] = p_layer1;
+		m_layers[1] = p_layer2;
+	}
+
+	DensityProfLayer m_layers[2];
+};
+
+// A planet's atmosphere parameters
+struct AtmosphereParameters
+{
+	AtmosphereParameters() :
+		m_solarIrradiance(0.0f),
+		m_sunAngularRadius(0.0f),
+		m_bottomRadius(0.0f),
+		m_topRadius(0.0f),
+		m_rayleighScattering(0.0f),
+		m_mieScattering(0.0f),
+		m_mieExtinction(0.0f),
+		m_miePhaseFunctionG(0.0f),
+		m_absorptionExtinction(0.0f),
+		m_groundAlbedo(0.0f),
+		m_muSMin(0.0f)
+	{
+
+	}
+
+	AtmosphereParameters(
+		Math::Vec3f p_solarIrradiance, 
+		float p_sunAngularRadius, 
+		DensityProfile p_rayleighDensity, 
+		DensityProfile p_mieDensity, 
+		DensityProfile p_absorptionDensity, 
+		Math::Vec3f p_rayleighScattering, 
+		float p_bottomRadius, 
+		Math::Vec3f p_mieScattering, 
+		float p_topRadius, 
+		Math::Vec3f p_mieExtinction, 
+		float p_miePhaseFunctionG, 
+		Math::Vec3f p_absorptionExtinction, 
+		float p_muSMin, 
+		Math::Vec3f p_groundAlbedo) :
+		m_solarIrradiance(p_solarIrradiance),
+		m_sunAngularRadius(p_sunAngularRadius),
+		m_rayleighDensity(p_rayleighDensity),
+		m_mieDensity(p_mieDensity),
+		m_absorptionDensity(p_absorptionDensity),
+		m_rayleighScattering(p_rayleighScattering),
+		m_bottomRadius(p_bottomRadius),
+		m_mieScattering(p_mieScattering),
+		m_topRadius(p_topRadius),
+		m_mieExtinction(p_mieExtinction),
+		m_miePhaseFunctionG(p_miePhaseFunctionG),
+		m_absorptionExtinction(p_absorptionExtinction),
+		m_muSMin(p_muSMin),
+		m_groundAlbedo(p_groundAlbedo)
+	{
+
+	}
+	
+	// The solar irradiance at the top of the atmosphere.
+	Math::Vec3f m_solarIrradiance;
+	
+	// The sun's angular radius
+	float m_sunAngularRadius;
+
+	// The density profile of air molecules
+	DensityProfile m_rayleighDensity;
+
+	// The density profile of aerosols
+	DensityProfile m_mieDensity;
+
+	// The density profile of air molecules that absorb light (e.g. ozone)
+	DensityProfile m_absorptionDensity;
+
+	// The scattering coefficient of air molecules at the altitude where their density is maximum
+	Math::Vec3f m_rayleighScattering;
+	
+	// The distance between the planet center and the bottom of the atmosphere.
+	float m_bottomRadius;
+
+	// The scattering coefficient of aerosols at the altitude where their density is maximum
+	Math::Vec3f m_mieScattering;
+
+	// The distance between the planet center and the top of the atmosphere.
+	float m_topRadius;
+
+	// The extinction coefficient of aerosols at the altitude where their density is maximum
+	Math::Vec3f m_mieExtinction;
+	
+	// The asymetry parameter for the Cornette-Shanks phase function for the aerosols.
+	float m_miePhaseFunctionG;
+
+	// The extinction coefficient of molecules that absorb light (e.g. ozone)
+	Math::Vec3f m_absorptionExtinction;
+	
+	// The cosine of the maximum Sun zenith angle for which atmospheric scattering must be precomputed
+	float m_muSMin;
+
+	// The average albedo of the ground.
+	Math::Vec3f m_groundAlbedo;
+};
+
+// Parameters for atmospheric scattering shader
+struct AtmScatteringParameters
+{
+	AtmScatteringParameters() :
+		m_whitePoint(1.0f, 1.0f, 1.0f),
+		m_earthCenter(0.0f, -6360000.0f / 1000.0f, 0.0f),
+		m_sunSize(tan(0.01935f / 2.0f), cos(0.01935f / 2.0f)) { }
+
+	AtmScatteringParameters(AtmosphereParameters p_atmosphereParam) : 
+		m_atmosphereParam(p_atmosphereParam),
+		m_whitePoint(1.0f, 1.0f, 1.0f),
+		m_earthCenter(0.0f, -6360000.0f / 1000.0f, 0.0f),
+		m_sunSize(tan(0.01935f / 2.0f), cos(0.01935f / 2.0f)) { }
+	
+	AtmScatteringParameters(
+		AtmosphereParameters p_atmosphereParam,
+		Math::Vec3f p_whitePoint,
+		Math::Vec3f p_earthCenter,
+		Math::Vec2f p_sunSize) : 
+		m_atmosphereParam(p_atmosphereParam),
+		m_whitePoint(p_whitePoint),
+		m_earthCenter(p_earthCenter),
+		m_sunSize(p_sunSize) { }
+
+	Math::Vec3f m_whitePoint;
+	float m_padding1;
+	Math::Vec3f m_earthCenter;
+	float m_padding2;
+	Math::Vec2f m_sunSize;
+	float m_padding3;
+	float m_padding4;
+
+	AtmosphereParameters m_atmosphereParam;
 };

+ 5 - 2
Praxis3D/Source/LightingGraphicsObjects.h

@@ -8,7 +8,10 @@ class DirectionalLightObject : public SystemObject
 	//friend class RendererScene;
 public:
 	DirectionalLightObject(SystemScene *p_systemScene, const std::string &p_name, DirectionalLightDataSet p_lightDataSet)
-		: SystemObject(p_systemScene, p_name, Properties::DirectionalLight), m_lightDataSet(p_lightDataSet), m_active(true) { }
+		: SystemObject(p_systemScene, p_name, Properties::DirectionalLight), m_lightDataSet(p_lightDataSet), m_active(true) 
+	{ 
+
+	}
 
 	ErrorCode init()
 	{
@@ -81,7 +84,7 @@ public:
 
 private:
 	DirectionalLightDataSet m_lightDataSet;
-
+	
 	bool m_active;
 };
 

+ 8 - 7
Praxis3D/Source/LightingPass.h

@@ -19,8 +19,8 @@ public:
 		m_name = "Lighting Rendering Pass";
 
 		// Set lightbuffer values
-		m_pointLightBuffer.m_bindingIndex = LightBufferBinding_PointLight;
-		m_spotLightBuffer.m_bindingIndex = LightBufferBinding_SpotLight;
+		m_pointLightBuffer.m_bindingIndex = UniformBufferBinding_PointLights;
+		m_spotLightBuffer.m_bindingIndex = UniformBufferBinding_SpotLights;
 
 		// Set the light buffer sizes
 		m_pointLightBuffer.m_size = sizeof(PointLightDataSet) * Config::graphicsVar().max_num_point_lights;
@@ -57,16 +57,17 @@ public:
 
 	void update(RenderPassData &p_renderPassData, const SceneObjects &p_sceneObjects, const float p_deltaTime)
 	{
-		glDisable(GL_DEPTH_TEST);
-		//glEnable(GL_DEPTH_TEST);
-		//glDepthFunc(GL_NOTEQUAL);
+		//glDisable(GL_DEPTH_TEST);
+		glEnable(GL_DEPTH_TEST);
+		glDepthFunc(GL_GREATER);
+		glDepthMask(GL_FALSE);
 
 		// Setup point light buffer values
-		m_pointLightBuffer.m_size = sizeof(PointLightDataSet) * p_sceneObjects.m_pointLights.size();
+		m_pointLightBuffer.m_updateSize = sizeof(PointLightDataSet) * p_sceneObjects.m_pointLights.size();
 		m_pointLightBuffer.m_data = (void*)p_sceneObjects.m_pointLights.data();
 
 		// Setup spot light buffer values
-		m_spotLightBuffer.m_size = sizeof(SpotLightDataSet) * p_sceneObjects.m_spotLights.size();
+		m_spotLightBuffer.m_updateSize = sizeof(SpotLightDataSet) * p_sceneObjects.m_spotLights.size();
 		m_spotLightBuffer.m_data = (void*)p_sceneObjects.m_spotLights.data();
 
 		// Bind textures for reading

+ 11 - 0
Praxis3D/Source/Math.cpp

@@ -63,6 +63,17 @@ namespace Math
 
 		*this = (*this * rotY * rotX * rotZ);
 	}
+	void Mat4f::perspectiveRadian(const float p_FOV, const int p_screenWidth, const int p_screenHeight)
+	{
+		float aspectRatio = static_cast<float>(p_screenWidth) / static_cast<float>(p_screenHeight);
+		float kFovY = 80.0f / 180.0f * (float)PI;
+		float kTanFovY = tan(kFovY / 2.0f);
+
+		 m[0] = kTanFovY * aspectRatio;	 m[4] = 0.0f;		m[8] = 0.0f;	m[12] = 0.0f;
+		 m[1] = 0.0f;					 m[5] = kTanFovY;	m[9] = 0.0f;	m[13] = 0.0f;
+		 m[2] = 0.0f;					 m[6] = 0.0f;		m[10] = 0.0f;	m[14] = 1.0f;
+		 m[3] = 0.0f;					 m[7] = 0.0f;		m[11] = -1.0f;	m[15] = 1.0f;
+	}
 	void Mat4f::perspective(const float p_FOV, const int p_screenWidth, const int p_screenHeight, const float p_zNear, const float p_zFar)
 	{
 		float	radFOV = toRadian(p_FOV),

+ 19 - 5
Praxis3D/Source/Math.h

@@ -15,6 +15,9 @@
 #define SQRTPI		1.12837916709551257390
 #define SQRT2		1.41421356237309504880
 #define SQRT1_2		0.707106781186547524401
+#define RAD			(PI / 180.0)
+#define DEG			(180.0 / PI)
+#define TWOPI		(PI * 2)
 
 namespace Math
 {
@@ -419,10 +422,10 @@ namespace Math
 		}
 		Mat4f(float p_float[16])
 		{
-			m[0] = p_float[0]; m[4] = p_float[4]; m[8] = p_float[8];	 m[12] = p_float[12];
-			m[1] = p_float[1]; m[5] = p_float[5]; m[9] = p_float[9];	 m[13] = p_float[13];
-			m[2] = p_float[2]; m[6] = p_float[6]; m[10] = p_float[10]; m[14] = p_float[14];
-			m[3] = p_float[3]; m[7] = p_float[7]; m[11] = p_float[11]; m[15] = p_float[15];
+			m[0] = p_float[0]; m[4] = p_float[4]; m[8] = p_float[8];	m[12] = p_float[12];
+			m[1] = p_float[1]; m[5] = p_float[5]; m[9] = p_float[9];	m[13] = p_float[13];
+			m[2] = p_float[2]; m[6] = p_float[6]; m[10] = p_float[10];	m[14] = p_float[14];
+			m[3] = p_float[3]; m[7] = p_float[7]; m[11] = p_float[11];	m[15] = p_float[15];
 		}
 		Mat4f(float p_m01, float p_m02, float p_m03, float p_m04,
 			  float p_m05, float p_m06, float p_m07, float p_m08,
@@ -491,6 +494,7 @@ namespace Math
 		}
 
 		void rotate(const Vec3f& p_vec3f);
+		void perspectiveRadian(const float p_FOV, const int p_screenWidth, const int p_screenHeight);
 		void perspective(const float p_FOV, const int p_screenWidth, const int p_screenHeight, const float p_zNear, const float p_zFar);
 		void perspective(const float p_FOV, const float p_aspectRatio, const float p_zNear, const float p_zFar);
 		inline void initCamera(const Vec3f& p_position, const Vec3f& p_target, const Vec3f& p_up)
@@ -535,6 +539,13 @@ namespace Math
 		}
 	};
 
+	const inline Mat4f transpose(const Mat4f& p_mat)
+	{
+		return Mat4f(	p_mat.m[0], p_mat.m[4], p_mat.m[8], p_mat.m[12],
+						p_mat.m[1], p_mat.m[5], p_mat.m[9], p_mat.m[13],
+						p_mat.m[2], p_mat.m[6], p_mat.m[10],p_mat.m[14],
+						p_mat.m[3], p_mat.m[7], p_mat.m[11],p_mat.m[15]);
+	}
 	const inline Mat4f operator*(const Mat4f& p_left, const Mat4f p_right)
 	{
 		// Multiplication can be done more stylish with a nested loop, but this should be slightly faster
@@ -568,9 +579,12 @@ namespace Math
 				p_vec3.y * 180.0f / (float)PI,
 				p_vec3.z * 180.0f / (float)PI);
 	}
-	inline float toRadian(const float p_float) { return (p_float * (float) PI / 180.0f); }
+	inline float toRadian(const float p_angle) { return (p_angle * (float) PI / 180.0f); }
+	inline double toRadian(const double p_angle) { return (p_angle * PI / 180.0); }
 	inline float toDegree(const float p_float) { return (p_float * 180.0f / (float) PI); }
 	inline float getMax(const float p_left, const float p_right) { return p_left > p_right ? p_left : p_right; }
+	inline float wrapRadianAngle(float p_angleInRadians) { return p_angleInRadians - (float)TWOPI * floor(p_angleInRadians / (float)TWOPI); }
+	inline double wrapRadianAngle(double p_angleInRadians) { return p_angleInRadians - TWOPI * floor(p_angleInRadians / TWOPI); }
 
 	template <typename T>
 	inline T clamp(T p_in, T p_low, T p_high) { return std::min(std::max(p_in, p_low), p_high); }

+ 4 - 0
Praxis3D/Source/RenderPassBase.h

@@ -12,6 +12,8 @@ struct RenderPassData
 		m_colorInputMap = GeometryBuffer::GBufferDiffuse;
 		m_colorOutputMap = GeometryBuffer::GBufferFinal;
 		m_emissiveInputMap = GeometryBuffer::GBufferEmissive;
+
+		m_atmScatDoSkyPass = true;
 	}
 
 	inline void swapColorInputOutputMaps()
@@ -35,6 +37,8 @@ struct RenderPassData
 	GeometryBuffer::GBufferTextureType	m_colorInputMap,
 										m_colorOutputMap,
 										m_emissiveInputMap;
+
+	bool m_atmScatDoSkyPass;
 };
 
 class RenderPass

+ 1 - 1
Praxis3D/Source/RendererBackend.cpp

@@ -36,7 +36,7 @@ ErrorCode RendererBackend::init(const UniformFrameData &p_frameData)
 		else
 			glDisable(GL_DEPTH_TEST);
 
-		glDepthFunc(GL_LESS);
+		//glDepthFunc(GL_LESS);
 
 		// Set face culling mode
 		glCullFace(Config::rendererVar().face_culling_mode);

+ 15 - 13
Praxis3D/Source/RendererBackend.h

@@ -130,20 +130,23 @@ public:
 	struct BufferUpdateCommand
 	{
 		BufferUpdateCommand(const unsigned int p_bufferHandle,
-					  const int64_t p_offset,
-					  const int64_t p_size,
-					  const void *p_data = NULL,
-					  const BufferUpdateType p_updateType = BufferUpdate_Data,
-					  const BufferType p_bufferType = BufferType_Uniform) :
+			const int64_t p_offset,
+			const int64_t p_size,
+			const void *p_data = NULL,
+			const BufferUpdateType p_updateType = BufferUpdate_Data,
+			const BufferType p_bufferType = BufferType_Uniform,
+			const BufferUsageHint p_bufferUsageHint = BufferUsageHint_DynamicDraw) :
 			m_bufferHandle(p_bufferHandle),
 			m_offset(p_offset),
 			m_size(p_size),
 			m_data(p_data),
 			m_updateType(p_updateType),
-			m_bufferType(p_bufferType) { }
-
+			m_bufferType(p_bufferType),
+			m_bufferUsageHint(p_bufferUsageHint) { }
+		
 		const BufferUpdateType m_updateType;
 		const BufferType m_bufferType;
+		const BufferUsageHint m_bufferUsageHint;
 
 		const unsigned int m_bufferHandle;
 		const void *m_data;
@@ -448,7 +451,7 @@ protected:
 	inline void textureUniformUpdate(const unsigned int p_shaderHandle, const ShaderUniformUpdater &p_uniformUpdater, const UniformObjectData &p_objectData, const UniformFrameData &p_frameData)
 	{
 		// Check if the texture uniforms haven't been updated already
-		if(m_rendererState.m_lastTexUpdate != p_shaderHandle)
+		//if(m_rendererState.m_lastTexUpdate != p_shaderHandle)
 		{
 			// Declare uniform data
 			UniformData uniformData(p_objectData, p_frameData);
@@ -458,7 +461,7 @@ protected:
 			m_rendererState.m_lastTexUpdate = p_shaderHandle;
 		}
 	}
-	inline void frameUniformUpdate(const unsigned int p_shaderHandle, const ShaderUniformUpdater &p_uniformUpdater,const UniformObjectData &p_objectData, const UniformFrameData &p_frameData)
+	inline void frameUniformUpdate(const unsigned int p_shaderHandle, const ShaderUniformUpdater &p_uniformUpdater, const UniformObjectData &p_objectData, const UniformFrameData &p_frameData)
 	{
 		// Check if the frame uniforms haven't been updated already
 		//if(m_rendererState.m_lastFrameUpdate != p_shaderHandle)
@@ -486,10 +489,10 @@ protected:
 			m_rendererState.m_lastModelUpdate = p_shaderHandle;
 		}
 	}
-	inline void meshUniformUpdate(const unsigned int p_shaderHandle, const ShaderUniformUpdater &p_uniformUpdater,const UniformObjectData &p_objectData, const UniformFrameData &p_frameData)
+	inline void meshUniformUpdate(const unsigned int p_shaderHandle, const ShaderUniformUpdater &p_uniformUpdater, const UniformObjectData &p_objectData, const UniformFrameData &p_frameData)
 	{
 		// Check if the mesh uniforms haven't been updated already
-		if(m_rendererState.m_lastMeshUpdate != p_shaderHandle)
+		//if(m_rendererState.m_lastMeshUpdate != p_shaderHandle)
 		{
 			// Declare uniform data
 			UniformData uniformData(p_objectData, p_frameData);
@@ -554,7 +557,7 @@ protected:
 			glBufferData(p_command.m_bufferType,
 						 p_command.m_size,
 						 p_command.m_data,
-						 GL_DYNAMIC_DRAW);
+						 p_command.m_bufferUsageHint);
 			break;
 
 		case BufferUpdate_SubData:
@@ -562,7 +565,6 @@ protected:
 							p_command.m_offset,
 							p_command.m_size,
 							p_command.m_data);
-
 			break;
 		}
 	}

+ 55 - 2
Praxis3D/Source/RendererFrontend.cpp

@@ -1,4 +1,5 @@
 
+#include "AtmScatteringPass.h"
 #include "BlurPass.h"
 #include "GeometryPass.h"
 #include "LightingPass.h"
@@ -7,6 +8,7 @@
 #include "PostProcessPass.h"
 #include "ReflectionPass.h"
 #include "RendererFrontend.h"
+#include "SkyPass.h"
 
 RendererFrontend::~RendererFrontend()
 {
@@ -38,16 +40,28 @@ ErrorCode RendererFrontend::init()
 	// Create the render pass data struct
 	m_renderPassData = new RenderPassData();
 
+	m_renderingPasses.reserve(7);
+
 	// Add geometry rendering pass, if it was initialized successfuly
 	GeometryPass *geometryPass = new GeometryPass(*this);
 	if(geometryPass->init() == ErrorCode::Success)
 		m_renderingPasses.push_back(geometryPass);
+	
+	// Add SKY atmospheric scattering pass, if it was initialized successfuly
+	AtmScatteringPass *atmScatteringPass = new AtmScatteringPass(*this);
+	ErrorCode atmScatPassError = atmScatteringPass->init();
+	if(atmScatPassError == ErrorCode::Success)
+		m_renderingPasses.push_back(atmScatteringPass);
 
 	// Add lighting rendering pass, if it was initialized successfuly
 	LightingPass *lightingPass = new LightingPass(*this);
 	if(lightingPass->init() == ErrorCode::Success)
 		m_renderingPasses.push_back(lightingPass);
 
+	// Add GROUND atmospheric scattering pass, if it was initialized successfuly
+	if(atmScatPassError == ErrorCode::Success)
+		m_renderingPasses.push_back(atmScatteringPass);
+
 	// Add HDR mapping rendering pass, if it was initialized successfully
 	HdrMappingPass *hdrPass = new HdrMappingPass(*this);
 	if(hdrPass->init() == ErrorCode::Success)
@@ -68,10 +82,15 @@ ErrorCode RendererFrontend::init()
 	if(finalPass->init() == ErrorCode::Success)
 		m_renderingPasses.push_back(finalPass);
 
+	//if(atmScatteringPass->init() == ErrorCode::Success)
+	//	m_renderingPasses.push_back(atmScatteringPass);
+
 	updateProjectionMatrix();
 
 	passLoadCommandsToBackend();
 
+	glViewport(0, 0, m_frameData.m_screenSize.x, m_frameData.m_screenSize.y);
+
 	return returnCode;
 }
 
@@ -110,9 +129,40 @@ void RendererFrontend::renderFrame(const SceneObjects &p_sceneObjects, const flo
 	m_frameData.m_viewMatrix = p_sceneObjects.m_camera->getBaseObjectData().m_modelMat;
 	m_frameData.m_viewProjMatrix = m_frameData.m_projMatrix * p_sceneObjects.m_camera->getBaseObjectData().m_modelMat;
 	
+	// Convert the view matrix to row major for the atmospheric scattering shaders
+	m_frameData.m_transposeViewMatrix = Math::transpose(m_frameData.m_viewMatrix);
+
+	/*auto tempMatrix = m_frameData.m_viewMatrix;
+	
+	m_frameData.m_viewMatrix.m[0] = tempMatrix.m[0];
+	m_frameData.m_viewMatrix.m[1] = tempMatrix.m[4];
+	m_frameData.m_viewMatrix.m[2] = tempMatrix.m[8];
+	m_frameData.m_viewMatrix.m[3] = tempMatrix.m[12];
+	m_frameData.m_viewMatrix.m[4] = tempMatrix.m[1];
+	m_frameData.m_viewMatrix.m[5] = tempMatrix.m[5];
+	m_frameData.m_viewMatrix.m[6] = tempMatrix.m[9];
+	m_frameData.m_viewMatrix.m[7] = tempMatrix.m[13];
+	m_frameData.m_viewMatrix.m[8] = tempMatrix.m[2];
+	m_frameData.m_viewMatrix.m[9] = tempMatrix.m[6];
+	m_frameData.m_viewMatrix.m[10] = tempMatrix.m[10];
+	m_frameData.m_viewMatrix.m[11] = tempMatrix.m[14];
+	m_frameData.m_viewMatrix.m[12] = tempMatrix.m[3];
+	m_frameData.m_viewMatrix.m[13] = tempMatrix.m[7];
+	m_frameData.m_viewMatrix.m[14] = tempMatrix.m[11];
+	m_frameData.m_viewMatrix.m[15] = tempMatrix.m[15];*/
+
+	/*std::cout << "VIEW MATRIX:" << std::endl;
+	std::cout << m_frameData.m_viewMatrix.m[0] << " : " << m_frameData.m_viewMatrix.m[1] << " : " << m_frameData.m_viewMatrix.m[2] << " : " << m_frameData.m_viewMatrix.m[3] << std::endl;
+	std::cout << m_frameData.m_viewMatrix.m[4] << " : " << m_frameData.m_viewMatrix.m[5] << " : " << m_frameData.m_viewMatrix.m[6] << " : " << m_frameData.m_viewMatrix.m[7] << std::endl;
+	std::cout << m_frameData.m_viewMatrix.m[8] << " : " << m_frameData.m_viewMatrix.m[9] << " : " << m_frameData.m_viewMatrix.m[10] << " : " << m_frameData.m_viewMatrix.m[11] << std::endl;
+	std::cout << m_frameData.m_viewMatrix.m[12] << " : " << m_frameData.m_viewMatrix.m[13] << " : " << m_frameData.m_viewMatrix.m[14] << " : " << m_frameData.m_viewMatrix.m[15] << std::endl;
+	*/
 	// Set the camera position
 	m_frameData.m_cameraPosition = p_sceneObjects.m_camera->getVec3(nullptr, Systems::Changes::Spacial::Position);
 	
+	// Set the camera target vector
+	m_frameData.m_cameraTarget = p_sceneObjects.m_camera->getVec3(nullptr, Systems::Changes::Spacial::Rotation);
+
 	// Assign directional light values and also normalize its direction, so it's not neccessary to do it in a shader
 	m_frameData.m_dirLightColor = p_sceneObjects.m_directionalLight->m_color;
 	m_frameData.m_dirLightIntensity = p_sceneObjects.m_directionalLight->m_intensity;
@@ -125,10 +175,13 @@ void RendererFrontend::renderFrame(const SceneObjects &p_sceneObjects, const flo
 	
 	// Prepare the geometry buffer for a new frame and a geometry pass
 	m_backend.getGeometryBuffer()->initFrame();
-	//m_backend.getGeometryBuffer()->initGeometryPass();
-
+	
+	glDepthMask(GL_TRUE);
 	glEnable(GL_DEPTH_TEST);		// Enable depth testing, as this is much like a regular forward render pass
 	glClear(GL_DEPTH_BUFFER_BIT);	// Make sure to clear the depth buffer for the new frame
+
+	// Set depth test function
+	glDepthFunc(Config::rendererVar().depth_test_func);
 	//glDisable(GL_CULL_FACE);
 
 	//m_renderingPasses[0]->update(p_sceneObjects, p_deltaTime);

+ 28 - 19
Praxis3D/Source/RendererFrontend.h

@@ -9,6 +9,7 @@ struct RenderPassData;
 
 class RendererFrontend
 {
+	friend class AtmScatteringPass;
 	friend class BlurPass;
 	friend class GeometryPass;
 	friend class HdrMappingPass;
@@ -25,12 +26,14 @@ public:
 		{
 			m_data = 0;
 			m_size = 0;
+			m_updateSize = 0;
 			m_offset = 0;
 			m_handle = 0;
 			m_bindingIndex = 0;
 		}
 
 		int64_t m_size;
+		int64_t m_updateSize;
 		unsigned int m_offset;
 		unsigned int m_handle;
 		unsigned int m_bindingIndex;
@@ -169,24 +172,26 @@ protected:
 			for(int matType = 0; matType < MaterialType_NumOfTypes; matType++)
 				queueForLoading(p_objectData.m_materials[matType][i]);
 	}
-	inline void queueForLoading(ShaderBuffer &p_lightBuffer)
+	inline void queueForLoading(ShaderBuffer &p_shaderBuffer)
 	{
-		m_loadCommands.emplace_back(p_lightBuffer.m_handle,
-			p_lightBuffer.m_bufferType,
-			p_lightBuffer.m_bufferUsage,
-			p_lightBuffer.m_bindingIndex,
-			p_lightBuffer.m_size,
-			p_lightBuffer.m_data);
+		m_loadCommands.emplace_back(p_shaderBuffer.m_handle,
+			p_shaderBuffer.m_bufferType,
+			p_shaderBuffer.m_bufferUsage,
+			p_shaderBuffer.m_bindingIndex,
+			p_shaderBuffer.m_size,
+			p_shaderBuffer.m_data);
 	}
 
-	inline void queueForUpdate(ShaderBuffer &p_lightBuffer)
+	inline void queueForUpdate(ShaderBuffer &p_shaderBuffer)
 	{
-		m_bufferUpdateCommands.emplace_back(p_lightBuffer.m_handle,
-			p_lightBuffer.m_offset,
-			p_lightBuffer.m_size,
-			p_lightBuffer.m_data,
-			BufferUpdateType::BufferUpdate_SubData,
-			BufferType::BufferType_Uniform);
+		m_bufferUpdateCommands.emplace_back(p_shaderBuffer.m_handle,
+			p_shaderBuffer.m_offset,
+			p_shaderBuffer.m_updateSize,
+			p_shaderBuffer.m_data,
+			p_shaderBuffer.m_updateSize == p_shaderBuffer.m_size ?	// If update size is the same as buffer size
+			BufferUpdateType::BufferUpdate_Data :					// Update the whole buffer
+			BufferUpdateType::BufferUpdate_SubData,					// Otherwise update only part of the data
+			p_shaderBuffer.m_bufferType);
 	}
 
 	inline void passLoadCommandsToBackend()
@@ -225,11 +230,15 @@ protected:
 	// Recalculates the projection matrix
 	inline void updateProjectionMatrix()
 	{
-		m_frameData.m_projMatrix.perspective(Config::graphicsVar().fov, 
-											 m_frameData.m_screenSize.x, 
-											 m_frameData.m_screenSize.y, 
-											 Config::graphicsVar().z_near, 
-											 Config::graphicsVar().z_far);
+		m_frameData.m_projMatrix.perspective(	Config::graphicsVar().fov, 
+												m_frameData.m_screenSize.x, 
+												m_frameData.m_screenSize.y, 
+												Config::graphicsVar().z_near, 
+												Config::graphicsVar().z_far);
+
+		m_frameData.m_atmScatProjMatrix.perspectiveRadian(	Config::graphicsVar().fov,
+															m_frameData.m_screenSize.x,
+															m_frameData.m_screenSize.y);
 	}
 
 	// Renderer backend, serves as an interface layer to GPU

+ 6 - 0
Praxis3D/Source/RendererScene.cpp

@@ -354,6 +354,10 @@ void RendererScene::update(const float p_deltaTime)
 		}
 	}
 
+	// TODO make isActive a part of system object
+	if(m_directionalLight->active())
+		m_directionalLight->update(p_deltaTime);
+
 	//	 ___________________________
 	//	|							|
 	//	|		Point Lights		|
@@ -716,6 +720,8 @@ DirectionalLightObject *RendererScene::loadDirectionalLight(const PropertySet &p
 		}
 	}
 
+	m_directionalLight->init();
+
 	return m_directionalLight;
 }
 PointLightObject *RendererScene::loadPointLight(const PropertySet &p_properties)

+ 38 - 3
Praxis3D/Source/ScriptingScene.cpp

@@ -106,6 +106,9 @@ SystemObject *ScriptingScene::createObject(const PropertySet &p_properties)
 	case Properties::SolarTimeScript:
 		newObject = loadSolarTime(p_properties);
 		break;
+	case Properties::SunScript:
+		newObject = loadSun(p_properties);
+		break;
 	case Properties::WorldEditScript:
 		newObject = loadWorldEdit(p_properties);
 		break;
@@ -348,15 +351,24 @@ SolarTimeScript *ScriptingScene::loadSolarTime(const PropertySet & p_properties)
 		case Properties::Seconds:
 			solarTimeScript->setSeconds(p_properties[i].getFloat());
 			break;
+		case Properties::Year:
+			solarTimeScript->setYear(p_properties[i].getInt());
+			break;
+		case Properties::Month:
+			solarTimeScript->setMonth(p_properties[i].getInt());
+			break;
+		case Properties::Day:
+			solarTimeScript->setDay(p_properties[i].getInt());
+			break;
+		case Properties::TimeZone:
+			solarTimeScript->setTimeZone(p_properties[i].getInt());
+			break;
 		case Properties::Latitude:
 			solarTimeScript->setLatitude(p_properties[i].getFloat());
 			break;
 		case Properties::Longitude:
 			solarTimeScript->setLongitude(p_properties[i].getFloat());
 			break;
-		case Properties::DayOfYear:
-			solarTimeScript->setDayOfYear(p_properties[i].getInt());
-			break;
 		case Properties::TimeMultiplier:
 			solarTimeScript->setTimeMultiplier(p_properties[i].getFloat());
 			break;
@@ -371,6 +383,29 @@ SolarTimeScript *ScriptingScene::loadSolarTime(const PropertySet & p_properties)
 	return solarTimeScript;
 }
 
+SunScript *ScriptingScene::loadSun(const PropertySet & p_properties)
+{
+	SunScript *sunScript = new SunScript(this, p_properties.getPropertyByID(Properties::Name).getString());
+
+	// Load property data
+	for(decltype(p_properties.getNumProperties()) i = 0, size = p_properties.getNumProperties(); i < size; i++)
+	{
+		switch(p_properties[i].getPropertyID())
+		{
+		case Properties::Azimuth:
+			sunScript->setAzimuthAngle(p_properties[i].getFloat());
+			break;
+		case Properties::Zenith:
+			sunScript->setZenithAngle(p_properties[i].getFloat());
+			break;
+		}
+	}
+
+	m_scriptObjects.push_back(sunScript);
+
+	return sunScript;
+}
+
 WorldEditScript *ScriptingScene::loadWorldEdit(const PropertySet & p_properties)
 {
 	WorldEditScript *worldEditor = new WorldEditScript(this, p_properties.getPropertyByID(Properties::Name).getString(), m_sceneLoader);

+ 2 - 0
Praxis3D/Source/ScriptingScene.h

@@ -9,6 +9,7 @@
 #include "ScriptingTask.h"
 #include "SolarTimeScript.h"
 #include "System.h"
+#include "SunScript.h"
 #include "WorldEditObject.h"
 
 class ScriptingSystem;
@@ -56,6 +57,7 @@ private:
 	DebugMoveScript *loadDebugMove(const PropertySet &p_properties);
 	DebugRotateScript *loadDebugRotate(const PropertySet &p_properties);
 	SolarTimeScript *loadSolarTime(const PropertySet &p_properties);
+	SunScript *loadSun(const PropertySet &p_properties);
 	WorldEditScript *loadWorldEdit(const PropertySet &p_properties);
 
 	ScriptingTask *m_scriptingTask;

+ 45 - 33
Praxis3D/Source/ShaderLoader.cpp

@@ -42,6 +42,8 @@ ShaderLoader::ShaderProgram *ShaderLoader::load(const PropertySet &p_properties)
 		std::string shaderFilename[ShaderType_NumOfTypes];
 		std::string programName;
 
+		int numberOfShaders = 0;
+
 		// Iterate over all passed properties
 		for(decltype(p_properties.getNumProperties()) i = 0, size = p_properties.getNumProperties(); i < size; i++)
 		{
@@ -53,70 +55,80 @@ ShaderLoader::ShaderProgram *ShaderLoader::load(const PropertySet &p_properties)
 
 			case Properties::FragmentShader:
 				shaderFilename[ShaderType_Fragment] = p_properties[i].getString();
+				numberOfShaders++;
 				break;
 
 			case Properties::VertexShader:
 				shaderFilename[ShaderType_Vertex] = p_properties[i].getString();
+				numberOfShaders++;
 				break;
 
 			case Properties::GeometryShader:
 				shaderFilename[ShaderType_Geometry] = p_properties[i].getString();
+				numberOfShaders++;
 				break;
 
 			case Properties::TessControlShader:
 				shaderFilename[ShaderType_TessControl] = p_properties[i].getString();
+				numberOfShaders++;
 				break;
 
 			case Properties::TessEvaluationShader:
 				shaderFilename[ShaderType_TessEvaluation] = p_properties[i].getString();
+				numberOfShaders++;
 				break;
 			}
 		}
 
-		// If program name was not specified, combine all shader names into one program name
-		if(programName.empty())
+		if(numberOfShaders > 0)
 		{
-			// For every shader filename, if it's not empty, add it to the program name
-			for(unsigned int shaderType = 0; shaderType < ShaderType_NumOfTypes; shaderType++)
-				if(!shaderFilename[shaderType].empty())
-					programName += shaderFilename[shaderType] + ", ";
 
-			// Remove the last 2 characters from the filename (comma and space)
-			if(!programName.empty())
+			// If program name was not specified, combine all shader names into one program name
+			if(programName.empty())
 			{
-				programName.pop_back();
-				programName.pop_back();
+				// For every shader filename, if it's not empty, add it to the program name
+				for(unsigned int shaderType = 0; shaderType < ShaderType_NumOfTypes; shaderType++)
+					if(!shaderFilename[shaderType].empty())
+						programName += shaderFilename[shaderType] + ", ";
+
+				// Remove the last 2 characters from the filename (comma and space)
+				if(!programName.empty())
+				{
+					programName.pop_back();
+					programName.pop_back();
+				}
 			}
-		}
 
-		// Generate hash key from program's name
-		unsigned int programHashkey = Utilities::getHashKey(programName);
+			// Generate hash key from program's name
+			unsigned int programHashkey = Utilities::getHashKey(programName);
 
-		// Make sure calls from other threads are locked, while current call is in progress
-		// This is needed to as the object that is being requested might be currently loading /
-		// being added to the pool. Mutex prevents duplicates being loaded, and same data being changed.
-		SpinWait::Lock lock(m_mutex);
+			// Make sure calls from other threads are locked, while current call is in progress
+			// This is needed to as the object that is being requested might be currently loading /
+			// being added to the pool. Mutex prevents duplicates being loaded, and same data being changed.
+			SpinWait::Lock lock(m_mutex);
 
-		// Iterate over all shader programs and match hash key and name; if match is found, return it
-		for(decltype(m_shaderPrograms.size()) i = 0, size = m_shaderPrograms.size(); i < size; i++)
-			if(m_shaderPrograms[i].m_filenameHash == programHashkey)
-				if(m_shaderPrograms[i].m_combinedFilename == programName)
-					return &m_shaderPrograms[i];
+			// Iterate over all shader programs and match hash key and name; if match is found, return it
+			for(decltype(m_shaderPrograms.size()) i = 0, size = m_shaderPrograms.size(); i < size; i++)
+				if(m_shaderPrograms[i].m_filenameHash == programHashkey)
+					if(m_shaderPrograms[i].m_combinedFilename == programName)
+						return &m_shaderPrograms[i];
 
-		// Add the new program to the array
-		m_shaderPrograms.push_back(ShaderProgram(programName, programHashkey));
-		ShaderProgram *newProgram = &m_shaderPrograms[m_shaderPrograms.size() - 1];
+			// Add the new program to the array
+			m_shaderPrograms.push_back(ShaderProgram(programName, programHashkey));
+			ShaderProgram *newProgram = &m_shaderPrograms[m_shaderPrograms.size() - 1];
 
-		// Iterate over shader types
-		for(unsigned int shaderType = 0; shaderType < ShaderType_NumOfTypes; shaderType++)
-			// If shader filename is valid
-			if(!shaderFilename[shaderType].empty())
-				newProgram->addShader(static_cast<ShaderType>(shaderType), shaderFilename[shaderType]);
+			// Iterate over shader types
+			for(unsigned int shaderType = 0; shaderType < ShaderType_NumOfTypes; shaderType++)
+				// If shader filename is valid
+				if(!shaderFilename[shaderType].empty())
+					newProgram->addShader(static_cast<ShaderType>(shaderType), shaderFilename[shaderType]);
 
-		// Create a uniform updater for the new shader
-		newProgram->m_uniformUpdater = new ShaderUniformUpdater(*newProgram);
 
-		return newProgram;
+			// Create a uniform updater for the new shader
+			newProgram->m_uniformUpdater = new ShaderUniformUpdater(*newProgram);
+
+			return newProgram;
+		}
 	}
 
 	return &m_defaultProgram;

+ 11 - 0
Praxis3D/Source/ShaderUniformUpdater.cpp

@@ -69,6 +69,12 @@ ErrorCode ShaderUniformUpdater::generateTextureUpdateList()
 	uniformList.push_back(new NormalTextureUniform(m_shaderHandle));
 	uniformList.push_back(new EmissiveTextureUniform(m_shaderHandle));
 	uniformList.push_back(new CombinedTextureUniform(m_shaderHandle));
+
+	// Atmoshperic scattering textures
+	uniformList.push_back(new AtmIrradianceTextureUniform(m_shaderHandle));
+	uniformList.push_back(new AtmScatteringTextureUniform(m_shaderHandle));
+	uniformList.push_back(new AtmSingleMieTextureUniform(m_shaderHandle));
+	uniformList.push_back(new AtmTransmittanceTextureUniform(m_shaderHandle));
 		
 	// Go through each uniform and check if it is valid
 	// If it is, add it to the update list, if not, delete it
@@ -88,9 +94,11 @@ ErrorCode ShaderUniformUpdater::generatePerFrameList()
 	std::vector<BaseUniform*> uniformList;
 
 	// View, Projection matrices
+	uniformList.push_back(new AtmScatProjMatUniform(m_shaderHandle));
 	uniformList.push_back(new ViewMatUniform(m_shaderHandle));
 	uniformList.push_back(new ProjectionMatUniform(m_shaderHandle));
 	uniformList.push_back(new ViewProjectionMatUniform(m_shaderHandle));
+	uniformList.push_back(new TransposeViewMatUniform(m_shaderHandle));
 	uniformList.push_back(new DirShadowMapMVPUniform(m_shaderHandle));
 	uniformList.push_back(new DirShadowMapBiasMVPUniform(m_shaderHandle));
 
@@ -187,6 +195,9 @@ ErrorCode ShaderUniformUpdater::generateUniformBlockList()
 	uniformBlockList.push_back(new PointLightBufferUniform(m_shaderHandle));
 	uniformBlockList.push_back(new SpotLightBufferUniform(m_shaderHandle));
 
+	// Atmospheric scattering buffer
+	uniformBlockList.push_back(new AtmScatParametersUniform(m_shaderHandle));
+	
 	// Go through each uniform and check if it is valid
 	// If it is, add it to the update list, if not, delete it
 	for(decltype(uniformBlockList.size()) i = 0, size = uniformBlockList.size(); i < size; i++)

+ 91 - 5
Praxis3D/Source/ShaderUniforms.h

@@ -105,6 +105,18 @@ protected:
 	const unsigned int m_shaderHandle;
 };
 
+class AtmScatProjMatUniform : public BaseUniform
+{
+public:
+	AtmScatProjMatUniform(unsigned int p_shaderHandle) : BaseUniform(Config::shaderVar().atmScatProjMatUniform, p_shaderHandle)
+	{
+	}
+
+	void update(const UniformData &p_uniformData)
+	{
+		glUniformMatrix4fv(m_uniformHandle, 1, GL_TRUE, &p_uniformData.m_frameData.m_atmScatProjMatrix.m[0]);
+	}
+};
 class ModelMatUniform : public BaseUniform
 {
 public:
@@ -172,6 +184,18 @@ public:
 		//glUniformMatrix4fv(m_uniformHandle, 1, GL_FALSE, &p_uniformData.m_objectData.m_modelViewProjMatrix.m[0]);
 	}
 };
+class TransposeViewMatUniform : public BaseUniform
+{
+public:
+	TransposeViewMatUniform(unsigned int p_shaderHandle) : BaseUniform(Config::shaderVar().transposeViewMatUniform, p_shaderHandle)
+	{
+	}
+
+	void update(const UniformData &p_uniformData)
+	{
+		glUniformMatrix4fv(m_uniformHandle, 1, GL_FALSE, &p_uniformData.m_frameData.m_transposeViewMatrix.m[0]);
+	}
+};
 
 class ScreenSizeUniform : public BaseUniform
 {
@@ -526,15 +550,17 @@ public:
 					p_uniformData.m_frameData.m_cameraPosition.z);
 	}
 };
-/* Unused */ class CameraTargetVecUniform : public BaseUniform
+class CameraTargetVecUniform : public BaseUniform
 {
 public:
 	CameraTargetVecUniform(unsigned int p_shaderHandle) : BaseUniform(Config::shaderVar().cameraTargetVecUniform, p_shaderHandle) { }
 
 	void update(const UniformData &p_uniformData)
 	{
-		//auto &cameraPosVec = p_uniformData.getCameraTarget();
-		//glUniform3f(m_uniformHandle, cameraPosVec.x, cameraPosVec.y, cameraPosVec.z);
+		glUniform3f(m_uniformHandle,
+			p_uniformData.m_frameData.m_cameraTarget.x,
+			p_uniformData.m_frameData.m_cameraTarget.y,
+			p_uniformData.m_frameData.m_cameraTarget.z);
 	}
 };
 /* Unused */ class CameraUpVecUniform : public BaseUniform // Unused
@@ -736,6 +762,55 @@ public:
 	}
 };
 
+class AtmIrradianceTextureUniform : public BaseUniform
+{
+public:
+	AtmIrradianceTextureUniform(unsigned int p_shaderHandle) : BaseUniform(Config::shaderVar().atmIrradianceTextureUniform, p_shaderHandle)
+	{
+	}
+
+	void update(const UniformData &p_uniformData)
+	{
+		glUniform1i(m_uniformHandle, AtmScatteringTextureType::AtmScatteringTextureType_Irradiance);
+	}
+};
+class AtmScatteringTextureUniform : public BaseUniform
+{
+public:
+	AtmScatteringTextureUniform(unsigned int p_shaderHandle) : BaseUniform(Config::shaderVar().atmScatteringTextureUniform, p_shaderHandle)
+	{
+	}
+
+	void update(const UniformData &p_uniformData)
+	{
+		glUniform1i(m_uniformHandle, AtmScatteringTextureType::AtmScatteringTextureType_Scattering);
+	}
+};
+class AtmSingleMieTextureUniform : public BaseUniform
+{
+public:
+	AtmSingleMieTextureUniform(unsigned int p_shaderHandle) : BaseUniform(Config::shaderVar().atmSingleMieScatTextureUniform, p_shaderHandle)
+	{
+	}
+
+	void update(const UniformData &p_uniformData)
+	{
+		glUniform1i(m_uniformHandle, AtmScatteringTextureType::AtmScatteringTextureType_SingleMie);
+	}
+};
+class AtmTransmittanceTextureUniform : public BaseUniform
+{
+public:
+	AtmTransmittanceTextureUniform(unsigned int p_shaderHandle) : BaseUniform(Config::shaderVar().atmTransmittanceTextureUniform, p_shaderHandle)
+	{
+	}
+
+	void update(const UniformData &p_uniformData)
+	{
+		glUniform1i(m_uniformHandle, AtmScatteringTextureType::AtmScatteringTextureType_Transmittance);
+	}
+};
+
 class DynamicEnvironmentMapUniform : public BaseUniform
 {
 public:
@@ -871,7 +946,7 @@ public:
 
 	void update(const UniformData &p_uniformData)
 	{
-		updateBlockBinding(LightBufferBinding_PointLight);
+		updateBlockBinding(UniformBufferBinding_PointLights);
 	}
 };
 class SpotLightBufferUniform : public BaseUniformBlock
@@ -881,9 +956,20 @@ public:
 
 	void update(const UniformData &p_uniformData)
 	{
-		updateBlockBinding(LightBufferBinding_SpotLight);
+		updateBlockBinding(UniformBufferBinding_SpotLights);
 	}
 };
+class AtmScatParametersUniform : public BaseUniformBlock
+{
+public:
+	AtmScatParametersUniform(unsigned int p_shaderHandle) : BaseUniformBlock(Config::shaderVar().atmScatParamBuffer, p_shaderHandle) { }
+
+	void update(const UniformData &p_uniformData)
+	{
+		updateBlockBinding(UniformBufferBinding_AtmScatParam);
+	}
+};
+
 class HDRShaderStorageBuffer : public BaseShaderStorageBlock
 {
 public:

+ 16 - 14
Praxis3D/Source/SkyPass.h

@@ -16,12 +16,12 @@ public:
 		ErrorCode returnError = ErrorCode::Success;
 		ErrorCode shaderError;
 
-		m_name = "Blur Rendering Pass";
+		m_name = "Sky Rendering Pass";
 
 		// Create a property-set used to load blur vertical shaders
 		PropertySet skyPassShaderShaderProperties(Properties::Shaders);
-		skyPassShaderShaderProperties.addProperty(Properties::VertexShader, Config::rendererVar().gaussian_blur_vertical_vert_shader);
-		skyPassShaderShaderProperties.addProperty(Properties::FragmentShader, Config::rendererVar().gaussian_blur_vertical_frag_shader);
+		skyPassShaderShaderProperties.addProperty(Properties::VertexShader, Config::rendererVar().atm_scattering_sky_vert_shader);
+		skyPassShaderShaderProperties.addProperty(Properties::FragmentShader, Config::rendererVar().atm_scattering_sky_frag_shader);
 
 		// Create shaders
 		m_skyPassShader = Loaders::shader().load(skyPassShaderShaderProperties);
@@ -30,7 +30,7 @@ public:
 		//		|	  LOAD SKY PASS SHADER		|
 		//		|_______________________________|
 		shaderError = m_skyPassShader->loadToMemory();		// Load shader to memory
-		if(shaderError == ErrorCode::Success)					// Check if shader was loaded successfully
+		if(shaderError == ErrorCode::Success)				// Check if shader was loaded successfully
 			m_renderer.queueForLoading(*m_skyPassShader);	// Queue the shader to be loaded to GPU
 		else
 			returnError = shaderError;
@@ -39,21 +39,23 @@ public:
 		return returnError;
 	}
 
-	void update(const SceneObjects &p_sceneObjects, const float p_deltaTime)
+	void update(RenderPassData &p_renderPassData, const SceneObjects &p_sceneObjects, const float p_deltaTime)
 	{
-		glDisable(GL_DEPTH_TEST);
+		//glDisable(GL_DEPTH_TEST);
+		glEnable(GL_DEPTH_TEST);
+		//glDepthFunc(GL_LEQUAL);
+		//glDepthMask(GL_FALSE);
 
-		// Set the default framebuffer to be drawn to
-		//m_renderer.m_backend.getGeometryBuffer()->bindFramebufferForWriting(GeometryBuffer::FramebufferDefault);
+		// Bind input color texture for reading so it can be accessed in the shaders
+		m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(p_renderPassData.getColorInputMap(), GeometryBuffer::GBufferInputTexture);
 
-		// Bind final texture for reading so it can be accessed in the shaders
-		m_renderer.m_backend.getGeometryBuffer()->bindBufferForReading(GeometryBuffer::GBufferFinal, GeometryBuffer::GBufferFinal);
+		// Bind output color texture for writing to, so it can be used as an intermediate buffer between blur passes
+		m_renderer.m_backend.getGeometryBuffer()->bindBufferForWriting(p_renderPassData.getColorOutputMap());
 
-		// Bind intermediate texture for writing to, so HDR mapping is outputed to it
-		m_renderer.m_backend.getGeometryBuffer()->bindBufferForWriting(GeometryBuffer::GBufferIntermediate);
-
-		// Perform HDR mapping. Queue and render a full screen quad using an HDR pass shader
+		// Perform various visual effects in the post process shader
 		m_renderer.queueForDrawing(m_skyPassShader->getShaderHandle(), m_skyPassShader->getUniformUpdater(), p_sceneObjects.m_camera->getBaseObjectData().m_modelMat);
+		
+		// Pass the draw command so it is executed
 		m_renderer.passScreenSpaceDrawCommandsToBackend();
 	}
 

+ 250 - 32
Praxis3D/Source/SolarTimeScript.h

@@ -2,11 +2,16 @@
 
 #include "BaseScriptObject.h"
 #include "ClockLocator.h"
+#include "Math.h"
 #include "RendererScene.h"
 #include "KeyCommand.h"
 
 // Amount of seconds contained in one hour
-#define SECONDS_IN_HOUR 82800.0f
+#define SECONDS_IN_HOUR		82800.0f
+
+// Declaration of some constants
+#define dEarthMeanRadius     6371.01	// In km
+#define dAstronomicalUnit    149597890	// In km
 
 // Provides bare user interface functionality, like button presses to toggle features (for debugging)
 class SolarTimeScript : public BaseScriptObject
@@ -25,6 +30,14 @@ public:
 		m_offsetPosition = 10.0f;
 		m_latitude = 0.0f;
 		m_longitude = 0.0f;
+		m_timeZone = 0;
+		m_year = 2018;
+		m_month = 5;
+		m_day = 14;
+
+		m_azimuthAngle = 0.0;
+		m_zenithAngle = 0.0;
+		m_elevationAngle = 0.0;
 	}
 	~SolarTimeScript()
 	{
@@ -34,7 +47,6 @@ public:
 
 	virtual ErrorCode init()
 	{
-
 		return ErrorCode::Success;
 	}
 
@@ -65,37 +77,120 @@ public:
 
 	virtual void update(const float p_deltaTime)
 	{
-		/* Not used at this time
-		// _____________________________________________________________________________________
+		// Add delta seconds to elapsed time
+		m_elaspedSeconds += ClockLocator::get().getDeltaSecondsF() * m_timeMultiplier;
+
 		// Calculate hours, minutes and seconds
 		m_hours = (int)(m_elaspedSeconds / 3600.0f);
 		m_minutes = (int)((m_elaspedSeconds - (m_hours * 3600.0f)) / 60.0f);
 		m_seconds = (m_elaspedSeconds - (m_hours * 3600.0f) - (m_minutes * 60.0f));
 
-		// Reset the hours when it reaches 24
+		// Increment date counters and reset the hours when it reaches 24
 		if(m_hours > 23)
 		{
 			m_elaspedSeconds -= m_hours * 3600.0f;
 			m_hours = 0;
+			m_day++;
+
+			if(m_day > 28)
+			{
+				switch(m_day)
+				{
+				case 29:
+
+					// If the month has 28 days (February)
+					if(m_month == 2)
+					{
+						m_month++;
+						m_day = 1;
+					}
+
+					break;
+				case 31:
+
+					// If the month has 30 days
+					if(m_month == 4 || m_month == 6 || m_month == 9 || m_month == 11)
+					{
+						m_month++;
+						m_day = 1;
+					}
+
+					break;
+				case 32:
+
+					// If the month has 31 days
+					if(m_month == 1 || m_month == 3 || m_month == 5 || m_month == 7 || m_month == 8 || m_month == 10 || m_month == 12)
+					{
+						m_month++;
+						m_day = 1;
+					}
+
+					break;
+				}
+			}
 		}
-		// _____________________________________________________________________________________*/
-
-		// Add delta seconds to elapsed time
-		m_elaspedSeconds += ClockLocator::get().getDeltaSecondsF() * m_timeMultiplier;
-
-		// Reset the hours when it reaches 24
-		if(m_elaspedSeconds > SECONDS_IN_HOUR)
-			m_elaspedSeconds -= SECONDS_IN_HOUR;
-
-		// Calculate a crude approximation of sun angle based on time of day
-		float angle = (m_elaspedSeconds / SECONDS_IN_HOUR) * (float)(2.0*PI) + (float)(1.5*PI);
-
-		// Set the sun direction
-		m_sunDirection = Math::normalize(Math::Vec3f(cosf(angle), sinf(angle), sinf(angle)));
-
+				
+		// Calculate sun position
+		calcSunPosition();
+
+		// Transform angle values into radian
+		m_azimuthAngle = Math::toRadian(m_azimuthAngle);
+		m_zenithAngle = Math::toRadian(m_zenithAngle);
+		m_elevationAngle = Math::toRadian(m_elevationAngle);
+		
+		/*
+		m_sunDirection = Math::Vec3f(
+			cos(m_azimuthAngle) * sin(m_zenithAngle),
+			cos(m_zenithAngle),
+			sin(m_azimuthAngle) * sin(m_zenithAngle));
+
+		m_sunDirection = Math::Vec3f(
+			sin(m_azimuthAngle) * sin(altitude),
+			cos(m_azimuthAngle) * sin(altitude),
+			sin(altitude));
+
+		m_sunDirection = Math::Vec3f(
+			sin(m_zenithAngle) * cos(m_azimuthAngle),
+			sin(m_zenithAngle) * sin(m_azimuthAngle),
+			cos(m_zenithAngle));
+
+		m_sunDirection.z = cos(m_zenithAngle) * cos(m_azimuthAngle) * radius;
+		m_sunDirection.x = sin(m_zenithAngle) * cos(m_azimuthAngle) * radius;
+		m_sunDirection.y = sin(m_azimuthAngle) * radius;
+
+		m_sunDirection.x = cos(m_zenithAngle) * sin(m_azimuthAngle) * radius;
+		m_sunDirection.y = sin(m_zenithAngle) * sin(m_azimuthAngle) * radius;
+		m_sunDirection.z = cos(m_azimuthAngle) * radius;
+
+		m_sunDirection.x = sin(m_azimuthAngle) * cos(m_zenithAngle) * radius;
+		m_sunDirection.y = sin(m_azimuthAngle) * sin(m_zenithAngle) * radius;
+		m_sunDirection.z = cos(m_azimuthAngle) * radius;
+		
+		m_sunDirection.x = sin(m_azimuthAngle) * cos(altitude);
+		m_sunDirection.y = cos(m_azimuthAngle) * cos(altitude);
+		m_sunDirection.z = sin(altitude);
+		
+		m_sunDirection.x = 0.0f;// sin(m_azimuthAngle) * cos(m_elevationAngle);
+		m_sunDirection.y = cos(m_azimuthAngle);// *cos(m_elevationAngle);
+		m_sunDirection.z = sin(m_elevationAngle);
+		
+		m_sunDirection.x = sin(m_azimuthAngle);
+		m_sunDirection.y = cos(m_azimuthAngle) * cos(m_elevationAngle);
+		m_sunDirection.z = sin(m_elevationAngle);*/
+		
+		// Translate sun spherical coordinates into cartesian direction vector
+		m_sunDirection.x = static_cast<float>(sin(m_azimuthAngle) * cos(m_elevationAngle));
+		m_sunDirection.y = static_cast<float>(sin(m_elevationAngle));
+		m_sunDirection.z = static_cast<float>(cos(m_azimuthAngle) * cos(m_elevationAngle));
+		
+		// Normalize sun direction
+		m_sunDirection.normalize();
+
+		// Calculate sun position in the sky
 		m_sunPosition = (-m_sunDirection * m_offsetPosition) + m_originPosition;
 
-		//postChanges(Systems::Changes::Spacial::Position | Systems::Changes::Spacial::Rotation);
+		// Notify observers of the changes
+		postChanges(Systems::Changes::Spacial::Position | Systems::Changes::Spacial::Rotation);
 	}
 
 	virtual BitMask getDesiredSystemChanges() { return Systems::Changes::Spacial::All; }
@@ -137,12 +232,28 @@ public:
 	}
 
 	// Setters
-	const inline void setHours(const int p_hours)							{ m_hours = p_hours;					}
-	const inline void setMinutes(const int p_minutes)						{ m_minutes = p_minutes;				}
-	const inline void setSeconds(const float p_seconds)						{ m_seconds = p_seconds;				}
+	const inline void setHours(const int p_hours)
+	{
+		m_hours = p_hours; 
+		m_elaspedSeconds += p_hours * 3600;
+	}
+	const inline void setMinutes(const int p_minutes)
+	{
+		m_minutes = p_minutes; 
+		m_elaspedSeconds += p_minutes * 60;
+	}
+	const inline void setSeconds(const float p_seconds)
+	{
+		m_seconds = p_seconds; 
+		m_elaspedSeconds += p_seconds;
+	}
 	const inline void setLatitude(const float p_latitude)					{ m_latitude = p_latitude;				}
 	const inline void setLongitude(const float p_longitude)					{ m_longitude = p_longitude;			}
 	const inline void setDayOfYear(const int p_dayOfYear)					{ m_dayOfYear = p_dayOfYear;			}
+	const inline void setYear(const int p_year)								{ m_year = p_year;						}
+	const inline void setMonth(const int p_month)							{ m_month = p_month;					}
+	const inline void setDay(const int p_day)								{ m_day = p_day;						}
+	const inline void setTimeZone(const int p_timeZone)						{ m_timeZone = p_timeZone;				}
 	const inline void setTimeMultiplier(const float p_timeMultiplier)		{ m_timeMultiplier = p_timeMultiplier;	}
 	const inline void setOffsetPosition(const float p_offSetPosition)		{ m_offsetPosition = p_offSetPosition;	}
 	const inline void setOriginPosition(const Math::Vec3f p_originPosition) { m_originPosition = p_originPosition;	}
@@ -158,8 +269,109 @@ public:
 		m_forwardKey.unbindAll();
 		m_forwardKey.bind(p_string);
 	}
+	
+	// PSA Sun position algorithm
+	void calcSunPosition()
+	{
+		// Main variables
+		double dElapsedJulianDays;
+		double dDecimalHours;
+		double dEclipticLongitude;
+		double dEclipticObliquity;
+		double dRightAscension;
+		double dDeclination;
+
+		// Auxiliary variables
+		double dY;
+		double dX;
+
+		// Calculate difference in days between the current Julian Day 
+		// and JD 2451545.0, which is noon 1 January 2000 Universal Time
+		{
+			double dJulianDate;
+			long int liAux1;
+			long int liAux2;
+
+			// Calculate time of the day in UT decimal hours
+			dDecimalHours = (m_hours + (m_minutes + m_seconds / 60.0) / 60.0) - m_timeZone;
+
+			// Calculate current Julian Day
+			liAux1 = (m_month - 14) / 12;
+			liAux2 = (1461 * (m_year + 4800 + liAux1)) / 4 
+				+ (367 * (m_month - 2 - 12 * liAux1)) / 12 
+				- (3 * ((m_year + 4900 + liAux1) / 100)) / 4 
+				+ m_day - 32075;
+
+			dJulianDate = (double)(liAux2)-0.5 + dDecimalHours / 24.0;
+
+			// Calculate difference between current Julian Day and JD 2451545.0 
+			dElapsedJulianDays = dJulianDate - 2451545.0;
+		}
+
+		// Calculate ecliptic coordinates (ecliptic longitude and obliquity of the 
+		// ecliptic in radians but without limiting the angle to be less than 2*Pi 
+		// (i.e., the result may be greater than 2*Pi)
+		{
+			double dMeanLongitude;
+			double dMeanAnomaly;
+			double dOmega;
+			dOmega = 2.1429 - 0.0010394594*dElapsedJulianDays;
+			dMeanLongitude = 4.8950630 + 0.017202791698*dElapsedJulianDays; // Radians
+			dMeanAnomaly = 6.2400600 + 0.0172019699*dElapsedJulianDays;
+			dEclipticLongitude = dMeanLongitude 
+				+ 0.03341607 * sin(dMeanAnomaly) 
+				+ 0.00034894 * sin(2 * dMeanAnomaly) 
+				- 0.0001134 - 0.0000203 * sin(dOmega);
+			dEclipticObliquity = 0.4090928 - 6.2140e-9 * dElapsedJulianDays + 0.0000396 * cos(dOmega);
+		}
 
-	inline Math::Vec2f calcSunPosition() const
+		// Calculate celestial coordinates ( right ascension and declination ) in radians 
+		// but without limiting the angle to be less than 2*Pi (i.e., the result may be 
+		// greater than 2*Pi)
+		{
+			double dSin_EclipticLongitude;
+			dSin_EclipticLongitude = sin(dEclipticLongitude);
+			dY = cos(dEclipticObliquity) * dSin_EclipticLongitude;
+			dX = cos(dEclipticLongitude);
+			dRightAscension = atan2(dY, dX);
+			if(dRightAscension < 0.0) 
+				dRightAscension = dRightAscension + TWOPI;
+			dDeclination = asin(sin(dEclipticObliquity) * dSin_EclipticLongitude);
+		}
+
+		// Calculate local coordinates ( azimuth and zenith angle ) in degrees
+		{
+			double dGreenwichMeanSiderealTime;
+			double dLocalMeanSiderealTime;
+			double dLatitudeInRadians;
+			double dHourAngle;
+			double dCos_Latitude;
+			double dSin_Latitude;
+			double dCos_HourAngle;
+			double dParallax;
+			dGreenwichMeanSiderealTime = 6.6974243242 + 0.0657098283 * dElapsedJulianDays + dDecimalHours;
+			dLocalMeanSiderealTime = (dGreenwichMeanSiderealTime * 15 + m_longitude) * RAD;
+			dHourAngle = dLocalMeanSiderealTime - dRightAscension;
+			dLatitudeInRadians = m_latitude * RAD;
+			dCos_Latitude = cos(dLatitudeInRadians);
+			dSin_Latitude = sin(dLatitudeInRadians);
+			dCos_HourAngle = cos(dHourAngle);
+			m_zenithAngle = (acos(dCos_Latitude * dCos_HourAngle * cos(dDeclination) + sin(dDeclination) * dSin_Latitude));
+			dY = -sin(dHourAngle);
+			dX = tan(dDeclination)*dCos_Latitude - dSin_Latitude * dCos_HourAngle;
+			m_azimuthAngle = atan2(dY, dX);
+			if(m_azimuthAngle < 0.0)
+				m_azimuthAngle = m_azimuthAngle + TWOPI;
+			m_azimuthAngle = m_azimuthAngle / RAD;
+
+			// Parallax Correction
+			dParallax = (dEarthMeanRadius / dAstronomicalUnit) * sin(m_zenithAngle);
+			m_zenithAngle = (m_zenithAngle + dParallax) / RAD;
+			m_elevationAngle = 90.0f - m_zenithAngle;
+		}
+	}
+
+	void calcSunPositionOld()
 	{
 		// Get the fractional year in radians
 		float fracYear = (2.0f * (float)PI / 365.0f) * (m_dayOfYear - 1 + (m_hours - 12.0f) / 24.0f);
@@ -187,18 +399,20 @@ public:
 		float solarHour = solarTime / 4.0f - 180.0f;
 
 		// Calculate solar zenith and azimuth angles
-		float solarZenith = sinf(m_latitude) * sinf(declinAngle) + cosf(m_latitude) * cosf(declinAngle) * cosf(solarHour);
-		float solarAzimuth = -(sinf(m_latitude) * cosf(solarZenith) - sinf(declinAngle)) / (cosf(m_latitude) * sinf(solarZenith));
-
-		return Math::Vec2f(solarZenith, solarAzimuth);
+		m_zenithAngle = (double)(sinf(m_latitude) * sinf(declinAngle) + cosf(m_latitude) * cosf(declinAngle) * cosf(solarHour));
+		m_azimuthAngle = (double)(-(sinf(m_latitude) * cosf((float)m_zenithAngle) - sinf(declinAngle)) / (cosf(m_latitude) * sinf((float)m_zenithAngle)));
 	}
-
+	
 private:
-	KeyCommand  m_forwardKey;
+	KeyCommand m_forwardKey;
 
 	int	m_dayOfYear,
 		m_hours,
-		m_minutes;
+		m_minutes,
+		m_year,
+		m_month,
+		m_day,
+		m_timeZone;
 
 	float	m_seconds,
 			m_elaspedSeconds,
@@ -206,6 +420,10 @@ private:
 			m_latitude,
 			m_longitude,
 			m_offsetPosition;	// Used as an offset from origin when calculating celestial bodies positions
+	
+	double	m_azimuthAngle,
+			m_zenithAngle,
+			m_elevationAngle;
 
 	Math::Vec3f	m_originPosition,
 				m_sunPosition,

+ 83 - 0
Praxis3D/Source/SunScript.h

@@ -0,0 +1,83 @@
+#pragma once
+
+#include "BaseScriptObject.h"
+
+// Provides bare object rotation functionality for debugging purposes (like making a spotlight rotate)
+class SunScript : public BaseScriptObject
+{
+	friend class ScriptingScene;
+public:
+	SunScript(SystemScene *p_systemScene, std::string p_name)
+		: BaseScriptObject(p_systemScene, p_name, Properties::SunScript)
+	{
+		m_azimuthAngle = 1.0f;
+		m_zenithAngle = 1.0f;
+	}
+	~SunScript()
+	{
+	}
+
+
+	virtual ErrorCode init()
+	{
+		return ErrorCode::Success;
+	}
+
+	void loadToMemory()
+	{
+
+	}
+
+	// Exports all the data of the object as a PropertySet
+	virtual PropertySet exportObject()
+	{
+		// Create the root property set
+		PropertySet propertySet(Properties::ArrayEntry);
+
+		// Add variables
+		propertySet.addProperty(Properties::Type, Properties::SunScript);
+		propertySet.addProperty(Properties::Name, m_name);
+		propertySet.addProperty(Properties::Azimuth, m_azimuthAngle);
+		propertySet.addProperty(Properties::Zenith, m_zenithAngle);
+
+		return propertySet;
+	}
+
+	virtual void update(const float p_deltaTime)
+	{
+		m_rotation = Math::Vec3f(
+			cos(m_azimuthAngle) * sin(m_zenithAngle),
+			cos(m_zenithAngle),
+			sin(m_azimuthAngle) * sin(m_zenithAngle));
+
+		postChanges(Systems::Changes::Spacial::Rotation);
+	}
+
+	const virtual Math::Vec3f &getVec3(const Observer *p_observer, BitMask p_changedBits) const
+	{
+		switch(p_changedBits)
+		{
+		case Systems::Changes::Spacial::Rotation:
+			return m_rotation;
+			break;
+		}
+
+		return ObservedSubject::getVec3(p_observer, p_changedBits);
+	}
+
+	// Setters
+	inline void setAzimuthAngle(float p_azimuthAngle)
+	{
+		m_azimuthAngle = p_azimuthAngle;
+	}
+	inline void setZenithAngle(float p_zenithAngle)
+	{
+		m_zenithAngle = p_zenithAngle;
+	}
+
+protected:
+	float	m_azimuthAngle,
+			m_zenithAngle;
+
+	Math::Vec3f m_rotation;
+};

+ 6 - 1
Praxis3D/Source/UniformData.h

@@ -17,10 +17,15 @@ struct UniformFrameData
 	// Camera's position in the scene
 	Math::Vec3f m_cameraPosition;
 
+	// Camera's target vector in the scene
+	Math::Vec3f m_cameraTarget;
+
 	// Matrices that can only change once per frame
 	Math::Mat4f m_projMatrix,
 				m_viewMatrix,
-				m_viewProjMatrix;
+				m_viewProjMatrix,
+				m_atmScatProjMatrix,
+				m_transposeViewMatrix;
 
 	// Parameters of direction light, since there can be only one of it
 	Math::Vec3f m_dirLightColor,