FIX: FXAA, bloom

This commit is contained in:
2025-12-05 19:18:16 +09:00
parent 20f6ad494c
commit d2aa6855a3
12 changed files with 366 additions and 6 deletions

View File

@@ -70,6 +70,8 @@ add_executable (vulkan_engine
render/passes/lighting.cpp
render/passes/shadow.h
render/passes/shadow.cpp
render/passes/fxaa.h
render/passes/fxaa.cpp
render/passes/ssr.h
render/passes/ssr.cpp
render/passes/transparent.h

View File

@@ -42,6 +42,7 @@
#include "render/passes/imgui_pass.h"
#include "render/passes/lighting.h"
#include "render/passes/transparent.h"
#include "render/passes/fxaa.h"
#include "render/passes/tonemap.h"
#include "render/passes/shadow.h"
#include "device/resource.h"
@@ -848,6 +849,12 @@ void VulkanEngine::draw()
{
RGImageHandle hdrInput = (ssrEnabled && hSSR.valid()) ? hSSR : hDraw;
finalColor = tonemap->register_graph(_renderGraph.get(), hdrInput);
// Optional FXAA pass: runs on LDR tonemapped output.
if (auto *fxaa = _renderPassManager->getPass<FxaaPass>())
{
finalColor = fxaa->register_graph(_renderGraph.get(), finalColor);
}
}
else
{

View File

@@ -12,6 +12,7 @@
#include "render/primitives.h"
#include "vk_mem_alloc.h"
#include "render/passes/tonemap.h"
#include "render/passes/fxaa.h"
#include "render/passes/background.h"
#include <glm/gtx/euler_angles.hpp>
#include <glm/gtc/matrix_transform.hpp>
@@ -147,6 +148,30 @@ namespace
"5x5 spheres: metallic across columns, roughness across rows.\nExtra: chrome + glass.");
ImGui::Separator();
// Post-processing: FXAA
if (auto *fx = eng->_renderPassManager ? eng->_renderPassManager->getPass<FxaaPass>() : nullptr)
{
bool fxaaEnabled = fx->enabled();
if (ImGui::Checkbox("FXAA", &fxaaEnabled))
{
fx->set_enabled(fxaaEnabled);
}
float edgeTh = fx->edge_threshold();
if (ImGui::SliderFloat("FXAA Edge Threshold", &edgeTh, 0.01f, 0.5f))
{
fx->set_edge_threshold(edgeTh);
}
float edgeThMin = fx->edge_threshold_min();
if (ImGui::SliderFloat("FXAA Edge Threshold Min", &edgeThMin, 0.0f, 0.1f))
{
fx->set_edge_threshold_min(edgeThMin);
}
}
else
{
ImGui::TextUnformatted("FXAA pass not available");
}
ImGui::TextUnformatted("IBL Volumes (reflection probes)");
if (!eng->_iblManager)
@@ -740,6 +765,23 @@ namespace
mode = 1;
tm->setMode(mode);
}
// Bloom controls
bool bloomEnabled = tm->bloomEnabled();
if (ImGui::Checkbox("Bloom", &bloomEnabled))
{
tm->setBloomEnabled(bloomEnabled);
}
float bloomThreshold = tm->bloomThreshold();
if (ImGui::SliderFloat("Bloom Threshold", &bloomThreshold, 0.0f, 5.0f))
{
tm->setBloomThreshold(bloomThreshold);
}
float bloomIntensity = tm->bloomIntensity();
if (ImGui::SliderFloat("Bloom Intensity", &bloomIntensity, 0.0f, 2.0f))
{
tm->setBloomIntensity(bloomIntensity);
}
}
else
{

140
src/render/passes/fxaa.cpp Normal file
View File

@@ -0,0 +1,140 @@
#include "fxaa.h"
#include <core/context.h>
#include <core/descriptor/descriptors.h>
#include <core/descriptor/manager.h>
#include <core/pipeline/manager.h>
#include <core/assets/manager.h>
#include <core/device/device.h>
#include <core/device/resource.h>
#include <core/pipeline/sampler.h>
#include <render/graph/graph.h>
#include <render/graph/resources.h>
#include "core/frame/resources.h"
void FxaaPass::init(EngineContext *context)
{
_context = context;
if (!_context || !_context->getDevice() || !_context->getDescriptorLayouts() || !_context->pipelines)
{
return;
}
_inputSetLayout = _context->getDescriptorLayouts()->singleImageLayout();
GraphicsPipelineCreateInfo info{};
info.vertexShaderPath = _context->getAssets()->shaderPath("fullscreen.vert.spv");
info.fragmentShaderPath = _context->getAssets()->shaderPath("fxaa.frag.spv");
info.setLayouts = { _inputSetLayout };
VkPushConstantRange pcr{};
pcr.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
pcr.offset = 0;
pcr.size = sizeof(FxaaPush);
info.pushConstants = { pcr };
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();
b.set_color_attachment_format(VK_FORMAT_R8G8B8A8_UNORM);
};
_context->pipelines->createGraphicsPipeline("fxaa", info);
}
void FxaaPass::cleanup()
{
_deletionQueue.flush();
}
void FxaaPass::execute(VkCommandBuffer)
{
// Executed via render graph.
}
RGImageHandle FxaaPass::register_graph(RenderGraph *graph, RGImageHandle ldrInput)
{
if (!graph || !ldrInput.valid() || !_context)
{
return {};
}
// If disabled, simply bypass and return the input image.
if (!_enabled)
{
return ldrInput;
}
RGImageDesc desc{};
desc.name = "ldr.fxaa";
desc.format = VK_FORMAT_R8G8B8A8_UNORM;
desc.extent = _context->getDrawExtent();
desc.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_SAMPLED_BIT
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
RGImageHandle aaOutput = graph->create_image(desc);
graph->add_pass(
"FXAA",
RGPassType::Graphics,
[ldrInput, aaOutput](RGPassBuilder &builder, EngineContext *) {
builder.read(ldrInput, RGImageUsage::SampledFragment);
builder.write_color(aaOutput, true /*clear*/);
},
[this, ldrInput](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) {
draw_fxaa(cmd, ctx, res, ldrInput);
});
return aaOutput;
}
void FxaaPass::draw_fxaa(VkCommandBuffer cmd, EngineContext *ctx, const RGPassResources &res,
RGImageHandle ldrInput)
{
if (!ctx || !ctx->currentFrame) return;
DeviceManager *deviceManager = ctx->getDevice();
DescriptorManager *descriptorLayouts = ctx->getDescriptorLayouts();
PipelineManager *pipelineManager = ctx->pipelines;
if (!deviceManager || !descriptorLayouts || !pipelineManager) return;
VkImageView srcView = res.image_view(ldrInput);
if (srcView == VK_NULL_HANDLE) return;
VkDevice device = deviceManager->device();
VkDescriptorSet set = ctx->currentFrame->_frameDescriptors.allocate(device, _inputSetLayout);
DescriptorWriter writer;
writer.write_image(0, srcView, ctx->getSamplers()->defaultLinear(),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
writer.update_set(device, set);
VkPipeline pipeline{};
VkPipelineLayout layout{};
if (!pipelineManager->getGraphics("fxaa", pipeline, layout))
{
return;
}
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, 1, &set, 0, nullptr);
VkExtent2D extent = ctx->getDrawExtent();
FxaaPush push{};
push.inverse_width = extent.width > 0 ? 1.0f / static_cast<float>(extent.width) : 0.0f;
push.inverse_height = extent.height > 0 ? 1.0f / static_cast<float>(extent.height) : 0.0f;
push.edge_threshold = _edge_threshold;
push.edge_threshold_min = _edge_threshold_min;
vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(FxaaPush), &push);
VkViewport vp{0.f, 0.f, static_cast<float>(extent.width), static_cast<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);
}

