diff --git a/.gitignore b/.gitignore index 9b6fa6fe..0377b382 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ /test/assets/assets.db /test/products/ /python/.idea +*.log diff --git a/assets/shaders/post_processing/tsaa.hlsl b/assets/shaders/post_processing/tsaa.hlsl new file mode 100644 index 00000000..5fbcb567 --- /dev/null +++ b/assets/shaders/post_processing/tsaa.hlsl @@ -0,0 +1,226 @@ +// TSAA (Temporal Specular Anti-Aliasing) Shader +// +// Performs temporal anti-aliasing by reprojecting the previous frame's result +// using motion vectors derived from depth and view-projection matrices. +// Uses variance-based neighborhood clamping (Karis 2014) to reduce ghosting, +// and Reinhard-based tonemap weighting to suppress firefly artifacts. + +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; +} + +// ---- Resource bindings ---- + +struct ViewInfo { + float4x4 World; + float4x4 View; + float4x4 ViewProj; + float4x4 LastViewProj; +}; + +[[vk::binding(0, 0)]] Texture2D InColor : register(t0, space0); +[[vk::binding(1, 0)]] SamplerState InColorSampler : register(s0, space0); +[[vk::binding(2, 0)]] Texture2D HistoryColor : register(t1, space0); +[[vk::binding(3, 0)]] SamplerState HistorySampler : register(s1, space0); +[[vk::binding(4, 0)]] Texture2D DepthTex : register(t2, space0); +[[vk::binding(5, 0)]] SamplerState DepthSampler : register(s2, space0); +[[vk::binding(6, 0)]] cbuffer view : register(b0, space0) +{ + ViewInfo View; +} +[[vk::binding(7, 0)]] cbuffer global : register(b1, space0) +{ + float4x4 LightMatrix; + float4 MainLightColor; + float4 MainLightDirection; + float4 Viewport; + float4 Jitter; // xy: jitter in UV space, zw: reserved +} + +// ---- Constants ---- + +static const float TSAA_BLEND_MIN = 0.05; +static const float TSAA_BLEND_MAX = 0.12; +static const float TSAA_VELOCITY_WEIGHT = 20.0; + +// ---- Utility functions ---- + +float Luminance(float3 color) +{ + return dot(color, float3(0.2126, 0.7152, 0.0722)); +} + +float3 TonemapColor(float3 c) +{ + return c / (1.0 + Luminance(c)); +} + +float3 UntonemapColor(float3 c) +{ + return c / max(1.0 - Luminance(c), 1e-5); +} + +// 4x4 matrix inverse using cofactor expansion +float4x4 InverseMatrix(float4x4 m) +{ + float n11 = m[0][0], n12 = m[0][1], n13 = m[0][2], n14 = m[0][3]; + float n21 = m[1][0], n22 = m[1][1], n23 = m[1][2], n24 = m[1][3]; + float n31 = m[2][0], n32 = m[2][1], n33 = m[2][2], n34 = m[2][3]; + float n41 = m[3][0], n42 = m[3][1], n43 = m[3][2], n44 = m[3][3]; + + float t11 = n23*n34*n42 - n24*n33*n42 + n24*n32*n43 - n22*n34*n43 - n23*n32*n44 + n22*n33*n44; + float t12 = n14*n33*n42 - n13*n34*n42 - n14*n32*n43 + n12*n34*n43 + n13*n32*n44 - n12*n33*n44; + float t13 = n13*n24*n42 - n14*n23*n42 + n14*n22*n43 - n12*n24*n43 - n13*n22*n44 + n12*n23*n44; + float t14 = n14*n23*n32 - n13*n24*n32 - n14*n22*n33 + n12*n24*n33 + n13*n22*n34 - n12*n23*n34; + + float det = n11*t11 + n21*t12 + n31*t13 + n41*t14; + float idet = 1.0 / det; + + float4x4 ret; + ret[0][0] = t11 * idet; + ret[0][1] = t12 * idet; + ret[0][2] = t13 * idet; + ret[0][3] = t14 * idet; + ret[1][0] = (n24*n33*n41 - n23*n34*n41 - n24*n31*n43 + n21*n34*n43 + n23*n31*n44 - n21*n33*n44) * idet; + ret[1][1] = (n13*n34*n41 - n14*n33*n41 + n14*n31*n43 - n11*n34*n43 - n13*n31*n44 + n11*n33*n44) * idet; + ret[1][2] = (n14*n23*n41 - n13*n24*n41 - n14*n21*n43 + n11*n24*n43 + n13*n21*n44 - n11*n23*n44) * idet; + ret[1][3] = (n13*n24*n31 - n14*n23*n31 + n14*n21*n33 - n11*n24*n33 - n13*n21*n34 + n11*n23*n34) * idet; + ret[2][0] = (n22*n34*n41 - n24*n32*n41 + n24*n31*n42 - n21*n34*n42 - n22*n31*n44 + n21*n32*n44) * idet; + ret[2][1] = (n14*n32*n41 - n12*n34*n41 - n14*n31*n42 + n11*n34*n42 + n12*n31*n44 - n11*n32*n44) * idet; + ret[2][2] = (n12*n24*n41 - n14*n22*n41 + n14*n21*n42 - n11*n24*n42 - n12*n21*n44 + n11*n22*n44) * idet; + ret[2][3] = (n14*n22*n31 - n12*n24*n31 - n14*n21*n32 + n11*n24*n32 + n12*n21*n34 - n11*n22*n34) * idet; + ret[3][0] = (n23*n32*n41 - n22*n33*n41 - n23*n31*n42 + n21*n33*n42 + n22*n31*n43 - n21*n32*n43) * idet; + ret[3][1] = (n12*n33*n41 - n13*n32*n41 + n13*n31*n42 - n11*n33*n42 - n12*n31*n43 + n11*n32*n43) * idet; + ret[3][2] = (n13*n22*n41 - n12*n23*n41 - n13*n21*n42 + n11*n23*n42 + n12*n21*n43 - n11*n22*n43) * idet; + ret[3][3] = (n12*n23*n31 - n13*n22*n31 + n13*n21*n32 - n11*n23*n32 - n12*n21*n33 + n11*n22*n33) * idet; + return ret; +} + +// Find the closest depth in a 3x3 neighborhood (dilated motion vectors) +float2 GetClosestUV(float2 uv, float2 texelSize) +{ + float closestDepth = 0.0; + float2 closestUV = uv; + + [unroll] + for (int y = -1; y <= 1; y++) + { + [unroll] + for (int x = -1; x <= 1; x++) + { + float2 sampleUV = uv + float2(x, y) * texelSize; + float d = DepthTex.SampleLevel(DepthSampler, sampleUV, 0).r; + if (d > closestDepth) + { + closestDepth = d; + closestUV = sampleUV; + } + } + } + + return closestUV; +} + +// ---- Main fragment shader ---- + +float4 FSMain(VSOutput input) : SV_TARGET +{ + float2 uv = input.UV; + float2 screenSize = Viewport.zw; + float2 texelSize = 1.0 / screenSize; + + // 1. Sample current frame color + float3 currentColor = InColor.SampleLevel(InColorSampler, uv, 0).rgb; + + // 2. Find closest depth in 3x3 for dilated motion vectors (reduces edge artifacts) + float2 closestUV = GetClosestUV(uv, texelSize); + float depth = DepthTex.SampleLevel(DepthSampler, closestUV, 0).r; + + // 3. Compute motion vector: current clip -> world -> previous clip + float4x4 invViewProj = InverseMatrix(View.ViewProj); + + float2 ndc = closestUV * 2.0 - 1.0; + ndc.y = -ndc.y; + + float4 worldPos = mul(invViewProj, float4(ndc, depth, 1.0)); + worldPos /= worldPos.w; + + float4 prevClip = mul(View.LastViewProj, worldPos); + float2 prevNDC = prevClip.xy / prevClip.w; + float2 prevUV = float2(prevNDC.x, -prevNDC.y) * 0.5 + 0.5; + + float2 velocity = prevUV - closestUV; + + // 4. Sample history at reprojected position + float2 historyUV = uv + velocity; + float3 historyColor = HistoryColor.SampleLevel(HistorySampler, historyUV, 0).rgb; + + // 5. Neighborhood clamping with variance clip (Karis 2014) + float3 m1 = float3(0.0, 0.0, 0.0); + float3 m2 = float3(0.0, 0.0, 0.0); + float3 neighborMin = float3(1e10, 1e10, 1e10); + float3 neighborMax = float3(-1e10, -1e10, -1e10); + + static const float2 offsets[9] = { + float2(-1, -1), float2( 0, -1), float2( 1, -1), + float2(-1, 0), float2( 0, 0), float2( 1, 0), + float2(-1, 1), float2( 0, 1), float2( 1, 1) + }; + + [unroll] + for (int i = 0; i < 9; i++) + { + float3 s = TonemapColor(InColor.SampleLevel(InColorSampler, uv + offsets[i] * texelSize, 0).rgb); + m1 += s; + m2 += s * s; + neighborMin = min(neighborMin, s); + neighborMax = max(neighborMax, s); + } + + float3 mu = m1 / 9.0; + float3 sigma = sqrt(abs(m2 / 9.0 - mu * mu)); + float3 boxMin = max(mu - 1.25 * sigma, neighborMin); + float3 boxMax = min(mu + 1.25 * sigma, neighborMax); + + // Clip history to AABB in tonemapped space + float3 historyTM = TonemapColor(historyColor); + float3 clippedHistory = clamp(historyTM, boxMin, boxMax); + historyColor = UntonemapColor(clippedHistory); + + // 6. Adaptive blend factor based on velocity + float velocityMag = length(velocity * screenSize); + float blendFactor = lerp(TSAA_BLEND_MIN, TSAA_BLEND_MAX, saturate(velocityMag * TSAA_VELOCITY_WEIGHT)); + + // Reject history for out-of-screen reprojection + if (any(historyUV < 0.0) || any(historyUV > 1.0)) + { + blendFactor = 1.0; + } + + // 7. Final temporal blend: result = lerp(history, current, alpha) + float3 result = lerp(historyColor, currentColor, blendFactor); + + return float4(result, 1.0); +} diff --git a/assets/techniques/tsaa.tech b/assets/techniques/tsaa.tech new file mode 100644 index 00000000..fe0bda17 --- /dev/null +++ b/assets/techniques/tsaa.tech @@ -0,0 +1,18 @@ +{ + "type": "graphics", + "shader": { + "path": "post_processing/tsaa.hlsl", + "vertex": "VSMain", + "fragment": "FSMain" + }, + "pass": { + "tag": "TSAA" + }, + "depth_stencil": { + "depthTestEnable": false, + "depthWriteEnable": false + }, + "raster_state": { + "cullMode": "NONE" + } +} diff --git a/configs/render_preload_assets.json b/configs/render_preload_assets.json index d21a76fa..cfc250c8 100644 --- a/configs/render_preload_assets.json +++ b/configs/render_preload_assets.json @@ -9,6 +9,7 @@ "techniques/debug.tech", "techniques/skybox.tech", "techniques/meshlet_debug.tech", - "techniques/brdf_lut.tech" + "techniques/brdf_lut.tech", + "techniques/tsaa.tech" ] } \ No newline at end of file diff --git a/engine/render/adaptor/include/render/adaptor/pipeline/DefaultForwardPipeline.h b/engine/render/adaptor/include/render/adaptor/pipeline/DefaultForwardPipeline.h index 16ff4b52..f3b4a377 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,10 +46,13 @@ namespace sky { std::unique_ptr hiz; std::unique_ptr shadowMap; std::unique_ptr forward; + std::unique_ptr tsaa; std::unique_ptr postProcess; std::unique_ptr present; std::unique_ptr brdfLut; std::unique_ptr empty; + + uint32_t frameIndex = 0; }; } // namespace sky diff --git a/engine/render/adaptor/include/render/adaptor/pipeline/DefaultPassConstants.h b/engine/render/adaptor/include/render/adaptor/pipeline/DefaultPassConstants.h index 2b1db4b4..6deb0485 100644 --- a/engine/render/adaptor/include/render/adaptor/pipeline/DefaultPassConstants.h +++ b/engine/render/adaptor/include/render/adaptor/pipeline/DefaultPassConstants.h @@ -18,6 +18,9 @@ namespace sky { static constexpr std::string_view FWD_DS_RESOLVE = "ForwardDepthResolve"; + static constexpr std::string_view TSAA_CL = "TSAAColor"; + static constexpr std::string_view TSAA_HIST = "TSAAHistory"; + static constexpr std::string_view SWAP_CHAIN = "SwapChain"; } // namespace sky \ No newline at end of file diff --git a/engine/render/adaptor/include/render/adaptor/pipeline/PostProcessingPass.h b/engine/render/adaptor/include/render/adaptor/pipeline/PostProcessingPass.h index c772d2c3..1240e5f2 100644 --- a/engine/render/adaptor/include/render/adaptor/pipeline/PostProcessingPass.h +++ b/engine/render/adaptor/include/render/adaptor/pipeline/PostProcessingPass.h @@ -11,7 +11,7 @@ namespace sky { class PostProcessingPass : public FullScreenPass { public: - explicit PostProcessingPass(const RDGfxTechPtr &tech); + PostProcessingPass(const RDGfxTechPtr &tech, std::string_view inputColorName = FWD_CL); ~PostProcessingPass() override = default; void Setup(rdg::RenderGraph &rdg, RenderScene &scene) override; diff --git a/engine/render/adaptor/include/render/adaptor/pipeline/TSAAPass.h b/engine/render/adaptor/include/render/adaptor/pipeline/TSAAPass.h new file mode 100644 index 00000000..839aac8a --- /dev/null +++ b/engine/render/adaptor/include/render/adaptor/pipeline/TSAAPass.h @@ -0,0 +1,31 @@ +// +// Created by Copilot on 2026/3/7. +// + +#pragma once + +#include +#include + +namespace sky { + + class TSAAPass : public FullScreenPass { + public: + TSAAPass(const RDGfxTechPtr &tech, rhi::PixelFormat colorFormat); + ~TSAAPass() override = default; + + void Setup(rdg::RenderGraph &rdg, RenderScene &scene) override; + void SetupSubPass(rdg::RasterSubPassBuilder& builder, RenderScene &scene) override; + + private: + Name tsaaColorName; + Name tsaaHistoryName; + + rhi::ImagePtr historyImages[2]; + uint32_t writeIndex = 0; + bool firstFrame = true; + + rhi::PixelFormat format; + }; + +} // namespace sky diff --git a/engine/render/adaptor/src/pipeline/DefaultForwardPipeline.cpp b/engine/render/adaptor/src/pipeline/DefaultForwardPipeline.cpp index cccfcfc7..7fb0c57e 100644 --- a/engine/render/adaptor/src/pipeline/DefaultForwardPipeline.cpp +++ b/engine/render/adaptor/src/pipeline/DefaultForwardPipeline.cpp @@ -11,10 +11,25 @@ #include #include #include +#include #include +#include namespace sky { + static float Halton(uint32_t index, uint32_t base) + { + float result = 0.f; + float f = 1.f / static_cast(base); + uint32_t i = index; + while (i > 0) { + result += f * static_cast(i % base); + i /= base; + f /= static_cast(base); + } + return result; + } + RDTechniquePtr LoadGfxTech(const std::string& name) { auto asset = AssetManager::Get()->LoadAssetFromPath(name); @@ -28,6 +43,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 tsaaTech = LoadGfxTech("techniques/tsaa.tech"); rhi::DescriptorSetLayout::Descriptor desc = {}; auto stageFlags = rhi::ShaderStageFlagBit::VS | rhi::ShaderStageFlagBit::FS | rhi::ShaderStageFlagBit::TAS | rhi::ShaderStageFlagBit::MS; @@ -84,7 +100,8 @@ namespace sky { shadowMap->SetLayout(defaultRasterLayout); brdfLut = std::make_unique(brdfTech); - postProcess = std::make_unique(postTech); + tsaa = std::make_unique(tsaaTech, rhi::PixelFormat::RGBA16_SFLOAT); + postProcess = std::make_unique(postTech, TSAA_CL); present = std::make_unique(output->GetSwapChain()); hiz = std::make_unique(depthResolveTech, depthDownSampleTech); @@ -143,13 +160,31 @@ namespace sky { info.viewport.w = (float)h; } } + + // Apply Halton jitter for TSAA + // Uses a 16-sample Halton(2,3) sequence cycling every 16 frames. + // Index starts at 1 because Halton(0, base) = 0, which provides no jitter. + Name mainCameraName = Name("MainCamera"); + auto *sceneView = scene->GetSceneView(mainCameraName); + if (sceneView != nullptr && w > 0 && h > 0) { + uint32_t jitterIdx = (frameIndex % 16) + 1; + float jitterX = Halton(jitterIdx, 2) - 0.5f; + float jitterY = Halton(jitterIdx, 3) - 0.5f; + + float clipJitterX = jitterX * 2.0f / static_cast(w); + float clipJitterY = jitterY * 2.0f / static_cast(h); + sceneView->SetJitter(clipJitterX, clipJitterY); + + info.jitter.x = jitterX / static_cast(w); + info.jitter.y = jitterY / static_cast(h); + } + frameIndex++; + defaultGlobal->WriteT(0, info); Name colorViewName = Name("SCENE_VIEW"); Name fwdPassInfoName = Name("FWD_PassInfo"); - Name mainCameraName = Name("MainCamera"); - auto *sceneView = scene->GetSceneView(mainCameraName); auto &rg = rdg.resourceGraph; rg.ImportUBO(colorViewName, sceneView->GetUBO()); rg.ImportUBO(fwdPassInfoName, defaultGlobal); @@ -175,6 +210,9 @@ namespace sky { // AddPass(empty.get()); + tsaa->Resize(renderWidth, renderHeight); + AddPass(tsaa.get()); + postProcess->Resize(renderWidth, renderHeight); AddPass(postProcess.get()); diff --git a/engine/render/adaptor/src/pipeline/PostProcessingPass.cpp b/engine/render/adaptor/src/pipeline/PostProcessingPass.cpp index d4e8cdf6..735720e4 100644 --- a/engine/render/adaptor/src/pipeline/PostProcessingPass.cpp +++ b/engine/render/adaptor/src/pipeline/PostProcessingPass.cpp @@ -10,11 +10,11 @@ namespace sky { - PostProcessingPass::PostProcessingPass(const RDGfxTechPtr &tech) + PostProcessingPass::PostProcessingPass(const RDGfxTechPtr &tech, std::string_view inputColorName) : FullScreenPass(Name("PostProcessingPass"), tech) { Name swapChainName(SWAP_CHAIN.data()); - Name fwdColor(FWD_CL.data()); + Name inputColor(inputColorName.data()); auto stageFlags = rhi::ShaderStageFlagBit::VS | rhi::ShaderStageFlagBit::FS | rhi::ShaderStageFlagBit::TAS | rhi::ShaderStageFlagBit::MS; colors.emplace_back(Attachment{ @@ -23,7 +23,7 @@ namespace sky { }); computeResources.emplace_back(ComputeResource{ - fwdColor, + inputColor, rdg::ComputeView{Name("InColor"), rdg::ComputeType::SRV, stageFlags} }); diff --git a/engine/render/adaptor/src/pipeline/TSAAPass.cpp b/engine/render/adaptor/src/pipeline/TSAAPass.cpp new file mode 100644 index 00000000..14a763c5 --- /dev/null +++ b/engine/render/adaptor/src/pipeline/TSAAPass.cpp @@ -0,0 +1,111 @@ +// +// Created by Copilot on 2026/3/7. +// + +#include +#include +#include +#include +#include + +namespace sky { + + TSAAPass::TSAAPass(const RDGfxTechPtr &tech, rhi::PixelFormat colorFormat) + : FullScreenPass(Name("TSAAPass"), tech) + , tsaaColorName(TSAA_CL.data()) + , tsaaHistoryName(TSAA_HIST.data()) + , format(colorFormat) + { + auto stageFlags = rhi::ShaderStageFlagBit::VS | rhi::ShaderStageFlagBit::FS; + + colors.emplace_back(Attachment{ + rdg::RasterAttachment{tsaaColorName, rhi::LoadOp::DONT_CARE, rhi::StoreOp::STORE}, + rhi::ClearValue(0.f, 0.f, 0.f, 0.f) + }); + + computeResources.emplace_back(ComputeResource{ + Name(FWD_CL.data()), + rdg::ComputeView{Name("InColor"), rdg::ComputeType::SRV, stageFlags} + }); + + computeResources.emplace_back(ComputeResource{ + tsaaHistoryName, + rdg::ComputeView{Name("HistoryColor"), rdg::ComputeType::SRV, stageFlags} + }); + + computeResources.emplace_back(ComputeResource{ + Name(FWD_DS.data()), + rdg::ComputeView{Name("DepthTex"), rdg::ComputeType::SRV, stageFlags} + }); + + computeResources.emplace_back(ComputeResource{ + Name("SCENE_VIEW"), + rdg::ComputeView{Name("viewInfo"), rdg::ComputeType::CBV, stageFlags} + }); + + computeResources.emplace_back(ComputeResource{ + Name("FWD_PassInfo"), + rdg::ComputeView{Name("passInfo"), rdg::ComputeType::CBV, stageFlags} + }); + } + + void TSAAPass::Setup(rdg::RenderGraph &rdg, RenderScene &scene) + { + auto &rsg = rdg.resourceGraph; + uint32_t readIndex = 1 - writeIndex; + + // Create persistent history images on first frame + if (firstFrame) { + rhi::Image::Descriptor desc = {}; + desc.imageType = rhi::ImageType::IMAGE_2D; + desc.format = format; + desc.extent = {static_cast(width), static_cast(height), 1}; + desc.mipLevels = 1; + desc.arrayLayers = 1; + desc.samples = rhi::SampleCount::X1; + desc.usage = rhi::ImageUsageFlagBit::RENDER_TARGET | rhi::ImageUsageFlagBit::SAMPLED; + desc.memory = rhi::MemoryType::GPU_ONLY; + + historyImages[0] = RHI::Get()->GetDevice()->CreateImage(desc); + historyImages[1] = RHI::Get()->GetDevice()->CreateImage(desc); + } + + // Recreate if resolution changed + if (!firstFrame && historyImages[0] && + (historyImages[0]->GetDescriptor().extent.width != width || + historyImages[0]->GetDescriptor().extent.height != height)) { + rhi::Image::Descriptor desc = {}; + desc.imageType = rhi::ImageType::IMAGE_2D; + desc.format = format; + desc.extent = {static_cast(width), static_cast(height), 1}; + desc.mipLevels = 1; + desc.arrayLayers = 1; + desc.samples = rhi::SampleCount::X1; + desc.usage = rhi::ImageUsageFlagBit::RENDER_TARGET | rhi::ImageUsageFlagBit::SAMPLED; + desc.memory = rhi::MemoryType::GPU_ONLY; + + historyImages[0] = RHI::Get()->GetDevice()->CreateImage(desc); + historyImages[1] = RHI::Get()->GetDevice()->CreateImage(desc); + firstFrame = true; + } + + // Import write target as render target (TSAAColor) + rsg.ImportImage(tsaaColorName, historyImages[writeIndex], rhi::ImageViewType::VIEW_2D, + firstFrame ? rhi::AccessFlagBit::NONE : rhi::AccessFlagBit::COLOR_WRITE); + + // Import read target as SRV (TSAAHistory) + rsg.ImportImage(tsaaHistoryName, historyImages[readIndex], rhi::ImageViewType::VIEW_2D, + firstFrame ? rhi::AccessFlagBit::NONE : rhi::AccessFlagBit::FRAGMENT_SRV); + + FullScreenPass::Setup(rdg, scene); + + // Swap ping-pong index for next frame + writeIndex = readIndex; + firstFrame = false; + } + + void TSAAPass::SetupSubPass(rdg::RasterSubPassBuilder& builder, RenderScene &scene) + { + } + +} // namespace sky diff --git a/engine/render/core/include/render/RenderBuiltinLayout.h b/engine/render/core/include/render/RenderBuiltinLayout.h index 75a2e07c..73ef39ee 100644 --- a/engine/render/core/include/render/RenderBuiltinLayout.h +++ b/engine/render/core/include/render/RenderBuiltinLayout.h @@ -32,6 +32,7 @@ namespace sky { Vector3 mainLightDirection; float padding; Vector4 viewport; + Vector4 jitter; // xy: jitter in UV space, zw: reserved }; } // namespace sky diff --git a/engine/render/core/include/render/SceneView.h b/engine/render/core/include/render/SceneView.h index adf17121..04fe7b84 100644 --- a/engine/render/core/include/render/SceneView.h +++ b/engine/render/core/include/render/SceneView.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -23,6 +24,7 @@ namespace sky { void SetPerspective(float near, float far, float fov, float aspect, uint32_t index = 0); void SetOrthogonal(float l, float r, float t, float b, float near, float far, uint32_t index = 0); void SetFlipY(bool flip) { flipY = flip; } + void SetJitter(float jitterX, float jitterY); void Update(); const Matrix4 &GetWorld() const { return viewInfo[0].world; } @@ -59,6 +61,8 @@ namespace sky { bool dirty; bool flipY = true; + Vector2 jitter; + RDUniformBufferPtr viewUbo; }; diff --git a/engine/render/core/src/SceneView.cpp b/engine/render/core/src/SceneView.cpp index f702b7de..b1a3975e 100644 --- a/engine/render/core/src/SceneView.cpp +++ b/engine/render/core/src/SceneView.cpp @@ -53,6 +53,12 @@ namespace sky { dirty = true; } + void SceneView::SetJitter(float jitterX, float jitterY) + { + jitter = Vector2(jitterX, jitterY); + dirty = true; + } + void SceneView::Update() { if (!dirty) { @@ -67,7 +73,11 @@ namespace sky { Matrix4 p = Matrix4::Identity(); p[1][1] = RHI::Get()->GetDevice()->GetConstants().flipY && flipY ? -1.f : 1.f; - viewInfo[i].viewProject = p * projects[i] * viewInfo[i].view; + Matrix4 proj = projects[i]; + proj[2][0] += jitter.x; + proj[2][1] += jitter.y; + + viewInfo[i].viewProject = p * proj * viewInfo[i].view; frustums[i] = CreateFrustumByViewProjectMatrix(viewInfo[i].viewProject); viewUbo->WriteT(i * sizeof(SceneViewInfo), viewInfo[i]);