diff --git a/Editor/TerrainWindow.cpp b/Editor/TerrainWindow.cpp index 66a0652743..ce52c52a3a 100644 --- a/Editor/TerrainWindow.cpp +++ b/Editor/TerrainWindow.cpp @@ -1790,6 +1790,8 @@ void TerrainWindow::SetupAssets() transform.Translate(XMFLOAT3(0, 4, 0)); } + // currentScene.EnsureMoonLight(currentScene.weather); + presetCombo.SetSelected(0); editor->paintToolWnd.RecreateTerrainMaterialButtons(); diff --git a/Editor/WeatherWindow.cpp b/Editor/WeatherWindow.cpp index 50e7c389ae..15559283fe 100644 --- a/Editor/WeatherWindow.cpp +++ b/Editor/WeatherWindow.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "WeatherWindow.h" +#include using namespace wi::ecs; using namespace wi::scene; @@ -9,7 +10,7 @@ void WeatherWindow::Create(EditorComponent* _editor) { editor = _editor; wi::gui::Window::Create(ICON_WEATHER " Weather", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE | wi::gui::Window::WindowControls::FIT_ALL_WIDGETS_VERTICAL); - SetSize(XMFLOAT2(660, 2140)); + SetSize(XMFLOAT2(660, 2260)); closeButton.SetTooltip("Delete WeatherComponent"); OnClose([=](wi::gui::EventArgs args) { @@ -63,6 +64,7 @@ void WeatherWindow::Create(EditorComponent* _editor) colorComboBox.AddItem("Cloud extinction 1"); colorComboBox.AddItem("Cloud extinction 2"); colorComboBox.AddItem("Rain color"); + colorComboBox.AddItem("Moon color"); colorComboBox.SetTooltip("Choose the destination data of the color picker."); colorComboBox.SetMaxVisibleItemCount(100); AddWidget(&colorComboBox); @@ -106,6 +108,11 @@ void WeatherWindow::Create(EditorComponent* _editor) case 9: weather.rain_color = args.color.toFloat4(); break; + case 10: + weather.moonColor = args.color.toFloat3(); + editor->GetCurrentScene().EnsureMoonLight(weather); + InvalidateProbes(); + break; } }); AddWidget(&colorPicker); @@ -233,6 +240,161 @@ void WeatherWindow::Create(EditorComponent* _editor) }); AddWidget(&starsSlider); + sunEclipseAutoCheckBox.Create("Auto Solar Eclipse"); + sunEclipseAutoCheckBox.SetTooltip("Automatically attenuate the sun when the moon passes in front of it."); + sunEclipseAutoCheckBox.SetSize(XMFLOAT2(hei, hei)); + sunEclipseAutoCheckBox.SetPos(XMFLOAT2(x, y += step)); + sunEclipseAutoCheckBox.OnClick([this](wi::gui::EventArgs args) { + auto& weather = GetWeather(); + weather.sunEclipseAutomatic = args.bValue; + sunEclipseSlider.SetEnabled(!args.bValue); + InvalidateProbes(); + }); + AddWidget(&sunEclipseAutoCheckBox); + + sunEclipseSlider.Create(0.0f, 1.0f, 0.0f, 1000, "Solar Eclipse: "); + sunEclipseSlider.SetTooltip("Fraction of the sun obscured by the moon (0 = none, 1 = total eclipse). Disabled when automation is enabled."); + sunEclipseSlider.SetSize(XMFLOAT2(wid, hei)); + sunEclipseSlider.SetPos(XMFLOAT2(x, y += step)); + sunEclipseSlider.OnSlide([this](wi::gui::EventArgs args) { + auto& weather = GetWeather(); + weather.sunEclipseStrength = args.fValue; + InvalidateProbes(); + }); + AddWidget(&sunEclipseSlider); + + moonAzimuthSlider.Create(0, 360, 0, 10000, "Moon Azimuth: "); + moonAzimuthSlider.SetTooltip("Horizontal moon orientation in degrees (0° faces +Z)."); + moonAzimuthSlider.SetSize(XMFLOAT2(wid, hei)); + moonAzimuthSlider.SetPos(XMFLOAT2(x, y += step)); + moonAzimuthSlider.OnSlide([this](wi::gui::EventArgs args) { + UpdateMoonDirection(); + }); + AddWidget(&moonAzimuthSlider); + + moonElevationSlider.Create(-90, 90, -10, 10000, "Moon Elevation: "); + moonElevationSlider.SetTooltip("Vertical moon orientation in degrees (0° on horizon)."); + moonElevationSlider.SetSize(XMFLOAT2(wid, hei)); + moonElevationSlider.SetPos(XMFLOAT2(x, y += step)); + moonElevationSlider.OnSlide([this](wi::gui::EventArgs args) { + UpdateMoonDirection(); + }); + AddWidget(&moonElevationSlider); + + moonSizeSlider.Create(0.05f, 5.0f, 0.5f, 10000, "Moon Size (deg): "); + moonSizeSlider.SetTooltip("Apparent angular size of the moon."); + moonSizeSlider.SetSize(XMFLOAT2(wid, hei)); + moonSizeSlider.SetPos(XMFLOAT2(x, y += step)); + moonSizeSlider.OnSlide([this](wi::gui::EventArgs args) { + GetWeather().moonSize = wi::math::DegreesToRadians(args.fValue); + }); + AddWidget(&moonSizeSlider); + + moonGlowSizeSlider.Create(0.0f, 20.0f, 2.0f, 10000, "Moon Glow (deg): "); + moonGlowSizeSlider.SetTooltip("Additional halo radius around the moon."); + moonGlowSizeSlider.SetSize(XMFLOAT2(wid, hei)); + moonGlowSizeSlider.SetPos(XMFLOAT2(x, y += step)); + moonGlowSizeSlider.OnSlide([this](wi::gui::EventArgs args) { + GetWeather().moonGlowSize = wi::math::DegreesToRadians(args.fValue); + }); + AddWidget(&moonGlowSizeSlider); + + moonGlowSharpnessSlider.Create(0.2f, 8.0f, 2.0f, 10000, "Moon Glow Sharpness: "); + moonGlowSharpnessSlider.SetTooltip("Controls how quickly the halo fades."); + moonGlowSharpnessSlider.SetSize(XMFLOAT2(wid, hei)); + moonGlowSharpnessSlider.SetPos(XMFLOAT2(x, y += step)); + moonGlowSharpnessSlider.OnSlide([this](wi::gui::EventArgs args) { + GetWeather().moonGlowSharpness = args.fValue; + }); + AddWidget(&moonGlowSharpnessSlider); + + moonGlowIntensitySlider.Create(0.0f, 2.0f, 0.25f, 10000, "Moon Glow Intensity: "); + moonGlowIntensitySlider.SetTooltip("Halo brightness multiplier."); + moonGlowIntensitySlider.SetSize(XMFLOAT2(wid, hei)); + moonGlowIntensitySlider.SetPos(XMFLOAT2(x, y += step)); + moonGlowIntensitySlider.OnSlide([this](wi::gui::EventArgs args) { + GetWeather().moonGlowIntensity = args.fValue; + }); + AddWidget(&moonGlowIntensitySlider); + + moonLightIntensitySlider.Create(0.0f, 5.0f, 0.05f, 1000, "Moon Light Intensity: "); + moonLightIntensitySlider.SetTooltip("Illuminance emitted by the moon directional light (in lux). Set to 0 to disable moon lighting."); + moonLightIntensitySlider.SetSize(XMFLOAT2(wid, hei)); + moonLightIntensitySlider.SetPos(XMFLOAT2(x, y += step)); + moonLightIntensitySlider.OnSlide([this](wi::gui::EventArgs args) { + auto& weather = GetWeather(); + weather.moonLightIntensity = args.fValue; + editor->GetCurrentScene().EnsureMoonLight(weather); + InvalidateProbes(); + }); + AddWidget(&moonLightIntensitySlider); + + moonEclipseAutoCheckBox.Create("Auto Moon Eclipse"); + moonEclipseAutoCheckBox.SetTooltip("Automatically darken the moon when it enters Earth's shadow."); + moonEclipseAutoCheckBox.SetSize(XMFLOAT2(hei, hei)); + moonEclipseAutoCheckBox.SetPos(XMFLOAT2(x, y += step)); + moonEclipseAutoCheckBox.OnClick([this](wi::gui::EventArgs args) { + auto& weather = GetWeather(); + weather.moonEclipseAutomatic = args.bValue; + moonEclipseSlider.SetEnabled(!args.bValue); + editor->GetCurrentScene().EnsureMoonLight(weather); + InvalidateProbes(); + }); + AddWidget(&moonEclipseAutoCheckBox); + + moonEclipseSlider.Create(0.0f, 1.0f, 0.0f, 1000, "Moon Eclipse: "); + moonEclipseSlider.SetTooltip("Fraction of the moon darkened by Earth's shadow (0 = none, 1 = total eclipse). Disabled when automation is enabled."); + moonEclipseSlider.SetSize(XMFLOAT2(wid, hei)); + moonEclipseSlider.SetPos(XMFLOAT2(x, y += step)); + moonEclipseSlider.OnSlide([this](wi::gui::EventArgs args) { + auto& weather = GetWeather(); + weather.moonEclipseStrength = args.fValue; + editor->GetCurrentScene().EnsureMoonLight(weather); + InvalidateProbes(); + }); + AddWidget(&moonEclipseSlider); + + moonTextureButton.Create("Load Moon Texture"); + moonTextureButton.SetTooltip("Load a dedicated texture for the moon disk. Click again to clear."); + moonTextureButton.SetSize(XMFLOAT2(mod_wid, hei)); + moonTextureButton.SetPos(XMFLOAT2(mod_x, y += step)); + moonTextureButton.OnClick([=](wi::gui::EventArgs args) { + auto& weather = GetWeather(); + if (!weather.moonTexture.IsValid()) + { + wi::helper::FileDialogParams params; + params.type = wi::helper::FileDialogParams::OPEN; + params.description = "Texture"; + params.extensions = wi::resourcemanager::GetSupportedImageExtensions(); + wi::helper::FileDialog(params, [=](std::string fileName) { + wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [=](uint64_t userdata) { + auto& weather = GetWeather(); + weather.moonTextureName = fileName; + weather.moonTexture = wi::resourcemanager::Load(fileName); + moonTextureButton.SetText(wi::helper::GetFileNameFromPath(fileName)); + InvalidateProbes(); + }); + }); + } + else + { + weather.moonTexture = {}; + weather.moonTextureName.clear(); + moonTextureButton.SetText("Load Moon Texture"); + InvalidateProbes(); + } + }); + AddWidget(&moonTextureButton); + + moonTextureMipBiasSlider.Create(-4.0f, 4.0f, 0.0f, 1000, "Moon Texture Mip Bias: "); + moonTextureMipBiasSlider.SetTooltip("Adjust mip bias when sampling the moon texture for sharper or softer results."); + moonTextureMipBiasSlider.SetSize(XMFLOAT2(wid, hei)); + moonTextureMipBiasSlider.SetPos(XMFLOAT2(x, y += step)); + moonTextureMipBiasSlider.OnSlide([this](wi::gui::EventArgs args) { + GetWeather().moonTextureMipBias = args.fValue; + }); + AddWidget(&moonTextureMipBiasSlider); + skyRotationSlider.Create(0, 360, 0, 10000, "Sky Texture Rotation: "); skyRotationSlider.SetTooltip("Rotate the sky texture horizontally. (If using a sky texture)"); skyRotationSlider.SetSize(XMFLOAT2(wid, hei)); @@ -1053,6 +1215,15 @@ void WeatherWindow::UpdateData() colorgradingButton.SetText(wi::helper::GetFileNameFromPath(weather.colorGradingMapName)); } + if (!weather.moonTextureName.empty()) + { + moonTextureButton.SetText(wi::helper::GetFileNameFromPath(weather.moonTextureName)); + } + else + { + moonTextureButton.SetText("Load Moon Texture"); + } + if (!weather.volumetricCloudsWeatherMapFirstName.empty()) { volumetricCloudsWeatherMapFirstButton.SetText(wi::helper::GetFileNameFromPath(weather.volumetricCloudsWeatherMapFirstName)); @@ -1075,6 +1246,37 @@ void WeatherWindow::UpdateData() windRandomnessSlider.SetValue(weather.windRandomness); skyExposureSlider.SetValue(weather.skyExposure); starsSlider.SetValue(weather.stars); + sunEclipseSlider.SetValue(weather.sunEclipseStrength); + sunEclipseAutoCheckBox.SetCheck(weather.sunEclipseAutomatic); + sunEclipseSlider.SetEnabled(!weather.sunEclipseAutomatic); + + { + XMFLOAT3 moonDir = weather.moonDirection; + if (wi::math::LengthSquared(moonDir) < 1e-6f) + { + moonDir = XMFLOAT3(0.0f, 0.5f, 0.8660254f); + } + XMVECTOR moonDirVec = XMVector3Normalize(XMLoadFloat3(&moonDir)); + XMFLOAT3 moonDirNorm; + XMStoreFloat3(&moonDirNorm, moonDirVec); + float azimuth = wi::math::RadiansToDegrees(std::atan2(moonDirNorm.x, moonDirNorm.z)); + if (azimuth < 0) + { + azimuth += 360.0f; + } + moonAzimuthSlider.SetValue(azimuth); + float elevation = wi::math::RadiansToDegrees(std::asin(wi::math::Clamp(moonDirNorm.y, -1.0f, 1.0f))); + moonElevationSlider.SetValue(elevation); + } + moonSizeSlider.SetValue(wi::math::RadiansToDegrees(weather.moonSize)); + moonGlowSizeSlider.SetValue(wi::math::RadiansToDegrees(weather.moonGlowSize)); + moonGlowSharpnessSlider.SetValue(weather.moonGlowSharpness); + moonGlowIntensitySlider.SetValue(weather.moonGlowIntensity); + moonLightIntensitySlider.SetValue(weather.moonLightIntensity); + moonEclipseSlider.SetValue(weather.moonEclipseStrength); + moonEclipseAutoCheckBox.SetCheck(weather.moonEclipseAutomatic); + moonEclipseSlider.SetEnabled(!weather.moonEclipseAutomatic); + moonTextureMipBiasSlider.SetValue(weather.moonTextureMipBias); skyRotationSlider.SetValue(wi::math::RadiansToDegrees(weather.sky_rotation)); rainAmountSlider.SetValue(weather.rain_amount); rainLengthSlider.SetValue(weather.rain_length); @@ -1116,6 +1318,9 @@ void WeatherWindow::UpdateData() case 9: colorPicker.SetPickColor(wi::Color::fromFloat4(weather.rain_color)); break; + case 10: + colorPicker.SetPickColor(wi::Color::fromFloat3(weather.moonColor)); + break; } realisticskyCheckBox.SetCheck(weather.IsRealisticSky()); @@ -1204,6 +1409,15 @@ void WeatherWindow::UpdateData() colorgradingButton.SetText("Load Color Grading LUT"); } + if (weather.moonTexture.IsValid()) + { + moonTextureButton.SetText(wi::helper::GetFileNameFromPath(weather.moonTextureName)); + } + else + { + moonTextureButton.SetText("Load Moon Texture"); + } + if (weather.volumetricCloudsWeatherMapFirst.IsValid()) { volumetricCloudsWeatherMapFirstButton.SetText(wi::helper::GetFileNameFromPath(weather.volumetricCloudsWeatherMapFirstName)); @@ -1230,6 +1444,16 @@ void WeatherWindow::UpdateData() scene.weather.horizon = default_sky_horizon; scene.weather.fogStart = std::numeric_limits::max(); scene.weather.fogDensity = 0; + scene.weather.sunEclipseStrength = 0.0f; + scene.weather.sunEclipseAutomatic = false; + scene.weather.moonColor = XMFLOAT3(0.04f, 0.04f, 0.05f); + scene.weather.moonDirection = XMFLOAT3(0.0f, 0.5f, 0.8660254f); + scene.weather.moonSize = 0.0095f; + scene.weather.moonGlowSize = 0.03f; + scene.weather.moonGlowSharpness = 2.0f; + scene.weather.moonGlowIntensity = 0.25f; + scene.weather.moonLightIntensity = 0.05f; + scene.weather.moonTextureMipBias = 0; } } @@ -1263,6 +1487,24 @@ void WeatherWindow::UpdateWind() XMStoreFloat3(&GetWeather().windDirection, dir); } +void WeatherWindow::UpdateMoonDirection() +{ + float azimuth = wi::math::DegreesToRadians(moonAzimuthSlider.GetValue()); + float elevation = wi::math::DegreesToRadians(moonElevationSlider.GetValue()); + float cosElev = std::cos(elevation); + XMFLOAT3 dir = { + std::sin(azimuth) * cosElev, + std::sin(elevation), + std::cos(azimuth) * cosElev + }; + XMVECTOR dirVec = XMVector3Normalize(XMLoadFloat3(&dir)); + XMFLOAT3 normalized; + XMStoreFloat3(&normalized, dirVec); + GetWeather().moonDirection = normalized; + editor->GetCurrentScene().EnsureMoonLight(GetWeather()); + InvalidateProbes(); +} + void WeatherWindow::ResizeLayout() { wi::gui::Window::ResizeLayout(); @@ -1291,6 +1533,19 @@ void WeatherWindow::ResizeLayout() layout.add(windRandomnessSlider); layout.add(skyExposureSlider); layout.add(starsSlider); + layout.add(sunEclipseAutoCheckBox); + layout.add(sunEclipseSlider); + layout.add(moonAzimuthSlider); + layout.add(moonElevationSlider); + layout.add(moonSizeSlider); + layout.add(moonGlowSizeSlider); + layout.add(moonGlowSharpnessSlider); + layout.add(moonGlowIntensitySlider); + layout.add(moonLightIntensitySlider); + layout.add(moonEclipseAutoCheckBox); + layout.add(moonEclipseSlider); + layout.add_fullwidth(moonTextureButton); + layout.add(moonTextureMipBiasSlider); layout.add(skyRotationSlider); layout.add(rainAmountSlider); layout.add(rainLengthSlider); diff --git a/Editor/WeatherWindow.h b/Editor/WeatherWindow.h index aacb7e541e..b2432d7046 100644 --- a/Editor/WeatherWindow.h +++ b/Editor/WeatherWindow.h @@ -4,6 +4,7 @@ class EditorComponent; class WeatherWindow : public wi::gui::Window { void UpdateWind(); + void UpdateMoonDirection(); public: void Create(EditorComponent* editor); @@ -34,7 +35,19 @@ class WeatherWindow : public wi::gui::Window wi::gui::Slider windRandomnessSlider; wi::gui::Slider skyExposureSlider; wi::gui::Slider starsSlider; + wi::gui::Slider sunEclipseSlider; + wi::gui::CheckBox sunEclipseAutoCheckBox; wi::gui::Slider skyRotationSlider; + wi::gui::Slider moonAzimuthSlider; + wi::gui::Slider moonElevationSlider; + wi::gui::Slider moonSizeSlider; + wi::gui::Slider moonGlowSizeSlider; + wi::gui::Slider moonGlowSharpnessSlider; + wi::gui::Slider moonGlowIntensitySlider; + wi::gui::Slider moonLightIntensitySlider; + wi::gui::Slider moonEclipseSlider; + wi::gui::CheckBox moonEclipseAutoCheckBox; + wi::gui::Slider moonTextureMipBiasSlider; wi::gui::Slider rainAmountSlider; wi::gui::Slider rainLengthSlider; wi::gui::Slider rainSpeedSlider; @@ -45,6 +58,7 @@ class WeatherWindow : public wi::gui::Window wi::gui::CheckBox realisticskyHighQualityCheckBox; wi::gui::CheckBox realisticskyReceiveShadowCheckBox; wi::gui::Button skyButton; + wi::gui::Button moonTextureButton; wi::gui::Button colorgradingButton; // ocean params: diff --git a/WickedEngine/ArchiveVersionHistory.txt b/WickedEngine/ArchiveVersionHistory.txt index 97d23220d7..2a1a3d028e 100644 --- a/WickedEngine/ArchiveVersionHistory.txt +++ b/WickedEngine/ArchiveVersionHistory.txt @@ -1,5 +1,12 @@ This file contains changelog of wi::Archive versions +100: serialized WeatherComponent sun eclipse controls +99: serialized WeatherComponent moon eclipse automation +98: serialized WeatherComponent moon eclipse strength +97: serialized WeatherComponent moon light intensity +96: serialized WeatherComponent moon texture mip bias +95: serialized WeatherComponent moon texture path +94: serialized WeatherComponent moon parameters 93: DDGI changed to store irradiance in spherical harmonics instead of octahedral atlas 92: added support for compressed archive 91: thumbnail image support for Archive diff --git a/WickedEngine/shaders/ShaderInterop_Weather.h b/WickedEngine/shaders/ShaderInterop_Weather.h index 53e04ae733..225cfbf767 100644 --- a/WickedEngine/shaders/ShaderInterop_Weather.h +++ b/WickedEngine/shaders/ShaderInterop_Weather.h @@ -357,6 +357,8 @@ struct alignas(16) ShaderWeather { uint2 sun_direction; // packed half3 uint2 sun_color; // packed half3 + uint2 moon_direction; // packed half3 + uint2 moon_color; // packed half3 uint2 ambient; // packed half3 uint most_important_light_index; @@ -366,8 +368,16 @@ struct alignas(16) ShaderWeather uint2 zenith; // packed half3 float4 stars_rotation; // quaternion - - float3 padding_stars; + float4 moon_params; // x=size(rad), y=halo size(rad), z=halo sharpness, w=halo intensity + int moon_texture; // bindless descriptor index for moon texture (SRV), -1 if unused + float moon_texture_mip_bias; // optional mip bias when sampling moon texture + float moon_light_intensity; // illuminance emitted by moon directional light + float moon_eclipse_strength; // 0-1 multiplier describing earth shadow on moon + uint moon_light_index; + uint padding_moon0; + + float sun_eclipse_strength; // 0-1 multiplier describing moon shadow on sun + float padding_sun0; float sky_rotation_sin; float sky_rotation_cos; diff --git a/WickedEngine/shaders/globals.hlsli b/WickedEngine/shaders/globals.hlsli index d2e98f9734..0bd9d0a30d 100644 --- a/WickedEngine/shaders/globals.hlsli +++ b/WickedEngine/shaders/globals.hlsli @@ -972,8 +972,44 @@ float acosFastPositive(float x) return p * sqrt(1.0 - x); } -inline half3 GetSunColor() { return unpack_half3(GetWeather().sun_color); } // sun color with intensity applied +inline float GetSunEclipseStrength() { return saturate(GetWeather().sun_eclipse_strength); } +inline half3 GetSunColor() +{ + const half3 rawColor = unpack_half3(GetWeather().sun_color); + const half eclipseScale = (half)(1.0f - GetSunEclipseStrength()); + return rawColor * eclipseScale; // sun color with eclipse dimming applied +} +inline float3 GetMoonDirection() +{ + float3 dir = unpack_half3(GetWeather().moon_direction); + float len_sq = dot(dir, dir); + return len_sq > 0 ? dir * rsqrt(len_sq) : float3(0.0f, 0.5f, 0.8660254f); +} +inline half3 GetMoonColor() { return unpack_half3(GetWeather().moon_color); } +inline float GetMoonSize() { return GetWeather().moon_params.x; } +inline float GetMoonHaloSize() { return GetWeather().moon_params.y; } +inline float GetMoonHaloSharpness() { return GetWeather().moon_params.z; } +inline float GetMoonHaloIntensity() { return GetWeather().moon_params.w; } +inline bool HasMoonTexture() { return GetWeather().moon_texture >= 0; } +inline float GetMoonTextureMipBias() { return GetWeather().moon_texture_mip_bias; } +inline float GetMoonLightIntensity() { return GetWeather().moon_light_intensity; } inline half3 GetSunDirection() { return normalize(unpack_half3(GetWeather().sun_direction)); } +inline float GetMoonPhaseVisibility() +{ + float3 sunDir = (float3)GetSunDirection(); + float3 moonDir = GetMoonDirection(); + float sunLenSq = dot(sunDir, sunDir); + float moonLenSq = dot(moonDir, moonDir); + if (sunLenSq < 1e-6f || moonLenSq < 1e-6f) + { + return 1.0f; + } + sunDir *= rsqrt(sunLenSq); + moonDir *= rsqrt(moonLenSq); + return saturate(0.5f * (1.0f - dot(sunDir, moonDir))); +} +inline float GetMoonEclipseStrength() { return saturate(GetWeather().moon_eclipse_strength); } +inline float3 GetMoonIlluminance() { return GetMoonColor() * GetMoonLightIntensity(); } inline half3 GetHorizonColor() { return unpack_half3(GetWeather().horizon); } inline half3 GetZenithColor() { return unpack_half3(GetWeather().zenith); } inline half3 GetAmbientColor() { return unpack_half3(GetWeather().ambient); } diff --git a/WickedEngine/shaders/shadowHF.hlsli b/WickedEngine/shaders/shadowHF.hlsli index e3108a44cb..a9c2625b17 100644 --- a/WickedEngine/shaders/shadowHF.hlsli +++ b/WickedEngine/shaders/shadowHF.hlsli @@ -350,20 +350,30 @@ inline half shadow_2D_volumetricclouds(float3 P) // Sample light and furthest cascade for large mediums (volumetrics) // Used with SkyAtmosphere and Volumetric Clouds -inline bool furthest_cascade_volumetrics(inout ShaderEntity light, inout uint furthestCascade) +inline bool furthest_cascade_volumetrics(inout ShaderEntity light, inout uint furthestCascade, uint light_index) { - light = load_entity(lights().first_item() + GetWeather().most_important_light_index); - furthestCascade = light.GetShadowCascadeCount() - 1; - - if (!light.IsStaticLight() && light.IsCastingShadow() && furthestCascade >= 0) + ShaderEntityIterator dir_lights = directional_lights(); + if (light_index == ~0u || light_index >= dir_lights.item_count()) + { + return false; + } + + light = load_entity(dir_lights.first_item() + light_index); + const uint cascade_count = light.GetShadowCascadeCount(); + if (!light.IsStaticLight() && light.IsCastingShadow() && cascade_count > 0) { - // We consider this light useless if it is static, is not casting shadow and if there are no available cascades + furthestCascade = cascade_count - 1; return true; } - + return false; } +inline bool furthest_cascade_volumetrics(inout ShaderEntity light, inout uint furthestCascade) +{ + return furthest_cascade_volumetrics(light, furthestCascade, GetWeather().most_important_light_index); +} + static const float rain_blocker_head_size = 1; static const float rain_blocker_head_size_sq = rain_blocker_head_size * rain_blocker_head_size; diff --git a/WickedEngine/shaders/skyAtmosphere.hlsli b/WickedEngine/shaders/skyAtmosphere.hlsli index 98c150712e..d07d391e32 100644 --- a/WickedEngine/shaders/skyAtmosphere.hlsli +++ b/WickedEngine/shaders/skyAtmosphere.hlsli @@ -424,17 +424,49 @@ half3 GetSunLuminance(float3 worldPosition, float3 worldDirection, float3 sunDir float sunHalfApexAngleRadian = 0.5 * sunApexAngleDegree * PI / 180.0; float sunCosHalfApexAngle = cos(sunHalfApexAngleRadian); + float3 sunDirNorm = normalize(sunDirection); + float sunEclipseStrength = GetSunEclipseStrength(); float3 retval = 0; float t = RaySphereIntersectNearest(worldPosition, worldDirection, 0, atmosphere.bottomRadius); if (t < 0) // no intersection { - float VdotL = dot(worldDirection, normalize(sunDirection)); // weird... the sun disc shrinks near the horizon if we don't normalize sun direction + float VdotL = dot(worldDirection, sunDirNorm); // weird... the sun disc shrinks near the horizon if we don't normalize sun direction if (VdotL > sunCosHalfApexAngle) { // Edge fade const float halfCosHalfApex = sunCosHalfApexAngle + (1.0 - sunCosHalfApexAngle) * 0.25; // Start fading when at 75% distance from light disk center - const float weight = 1.0 - saturate((halfCosHalfApex - VdotL) / (halfCosHalfApex - sunCosHalfApexAngle)); + float weight = 1.0 - saturate((halfCosHalfApex - VdotL) / (halfCosHalfApex - sunCosHalfApexAngle)); + + if (sunEclipseStrength > 0.0f) + { + float3 moonDir = GetMoonDirection(); + float moonLenSq = dot(moonDir, moonDir); + if (moonLenSq > 1e-6f) + { + float3 moonDirNorm = moonDir * rsqrt(moonLenSq); + if (dot(moonDirNorm, sunDirNorm) > 0.0f) + { + float3 referenceUp = abs(sunDirNorm.y) > 0.95f ? float3(1, 0, 0) : float3(0, 1, 0); + float3 sunRight = normalize(cross(referenceUp, sunDirNorm)); + float3 sunUp = normalize(cross(sunDirNorm, sunRight)); + + float2 sunPlane = float2(dot(worldDirection, sunRight), dot(worldDirection, sunUp)); + float2 moonPlane = float2(dot(moonDirNorm, sunRight), dot(moonDirNorm, sunUp)); + float sunRadius = sin(sunHalfApexAngleRadian); + float moonRadius = sin(GetMoonSize()); + float2 delta = sunPlane - moonPlane; + float distSq = dot(delta, delta); + float moonRadiusSq = moonRadius * moonRadius; + if (moonRadiusSq > 1e-8f) + { + float coverage = saturate(1.0f - distSq / moonRadiusSq); + coverage = pow(coverage, 1.5f); + weight *= saturate(1.0f - sunEclipseStrength * coverage); + } + } + } + } retval = weight * sunIlluminance; } @@ -448,6 +480,22 @@ half3 GetSunLuminance(float3 worldPosition, float3 worldDirection, float3 sunDir float stars_exposure = lerp(0, 512, stars_visibility); // modifies the overall strength of the stars float stars = saturate(pow(noise_gradient_3D(stars_direction * 300), stars_threshold)) * stars_exposure; stars *= lerp(0.4, 1.4, noise_gradient_3D(stars_direction * 256 + GetTime())); // time based flickering + + // Ensure the moon disk always occludes stars (moon silhouette in + // front of starfield). + float moonSize = GetMoonSize(); + if (moonSize > 0.0) + { + float3 moonDir = GetMoonDirection(); + // soft-edge mask for nicer transition (1.0 inside disk, 0.0 outside) + float edgeSoftness = 0.0025; // radians (very small) + float cosInner = cos(moonSize - edgeSoftness); + float cosOuter = cos(moonSize + edgeSoftness); + float moonDot = dot(worldDirection, moonDir); + float moonMask = smoothstep(cosOuter, cosInner, moonDot); + stars *= (1.0 - moonMask); + } + retval += stars; } diff --git a/WickedEngine/shaders/skyHF.hlsli b/WickedEngine/shaders/skyHF.hlsli index 2a882b63ba..211db47397 100644 --- a/WickedEngine/shaders/skyHF.hlsli +++ b/WickedEngine/shaders/skyHF.hlsli @@ -143,6 +143,89 @@ float3 GetDynamicSkyColor(in float2 pixel, in float3 V, bool sun_enabled = true, sky = lerp(GetHorizonColor(), GetZenithColor(), saturate(V.y * 0.5f + 0.5f)); } + if (!dark_enabled) + { + float moonSize = GetMoonSize(); + float3 moonColor = GetMoonColor(); + if (moonSize > 0 && dot(moonColor, moonColor) > 0) + { + float3 moonDir = GetMoonDirection(); + float3 sunDir = (float3)GetSunDirection(); + float sunLenSq = dot(sunDir, sunDir); + float3 sunToMoonDir = moonDir; + float phaseVisibility = 1.0f; + if (sunLenSq > 1e-6f) + { + float invSunLen = rsqrt(sunLenSq); + sunToMoonDir = -sunDir * invSunLen; + phaseVisibility = saturate(0.5f * (1.0f + dot(sunToMoonDir, moonDir))); + } + float eclipseStrength = GetMoonEclipseStrength(); + phaseVisibility *= saturate(1.0f - eclipseStrength); + float cosAngle = dot(V, moonDir); + float innerEdge = cos(moonSize); + float core = smoothstep(innerEdge, cos(moonSize * 0.8f), cosAngle); + float3 diskColor = moonColor; + float diskMask = 0.0f; + if (phaseVisibility > 0.0f) + { + float3 referenceUp = abs(moonDir.y) > 0.95f ? float3(1, 0, 0) : float3(0, 1, 0); + float3 moonRight = normalize(cross(referenceUp, moonDir)); + float3 moonUp = normalize(cross(moonDir, moonRight)); + float2 local = float2(dot(V, moonRight), dot(V, moonUp)); + float invRadius = 0.5f / max(sin(moonSize), 0.0001f); + float2 moonUV = local * invRadius + 0.5f; + if (all(moonUV >= 0.0f) && all(moonUV <= 1.0f)) + { + float2 diskCoord = (moonUV - 0.5f) * 2.0f; + float radialSq = dot(diskCoord, diskCoord); + float localZ = sqrt(saturate(1.0f - radialSq)); + float3 localNormal = normalize(moonRight * diskCoord.x + moonUp * diskCoord.y + moonDir * localZ); + float lit = saturate(dot(localNormal, sunToMoonDir)); + if (lit > 0.0f) + { + diskMask = core * lit; + if (HasMoonTexture()) + { + float4 tex = bindless_textures[NonUniformResourceIndex(descriptor_index(GetWeather().moon_texture))].SampleLevel(sampler_linear_clamp, moonUV, GetMoonTextureMipBias()); + diskMask *= tex.a; + diskColor *= tex.rgb; + } + float phaseBlend = saturate(phaseVisibility); + float3 shadingNormal = normalize(lerp(moonDir, localNormal, phaseBlend)); + float diffuse = saturate(dot(shadingNormal, sunToMoonDir)); + float rim = pow(saturate(1.0f - dot(localNormal, V)), 4.0f); + float wave = dot(diskCoord, float2(21.7f, -14.9f)); + float time = GetTime(); + float anim0 = sin(wave + time * 0.35f); + float anim1 = sin((diskCoord.x * -18.3f + diskCoord.y * 11.1f) - time * 0.22f); + float craterMask = 0.85f + 0.15f * (anim0 * 0.6f + anim1 * 0.4f); + float detailMask = lerp(1.0f, craterMask, phaseBlend); + float shading = lerp(0.35f, 1.0f, diffuse); + diskColor *= shading * detailMask; + diskColor += moonColor * rim * 0.08f * phaseBlend; + } + } + } + float haloContribution = 0; + float haloIntensity = GetMoonHaloIntensity(); + if (haloIntensity > 0) + { + float haloSize = max(GetMoonHaloSize(), 0.0001f); + float haloRadius = moonSize + haloSize; + float halo = smoothstep(cos(haloRadius), innerEdge, cosAngle); + halo = pow(saturate(halo), max(GetMoonHaloSharpness(), 0.0001f)); + + // Mask halo to the lit hemisphere so crescents don't get a full circular glow. + // Use the view direction V and sunToMoonDir (direction toward lit side). + float litHaloMask = saturate(dot(V, sunToMoonDir)); + haloContribution = halo * haloIntensity * phaseVisibility * litHaloMask; + } + sky += moonColor * haloContribution; + sky += diskColor * diskMask * saturate(1.0f - eclipseStrength * 0.9f); + } + } + sky *= GetWeather().sky_exposure; if (clouds_enabled && V.y > 0 && GetScene().texture_cloudmap >= 0 && !stationary) diff --git a/WickedEngine/shaders/volumetricCloud_renderCS.hlsl b/WickedEngine/shaders/volumetricCloud_renderCS.hlsl index 0d590c2cdb..75f89c7315 100644 --- a/WickedEngine/shaders/volumetricCloud_renderCS.hlsl +++ b/WickedEngine/shaders/volumetricCloud_renderCS.hlsl @@ -150,7 +150,7 @@ ParticipatingMedia SampleParticipatingMedia(float3 baseAlbedo, float3 baseExtinc return participatingMedia; } -void OpaqueShadow(inout ParticipatingMedia participatingMedia, float3 worldPosition) +void OpaqueShadow(inout ParticipatingMedia participatingMedia, float3 worldPosition, uint light_index) { float shadow = 1.0f; @@ -158,7 +158,7 @@ void OpaqueShadow(inout ParticipatingMedia participatingMedia, float3 worldPosit ShaderEntity light = (ShaderEntity) 0; uint furthestCascade = 0; - if (furthest_cascade_volumetrics(light, furthestCascade)) + if (furthest_cascade_volumetrics(light, furthestCascade, light_index)) { float3 shadow_pos = mul(load_entitymatrix(light.GetMatrixIndex() + furthestCascade), float4(worldPosition, 1)).xyz; // ortho matrix, no divide by .w float3 shadow_uv = clipspace_to_uv(shadow_pos.xyz); @@ -176,7 +176,7 @@ void OpaqueShadow(inout ParticipatingMedia participatingMedia, float3 worldPosit } } -void VolumetricShadow(inout ParticipatingMedia participatingMedia, in AtmosphereParameters atmosphere, float3 worldPosition, float3 sunDirection, LayerParameters layerParametersFirst, LayerParameters layerParametersSecond, float lod) +void VolumetricShadow(inout ParticipatingMedia participatingMedia, in AtmosphereParameters atmosphere, float3 worldPosition, float3 lightDirection, LayerParameters layerParametersFirst, LayerParameters layerParametersSecond, float lod) { int ms = 0; float3 extinctionAccumulation[MS_COUNT]; @@ -204,7 +204,7 @@ void VolumetricShadow(inout ParticipatingMedia participatingMedia, in Atmosphere float t = t0 + delta * sampleSegmentT; // 5 samples: 0.02, 0.1, 0.26, 0.5, 0.82 float shadowSampleT = GetWeather().volumetric_clouds.shadowStepLength * t; - float3 samplePoint = worldPosition + sunDirection * shadowSampleT; // Step futher towards the light + float3 samplePoint = worldPosition + lightDirection * shadowSampleT; // Step futher towards the light float heightFraction = GetHeightFractionForPoint(atmosphere, samplePoint); if (heightFraction < 0.0 || heightFraction > 1.0) @@ -434,7 +434,10 @@ float3 SampleLocalLights(float3 worldPosition) return localLightLuminance; } -void VolumetricCloudLighting(AtmosphereParameters atmosphere, float3 startPosition, float3 worldPosition, float3 sunDirection, float3 sunIlluminance, float cosTheta, float stepSize, float heightFraction, +void VolumetricCloudLighting(AtmosphereParameters atmosphere, float3 startPosition, float3 worldPosition, + float3 sunDirection, float3 sunIlluminance, float cosThetaSun, + float3 moonDirection, float3 moonIlluminance, float cosThetaMoon, + float stepSize, float heightFraction, float cloudDensityFirst, float cloudDensitySecond, float3 weatherDataFirst, float3 weatherDataSecond, LayerParameters layerParametersFirst, LayerParameters layerParametersSecond, float lod, inout float3 luminance, inout float3 transmittanceToView, inout float depthWeightedSum, inout float depthWeightsSum) @@ -443,45 +446,89 @@ void VolumetricCloudLighting(AtmosphereParameters atmosphere, float3 startPositi float3 albedo = SampleAlbedo(cloudDensityFirst, cloudDensitySecond, weatherDataFirst, weatherDataSecond); float3 extinction = SampleExtinction(cloudDensityFirst, cloudDensitySecond); - float3 atmosphereTransmittanceToLight = 1.0; + float3 sunAtmosphereTransmittanceToLight = 1.0; if (GetFrame().options & OPTION_BIT_REALISTIC_SKY) { - atmosphereTransmittanceToLight = GetAtmosphericLightTransmittance(atmosphere, worldPosition, sunDirection, texture_transmittancelut); // Has to be in meters + sunAtmosphereTransmittanceToLight = GetAtmosphericLightTransmittance(atmosphere, worldPosition, sunDirection, texture_transmittancelut); // Has to be in meters } - + + float3 moonAtmosphereTransmittanceToLight = 1.0; + if (GetFrame().options & OPTION_BIT_REALISTIC_SKY) + { + moonAtmosphereTransmittanceToLight = GetAtmosphericLightTransmittance(atmosphere, worldPosition, moonDirection, texture_transmittancelut); + } + // Sample participating media with multiple scattering - ParticipatingMedia participatingMedia = SampleParticipatingMedia(albedo, extinction, GetWeather().volumetric_clouds.multiScatteringScattering, GetWeather().volumetric_clouds.multiScatteringExtinction, atmosphereTransmittanceToLight); + ParticipatingMedia participatingMediaSun = SampleParticipatingMedia(albedo, extinction, GetWeather().volumetric_clouds.multiScatteringScattering, GetWeather().volumetric_clouds.multiScatteringExtinction, sunAtmosphereTransmittanceToLight); + ParticipatingMedia participatingMediaMoon = participatingMediaSun; + + [unroll] + for (int ms = 0; ms < MS_COUNT; ++ms) + { + participatingMediaMoon.transmittanceToLight[ms] = moonAtmosphereTransmittanceToLight; + } + const bool sunEnabled = GetWeather().most_important_light_index != ~0u && dot(sunIlluminance, sunIlluminance) > 0.0; + const bool moonEnabled = GetWeather().moon_light_index != ~0u && dot(moonIlluminance, moonIlluminance) > 0.0; + // Sample environment lighting float3 environmentLuminance = SampleAmbientLight(heightFraction); // Only render if there is any sign of scattering (albedo * extinction) - if (any(participatingMedia.scatteringCoefficients[0] > 0.0)) + if (any(participatingMediaSun.scatteringCoefficients[0] > 0.0)) { // Sample shadows from opaque shadow maps if (GetFrame().options & OPTION_BIT_VOLUMETRICCLOUDS_RECEIVE_SHADOW) { - OpaqueShadow(participatingMedia, worldPosition); + if (sunEnabled) + { + OpaqueShadow(participatingMediaSun, worldPosition, GetWeather().most_important_light_index); + } + if (moonEnabled) + { + OpaqueShadow(participatingMediaMoon, worldPosition, GetWeather().moon_light_index); + } } // Calcualte volumetric shadow - VolumetricShadow(participatingMedia, atmosphere, worldPosition, sunDirection, layerParametersFirst, layerParametersSecond, lod); + if (sunEnabled) + { + VolumetricShadow(participatingMediaSun, atmosphere, worldPosition, sunDirection, layerParametersFirst, layerParametersSecond, lod); + } + if (moonEnabled) + { + VolumetricShadow(participatingMediaMoon, atmosphere, worldPosition, moonDirection, layerParametersFirst, layerParametersSecond, lod); + } // Calculate bounced light from ground onto clouds const float maxTransmittanceToView = max(max(transmittanceToView.x, transmittanceToView.y), transmittanceToView.z); if (maxTransmittanceToView > 0.01f) { - VolumetricGroundContribution(environmentLuminance, atmosphere, worldPosition, sunDirection, sunIlluminance, atmosphereTransmittanceToLight, layerParametersFirst, layerParametersSecond, lod); + if (sunEnabled) + { + VolumetricGroundContribution(environmentLuminance, atmosphere, worldPosition, sunDirection, sunIlluminance, sunAtmosphereTransmittanceToLight, layerParametersFirst, layerParametersSecond, lod); + } + if (moonEnabled) + { + VolumetricGroundContribution(environmentLuminance, atmosphere, worldPosition, moonDirection, moonIlluminance, moonAtmosphereTransmittanceToLight, layerParametersFirst, layerParametersSecond, lod); + } } } // Sample dual lob phase with multiple scattering - float phaseFunction = DualLobPhase(GetWeather().volumetric_clouds.phaseG, GetWeather().volumetric_clouds.phaseG2, GetWeather().volumetric_clouds.phaseBlend, -cosTheta); - ParticipatingMediaPhase participatingMediaPhase = SampleParticipatingMediaPhase(phaseFunction, GetWeather().volumetric_clouds.multiScatteringEccentricity); + float phaseFunctionSun = DualLobPhase(GetWeather().volumetric_clouds.phaseG, GetWeather().volumetric_clouds.phaseG2, GetWeather().volumetric_clouds.phaseBlend, -cosThetaSun); + ParticipatingMediaPhase participatingMediaPhaseSun = SampleParticipatingMediaPhase(phaseFunctionSun, GetWeather().volumetric_clouds.multiScatteringEccentricity); + + ParticipatingMediaPhase participatingMediaPhaseMoon; + if (moonEnabled) + { + float phaseFunctionMoon = DualLobPhase(GetWeather().volumetric_clouds.phaseG, GetWeather().volumetric_clouds.phaseG2, GetWeather().volumetric_clouds.phaseBlend, -cosThetaMoon); + participatingMediaPhaseMoon = SampleParticipatingMediaPhase(phaseFunctionMoon, GetWeather().volumetric_clouds.multiScatteringEccentricity); + } // Update depth sampling @@ -499,11 +546,13 @@ void VolumetricCloudLighting(AtmosphereParameters atmosphere, float3 startPositi [unroll] for (int ms = MS_COUNT - 1; ms >= 0; ms--) // Should terminate at 0 { - const float3 scatteringCoefficients = participatingMedia.scatteringCoefficients[ms]; - const float3 extinctionCoefficients = participatingMedia.extinctionCoefficients[ms]; - const float3 transmittanceToLight = participatingMedia.transmittanceToLight[ms]; - - float3 lightLuminance = transmittanceToLight * sunIlluminance * participatingMediaPhase.phase[ms]; + const float3 scatteringCoefficients = participatingMediaSun.scatteringCoefficients[ms]; + const float3 extinctionCoefficients = participatingMediaSun.extinctionCoefficients[ms]; + float3 lightLuminance = participatingMediaSun.transmittanceToLight[ms] * sunIlluminance * participatingMediaPhaseSun.phase[ms]; + if (moonEnabled) + { + lightLuminance += participatingMediaMoon.transmittanceToLight[ms] * moonIlluminance * participatingMediaPhaseMoon.phase[ms]; + } lightLuminance += (ms == 0 ? environmentLuminance : float3(0.0, 0.0, 0.0)); // only apply at last lightLuminance += localLightLuminance; @@ -624,8 +673,11 @@ void RenderClouds(uint3 DTid, float2 uv, float depth, float3 depthWorldPosition, float3 sunIlluminance = GetSunColor(); float3 sunDirection = GetSunDirection(); - - float cosTheta = dot(rayDirection, sunDirection); + float3 moonIlluminance = GetMoonIlluminance(); + float3 moonDirection = GetMoonDirection(); + + float cosThetaSun = dot(rayDirection, sunDirection); + float cosThetaMoon = dot(rayDirection, moonDirection); float3 luminance = 0.0; @@ -666,7 +718,10 @@ void RenderClouds(uint3 DTid, float2 uv, float depth, float3 depthWorldPosition, if (saturate(cloudDensityFirst + cloudDensitySecond) > CLOUD_DENSITY_THRESHOLD) { - VolumetricCloudLighting(atmosphere, rayOrigin, sampleWorldPosition, sunDirection, sunIlluminance, cosTheta, stepSize, heightFraction, + VolumetricCloudLighting(atmosphere, rayOrigin, sampleWorldPosition, + sunDirection, sunIlluminance, cosThetaSun, + moonDirection, moonIlluminance, cosThetaMoon, + stepSize, heightFraction, cloudDensityFirst, cloudDensitySecond, weatherDataFirst, weatherDataSecond, layerParametersFirst, layerParametersSecond, lod, luminance, transmittanceToView, depthWeightedSum, depthWeightsSum); diff --git a/WickedEngine/wiArchive.cpp b/WickedEngine/wiArchive.cpp index 6e44ce9a9b..a04b06a71d 100644 --- a/WickedEngine/wiArchive.cpp +++ b/WickedEngine/wiArchive.cpp @@ -15,7 +15,7 @@ namespace wi { // this should always be only INCREMENTED and only if a new serialization is implemeted somewhere! - static constexpr uint64_t __archiveVersion = 93; + static constexpr uint64_t __archiveVersion = 100; // this is the version number of which below the archive is not compatible with the current version static constexpr uint64_t __archiveVersionBarrier = 22; diff --git a/WickedEngine/wiMath/AngularDisk/AngularDisk.cpp b/WickedEngine/wiMath/AngularDisk/AngularDisk.cpp new file mode 100644 index 0000000000..d85eb0e5a1 --- /dev/null +++ b/WickedEngine/wiMath/AngularDisk/AngularDisk.cpp @@ -0,0 +1,227 @@ +#include "AngularDisk.h" +#include "wiMath.h" +#include +#include +#include +#include + +#if __has_include() + // In this case, DirectXMath is coming from Windows SDK. + // It is better to use this on Windows as some Windows libraries could + // depend on the same DirectXMath headers + #include +#else + // In this case, DirectXMath is coming from supplied source code + // On platforms that don't have Windows SDK, the source code for DirectXMath + // is provided as part of the engine utilities + #include "Utility/DirectXMath/DirectXMath.h" +#endif + +using namespace wi::math; + +/* +################################################################################ +Public +################################################################################ +*/ + +// Getters +//============================================================================== + +const XMVECTOR &AngularDisk::GetDirection() const noexcept { + return props.direction; +} + +float AngularDisk::GetRadius() const noexcept { + return props.radius; +} + +// Methods +//============================================================================== + +const AngularDisk::AngularDiskProps &AngularDisk::GetProps() const noexcept { + return props; +} + +float AngularDisk::Diameter() const noexcept { + return GetRadius() * 2; +} + +float AngularDisk::Perimeter() const noexcept { + return 2 * XM_PI * GetRadius(); +} + +float AngularDisk::Area() const noexcept { + return XM_PI * GetRadius() * GetRadius(); +} + +bool AngularDisk::HasSameRadius(const AngularDisk &other) const noexcept { + return FP_Equal(GetRadius(), other.GetRadius()); +} + +bool AngularDisk::HasSameDirection(const AngularDisk &other) const noexcept { + return FP_Equal(CosAngularSeparation(other.props.direction), 1.0F); +} + +float AngularDisk::AngularDistanceToPoint(const XMVECTOR &point) const { + assert(IsNormalized(point)); + + return XMVectorGetX( + XMVector3AngleBetweenNormals(GetDirection(), point) + ); +}; + +float AngularDisk::CosAngularSeparation(const XMVECTOR &point) const noexcept { + assert(IsNormalized(point)); + + return XMVectorGetX(XMVector3Dot(GetDirection(), point)); +} + +float AngularDisk::DisksOverlapArea(const AngularDisk &other) const { + const float radius_a = GetRadius(); + const float radius_b = other.GetRadius(); + + const float angular_separation + = AngularDistanceToPoint(other.GetDirection()); + + // No overlap + if (angular_separation >= radius_a + radius_b) + { + return 0.0F; + } + + // Same radius fast path + if (HasSameRadius(other)) + { + return SameRadiusDisksOverlapArea( + radius_a, angular_separation + ); + } + + return DifferentRadiusDisksOverlapArea( + radius_a, angular_separation, radius_b + ); +} + +float AngularDisk::DisksOverlapRatio(const AngularDisk &other) const { + return Clamp(DisksOverlapArea(other) / Area(), 0.0F, 1.0F); +} + +float AngularDisk::DisksOverlapRatioEst(const AngularDisk &other) const { + // Make sure radii are the same! + assert(HasSameRadius(other)); + + const float radius_a = GetRadius(); + const float radius_b = other.GetRadius(); + + const XMVECTOR dir_a = GetDirection(); + const XMVECTOR dir_b = other.GetDirection(); + + // Cosine of angular separation between disk centers + const float cos_theta = CosAngularSeparation(dir_b); + + // Maximum and minimum cosines defining overlap range + const float cos_max = XMScalarCosEst(2.0F * radius_a); // completely separate + const float cos_min = 1.0F; // completely overlapping + + // Linear interpolation of overlap fraction + const float overlap_factor = (cos_theta - cos_max) / (cos_min - cos_max); + + // Clamp to [0,1] + return Clamp(overlap_factor, 0.0F, 1.0F); +} + +// Operator overloading +//============================================================================== + +[[nodiscard]] bool AngularDisk::operator==( + const AngularDisk &other +) const noexcept { + return HasSameRadius(other) && HasSameDirection(other); +} + +[[nodiscard]] bool AngularDisk::operator!=( + const AngularDisk &other +) const noexcept { + return !(*this == other); +} + +/* +################################################################################ +Private +################################################################################ +*/ + +// Methods +//============================================================================== + +float AngularDisk::SameRadiusDisksOverlapArea( + const float radius, + const float distance +) const { + const float radius_sq = radius * radius; + + // Fully overlapping (identical centers) + if (distance <= 0.0F) + { + return XM_PI * radius_sq; + } + + const float distance_ratio = + Clamp(distance / (2.0F * radius), -1.0F, 1.0F); + + const float intersection_angle = XMScalarACos(distance_ratio); + + const float sqrt_term = std::sqrt( + std::max(0.0F, (4.0F * radius_sq) - (distance * distance)) + ); + + return (2.0F * radius_sq * intersection_angle) + - (0.5F * distance * sqrt_term ); +} + +float AngularDisk::DifferentRadiusDisksOverlapArea( + const float radius_a, + const float distance, + const float radius_b +) const { + const float ra_sq = radius_a * radius_a; + const float rb_sq = radius_b * radius_b; + const float distance_sq = distance * distance; + + const float alpha = + LawOfCosinesAngle(distance, radius_a, radius_b); + const float beta = + LawOfCosinesAngle(distance, radius_b, radius_a); + + return + (ra_sq * alpha) + + (rb_sq * beta) - + (0.5F * std::sqrt( + std::max(0.0F, + (-distance + radius_a + radius_b) + * ( distance + radius_a - radius_b) + * ( distance - radius_a + radius_b) + * ( distance + radius_a + radius_b)) + )); +} + +/* +################################################################################ +Free functions +################################################################################ +*/ + +std::ostream &operator<<( + std::ostream &output_stream, + const AngularDisk &disk +) { + XMFLOAT3 dir; + XMStoreFloat3(&dir, disk.GetDirection()); + + output_stream << "AngularDisk(direction=(" + << dir.x << ", " << dir.y << ", " << dir.z + << "), radius=" << disk.GetRadius() << ")"; + + return output_stream; +} diff --git a/WickedEngine/wiMath/AngularDisk/AngularDisk.h b/WickedEngine/wiMath/AngularDisk/AngularDisk.h new file mode 100644 index 0000000000..1750aa535d --- /dev/null +++ b/WickedEngine/wiMath/AngularDisk/AngularDisk.h @@ -0,0 +1,644 @@ +#pragma once + +#include "wiMath.h" +#include +#include +#if __has_include() + // In this case, DirectXMath is coming from Windows SDK. + // It is better to use this on Windows as some Windows libraries could + // depend on the same DirectXMath headers + #include +#else + // In this case, DirectXMath is coming from supplied source code + // On platforms that don't have Windows SDK, the source code for DirectXMath + // is provided as part of the engine utilities + #include "Utility/DirectXMath/DirectXMath.h" +#endif + +using namespace DirectX; + +namespace wi::math { + class AngularDisk; +} // namespace wi::math + +/** + * @brief Represents an angular disk and its operations. + * + * This class models an **angular disk**, which is a 2D circular approximation + * of a spherical cap on the unit sphere. It is defined by a **center direction + * vector** (normalized) and an **angular radius** (in radians). Although it + * resembles a spherical cap, the computations (like overlap area) are performed + * in 2D for simplicity and efficiency. + * + * Typical applications include: + * - Approximating sun or moon disks and computing eclipses. + * - Field-of-view or visibility cones. + * - Any angular region on a unit sphere for rendering or gameplay logic. + * + * References: + * https://en.wikipedia.org/wiki/Spherical_cap + * https://mathworld.wolfram.com/SphericalCap.html + * + * Example usage: + * @code + * XMVECTOR dirA = XMVectorSet(1, 0, 0, 0); + * XMVECTOR dirB = XMVectorSet(0.707f, 0.707f, 0, 0); + * AngularDisk diskA(dirA, XM_PIDIV4); // 45 degrees + * AngularDisk diskB(dirB, XM_PIDIV4); + * + * float area = diskA.ComputeDiskOverlapArea(diskB); + * float ratio = diskA.ComputeDiskOverlapRatio(diskB); + * @endcode + */ +class wi::math::AngularDisk { + /* + ############################################################################ + Private + ############################################################################ + */ + private: + + // Properties + //========================================================================== + + /** + * @brief Stores the internal representation of an angular disk. + * + * This can be passed to shaders. + */ + struct alignas(16) AngularDiskProps { + /** + * @brief The normalized direction vector of the disk center. + * + * Represents the central orientation of the angular disk in 3D space. + * Must always be normalized (unit length) to ensure correct geometric + * calculations. Defaults to pointing along the positive X axis. + */ + XMVECTOR direction = XMVectorSet(1,0,0,0); + + /** + * @brief The angular radius of the disk in radians. + * + * Defines the size of the disk on the unit sphere. + * Must be non-negative. A value of 0 represents a disk with no area. + */ + float radius = 0.0F; + } props; + + /* + ############################################################################ + Public + ############################################################################ + */ + public: + + // Constructors + //========================================================================== + + /** + * @brief Constructs an empty AngularDisk with zero radius and default + * direction. + * + * Direction is set to (1,0,0) and radius to 0.0f. + * + * Example usage: + * @code + * AngularDisk disk; // direction = (1,0,0), radius = 0 + * @endcode + * + * @note The disk is effectively invalid until a proper radius and direction + * are set. + */ + explicit AngularDisk() noexcept = default; + + /** + * @brief Constructs an AngularDisk from a non-normalized direction vector. + * + * The direction will be normalized internally. + * + * Example usage: + * @code + * XMFLOAT3 dir{1.0f, 2.0f, 3.0f}; + * AngularDisk disk(dir, XM_PIDIV4); // direction normalized internally + * @endcode + * + * @param[in] dir - Non-normalized 3D direction vector (XMFLOAT3). + * @param[in] radius - Angular radius in radians. Must be positive. + */ + explicit AngularDisk(const XMFLOAT3 &dir, float radius) noexcept + : props{ wi::math::NormalizeDirection(dir), radius } + { + assert(radius > 0.0F && "AngularDisk radius must be positive"); + } + + /** + * @brief Constructs an AngularDisk from an already normalized direction + * vector. + * + * Example usage: + * @code + * XMVECTOR dir = XMVectorSet(0.707f, 0.707f, 0, 0); + * AngularDisk disk(dir, XM_PIDIV4); // direction must be normalized + * @endcode + * + * @param[in] dir - Normalized 3D direction vector (XMVECTOR). + * @param[in] radius - Angular radius in radians. Must be positive. + */ + explicit AngularDisk(const XMVECTOR &dir, float radius) noexcept + : props{ dir, radius } + { + assert(radius > 0.0F && "AngularDisk radius must be positive"); + assert(wi::math::IsNormalized(dir)); + } + + // Getters + //========================================================================== + + /** + * @brief Returns the normalized center direction of this disk. + * + * The returned vector is guaranteed to be unit length. + * + * Example usage: + * @code + * AngularDisk disk(XMVectorSet(1,0,0,0), XM_PIDIV4); + * XMVECTOR dir = disk.GetDirection(); + * @endcode + * + * @return A normalized direction vector representing the disk’s center. + */ + [[nodiscard]] const XMVECTOR &GetDirection() const noexcept; + + /** + * @brief Returns the angular radius of this disk. + * + * The radius is expressed in radians and is guaranteed to be non-negative. + * + * Example usage: + * @code + * AngularDisk disk(XMVectorSet(1,0,0,0), XM_PIDIV4); + * float radius = disk.GetRadius(); // XM_PIDIV4 + * @endcode + * + * @return The angular radius (in radians). + */ + [[nodiscard]] float GetRadius() const noexcept; + + // Methods + //========================================================================== + + /** + * @brief Returns the internal representation of this angular disk. + * + * The returned `AngularDiskProps` struct contains the normalized center + * direction and angular radius of the disk. This can be useful for: + * - Passing disk data directly to shaders. + * - Serializing or logging disk properties. + * - Performing calculations without repeatedly calling individual getters. + * + * Example usage: + * @code + * AngularDisk disk(XMVectorSet(1,0,0,0), XM_PIDIV4); + * const auto& props = disk.GetProps(); + * + * // Access direction and radius + * XMVECTOR centerDir = props.direction; + * float radius = props.radius; + * + * // Can pass props directly to a GPU buffer + * shader.SetAngularDisk(props); + * @endcode + * + * @return A constant reference to the internal AngularDiskProps struct. + */ + [[nodiscard]] const AngularDiskProps &GetProps() const noexcept; + + /** + * @brief Computes the diameter of this angular disk. + * + * The diameter is defined as twice the angular radius: + * \[ + * D = 2 r + * \] + * + * Example usage: + * @code + * AngularDisk disk(XMVectorSet(1,0,0,0), XM_PIDIV4); // 45° radius + * float diameter = disk.Diameter(); // 90° in radians + * @endcode + * + * References: + * https://en.wikipedia.org/wiki/Circle + * + * @return The diameter of this angular disk (in radians). + */ + [[nodiscard]] float Diameter() const noexcept; + + /** + * @brief Computes the perimeter of this angular disk. + * + * The perimeter is the circumference of the circle: + * \[ + * P = 2 \pi r + * \] + * + * Example usage: + * @code + * AngularDisk disk(XMVectorSet(1,0,0,0), XM_PIDIV4); // 45° radius + * float perimeter = disk.Perimeter(); + * @endcode + * + * References: + * https://en.wikipedia.org/wiki/Circle + * + * @return The perimeter of this angular disk (in radians). + */ + [[nodiscard]] float Perimeter() const noexcept; + + /** + * @brief Computes the area of this angular disk. + * + * The area is computed using the standard circle area formula: + * \[ + * A = \pi r^2 + * \] + * + * Example usage: + * @code + * AngularDisk disk(XMVectorSet(1,0,0,0), XM_PIDIV4); // 45° radius + * float area = disk.Area(); + * @endcode + * + * References: + * https://en.wikipedia.org/wiki/Circle + * + * @return The area of this angular disk (in steradians). + */ + [[nodiscard]] float Area() const noexcept; + + /** + * @brief Checks if this disk `a` has approximately the same radius as + * another disk `b`. + * + * Example usage: + * @code + * AngularDisk diskA(XMVectorSet(1,0,0,0), XM_PIDIV4); + * AngularDisk diskB(XMVectorSet(0,1,0,0), XM_PIDIV4 + 0.0001f); + * + * bool same = diskA.HasSameRadius(diskB); // likely returns true + * @endcode + * + * @param[in] other - The other disk `b`. + * + * @return `true` if both disks have approximately the same radius, `false` + * otherwise. + */ + [[nodiscard]] bool HasSameRadius(const AngularDisk &other) const noexcept; + + /** + * @brief Checks if this angular disk has the same center direction as + * another disk. + * + * Two disks are considered to have the same direction if the angle between + * their normalized center vectors is effectively zero. Internally, this is + * done by comparing the cosine of the angular separation to 1.0 using a + * floating-point tolerant comparison. + * + * Example usage: + * @code + * AngularDisk diskA(XMVectorSet(1,0,0,0), XM_PIDIV4); + * AngularDisk diskB(XMVectorSet(0.999999f,0.0f,0.0f,0.0f), XM_PIDIV4); + * + * // true, almost identical + * bool sameDirection = diskA.HasSameDirection(diskB); + * @endcode + * + * @param[in] other - The other disk `b` to compare with. + * + * @return `true` if the center directions are approximately equal, `false` + * otherwise. + */ + [[nodiscard]] bool HasSameDirection( + const AngularDisk &other + ) const noexcept; + + /** + * @brief Computes the angular distance between this disk's center direction + * and a given point on the unit sphere. + * + * The angular distance is defined as the angle between two normalized + * vectors on the unit sphere and is computed using the dot product: + * + * \[ + * \theta = \arccos\!\left( \mathbf{c} \cdot \mathbf{p} \right) + * \] + * + * Where: + * - \(\mathbf{c}\) is the normalized center direction of this disk, + * - \(\mathbf{p}\) is the normalized input direction, + * - \(\theta\) is the angular separation in radians. + * + * This corresponds to the geodesic distance between two points on the + * unit sphere. + * + * Example usage: + * @code + * XMVECTOR center = XMVectorSet(1, 0, 0, 0); + * XMVECTOR point = XMVectorSet(0, 1, 0, 0); + * + * AngularDisk disk(center, XM_PIDIV4); + * + * // returns ~1.5708 rad (90 degrees) + * float angle = disk.AngularDistanceToPoint(point); + * @endcode + * + * References: + * https://en.wikipedia.org/wiki/Dot_product + * https://en.wikipedia.org/wiki/Great-circle_distance + * + * @param[in] point - Normalized direction vector representing the point + * on the unit sphere. + * + * @return The angular distance (in radians) between this disk’s center + * and the input point. + */ + [[nodiscard]] float AngularDistanceToPoint(const XMVECTOR &point) const; + + /** + * @brief Computes the cosine of the angular separation between the disk + * center and a given normalized point. + * + * Faster than AngularDistanceToPoint if you only need the cosine. + * + * Example usage: + * @code + * float cos_theta = disk.CosAngularSeparation(point); + * if (cos_theta > some_threshold) { ... } + * @endcode + * + * @param[in] point - Normalized direction vector + * + * @return Cosine of the angular separation + */ + [[nodiscard]] float CosAngularSeparation( + const XMVECTOR &point + ) const noexcept; + + /** + * @brief Computes the area of this angular disk `a` that is overlapped by + * another angular disk `b`. + * + * If the disks have approximately equal angular radii, a faster + * approximation is used to compute the overlap area. + * + * Example usage: + * @code + * XMVECTOR dirA = XMVectorSet(1, 0, 0, 0); + * XMVECTOR dirB = XMVectorSet(0.707f, 0.707f, 0, 0); + * + * AngularDisk diskA(dirA, XM_PIDIV4); // 45 degrees + * AngularDisk diskB(dirB, XM_PIDIV4); + * + * // area of A overlapped by B + * float overlapArea = diskA.DisksOverlapArea(diskB); + * @endcode + * + * @param[in] other - The other disk `b`. + * + * @return The area of disk `a` that is overlapped by disk `b` (same units + * as the disk’s angular radius squared). + */ + [[nodiscard]] float DisksOverlapArea(const AngularDisk &other) const; + + /** + * @brief Computes the fraction of this angular disk `a` overlapped by + * another angular disk `b` ($|a \cap b| / |a|$). + * + * If the disks have approximately equal angular radii, a faster + * approximation is used to compute the overlap area. + * + * Example usage: + * @code + * XMVECTOR dirA = XMVectorSet(1, 0, 0, 0); + * XMVECTOR dirB = XMVectorSet(0.707f, 0.707f, 0, 0); + * + * AngularDisk diskA(dirA, XM_PIDIV4); // 45 degrees + * AngularDisk diskB(dirB, XM_PIDIV4); + * + * // fraction of A overlapped by B + * float overlapRatio = diskA.DisksOverlapRatio(diskB); + * @endcode + * + * @param[in] other - The other disk `b`. + * + * @return A value in the range [0, 1]: + * - 0.0f if disk `b` does not overlap disk `a`. + * - 1.0f if disk a is fully covered by disk b. + * - A value in (0, 1) if disk `b` partially overlaps disk `a`. + */ + [[nodiscard]] float DisksOverlapRatio(const AngularDisk &other) const; + + /** + * @brief Estimates the fraction of overlap between this angular disk `a` + * and another angular disk `b` of same radius. + * + * This is a fast linear approximation based on the angular separation + * between disk centers. Only valid when both disks have the same radius! + * + * Since the disk radii are assumed to be approximately equal, the overlap + * ratio is symmetric and can be interpreted as either $|a \cap b| / |a|$ or + * $|a \cap b| / |b|$. + * + * Example usage: + * @code + * XMVECTOR dirA = XMVectorSet(1, 0, 0, 0); + * XMVECTOR dirB = XMVectorSet(0.707f, 0.707f, 0, 0); + * + * AngularDisk diskA(dirA, XM_PIDIV4); + * AngularDisk diskB(dirB, XM_PIDIV4); + * + * float ratio = diskA.DisksOverlapRatioEst(diskB); // estimated overlap fraction + * @endcode + * + * @param[in] other - The other disk `b`. + * + * @return A value in the range [0, 1]: + * - 0.0f if disks do not overlap. + * - 1.0f if disks fully overlap. + * - A value in (0, 1) if disks partially overlap. + */ + [[nodiscard]] float DisksOverlapRatioEst(const AngularDisk &other) const; + + // Operator overloading + //========================================================================== + + /** + * @brief Checks if two angular disks are equal. + * + * Two disks are considered equal if both their normalized directions and + * angular radii are approximately equal. + * + * Example usage: + * @code + * AngularDisk diskA(XMVectorSet(1,0,0,0), XM_PIDIV4); + * AngularDisk diskB(XMVectorSet(0.707f,0.707f,0,0), XM_PIDIV4); + * + * bool equal = (diskA == diskB); // false + * @endcode + * + * @param[in] other - The other disk to compare. + * + * @return true if disks are approximately equal, false otherwise. + */ + [[nodiscard]] bool operator==(const AngularDisk &other) const noexcept; + + /** + * @brief Checks if two angular disks are not equal. + * + * Example usage: + * @code + * AngularDisk diskA(XMVectorSet(1,0,0,0), XM_PIDIV4); + * AngularDisk diskB(XMVectorSet(0.707f,0.707f,0,0), XM_PIDIV4); + * + * bool notEqual = (diskA != diskB); // true + * @endcode + * + * @param[in] other - The other disk to compare. + * + * @return true if disks differ in radius or direction, false otherwise. + */ + [[nodiscard]] bool operator!=(const AngularDisk &other) const noexcept; + + /* + ############################################################################ + Private + ############################################################################ + */ + private: + + // Methods + //========================================================================== + + /** + * @brief Computes the overlap area between this angular disk `a` and + * another angular disk `b` where both have approximately the same radius. + * + * Since the disk radii are assumed to be approximately equal, the overlap + * ratio is symmetric and can be interpreted as either $|a \cap b| / |a|$ or + * $|a \cap b| / |b|$. + * + * This function only requires the **common angular radius** of the disks + * and the **angular distance** between their centers (both in radians). The + * full direction vectors of the disks are **not needed**. + * + * Equation: + * \[ + * A_{\text{intersection}}(r, d) = + * 2 r^2 \arccos\Big(\frac{d}{2r}\Big) - \frac{d}{2} \sqrt{4 r^2 - d^2} + * \] + * + * Example usage: + * @code + * float radius = XM_PIDIV4; // 45 degrees + * float distance = XM_PIDIV4 / 2; // angular separation + * + * float overlapArea = disk.SameRadiusDisksOverlapArea(radius, distance); + * @endcode + * + * References: + * https://mathworld.wolfram.com/Circle-CircleIntersection.html + * + * @param[in] radius - Angular radius of the disks (in radians). + * @param[in] distance - Angular distance between disk centers (in radians). + * + * @return Area of the disks’ overlap. + */ + [[nodiscard]] float SameRadiusDisksOverlapArea( + float radius, + float distance + ) const; + + /** + * @brief Computes the overlap area of another angular disk `b` on this + * angular disk `a` when the disks have different angular radii (in + * radians). + * + * The computation is based on the **circle–circle intersection formula**: + * \[ + * A_{\text{intersection}} = + * r_a^2 \alpha + r_b^2 \beta - \frac{1}{2} \sqrt{(-d+r_a+r_b)(d+r_a-r_b)(d-r_a+r_b)(d+r_a+r_b)} + * \] + * + * Where: + * - \(r_a, r_b\) are the angular radii of disks `a` and `b` + * - \(d\) is the angular distance between the disk centers + * - \(\alpha = \arccos \frac{d^2 + r_a^2 - r_b^2}{2 d r_a}\) + * - \(\beta = \arccos \frac{d^2 + r_b^2 - r_a^2}{2 d r_b}\) + * + * Example usage: + * @code + * float radiusA = 0.5f; // radians + * float radiusB = 0.3f; // radians + * float distance = 0.4f; // radians between centers + * + * float area = diskA.DifferentRadiusDisksOverlapArea( + * radiusA, + * distance, + * radiusB + * ); + * + * // 'area' contains the 2D circular overlap area approximation + * // expressed in radians^2. + * @endcode + * + * References: + * https://mathworld.wolfram.com/Circle-CircleIntersection.html + * + * @param[in] radius_a - Angular radius of disk `a` (in radians). + * @param[in] distance - Angular distance between disk centers (in radians). + * @param[in] radius_b - Angular radius of disk `b` (in radians). + * + * @return Area of disk `a` that is overlapped by disk `b`. + */ + [[nodiscard]] float DifferentRadiusDisksOverlapArea( + float radius_a, + float distance, + float radius_b + ) const; +}; + +/* +################################################################################ +Free functions +################################################################################ +*/ + +/** + * @brief Writes a textual representation of an AngularDisk to an output stream. + * + * Format: + * AngularDisk(direction=(x, y, z), radius=r) + * + * Example usage: + * @code + * wi::math::AngularDisk disk( + * XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f), + * 0.5f + * ); + * + * std::cout << disk << std::endl; + * + * // Possible output: + * // AngularDisk(direction=(1, 0, 0), radius=0.5) + * @endcode + * + * @param[in,out] output_stream - The output stream to which the disk + * representation will be written. + * @param[in] disk - The AngularDisk instance to print. + * + * @return A reference to the same output stream, allowing chained + * stream operations (e.g., std::cout << disk << std::endl). + */ +std::ostream& operator<<( + std::ostream& output_stream, const wi::math::AngularDisk &disk +); diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index a137fa827d..97e5ab3264 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -4662,6 +4662,10 @@ void UpdatePerFrameData( } const XMFLOAT2 atlas_dim_rcp = XMFLOAT2(1.0f / float(shadowMapAtlas.desc.width), 1.0f / float(shadowMapAtlas.desc.height)); + const uint32_t most_important_light_component_index = vis.scene->weather.most_important_light_index; + frameCB.scene.weather.most_important_light_index = ~0u; + frameCB.scene.weather.moon_light_index = ~0u; + uint32_t moon_directional_entity_index = ~0u; // Write directional lights into entity array: lightarray_offset = entityCounter; @@ -4694,7 +4698,18 @@ void UpdatePerFrameData( shaderentity.SetRadius(light.radius); shaderentity.SetLength(light.length); shaderentity.SetDirection(light.direction); - shaderentity.SetColor(float4(light.color.x * light.intensity, light.color.y * light.intensity, light.color.z * light.intensity, 1)); + XMFLOAT3 directional_color = light.color; + float light_intensity_scale = 1.0f; + if (lightIndex == most_important_light_component_index) + { + light_intensity_scale = wi::math::Clamp(1.0f - vis.scene->weather.resolvedSunEclipseStrength, 0.0f, 1.0f); + } + directional_color.x *= light.intensity * light_intensity_scale; + directional_color.y *= light.intensity * light_intensity_scale; + directional_color.z *= light.intensity * light_intensity_scale; + shaderentity.SetColor(float4(directional_color.x, directional_color.y, directional_color.z, 1)); + + const uint32_t directional_entity_index = lightarray_count_directional; const bool shadowmap = IsShadowsEnabled() && light.IsCastingShadow() && !light.IsStatic(); const wi::rectpacker::Rect& shadow_rect = vis.visibleLightShadowRects[lightIndex]; @@ -4736,11 +4751,22 @@ void UpdatePerFrameData( shaderentity.SetFlags(ENTITY_FLAG_LIGHT_VOLUMETRICCLOUDS); } + if (lightIndex == most_important_light_component_index) + { + frameCB.scene.weather.most_important_light_index = directional_entity_index; + } + if (light.IsMoonLight()) + { + moon_directional_entity_index = directional_entity_index; + } + std::memcpy(entityArray + entityCounter, &shaderentity, sizeof(ShaderEntity)); entityCounter++; lightarray_count_directional++; } + frameCB.scene.weather.moon_light_index = moon_directional_entity_index; + // Write spot lights into entity array: lightarray_offset_spot = entityCounter; for (uint32_t lightIndex : vis.visibleLights) diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 3cf899a783..8f51cd2a6b 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -16,6 +16,8 @@ #include "shaders/ShaderInterop_SurfelGI.h" #include "shaders/ShaderInterop_DDGI.h" +#include + #if __has_include() #include #else @@ -31,6 +33,111 @@ namespace wi::scene { static constexpr uint32_t small_subtask_groupsize = 256u; + static float ComputeMoonPhaseVisibility(const XMFLOAT3& sun_dir, const XMFLOAT3& moon_dir) + { + XMVECTOR sun = XMLoadFloat3(&sun_dir); + XMVECTOR moon = XMLoadFloat3(&moon_dir); + const float sun_len_sq = XMVectorGetX(XMVector3LengthSq(sun)); + const float moon_len_sq = XMVectorGetX(XMVector3LengthSq(moon)); + if (!std::isfinite(sun_len_sq) || !std::isfinite(moon_len_sq) || sun_len_sq < 1e-6f || moon_len_sq < 1e-6f) + { + return 1.0f; + } + sun = XMVector3Normalize(sun); + moon = XMVector3Normalize(moon); + const float dot_sm = XMVectorGetX(XMVector3Dot(sun, moon)); + return wi::math::Clamp(0.5f * (1.0f - dot_sm), 0.0f, 1.0f); + } + + static float ResolveSunEclipseStrength(const WeatherComponent& weather) + { + const float manual = wi::math::Clamp(weather.sunEclipseStrength, 0.0f, 1.0f); + if (!weather.sunEclipseAutomatic) + { + return manual; + } + + XMVECTOR sun = XMLoadFloat3(&weather.sunDirection); + XMVECTOR moon = XMLoadFloat3(&weather.moonDirection); + const float sun_len_sq = XMVectorGetX(XMVector3LengthSq(sun)); + const float moon_len_sq = XMVectorGetX(XMVector3LengthSq(moon)); + if (!std::isfinite(sun_len_sq) || sun_len_sq < 1e-6f) + { + return manual; + } + XMFLOAT3 default_moon_dir = XMFLOAT3(0.0f, 0.5f, 0.8660254f); + if (!std::isfinite(moon_len_sq) || moon_len_sq < 1e-6f) + { + moon = XMLoadFloat3(&default_moon_dir); + } + + sun = XMVector3Normalize(sun); + moon = XMVector3Normalize(moon); + const float alignment = wi::math::Clamp(XMVectorGetX(XMVector3Dot(sun, moon)), -1.0f, 1.0f); + if (alignment <= 0.0f) + { + return manual; + } + + constexpr float sun_apparent_radius = wi::math::DegreesToRadians(0.27f); + constexpr float penumbra_padding = wi::math::DegreesToRadians(0.35f); + const float moon_radius = std::max(weather.moonSize, 0.0f); + const float totality_angle = std::max(moon_radius + sun_apparent_radius, 0.0f); + const float penumbra_angle = totality_angle + penumbra_padding; + const float cos_totality = std::cos(totality_angle); + const float cos_penumbra = std::cos(penumbra_angle); + if (cos_totality <= cos_penumbra) + { + return manual; + } + + const float eclipse = wi::math::Clamp((alignment - cos_penumbra) / (cos_totality - cos_penumbra), 0.0f, 1.0f); + return eclipse; + } + + static float ResolveMoonEclipseStrength(const WeatherComponent& weather) + { + const float manual = wi::math::Clamp(weather.moonEclipseStrength, 0.0f, 1.0f); + if (!weather.moonEclipseAutomatic) + { + return manual; + } + + XMFLOAT3 sun_dir = weather.sunDirection; + XMFLOAT3 moon_dir = weather.moonDirection; + XMVECTOR sun = XMLoadFloat3(&sun_dir); + XMVECTOR moon = XMLoadFloat3(&moon_dir); + const float sun_len_sq = XMVectorGetX(XMVector3LengthSq(sun)); + if (!std::isfinite(sun_len_sq) || sun_len_sq < 1e-6f) + { + return manual; + } + float moon_len_sq = XMVectorGetX(XMVector3LengthSq(moon)); + if (!std::isfinite(moon_len_sq) || moon_len_sq < 1e-6f) + { + moon_dir = XMFLOAT3(0.0f, 0.5f, 0.8660254f); + moon = XMLoadFloat3(&moon_dir); + moon_len_sq = XMVectorGetX(XMVector3LengthSq(moon)); + } + + sun = XMVector3Normalize(sun); + moon = XMVector3Normalize(moon); + const float opposition = wi::math::Clamp(-XMVectorGetX(XMVector3Dot(sun, moon)), -1.0f, 1.0f); + constexpr float sun_apparent_radius = wi::math::DegreesToRadians(0.27f); + constexpr float penumbra_padding = wi::math::DegreesToRadians(0.35f); + const float totality_angle = std::max(weather.moonSize + sun_apparent_radius, 0.0f); + const float penumbra_angle = totality_angle + penumbra_padding; + const float cos_totality = std::cos(totality_angle); + const float cos_penumbra = std::cos(penumbra_angle); + if (cos_totality <= cos_penumbra) + { + return manual; + } + + const float eclipse = wi::math::Clamp((opposition - cos_penumbra) / (cos_totality - cos_penumbra), 0.0f, 1.0f); + return eclipse; + } + void Scene::Update(float dt) { GraphicsDevice* device = wi::graphics::GetDevice(); @@ -928,8 +1035,37 @@ namespace wi::scene shaderscene.aabb_extents_rcp.y = 1.0f / shaderscene.aabb_extents.y; shaderscene.aabb_extents_rcp.z = 1.0f / shaderscene.aabb_extents.z; + const float resolved_sun_eclipse = ResolveSunEclipseStrength(weather); + weather.resolvedSunEclipseStrength = resolved_sun_eclipse; + if (weathers.GetCount() > 0) + { + weathers[0].resolvedSunEclipseStrength = weather.resolvedSunEclipseStrength; + } shaderscene.weather.sun_color = wi::math::pack_half3(weather.sunColor); shaderscene.weather.sun_direction = wi::math::pack_half3(weather.sunDirection); + shaderscene.weather.sun_eclipse_strength = resolved_sun_eclipse; + shaderscene.weather.padding_sun0 = 0.0f; + auto moonDir = weather.moonDirection; + if (wi::math::LengthSquared(moonDir) < 1e-6f) + { + moonDir = XMFLOAT3(0.0f, 0.5f, 0.8660254f); + } + else + { + XMVECTOR moonDirVec = XMVector3Normalize(XMLoadFloat3(&moonDir)); + XMStoreFloat3(&moonDir, moonDirVec); + } + shaderscene.weather.moon_color = wi::math::pack_half3(weather.moonColor); + shaderscene.weather.moon_direction = wi::math::pack_half3(moonDir); + shaderscene.weather.moon_params = XMFLOAT4(weather.moonSize, weather.moonGlowSize, weather.moonGlowSharpness, weather.moonGlowIntensity); + shaderscene.weather.moon_texture = weather.moonTexture.IsValid() ? device->GetDescriptorIndex(&weather.moonTexture.GetTexture(), SubresourceType::SRV, weather.moonTexture.GetTextureSRGBSubresource()) : -1; + shaderscene.weather.moon_texture_mip_bias = weather.moonTextureMipBias; + const float moon_phase_visibility = ComputeMoonPhaseVisibility(weather.sunDirection, moonDir); + const float resolved_moon_eclipse = ResolveMoonEclipseStrength(weather); + const float moon_eclipse_scale = wi::math::Clamp(1.0f - resolved_moon_eclipse, 0.0f, 1.0f); + shaderscene.weather.moon_light_intensity = weather.moonLightIntensity * moon_phase_visibility * moon_eclipse_scale; + shaderscene.weather.moon_eclipse_strength = resolved_moon_eclipse; + shaderscene.weather.moon_light_index = weather.moon_light_index; shaderscene.weather.most_important_light_index = weather.most_important_light_index; shaderscene.weather.ambient = wi::math::pack_half3(weather.ambient); shaderscene.weather.sky_rotation_sin = std::sin(weather.sky_rotation); @@ -1393,6 +1529,155 @@ namespace wi::scene return entity; } + + void Scene::EnsureMoonLight(WeatherComponent& weather_component) + { + if (weather_component.moonLight != INVALID_ENTITY) + { + if (!lights.Contains(weather_component.moonLight) || !transforms.Contains(weather_component.moonLight)) + { + weather_component.moonLight = INVALID_ENTITY; + } + } + + auto adopt_candidate = [&](Entity candidate) + { + if (candidate == INVALID_ENTITY) + { + return false; + } + if (!lights.Contains(candidate) || !transforms.Contains(candidate)) + { + return false; + } + LightComponent& light = *lights.GetComponent(candidate); + if (light.GetType() != LightComponent::DIRECTIONAL) + { + return false; + } + light.SetMoonLight(true); + weather_component.moonLight = candidate; + return true; + }; + + if (weather_component.moonLight == INVALID_ENTITY) + { + for (size_t i = 0; i < lights.GetCount(); ++i) + { + Entity entity = lights.GetEntity(i); + LightComponent& light = lights[i]; + if (light.IsMoonLight() && adopt_candidate(entity)) + { + break; + } + } + } + + if (weather_component.moonLight == INVALID_ENTITY) + { + for (size_t i = 0; i < lights.GetCount(); ++i) + { + Entity entity = lights.GetEntity(i); + const NameComponent* name = names.GetComponent(entity); + if (name != nullptr) + { + if (wi::helper::toLower(name->name) == "moon") + { + if (adopt_candidate(entity)) + { + break; + } + } + } + } + } + + const float moon_phase_visibility = ComputeMoonPhaseVisibility(weather_component.sunDirection, weather_component.moonDirection); + const float resolved_moon_eclipse = ResolveMoonEclipseStrength(weather_component); + weather_component.resolvedSunEclipseStrength = ResolveSunEclipseStrength(weather_component); + const float moon_eclipse_scale = wi::math::Clamp(1.0f - resolved_moon_eclipse, 0.0f, 1.0f); + const float moon_effective_intensity = weather_component.moonLightIntensity * moon_phase_visibility * moon_eclipse_scale; + const bool moon_active = moon_effective_intensity > 0.0f; + + if (weather_component.moonLight == INVALID_ENTITY) + { + weather_component.moonLight = Entity_CreateLight("moon"); + LightComponent& light = *lights.GetComponent(weather_component.moonLight); + light.SetType(LightComponent::DIRECTIONAL); + light.SetMoonLight(true); + light.SetCastShadow(moon_active); + light.SetVolumetricsEnabled(moon_active); + light.intensity = moon_effective_intensity; + light.color = weather_component.moonColor; + TransformComponent& transform = *transforms.GetComponent(weather_component.moonLight); + transform.Translate(XMFLOAT3(0, 4, 0)); + transform.UpdateTransform(); + } + + LightComponent* light = lights.GetComponent(weather_component.moonLight); + if (light == nullptr) + { + weather_component.moonLight = INVALID_ENTITY; + return; + } + light->SetType(LightComponent::DIRECTIONAL); + light->SetMoonLight(true); + light->SetCastShadow(moon_active); + light->SetVolumetricsEnabled(moon_active); + light->color = weather_component.moonColor; + light->intensity = moon_effective_intensity; + light->SetVolumetricCloudsEnabled(moon_active); + + TransformComponent* transform = transforms.GetComponent(weather_component.moonLight); + if (transform == nullptr) + { + return; + } + + XMVECTOR target_dir = XMLoadFloat3(&weather_component.moonDirection); + XMFLOAT3 target_dir_f; + XMStoreFloat3(&target_dir_f, target_dir); + float dir_length_sq = wi::math::LengthSquared(target_dir_f); + if (!std::isfinite(dir_length_sq) || dir_length_sq < 1e-6f) + { + target_dir = XMVectorSet(0.0f, 0.5f, 0.8660254f, 0); + } + else + { + target_dir = XMVector3Normalize(target_dir); + } + target_dir = XMVector3Normalize(target_dir); + XMVECTOR default_dir = XMVectorSet(0, 1, 0, 0); + float dot = XMVectorGetX(XMVector3Dot(default_dir, target_dir)); + dot = wi::math::Clamp(dot, -1.0f, 1.0f); + XMVECTOR rotationQuat; + if (dot > 0.9999f) + { + rotationQuat = XMQuaternionIdentity(); + } + else if (dot < -0.9999f) + { + XMVECTOR axis = XMVector3Cross(default_dir, XMVectorSet(1, 0, 0, 0)); + if (XMVectorGetX(XMVector3LengthSq(axis)) < 1e-6f) + { + axis = XMVectorSet(0, 0, 1, 0); + } + axis = XMVector3Normalize(axis); + rotationQuat = XMQuaternionRotationAxis(axis, XM_PI); + } + else + { + XMVECTOR axis = XMVector3Normalize(XMVector3Cross(default_dir, target_dir)); + float angle = std::acos(dot); + rotationQuat = XMQuaternionRotationAxis(axis, angle); + } + + XMFLOAT4 rotation; + XMStoreFloat4(&rotation, rotationQuat); + transform->rotation_local = rotation; + transform->SetDirty(); + transform->UpdateTransform(); + } Entity Scene::Entity_CreateForce( const std::string& name, const XMFLOAT3& position @@ -4972,18 +5257,30 @@ namespace wi::scene case LightComponent::DIRECTIONAL: XMStoreFloat3(&light.direction, XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 1, 0, 0), W))); aabb.createFromHalfWidth(XMFLOAT3(0, 0, 0), XMFLOAT3(FLT_MAX, FLT_MAX, FLT_MAX)); - locker.lock(); - if (args.jobIndex < weather.most_important_light_index) + if (!light.IsMoonLight()) { - weather.most_important_light_index = args.jobIndex; - weather.sunColor = light.color; - weather.sunColor.x *= light.intensity; - weather.sunColor.y *= light.intensity; - weather.sunColor.z *= light.intensity; - weather.sunDirection = light.direction; - weather.stars_rotation_quaternion = light.rotation; + locker.lock(); + if (args.jobIndex < weather.most_important_light_index) + { + weather.most_important_light_index = args.jobIndex; + weather.sunColor = light.color; + weather.sunColor.x *= light.intensity; + weather.sunColor.y *= light.intensity; + weather.sunColor.z *= light.intensity; + weather.sunDirection = light.direction; + weather.stars_rotation_quaternion = light.rotation; + } + locker.unlock(); + } + else + { + locker.lock(); + if (args.jobIndex < weather.moon_light_index) + { + weather.moon_light_index = args.jobIndex; + } + locker.unlock(); } - locker.unlock(); break; case LightComponent::SPOT: XMStoreFloat3(&light.direction, XMVector3Normalize(XMVector3TransformNormal(XMVectorSet(0, 1, 0, 0), W))); @@ -5268,8 +5565,11 @@ namespace wi::scene { if (weathers.GetCount() > 0) { - weather = weathers[0]; + WeatherComponent& primary_weather = weathers[0]; + EnsureMoonLight(primary_weather); + weather = primary_weather; weather.most_important_light_index = ~0; + weather.moon_light_index = ~0; if (weather.IsOceanEnabled() && !ocean.IsValid()) { @@ -5300,6 +5600,11 @@ namespace wi::scene } ocean.occlusionQueries[queryheap_idx] = -1; // invalidate query } + // else + // { + // EnsureMoonLight(weather); + // weather.most_important_light_index = ~0; + // } if (ocean.IsValid()) { diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index fda44fe3e8..e8e4a39d07 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -47,7 +47,7 @@ namespace wi::scene wi::ecs::ComponentManager& animation_datas = componentLibrary.Register("wi::scene::Scene::animation_datas"); wi::ecs::ComponentManager& emitters = componentLibrary.Register("wi::scene::Scene::emitters", 2); // version = 2 wi::ecs::ComponentManager& hairs = componentLibrary.Register("wi::scene::Scene::hairs", 3); // version = 3 - wi::ecs::ComponentManager& weathers = componentLibrary.Register("wi::scene::Scene::weathers", 6); // version = 6 + wi::ecs::ComponentManager& weathers = componentLibrary.Register("wi::scene::Scene::weathers", 13); // version = 13 wi::ecs::ComponentManager& sounds = componentLibrary.Register("wi::scene::Scene::sounds", 1); // version = 1 wi::ecs::ComponentManager& videos = componentLibrary.Register("wi::scene::Scene::videos", 1); // version = 1 wi::ecs::ComponentManager& inverse_kinematics = componentLibrary.Register("wi::scene::Scene::inverse_kinematics"); @@ -396,6 +396,7 @@ namespace wi::scene float outerConeAngle = XM_PIDIV4, float innerConeAngle = 0 ); + void EnsureMoonLight(WeatherComponent& weather_component); wi::ecs::Entity Entity_CreateForce( const std::string& name, const XMFLOAT3& position = XMFLOAT3(0, 0, 0) diff --git a/WickedEngine/wiScene_BindLua.cpp b/WickedEngine/wiScene_BindLua.cpp index 4722a0ce2a..758eb3b72d 100644 --- a/WickedEngine/wiScene_BindLua.cpp +++ b/WickedEngine/wiScene_BindLua.cpp @@ -6843,6 +6843,8 @@ Luna::FunctionType WeatherComponent_BindLua::methods[] Luna::PropertyType WeatherComponent_BindLua::properties[] = { lunaproperty(WeatherComponent_BindLua, sunColor), lunaproperty(WeatherComponent_BindLua, sunDirection), + lunaproperty(WeatherComponent_BindLua, moonColor), + lunaproperty(WeatherComponent_BindLua, moonDirection), lunaproperty(WeatherComponent_BindLua, skyExposure), lunaproperty(WeatherComponent_BindLua, horizon), lunaproperty(WeatherComponent_BindLua, zenith), @@ -6864,6 +6866,15 @@ Luna::PropertyType WeatherComponent_BindLua::propertie lunaproperty(WeatherComponent_BindLua, windWaveSize), lunaproperty(WeatherComponent_BindLua, windSpeed), lunaproperty(WeatherComponent_BindLua, stars), + lunaproperty(WeatherComponent_BindLua, sunEclipseStrength), + lunaproperty(WeatherComponent_BindLua, sunEclipseAutomatic), + lunaproperty(WeatherComponent_BindLua, moonSize), + lunaproperty(WeatherComponent_BindLua, moonGlowSize), + lunaproperty(WeatherComponent_BindLua, moonGlowSharpness), + lunaproperty(WeatherComponent_BindLua, moonGlowIntensity), + lunaproperty(WeatherComponent_BindLua, moonLightIntensity), + lunaproperty(WeatherComponent_BindLua, moonEclipseStrength), + lunaproperty(WeatherComponent_BindLua, moonEclipseAutomatic), lunaproperty(WeatherComponent_BindLua, rainAmount), lunaproperty(WeatherComponent_BindLua, rainLength), lunaproperty(WeatherComponent_BindLua, rainSpeed), diff --git a/WickedEngine/wiScene_BindLua.h b/WickedEngine/wiScene_BindLua.h index c62fea2059..d50c934354 100644 --- a/WickedEngine/wiScene_BindLua.h +++ b/WickedEngine/wiScene_BindLua.h @@ -1589,6 +1589,15 @@ namespace wi::lua::scene { sunColor = VectorProperty(&component->sunColor); sunDirection = VectorProperty(&component->sunDirection); + moonColor = VectorProperty(&component->moonColor); + moonDirection = VectorProperty(&component->moonDirection); + moonSize = FloatProperty(&component->moonSize); + moonGlowSize = FloatProperty(&component->moonGlowSize); + moonGlowSharpness = FloatProperty(&component->moonGlowSharpness); + moonGlowIntensity = FloatProperty(&component->moonGlowIntensity); + moonLightIntensity = FloatProperty(&component->moonLightIntensity); + moonEclipseStrength = FloatProperty(&component->moonEclipseStrength); + moonEclipseAutomatic = BoolProperty(&component->moonEclipseAutomatic); skyExposure = FloatProperty(&component->skyExposure); horizon = VectorProperty(&component->horizon); zenith = VectorProperty(&component->zenith); @@ -1602,6 +1611,8 @@ namespace wi::lua::scene windWaveSize = FloatProperty(&component->windWaveSize); windSpeed = FloatProperty(&component->windSpeed); stars = FloatProperty(&component->stars); + sunEclipseStrength = FloatProperty(&component->sunEclipseStrength); + sunEclipseAutomatic = BoolProperty(&component->sunEclipseAutomatic); rainAmount = FloatProperty(&component->rain_amount); rainLength = FloatProperty(&component->rain_length); rainSpeed = FloatProperty(&component->rain_speed); @@ -1630,6 +1641,8 @@ namespace wi::lua::scene VectorProperty sunColor; VectorProperty sunDirection; + VectorProperty moonColor; + VectorProperty moonDirection; FloatProperty skyExposure; VectorProperty horizon; VectorProperty zenith; @@ -1651,6 +1664,15 @@ namespace wi::lua::scene FloatProperty windWaveSize; FloatProperty windSpeed; FloatProperty stars; + FloatProperty sunEclipseStrength; + BoolProperty sunEclipseAutomatic; + FloatProperty moonSize; + FloatProperty moonGlowSize; + FloatProperty moonGlowSharpness; + FloatProperty moonGlowIntensity; + FloatProperty moonLightIntensity; + FloatProperty moonEclipseStrength; + BoolProperty moonEclipseAutomatic; FloatProperty rainAmount; FloatProperty rainLength; FloatProperty rainSpeed; @@ -1659,6 +1681,8 @@ namespace wi::lua::scene PropertyFunction(sunColor) PropertyFunction(sunDirection) + PropertyFunction(moonColor) + PropertyFunction(moonDirection) PropertyFunction(skyExposure) PropertyFunction(horizon) PropertyFunction(zenith) @@ -1680,6 +1704,15 @@ namespace wi::lua::scene PropertyFunction(windWaveSize) PropertyFunction(windSpeed) PropertyFunction(stars) + PropertyFunction(sunEclipseStrength) + PropertyFunction(sunEclipseAutomatic) + PropertyFunction(moonSize) + PropertyFunction(moonGlowSize) + PropertyFunction(moonGlowSharpness) + PropertyFunction(moonGlowIntensity) + PropertyFunction(moonLightIntensity) + PropertyFunction(moonEclipseStrength) + PropertyFunction(moonEclipseAutomatic) PropertyFunction(rainAmount) PropertyFunction(rainLength) PropertyFunction(rainSpeed) diff --git a/WickedEngine/wiScene_Components.h b/WickedEngine/wiScene_Components.h index e6d7e31171..09abf698ef 100644 --- a/WickedEngine/wiScene_Components.h +++ b/WickedEngine/wiScene_Components.h @@ -1328,6 +1328,7 @@ namespace wi::scene VISUALIZER = 1 << 2, LIGHTMAPONLY_STATIC = 1 << 3, VOLUMETRICCLOUDS = 1 << 4, + MOON_LIGHT = 1 << 5, }; uint32_t _flags = EMPTY; @@ -1373,12 +1374,14 @@ namespace wi::scene constexpr void SetVisualizerEnabled(bool value) { set_flag(_flags, VISUALIZER, value); } constexpr void SetStatic(bool value) { set_flag(_flags, LIGHTMAPONLY_STATIC, value); } constexpr void SetVolumetricCloudsEnabled(bool value) { set_flag(_flags, VOLUMETRICCLOUDS, value); } + constexpr void SetMoonLight(bool value) { set_flag(_flags, MOON_LIGHT, value); } constexpr bool IsCastingShadow() const { return _flags & CAST_SHADOW; } constexpr bool IsVolumetricsEnabled() const { return _flags & VOLUMETRICS; } constexpr bool IsVisualizerEnabled() const { return _flags & VISUALIZER; } constexpr bool IsStatic() const { return _flags & LIGHTMAPONLY_STATIC; } constexpr bool IsVolumetricCloudsEnabled() const { return _flags & VOLUMETRICCLOUDS; } + constexpr bool IsMoonLight() const { return _flags & MOON_LIGHT; } constexpr bool IsInactive() const { return intensity < 0.0001f || range < 0.0001f; } constexpr float GetRange() const @@ -1856,6 +1859,19 @@ namespace wi::scene XMFLOAT3 sunColor = XMFLOAT3(0, 0, 0); XMFLOAT3 sunDirection = XMFLOAT3(0, 1, 0); + float sunEclipseStrength = 0.0f; + bool sunEclipseAutomatic = false; + uint8_t padding_sun0[3] = {}; + XMFLOAT3 moonColor = XMFLOAT3(0.04f, 0.04f, 0.05f); + XMFLOAT3 moonDirection = XMFLOAT3(0.0f, 0.5f, 0.8660254f); + float moonSize = 0.0095f; // radians + float moonGlowSize = 0.03f; // additional radius in radians for halo falloff + float moonGlowSharpness = 2.0f; + float moonGlowIntensity = 0.25f; + float moonLightIntensity = 0.05f; + float moonTextureMipBias = 0; + float moonEclipseStrength = 0.0f; + bool moonEclipseAutomatic = false; float skyExposure = 1; XMFLOAT3 horizon = XMFLOAT3(0.0f, 0.0f, 0.0f); XMFLOAT3 zenith = XMFLOAT3(0.0f, 0.0f, 0.0f); @@ -1882,18 +1898,23 @@ namespace wi::scene AtmosphereParameters atmosphereParameters; VolumetricCloudParameters volumetricCloudParameters; + std::string moonTextureName; std::string skyMapName; std::string colorGradingMapName; std::string volumetricCloudsWeatherMapFirstName; std::string volumetricCloudsWeatherMapSecondName; // Non-serialized attributes: + wi::Resource moonTexture; wi::Resource skyMap; wi::Resource colorGradingMap; wi::Resource volumetricCloudsWeatherMapFirst; wi::Resource volumetricCloudsWeatherMapSecond; + float resolvedSunEclipseStrength = 0.0f; XMFLOAT4 stars_rotation_quaternion = XMFLOAT4(0, 0, 0, 1); uint32_t most_important_light_index = ~0u; + uint32_t moon_light_index = ~0u; + wi::ecs::Entity moonLight = wi::ecs::INVALID_ENTITY; void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri); }; diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp index ee4b5f49b1..0fc5d12bee 100644 --- a/WickedEngine/wiScene_Serializers.cpp +++ b/WickedEngine/wiScene_Serializers.cpp @@ -1613,6 +1613,93 @@ namespace wi::scene archive >> _flags; archive >> sunDirection; archive >> sunColor; + const bool supports_sun_eclipse = (seri.GetVersion() >= 13) || (archive.GetVersion() >= 100); + if (supports_sun_eclipse) + { + archive >> sunEclipseStrength; + archive >> sunEclipseAutomatic; + } + else + { + sunEclipseStrength = 0.0f; + sunEclipseAutomatic = false; + } + moonLight = wi::ecs::INVALID_ENTITY; + const bool supports_moon_params = (seri.GetVersion() >= 7) || (archive.GetVersion() >= 94); + const bool supports_moon_texture = (seri.GetVersion() >= 8) || (archive.GetVersion() >= 95); + const bool supports_moon_texture_bias = (seri.GetVersion() >= 9) || (archive.GetVersion() >= 96); + const bool supports_moon_light_intensity = (seri.GetVersion() >= 10) || (archive.GetVersion() >= 97); + const bool supports_moon_eclipse = (seri.GetVersion() >= 11) || (archive.GetVersion() >= 98); + const bool supports_moon_eclipse_auto = (seri.GetVersion() >= 12) || (archive.GetVersion() >= 99); + if (supports_moon_params) + { + archive >> moonDirection; + archive >> moonColor; + archive >> moonSize; + archive >> moonGlowSize; + archive >> moonGlowSharpness; + archive >> moonGlowIntensity; + if (supports_moon_light_intensity) + { + archive >> moonLightIntensity; + if (supports_moon_eclipse) + { + archive >> moonEclipseStrength; + if (supports_moon_eclipse_auto) + { + archive >> moonEclipseAutomatic; + } + } + } + } + else + { + moonDirection = XMFLOAT3(0.0f, 0.5f, 0.8660254f); + moonColor = XMFLOAT3(0.04f, 0.04f, 0.05f); + moonSize = 0.0095f; + moonGlowSize = 0.03f; + moonGlowSharpness = 2.0f; + moonGlowIntensity = 0.25f; + moonLightIntensity = 0.05f; + } + if (!supports_moon_light_intensity) + { + moonLightIntensity = 0.05f; + } + if (!supports_moon_eclipse) + { + moonEclipseStrength = 0.0f; + } + if (!supports_moon_eclipse_auto) + { + moonEclipseAutomatic = false; + } + if (supports_moon_texture) + { + archive >> moonTextureName; + if (!moonTextureName.empty()) + { + moonTextureName = dir + moonTextureName; + moonTexture = wi::resourcemanager::Load(moonTextureName); + } + else + { + moonTexture = {}; + } + } + else + { + moonTextureName.clear(); + moonTexture = {}; + } + if (supports_moon_texture_bias) + { + archive >> moonTextureMipBias; + } + else + { + moonTextureMipBias = 0; + } archive >> horizon; archive >> zenith; archive >> ambient; @@ -1894,14 +1981,55 @@ namespace wi::scene } else { + const bool supports_sun_eclipse = (seri.GetVersion() >= 13) || (archive.GetVersion() >= 100); + const bool supports_moon_params = (seri.GetVersion() >= 7) || (archive.GetVersion() >= 94); + const bool supports_moon_texture = (seri.GetVersion() >= 8) || (archive.GetVersion() >= 95); + const bool supports_moon_texture_bias = (seri.GetVersion() >= 9) || (archive.GetVersion() >= 96); + const bool supports_moon_light_intensity = (seri.GetVersion() >= 10) || (archive.GetVersion() >= 97); + const bool supports_moon_eclipse = (seri.GetVersion() >= 11) || (archive.GetVersion() >= 98); + const bool supports_moon_eclipse_auto = (seri.GetVersion() >= 12) || (archive.GetVersion() >= 99); + seri.RegisterResource(moonTextureName); seri.RegisterResource(skyMapName); seri.RegisterResource(colorGradingMapName); seri.RegisterResource(volumetricCloudsWeatherMapFirstName); seri.RegisterResource(volumetricCloudsWeatherMapSecondName); - archive << _flags; archive << sunDirection; archive << sunColor; + if (supports_sun_eclipse) + { + archive << sunEclipseStrength; + archive << sunEclipseAutomatic; + } + if (supports_moon_params) + { + archive << moonDirection; + archive << moonColor; + archive << moonSize; + archive << moonGlowSize; + archive << moonGlowSharpness; + archive << moonGlowIntensity; + if (supports_moon_light_intensity) + { + archive << moonLightIntensity; + if (supports_moon_eclipse) + { + archive << moonEclipseStrength; + if (supports_moon_eclipse_auto) + { + archive << moonEclipseAutomatic; + } + } + } + } + if (supports_moon_texture) + { + archive << wi::helper::GetPathRelative(dir, moonTextureName); + } + if (supports_moon_texture_bias) + { + archive << moonTextureMipBias; + } archive << horizon; archive << zenith; archive << ambient;