56
src/render/passes/fxaa.h Normal file
View File

@@ -0,0 +1,56 @@
#pragma once
#include <core/types.h>
#include <render/renderpass.h>
#include <render/graph/types.h>
class EngineContext;
class RenderGraph;
class RGPassResources;
// Simple post-process anti-aliasing pass (FXAA-like).
// Operates on the LDR tonemapped image and outputs a smoothed LDR image.
class FxaaPass final : public IRenderPass
{
public:
void init(EngineContext *context) override;
void cleanup() override;
void execute(VkCommandBuffer) override; // Not used directly; executed via render graph
const char *getName() const override { return "FXAA"; }
// Register pass in the render graph. Returns the AA output image handle.
RGImageHandle register_graph(RenderGraph *graph, RGImageHandle ldrInput);
// Runtime parameters
void set_enabled(bool e) { _enabled = e; }
bool enabled() const { return _enabled; }
void set_edge_threshold(float v) { _edge_threshold = v; }
float edge_threshold() const { return _edge_threshold; }
void set_edge_threshold_min(float v) { _edge_threshold_min = v; }
float edge_threshold_min() const { return _edge_threshold_min; }
private:
struct FxaaPush
{
float inverse_width;
float inverse_height;
float edge_threshold;
float edge_threshold_min;
};
void draw_fxaa(VkCommandBuffer cmd, EngineContext *ctx, const RGPassResources &res,
RGImageHandle ldrInput);
EngineContext *_context = nullptr;
VkPipeline _pipeline = VK_NULL_HANDLE;
VkPipelineLayout _pipelineLayout = VK_NULL_HANDLE;
VkDescriptorSetLayout _inputSetLayout = VK_NULL_HANDLE;
// Tunables for edge detection; chosen to be conservative by default.
bool _enabled = true;
float _edge_threshold = 0.125f;
float _edge_threshold_min = 0.0312f;
DeletionQueue _deletionQueue;
};

