From 98e0dd1e9775dd75f911492718dfa35fc7927b01 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 12:36:50 +0000 Subject: [PATCH 1/4] Initial plan From cdf2e5d6c93493b93ba8dd5f47aa5acf9fe49133 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 12:55:01 +0000 Subject: [PATCH 2/4] Implement height fog system (HeightFogFeature, HeightFogPass, HeightFogComponent) Co-authored-by: bluesky013 <35895395+bluesky013@users.noreply.github.com> --- assets/shaders/atmosphere/height_fog.hlsl | 123 ++++++++++++++++++ .../shaders/layout/default_global_view.hlslh | 1 + assets/techniques/height_fog.tech | 18 +++ .../render/adaptor/atmosphere/HeightFogPass.h | 22 ++++ .../adaptor/components/HeightFogComponent.h | 61 +++++++++ .../adaptor/pipeline/DefaultForwardPipeline.h | 2 + .../adaptor/pipeline/PostProcessingPass.h | 3 + engine/render/adaptor/src/RenderModule.cpp | 6 + .../adaptor/src/atmosphere/HeightFogPass.cpp | 68 ++++++++++ .../src/components/HeightFogComponent.cpp | 108 +++++++++++++++ .../src/pipeline/DefaultForwardPipeline.cpp | 17 ++- .../src/pipeline/PostProcessingPass.cpp | 7 + .../core/include/render/RenderBuiltinLayout.h | 14 ++ .../render/atmosphere/HeightFogFeature.h | 45 +++++++ engine/render/core/src/SceneView.cpp | 1 + .../core/src/atmosphere/HeightFogFeature.cpp | 82 ++++++++++++ 16 files changed, 574 insertions(+), 4 deletions(-) create mode 100644 assets/shaders/atmosphere/height_fog.hlsl create mode 100644 assets/techniques/height_fog.tech create mode 100644 engine/render/adaptor/include/render/adaptor/atmosphere/HeightFogPass.h create mode 100644 engine/render/adaptor/include/render/adaptor/components/HeightFogComponent.h create mode 100644 engine/render/adaptor/src/atmosphere/HeightFogPass.cpp create mode 100644 engine/render/adaptor/src/components/HeightFogComponent.cpp create mode 100644 engine/render/core/include/render/atmosphere/HeightFogFeature.h create mode 100644 engine/render/core/src/atmosphere/HeightFogFeature.cpp diff --git a/assets/shaders/atmosphere/height_fog.hlsl b/assets/shaders/atmosphere/height_fog.hlsl new file mode 100644 index 00000000..82ef0ef9 --- /dev/null +++ b/assets/shaders/atmosphere/height_fog.hlsl @@ -0,0 +1,123 @@ + +const float2 FT_POSITIONS[3] = { + float2(-1.0, 3.0), + float2(-1.0, -1.0), + float2( 3.0, -1.0) +}; + +const float2 FT_UVS[3] = { + float2(0.0, 2.0), + float2(0.0, 0.0), + float2(2.0, 0.0) +}; + +struct VSOutput +{ + float4 Pos : SV_POSITION; + float2 UV : TEXCOORD; +}; + +VSOutput VSMain(uint vid : SV_VertexID) +{ + VSOutput res; + res.Pos = float4(FT_POSITIONS[vid], 0.0, 1.0); + res.UV = FT_UVS[vid]; + return res; +} + +struct ViewInfo { + float4x4 World; + float4x4 View; + float4x4 ViewProj; + float4x4 LastViewProj; + float4x4 InvViewProj; +}; + +[[vk::binding(0, 0)]] cbuffer viewInfo : register(b0, space0) +{ + ViewInfo View; +} + +[[vk::binding(1, 0)]] cbuffer heightFogCB : register(b1, space0) +{ + float4 FogColor; // rgb: fog color, a: unused + float4 InscatterColor; // rgb: inscatter color, a: unused + float FogDensity; // global fog density + float HeightFalloff; // fog density falloff per unit height + float BaseHeight; // height where full fog starts + float MaxHeight; // height above which there is no fog + float StartDistance; // view distance before fog starts + float InscatterExponent; // exponent for directional inscattering + float Pad0; + float Pad1; +} + +[[vk::binding(2, 0)]] Texture2D InColor : register(t0, space0); +[[vk::binding(3, 0)]] SamplerState InColorSampler : register(s0, space0); + +[[vk::binding(4, 0)]] Texture2D InDepth : register(t1, space0); +[[vk::binding(5, 0)]] SamplerState InDepthSampler : register(s1, space0); + +// Reconstruct world position from a depth value and screen UV. +float3 ReconstructWorldPos(float2 uv, float depth) +{ + float2 ndc = uv * 2.0 - 1.0; + float4 clipPos = float4(ndc.x, ndc.y, depth, 1.0); + float4 worldPos = mul(View.InvViewProj, clipPos); + return worldPos.xyz / worldPos.w; +} + +// Analytical integration of exponential height fog density along a view ray. +// density(h) = FogDensity * exp(-HeightFalloff * max(h - BaseHeight, 0)) +float ComputeHeightFogFactor(float3 worldPos) +{ + float3 camPos = float3(View.World[0][3], View.World[1][3], View.World[2][3]); + float3 ray = worldPos - camPos; + float dist = length(ray); + + if (dist <= StartDistance) { + return 0.0; + } + float effectiveDist = dist - StartDistance; + + float camH = camPos.y; + float fragH = worldPos.y; + float deltaH = fragH - camH; + + float fogIntegral; + if (abs(deltaH) < 0.001) { + // Horizontal ray: constant density + float h = max(camH - BaseHeight, 0.0); + fogIntegral = FogDensity * exp(-HeightFalloff * h) * effectiveDist; + } else { + // Integrate density along the ray analytically + float h0 = max(camH - BaseHeight, 0.0); + float h1 = max(fragH - BaseHeight, 0.0); + fogIntegral = FogDensity * effectiveDist * + (exp(-HeightFalloff * h0) - exp(-HeightFalloff * h1)) / + (HeightFalloff * abs(deltaH) + 0.0001); + } + + // Suppress fog above MaxHeight + float heightMask = 1.0 - saturate((fragH - MaxHeight) / (max(MaxHeight - BaseHeight, 0.001))); + fogIntegral *= heightMask; + + return 1.0 - exp(-max(fogIntegral, 0.0)); +} + +float4 FSMain(VSOutput input) : SV_TARGET +{ + float4 sceneColor = InColor.Sample(InColorSampler, input.UV); + float depth = InDepth.Sample(InDepthSampler, input.UV).r; + + // Skip sky pixels (depth == 1.0 means far plane / no geometry) + if (depth >= 1.0) { + return sceneColor; + } + + float3 worldPos = ReconstructWorldPos(input.UV, depth); + float fogFactor = ComputeHeightFogFactor(worldPos); + + float3 finalColor = lerp(sceneColor.rgb, FogColor.rgb, fogFactor); + return float4(finalColor, sceneColor.a); +} diff --git a/assets/shaders/layout/default_global_view.hlslh b/assets/shaders/layout/default_global_view.hlslh index 19e8e390..13f9e8f7 100644 --- a/assets/shaders/layout/default_global_view.hlslh +++ b/assets/shaders/layout/default_global_view.hlslh @@ -3,6 +3,7 @@ struct ViewInfo { float4x4 View; float4x4 ViewProj; float4x4 LastViewProj; + float4x4 InvViewProj; }; [[vk::binding(0, 0)]] cbuffer global : register(b0, space0) diff --git a/assets/techniques/height_fog.tech b/assets/techniques/height_fog.tech new file mode 100644 index 00000000..bbf250ce --- /dev/null +++ b/assets/techniques/height_fog.tech @@ -0,0 +1,18 @@ +{ + "type": "graphics", + "shader": { + "path": "atmosphere/height_fog.hlsl", + "vertex": "VSMain", + "fragment": "FSMain" + }, + "pass": { + "tag": "HeightFog" + }, + "depth_stencil": { + "depthTestEnable": false, + "depthWriteEnable": false + }, + "raster_state": { + "cullMode": "NONE" + } +} diff --git a/engine/render/adaptor/include/render/adaptor/atmosphere/HeightFogPass.h b/engine/render/adaptor/include/render/adaptor/atmosphere/HeightFogPass.h new file mode 100644 index 00000000..2f4546b4 --- /dev/null +++ b/engine/render/adaptor/include/render/adaptor/atmosphere/HeightFogPass.h @@ -0,0 +1,22 @@ +// +// Created by SkyEngine on 2025/3/7. +// + +#pragma once + +#include +#include + +namespace sky { + + static constexpr std::string_view HEIGHT_FOG_OUTPUT = "HeightFogOutput"; + + class HeightFogPass : public FullScreenPass { + public: + explicit HeightFogPass(const RDGfxTechPtr &tech); + ~HeightFogPass() override = default; + + void Setup(rdg::RenderGraph &rdg, RenderScene &scene) override; + }; + +} // namespace sky diff --git a/engine/render/adaptor/include/render/adaptor/components/HeightFogComponent.h b/engine/render/adaptor/include/render/adaptor/components/HeightFogComponent.h new file mode 100644 index 00000000..751d5678 --- /dev/null +++ b/engine/render/adaptor/include/render/adaptor/components/HeightFogComponent.h @@ -0,0 +1,61 @@ +// +// Created by SkyEngine on 2025/3/7. +// + +#pragma once + +#include +#include +#include + +namespace sky { + + struct HeightFogData { + ColorRGB fogColor = ColorRGB{0.7f, 0.8f, 0.9f}; + ColorRGB inscatterColor = ColorRGB{0.9f, 0.85f, 0.7f}; + float fogDensity = 0.02f; + float heightFalloff = 0.15f; + float baseHeight = 0.f; + float maxHeight = 50.f; + float startDistance = 0.f; + }; + + class HeightFogComponent : public ComponentAdaptor { + public: + HeightFogComponent() = default; + ~HeightFogComponent() override = default; + + COMPONENT_RUNTIME_INFO(HeightFogComponent) + + static void Reflect(SerializationContext *context); + + void Tick(float time) override {} + void OnAttachToWorld() override; + void OnDetachFromWorld() override; + + void SetFogColor(const ColorRGB &color); + const ColorRGB &GetFogColor() const { return data.fogColor; } + + void SetInscatterColor(const ColorRGB &color); + const ColorRGB &GetInscatterColor() const { return data.inscatterColor; } + + void SetFogDensity(float density); + float GetFogDensity() const { return data.fogDensity; } + + void SetHeightFalloff(float falloff); + float GetHeightFalloff() const { return data.heightFalloff; } + + void SetBaseHeight(float height); + float GetBaseHeight() const { return data.baseHeight; } + + void SetMaxHeight(float height); + float GetMaxHeight() const { return data.maxHeight; } + + void SetStartDistance(float distance); + float GetStartDistance() const { return data.startDistance; } + + private: + HeightFogFeatureProcessor *fp = nullptr; + }; + +} // namespace sky diff --git a/engine/render/adaptor/include/render/adaptor/pipeline/DefaultForwardPipeline.h b/engine/render/adaptor/include/render/adaptor/pipeline/DefaultForwardPipeline.h index 16ff4b52..8012ceb0 100644 --- a/engine/render/adaptor/include/render/adaptor/pipeline/DefaultForwardPipeline.h +++ b/engine/render/adaptor/include/render/adaptor/pipeline/DefaultForwardPipeline.h @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace sky { @@ -45,6 +46,7 @@ namespace sky { std::unique_ptr hiz; std::unique_ptr shadowMap; std::unique_ptr forward; + std::unique_ptr heightFog; std::unique_ptr postProcess; std::unique_ptr present; std::unique_ptr brdfLut; diff --git a/engine/render/adaptor/include/render/adaptor/pipeline/PostProcessingPass.h b/engine/render/adaptor/include/render/adaptor/pipeline/PostProcessingPass.h index c772d2c3..ffdb1469 100644 --- a/engine/render/adaptor/include/render/adaptor/pipeline/PostProcessingPass.h +++ b/engine/render/adaptor/include/render/adaptor/pipeline/PostProcessingPass.h @@ -18,6 +18,9 @@ namespace sky { void SetupSubPass(rdg::RasterSubPassBuilder& builder, RenderScene &scene) override; + // Change the source color input (e.g. redirect to fog pass output) + void SetColorInput(const Name &name); + private: RDResourceLayoutPtr debugLayout; }; diff --git a/engine/render/adaptor/src/RenderModule.cpp b/engine/render/adaptor/src/RenderModule.cpp index 93c6b45e..57e00fe0 100644 --- a/engine/render/adaptor/src/RenderModule.cpp +++ b/engine/render/adaptor/src/RenderModule.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include #include #include @@ -50,6 +52,7 @@ namespace sky { SkeletalMeshComponent::Reflect(context); SkyBoxComponent::Reflect(context); PrefabComponent::Reflect(context); + HeightFogComponent::Reflect(context); SkeletonDisplayComponent::Reflect(context); AnimationPreviewComponent::Reflect(context); @@ -63,6 +66,7 @@ namespace sky { ComponentFactory::Get()->RegisterComponent(GROUP); ComponentFactory::Get()->RegisterComponent(GROUP); ComponentFactory::Get()->RegisterComponent(GROUP); + ComponentFactory::Get()->RegisterComponent(GROUP); } { @@ -146,6 +150,7 @@ namespace sky { TextFeature::Get()->Init(); LightFeature::Get()->Init(); EnvFeature::Get()->Init(); + HeightFogFeature::Get()->Init(); auto *am = AssetManager::Get(); { @@ -175,6 +180,7 @@ namespace sky { ImGuiFeature::Destroy(); TextFeature::Destroy(); EnvFeature::Destroy(); + HeightFogFeature::Destroy(); Renderer::Destroy(); RHI::Destroy(); diff --git a/engine/render/adaptor/src/atmosphere/HeightFogPass.cpp b/engine/render/adaptor/src/atmosphere/HeightFogPass.cpp new file mode 100644 index 00000000..c674c269 --- /dev/null +++ b/engine/render/adaptor/src/atmosphere/HeightFogPass.cpp @@ -0,0 +1,68 @@ +// +// Created by SkyEngine on 2025/3/7. +// + +#include +#include +#include +#include + +namespace sky { + + HeightFogPass::HeightFogPass(const RDGfxTechPtr &tech) + : FullScreenPass(Name("HeightFogPass"), tech) + { + Name fogOutputName(HEIGHT_FOG_OUTPUT.data()); + Name fwdColor(FWD_CL.data()); + Name fwdDepth(FWD_DS.data()); + + rdg::GraphImage image = {}; + image.extent.width = width; + image.extent.height = height; + image.format = rhi::PixelFormat::RGBA16_SFLOAT; + image.usage = rhi::ImageUsageFlagBit::RENDER_TARGET | rhi::ImageUsageFlagBit::SAMPLED; + image.samples = rhi::SampleCount::X1; + images.emplace_back(fogOutputName, image); + + colors.emplace_back(Attachment{ + rdg::RasterAttachment{fogOutputName, rhi::LoadOp::DONT_CARE, rhi::StoreOp::STORE}, + rhi::ClearValue(0.f, 0.f, 0.f, 0.f) + }); + + auto stageFlags = rhi::ShaderStageFlagBit::FS; + + // View info (contains InvViewProj for world-space reconstruction) + computeResources.emplace_back(ComputeResource{ + Name("SCENE_VIEW"), + rdg::ComputeView{Name("viewInfo"), rdg::ComputeType::CBV, stageFlags} + }); + + // Height fog parameters UBO + computeResources.emplace_back(ComputeResource{ + Name("HeightFogParamsBuffer"), + rdg::ComputeView{Name("heightFogCB"), rdg::ComputeType::CBV, stageFlags} + }); + + // Scene color input + computeResources.emplace_back(ComputeResource{ + fwdColor, + rdg::ComputeView{Name("InColor"), rdg::ComputeType::SRV, stageFlags} + }); + + // Scene depth input + computeResources.emplace_back(ComputeResource{ + fwdDepth, + rdg::ComputeView{Name("InDepth"), rdg::ComputeType::SRV, stageFlags} + }); + + // Samplers + samplers.emplace_back(SamplerResource{Name("PointSampler"), Name("InColorSampler")}); + samplers.emplace_back(SamplerResource{Name("PointSampler"), Name("InDepthSampler")}); + } + + void HeightFogPass::Setup(rdg::RenderGraph &rdg, RenderScene &scene) + { + FullScreenPass::Setup(rdg, scene); + } + +} // namespace sky diff --git a/engine/render/adaptor/src/components/HeightFogComponent.cpp b/engine/render/adaptor/src/components/HeightFogComponent.cpp new file mode 100644 index 00000000..d3f34e55 --- /dev/null +++ b/engine/render/adaptor/src/components/HeightFogComponent.cpp @@ -0,0 +1,108 @@ +// +// Created by SkyEngine on 2025/3/7. +// + +#include +#include +#include +#include + +namespace sky { + + void HeightFogComponent::Reflect(SerializationContext *context) + { + context->Register("HeightFogData") + .Member<&HeightFogData::fogColor>("FogColor") + .Member<&HeightFogData::inscatterColor>("InscatterColor") + .Member<&HeightFogData::fogDensity>("FogDensity") + .Member<&HeightFogData::heightFalloff>("HeightFalloff") + .Member<&HeightFogData::baseHeight>("BaseHeight") + .Member<&HeightFogData::maxHeight>("MaxHeight") + .Member<&HeightFogData::startDistance>("StartDistance"); + + REGISTER_BEGIN(HeightFogComponent, context) + REGISTER_MEMBER(FogColor, SetFogColor, GetFogColor) + REGISTER_MEMBER(InscatterColor, SetInscatterColor, GetInscatterColor) + REGISTER_MEMBER(FogDensity, SetFogDensity, GetFogDensity) + REGISTER_MEMBER(HeightFalloff, SetHeightFalloff, GetHeightFalloff) + REGISTER_MEMBER(BaseHeight, SetBaseHeight, GetBaseHeight) + REGISTER_MEMBER(MaxHeight, SetMaxHeight, GetMaxHeight) + REGISTER_MEMBER(StartDistance, SetStartDistance, GetStartDistance); + } + + void HeightFogComponent::OnAttachToWorld() + { + fp = GetFeatureProcessor(actor); + if (fp != nullptr) { + fp->SetFogColor(Vector4(data.fogColor.r, data.fogColor.g, data.fogColor.b, 0.f)); + fp->SetInscatterColor(Vector4(data.inscatterColor.r, data.inscatterColor.g, data.inscatterColor.b, 0.f)); + fp->SetFogDensity(data.fogDensity); + fp->SetHeightFalloff(data.heightFalloff); + fp->SetBaseHeight(data.baseHeight); + fp->SetMaxHeight(data.maxHeight); + fp->SetStartDistance(data.startDistance); + } + } + + void HeightFogComponent::OnDetachFromWorld() + { + fp = nullptr; + } + + void HeightFogComponent::SetFogColor(const ColorRGB &color) + { + data.fogColor = color; + if (fp != nullptr) { + fp->SetFogColor(Vector4(color.r, color.g, color.b, 0.f)); + } + } + + void HeightFogComponent::SetInscatterColor(const ColorRGB &color) + { + data.inscatterColor = color; + if (fp != nullptr) { + fp->SetInscatterColor(Vector4(color.r, color.g, color.b, 0.f)); + } + } + + void HeightFogComponent::SetFogDensity(float density) + { + data.fogDensity = density; + if (fp != nullptr) { + fp->SetFogDensity(density); + } + } + + void HeightFogComponent::SetHeightFalloff(float falloff) + { + data.heightFalloff = falloff; + if (fp != nullptr) { + fp->SetHeightFalloff(falloff); + } + } + + void HeightFogComponent::SetBaseHeight(float height) + { + data.baseHeight = height; + if (fp != nullptr) { + fp->SetBaseHeight(height); + } + } + + void HeightFogComponent::SetMaxHeight(float height) + { + data.maxHeight = height; + if (fp != nullptr) { + fp->SetMaxHeight(height); + } + } + + void HeightFogComponent::SetStartDistance(float distance) + { + data.startDistance = distance; + if (fp != nullptr) { + fp->SetStartDistance(distance); + } + } + +} // namespace sky diff --git a/engine/render/adaptor/src/pipeline/DefaultForwardPipeline.cpp b/engine/render/adaptor/src/pipeline/DefaultForwardPipeline.cpp index cccfcfc7..d83a46d6 100644 --- a/engine/render/adaptor/src/pipeline/DefaultForwardPipeline.cpp +++ b/engine/render/adaptor/src/pipeline/DefaultForwardPipeline.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,7 @@ namespace sky { auto brdfTech = LoadGfxTech("techniques/brdf_lut.tech"); auto depthResolveTech = LoadGfxTech("techniques/depth_resolve.tech"); auto depthDownSampleTech = LoadGfxTech("techniques/depth_downsample.tech"); + auto heightFogTech = LoadGfxTech("techniques/height_fog.tech"); rhi::DescriptorSetLayout::Descriptor desc = {}; auto stageFlags = rhi::ShaderStageFlagBit::VS | rhi::ShaderStageFlagBit::FS | rhi::ShaderStageFlagBit::TAS | rhi::ShaderStageFlagBit::MS; @@ -86,6 +88,7 @@ namespace sky { brdfLut = std::make_unique(brdfTech); postProcess = std::make_unique(postTech); present = std::make_unique(output->GetSwapChain()); + heightFog = std::make_unique(heightFogTech); hiz = std::make_unique(depthResolveTech, depthDownSampleTech); @@ -173,14 +176,20 @@ namespace sky { forward->Resize(renderWidth, renderHeight); AddPass(forward.get()); -// AddPass(empty.get()); + // If height fog feature processor is active, add the fog pass and + // redirect the post-processing pass to read from the fog output. + auto *fogFP = GetFeatureProcessor(scene); + if (fogFP != nullptr) { + heightFog->Resize(renderWidth, renderHeight); + AddPass(heightFog.get()); + postProcess->SetColorInput(Name(HEIGHT_FOG_OUTPUT.data())); + } else { + postProcess->SetColorInput(Name(FWD_CL.data())); + } postProcess->Resize(renderWidth, renderHeight); AddPass(postProcess.get()); -// hiz->BuildHizPass(rdg, hizDepth, renderWidth, renderHeight); -// hiz->AddPass(*this); - AddPass(present.get()); } diff --git a/engine/render/adaptor/src/pipeline/PostProcessingPass.cpp b/engine/render/adaptor/src/pipeline/PostProcessingPass.cpp index d4e8cdf6..e5752e18 100644 --- a/engine/render/adaptor/src/pipeline/PostProcessingPass.cpp +++ b/engine/render/adaptor/src/pipeline/PostProcessingPass.cpp @@ -52,6 +52,13 @@ namespace sky { FullScreenPass::Setup(rdg, scene); } + void PostProcessingPass::SetColorInput(const Name &name) + { + auto stageFlags = rhi::ShaderStageFlagBit::VS | rhi::ShaderStageFlagBit::FS | rhi::ShaderStageFlagBit::TAS | rhi::ShaderStageFlagBit::MS; + // computeResources[0] is always the color input (set in constructor) + computeResources[0] = ComputeResource{name, rdg::ComputeView{Name("InColor"), rdg::ComputeType::SRV, stageFlags}}; + } + void PostProcessingPass::SetupSubPass(rdg::RasterSubPassBuilder& builder, RenderScene &scene) { static const Name ViewName("MainCamera"); diff --git a/engine/render/core/include/render/RenderBuiltinLayout.h b/engine/render/core/include/render/RenderBuiltinLayout.h index 75a2e07c..30f45069 100644 --- a/engine/render/core/include/render/RenderBuiltinLayout.h +++ b/engine/render/core/include/render/RenderBuiltinLayout.h @@ -12,6 +12,7 @@ namespace sky { Matrix4 view; Matrix4 viewProject; Matrix4 lastViewProject; + Matrix4 invViewProject; }; struct InstanceLocal { @@ -34,4 +35,17 @@ namespace sky { Vector4 viewport; }; + struct HeightFogParams { + Vector4 fogColor; // rgb: fog color, a: unused + Vector4 inscatterColor; // rgb: inscatter color (sun/sky color), a: unused + float fogDensity; // global fog density + float heightFalloff; // how quickly fog increases with lower altitude + float baseHeight; // height below which fog starts + float maxHeight; // height above which there is no fog + float startDistance; // distance before fog starts + float inscatterExponent; // exponent for directional inscattering + float padding0; + float padding1; + }; + } // namespace sky diff --git a/engine/render/core/include/render/atmosphere/HeightFogFeature.h b/engine/render/core/include/render/atmosphere/HeightFogFeature.h new file mode 100644 index 00000000..c6b2b350 --- /dev/null +++ b/engine/render/core/include/render/atmosphere/HeightFogFeature.h @@ -0,0 +1,45 @@ +// +// Created by SkyEngine on 2025/3/7. +// + +#pragma once + +#include +#include +#include +#include + +namespace sky { + + class HeightFogFeature : public Singleton { + public: + HeightFogFeature() = default; + ~HeightFogFeature() override = default; + + void Init(); + }; + + class HeightFogFeatureProcessor : public IFeatureProcessor { + public: + explicit HeightFogFeatureProcessor(RenderScene *scn); + ~HeightFogFeatureProcessor() override = default; + + void SetFogColor(const Vector4 &color); + void SetInscatterColor(const Vector4 &color); + void SetFogDensity(float density); + void SetHeightFalloff(float falloff); + void SetBaseHeight(float height); + void SetMaxHeight(float height); + void SetStartDistance(float distance); + + const HeightFogParams &GetParams() const { return params; } + + void Tick(float time) override {} + void Render(rdg::RenderGraph &rdg) override; + + private: + HeightFogParams params; + RDUniformBufferPtr ubo; + }; + +} // namespace sky diff --git a/engine/render/core/src/SceneView.cpp b/engine/render/core/src/SceneView.cpp index f702b7de..8152ec88 100644 --- a/engine/render/core/src/SceneView.cpp +++ b/engine/render/core/src/SceneView.cpp @@ -68,6 +68,7 @@ namespace sky { p[1][1] = RHI::Get()->GetDevice()->GetConstants().flipY && flipY ? -1.f : 1.f; viewInfo[i].viewProject = p * projects[i] * viewInfo[i].view; + viewInfo[i].invViewProject = viewInfo[i].viewProject.Inverse(); frustums[i] = CreateFrustumByViewProjectMatrix(viewInfo[i].viewProject); viewUbo->WriteT(i * sizeof(SceneViewInfo), viewInfo[i]); diff --git a/engine/render/core/src/atmosphere/HeightFogFeature.cpp b/engine/render/core/src/atmosphere/HeightFogFeature.cpp new file mode 100644 index 00000000..7499c025 --- /dev/null +++ b/engine/render/core/src/atmosphere/HeightFogFeature.cpp @@ -0,0 +1,82 @@ +// +// Created by SkyEngine on 2025/3/7. +// + +#include +#include +#include + +namespace sky { + + void HeightFogFeature::Init() + { + Renderer::Get()->RegisterRenderFeature(); + } + + HeightFogFeatureProcessor::HeightFogFeatureProcessor(RenderScene *scn) + : IFeatureProcessor(scn) + { + params.fogColor = Vector4(0.7f, 0.8f, 0.9f, 0.f); + params.inscatterColor = Vector4(0.9f, 0.85f, 0.7f, 0.f); + params.fogDensity = 0.02f; + params.heightFalloff = 0.15f; + params.baseHeight = 0.f; + params.maxHeight = 50.f; + params.startDistance = 0.f; + params.inscatterExponent = 8.f; + params.padding0 = 0.f; + params.padding1 = 0.f; + + ubo = new UniformBuffer(); + ubo->Init(sizeof(HeightFogParams)); + ubo->WriteT(0, params); + } + + void HeightFogFeatureProcessor::SetFogColor(const Vector4 &color) + { + params.fogColor = color; + ubo->WriteT(0, params); + } + + void HeightFogFeatureProcessor::SetInscatterColor(const Vector4 &color) + { + params.inscatterColor = color; + ubo->WriteT(0, params); + } + + void HeightFogFeatureProcessor::SetFogDensity(float density) + { + params.fogDensity = density; + ubo->WriteT(0, params); + } + + void HeightFogFeatureProcessor::SetHeightFalloff(float falloff) + { + params.heightFalloff = falloff; + ubo->WriteT(0, params); + } + + void HeightFogFeatureProcessor::SetBaseHeight(float height) + { + params.baseHeight = height; + ubo->WriteT(0, params); + } + + void HeightFogFeatureProcessor::SetMaxHeight(float height) + { + params.maxHeight = height; + ubo->WriteT(0, params); + } + + void HeightFogFeatureProcessor::SetStartDistance(float distance) + { + params.startDistance = distance; + ubo->WriteT(0, params); + } + + void HeightFogFeatureProcessor::Render(rdg::RenderGraph &rdg) + { + rdg.resourceGraph.ImportUBO(Name("HeightFogParamsBuffer"), ubo); + } + +} // namespace sky From 78cbcf8e4b6d44f5f1d46f349b2f7c54a517e343 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 13:01:39 +0000 Subject: [PATCH 3/4] Fix NDC Y-axis convention for cross-API depth reconstruction in height fog Co-authored-by: bluesky013 <35895395+bluesky013@users.noreply.github.com> --- assets/shaders/atmosphere/height_fog.hlsl | 6 ++++-- engine/render/core/include/render/RenderBuiltinLayout.h | 2 +- engine/render/core/src/atmosphere/HeightFogFeature.cpp | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/assets/shaders/atmosphere/height_fog.hlsl b/assets/shaders/atmosphere/height_fog.hlsl index 82ef0ef9..46049068 100644 --- a/assets/shaders/atmosphere/height_fog.hlsl +++ b/assets/shaders/atmosphere/height_fog.hlsl @@ -48,8 +48,8 @@ struct ViewInfo { float MaxHeight; // height above which there is no fog float StartDistance; // view distance before fog starts float InscatterExponent; // exponent for directional inscattering + float ClipYSign; // +1 Vulkan (NDC Y=-1 at top), -1 DX12 (NDC Y=+1 at top) float Pad0; - float Pad1; } [[vk::binding(2, 0)]] Texture2D InColor : register(t0, space0); @@ -61,7 +61,9 @@ struct ViewInfo { // Reconstruct world position from a depth value and screen UV. float3 ReconstructWorldPos(float2 uv, float depth) { - float2 ndc = uv * 2.0 - 1.0; + // ClipYSign handles API differences: +1 for Vulkan (NDC Y=-1 at top), + // -1 for DX12/Metal (NDC Y=+1 at top). + float2 ndc = float2(uv.x * 2.0 - 1.0, ClipYSign * (uv.y * 2.0 - 1.0)); float4 clipPos = float4(ndc.x, ndc.y, depth, 1.0); float4 worldPos = mul(View.InvViewProj, clipPos); return worldPos.xyz / worldPos.w; diff --git a/engine/render/core/include/render/RenderBuiltinLayout.h b/engine/render/core/include/render/RenderBuiltinLayout.h index 30f45069..561d201e 100644 --- a/engine/render/core/include/render/RenderBuiltinLayout.h +++ b/engine/render/core/include/render/RenderBuiltinLayout.h @@ -44,8 +44,8 @@ namespace sky { float maxHeight; // height above which there is no fog float startDistance; // distance before fog starts float inscatterExponent; // exponent for directional inscattering + float clipYSign; // +1 for Vulkan (Y=-1 at top), -1 for DX12 (Y=+1 at top) float padding0; - float padding1; }; } // namespace sky diff --git a/engine/render/core/src/atmosphere/HeightFogFeature.cpp b/engine/render/core/src/atmosphere/HeightFogFeature.cpp index 7499c025..ddf7e2b0 100644 --- a/engine/render/core/src/atmosphere/HeightFogFeature.cpp +++ b/engine/render/core/src/atmosphere/HeightFogFeature.cpp @@ -4,6 +4,7 @@ #include #include +#include #include namespace sky { @@ -24,8 +25,8 @@ namespace sky { params.maxHeight = 50.f; params.startDistance = 0.f; params.inscatterExponent = 8.f; + params.clipYSign = RHI::Get()->GetDevice()->GetConstants().flipY ? 1.f : -1.f; params.padding0 = 0.f; - params.padding1 = 0.f; ubo = new UniformBuffer(); ubo->Init(sizeof(HeightFogParams)); From 4e86b422639ed6f0524f6a70ec4af566b0da0f24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 13:03:18 +0000 Subject: [PATCH 4/4] Fix: reset fog density to 0 on detach, set invisible default density Co-authored-by: bluesky013 <35895395+bluesky013@users.noreply.github.com> --- engine/render/adaptor/src/components/HeightFogComponent.cpp | 3 +++ engine/render/core/src/atmosphere/HeightFogFeature.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/engine/render/adaptor/src/components/HeightFogComponent.cpp b/engine/render/adaptor/src/components/HeightFogComponent.cpp index d3f34e55..25e3f7cd 100644 --- a/engine/render/adaptor/src/components/HeightFogComponent.cpp +++ b/engine/render/adaptor/src/components/HeightFogComponent.cpp @@ -46,6 +46,9 @@ namespace sky { void HeightFogComponent::OnDetachFromWorld() { + if (fp != nullptr) { + fp->SetFogDensity(0.f); + } fp = nullptr; } diff --git a/engine/render/core/src/atmosphere/HeightFogFeature.cpp b/engine/render/core/src/atmosphere/HeightFogFeature.cpp index ddf7e2b0..8608e15c 100644 --- a/engine/render/core/src/atmosphere/HeightFogFeature.cpp +++ b/engine/render/core/src/atmosphere/HeightFogFeature.cpp @@ -19,7 +19,7 @@ namespace sky { { params.fogColor = Vector4(0.7f, 0.8f, 0.9f, 0.f); params.inscatterColor = Vector4(0.9f, 0.85f, 0.7f, 0.f); - params.fogDensity = 0.02f; + params.fogDensity = 0.0f; // disabled by default; set by HeightFogComponent params.heightFalloff = 0.15f; params.baseHeight = 0.f; params.maxHeight = 50.f;