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);