View File

@@ -17,6 +17,9 @@ struct TonemapPush
{
float exposure;
int mode;
int bloomEnabled;
float bloomThreshold;
float bloomIntensity;
};
void TonemapPass::init(EngineContext *context)
@@ -72,7 +75,9 @@ RGImageHandle TonemapPass::register_graph(RenderGraph *graph, RGImageHandle hdrI
desc.name = "ldr.tonemap";
desc.format = VK_FORMAT_R8G8B8A8_UNORM;
desc.extent = _context->getDrawExtent();
desc.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
desc.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_SAMPLED_BIT
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
RGImageHandle ldr = graph->create_image(desc);
graph->add_pass(
@@ -109,7 +114,12 @@ void TonemapPass::draw_tonemap(VkCommandBuffer cmd, EngineContext *ctx, const RG
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipeline);
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 0, 1, &set, 0, nullptr);
TonemapPush push{_exposure, _mode};
TonemapPush push{};
push.exposure = _exposure;
push.mode = _mode;
push.bloomEnabled = _bloomEnabled ? 1 : 0;
push.bloomThreshold = _bloomThreshold;
push.bloomIntensity = _bloomIntensity;
vkCmdPushConstants(cmd, _pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(TonemapPush), &push);
VkExtent2D extent = ctx->getDrawExtent();

View File

@@ -25,6 +25,13 @@ public:
void setMode(int m) { _mode = m; }
int mode() const { return _mode; }
void setBloomEnabled(bool b) { _bloomEnabled = b; }
bool bloomEnabled() const { return _bloomEnabled; }
void setBloomThreshold(float t) { _bloomThreshold = t; }
float bloomThreshold() const { return _bloomThreshold; }
void setBloomIntensity(float i) { _bloomIntensity = i; }
float bloomIntensity() const { return _bloomIntensity; }
private:
void draw_tonemap(VkCommandBuffer cmd, EngineContext *ctx, const RGPassResources &res,
RGImageHandle hdrInput);
@@ -38,6 +45,10 @@ private:
float _exposure = 1.0f;
int _mode = 1; // default to ACES
bool _bloomEnabled = true;
float _bloomThreshold = 1.0f;
float _bloomIntensity = 0.7f;
DeletionQueue _deletionQueue;
};

View File

@@ -5,6 +5,7 @@
#include "passes/imgui_pass.h"
#include "passes/lighting.h"
#include "passes/ssr.h"
#include "passes/fxaa.h"
#include "passes/transparent.h"
#include "passes/tonemap.h"
#include "passes/shadow.h"
@@ -35,6 +36,11 @@ void RenderPassManager::init(EngineContext *context)
ssrPass->init(context);
addPass(std::move(ssrPass));
// Post-process AA (FXAA-like) after tonemapping.
auto fxaaPass = std::make_unique<FxaaPass>();
fxaaPass->init(context);
addPass(std::move(fxaaPass));
auto transparentPass = std::make_unique<TransparentPass>();
transparentPass->init(context);
addPass(std::move(transparentPass));