add compute shader to auto calculate screen exposure, add SSBO impl

This commit is contained in:
2026-03-12 23:42:52 +08:00
parent 8ba00467fd
commit 5cb9b04ab0
16 changed files with 511 additions and 199 deletions

View File

@ -0,0 +1,63 @@
#type compute
#version 460 core
layout(local_size_x = 1, local_size_y = 1) in;
layout(binding = 0, std430) buffer Histogram {
uint bins[64];
};
layout(binding = 1, std430) buffer Exposure {
float exposure;
};
uniform float u_SpeedUp;
uniform float u_SpeedDown;
uniform float u_Key;
uniform float u_LowPercent;
uniform float u_HighPercent;
uniform float u_MinExposure;
uniform float u_MaxExposure;
uniform float u_DeltaTime;
uniform float u_LogMin;
uniform float u_LogMax;
void main() {
float currentExposure = exposure;
uint total = 0;
uint prefix[64];
for (int i = 0; i < 64; i++) {
total += bins[i];
prefix[i] = total;
}
float lowCount = u_LowPercent * 0.01 * total;
float highCount = u_HighPercent * 0.01 * total;
int lowBin = 0, highBin = 63;
for (int i = 0; i < 64; i++) {
if (prefix[i] < lowCount) lowBin = i + 1;
if (prefix[i] < highCount) highBin = i + 1;
}
lowBin = clamp(lowBin, 0, 63);
highBin = clamp(highBin, 0, 63);
float sumLum = 0.0;
uint count = 0;
for (int i = lowBin; i <= highBin; i++) {
float t = (float(i) + 0.5) / 64.0;
float logLum = u_LogMin + t * (u_LogMax - u_LogMin);
float lum = exp2(logLum);
sumLum += lum * float(bins[i]);
count += bins[i];
}
float avgLum = count > 0 ? sumLum / count : 0.18;
float targetExposure = u_Key / max(avgLum, 0.0001);
targetExposure = clamp(targetExposure, u_MinExposure, u_MaxExposure);
float speed = (targetExposure > currentExposure) ? u_SpeedUp : u_SpeedDown;
float adaptFactor = 1.0 - exp(-speed * u_DeltaTime);
float newExposure = mix(currentExposure, targetExposure, adaptFactor);
newExposure = clamp(newExposure, u_MinExposure, u_MaxExposure);
exposure = newExposure;
}

View File

@ -0,0 +1,26 @@
#type compute
#version 460 core
layout(local_size_x = 16, local_size_y = 16) in;
layout(binding = 0) uniform sampler2D u_SceneColor;
layout(binding = 1, std430) buffer Histogram {
uint bins[64];
};
uniform float u_LogMin;
uniform float u_LogMax;
void main() {
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = textureSize(u_SceneColor, 0);
if (texel.x >= size.x || texel.y >= size.y) return;
vec3 color = texelFetch(u_SceneColor, texel, 0).rgb;
float lum = max(dot(color, vec3(0.2126, 0.7152, 0.0722)), 0.0001);
float logLum = log2(lum);
float invLogRange = 1.0 / (u_LogMax - u_LogMin);
float t = (logLum - u_LogMin) * invLogRange;
int bin = int(clamp(t * 64.0, 0.0, 63.0));
atomicAdd(bins[bin], 1u);
}

View File

@ -23,7 +23,14 @@ in vec2 v_TexCoord;
uniform sampler2DMS u_Texture; uniform sampler2DMS u_Texture;
uniform float u_Exposure;
uniform bool u_EnableAutoExposure;
uniform float u_ManualExposure;
layout(std430, binding = 2) buffer Exposure
{
float u_Exposure;
};
uniform int u_TextureSamples; uniform int u_TextureSamples;
uniform bool u_EnableBloom; uniform bool u_EnableBloom;
@ -64,7 +71,10 @@ void main()
color += bloomColor; color += bloomColor;
} }
if(u_EnableAutoExposure)
color *= u_Exposure; color *= u_Exposure;
else
color *= u_ManualExposure;
// Reinhard tonemapping operator. // Reinhard tonemapping operator.
// see: "Photographic Tone Reproduction for Digital Images", eq. 4 // see: "Photographic Tone Reproduction for Digital Images", eq. 4

View File

