From cf6b2655e7633059f304392fe56ba5ecb61f4591 Mon Sep 17 00:00:00 2001 From: bloeys Date: Sun, 12 May 2024 06:46:46 +0400 Subject: [PATCH] After all why not, why shouldn't we have HDR --- buffers/framebuffer.go | 89 ++++++++++++++++++++++--- main.go | 86 +++++++++++++++++++----- res/shaders/tonemapped-screen-quad.glsl | 50 ++++++++++++++ 3 files changed, 201 insertions(+), 24 deletions(-) create mode 100755 res/shaders/tonemapped-screen-quad.glsl diff --git a/buffers/framebuffer.go b/buffers/framebuffer.go index 6491847..0a0dedf 100755 --- a/buffers/framebuffer.go +++ b/buffers/framebuffer.go @@ -42,6 +42,7 @@ const ( FramebufferAttachmentDataFormat_Unknown FramebufferAttachmentDataFormat = iota FramebufferAttachmentDataFormat_R32Int FramebufferAttachmentDataFormat_RGBA8 + FramebufferAttachmentDataFormat_RGBAF16 FramebufferAttachmentDataFormat_SRGBA FramebufferAttachmentDataFormat_DepthF32 FramebufferAttachmentDataFormat_Depth24Stencil8 @@ -50,7 +51,8 @@ const ( func (f FramebufferAttachmentDataFormat) IsColorFormat() bool { return f == FramebufferAttachmentDataFormat_R32Int || f == FramebufferAttachmentDataFormat_RGBA8 || - f == FramebufferAttachmentDataFormat_SRGBA + f == FramebufferAttachmentDataFormat_SRGBA || + f == FramebufferAttachmentDataFormat_RGBAF16 } func (f FramebufferAttachmentDataFormat) IsDepthFormat() bool { @@ -65,6 +67,8 @@ func (f FramebufferAttachmentDataFormat) GlInternalFormat() int32 { return gl.R32I case FramebufferAttachmentDataFormat_RGBA8: return gl.RGB8 + case FramebufferAttachmentDataFormat_RGBAF16: + return gl.RGBA16F case FramebufferAttachmentDataFormat_SRGBA: return gl.SRGB_ALPHA case FramebufferAttachmentDataFormat_DepthF32: @@ -85,6 +89,8 @@ func (f FramebufferAttachmentDataFormat) GlFormat() uint32 { case FramebufferAttachmentDataFormat_RGBA8: fallthrough + case FramebufferAttachmentDataFormat_RGBAF16: + fallthrough case FramebufferAttachmentDataFormat_SRGBA: return gl.RGBA @@ -100,6 +106,33 @@ func (f FramebufferAttachmentDataFormat) GlFormat() uint32 { } } +func (f FramebufferAttachmentDataFormat) GlComponentType() uint32 { + + switch f { + + case FramebufferAttachmentDataFormat_R32Int: + return gl.INT + + case FramebufferAttachmentDataFormat_RGBA8: + fallthrough + case FramebufferAttachmentDataFormat_SRGBA: + return gl.UNSIGNED_BYTE + + case FramebufferAttachmentDataFormat_RGBAF16: + // Seems this is fine to be float instead of half float + fallthrough + case FramebufferAttachmentDataFormat_DepthF32: + return gl.FLOAT + + case FramebufferAttachmentDataFormat_Depth24Stencil8: + return gl.UNSIGNED_INT_24_8 + + default: + logging.ErrLog.Fatalf("unknown framebuffer attachment data format. Format=%d\n", f) + return 0 + } +} + type FramebufferAttachment struct { Id uint32 Type FramebufferAttachmentType @@ -124,7 +157,7 @@ func (fbo *Framebuffer) BindWithViewport() { gl.Viewport(0, 0, int32(fbo.Width), int32(fbo.Height)) } -// Clear calls gl.Clear with the fob's clear flags. +// Clear calls gl.Clear with the fbo's clear flags. // Note that the fbo must be complete and bound. // Calling this without a bound fbo will clear something else, like your screen. func (fbo *Framebuffer) Clear() { @@ -207,7 +240,17 @@ func (fbo *Framebuffer) NewColorAttachment( } gl.BindTexture(gl.TEXTURE_2D, a.Id) - gl.TexImage2D(gl.TEXTURE_2D, 0, attachFormat.GlInternalFormat(), int32(fbo.Width), int32(fbo.Height), 0, attachFormat.GlFormat(), gl.UNSIGNED_BYTE, nil) + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + attachFormat.GlInternalFormat(), + int32(fbo.Width), + int32(fbo.Height), + 0, + attachFormat.GlFormat(), + attachFormat.GlComponentType(), + nil, + ) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) @@ -298,7 +341,17 @@ func (fbo *Framebuffer) NewDepthAttachment( } gl.BindTexture(gl.TEXTURE_2D, a.Id) - gl.TexImage2D(gl.TEXTURE_2D, 0, attachFormat.GlInternalFormat(), int32(fbo.Width), int32(fbo.Height), 0, attachFormat.GlFormat(), gl.FLOAT, nil) + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + attachFormat.GlInternalFormat(), + int32(fbo.Width), + int32(fbo.Height), + 0, + attachFormat.GlFormat(), + attachFormat.GlComponentType(), + nil, + ) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) @@ -341,7 +394,17 @@ func (fbo *Framebuffer) NewDepthAttachment( gl.BindTexture(gl.TEXTURE_CUBE_MAP, a.Id) for i := 0; i < 6; i++ { - gl.TexImage2D(uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X+i), 0, attachFormat.GlInternalFormat(), int32(fbo.Width), int32(fbo.Height), 0, attachFormat.GlFormat(), gl.FLOAT, nil) + gl.TexImage2D( + uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X+i), + 0, + attachFormat.GlInternalFormat(), + int32(fbo.Width), + int32(fbo.Height), + 0, + attachFormat.GlFormat(), + attachFormat.GlComponentType(), + nil, + ) } gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.NEAREST) @@ -398,7 +461,7 @@ func (fbo *Framebuffer) NewDepthCubemapArrayAttachment( 6*numCubemaps, 0, attachFormat.GlFormat(), - gl.FLOAT, + attachFormat.GlComponentType(), nil, ) @@ -455,7 +518,7 @@ func (fbo *Framebuffer) NewDepthTextureArrayAttachment( numTextures, 0, attachFormat.GlFormat(), - gl.FLOAT, + attachFormat.GlComponentType(), nil, ) @@ -513,7 +576,17 @@ func (fbo *Framebuffer) NewDepthStencilAttachment( } gl.BindTexture(gl.TEXTURE_2D, a.Id) - gl.TexImage2D(gl.TEXTURE_2D, 0, attachFormat.GlInternalFormat(), int32(fbo.Width), int32(fbo.Height), 0, attachFormat.GlFormat(), gl.UNSIGNED_INT_24_8, nil) + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + attachFormat.GlInternalFormat(), + int32(fbo.Width), + int32(fbo.Height), + 0, + attachFormat.GlFormat(), + attachFormat.GlComponentType(), + nil, + ) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) diff --git a/main.go b/main.go index 85bf852..afc43a2 100755 --- a/main.go +++ b/main.go @@ -36,9 +36,10 @@ import ( - Point light shadows ✅ - Spotlight shadows ✅ - Create VAO struct independent from VBO to support multi-VBO use cases (e.g. instancing) ✅ - - Normals maps + - Normals maps ✅ + - HDR ✅ + - Fix bad point light acne - UBO support - - HDR - Cascaded shadow mapping - Skeletal animations - In some cases we DO want input even when captured by UI. We need two systems within input package, one filtered and one not✅ @@ -202,12 +203,13 @@ var ( yaw float32 = -1.5 cam camera.Camera - // Demo fbo - renderToDemoFbo = false renderToBackBuffer = true - demoFboScale = gglm.NewVec2(0.25, 0.25) - demoFboOffset = gglm.NewVec2(0.75, -0.75) - demoFbo buffers.Framebuffer + + // Demo fbo + renderToDemoFbo = false + demoFboScale = gglm.NewVec2(0.25, 0.25) + demoFboOffset = gglm.NewVec2(0.75, -0.75) + demoFbo buffers.Framebuffer // Dir light fbo showDirLightDepthMapFbo = false @@ -221,6 +223,12 @@ var ( // Spot light fbo spotLightDepthMapFbo buffers.Framebuffer + // Hdr Fbo + hdrRendering = true + hdrExposure float32 = 1 + tonemappedScreenQuadMat materials.Material + hdrFbo buffers.Framebuffer + screenQuadVao buffers.VertexArray screenQuadMat materials.Material @@ -243,7 +251,7 @@ var ( cubeModelMat = gglm.NewTrMatId() renderSkybox = true - renderDepthBuffer bool + renderDepthBuffer = false skyboxCmap assets.Cubemap @@ -518,6 +526,9 @@ func (g *Game) Init() { screenQuadMat.SetUnifVec2("offset", &demoFboOffset) screenQuadMat.SetUnifInt32("material.diffuse", int32(materials.TextureSlot_Diffuse)) + tonemappedScreenQuadMat = materials.NewMaterial("Tonemapped Screen Quad Mat", "./res/shaders/tonemapped-screen-quad.glsl") + tonemappedScreenQuadMat.SetUnifInt32("material.diffuse", int32(materials.TextureSlot_Diffuse)) + unlitMat = materials.NewMaterial("Unlit mat", "./res/shaders/simple-unlit.glsl") unlitMat.Settings.Set(materials.MaterialSettings_HasModelMtx) unlitMat.SetUnifInt32("material.diffuse", int32(materials.TextureSlot_Diffuse)) @@ -635,6 +646,8 @@ func (g *Game) Init() { func (g *Game) initFbos() { + // @TODO: Resize window sized fbos on window resize + // Demo fbo demoFbo = buffers.NewFramebuffer(uint32(g.WinWidth), uint32(g.WinHeight)) @@ -679,6 +692,20 @@ func (g *Game) initFbos() { ) assert.T(spotLightDepthMapFbo.IsComplete(), "Spot light depth map fbo is not complete after init") + + // Hdr fbo + hdrFbo = buffers.NewFramebuffer(uint32(g.WinWidth), uint32(g.WinHeight)) + hdrFbo.NewColorAttachment( + buffers.FramebufferAttachmentType_Texture, + buffers.FramebufferAttachmentDataFormat_RGBAF16, + ) + + hdrFbo.NewDepthStencilAttachment( + buffers.FramebufferAttachmentType_Renderbuffer, + buffers.FramebufferAttachmentDataFormat_Depth24Stencil8, + ) + + assert.T(hdrFbo.IsComplete(), "Hdr fbo is not complete after init") } func (g *Game) updateLights() { @@ -819,6 +846,14 @@ func (g *Game) showDebugWindow() { imgui.Spacing() + imgui.Text("HDR") + imgui.Checkbox("Enable HDR", &hdrRendering) + if imgui.DragFloat("Exposure", &hdrExposure) { + tonemappedScreenQuadMat.SetUnifFloat32("exposure", hdrExposure) + } + + imgui.Spacing() + // Ambient light imgui.Text("Ambient Light") @@ -1075,8 +1110,8 @@ func (g *Game) updateCameraPos() { } var ( - renderDirLightShadows = false - renderPointLightShadows = false + renderDirLightShadows = true + renderPointLightShadows = true renderSpotLightShadows = true rotatingCubeSpeedDeg1 float32 = 45 @@ -1114,17 +1149,19 @@ func (g *Game) Render() { if renderDepthBuffer { g.RenderScene(&debugDepthMat) + } else if hdrRendering { + g.renderHdrFbo() } else { + g.RenderScene(nil) + if renderSkybox { + g.DrawSkybox() + } } } - if renderSkybox { - g.DrawSkybox() - } - if renderToDemoFbo { - g.renderDemoFob() + g.renderDemoFbo() } } @@ -1222,7 +1259,7 @@ func (g *Game) renderPointLightShadowmaps() { pointLightDepthMapFbo.UnBindWithViewport(uint32(g.WinWidth), uint32(g.WinHeight)) } -func (g *Game) renderDemoFob() { +func (g *Game) renderDemoFbo() { demoFbo.Bind() demoFbo.Clear() @@ -1246,6 +1283,23 @@ func (g *Game) renderDemoFob() { window.Rend.DrawVertexArray(&screenQuadMat, &screenQuadVao, 0, 6) } +func (g *Game) renderHdrFbo() { + + hdrFbo.Bind() + hdrFbo.Clear() + + g.RenderScene(nil) + + if renderSkybox { + g.DrawSkybox() + } + + hdrFbo.UnBind() + + tonemappedScreenQuadMat.DiffuseTex = hdrFbo.Attachments[0].Id + window.Rend.DrawVertexArray(&tonemappedScreenQuadMat, &screenQuadVao, 0, 6) +} + func (g *Game) RenderScene(overrideMat *materials.Material) { tempModelMatrix := cubeModelMat.Clone() diff --git a/res/shaders/tonemapped-screen-quad.glsl b/res/shaders/tonemapped-screen-quad.glsl new file mode 100755 index 0000000..3d2eee8 --- /dev/null +++ b/res/shaders/tonemapped-screen-quad.glsl @@ -0,0 +1,50 @@ +//shader:vertex +#version 410 + +out vec2 vertUV0; + +// Hardcoded vertex positions for a fullscreen quad. +// Format: vec4(pos.x, pos.y, uv0.x, uv0.y) +vec4 quadData[6] = vec4[]( + vec4(-1.0, 1.0, 0.0, 1.0), + vec4(-1.0, -1.0, 0.0, 0.0), + vec4(1.0, -1.0, 1.0, 0.0), + vec4(-1.0, 1.0, 0.0, 1.0), + vec4(1.0, -1.0, 1.0, 0.0), + vec4(1.0, 1.0, 1.0, 1.0) +); + +void main() +{ + vec4 vertData = quadData[gl_VertexID]; + + vertUV0 = vertData.zw; + gl_Position = vec4(vertData.xy, 0.0, 1.0); +} + +//shader:fragment +#version 410 + +struct Material { + sampler2D diffuse; +}; + +uniform float exposure = 1; +uniform Material material; + +in vec2 vertUV0; + +out vec4 fragColor; + +void main() +{ + vec4 diffuseTexColor = texture(material.diffuse, vertUV0); + + // Reinhard tone mapping + // vec3 mappedColor = diffuseTexColor.rgb / (diffuseTexColor.rgb + vec3(1.0)); + + // Exposure tone mapping + vec3 mappedColor = vec3(1.0) - exp(-diffuseTexColor.rgb * exposure); + + fragColor = vec4(mappedColor, 1); +}