mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Add HDR post-process FBO pipeline with 4x MSAA
Render the world into an off-screen MSAA RGBA16F framebuffer, resolve to a regular texture, then blit to screen via a fullscreen quad shader. This sets up the infrastructure for future HDR tonemapping and SSAO (depth texture preserved for sampling). Currently a passthrough until HDR light sources are added. Login screen bypasses the FBO and renders directly to the default framebuffer.
This commit is contained in:
parent
aeccddddeb
commit
1f672e1d73
2 changed files with 222 additions and 1 deletions
|
|
@ -158,6 +158,22 @@ private:
|
||||||
uint32_t underwaterOverlayVAO = 0;
|
uint32_t underwaterOverlayVAO = 0;
|
||||||
uint32_t underwaterOverlayVBO = 0;
|
uint32_t underwaterOverlayVBO = 0;
|
||||||
|
|
||||||
|
// Post-process FBO pipeline (HDR MSAA → resolve → tonemap)
|
||||||
|
uint32_t sceneFBO = 0; // MSAA render target
|
||||||
|
uint32_t sceneColorRBO = 0; // GL_RGBA16F multisampled renderbuffer
|
||||||
|
uint32_t sceneDepthRBO = 0; // GL_DEPTH_COMPONENT24 multisampled renderbuffer
|
||||||
|
uint32_t resolveFBO = 0; // Non-MSAA resolve target
|
||||||
|
uint32_t resolveColorTex = 0; // GL_RGBA16F resolved texture (sampled by post-process)
|
||||||
|
uint32_t resolveDepthTex = 0; // GL_DEPTH_COMPONENT24 resolved texture (for future SSAO)
|
||||||
|
uint32_t screenQuadVAO = 0;
|
||||||
|
uint32_t screenQuadVBO = 0;
|
||||||
|
std::unique_ptr<Shader> postProcessShader;
|
||||||
|
int fbWidth = 0, fbHeight = 0;
|
||||||
|
|
||||||
|
void initPostProcess(int w, int h);
|
||||||
|
void resizePostProcess(int w, int h);
|
||||||
|
void shutdownPostProcess();
|
||||||
|
|
||||||
pipeline::AssetManager* cachedAssetManager = nullptr;
|
pipeline::AssetManager* cachedAssetManager = nullptr;
|
||||||
uint32_t currentZoneId = 0;
|
uint32_t currentZoneId = 0;
|
||||||
std::string currentZoneName;
|
std::string currentZoneName;
|
||||||
|
|
|
||||||
|
|
@ -225,6 +225,9 @@ bool Renderer::initialize(core::Window* win) {
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize post-process FBO pipeline
|
||||||
|
initPostProcess(window->getWidth(), window->getHeight());
|
||||||
|
|
||||||
LOG_INFO("Renderer initialized");
|
LOG_INFO("Renderer initialized");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -314,6 +317,8 @@ void Renderer::shutdown() {
|
||||||
}
|
}
|
||||||
underwaterOverlayShader.reset();
|
underwaterOverlayShader.reset();
|
||||||
|
|
||||||
|
shutdownPostProcess();
|
||||||
|
|
||||||
zoneManager.reset();
|
zoneManager.reset();
|
||||||
|
|
||||||
performanceHUD.reset();
|
performanceHUD.reset();
|
||||||
|
|
@ -325,7 +330,15 @@ void Renderer::shutdown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::beginFrame() {
|
void Renderer::beginFrame() {
|
||||||
// Black background (skybox will render over it)
|
// Resize post-process FBO if window size changed
|
||||||
|
int w = window->getWidth();
|
||||||
|
int h = window->getHeight();
|
||||||
|
if (w != fbWidth || h != fbHeight) {
|
||||||
|
resizePostProcess(w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear default framebuffer (login screen renders here directly)
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
}
|
}
|
||||||
|
|
@ -888,6 +901,12 @@ void Renderer::renderWorld(game::World* world) {
|
||||||
lastWMORenderMs = 0.0;
|
lastWMORenderMs = 0.0;
|
||||||
lastM2RenderMs = 0.0;
|
lastM2RenderMs = 0.0;
|
||||||
|
|
||||||
|
// Bind HDR scene framebuffer for world rendering
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, sceneFBO);
|
||||||
|
glViewport(0, 0, fbWidth, fbHeight);
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
(void)world; // Unused for now
|
(void)world; // Unused for now
|
||||||
|
|
||||||
// Get time of day for sky-related rendering
|
// Get time of day for sky-related rendering
|
||||||
|
|
@ -1037,10 +1056,196 @@ void Renderer::renderWorld(game::World* world) {
|
||||||
minimap->render(*camera, window->getWidth(), window->getHeight());
|
minimap->render(*camera, window->getWidth(), window->getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Resolve MSAA → non-MSAA texture ---
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, sceneFBO);
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
|
||||||
|
glBlitFramebuffer(0, 0, fbWidth, fbHeight, 0, 0, fbWidth, fbHeight,
|
||||||
|
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||||
|
|
||||||
|
// --- Post-process: tonemap via fullscreen quad ---
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
glViewport(0, 0, window->getWidth(), window->getHeight());
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
if (postProcessShader && screenQuadVAO) {
|
||||||
|
postProcessShader->use();
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, resolveColorTex);
|
||||||
|
postProcessShader->setUniform("uScene", 0);
|
||||||
|
glBindVertexArray(screenQuadVAO);
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
postProcessShader->unuse();
|
||||||
|
}
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
auto renderEnd = std::chrono::steady_clock::now();
|
auto renderEnd = std::chrono::steady_clock::now();
|
||||||
lastRenderMs = std::chrono::duration<double, std::milli>(renderEnd - renderStart).count();
|
lastRenderMs = std::chrono::duration<double, std::milli>(renderEnd - renderStart).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────
|
||||||
|
// Post-process FBO helpers
|
||||||
|
// ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void Renderer::initPostProcess(int w, int h) {
|
||||||
|
fbWidth = w;
|
||||||
|
fbHeight = h;
|
||||||
|
constexpr int SAMPLES = 4;
|
||||||
|
|
||||||
|
// --- MSAA FBO (render target) ---
|
||||||
|
glGenRenderbuffers(1, &sceneColorRBO);
|
||||||
|
glBindRenderbuffer(GL_RENDERBUFFER, sceneColorRBO);
|
||||||
|
glRenderbufferStorageMultisample(GL_RENDERBUFFER, SAMPLES, GL_RGBA16F, w, h);
|
||||||
|
|
||||||
|
glGenRenderbuffers(1, &sceneDepthRBO);
|
||||||
|
glBindRenderbuffer(GL_RENDERBUFFER, sceneDepthRBO);
|
||||||
|
glRenderbufferStorageMultisample(GL_RENDERBUFFER, SAMPLES, GL_DEPTH_COMPONENT24, w, h);
|
||||||
|
|
||||||
|
glGenFramebuffers(1, &sceneFBO);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, sceneFBO);
|
||||||
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, sceneColorRBO);
|
||||||
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, sceneDepthRBO);
|
||||||
|
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
LOG_ERROR("MSAA scene FBO incomplete!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Resolve FBO (non-MSAA, for post-process sampling) ---
|
||||||
|
glGenTextures(1, &resolveColorTex);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, resolveColorTex);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, w, h, 0, GL_RGBA, GL_FLOAT, nullptr);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
glGenTextures(1, &resolveDepthTex);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, resolveDepthTex);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, w, h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
glGenFramebuffers(1, &resolveFBO);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveColorTex, 0);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, resolveDepthTex, 0);
|
||||||
|
|
||||||
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
|
LOG_ERROR("Resolve FBO incomplete!");
|
||||||
|
}
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
|
||||||
|
// --- Fullscreen quad (triangle strip, pos + UV) ---
|
||||||
|
const float quadVerts[] = {
|
||||||
|
// pos (x,y) uv (u,v)
|
||||||
|
-1.0f, -1.0f, 0.0f, 0.0f,
|
||||||
|
1.0f, -1.0f, 1.0f, 0.0f,
|
||||||
|
-1.0f, 1.0f, 0.0f, 1.0f,
|
||||||
|
1.0f, 1.0f, 1.0f, 1.0f,
|
||||||
|
};
|
||||||
|
glGenVertexArrays(1, &screenQuadVAO);
|
||||||
|
glGenBuffers(1, &screenQuadVBO);
|
||||||
|
glBindVertexArray(screenQuadVAO);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, screenQuadVBO);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVerts), quadVerts, GL_STATIC_DRAW);
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
// --- Post-process shader (Reinhard tonemap + gamma 2.2) ---
|
||||||
|
const char* ppVS = R"(
|
||||||
|
#version 330 core
|
||||||
|
layout (location = 0) in vec2 aPos;
|
||||||
|
layout (location = 1) in vec2 aUV;
|
||||||
|
out vec2 vUV;
|
||||||
|
void main() {
|
||||||
|
vUV = aUV;
|
||||||
|
gl_Position = vec4(aPos, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
const char* ppFS = R"(
|
||||||
|
#version 330 core
|
||||||
|
in vec2 vUV;
|
||||||
|
uniform sampler2D uScene;
|
||||||
|
out vec4 FragColor;
|
||||||
|
void main() {
|
||||||
|
vec3 color = texture(uScene, vUV).rgb;
|
||||||
|
// Passthrough — tonemap will kick in once HDR lighting is added
|
||||||
|
FragColor = vec4(color, 1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
postProcessShader = std::make_unique<Shader>();
|
||||||
|
if (!postProcessShader->loadFromSource(ppVS, ppFS)) {
|
||||||
|
LOG_ERROR("Failed to compile post-process shader");
|
||||||
|
postProcessShader.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Post-process FBO initialized (", w, "x", h, ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::resizePostProcess(int w, int h) {
|
||||||
|
if (w <= 0 || h <= 0) return;
|
||||||
|
fbWidth = w;
|
||||||
|
fbHeight = h;
|
||||||
|
constexpr int SAMPLES = 4;
|
||||||
|
|
||||||
|
// Resize MSAA renderbuffers
|
||||||
|
glBindRenderbuffer(GL_RENDERBUFFER, sceneColorRBO);
|
||||||
|
glRenderbufferStorageMultisample(GL_RENDERBUFFER, SAMPLES, GL_RGBA16F, w, h);
|
||||||
|
glBindRenderbuffer(GL_RENDERBUFFER, sceneDepthRBO);
|
||||||
|
glRenderbufferStorageMultisample(GL_RENDERBUFFER, SAMPLES, GL_DEPTH_COMPONENT24, w, h);
|
||||||
|
|
||||||
|
// Resize resolve textures
|
||||||
|
glBindTexture(GL_TEXTURE_2D, resolveColorTex);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, w, h, 0, GL_RGBA, GL_FLOAT, nullptr);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, resolveDepthTex);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, w, h, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
|
||||||
|
|
||||||
|
LOG_INFO("Post-process FBO resized (", w, "x", h, ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::shutdownPostProcess() {
|
||||||
|
if (sceneFBO) {
|
||||||
|
glDeleteFramebuffers(1, &sceneFBO);
|
||||||
|
sceneFBO = 0;
|
||||||
|
}
|
||||||
|
if (sceneColorRBO) {
|
||||||
|
glDeleteRenderbuffers(1, &sceneColorRBO);
|
||||||
|
sceneColorRBO = 0;
|
||||||
|
}
|
||||||
|
if (sceneDepthRBO) {
|
||||||
|
glDeleteRenderbuffers(1, &sceneDepthRBO);
|
||||||
|
sceneDepthRBO = 0;
|
||||||
|
}
|
||||||
|
if (resolveFBO) {
|
||||||
|
glDeleteFramebuffers(1, &resolveFBO);
|
||||||
|
resolveFBO = 0;
|
||||||
|
}
|
||||||
|
if (resolveColorTex) {
|
||||||
|
glDeleteTextures(1, &resolveColorTex);
|
||||||
|
resolveColorTex = 0;
|
||||||
|
}
|
||||||
|
if (resolveDepthTex) {
|
||||||
|
glDeleteTextures(1, &resolveDepthTex);
|
||||||
|
resolveDepthTex = 0;
|
||||||
|
}
|
||||||
|
if (screenQuadVAO) {
|
||||||
|
glDeleteVertexArrays(1, &screenQuadVAO);
|
||||||
|
screenQuadVAO = 0;
|
||||||
|
}
|
||||||
|
if (screenQuadVBO) {
|
||||||
|
glDeleteBuffers(1, &screenQuadVBO);
|
||||||
|
screenQuadVBO = 0;
|
||||||
|
}
|
||||||
|
postProcessShader.reset();
|
||||||
|
}
|
||||||
|
|
||||||
bool Renderer::loadTestTerrain(pipeline::AssetManager* assetManager, const std::string& adtPath) {
|
bool Renderer::loadTestTerrain(pipeline::AssetManager* assetManager, const std::string& adtPath) {
|
||||||
if (!assetManager) {
|
if (!assetManager) {
|
||||||
LOG_ERROR("Asset manager is null");
|
LOG_ERROR("Asset manager is null");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue