From 73ee94d1149ede2d77c79ea6b13b96b1dfdd62ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tur=C3=A1nszki=20J=C3=A1nos?= Date: Tue, 30 Aug 2022 16:37:51 +0200 Subject: [PATCH] Volumetric cloud updates (#544) - Reprojection update - Cloud model changes - Weather map import - Local lights - Environment capture - Volumetric cloud shadow - Aerial perspective for clouds Co-authored-by: Silas Oler --- .../ScriptingAPI-Documentation.md | 2 +- Content/models/emitter_skinned.wiscene | Bin 379839 -> 435097 bytes Content/models/physics_test.wiscene | Bin 65176 -> 84082 bytes Editor/ComponentsWindow.cpp | 9 + Editor/Editor.cpp | 2 +- Editor/ForceFieldWindow.cpp | 10 +- Editor/LightWindow.cpp | 20 +- Editor/LightWindow.h | 1 + Editor/TerrainGenerator.cpp | 7 +- Editor/WeatherWindow.cpp | 168 ++--- Editor/WeatherWindow.h | 10 +- Tests/Tests.cpp | 1 - Tests/test_script.lua | 2 +- WickedEngine/ArchiveVersionHistory.txt | 1 + WickedEngine/offlineshadercompiler.cpp | 11 +- WickedEngine/shaders/ShaderInterop_Renderer.h | 27 +- WickedEngine/shaders/ShaderInterop_Weather.h | 67 +- WickedEngine/shaders/Shaders_SOURCE.vcxitems | 17 +- .../shaders/Shaders_SOURCE.vcxitems.filters | 15 +- WickedEngine/shaders/envMap_skyPS_static.hlsl | 1 - WickedEngine/shaders/globals.hlsli | 11 + WickedEngine/shaders/lightingHF.hlsli | 8 +- WickedEngine/shaders/objectPS_voxelizer.hlsl | 9 +- WickedEngine/shaders/skyAtmosphere.hlsli | 48 +- ...mosphere_multiScatteredLuminanceLutCS.hlsl | 8 +- .../skyAtmosphere_skyLuminanceLutCS.hlsl | 10 +- .../shaders/skyAtmosphere_skyViewLutCS.hlsl | 6 +- .../skyAtmosphere_transmittanceLutCS.hlsl | 7 +- WickedEngine/shaders/skyHF.hlsli | 189 +----- WickedEngine/shaders/skyPS_static.hlsl | 1 - WickedEngine/shaders/ssr_resolveCS.hlsl | 10 +- .../shaders/volumetricCloud_renderCS.hlsl | 622 ++++++++++-------- .../volumetricCloud_renderCS_capture.hlsl | 2 + ...volumetricCloud_renderCS_capture_MSAA.hlsl | 2 + .../shaders/volumetricCloud_reprojectCS.hlsl | 160 +++-- .../volumetricCloud_shadow_filterCS.hlsl | 43 ++ .../volumetricCloud_shadow_renderCS.hlsl | 144 ++++ .../shaders/volumetricCloud_temporalCS.hlsl | 205 ------ .../shaders/volumetricCloud_weathermapCS.hlsl | 6 +- WickedEngine/shaders/volumetricCloudsHF.hlsli | 138 +++- .../volumetricLight_DirectionalPS.hlsl | 8 +- WickedEngine/wiArchive.cpp | 2 +- WickedEngine/wiEnums.h | 6 +- WickedEngine/wiGraphics.h | 26 +- WickedEngine/wiGraphicsDevice_DX12.cpp | 80 ++- WickedEngine/wiGraphicsDevice_Vulkan.cpp | 64 +- WickedEngine/wiGraphicsDevice_Vulkan.h | 1 + WickedEngine/wiRenderPath3D.cpp | 12 +- WickedEngine/wiRenderer.cpp | 388 +++++++++-- WickedEngine/wiRenderer.h | 12 +- WickedEngine/wiScene.cpp | 43 +- WickedEngine/wiScene.h | 32 +- WickedEngine/wiScene_BindLua.cpp | 10 +- WickedEngine/wiScene_BindLua.h | 28 +- WickedEngine/wiScene_Serializers.cpp | 103 ++- WickedEngine/wiVersion.cpp | 2 +- 56 files changed, 1673 insertions(+), 1144 deletions(-) create mode 100644 WickedEngine/shaders/volumetricCloud_renderCS_capture.hlsl create mode 100644 WickedEngine/shaders/volumetricCloud_renderCS_capture_MSAA.hlsl create mode 100644 WickedEngine/shaders/volumetricCloud_shadow_filterCS.hlsl create mode 100644 WickedEngine/shaders/volumetricCloud_shadow_renderCS.hlsl delete mode 100644 WickedEngine/shaders/volumetricCloud_temporalCS.hlsl diff --git a/Content/Documentation/ScriptingAPI-Documentation.md b/Content/Documentation/ScriptingAPI-Documentation.md index df7761b79..c6acc25c2 100644 --- a/Content/Documentation/ScriptingAPI-Documentation.md +++ b/Content/Documentation/ScriptingAPI-Documentation.md @@ -846,7 +846,7 @@ Describes a Force Field effector. - Range : float #### WeatherComponent -Describes a Rigid Body Physics object. +Describes a Weather - OceanParameters : OceanParameters -- Returns a table to modify ocean parameters (if ocean is enabled) - AtmosphereParameters : AtmosphereParameters -- Returns a table to modify atmosphere parameters - VolumetricCloudParameters : VolumetricCloudParameters -- Returns a table to modify volumetric cloud parameters diff --git a/Content/models/emitter_skinned.wiscene b/Content/models/emitter_skinned.wiscene index 6e6ee97db5d94800a6d73e2f7584192a38949363..ab9ce9059841f73361fba3c8083892671d8de151 100644 GIT binary patch delta 59088 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 EKe{)N0RR91 diff --git a/Content/models/physics_test.wiscene b/Content/models/physics_test.wiscene index 18c7f4e772ad0bc417549bf7d98f0bb0669dfa2b..e90cee35b164651e3e9b2235596b2887b61c6c4d 100644 GIT binary patch delta 19232 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 S>A!_|E*Qm)c!JSD;Qs)!ntp%) delta 3378 zcmd5;c~BEq7~ep`84Qqv72-6-ARZO4)d4Hy?Td;AqX9u%5jm99pg_26(NYtzEv?#O zeA+sSiuIyn+YuubwI-=8g|YC5)=CeV+Nv`Jnc`7O$7AfiBrnKSkB&3lnYX`v-~0C4 z-#hmAeK$Apw%v30mQapMiZt~>s5f-v9LnEmO3cm5*Jjx75m8Z-DDlwtj67|zj^aB9 zwb?l(`XY7Ca$N}(jIM{u4Acnc(DYSHwHf$kk0u<-I()35gZPjjdX}l3N3hePQ6{MJ z3iL%Kn@9T_DBc5p!|)mU1CL|v_mKxXZe%40UDYyuae+=-q@@H-VlIng7K0t1%+hZ) z(A`jxfg*uY^hK+R?K?p@xGWA1quc#7nBA4zAOz!?9fuMzUaji%!+7CL=sn$F-~rkEyu_Y^_Y+G#%7h_E68b2N5?wk8FF`8|?N{q{SEn4#l$_yP;3^tB6qNU zx7hX_+sYcNEr-_Sf?tKNSOQUpbe8J!*X`gO+pe&!4X&^>hIv|7tKWz_(wqqNyB9No zsebTSNw_Q8#Kv}TpY`ov8`suqSujxmlKl*d^XAo~1RTS=>$|~<{18wT=?i*in5OMt zAhHJEZ?Nu8H8PTR9(>9hWN@9OVRoqB2H|xl+DM3z6GIOBBO>4piP&!sLI{r ze?=>omSRp`T}d&D)|QqyRL7B^GO;M((bkHnBvC`V%CT!q#0ra#`J=IXP5|*F8{mQV z1Oy?O2iL3|uj6uY0Q6ae3~l0gnb$_q5-tvfw#Pdxo%3o1TpSdAknXTtSgQMCHqkZ` z)M%Gx(sLjYZX(b`kcoa`IW$2j7yFsA)3yApGMO-e3uW7HP}aj?r=u9d>nBk zD9vr4ZQ!?p*=oetQwZ2QAS?!cVPGH6!jwhRX{BS>_wd#Cu44>Gt-5FE7u7K$vFAr4 zC{%e4G1|w@;oaA_uUMAcl|TNQ3Oun0plzPd(kZoHSr41G15150lrHZE6~*r`6~E*_ zbitlgp@-Rf_A)p&Y9*Xk z69L0Bd#!sa^swoNXJFW(YLNW-W*DwfL)|#7wN|wa<}LDtIZXkK`r@ZhlN$qVN3y|5 zeIxT{T@MrFZDr!`M#B>S6nK51(3WlvhS|4vFqRz}rc0RzTlivlZKnu`f__9X-U0MN zbrsNwUSm4)$3Xp3$V^^;l6{VcsN9>TzlV#h8wF92@|gs8wY>#43fKhblLXOOZ`#ml zddt>u{(10Q$Z_UX)jn`AwUe3lT%;>yJmozR4=OY4IlRy>6z$^ppu7_%pw64`CTbW2D_3P6zGOK&9xU= zKrWTXRlcksKc_44P!vN^1hodjY!2U4!$q1gUg)~e l104^G;*U$KQp{KFoN8xrH7tSE3$BALzB6r8!(YS?{{W)3SF`{C diff --git a/Editor/ComponentsWindow.cpp b/Editor/ComponentsWindow.cpp index 03ad3c30d..304d970bc 100644 --- a/Editor/ComponentsWindow.cpp +++ b/Editor/ComponentsWindow.cpp @@ -69,6 +69,7 @@ void ComponentsWindow::Create(EditorComponent* _editor) newComponentCombo.AddItem("Soft Body " ICON_SOFTBODY, 17); newComponentCombo.AddItem("Collider " ICON_COLLIDER, 18); newComponentCombo.AddItem("Camera " ICON_CAMERA, 20); + newComponentCombo.AddItem("Object " ICON_OBJECT, 21); newComponentCombo.OnSelect([=](wi::gui::EventArgs args) { newComponentCombo.SetSelectedWithoutCallback(-1); if (editor->translator.selected.empty()) @@ -168,6 +169,10 @@ void ComponentsWindow::Create(EditorComponent* _editor) if (scene.cameras.Contains(entity)) return; break; + case 21: + if (scene.objects.Contains(entity)) + return; + break; default: return; } @@ -264,6 +269,10 @@ void ComponentsWindow::Create(EditorComponent* _editor) case 20: scene.cameras.Create(entity); break; + case 21: + scene.objects.Create(entity); + scene.aabb_objects.Create(entity); + break; default: break; } diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index 27a510ceb..bdc272c5a 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -1001,7 +1001,7 @@ void EditorComponent::Update(float dt) } ForceFieldComponent& force = scene.forces.Create(grass_interaction_entity); TransformComponent& transform = scene.transforms.Create(grass_interaction_entity); - force.type = ENTITY_TYPE_FORCEFIELD_POINT; + force.type = ForceFieldComponent::Type::Point; force.gravity = -80; force.range = 3; transform.Translate(P); diff --git a/Editor/ForceFieldWindow.cpp b/Editor/ForceFieldWindow.cpp index 3e210d3ae..93731fe7d 100644 --- a/Editor/ForceFieldWindow.cpp +++ b/Editor/ForceFieldWindow.cpp @@ -42,10 +42,10 @@ void ForceFieldWindow::Create(EditorComponent* _editor) switch (args.iValue) { case 0: - force->type = ENTITY_TYPE_FORCEFIELD_POINT; + force->type = ForceFieldComponent::Type::Point; break; case 1: - force->type = ENTITY_TYPE_FORCEFIELD_PLANE; + force->type = ForceFieldComponent::Type::Plane; break; default: assert(0); // error @@ -53,8 +53,8 @@ void ForceFieldWindow::Create(EditorComponent* _editor) } } }); - typeComboBox.AddItem("Point"); - typeComboBox.AddItem("Plane"); + typeComboBox.AddItem("Point", (uint64_t)ForceFieldComponent::Type::Point); + typeComboBox.AddItem("Plane", (uint64_t)ForceFieldComponent::Type::Plane); typeComboBox.SetEnabled(false); typeComboBox.SetTooltip("Choose the force field type."); AddWidget(&typeComboBox); @@ -105,7 +105,7 @@ void ForceFieldWindow::SetEntity(Entity entity) if (force != nullptr) { - typeComboBox.SetSelected(force->type == ENTITY_TYPE_FORCEFIELD_POINT ? 0 : 1); + typeComboBox.SetSelectedByUserdataWithoutCallback((uint64_t)force->type); gravitySlider.SetValue(force->gravity); rangeSlider.SetValue(force->range); diff --git a/Editor/LightWindow.cpp b/Editor/LightWindow.cpp index 36fb80fcc..62301184a 100644 --- a/Editor/LightWindow.cpp +++ b/Editor/LightWindow.cpp @@ -13,7 +13,7 @@ void LightWindow::Create(EditorComponent* _editor) { editor = _editor; wi::gui::Window::Create(ICON_POINTLIGHT " Light", wi::gui::Window::WindowControls::COLLAPSE | wi::gui::Window::WindowControls::CLOSE); - SetSize(XMFLOAT2(650, 740)); + SetSize(XMFLOAT2(650, 760)); closeButton.SetTooltip("Delete LightComponent"); OnClose([=](wi::gui::EventArgs args) { @@ -167,6 +167,20 @@ void LightWindow::Create(EditorComponent* _editor) staticCheckBox.SetTooltip("Static lights will only be used for baking into lightmaps."); AddWidget(&staticCheckBox); + volumetricCloudsCheckBox.Create("Volumetric Clouds: "); + volumetricCloudsCheckBox.SetSize(XMFLOAT2(hei, hei)); + volumetricCloudsCheckBox.SetPos(XMFLOAT2(x, y += step)); + volumetricCloudsCheckBox.OnClick([&](wi::gui::EventArgs args) { + LightComponent* light = editor->GetCurrentScene().lights.GetComponent(entity); + if (light != nullptr) + { + light->SetVolumetricCloudsEnabled(args.bValue); + } + }); + volumetricCloudsCheckBox.SetEnabled(false); + volumetricCloudsCheckBox.SetTooltip("When enabled light emission will affect volumetric clouds."); + AddWidget(&volumetricCloudsCheckBox); + typeSelectorComboBox.Create("Type: "); typeSelectorComboBox.SetSize(XMFLOAT2(wid, hei)); typeSelectorComboBox.SetPos(XMFLOAT2(x, y += step)); @@ -282,6 +296,8 @@ void LightWindow::SetEntity(Entity entity) volumetricsCheckBox.SetCheck(light->IsVolumetricsEnabled()); staticCheckBox.SetEnabled(true); staticCheckBox.SetCheck(light->IsStatic()); + volumetricCloudsCheckBox.SetEnabled(true); + volumetricCloudsCheckBox.SetCheck(light->IsVolumetricCloudsEnabled()); colorPicker.SetEnabled(true); colorPicker.SetPickColor(wi::Color::fromFloat3(light->color)); typeSelectorComboBox.SetSelected((int)light->GetType()); @@ -312,6 +328,7 @@ void LightWindow::SetEntity(Entity entity) haloCheckBox.SetEnabled(false); volumetricsCheckBox.SetEnabled(false); staticCheckBox.SetEnabled(false); + volumetricCloudsCheckBox.SetEnabled(false); intensitySlider.SetEnabled(false); colorPicker.SetEnabled(false); shadowResolutionComboBox.SetEnabled(false); @@ -394,6 +411,7 @@ void LightWindow::ResizeLayout() add_right(haloCheckBox); add_right(volumetricsCheckBox); add_right(staticCheckBox); + add_right(volumetricCloudsCheckBox); add(shadowResolutionComboBox); y += jump; diff --git a/Editor/LightWindow.h b/Editor/LightWindow.h index ab9f1feed..4adb1712e 100644 --- a/Editor/LightWindow.h +++ b/Editor/LightWindow.h @@ -22,6 +22,7 @@ public: wi::gui::CheckBox haloCheckBox; wi::gui::CheckBox volumetricsCheckBox; wi::gui::CheckBox staticCheckBox; + wi::gui::CheckBox volumetricCloudsCheckBox; wi::gui::ColorPicker colorPicker; wi::gui::ComboBox typeSelectorComboBox; wi::gui::ComboBox shadowResolutionComboBox; diff --git a/Editor/TerrainGenerator.cpp b/Editor/TerrainGenerator.cpp index d9c17abec..9eeb245dc 100644 --- a/Editor/TerrainGenerator.cpp +++ b/Editor/TerrainGenerator.cpp @@ -794,8 +794,8 @@ void TerrainGenerator::Generation_Restart() weather.ambient = XMFLOAT3(0.2f, 0.2f, 0.2f); weather.SetRealisticSky(true); weather.SetVolumetricClouds(true); - weather.volumetricCloudParameters.CoverageAmount = 0.4f; - weather.volumetricCloudParameters.CoverageMinimum = 1.35f; + weather.volumetricCloudParameters.CoverageAmount = 0.65f; + weather.volumetricCloudParameters.CoverageMinimum = 0.15f; if (presetCombo.GetItemUserData(presetCombo.GetSelected()) == PRESET_ISLANDS) { weather.SetOceanEnabled(true); @@ -813,9 +813,6 @@ void TerrainGenerator::Generation_Restart() weather.fogHeightEnd = 100; weather.windDirection = XMFLOAT3(0.05f, 0.05f, 0.05f); weather.windSpeed = 4; - weather.cloud_shadow_amount = 0.4f; - weather.cloud_shadow_scale = 0.003f; - weather.cloud_shadow_speed = 0.25f; weather.stars = 0.6f; } if (scene->lights.GetCount() == 0) diff --git a/Editor/WeatherWindow.cpp b/Editor/WeatherWindow.cpp index cb62aa291..1a171c763 100644 --- a/Editor/WeatherWindow.cpp +++ b/Editor/WeatherWindow.cpp @@ -10,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); - SetSize(XMFLOAT2(660, 1400)); + SetSize(XMFLOAT2(660, 1300)); closeButton.SetTooltip("Delete WeatherComponent"); OnClose([=](wi::gui::EventArgs args) { @@ -58,7 +58,7 @@ void WeatherWindow::Create(EditorComponent* _editor) colorComboBox.AddItem("Horizon color"); colorComboBox.AddItem("Zenith color"); colorComboBox.AddItem("Ocean color"); - colorComboBox.AddItem("V. Cloud color"); + colorComboBox.AddItem("Cloud color"); colorComboBox.SetTooltip("Choose the destination data of the color picker."); AddWidget(&colorComboBox); @@ -137,62 +137,6 @@ void WeatherWindow::Create(EditorComponent* _editor) }); AddWidget(&fogHeightEndSlider); - fogHeightSkySlider.Create(0, 1, 0, 10000, "Fog Height Sky: "); - fogHeightSkySlider.SetSize(XMFLOAT2(wid, hei)); - fogHeightSkySlider.SetPos(XMFLOAT2(x, y += step)); - fogHeightSkySlider.OnSlide([&](wi::gui::EventArgs args) { - GetWeather().fogHeightSky = args.fValue; - }); - AddWidget(&fogHeightSkySlider); - - cloudinessSlider.Create(0, 1, 0.0f, 10000, "Cloudiness: "); - cloudinessSlider.SetSize(XMFLOAT2(wid, hei)); - cloudinessSlider.SetPos(XMFLOAT2(x, y += step)); - cloudinessSlider.OnSlide([&](wi::gui::EventArgs args) { - GetWeather().cloudiness = args.fValue; - }); - AddWidget(&cloudinessSlider); - - cloudScaleSlider.Create(0.00005f, 0.001f, 0.0005f, 10000, "Cloud Scale: "); - cloudScaleSlider.SetSize(XMFLOAT2(wid, hei)); - cloudScaleSlider.SetPos(XMFLOAT2(x, y += step)); - cloudScaleSlider.OnSlide([&](wi::gui::EventArgs args) { - GetWeather().cloudScale = args.fValue; - }); - AddWidget(&cloudScaleSlider); - - cloudSpeedSlider.Create(0.001f, 0.2f, 0.1f, 10000, "Cloud Speed: "); - cloudSpeedSlider.SetSize(XMFLOAT2(wid, hei)); - cloudSpeedSlider.SetPos(XMFLOAT2(x, y += step)); - cloudSpeedSlider.OnSlide([&](wi::gui::EventArgs args) { - GetWeather().cloudSpeed = args.fValue; - }); - AddWidget(&cloudSpeedSlider); - - cloudShadowAmountSlider.Create(0, 1, 0, 10000, "Cloud Shadow: "); - cloudShadowAmountSlider.SetSize(XMFLOAT2(wid, hei)); - cloudShadowAmountSlider.SetPos(XMFLOAT2(x, y += step)); - cloudShadowAmountSlider.OnSlide([&](wi::gui::EventArgs args) { - GetWeather().cloud_shadow_amount = args.fValue; - }); - AddWidget(&cloudShadowAmountSlider); - - cloudShadowSpeedSlider.Create(0, 1, 0.2f, 10000, "Cloud Shadow Speed: "); - cloudShadowSpeedSlider.SetSize(XMFLOAT2(wid, hei)); - cloudShadowSpeedSlider.SetPos(XMFLOAT2(x, y += step)); - cloudShadowSpeedSlider.OnSlide([&](wi::gui::EventArgs args) { - GetWeather().cloud_shadow_speed = args.fValue; - }); - AddWidget(&cloudShadowSpeedSlider); - - cloudShadowScaleSlider.Create(0.0001f, 0.02f, 0.005f, 10000, "Cloud Shadow Scale: "); - cloudShadowScaleSlider.SetSize(XMFLOAT2(wid, hei)); - cloudShadowScaleSlider.SetPos(XMFLOAT2(x, y += step)); - cloudShadowScaleSlider.OnSlide([&](wi::gui::EventArgs args) { - GetWeather().cloud_shadow_scale = args.fValue; - }); - AddWidget(&cloudShadowScaleSlider); - windSpeedSlider.Create(0.0f, 4.0f, 1.0f, 10000, "Wind Speed: "); windSpeedSlider.SetSize(XMFLOAT2(wid, hei)); windSpeedSlider.SetPos(XMFLOAT2(x, y += step)); @@ -250,31 +194,13 @@ void WeatherWindow::Create(EditorComponent* _editor) }); AddWidget(&starsSlider); - simpleskyCheckBox.Create("Simple sky: "); - simpleskyCheckBox.SetTooltip("Simple sky will simply blend horizon and zenith color from bottom to top."); - simpleskyCheckBox.SetSize(XMFLOAT2(hei, hei)); - simpleskyCheckBox.SetPos(XMFLOAT2(x, y += step)); - simpleskyCheckBox.OnClick([&](wi::gui::EventArgs args) { - auto& weather = GetWeather(); - weather.SetSimpleSky(args.bValue); - if (args.bValue) - { - weather.SetRealisticSky(false); - } - }); - AddWidget(&simpleskyCheckBox); - realisticskyCheckBox.Create("Realistic sky: "); - realisticskyCheckBox.SetTooltip("Physically based sky rendering model."); + realisticskyCheckBox.SetTooltip("Physically based sky rendering model.\nNote that realistic sky requires a sun (directional light) to be visible."); realisticskyCheckBox.SetSize(XMFLOAT2(hei, hei)); realisticskyCheckBox.SetPos(XMFLOAT2(x, y += step)); realisticskyCheckBox.OnClick([&](wi::gui::EventArgs args) { auto& weather = GetWeather(); weather.SetRealisticSky(args.bValue); - if (args.bValue) - { - weather.SetSimpleSky(false); - } }); AddWidget(&realisticskyCheckBox); @@ -289,7 +215,17 @@ void WeatherWindow::Create(EditorComponent* _editor) }); AddWidget(&volumetricCloudsCheckBox); - coverageAmountSlider.Create(0, 10, 0, 1000, "Coverage amount: "); + volumetricCloudsShadowsCheckBox.Create("Volumetric clouds shadows: "); + volumetricCloudsShadowsCheckBox.SetTooltip("Compute shadows for volumetric clouds that will be used for geometry and lighting."); + volumetricCloudsShadowsCheckBox.SetSize(XMFLOAT2(hei, hei)); + volumetricCloudsShadowsCheckBox.SetPos(XMFLOAT2(x, y += step)); + volumetricCloudsShadowsCheckBox.OnClick([&](wi::gui::EventArgs args) { + auto& weather = GetWeather(); + weather.SetVolumetricCloudsShadows(args.bValue); + }); + AddWidget(&volumetricCloudsShadowsCheckBox); + + coverageAmountSlider.Create(0, 10, 1, 1000, "Coverage amount: "); coverageAmountSlider.SetSize(XMFLOAT2(wid, hei)); coverageAmountSlider.SetPos(XMFLOAT2(x, y += step)); coverageAmountSlider.OnSlide([&](wi::gui::EventArgs args) { @@ -298,7 +234,7 @@ void WeatherWindow::Create(EditorComponent* _editor) }); AddWidget(&coverageAmountSlider); - coverageMinimumSlider.Create(1, 2, 1, 1000, "Coverage minimmum: "); + coverageMinimumSlider.Create(0, 1, 0, 1000, "Coverage minimmum: "); coverageMinimumSlider.SetSize(XMFLOAT2(wid, hei)); coverageMinimumSlider.SetPos(XMFLOAT2(x, y += step)); coverageMinimumSlider.OnSlide([&](wi::gui::EventArgs args) { @@ -374,6 +310,38 @@ void WeatherWindow::Create(EditorComponent* _editor) }); AddWidget(&colorgradingButton); + volumetricCloudsWeatherMapButton.Create("Load Volumetric Clouds Weather Map"); + volumetricCloudsWeatherMapButton.SetTooltip("Load a weather map for volumetric clouds. Red channel is coverage, green is type and blue is water density (rain)."); + volumetricCloudsWeatherMapButton.SetSize(XMFLOAT2(mod_wid, hei)); + volumetricCloudsWeatherMapButton.SetPos(XMFLOAT2(mod_x, y += step)); + volumetricCloudsWeatherMapButton.OnClick([=](wi::gui::EventArgs args) { + auto& weather = GetWeather(); + + if (!weather.volumetricCloudsWeatherMap.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.volumetricCloudsWeatherMapName = fileName; + weather.volumetricCloudsWeatherMap = wi::resourcemanager::Load(fileName, wi::resourcemanager::Flags::IMPORT_RETAIN_FILEDATA); + volumetricCloudsWeatherMapButton.SetText(wi::helper::GetFileNameFromPath(fileName)); + }); + }); + } + else + { + weather.volumetricCloudsWeatherMap = {}; + weather.volumetricCloudsWeatherMapName.clear(); + volumetricCloudsWeatherMapButton.SetText("Load Volumetric Clouds Weather Map"); + } + + }); + AddWidget(&volumetricCloudsWeatherMapButton); + // Ocean params: @@ -519,10 +487,8 @@ void WeatherWindow::Create(EditorComponent* _editor) weather.ambient = XMFLOAT3(33.0f / 255.0f, 47.0f / 255.0f, 127.0f / 255.0f); weather.horizon = XMFLOAT3(101.0f / 255.0f, 101.0f / 255.0f, 227.0f / 255.0f); weather.zenith = XMFLOAT3(99.0f / 255.0f, 133.0f / 255.0f, 255.0f / 255.0f); - weather.cloudiness = 0.4f; weather.fogStart = 100; weather.fogEnd = 1000; - weather.fogHeightSky = 0; InvalidateProbes(); @@ -538,11 +504,9 @@ void WeatherWindow::Create(EditorComponent* _editor) auto& weather = GetWeather(); weather.ambient = XMFLOAT3(86.0f / 255.0f, 29.0f / 255.0f, 29.0f / 255.0f); weather.horizon = XMFLOAT3(121.0f / 255.0f, 28.0f / 255.0f, 22.0f / 255.0f); - weather.zenith = XMFLOAT3(146.0f / 255.0f, 51.0f / 255.0f, 51.0f / 255.0f); - weather.cloudiness = 0.36f; + weather.zenith = XMFLOAT3(80.0f / 255.0f, 10.0f / 255.0f, 10.0f / 255.0f); weather.fogStart = 50; weather.fogEnd = 600; - weather.fogHeightSky = 0; InvalidateProbes(); @@ -559,10 +523,8 @@ void WeatherWindow::Create(EditorComponent* _editor) weather.ambient = XMFLOAT3(0.1f, 0.1f, 0.1f); weather.horizon = XMFLOAT3(0.38f, 0.38f, 0.38f); weather.zenith = XMFLOAT3(0.42f, 0.42f, 0.42f); - weather.cloudiness = 0.75f; weather.fogStart = 0; weather.fogEnd = 500; - weather.fogHeightSky = 0; InvalidateProbes(); @@ -577,12 +539,10 @@ void WeatherWindow::Create(EditorComponent* _editor) auto& weather = GetWeather(); weather.ambient = XMFLOAT3(12.0f / 255.0f, 21.0f / 255.0f, 77.0f / 255.0f); - weather.horizon = XMFLOAT3(10.0f / 255.0f, 33.0f / 255.0f, 70.0f / 255.0f); - weather.zenith = XMFLOAT3(4.0f / 255.0f, 20.0f / 255.0f, 51.0f / 255.0f); - weather.cloudiness = 0.28f; + weather.horizon = XMFLOAT3(2.0f / 255.0f, 10.0f / 255.0f, 20.0f / 255.0f); + weather.zenith = XMFLOAT3(0, 0, 0); weather.fogStart = 10; weather.fogEnd = 400; - weather.fogHeightSky = 0; InvalidateProbes(); @@ -599,11 +559,8 @@ void WeatherWindow::Create(EditorComponent* _editor) weather.ambient = XMFLOAT3(0, 0, 0); weather.horizon = XMFLOAT3(1, 1, 1); weather.zenith = XMFLOAT3(1, 1, 1); - weather.SetSimpleSky(true); - weather.cloudiness = 0; weather.fogStart = 1000000; weather.fogEnd = 1000000; - weather.fogHeightSky = 0; InvalidateProbes(); @@ -712,18 +669,16 @@ void WeatherWindow::Update() colorgradingButton.SetText(wi::helper::GetFileNameFromPath(weather.colorGradingMapName)); } + if (!weather.volumetricCloudsWeatherMapName.empty()) + { + volumetricCloudsWeatherMapButton.SetText(wi::helper::GetFileNameFromPath(weather.volumetricCloudsWeatherMapName)); + } + heightFogCheckBox.SetCheck(weather.IsHeightFog()); fogStartSlider.SetValue(weather.fogStart); fogEndSlider.SetValue(weather.fogEnd); fogHeightStartSlider.SetValue(weather.fogHeightStart); fogHeightEndSlider.SetValue(weather.fogHeightEnd); - fogHeightSkySlider.SetValue(weather.fogHeightSky); - cloudinessSlider.SetValue(weather.cloudiness); - cloudScaleSlider.SetValue(weather.cloudScale); - cloudSpeedSlider.SetValue(weather.cloudSpeed); - cloudShadowAmountSlider.SetValue(weather.cloud_shadow_amount); - cloudShadowScaleSlider.SetValue(weather.cloud_shadow_scale); - cloudShadowSpeedSlider.SetValue(weather.cloud_shadow_speed); windSpeedSlider.SetValue(weather.windSpeed); windWaveSizeSlider.SetValue(weather.windWaveSize); windRandomnessSlider.SetValue(weather.windRandomness); @@ -751,7 +706,6 @@ void WeatherWindow::Update() break; } - simpleskyCheckBox.SetCheck(weather.IsSimpleSky()); realisticskyCheckBox.SetCheck(weather.IsRealisticSky()); ocean_enabledCheckBox.SetCheck(weather.IsOceanEnabled()); @@ -765,13 +719,13 @@ void WeatherWindow::Update() ocean_toleranceSlider.SetValue(weather.oceanParameters.surfaceDisplacementTolerance); volumetricCloudsCheckBox.SetCheck(weather.IsVolumetricClouds()); + volumetricCloudsShadowsCheckBox.SetCheck(weather.IsVolumetricCloudsShadows()); coverageAmountSlider.SetValue(weather.volumetricCloudParameters.CoverageAmount); coverageMinimumSlider.SetValue(weather.volumetricCloudParameters.CoverageMinimum); } else { scene.weather = {}; - scene.weather.SetSimpleSky(true); scene.weather.ambient = XMFLOAT3(0.5f, 0.5f, 0.5f); scene.weather.zenith = default_sky_zenith; scene.weather.horizon = default_sky_horizon; @@ -846,7 +800,6 @@ void WeatherWindow::ResizeLayout() }; add_fullwidth(primaryButton); - add_right(simpleskyCheckBox); add_right(realisticskyCheckBox); add(colorComboBox); add_fullwidth(colorPicker); @@ -857,13 +810,6 @@ void WeatherWindow::ResizeLayout() add(fogEndSlider); add(fogHeightStartSlider); add(fogHeightEndSlider); - add(fogHeightSkySlider); - add(cloudinessSlider); - add(cloudScaleSlider); - add(cloudSpeedSlider); - add(cloudShadowAmountSlider); - add(cloudShadowScaleSlider); - add(cloudShadowSpeedSlider); add(windSpeedSlider); add(windMagnitudeSlider); add(windDirectionSlider); @@ -875,8 +821,10 @@ void WeatherWindow::ResizeLayout() y += jump; add_right(volumetricCloudsCheckBox); + add_right(volumetricCloudsShadowsCheckBox); add(coverageAmountSlider); add(coverageMinimumSlider); + add_fullwidth(volumetricCloudsWeatherMapButton); y += jump; diff --git a/Editor/WeatherWindow.h b/Editor/WeatherWindow.h index d5fc55876..5d9bef0a5 100644 --- a/Editor/WeatherWindow.h +++ b/Editor/WeatherWindow.h @@ -27,13 +27,6 @@ public: wi::gui::Slider fogEndSlider; wi::gui::Slider fogHeightStartSlider; wi::gui::Slider fogHeightEndSlider; - wi::gui::Slider fogHeightSkySlider; - wi::gui::Slider cloudinessSlider; - wi::gui::Slider cloudScaleSlider; - wi::gui::Slider cloudSpeedSlider; - wi::gui::Slider cloudShadowAmountSlider; - wi::gui::Slider cloudShadowScaleSlider; - wi::gui::Slider cloudShadowSpeedSlider; wi::gui::Slider windSpeedSlider; wi::gui::Slider windMagnitudeSlider; wi::gui::Slider windDirectionSlider; @@ -41,7 +34,6 @@ public: wi::gui::Slider windRandomnessSlider; wi::gui::Slider skyExposureSlider; wi::gui::Slider starsSlider; - wi::gui::CheckBox simpleskyCheckBox; wi::gui::CheckBox realisticskyCheckBox; wi::gui::Button skyButton; wi::gui::Button colorgradingButton; @@ -63,8 +55,10 @@ public: // volumetric clouds: wi::gui::CheckBox volumetricCloudsCheckBox; + wi::gui::CheckBox volumetricCloudsShadowsCheckBox; wi::gui::Slider coverageAmountSlider; wi::gui::Slider coverageMinimumSlider; + wi::gui::Button volumetricCloudsWeatherMapButton; wi::gui::Button preset0Button; wi::gui::Button preset1Button; diff --git a/Tests/Tests.cpp b/Tests/Tests.cpp index 8b415237c..81a96d876 100644 --- a/Tests/Tests.cpp +++ b/Tests/Tests.cpp @@ -384,7 +384,6 @@ void TestsRenderer::Load() weather.ambient = XMFLOAT3(0.2f, 0.2f, 0.2f); weather.horizon = XMFLOAT3(0.38f, 0.38f, 0.38f); weather.zenith = XMFLOAT3(0.42f, 0.42f, 0.42f); - weather.cloudiness = 0.75f; wi::scene::GetScene().Merge(scene); // add lodaded scene to global scene } diff --git a/Tests/test_script.lua b/Tests/test_script.lua index 9ce0f3328..38859927d 100644 --- a/Tests/test_script.lua +++ b/Tests/test_script.lua @@ -12,7 +12,7 @@ dofile("../Content/scripts/camera_animation_repeat.lua"); ToggleCameraAnimation(); -- Load an image: -local sprite = Sprite("../logo_small.png"); +local sprite = Sprite("../Content/logo_small.png"); sprite.SetParams(ImageParams(100,100,128,128)); -- Set this image as renderable to the active component: local component = main.GetActivePath(); diff --git a/WickedEngine/ArchiveVersionHistory.txt b/WickedEngine/ArchiveVersionHistory.txt index 34a660ce7..fce0a7bc5 100644 --- a/WickedEngine/ArchiveVersionHistory.txt +++ b/WickedEngine/ArchiveVersionHistory.txt @@ -1,5 +1,6 @@ This file contains changelog of wi::Archive versions +86: serialized volumetric clouds weather map, removed unused values and remapped values from VolumetricCloudParameters 85: DDGI serialization 84: component library serialization 83: physical light units diff --git a/WickedEngine/offlineshadercompiler.cpp b/WickedEngine/offlineshadercompiler.cpp index b920daa69..45aad9dc7 100644 --- a/WickedEngine/offlineshadercompiler.cpp +++ b/WickedEngine/offlineshadercompiler.cpp @@ -150,13 +150,16 @@ wi::vector shaders = { {"blur_bilateral_unorm1CS", wi::graphics::ShaderStage::CS }, {"voxelSceneCopyClearCS_TemporalSmoothing", wi::graphics::ShaderStage::CS }, {"normalsfromdepthCS", wi::graphics::ShaderStage::CS }, - {"volumetricCloud_shapenoiseCS", wi::graphics::ShaderStage::CS }, - {"volumetricCloud_detailnoiseCS", wi::graphics::ShaderStage::CS }, {"volumetricCloud_curlnoiseCS", wi::graphics::ShaderStage::CS }, - {"volumetricCloud_weathermapCS", wi::graphics::ShaderStage::CS }, + {"volumetricCloud_detailnoiseCS", wi::graphics::ShaderStage::CS }, {"volumetricCloud_renderCS", wi::graphics::ShaderStage::CS }, + {"volumetricCloud_renderCS_capture", wi::graphics::ShaderStage::CS }, + {"volumetricCloud_renderCS_capture_MSAA", wi::graphics::ShaderStage::CS }, {"volumetricCloud_reprojectCS", wi::graphics::ShaderStage::CS }, - {"volumetricCloud_temporalCS", wi::graphics::ShaderStage::CS }, + {"volumetricCloud_shadow_filterCS", wi::graphics::ShaderStage::CS }, + {"volumetricCloud_shadow_renderCS", wi::graphics::ShaderStage::CS }, + {"volumetricCloud_shapenoiseCS", wi::graphics::ShaderStage::CS }, + {"volumetricCloud_weathermapCS", wi::graphics::ShaderStage::CS }, {"shadingRateClassificationCS", wi::graphics::ShaderStage::CS }, {"shadingRateClassificationCS_DEBUG", wi::graphics::ShaderStage::CS }, {"skyAtmosphere_transmittanceLutCS", wi::graphics::ShaderStage::CS }, diff --git a/WickedEngine/shaders/ShaderInterop_Renderer.h b/WickedEngine/shaders/ShaderInterop_Renderer.h index 1839d991e..7e0d4bf23 100644 --- a/WickedEngine/shaders/ShaderInterop_Renderer.h +++ b/WickedEngine/shaders/ShaderInterop_Renderer.h @@ -632,6 +632,7 @@ enum SHADER_ENTITY_TYPE }; static const uint ENTITY_FLAG_LIGHT_STATIC = 1 << 0; +static const uint ENTITY_FLAG_LIGHT_VOLUMETRICCLOUDS = 1 << 1; static const uint SHADER_ENTITY_COUNT = 256; static const uint SHADER_ENTITY_TILE_BUCKET_COUNT = SHADER_ENTITY_COUNT / 32; @@ -653,7 +654,6 @@ static const uint OPTION_BIT_TRANSPARENTSHADOWS_ENABLED = 1 << 1; static const uint OPTION_BIT_VOXELGI_ENABLED = 1 << 2; static const uint OPTION_BIT_VOXELGI_REFLECTIONS_ENABLED = 1 << 3; static const uint OPTION_BIT_VOXELGI_RETARGETTED = 1 << 4; -static const uint OPTION_BIT_SIMPLE_SKY = 1 << 5; static const uint OPTION_BIT_REALISTIC_SKY = 1 << 6; static const uint OPTION_BIT_HEIGHT_FOG = 1 << 7; static const uint OPTION_BIT_RAYTRACED_SHADOWS = 1 << 8; @@ -662,6 +662,7 @@ static const uint OPTION_BIT_SURFELGI_ENABLED = 1 << 10; static const uint OPTION_BIT_DISABLE_ALBEDO_MAPS = 1 << 11; static const uint OPTION_BIT_FORCE_DIFFUSE_LIGHTING = 1 << 12; static const uint OPTION_BIT_STATIC_SKY_HDR = 1 << 13; +static const uint OPTION_BIT_VOLUMETRICCLOUDS_SHADOWS = 1 << 14; // ---------- Common Constant buffers: ----------------- @@ -680,6 +681,13 @@ struct FrameCB uint2 shadow_atlas_resolution; float2 shadow_atlas_resolution_rcp; + float4x4 cloudShadowLightSpaceMatrix; + float4x4 cloudShadowLightSpaceMatrixInverse; + + float cloudShadowFarPlaneKm; + int texture_volumetricclouds_shadow_index; + float2 padding0; + float3 voxelradiance_center; // center of the voxel grid in world space units float voxelradiance_max_distance; // maximum raymarch distance for voxel GI in world-space @@ -840,6 +848,7 @@ struct LensFlarePush struct CubemapRenderCam { float4x4 view_projection; + float4x4 inverse_view_projection; uint4 properties; }; CBUFFER(CubemapRenderCB, CBSLOT_RENDERER_CUBEMAPRENDER) @@ -928,5 +937,21 @@ struct SkinningPushConstants int so_tan; }; +struct VolumetricCloudCapturePushConstants +{ + uint2 resolution; + float2 resolution_rcp; + + uint arrayIndex; + int texture_input; + int texture_output; + int MaxStepCount; + + float LODMin; + float ShadowSampleCount; + float GroundContributionSampleCount; + float padding; +}; + #endif // WI_SHADERINTEROP_RENDERER_H diff --git a/WickedEngine/shaders/ShaderInterop_Weather.h b/WickedEngine/shaders/ShaderInterop_Weather.h index 7d0f064dc..cbfc56063 100644 --- a/WickedEngine/shaders/ShaderInterop_Weather.h +++ b/WickedEngine/shaders/ShaderInterop_Weather.h @@ -95,7 +95,7 @@ struct VolumetricCloudParameters float3 Albedo; // Cloud albedo is normally very close to 1 float CloudAmbientGroundMultiplier; // [0; 1] Amount of ambient light to reach the bottom of clouds - float3 ExtinctionCoefficient; // * 0.05 looks good too + float3 ExtinctionCoefficient; float BeerPowder; float BeerPowderPower; @@ -120,12 +120,8 @@ struct VolumetricCloudParameters float WeatherScale; float CurlScale; - float ShapeNoiseHeightGradientAmount; - float ShapeNoiseMultiplier; - - float2 ShapeNoiseMinMax; - float ShapeNoisePower; float DetailNoiseModifier; + float padding0; float DetailNoiseHeightFraction; float CurlNoiseModifier; @@ -133,7 +129,7 @@ struct VolumetricCloudParameters float CoverageMinimum; float TypeAmount; - float TypeOverall; + float TypeMinimum; float AnvilAmount; // Anvil clouds disabled by default. float AnvilOverhangHeight; @@ -143,7 +139,7 @@ struct VolumetricCloudParameters float WindAngle; float WindUpAmount; - float2 padding0; + float2 padding1; float CoverageWindSpeed; float CoverageWindAngle; @@ -161,21 +157,19 @@ struct VolumetricCloudParameters float LODDistance; // After a certain distance, noises will get higher LOD float LODMin; // - float BigStepMarch; // How long inital rays should be until they hit something. Lower values may ives a better image but may be slower. + float BigStepMarch; // How long inital rays should be until they hit something. Lower values may give a better image but may be slower. float TransmittanceThreshold; // Default: 0.005. If the clouds transmittance has reached it's desired opacity, there's no need to keep raymarching for performance. - float2 padding1; + float2 padding2; float ShadowSampleCount; float GroundContributionSampleCount; void init() { - - // Lighting Albedo = float3(0.9f, 0.9f, 0.9f); CloudAmbientGroundMultiplier = 0.75f; - ExtinctionCoefficient = float3(0.71f * 0.1f, 0.86f * 0.1f, 1.0f * 0.1f); + ExtinctionCoefficient = float3(0.71f * 0.05f, 0.86f * 0.05f, 1.0f * 0.05f); BeerPowder = 20.0f; BeerPowderPower = 0.5f; PhaseG = 0.5f; // [-0.999; 0.999] @@ -185,7 +179,7 @@ struct VolumetricCloudParameters MultiScatteringExtinction = 0.1f; MultiScatteringEccentricity = 0.2f; ShadowStepLength = 3000.0f; - HorizonBlendAmount = 1.25f; + HorizonBlendAmount = 0.0000125f; HorizonBlendPower = 2.0f; WeatherDensityAmount = 0.0f; @@ -194,24 +188,19 @@ struct VolumetricCloudParameters CloudThickness = 4000.0f; SkewAlongWindDirection = 700.0f; - TotalNoiseScale = 1.0f; - DetailScale = 5.0f; - WeatherScale = 0.0625f; - CurlScale = 7.5f; - - ShapeNoiseHeightGradientAmount = 0.2f; - ShapeNoiseMultiplier = 0.8f; - ShapeNoisePower = 6.0f; - ShapeNoiseMinMax = float2(0.25f, 1.1f); + TotalNoiseScale = 0.0006f; + DetailScale = 2.0f; + WeatherScale = 0.000025f; + CurlScale = 0.3f; DetailNoiseModifier = 0.2f; DetailNoiseHeightFraction = 10.0f; - CurlNoiseModifier = 550.0f; + CurlNoiseModifier = 500.0f; - CoverageAmount = 2.0f; - CoverageMinimum = 1.05f; + CoverageAmount = 1.0f; + CoverageMinimum = 0.0f; TypeAmount = 1.0f; - TypeOverall = 0.0f; + TypeMinimum = 0.0f; AnvilAmount = 0.0f; AnvilOverhangHeight = 3.0f; @@ -225,21 +214,21 @@ struct VolumetricCloudParameters // Cloud types // 4 positions of a black, white, white, black gradient - CloudGradientSmall = float4(0.02f, 0.07f, 0.12f, 0.28f); - CloudGradientMedium = float4(0.02f, 0.07f, 0.39f, 0.59f); + CloudGradientSmall = float4(0.02f, 0.1f, 0.12f, 0.28f); + CloudGradientMedium = float4(0.02f, 0.1f, 0.39f, 0.59f); CloudGradientLarge = float4(0.02f, 0.07f, 0.88f, 1.0f); // Performance - MaxStepCount = 128; + MaxStepCount = 96; MaxMarchingDistance = 30000.0f; InverseDistanceStepCount = 15000.0f; RenderDistance = 70000.0f; LODDistance = 25000.0f; LODMin = 0.0f; - BigStepMarch = 3.0f; + BigStepMarch = 1.0f; TransmittanceThreshold = 0.005f; ShadowSampleCount = 5.0f; - GroundContributionSampleCount = 2.0f; + GroundContributionSampleCount = 3.0f; } #ifdef __cplusplus @@ -254,11 +243,6 @@ struct ShaderFog float end; float height_start; float height_end; - - float height_sky; - float padding0; - float padding1; - float padding2; }; struct ShaderWind @@ -293,15 +277,10 @@ struct ShaderWeather float sky_exposure; float3 zenith; - float cloudiness; + float padding0; float3 ambient; - float cloud_scale; - - float cloud_speed; - float cloud_shadow_amount; - float cloud_shadow_scale; - float cloud_shadow_speed; + float padding1; float4x4 stars_rotation; diff --git a/WickedEngine/shaders/Shaders_SOURCE.vcxitems b/WickedEngine/shaders/Shaders_SOURCE.vcxitems index c64825541..a894ac4e0 100644 --- a/WickedEngine/shaders/Shaders_SOURCE.vcxitems +++ b/WickedEngine/shaders/Shaders_SOURCE.vcxitems @@ -1125,7 +1125,22 @@ 4.0 - + + Compute + 4.0 + + + Compute + 4.0 + + + Compute + 4.0 + + + Compute + 4.0 + Vertex Vertex diff --git a/WickedEngine/shaders/Shaders_SOURCE.vcxitems.filters b/WickedEngine/shaders/Shaders_SOURCE.vcxitems.filters index 5caa1e51e..61a28a8cd 100644 --- a/WickedEngine/shaders/Shaders_SOURCE.vcxitems.filters +++ b/WickedEngine/shaders/Shaders_SOURCE.vcxitems.filters @@ -947,9 +947,6 @@ CS - - CS - CS @@ -1055,6 +1052,18 @@ CS + + CS + + + CS + + + CS + + + CS + diff --git a/WickedEngine/shaders/envMap_skyPS_static.hlsl b/WickedEngine/shaders/envMap_skyPS_static.hlsl index 76a4d4770..f12c5f4ea 100644 --- a/WickedEngine/shaders/envMap_skyPS_static.hlsl +++ b/WickedEngine/shaders/envMap_skyPS_static.hlsl @@ -9,6 +9,5 @@ float4 main(PixelInput input) : SV_TARGET { float3 normal = normalize(input.nor); float3 sky = DEGAMMA(texture_sky.SampleLevel(sampler_linear_clamp, normal, 0).rgb); - CalculateClouds(sky, normal, false); return float4(sky, 1); } diff --git a/WickedEngine/shaders/globals.hlsli b/WickedEngine/shaders/globals.hlsli index aaf26bf30..c72d81605 100644 --- a/WickedEngine/shaders/globals.hlsli +++ b/WickedEngine/shaders/globals.hlsli @@ -210,6 +210,9 @@ struct PrimitiveID #define sqr(a) ((a)*(a)) #define pow5(x) pow(x, 5) +#define M_TO_SKY_UNIT 0.001f // Engine units are in meters +#define SKY_UNIT_TO_M (1.0 / M_TO_SKY_UNIT) + // attribute computation with barycentric interpolation // a0 : attribute at triangle corner 0 // a1 : attribute at triangle corner 1 @@ -571,6 +574,14 @@ inline float compute_lineardepth(in float z) return compute_lineardepth(z, GetCamera().z_near, GetCamera().z_far); } +// Computes post-projection depth from linear depth +inline float compute_inverse_lineardepth(in float lin, in float near, in float far) +{ + float z_n = ((lin - 2 * far) * near + far * lin) / (lin * near - far * lin); + float z = (z_n + 1) / 2; + return z; +} + inline float3x3 get_tangentspace(in float3 normal) { // Choose a helper vector for the cross product diff --git a/WickedEngine/shaders/lightingHF.hlsli b/WickedEngine/shaders/lightingHF.hlsli index 2146b3cde..619e4c615 100644 --- a/WickedEngine/shaders/lightingHF.hlsli +++ b/WickedEngine/shaders/lightingHF.hlsli @@ -131,13 +131,11 @@ inline void light_directional(in ShaderEntity light, in Surface surface, inout L [branch] if (light.IsCastingShadow() && surface.IsReceiveShadow()) { - if (GetWeather().cloud_shadow_amount > 0) + if (GetFrame().options & OPTION_BIT_VOLUMETRICCLOUDS_SHADOWS) { - // Fake cloud shadows: - float cloud_shadow = noise_simplex_2D(surface.P.xz * GetWeather().cloud_shadow_scale + GetTime() * GetWeather().cloud_shadow_speed) * 0.5 + 0.5; - shadow *= saturate(cloud_shadow + 1 - GetWeather().cloud_shadow_amount * 2); + shadow *= shadow_2D_volumetricclouds(surface.P); } - + #ifdef SHADOW_MASK_ENABLED [branch] if ((GetFrame().options & OPTION_BIT_RAYTRACED_SHADOWS) == 0 || GetCamera().texture_rtshadow_index < 0) diff --git a/WickedEngine/shaders/objectPS_voxelizer.hlsl b/WickedEngine/shaders/objectPS_voxelizer.hlsl index 18e2a8c6d..f489018b9 100644 --- a/WickedEngine/shaders/objectPS_voxelizer.hlsl +++ b/WickedEngine/shaders/objectPS_voxelizer.hlsl @@ -2,6 +2,7 @@ #define DISABLE_VOXELGI #include "objectHF.hlsli" #include "voxelHF.hlsli" +#include "volumetricCloudsHF.hlsli" // Note: the voxelizer uses an overall simplified material and lighting model (no normal maps, only diffuse light and emissive) @@ -102,10 +103,16 @@ void main(PSInput input) float3 ShPos = mul(load_entitymatrix(light.GetMatrixIndex() + cascade), float4(P, 1)).xyz; // ortho matrix, no divide by .w float3 ShTex = ShPos.xyz * float3(0.5f, -0.5f, 0.5f) + 0.5f; - [branch] if ((saturate(ShTex.x) == ShTex.x) && (saturate(ShTex.y) == ShTex.y) && (saturate(ShTex.z) == ShTex.z)) + [branch] + if ((saturate(ShTex.x) == ShTex.x) && (saturate(ShTex.y) == ShTex.y) && (saturate(ShTex.z) == ShTex.z)) { lightColor *= shadow_2D(light, ShPos, ShTex.xy, cascade); } + + if (GetFrame().options & OPTION_BIT_VOLUMETRICCLOUDS_SHADOWS) + { + lightColor *= shadow_2D_volumetricclouds(P); + } } lighting.direct.diffuse += lightColor; diff --git a/WickedEngine/shaders/skyAtmosphere.hlsli b/WickedEngine/shaders/skyAtmosphere.hlsli index 2a08ec3f2..54a4a2b0d 100644 --- a/WickedEngine/shaders/skyAtmosphere.hlsli +++ b/WickedEngine/shaders/skyAtmosphere.hlsli @@ -1,6 +1,7 @@ #ifndef WI_SKYATMOSPHERE_HF #define WI_SKYATMOSPHERE_HF #include "globals.hlsli" +#include "volumetricCloudsHF.hlsli" /* * @@ -14,13 +15,11 @@ static const float2 transmittanceLUTRes = float2(256, 64); static const float2 multiScatteringLUTRes = float2(32, 32); -static const float2 skyViewLUTRes = float2(192.0, 104); +static const float2 skyViewLUTRes = float2(192, 104); +static const float2 skyLuminanceLUTRes = float2(1, 1); #define USE_CornetteShanks -#define M_TO_SKY_UNIT 0.001f // Engine units are in meters -#define SKY_UNIT_TO_M (1.0 / M_TO_SKY_UNIT) - #define PLANET_RADIUS_OFFSET 0.001f // Float accuracy offset in Sky unit (km, so this is 1m) //////////////////////////////////////////////////////////// @@ -470,12 +469,12 @@ struct SamplingParameters float sampleCountIni; // Used when variableSampleCount is false float2 rayMarchMinMaxSPP; float distanceSPPMaxInv; - //bool perPixelNoise; + bool perPixelNoise; }; SingleScatteringResult IntegrateScatteredLuminance( - in AtmosphereParameters atmosphere, in float2 pixPos, in float3 worldPosition, in float3 worldDirection, in float3 sunDirection, in float3 sunIlluminance, - in SamplingParameters sampling, in bool ground, in float3 depthBufferWorldPos, in bool opaque, in bool mieRayPhase, in bool multiScatteringApprox, + in AtmosphereParameters atmosphere, in float2 pixelPosition, in float3 worldPosition, in float3 worldDirection, in float3 sunDirection, in float3 sunIlluminance, + in SamplingParameters sampling, in float tDepth, in bool opaque, in bool ground, in bool mieRayPhase, in bool multiScatteringApprox, in bool volumetricCloudShadow, in Texture2D transmittanceLutTexture, in Texture2D multiScatteringLUTTexture, in float tMaxMax = 9000000.0f) { SingleScatteringResult result = (SingleScatteringResult) 0; @@ -512,19 +511,10 @@ SingleScatteringResult IntegrateScatteredLuminance( { if (opaque) { - float3 depthBufferWorldPosKm = depthBufferWorldPos * M_TO_SKY_UNIT; - float3 traceStartWorldPosKm = worldPosition + atmosphere.planetCenter; // Planet center is in km - float3 traceStartToSurfaceWorldKm = depthBufferWorldPosKm - traceStartWorldPosKm; - float tDepth = length(traceStartToSurfaceWorldKm); // Apply earth offset to go back to origin as top of earth mode. if (tDepth < tMax) { tMax = tDepth; } - - //if (dot(worldDirection, traceStartToSurfaceWorldKm) < 0.0) - //{ - // return result; - //} } tMax = min(tMax, tMaxMax); @@ -579,16 +569,16 @@ SingleScatteringResult IntegrateScatteredLuminance( t1 = tMaxFloor * t1; } - //if (Sampling.PerPixelNoise) - //{ - // t = t0 + (t1 - t0) * InterleavedGradientNoise(pixPos, GetFrame().frame_count % 16); - //} - //else - //{ - // t = t0 + (t1 - t0) * SampleSegmentT; - //} - t = t0 + (t1 - t0) * sampleSegmentT; - + // Reduce sample count with noise and Temporal AA: + if (sampling.perPixelNoise) + { + t = t0 + (t1 - t0) * InterleavedGradientNoise(pixelPosition, GetFrame().frame_count); + } + else + { + t = t0 + (t1 - t0) * sampleSegmentT; + } + dt = t1 - t0; } else @@ -625,6 +615,12 @@ SingleScatteringResult IntegrateScatteredLuminance( float tEarth = RaySphereIntersectNearest(P, sunDirection, earthO + PLANET_RADIUS_OFFSET * UpVector, atmosphere.bottomRadius); float earthShadow = tEarth >= 0.0f ? 0.0f : 1.0f; + // Volumetric cloud shadow + if (volumetricCloudShadow && GetFrame().options & OPTION_BIT_VOLUMETRICCLOUDS_SHADOWS) + { + earthShadow *= shadow_2D_volumetricclouds(P * SKY_UNIT_TO_M + atmosphere.planetCenter * SKY_UNIT_TO_M); + } + // Dual scattering for multi scattering float3 multiScatteredLuminance = 0.0f; diff --git a/WickedEngine/shaders/skyAtmosphere_multiScatteredLuminanceLutCS.hlsl b/WickedEngine/shaders/skyAtmosphere_multiScatteredLuminanceLutCS.hlsl index 344c2a3fc..c0091eb9c 100644 --- a/WickedEngine/shaders/skyAtmosphere_multiScatteredLuminanceLutCS.hlsl +++ b/WickedEngine/shaders/skyAtmosphere_multiScatteredLuminanceLutCS.hlsl @@ -16,7 +16,6 @@ void main(uint3 DTid : SV_DispatchThreadID) { float2 pixelPosition = float2(DTid.xy) + 0.5; float2 uv = pixelPosition * rcp(multiScatteringLUTRes); - uv = float2(FromSubUvsToUnit(uv.x, multiScatteringLUTRes.x), FromSubUvsToUnit(uv.y, multiScatteringLUTRes.y)); @@ -39,11 +38,12 @@ void main(uint3 DTid : SV_DispatchThreadID) sampling.variableSampleCount = false; sampling.sampleCountIni = 20; // a minimum set of step is required for accuracy unfortunately } - const bool ground = true; - const float depthBufferWorldPos = 0.0; + const float tDepth = 0.0; const bool opaque = false; + const bool ground = true; const bool mieRayPhase = false; const bool multiScatteringApprox = false; + const bool volumetricCloudShadow = false; const float sphereSolidAngle = 4.0 * PI; const float isotropicPhase = 1.0 / sphereSolidAngle; @@ -68,7 +68,7 @@ void main(uint3 DTid : SV_DispatchThreadID) worldDirection.z = cosPhi; SingleScatteringResult result = IntegrateScatteredLuminance( atmosphere, pixelPosition, worldPosition, worldDirection, sunDirection, sunIlluminance, - sampling, ground, depthBufferWorldPos, opaque, mieRayPhase, multiScatteringApprox, transmittanceLUT, multiScatteringLUT); + sampling, tDepth, opaque, ground, mieRayPhase, multiScatteringApprox, volumetricCloudShadow, transmittanceLUT, multiScatteringLUT); MultiScatAs1SharedMem[DTid.z] = result.multiScatAs1 * sphereSolidAngle / (sqrtSample * sqrtSample); LSharedMem[DTid.z] = result.L * sphereSolidAngle / (sqrtSample * sqrtSample); diff --git a/WickedEngine/shaders/skyAtmosphere_skyLuminanceLutCS.hlsl b/WickedEngine/shaders/skyAtmosphere_skyLuminanceLutCS.hlsl index 6681d8e87..5d20bf235 100644 --- a/WickedEngine/shaders/skyAtmosphere_skyLuminanceLutCS.hlsl +++ b/WickedEngine/shaders/skyAtmosphere_skyLuminanceLutCS.hlsl @@ -14,8 +14,7 @@ groupshared float3 SkyLuminanceSharedMem[64]; void main(uint3 DTid : SV_DispatchThreadID) { float2 pixelPosition = float2(DTid.xy) + 0.5; - float2 uv = pixelPosition * rcp(multiScatteringLUTRes); - + float2 uv = pixelPosition * rcp(skyLuminanceLUTRes); AtmosphereParameters atmosphere = GetWeather().atmosphere; @@ -32,11 +31,12 @@ void main(uint3 DTid : SV_DispatchThreadID) sampling.variableSampleCount = false; sampling.sampleCountIni = 10; // Should be enough since we're taking an approximation of luminance around a point } - const bool ground = false; - const float depthBufferWorldPos = 0.0; + const float tDepth = 0.0; const bool opaque = false; + const bool ground = false; const bool mieRayPhase = false; // Perhabs? const bool multiScatteringApprox = true; + const bool volumetricCloudShadow = false; const float sphereSolidAngle = 4.0 * PI; const float isotropicPhase = 1.0 / sphereSolidAngle; @@ -61,7 +61,7 @@ void main(uint3 DTid : SV_DispatchThreadID) worldDirection.z = cosPhi; SingleScatteringResult result = IntegrateScatteredLuminance( atmosphere, pixelPosition, worldPosition, worldDirection, sunDirection, sunIlluminance, - sampling, ground, depthBufferWorldPos, opaque, mieRayPhase, multiScatteringApprox, transmittanceLUT, multiScatteringLUT); + sampling, tDepth, opaque, ground, mieRayPhase, multiScatteringApprox, volumetricCloudShadow, transmittanceLUT, multiScatteringLUT); SkyLuminanceSharedMem[DTid.z] = result.L * sphereSolidAngle / (sqrtSample * sqrtSample); } diff --git a/WickedEngine/shaders/skyAtmosphere_skyViewLutCS.hlsl b/WickedEngine/shaders/skyAtmosphere_skyViewLutCS.hlsl index 63c2152fa..2febe2c84 100644 --- a/WickedEngine/shaders/skyAtmosphere_skyViewLutCS.hlsl +++ b/WickedEngine/shaders/skyAtmosphere_skyViewLutCS.hlsl @@ -57,15 +57,17 @@ void main(uint3 DTid : SV_DispatchThreadID) sampling.sampleCountIni = 30; sampling.rayMarchMinMaxSPP = float2(4, 14); sampling.distanceSPPMaxInv = 0.01; + sampling.perPixelNoise = false; } + const float tDepth = 0.0; const bool ground = false; - const float depthBufferWorldPos = 0.0; const bool opaque = false; const bool mieRayPhase = true; const bool multiScatteringApprox = true; + const bool volumetricCloudShadow = false; SingleScatteringResult ss = IntegrateScatteredLuminance( atmosphere, pixelPosition, worldPosition, worldDirection, sunDirection, sunIlluminance, - sampling, ground, depthBufferWorldPos, opaque, mieRayPhase, multiScatteringApprox, transmittanceLUT, multiScatteringLUT); + sampling, tDepth, opaque, ground, mieRayPhase, multiScatteringApprox, volumetricCloudShadow, transmittanceLUT, multiScatteringLUT); float3 L = ss.L; diff --git a/WickedEngine/shaders/skyAtmosphere_transmittanceLutCS.hlsl b/WickedEngine/shaders/skyAtmosphere_transmittanceLutCS.hlsl index 1b26ca597..5a7f6a9eb 100644 --- a/WickedEngine/shaders/skyAtmosphere_transmittanceLutCS.hlsl +++ b/WickedEngine/shaders/skyAtmosphere_transmittanceLutCS.hlsl @@ -29,14 +29,15 @@ void main(uint3 DTid : SV_DispatchThreadID) sampling.variableSampleCount = false; sampling.sampleCountIni = 40.0f; // Can go a low as 10 sample but energy lost starts to be visible. } - const bool ground = false; - const float depthBufferWorldPos = 0.0; + const float tDepth = 0.0; const bool opaque = false; + const bool ground = false; const bool mieRayPhase = false; const bool multiScatteringApprox = false; + const bool volumetricCloudShadow = false; SingleScatteringResult ss = IntegrateScatteredLuminance( atmosphere, pixelPosition, worldPosition, worldDirection, sunDirection, sunIlluminance, - sampling, ground, depthBufferWorldPos, opaque, mieRayPhase, multiScatteringApprox, transmittanceLUT, multiScatteringLUT); + sampling, tDepth, opaque, ground, mieRayPhase, multiScatteringApprox, volumetricCloudShadow, transmittanceLUT, multiScatteringLUT); float3 transmittance = exp(-ss.opticalDepth); diff --git a/WickedEngine/shaders/skyHF.hlsli b/WickedEngine/shaders/skyHF.hlsli index c8f191622..e4ee8ce52 100644 --- a/WickedEngine/shaders/skyHF.hlsli +++ b/WickedEngine/shaders/skyHF.hlsli @@ -56,16 +56,18 @@ float3 AccurateAtmosphericScattering(Texture2D skyViewLutTexture, Textur sampling.sampleCountIni = 0.0f; sampling.rayMarchMinMaxSPP = float2(4, 14); sampling.distanceSPPMaxInv = 0.01; - } - const bool ground = false; - const float depthBufferWorldPos = 0.0; + sampling.perPixelNoise = false; + } + const float2 pixelPosition = float2(0.0, 0.0); + const float tDepth = 0.0; const bool opaque = false; + const bool ground = false; const bool mieRayPhase = true; const bool multiScatteringApprox = true; - const float2 pixPos = float2(0, 0); + const bool volumetricCloudShadow = false; SingleScatteringResult ss = IntegrateScatteredLuminance( - atmosphere, pixPos, worldPosition, worldDirection, sunDirection, sunIlluminance, - sampling, ground, depthBufferWorldPos, opaque, mieRayPhase, multiScatteringApprox, transmittanceLUT, multiScatteringLUT); + atmosphere, pixelPosition, worldPosition, worldDirection, sunDirection, sunIlluminance, + sampling, tDepth, opaque, ground, mieRayPhase, multiScatteringApprox, volumetricCloudShadow, transmittanceLUT, multiScatteringLUT); luminance = ss.L; } @@ -91,166 +93,11 @@ float3 AccurateAtmosphericScattering(Texture2D skyViewLutTexture, Textur return totalColor; } -float2 hash(float2 p) -{ - p = float2(dot(p, float2(127.1, 311.7)), dot(p, float2(269.5, 183.3))); - return -1.0 + 2.0 * frac(sin(p) * 43758.5453123); -} -float noise(in float2 p) -{ - const float K1 = 0.366025404; // (sqrt(3)-1)/2; - const float K2 = 0.211324865; // (3-sqrt(3))/6; - float2 i = floor(p + (p.x + p.y) * K1); - float2 a = p - i + (i.x + i.y) * K2; - float2 o = (a.x > a.y) ? float2(1.0, 0.0) : float2(0.0, 1.0); //float2 of = 0.5 + 0.5*float2(sign(a.x-a.y), sign(a.y-a.x)); - float2 b = a - o + K2; - float2 c = a - 1.0 + 2.0 * K2; - float3 h = max(0.5 - float3(dot(a, a), dot(b, b), dot(c, c)), 0.0); - float3 n = h * h * h * h * float3(dot(a, hash(i + 0.0)), dot(b, hash(i + o)), dot(c, hash(i + 1.0))); - return dot(n, float3(70.0, 70.0, 70.0)); -} - -float3 CustomAtmosphericScattering(float3 V, float3 sunDirection, float3 sunColor, bool sun_enabled, bool dark_enabled) -{ - const float3 skyColor = GetZenithColor(); - const bool sunPresent = any(sunColor); - const bool sunDisc = sun_enabled && sunPresent; - - const float zenith = V.y; // how much is above (0: horizon, 1: directly above) - const float sunScatter = saturate(sunDirection.y + 0.1f); // how much the sun is directly above. Even if sunis at horizon, we add a constant scattering amount so that light still scatters at horizon - - const float atmosphereDensity = 0.5 + GetWeather().fog.height_sky; // constant of air density, or "fog height" as interpreted here (bigger is more obstruction of sun) - const float zenithDensity = atmosphereDensity / pow(max(0.000001f, zenith), 0.75f); - const float sunScatterDensity = atmosphereDensity / pow(max(0.000001f, sunScatter), 0.75f); - - const float3 aberration = float3(0.39, 0.57, 1.0); // the chromatic aberration effect on the horizon-zenith fade line - const float3 skyAbsorption = saturate(exp2(aberration * -zenithDensity) * 2.0f); // gradient on horizon - const float3 sunAbsorption = sunPresent ? saturate(sunColor * exp2(aberration * -sunScatterDensity) * 2.0f) : 1; // gradient of sun when it's getting below horizon - - const float sunAmount = distance(V, sunDirection); // sun falloff descreasing from mid point - const float rayleigh = sunPresent ? 1.0 + pow(1.0 - saturate(sunAmount), 2.0) * PI * 0.5 : 1; - const float mie_disk = saturate(1.0 - pow(sunAmount, 0.1)); - const float3 mie = mie_disk * mie_disk * (3.0 - 2.0 * mie_disk) * 2.0 * PI * sunAbsorption; - - float3 totalColor = lerp(GetHorizonColor(), GetZenithColor() * zenithDensity * rayleigh, skyAbsorption); - totalColor = lerp(totalColor * skyAbsorption, totalColor, sunScatter); // when sun goes below horizon, absorb sky color - if (sunDisc) - { - const float3 sun = smoothstep(0.03, 0.026, sunAmount) * sunColor * 50.0 * skyAbsorption; // sun disc - totalColor += sun; - totalColor += mie; - } - totalColor *= (sunAbsorption + length(sunAbsorption)) * 0.5f; // when sun goes below horizon, fade out whole sky - totalColor *= 0.25; // exposure level - - if (dark_enabled) - { - totalColor = max(pow(saturate(dot(sunDirection, V)), 64) * sunColor, 0) * skyAbsorption; - } - - return totalColor; -} - -void CalculateClouds(inout float3 sky, float3 V, bool dark_enabled) -{ - if (GetWeather().cloudiness <= 0) - { - return; - } - - // Trace a cloud layer plane: - const float3 o = GetCamera().position; - const float3 d = V; - const float3 planeOrigin = float3(0, 1000, 0); - const float3 planeNormal = float3(0, -1, 0); - const float t = trace_plane(o, d, planeOrigin, planeNormal); - - if (t < 0) - { - return; - } - - const float3 cloudPos = o + d * t; - const float2 cloudUV = cloudPos.xz * GetWeather().cloud_scale; - const float cloudTime = GetFrame().time * GetWeather().cloud_speed; - const float2x2 m = float2x2(1.6, 1.2, -1.2, 1.6); - const uint quality = 8; - - // rotate uvs like a flow effect: - float flow = 0; - { - float2 uv = cloudUV * 0.5f; - float amount = 0.1; - for (uint i = 0; i < quality; i++) - { - flow += noise(uv) * amount; - uv = mul(m, uv); - amount *= 0.4; - } - } - - - // Main shape: - float clouds = 0.0; - { - const float time = cloudTime * 0.2f; - float density = 1.1f; - float2 uv = cloudUV * 0.8f; - uv -= flow - time; - for (uint i = 0; i < quality; i++) - { - clouds += density * noise(uv); - uv = mul(m, uv) + time; - density *= 0.6f; - } - } - - // Detail shape: - { - float detail_shape = 0.0; - const float time = cloudTime * 0.1f; - float density = 0.8f; - float2 uv = cloudUV; - uv -= flow - time; - for (uint i = 0; i < quality; i++) - { - detail_shape += abs(density * noise(uv)); - uv = mul(m, uv) + time; - density *= 0.7f; - } - clouds *= detail_shape + clouds; - clouds *= detail_shape; - } - - - // lerp between "choppy clouds" and "uniform clouds". Lower cloudiness will produce choppy clouds, but very high cloudiness will switch to overcast unfiform clouds: - clouds = lerp(clouds * 9.0f * GetWeather().cloudiness + 0.3f, clouds * 0.5f + 0.5f, pow(saturate(GetWeather().cloudiness), 8)); - clouds = saturate(clouds - (1 - GetWeather().cloudiness)); // modulate constant cloudiness - clouds *= pow(1 - saturate(length(abs(cloudPos.xz * 0.00001f))), 16); //fade close to horizon - - if (dark_enabled) - { - sky *= pow(saturate(1 - clouds), 16.0f); // only sun and clouds. Boost clouds to have nicer sun shafts occlusion - } - else - { - sky = lerp(sky, 1, clouds); // sky and clouds on top - } -} - // Returns sky color modulated by the sun and clouds // V : view direction float3 GetDynamicSkyColor(in float3 V, bool sun_enabled = true, bool clouds_enabled = true, bool dark_enabled = false, bool realistic_sky_stationary = false) { - if (GetFrame().options & OPTION_BIT_SIMPLE_SKY) - { - return lerp(GetHorizonColor(), GetZenithColor(), saturate(V.y * 0.5f + 0.5f)) * GetWeather().sky_exposure; - } - - const float3 sunDirection = GetSunDirection(); - const float3 sunColor = GetSunColor(); - - float3 sky = float3(0, 0, 0); + float3 sky = 0; if (GetFrame().options & OPTION_BIT_REALISTIC_SKY) { @@ -261,8 +108,8 @@ float3 GetDynamicSkyColor(in float3 V, bool sun_enabled = true, bool clouds_enab texture_multiscatteringlut, GetCamera().position, // Ray origin V, // Ray direction - sunDirection, // Position of the sun - sunColor, // Sun Color + GetSunDirection(), // Position of the sun + GetSunColor(), // Sun Color sun_enabled, // Use sun and total dark_enabled, // Enable dark mode for light shafts etc. realistic_sky_stationary // Fixed position for ambient and environment capture. @@ -270,23 +117,11 @@ float3 GetDynamicSkyColor(in float3 V, bool sun_enabled = true, bool clouds_enab } else { - sky = CustomAtmosphericScattering - ( - V, // normalized ray direction - sunDirection, // position of the sun - sunColor, // color of the sun, for disc - sun_enabled, // use sun and total - dark_enabled // enable dark mode for light shafts etc. - ); + sky = lerp(GetHorizonColor(), GetZenithColor(), saturate(V.y * 0.5f + 0.5f)); } sky *= GetWeather().sky_exposure; - if (clouds_enabled) - { - CalculateClouds(sky, V, dark_enabled); - } - return sky; } diff --git a/WickedEngine/shaders/skyPS_static.hlsl b/WickedEngine/shaders/skyPS_static.hlsl index 22e83fc8a..6ee0fe215 100644 --- a/WickedEngine/shaders/skyPS_static.hlsl +++ b/WickedEngine/shaders/skyPS_static.hlsl @@ -9,7 +9,6 @@ float4 main(float4 pos : SV_POSITION, float2 clipspace : TEXCOORD) : SV_TARGET const float3 V = normalize(unprojected.xyz - GetCamera().position); float4 color = float4(DEGAMMA_SKY(texture_globalenvmap.SampleLevel(sampler_linear_clamp, V, 0).rgb), 1); - CalculateClouds(color.rgb, V, false); float4 pos2DPrev = mul(GetCamera().previous_view_projection, float4(unprojected.xyz, 1)); float2 velocity = ((pos2DPrev.xy / pos2DPrev.w - GetCamera().temporalaa_jitter_prev) - (clipspace - GetCamera().temporalaa_jitter)) * float2(0.5f, -0.5f); diff --git a/WickedEngine/shaders/ssr_resolveCS.hlsl b/WickedEngine/shaders/ssr_resolveCS.hlsl index f95a30c6c..9644f46b3 100644 --- a/WickedEngine/shaders/ssr_resolveCS.hlsl +++ b/WickedEngine/shaders/ssr_resolveCS.hlsl @@ -83,14 +83,6 @@ uint3 hash33(uint3 x) return uint3(n, n * 16807u, n * 48271u); //see: http://random.mat.sbg.ac.at/results/karl/server/node4.html } -// Computes post-projection depth from linear depth -float getInverseLinearDepth(float lin, float near, float far) -{ - float z_n = ((lin - 2 * far) * near + far * lin) / (lin * near - far * lin); - float z = (z_n + 1) / 2; - return z; -} - [numthreads(POSTPROCESS_BLOCKSIZE, POSTPROCESS_BLOCKSIZE, 1)] void main(uint3 DTid : SV_DispatchThreadID) { @@ -164,7 +156,7 @@ void main(uint3 DTid : SV_DispatchThreadID) // Convert to post-projection depth so we can construct dual source reprojection buffers later const float lineardepth = texture_lineardepth[DTid.xy] * GetCamera().z_far; - float reprojectionDepth = getInverseLinearDepth(lineardepth + closestRayLength, GetCamera().z_near, GetCamera().z_far); + float reprojectionDepth = compute_inverse_lineardepth(lineardepth + closestRayLength, GetCamera().z_near, GetCamera().z_far); texture_resolve[DTid.xy] = max(result, 0.00001f); texture_resolve_variance[DTid.xy] = resolveVariance; diff --git a/WickedEngine/shaders/volumetricCloud_renderCS.hlsl b/WickedEngine/shaders/volumetricCloud_renderCS.hlsl index 0a14422a9..dc8d19757 100644 --- a/WickedEngine/shaders/volumetricCloud_renderCS.hlsl +++ b/WickedEngine/shaders/volumetricCloud_renderCS.hlsl @@ -1,6 +1,7 @@ #include "globals.hlsli" #include "volumetricCloudsHF.hlsli" #include "skyAtmosphere.hlsli" +#include "lightingHF.hlsli" #include "ShaderInterop_Postprocess.h" /** @@ -23,150 +24,47 @@ * */ +#ifdef VOLUMETRICCLOUD_CAPTURE +PUSHCONSTANT(capture, VolumetricCloudCapturePushConstants); +#else PUSHCONSTANT(postprocess, PostProcess); +#endif // VOLUMETRICCLOUD_CAPTURE -Texture3D texture_shapeNoise : register(t1); -Texture3D texture_detailNoise : register(t2); -Texture2D texture_curlNoise : register(t3); -Texture2D texture_weatherMap : register(t4); +Texture3D texture_shapeNoise : register(t0); +Texture3D texture_detailNoise : register(t1); +Texture2D texture_curlNoise : register(t2); +Texture2D texture_weatherMap : register(t3); +#ifdef VOLUMETRICCLOUD_CAPTURE + +#ifdef MSAA +Texture2DMSArray texture_input_depth_MSAA : register(t4); +#else +TextureCube texture_input_depth : register(t4); +#endif // MSAA + +#else RWTexture2D texture_render : register(u0); RWTexture2D texture_cloudDepth : register(u1); - +#endif // VOLUMETRICCLOUD_CAPTURE // Octaves for multiple-scattering approximation. 1 means single-scattering only. #define MS_COUNT 2 -// Simple FBM cloud model. Old but has its uses -//#define CLOUD_MODE_SIMPLE_FBM +// Because the lights color is limited to half-float precision, we can only set intensity to 65504 which in some cases isn't enough. +#define LOCAL_LIGHTS_INTENSITY_MULTIPLIER 100000.0 - -float GetHeightFractionForPoint(AtmosphereParameters atmosphere, float3 pos) -{ - float planetRadius = atmosphere.bottomRadius * SKY_UNIT_TO_M; - float3 planetCenterWorld = atmosphere.planetCenter * SKY_UNIT_TO_M; - - return saturate((distance(pos, planetCenterWorld) - (planetRadius + GetWeather().volumetric_clouds.CloudStartHeight)) / GetWeather().volumetric_clouds.CloudThickness); -} - -float SampleGradient(float4 gradient, float heightFraction) -{ - return smoothstep(gradient.x, gradient.y, heightFraction) - smoothstep(gradient.z, gradient.w, heightFraction); -} - -float GetDensityHeightGradient(float heightFraction, float3 weatherData) -{ - float cloudType = weatherData.g; - - float smallType = 1.0f - saturate(cloudType * 2.0f); - float mediumType = 1.0f - abs(cloudType - 0.5f) * 2.0f; - float largeType = saturate(cloudType - 0.5f) * 2.0f; - - float4 cloudGradient = (GetWeather().volumetric_clouds.CloudGradientSmall * smallType) + (GetWeather().volumetric_clouds.CloudGradientMedium * mediumType) + (GetWeather().volumetric_clouds.CloudGradientLarge * largeType); - return SampleGradient(cloudGradient, heightFraction); -} - -float3 SampleWeather(float3 pos, float heightFraction, float2 coverageWindOffset) -{ - float4 weatherData = texture_weatherMap.SampleLevel(sampler_linear_wrap, (pos.xz + coverageWindOffset) * GetWeather().volumetric_clouds.WeatherScale * 0.0004, 0); - - // Anvil clouds - weatherData.r = pow(abs(weatherData.r), RemapClamped(heightFraction * GetWeather().volumetric_clouds.AnvilOverhangHeight, 0.7, 0.8, 1.0, lerp(1.0, 0.5, GetWeather().volumetric_clouds.AnvilAmount))); - //weatherData.r *= lerp(1, RemapClamped(pow(heightFraction * xPPDebugParams.y, 0.5), 0.4, 0.95, 1.0, 0.2), xPPDebugParams.x); - - // Apply effects for coverage - weatherData.r = RemapClamped(weatherData.r * GetWeather().volumetric_clouds.CoverageAmount, 0.0, 1.0, saturate(GetWeather().volumetric_clouds.CoverageMinimum - 1.0), 1.0); - weatherData.g = RemapClamped(weatherData.g * GetWeather().volumetric_clouds.TypeAmount, 0.0, 1.0, GetWeather().volumetric_clouds.TypeOverall, 1.0); - - return weatherData.rgb; -} - -float WeatherDensity(float3 weatherData) -{ - const float wetness = saturate(weatherData.b); - return lerp(1.0, 1.0 - GetWeather().volumetric_clouds.WeatherDensityAmount, wetness); -} - -float SampleCloudDensity(float3 p, float heightFraction, float3 weatherData, float3 windOffset, float3 windDirection, float lod, bool sampleDetail) -{ -#ifdef CLOUD_MODE_SIMPLE_FBM - - float3 pos = p + windOffset; - pos += heightFraction * windDirection * GetWeather().volumetric_clouds.SkewAlongWindDirection; - - // Since the clouds have a massive size, we have to adjust scale accordingly - float noiseScale = max(GetWeather().volumetric_clouds.TotalNoiseScale * 0.0004, 0.00001); - - float4 lowFrequencyNoises = texture_shapeNoise.SampleLevel(sampler_linear_wrap, pos * noiseScale, lod); - - // Create an FBM out of the low-frequency Worley Noises - float lowFrequencyFBM = (lowFrequencyNoises.g * 0.625) + - (lowFrequencyNoises.b * 0.25) + - (lowFrequencyNoises.a * 0.125); - - lowFrequencyFBM = saturate(lowFrequencyFBM); - - float cloudSample = Remap(lowFrequencyNoises.r * pow(1.2 - heightFraction, 0.1), lowFrequencyFBM * GetWeather().volumetric_clouds.ShapeNoiseMinMax.x, GetWeather().volumetric_clouds.ShapeNoiseMinMax.y, 0.0, 1.0); - cloudSample *= GetDensityHeightGradient(heightFraction, weatherData); - - float cloudCoverage = weatherData.r; - cloudSample = saturate(Remap(cloudSample, saturate(heightFraction / cloudCoverage), 1.0, 0.0, 1.0)); - cloudSample *= cloudCoverage; - +#ifdef VOLUMETRICCLOUD_CAPTURE + #define MAX_STEP_COUNT capture.MaxStepCount + #define LOD_Min capture.LODMin + #define SHADOW_SAMPLE_COUNT capture.ShadowSampleCount + #define GROUND_CONTRIBUTION_SAMPLE_COUNT capture.GroundContributionSampleCount #else - - float3 pos = p + windOffset; - pos += heightFraction * windDirection * GetWeather().volumetric_clouds.SkewAlongWindDirection; - - float noiseScale = max(GetWeather().volumetric_clouds.TotalNoiseScale * 0.0004, 0.00001); - - float4 lowFrequencyNoises = texture_shapeNoise.SampleLevel(sampler_linear_wrap, pos * noiseScale, lod); - - float3 heightGradient = float3(SampleGradient(GetWeather().volumetric_clouds.CloudGradientSmall, heightFraction), - SampleGradient(GetWeather().volumetric_clouds.CloudGradientMedium, heightFraction), - SampleGradient(GetWeather().volumetric_clouds.CloudGradientLarge, heightFraction)); - - // Depending on the type, clouds with higher altitudes may recieve smaller noises - lowFrequencyNoises.gba *= heightGradient * GetWeather().volumetric_clouds.ShapeNoiseHeightGradientAmount; - - float densityHeightGradient = GetDensityHeightGradient(heightFraction, weatherData); - - float cloudSample = (lowFrequencyNoises.r + lowFrequencyNoises.g + lowFrequencyNoises.b + lowFrequencyNoises.a) * GetWeather().volumetric_clouds.ShapeNoiseMultiplier * densityHeightGradient; - cloudSample = pow(abs(cloudSample), min(1.0, GetWeather().volumetric_clouds.ShapeNoisePower * heightFraction)); - - cloudSample = smoothstep(GetWeather().volumetric_clouds.ShapeNoiseMinMax.x, GetWeather().volumetric_clouds.ShapeNoiseMinMax.y, cloudSample); - - // Remap function for noise against coverage, see GPU Pro 7 - float cloudCoverage = weatherData.r; - cloudSample = saturate(cloudSample - (1.0 - cloudCoverage)) * cloudCoverage; - -#endif - - // Erode with detail noise if cloud sample > 0 - if (cloudSample > 0.0 && sampleDetail) - { - // Apply our curl noise to erode with tiny details. - float3 curlNoise = DecodeCurlNoise(texture_curlNoise.SampleLevel(sampler_linear_wrap, p.xz * GetWeather().volumetric_clouds.CurlScale * noiseScale, 0).rgb); - pos += float3(curlNoise.r, curlNoise.b, curlNoise.g) * heightFraction * GetWeather().volumetric_clouds.CurlNoiseModifier; - - float3 highFrequencyNoises = texture_detailNoise.SampleLevel(sampler_linear_wrap, pos * GetWeather().volumetric_clouds.DetailScale * noiseScale, lod).rgb; - - // Create an FBM out of the high-frequency Worley Noises - float highFrequencyFBM = (highFrequencyNoises.r * 0.625) + - (highFrequencyNoises.g * 0.25) + - (highFrequencyNoises.b * 0.125); - - highFrequencyFBM = saturate(highFrequencyFBM); - - // Dilate detail noise based on height - float highFrequenceNoiseModifier = lerp(1.0 - highFrequencyFBM, highFrequencyFBM, saturate(heightFraction * GetWeather().volumetric_clouds.DetailNoiseHeightFraction)); - - // Erode with base of clouds - cloudSample = Remap(cloudSample, highFrequenceNoiseModifier * GetWeather().volumetric_clouds.DetailNoiseModifier, 1.0, 0.0, 1.0); - } - - return max(cloudSample, 0.0); -} + #define MAX_STEP_COUNT GetWeather().volumetric_clouds.MaxStepCount + #define LOD_Min GetWeather().volumetric_clouds.LODMin + #define SHADOW_SAMPLE_COUNT GetWeather().volumetric_clouds.ShadowSampleCount + #define GROUND_CONTRIBUTION_SAMPLE_COUNT GetWeather().volumetric_clouds.GroundContributionSampleCount +#endif // VOLUMETRICCLOUD_CAPTURE // Participating media is the term used to describe volumes filled with particles. // Such particles can be large impurities, e.g. dust, pollution, water droplets, or simply particles, e.g. molecules @@ -214,7 +112,7 @@ void VolumetricShadow(inout ParticipatingMedia participatingMedia, in Atmosphere extinctionAccumulation[ms] = 0.0f; } - const float sampleCount = GetWeather().volumetric_clouds.ShadowSampleCount; + const float sampleCount = SHADOW_SAMPLE_COUNT; const float sampleSegmentT = 0.5f; float lodOffset = 0.5; @@ -239,13 +137,13 @@ void VolumetricShadow(inout ParticipatingMedia participatingMedia, in Atmosphere break; } - float3 weatherData = SampleWeather(samplePoint, heightFraction, coverageWindOffset); - if (weatherData.r < 0.4) + float3 weatherData = SampleWeather(texture_weatherMap, samplePoint, heightFraction, coverageWindOffset); + if (weatherData.r < 0.25) { continue; } - float shadowCloudDensity = SampleCloudDensity(samplePoint, heightFraction, weatherData, windOffset, windDirection, lod + lodOffset, true); + float shadowCloudDensity = SampleCloudDensity(texture_shapeNoise, texture_detailNoise, texture_curlNoise, samplePoint, heightFraction, weatherData, windOffset, windDirection, lod + lodOffset, true); float3 shadowExtinction = GetWeather().volumetric_clouds.ExtinctionCoefficient * shadowCloudDensity; ParticipatingMedia shadowParticipatingMedia = SampleParticipatingMedia(0.0f, shadowExtinction, GetWeather().volumetric_clouds.MultiScatteringScattering, GetWeather().volumetric_clouds.MultiScatteringExtinction, 0.0f); @@ -281,7 +179,7 @@ void VolumetricGroundContribution(inout float3 environmentLuminance, in Atmosphe const float contributionStepLength = min(4000.0, cloudSampleHeightToBottom); const float3 groundScatterDirection = float3(0.0, -1.0, 0.0); - const float sampleCount = GetWeather().volumetric_clouds.GroundContributionSampleCount; + const float sampleCount = GROUND_CONTRIBUTION_SAMPLE_COUNT; const float sampleSegmentT = 0.5f; // Ground Contribution tracing loop, same idea as volumetric shadow @@ -307,13 +205,13 @@ void VolumetricGroundContribution(inout float3 environmentLuminance, in Atmosphe break; }*/ - float3 weatherData = SampleWeather(samplePoint, heightFraction, coverageWindOffset); - if (weatherData.r < 0.4) + float3 weatherData = SampleWeather(texture_weatherMap, samplePoint, heightFraction, coverageWindOffset); + if (weatherData.r < 0.25) { continue; } - float contributionCloudDensity = SampleCloudDensity(samplePoint, heightFraction, weatherData, windOffset, windDirection, lod + lodOffset, true); + float contributionCloudDensity = SampleCloudDensity(texture_shapeNoise, texture_detailNoise, texture_curlNoise, samplePoint, heightFraction, weatherData, windOffset, windDirection, lod + lodOffset, true); float3 contributionExtinction = GetWeather().volumetric_clouds.ExtinctionCoefficient * contributionCloudDensity; @@ -385,12 +283,82 @@ float3 SampleAmbientLight(float heightFraction) } } +float3 SampleLocalLights(float3 worldPosition) +{ + float3 localLightLuminance = 0; + + [loop] + for (uint iterator = 0; iterator < GetFrame().lightarray_count; iterator++) + { + ShaderEntity light = load_entity(GetFrame().lightarray_offset + iterator); + + if (light.GetFlags() & ENTITY_FLAG_LIGHT_VOLUMETRICCLOUDS) + { + float3 L = 0; + float dist = 0; + float3 lightColor = 0; + + // Only point and spot lights are available for now + switch (light.GetType()) + { + case ENTITY_TYPE_POINTLIGHT: + { + L = light.position - worldPosition; + const float dist2 = dot(L, L); + const float range = light.GetRange(); + const float range2 = range * range; + + [branch] + if (dist2 < range2) + { + dist = sqrt(dist2); + + lightColor = light.GetColor().rgb; + lightColor = min(lightColor, HALF_FLT_MAX) * LOCAL_LIGHTS_INTENSITY_MULTIPLIER; + lightColor *= attenuation_pointlight(dist, dist2, range, range2); + } + } + break; + case ENTITY_TYPE_SPOTLIGHT: + { + L = light.position - worldPosition; + const float dist2 = dot(L, L); + const float range = light.GetRange(); + const float range2 = range * range; + + [branch] + if (dist2 < range2) + { + dist = sqrt(dist2); + L /= dist; + + const float spotFactor = dot(L, light.GetDirection()); + const float spotCutoff = light.GetConeAngleCos(); + + [branch] + if (spotFactor > spotCutoff) + { + lightColor = light.GetColor().rgb; + lightColor = min(lightColor, HALF_FLT_MAX) * LOCAL_LIGHTS_INTENSITY_MULTIPLIER; + lightColor *= attenuation_spotlight(dist, dist2, range, range2, spotFactor, light.GetAngleScale(), light.GetAngleOffset()); + } + } + } + break; + } + + localLightLuminance += lightColor * UniformPhase(); + } + } + + return localLightLuminance; +} + void VolumetricCloudLighting(AtmosphereParameters atmosphere, float3 startPosition, float3 worldPosition, float3 sunDirection, float3 sunIlluminance, float cosTheta, float stepSize, float heightFraction, float cloudDensity, float3 weatherData, float3 windOffset, float3 windDirection, float2 coverageWindOffset, float lod, inout float3 luminance, inout float3 transmittanceToView, inout float depthWeightedSum, inout float depthWeightsSum) { // Setup base parameters - //float3 albedo = GetWeather().volumetric_clouds.Albedo * cloudDensity; float3 albedo = pow(saturate(GetWeather().volumetric_clouds.Albedo * cloudDensity * GetWeather().volumetric_clouds.BeerPowder), GetWeather().volumetric_clouds.BeerPowderPower); // Artistic approach float3 extinction = GetWeather().volumetric_clouds.ExtinctionCoefficient * cloudDensity; @@ -434,7 +402,11 @@ void VolumetricCloudLighting(AtmosphereParameters atmosphere, float3 startPositi depthWeightedSum += depthWeight * length(worldPosition - startPosition); depthWeightsSum += depthWeight; + + // Sample local lights + float3 localLightLuminance = SampleLocalLights(worldPosition); + // Analytical scattering integration based on multiple scattering [unroll] @@ -444,10 +416,11 @@ void VolumetricCloudLighting(AtmosphereParameters atmosphere, float3 startPositi const float3 extinctionCoefficients = participatingMedia.extinctionCoefficients[ms]; const float3 transmittanceToLight = participatingMedia.transmittanceToLight[ms]; - float3 sunSkyLuminance = transmittanceToLight * sunIlluminance * participatingMediaPhase.phase[ms]; - sunSkyLuminance += (ms == 0 ? environmentLuminance : float3(0.0, 0.0, 0.0)); // only apply at last + float3 lightLuminance = transmittanceToLight * sunIlluminance * participatingMediaPhase.phase[ms]; + lightLuminance += (ms == 0 ? environmentLuminance : float3(0.0, 0.0, 0.0)); // only apply at last + lightLuminance += localLightLuminance; - const float3 scatteredLuminance = (sunSkyLuminance * scatteringCoefficients) * WeatherDensity(weatherData); // + emission. Light can be emitted when media reach high heat. Could be used to make lightning + const float3 scatteredLuminance = (lightLuminance * scatteringCoefficients) * WeatherDensity(weatherData); // + emission. Light can be emitted when media reach high heat. Could be used to make lightning // See slide 28 at http://www.frostbite.com/2015/08/physically-based-unified-volumetric-rendering-in-frostbite/ @@ -463,94 +436,10 @@ void VolumetricCloudLighting(AtmosphereParameters atmosphere, float3 startPositi } } - -void RenderClouds(float3 rayOrigin, float3 rayDirection, float t, float steps, float stepSize, - inout float3 luminance, inout float3 transmittanceToView, inout float depthWeightedSum, inout float depthWeightsSum) -{ - // Wind animation offsets - float3 windDirection = float3(cos(GetWeather().volumetric_clouds.WindAngle), -GetWeather().volumetric_clouds.WindUpAmount, sin(GetWeather().volumetric_clouds.WindAngle)); - float3 windOffset = GetWeather().volumetric_clouds.WindSpeed * GetWeather().volumetric_clouds.AnimationMultiplier * windDirection * GetFrame().time; - - float2 coverageWindDirection = float2(cos(GetWeather().volumetric_clouds.CoverageWindAngle), sin(GetWeather().volumetric_clouds.CoverageWindAngle)); - float2 coverageWindOffset = GetWeather().volumetric_clouds.CoverageWindSpeed * GetWeather().volumetric_clouds.AnimationMultiplier * coverageWindDirection * GetFrame().time; - - - AtmosphereParameters atmosphere = GetWeather().atmosphere; - - float3 sunIlluminance = GetSunColor(); - float3 sunDirection = GetSunDirection(); - - float cosTheta = dot(rayDirection, sunDirection); - - - // Init - float zeroDensitySampleCount = 0.0; - float stepLength = GetWeather().volumetric_clouds.BigStepMarch; - - float3 sampleWorldPosition = rayOrigin + rayDirection * t; - - [loop] - for (float i = 0.0; i < steps; i += stepLength) - { - float heightFraction = GetHeightFractionForPoint(atmosphere, sampleWorldPosition); - if (heightFraction < 0.0 || heightFraction > 1.0) - { - break; - } - - float3 weatherData = SampleWeather(sampleWorldPosition, heightFraction, coverageWindOffset); - if (weatherData.r < 0.3) - { - // If value is low, continue marching until we quit or hit something. - sampleWorldPosition += rayDirection * stepSize * stepLength; - zeroDensitySampleCount += 1.0; - stepLength = zeroDensitySampleCount > 10.0 ? GetWeather().volumetric_clouds.BigStepMarch : 1.0; // If zero count has reached a high number, switch to big steps - continue; - } - - - float rayDepth = distance(GetCamera().position, sampleWorldPosition); - float lod = step(GetWeather().volumetric_clouds.LODDistance, rayDepth) + GetWeather().volumetric_clouds.LODMin; - float cloudDensity = saturate(SampleCloudDensity(sampleWorldPosition, heightFraction, weatherData, windOffset, windDirection, lod, true)); - - if (cloudDensity > 0.0) - { - zeroDensitySampleCount = 0.0; - - if (stepLength > 1.0) - { - // If we already did big steps, then move back and refine ray - i -= stepLength - 1.0; - sampleWorldPosition -= rayDirection * stepSize * (stepLength - 1.0); - weatherData = SampleWeather(sampleWorldPosition, heightFraction, coverageWindOffset); - cloudDensity = saturate(SampleCloudDensity(sampleWorldPosition, heightFraction, weatherData, windOffset, windDirection, lod, true)); - } - - - VolumetricCloudLighting(atmosphere, rayOrigin, sampleWorldPosition, sunDirection, sunIlluminance, cosTheta, - stepSize, heightFraction, cloudDensity, weatherData, windOffset, windDirection, coverageWindOffset, lod, - luminance, transmittanceToView, depthWeightedSum, depthWeightsSum); - - if (all(transmittanceToView < GetWeather().volumetric_clouds.TransmittanceThreshold)) - { - break; - } - } - else - { - zeroDensitySampleCount += 1.0; - } - - stepLength = zeroDensitySampleCount > 10.0 ? GetWeather().volumetric_clouds.BigStepMarch : 1.0; - - sampleWorldPosition += rayDirection * stepSize * stepLength; - } -} - float CalculateAtmosphereBlend(float tDepth) { // Progressively increase alpha as clouds reaches the desired distance. - float fogDistance = saturate(tDepth * GetWeather().volumetric_clouds.HorizonBlendAmount * 0.00001); + float fogDistance = saturate(tDepth * GetWeather().volumetric_clouds.HorizonBlendAmount); float fade = pow(fogDistance, GetWeather().volumetric_clouds.HorizonBlendPower); fade = smoothstep(0.0, 1.0, fade); @@ -560,37 +449,11 @@ float CalculateAtmosphereBlend(float tDepth) return fade; } - -static const uint2 g_HalfResIndexToCoordinateOffset[4] = { uint2(0, 0), uint2(1, 0), uint2(0, 1), uint2(1, 1) }; - -// Calculates checkerboard undersampling position -int ComputeCheckerBoardIndex(int2 renderCoord, int subPixelIndex) -{ - const int localOffset = (renderCoord.x & 1 + renderCoord.y & 1) & 1; - const int checkerBoardLocation = (subPixelIndex + localOffset) & 0x3; - return checkerBoardLocation; -} - -[numthreads(POSTPROCESS_BLOCKSIZE, POSTPROCESS_BLOCKSIZE, 1)] -void main(uint3 DTid : SV_DispatchThreadID) -{ - int subPixelIndex = GetFrame().frame_count % 4; - int checkerBoardIndex = ComputeCheckerBoardIndex(DTid.xy, subPixelIndex); - uint2 halfResCoord = DTid.xy * 2 + g_HalfResIndexToCoordinateOffset[checkerBoardIndex]; - - const float2 uv = (halfResCoord + 0.5) * postprocess.params0.zw; + +void RenderClouds(uint3 DTid, float2 uv, float depth, float3 depthWorldPosition, float3 rayOrigin, float3 rayDirection, inout float4 cloudColor, inout float2 cloudDepth) +{ + AtmosphereParameters atmosphere = GetWeather().atmosphere; - float x = uv.x * 2 - 1; - float y = (1 - uv.y) * 2 - 1; - float2 screenPosition = float2(x, y); - - float4 unprojected = mul(GetCamera().inverse_view_projection, float4(screenPosition, 0, 1)); - unprojected.xyz /= unprojected.w; - - float3 rayOrigin = GetCamera().position; - float3 rayDirection = normalize(unprojected.xyz - rayOrigin); - - float tMin = -FLT_MAX; float tMax = -FLT_MAX; float t; @@ -598,10 +461,8 @@ void main(uint3 DTid : SV_DispatchThreadID) float steps; float stepSize; { - AtmosphereParameters parameters = GetWeather().atmosphere; - - float planetRadius = parameters.bottomRadius * SKY_UNIT_TO_M; - float3 planetCenterWorld = parameters.planetCenter * SKY_UNIT_TO_M; + float planetRadius = atmosphere.bottomRadius * SKY_UNIT_TO_M; + float3 planetCenterWorld = atmosphere.planetCenter * SKY_UNIT_TO_M; const float cloudBottomRadius = planetRadius + GetWeather().volumetric_clouds.CloudStartHeight; const float cloudTopRadius = planetRadius + GetWeather().volumetric_clouds.CloudStartHeight + GetWeather().volumetric_clouds.CloudThickness; @@ -636,70 +497,251 @@ void main(uint3 DTid : SV_DispatchThreadID) } else { - texture_render[DTid.xy] = float4(0.0, 0.0, 0.0, 0.0); // Inverted alpha - texture_cloudDepth[DTid.xy] = FLT_MAX; + cloudColor = float4(0.0, 0.0, 0.0, 0.0); + cloudDepth = HALF_FLT_MAX; return; } if (tMax <= tMin || tMin > GetWeather().volumetric_clouds.RenderDistance) { - texture_render[DTid.xy] = float4(0.0, 0.0, 0.0, 0.0); // Inverted alpha - texture_cloudDepth[DTid.xy] = FLT_MAX; + cloudColor = float4(0.0, 0.0, 0.0, 0.0); + cloudDepth = HALF_FLT_MAX; return; } - // Depth buffer intersection - float depth = texture_depth.SampleLevel(sampler_point_clamp, uv, 1).r; - float3 depthWorldPosition = reconstruct_position(uv, depth); tToDepthBuffer = length(depthWorldPosition - rayOrigin); - tMax = depth == 0.0 ? tMax : min(tMax, tToDepthBuffer); // Exclude skybox + + // Exclude skybox to allow infinite distance + tMax = depth == 0.0 ? tMax : min(tMax, tToDepthBuffer); + + // Set infinite distance value to half precision for reprojection, which is rendertarget precision + tToDepthBuffer = depth == 0.0 ? HALF_FLT_MAX : tToDepthBuffer; const float marchingDistance = min(GetWeather().volumetric_clouds.MaxMarchingDistance, tMax - tMin); tMax = tMin + marchingDistance; - steps = GetWeather().volumetric_clouds.MaxStepCount * saturate((tMax - tMin) * (1.0 / GetWeather().volumetric_clouds.InverseDistanceStepCount)); + steps = MAX_STEP_COUNT * saturate((tMax - tMin) * (1.0 / GetWeather().volumetric_clouds.InverseDistanceStepCount)); stepSize = (tMax - tMin) / steps; +#ifdef VOLUMETRICCLOUD_CAPTURE + float offset = 0.5; // noise avg = 0.5 +#else //float offset = dither(DTid.xy + GetTemporalAASampleRotation()); - float offset = blue_noise(DTid.xy).x; //float offset = InterleavedGradientNoise(DTid.xy, GetFrame().frame_count % 16); - + float offset = blue_noise(DTid.xy).x; +#endif + //t = tMin + 0.5 * stepSize; - t = tMin + offset * stepSize * GetWeather().volumetric_clouds.BigStepMarch; // offset avg = 0.5 + t = tMin + offset * stepSize * GetWeather().volumetric_clouds.BigStepMarch; } - - // Raymarch + + float3 windDirection = float3(cos(GetWeather().volumetric_clouds.WindAngle), -GetWeather().volumetric_clouds.WindUpAmount, sin(GetWeather().volumetric_clouds.WindAngle)); + float3 windOffset = GetWeather().volumetric_clouds.WindSpeed * GetWeather().volumetric_clouds.AnimationMultiplier * windDirection * GetFrame().time; + + float2 coverageWindDirection = float2(cos(GetWeather().volumetric_clouds.CoverageWindAngle), sin(GetWeather().volumetric_clouds.CoverageWindAngle)); + float2 coverageWindOffset = GetWeather().volumetric_clouds.CoverageWindSpeed * GetWeather().volumetric_clouds.AnimationMultiplier * coverageWindDirection * GetFrame().time; + + float3 sunIlluminance = GetSunColor(); + float3 sunDirection = GetSunDirection(); + + float cosTheta = dot(rayDirection, sunDirection); + + float3 luminance = 0.0; float3 transmittanceToView = 1.0; float depthWeightedSum = 0.0; float depthWeightsSum = 0.0; - RenderClouds(rayOrigin, rayDirection, t, steps, stepSize, - luminance, transmittanceToView, depthWeightedSum, depthWeightsSum); + + float3 sampleWorldPosition = rayOrigin + rayDirection * t; + + float zeroDensitySampleCount = 0.0; + float stepLength = GetWeather().volumetric_clouds.BigStepMarch; + + [loop] + for (float i = 0.0; i < steps; i += stepLength) + { + float heightFraction = GetHeightFractionForPoint(atmosphere, sampleWorldPosition); + if (heightFraction < 0.0 || heightFraction > 1.0) + { + break; + } + + float3 weatherData = SampleWeather(texture_weatherMap, sampleWorldPosition, heightFraction, coverageWindOffset); + if (weatherData.r < 0.25) + { + // If value is low, continue marching until we quit or hit something. + sampleWorldPosition += rayDirection * stepSize * stepLength; + zeroDensitySampleCount += 1.0; + stepLength = zeroDensitySampleCount > 10.0 ? GetWeather().volumetric_clouds.BigStepMarch : 1.0; // If zero count has reached a high number, switch to big steps + continue; + } + + + float rayDepth = distance(GetCamera().position, sampleWorldPosition); + float lod = step(GetWeather().volumetric_clouds.LODDistance, rayDepth) + LOD_Min; + float cloudDensity = saturate(SampleCloudDensity(texture_shapeNoise, texture_detailNoise, texture_curlNoise, sampleWorldPosition, heightFraction, weatherData, windOffset, windDirection, lod, true)); + + if (cloudDensity > 0.0) + { + zeroDensitySampleCount = 0.0; + + if (stepLength > 1.0) + { + // If we already did big steps, then move back and refine ray + i -= stepLength - 1.0; + sampleWorldPosition -= rayDirection * stepSize * (stepLength - 1.0); + weatherData = SampleWeather(texture_weatherMap, sampleWorldPosition, heightFraction, coverageWindOffset); + cloudDensity = saturate(SampleCloudDensity(texture_shapeNoise, texture_detailNoise, texture_curlNoise, sampleWorldPosition, heightFraction, weatherData, windOffset, windDirection, lod, true)); + } + + + VolumetricCloudLighting(atmosphere, rayOrigin, sampleWorldPosition, sunDirection, sunIlluminance, cosTheta, + stepSize, heightFraction, cloudDensity, weatherData, windOffset, windDirection, coverageWindOffset, lod, + luminance, transmittanceToView, depthWeightedSum, depthWeightsSum); + + if (all(transmittanceToView < GetWeather().volumetric_clouds.TransmittanceThreshold)) + { + break; + } + } + else + { + zeroDensitySampleCount += 1.0; + } + + stepLength = zeroDensitySampleCount > 10.0 ? GetWeather().volumetric_clouds.BigStepMarch : 1.0; + + sampleWorldPosition += rayDirection * stepSize * stepLength; + } float tDepth = depthWeightsSum == 0.0 ? tMax : depthWeightedSum / max(depthWeightsSum, 0.0000000001); - //float3 absoluteWorldPosition = rayOrigin + rayDirection * tDepth; // Could be used for other effects later that require worldPosition + //float3 cloudWorldPosition = rayOrigin + rayDirection * tDepth; float approxTransmittance = dot(transmittanceToView.rgb, 1.0 / 3.0); float grayScaleTransmittance = approxTransmittance < GetWeather().volumetric_clouds.TransmittanceThreshold ? 0.0 : approxTransmittance; - - float4 color = float4(luminance, grayScaleTransmittance); - color.a = 1.0 - color.a; // Invert to match reprojection. Early color returns has to be inverted too. + // Apply aerial perspective + if (depthWeightsSum > 0.0 && GetFrame().options & OPTION_BIT_REALISTIC_SKY) + { + float3 worldPosition = GetCameraPlanetPos(atmosphere, rayOrigin); + float3 worldDirection = rayDirection; + + // Move to top atmosphere as the starting point for ray marching. + // This is critical to be after the above to not disrupt above atmosphere tests and voxel selection. + if (MoveToTopAtmosphere(worldPosition, worldDirection, atmosphere.topRadius)) + { + SamplingParameters sampling; + { + sampling.variableSampleCount = true; + sampling.sampleCountIni = 0.0f; + sampling.rayMarchMinMaxSPP = float2(10, 25); + sampling.distanceSPPMaxInv = 0.01; +#ifdef VOLUMETRICCLOUD_CAPTURE + sampling.perPixelNoise = false; +#else + sampling.perPixelNoise = true; +#endif + } + const bool opaque = true; + const bool ground = false; + const bool mieRayPhase = true; + const bool multiScatteringApprox = true; + const bool volumetricCloudShadow = true; + SingleScatteringResult ss = IntegrateScatteredLuminance( + atmosphere, DTid.xy, worldPosition, worldDirection, sunDirection, sunIlluminance, + sampling, tDepth * M_TO_SKY_UNIT, opaque, ground, mieRayPhase, multiScatteringApprox, volumetricCloudShadow, texture_transmittancelut, texture_multiscatteringlut); + float transmittance = dot(ss.transmittance, float3(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f)); + float4 aerialPerspective = float4(ss.L, transmittance); + + luminance = aerialPerspective.a * luminance + aerialPerspective.rgb * (1.0 - approxTransmittance); + //luminance = aerialPerspective.rgb * (1.0 - approxTransmittance); // Debug + } + } + + float4 color = float4(luminance, 1.0 - grayScaleTransmittance); + // Blend clouds with horizon if (depthWeightsSum > 0.0) { - // Only apply to clouds float atmosphereBlend = CalculateAtmosphereBlend(tDepth); - color *= 1.0 - atmosphereBlend; - //color.rgb = color.rgb * (1.0 - atmosphereBlend) + (float3(0.7, 0.8, 1.0) * 2.0) * atmosphereBlend; } - // Output - texture_render[DTid.xy] = color; - texture_cloudDepth[DTid.xy] = float2(tDepth, tToDepthBuffer); // Linear depth + cloudColor = color; + cloudDepth = float2(tDepth, tToDepthBuffer); // Linear depth } + +#ifdef VOLUMETRICCLOUD_CAPTURE +[numthreads(GENERATEMIPCHAIN_2D_BLOCK_SIZE, GENERATEMIPCHAIN_2D_BLOCK_SIZE, 1)] +void main(uint3 DTid : SV_DispatchThreadID) +{ + TextureCubeArray input = bindless_cubearrays[capture.texture_input]; + RWTexture2DArray output = bindless_rwtextures2DArray[capture.texture_output]; + + const float2 uv = (DTid.xy + 0.5) * capture.resolution_rcp; + const float3 N = uv_to_cubemap(uv, DTid.z); + +#ifdef MSAA + float3 uv_slice = cubemap_to_uv(N); + uint2 cube_dim; + uint cube_elements; + uint cube_sam; + texture_input_depth_MSAA.GetDimensions(cube_dim.x, cube_dim.y, cube_elements, cube_sam); + uv_slice.xy *= cube_dim; + const float depth = texture_input_depth_MSAA.Load(uv_slice, 0); +#else + const float depth = texture_input_depth.SampleLevel(sampler_point_clamp, N, 0).r; +#endif // MSAA + + float3 depthWorldPosition = reconstruct_position(uv, depth, xCubemapRenderCams[DTid.z].inverse_view_projection); + + float3 rayOrigin = GetCamera().position; + float3 rayDirection = normalize(N); + + float4 cloudColor = 0; + float2 cloudDepth = 0; + RenderClouds(DTid, uv, depth, depthWorldPosition, rayOrigin, rayDirection, cloudColor, cloudDepth); + + float4 composite = input.SampleLevel(sampler_linear_clamp, float4(N, capture.arrayIndex), 0); + + // Output + output[uint3(DTid.xy, DTid.z + capture.arrayIndex * 6)] = float4(composite.rgb * (1.0 - cloudColor.a) + cloudColor.rgb, composite.a * (1.0 - cloudColor.a)); +} +#else +[numthreads(POSTPROCESS_BLOCKSIZE, POSTPROCESS_BLOCKSIZE, 1)] +void main(uint3 DTid : SV_DispatchThreadID) +{ + const uint2 halfResIndexToCoordinateOffset[4] = { uint2(0, 0), uint2(1, 0), uint2(0, 1), uint2(1, 1) }; + + int subPixelIndex = GetFrame().frame_count % 4; + int checkerBoardIndex = ComputeCheckerBoardIndex(DTid.xy, subPixelIndex); + uint2 halfResCoord = DTid.xy * 2 + halfResIndexToCoordinateOffset[checkerBoardIndex]; + + const float2 uv = (halfResCoord + 0.5) * postprocess.params0.zw; + const float depth = texture_depth.SampleLevel(sampler_point_clamp, uv, 1).r; // Second mip reprojection + + float x = uv.x * 2 - 1; + float y = (1 - uv.y) * 2 - 1; + float2 screenPosition = float2(x, y); + + float4 unprojected = mul(GetCamera().inverse_view_projection, float4(screenPosition, 0, 1)); + unprojected.xyz /= unprojected.w; + + float3 depthWorldPosition = reconstruct_position(uv, depth); + + float3 rayOrigin = GetCamera().position; + float3 rayDirection = normalize(unprojected.xyz - rayOrigin); + + float4 cloudColor = 0; + float2 cloudDepth = 0; + RenderClouds(DTid, uv, depth, depthWorldPosition, rayOrigin, rayDirection, cloudColor, cloudDepth); + + // Output + texture_render[DTid.xy] = cloudColor; + texture_cloudDepth[DTid.xy] = cloudDepth; +} +#endif // VOLUMETRICCLOUD_CAPTURE diff --git a/WickedEngine/shaders/volumetricCloud_renderCS_capture.hlsl b/WickedEngine/shaders/volumetricCloud_renderCS_capture.hlsl new file mode 100644 index 000000000..4fe164aa7 --- /dev/null +++ b/WickedEngine/shaders/volumetricCloud_renderCS_capture.hlsl @@ -0,0 +1,2 @@ +#define VOLUMETRICCLOUD_CAPTURE +#include "volumetricCloud_renderCS.hlsl" diff --git a/WickedEngine/shaders/volumetricCloud_renderCS_capture_MSAA.hlsl b/WickedEngine/shaders/volumetricCloud_renderCS_capture_MSAA.hlsl new file mode 100644 index 000000000..d310d6beb --- /dev/null +++ b/WickedEngine/shaders/volumetricCloud_renderCS_capture_MSAA.hlsl @@ -0,0 +1,2 @@ +#define MSAA +#include "volumetricCloud_renderCS_capture.hlsl" diff --git a/WickedEngine/shaders/volumetricCloud_reprojectCS.hlsl b/WickedEngine/shaders/volumetricCloud_reprojectCS.hlsl index b2977a198..96a79880e 100644 --- a/WickedEngine/shaders/volumetricCloud_reprojectCS.hlsl +++ b/WickedEngine/shaders/volumetricCloud_reprojectCS.hlsl @@ -1,4 +1,5 @@ #include "globals.hlsli" +#include "volumetricCloudsHF.hlsli" #include "ShaderInterop_Postprocess.h" PUSHCONSTANT(postprocess, PostProcess); @@ -7,32 +8,29 @@ Texture2D cloud_current : register(t0); Texture2D cloud_depth_current : register(t1); Texture2D cloud_history : register(t2); Texture2D cloud_depth_history : register(t3); +Texture2D cloud_additional_history : register(t4); RWTexture2D output : register(u0); RWTexture2D output_depth : register(u1); +RWTexture2D output_additional : register(u2); +RWTexture2D output_cloudMask : register(u3); -// This function compute the checkerboard undersampling position -int ComputeCheckerBoardIndex(int2 renderCoord, int subPixelIndex) -{ - const int localOffset = (renderCoord.x & 1 + renderCoord.y & 1) & 1; - const int checkerBoardLocation = (subPixelIndex + localOffset) & 0x3; - return checkerBoardLocation; -} +static const float2 temporalResponseMinMax = float2(0.5, 0.9); -// Computes post-projection depth from linear depth -float getInverseLinearDepth(float lin, float near, float far) -{ - float z_n = ((lin - 2 * far) * near + far * lin) / (lin * near - far * lin); - float z = (z_n + 1) / 2; - return z; -} +// When moving fast reprojection cannot catch up. This value eliminates the ghosting but results in clipping artefacts +//#define ADDITIONAL_BOX_CLAMP [numthreads(POSTPROCESS_BLOCKSIZE, POSTPROCESS_BLOCKSIZE, 1)] void main(uint3 DTid : SV_DispatchThreadID) { - uint2 renderCoord = DTid.xy / 2; const float2 uv = (DTid.xy + 0.5f) * postprocess.resolution_rcp; + uint2 renderResolution = postprocess.resolution / 2; + uint2 renderCoord = DTid.xy / 2; + + uint2 minRenderCoord = uint2(0, 0); + uint2 maxRenderCoord = renderResolution - 1; + #if 0 // Calculate screen dependant motion vector @@ -55,7 +53,7 @@ void main(uint3 DTid : SV_DispatchThreadID) float2 screenPosition = float2(x, y); float currentCloudLinearDepth = cloud_depth_current.SampleLevel(sampler_point_clamp, uv, 0).x; - float currentCloudDepth = getInverseLinearDepth(currentCloudLinearDepth, GetCamera().z_near, GetCamera().z_far); + float currentCloudDepth = compute_inverse_lineardepth(currentCloudLinearDepth, GetCamera().z_near, GetCamera().z_far); float4 thisClip = float4(screenPosition, currentCloudDepth, 1.0); @@ -83,6 +81,7 @@ void main(uint3 DTid : SV_DispatchThreadID) float4 result = 0.0; float2 depthResult = 0.0; + float additionalResult = 0.0; #if 0 // Simple reprojection version @@ -98,31 +97,78 @@ void main(uint3 DTid : SV_DispatchThreadID) } output[DTid.xy] = result; output_depth[DTid.xy] = depthResult; + output_additional[DTid.xy] = 0.0; + output_cloudMask[DTid.xy] = pow(saturate(1 - result.a), 64); return; #endif if (validHistory) { - float4 newResult = cloud_current[renderCoord]; - float2 newDepthResult = cloud_depth_current[renderCoord]; + float4 newResult = cloud_current[clamp(renderCoord, minRenderCoord, maxRenderCoord)]; + float2 newDepthResult = cloud_depth_current[clamp(renderCoord, minRenderCoord, maxRenderCoord)]; + + float4 previousResult = cloud_history.SampleLevel(sampler_linear_clamp, prevUV, 0); + float2 previousDepthResult = cloud_depth_history.SampleLevel(sampler_linear_clamp, prevUV, 0); + float previousAdditionalResult = cloud_additional_history.SampleLevel(sampler_linear_clamp, prevUV, 0); + + float depth = texture_depth.SampleLevel(sampler_point_clamp, uv, 1).r; // Half res + float3 depthWorldPosition = reconstruct_position(uv, depth); + float tToDepthBuffer = length(depthWorldPosition - GetCamera().position); + + // Accommodate so when we compare tToDepthBuffer (float precision) with cloud_depth_current (half float precision) we don't overshoot and get incorrect testing + // No issues has been noticed so far + tToDepthBuffer = depth == 0.0 ? HALF_FLT_MAX : tToDepthBuffer; if (shouldUpdatePixel) { - result = newResult; - depthResult = newDepthResult; + if (abs(tToDepthBuffer - previousDepthResult.y) > tToDepthBuffer * 0.1) + { + result = newResult; + depthResult = newDepthResult; + additionalResult = temporalResponseMinMax.x; + } + else + { + // Based on Welford's online algorithm: + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance + + float4 m1 = 0.0; + float4 m2 = 0.0; + for (int x = -1; x <= 1; x++) + { + for (int y = -1; y <= 1; y++) + { + int2 offset = int2(x, y); + int2 neighborCoord = renderCoord + offset; + neighborCoord = clamp(neighborCoord, minRenderCoord, maxRenderCoord); + + float4 neighborResult = cloud_current[neighborCoord]; + + m1 += neighborResult; + m2 += neighborResult * neighborResult; + } + } + + float4 mean = m1 / 9.0; + float4 variance = (m2 / 9.0) - (mean * mean); + float4 stddev = sqrt(max(variance, 0.0f)); + + // Color box clamp + float4 colorMin = mean - stddev; + float4 colorMax = mean + stddev; + previousResult = clamp(previousResult, colorMin, colorMax); + + result = lerp(newResult, previousResult, previousAdditionalResult); + depthResult = newDepthResult; + additionalResult = temporalResponseMinMax.y; + } } else { - float4 previousResult = cloud_history.SampleLevel(sampler_linear_clamp, prevUV, 0); - float2 previousDepthResult = cloud_depth_history.SampleLevel(sampler_linear_clamp, prevUV, 0); - result = previousResult; depthResult = previousDepthResult; - - float depth = texture_depth.SampleLevel(sampler_point_clamp, uv, 1).r; // Half res - float3 depthWorldPosition = reconstruct_position(uv, depth); - float tToDepthBuffer = length(depthWorldPosition - GetCamera().position); + additionalResult = previousAdditionalResult; if (abs(tToDepthBuffer - previousDepthResult.y) > tToDepthBuffer * 0.1) { @@ -135,31 +181,66 @@ void main(uint3 DTid : SV_DispatchThreadID) if ((abs(x) + abs(y)) == 0) continue; - int2 neighborCoord = renderCoord + int2(x, y); - + int2 offset = int2(x, y); + int2 neighborCoord = renderCoord + offset; + neighborCoord = clamp(neighborCoord, minRenderCoord, maxRenderCoord); + float2 neighboorDepthResult = cloud_depth_current[neighborCoord]; float neighborClosestDepth = abs(tToDepthBuffer - neighboorDepthResult.y); if (neighborClosestDepth < closestDepth) { closestDepth = neighborClosestDepth; + float4 neighborResult = cloud_current[neighborCoord]; - + result = neighborResult; depthResult = neighboorDepthResult; + additionalResult = temporalResponseMinMax.x; + + } } } - - if (abs(tToDepthBuffer - newDepthResult.y) < closestDepth) - { - result = newResult; - depthResult = newDepthResult; - } } else { - +#ifdef ADDITIONAL_BOX_CLAMP + // Simple box clamping from neighbour pixels + float4 resultAABBMin = FLT_MAX; + float4 resultAABBMax = 0.0; + float2 depthResultAABBMin = FLT_MAX; + float2 depthResultAABBMax = 0.0; + + for (int y = -1; y <= 1; y++) + { + for (int x = -1; x <= 1; x++) + { + // If it's middle then skip. We only evaluate neighbor samples + if ((abs(x) + abs(y)) == 0) + continue; + + int2 offset = int2(x, y); + int2 neighborCoord = renderCoord + offset; + + float4 neighborResult = cloud_current[neighborCoord]; + float2 neighboorDepthResult = cloud_depth_current[neighborCoord]; + + resultAABBMin = min(resultAABBMin, neighborResult); + resultAABBMax = max(resultAABBMax, neighborResult); + depthResultAABBMin = min(depthResultAABBMin, neighboorDepthResult); + depthResultAABBMax = max(depthResultAABBMax, neighboorDepthResult); + } + } + + resultAABBMin = min(resultAABBMin, newResult); + resultAABBMax = max(resultAABBMax, newResult); + depthResultAABBMin = min(depthResultAABBMin, newDepthResult); + depthResultAABBMax = max(depthResultAABBMax, newDepthResult); + + result = clamp(result, resultAABBMin, resultAABBMax); + depthResult = clamp(depthResult, depthResultAABBMin, depthResultAABBMax); +#endif // ADDITIONAL_CLIPPING } } } @@ -167,8 +248,11 @@ void main(uint3 DTid : SV_DispatchThreadID) { result = cloud_current.SampleLevel(sampler_linear_clamp, uv, 0); depthResult = cloud_depth_current.SampleLevel(sampler_linear_clamp, uv, 0); + additionalResult = temporalResponseMinMax.x; } - + output[DTid.xy] = result; output_depth[DTid.xy] = depthResult; + output_additional[DTid.xy] = additionalResult; + output_cloudMask[DTid.xy] = pow(saturate(1 - result.a), 64); } diff --git a/WickedEngine/shaders/volumetricCloud_shadow_filterCS.hlsl b/WickedEngine/shaders/volumetricCloud_shadow_filterCS.hlsl new file mode 100644 index 000000000..4b0be0e6f --- /dev/null +++ b/WickedEngine/shaders/volumetricCloud_shadow_filterCS.hlsl @@ -0,0 +1,43 @@ +#include "globals.hlsli" +#include "ShaderInterop_Postprocess.h" + +PUSHCONSTANT(postprocess, PostProcess); + +Texture2D input : register(t0); +RWTexture2D output : register(u0); + +[numthreads(POSTPROCESS_BLOCKSIZE, POSTPROCESS_BLOCKSIZE, 1)] +void main(uint3 DTid : SV_DispatchThreadID) +{ + const float2 uv = (DTid.xy + 0.5) * postprocess.resolution_rcp; + +#if 0 // Debug + output[DTid.xy] = input[DTid.xy]; + return; +#endif + + // Filter + float3 filteredResult = 0.0; + float sum = 0.0; + + const int radius = 1; + for (int x = -radius; x <= radius; x++) + { + for (int y = -radius; y <= radius; y++) + { + int2 offset = int2(x, y); + int2 neighborCoord = DTid.xy + offset; + + if (all(neighborCoord >= int2(0, 0) && neighborCoord < (int2) postprocess.resolution)) + { + float3 neighborResult = input[neighborCoord]; + + filteredResult += neighborResult; + sum += 1.0; + } + } + } + filteredResult /= sum; + + output[DTid.xy] = filteredResult; +} diff --git a/WickedEngine/shaders/volumetricCloud_shadow_renderCS.hlsl b/WickedEngine/shaders/volumetricCloud_shadow_renderCS.hlsl new file mode 100644 index 000000000..d520894bc --- /dev/null +++ b/WickedEngine/shaders/volumetricCloud_shadow_renderCS.hlsl @@ -0,0 +1,144 @@ +#include "globals.hlsli" +#include "volumetricCloudsHF.hlsli" +#include "skyAtmosphere.hlsli" +#include "ShaderInterop_Postprocess.h" + +PUSHCONSTANT(postprocess, PostProcess); + +Texture3D texture_shapeNoise : register(t0); +Texture3D texture_detailNoise : register(t1); +Texture2D texture_curlNoise : register(t2); +Texture2D texture_weatherMap : register(t3); + +RWTexture2D texture_render : register(u0); + +static const float2 sampleCountMinMax = float2(16.0, 32.0); // Based on sun angle, more angle more samples + +void VolumetricShadowMap(out float3 result, in AtmosphereParameters atmosphere, float3 worldPosition, float3 sunDirection, + float tMin, float tMax, float3 windOffset, float3 windDirection, float2 coverageWindOffset) +{ + float nearDepth = 0.0; + float3 extinctionAccumulation = 0.0f; + float extinctionAccumulationCount = 0.0f; + float3 maxOpticalDepth = 0.0f; + + float3 planetSurfaceNormal = normalize(GetCamera().position - (atmosphere.planetCenter * SKY_UNIT_TO_M)); + float NdotL = saturate(dot(planetSurfaceNormal, sunDirection)); + float sampleCount = lerp(sampleCountMinMax.y, sampleCountMinMax.x, NdotL); + + const float shadowLength = tMax - tMin; + const float delta = shadowLength / sampleCount; // Since our samples our linear this stays constant + + for (float s = 0.0f; s < sampleCount; s += 1.0) + { + // Linear Distribution + float t = (s + 1.0f) / sampleCount; + + float shadowSampleT = shadowLength * t; + float3 samplePoint = worldPosition + sunDirection * shadowSampleT; // Step futher towards the light + + float heightFraction = GetHeightFractionForPoint(atmosphere, samplePoint); + if (heightFraction < 0.0 || heightFraction > 1.0) + { + break; + } + + float3 weatherData = SampleWeather(texture_weatherMap, samplePoint, heightFraction, coverageWindOffset); + if (weatherData.r < 0.25) + { + continue; + } + + float shadowCloudDensity = SampleCloudDensity(texture_shapeNoise, texture_detailNoise, texture_curlNoise, samplePoint, heightFraction, weatherData, windOffset, windDirection, 0.0f, true); + + float3 shadowExtinction = GetWeather().volumetric_clouds.ExtinctionCoefficient * shadowCloudDensity; + + if (any(shadowExtinction > 0.0f)) + { + nearDepth = max(shadowSampleT, nearDepth); // If there exists extinction "push" the near depth futher + extinctionAccumulationCount += 1.0; + } + + extinctionAccumulation += shadowExtinction; + maxOpticalDepth += shadowExtinction * delta; + } + + const float averageGreyExtinction = dot(extinctionAccumulation / max(extinctionAccumulationCount, 1.0f), 1.0f / 3.0f); + const float maxGreyOpticalDepth = dot(maxOpticalDepth, 1.0f / 3.0f); + + // Values can get to big for the assigned texture, so we pack this into kilometer type + const bool miss = nearDepth == 0.0; + const float frontDepth = miss ? tMax * M_TO_SKY_UNIT : (tMin + nearDepth) * M_TO_SKY_UNIT; + + result = float3(frontDepth, averageGreyExtinction, maxGreyOpticalDepth); +} + +[numthreads(POSTPROCESS_BLOCKSIZE, POSTPROCESS_BLOCKSIZE, 1)] +void main(uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID, uint3 Gid : SV_GroupID, uint groupIndex : SV_GroupIndex) +{ + const float2 uv = (DTid.xy + 0.5) * postprocess.resolution_rcp; + + const float nearDepth = 1.0f; + float3 nearPlaneWorldPosition = reconstruct_position(uv, nearDepth, GetFrame().cloudShadowLightSpaceMatrixInverse); + + // Setup for shadow capture: + float3 rayOrigin = nearPlaneWorldPosition; + float3 rayDirection = GetSunDirection(); + + float3 result = float3(GetFrame().cloudShadowFarPlaneKm, 0.0, 0.0); + + AtmosphereParameters parameters = GetWeather().atmosphere; + + float planetRadius = parameters.bottomRadius * SKY_UNIT_TO_M; + float3 planetCenterWorld = parameters.planetCenter * SKY_UNIT_TO_M; + + const float cloudBottomRadius = planetRadius + GetWeather().volumetric_clouds.CloudStartHeight; + const float cloudTopRadius = planetRadius + GetWeather().volumetric_clouds.CloudStartHeight + GetWeather().volumetric_clouds.CloudThickness; + + float2 tTopSolutions = RaySphereIntersect(rayOrigin, rayDirection, planetCenterWorld, cloudTopRadius); + if (tTopSolutions.x > 0.0 || tTopSolutions.y > 0.0) // Only calculate if any solutions are visible on screen! + { + float tMin = -FLT_MAX; + float tMax = -FLT_MAX; + + float2 tBottomSolutions = RaySphereIntersect(rayOrigin, rayDirection, planetCenterWorld, cloudBottomRadius); + if (tBottomSolutions.x > 0.0 || tBottomSolutions.y > 0.0) + { + // If we see both intersections on the screen, keep the min closest, otherwise the max furthest + float tempTop = all(tTopSolutions > 0.0f) ? min(tTopSolutions.x, tTopSolutions.y) : max(tTopSolutions.x, tTopSolutions.y); + float tempBottom = all(tBottomSolutions > 0.0f) ? min(tBottomSolutions.x, tBottomSolutions.y) : max(tBottomSolutions.x, tBottomSolutions.y); + + // But if we can see the bottom of the layer, make sure we use the camera view or the highest top layer intersection + if (all(tBottomSolutions > 0.0f)) + { + tempTop = max(0.0f, min(tTopSolutions.x, tTopSolutions.y)); + } + + tMin = min(tempBottom, tempTop); + tMax = max(tempBottom, tempTop); + } + else + { + tMin = tTopSolutions.x; + tMax = tTopSolutions.y; + } + + tMin = max(0.0, tMin); + tMax = max(0.0, tMax); + + float3 worldPositionClosestToCloudLayer = rayOrigin + rayDirection * tMin; // Determined by tMin + + // Wind animation offsets + float3 windDirection = float3(cos(GetWeather().volumetric_clouds.WindAngle), -GetWeather().volumetric_clouds.WindUpAmount, sin(GetWeather().volumetric_clouds.WindAngle)); + float3 windOffset = GetWeather().volumetric_clouds.WindSpeed * GetWeather().volumetric_clouds.AnimationMultiplier * windDirection * GetFrame().time; + + float2 coverageWindDirection = float2(cos(GetWeather().volumetric_clouds.CoverageWindAngle), sin(GetWeather().volumetric_clouds.CoverageWindAngle)); + float2 coverageWindOffset = GetWeather().volumetric_clouds.CoverageWindSpeed * GetWeather().volumetric_clouds.AnimationMultiplier * coverageWindDirection * GetFrame().time; + + // Render + VolumetricShadowMap(result, parameters, worldPositionClosestToCloudLayer, GetSunDirection(), tMin, tMax, windOffset, windDirection, coverageWindOffset); + } + + // Output + texture_render[DTid.xy] = result; +} diff --git a/WickedEngine/shaders/volumetricCloud_temporalCS.hlsl b/WickedEngine/shaders/volumetricCloud_temporalCS.hlsl deleted file mode 100644 index ae120b83a..000000000 --- a/WickedEngine/shaders/volumetricCloud_temporalCS.hlsl +++ /dev/null @@ -1,205 +0,0 @@ -#include "globals.hlsli" -#include "ShaderInterop_Postprocess.h" - -PUSHCONSTANT(postprocess, PostProcess); - -Texture2D cloud_reproject : register(t0); -Texture2D cloud_reproject_depth : register(t1); -Texture2D cloud_history : register(t2); - -RWTexture2D output : register(u0); -RWTexture2D output_cloudMask : register(u1); - - -// If the clouds are moving fast, the upsampling will most likely not be able to keep up. You can modify these values to relax the effect: -static const float temporalResponse = 0.05; -static const float temporalScale = 2.0; -static const float temporalExposure = 10.0; - -// Different aabb clipping method from eg. SSR temporal, suitable for clouds in this case -float4 clip_aabb(float4 aabb_min, float4 aabb_max, float4 prev_sample) -{ - float4 p_clip = 0.5 * (aabb_max + aabb_min); - float4 e_clip = 0.5 * (aabb_max - aabb_min) + 0.00000001f; - - float4 v_clip = prev_sample - p_clip; - float4 v_unit = v_clip / e_clip; - float4 a_unit = abs(v_unit); - float ma_unit = max(max(a_unit.x, max(a_unit.y, a_unit.z)), a_unit.w); - - if (ma_unit > 1.0) - return p_clip + v_clip / ma_unit; - else - return prev_sample; // point inside aabb -} - -inline void ResolverAABB(Texture2D currentColor, SamplerState currentSampler, float sharpness, float exposureScale, float AABBScale, float2 uv, float2 texelSize, inout float4 currentMin, inout float4 currentMax, inout float4 currentAverage, inout float4 currentOutput) -{ - const int2 SampleOffset[9] = { int2(-1.0, -1.0), int2(0.0, -1.0), int2(1.0, -1.0), int2(-1.0, 0.0), int2(0.0, 0.0), int2(1.0, 0.0), int2(-1.0, 1.0), int2(0.0, 1.0), int2(1.0, 1.0) }; - - // Modulate Luma HDR - - float4 sampleColors[9]; - [unroll] - for (uint i = 0; i < 9; i++) - { - sampleColors[i] = currentColor.SampleLevel(currentSampler, uv + (SampleOffset[i] / texelSize), 0.0f); - } - - -#if 0 // Exaggerates outline between clouds and geometry - float sampleWeights[9]; - [unroll] - for (uint j = 0; j < 9; j++) - { - sampleWeights[j] = HdrWeight4(sampleColors[j].rgb, exposureScale); - } - - float totalWeight = 0; - [unroll] - for (uint k = 0; k < 9; k++) - { - totalWeight += sampleWeights[k]; - } - sampleColors[4] = (sampleColors[0] * sampleWeights[0] + sampleColors[1] * sampleWeights[1] + sampleColors[2] * sampleWeights[2] + sampleColors[3] * sampleWeights[3] + sampleColors[4] * sampleWeights[4] + - sampleColors[5] * sampleWeights[5] + sampleColors[6] * sampleWeights[6] + sampleColors[7] * sampleWeights[7] + sampleColors[8] * sampleWeights[8]) / totalWeight; -#endif - - -#if 0 // Standard clipping - - // Variance Clipping (AABB) - - float4 m1 = 0.0; - float4 m2 = 0.0; - [unroll] - for (uint x = 0; x < 9; x++) - { - m1 += sampleColors[x]; - m2 += sampleColors[x] * sampleColors[x]; - } - - float4 mean = m1 / 9.0; - float4 stddev = sqrt((m2 / 9.0) - sqr(mean)); - -#else // Depth check - - float depth = texture_depth.SampleLevel(sampler_point_clamp, uv, 1).r; // Half res - float3 depthWorldPosition = reconstruct_position(uv, depth); - float tToDepthBuffer = length(depthWorldPosition - GetCamera().position); - - float validSampleCount = 1.0; - - float4 m1 = 0.0; - float4 m2 = 0.0; - [unroll] - for (uint x = 0; x < 9; x++) - { - if (x == 4) - { - m1 += sampleColors[x]; - m2 += sampleColors[x] * sampleColors[x]; - } - else - { - float2 reprojectionDepthResults = cloud_reproject_depth.SampleLevel(sampler_point_clamp, uv + (SampleOffset[x] / texelSize), 1); - if (abs(tToDepthBuffer - reprojectionDepthResults.y) < tToDepthBuffer * 0.1) - { - m1 += sampleColors[x]; - m2 += sampleColors[x] * sampleColors[x]; - validSampleCount += 1.0; - } - } - } - - float4 mean = m1 / validSampleCount; - float4 stddev = sqrt((m2 / validSampleCount) - sqr(mean)); - -#endif - - currentMin = mean - AABBScale * stddev; - currentMax = mean + AABBScale * stddev; - - currentOutput = sampleColors[4]; - currentMin = min(currentMin, currentOutput); - currentMax = max(currentMax, currentOutput); - currentAverage = mean; -} - -// Computes post-projection depth from linear depth -float getInverseLinearDepth(float lin, float near, float far) -{ - float z_n = ((lin - 2 * far) * near + far * lin) / (lin * near - far * lin); - float z = (z_n + 1) / 2; - return z; -} - -[numthreads(POSTPROCESS_BLOCKSIZE, POSTPROCESS_BLOCKSIZE, 1)] -void main(uint3 DTid : SV_DispatchThreadID) -{ - const float2 uv = (DTid.xy + 0.5f) * postprocess.resolution_rcp; - -#if 0 - - // Calculate screen dependant motion vector - float4 prevPos = float4(uv * 2.0 - 1.0, 1.0, 1.0); - prevPos = mul(GetCamera().inverse_projection, prevPos); - prevPos = prevPos / prevPos.w; - - prevPos.xyz = mul((float3x3)GetCamera().inverse_view, prevPos.xyz); - prevPos.xyz = mul((float3x3)GetCamera().previous_view, prevPos.xyz); - - float4 reproj = mul(GetCamera().projection, prevPos); - reproj /= reproj.w; - - float2 prevUV = reproj.xy * 0.5 + 0.5; - -#else - - // We must recalculate motion with new upscaled cloud depths: - - float x = uv.x * 2 - 1; - float y = (1 - uv.y) * 2 - 1; - float2 screenPosition = float2(x, y); - - float currentCloudLinearDepth = cloud_reproject_depth[DTid.xy].x; - float currentCloudDepth = getInverseLinearDepth(currentCloudLinearDepth, GetCamera().z_near, GetCamera().z_far); - - float4 thisClip = float4(screenPosition, currentCloudDepth, 1.0); - - float4 prevClip = mul(GetCamera().inverse_view_projection, thisClip); - prevClip = mul(GetCamera().previous_view_projection, prevClip); - - //float4 prevClip = mul(GetCamera().previous_view_projection, worldPosition); - float2 prevScreen = prevClip.xy / prevClip.w; - - float2 screenVelocity = screenPosition - prevScreen; - float2 prevScreenPosition = screenPosition - screenVelocity; - - // Transform from screen position to uv - float2 prevUV = prevScreenPosition * float2(0.5, -0.5) + 0.5; - -#endif - - float4 previous = cloud_history.SampleLevel(sampler_linear_clamp, prevUV, 0); - - float4 current = 0; - float4 currentMin, currentMax, currentAverage; - ResolverAABB(cloud_reproject, sampler_point_clamp, 0, temporalExposure, temporalScale, uv, postprocess.resolution, currentMin, currentMax, currentAverage, current); - - //previous = clip_aabb(currentMin.xyz, currentMax.xyz, clamp(currentAverage, currentMin, currentMax), previous); - previous = clip_aabb(currentMin, currentMax, previous); - - float4 result = lerp(previous, current, temporalResponse); - - result = (is_saturated(prevUV) && volumetricclouds_frame > 0) ? result : current; - - output[DTid.xy] = result; - - [branch] - if (DTid.x % 2 == 0 && DTid.y % 2 == 0) - { - // the mask is half the resolution of the clouds - output_cloudMask[DTid.xy / 2] = pow(saturate(1 - result.a), 64); - } -} diff --git a/WickedEngine/shaders/volumetricCloud_weathermapCS.hlsl b/WickedEngine/shaders/volumetricCloud_weathermapCS.hlsl index c3a4da9a8..4989d4188 100644 --- a/WickedEngine/shaders/volumetricCloud_weathermapCS.hlsl +++ b/WickedEngine/shaders/volumetricCloud_weathermapCS.hlsl @@ -17,7 +17,7 @@ void main(uint3 DTid : SV_DispatchThreadID) const float depthOffset4 = 200.0; const float totalSize = 3.0; // Adjust the overall size for all channels - const float coveragePerlinWorleyDifference = 0.7; // For more bigger and mean clouds, you can use something like 0.9 or 1.0 + const float coveragePerlinWorleyDifference = 0.4; // For more bigger and mean clouds, you can use something like 0.9 or 1.0 const float worleySeed = 1.0; // Randomize the worley coverage noise with a seed. const float totalRemapLow = 0.5; @@ -53,6 +53,8 @@ void main(uint3 DTid : SV_DispatchThreadID) perlinNoise1 -= perlinNoise4; perlinNoise2 -= pow(perlinNoise4, 2.0); - + + perlinNoise1 = RemapClamped(perlinNoise1 * 2.0, 0.0, 1.0, 0.05, 1.0); + output[DTid.xy] = float4(perlinNoise1, perlinNoise2, perlinNoise3, 1.0); } diff --git a/WickedEngine/shaders/volumetricCloudsHF.hlsli b/WickedEngine/shaders/volumetricCloudsHF.hlsli index 22aa7fd22..ef4b53e3b 100644 --- a/WickedEngine/shaders/volumetricCloudsHF.hlsli +++ b/WickedEngine/shaders/volumetricCloudsHF.hlsli @@ -1,5 +1,8 @@ #ifndef WI_VOLUMETRICCLOUDS_HF #define WI_VOLUMETRICCLOUDS_HF +#include "globals.hlsli" + +#define HALF_FLT_MAX 65504.0 // Amazing noise and weather creation, modified from: https://github.com/greje656/clouds @@ -351,4 +354,137 @@ float DilatePerlinWorley(float p, float w, float x) } } -#endif // WI_VOLUMETRICCLOUDS_HF \ No newline at end of file +// Calculates checkerboard undersampling position +int ComputeCheckerBoardIndex(int2 renderCoord, int subPixelIndex) +{ + const int localOffset = (renderCoord.x & 1 + renderCoord.y & 1) & 1; + const int checkerBoardLocation = (subPixelIndex + localOffset) & 0x3; + return checkerBoardLocation; +} + +////////////////////////////////////// Cloud Model //////////////////////////////////////////////// + +float GetHeightFractionForPoint(AtmosphereParameters atmosphere, float3 pos) +{ + float planetRadius = atmosphere.bottomRadius * SKY_UNIT_TO_M; + float3 planetCenterWorld = atmosphere.planetCenter * SKY_UNIT_TO_M; + + return saturate((distance(pos, planetCenterWorld) - (planetRadius + GetWeather().volumetric_clouds.CloudStartHeight)) / GetWeather().volumetric_clouds.CloudThickness); +} + +float SampleGradient(float4 gradient, float heightFraction) +{ + return smoothstep(gradient.x, gradient.y, heightFraction) - smoothstep(gradient.z, gradient.w, heightFraction); +} + +float GetDensityHeightGradient(float heightFraction, float3 weatherData) +{ + float cloudType = weatherData.g; + + float smallType = 1.0f - saturate(cloudType * 2.0f); + float mediumType = 1.0f - abs(cloudType - 0.5f) * 2.0f; + float largeType = saturate(cloudType - 0.5f) * 2.0f; + + float4 cloudGradient = + (GetWeather().volumetric_clouds.CloudGradientSmall * smallType) + + (GetWeather().volumetric_clouds.CloudGradientMedium * mediumType) + + (GetWeather().volumetric_clouds.CloudGradientLarge * largeType); + + return SampleGradient(cloudGradient, heightFraction); +} + +float3 SampleWeather(Texture2D texture_weatherMap, float3 pos, float heightFraction, float2 coverageWindOffset) +{ + float4 weatherData = texture_weatherMap.SampleLevel(sampler_linear_wrap, (pos.xz + coverageWindOffset) * GetWeather().volumetric_clouds.WeatherScale, 0); + + // Apply effects for coverage + weatherData.r = RemapClamped(weatherData.r * GetWeather().volumetric_clouds.CoverageAmount, 0.0, 1.0, GetWeather().volumetric_clouds.CoverageMinimum, 1.0); + weatherData.g = RemapClamped(weatherData.g * GetWeather().volumetric_clouds.TypeAmount, 0.0, 1.0, GetWeather().volumetric_clouds.TypeMinimum, 1.0); + + // Apply anvil clouds to coverage + weatherData.r = pow(weatherData.r, max(Remap(pow(1.0 - heightFraction, GetWeather().volumetric_clouds.AnvilOverhangHeight), 0.7, 0.8, 1.0, GetWeather().volumetric_clouds.AnvilAmount + 1.0), 0.0)); + + return weatherData.rgb; +} + +float WeatherDensity(float3 weatherData) +{ + const float wetness = saturate(weatherData.b); + return lerp(1.0, 1.0 - GetWeather().volumetric_clouds.WeatherDensityAmount, wetness); +} + +float SampleCloudDensity(Texture3D texture_shapeNoise, Texture3D texture_detailNoise, Texture2D texture_curlNoise, float3 p, float heightFraction, float3 weatherData, float3 windOffset, float3 windDirection, float lod, bool sampleDetail) +{ + float3 pos = p + windOffset; + pos += heightFraction * windDirection * GetWeather().volumetric_clouds.SkewAlongWindDirection; + + float4 lowFrequencyNoises = texture_shapeNoise.SampleLevel(sampler_linear_wrap, pos * GetWeather().volumetric_clouds.TotalNoiseScale, lod); + + // Create an FBM out of the low-frequency Perlin-Worley Noises + float lowFrequencyFBM = (lowFrequencyNoises.g * 0.625) + (lowFrequencyNoises.b * 0.25) + (lowFrequencyNoises.a * 0.125); + lowFrequencyFBM = saturate(lowFrequencyFBM); + + float cloudSample = Remap(lowFrequencyNoises.r, -(1.0 - lowFrequencyFBM), 1.0, 0.0, 1.0); + + // Apply height gradients + float densityHeightGradient = GetDensityHeightGradient(heightFraction, weatherData); + cloudSample *= densityHeightGradient; + + float cloudCoverage = weatherData.r; + + // Apply Coverage to sample + cloudSample = Remap(cloudSample, 1.0 - cloudCoverage, 1.0, 0.0, 1.0); + cloudSample *= cloudCoverage; + + // Erode with detail noise if cloud sample > 0 + if (cloudSample > 0.0 && sampleDetail) + { + // Apply our curl noise to erode with tiny details. + float3 curlNoise = DecodeCurlNoise(texture_curlNoise.SampleLevel(sampler_linear_wrap, p.xz * GetWeather().volumetric_clouds.CurlScale * GetWeather().volumetric_clouds.TotalNoiseScale, 0).rgb); + pos += float3(curlNoise.r, curlNoise.b, curlNoise.g) * (1.0 - heightFraction) * GetWeather().volumetric_clouds.CurlNoiseModifier; + + float3 highFrequencyNoises = texture_detailNoise.SampleLevel(sampler_linear_wrap, pos * GetWeather().volumetric_clouds.DetailScale * GetWeather().volumetric_clouds.TotalNoiseScale, lod).rgb; + + // Create an FBM out of the high-frequency Worley Noises + float highFrequencyFBM = (highFrequencyNoises.r * 0.625) + (highFrequencyNoises.g * 0.25) + (highFrequencyNoises.b * 0.125); + highFrequencyFBM = saturate(highFrequencyFBM); + + // Dilate detail noise based on height + float highFrequenceNoiseModifier = lerp(1.0 - highFrequencyFBM, highFrequencyFBM, saturate(heightFraction * GetWeather().volumetric_clouds.DetailNoiseHeightFraction)); + + // Erode with base of clouds + cloudSample = Remap(cloudSample, highFrequenceNoiseModifier * GetWeather().volumetric_clouds.DetailNoiseModifier, 1.0, 0.0, 1.0); + } + + return max(cloudSample, 0.0); +} + +////////////////////////////////////// Shadow //////////////////////////////////////////////// + +inline float shadow_2D_volumetricclouds(float3 P) +{ + // Project into shadow map space (no need to divide by .w because ortho projection!): + float3 shadow_pos = mul(GetFrame().cloudShadowLightSpaceMatrix, float4(P, 1)).xyz; + float3 shadow_uv = clipspace_to_uv(shadow_pos); + + [branch] + if (is_saturated(shadow_uv)) + { + float cloudShadowSampleZ = shadow_pos.z; + + Texture2D texture_volumetricclouds_shadow = bindless_textures[GetFrame().texture_volumetricclouds_shadow_index]; + float3 cloudShadowData = texture_volumetricclouds_shadow.SampleLevel(sampler_linear_clamp, shadow_uv.xy, 0.0f).rgb; + + float sampleDepthKm = saturate(1.0 - cloudShadowSampleZ) * GetFrame().cloudShadowFarPlaneKm; + + float opticalDepth = cloudShadowData.g * (max(0.0f, cloudShadowData.r - sampleDepthKm) * SKY_UNIT_TO_M); + opticalDepth = min(cloudShadowData.b, opticalDepth); + + float transmittance = saturate(exp(-opticalDepth)); + return transmittance; + } + + return 1.0; +} + +#endif // WI_VOLUMETRICCLOUDS_HF diff --git a/WickedEngine/shaders/volumetricLight_DirectionalPS.hlsl b/WickedEngine/shaders/volumetricLight_DirectionalPS.hlsl index c5648fd21..21e7b1438 100644 --- a/WickedEngine/shaders/volumetricLight_DirectionalPS.hlsl +++ b/WickedEngine/shaders/volumetricLight_DirectionalPS.hlsl @@ -1,6 +1,7 @@ #define DISABLE_SOFT_SHADOWMAP #define TRANSPARENT_SHADOWMAP_SECONDARY_DEPTH_CHECK // fix the lack of depth testing #include "volumetricLightHF.hlsli" +#include "volumetricCloudsHF.hlsli" float4 main(VertexToPixel input) : SV_TARGET { @@ -38,7 +39,7 @@ float4 main(VertexToPixel input) : SV_TARGET for (uint i = 0; i < sampleCount; ++i) { bool valid = false; - + for (uint cascade = 0; cascade < GetFrame().shadow_cascade_count; ++cascade) { float3 shadow_pos = mul(load_entitymatrix(light.GetMatrixIndex() + cascade), float4(P, 1)).xyz; // ortho matrix, no divide by .w @@ -49,6 +50,11 @@ float4 main(VertexToPixel input) : SV_TARGET { float3 attenuation = shadow_2D(light, shadow_pos, shadow_uv.xy, cascade); + if (GetFrame().options & OPTION_BIT_VOLUMETRICCLOUDS_SHADOWS) + { + attenuation *= shadow_2D_volumetricclouds(P); + } + // Evaluate sample height for height fog calculation, given 0 for V: attenuation *= GetFogAmount(cameraDistance - marchedDistance, P, float3(0.0, 0.0, 0.0)); attenuation *= scattering; diff --git a/WickedEngine/wiArchive.cpp b/WickedEngine/wiArchive.cpp index 78be1a9e7..99a68ad45 100644 --- a/WickedEngine/wiArchive.cpp +++ b/WickedEngine/wiArchive.cpp @@ -5,7 +5,7 @@ namespace wi { // this should always be only INCREMENTED and only if a new serialization is implemeted somewhere! - static constexpr uint64_t __archiveVersion = 85; + static constexpr uint64_t __archiveVersion = 86; // 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/wiEnums.h b/WickedEngine/wiEnums.h index e91e12c05..2598967fc 100644 --- a/WickedEngine/wiEnums.h +++ b/WickedEngine/wiEnums.h @@ -78,6 +78,7 @@ namespace wi::enums { TEXTYPE_3D_VOXELRADIANCE, TEXTYPE_3D_VOXELRADIANCE_HELPER, + TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW, TEXTYPE_2D_SKYATMOSPHERE_TRANSMITTANCELUT, TEXTYPE_2D_SKYATMOSPHERE_MULTISCATTEREDLUMINANCELUT, TEXTYPE_2D_SKYATMOSPHERE_SKYVIEWLUT, @@ -316,8 +317,11 @@ namespace wi::enums CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_CURLNOISE, CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_WEATHERMAP, CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_RENDER, + CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_RENDER_CAPTURE, + CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_RENDER_CAPTURE_MSAA, CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_REPROJECT, - CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_TEMPORAL, + CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_SHADOW_RENDER, + CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_SHADOW_FILTER, CSTYPE_POSTPROCESS_FXAA, CSTYPE_POSTPROCESS_TEMPORALAA, CSTYPE_POSTPROCESS_SHARPEN, diff --git a/WickedEngine/wiGraphics.h b/WickedEngine/wiGraphics.h index 47072c66f..44503cc45 100644 --- a/WickedEngine/wiGraphics.h +++ b/WickedEngine/wiGraphics.h @@ -631,7 +631,8 @@ namespace wi::graphics { RENDERTARGET, DEPTH_STENCIL, - RESOLVE, + RESOLVE, // resolve render target (color) + RESOLVE_DEPTH, SHADING_RATE_SOURCE } type = Type::RENDERTARGET; enum class LoadOp @@ -650,6 +651,11 @@ namespace wi::graphics ResourceState initial_layout = ResourceState::UNDEFINED; // layout before the render pass ResourceState subpass_layout = ResourceState::UNDEFINED; // layout within the render pass ResourceState final_layout = ResourceState::UNDEFINED; // layout after the render pass + enum class DepthResolveMode + { + Min, + Max, + } depth_resolve_mode = DepthResolveMode::Min; static RenderPassAttachment RenderTarget( const Texture* resource = nullptr, @@ -711,6 +717,24 @@ namespace wi::graphics return attachment; } + static RenderPassAttachment ResolveDepth( + const Texture* resource = nullptr, + DepthResolveMode depth_resolve_mode = DepthResolveMode::Min, + ResourceState initial_layout = ResourceState::SHADER_RESOURCE, + ResourceState final_layout = ResourceState::SHADER_RESOURCE, + int subresource_SRV = -1 + ) + { + RenderPassAttachment attachment; + attachment.type = Type::RESOLVE_DEPTH; + attachment.texture = resource; + attachment.initial_layout = initial_layout; + attachment.final_layout = final_layout; + attachment.subresource = subresource_SRV; + attachment.depth_resolve_mode = depth_resolve_mode; + return attachment; + } + static RenderPassAttachment ShadingRateSource( const Texture* resource = nullptr, ResourceState initial_layout = ResourceState::SHADING_RATE_SOURCE, diff --git a/WickedEngine/wiGraphicsDevice_DX12.cpp b/WickedEngine/wiGraphicsDevice_DX12.cpp index 42e710961..1f3574610 100644 --- a/WickedEngine/wiGraphicsDevice_DX12.cpp +++ b/WickedEngine/wiGraphicsDevice_DX12.cpp @@ -1486,6 +1486,7 @@ namespace dx12_internal // Due to a API bug, this resolve_subresources array must be kept alive between BeginRenderpass() and EndRenderpass()! wi::vector resolve_subresources[D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT] = {}; + wi::vector resolve_subresources_dsv = {}; }; struct SwapChain_DX12 { @@ -2024,6 +2025,7 @@ using namespace dx12_internal; for (auto& attachment : commandlist.active_renderpass->desc.attachments) { if (attachment.type == RenderPassAttachment::Type::RESOLVE || + attachment.type == RenderPassAttachment::Type::RESOLVE_DEPTH || attachment.type == RenderPassAttachment::Type::SHADING_RATE_SOURCE || attachment.texture == nullptr) continue; @@ -3835,11 +3837,10 @@ using namespace dx12_internal; const SingleDescriptor& src_descriptor = src_subresource < 0 ? src_internal->rtv : src_internal->subresources_rtv[src_subresource]; D3D12_RENDER_PASS_RENDER_TARGET_DESC& src_RTV = internal_state->RTVs[resolve_src_counter]; - src_RTV.EndingAccess.Resolve.PreserveResolveSource = src_RTV.EndingAccess.Type == D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE; src_RTV.EndingAccess.Type = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_RESOLVE; - src_RTV.EndingAccess.Resolve.Format = clear_value.Format; + src_RTV.EndingAccess.Resolve.PreserveResolveSource = src.storeop == RenderPassAttachment::StoreOp::STORE ? TRUE : FALSE; + src_RTV.EndingAccess.Resolve.Format = src_descriptor.rtv.Format; src_RTV.EndingAccess.Resolve.ResolveMode = D3D12_RESOLVE_MODE_AVERAGE; - src_RTV.EndingAccess.Resolve.PreserveResolveSource = src_RTV.EndingAccess.Type == D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD ? FALSE : TRUE; src_RTV.EndingAccess.Resolve.pDstResource = texture_internal->resource.Get(); src_RTV.EndingAccess.Resolve.pSrcResource = src_internal->resource.Get(); @@ -3867,6 +3868,63 @@ using namespace dx12_internal; } resolve_dst_counter++; } + else if (attachment.type == RenderPassAttachment::Type::RESOLVE_DEPTH) + { + if (texture != nullptr) + { + for (auto& src : renderpass->desc.attachments) + { + if (src.type == RenderPassAttachment::Type::DEPTH_STENCIL && src.texture != nullptr) + { + auto src_internal = to_internal(src.texture); + int src_subresource = src.subresource; + const SingleDescriptor& src_descriptor = src_subresource < 0 ? src_internal->dsv : src_internal->subresources_dsv[src_subresource]; + + D3D12_RENDER_PASS_DEPTH_STENCIL_DESC& src_DSV = internal_state->DSV; + src_DSV.DepthEndingAccess.Type = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_RESOLVE; + src_DSV.DepthEndingAccess.Resolve.PreserveResolveSource = src.storeop == RenderPassAttachment::StoreOp::STORE ? TRUE : FALSE; + src_DSV.DepthEndingAccess.Resolve.Format = src_descriptor.dsv.Format; + + switch (attachment.depth_resolve_mode) + { + default: + case RenderPassAttachment::DepthResolveMode::Min: + src_DSV.DepthEndingAccess.Resolve.ResolveMode = D3D12_RESOLVE_MODE_MIN; + break; + case RenderPassAttachment::DepthResolveMode::Max: + src_DSV.DepthEndingAccess.Resolve.ResolveMode = D3D12_RESOLVE_MODE_MAX; + break; + } + + src_DSV.DepthEndingAccess.Resolve.pDstResource = texture_internal->resource.Get(); + src_DSV.DepthEndingAccess.Resolve.pSrcResource = src_internal->resource.Get(); + + const SingleDescriptor& dst_descriptor = subresource < 0 ? texture_internal->srv : texture_internal->subresources_srv[subresource]; + for (uint32_t mip = 0; mip < std::min(attachment.texture->desc.mip_levels, dst_descriptor.mipCount); ++mip) + { + for (uint32_t slice = 0; slice < std::min(attachment.texture->desc.array_size, dst_descriptor.sliceCount); ++slice) + { + D3D12_RENDER_PASS_ENDING_ACCESS_RESOLVE_SUBRESOURCE_PARAMETERS& params = internal_state->resolve_subresources_dsv.emplace_back(); + params.SrcSubresource = D3D12CalcSubresource(src_descriptor.firstMip + mip, src_descriptor.firstSlice + slice, 0, src.texture->desc.mip_levels, src.texture->desc.array_size); + params.DstSubresource = D3D12CalcSubresource(dst_descriptor.firstMip + mip, dst_descriptor.firstSlice + slice, 0, texture->desc.mip_levels, texture->desc.array_size); + params.SrcRect.left = 0; + params.SrcRect.top = 0; + params.SrcRect.right = (LONG)texture->desc.width; + params.SrcRect.bottom = (LONG)texture->desc.height; + } + } + src_DSV.DepthEndingAccess.Resolve.SubresourceCount = (UINT)internal_state->resolve_subresources_dsv.size(); + src_DSV.DepthEndingAccess.Resolve.pSubresourceParameters = internal_state->resolve_subresources_dsv.data(); + if (IsFormatStencilSupport(attachment.texture->desc.format)) + { + src_DSV.StencilBeginningAccess = src_DSV.DepthBeginningAccess; + src_DSV.StencilEndingAccess = src_DSV.DepthEndingAccess; + } + break; + } + } + } + } else if (attachment.type == RenderPassAttachment::Type::SHADING_RATE_SOURCE) { internal_state->shading_rate_image = texture; @@ -3881,7 +3939,11 @@ using namespace dx12_internal; continue; D3D12_RESOURCE_STATES before = _ParseResourceState(attachment.initial_layout); - D3D12_RESOURCE_STATES after = attachment.type == RenderPassAttachment::Type::RESOLVE ? D3D12_RESOURCE_STATE_RESOLVE_DEST : _ParseResourceState(attachment.subpass_layout); + D3D12_RESOURCE_STATES after = _ParseResourceState(attachment.subpass_layout); + if (attachment.type == RenderPassAttachment::Type::RESOLVE || attachment.type == RenderPassAttachment::Type::RESOLVE_DEPTH) + { + after = D3D12_RESOURCE_STATE_RESOLVE_DEST; + } if (before == after) continue; @@ -3906,7 +3968,7 @@ using namespace dx12_internal; { descriptor = &texture_internal->subresources_dsv[attachment.subresource]; } - else if (attachment.type == RenderPassAttachment::Type::RESOLVE) + else if (attachment.type == RenderPassAttachment::Type::RESOLVE || attachment.type == RenderPassAttachment::Type::RESOLVE_DEPTH) { // Single barrier for whole resource: // From debug layer it looks like the resolve operation requires entire resource to be in RESOLVE_DEST @@ -3943,8 +4005,12 @@ using namespace dx12_internal; if (attachment.texture == nullptr) continue; - D3D12_RESOURCE_STATES before = attachment.type == RenderPassAttachment::Type::RESOLVE ? D3D12_RESOURCE_STATE_RESOLVE_DEST : _ParseResourceState(attachment.subpass_layout); + D3D12_RESOURCE_STATES before = _ParseResourceState(attachment.subpass_layout); D3D12_RESOURCE_STATES after = _ParseResourceState(attachment.final_layout); + if (attachment.type == RenderPassAttachment::Type::RESOLVE || attachment.type == RenderPassAttachment::Type::RESOLVE_DEPTH) + { + before = D3D12_RESOURCE_STATE_RESOLVE_DEST; + } if (before == after) continue; @@ -3969,7 +4035,7 @@ using namespace dx12_internal; { descriptor = &texture_internal->subresources_dsv[attachment.subresource]; } - else if (attachment.type == RenderPassAttachment::Type::RESOLVE) + else if (attachment.type == RenderPassAttachment::Type::RESOLVE || attachment.type == RenderPassAttachment::Type::RESOLVE_DEPTH) { // Single barrier for whole resource: // From debug layer it looks like the resolve operation requires entire resource to be in RESOLVE_DEST diff --git a/WickedEngine/wiGraphicsDevice_Vulkan.cpp b/WickedEngine/wiGraphicsDevice_Vulkan.cpp index 2bb27caf9..18738f285 100644 --- a/WickedEngine/wiGraphicsDevice_Vulkan.cpp +++ b/WickedEngine/wiGraphicsDevice_Vulkan.cpp @@ -2477,6 +2477,10 @@ using namespace vulkan_internal; *properties_chain = &sampler_minmax_properties; properties_chain = &sampler_minmax_properties.pNext; + depth_stencil_resolve_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES; + *properties_chain = &depth_stencil_resolve_properties; + properties_chain = &depth_stencil_resolve_properties.pNext; + enabled_deviceExtensions = required_deviceExtensions; if (checkExtensionSupport(VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME, available_deviceExtensions)) @@ -2577,6 +2581,8 @@ using namespace vulkan_internal; } assert(properties2.properties.limits.timestampComputeAndGraphics == VK_TRUE); + assert(depth_stencil_resolve_properties.supportedDepthResolveModes& VK_RESOLVE_MODE_MIN_BIT); + assert(depth_stencil_resolve_properties.supportedDepthResolveModes& VK_RESOLVE_MODE_MAX_BIT); vkGetPhysicalDeviceFeatures2(physicalDevice, &features2); @@ -5097,6 +5103,8 @@ using namespace vulkan_internal; VkAttachmentReference2 resolveAttachmentRefs[8] = {}; VkAttachmentReference2 shadingRateAttachmentRef = {}; VkAttachmentReference2 depthAttachmentRef = {}; + VkSubpassDescriptionDepthStencilResolve depthResolve = {}; + VkAttachmentReference2 depthResolveAttachmentRef = {}; VkFragmentShadingRateAttachmentInfoKHR shading_rate_attachment = {}; shading_rate_attachment.sType = VK_STRUCTURE_TYPE_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR; @@ -5109,6 +5117,7 @@ using namespace vulkan_internal; VkSubpassDescription2 subpass = {}; subpass.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + const void** subpass_chain = &subpass.pNext; uint32_t validAttachmentCount = 0; for (auto& attachment : renderpass->desc.attachments) @@ -5258,6 +5267,58 @@ using namespace vulkan_internal; resolvecount++; subpass.pResolveAttachments = resolveAttachmentRefs; } + else if (attachment.type == RenderPassAttachment::Type::RESOLVE_DEPTH) + { + + if (attachment.texture == nullptr) + { + resolveAttachmentRefs[resolvecount].attachment = VK_ATTACHMENT_UNUSED; + } + else + { + if (subresource < 0 || texture_internal_state->subresources_srv.empty()) + { + attachments[validAttachmentCount] = texture_internal_state->srv.image_view; + } + else + { + assert(texture_internal_state->subresources_srv.size() > size_t(subresource) && "Invalid SRV subresource!"); + attachments[validAttachmentCount] = texture_internal_state->subresources_srv[subresource].image_view; + } + if (attachments[validAttachmentCount] == VK_NULL_HANDLE) + { + continue; + } + } + + depthResolve.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE; + + switch (attachment.depth_resolve_mode) + { + default: + case RenderPassAttachment::DepthResolveMode::Min: + depthResolve.depthResolveMode = VK_RESOLVE_MODE_MIN_BIT; + depthResolve.stencilResolveMode = VK_RESOLVE_MODE_MIN_BIT; + break; + case RenderPassAttachment::DepthResolveMode::Max: + depthResolve.depthResolveMode = VK_RESOLVE_MODE_MAX_BIT; + depthResolve.stencilResolveMode = VK_RESOLVE_MODE_MAX_BIT; + break; + } + + depthResolveAttachmentRef.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; + depthResolveAttachmentRef.attachment = validAttachmentCount; + depthResolveAttachmentRef.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + if (IsFormatStencilSupport(attachment.texture->desc.format)) + { + depthResolveAttachmentRef.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + depthResolveAttachmentRef.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + depthResolve.pDepthStencilResolveAttachment = &depthResolveAttachmentRef; + + *subpass_chain = &depthResolve; + subpass_chain = &depthResolve.pNext; + } else if (attachment.type == RenderPassAttachment::Type::SHADING_RATE_SOURCE && CheckCapability(GraphicsDeviceCapability::VARIABLE_RATE_SHADING_TIER2)) { shadingRateAttachmentRef.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2; @@ -5286,7 +5347,8 @@ using namespace vulkan_internal; shadingRateAttachmentRef.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; } - subpass.pNext = &shading_rate_attachment; + *subpass_chain = &shading_rate_attachment; + subpass_chain = &shading_rate_attachment.pNext; } validAttachmentCount++; diff --git a/WickedEngine/wiGraphicsDevice_Vulkan.h b/WickedEngine/wiGraphicsDevice_Vulkan.h index 425ce78e0..1b5e03fcd 100644 --- a/WickedEngine/wiGraphicsDevice_Vulkan.h +++ b/WickedEngine/wiGraphicsDevice_Vulkan.h @@ -55,6 +55,7 @@ namespace wi::graphics VkPhysicalDeviceFragmentShadingRatePropertiesKHR fragment_shading_rate_properties = {}; VkPhysicalDeviceMeshShaderPropertiesNV mesh_shader_properties = {}; VkPhysicalDeviceMemoryProperties2 memory_properties_2 = {}; + VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve_properties = {}; VkPhysicalDeviceFeatures2 features2 = {}; VkPhysicalDeviceVulkan11Features features_1_1 = {}; diff --git a/WickedEngine/wiRenderPath3D.cpp b/WickedEngine/wiRenderPath3D.cpp index e7516a5ad..21af243ad 100644 --- a/WickedEngine/wiRenderPath3D.cpp +++ b/WickedEngine/wiRenderPath3D.cpp @@ -220,7 +220,7 @@ void RenderPath3D::ResizeBuffers() desc.sample_count = getMSAASampleCount(); desc.layout = ResourceState::DEPTHSTENCIL_READONLY; - desc.format = Format::R32G8X24_TYPELESS; + desc.format = Format::D32_FLOAT_S8X24_UINT; desc.bind_flags = BindFlag::DEPTH_STENCIL; device->CreateTexture(&desc, nullptr, &depthBuffer_Main); device->SetName(&depthBuffer_Main, "depthBuffer_Main"); @@ -918,7 +918,8 @@ void RenderPath3D::Render() const { wi::renderer::Postprocess_VolumetricClouds( volumetriccloudResources, - cmd + cmd, + scene->weather.volumetricCloudsWeatherMap.IsValid() ? &scene->weather.volumetricCloudsWeatherMap.GetTexture() : nullptr ); } @@ -1017,7 +1018,8 @@ void RenderPath3D::Render() const { wi::renderer::Postprocess_VolumetricClouds( volumetriccloudResources_reflection, - cmd + cmd, + scene->weather.volumetricCloudsWeatherMap.IsValid() ? &scene->weather.volumetricCloudsWeatherMap.GetTexture() : nullptr ); } @@ -1066,7 +1068,7 @@ void RenderPath3D::Render() const wi::image::Params fx; fx.enableFullScreen(); fx.blendFlag = BLENDMODE_PREMULTIPLIED; - wi::image::Draw(&volumetriccloudResources_reflection.texture_temporal[device->GetFrameCount() % 2], fx, cmd); + wi::image::Draw(&volumetriccloudResources_reflection.texture_reproject[device->GetFrameCount() % 2], fx, cmd); device->EventEnd(cmd); } @@ -1175,7 +1177,7 @@ void RenderPath3D::Render() const { device->EventBegin("Volumetric Clouds Upsample + Blend", cmd); wi::renderer::Postprocess_Upsample_Bilateral( - volumetriccloudResources.texture_temporal[device->GetFrameCount() % 2], + volumetriccloudResources.texture_reproject[device->GetFrameCount() % 2], rtLinearDepth, rtMain_render, // only desc is taken if pixel shader upsampling is used cmd, diff --git a/WickedEngine/wiRenderer.cpp b/WickedEngine/wiRenderer.cpp index b4e6e1613..294e974bb 100644 --- a/WickedEngine/wiRenderer.cpp +++ b/WickedEngine/wiRenderer.cpp @@ -1013,8 +1013,11 @@ void LoadShaders() wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_CURLNOISE], "volumetricCloud_curlnoiseCS.cso"); }); wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_WEATHERMAP], "volumetricCloud_weathermapCS.cso"); }); wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_RENDER], "volumetricCloud_renderCS.cso"); }); + wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_RENDER_CAPTURE], "volumetricCloud_renderCS_capture.cso"); }); + wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_RENDER_CAPTURE_MSAA], "volumetricCloud_renderCS_capture_MSAA.cso"); }); wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_REPROJECT], "volumetricCloud_reprojectCS.cso"); }); - wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_TEMPORAL], "volumetricCloud_temporalCS.cso"); }); + wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_SHADOW_RENDER], "volumetricCloud_shadow_renderCS.cso"); }); + wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_SHADOW_FILTER], "volumetricCloud_shadow_filterCS.cso"); }); wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_POSTPROCESS_FXAA], "fxaaCS.cso"); }); wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_POSTPROCESS_TEMPORALAA], "temporalaaCS.cso"); }); wi::jobsystem::Execute(ctx, [](wi::jobsystem::JobArgs args) { LoadShader(ShaderStage::CS, shaders[CSTYPE_POSTPROCESS_SHARPEN], "sharpenCS.cso"); }); @@ -1762,6 +1765,17 @@ void LoadBuffers() device->SetName(&textures[TEXTYPE_2D_SHEENLUT], "textures[TEXTYPE_2D_SHEENLUT]"); } + { + TextureDesc desc; + desc.type = TextureDesc::Type::TEXTURE_2D; + desc.width = 512; + desc.height = 512; + desc.format = Format::R11G11B10_FLOAT; + desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS; + desc.layout = ResourceState::SHADER_RESOURCE_COMPUTE; + device->CreateTexture(&desc, nullptr, &textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW]); + device->SetName(&textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW], "textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW]"); + } { TextureDesc desc; desc.type = TextureDesc::Type::TEXTURE_2D; @@ -2225,6 +2239,7 @@ static const uint32_t CASCADE_COUNT = 3; struct SHCAM { XMMATRIX view_projection; + XMMATRIX inverse_view_projection; Frustum frustum; // This frustum can be used for intersection test with wiPrimitive primitives BoundingFrustum boundingfrustum; // This boundingfrustum can be used for frustum vs frustum intersection test @@ -2239,6 +2254,7 @@ struct SHCAM const XMMATRIX V = XMMatrixLookToLH(E, to, up); const XMMATRIX P = XMMatrixPerspectiveFovLH(fov, 1, farPlane, nearPlane); view_projection = XMMatrixMultiply(V, P); + inverse_view_projection = XMMatrixInverse(nullptr, view_projection); frustum.Create(view_projection); BoundingFrustum::CreateFromMatrix(boundingfrustum, P); @@ -3172,7 +3188,7 @@ void UpdatePerFrameData( ); device->CreateRenderPass(&renderpassdesc, &renderpass_shadowMapAtlas); } - + break; } else @@ -3188,6 +3204,45 @@ void UpdatePerFrameData( wi::profiler::EndRange(range); } + // Calculate volumetric cloud shadow data: + if (vis.scene->weather.IsVolumetricClouds() && vis.scene->weather.IsVolumetricCloudsShadows()) + { + const float cloudShadowSnapLength = 5000.0f; + const float cloudShadowExtent = 35000.0f; // The cloud shadow bounding box size + const float cloudShadowNearPlane = 0.0f; + const float cloudShadowFarPlane = cloudShadowExtent * 2.0; + + const float metersToSkyUnit = 0.001f; // Engine units are in meters (must be same as skyAtmosphere.hlsli) + const float skyUnitToMeters = 1.0f / metersToSkyUnit; + + XMVECTOR atmosphereCenter = XMLoadFloat3(&vis.scene->weather.atmosphereParameters.planetCenter); + XMVECTOR sunDirection = XMLoadFloat3(&vis.scene->weather.sunDirection); + const float planetRadius = vis.scene->weather.atmosphereParameters.bottomRadius; + + // Point on the surface of the planet relative to camera position and planet normal + XMVECTOR lookAtPosition = XMVector3Normalize(vis.camera->GetEye() - (atmosphereCenter * skyUnitToMeters)); + lookAtPosition = (atmosphereCenter + lookAtPosition * planetRadius) * skyUnitToMeters; + + // Snap with user defined value + lookAtPosition = XMVectorFloor(XMVectorAdd(lookAtPosition, XMVectorReplicate(0.5f * cloudShadowSnapLength)) / cloudShadowSnapLength) * cloudShadowSnapLength; + + XMVECTOR lightPosition = lookAtPosition + sunDirection * cloudShadowExtent; // far plane not needed here + + const XMMATRIX lightRotation = XMMatrixRotationQuaternion(XMLoadFloat4(&scene.weather.stars_rotation_quaternion)); // We only care about prioritized directional light anyway + const XMVECTOR up = XMVector3TransformNormal(XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f), lightRotation); + + XMMATRIX cloudShadowProjection = XMMatrixOrthographicOffCenterLH(-cloudShadowExtent, cloudShadowExtent, -cloudShadowExtent, cloudShadowExtent, cloudShadowNearPlane, cloudShadowFarPlane); + XMMATRIX cloudShadowView = XMMatrixLookAtLH(lightPosition, lookAtPosition, up); + + XMMATRIX cloudShadowLightSpaceMatrix = XMMatrixMultiply(cloudShadowView, cloudShadowProjection); + XMMATRIX cloudShadowLightSpaceMatrixInverse = XMMatrixInverse(nullptr, cloudShadowLightSpaceMatrix); + + XMStoreFloat4x4(&frameCB.cloudShadowLightSpaceMatrix, cloudShadowLightSpaceMatrix); + XMStoreFloat4x4(&frameCB.cloudShadowLightSpaceMatrixInverse, cloudShadowLightSpaceMatrixInverse); + frameCB.cloudShadowFarPlaneKm = cloudShadowFarPlane * metersToSkyUnit; + frameCB.texture_volumetricclouds_shadow_index = device->GetDescriptorIndex(&textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW], SubresourceType::SRV); + } + // Update CPU-side frame constant buffer: frameCB.delta_time = dt * GetGameSpeed(); frameCB.time_previous = frameCB.time; @@ -3271,10 +3326,6 @@ void UpdatePerFrameData( { frameCB.options |= OPTION_BIT_VOXELGI_RETARGETTED; } - if (vis.scene->weather.IsSimpleSky()) - { - frameCB.options |= OPTION_BIT_SIMPLE_SKY; - } if (vis.scene->weather.IsRealisticSky()) { frameCB.options |= OPTION_BIT_REALISTIC_SKY; @@ -3312,7 +3363,11 @@ void UpdatePerFrameData( frameCB.options |= OPTION_BIT_STATIC_SKY_HDR; } } - + if (vis.scene->weather.IsVolumetricClouds() && vis.scene->weather.IsVolumetricCloudsShadows()) + { + frameCB.options |= OPTION_BIT_VOLUMETRICCLOUDS_SHADOWS; + } + frameCB.scene = vis.scene->shaderscene; frameCB.sampler_objectshader_index = device->GetDescriptorIndex(&samplers[SAMPLER_OBJECTSHADER]); @@ -3677,6 +3732,11 @@ void UpdateRenderData( shaderentity.SetFlags(ENTITY_FLAG_LIGHT_STATIC); } + if (light.IsVolumetricCloudsEnabled()) + { + shaderentity.SetFlags(ENTITY_FLAG_LIGHT_VOLUMETRICCLOUDS); + } + std::memcpy(entityArray + entityCounter, &shaderentity, sizeof(ShaderEntity)); entityCounter++; } @@ -3754,7 +3814,16 @@ void UpdateRenderData( shaderentity.layerMask = layer->layerMask; } - shaderentity.SetType(force.type); + switch (force.type) + { + default: + case ForceFieldComponent::Type::Point: + shaderentity.SetType(ENTITY_TYPE_FORCEFIELD_POINT); + break; + case ForceFieldComponent::Type::Plane: + shaderentity.SetType(ENTITY_TYPE_FORCEFIELD_PLANE); + break; + } shaderentity.position = force.position; shaderentity.SetGravity(force.gravity); shaderentity.SetRange(std::max(0.001f, force.GetRange())); @@ -3983,10 +4052,15 @@ void UpdateRenderDataAsync( BindCommonResources(cmd); + if (vis.scene->weather.IsVolumetricClouds() && vis.scene->weather.IsVolumetricCloudsShadows()) + { + ComputeVolumetricCloudShadows(cmd, vis.scene->weather.volumetricCloudsWeatherMap.IsValid() ? &vis.scene->weather.volumetricCloudsWeatherMap.GetTexture() : nullptr); + } + if (vis.scene->weather.IsRealisticSky()) { // Render Atmospheric Scattering textures for lighting and sky - RenderAtmosphericScatteringTextures(cmd); + ComputeAtmosphericScatteringTextures(cmd); } // Precompute static volumetric cloud textures: @@ -6227,11 +6301,11 @@ void DrawDebugWorld( switch (force.type) { - case ENTITY_TYPE_FORCEFIELD_POINT: + case ForceFieldComponent::Type::Point: device->BindPipelineState(&PSO_debug[DEBUGRENDERING_FORCEFIELD_POINT], cmd); device->Draw(2880, 0, cmd); // uv-sphere break; - case ENTITY_TYPE_FORCEFIELD_PLANE: + case ForceFieldComponent::Type::Plane: device->BindPipelineState(&PSO_debug[DEBUGRENDERING_FORCEFIELD_PLANE], cmd); device->Draw(14, 0, cmd); // box break; @@ -6314,7 +6388,114 @@ void DrawDebugWorld( } -void RenderAtmosphericScatteringTextures(CommandList cmd) +void ComputeVolumetricCloudShadows( + CommandList cmd, + const Texture* weatherMap) +{ + device->EventBegin("RenderVolumetricCloudShadows", cmd); + auto range = wi::profiler::BeginRangeGPU("Volumetric Clouds Shadow", cmd); + + BindCommonResources(cmd); + + const TextureDesc& desc = textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW].GetDesc(); + PostProcess postprocess; + postprocess.resolution.x = desc.width; + postprocess.resolution.y = desc.height; + postprocess.resolution_rcp.x = 1.0f / postprocess.resolution.x; + postprocess.resolution_rcp.y = 1.0f / postprocess.resolution.y; + + // Cloud shadow render pass: + { + device->EventBegin("Volumetric Cloud Rendering Shadow", cmd); + device->BindComputeShader(&shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_SHADOW_RENDER], cmd); + device->PushConstants(&postprocess, sizeof(postprocess), cmd); + + device->BindResource(&texture_shapeNoise, 0, cmd); + device->BindResource(&texture_detailNoise, 1, cmd); + device->BindResource(&texture_curlNoise, 2, cmd); + + if (weatherMap != nullptr) + { + device->BindResource(weatherMap, 3, cmd); + } + else + { + device->BindResource(&texture_weatherMap, 3, cmd); + } + + const GPUResource* uavs[] = { + &textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW], + }; + device->BindUAVs(uavs, 0, arraysize(uavs), cmd); + + { + GPUBarrier barriers[] = { + GPUBarrier::Image(&textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW], textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW].desc.layout, ResourceState::UNORDERED_ACCESS), + }; + device->Barrier(barriers, arraysize(barriers), cmd); + } + + device->Dispatch( + (textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW].GetDesc().width + POSTPROCESS_BLOCKSIZE - 1) / POSTPROCESS_BLOCKSIZE, + (textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW].GetDesc().height + POSTPROCESS_BLOCKSIZE - 1) / POSTPROCESS_BLOCKSIZE, + 1, + cmd + ); + + { + GPUBarrier barriers[] = { + GPUBarrier::Memory(), + GPUBarrier::Image(&textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW], ResourceState::UNORDERED_ACCESS, textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW].desc.layout), + }; + device->Barrier(barriers, arraysize(barriers), cmd); + } + + device->EventEnd(cmd); + } + + // Cloud shadow filter pass: + { + device->EventBegin("Volumetric Cloud Filter Shadow", cmd); + device->BindComputeShader(&shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_SHADOW_FILTER], cmd); + device->PushConstants(&postprocess, sizeof(postprocess), cmd); + + device->BindResource(&textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW], 0, cmd); + + const GPUResource* uavs[] = { + &textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW], + }; + device->BindUAVs(uavs, 0, arraysize(uavs), cmd); + + { + GPUBarrier barriers[] = { + GPUBarrier::Image(&textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW], textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW].desc.layout, ResourceState::UNORDERED_ACCESS), + }; + device->Barrier(barriers, arraysize(barriers), cmd); + } + + device->Dispatch( + (textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW].GetDesc().width + POSTPROCESS_BLOCKSIZE - 1) / POSTPROCESS_BLOCKSIZE, + (textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW].GetDesc().height + POSTPROCESS_BLOCKSIZE - 1) / POSTPROCESS_BLOCKSIZE, + 1, + cmd + ); + + { + GPUBarrier barriers[] = { + GPUBarrier::Memory(), + GPUBarrier::Image(&textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW], ResourceState::UNORDERED_ACCESS, textures[TEXTYPE_2D_VOLUMETRICCLOUDS_SHADOW].desc.layout), + }; + device->Barrier(barriers, arraysize(barriers), cmd); + } + + device->EventEnd(cmd); + } + + wi::profiler::EndRange(range); + device->EventEnd(cmd); +} + +void ComputeAtmosphericScatteringTextures(CommandList cmd) { device->EventBegin("ComputeAtmosphericScatteringTextures", cmd); auto range = wi::profiler::BeginRangeGPU("Atmospheric Scattering Textures", cmd); @@ -6482,6 +6663,7 @@ void RefreshAtmosphericScatteringTextures(CommandList cmd) device->EventEnd(cmd); } + void DrawSky(const Scene& scene, CommandList cmd) { device->EventBegin("DrawSky", cmd); @@ -6543,6 +6725,7 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd) { frusta[i] = cameras[i].frustum; XMStoreFloat4x4(&cb.xCubemapRenderCams[i].view_projection, cameras[i].view_projection); + XMStoreFloat4x4(&cb.xCubemapRenderCams[i].inverse_view_projection, cameras[i].view_projection); cb.xCubemapRenderCams[i].properties = uint4(i, 0, 0, 0); } device->BindDynamicConstantBuffer(cb, CB_GETBINDSLOT(CubemapRenderCB), cmd); @@ -6609,6 +6792,99 @@ void RefreshEnvProbes(const Visibility& vis, CommandList cmd) device->RenderPassEnd(cmd); + // Compute Volumetric Clouds for environment map + if (vis.scene->weather.IsVolumetricClouds() && (probe_aabb.layerMask & vis.layerMask)) + { + if (probe.IsMSAA()) + { + device->EventBegin("Volumetric Cloud Rendering Capture [MSAA]", cmd); + device->BindComputeShader(&shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_RENDER_CAPTURE_MSAA], cmd); + device->BindResource(&vis.scene->envrenderingDepthBuffer_MSAA, 4, cmd); + } + else + { + device->EventBegin("Volumetric Cloud Rendering Capture", cmd); + device->BindComputeShader(&shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_RENDER_CAPTURE], cmd); + device->BindResource(&vis.scene->envrenderingDepthBuffer, 4, cmd); + } + + device->BindResource(&texture_shapeNoise, 0, cmd); + device->BindResource(&texture_detailNoise, 1, cmd); + device->BindResource(&texture_curlNoise, 2, cmd); + + if (vis.scene->weather.volumetricCloudsWeatherMap.IsValid()) + { + device->BindResource(&vis.scene->weather.volumetricCloudsWeatherMap.GetTexture(), 3, cmd); + } + else + { + device->BindResource(&texture_weatherMap, 3, cmd); + } + + TextureDesc desc = vis.scene->envmapArray.GetDesc(); + int arrayIndex = probe.textureIndex; + + VolumetricCloudCapturePushConstants push; + push.resolution.x = desc.width; + push.resolution.y = desc.height; + push.resolution_rcp.x = 1.0f / push.resolution.x; + push.resolution_rcp.y = 1.0f / push.resolution.y; + push.arrayIndex = arrayIndex; + push.texture_input = device->GetDescriptorIndex(&vis.scene->envmapArray, SubresourceType::SRV); + push.texture_output = device->GetDescriptorIndex(&vis.scene->envmapArray, SubresourceType::UAV); + + if (probe.IsRealTime()) + { + push.MaxStepCount = 32; + push.LODMin = 3; + push.ShadowSampleCount = 3; + push.GroundContributionSampleCount = 2; + } + else + { + // Use same parameters as current view + push.MaxStepCount = vis.scene->weather.volumetricCloudParameters.MaxStepCount; + push.LODMin = vis.scene->weather.volumetricCloudParameters.LODMin; + push.ShadowSampleCount = vis.scene->weather.volumetricCloudParameters.ShadowSampleCount; + push.GroundContributionSampleCount = vis.scene->weather.volumetricCloudParameters.GroundContributionSampleCount; + } + + device->PushConstants(&push, sizeof(push), cmd); + + { + GPUBarrier barriers[] = { + GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 0), + GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 1), + GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 2), + GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 3), + GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 4), + GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::SHADER_RESOURCE, ResourceState::UNORDERED_ACCESS, 0, arrayIndex * 6 + 5), + }; + device->Barrier(barriers, arraysize(barriers), cmd); + } + + device->Dispatch( + (desc.width + GENERATEMIPCHAIN_2D_BLOCK_SIZE - 1) / GENERATEMIPCHAIN_2D_BLOCK_SIZE, + (desc.height + GENERATEMIPCHAIN_2D_BLOCK_SIZE - 1) / GENERATEMIPCHAIN_2D_BLOCK_SIZE, + 6, + cmd); + + { + GPUBarrier barriers[] = { + GPUBarrier::Memory(), + GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 0), + GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 1), + GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 2), + GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 3), + GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 4), + GPUBarrier::Image(&vis.scene->envmapArray, ResourceState::UNORDERED_ACCESS, ResourceState::SHADER_RESOURCE, 0, arrayIndex * 6 + 5), + }; + device->Barrier(barriers, arraysize(barriers), cmd); + } + + device->EventEnd(cmd); + } + MIPGEN_OPTIONS mipopt; mipopt.arrayIndex = probe.textureIndex; GenerateMipChain(vis.scene->envmapArray, MIPGENFILTER_LINEAR, cmd, mipopt); @@ -12124,7 +12400,6 @@ void CreateVolumetricCloudResources(VolumetricCloudResources& res, XMUINT2 resol XMUINT2 renderResolution = XMUINT2(resolution.x / 4, resolution.y / 4); XMUINT2 reprojectionResolution = XMUINT2(resolution.x / 2, resolution.y / 2); - XMUINT2 maskResolution = XMUINT2(resolution.x / 4, resolution.y / 4); // Needs to be half of final cloud output TextureDesc desc; desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS; @@ -12150,22 +12425,19 @@ void CreateVolumetricCloudResources(VolumetricCloudResources& res, XMUINT2 resol device->SetName(&res.texture_reproject_depth[0], "texture_reproject_depth[0]"); device->CreateTexture(&desc, nullptr, &res.texture_reproject_depth[1]); device->SetName(&res.texture_reproject_depth[1], "texture_reproject_depth[1]"); - - desc.format = Format::R16G16B16A16_FLOAT; - device->CreateTexture(&desc, nullptr, &res.texture_temporal[0]); - device->SetName(&res.texture_temporal[0], "texture_temporal[0]"); - device->CreateTexture(&desc, nullptr, &res.texture_temporal[1]); - device->SetName(&res.texture_temporal[1], "texture_temporal[1]"); - - desc.width = maskResolution.x; - desc.height = maskResolution.y; + desc.format = Format::R8_UNORM; + device->CreateTexture(&desc, nullptr, &res.texture_reproject_additional[0]); + device->SetName(&res.texture_reproject_additional[0], "texture_reproject_additional[0]"); + device->CreateTexture(&desc, nullptr, &res.texture_reproject_additional[1]); + device->SetName(&res.texture_reproject_additional[1], "texture_reproject_additional[1]"); desc.format = Format::R8G8B8A8_UNORM; device->CreateTexture(&desc, nullptr, &res.texture_cloudMask); device->SetName(&res.texture_cloudMask, "texture_cloudMask"); } void Postprocess_VolumetricClouds( const VolumetricCloudResources& res, - CommandList cmd + CommandList cmd, + const Texture* weatherMap ) { device->EventBegin("Postprocess_VolumetricClouds", cmd); @@ -12184,16 +12456,24 @@ void Postprocess_VolumetricClouds( postprocess.params0.z = 1.0f / postprocess.params0.x; postprocess.params0.w = 1.0f / postprocess.params0.y; - // Cloud pass: + // Cloud render pass: { - device->EventBegin("Volumetric Cloud Rendering", cmd); + device->EventBegin("Volumetric Cloud Render", cmd); device->BindComputeShader(&shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_RENDER], cmd); device->PushConstants(&postprocess, sizeof(postprocess), cmd); - device->BindResource(&texture_shapeNoise, 1, cmd); - device->BindResource(&texture_detailNoise, 2, cmd); - device->BindResource(&texture_curlNoise, 3, cmd); - device->BindResource(&texture_weatherMap, 4, cmd); + device->BindResource(&texture_shapeNoise, 0, cmd); + device->BindResource(&texture_detailNoise, 1, cmd); + device->BindResource(&texture_curlNoise, 2, cmd); + + if (weatherMap != nullptr) + { + device->BindResource(weatherMap, 3, cmd); + } + else + { + device->BindResource(&texture_weatherMap, 3, cmd); + } const GPUResource* uavs[] = { &res.texture_cloudRender, @@ -12238,7 +12518,7 @@ void Postprocess_VolumetricClouds( int temporal_output = device->GetFrameCount() % 2; int temporal_history = 1 - temporal_output; - // Reprojection pass: + // Cloud reprojection pass: { device->EventBegin("Volumetric Cloud Reproject", cmd); device->BindComputeShader(&shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_REPROJECT], cmd); @@ -12248,10 +12528,13 @@ void Postprocess_VolumetricClouds( device->BindResource(&res.texture_cloudDepth, 1, cmd); device->BindResource(&res.texture_reproject[temporal_history], 2, cmd); device->BindResource(&res.texture_reproject_depth[temporal_history], 3, cmd); + device->BindResource(&res.texture_reproject_additional[temporal_history], 4, cmd); const GPUResource* uavs[] = { &res.texture_reproject[temporal_output], &res.texture_reproject_depth[temporal_output], + &res.texture_reproject_additional[temporal_output], + &res.texture_cloudMask, }; device->BindUAVs(uavs, 0, arraysize(uavs), cmd); @@ -12259,6 +12542,8 @@ void Postprocess_VolumetricClouds( GPUBarrier barriers[] = { GPUBarrier::Image(&res.texture_reproject[temporal_output], res.texture_reproject[temporal_output].desc.layout, ResourceState::UNORDERED_ACCESS), GPUBarrier::Image(&res.texture_reproject_depth[temporal_output], res.texture_reproject_depth[temporal_output].desc.layout, ResourceState::UNORDERED_ACCESS), + GPUBarrier::Image(&res.texture_reproject_additional[temporal_output], res.texture_reproject_additional[temporal_output].desc.layout, ResourceState::UNORDERED_ACCESS), + GPUBarrier::Image(&res.texture_cloudMask, res.texture_cloudMask.desc.layout, ResourceState::UNORDERED_ACCESS), }; device->Barrier(barriers, arraysize(barriers), cmd); } @@ -12275,48 +12560,7 @@ void Postprocess_VolumetricClouds( GPUBarrier::Memory(), GPUBarrier::Image(&res.texture_reproject[temporal_output], ResourceState::UNORDERED_ACCESS, res.texture_reproject[temporal_output].desc.layout), GPUBarrier::Image(&res.texture_reproject_depth[temporal_output], ResourceState::UNORDERED_ACCESS, res.texture_reproject_depth[temporal_output].desc.layout), - }; - device->Barrier(barriers, arraysize(barriers), cmd); - } - - device->EventEnd(cmd); - } - - // Temporal pass: - { - device->EventBegin("Volumetric Cloud Temporal", cmd); - device->BindComputeShader(&shaders[CSTYPE_POSTPROCESS_VOLUMETRICCLOUDS_TEMPORAL], cmd); - device->PushConstants(&postprocess, sizeof(postprocess), cmd); - - device->BindResource(&res.texture_reproject[temporal_output], 0, cmd); - device->BindResource(&res.texture_reproject_depth[temporal_output], 1, cmd); - device->BindResource(&res.texture_temporal[temporal_history], 2, cmd); - - const GPUResource* uavs[] = { - &res.texture_temporal[temporal_output], - &res.texture_cloudMask, - }; - device->BindUAVs(uavs, 0, arraysize(uavs), cmd); - - { - GPUBarrier barriers[] = { - GPUBarrier::Image(&res.texture_temporal[temporal_output], res.texture_temporal[temporal_output].desc.layout, ResourceState::UNORDERED_ACCESS), - GPUBarrier::Image(&res.texture_cloudMask, res.texture_cloudMask.desc.layout, ResourceState::UNORDERED_ACCESS), - }; - device->Barrier(barriers, arraysize(barriers), cmd); - } - - device->Dispatch( - (res.texture_temporal[temporal_output].GetDesc().width + POSTPROCESS_BLOCKSIZE - 1) / POSTPROCESS_BLOCKSIZE, - (res.texture_temporal[temporal_output].GetDesc().height + POSTPROCESS_BLOCKSIZE - 1) / POSTPROCESS_BLOCKSIZE, - 1, - cmd - ); - - { - GPUBarrier barriers[] = { - GPUBarrier::Memory(), - GPUBarrier::Image(&res.texture_temporal[temporal_output], ResourceState::UNORDERED_ACCESS, res.texture_temporal[temporal_output].desc.layout), + GPUBarrier::Image(&res.texture_reproject_additional[temporal_output], ResourceState::UNORDERED_ACCESS, res.texture_reproject_additional[temporal_output].desc.layout), GPUBarrier::Image(&res.texture_cloudMask, ResourceState::UNORDERED_ACCESS, res.texture_cloudMask.desc.layout), }; device->Barrier(barriers, arraysize(barriers), cmd); diff --git a/WickedEngine/wiRenderer.h b/WickedEngine/wiRenderer.h index c574834ba..6a7d4f5cc 100644 --- a/WickedEngine/wiRenderer.h +++ b/WickedEngine/wiRenderer.h @@ -206,8 +206,13 @@ namespace wi::renderer // Render mip levels for textures that reqested it: void ProcessDeferredMipGenRequests(wi::graphics::CommandList cmd); + // Compute volumetric cloud shadow data + void ComputeVolumetricCloudShadows( + wi::graphics::CommandList cmd, + const wi::graphics::Texture* weatherMap = nullptr + ); // Compute essential atmospheric scattering textures for skybox, fog and clouds - void RenderAtmosphericScatteringTextures(wi::graphics::CommandList cmd); + void ComputeAtmosphericScatteringTextures(wi::graphics::CommandList cmd); // Update atmospheric scattering primarily for environment probes. void RefreshAtmosphericScatteringTextures(wi::graphics::CommandList cmd); // Draw skydome centered to camera. @@ -621,13 +626,14 @@ namespace wi::renderer wi::graphics::Texture texture_cloudDepth; wi::graphics::Texture texture_reproject[2]; wi::graphics::Texture texture_reproject_depth[2]; - wi::graphics::Texture texture_temporal[2]; + wi::graphics::Texture texture_reproject_additional[2]; wi::graphics::Texture texture_cloudMask; }; void CreateVolumetricCloudResources(VolumetricCloudResources& res, XMUINT2 resolution); void Postprocess_VolumetricClouds( const VolumetricCloudResources& res, - wi::graphics::CommandList cmd + wi::graphics::CommandList cmd, + const wi::graphics::Texture* weatherMap = nullptr ); void Postprocess_FXAA( const wi::graphics::Texture& input, diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index d2e3d243c..fd775d8f8 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -2060,17 +2060,10 @@ namespace wi::scene shaderscene.weather.sun_direction = weather.sunDirection; shaderscene.weather.most_important_light_index = weather.most_important_light_index; shaderscene.weather.ambient = weather.ambient; - shaderscene.weather.cloudiness = weather.cloudiness; - shaderscene.weather.cloud_scale = weather.cloudScale; - shaderscene.weather.cloud_speed = weather.cloudSpeed; - shaderscene.weather.cloud_shadow_amount = weather.cloud_shadow_amount; - shaderscene.weather.cloud_shadow_scale = weather.cloud_shadow_scale; - shaderscene.weather.cloud_shadow_speed = weather.cloud_shadow_speed; shaderscene.weather.fog.start = weather.fogStart; shaderscene.weather.fog.end = weather.fogEnd; shaderscene.weather.fog.height_start = weather.fogHeightStart; shaderscene.weather.fog.height_end = weather.fogHeightEnd; - shaderscene.weather.fog.height_sky = weather.fogHeightSky; shaderscene.weather.horizon = weather.horizon; shaderscene.weather.zenith = weather.zenith; shaderscene.weather.sky_exposure = weather.skyExposure; @@ -2315,9 +2308,6 @@ namespace wi::scene transform.UpdateTransform(); ForceFieldComponent& force = forces.Create(entity); - force.gravity = 0; - force.range = 0; - force.type = ENTITY_TYPE_FORCEFIELD_POINT; return entity; } @@ -4835,12 +4825,9 @@ namespace wi::scene desc.mip_levels = 1; desc.usage = Usage::DEFAULT; - desc.bind_flags = BindFlag::DEPTH_STENCIL; - desc.format = Format::D16_UNORM; - desc.layout = ResourceState::DEPTHSTENCIL; - desc.misc_flags = ResourceMiscFlag::TRANSIENT_ATTACHMENT; - device->CreateTexture(&desc, nullptr, &envrenderingDepthBuffer); - device->SetName(&envrenderingDepthBuffer, "envrenderingDepthBuffer"); + desc.bind_flags = BindFlag::DEPTH_STENCIL | BindFlag::SHADER_RESOURCE; + desc.format = Format::R16_TYPELESS; + desc.layout = ResourceState::SHADER_RESOURCE; desc.sample_count = envmapMSAASampleCount; device->CreateTexture(&desc, nullptr, &envrenderingDepthBuffer_MSAA); device->SetName(&envrenderingDepthBuffer_MSAA, "envrenderingDepthBuffer_MSAA"); @@ -4862,17 +4849,25 @@ namespace wi::scene desc.misc_flags = ResourceMiscFlag::TEXTURECUBE; desc.usage = Usage::DEFAULT; desc.layout = ResourceState::SHADER_RESOURCE; - device->CreateTexture(&desc, nullptr, &envmapArray); device->SetName(&envmapArray, "envmapArray"); + desc.array_size = 6; + desc.mip_levels = 1; + desc.format = Format::R16_TYPELESS; + desc.bind_flags = BindFlag::DEPTH_STENCIL | BindFlag::SHADER_RESOURCE; + desc.layout = ResourceState::SHADER_RESOURCE; + device->CreateTexture(&desc, nullptr, &envrenderingDepthBuffer); + device->SetName(&envrenderingDepthBuffer, "envrenderingDepthBuffer"); + + // Cube arrays per mip level: for (uint32_t i = 0; i < envmapArray.desc.mip_levels; ++i) { int subresource_index; - subresource_index = device->CreateSubresource(&envmapArray, SubresourceType::SRV, 0, desc.array_size, i, 1); + subresource_index = device->CreateSubresource(&envmapArray, SubresourceType::SRV, 0, envmapArray.desc.array_size, i, 1); assert(subresource_index == i); - subresource_index = device->CreateSubresource(&envmapArray, SubresourceType::UAV, 0, desc.array_size, i, 1); + subresource_index = device->CreateSubresource(&envmapArray, SubresourceType::UAV, 0, envmapArray.desc.array_size, i, 1); assert(subresource_index == i); } @@ -4907,7 +4902,10 @@ namespace wi::scene RenderPassAttachment::DepthStencil( &envrenderingDepthBuffer, RenderPassAttachment::LoadOp::CLEAR, - RenderPassAttachment::StoreOp::DONTCARE + RenderPassAttachment::StoreOp::STORE, + ResourceState::SHADER_RESOURCE, + ResourceState::DEPTHSTENCIL, + ResourceState::SHADER_RESOURCE ) ); renderpassdesc.attachments.push_back( @@ -4931,7 +4929,10 @@ namespace wi::scene RenderPassAttachment::DepthStencil( &envrenderingDepthBuffer_MSAA, RenderPassAttachment::LoadOp::CLEAR, - RenderPassAttachment::StoreOp::DONTCARE + RenderPassAttachment::StoreOp::STORE, + ResourceState::SHADER_RESOURCE, + ResourceState::DEPTHSTENCIL, + ResourceState::SHADER_RESOURCE ) ); renderpassdesc.attachments.push_back( diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index 764cfe59c..23e0ce2d7 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -849,6 +849,7 @@ namespace wi::scene VOLUMETRICS = 1 << 1, VISUALIZER = 1 << 2, LIGHTMAPONLY_STATIC = 1 << 3, + VOLUMETRICCLOUDS = 1 << 4, }; uint32_t _flags = EMPTY; @@ -892,11 +893,13 @@ namespace wi::scene inline void SetVolumetricsEnabled(bool value) { if (value) { _flags |= VOLUMETRICS; } else { _flags &= ~VOLUMETRICS; } } inline void SetVisualizerEnabled(bool value) { if (value) { _flags |= VISUALIZER; } else { _flags &= ~VISUALIZER; } } inline void SetStatic(bool value) { if (value) { _flags |= LIGHTMAPONLY_STATIC; } else { _flags &= ~LIGHTMAPONLY_STATIC; } } + inline void SetVolumetricCloudsEnabled(bool value) { if (value) { _flags |= VOLUMETRICCLOUDS; } else { _flags &= ~VOLUMETRICCLOUDS; } } inline bool IsCastingShadow() const { return _flags & CAST_SHADOW; } inline bool IsVolumetricsEnabled() const { return _flags & VOLUMETRICS; } inline bool IsVisualizerEnabled() const { return _flags & VISUALIZER; } inline bool IsStatic() const { return _flags & LIGHTMAPONLY_STATIC; } + inline bool IsVolumetricCloudsEnabled() const { return _flags & VOLUMETRICCLOUDS; } inline float GetRange() const { @@ -1038,9 +1041,14 @@ namespace wi::scene }; uint32_t _flags = EMPTY; - int type = ENTITY_TYPE_FORCEFIELD_POINT; - float gravity = 0.0f; // negative = deflector, positive = attractor - float range = 0.0f; // affection range + enum class Type + { + Point, + Plane, + } type = Type::Point; + + float gravity = 0; // negative = deflector, positive = attractor + float range = 0; // affection range // Non-serialized attributes: XMFLOAT3 position; @@ -1227,24 +1235,25 @@ namespace wi::scene { EMPTY = 0, OCEAN_ENABLED = 1 << 0, - SIMPLE_SKY = 1 << 1, + _DEPRECATED_SIMPLE_SKY = 1 << 1, REALISTIC_SKY = 1 << 2, VOLUMETRIC_CLOUDS = 1 << 3, HEIGHT_FOG = 1 << 4, + VOLUMETRIC_CLOUDS_SHADOWS = 1 << 5, }; uint32_t _flags = EMPTY; inline bool IsOceanEnabled() const { return _flags & OCEAN_ENABLED; } - inline bool IsSimpleSky() const { return _flags & SIMPLE_SKY; } inline bool IsRealisticSky() const { return _flags & REALISTIC_SKY; } inline bool IsVolumetricClouds() const { return _flags & VOLUMETRIC_CLOUDS; } inline bool IsHeightFog() const { return _flags & HEIGHT_FOG; } + inline bool IsVolumetricCloudsShadows() const { return _flags & VOLUMETRIC_CLOUDS_SHADOWS; } inline void SetOceanEnabled(bool value = true) { if (value) { _flags |= OCEAN_ENABLED; } else { _flags &= ~OCEAN_ENABLED; } } - inline void SetSimpleSky(bool value = true) { if (value) { _flags |= SIMPLE_SKY; } else { _flags &= ~SIMPLE_SKY; } } inline void SetRealisticSky(bool value = true) { if (value) { _flags |= REALISTIC_SKY; } else { _flags &= ~REALISTIC_SKY; } } inline void SetVolumetricClouds(bool value = true) { if (value) { _flags |= VOLUMETRIC_CLOUDS; } else { _flags &= ~VOLUMETRIC_CLOUDS; } } inline void SetHeightFog(bool value = true) { if (value) { _flags |= HEIGHT_FOG; } else { _flags &= ~HEIGHT_FOG; } } + inline void SetVolumetricCloudsShadows(bool value = true) { if (value) { _flags |= VOLUMETRIC_CLOUDS_SHADOWS; } else { _flags &= ~VOLUMETRIC_CLOUDS_SHADOWS; } } XMFLOAT3 sunColor = XMFLOAT3(0, 0, 0); XMFLOAT3 sunDirection = XMFLOAT3(0, 1, 0); @@ -1256,13 +1265,6 @@ namespace wi::scene float fogEnd = 1000; float fogHeightStart = 1; float fogHeightEnd = 3; - float fogHeightSky = 0; - float cloudiness = 0.0f; - float cloudScale = 0.0003f; - float cloudSpeed = 0.1f; - float cloud_shadow_amount = 0; - float cloud_shadow_scale = 0.002f; - float cloud_shadow_speed = 0.2f; XMFLOAT3 windDirection = XMFLOAT3(0, 0, 0); float windRandomness = 5; float windWaveSize = 1; @@ -1275,11 +1277,13 @@ namespace wi::scene std::string skyMapName; std::string colorGradingMapName; + std::string volumetricCloudsWeatherMapName; // Non-serialized attributes: uint32_t most_important_light_index = ~0u; wi::Resource skyMap; wi::Resource colorGradingMap; + wi::Resource volumetricCloudsWeatherMap; XMFLOAT4 stars_rotation_quaternion = XMFLOAT4(0, 0, 0, 1); void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri); @@ -1554,7 +1558,7 @@ namespace wi::scene wi::ecs::ComponentManager& cameras = componentLibrary.Register("wi::scene::Scene::cameras"); wi::ecs::ComponentManager& probes = componentLibrary.Register("wi::scene::Scene::probes"); wi::ecs::ComponentManager& aabb_probes = componentLibrary.Register("wi::scene::Scene::aabb_probes"); - wi::ecs::ComponentManager& forces = componentLibrary.Register("wi::scene::Scene::forces"); + wi::ecs::ComponentManager& forces = componentLibrary.Register("wi::scene::Scene::forces", 1); // version = 1 wi::ecs::ComponentManager& decals = componentLibrary.Register("wi::scene::Scene::decals"); wi::ecs::ComponentManager& aabb_decals = componentLibrary.Register("wi::scene::Scene::aabb_decals"); wi::ecs::ComponentManager& animations = componentLibrary.Register("wi::scene::Scene::animations"); diff --git a/WickedEngine/wiScene_BindLua.cpp b/WickedEngine/wiScene_BindLua.cpp index 180b7ab7d..d5632d575 100644 --- a/WickedEngine/wiScene_BindLua.cpp +++ b/WickedEngine/wiScene_BindLua.cpp @@ -3872,13 +3872,9 @@ Luna::PropertyType Weather_VolumetricClou lunaproperty(Weather_VolumetricCloudParams_BindLua,DetailScale), lunaproperty(Weather_VolumetricCloudParams_BindLua,WeatherScale), lunaproperty(Weather_VolumetricCloudParams_BindLua,CurlScale), - lunaproperty(Weather_VolumetricCloudParams_BindLua,ShapeNoiseHeightGradientAmount), - lunaproperty(Weather_VolumetricCloudParams_BindLua,ShapeNoiseMultiplier), - lunaproperty(Weather_VolumetricCloudParams_BindLua,ShapeNoiseMinMax), - lunaproperty(Weather_VolumetricCloudParams_BindLua,ShapeNoisePower), lunaproperty(Weather_VolumetricCloudParams_BindLua,DetailNoiseModifier), lunaproperty(Weather_VolumetricCloudParams_BindLua,TypeAmount), - lunaproperty(Weather_VolumetricCloudParams_BindLua,TypeOverall), + lunaproperty(Weather_VolumetricCloudParams_BindLua,TypeMinimum), lunaproperty(Weather_VolumetricCloudParams_BindLua,AnvilAmount), lunaproperty(Weather_VolumetricCloudParams_BindLua,AnvilOverhangHeight), lunaproperty(Weather_VolumetricCloudParams_BindLua,AnimationMultiplier), @@ -4011,7 +4007,7 @@ int WeatherComponent_BindLua::SetOceanEnabled(lua_State* L) } int WeatherComponent_BindLua::IsSimpleSky(lua_State* L) { - wi::lua::SSetBool(L, component->IsSimpleSky()); + wi::lua::SSetBool(L, !component->IsRealisticSky()); return 1; } int WeatherComponent_BindLua::SetSimpleSky(lua_State* L) @@ -4020,7 +4016,7 @@ int WeatherComponent_BindLua::SetSimpleSky(lua_State* L) if (argc > 0) { bool value = wi::lua::SGetBool(L, 1); - component->SetSimpleSky(value); + component->SetRealisticSky(!value); } else { diff --git a/WickedEngine/wiScene_BindLua.h b/WickedEngine/wiScene_BindLua.h index f475ed810..2310b4f2b 100644 --- a/WickedEngine/wiScene_BindLua.h +++ b/WickedEngine/wiScene_BindLua.h @@ -849,15 +849,10 @@ namespace wi::lua::scene WeatherScale = FloatProperty(¶meter->WeatherScale); CurlScale = FloatProperty(¶meter->CurlScale); - ShapeNoiseHeightGradientAmount = FloatProperty(¶meter->ShapeNoiseHeightGradientAmount); - ShapeNoiseMultiplier = FloatProperty(¶meter->ShapeNoiseMultiplier); - - ShapeNoiseMinMax = VectorProperty(¶meter->ShapeNoiseMinMax); - ShapeNoisePower = FloatProperty(¶meter->ShapeNoisePower); DetailNoiseModifier = FloatProperty(¶meter->DetailNoiseModifier); TypeAmount = FloatProperty(¶meter->TypeAmount); - TypeOverall = FloatProperty(¶meter->TypeOverall); + TypeMinimum = FloatProperty(¶meter->TypeMinimum); AnvilAmount = FloatProperty(¶meter->AnvilAmount); AnvilOverhangHeight = FloatProperty(¶meter->AnvilOverhangHeight); @@ -895,15 +890,10 @@ namespace wi::lua::scene FloatProperty WeatherScale; FloatProperty CurlScale; - FloatProperty ShapeNoiseHeightGradientAmount; - FloatProperty ShapeNoiseMultiplier; - - VectorProperty ShapeNoiseMinMax; - FloatProperty ShapeNoisePower; FloatProperty DetailNoiseModifier; FloatProperty TypeAmount; - FloatProperty TypeOverall; + FloatProperty TypeMinimum; FloatProperty AnvilAmount; FloatProperty AnvilOverhangHeight; @@ -935,15 +925,10 @@ namespace wi::lua::scene PropertyFunction(WeatherScale) PropertyFunction(CurlScale) - PropertyFunction(ShapeNoiseHeightGradientAmount) - PropertyFunction(ShapeNoiseMultiplier) - - PropertyFunction(ShapeNoiseMinMax) - PropertyFunction(ShapeNoisePower) PropertyFunction(DetailNoiseModifier) PropertyFunction(TypeAmount) - PropertyFunction(TypeOverall) + PropertyFunction(TypeMinimum) PropertyFunction(AnvilAmount) PropertyFunction(AnvilOverhangHeight) @@ -992,13 +977,6 @@ namespace wi::lua::scene fogEnd = FloatProperty(&component->fogEnd); fogHeightStart = FloatProperty(&component->fogHeightStart); fogHeightEnd = FloatProperty(&component->fogHeightEnd); - fogHeightSky = FloatProperty(&component->fogHeightSky); - cloudiness = FloatProperty(&component->cloudiness); - cloudScale = FloatProperty(&component->cloudScale); - cloudSpeed = FloatProperty(&component->cloudSpeed); - cloud_shadow_amount = FloatProperty(&component->cloud_shadow_amount); - cloud_shadow_scale = FloatProperty(&component->cloud_shadow_scale); - cloud_shadow_speed = FloatProperty(&component->cloud_shadow_speed); windDirection = VectorProperty(&component->windDirection); windRandomness = FloatProperty(&component->windRandomness); windWaveSize = FloatProperty(&component->windWaveSize); diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp index 297eaa08b..1b1515a4d 100644 --- a/WickedEngine/wiScene_Serializers.cpp +++ b/WickedEngine/wiScene_Serializers.cpp @@ -900,14 +900,23 @@ namespace wi::scene if (archive.IsReadMode()) { archive >> _flags; - archive >> type; + uint32_t value; + archive >> value; + if (seri.GetVersion() < 1) + { + if (value == 200) + value = 0; + if (value == 201) + value = 1; + } + type = (Type)value; archive >> gravity; archive >> range; } else { archive << _flags; - archive << type; + archive << (uint32_t)type; archive << gravity; archive << range; } @@ -1034,10 +1043,17 @@ namespace wi::scene archive >> ambient; archive >> fogStart; archive >> fogEnd; - archive >> fogHeightSky; - archive >> cloudiness; - archive >> cloudScale; - archive >> cloudSpeed; + if (archive.GetVersion() < 86) + { + float fogHeightSky; + float cloudiness; + float cloudScale; + float cloudSpeed; + archive >> fogHeightSky; + archive >> cloudiness; + archive >> cloudScale; + archive >> cloudSpeed; + } archive >> windDirection; archive >> windRandomness; archive >> windWaveSize; @@ -1135,17 +1151,24 @@ namespace wi::scene archive >> volumetricCloudParameters.DetailScale; archive >> volumetricCloudParameters.WeatherScale; archive >> volumetricCloudParameters.CurlScale; - archive >> volumetricCloudParameters.ShapeNoiseHeightGradientAmount; - archive >> volumetricCloudParameters.ShapeNoiseMultiplier; - archive >> volumetricCloudParameters.ShapeNoiseMinMax; - archive >> volumetricCloudParameters.ShapeNoisePower; + if (archive.GetVersion() < 86) + { + float ShapeNoiseHeightGradientAmount; + float ShapeNoiseMultiplier; + XMFLOAT2 ShapeNoiseMinMax; + float ShapeNoisePower; + archive >> ShapeNoiseHeightGradientAmount; + archive >> ShapeNoiseMultiplier; + archive >> ShapeNoiseMinMax; + archive >> ShapeNoisePower; + } archive >> volumetricCloudParameters.DetailNoiseModifier; archive >> volumetricCloudParameters.DetailNoiseHeightFraction; archive >> volumetricCloudParameters.CurlNoiseModifier; archive >> volumetricCloudParameters.CoverageAmount; archive >> volumetricCloudParameters.CoverageMinimum; archive >> volumetricCloudParameters.TypeAmount; - archive >> volumetricCloudParameters.TypeOverall; + archive >> volumetricCloudParameters.TypeMinimum; archive >> volumetricCloudParameters.AnvilAmount; archive >> volumetricCloudParameters.AnvilOverhangHeight; archive >> volumetricCloudParameters.AnimationMultiplier; @@ -1167,6 +1190,15 @@ namespace wi::scene archive >> volumetricCloudParameters.TransmittanceThreshold; archive >> volumetricCloudParameters.ShadowSampleCount; archive >> volumetricCloudParameters.GroundContributionSampleCount; + + if (archive.GetVersion() < 86) + { + volumetricCloudParameters.HorizonBlendAmount *= 0.00001f; + volumetricCloudParameters.TotalNoiseScale *= 0.0004f; + volumetricCloudParameters.WeatherScale *= 0.0004f; + volumetricCloudParameters.CoverageAmount /= 2.0f; + volumetricCloudParameters.CoverageMinimum = std::max(0.0f, volumetricCloudParameters.CoverageMinimum - 1.0f); + } } if (archive.GetVersion() >= 71) @@ -1175,8 +1207,11 @@ namespace wi::scene archive >> fogHeightEnd; } - if (archive.GetVersion() >= 77) + if (archive.GetVersion() >= 77 && archive.GetVersion() < 86) { + float cloud_shadow_amount; + float cloud_shadow_scale; + float cloud_shadow_speed; archive >> cloud_shadow_amount; archive >> cloud_shadow_scale; archive >> cloud_shadow_speed; @@ -1186,6 +1221,16 @@ namespace wi::scene { archive >> stars; } + + if (archive.GetVersion() >= 86) + { + archive >> volumetricCloudsWeatherMapName; + if (!volumetricCloudsWeatherMapName.empty()) + { + volumetricCloudsWeatherMapName = dir + volumetricCloudsWeatherMapName; + volumetricCloudsWeatherMap = wi::resourcemanager::Load(volumetricCloudsWeatherMapName, wi::resourcemanager::Flags::IMPORT_RETAIN_FILEDATA); + } + } } else { @@ -1197,10 +1242,6 @@ namespace wi::scene archive << ambient; archive << fogStart; archive << fogEnd; - archive << fogHeightSky; - archive << cloudiness; - archive << cloudScale; - archive << cloudSpeed; archive << windDirection; archive << windRandomness; archive << windWaveSize; @@ -1220,6 +1261,7 @@ namespace wi::scene wi::helper::MakePathRelative(dir, skyMapName); wi::helper::MakePathRelative(dir, colorGradingMapName); + wi::helper::MakePathRelative(dir, volumetricCloudsWeatherMapName); if (archive.GetVersion() >= 32) { @@ -1280,17 +1322,24 @@ namespace wi::scene archive << volumetricCloudParameters.DetailScale; archive << volumetricCloudParameters.WeatherScale; archive << volumetricCloudParameters.CurlScale; - archive << volumetricCloudParameters.ShapeNoiseHeightGradientAmount; - archive << volumetricCloudParameters.ShapeNoiseMultiplier; - archive << volumetricCloudParameters.ShapeNoiseMinMax; - archive << volumetricCloudParameters.ShapeNoisePower; + if (archive.GetVersion() < 86) + { + float ShapeNoiseHeightGradientAmount = 0; + float ShapeNoiseMultiplier = 0; + XMFLOAT2 ShapeNoiseMinMax = XMFLOAT2(0, 0); + float ShapeNoisePower = 0; + archive << ShapeNoiseHeightGradientAmount; + archive << ShapeNoiseMultiplier; + archive << ShapeNoiseMinMax; + archive << ShapeNoisePower; + } archive << volumetricCloudParameters.DetailNoiseModifier; archive << volumetricCloudParameters.DetailNoiseHeightFraction; archive << volumetricCloudParameters.CurlNoiseModifier; archive << volumetricCloudParameters.CoverageAmount; archive << volumetricCloudParameters.CoverageMinimum; archive << volumetricCloudParameters.TypeAmount; - archive << volumetricCloudParameters.TypeOverall; + archive << volumetricCloudParameters.TypeMinimum; archive << volumetricCloudParameters.AnvilAmount; archive << volumetricCloudParameters.AnvilOverhangHeight; archive << volumetricCloudParameters.AnimationMultiplier; @@ -1320,17 +1369,15 @@ namespace wi::scene archive << fogHeightEnd; } - if (archive.GetVersion() >= 77) - { - archive << cloud_shadow_amount; - archive << cloud_shadow_scale; - archive << cloud_shadow_speed; - } - if (archive.GetVersion() >= 78) { archive << stars; } + + if (archive.GetVersion() >= 86) + { + archive << volumetricCloudsWeatherMapName; + } } } void SoundComponent::Serialize(wi::Archive& archive, EntitySerializer& seri) diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 57916b57f..3cad487ca 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 71; // minor bug fixes, alterations, refactors, updates - const int revision = 29; + const int revision = 30; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision);