ADD: SSR
This commit is contained in:
@@ -11,10 +11,10 @@ Current structure:
|
|||||||
- Supports texture compression(BCn, non glTF standard), LRU reload
|
- Supports texture compression(BCn, non glTF standard), LRU reload
|
||||||
- Object clicking, generation.
|
- Object clicking, generation.
|
||||||
- Multi light system
|
- Multi light system
|
||||||
|
- SSR
|
||||||
|
|
||||||
Work-In-Progress
|
Work-In-Progress
|
||||||
- [ ] TAA
|
- [ ] AA
|
||||||
- [ ] SSR
|
|
||||||
- [ ] bloom
|
- [ ] bloom
|
||||||
- [ ] Planet Rendering
|
- [ ] Planet Rendering
|
||||||
|
|
||||||
|
|||||||
150
shaders/ssr.frag
Normal file
150
shaders/ssr.frag
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
#version 460
|
||||||
|
#extension GL_GOOGLE_include_directive : require
|
||||||
|
|
||||||
|
#include "input_structures.glsl"
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 inUV;
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
// Set 0: scene data (see input_structures.glsl)
|
||||||
|
|
||||||
|
// Set 1: SSR inputs
|
||||||
|
layout(set = 1, binding = 0) uniform sampler2D hdrColor;
|
||||||
|
layout(set = 1, binding = 1) uniform sampler2D posTex;
|
||||||
|
layout(set = 1, binding = 2) uniform sampler2D normalTex;
|
||||||
|
layout(set = 1, binding = 3) uniform sampler2D albedoTex;
|
||||||
|
|
||||||
|
vec3 getCameraWorldPosition()
|
||||||
|
{
|
||||||
|
mat4 invView = inverse(sceneData.view);
|
||||||
|
return vec3(invView[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 projectToScreen(vec3 worldPos)
|
||||||
|
{
|
||||||
|
vec4 clip = sceneData.viewproj * vec4(worldPos, 1.0);
|
||||||
|
if (clip.w <= 0.0)
|
||||||
|
{
|
||||||
|
return vec3(0.0, 0.0, -1.0);
|
||||||
|
}
|
||||||
|
vec3 ndc = clip.xyz / clip.w;
|
||||||
|
if (ndc.x < -1.0 || ndc.x > 1.0 ||
|
||||||
|
ndc.y < -1.0 || ndc.y > 1.0 ||
|
||||||
|
ndc.z < 0.0 || ndc.z > 1.0)
|
||||||
|
{
|
||||||
|
return vec3(0.0, 0.0, -1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 uv = ndc.xy * 0.5 + 0.5;
|
||||||
|
return vec3(uv, ndc.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec3 baseColor = texture(hdrColor, inUV).rgb;
|
||||||
|
|
||||||
|
vec4 posSample = texture(posTex, inUV);
|
||||||
|
if (posSample.w == 0.0)
|
||||||
|
{
|
||||||
|
outColor = vec4(baseColor, 1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 worldPos = posSample.xyz;
|
||||||
|
|
||||||
|
vec4 normSample = texture(normalTex, inUV);
|
||||||
|
vec3 N = normalize(normSample.xyz);
|
||||||
|
float roughness = clamp(normSample.w, 0.04, 1.0);
|
||||||
|
|
||||||
|
vec4 albSample = texture(albedoTex, inUV);
|
||||||
|
vec3 albedo = albSample.rgb;
|
||||||
|
float metallic = clamp(albSample.a, 0.0, 1.0);
|
||||||
|
|
||||||
|
vec3 camPos = getCameraWorldPosition();
|
||||||
|
vec3 V = normalize(camPos - worldPos);
|
||||||
|
vec3 R = reflect(-V, N);
|
||||||
|
|
||||||
|
float gloss = 1.0 - roughness;
|
||||||
|
float reflectivity = gloss * mix(0.04, 1.0, metallic);
|
||||||
|
|
||||||
|
if (reflectivity <= 0.05)
|
||||||
|
{
|
||||||
|
outColor = vec4(baseColor, 1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dot(R, V) <= 0.0)
|
||||||
|
{
|
||||||
|
outColor = vec4(baseColor, 1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int MAX_STEPS = 64;
|
||||||
|
const float STEP_LENGTH = 0.5; // world units per step
|
||||||
|
const float MAX_DISTANCE = 50.0; // clamp ray length
|
||||||
|
const float THICKNESS = 3.0; // world-space thickness tolerance
|
||||||
|
|
||||||
|
int maxSteps = int(mix(8.0, float(MAX_STEPS), reflectivity));
|
||||||
|
|
||||||
|
bool hit = false;
|
||||||
|
vec2 hitUV = vec2(0.0);
|
||||||
|
|
||||||
|
float t = STEP_LENGTH;
|
||||||
|
for (int i = 0; i < maxSteps; ++i)
|
||||||
|
{
|
||||||
|
if (t > MAX_DISTANCE) break;
|
||||||
|
|
||||||
|
vec3 samplePos = worldPos + R * t;
|
||||||
|
vec3 proj = projectToScreen(samplePos);
|
||||||
|
if (proj.z < 0.0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 uv = proj.xy;
|
||||||
|
vec4 scenePosSample = texture(posTex, uv);
|
||||||
|
if (scenePosSample.w == 0.0)
|
||||||
|
{
|
||||||
|
t += STEP_LENGTH;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare distances along view direction as a simple intersection test.
|
||||||
|
vec3 viewSample = (sceneData.view * vec4(samplePos, 1.0)).xyz;
|
||||||
|
vec3 viewScene = (sceneData.view * vec4(scenePosSample.xyz, 1.0)).xyz;
|
||||||
|
|
||||||
|
float depthRay = -viewSample.z;
|
||||||
|
float depthScene = -viewScene.z;
|
||||||
|
|
||||||
|
float depthDiff = depthRay - depthScene;
|
||||||
|
|
||||||
|
if (depthRay > 0.0 && depthScene > 0.0 &&
|
||||||
|
depthDiff > 0.0 && depthDiff < THICKNESS)
|
||||||
|
{
|
||||||
|
hit = true;
|
||||||
|
hitUV = uv;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
t += STEP_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 result = baseColor;
|
||||||
|
if (hit)
|
||||||
|
{
|
||||||
|
vec3 reflColor = texture(hdrColor, hitUV).rgb;
|
||||||
|
|
||||||
|
float NoV = clamp(dot(N, V), 0.0, 1.0);
|
||||||
|
float F0 = mix(0.04, 1.0, metallic);
|
||||||
|
float F = F0 + (1.0 - F0) * pow(1.0 - NoV, 5.0); // Schlick
|
||||||
|
float gloss = 1.0 - roughness;
|
||||||
|
|
||||||
|
float ssrVisibility = gloss;
|
||||||
|
float weight = clamp(F * ssrVisibility, 0.0, 1.0);
|
||||||
|
|
||||||
|
result = mix(baseColor, reflColor, weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
outColor = vec4(result, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -68,6 +68,8 @@ add_executable (vulkan_engine
|
|||||||
render/passes/lighting.cpp
|
render/passes/lighting.cpp
|
||||||
render/passes/shadow.h
|
render/passes/shadow.h
|
||||||
render/passes/shadow.cpp
|
render/passes/shadow.cpp
|
||||||
|
render/passes/ssr.h
|
||||||
|
render/passes/ssr.cpp
|
||||||
render/passes/transparent.h
|
render/passes/transparent.h
|
||||||
render/passes/transparent.cpp
|
render/passes/transparent.cpp
|
||||||
render/passes/imgui_pass.h
|
render/passes/imgui_pass.h
|
||||||
|
|||||||
@@ -237,7 +237,29 @@ bool IBLManager::load(const IBLPaths &paths)
|
|||||||
_diff = _spec;
|
_diff = _spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If background is still missing but specular is valid, reuse the specular environment.
|
if (!paths.background2D.empty())
|
||||||
|
{
|
||||||
|
ktxutil::Ktx2D bg{};
|
||||||
|
if (ktxutil::load_ktx2_2d(paths.background2D.c_str(), bg))
|
||||||
|
{
|
||||||
|
std::vector<ResourceManager::MipLevelCopy> lv;
|
||||||
|
lv.reserve(bg.mipLevels);
|
||||||
|
for (uint32_t mip = 0; mip < bg.mipLevels; ++mip)
|
||||||
|
{
|
||||||
|
const auto &r = bg.copies[mip];
|
||||||
|
lv.push_back(ResourceManager::MipLevelCopy{
|
||||||
|
.offset = r.bufferOffset,
|
||||||
|
.length = 0,
|
||||||
|
.width = r.imageExtent.width,
|
||||||
|
.height = r.imageExtent.height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_background = rm->create_image_compressed(
|
||||||
|
bg.bytes.data(), bg.bytes.size(), bg.fmt, lv,
|
||||||
|
VK_IMAGE_USAGE_SAMPLED_BIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_background.image == VK_NULL_HANDLE && _spec.image != VK_NULL_HANDLE)
|
if (_background.image == VK_NULL_HANDLE && _spec.image != VK_NULL_HANDLE)
|
||||||
{
|
{
|
||||||
_background = _spec;
|
_background = _spec;
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public:
|
|||||||
|
|
||||||
// Runtime settings visible to passes/shaders
|
// Runtime settings visible to passes/shaders
|
||||||
ShadowSettings shadowSettings{};
|
ShadowSettings shadowSettings{};
|
||||||
|
bool enableSSR = false; // optional screen-space reflections toggle
|
||||||
|
|
||||||
// Ray tracing manager (optional, nullptr if unsupported)
|
// Ray tracing manager (optional, nullptr if unsupported)
|
||||||
RayTracingManager* ray = nullptr;
|
RayTracingManager* ray = nullptr;
|
||||||
|
|||||||
@@ -268,6 +268,8 @@ void VulkanEngine::init()
|
|||||||
|
|
||||||
_resourceManager->set_deferred_uploads(true);
|
_resourceManager->set_deferred_uploads(true);
|
||||||
|
|
||||||
|
_context->enableSSR = true;
|
||||||
|
|
||||||
//everything went fine
|
//everything went fine
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
}
|
}
|
||||||
@@ -716,16 +718,49 @@ void VulkanEngine::draw()
|
|||||||
lighting->register_graph(_renderGraph.get(), hDraw, hGBufferPosition, hGBufferNormal, hGBufferAlbedo,
|
lighting->register_graph(_renderGraph.get(), hDraw, hGBufferPosition, hGBufferNormal, hGBufferAlbedo,
|
||||||
std::span<RGImageHandle>(hShadowCascades.data(), hShadowCascades.size()));
|
std::span<RGImageHandle>(hShadowCascades.data(), hShadowCascades.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional Screen Space Reflections pass: consumes HDR draw + G-Buffer and
|
||||||
|
// produces an SSR-augmented HDR image. Controlled by EngineContext::enableSSR.
|
||||||
|
RGImageHandle hSSR{};
|
||||||
|
SSRPass *ssr = _renderPassManager->getPass<SSRPass>();
|
||||||
|
const bool ssrEnabled = (_context && _context->enableSSR && ssr != nullptr);
|
||||||
|
if (ssrEnabled)
|
||||||
|
{
|
||||||
|
RGImageDesc ssrDesc{};
|
||||||
|
ssrDesc.name = "hdr.ssr";
|
||||||
|
ssrDesc.format = _swapchainManager->drawImage().imageFormat;
|
||||||
|
ssrDesc.extent = _drawExtent;
|
||||||
|
ssrDesc.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
|
||||||
|
| VK_IMAGE_USAGE_SAMPLED_BIT
|
||||||
|
| VK_IMAGE_USAGE_STORAGE_BIT;
|
||||||
|
hSSR = _renderGraph->create_image(ssrDesc);
|
||||||
|
|
||||||
|
ssr->register_graph(_renderGraph.get(),
|
||||||
|
hDraw,
|
||||||
|
hGBufferPosition,
|
||||||
|
hGBufferNormal,
|
||||||
|
hGBufferAlbedo,
|
||||||
|
hSSR);
|
||||||
|
}
|
||||||
|
|
||||||
if (auto *transparent = _renderPassManager->getPass<TransparentPass>())
|
if (auto *transparent = _renderPassManager->getPass<TransparentPass>())
|
||||||
{
|
{
|
||||||
transparent->register_graph(_renderGraph.get(), hDraw, hDepth);
|
// Transparent objects draw on top of either the SSR output or the raw HDR draw.
|
||||||
|
RGImageHandle hdrTarget = (ssrEnabled && hSSR.valid()) ? hSSR : hDraw;
|
||||||
|
transparent->register_graph(_renderGraph.get(), hdrTarget, hDepth);
|
||||||
}
|
}
|
||||||
imguiPass = _renderPassManager->getImGuiPass();
|
imguiPass = _renderPassManager->getImGuiPass();
|
||||||
|
|
||||||
// Optional Tonemap pass: sample HDR draw -> LDR intermediate
|
// Optional Tonemap pass: sample HDR draw -> LDR intermediate
|
||||||
if (auto *tonemap = _renderPassManager->getPass<TonemapPass>())
|
if (auto *tonemap = _renderPassManager->getPass<TonemapPass>())
|
||||||
{
|
{
|
||||||
finalColor = tonemap->register_graph(_renderGraph.get(), hDraw);
|
RGImageHandle hdrInput = (ssrEnabled && hSSR.valid()) ? hSSR : hDraw;
|
||||||
|
finalColor = tonemap->register_graph(_renderGraph.get(), hdrInput);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If tonemapping is disabled, present whichever HDR buffer we ended up with.
|
||||||
|
finalColor = (ssrEnabled && hSSR.valid()) ? hSSR : hDraw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include "frame/resources.h"
|
#include "frame/resources.h"
|
||||||
#include "descriptor/manager.h"
|
#include "descriptor/manager.h"
|
||||||
#include "pipeline/sampler.h"
|
#include "pipeline/sampler.h"
|
||||||
|
#include "render/passes/ssr.h"
|
||||||
#include "core/context.h"
|
#include "core/context.h"
|
||||||
#include "core/pipeline/manager.h"
|
#include "core/pipeline/manager.h"
|
||||||
#include "core/assets/manager.h"
|
#include "core/assets/manager.h"
|
||||||
|
|||||||
208
src/render/passes/ssr.cpp
Normal file
208
src/render/passes/ssr.cpp
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
#include "ssr.h"
|
||||||
|
|
||||||
|
#include "core/frame/resources.h"
|
||||||
|
#include "core/descriptor/manager.h"
|
||||||
|
#include "core/descriptor/descriptors.h"
|
||||||
|
#include "core/device/device.h"
|
||||||
|
#include "core/device/resource.h"
|
||||||
|
#include "core/device/swapchain.h"
|
||||||
|
#include "core/context.h"
|
||||||
|
#include "core/pipeline/manager.h"
|
||||||
|
#include "core/assets/manager.h"
|
||||||
|
#include "core/pipeline/sampler.h"
|
||||||
|
|
||||||
|
#include "render/graph/graph.h"
|
||||||
|
#include "render/pipelines.h"
|
||||||
|
|
||||||
|
void SSRPass::init(EngineContext *context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
if (!_context || !_context->getDevice() || !_context->getDescriptorLayouts() || !_context->pipelines)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDevice device = _context->getDevice()->device();
|
||||||
|
|
||||||
|
// Set 1 layout: HDR + G-Buffer inputs (all sampled images).
|
||||||
|
{
|
||||||
|
DescriptorLayoutBuilder builder;
|
||||||
|
builder.add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); // hdrColor
|
||||||
|
builder.add_binding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); // posTex
|
||||||
|
builder.add_binding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); // normalTex
|
||||||
|
builder.add_binding(3, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); // albedoTex
|
||||||
|
_inputSetLayout = builder.build(
|
||||||
|
device,
|
||||||
|
VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||||
|
nullptr,
|
||||||
|
VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graphics pipeline: fullscreen triangle, no depth, HDR color attachment.
|
||||||
|
GraphicsPipelineCreateInfo info{};
|
||||||
|
info.vertexShaderPath = _context->getAssets()->shaderPath("fullscreen.vert.spv");
|
||||||
|
info.fragmentShaderPath = _context->getAssets()->shaderPath("ssr.frag.spv");
|
||||||
|
info.setLayouts = {
|
||||||
|
_context->getDescriptorLayouts()->gpuSceneDataLayout(), // set = 0 (sceneData UBO)
|
||||||
|
_inputSetLayout // set = 1 (HDR + GBuffer)
|
||||||
|
};
|
||||||
|
|
||||||
|
info.configure = [this](PipelineBuilder &b)
|
||||||
|
{
|
||||||
|
b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||||
|
b.set_polygon_mode(VK_POLYGON_MODE_FILL);
|
||||||
|
b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
|
||||||
|
b.set_multisampling_none();
|
||||||
|
b.disable_depthtest();
|
||||||
|
b.disable_blending();
|
||||||
|
if (_context && _context->getSwapchain())
|
||||||
|
{
|
||||||
|
b.set_color_attachment_format(_context->getSwapchain()->drawImage().imageFormat);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_context->pipelines->createGraphicsPipeline("ssr", info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSRPass::cleanup()
|
||||||
|
{
|
||||||
|
if (_context && _context->getDevice() && _inputSetLayout)
|
||||||
|
{
|
||||||
|
vkDestroyDescriptorSetLayout(_context->getDevice()->device(), _inputSetLayout, nullptr);
|
||||||
|
_inputSetLayout = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
_deletionQueue.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSRPass::execute(VkCommandBuffer)
|
||||||
|
{
|
||||||
|
// Executed via render graph; nothing to do here.
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSRPass::register_graph(RenderGraph *graph,
|
||||||
|
RGImageHandle hdrInput,
|
||||||
|
RGImageHandle gbufPos,
|
||||||
|
RGImageHandle gbufNorm,
|
||||||
|
RGImageHandle gbufAlbedo,
|
||||||
|
RGImageHandle hdrOutput)
|
||||||
|
{
|
||||||
|
if (!graph || !hdrInput.valid() || !hdrOutput.valid())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
graph->add_pass(
|
||||||
|
"SSR",
|
||||||
|
RGPassType::Graphics,
|
||||||
|
[hdrInput, gbufPos, gbufNorm, gbufAlbedo, hdrOutput](RGPassBuilder &builder, EngineContext *)
|
||||||
|
{
|
||||||
|
// Read current HDR lighting + G-Buffer; write to an HDR output.
|
||||||
|
builder.read(hdrInput, RGImageUsage::SampledFragment);
|
||||||
|
if (gbufPos.valid())
|
||||||
|
{
|
||||||
|
builder.read(gbufPos, RGImageUsage::SampledFragment);
|
||||||
|
}
|
||||||
|
if (gbufNorm.valid())
|
||||||
|
{
|
||||||
|
builder.read(gbufNorm, RGImageUsage::SampledFragment);
|
||||||
|
}
|
||||||
|
if (gbufAlbedo.valid())
|
||||||
|
{
|
||||||
|
builder.read(gbufAlbedo, RGImageUsage::SampledFragment);
|
||||||
|
}
|
||||||
|
builder.write_color(hdrOutput, false /*load existing contents*/);
|
||||||
|
},
|
||||||
|
[this, hdrInput, gbufPos, gbufNorm, gbufAlbedo](VkCommandBuffer cmd,
|
||||||
|
const RGPassResources &res,
|
||||||
|
EngineContext *ctx)
|
||||||
|
{
|
||||||
|
draw_ssr(cmd, ctx, res, hdrInput, gbufPos, gbufNorm, gbufAlbedo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSRPass::draw_ssr(VkCommandBuffer cmd,
|
||||||
|
EngineContext *context,
|
||||||
|
const RGPassResources &resources,
|
||||||
|
RGImageHandle hdrInput,
|
||||||
|
RGImageHandle gbufPos,
|
||||||
|
RGImageHandle gbufNorm,
|
||||||
|
RGImageHandle gbufAlbedo)
|
||||||
|
{
|
||||||
|
EngineContext *ctxLocal = context ? context : _context;
|
||||||
|
if (!ctxLocal || !ctxLocal->currentFrame) return;
|
||||||
|
|
||||||
|
ResourceManager *resourceManager = ctxLocal->getResources();
|
||||||
|
DeviceManager *deviceManager = ctxLocal->getDevice();
|
||||||
|
DescriptorManager *descriptorLayouts = ctxLocal->getDescriptorLayouts();
|
||||||
|
PipelineManager *pipelineManager = ctxLocal->pipelines;
|
||||||
|
if (!resourceManager || !deviceManager || !descriptorLayouts || !pipelineManager) return;
|
||||||
|
|
||||||
|
VkImageView hdrView = resources.image_view(hdrInput);
|
||||||
|
VkImageView posView = resources.image_view(gbufPos);
|
||||||
|
VkImageView normView = resources.image_view(gbufNorm);
|
||||||
|
VkImageView albedoView = resources.image_view(gbufAlbedo);
|
||||||
|
if (hdrView == VK_NULL_HANDLE || posView == VK_NULL_HANDLE ||
|
||||||
|
normView == VK_NULL_HANDLE || albedoView == VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch (or refresh) pipeline for hot-reload support.
|
||||||
|
if (!pipelineManager->getGraphics("ssr", _pipeline, _pipelineLayout))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scene UBO (set=0, binding=0) – mirror LightingPass behavior.
|
||||||
|
AllocatedBuffer sceneBuf = resourceManager->create_buffer(
|
||||||
|
sizeof(GPUSceneData),
|
||||||
|
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||||
|
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||||
|
ctxLocal->currentFrame->_deletionQueue.push_function([resourceManager, sceneBuf]()
|
||||||
|
{
|
||||||
|
resourceManager->destroy_buffer(sceneBuf);
|
||||||
|
});
|
||||||
|
|
||||||
|
VmaAllocationInfo allocInfo{};
|
||||||
|
vmaGetAllocationInfo(deviceManager->allocator(), sceneBuf.allocation, &allocInfo);
|
||||||
|
auto *sceneUniformData = static_cast<GPUSceneData *>(allocInfo.pMappedData);
|
||||||
|
*sceneUniformData = ctxLocal->getSceneData();
|
||||||
|
vmaFlushAllocation(deviceManager->allocator(), sceneBuf.allocation, 0, sizeof(GPUSceneData));
|
||||||
|
|
||||||
|
VkDescriptorSet globalSet = ctxLocal->currentFrame->_frameDescriptors.allocate(
|
||||||
|
deviceManager->device(), descriptorLayouts->gpuSceneDataLayout());
|
||||||
|
{
|
||||||
|
DescriptorWriter writer;
|
||||||
|
writer.write_buffer(0, sceneBuf.buffer, sizeof(GPUSceneData), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||||
|
writer.update_set(deviceManager->device(), globalSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input set (set=1): HDR color + G-Buffer textures.
|
||||||
|
VkDescriptorSet inputSet = ctxLocal->currentFrame->_frameDescriptors.allocate(
|
||||||
|
deviceManager->device(), _inputSetLayout);
|
||||||
|
{
|
||||||
|
DescriptorWriter writer;
|
||||||
|
writer.write_image(0, hdrView, ctxLocal->getSamplers()->defaultLinear(),
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||||
|
writer.write_image(1, posView, ctxLocal->getSamplers()->defaultLinear(),
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||||
|
writer.write_image(2, normView, ctxLocal->getSamplers()->defaultLinear(),
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||||
|
writer.write_image(3, albedoView, ctxLocal->getSamplers()->defaultLinear(),
|
||||||
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||||
|
writer.update_set(deviceManager->device(), inputSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipeline);
|
||||||
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 0, 1, &globalSet, 0, nullptr);
|
||||||
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 1, 1, &inputSet, 0, nullptr);
|
||||||
|
|
||||||
|
VkExtent2D extent = ctxLocal->getDrawExtent();
|
||||||
|
VkViewport vp{0.f, 0.f, (float)extent.width, (float)extent.height, 0.f, 1.f};
|
||||||
|
VkRect2D sc{{0, 0}, extent};
|
||||||
|
vkCmdSetViewport(cmd, 0, 1, &vp);
|
||||||
|
vkCmdSetScissor(cmd, 0, 1, &sc);
|
||||||
|
|
||||||
|
vkCmdDraw(cmd, 3, 1, 0, 0);
|
||||||
|
}
|
||||||
49
src/render/passes/ssr.h
Normal file
49
src/render/passes/ssr.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "render/renderpass.h"
|
||||||
|
#include "render/graph/types.h"
|
||||||
|
|
||||||
|
class RenderGraph;
|
||||||
|
class RGPassResources;
|
||||||
|
|
||||||
|
// Screen Space Reflections (SSR) pass.
|
||||||
|
// In v1 this is a lightweight stub wired into the RenderGraph by the engine.
|
||||||
|
// The full pipeline and shader implementation is added in later plan steps.
|
||||||
|
class SSRPass : public IRenderPass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void init(EngineContext *context) override;
|
||||||
|
void cleanup() override;
|
||||||
|
void execute(VkCommandBuffer cmd) override;
|
||||||
|
|
||||||
|
const char *getName() const override { return "SSR"; }
|
||||||
|
|
||||||
|
// Register SSR in the render graph.
|
||||||
|
// hdrInput : HDR color buffer produced by deferred lighting.
|
||||||
|
// gbufPos : G-Buffer world-space position (RGBA32F).
|
||||||
|
// gbufNorm : G-Buffer world-space normal + roughness.
|
||||||
|
// gbufAlbedo : G-Buffer albedo + metallic.
|
||||||
|
// hdrOutput : HDR color buffer that will carry lighting + SSR.
|
||||||
|
void register_graph(RenderGraph *graph,
|
||||||
|
RGImageHandle hdrInput,
|
||||||
|
RGImageHandle gbufPos,
|
||||||
|
RGImageHandle gbufNorm,
|
||||||
|
RGImageHandle gbufAlbedo,
|
||||||
|
RGImageHandle hdrOutput);
|
||||||
|
|
||||||
|
private:
|
||||||
|
EngineContext *_context = nullptr;
|
||||||
|
VkDescriptorSetLayout _inputSetLayout = VK_NULL_HANDLE; // set=1: HDR + GBuffer inputs
|
||||||
|
VkPipeline _pipeline = VK_NULL_HANDLE;
|
||||||
|
VkPipelineLayout _pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
void draw_ssr(VkCommandBuffer cmd,
|
||||||
|
EngineContext *context,
|
||||||
|
const class RGPassResources &resources,
|
||||||
|
RGImageHandle hdrInput,
|
||||||
|
RGImageHandle gbufPos,
|
||||||
|
RGImageHandle gbufNorm,
|
||||||
|
RGImageHandle gbufAlbedo);
|
||||||
|
|
||||||
|
DeletionQueue _deletionQueue;
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "passes/geometry.h"
|
#include "passes/geometry.h"
|
||||||
#include "passes/imgui_pass.h"
|
#include "passes/imgui_pass.h"
|
||||||
#include "passes/lighting.h"
|
#include "passes/lighting.h"
|
||||||
|
#include "passes/ssr.h"
|
||||||
#include "passes/transparent.h"
|
#include "passes/transparent.h"
|
||||||
#include "passes/tonemap.h"
|
#include "passes/tonemap.h"
|
||||||
#include "passes/shadow.h"
|
#include "passes/shadow.h"
|
||||||
@@ -29,6 +30,11 @@ void RenderPassManager::init(EngineContext *context)
|
|||||||
lightingPass->init(context);
|
lightingPass->init(context);
|
||||||
addPass(std::move(lightingPass));
|
addPass(std::move(lightingPass));
|
||||||
|
|
||||||
|
// Screen Space Reflections pass (wired between lighting and transparent)
|
||||||
|
auto ssrPass = std::make_unique<SSRPass>();
|
||||||
|
ssrPass->init(context);
|
||||||
|
addPass(std::move(ssrPass));
|
||||||
|
|
||||||
auto transparentPass = std::make_unique<TransparentPass>();
|
auto transparentPass = std::make_unique<TransparentPass>();
|
||||||
transparentPass->init(context);
|
transparentPass->init(context);
|
||||||
addPass(std::move(transparentPass));
|
addPass(std::move(transparentPass));
|
||||||
|
|||||||
Reference in New Issue
Block a user