Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions assets/shaders/atmosphere/height_fog.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@

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 ClipYSign; // +1 Vulkan (NDC Y=-1 at top), -1 DX12 (NDC Y=+1 at top)
float Pad0;
}

[[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)
{
// 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;
}

// 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);
}
1 change: 1 addition & 0 deletions assets/shaders/layout/default_global_view.hlslh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ struct ViewInfo {
float4x4 View;
float4x4 ViewProj;
float4x4 LastViewProj;
float4x4 InvViewProj;
};

[[vk::binding(0, 0)]] cbuffer global : register(b0, space0)
Expand Down
18 changes: 18 additions & 0 deletions assets/techniques/height_fog.tech
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// Created by SkyEngine on 2025/3/7.
//

#pragma once

#include <render/renderpass/RasterPass.h>
#include <render/adaptor/pipeline/DefaultPassConstants.h>

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// Created by SkyEngine on 2025/3/7.
//

#pragma once

#include <core/math/Color.h>
#include <framework/world/Component.h>
#include <render/atmosphere/HeightFogFeature.h>

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<HeightFogData> {
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <render/adaptor/pipeline/BRDFLutPass.h>
#include <render/adaptor/pipeline/ShadowMapPass.h>
#include <render/adaptor/pipeline/EmptyPass.h>
#include <render/adaptor/atmosphere/HeightFogPass.h>
#include <memory>

namespace sky {
Expand Down Expand Up @@ -45,6 +46,7 @@ namespace sky {
std::unique_ptr<HizGenerator> hiz;
std::unique_ptr<ShadowMapPass> shadowMap;
std::unique_ptr<ForwardMSAAPass> forward;
std::unique_ptr<HeightFogPass> heightFog;
std::unique_ptr<PostProcessingPass> postProcess;
std::unique_ptr<PresentPass> present;
std::unique_ptr<BRDFLutPass> brdfLut;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down
6 changes: 6 additions & 0 deletions engine/render/adaptor/src/RenderModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <render/adaptor/components/SkeletalMeshComponent.h>
#include <render/adaptor/components/SkyBoxComponent.h>
#include <render/adaptor/components/StaticMeshComponent.h>
#include <render/adaptor/components/HeightFogComponent.h>

#include <render/adaptor/animation/SkeletonDisplayComponent.h>
#include <render/adaptor/animation/AnimationPreviewComponent.h>
Expand All @@ -31,6 +32,7 @@
#include <render/env/EnvFeature.h>
#include <render/mesh/MeshFeature.h>
#include <render/text/TextFeature.h>
#include <render/atmosphere/HeightFogFeature.h>
#include <imgui/ImGuiFeature.h>

#include <render/RHI.h>
Expand All @@ -50,6 +52,7 @@ namespace sky {
SkeletalMeshComponent::Reflect(context);
SkyBoxComponent::Reflect(context);
PrefabComponent::Reflect(context);
HeightFogComponent::Reflect(context);

SkeletonDisplayComponent::Reflect(context);
AnimationPreviewComponent::Reflect(context);
Expand All @@ -63,6 +66,7 @@ namespace sky {
ComponentFactory::Get()->RegisterComponent<CameraComponent>(GROUP);
ComponentFactory::Get()->RegisterComponent<SkyBoxComponent>(GROUP);
ComponentFactory::Get()->RegisterComponent<PrefabComponent>(GROUP);
ComponentFactory::Get()->RegisterComponent<HeightFogComponent>(GROUP);
}

{
Expand Down Expand Up @@ -146,6 +150,7 @@ namespace sky {
TextFeature::Get()->Init();
LightFeature::Get()->Init();
EnvFeature::Get()->Init();
HeightFogFeature::Get()->Init();

auto *am = AssetManager::Get();
{
Expand Down Expand Up @@ -175,6 +180,7 @@ namespace sky {
ImGuiFeature::Destroy();
TextFeature::Destroy();
EnvFeature::Destroy();
HeightFogFeature::Destroy();

Renderer::Destroy();
RHI::Destroy();
Expand Down
68 changes: 68 additions & 0 deletions engine/render/adaptor/src/atmosphere/HeightFogPass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// Created by SkyEngine on 2025/3/7.
//

#include <render/adaptor/atmosphere/HeightFogPass.h>
#include <render/adaptor/pipeline/DefaultPassConstants.h>
#include <render/RHI.h>
#include <render/rdg/RenderGraph.h>

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
Loading