diff --git a/Editor/WeatherWindow.cpp b/Editor/WeatherWindow.cpp
index ba1c70a10..ea48a13f3 100644
--- a/Editor/WeatherWindow.cpp
+++ b/Editor/WeatherWindow.cpp
@@ -115,9 +115,27 @@ void WeatherWindow::Create(EditorComponent* editor)
simpleskyCheckBox.OnClick([&](wiEventArgs args) {
auto& weather = GetWeather();
weather.SetSimpleSky(args.bValue);
+ if (args.bValue)
+ {
+ weather.SetRealisticSky(false);
+ }
});
AddWidget(&simpleskyCheckBox);
+ realisticskyCheckBox.Create("Realistic sky: ");
+ realisticskyCheckBox.SetTooltip("Physically based sky rendering model.");
+ realisticskyCheckBox.SetSize(XMFLOAT2(hei, hei));
+ realisticskyCheckBox.SetPos(XMFLOAT2(x + 120, y));
+ realisticskyCheckBox.OnClick([&](wiEventArgs args) {
+ auto& weather = GetWeather();
+ weather.SetRealisticSky(args.bValue);
+ if (args.bValue)
+ {
+ weather.SetSimpleSky(false);
+ }
+ });
+ AddWidget(&realisticskyCheckBox);
+
skyButton.Create("Load Sky");
skyButton.SetTooltip("Load a skybox cubemap texture...");
skyButton.SetSize(XMFLOAT2(240, hei));
@@ -510,6 +528,7 @@ void WeatherWindow::Update()
}
simpleskyCheckBox.SetCheck(weather.IsSimpleSky());
+ realisticskyCheckBox.SetCheck(weather.IsRealisticSky());
ocean_enabledCheckBox.SetCheck(weather.IsOceanEnabled());
ocean_patchSizeSlider.SetValue(weather.oceanParameters.patch_length);
diff --git a/WickedEngine/ResourceMapping.h b/WickedEngine/ResourceMapping.h
index 7c74c2751..56b1f75e3 100644
--- a/WickedEngine/ResourceMapping.h
+++ b/WickedEngine/ResourceMapping.h
@@ -19,49 +19,52 @@
#define TEXSLOT_GLOBALLIGHTMAP 7
#define TEXSLOT_ENVMAPARRAY 8
#define TEXSLOT_DECALATLAS 9
+#define TEXSLOT_SKYVIEWLUT 10
+#define TEXSLOT_TRANSMITTANCELUT 11
+#define TEXSLOT_MULTISCATTERINGLUT 12
-#define TEXSLOT_SHADOWARRAY_2D 10
-#define TEXSLOT_SHADOWARRAY_CUBE 11
-#define TEXSLOT_SHADOWARRAY_TRANSPARENT 12
+#define TEXSLOT_SHADOWARRAY_2D 13
+#define TEXSLOT_SHADOWARRAY_CUBE 14
+#define TEXSLOT_SHADOWARRAY_TRANSPARENT 15
-#define TEXSLOT_VOXELRADIANCE 13
+#define TEXSLOT_VOXELRADIANCE 16
-#define SBSLOT_TILEFRUSTUMS 14
-#define SBSLOT_ENTITYTILES 15
-#define SBSLOT_ENTITYARRAY 16
-#define SBSLOT_MATRIXARRAY 17
+#define SBSLOT_TILEFRUSTUMS 17
+#define SBSLOT_ENTITYTILES 18
+#define SBSLOT_ENTITYARRAY 19
+#define SBSLOT_MATRIXARRAY 20
-#define TEXSLOT_FONTATLAS 19
+#define TEXSLOT_FONTATLAS 21
// Ondemand textures are 2d textures and declared in shader globals, these can be used independently in any shader:
-#define TEXSLOT_ONDEMAND0 20
-#define TEXSLOT_ONDEMAND1 21
-#define TEXSLOT_ONDEMAND2 22
-#define TEXSLOT_ONDEMAND3 23
-#define TEXSLOT_ONDEMAND4 24
-#define TEXSLOT_ONDEMAND5 25
-#define TEXSLOT_ONDEMAND6 26
-#define TEXSLOT_ONDEMAND7 27
-#define TEXSLOT_ONDEMAND8 28
-#define TEXSLOT_ONDEMAND9 29
-#define TEXSLOT_ONDEMAND10 30
-#define TEXSLOT_ONDEMAND11 31
-#define TEXSLOT_ONDEMAND12 32
-#define TEXSLOT_ONDEMAND13 33
-#define TEXSLOT_ONDEMAND14 34
-#define TEXSLOT_ONDEMAND15 35
-#define TEXSLOT_ONDEMAND16 36
-#define TEXSLOT_ONDEMAND17 37
-#define TEXSLOT_ONDEMAND18 38
-#define TEXSLOT_ONDEMAND19 39
-#define TEXSLOT_ONDEMAND20 40
-#define TEXSLOT_ONDEMAND21 41
-#define TEXSLOT_ONDEMAND22 42
+#define TEXSLOT_ONDEMAND0 22
+#define TEXSLOT_ONDEMAND1 23
+#define TEXSLOT_ONDEMAND2 24
+#define TEXSLOT_ONDEMAND3 25
+#define TEXSLOT_ONDEMAND4 26
+#define TEXSLOT_ONDEMAND5 27
+#define TEXSLOT_ONDEMAND6 28
+#define TEXSLOT_ONDEMAND7 29
+#define TEXSLOT_ONDEMAND8 30
+#define TEXSLOT_ONDEMAND9 31
+#define TEXSLOT_ONDEMAND10 32
+#define TEXSLOT_ONDEMAND11 33
+#define TEXSLOT_ONDEMAND12 34
+#define TEXSLOT_ONDEMAND13 35
+#define TEXSLOT_ONDEMAND14 36
+#define TEXSLOT_ONDEMAND15 37
+#define TEXSLOT_ONDEMAND16 38
+#define TEXSLOT_ONDEMAND17 39
+#define TEXSLOT_ONDEMAND18 40
+#define TEXSLOT_ONDEMAND19 41
+#define TEXSLOT_ONDEMAND20 42
+#define TEXSLOT_ONDEMAND21 43
+#define TEXSLOT_ONDEMAND22 44
#define TEXSLOT_ONDEMAND_COUNT (TEXSLOT_ONDEMAND19 - TEXSLOT_ONDEMAND0 + 1)
// These are reserved for demand of any type of textures in specific shaders:
-#define TEXSLOT_UNIQUE0 43
-#define TEXSLOT_UNIQUE1 44
+#define TEXSLOT_UNIQUE0 45
+#define TEXSLOT_UNIQUE1 46
#define TEXSLOT_COUNT TEXSLOT_UNIQUE1
diff --git a/WickedEngine/ShaderInterop_Renderer.h b/WickedEngine/ShaderInterop_Renderer.h
index d62071661..a089bb1c9 100644
--- a/WickedEngine/ShaderInterop_Renderer.h
+++ b/WickedEngine/ShaderInterop_Renderer.h
@@ -162,7 +162,8 @@ static const uint OPTION_BIT_VOXELGI_ENABLED = 1 << 2;
static const uint OPTION_BIT_VOXELGI_REFLECTIONS_ENABLED = 1 << 3;
static const uint OPTION_BIT_VOXELGI_RETARGETTED = 1 << 4;
static const uint OPTION_BIT_SIMPLE_SKY = 1 << 5;
-static const uint OPTION_BIT_RAYTRACED_SHADOWS = 1 << 6;
+static const uint OPTION_BIT_REALISTIC_SKY = 1 << 6;
+static const uint OPTION_BIT_RAYTRACED_SHADOWS = 1 << 7;
// ---------- Common Constant buffers: -----------------
diff --git a/WickedEngine/Shaders_SOURCE.vcxitems b/WickedEngine/Shaders_SOURCE.vcxitems
index d586002e0..230ecff49 100644
--- a/WickedEngine/Shaders_SOURCE.vcxitems
+++ b/WickedEngine/Shaders_SOURCE.vcxitems
@@ -36,6 +36,7 @@
+
@@ -764,6 +765,36 @@
Compute
Compute
+
+ Compute
+ Compute
+ Compute
+ Compute
+ Compute
+ Compute
+ Compute
+ Compute
+
+
+ Compute
+ Compute
+ Compute
+ Compute
+ Compute
+ Compute
+ Compute
+ Compute
+
+
+ Compute
+ Compute
+ Compute
+ Compute
+ Compute
+ Compute
+ Compute
+ Compute
+
Pixel
Pixel
diff --git a/WickedEngine/Shaders_SOURCE.vcxitems.filters b/WickedEngine/Shaders_SOURCE.vcxitems.filters
index 3d9e61381..ddad0be69 100644
--- a/WickedEngine/Shaders_SOURCE.vcxitems.filters
+++ b/WickedEngine/Shaders_SOURCE.vcxitems.filters
@@ -129,6 +129,9 @@
HF
+
+ HF
+
@@ -941,5 +944,14 @@
GS
+
+ CS
+
+
+ CS
+
+
+ CS
+
\ No newline at end of file
diff --git a/WickedEngine/dxc.exe b/WickedEngine/dxc.exe
index 797936cd9..7a1a481f1 100644
Binary files a/WickedEngine/dxc.exe and b/WickedEngine/dxc.exe differ
diff --git a/WickedEngine/dxcompiler.dll b/WickedEngine/dxcompiler.dll
index 58a39df14..d61ce699b 100644
Binary files a/WickedEngine/dxcompiler.dll and b/WickedEngine/dxcompiler.dll differ
diff --git a/WickedEngine/dxil.dll b/WickedEngine/dxil.dll
index 598d4aee1..96b71263d 100644
Binary files a/WickedEngine/dxil.dll and b/WickedEngine/dxil.dll differ
diff --git a/WickedEngine/globals.hlsli b/WickedEngine/globals.hlsli
index c7050f9df..7d8ead576 100644
--- a/WickedEngine/globals.hlsli
+++ b/WickedEngine/globals.hlsli
@@ -12,6 +12,9 @@ TEXTURECUBE(texture_globalenvmap, float4, TEXSLOT_GLOBALENVMAP);
TEXTURE2D(texture_globallightmap, float4, TEXSLOT_GLOBALLIGHTMAP);
TEXTURECUBEARRAY(texture_envmaparray, float4, TEXSLOT_ENVMAPARRAY);
TEXTURE2D(texture_decalatlas, float4, TEXSLOT_DECALATLAS);
+TEXTURE2D(texture_skyviewlut, float4, TEXSLOT_SKYVIEWLUT);
+TEXTURE2D(texture_transmittancelut, float4, TEXSLOT_TRANSMITTANCELUT);
+TEXTURE2D(texture_multiscatteringlut, float4, TEXSLOT_MULTISCATTERINGLUT);
TEXTURE2DARRAY(texture_shadowarray_2d, float, TEXSLOT_SHADOWARRAY_2D);
TEXTURECUBEARRAY(texture_shadowarray_cube, float, TEXSLOT_SHADOWARRAY_CUBE);
TEXTURE2DARRAY(texture_shadowarray_transparent, float4, TEXSLOT_SHADOWARRAY_TRANSPARENT);
diff --git a/WickedEngine/lightingHF.hlsli b/WickedEngine/lightingHF.hlsli
index 1592be34a..12741079c 100644
--- a/WickedEngine/lightingHF.hlsli
+++ b/WickedEngine/lightingHF.hlsli
@@ -190,7 +190,14 @@ inline void DirectionalLight(in ShaderEntity light, in Surface surface, inout Li
[branch]
if (any(sh))
{
- float3 lightColor = light.GetColor().rgb * light.energy * sh;
+ float3 atmosphereTransmittance = 1.0;
+ if (g_xFrame_Options & OPTION_BIT_REALISTIC_SKY)
+ {
+ AtmosphereParameters Atmosphere = GetAtmosphereParameters();
+ atmosphereTransmittance = GetAtmosphericLightTransmittance(Atmosphere, surface.P, L, texture_transmittancelut);
+ }
+
+ float3 lightColor = light.GetColor().rgb * light.energy * sh * atmosphereTransmittance;
lighting.direct.diffuse += max(0.0f, lightColor * BRDF_GetDiffuse(surface, surfaceToLight));
lighting.direct.specular += max(0.0f, lightColor * BRDF_GetSpecular(surface, surfaceToLight));
}
@@ -803,7 +810,11 @@ inline float3 GetAmbient(in float3 N)
else
#endif // ENVMAPRENDERING
{
- ambient = lerp(GetDynamicSkyColor(float3(0, -1, 0), false, false, false), GetDynamicSkyColor(float3(0, 1, 0), false, false, false), saturate(N.y * 0.5f + 0.5f)) + GetAmbientColor();
+ // Also set realistic_sky_stationary to true so we capture ambient at float3(0.0, 0.0, 0.0), similar to the standard sky to avoid flickering and weird behavior
+ ambient = lerp(
+ GetDynamicSkyColor(float3(0, -1, 0), false, false, false, true),
+ GetDynamicSkyColor(float3(0, 1, 0), false, false, false, true),
+ saturate(N.y * 0.5f + 0.5f)) + GetAmbientColor();
}
return ambient;
@@ -827,8 +838,13 @@ inline float3 EnvironmentReflection_Global(in Surface surface, in float MIP)
#endif // ENVMAPRENDERING
{
// There are no envmaps, approximate sky color:
- float3 realSkyColor = GetDynamicSkyColor(surface.R, false, false, false); // false: disable sun disk and clouds
- float3 roughSkyColor = lerp(GetDynamicSkyColor(float3(0, -1, 0), false, false, false), GetDynamicSkyColor(float3(0, 1, 0), false, false, false), saturate(surface.R.y * 0.5f + 0.5f));
+ // Also set realistic_sky_stationary to true so we capture environment at float3(0.0, 0.0, 0.0), similar to the standard sky to avoid flickering and weird behavior
+ float3 realSkyColor = GetDynamicSkyColor(surface.R, false, false, false, true); // false: disable sun disk and clouds
+ float3 roughSkyColor = lerp(
+ GetDynamicSkyColor(float3(0, -1, 0), false, false, false, true),
+ GetDynamicSkyColor(float3(0, 1, 0), false, false, false, true),
+ saturate(surface.R.y * 0.5f + 0.5f));
+
envColor = lerp(realSkyColor, roughSkyColor, saturate(surface.roughness));
}
diff --git a/WickedEngine/objectHF.hlsli b/WickedEngine/objectHF.hlsli
index d7debbd2a..7bd724c69 100644
--- a/WickedEngine/objectHF.hlsli
+++ b/WickedEngine/objectHF.hlsli
@@ -669,7 +669,8 @@ inline void ApplyLighting(in Surface surface, in Lighting lighting, inout float4
inline void ApplyFog(in float dist, inout float4 color)
{
- color.rgb = lerp(color.rgb, GetDynamicSkyColor(float3(0, -1, 0), false, false, false), GetFogAmount(dist));
+ float3 V = g_xFrame_Options & OPTION_BIT_REALISTIC_SKY ? float3(0.0, 1.0, 0.0) : float3(0.0, -1.0, 0.0);
+ color.rgb = lerp(color.rgb, GetDynamicSkyColor(V, false, false, false, true), GetFogAmount(dist));
}
diff --git a/WickedEngine/raytrace_shadeCS.hlsl b/WickedEngine/raytrace_shadeCS.hlsl
index ee5fda058..609cbfb66 100644
--- a/WickedEngine/raytrace_shadeCS.hlsl
+++ b/WickedEngine/raytrace_shadeCS.hlsl
@@ -171,7 +171,14 @@ void main(uint3 DTid : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex)
[branch]
if (NdotL > 0)
{
- float3 lightColor = light.GetColor().rgb * light.energy;
+ float3 atmosphereTransmittance = 1.0;
+ if (g_xFrame_Options & OPTION_BIT_REALISTIC_SKY)
+ {
+ AtmosphereParameters Atmosphere = GetAtmosphereParameters();
+ atmosphereTransmittance = GetAtmosphericLightTransmittance(Atmosphere, surface.P, L, texture_transmittancelut);
+ }
+
+ float3 lightColor = light.GetColor().rgb * light.energy * atmosphereTransmittance;
lighting.direct.specular = lightColor * BRDF_GetSpecular(surface, surfaceToLight);
lighting.direct.diffuse = lightColor * BRDF_GetDiffuse(surface, surfaceToLight);
diff --git a/WickedEngine/renderlightmapPS.hlsl b/WickedEngine/renderlightmapPS.hlsl
index 74add5453..f707cce4d 100644
--- a/WickedEngine/renderlightmapPS.hlsl
+++ b/WickedEngine/renderlightmapPS.hlsl
@@ -52,7 +52,14 @@ float4 main(Input input) : SV_TARGET
[branch]
if (NdotL > 0)
{
- float3 lightColor = light.GetColor().rgb * light.energy;
+ float3 atmosphereTransmittance = 1.0;
+ if (g_xFrame_Options & OPTION_BIT_REALISTIC_SKY)
+ {
+ AtmosphereParameters Atmosphere = GetAtmosphereParameters();
+ atmosphereTransmittance = GetAtmosphericLightTransmittance(Atmosphere, P, L, texture_transmittancelut);
+ }
+
+ float3 lightColor = light.GetColor().rgb * light.energy * atmosphereTransmittance;
lighting.direct.diffuse = lightColor;
}
@@ -173,7 +180,7 @@ float4 main(Input input) : SV_TARGET
}
else
{
- envColor = GetDynamicSkyColor(ray.direction);
+ envColor = GetDynamicSkyColor(ray.direction, true, true, false, true);
}
ray.color += max(0, ray.energy * envColor);
diff --git a/WickedEngine/skyAtmosphere.hlsli b/WickedEngine/skyAtmosphere.hlsli
new file mode 100644
index 000000000..23c6454ae
--- /dev/null
+++ b/WickedEngine/skyAtmosphere.hlsli
@@ -0,0 +1,746 @@
+#ifndef WI_SKYATMOSPHERE_HF
+#define WI_SKYATMOSPHERE_HF
+#include "globals.hlsli"
+
+/*
+ *
+ * Implementation is based on Sebastien Hillaires papers on "A Scalable and Production Ready Sky and Atmosphere Rendering Technique"
+ * See: https://sebh.github.io/publications/egsr2020.pdf
+ *
+ * And source that follows (MIT): https://github.com/sebh/UnrealEngineSkyAtmosphere
+ *
+ */
+
+
+static const float2 transmittanceLUTRes = float2(256, 64);
+static const float2 multiScatteringLUTRes = float2(32, 32);
+static const float2 skyViewLUTRes = float2(192.0, 104);
+
+#define USE_CornetteShanks
+
+#define M_TO_SKY_UNIT 0.001f // Engine units are in meters
+#define SKY_UNIT_TO_M (1.0 / M_TO_SKY_UNIT)
+
+#define PLANET_RADIUS_OFFSET 0.001f // Float accuracy offset in Sky unit (km, so this is 1m)
+
+struct AtmosphereParameters
+{
+ // Radius of the planet (center to ground)
+ float bottomRadius;
+ // Maximum considered atmosphere height (center to atmosphere top)
+ float topRadius;
+ // Center of the planet
+ float3 planetCenter;
+
+ // Rayleigh scattering exponential distribution scale in the atmosphere
+ float rayleighDensityExpScale;
+ // Rayleigh scattering coefficients
+ float3 rayleighScattering;
+
+ // Mie scattering exponential distribution scale in the atmosphere
+ float mieDensityExpScale;
+ // Mie scattering coefficients
+ float3 mieScattering;
+ // Mie extinction coefficients
+ float3 mieExtinction;
+ // Mie absorption coefficients
+ float3 mieAbsorption;
+ // Mie phase function excentricity
+ float miePhaseG;
+
+ // Another medium type in the atmosphere
+ float absorptionDensity0LayerWidth;
+ float absorptionDensity0ConstantTerm;
+ float absorptionDensity0LinearTerm;
+ float absorptionDensity1ConstantTerm;
+ float absorptionDensity1LinearTerm;
+ // This other medium only absorb light, e.g. useful to represent ozone in the earth atmosphere
+ float3 absorptionExtinction;
+
+ // The albedo of the ground.
+ float3 groundAlbedo;
+};
+
+AtmosphereParameters GetAtmosphereParameters()
+{
+ AtmosphereParameters parameters;
+
+ // Values shown here are the result of integration over wavelength power spectrum integrated with paricular function.
+ // Refer to https://github.com/ebruneton/precomputed_atmospheric_scattering for details.
+
+ // All units in kilometers
+ const float earthBottomRadius = 6360.0f;
+ const float earthTopRadius = 6460.0f; // 100km atmosphere radius, less edge visible and it contain 99.99% of the atmosphere medium https://en.wikipedia.org/wiki/K%C3%A1rm%C3%A1n_line
+ const float earthRayleighScaleHeight = 8.0f;
+ const float earthMieScaleHeight = 1.2f;
+
+ // Traslation from Bruneton2017 parameterisation.
+ parameters.rayleighDensityExpScale = -1.0 / earthRayleighScaleHeight;
+ parameters.mieDensityExpScale = -1.0 / earthMieScaleHeight;
+ parameters.absorptionDensity0LayerWidth = 25.0;
+ parameters.absorptionDensity0ConstantTerm = -2.0 / 3.0;
+ parameters.absorptionDensity0LinearTerm = 1.0 / 15.0;
+ parameters.absorptionDensity1ConstantTerm = 8.0 / 3.0;
+ parameters.absorptionDensity1LinearTerm = -1.0 / 15.0;
+
+ parameters.miePhaseG = 0.8;
+ parameters.rayleighScattering = float3(0.005802f, 0.013558f, 0.033100f);
+ parameters.mieScattering = float3(0.003996f, 0.003996f, 0.003996f);
+ parameters.mieExtinction = float3(0.004440f, 0.004440f, 0.004440f);
+ parameters.mieAbsorption = parameters.mieExtinction - parameters.mieScattering;
+
+ parameters.absorptionExtinction = float3(0.000650f, 0.001881f, 0.000085f);
+
+ parameters.groundAlbedo = float3(0.3, 0.3, 0.3); // 0.3 for earths ground albedo, see https://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html
+ parameters.bottomRadius = earthBottomRadius;
+ parameters.topRadius = earthTopRadius;
+ parameters.planetCenter = float3(0.0, -earthBottomRadius - 0.1, 0.0); // Spawn 100m in the air
+
+ return parameters;
+}
+
+
+
+////////////////////////////////////////////////////////////
+// LUT functions
+////////////////////////////////////////////////////////////
+
+
+
+// Transmittance LUT function parameterisation from Bruneton 2017 https://github.com/ebruneton/precomputed_atmospheric_scattering
+// uv in [0,1]
+// viewZenithCosAngle in [-1,1]
+// viewHeight in [bottomRAdius, topRadius]
+
+// We should precompute those terms from resolutions (Or set resolution as #defined constants)
+float FromUnitToSubUvs(float u, float resolution)
+{
+ return (u + 0.5f / resolution) * (resolution / (resolution + 1.0f));
+}
+float FromSubUvsToUnit(float u, float resolution)
+{
+ return (u - 0.5f / resolution) * (resolution / (resolution - 1.0f));
+}
+
+void UvToLutTransmittanceParams(AtmosphereParameters atmosphere, out float viewHeight, out float viewZenithCosAngle, in float2 uv)
+{
+ //uv = float2(FromSubUvsToUnit(uv.x, transmittanceLUTRes.x), FromSubUvsToUnit(uv.y, transmittanceLUTRes.y)); // No real impact so off
+ float x_mu = uv.x;
+ float x_r = uv.y;
+
+ float H = sqrt(atmosphere.topRadius * atmosphere.topRadius - atmosphere.bottomRadius * atmosphere.bottomRadius);
+ float rho = H * x_r;
+ viewHeight = sqrt(rho * rho + atmosphere.bottomRadius * atmosphere.bottomRadius);
+
+ float d_min = atmosphere.topRadius - viewHeight;
+ float d_max = rho + H;
+ float d = d_min + x_mu * (d_max - d_min);
+ viewZenithCosAngle = d == 0.0 ? 1.0f : (H * H - rho * rho - d * d) / (2.0 * viewHeight * d);
+ viewZenithCosAngle = clamp(viewZenithCosAngle, -1.0, 1.0);
+}
+
+void LutTransmittanceParamsToUv(AtmosphereParameters atmosphere, in float viewHeight, in float viewZenithCosAngle, out float2 uv)
+{
+ float H = sqrt(max(0.0f, atmosphere.topRadius * atmosphere.topRadius - atmosphere.bottomRadius * atmosphere.bottomRadius));
+ float rho = sqrt(max(0.0f, viewHeight * viewHeight - atmosphere.bottomRadius * atmosphere.bottomRadius));
+
+ float discriminant = viewHeight * viewHeight * (viewZenithCosAngle * viewZenithCosAngle - 1.0) + atmosphere.topRadius * atmosphere.topRadius;
+ float d = max(0.0, (-viewHeight * viewZenithCosAngle + sqrt(discriminant))); // Distance to atmosphere boundary
+
+ float d_min = atmosphere.topRadius - viewHeight;
+ float d_max = rho + H;
+ float x_mu = (d - d_min) / (d_max - d_min);
+ float x_r = rho / H;
+
+ uv = float2(x_mu, x_r);
+ //uv = float2(FromUnitToSubUvs(uv.x, transmittanceLUTRes.x), FromUnitToSubUvs(uv.y, transmittanceLUTRes.y)); // No real impact so off
+}
+
+
+#define NONLINEARSKYVIEWLUT 1
+void UvToSkyViewLutParams(AtmosphereParameters atmosphere, out float viewZenithCosAngle, out float lightViewCosAngle, in float viewHeight, in float2 uv)
+{
+ // Constrain uvs to valid sub texel range (avoid zenith derivative issue making LUT usage visible)
+ uv = float2(FromSubUvsToUnit(uv.x, skyViewLUTRes.x), FromSubUvsToUnit(uv.y, skyViewLUTRes.y));
+
+ float Vhorizon = sqrt(viewHeight * viewHeight - atmosphere.bottomRadius * atmosphere.bottomRadius);
+ float CosBeta = Vhorizon / viewHeight; // GroundToHorizonCos
+ float Beta = acos(CosBeta);
+ float ZenithHorizonAngle = PI - Beta;
+
+ if (uv.y < 0.5f)
+ {
+ float coord = 2.0 * uv.y;
+ coord = 1.0 - coord;
+#if NONLINEARSKYVIEWLUT
+ coord *= coord;
+#endif
+ coord = 1.0 - coord;
+ viewZenithCosAngle = cos(ZenithHorizonAngle * coord);
+ }
+ else
+ {
+ float coord = uv.y * 2.0 - 1.0;
+#if NONLINEARSKYVIEWLUT
+ coord *= coord;
+#endif
+ viewZenithCosAngle = cos(ZenithHorizonAngle + Beta * coord);
+ }
+
+ float coord = uv.x;
+ coord *= coord;
+ lightViewCosAngle = -(coord * 2.0 - 1.0);
+}
+
+void SkyViewLutParamsToUv(AtmosphereParameters atmosphere, in bool intersectGround, in float viewZenithCosAngle, in float lightViewCosAngle, in float viewHeight, out float2 uv)
+{
+ float Vhorizon = sqrt(viewHeight * viewHeight - atmosphere.bottomRadius * atmosphere.bottomRadius);
+ float CosBeta = Vhorizon / viewHeight; // GroundToHorizonCos
+ float Beta = acos(CosBeta);
+ float ZenithHorizonAngle = PI - Beta;
+
+ if (!intersectGround)
+ {
+ float coord = acos(viewZenithCosAngle) / ZenithHorizonAngle;
+ coord = 1.0 - coord;
+#if NONLINEARSKYVIEWLUT
+ coord = sqrt(abs(coord));
+#endif
+ coord = 1.0 - coord;
+ uv.y = coord * 0.5f;
+ }
+ else
+ {
+ float coord = (acos(viewZenithCosAngle) - ZenithHorizonAngle) / Beta;
+#if NONLINEARSKYVIEWLUT
+ coord = sqrt(abs(coord));
+#endif
+ uv.y = coord * 0.5f + 0.5f;
+ }
+
+ {
+ float coord = -lightViewCosAngle * 0.5f + 0.5f;
+ coord = sqrt(coord);
+ uv.x = coord;
+ }
+
+ // Constrain uvs to valid sub texel range (avoid zenith derivative issue making LUT usage visible)
+ uv = float2(FromUnitToSubUvs(uv.x, skyViewLUTRes.x), FromUnitToSubUvs(uv.y, skyViewLUTRes.y));
+}
+
+
+
+////////////////////////////////////////////////////////////
+// Participating media
+////////////////////////////////////////////////////////////
+
+
+
+float GetAlbedo(float scattering, float extinction)
+{
+ return scattering / max(0.001, extinction);
+}
+float3 GetAlbedo(float3 scattering, float3 extinction)
+{
+ return scattering / max(0.001, extinction);
+}
+
+
+struct MediumSampleRGB
+{
+ float3 scattering;
+ float3 absorption;
+ float3 extinction;
+
+ float3 scatteringMie;
+ float3 absorptionMie;
+ float3 extinctionMie;
+
+ float3 scatteringRay;
+ float3 absorptionRay;
+ float3 extinctionRay;
+
+ float3 scatteringOzo;
+ float3 absorptionOzo;
+ float3 extinctionOzo;
+
+ float3 albedo;
+};
+
+MediumSampleRGB SampleMediumRGB(in float3 worldPos, in AtmosphereParameters atmosphere)
+{
+ const float viewHeight = length(worldPos) - atmosphere.bottomRadius;
+
+ const float densityMie = exp(atmosphere.mieDensityExpScale * viewHeight);
+ const float densityRay = exp(atmosphere.rayleighDensityExpScale * viewHeight);
+ const float densityOzo = saturate(viewHeight < atmosphere.absorptionDensity0LayerWidth ?
+ atmosphere.absorptionDensity0LinearTerm * viewHeight + atmosphere.absorptionDensity0ConstantTerm :
+ atmosphere.absorptionDensity1LinearTerm * viewHeight + atmosphere.absorptionDensity1ConstantTerm);
+
+ MediumSampleRGB s;
+
+ s.scatteringMie = densityMie * atmosphere.mieScattering;
+ s.absorptionMie = densityMie * atmosphere.mieAbsorption;
+ s.extinctionMie = densityMie * atmosphere.mieExtinction;
+
+ s.scatteringRay = densityRay * atmosphere.rayleighScattering;
+ s.absorptionRay = 0.0f;
+ s.extinctionRay = s.scatteringRay + s.absorptionRay;
+
+ s.scatteringOzo = 0.0;
+ s.absorptionOzo = densityOzo * atmosphere.absorptionExtinction;
+ s.extinctionOzo = s.scatteringOzo + s.absorptionOzo;
+
+ s.scattering = s.scatteringMie + s.scatteringRay + s.scatteringOzo;
+ s.absorption = s.absorptionMie + s.absorptionRay + s.absorptionOzo;
+ s.extinction = s.extinctionMie + s.extinctionRay + s.extinctionOzo;
+ s.albedo = GetAlbedo(s.scattering, s.extinction);
+
+ return s;
+}
+
+
+
+////////////////////////////////////////////////////////////
+// Sampling functions
+////////////////////////////////////////////////////////////
+
+
+
+float RayleighPhase(float cosTheta)
+{
+ float factor = 3.0f / (16.0f * PI);
+ return factor * (1.0f + cosTheta * cosTheta);
+}
+
+float CornetteShanksMiePhaseFunction(float g, float cosTheta)
+{
+ float k = 3.0 / (8.0 * PI) * (1.0 - g * g) / (2.0 + g * g);
+ return k * (1.0 + cosTheta * cosTheta) / pow(abs(1.0 + g * g - 2.0 * g * -cosTheta), 1.5);
+}
+
+float HgPhase(float g, float cosTheta)
+{
+#ifdef USE_CornetteShanks
+ return CornetteShanksMiePhaseFunction(g, cosTheta);
+#else
+ // Reference implementation (i.e. not schlick approximation).
+ // See http://www.pbr-book.org/3ed-2018/Volume_Scattering/Phase_Functions.html
+ float numer = 1.0f - g * g;
+ float denom = 1.0f + g * g + 2.0f * g * cosTheta;
+ return numer / (4.0f * PI * denom * sqrt(denom));
+#endif
+}
+
+float DualLobPhase(float g0, float g1, float w, float cosTheta)
+{
+ return lerp(HgPhase(g0, cosTheta), HgPhase(g1, cosTheta), w);
+}
+
+float UniformPhase()
+{
+ return 1.0f / (4.0f * PI);
+}
+
+
+
+////////////////////////////////////////////////////////////
+// Misc functions
+////////////////////////////////////////////////////////////
+
+
+
+float2 RaySphereIntersect(float3 rayOrigin, float3 rayDirection, float3 sphereCenter, float sphereRadius)
+{
+ float3 s0_r0 = rayOrigin - sphereCenter;
+ float a = dot(rayDirection, rayDirection);
+ float b = 2.0 * dot(rayDirection, s0_r0);
+ float c = dot(s0_r0, s0_r0) - (sphereRadius * sphereRadius);
+
+ float delta = b * b - 4.0 * a * c;
+
+ float2 sol = -1;
+
+ if (delta >= 0.0)
+ {
+ return (-b + float2(-1, 1) * sqrt(delta)) / (2.0 * a);
+ }
+
+ return sol;
+}
+
+// - Returns distance from rayOrigin to first intersecion with sphere,
+// or -1.0 if no intersection.
+float RaySphereIntersectNearest(float3 rayOrigin, float3 rayDirection, float3 sphereCenter, float sphereRadius)
+{
+ float2 sol = RaySphereIntersect(rayOrigin, rayDirection, sphereCenter, sphereRadius);
+ float sol0 = sol.x;
+ float sol1 = sol.y;
+
+ if (sol0 < 0.0 && sol1 < 0.0)
+ {
+ return -1.0;
+ }
+ if (sol0 < 0.0)
+ {
+ return max(0.0, sol1);
+ }
+ else if (sol1 < 0.0)
+ {
+ return max(0.0, sol0);
+ }
+ return max(0.0, min(sol0, sol1));
+}
+
+bool MoveToTopAtmosphere(inout float3 worldPosition, in float3 worldDirection, in float atmosphereTopRadius)
+{
+ float viewHeight = length(worldPosition);
+ if (viewHeight > atmosphereTopRadius)
+ {
+ float tTop = RaySphereIntersectNearest(worldPosition, worldDirection, float3(0.0f, 0.0f, 0.0f), atmosphereTopRadius);
+ if (tTop >= 0.0f)
+ {
+ float3 upVector = worldPosition / viewHeight;
+ float3 upOffset = upVector * -PLANET_RADIUS_OFFSET;
+ worldPosition = worldPosition + worldDirection * tTop + upOffset;
+ }
+ else
+ {
+ // Ray is not intersecting the atmosphere
+ return false;
+ }
+ }
+ return true; // ok to start tracing
+}
+
+float3 GetMultipleScattering(AtmosphereParameters atmosphere, Texture2D multiScatteringLUTTexture, float2 multiScatteringLUTRes, float3 scattering, float3 extinction, float3 worldPosition, float viewZenithCosAngle)
+{
+ float2 uv = saturate(float2(viewZenithCosAngle * 0.5f + 0.5f, (length(worldPosition) - atmosphere.bottomRadius) / (atmosphere.topRadius - atmosphere.bottomRadius)));
+ uv = float2(FromUnitToSubUvs(uv.x, multiScatteringLUTRes.x), FromUnitToSubUvs(uv.y, multiScatteringLUTRes.y));
+
+ float3 multiScatteredLuminance = multiScatteringLUTTexture.SampleLevel(sampler_linear_clamp, uv, 0).rgb;
+ return multiScatteredLuminance;
+}
+
+float3 GetTransmittance(AtmosphereParameters atmosphere, float pHeight, float sunZenithCosAngle, Texture2D transmittanceLutTexture)
+{
+ float2 uv;
+ LutTransmittanceParamsToUv(atmosphere, pHeight, sunZenithCosAngle, uv);
+
+ float3 TransmittanceToSun = transmittanceLutTexture.SampleLevel(sampler_linear_clamp, uv, 0).rgb;
+ return TransmittanceToSun;
+}
+
+float3 GetAtmosphereTransmittance(float3 worldPosition, float3 worldDirection, AtmosphereParameters atmosphere, Texture2D transmittanceLutTexture)
+{
+ float pHeight = length(worldPosition);
+ const float3 UpVector = worldPosition / pHeight;
+ float SunZenithCosAngle = dot(worldDirection, UpVector);
+
+ float2 uv;
+ LutTransmittanceParamsToUv(atmosphere, pHeight, SunZenithCosAngle, uv);
+
+ float3 TransmittanceToSun = transmittanceLutTexture.SampleLevel(sampler_linear_clamp, uv, 0).rgb;
+ return TransmittanceToSun;
+}
+
+float3 GetAtmosphericLightTransmittance(AtmosphereParameters atmosphere, float3 worldPosition, float3 worldDirection, Texture2D transmittanceLutTexture)
+{
+ const float3 planetCenterWorld = atmosphere.planetCenter * SKY_UNIT_TO_M;
+ const float3 planetCenterToWorldPos = (worldPosition - planetCenterWorld) * M_TO_SKY_UNIT;
+
+ float3 atmosphereTransmittance = GetAtmosphereTransmittance(planetCenterToWorldPos, worldDirection, atmosphere, transmittanceLutTexture);
+ return atmosphereTransmittance;
+}
+
+float3 GetCameraPlanetPos(AtmosphereParameters atmosphere, float3 cameraPosition)
+{
+ const float planetRadiusOffset = 0.01; // Always force to be 10 meters above the ground/sea level
+
+ const float offset = planetRadiusOffset * SKY_UNIT_TO_M;
+ const float bottomRadiusWorld = atmosphere.bottomRadius * SKY_UNIT_TO_M;
+ const float3 planetCenterWorld = atmosphere.planetCenter * SKY_UNIT_TO_M;
+ const float3 planetCenterToCameraWorld = cameraPosition - planetCenterWorld;
+ const float distanceToPlanetCenterWorld = length(planetCenterToCameraWorld);
+
+ // If the camera is below the planet surface, we snap it back onto the surface.
+ // This is to make sure the sky is always visible even if the camera is inside the virtual planet.
+ float3 skyWorldCameraOrigin = distanceToPlanetCenterWorld < (bottomRadiusWorld + offset) ?
+ planetCenterWorld + (bottomRadiusWorld + offset) * (planetCenterToCameraWorld / distanceToPlanetCenterWorld) : cameraPosition;
+
+ return (skyWorldCameraOrigin - planetCenterWorld) * M_TO_SKY_UNIT;
+}
+
+float3 GetSunLuminance(float3 worldPosition, float3 worldDirection, float3 sunDirection, float3 sunIlluminance, AtmosphereParameters atmosphere, Texture2D transmittanceLutTexture)
+{
+ float sunApexAngleDegree = 0.545; // Angular diameter of sun to earth from sea level, see https://en.wikipedia.org/wiki/Solid_angle
+ float sunHalfApexAngleRadian = 0.5 * sunApexAngleDegree * PI / 180.0;
+ float sunCosHalfApexAngle = cos(sunHalfApexAngleRadian);
+
+ float VdotL = dot(worldDirection, normalize(sunDirection)); // weird... the sun disc shrinks near the horizon if we don't normalize sun direction
+ if (VdotL > sunCosHalfApexAngle)
+ {
+ float t = RaySphereIntersectNearest(worldPosition, worldDirection, float3(0.0f, 0.0f, 0.0f), atmosphere.bottomRadius);
+ if (t < 0.0f) // no intersection
+ {
+ const float3 atmosphereTransmittance = GetAtmosphereTransmittance(worldPosition, worldDirection, atmosphere, transmittanceLutTexture);
+
+ // Edge fade
+ const float halfCosHalfApex = sunCosHalfApexAngle + (1.0f - sunCosHalfApexAngle) * 0.25; // Start fading when at 75% distance from light disk center
+ const float weight = 1.0 - saturate((halfCosHalfApex - VdotL) / (halfCosHalfApex - sunCosHalfApexAngle));
+
+ return atmosphereTransmittance * weight * sunIlluminance;
+ }
+ }
+
+ return 0;
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+
+
+struct SingleScatteringResult
+{
+ float3 L; // Scattered light (luminance)
+ float3 opticalDepth; // Optical depth (1/m)
+ float3 transmittance; // Transmittance in [0,1] (unitless)
+ float3 multiScatAs1;
+
+ float3 newMultiScatStep0Out;
+ float3 newMultiScatStep1Out;
+};
+
+struct SamplingParameters
+{
+ bool variableSampleCount;
+ float sampleCountIni; // Used when variableSampleCount is false
+ float2 rayMarchMinMaxSPP;
+ float distanceSPPMaxInv;
+ //bool perPixelNoise;
+};
+
+SingleScatteringResult IntegrateScatteredLuminance(
+ in AtmosphereParameters atmosphere, in float2 pixPos, in float3 worldPosition, in float3 worldDirection, in float3 sunDirection, in float3 sunIlluminance,
+ in SamplingParameters sampling, in bool ground, in float3 depthBufferWorldPos, in bool opaque, in bool mieRayPhase, in bool multiScatteringApprox,
+ in Texture2D transmittanceLutTexture, in Texture2D multiScatteringLUTTexture, in float tMaxMax = 9000000.0f)
+{
+ SingleScatteringResult result = (SingleScatteringResult) 0;
+
+ // Compute next intersection with atmosphere or ground
+ float3 earthO = float3(0.0f, 0.0f, 0.0f);
+ float tBottom = RaySphereIntersectNearest(worldPosition, worldDirection, earthO, atmosphere.bottomRadius);
+ float tTop = RaySphereIntersectNearest(worldPosition, worldDirection, earthO, atmosphere.topRadius);
+ float tMax = 0.0f;
+ if (tBottom < 0.0f)
+ {
+ if (tTop < 0.0f)
+ {
+ tMax = 0.0f; // No intersection with earth nor atmosphere: stop right away
+ return result;
+ }
+ else
+ {
+ tMax = tTop;
+ }
+ }
+ else
+ {
+ if (tTop > 0.0f)
+ {
+ tMax = min(tTop, tBottom);
+ }
+ }
+
+ if (opaque)
+ {
+ float3 depthBufferWorldPosKm = depthBufferWorldPos * M_TO_SKY_UNIT;
+ float3 traceStartWorldPosKm = worldPosition + atmosphere.planetCenter; // Planet center is in km
+ float3 traceStartToSurfaceWorldKm = depthBufferWorldPosKm - traceStartWorldPosKm;
+ float tDepth = length(traceStartToSurfaceWorldKm); // Apply earth offset to go back to origin as top of earth mode.
+ if (tDepth < tMax)
+ {
+ tMax = tDepth;
+ }
+
+ //if (dot(worldDirection, traceStartToSurfaceWorldKm) < 0.0)
+ //{
+ // return result;
+ //}
+ }
+ tMax = min(tMax, tMaxMax);
+
+ // Sample count
+ float sampleCount = sampling.sampleCountIni;
+ float sampleCountFloor = sampling.sampleCountIni;
+ float tMaxFloor = tMax;
+ if (sampling.variableSampleCount)
+ {
+ sampleCount = lerp(sampling.rayMarchMinMaxSPP.x, sampling.rayMarchMinMaxSPP.y, saturate(tMax * sampling.distanceSPPMaxInv));
+ sampleCountFloor = floor(sampleCount);
+ tMaxFloor = tMax * sampleCountFloor / sampleCount; // rescale tMax to map to the last entire step segment.
+ }
+ float dt = tMax / sampleCount;
+
+ // Phase functions
+ const float uniformPhase = UniformPhase();
+ const float3 wi = sunDirection;
+ const float3 wo = worldDirection;
+ float cosTheta = dot(wi, wo);
+ float miePhaseValue = HgPhase(atmosphere.miePhaseG, -cosTheta); // mnegate cosTheta because due to WorldDir being a "in" direction.
+ float rayleighPhaseValue = RayleighPhase(cosTheta);
+
+ float3 globalL = sunIlluminance;
+
+ // Ray march the atmosphere to integrate optical depth
+ float3 L = 0.0f;
+ float3 throughput = 1.0;
+ float3 opticalDepth = 0.0;
+ float t = 0.0f;
+ float tPrev = 0.0;
+ const float sampleSegmentT = 0.3f;
+ for (float s = 0.0f; s < sampleCount; s += 1.0f)
+ {
+ if (sampling.variableSampleCount)
+ {
+ // More expenssive but artefact free
+ float t0 = (s) / sampleCountFloor;
+ float t1 = (s + 1.0f) / sampleCountFloor;
+ // Non linear distribution of sample within the range.
+ t0 = t0 * t0;
+ t1 = t1 * t1;
+ // Make t0 and t1 world space distances.
+ t0 = tMaxFloor * t0;
+ if (t1 > 1.0)
+ {
+ t1 = tMax;
+ // t1 = tMaxFloor; // this reveal depth slices
+ }
+ else
+ {
+ t1 = tMaxFloor * t1;
+ }
+
+ //if (Sampling.PerPixelNoise)
+ //{
+ // t = t0 + (t1 - t0) * InterleavedGradientNoise(pixPos, g_xFrame_FrameCount % 16);
+ //}
+ //else
+ //{
+ // t = t0 + (t1 - t0) * SampleSegmentT;
+ //}
+ t = t0 + (t1 - t0) * sampleSegmentT;
+
+ dt = t1 - t0;
+ }
+ else
+ {
+ //t = tMax * (s + SampleSegmentT) / SampleCount;
+ // Exact difference, important for accuracy of multiple scattering
+ float newT = tMax * (s + sampleSegmentT) / sampleCount;
+ dt = newT - t;
+ t = newT;
+ }
+ float3 P = worldPosition + t * worldDirection;
+ float pHeight = length(P);
+
+ MediumSampleRGB medium = SampleMediumRGB(P, atmosphere);
+ const float3 sampleOpticalDepth = medium.extinction * dt;
+ const float3 sampleTransmittance = exp(-sampleOpticalDepth);
+ opticalDepth += sampleOpticalDepth;
+
+ const float3 UpVector = P / pHeight;
+ float sunZenithCosAngle = dot(sunDirection, UpVector);
+ float3 transmittanceToSun = GetTransmittance(atmosphere, pHeight, sunZenithCosAngle, transmittanceLutTexture);
+
+ float3 phaseTimesScattering;
+ if (mieRayPhase)
+ {
+ phaseTimesScattering = medium.scatteringMie * miePhaseValue + medium.scatteringRay * rayleighPhaseValue;
+ }
+ else
+ {
+ phaseTimesScattering = medium.scattering * uniformPhase;
+ }
+
+ // Earth shadow
+ float tEarth = RaySphereIntersectNearest(P, sunDirection, earthO + PLANET_RADIUS_OFFSET * UpVector, atmosphere.bottomRadius);
+ float earthShadow = tEarth >= 0.0f ? 0.0f : 1.0f;
+
+ // Dual scattering for multi scattering
+
+ float3 multiScatteredLuminance = 0.0f;
+ if (multiScatteringApprox)
+ {
+ multiScatteredLuminance = GetMultipleScattering(atmosphere, multiScatteringLUTTexture, multiScatteringLUTRes, medium.scattering, medium.extinction, P, sunZenithCosAngle);
+ }
+
+ float3 S = globalL * (earthShadow * transmittanceToSun * phaseTimesScattering + multiScatteredLuminance * medium.scattering);
+
+ // When using the power serie to accumulate all sattering order, serie r must be <1 for a serie to converge.
+ // Under extreme coefficient, MultiScatAs1 can grow larger and thus result in broken visuals.
+ // The way to fix that is to use a proper analytical integration as proposed in slide 28 of http://www.frostbite.com/2015/08/physically-based-unified-volumetric-rendering-in-frostbite/
+ // However, it is possible to disable as it can also work using simple power serie sum unroll up to 5th order. The rest of the orders has a really low contribution.
+#define MULTI_SCATTERING_POWER_SERIE 1
+
+#if MULTI_SCATTERING_POWER_SERIE==0
+ // 1 is the integration of luminance over the 4pi of a sphere, and assuming an isotropic phase function of 1.0/(4*PI)
+ result.multiScatAs1 += throughput * medium.scattering * 1 * dt;
+#else
+ float3 MS = medium.scattering * 1;
+ float3 MSint = (MS - MS * sampleTransmittance) / medium.extinction;
+ result.multiScatAs1 += throughput * MSint;
+#endif
+
+ // Evaluate input to multi scattering
+ {
+ float3 newMS;
+
+ newMS = earthShadow * transmittanceToSun * medium.scattering * uniformPhase * 1;
+ result.newMultiScatStep0Out += throughput * (newMS - newMS * sampleTransmittance) / medium.extinction;
+ // result.NewMultiScatStep0Out += SampleTransmittance * throughput * newMS * dt;
+
+ newMS = medium.scattering * uniformPhase * multiScatteredLuminance;
+ result.newMultiScatStep1Out += throughput * (newMS - newMS * sampleTransmittance) / medium.extinction;
+ // result.NewMultiScatStep1Out += SampleTransmittance * throughput * newMS * dt;
+ }
+
+#if 0
+ L += throughput * S * dt;
+ throughput *= SampleTransmittance;
+#else
+ // See slide 28 at http://www.frostbite.com/2015/08/physically-based-unified-volumetric-rendering-in-frostbite/
+ float3 Sint = (S - S * sampleTransmittance) / medium.extinction; // integrate along the current step segment
+ L += throughput * Sint; // accumulate and also take into account the transmittance from previous steps
+ throughput *= sampleTransmittance;
+#endif
+
+ tPrev = t;
+ }
+
+ if (ground && tMax == tBottom && tBottom > 0.0)
+ {
+ // Account for bounced light off the earth
+ float3 P = worldPosition + tBottom * worldDirection;
+ float pHeight = length(P);
+
+ const float3 UpVector = P / pHeight;
+ float sunZenithCosAngle = dot(sunDirection, UpVector);
+ float3 transmittanceToSun = GetTransmittance(atmosphere, pHeight, sunZenithCosAngle, transmittanceLutTexture);
+
+ const float NdotL = saturate(dot(normalize(UpVector), normalize(sunDirection)));
+ L += globalL * transmittanceToSun * throughput * NdotL * atmosphere.groundAlbedo / PI;
+ }
+
+ result.L = L;
+ result.opticalDepth = opticalDepth;
+ result.transmittance = throughput;
+ return result;
+}
+
+
+#endif // WI_SKYATMOSPHERE_HF
\ No newline at end of file
diff --git a/WickedEngine/skyAtmosphere_multiScatteredLuminanceLutCS.hlsl b/WickedEngine/skyAtmosphere_multiScatteredLuminanceLutCS.hlsl
new file mode 100644
index 000000000..6961a2e8f
--- /dev/null
+++ b/WickedEngine/skyAtmosphere_multiScatteredLuminanceLutCS.hlsl
@@ -0,0 +1,142 @@
+#include "globals.hlsli"
+#include "skyAtmosphere.hlsli"
+
+TEXTURE2D(transmittanceLUT, float4, TEXSLOT_ONDEMAND0);
+TEXTURE2D(multiScatteringLUT, float4, TEXSLOT_ONDEMAND1);
+RWTEXTURE2D(output, float4, 0);
+
+static const float multipleScatteringFactor = 1.0;
+
+groupshared float3 MultiScatAs1SharedMem[64];
+groupshared float3 LSharedMem[64];
+
+[numthreads(1, 1, 64)]
+void main(uint3 DTid : SV_DispatchThreadID)
+{
+ float2 pixelPosition = float2(DTid.xy) + 0.5;
+ float2 uv = pixelPosition * rcp(multiScatteringLUTRes);
+
+
+ uv = float2(FromSubUvsToUnit(uv.x, multiScatteringLUTRes.x), FromSubUvsToUnit(uv.y, multiScatteringLUTRes.y));
+
+ AtmosphereParameters atmosphere = GetAtmosphereParameters();
+
+ float cosSunZenithAngle = uv.x * 2.0 - 1.0;
+ float3 sunDirection = float3(0.0, sqrt(saturate(1.0 - cosSunZenithAngle * cosSunZenithAngle)), cosSunZenithAngle);
+ // We adjust again viewHeight according to PLANET_RADIUS_OFFSET to be in a valid range.
+ float viewHeight = atmosphere.bottomRadius + saturate(uv.y + PLANET_RADIUS_OFFSET) * (atmosphere.topRadius - atmosphere.bottomRadius - PLANET_RADIUS_OFFSET);
+
+ float3 worldPosition = float3(0.0, 0.0, viewHeight);
+ float3 worldDirection = float3(0.0, 0.0, 1.0);
+
+ // When building the scattering factor, we assume light illuminance is 1 to compute a transfert function relative to identity illuminance of 1.
+ // This make the scattering factor independent of the light. It is now only linked to the atmosphere properties.
+ float3 sunIlluminance = 1.0;
+
+ SamplingParameters sampling;
+ {
+ sampling.variableSampleCount = false;
+ sampling.sampleCountIni = 20; // a minimum set of step is required for accuracy unfortunately
+ }
+ const bool ground = true;
+ const float depthBufferWorldPos = 0.0;
+ const bool opaque = false;
+ const bool mieRayPhase = false;
+ const bool multiScatteringApprox = false;
+
+ const float sphereSolidAngle = 4.0 * PI;
+ const float isotropicPhase = 1.0 / sphereSolidAngle;
+
+
+ // Reference. Since there are many sample, it requires MULTI_SCATTERING_POWER_SERIE to be true for accuracy and to avoid divergences (see declaration for explanations)
+#define SQRTSAMPLECOUNT 8
+ const float sqrtSample = float(SQRTSAMPLECOUNT);
+ float i = 0.5f + float(DTid.z / SQRTSAMPLECOUNT);
+ float j = 0.5f + float(DTid.z - float((DTid.z / SQRTSAMPLECOUNT) * SQRTSAMPLECOUNT));
+ {
+ float randA = i / sqrtSample;
+ float randB = j / sqrtSample;
+ float theta = 2.0f * PI * randA;
+ float phi = PI * randB;
+ float cosPhi = cos(phi);
+ float sinPhi = sin(phi);
+ float cosTheta = cos(theta);
+ float sinTheta = sin(theta);
+ worldDirection.x = cosTheta * sinPhi;
+ worldDirection.y = sinTheta * sinPhi;
+ worldDirection.z = cosPhi;
+ SingleScatteringResult result = IntegrateScatteredLuminance(
+ atmosphere, pixelPosition, worldPosition, worldDirection, sunDirection, sunIlluminance,
+ sampling, ground, depthBufferWorldPos, opaque, mieRayPhase, multiScatteringApprox, transmittanceLUT, multiScatteringLUT);
+
+ MultiScatAs1SharedMem[DTid.z] = result.multiScatAs1 * sphereSolidAngle / (sqrtSample * sqrtSample);
+ LSharedMem[DTid.z] = result.L * sphereSolidAngle / (sqrtSample * sqrtSample);
+ }
+#undef SQRTSAMPLECOUNT
+
+ GroupMemoryBarrierWithGroupSync();
+
+ // 64 to 32
+ if (DTid.z < 32)
+ {
+ MultiScatAs1SharedMem[DTid.z] += MultiScatAs1SharedMem[DTid.z + 32];
+ LSharedMem[DTid.z] += LSharedMem[DTid.z + 32];
+ }
+ GroupMemoryBarrierWithGroupSync();
+
+ // 32 to 16
+ if (DTid.z < 16)
+ {
+ MultiScatAs1SharedMem[DTid.z] += MultiScatAs1SharedMem[DTid.z + 16];
+ LSharedMem[DTid.z] += LSharedMem[DTid.z + 16];
+ }
+ GroupMemoryBarrierWithGroupSync();
+
+ // 16 to 8 (16 is thread group min hardware size with intel, no sync required from there)
+ if (DTid.z < 8)
+ {
+ MultiScatAs1SharedMem[DTid.z] += MultiScatAs1SharedMem[DTid.z + 8];
+ LSharedMem[DTid.z] += LSharedMem[DTid.z + 8];
+ }
+ GroupMemoryBarrierWithGroupSync();
+ if (DTid.z < 4)
+ {
+ MultiScatAs1SharedMem[DTid.z] += MultiScatAs1SharedMem[DTid.z + 4];
+ LSharedMem[DTid.z] += LSharedMem[DTid.z + 4];
+ }
+ GroupMemoryBarrierWithGroupSync();
+ if (DTid.z < 2)
+ {
+ MultiScatAs1SharedMem[DTid.z] += MultiScatAs1SharedMem[DTid.z + 2];
+ LSharedMem[DTid.z] += LSharedMem[DTid.z + 2];
+ }
+ GroupMemoryBarrierWithGroupSync();
+ if (DTid.z < 1)
+ {
+ MultiScatAs1SharedMem[DTid.z] += MultiScatAs1SharedMem[DTid.z + 1];
+ LSharedMem[DTid.z] += LSharedMem[DTid.z + 1];
+ }
+ GroupMemoryBarrierWithGroupSync();
+ if (DTid.z > 0)
+ return;
+
+ float3 MultiScatAs1 = MultiScatAs1SharedMem[0] * isotropicPhase; // Equation 7 f_ms
+ float3 InScatteredLuminance = LSharedMem[0] * isotropicPhase; // Equation 5 L_2ndOrder
+
+ // MultiScatAs1 represents the amount of luminance scattered as if the integral of scattered luminance over the sphere would be 1.
+ // - 1st order of scattering: one can ray-march a straight path as usual over the sphere. That is InScatteredLuminance.
+ // - 2nd order of scattering: the inscattered luminance is InScatteredLuminance at each of samples of fist order integration. Assuming a uniform phase function that is represented by MultiScatAs1,
+ // - 3nd order of scattering: the inscattered luminance is (InScatteredLuminance * MultiScatAs1 * MultiScatAs1)
+ // - etc.
+#if MULTI_SCATTERING_POWER_SERIE==0 // from IntegrateScatteredLuminance
+ float3 MultiScatAs1SQR = MultiScatAs1 * MultiScatAs1;
+ float3 L = InScatteredLuminance * (1.0 + MultiScatAs1 + MultiScatAs1SQR + MultiScatAs1 * MultiScatAs1SQR + MultiScatAs1SQR * MultiScatAs1SQR);
+#else
+ // For a serie, sum_{n=0}^{n=+inf} = 1 + r + r^2 + r^3 + ... + r^n = 1 / (1.0 - r), see https://en.wikipedia.org/wiki/Geometric_series
+ const float3 r = MultiScatAs1;
+ const float3 SumOfAllMultiScatteringEventsContribution = 1.0f / (1.0 - r);
+ float3 L = InScatteredLuminance * SumOfAllMultiScatteringEventsContribution; // Equation 10 Psi_ms
+#endif
+
+ output[DTid.xy] = float4(multipleScatteringFactor * L, 1.0f);
+}
\ No newline at end of file
diff --git a/WickedEngine/skyAtmosphere_skyViewLutCS.hlsl b/WickedEngine/skyAtmosphere_skyViewLutCS.hlsl
new file mode 100644
index 000000000..37fcefe97
--- /dev/null
+++ b/WickedEngine/skyAtmosphere_skyViewLutCS.hlsl
@@ -0,0 +1,72 @@
+#include "globals.hlsli"
+#include "skyAtmosphere.hlsli"
+
+TEXTURE2D(transmittanceLUT, float4, TEXSLOT_ONDEMAND0);
+TEXTURE2D(multiScatteringLUT, float4, TEXSLOT_ONDEMAND1);
+RWTEXTURE2D(output, float4, 0);
+
+[numthreads(8, 8, 1)]
+void main(uint3 DTid : SV_DispatchThreadID)
+{
+ AtmosphereParameters atmosphere = GetAtmosphereParameters();
+
+ float2 pixelPosition = float2(DTid.xy) + 0.5;
+ float2 uv = pixelPosition * rcp(skyViewLUTRes);
+
+ float3 skyRelativePosition = g_xCamera_CamPos;
+ float3 worldPosition = GetCameraPlanetPos(atmosphere, skyRelativePosition);
+
+ float viewHeight = length(worldPosition);
+
+ float viewZenithCosAngle;
+ float lightViewCosAngle;
+ UvToSkyViewLutParams(atmosphere, viewZenithCosAngle, lightViewCosAngle, viewHeight, uv);
+
+
+ float3 sunDirection;
+ {
+ float3 upVector = min(worldPosition / viewHeight, 1.0); // Causes flickering without min(x, 1.0) for untouched/edited directional lights
+ float sunZenithCosAngle = dot(upVector, GetSunDirection());
+ sunDirection = normalize(float3(sqrt(1.0 - sunZenithCosAngle * sunZenithCosAngle), 0.0, sunZenithCosAngle));
+ }
+
+
+ worldPosition = float3(0.0, 0.0, viewHeight);
+
+ float viewZenithSinAngle = sqrt(1 - viewZenithCosAngle * viewZenithCosAngle);
+ float3 worldDirection = float3(
+ viewZenithSinAngle * lightViewCosAngle,
+ viewZenithSinAngle * sqrt(1.0 - lightViewCosAngle * lightViewCosAngle),
+ viewZenithCosAngle);
+
+
+ // Move to top atmosphere
+ if (!MoveToTopAtmosphere(worldPosition, worldDirection, atmosphere.topRadius))
+ {
+ // Ray is not intersecting the atmosphere
+ output[DTid.xy] = float4(0, 0, 0, 1);
+ return;
+ }
+
+ float3 sunIlluminance = GetSunEnergy() * GetSunColor();
+
+ SamplingParameters sampling;
+ {
+ sampling.variableSampleCount = true;
+ sampling.sampleCountIni = 30;
+ sampling.rayMarchMinMaxSPP = float2(4, 14);
+ sampling.distanceSPPMaxInv = 0.01;
+ }
+ const bool ground = false;
+ const float depthBufferWorldPos = 0.0;
+ const bool opaque = false;
+ const bool mieRayPhase = true;
+ const bool multiScatteringApprox = true;
+ SingleScatteringResult ss = IntegrateScatteredLuminance(
+ atmosphere, pixelPosition, worldPosition, worldDirection, sunDirection, sunIlluminance,
+ sampling, ground, depthBufferWorldPos, opaque, mieRayPhase, multiScatteringApprox, transmittanceLUT, multiScatteringLUT);
+
+ float3 L = ss.L;
+
+ output[DTid.xy] = float4(L, 1.0);
+}
\ No newline at end of file
diff --git a/WickedEngine/skyAtmosphere_transmittanceLutCS.hlsl b/WickedEngine/skyAtmosphere_transmittanceLutCS.hlsl
new file mode 100644
index 000000000..963fcc768
--- /dev/null
+++ b/WickedEngine/skyAtmosphere_transmittanceLutCS.hlsl
@@ -0,0 +1,43 @@
+#include "globals.hlsli"
+#include "skyAtmosphere.hlsli"
+
+TEXTURE2D(transmittanceLUT, float4, TEXSLOT_ONDEMAND0);
+TEXTURE2D(multiScatteringLUT, float4, TEXSLOT_ONDEMAND1);
+RWTEXTURE2D(output, float4, 0);
+
+[numthreads(8, 8, 1)]
+void main(uint3 DTid : SV_DispatchThreadID)
+{
+ float2 pixelPosition = float2(DTid.xy) + 0.5;
+ AtmosphereParameters atmosphere = GetAtmosphereParameters();
+
+ // Compute camera position from LUT coords
+ const float2 uv = pixelPosition * rcp(transmittanceLUTRes);
+ float viewHeight;
+ float viewZenithCosAngle;
+ UvToLutTransmittanceParams(atmosphere, viewHeight, viewZenithCosAngle, uv);
+
+ // A few ekstra needed constants
+ float3 worldPosition = float3(0.0, 0.0, viewHeight);
+ float3 worldDirection = float3(0.0f, sqrt(1.0 - viewZenithCosAngle * viewZenithCosAngle), viewZenithCosAngle);
+ float3 sunDirection = GetSunDirection();
+ float3 sunIlluminance = GetSunEnergy() * GetSunColor();
+
+ SamplingParameters sampling;
+ {
+ sampling.variableSampleCount = false;
+ sampling.sampleCountIni = 40.0f; // Can go a low as 10 sample but energy lost starts to be visible.
+ }
+ const bool ground = false;
+ const float depthBufferWorldPos = 0.0;
+ const bool opaque = false;
+ const bool mieRayPhase = false;
+ const bool multiScatteringApprox = false;
+ SingleScatteringResult ss = IntegrateScatteredLuminance(
+ atmosphere, pixelPosition, worldPosition, worldDirection, sunDirection, sunIlluminance,
+ sampling, ground, depthBufferWorldPos, opaque, mieRayPhase, multiScatteringApprox, transmittanceLUT, multiScatteringLUT);
+
+ float3 transmittance = exp(-ss.opticalDepth);
+
+ output[DTid.xy] = float4(transmittance, 1.0);
+}
\ No newline at end of file
diff --git a/WickedEngine/skyHF.hlsli b/WickedEngine/skyHF.hlsli
index e77b7ebf0..8bd1e2896 100644
--- a/WickedEngine/skyHF.hlsli
+++ b/WickedEngine/skyHF.hlsli
@@ -1,256 +1,94 @@
#ifndef WI_SKY_HF
#define WI_SKY_HF
#include "globals.hlsli"
+#include "skyAtmosphere.hlsli"
-// This can enable realistic sky simulation (performance heavy)
-//#define REALISTIC_SKY
-
-// Accurate Atmosphere based on: https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/simulating-sky
// Custom Atmosphere based on: https://www.shadertoy.com/view/Ml2cWG
// Cloud noise based on: https://www.shadertoy.com/view/4tdSWr
-// warning X4122: sum of X and Y cannot be represented accurately in double precision
-#pragma warning( disable : 4122 )
-
-struct AtmosphericMedium
+float3 AccurateAtmosphericScattering(Texture2D skyViewLutTexture, Texture2D transmittanceLUT, Texture2D multiScatteringLUT, float3 rayOrigin, float3 rayDirection, float3 sunDirection, float sunEnergy, float3 sunColor, bool enableSun, bool darkMode, bool stationary)
{
- // Scattering
- float3 rayleighScattering; // Affects the color of the sky
- float3 mieScattering; // Affects the color of the blob around the sun
- float3 absorptionScattering; // What color gets absorbed by the atmosphere (due to things like ozone)
- float3 ambientScattering; // Affects the scattering color when there is no lighting from the sun
+ AtmosphereParameters atmosphere = GetAtmosphereParameters();
+
+ float3 worldDirection = rayDirection;
+
+ float3 skyRelativePosition = stationary ? float3(0.00001, 0.00001, 0.00001) : rayOrigin; // We get compiler warnings: "floating point division by zero" when stationary is true, but it gets handled by GetCameraPlanetPos anyway
+ float3 worldPosition = GetCameraPlanetPos(atmosphere, skyRelativePosition);
+
+ float viewHeight = length(worldPosition);
- // Scale heights
- float rayleighScaleHeight; // Rayleigh scale height
- float mieScaleHeight; // Mie scale height
- float absorptionScaleHeight; // Absorption scale height, at what height the absorption is at it's fullest
-
- // Etc.
- float mieEccentricity; // Mie preferred scattering direction
- float absorptionFalloff; // How much the absorption decreases the further away it gets from the maximum height
-};
+ float3 luminance = float3(0.0, 0.0, 0.0);
-inline AtmosphericMedium CreateAtmosphericScattering()
-{
- AtmosphericMedium medium;
-
- medium.rayleighScattering = float3(5.5e-6, 13.0e-6, 22.4e-6); // Causes a blue atmosphere for earth.
- medium.mieScattering = float3(21e-6, 21e-6, 21e-6);
- medium.absorptionScattering = float3(2.04e-5, 4.97e-5, 1.95e-6);
- medium.ambientScattering = float3(0.0, 0.0, 0.0); // Disabled by default
-
- medium.rayleighScaleHeight = 8e3;
- medium.mieScaleHeight = 1.2e3;
- medium.absorptionScaleHeight = 30e3; // Ozone layer starts around 30 km
-
- medium.mieEccentricity = 0.758;
- medium.absorptionFalloff = 3e3;
-
- return medium;
-}
-
-inline AtmosphericMedium CreateAtmosphericScattering(float3 rayleighScattering, float3 mieScattering, float3 absorptionScattering, float3 ambientScattering,
- float rayleighScaleHeight, float mieScaleHeight, float absorptionScaleHeight, float mieEccentricity, float absorptionFalloff)
-{
- AtmosphericMedium medium;
-
- medium.rayleighScattering = rayleighScattering;
- medium.mieScattering = mieScattering;
- medium.absorptionScattering = absorptionScattering;
- medium.ambientScattering = ambientScattering;
-
- medium.rayleighScaleHeight = rayleighScaleHeight;
- medium.mieScaleHeight = mieScaleHeight;
- medium.absorptionScaleHeight = absorptionScaleHeight;
-
- medium.mieEccentricity = mieEccentricity;
- medium.absorptionFalloff = absorptionFalloff;
-
- return medium;
-}
-
-bool TraceSphereIntersections(float3 rayOrigin, float3 rayDirection, float3 sphereCenter, float sphereRadius, inout float2 solutions)
-{
- float3 localPosition = rayOrigin - sphereCenter;
- float localPositionSqr = dot(localPosition, localPosition);
-
- // Quadratic Coefficients
- float a = dot(rayDirection, rayDirection);
- float b = 2 * dot(rayDirection, localPosition);
- float c = localPositionSqr - sphereRadius * sphereRadius;
-
- float discriminant = b * b - 4 * a * c;
-
- // Only continue if the ray intersects with the sphere
- if (discriminant >= 0.0)
- {
- float sqrtDiscriminant = sqrt(discriminant);
- solutions = (-b + float2(-1, 1) * sqrtDiscriminant) / (2 * a);
- return true;
- }
-
- return false;
-}
-
-// RayLeigh phase function
-float computeRayleighPhase(float cosTheta)
-{
- return 3.0 / (16.0 * PI) * (1.0 + cosTheta * cosTheta);
-}
-
-// Henyey Greenstein Phase
-// See http://www.pbr-book.org/3ed-2018/Volume_Scattering/Phase_Functions.html
-float computeMiePhase(float g, float cosTheta)
-{
- float gg = g * g;
- float a = (1.0 - gg) * (1.0 + cosTheta * cosTheta);
- float b = (2.0 + gg) * pow(abs(1.0 + gg - 2.0 * g * cosTheta), 1.5);
-
- return 3.0 / (8.0 * PI) * (a / b);
-}
-
-// AccurateAtmosphericScattering - WIP
-float3 AccurateAtmosphericScattering(float3 rayOrigin, float3 rayDirection, float3 sunDirection,
- float3 planetCenter, float planetRadius, float AtmosphereRadius, AtmosphericMedium medium, bool enableSun, bool darkMode)
-{
- const int numSteps = 16;
- const int numStepsLight = 8;
-
- float tMaxDistance = 1e12;
- float2 tBottomSolutions = 0.0; // Planet intersections
- if (TraceSphereIntersections(rayOrigin, rayDirection, planetCenter, planetRadius, tBottomSolutions))
- {
- // If we hit the planet
- if (0.0 < tBottomSolutions.y)
- {
- tMaxDistance = max(tBottomSolutions.x, 0.0);
- }
- }
-
- float2 tTopSolutions = 0.0; // Atmosphere intersections
- if (TraceSphereIntersections(rayOrigin, rayDirection, planetCenter, AtmosphereRadius, tTopSolutions))
- {
- // Make sure the ray is no longer than allowed
- tTopSolutions.y = min(tTopSolutions.y, tMaxDistance);
- tTopSolutions.x = max(tTopSolutions.x, 0.0);
- }
- else
- {
- return float3(0.0, 0.0, 0.0);
- }
-
- float stepSize = (tTopSolutions.y - tTopSolutions.x) / float(numSteps);
-
- float tCurrent = tTopSolutions.x;
- float tCurrentLight = 0.0;
-
- // How much light coming from the sun direction is scattered in direction V
- float cosTheta = dot(rayDirection, sunDirection);
-
- // Phase functions - Rayleigh and Mie
- float phaseRayleigh = computeRayleighPhase(cosTheta);
- float phaseMie = computeMiePhase(medium.mieEccentricity, cosTheta);
-
- float3 totalRayleigh = float3(0.0, 0.0, 0.0);
- float3 totalMie = float3(0.0, 0.0, 0.0);
-
- // Initialize optical depth accumulators for each ray - How much air was in the ray
- float3 opticalDepth = float3(0.0, 0.0, 0.0);
- float3 opticalDepthLight = float3(0.0, 0.0, 0.0);
-
- float2 scaleHeight = float2(medium.rayleighScaleHeight, medium.mieScaleHeight);
-
- // Planet location relative to the camera position
- float3 planetLocalPosition = rayOrigin - planetCenter;
-
- // Sample the primary ray.
-
- [loop]
- for (int i = 0; i < numSteps; i++)
- {
- float3 samplePosition = planetLocalPosition + rayDirection * (tCurrent + stepSize * 0.5);
+ const bool fastSky = true;
+ if (viewHeight < atmosphere.topRadius && fastSky)
+ {
+ float2 uv;
+ float3 upVector = normalize(worldPosition);
+ float viewZenithCosAngle = dot(worldDirection, upVector);
- float sampleHeight = length(samplePosition) - planetRadius;
-
- // Density of the particles for rayleigh and mie
- float3 density = float3(exp(-sampleHeight / scaleHeight), 0.0);
-
- // And the absorption density. This is for ozone, which scales together with the rayleigh.
- density.z = saturate((1.0 / cosh((medium.absorptionScaleHeight - sampleHeight) / medium.absorptionFalloff)) * density.x);
- density *= stepSize;
-
- opticalDepth += density;
-
- // Step size of light ray.
- float2 tLightSolutions;
- TraceSphereIntersections(samplePosition + planetCenter, sunDirection, planetCenter, AtmosphereRadius, tLightSolutions); // Undo planetLocalPosition to rayOrigin
- float stepSizeLight = tLightSolutions.y / float(numStepsLight);
+ float3 sideVector = normalize(cross(upVector, worldDirection)); // Assumes non parallel vectors
+ float3 forwardVector = normalize(cross(sideVector, upVector)); // Aligns toward the sun light but perpendicular to up vector
+ float2 lightOnPlane = float2(dot(sunDirection, forwardVector), dot(sunDirection, sideVector));
+ lightOnPlane = normalize(lightOnPlane);
+ float lightViewCosAngle = lightOnPlane.x;
- // Sample the secondary ray. (Light)
+ bool intersectGround = RaySphereIntersectNearest(worldPosition, worldDirection, float3(0, 0, 0), atmosphere.bottomRadius) >= 0.0f;
- [unroll]
- for (int j = 0; j < numStepsLight; j++)
- {
- float3 samplePositionLight = samplePosition + sunDirection * (tCurrentLight + stepSizeLight * 0.5);
-
- float sampleHeightLight = length(samplePositionLight) - planetRadius;
-
- // Calculate the particle density.
- float3 densityLight = float3(exp(-sampleHeightLight / scaleHeight), 0.0);
+ SkyViewLutParamsToUv(atmosphere, intersectGround, viewZenithCosAngle, lightViewCosAngle, viewHeight, uv);
+
+ luminance = skyViewLutTexture.SampleLevel(sampler_linear_clamp, uv, 0).rgb;
+ }
+ else
+ {
+ // Move to top atmosphere as the starting point for ray marching.
+ // This is critical to be after the above to not disrupt above atmosphere tests and voxel selection.
+ if (MoveToTopAtmosphere(worldPosition, worldDirection, atmosphere.topRadius))
+ {
+ // Apply the start offset after moving to the top of atmosphere to avoid black pixels
+ const float startOffsetKm = 0.1; // 100m seems enough for long distances
+ worldPosition += worldDirection * startOffsetKm;
- // And the absorption density.
- densityLight.z = saturate((1.0 / cosh((medium.absorptionScaleHeight - sampleHeightLight) / medium.absorptionFalloff)) * densityLight.x);
- densityLight *= stepSizeLight;
-
- opticalDepthLight += densityLight;
-
- tCurrentLight += stepSizeLight;
- }
-
- // Calculate attenuation. How much light reaches the current sample point due to scattering
- float3 tauRayleigh = medium.rayleighScattering * (opticalDepth.x + opticalDepthLight.x);
- float3 tauMie = medium.mieScattering * (opticalDepth.y + opticalDepthLight.y);
- float3 tauAbsorption = medium.absorptionScattering * (opticalDepth.z + opticalDepthLight.z);
-
- float3 attenuation = exp(-(tauMie + tauRayleigh + tauAbsorption));
-
- totalRayleigh += density.x * attenuation;
- totalMie += density.y * attenuation;
-
- tCurrent += stepSize;
- }
+ float3 sunIlluminance = sunEnergy * sunColor;
+
+ SamplingParameters sampling;
+ {
+ sampling.variableSampleCount = true;
+ sampling.sampleCountIni = 0.0f;
+ sampling.rayMarchMinMaxSPP = float2(4, 14);
+ sampling.distanceSPPMaxInv = 0.01;
+ }
+ const bool ground = false;
+ const float depthBufferWorldPos = 0.0;
+ const bool opaque = false;
+ const bool mieRayPhase = true;
+ const bool multiScatteringApprox = true;
+ const float2 pixPos = float2(0, 0);
+ SingleScatteringResult ss = IntegrateScatteredLuminance(
+ atmosphere, pixPos, worldPosition, worldDirection, sunDirection, sunIlluminance,
+ sampling, ground, depthBufferWorldPos, opaque, mieRayPhase, multiScatteringApprox, transmittanceLUT, multiScatteringLUT);
+
+ luminance = ss.L;
+ }
+ }
+
+ float3 totalColor = float3(0.0, 0.0, 0.0);
+
+ if (enableSun)
+ {
+ float3 sunIlluminance = sunEnergy * sunColor;
+ totalColor = luminance + GetSunLuminance(worldPosition, worldDirection, sunDirection, sunIlluminance, atmosphere, transmittanceLUT);
+ }
+ else
+ {
+ totalColor = luminance; // We cant really seperate mie from luminance due to precomputation, todo?
+ }
- // Calculate how much light can pass through the atmosphere. Can be utilized when scene data is available.
- //float3 transmittance = exp(-(medium.rayleighScattering * opticalDepth.x + medium.mieScattering * opticalDepth.y + medium.absorptionScattering * opticalDepth.z));
+ if (darkMode)
+ {
+ totalColor = max(pow(saturate(dot(sunDirection, rayDirection)), 64) * sunColor, 0) * luminance * 1.0;
+ }
- float3 rayleigh = phaseRayleigh * medium.rayleighScattering * totalRayleigh;
- float3 mie = phaseMie * medium.mieScattering * totalMie;
- float3 ambient = opticalDepth.x * medium.ambientScattering;
-
- float sunIntensity = GetSunEnergy();
- float3 sunColor = GetSunColor();
-
- float3 totalColor = float3(0, 0, 0);
-
- if (enableSun)
- {
- const float maxSunDiscIntensity = 20.0;
- const float discAmount = distance(rayDirection, sunDirection); // sun falloff descreasing from mid point
- const float3 sun = smoothstep(0.03, 0.026, discAmount) * sunColor * min(sunIntensity, maxSunDiscIntensity); // sun disc
-
- totalColor = sunIntensity * 2.0 * (rayleigh + mie + ambient) + sun; // Calculate and return the final color.
- }
- else
- {
- totalColor = sunIntensity * 2.0 * (rayleigh + ambient); // Exclude mie when sun disc is not enabled.
- }
-
- if (darkMode)
- {
- totalColor = max(pow(saturate(dot(sunDirection, rayDirection)), 64) * sunColor, 0) * mie * 150.0f;
- }
-
- return totalColor;
+ return totalColor;
}
float2 hash(float2 p)
@@ -402,51 +240,54 @@ void CalculateClouds(inout float3 sky, float3 V, bool dark_enabled)
// Returns sky color modulated by the sun and clouds
// V : view direction
-float3 GetDynamicSkyColor(in float3 V, bool sun_enabled = true, bool clouds_enabled = true, bool dark_enabled = false)
+float3 GetDynamicSkyColor(in float3 V, bool sun_enabled = true, bool clouds_enabled = true, bool dark_enabled = false, bool realistic_sky_stationary = false)
{
- if (g_xFrame_Options & OPTION_BIT_SIMPLE_SKY)
- {
- return lerp(GetHorizonColor(), GetZenithColor(), saturate(V.y * 0.5f + 0.5f));
- }
+ if (g_xFrame_Options & OPTION_BIT_SIMPLE_SKY)
+ {
+ return lerp(GetHorizonColor(), GetZenithColor(), saturate(V.y * 0.5f + 0.5f));
+ }
- const float3 sunDirection = GetSunDirection();
- const float3 sunColor = GetSunColor();
- const float sunEnergy = GetSunEnergy();
+ const float3 sunDirection = GetSunDirection();
+ const float3 sunColor = GetSunColor();
+ const float sunEnergy = GetSunEnergy();
- float3 sky = float3(0, 0, 0);
+ float3 sky = float3(0, 0, 0);
-#ifdef REALISTIC_SKY
- AtmosphericMedium medium = CreateAtmosphericScattering();
+ if (g_xFrame_Options & OPTION_BIT_REALISTIC_SKY)
+ {
+ sky = AccurateAtmosphericScattering
+ (
+ texture_skyviewlut, // Sky View Lut (combination of precomputed atmospheric LUTs)
+ texture_transmittancelut,
+ texture_multiscatteringlut,
+ g_xCamera_CamPos, // Ray origin
+ V, // Ray direction
+ sunDirection, // Position of the sun
+ sunEnergy, // Sun energy
+ sunColor, // Sun Color
+ sun_enabled, // Use sun and total
+ dark_enabled, // Enable dark mode for light shafts etc.
+ realistic_sky_stationary // Fixed position for ambient and environment capture.
+ );
+ }
+ else
+ {
+ sky = CustomAtmosphericScattering
+ (
+ V, // normalized ray direction
+ sunDirection, // position of the sun
+ sunColor, // color of the sun, for disc
+ sun_enabled, // use sun and total
+ dark_enabled // enable dark mode for light shafts etc.
+ );
+ }
- sky = AccurateAtmosphericScattering
- (
- g_xCamera_CamPos, // Ray origin
- V, // Ray direction
- sunDirection, // Position of the sun
- float3(0.0, -6372e3, 0.0), // Center of the planet
- 6371e3, // Radius of the planet in meters
- 6471e3, // Radius of the atmosphere in meters
- medium, // Atmospheric medium constructor.
- sun_enabled, // Use sun and total
- dark_enabled // Enable dark mode for light shafts etc.
- );
-#else
- sky = CustomAtmosphericScattering
- (
- V, // normalized ray direction
- sunDirection, // position of the sun
- sunColor, // color of the sun, for disc
- sun_enabled, // use sun and total
- dark_enabled // enable dark mode for light shafts etc.
- );
-#endif // REALISTIC_SKY
+ if (clouds_enabled)
+ {
+ CalculateClouds(sky, V, dark_enabled);
+ }
- if (clouds_enabled)
- {
- CalculateClouds(sky, V, dark_enabled);
- }
-
- return sky;
+ return sky;
}
diff --git a/WickedEngine/ssr_temporalCS.hlsl b/WickedEngine/ssr_temporalCS.hlsl
index bdd18687b..ff94607ab 100644
--- a/WickedEngine/ssr_temporalCS.hlsl
+++ b/WickedEngine/ssr_temporalCS.hlsl
@@ -8,7 +8,7 @@ TEXTURE2D(resolve_history, float4, TEXSLOT_ONDEMAND1);
RWTEXTURE2D(output, float4, 0);
static const float temporalResponseMin = 0.85;
-static const float temporalResponseMax = 1.0f;
+static const float temporalResponseMax = 0.98f;
static const float temporalScale = 2.0;
static const float temporalExposure = 10.0f;
@@ -131,19 +131,8 @@ void main(uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID, uint3
previous.a = clamp(previous.a, currentMin.a, currentMax.a);
// Blend color & history
- // Feedback weight from unbiased luminance difference (Timothy Lottes)
- float lumFiltered = Luminance(current.rgb); // Luma4(current.rgb)
- float lumHistory = Luminance(previous.rgb);
-
- float lumDifference = abs(lumFiltered - lumHistory) / max(lumFiltered, max(lumHistory, 0.2f));
- float lumWeight = sqr(1.0f - lumDifference);
- float blendFinal = lerp(temporalResponseMin, temporalResponseMax, lumWeight);
-
- // Reduce ghosting by refreshing the blend by velocity (Unreal)
- float2 velocityScreen = customVelocity * xPPResolution;
- float velocityBlend = sqrt(dot(velocityScreen, velocityScreen));
- blendFinal = lerp(blendFinal, 0.2, saturate(velocityBlend / 100.0));
+ float blendFinal = lerp(temporalResponseMin, temporalResponseMax, saturate(1.0 - length(velocity) * 100));
float4 result = lerp(current, previous, blendFinal);
diff --git a/WickedEngine/wiEnums.h b/WickedEngine/wiEnums.h
index ded45f05b..f5f71a5c4 100644
--- a/WickedEngine/wiEnums.h
+++ b/WickedEngine/wiEnums.h
@@ -107,6 +107,9 @@ enum TEXTYPES
TEXTYPE_3D_VOXELRADIANCE_HELPER,
TEXTYPE_2D_IMPOSTORARRAY,
TEXTYPE_CUBEARRAY_ENVMAPARRAY,
+ TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT,
+ TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT,
+ TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT,
TEXTYPE_COUNT
};
@@ -255,6 +258,9 @@ enum CSTYPES
CSTYPE_VOXELSCENECOPYCLEAR_TEMPORALSMOOTHING,
CSTYPE_VOXELRADIANCESECONDARYBOUNCE,
CSTYPE_VOXELCLEARONLYNORMAL,
+ CSTYPE_SKYATMOSPHERE_TRANSMITTANCELUT,
+ CSTYPE_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT,
+ CSTYPE_SKYATMOSPHERE_SKYVIEWLUT,
CSTYPE_GENERATEMIPCHAIN2D_UNORM4,
CSTYPE_GENERATEMIPCHAIN2D_FLOAT4,
CSTYPE_GENERATEMIPCHAIN3D_UNORM4,
diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp
index 1d7ff7549..e0252ff4e 100644
--- a/WickedEngine/wiRenderer.cpp
+++ b/WickedEngine/wiRenderer.cpp
@@ -1167,6 +1167,9 @@ void LoadShaders()
wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_VOXELSCENECOPYCLEAR_TEMPORALSMOOTHING], "voxelSceneCopyClearCS_TemporalSmoothing.cso"); });
wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_VOXELRADIANCESECONDARYBOUNCE], "voxelRadianceSecondaryBounceCS.cso"); });
wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_VOXELCLEARONLYNORMAL], "voxelClearOnlyNormalCS.cso"); });
+ wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_SKYATMOSPHERE_TRANSMITTANCELUT], "skyAtmosphere_transmittanceLutCS.cso"); });
+ wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT], "skyAtmosphere_multiScatteredLuminanceLutCS.cso"); });
+ wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_SKYATMOSPHERE_SKYVIEWLUT], "skyAtmosphere_skyViewLutCS.cso"); });
wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_GENERATEMIPCHAIN2D_UNORM4], "generateMIPChain2DCS_unorm4.cso"); });
wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_GENERATEMIPCHAIN2D_FLOAT4], "generateMIPChain2DCS_float4.cso"); });
wiJobSystem::Execute(ctx, [](wiJobArgs args) { LoadShader(CS, computeShaders[CSTYPE_GENERATEMIPCHAIN3D_UNORM4], "generateMIPChain3DCS_unorm4.cso"); });
@@ -2817,6 +2820,9 @@ void BindEnvironmentTextures(SHADERSTAGE stage, CommandList cmd)
device->BindResource(stage, &textures[TEXTYPE_CUBEARRAY_ENVMAPARRAY], TEXSLOT_ENVMAPARRAY, cmd);
device->BindResource(stage, &textures[TEXTYPE_CUBEARRAY_ENVMAPARRAY], TEXSLOT_ENVMAPARRAY, cmd);
+ device->BindResource(stage, &textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT], TEXSLOT_SKYVIEWLUT, cmd);
+ device->BindResource(stage, &textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT], TEXSLOT_TRANSMITTANCELUT, cmd);
+ device->BindResource(stage, &textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT], TEXSLOT_MULTISCATTERINGLUT, cmd);
device->BindResource(stage, GetVoxelRadianceSecondaryBounceEnabled() ? &textures[TEXTYPE_3D_VOXELRADIANCE_HELPER] : &textures[TEXTYPE_3D_VOXELRADIANCE], TEXSLOT_VOXELRADIANCE, cmd);
if (GetScene().weather.skyMap != nullptr)
@@ -3937,6 +3943,12 @@ void UpdateRenderData(CommandList cmd)
pendingMaterialUpdates.clear();
+ if (scene.weather.IsRealisticSky())
+ {
+ // Render Atmospheric Scattering textures for lighting and sky
+ RenderAtmosphericScatteringTextures(cmd);
+ }
+
const FrameCulling& mainCameraCulling = frameCullings.at(&GetCamera());
// Fill Entity Array with decals + envprobes + lights in the frustum:
@@ -6527,6 +6539,139 @@ void DrawDebugWorld(const CameraComponent& camera, CommandList cmd)
device->EventEnd(cmd);
}
+
+void RenderAtmosphericScatteringTextures(CommandList cmd)
+{
+ GraphicsDevice* device = GetDevice();
+
+ device->EventBegin("ComputeAtmosphericScatteringTextures", cmd);
+ auto range = wiProfiler::BeginRangeGPU("Atmospheric Scattering Textures", cmd);
+
+ GPUBarrier memory_barrier = GPUBarrier::Memory();
+
+ if (!textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT].IsValid())
+ {
+ TextureDesc desc;
+ desc.type = TextureDesc::TEXTURE_2D;
+ desc.Width = 256;
+ desc.Height = 64;
+ desc.Format = FORMAT_R16G16B16A16_FLOAT;
+ desc.BindFlags = BIND_SHADER_RESOURCE | BIND_UNORDERED_ACCESS;
+ device->CreateTexture(&desc, nullptr, &textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT]);
+ }
+ if (!textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT].IsValid())
+ {
+ TextureDesc desc;
+ desc.type = TextureDesc::TEXTURE_2D;
+ desc.Width = 32;
+ desc.Height = 32;
+ desc.Format = FORMAT_R16G16B16A16_FLOAT;
+ desc.BindFlags = BIND_SHADER_RESOURCE | BIND_UNORDERED_ACCESS;
+ device->CreateTexture(&desc, nullptr, &textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT]);
+ }
+ if (!textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT].IsValid())
+ {
+ TextureDesc desc;
+ desc.type = TextureDesc::TEXTURE_2D;
+ desc.Width = 192;
+ desc.Height = 104;
+ desc.Format = FORMAT_R16G16B16A16_FLOAT;
+ desc.BindFlags = BIND_SHADER_RESOURCE | BIND_UNORDERED_ACCESS;
+ device->CreateTexture(&desc, nullptr, &textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT]);
+ }
+
+ // Transmittance Lut pass:
+ {
+ device->EventBegin("TransmittanceLut", cmd);
+ device->BindComputeShader(&computeShaders[CSTYPE_SKYATMOSPHERE_TRANSMITTANCELUT], cmd);
+
+ device->BindResource(CS, &textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT], TEXSLOT_ONDEMAND0, cmd); // empty
+ device->BindResource(CS, &textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT], TEXSLOT_ONDEMAND1, cmd); // empty
+
+ const GPUResource* uavs[] = {
+ &textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT],
+ };
+ device->BindUAVs(CS, uavs, 0, arraysize(uavs), cmd);
+
+ const int threadSize = 8;
+ const int transmittanceLutWidth = textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT].GetDesc().Width;
+ const int transmittanceLutHeight = textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT].GetDesc().Height;
+ const int transmittanceLutThreadX = static_cast(std::ceil(transmittanceLutWidth / threadSize));
+ const int transmittanceLutThreadY = static_cast(std::ceil(transmittanceLutHeight / threadSize));
+
+ device->Dispatch(transmittanceLutThreadX, transmittanceLutThreadY, 1, cmd);
+
+ device->Barrier(&memory_barrier, 1, cmd);
+ device->UnbindUAVs(0, arraysize(uavs), cmd);
+ device->EventEnd(cmd);
+ }
+
+ // MultiScattered Luminance Lut pass:
+ {
+ device->EventBegin("MultiScatteredLuminanceLut", cmd);
+ device->BindComputeShader(&computeShaders[CSTYPE_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT], cmd);
+
+ // Use transmittance from previous pass
+ device->BindResource(CS, &textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT], TEXSLOT_ONDEMAND0, cmd);
+ device->BindResource(CS, &textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT], TEXSLOT_ONDEMAND1, cmd);
+
+ const GPUResource* uavs[] = {
+ &textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT],
+ };
+ device->BindUAVs(CS, uavs, 0, arraysize(uavs), cmd);
+
+ const int multiScatteredLutWidth = textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT].GetDesc().Width;
+ const int multiScatteredLutHeight = textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT].GetDesc().Height;
+
+ device->Dispatch(multiScatteredLutWidth, multiScatteredLutHeight, 1, cmd);
+
+ device->Barrier(&memory_barrier, 1, cmd);
+ device->UnbindUAVs(0, arraysize(uavs), cmd);
+ device->EventEnd(cmd);
+ }
+
+ device->EventEnd(cmd);
+
+ RefreshAtmosphericScatteringTextures(cmd);
+
+ wiProfiler::EndRange(range);
+}
+void RefreshAtmosphericScatteringTextures(CommandList cmd)
+{
+ GraphicsDevice* device = GetDevice();
+
+ device->EventBegin("UpdateAtmosphericScatteringTextures", cmd);
+
+ GPUBarrier memory_barrier = GPUBarrier::Memory();
+
+ // Sky View Lut pass:
+ {
+ device->EventBegin("SkyViewLut", cmd);
+ device->BindComputeShader(&computeShaders[CSTYPE_SKYATMOSPHERE_SKYVIEWLUT], cmd);
+
+ device->BindResource(CS, &textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT], TEXSLOT_ONDEMAND0, cmd);
+ device->BindResource(CS, &textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT], TEXSLOT_ONDEMAND1, cmd);
+
+ const GPUResource* uavs[] = {
+ &textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT],
+ };
+ device->BindUAVs(CS, uavs, 0, arraysize(uavs), cmd);
+
+ const int threadSize = 8;
+ const int skyViewLutWidth = textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT].GetDesc().Width;
+ const int skyViewLutHeight = textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT].GetDesc().Height;
+ const int skyViewLutThreadX = static_cast(std::ceil(skyViewLutWidth / threadSize));
+ const int skyViewLutThreadY = static_cast(std::ceil(skyViewLutHeight / threadSize));
+
+ device->Dispatch(skyViewLutThreadX, skyViewLutThreadY, 1, cmd);
+
+ device->Barrier(&memory_barrier, 1, cmd);
+ device->UnbindUAVs(0, arraysize(uavs), cmd);
+ device->EventEnd(cmd);
+ }
+
+ device->EventEnd(cmd);
+}
void DrawSky(CommandList cmd)
{
GraphicsDevice* device = GetDevice();
@@ -6542,6 +6687,9 @@ void DrawSky(CommandList cmd)
else
{
device->BindPipelineState(&PSO_sky[SKYRENDERING_DYNAMIC], cmd);
+ device->BindResource(PS, &textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT], TEXSLOT_SKYVIEWLUT, cmd);
+ device->BindResource(PS, &textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT], TEXSLOT_TRANSMITTANCELUT, cmd);
+ device->BindResource(PS, &textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT], TEXSLOT_MULTISCATTERINGLUT, cmd);
}
BindConstantBuffers(VS, cmd);
@@ -6559,6 +6707,10 @@ void DrawSun(CommandList cmd)
device->BindPipelineState(&PSO_sky[SKYRENDERING_SUN], cmd);
+ device->BindResource(PS, &textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT], TEXSLOT_SKYVIEWLUT, cmd);
+ device->BindResource(PS, &textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT], TEXSLOT_TRANSMITTANCELUT, cmd);
+ device->BindResource(PS, &textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT], TEXSLOT_MULTISCATTERINGLUT, cmd);
+
BindConstantBuffers(VS, cmd);
BindConstantBuffers(PS, cmd);
@@ -6784,6 +6936,17 @@ void RefreshEnvProbes(CommandList cmd)
device->RenderPassBegin(&renderpasses_envmap[probe.textureIndex], cmd);
+ if (scene.weather.IsRealisticSky())
+ {
+ // Refresh atmospheric textures, since each probe has different positions
+ RefreshAtmosphericScatteringTextures(cmd);
+ }
+
+ // Bind the atmospheric textures, as lighting and sky needs them
+ device->BindResource(PS, &textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT], TEXSLOT_SKYVIEWLUT, cmd);
+ device->BindResource(PS, &textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT], TEXSLOT_TRANSMITTANCELUT, cmd);
+ device->BindResource(PS, &textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT], TEXSLOT_MULTISCATTERINGLUT, cmd);
+
if (!renderQueue.empty())
{
BindShadowmaps(PS, cmd);
@@ -7938,6 +8101,12 @@ void RayTraceScene(
{
device->BindResource(CS, scene.weather.skyMap->texture, TEXSLOT_GLOBALENVMAP, cmd);
}
+ else
+ {
+ device->BindResource(CS, &textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT], TEXSLOT_SKYVIEWLUT, cmd);
+ device->BindResource(CS, &textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT], TEXSLOT_TRANSMITTANCELUT, cmd);
+ device->BindResource(CS, &textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT], TEXSLOT_MULTISCATTERINGLUT, cmd);
+ }
const XMFLOAT4& halton = wiMath::GetHaltonSequence((int)GetDevice()->GetFrameCount());
RaytracingCB cb;
@@ -8493,6 +8662,18 @@ void RenderObjectLightMap(const ObjectComponent& object, CommandList cmd)
device->BindConstantBuffer(PS, &constantBuffers[CBTYPE_RAYTRACE], CB_GETBINDSLOT(RaytracingCB), cmd);
device->BindPipelineState(&PSO_renderlightmap, cmd);
+
+ if (scene.weather.skyMap != nullptr)
+ {
+ device->BindResource(PS, scene.weather.skyMap->texture, TEXSLOT_GLOBALENVMAP, cmd);
+ }
+ else
+ {
+ device->BindResource(PS, &textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT], TEXSLOT_SKYVIEWLUT, cmd);
+ device->BindResource(PS, &textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT], TEXSLOT_TRANSMITTANCELUT, cmd);
+ device->BindResource(PS, &textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT], TEXSLOT_MULTISCATTERINGLUT, cmd);
+ }
+
device->DrawIndexedInstanced((uint32_t)mesh.indices.size(), 1, 0, 0, 0, cmd);
device->RenderPassEnd(cmd);
@@ -8740,6 +8921,10 @@ void UpdateFrameCB(CommandList cmd)
{
cb.g_xFrame_Options |= OPTION_BIT_SIMPLE_SKY;
}
+ if (scene.weather.IsRealisticSky())
+ {
+ cb.g_xFrame_Options |= OPTION_BIT_REALISTIC_SKY;
+ }
if (GetDevice()->CheckCapability(GRAPHICSDEVICE_CAPABILITY_RAYTRACING) && GetRaytracedShadowsEnabled())
{
cb.g_xFrame_Options |= OPTION_BIT_RAYTRACED_SHADOWS;
@@ -10210,6 +10395,9 @@ void Postprocess_RTReflection(
descriptorTable.resources.push_back({ TEXTURECUBEARRAY, TEXSLOT_SHADOWARRAY_TRANSPARENT });
descriptorTable.resources.push_back({ STRUCTUREDBUFFER, SBSLOT_ENTITYARRAY });
descriptorTable.resources.push_back({ STRUCTUREDBUFFER, SBSLOT_MATRIXARRAY });
+ descriptorTable.resources.push_back({ TEXTURE2D, TEXSLOT_SKYVIEWLUT });
+ descriptorTable.resources.push_back({ TEXTURE2D, TEXSLOT_TRANSMITTANCELUT });
+ descriptorTable.resources.push_back({ TEXTURE2D, TEXSLOT_MULTISCATTERINGLUT });
descriptorTable.resources.push_back({ ROOT_CONSTANTBUFFER, CB_GETBINDSLOT(FrameCB) });
descriptorTable.resources.push_back({ ROOT_CONSTANTBUFFER, CB_GETBINDSLOT(CameraCB) });
descriptorTable.resources.push_back({ ROOT_CONSTANTBUFFER, CB_GETBINDSLOT(PostProcessCB) });
@@ -10303,6 +10491,9 @@ void Postprocess_RTReflection(
device->WriteDescriptor(&descriptorTable, 8, 0, &shadowMapArray_Transparent);
device->WriteDescriptor(&descriptorTable, 9, 0, &resourceBuffers[RBTYPE_ENTITYARRAY]);
device->WriteDescriptor(&descriptorTable, 10, 0, &resourceBuffers[RBTYPE_MATRIXARRAY]);
+ device->WriteDescriptor(&descriptorTable, 11, 0, &textures[TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT]);
+ device->WriteDescriptor(&descriptorTable, 12, 0, &textures[TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT]);
+ device->WriteDescriptor(&descriptorTable, 13, 0, &textures[TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT]);
device->BindDescriptorTable(RAYTRACING, 0, &descriptorTable, cmd);
device->BindDescriptorTable(RAYTRACING, 1, &scene.descriptorTable, cmd);
device->BindRootDescriptor(RAYTRACING, 0, &constantBuffers[CBTYPE_FRAME], 0, cmd);
diff --git a/WickedEngine/wiRenderer.h b/WickedEngine/wiRenderer.h
index af690a97f..029a7d4d8 100644
--- a/WickedEngine/wiRenderer.h
+++ b/WickedEngine/wiRenderer.h
@@ -100,6 +100,10 @@ namespace wiRenderer
uint32_t flags = DRAWSCENE_OPAQUE
);
+ // Compute essential atmospheric scattering textures for skybox, fog and clouds
+ void RenderAtmosphericScatteringTextures(wiGraphics::CommandList cmd);
+ // Update atmospheric scattering primarily for environment probes.
+ void RefreshAtmosphericScatteringTextures(wiGraphics::CommandList cmd);
// Draw skydome centered to camera.
void DrawSky(wiGraphics::CommandList cmd);
// A black skydome will be draw with only the sun being visible on it
diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h
index 588fab49a..1f6a941b9 100644
--- a/WickedEngine/wiScene.h
+++ b/WickedEngine/wiScene.h
@@ -1004,14 +1004,17 @@ namespace wiScene
EMPTY = 0,
OCEAN_ENABLED = 1 << 0,
SIMPLE_SKY = 1 << 1,
+ REALISTIC_SKY = 1 << 2,
};
uint32_t _flags = EMPTY;
inline bool IsOceanEnabled() const { return _flags & OCEAN_ENABLED; }
inline bool IsSimpleSky() const { return _flags & SIMPLE_SKY; }
+ inline bool IsRealisticSky() const { return _flags & REALISTIC_SKY; }
inline void SetOceanEnabled(bool value = true) { if (value) { _flags |= OCEAN_ENABLED; } else { _flags &= ~OCEAN_ENABLED; } }
inline void SetSimpleSky(bool value = true) { if (value) { _flags |= SIMPLE_SKY; } else { _flags &= ~SIMPLE_SKY; } }
+ inline void SetRealisticSky(bool value = true) { if (value) { _flags |= REALISTIC_SKY; } else { _flags &= ~REALISTIC_SKY; } }
XMFLOAT3 sunColor = XMFLOAT3(0, 0, 0);
XMFLOAT3 sunDirection = XMFLOAT3(0, 1, 0);