@ -44,7 +44,7 @@ namespace Prism
PhysicsMaterial() = default; PhysicsMaterial() = default;
PhysicsMaterial(float staticFriction, float dynamicFriction, float bounciness) PhysicsMaterial(const float staticFriction, const float dynamicFriction, const float bounciness)
: StaticFriction(staticFriction), DynamicFriction(dynamicFriction), Bounciness(bounciness) : StaticFriction(staticFriction), DynamicFriction(dynamicFriction), Bounciness(bounciness)
{ {
Type = AssetType::PhysicsMaterial; Type = AssetType::PhysicsMaterial;

View File

@ -41,11 +41,10 @@ namespace Prism
ScriptEngine::Init("assets/scripts/ExampleApp.dll"); ScriptEngine::Init("assets/scripts/ExampleApp.dll");
Physics3D::Init(); Physics3D::Init();
Renderer::Init();
Renderer::WaitAndRender();
AssetTypes::Init(); AssetTypes::Init();
AssetsManager::Init(); AssetsManager::Init();
Renderer::Init();
Renderer::WaitAndRender();
} }
Application::~Application() Application::~Application()

View File

@ -27,6 +27,8 @@ namespace Prism
return Elapsed() * 1000.0f; return Elapsed() * 1000.0f;
} }
operator float() const { return Elapsed(); }
private: private:
std::chrono::time_point<std::chrono::high_resolution_clock> m_Start; std::chrono::time_point<std::chrono::high_resolution_clock> m_Start;
}; };

View File

@ -188,4 +188,15 @@ namespace Prism
{ {
glLineWidth(thickness); glLineWidth(thickness);
} }
void RendererAPI::DispatchCompute(int x, int y, int z)
{
glDispatchCompute(x, y, z);
}
void RendererAPI::MemoryBarrier(const int barrier)
{
glMemoryBarrier(barrier);
}
} }

View File

@ -0,0 +1,73 @@
//
// Created by Atdunbg on 2026/3/12.
//
#include "OpenGLStorageBuffer.h"
#include "Prism/Renderer/Renderer.h"
#include <glad/glad.h>
namespace Prism {
static GLenum UsageToGL(const StorageBufferUsage usage)
{
switch (usage)
{
case StorageBufferUsage::Static: return GL_STATIC_DRAW;
case StorageBufferUsage::Dynamic: return GL_DYNAMIC_DRAW;
case StorageBufferUsage::Stream: return GL_STREAM_DRAW;
case StorageBufferUsage::DynamicCopy: return GL_DYNAMIC_COPY;
default: return GL_DYNAMIC_DRAW;
}
}
OpenGLStorageBuffer::OpenGLStorageBuffer(uint32_t size, const void* data, StorageBufferUsage usage)
: m_Size(size), m_Usage(usage)
{
Renderer::Submit([this, data]() {
glCreateBuffers(1, &m_RendererID);
glNamedBufferData(m_RendererID, m_Size, data, UsageToGL(m_Usage));
});
}
OpenGLStorageBuffer::~OpenGLStorageBuffer()
{
Renderer::Submit([this]() {
glDeleteBuffers(1, &m_RendererID);
});
}
void OpenGLStorageBuffer::Bind() const
{
Renderer::Submit([this]() {
glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_RendererID);
});
}
void OpenGLStorageBuffer::Unbind() const
{
Renderer::Submit([]() {
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
});
}
void OpenGLStorageBuffer::BindBase(uint32_t index) const
{
Renderer::Submit([this, index]() {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, index, m_RendererID);
});
}
void OpenGLStorageBuffer::SetData(const void* data, uint32_t size, uint32_t offset)
{
Renderer::Submit([this, data, size, offset]() {
glNamedBufferSubData(m_RendererID, offset, size, data);
});
}
void OpenGLStorageBuffer::GetData(void* outData, const uint32_t size, const uint32_t offset) const
{
glGetNamedBufferSubData(m_RendererID, offset, size, outData);
}
}

View File

@ -0,0 +1,35 @@
//
// Created by Atdunbg on 2026/3/12.
//
#ifndef PRISM_OPENGLSTORAGEBUFFER_H
#define PRISM_OPENGLSTORAGEBUFFER_H
#include "Prism/Renderer/StorageBuffer.h"
namespace Prism {
class OpenGLStorageBuffer : public StorageBuffer
{
public:
OpenGLStorageBuffer(uint32_t size, const void* data, StorageBufferUsage usage);
virtual ~OpenGLStorageBuffer();
virtual void Bind() const override;
virtual void Unbind() const override;
virtual void BindBase(uint32_t index) const override;
virtual void SetData(const void* data, uint32_t size, uint32_t offset = 0) override;
virtual void GetData(void* outData, uint32_t size, uint32_t offset = 0) const override;
virtual uint32_t GetRendererID() const override { return m_RendererID; }
virtual uint32_t GetSize() const override { return m_Size; }
private:
uint32_t m_RendererID = 0;
uint32_t m_Size = 0;
StorageBufferUsage m_Usage;
};
}
#endif //PRISM_OPENGLSTORAGEBUFFER_H

View File

