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 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 bool u_EnableBloom;
@ -64,7 +71,10 @@ void main()
color += bloomColor;
}
color *= u_Exposure;
if(u_EnableAutoExposure)
color *= u_Exposure;
else
color *= u_ManualExposure;
// Reinhard tonemapping operator.
// see: "Photographic Tone Reproduction for Digital Images", eq. 4

View File

@ -44,7 +44,7 @@ namespace Prism
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)
{
Type = AssetType::PhysicsMaterial;

View File

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

View File

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

View File

@ -188,4 +188,15 @@ namespace Prism
{
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)
{
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 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:
static RenderCommandQueue& GetRenderCommandQueue();
};

View File

@ -11,6 +11,7 @@
#include "Renderer.h"
#include "Renderer2D.h"
#include "SceneRenderer.h"
#include "StorageBuffer.h"
#include "Prism/Scene/Components.h"
namespace Prism
@ -39,19 +40,29 @@ namespace Prism
Ref<RenderPass> BloomBlurPass[2];
Ref<RenderPass> BloomBlendPass;
// Auto exposure
struct AutoExposureData
{
Ref<RenderPass> LuminancePass;
float CurrentExposure = 1.0f;
bool EnableAutoExposure = true;
float Key = 0.11f; // middle gray
float AdaptationSpeed = 5.0f; // stops per second
float Key = 0.11f;
Timer ExposureTimer;
float MaxExposure = 5.0f;
}AutoExposureData;
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;
Ref<Shader> ShadowMapShader, ShadowMapAnimShader;
Ref<RenderPass> ShadowMapRenderPass;
@ -135,65 +146,100 @@ namespace Prism
void Renderer3D::Init()
{
FramebufferSpecification geoFramebufferSpec;
geoFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F, FramebufferTextureFormat::DEPTH24STENCIL8 };
geoFramebufferSpec.Samples = 8;
geoFramebufferSpec.ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f };
//////////////// GeoPass ////////////////
{
FramebufferSpecification geoFramebufferSpec;
geoFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F, FramebufferTextureFormat::DEPTH24STENCIL8 };
geoFramebufferSpec.Samples = 8;
geoFramebufferSpec.ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f };
RenderPassSpecification geoRenderPassSpec;
geoRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(geoFramebufferSpec);
s_Data.GeoPass = RenderPass::Create(geoRenderPassSpec);
RenderPassSpecification geoRenderPassSpec;
geoRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(geoFramebufferSpec);
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;
bloomBlurFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F , FramebufferTextureFormat::RGBA8};
bloomBlurFramebufferSpec.ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f };
FramebufferSpecification bloomBlurFramebufferSpec;
bloomBlurFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F , FramebufferTextureFormat::RGBA8};
bloomBlurFramebufferSpec.ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f };
RenderPassSpecification bloomBlurRenderPassSpec;
bloomBlurRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(bloomBlurFramebufferSpec);
s_Data.BloomBlurPass[0] = RenderPass::Create(bloomBlurRenderPassSpec);
bloomBlurRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(bloomBlurFramebufferSpec);
s_Data.BloomBlurPass[1] = RenderPass::Create(bloomBlurRenderPassSpec);
RenderPassSpecification bloomBlurRenderPassSpec;
bloomBlurRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(bloomBlurFramebufferSpec);
s_Data.BloomBlurPass[0] = RenderPass::Create(bloomBlurRenderPassSpec);
bloomBlurRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(bloomBlurFramebufferSpec);
s_Data.BloomBlurPass[1] = RenderPass::Create(bloomBlurRenderPassSpec);
FramebufferSpecification bloomBlendFramebufferSpec;
bloomBlendFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA8 };
bloomBlendFramebufferSpec.ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f };
FramebufferSpecification bloomBlendFramebufferSpec;
bloomBlendFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA8 };
bloomBlendFramebufferSpec.ClearColor = { 0.1f, 0.1f, 0.1f, 1.0f };
RenderPassSpecification bloomBlendRenderPassSpec;
bloomBlendRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(bloomBlendFramebufferSpec);
s_Data.BloomBlendPass = RenderPass::Create(bloomBlendRenderPassSpec);
RenderPassSpecification bloomBlendRenderPassSpec;
bloomBlendRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(bloomBlendFramebufferSpec);
s_Data.BloomBlendPass = RenderPass::Create(bloomBlendRenderPassSpec);
s_Data.BloomBlurShader = Shader::Create("assets/shaders/BloomBlur.glsl");
s_Data.BloomBlendShader = Shader::Create("assets/shaders/BloomBlend.glsl");
}
/////////////////////////////////////////////
//////////////// AutoExposure ////////////////
{
// Luminance pass: used to compute average scene luminance (for auto exposure)
FramebufferSpecification luminanceFramebufferSpec;
luminanceFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F };
luminanceFramebufferSpec.ClearColor = { 0.0f, 0.0f, 0.0f, 0.0f };
RenderPassSpecification luminanceRenderPassSpec;
luminanceRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(luminanceFramebufferSpec);
s_Data.AutoExposureData.LuminancePass = RenderPass::Create(luminanceRenderPassSpec);
constexpr uint32_t histogramSize = 64 * sizeof(uint32_t);
std::vector<uint32_t> zeros(64, 0);
s_Data.AutoExposureData.HistogramSSBO = StorageBuffer::Create(histogramSize, zeros.data());
float initialExposure = 1.0f;
s_Data.AutoExposureData.ExposureSSBO = StorageBuffer::Create(sizeof(float), &initialExposure);
s_Data.AutoExposureData.HistogramCS = Shader::Create("assets/shaders/Histogram.glsl");
s_Data.AutoExposureData.ExposureCS = Shader::Create("assets/shaders/Exposure.glsl");
}
/////////////////////////////////////////////
//////////////// ShadowMapPass ////////////////
{
FramebufferSpecification shadowMapFramebufferSpec;
shadowMapFramebufferSpec.Width = 4096;
shadowMapFramebufferSpec.Height = 4096;
shadowMapFramebufferSpec.Attachments = { FramebufferTextureFormat::DEPTH32F };
shadowMapFramebufferSpec.ClearColor = { 0.0f, 0.0f, 0.0f, 0.0f };
shadowMapFramebufferSpec.NoResize = true;
RenderPassSpecification shadowMapRenderPassSpec;
shadowMapRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(shadowMapFramebufferSpec);
s_Data.ShadowMapRenderPass = RenderPass::Create(shadowMapRenderPassSpec);
Renderer::Submit([]()
{
glGenSamplers(1, &s_Data.ShadowMapSampler);
// Setup the shadowMap depth sampler
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_WRAP_S, 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.BloomBlurShader = Shader::Create("assets/shaders/BloomBlur.glsl");
s_Data.BloomBlendShader = Shader::Create("assets/shaders/BloomBlend.glsl");
s_Data.BRDFLUT = Texture2D::Create("assets/textures/BRDF_LUT.tga");
// Luminance pass: used to compute average scene luminance (for auto exposure)
FramebufferSpecification luminanceFramebufferSpec;
luminanceFramebufferSpec.Attachments = { FramebufferTextureFormat::RGBA16F };
luminanceFramebufferSpec.ClearColor = { 0.0f, 0.0f, 0.0f, 0.0f };
RenderPassSpecification luminanceRenderPassSpec;
luminanceRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(luminanceFramebufferSpec);
s_Data.AutoExposureData.LuminancePass = RenderPass::Create(luminanceRenderPassSpec);
// 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));
// constexpr float gridScale = 16.025f;
// constexpr float gridSize = 0.025f;
// s_Data.GridMaterial->Set("u_Scale", gridScale);
// s_Data.GridMaterial->Set("u_Res", gridSize);
s_Data.GridData.GridMaterial->SetFlag(MaterialFlag::TwoSided, true);
// outline
@ -201,41 +247,18 @@ namespace Prism
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);
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");
FramebufferSpecification shadowMapFramebufferSpec;
shadowMapFramebufferSpec.Width = 4096;
shadowMapFramebufferSpec.Height = 4096;
shadowMapFramebufferSpec.Attachments = { FramebufferTextureFormat::DEPTH32F };
shadowMapFramebufferSpec.ClearColor = { 0.0f, 0.0f, 0.0f, 0.0f };
shadowMapFramebufferSpec.NoResize = true;
RenderPassSpecification shadowMapRenderPassSpec;
shadowMapRenderPassSpec.TargetFramebuffer = FrameBuffer::Create(shadowMapFramebufferSpec);
s_Data.ShadowMapRenderPass = RenderPass::Create(shadowMapRenderPassSpec);
Renderer::Submit([]()
{
glGenSamplers(1, &s_Data.ShadowMapSampler);
// Setup the shadowmap depth sampler
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_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(s_Data.ShadowMapSampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
});
}
void Renderer3D::SetViewportSize(uint32_t width, uint32_t height)
@ -429,7 +452,10 @@ namespace Prism
Renderer::BeginRenderPass(outRenderPass);
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->SetFloat("u_EnableBloom", s_Data.EnableBloom);
@ -790,125 +816,72 @@ namespace Prism
void Renderer3D::AutoExposurePass()
{
if (!s_Data.AutoExposureData.EnableAutoExposure)
auto& ae = s_Data.AutoExposureData;
if (!ae.EnableAutoExposure)
return;
auto srcFB = s_Data.GeoPass->GetSpecification().TargetFramebuffer;
auto dstFB = s_Data.AutoExposureData.LuminancePass->GetSpecification().TargetFramebuffer;
if (!srcFB || !dstFB)
return;
auto dstFB = ae.LuminancePass->GetSpecification().TargetFramebuffer;
if (!srcFB || !dstFB) return;
const uint32_t dstID = dstFB->GetColorAttachmentRendererID();
const uint32_t width = dstFB->GetWidth();
const uint32_t height = dstFB->GetHeight();
float dt = ae.ExposureTimer;
ae.ExposureTimer.Reset();
Renderer::Submit([dstID, width, height, srcFB]() mutable
{
// Use framebuffer blit to resolve multisampled source into non-multisampled luminance target.
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);
Renderer::Submit([srcFB, dstFB, logMin = ae.LogMin, logMax = ae.LogMax, histCS = ae.HistogramCS, histSSBO = ae.HistogramSSBO]() {
glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFB->GetRendererID());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFB->GetRendererID());
glReadBuffer(GL_COLOR_ATTACHMENT0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
// Source size — try to use srcFB dimensions if available
const int srcWidth = srcFB->GetWidth();
const int srcHeight = srcFB->GetHeight();
glBlitFramebuffer(0, 0, srcWidth, srcHeight, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
// Unbind
glBlitFramebuffer(0, 0, srcFB->GetWidth(), srcFB->GetHeight(),
0, 0, dstFB->GetWidth(), dstFB->GetHeight(),
GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// Generate mipmaps so the smallest mip is the average color
glGenerateTextureMipmap(dstID);
const uint32_t zero = 0;
glClearNamedBufferData(histSSBO->GetRendererID(), GL_R32UI, GL_RED, GL_UNSIGNED_INT, &zero);
// Determine highest mip level
int maxLevel = (int)std::floor(std::log2((float)std::max(width, height)));
if (maxLevel < 0) maxLevel = 0;
glUseProgram(histCS->GetRendererID());
glBindTextureUnit(0, dstFB->GetColorAttachmentRendererID());
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;
glGetTextureLevelParameteriv(dstID, maxLevel, GL_TEXTURE_WIDTH, &levelWidth);
glGetTextureLevelParameteriv(dstID, maxLevel, GL_TEXTURE_HEIGHT, &levelHeight);
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);
}
*/
const uint32_t groupsX = (dstFB->GetWidth() + 15) / 16;
const uint32_t groupsY = (dstFB->GetHeight() + 15) / 16;
glDispatchCompute(groupsX, groupsY, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
});
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) {
const GLint loc = glGetUniformLocation(ae.ExposureCS->GetRendererID(), name);
if (loc != -1) glProgramUniform1f(ae.ExposureCS->GetRendererID(), loc, value);
};
setUniform("u_SpeedUp", ae.SpeedUp);
setUniform("u_SpeedDown", ae.SpeedDown);
setUniform("u_Key", ae.Key);
setUniform("u_LowPercent", ae.LowPercent);
setUniform("u_HighPercent", ae.HighPercent);
setUniform("u_MinExposure", ae.MinExposure);
setUniform("u_MaxExposure", ae.MaxExposure);
setUniform("u_DeltaTime", dt);
setUniform("u_LogMin", ae.LogMin);
setUniform("u_LogMax", ae.LogMax);
glDispatchCompute(1, 1, 1);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
});
}
/*
void Renderer3D::CompositePass()
{
auto& compositeBuffer = s_Data.CompositePass->GetSpecification().TargetFramebuffer;
Renderer::BeginRenderPass(s_Data.CompositePass);
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.CompositeShader->SetInt("u_TextureSamples", s_Data.GeoPass->GetSpecification().TargetFramebuffer->GetSpecification().Samples);
// s_Data.CompositeShader->SetFloat2("u_ViewportSize", glm::vec2(compositeBuffer->GetWidth(), compositeBuffer->GetHeight()));
// s_Data.CompositeShader->SetFloat2("u_FocusPoint", s_Data.FocusPoint);
s_Data.CompositeShader->SetInt("u_TextureSamples", s_Data.GeoPass->GetSpecification().TargetFramebuffer->GetSpecification().Samples);
// s_Data.CompositeShader->SetFloat("u_BloomThreshold", s_Data.BloomThreshold);
s_Data.CompositeShader->SetFloat("u_EnableBloom", s_Data.EnableBloom);
s_Data.GeoPass->GetSpecification().TargetFramebuffer->BindTexture();
Renderer::Submit([]()
{
glBindTextureUnit(1, s_Data.GeoPass->GetSpecification().TargetFramebuffer->GetDepthAttachmentRendererID());
});
Renderer::SubmitFullscreenQuad(nullptr);
Renderer::EndRenderPass();
}
*/
/*
void Renderer3D::BloomBlurPass()
{
@ -1111,7 +1084,7 @@ namespace Prism
// TODO: this will not be hardcode
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));
@ -1225,6 +1198,7 @@ namespace Prism
UI::EndTreeNode();
}
/*
if (UI::BeginTreeNode("Auto Exposure", false))
{
UI::BeginPropertyGrid();
@ -1236,6 +1210,36 @@ namespace Prism
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();

View File

@ -52,6 +52,9 @@ namespace Prism
return capabilities;
}
static void DispatchCompute(int x, int y, int z);
static void MemoryBarrier(int barrier);
private:
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,19 +95,19 @@ ScriptComponent
2. **配置依赖**
- PhysX SDK: Prism/CmakeLists.txt内记录了是用的PhysX的构建配置建议手动按照配置构建SDK构建好后无需移动文件项目可以自动链接库
3. **编译**
**Cmake**:
```bash
cd Prism
cmake -B build -G Ninja
cmake --build build --target PrismEditor --config Release -j10
```
3. **编译**:
- **Cmake**:
```bash
cd Prism
cmake -B build -G Ninja
cmake --build build --target PrismEditor --config Release -j10
```
**Visual Studio**:
visual Studio启用Cmake。 然后点击project下拉框找到 PrismEditor构建。
**Visual Studio**:
- visual Studio启用Cmake。 然后点击project下拉框找到 PrismEditor构建。
**JetBrain Clion**:
clion 会自动识别Cmake, 运行调试配置找到PrismEditor构建。
**JetBrain Clion**:
- clion 会自动识别Cmake, 运行调试配置找到PrismEditor构建。
---