@ -253,6 +253,22 @@ namespace Prism
} }
} }
void Renderer::DispatchCompute(int x, int y, int z)
{
Submit([x, y, z]()
{
RendererAPI::DispatchCompute(x, y, z);
});
}
void Renderer::MemoryBarrier(int barrier)
{
Submit([barrier]()
{
RendererAPI::MemoryBarrier(barrier);
});
}
void Renderer::DrawAABB(const AABB& aabb, const glm::mat4& transform, const glm::vec4& color) void Renderer::DrawAABB(const AABB& aabb, const glm::mat4& transform, const glm::vec4& color)
{ {
glm::vec4 min = { aabb.Min.x, aabb.Min.y, aabb.Min.z, 1.0f }; glm::vec4 min = { aabb.Min.x, aabb.Min.y, aabb.Min.z, 1.0f };

View File

@ -59,6 +59,9 @@ namespace Prism
static void DrawAABB(const AABB& aabb, const glm::mat4& transform, const glm::vec4& color = glm::vec4(1.0f)); static void DrawAABB(const AABB& aabb, const glm::mat4& transform, const glm::vec4& color = glm::vec4(1.0f));
static void DrawAABB(const Ref<Mesh>& mesh,const glm::mat4& transform, const glm::vec4& color = glm::vec4(1.0f)); static void DrawAABB(const Ref<Mesh>& mesh,const glm::mat4& transform, const glm::vec4& color = glm::vec4(1.0f));
static void DispatchCompute(int x, int y, int z);
static void MemoryBarrier(int barrier);
private: private:
static RenderCommandQueue& GetRenderCommandQueue(); static RenderCommandQueue& GetRenderCommandQueue();
}; };

View File

@ -11,6 +11,7 @@
#include "Renderer.h" #include "Renderer.h"
#include "Renderer2D.h" #include "Renderer2D.h"
#include "SceneRenderer.h" #include "SceneRenderer.h"
#include "StorageBuffer.h"
#include "Prism/Scene/Components.h" #include "Prism/Scene/Components.h"
namespace Prism namespace Prism
@ -39,18 +40,28 @@ namespace Prism
Ref<RenderPass> BloomBlurPass[2]; Ref<RenderPass> BloomBlurPass[2];
Ref<RenderPass> BloomBlendPass; Ref<RenderPass> BloomBlendPass;
// Auto exposure
struct AutoExposureData struct AutoExposureData
{ {
Ref<RenderPass> LuminancePass; Ref<RenderPass> LuminancePass;
float CurrentExposure = 1.0f;
bool EnableAutoExposure = true; bool EnableAutoExposure = true;
float Key = 0.11f; // middle gray float Key = 0.11f;
float AdaptationSpeed = 5.0f; // stops per second
Timer ExposureTimer; Timer ExposureTimer;
float MaxExposure = 5.0f; float MaxExposure = 5.0f;
float MinExposure = 0.01f;
// 直方图模式参数
float SpeedUp = 5.0f;
float SpeedDown = 2.0f;
float LowPercent = 70.0f;
float HighPercent = 95.0f;
float LogMin = -4.0f;
float LogMax = 4.0f;
Ref<StorageBuffer> HistogramSSBO;
Ref<StorageBuffer> ExposureSSBO;
Ref<Shader> HistogramCS;
Ref<Shader> ExposureCS;
} AutoExposureData; } AutoExposureData;
Ref<Shader> ShadowMapShader, ShadowMapAnimShader; Ref<Shader> ShadowMapShader, ShadowMapAnimShader;
@ -134,6 +145,8 @@ namespace Prism
static Renderer3DStats s_Stats; static Renderer3DStats s_Stats;
void Renderer3D::Init() void Renderer3D::Init()
{
//////////////// GeoPass ////////////////
{ {
FramebufferSpecification geoFramebufferSpec; FramebufferSpecification geoFramebufferSpec;
geoFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F, FramebufferTextureFormat::DEPTH24STENCIL8 }; geoFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F, FramebufferTextureFormat::DEPTH24STENCIL8 };
@ -143,17 +156,12 @@ namespace Prism
RenderPassSpecification geoRenderPassSpec; RenderPassSpecification geoRenderPassSpec;
geoRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(geoFramebufferSpec); geoRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(geoFramebufferSpec);
s_Data.GeoPass = RenderPass::Create(geoRenderPassSpec); s_Data.GeoPass = RenderPass::Create(geoRenderPassSpec);
}
/////////////////////////////////////////////
/*
FramebufferSpecification compFramebufferSpec;
compFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA8 , FramebufferTextureFormat::RGBA8};
compFramebufferSpec.ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f };
RenderPassSpecification compRenderPassSpec;
compRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(compFramebufferSpec);
s_Data.CompositePass = RenderPass::Create(compRenderPassSpec);
*/
//////////////// BloomPass ////////////////
{
FramebufferSpecification bloomBlurFramebufferSpec; FramebufferSpecification bloomBlurFramebufferSpec;
bloomBlurFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F , FramebufferTextureFormat::RGBA8}; bloomBlurFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F , FramebufferTextureFormat::RGBA8};
bloomBlurFramebufferSpec.ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f }; bloomBlurFramebufferSpec.ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f };
@ -172,11 +180,13 @@ namespace Prism
bloomBlendRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(bloomBlendFramebufferSpec); bloomBlendRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(bloomBlendFramebufferSpec);
s_Data.BloomBlendPass = RenderPass::Create(bloomBlendRenderPassSpec); s_Data.BloomBlendPass = RenderPass::Create(bloomBlendRenderPassSpec);
s_Data.CompositeShader = Shader::Create("assets/shaders/SceneComposite.glsl");
s_Data.BloomBlurShader = Shader::Create("assets/shaders/BloomBlur.glsl"); s_Data.BloomBlurShader = Shader::Create("assets/shaders/BloomBlur.glsl");
s_Data.BloomBlendShader = Shader::Create("assets/shaders/BloomBlend.glsl"); s_Data.BloomBlendShader = Shader::Create("assets/shaders/BloomBlend.glsl");
s_Data.BRDFLUT = Texture2D::Create("assets/textures/BRDF_LUT.tga"); }
/////////////////////////////////////////////
//////////////// AutoExposure ////////////////
{
// Luminance pass: used to compute average scene luminance (for auto exposure) // Luminance pass: used to compute average scene luminance (for auto exposure)
FramebufferSpecification luminanceFramebufferSpec; FramebufferSpecification luminanceFramebufferSpec;
luminanceFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F }; luminanceFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F };
@ -186,34 +196,19 @@ namespace Prism
luminanceRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(luminanceFramebufferSpec); luminanceRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(luminanceFramebufferSpec);
s_Data.AutoExposureData.LuminancePass = RenderPass::Create(luminanceRenderPassSpec); s_Data.AutoExposureData.LuminancePass = RenderPass::Create(luminanceRenderPassSpec);
// Grid constexpr uint32_t histogramSize = 64 * sizeof(uint32_t);
// const auto gridShader = Shader::Create("assets/shaders/Grid.glsl"); std::vector<uint32_t> zeros(64, 0);
const auto gridShader = Shader::Create("assets/shaders/InfiniteGrid.glsl"); s_Data.AutoExposureData.HistogramSSBO = StorageBuffer::Create(histogramSize, zeros.data());
s_Data.GridData.GridMaterial = MaterialInstance::Create(Material::Create(gridShader)); float initialExposure = 1.0f;
// constexpr float gridScale = 16.025f; s_Data.AutoExposureData.ExposureSSBO = StorageBuffer::Create(sizeof(float), &initialExposure);
// constexpr float gridSize = 0.025f; s_Data.AutoExposureData.HistogramCS = Shader::Create("assets/shaders/Histogram.glsl");
// s_Data.GridMaterial->Set("u_Scale", gridScale); s_Data.AutoExposureData.ExposureCS = Shader::Create("assets/shaders/Exposure.glsl");
// s_Data.GridMaterial->Set("u_Res", gridSize); }
s_Data.GridData.GridMaterial->SetFlag(MaterialFlag::TwoSided, true); /////////////////////////////////////////////
// outline
const auto outlineShader = Shader::Create("assets/shaders/Outline.glsl");
s_Data.OutlineMaterial = MaterialInstance::Create(Material::Create(outlineShader));
s_Data.OutlineMaterial->SetFlag(MaterialFlag::DepthTest, false);
// Collider
const auto colliderShader = Shader::Create("assets/shaders/Collider.glsl");
s_Data.ColliderMaterial = MaterialInstance::Create(Material::Create(colliderShader));
s_Data.ColliderMaterial->SetFlag(MaterialFlag::DepthTest, false);
auto outlineAnimShader = Shader::Create("assets/shaders/Outline_Anim.glsl");
s_Data.OutlineAnimMaterial = MaterialInstance::Create(Material::Create(outlineAnimShader));
s_Data.OutlineAnimMaterial->SetFlag(MaterialFlag::DepthTest, false);
// Shadow Map
s_Data.ShadowMapShader = Shader::Create("assets/shaders/ShadowMap.glsl");
s_Data.ShadowMapAnimShader = Shader::Create("assets/shaders/ShadowMap_Anim.glsl");
//////////////// ShadowMapPass ////////////////
{
FramebufferSpecification shadowMapFramebufferSpec; FramebufferSpecification shadowMapFramebufferSpec;
shadowMapFramebufferSpec.Width = 4096; shadowMapFramebufferSpec.Width = 4096;
shadowMapFramebufferSpec.Height = 4096; shadowMapFramebufferSpec.Height = 4096;
@ -229,13 +224,41 @@ namespace Prism
{ {
glGenSamplers(1, &s_Data.ShadowMapSampler); glGenSamplers(1, &s_Data.ShadowMapSampler);
// Setup the shadowmap depth sampler // Setup the shadowMap depth sampler
glSamplerParameteri(s_Data.ShadowMapSampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glSamplerParameteri(s_Data.ShadowMapSampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(s_Data.ShadowMapSampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glSamplerParameteri(s_Data.ShadowMapSampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(s_Data.ShadowMapSampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glSamplerParameteri(s_Data.ShadowMapSampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(s_Data.ShadowMapSampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glSamplerParameteri(s_Data.ShadowMapSampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}); });
}
/////////////////////////////////////////////
s_Data.CompositeShader = Shader::Create("assets/shaders/SceneComposite.glsl");
s_Data.BRDFLUT = Texture2D::Create("assets/textures/BRDF_LUT.tga");
// Grid
// const auto gridShader = Shader::Create("assets/shaders/Grid.glsl");
const auto gridShader = Shader::Create("assets/shaders/InfiniteGrid.glsl");
s_Data.GridData.GridMaterial = MaterialInstance::Create(Material::Create(gridShader));
s_Data.GridData.GridMaterial->SetFlag(MaterialFlag::TwoSided, true);
// outline
const auto outlineShader = Shader::Create("assets/shaders/Outline.glsl");
s_Data.OutlineMaterial = MaterialInstance::Create(Material::Create(outlineShader));
s_Data.OutlineMaterial->SetFlag(MaterialFlag::DepthTest, false);
// outlineAnim
auto outlineAnimShader = Shader::Create("assets/shaders/Outline_Anim.glsl");
s_Data.OutlineAnimMaterial = MaterialInstance::Create(Material::Create(outlineAnimShader));
s_Data.OutlineAnimMaterial->SetFlag(MaterialFlag::DepthTest, false);
// Collider
const auto colliderShader = Shader::Create("assets/shaders/Collider.glsl");
s_Data.ColliderMaterial = MaterialInstance::Create(Material::Create(colliderShader));
s_Data.ColliderMaterial->SetFlag(MaterialFlag::DepthTest, false);
s_Data.ShadowMapShader = Shader::Create("assets/shaders/ShadowMap.glsl");
s_Data.ShadowMapAnimShader = Shader::Create("assets/shaders/ShadowMap_Anim.glsl");
} }
void Renderer3D::SetViewportSize(uint32_t width, uint32_t height) void Renderer3D::SetViewportSize(uint32_t width, uint32_t height)
@ -429,7 +452,10 @@ namespace Prism
Renderer::BeginRenderPass(outRenderPass); Renderer::BeginRenderPass(outRenderPass);
s_Data.CompositeShader->Bind(); s_Data.CompositeShader->Bind();
s_Data.CompositeShader->SetFloat("u_Exposure", s_Data.AutoExposureData.EnableAutoExposure ? s_Data.AutoExposureData.CurrentExposure : s_Data.SceneData.SceneCamera.Camera.GetExposure());
s_Data.AutoExposureData.ExposureSSBO->BindBase(2);
s_Data.CompositeShader->SetBool("u_EnableAutoExposure",s_Data.AutoExposureData.EnableAutoExposure);
s_Data.CompositeShader->SetFloat("u_ManualExposure", s_Data.SceneData.SceneCamera.Camera.GetExposure());
s_Data.CompositeShader->SetInt("u_TextureSamples", s_Data.GeoPass->GetSpecification().TargetFramebuffer->GetSpecification().Samples); s_Data.CompositeShader->SetInt("u_TextureSamples", s_Data.GeoPass->GetSpecification().TargetFramebuffer->GetSpecification().Samples);
s_Data.CompositeShader->SetFloat("u_EnableBloom", s_Data.EnableBloom); s_Data.CompositeShader->SetFloat("u_EnableBloom", s_Data.EnableBloom);
@ -790,124 +816,71 @@ namespace Prism
void Renderer3D::AutoExposurePass() void Renderer3D::AutoExposurePass()
{ {
if (!s_Data.AutoExposureData.EnableAutoExposure) auto& ae = s_Data.AutoExposureData;
if (!ae.EnableAutoExposure)
return; return;
auto srcFB = s_Data.GeoPass->GetSpecification().TargetFramebuffer; auto srcFB = s_Data.GeoPass->GetSpecification().TargetFramebuffer;
auto dstFB = s_Data.AutoExposureData.LuminancePass->GetSpecification().TargetFramebuffer; auto dstFB = ae.LuminancePass->GetSpecification().TargetFramebuffer;
if (!srcFB || !dstFB) if (!srcFB || !dstFB) return;
return;
const uint32_t dstID = dstFB->GetColorAttachmentRendererID(); float dt = ae.ExposureTimer;
const uint32_t width = dstFB->GetWidth(); ae.ExposureTimer.Reset();
const uint32_t height = dstFB->GetHeight();
Renderer::Submit([dstID, width, height, srcFB]() mutable Renderer::Submit([srcFB, dstFB, logMin = ae.LogMin, logMax = ae.LogMax, histCS = ae.HistogramCS, histSSBO = ae.HistogramSSBO]() {
{ glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFB->GetRendererID());
// Use framebuffer blit to resolve multisampled source into non-multisampled luminance target. glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFB->GetRendererID());
const GLuint srcFBO = srcFB->GetRendererID();
const GLuint dstFBO = s_Data.AutoExposureData.LuminancePass->GetSpecification().TargetFramebuffer->GetRendererID();
// Bind read/draw FBOs and blit color attachment 0
glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFBO);
glReadBuffer(GL_COLOR_ATTACHMENT0); glReadBuffer(GL_COLOR_ATTACHMENT0);
glDrawBuffer(GL_COLOR_ATTACHMENT0); glDrawBuffer(GL_COLOR_ATTACHMENT0);
glBlitFramebuffer(0, 0, srcFB->GetWidth(), srcFB->GetHeight(),
// Source size — try to use srcFB dimensions if available 0, 0, dstFB->GetWidth(), dstFB->GetHeight(),
const int srcWidth = srcFB->GetWidth(); GL_COLOR_BUFFER_BIT, GL_NEAREST);
const int srcHeight = srcFB->GetHeight();
glBlitFramebuffer(0, 0, srcWidth, srcHeight, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
// Unbind
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// Generate mipmaps so the smallest mip is the average color const uint32_t zero = 0;
glGenerateTextureMipmap(dstID); glClearNamedBufferData(histSSBO->GetRendererID(), GL_R32UI, GL_RED, GL_UNSIGNED_INT, &zero);
// Determine highest mip level glUseProgram(histCS->GetRendererID());
int maxLevel = (int)std::floor(std::log2((float)std::max(width, height))); glBindTextureUnit(0, dstFB->GetColorAttachmentRendererID());
if (maxLevel < 0) maxLevel = 0; glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, histSSBO->GetRendererID());
const GLint locLogMin = glGetUniformLocation(histCS->GetRendererID(), "u_LogMin");
const GLint locLogMax = glGetUniformLocation(histCS->GetRendererID(), "u_LogMax");
if (locLogMin != -1) glProgramUniform1f(histCS->GetRendererID(), locLogMin, logMin);
if (locLogMax != -1) glProgramUniform1f(histCS->GetRendererID(), locLogMax, logMax);
GLint levelWidth = 0, levelHeight = 0; const uint32_t groupsX = (dstFB->GetWidth() + 15) / 16;
glGetTextureLevelParameteriv(dstID, maxLevel, GL_TEXTURE_WIDTH, &levelWidth); const uint32_t groupsY = (dstFB->GetHeight() + 15) / 16;
glGetTextureLevelParameteriv(dstID, maxLevel, GL_TEXTURE_HEIGHT, &levelHeight); glDispatchCompute(groupsX, groupsY, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
if (levelWidth == 0 || levelHeight == 0) return;
const int bufSize = levelWidth * levelHeight * 4 * sizeof(float);
std::vector<float> pixelData(bufSize / sizeof(float));
glGetTextureImage(dstID, maxLevel, GL_RGBA, GL_FLOAT, bufSize, pixelData.data());
// Sanitize pixel values (handle NaN/Inf or negative values coming from GPU)
for (int i = 0; i < 3; ++i)
{
if (!std::isfinite(pixelData[i]) || pixelData[i] < 0.0f)
pixelData[i] = 0.0f;
}
// Convert to luminance
float lum = 0.2126f * pixelData[0] + 0.7152f * pixelData[1] + 0.0722f * pixelData[2];
if (!std::isfinite(lum) || lum <= 0.0f)
lum = 1e-6f; // fallback minimum luminance
// Compute desired exposure (simple key/avg approach)
const float key = s_Data.AutoExposureData.Key;
constexpr float minLum = 1e-6f;
float desiredExposure = key / std::max(lum, minLum);
desiredExposure = std::clamp(desiredExposure, 0.0001f, s_Data.AutoExposureData.MaxExposure);
// Adapt exposure over time (exponential)
const float dt = s_Data.AutoExposureData.ExposureTimer.ElapsedMillis() / 1000.0f;
s_Data.AutoExposureData.ExposureTimer.Reset();
const float tau = s_Data.AutoExposureData.AdaptationSpeed;
const float adaptFactor = 1.0f - std::exp(-tau * dt);
s_Data.AutoExposureData.CurrentExposure = s_Data.AutoExposureData.CurrentExposure + (desiredExposure - s_Data.AutoExposureData.CurrentExposure) * adaptFactor;
s_Data.AutoExposureData.CurrentExposure = std::clamp(s_Data.AutoExposureData.CurrentExposure, 0.0001f, s_Data.AutoExposureData.MaxExposure);
// Write exposure directly into composite shader program uniform so the subsequent composite pass uses it
/*
if (const GLuint prog = s_Data.CompositeShader->GetRendererID())
{
const GLint loc = glGetUniformLocation(prog, "u_Exposure");
if (loc >= 0)
glProgramUniform1f(prog, loc, s_Data.CurrentExposure);
}
*/
}); });
} Renderer::Submit([&ae, dt]() {
glUseProgram(ae.ExposureCS->GetRendererID());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ae.HistogramSSBO->GetRendererID());
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, ae.ExposureSSBO->GetRendererID());
/* auto setUniform = [&](const char* name, const float value) {
void Renderer3D::CompositePass() const GLint loc = glGetUniformLocation(ae.ExposureCS->GetRendererID(), name);
{ if (loc != -1) glProgramUniform1f(ae.ExposureCS->GetRendererID(), loc, value);
auto& compositeBuffer = s_Data.CompositePass->GetSpecification().TargetFramebuffer; };
Renderer::BeginRenderPass(s_Data.CompositePass); setUniform("u_SpeedUp", ae.SpeedUp);
s_Data.CompositeShader->Bind(); setUniform("u_SpeedDown", ae.SpeedDown);
s_Data.CompositeShader->SetFloat("u_Exposure", s_Data.AutoExposureData.EnableAutoExposure ? s_Data.AutoExposureData.CurrentExposure : s_Data.SceneData.SceneCamera.Camera.GetExposure()); setUniform("u_Key", ae.Key);
s_Data.CompositeShader->SetInt("u_TextureSamples", s_Data.GeoPass->GetSpecification().TargetFramebuffer->GetSpecification().Samples); setUniform("u_LowPercent", ae.LowPercent);
// s_Data.CompositeShader->SetFloat2("u_ViewportSize", glm::vec2(compositeBuffer->GetWidth(), compositeBuffer->GetHeight())); setUniform("u_HighPercent", ae.HighPercent);
// s_Data.CompositeShader->SetFloat2("u_FocusPoint", s_Data.FocusPoint); setUniform("u_MinExposure", ae.MinExposure);
s_Data.CompositeShader->SetInt("u_TextureSamples", s_Data.GeoPass->GetSpecification().TargetFramebuffer->GetSpecification().Samples); setUniform("u_MaxExposure", ae.MaxExposure);
// s_Data.CompositeShader->SetFloat("u_BloomThreshold", s_Data.BloomThreshold); setUniform("u_DeltaTime", dt);
s_Data.CompositeShader->SetFloat("u_EnableBloom", s_Data.EnableBloom); setUniform("u_LogMin", ae.LogMin);
setUniform("u_LogMax", ae.LogMax);
s_Data.GeoPass->GetSpecification().TargetFramebuffer->BindTexture(); glDispatchCompute(1, 1, 1);
Renderer::Submit([]() glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
{
glBindTextureUnit(1, s_Data.GeoPass->GetSpecification().TargetFramebuffer->GetDepthAttachmentRendererID());
}); });
Renderer::SubmitFullscreenQuad(nullptr);
Renderer::EndRenderPass();
} }
*/
/* /*
void Renderer3D::BloomBlurPass() void Renderer3D::BloomBlurPass()
@ -1111,7 +1084,7 @@ namespace Prism
// TODO: this will not be hardcode // TODO: this will not be hardcode
const glm::vec3 lightDir = glm::normalize(directionalLights[0].Direction); // 光线方向(从光源指向场景) const glm::vec3 lightDir = glm::normalize(directionalLights[0].Direction); // 光线方向(从光源指向场景)
glm::vec3 lightPos = lightDir * 10.0f; glm::vec3 lightPos = lightDir * 100.0f;
glm::mat4 lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); glm::mat4 lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
@ -1225,6 +1198,7 @@ namespace Prism
UI::EndTreeNode(); UI::EndTreeNode();
} }
/*
if (UI::BeginTreeNode("Auto Exposure", false)) if (UI::BeginTreeNode("Auto Exposure", false))
{ {
UI::BeginPropertyGrid(); UI::BeginPropertyGrid();
@ -1236,6 +1210,36 @@ namespace Prism
UI::EndTreeNode(); UI::EndTreeNode();
} }
*/
if (UI::BeginTreeNode("Auto Exposure", false))
{
UI::BeginPropertyGrid();
UI::Property("Enable Auto Exposure", s_Data.AutoExposureData.EnableAutoExposure);
if (s_Data.AutoExposureData.EnableAutoExposure)
{
UI::Property("Key (middle gray)", s_Data.AutoExposureData.Key, 0.001f, 0.001f, 2.5f);
UI::Separator();
UI::Property("Speed Up (brighten)", s_Data.AutoExposureData.SpeedUp, 0.1f, 0.1f, 20.0f);
UI::Property("Speed Down (darken)", s_Data.AutoExposureData.SpeedDown, 0.1f, 0.1f, 20.0f);
UI::Separator();
UI::Property("Low Percent", s_Data.AutoExposureData.LowPercent, 1.0f, 0.0f, 100.0f);
UI::Property("High Percent", s_Data.AutoExposureData.HighPercent, 1.0f, 0.0f, 100.0f);
UI::Separator();
UI::Property("Min Exposure", s_Data.AutoExposureData.MinExposure, 0.01f, 0.0f, 10.0f);
UI::Property("Max Exposure", s_Data.AutoExposureData.MaxExposure, 0.1f, 0.0f, 20.0f);
UI::Separator();
UI::Property("Log Min", s_Data.AutoExposureData.LogMin, 0.1f, -10.0f, 10.0f);
UI::Property("Log Max", s_Data.AutoExposureData.LogMax, 0.1f, -10.0f, 10.0f);
}
UI::EndPropertyGrid();
UI::EndTreeNode();
}
ImGui::End(); ImGui::End();

View File

@ -52,6 +52,9 @@ namespace Prism
return capabilities; return capabilities;
} }
static void DispatchCompute(int x, int y, int z);
static void MemoryBarrier(int barrier);
private: private:
static RendererAPIType s_CurrentRendererAPI; static RendererAPIType s_CurrentRendererAPI;
}; };

View File

@ -0,0 +1,25 @@
//
// Created by Atdunbg on 2026/3/12.
//
#include "StorageBuffer.h"
#include "RendererAPI.h"
#include "Prism/Platform/OpenGL/OpenGLStorageBuffer.h"
namespace Prism
{
Ref<StorageBuffer> StorageBuffer::Create(uint32_t size, const void* data, StorageBufferUsage usage)
{
Ref<StorageBuffer> result = nullptr;
switch (RendererAPI::Current())
{
case RendererAPIType::None: return nullptr;
case RendererAPIType::OpenGL: result = Ref<OpenGLStorageBuffer>::Create(size, data, usage); break;
}
return result;
}
}

View File

@ -0,0 +1,42 @@
//
// Created by Atdunbg on 2026/3/12.
//
#ifndef PRISM_STORAGEBUFFER_H
#define PRISM_STORAGEBUFFER_H
#include "Prism/Core/Ref.h"
namespace Prism
{
enum class StorageBufferUsage
{
Static = 0,
Dynamic,
Stream,
DynamicCopy
};
class StorageBuffer : public RefCounted
{
public:
virtual ~StorageBuffer() = default;
static Ref<StorageBuffer> Create(uint32_t size, const void* data = nullptr, StorageBufferUsage usage = StorageBufferUsage::Dynamic);
virtual void Bind() const = 0;
virtual void Unbind() const = 0;
virtual void BindBase(uint32_t index) const = 0;
virtual void SetData(const void* data, uint32_t size, uint32_t offset = 0) = 0;
virtual void GetData(void* outData, uint32_t size, uint32_t offset = 0) const = 0;
virtual uint32_t GetRendererID() const = 0;
virtual uint32_t GetSize() const = 0;
protected:
StorageBuffer() = default;
};
}
#endif //PRISM_STORAGEBUFFER_H

View File

@ -95,8 +95,8 @@ ScriptComponent
2. **配置依赖** 2. **配置依赖**
- PhysX SDK: Prism/CmakeLists.txt内记录了是用的PhysX的构建配置建议手动按照配置构建SDK构建好后无需移动文件项目可以自动链接库 - PhysX SDK: Prism/CmakeLists.txt内记录了是用的PhysX的构建配置建议手动按照配置构建SDK构建好后无需移动文件项目可以自动链接库
3. **编译** 3. **编译**:
**Cmake**: - **Cmake**:
```bash ```bash
cd Prism cd Prism
cmake -B build -G Ninja cmake -B build -G Ninja
@ -104,10 +104,10 @@ cmake --build build --target PrismEditor --config Release -j10
``` ```
**Visual Studio**: **Visual Studio**:
visual Studio启用Cmake。 然后点击project下拉框找到 PrismEditor构建。 - visual Studio启用Cmake。 然后点击project下拉框找到 PrismEditor构建。
**JetBrain Clion**: **JetBrain Clion**:
clion 会自动识别Cmake, 运行调试配置找到PrismEditor构建。 - clion 会自动识别Cmake, 运行调试配置找到PrismEditor构建。
--- ---