From c2f8faa77e00173154e50777fe4ce94113c11275 Mon Sep 17 00:00:00 2001 From: Alexander Christensen Date: Fri, 27 May 2022 19:09:52 +0200 Subject: [PATCH 1/3] Create physically_based_bloom.cpp --- src/8.guest/2022/physically_based_bloom.cpp | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/8.guest/2022/physically_based_bloom.cpp diff --git a/src/8.guest/2022/physically_based_bloom.cpp b/src/8.guest/2022/physically_based_bloom.cpp new file mode 100644 index 0000000..e965047 --- /dev/null +++ b/src/8.guest/2022/physically_based_bloom.cpp @@ -0,0 +1 @@ +Hello From 0bc8312a14727c7ca9c1712ef7661785276a5a34 Mon Sep 17 00:00:00 2001 From: alexpanter Date: Fri, 27 May 2022 20:12:35 +0200 Subject: [PATCH 2/3] Adding pbr bloom initial --- CMakeLists.txt | 1 + .../2022/6.physically_based_bloom/6.bloom.fs | 49 ++ .../2022/6.physically_based_bloom/6.bloom.vs | 25 + .../6.physically_based_bloom/6.bloom_final.fs | 50 ++ .../6.physically_based_bloom/6.bloom_final.vs | 11 + .../6.physically_based_bloom/6.light_box.fs | 21 + .../6.new_downsample.fs | 114 ++++ .../6.new_downsample.vs | 12 + .../6.new_upsample.fs | 47 ++ .../6.new_upsample.vs | 12 + .../6.physically_based_bloom/6.old_blur.fs | 32 + .../6.physically_based_bloom/6.old_blur.vs | 11 + .../physically_based_bloom.cpp | 574 ++++++++++++++++++ src/8.guest/2022/physically_based_bloom.cpp | 1 - 14 files changed, 959 insertions(+), 1 deletion(-) create mode 100644 src/8.guest/2022/6.physically_based_bloom/6.bloom.fs create mode 100644 src/8.guest/2022/6.physically_based_bloom/6.bloom.vs create mode 100644 src/8.guest/2022/6.physically_based_bloom/6.bloom_final.fs create mode 100644 src/8.guest/2022/6.physically_based_bloom/6.bloom_final.vs create mode 100644 src/8.guest/2022/6.physically_based_bloom/6.light_box.fs create mode 100644 src/8.guest/2022/6.physically_based_bloom/6.new_downsample.fs create mode 100644 src/8.guest/2022/6.physically_based_bloom/6.new_downsample.vs create mode 100644 src/8.guest/2022/6.physically_based_bloom/6.new_upsample.fs create mode 100644 src/8.guest/2022/6.physically_based_bloom/6.new_upsample.vs create mode 100644 src/8.guest/2022/6.physically_based_bloom/6.old_blur.fs create mode 100644 src/8.guest/2022/6.physically_based_bloom/6.old_blur.vs create mode 100644 src/8.guest/2022/6.physically_based_bloom/physically_based_bloom.cpp delete mode 100644 src/8.guest/2022/physically_based_bloom.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 23e9ed3..72afe07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -183,6 +183,7 @@ set(GUEST_ARTICLES #8.guest/2021/3.tessellation/terrain_cpu_src 8.guest/2021/4.dsa 8.guest/2022/5.computeshader_helloworld + 8.guest/2022/6.physically_based_bloom ) configure_file(configuration/root_directory.h.in configuration/root_directory.h) diff --git a/src/8.guest/2022/6.physically_based_bloom/6.bloom.fs b/src/8.guest/2022/6.physically_based_bloom/6.bloom.fs new file mode 100644 index 0000000..a687beb --- /dev/null +++ b/src/8.guest/2022/6.physically_based_bloom/6.bloom.fs @@ -0,0 +1,49 @@ +#version 330 core +layout (location = 0) out vec4 FragColor; +layout (location = 1) out vec4 BrightColor; + +in VS_OUT { + vec3 FragPos; + vec3 Normal; + vec2 TexCoords; +} fs_in; + +struct Light { + vec3 Position; + vec3 Color; +}; + +uniform Light lights[4]; +uniform sampler2D diffuseTexture; +uniform vec3 viewPos; + +void main() +{ + vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb; + vec3 normal = normalize(fs_in.Normal); + // ambient + vec3 ambient = 0.0 * color; + // lighting + vec3 lighting = vec3(0.0); + vec3 viewDir = normalize(viewPos - fs_in.FragPos); + for(int i = 0; i < 4; i++) + { + // diffuse + vec3 lightDir = normalize(lights[i].Position - fs_in.FragPos); + float diff = max(dot(lightDir, normal), 0.0); + vec3 result = lights[i].Color * diff * color; + // attenuation (use quadratic as we have gamma correction) + float distance = length(fs_in.FragPos - lights[i].Position); + result *= 1.0 / (distance * distance); + lighting += result; + + } + vec3 result = ambient + lighting; + // check whether result is higher than some threshold, if so, output as bloom threshold color + float brightness = dot(result, vec3(0.2126, 0.7152, 0.0722)); + if(brightness > 1.0) + BrightColor = vec4(result, 1.0); + else + BrightColor = vec4(0.0, 0.0, 0.0, 1.0); + FragColor = vec4(result, 1.0); +} diff --git a/src/8.guest/2022/6.physically_based_bloom/6.bloom.vs b/src/8.guest/2022/6.physically_based_bloom/6.bloom.vs new file mode 100644 index 0000000..0548593 --- /dev/null +++ b/src/8.guest/2022/6.physically_based_bloom/6.bloom.vs @@ -0,0 +1,25 @@ +#version 330 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aNormal; +layout (location = 2) in vec2 aTexCoords; + +out VS_OUT { + vec3 FragPos; + vec3 Normal; + vec2 TexCoords; +} vs_out; + +uniform mat4 projection; +uniform mat4 view; +uniform mat4 model; + +void main() +{ + vs_out.FragPos = vec3(model * vec4(aPos, 1.0)); + vs_out.TexCoords = aTexCoords; + + mat3 normalMatrix = transpose(inverse(mat3(model))); + vs_out.Normal = normalize(normalMatrix * aNormal); + + gl_Position = projection * view * model * vec4(aPos, 1.0); +} \ No newline at end of file diff --git a/src/8.guest/2022/6.physically_based_bloom/6.bloom_final.fs b/src/8.guest/2022/6.physically_based_bloom/6.bloom_final.fs new file mode 100644 index 0000000..e004f2e --- /dev/null +++ b/src/8.guest/2022/6.physically_based_bloom/6.bloom_final.fs @@ -0,0 +1,50 @@ +#version 330 core +out vec4 FragColor; + +in vec2 TexCoords; + +uniform sampler2D scene; +uniform sampler2D bloomBlur; +uniform float exposure; +uniform float bloomStrength = 0.04f; +uniform int programChoice; + +vec3 bloom_none() +{ + vec3 hdrColor = texture(scene, TexCoords).rgb; + return hdrColor; +} + +vec3 bloom_old() +{ + vec3 hdrColor = texture(scene, TexCoords).rgb; + vec3 bloomColor = texture(bloomBlur, TexCoords).rgb; + return hdrColor + bloomColor; // additive blending +} + +vec3 bloom_new() +{ + vec3 hdrColor = texture(scene, TexCoords).rgb; + vec3 bloomColor = texture(bloomBlur, TexCoords).rgb; + return mix(hdrColor, bloomColor, bloomStrength); // linear interpolation +} + +void main() +{ + // to bloom or not to bloom + vec3 result = vec3(0.0); + switch (programChoice) + { + case 1: result = bloom_none(); break; + case 2: result = bloom_old(); break; + case 3: result = bloom_new(); break; + default: + result = bloom_none(); break; + } + // tone mapping + vec3 result = vec3(1.0) - exp(-hdrColor * exposure); + // also gamma correct while we're at it + const float gamma = 2.2; + result = pow(result, vec3(1.0 / gamma)); + FragColor = vec4(result, 1.0); +} diff --git a/src/8.guest/2022/6.physically_based_bloom/6.bloom_final.vs b/src/8.guest/2022/6.physically_based_bloom/6.bloom_final.vs new file mode 100644 index 0000000..9f93e29 --- /dev/null +++ b/src/8.guest/2022/6.physically_based_bloom/6.bloom_final.vs @@ -0,0 +1,11 @@ +#version 330 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aTexCoords; + +out vec2 TexCoords; + +void main() +{ + TexCoords = aTexCoords; + gl_Position = vec4(aPos, 1.0); +} \ No newline at end of file diff --git a/src/8.guest/2022/6.physically_based_bloom/6.light_box.fs b/src/8.guest/2022/6.physically_based_bloom/6.light_box.fs new file mode 100644 index 0000000..6a48faa --- /dev/null +++ b/src/8.guest/2022/6.physically_based_bloom/6.light_box.fs @@ -0,0 +1,21 @@ +#version 330 core +layout (location = 0) out vec4 FragColor; +layout (location = 1) out vec4 BrightColor; + +in VS_OUT { + vec3 FragPos; + vec3 Normal; + vec2 TexCoords; +} fs_in; + +uniform vec3 lightColor; + +void main() +{ + FragColor = vec4(lightColor, 1.0); + float brightness = dot(FragColor.rgb, vec3(0.2126, 0.7152, 0.0722)); + if(brightness > 1.0) + BrightColor = vec4(FragColor.rgb, 1.0); + else + BrightColor = vec4(0.0, 0.0, 0.0, 1.0); +} \ No newline at end of file diff --git a/src/8.guest/2022/6.physically_based_bloom/6.new_downsample.fs b/src/8.guest/2022/6.physically_based_bloom/6.new_downsample.fs new file mode 100644 index 0000000..da431c0 --- /dev/null +++ b/src/8.guest/2022/6.physically_based_bloom/6.new_downsample.fs @@ -0,0 +1,114 @@ +#version 330 core + +// This shader performs downsampling on a texture, +// as taken from Call Of Duty method, presented at ACM Siggraph 2014. +// This particular method was customly designed to eliminate +// "pulsating artifacts and temporal stability issues". + +// Remember to add bilinear minification filter for this texture! +// Remember to use a floating-point texture format (for HDR)! +// Remember to use edge clamping for this texture! +uniform sampler2D srcTexture; +uniform vec2 srcResolution; + +// which mip we are writing to, used for Karis average +uniform int mipLevel = 1; + +in vec2 texCoord; +layout (location = 0) out vec3 downsample; + +vec3 PowVec3(vec3 v, float p) +{ + return vec3(pow(v.x, p), pow(v.y, p), pow(v.z, p)); +} + +const float invGamma = 1.0 / 2.2; +vec3 ToSRGB(vec3 v) { return PowVec3(v, invGamma); } + +float sRGBToLuma(vec3 col) +{ + //return dot(col, vec3(0.2126f, 0.7152f, 0.0722f)); + return dot(col, vec3(0.299f, 0.587f, 0.114f)); +} + +float KarisAverage(vec3 col) +{ + // Formula is 1 / (1 + luma) + float luma = sRGBToLuma(ToSRGB(col)) * 0.25f; + return 1.0f / (1.0f + luma); +} + +// NOTE: This is the readable version of this shader. It will be optimized! +void main() +{ + vec2 srcTexelSize = 1.0 / srcResolution; + float x = srcTexelSize.x; + float y = srcTexelSize.y; + + // Take 13 samples around current texel: + // a - b - c + // - j - k - + // d - e - f + // - l - m - + // g - h - i + // === ('e' is the current texel) === + vec3 a = texture(srcTexture, vec2(texCoord.x - 2*x, texCoord.y + 2*y)).rgb; + vec3 b = texture(srcTexture, vec2(texCoord.x, texCoord.y + 2*y)).rgb; + vec3 c = texture(srcTexture, vec2(texCoord.x + 2*x, texCoord.y + 2*y)).rgb; + + vec3 d = texture(srcTexture, vec2(texCoord.x - 2*x, texCoord.y)).rgb; + vec3 e = texture(srcTexture, vec2(texCoord.x, texCoord.y)).rgb; + vec3 f = texture(srcTexture, vec2(texCoord.x + 2*x, texCoord.y)).rgb; + + vec3 g = texture(srcTexture, vec2(texCoord.x - 2*x, texCoord.y - 2*y)).rgb; + vec3 h = texture(srcTexture, vec2(texCoord.x, texCoord.y - 2*y)).rgb; + vec3 i = texture(srcTexture, vec2(texCoord.x + 2*x, texCoord.y - 2*y)).rgb; + + vec3 j = texture(srcTexture, vec2(texCoord.x - x, texCoord.y + y)).rgb; + vec3 k = texture(srcTexture, vec2(texCoord.x + x, texCoord.y + y)).rgb; + vec3 l = texture(srcTexture, vec2(texCoord.x - x, texCoord.y - y)).rgb; + vec3 m = texture(srcTexture, vec2(texCoord.x + x, texCoord.y - y)).rgb; + + // Apply weighted distribution: + // 0.5 + 0.125 + 0.125 + 0.125 + 0.125 = 1 + // a,b,d,e * 0.125 + // b,c,e,f * 0.125 + // d,e,g,h * 0.125 + // e,f,h,i * 0.125 + // j,k,l,m * 0.5 + // This shows 5 square areas that are being sampled. But some of them overlap, + // so to have an energy preserving downsample we need to make some adjustments. + // The weights are the distributed, so that the sum of j,k,l,m (e.g.) + // contribute 0.5 to the final color output. The code below is written + // to effectively yield this sum. We get: + // 0.125*5 + 0.03125*4 + 0.0625*4 = 1 + + // Check if we need to perform Karis average on each block of 4 samples + vec3 groups[5]; + switch (mipLevel) + { + case 0: + // We are writing to mip 0, so we need to apply Karis average to each block + // of 4 samples to prevent fireflies (very bright subpixels, leads to pulsating + // artifacts). + groups[0] = (a+b+d+e) * (0.125f/4.0f); + groups[1] = (b+c+e+f) * (0.125f/4.0f); + groups[2] = (d+e+g+h) * (0.125f/4.0f); + groups[3] = (e+f+h+i) * (0.125f/4.0f); + groups[4] = (j+k+l+m) * (0.5f/4.0f); + groups[0] *= KarisAverage(groups[0]); + groups[1] *= KarisAverage(groups[1]); + groups[2] *= KarisAverage(groups[2]); + groups[3] *= KarisAverage(groups[3]); + groups[4] *= KarisAverage(groups[4]); + downsample = groups[0]+groups[1]+groups[2]+groups[3]+groups[4]; + downsample = max(downsample, 0.0001f); + break; + default: + downsample = e*0.125; // ok + downsample += (a+c+g+i)*0.03125; // ok + downsample += (b+d+f+h)*0.0625; // ok + downsample += (j+k+l+m)*0.125; // ok + break; + } +} diff --git a/src/8.guest/2022/6.physically_based_bloom/6.new_downsample.vs b/src/8.guest/2022/6.physically_based_bloom/6.new_downsample.vs new file mode 100644 index 0000000..a0fd075 --- /dev/null +++ b/src/8.guest/2022/6.physically_based_bloom/6.new_downsample.vs @@ -0,0 +1,12 @@ +#version 330 core + +layout (location = 0) in vec2 aPosition; +layout (location = 1) in vec2 aTexCoord; + +out vec2 texCoord; + +void main() +{ + gl_Position = vec4(aPosition.x, aPosition.y, 0.0, 1.0); + texCoord = aTexCoord; +} diff --git a/src/8.guest/2022/6.physically_based_bloom/6.new_upsample.fs b/src/8.guest/2022/6.physically_based_bloom/6.new_upsample.fs new file mode 100644 index 0000000..2d8789a --- /dev/null +++ b/src/8.guest/2022/6.physically_based_bloom/6.new_upsample.fs @@ -0,0 +1,47 @@ +#version 330 core + +// This shader performs upsampling on a texture, +// as taken from Call Of Duty method, presented at ACM Siggraph 2014. + +// Remember to add bilinear minification filter for this texture! +// Remember to use a floating-point texture format (for HDR)! +// Remember to use edge clamping for this texture! +uniform sampler2D srcTexture; +uniform float filterRadius; + +in vec2 texCoord; +layout (location = 0) out vec3 upsample; + +void main() +{ + // The filter kernel is applied with a radius, specified in texture + // coordinates, so that the radius will vary across mip resolutions. + float x = filterRadius; + float y = filterRadius; + + // Take 9 samples around current texel: + // a - b - c + // d - e - f + // g - h - i + // === ('e' is the current texel) === + vec3 a = texture(srcTexture, vec2(texCoord.x - x, texCoord.y + y)).rgb; + vec3 b = texture(srcTexture, vec2(texCoord.x, texCoord.y + y)).rgb; + vec3 c = texture(srcTexture, vec2(texCoord.x + x, texCoord.y + y)).rgb; + + vec3 d = texture(srcTexture, vec2(texCoord.x - x, texCoord.y)).rgb; + vec3 e = texture(srcTexture, vec2(texCoord.x, texCoord.y)).rgb; + vec3 f = texture(srcTexture, vec2(texCoord.x + x, texCoord.y)).rgb; + + vec3 g = texture(srcTexture, vec2(texCoord.x - x, texCoord.y - y)).rgb; + vec3 h = texture(srcTexture, vec2(texCoord.x, texCoord.y - y)).rgb; + vec3 i = texture(srcTexture, vec2(texCoord.x + x, texCoord.y - y)).rgb; + + // Apply weighted distribution, by using a 3x3 tent filter: + // 1 | 1 2 1 | + // -- * | 2 4 2 | + // 16 | 1 2 1 | + upsample = e*4.0; + upsample += (b+d+f+h)*2.0; + upsample += (a+c+g+i); + upsample *= 1.0 / 16.0; +} diff --git a/src/8.guest/2022/6.physically_based_bloom/6.new_upsample.vs b/src/8.guest/2022/6.physically_based_bloom/6.new_upsample.vs new file mode 100644 index 0000000..a0fd075 --- /dev/null +++ b/src/8.guest/2022/6.physically_based_bloom/6.new_upsample.vs @@ -0,0 +1,12 @@ +#version 330 core + +layout (location = 0) in vec2 aPosition; +layout (location = 1) in vec2 aTexCoord; + +out vec2 texCoord; + +void main() +{ + gl_Position = vec4(aPosition.x, aPosition.y, 0.0, 1.0); + texCoord = aTexCoord; +} diff --git a/src/8.guest/2022/6.physically_based_bloom/6.old_blur.fs b/src/8.guest/2022/6.physically_based_bloom/6.old_blur.fs new file mode 100644 index 0000000..88fe044 --- /dev/null +++ b/src/8.guest/2022/6.physically_based_bloom/6.old_blur.fs @@ -0,0 +1,32 @@ +#version 330 core +out vec4 FragColor; + +in vec2 TexCoords; + +uniform sampler2D image; + +uniform bool horizontal; +uniform float weight[5] = float[] (0.2270270270, 0.1945945946, 0.1216216216, 0.0540540541, 0.0162162162); + +void main() +{ + vec2 tex_offset = 1.0 / textureSize(image, 0); // gets size of single texel + vec3 result = texture(image, TexCoords).rgb * weight[0]; + if(horizontal) + { + for(int i = 1; i < 5; ++i) + { + result += texture(image, TexCoords + vec2(tex_offset.x * i, 0.0)).rgb * weight[i]; + result += texture(image, TexCoords - vec2(tex_offset.x * i, 0.0)).rgb * weight[i]; + } + } + else + { + for(int i = 1; i < 5; ++i) + { + result += texture(image, TexCoords + vec2(0.0, tex_offset.y * i)).rgb * weight[i]; + result += texture(image, TexCoords - vec2(0.0, tex_offset.y * i)).rgb * weight[i]; + } + } + FragColor = vec4(result, 1.0); +} \ No newline at end of file diff --git a/src/8.guest/2022/6.physically_based_bloom/6.old_blur.vs b/src/8.guest/2022/6.physically_based_bloom/6.old_blur.vs new file mode 100644 index 0000000..9f93e29 --- /dev/null +++ b/src/8.guest/2022/6.physically_based_bloom/6.old_blur.vs @@ -0,0 +1,11 @@ +#version 330 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aTexCoords; + +out vec2 TexCoords; + +void main() +{ + TexCoords = aTexCoords; + gl_Position = vec4(aPos, 1.0); +} \ No newline at end of file diff --git a/src/8.guest/2022/6.physically_based_bloom/physically_based_bloom.cpp b/src/8.guest/2022/6.physically_based_bloom/physically_based_bloom.cpp new file mode 100644 index 0000000..4497c2e --- /dev/null +++ b/src/8.guest/2022/6.physically_based_bloom/physically_based_bloom.cpp @@ -0,0 +1,574 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +void framebuffer_size_callback(GLFWwindow* window, int width, int height); +void mouse_callback(GLFWwindow* window, double xpos, double ypos); +void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); +void processInput(GLFWwindow *window); +unsigned int loadTexture(const char *path, bool gammaCorrection); +void renderQuad(); +void renderCube(); + +// settings +const unsigned int SCR_WIDTH = 800; +const unsigned int SCR_HEIGHT = 600; +bool bloom = true; +float exposure = 1.0f; +int programChoice = 1; + +// camera +Camera camera(glm::vec3(0.0f, 0.0f, 5.0f)); +float lastX = (float)SCR_WIDTH / 2.0; +float lastY = (float)SCR_HEIGHT / 2.0; +bool firstMouse = true; + +// timing +float deltaTime = 0.0f; +float lastFrame = 0.0f; + +int main() +{ + // glfw: initialize and configure + // ------------------------------ + glfwInit(); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + +#ifdef __APPLE__ + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); +#endif + + // glfw window creation + // -------------------- + GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); + if (window == NULL) + { + std::cout << "Failed to create GLFW window" << std::endl; + glfwTerminate(); + return -1; + } + glfwMakeContextCurrent(window); + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + glfwSetCursorPosCallback(window, mouse_callback); + glfwSetScrollCallback(window, scroll_callback); + + // tell GLFW to capture our mouse + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + + // glad: load all OpenGL function pointers + // --------------------------------------- + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) + { + std::cout << "Failed to initialize GLAD" << std::endl; + return -1; + } + + // configure global opengl state + // ----------------------------- + glEnable(GL_DEPTH_TEST); + + // build and compile shaders + // ------------------------- + Shader shader("6.bloom.vs", "6.bloom.fs"); + Shader shaderLight("6.bloom.vs", "6.light_box.fs"); + Shader shaderBlur("6.old_blur.vs", "6.old_blur.fs"); + Shader shaderBloomFinal("6.bloom_final.vs", "6.bloom_final.fs"); + + Shader shaderDownsample("6.new_downsample.vs", "6.new_downsample.fs"); + Shader shaderUpsample("6.new_upsample.vs", "6.new_upsample.fs"); + + // load textures + // ------------- + unsigned int woodTexture = loadTexture(FileSystem::getPath("resources/textures/wood.png").c_str(), true); // note that we're loading the texture as an SRGB texture + unsigned int containerTexture = loadTexture(FileSystem::getPath("resources/textures/container2.png").c_str(), true); // note that we're loading the texture as an SRGB texture + + // configure (floating point) framebuffers + // --------------------------------------- + unsigned int hdrFBO; + glGenFramebuffers(1, &hdrFBO); + glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO); + // create 2 floating point color buffers (1 for normal rendering, other for brightness threshold values) + unsigned int colorBuffers[2]; + glGenTextures(2, colorBuffers); + for (unsigned int i = 0; i < 2; i++) + { + glBindTexture(GL_TEXTURE_2D, colorBuffers[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL); + 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); // we clamp to the edge as the blur filter would otherwise sample repeated texture values! + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + // attach texture to framebuffer + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, colorBuffers[i], 0); + } + // create and attach depth buffer (renderbuffer) + unsigned int rboDepth; + glGenRenderbuffers(1, &rboDepth); + glBindRenderbuffer(GL_RENDERBUFFER, rboDepth); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, SCR_WIDTH, SCR_HEIGHT); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth); + // tell OpenGL which color attachments we'll use (of this framebuffer) for rendering + unsigned int attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; + glDrawBuffers(2, attachments); + // finally check if framebuffer is complete + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + std::cout << "Framebuffer not complete!" << std::endl; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // ping-pong-framebuffer for blurring + unsigned int pingpongFBO[2]; + unsigned int pingpongColorbuffers[2]; + glGenFramebuffers(2, pingpongFBO); + glGenTextures(2, pingpongColorbuffers); + for (unsigned int i = 0; i < 2; i++) + { + glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]); + glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL); + 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); // we clamp to the edge as the blur filter would otherwise sample repeated texture values! + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongColorbuffers[i], 0); + // also check if framebuffers are complete (no need for depth buffer) + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + std::cout << "Framebuffer not complete!" << std::endl; + } + + // lighting info + // ------------- + // positions + std::vector lightPositions; + lightPositions.push_back(glm::vec3( 0.0f, 0.5f, 1.5f)); + lightPositions.push_back(glm::vec3(-4.0f, 0.5f, -3.0f)); + lightPositions.push_back(glm::vec3( 3.0f, 0.5f, 1.0f)); + lightPositions.push_back(glm::vec3(-.8f, 2.4f, -1.0f)); + // colors + std::vector lightColors; + lightColors.push_back(glm::vec3(5.0f, 5.0f, 5.0f)); + lightColors.push_back(glm::vec3(10.0f, 0.0f, 0.0f)); + lightColors.push_back(glm::vec3(0.0f, 0.0f, 15.0f)); + lightColors.push_back(glm::vec3(0.0f, 5.0f, 0.0f)); + + + // shader configuration + // -------------------- + shader.use(); + shader.setInt("diffuseTexture", 0); + shaderBlur.use(); + shaderBlur.setInt("image", 0); + shaderBloomFinal.use(); + shaderBloomFinal.setInt("scene", 0); + shaderBloomFinal.setInt("bloomBlur", 1); + + // render loop + // ----------- + while (!glfwWindowShouldClose(window)) + { + // per-frame time logic + // -------------------- + float currentFrame = static_cast(glfwGetTime()); + deltaTime = currentFrame - lastFrame; + lastFrame = currentFrame; + + // input + // ----- + processInput(window); + + // render + // ------ + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // 1. render scene into floating point framebuffer + // ----------------------------------------------- + glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); + glm::mat4 view = camera.GetViewMatrix(); + glm::mat4 model = glm::mat4(1.0f); + shader.use(); + shader.setMat4("projection", projection); + shader.setMat4("view", view); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, woodTexture); + // set lighting uniforms + for (unsigned int i = 0; i < lightPositions.size(); i++) + { + shader.setVec3("lights[" + std::to_string(i) + "].Position", lightPositions[i]); + shader.setVec3("lights[" + std::to_string(i) + "].Color", lightColors[i]); + } + shader.setVec3("viewPos", camera.Position); + // create one large cube that acts as the floor + model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3(0.0f, -1.0f, 0.0)); + model = glm::scale(model, glm::vec3(12.5f, 0.5f, 12.5f)); + shader.setMat4("model", model); + renderCube(); + // then create multiple cubes as the scenery + glBindTexture(GL_TEXTURE_2D, containerTexture); + model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3(0.0f, 1.5f, 0.0)); + model = glm::scale(model, glm::vec3(0.5f)); + shader.setMat4("model", model); + renderCube(); + + model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3(2.0f, 0.0f, 1.0)); + model = glm::scale(model, glm::vec3(0.5f)); + shader.setMat4("model", model); + renderCube(); + + model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3(-1.0f, -1.0f, 2.0)); + model = glm::rotate(model, glm::radians(60.0f), glm::normalize(glm::vec3(1.0, 0.0, 1.0))); + shader.setMat4("model", model); + renderCube(); + + model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3(0.0f, 2.7f, 4.0)); + model = glm::rotate(model, glm::radians(23.0f), glm::normalize(glm::vec3(1.0, 0.0, 1.0))); + model = glm::scale(model, glm::vec3(1.25)); + shader.setMat4("model", model); + renderCube(); + + model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3(-2.0f, 1.0f, -3.0)); + model = glm::rotate(model, glm::radians(124.0f), glm::normalize(glm::vec3(1.0, 0.0, 1.0))); + shader.setMat4("model", model); + renderCube(); + + model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3(-3.0f, 0.0f, 0.0)); + model = glm::scale(model, glm::vec3(0.5f)); + shader.setMat4("model", model); + renderCube(); + + // finally show all the light sources as bright cubes + shaderLight.use(); + shaderLight.setMat4("projection", projection); + shaderLight.setMat4("view", view); + + for (unsigned int i = 0; i < lightPositions.size(); i++) + { + model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3(lightPositions[i])); + model = glm::scale(model, glm::vec3(0.25f)); + shaderLight.setMat4("model", model); + shaderLight.setVec3("lightColor", lightColors[i]); + renderCube(); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if (programChoice < 1 || programChoice > 3) { programChoice = 1; } + bloom = (programChoice == 1) ? false : true; + + // 2.A) bloom is disabled + // ---------------------- + if (programChoice == 1) + { + + } + + // 2.B) blur bright fragments with two-pass Gaussian Blur + // ------------------------------------------------------ + else if (programChoice == 2) + { + bool horizontal = true, first_iteration = true; + unsigned int amount = 10; + shaderBlur.use(); + for (unsigned int i = 0; i < amount; i++) + { + glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]); + shaderBlur.setInt("horizontal", horizontal); + glBindTexture(GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongColorbuffers[!horizontal]); // bind texture of other framebuffer (or scene if first iteration) + renderQuad(); + horizontal = !horizontal; + if (first_iteration) + first_iteration = false; + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + // 2.C) use unthresholded bloom with progressive downsample/upsampling + // ------------------------------------------------------------------- + else if (programChoice == 3) + { + + } + + // 3. now render floating point color buffer to 2D quad and tonemap HDR colors to default framebuffer's (clamped) color range + // -------------------------------------------------------------------------------------------------------------------------- + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + shaderBloomFinal.use(); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, colorBuffers[0]); + glActiveTexture(GL_TEXTURE1); + if (programChoice == 2) { + glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[!horizontal]); + } + else if (programChoice == 3) { + glBindTexture(GL_TEXTURE_2D, bloomRenderer.Get...) + } + shaderBloomFinal.setInt("programChoice", programChoice)); + shaderBloomFinal.setFloat("exposure", exposure); + renderQuad(); + + //std::cout << "bloom: " << (bloom ? "on" : "off") << "| exposure: " << exposure << std::endl; + + // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) + // ------------------------------------------------------------------------------- + glfwSwapBuffers(window); + glfwPollEvents(); + } + + glfwTerminate(); + return 0; +} + +// renderCube() renders a 1x1 3D cube in NDC. +// ------------------------------------------------- +unsigned int cubeVAO = 0; +unsigned int cubeVBO = 0; +void renderCube() +{ + // initialize (if necessary) + if (cubeVAO == 0) + { + float vertices[] = { + // back face + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, // top-left + // front face + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left + // left face + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right + // right face + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left + // bottom face + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right + // top face + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left + 1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left + }; + glGenVertexArrays(1, &cubeVAO); + glGenBuffers(1, &cubeVBO); + // fill buffer + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + // link vertex attributes + glBindVertexArray(cubeVAO); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + } + // render Cube + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); +} + +// renderQuad() renders a 1x1 XY quad in NDC +// ----------------------------------------- +unsigned int quadVAO = 0; +unsigned int quadVBO; +void renderQuad() +{ + if (quadVAO == 0) + { + float quadVertices[] = { + // positions // texture Coords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + // setup plane VAO + glGenVertexArrays(1, &quadVAO); + glGenBuffers(1, &quadVBO); + glBindVertexArray(quadVAO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); + } + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); +} + +// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly +// --------------------------------------------------------------------------------------------------------- +void processInput(GLFWwindow *window) +{ + if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) + glfwSetWindowShouldClose(window, true); + + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) + camera.ProcessKeyboard(FORWARD, deltaTime); + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) + camera.ProcessKeyboard(BACKWARD, deltaTime); + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) + camera.ProcessKeyboard(LEFT, deltaTime); + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) + camera.ProcessKeyboard(RIGHT, deltaTime); + + if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS) + { + if (exposure > 0.0f) + exposure -= 0.001f; + else + exposure = 0.0f; + } + else if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS) + { + exposure += 0.001f; + } + + if (glfwGetKey(window, GLFW_KEY_1) == GLFW_PRESS) + { + programChoice = 1; + } + else if (glfwGetKey(window, GLFW_KEY_2) == GLFW_PRESS) + { + programChoice = 2; + } + else if (glfwGetKey(window, GLFW_KEY_3) == GLFW_PRESS) + { + programChoice = 3; + } +} + +// glfw: whenever the window size changed (by OS or user resize) this callback function executes +// --------------------------------------------------------------------------------------------- +void framebuffer_size_callback(GLFWwindow* window, int width, int height) +{ + // make sure the viewport matches the new window dimensions; note that width and + // height will be significantly larger than specified on retina displays. + glViewport(0, 0, width, height); +} + +// glfw: whenever the mouse moves, this callback is called +// ------------------------------------------------------- +void mouse_callback(GLFWwindow* window, double xposIn, double yposIn) +{ + float xpos = static_cast(xposIn); + float ypos = static_cast(yposIn); + if (firstMouse) + { + lastX = xpos; + lastY = ypos; + firstMouse = false; + } + + float xoffset = xpos - lastX; + float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top + + lastX = xpos; + lastY = ypos; + + camera.ProcessMouseMovement(xoffset, yoffset); +} + +// glfw: whenever the mouse scroll wheel scrolls, this callback is called +// ---------------------------------------------------------------------- +void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) +{ + camera.ProcessMouseScroll(static_cast(yoffset)); +} + +// utility function for loading a 2D texture from file +// --------------------------------------------------- +unsigned int loadTexture(char const * path, bool gammaCorrection) +{ + unsigned int textureID; + glGenTextures(1, &textureID); + + int width, height, nrComponents; + unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0); + if (data) + { + GLenum internalFormat; + GLenum dataFormat; + if (nrComponents == 1) + { + internalFormat = dataFormat = GL_RED; + } + else if (nrComponents == 3) + { + internalFormat = gammaCorrection ? GL_SRGB : GL_RGB; + dataFormat = GL_RGB; + } + else if (nrComponents == 4) + { + internalFormat = gammaCorrection ? GL_SRGB_ALPHA : GL_RGBA; + dataFormat = GL_RGBA; + } + + glBindTexture(GL_TEXTURE_2D, textureID); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, dataFormat, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + stbi_image_free(data); + } + else + { + std::cout << "Texture failed to load at path: " << path << std::endl; + stbi_image_free(data); + } + + return textureID; +} diff --git a/src/8.guest/2022/physically_based_bloom.cpp b/src/8.guest/2022/physically_based_bloom.cpp deleted file mode 100644 index e965047..0000000 --- a/src/8.guest/2022/physically_based_bloom.cpp +++ /dev/null @@ -1 +0,0 @@ -Hello From 4ab092ec969fb0c27bab4ae934e2b531378d07cf Mon Sep 17 00:00:00 2001 From: alexpanter Date: Fri, 27 May 2022 21:00:48 +0200 Subject: [PATCH 3/3] Make pbr bloom example compile --- .../6.physically_based_bloom/6.bloom_final.fs | 2 +- .../physically_based_bloom.cpp | 300 +++++++++++++++++- 2 files changed, 294 insertions(+), 8 deletions(-) diff --git a/src/8.guest/2022/6.physically_based_bloom/6.bloom_final.fs b/src/8.guest/2022/6.physically_based_bloom/6.bloom_final.fs index e004f2e..09879cb 100644 --- a/src/8.guest/2022/6.physically_based_bloom/6.bloom_final.fs +++ b/src/8.guest/2022/6.physically_based_bloom/6.bloom_final.fs @@ -42,7 +42,7 @@ void main() result = bloom_none(); break; } // tone mapping - vec3 result = vec3(1.0) - exp(-hdrColor * exposure); + result = vec3(1.0) - exp(-result * exposure); // also gamma correct while we're at it const float gamma = 2.2; result = pow(result, vec3(1.0 / gamma)); diff --git a/src/8.guest/2022/6.physically_based_bloom/physically_based_bloom.cpp b/src/8.guest/2022/6.physically_based_bloom/physically_based_bloom.cpp index 4497c2e..4103189 100644 --- a/src/8.guest/2022/6.physically_based_bloom/physically_based_bloom.cpp +++ b/src/8.guest/2022/6.physically_based_bloom/physically_based_bloom.cpp @@ -12,6 +12,7 @@ #include #include +#include void framebuffer_size_callback(GLFWwindow* window, int width, int height); void mouse_callback(GLFWwindow* window, double xpos, double ypos); @@ -27,6 +28,7 @@ const unsigned int SCR_HEIGHT = 600; bool bloom = true; float exposure = 1.0f; int programChoice = 1; +float bloomFilterRadius = 0.005f; // camera Camera camera(glm::vec3(0.0f, 0.0f, 5.0f)); @@ -38,6 +40,283 @@ bool firstMouse = true; float deltaTime = 0.0f; float lastFrame = 0.0f; +// bloom stuff +struct bloomMip +{ + glm::vec2 size; + glm::ivec2 intSize; + unsigned int texture; +}; + +class bloomFBO +{ +public: + bloomFBO(); + ~bloomFBO(); + bool Init(unsigned int windowWidth, unsigned int windowHeight, unsigned int mipChainLength); + void Destroy(); + void BindForWriting(); + const std::vector& MipChain() const; + +private: + bool mInit; + unsigned int mFBO; + std::vector mMipChain; +}; + +bloomFBO::bloomFBO() : mInit(false) {} +bloomFBO::~bloomFBO() {} + +bool bloomFBO::Init(unsigned int windowWidth, unsigned int windowHeight, unsigned int mipChainLength) +{ + if (mInit) return true; + + glGenFramebuffers(1, &mFBO); + glBindFramebuffer(GL_FRAMEBUFFER, mFBO); + + glm::vec2 mipSize((float)windowWidth, (float)windowHeight); + glm::ivec2 mipIntSize((int)windowWidth, (int)windowHeight); + // Safety check + if (windowWidth > (unsigned int)INT_MAX || windowHeight > (unsigned int)INT_MAX) { + std::cerr << "Window size conversion overflow - cannot build bloom FBO!" << std::endl; + return false; + } + + for (GLuint i = 0; i < mipChainLength; i++) + { + bloomMip mip; + + mipSize *= 0.5f; + mipIntSize /= 2; + mip.size = mipSize; + mip.intSize = mipIntSize; + + glGenTextures(1, &mip.texture); + glBindTexture(GL_TEXTURE_2D, mip.texture); + // we are downscaling an HDR color buffer, so we need a float texture format + glTexImage2D(GL_TEXTURE_2D, 0, GL_R11F_G11F_B10F, + (int)mipSize.x, (int)mipSize.y, + 0, GL_RGB, 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); + + std::cout << "Created bloom mip " << mipIntSize.x << 'x' << mipIntSize.y << std::endl; + mMipChain.emplace_back(mip); + } + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, mMipChain[0].texture, 0); + + // setup attachments + unsigned int attachments[1] = { GL_COLOR_ATTACHMENT0 }; + glDrawBuffers(1, attachments); + + // check completion status + int status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) + { + printf("gbuffer FBO error, status: 0x%x\n", status); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return false; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + mInit = true; + return true; +} + +void bloomFBO::Destroy() +{ + for (int i = 0; i < (int)mMipChain.size(); i++) { + glDeleteTextures(1, &mMipChain[i].texture); + mMipChain[i].texture = 0; + } + glDeleteFramebuffers(1, &mFBO); + mFBO = 0; + mInit = false; +} + +void bloomFBO::BindForWriting() +{ + glBindFramebuffer(GL_FRAMEBUFFER, mFBO); +} + +const std::vector& bloomFBO::MipChain() const +{ + return mMipChain; +} + + + +class BloomRenderer +{ +public: + BloomRenderer(); + ~BloomRenderer(); + bool Init(unsigned int windowWidth, unsigned int windowHeight); + void Destroy(); + void RenderBloomTexture(unsigned int srcTexture, float filterRadius); + unsigned int BloomTexture(); + unsigned int BloomMip_i(int index); + +private: + void RenderDownsamples(unsigned int srcTexture); + void RenderUpsamples(float filterRadius); + + bool mInit; + bloomFBO mFBO; + glm::ivec2 mSrcViewportSize; + glm::vec2 mSrcViewportSizeFloat; + Shader* mDownsampleShader; + Shader* mUpsampleShader; + + bool mKarisAverageOnDownsample = true; +}; + +BloomRenderer::BloomRenderer() : mInit(false) {} +BloomRenderer::~BloomRenderer() {} + +bool BloomRenderer::Init(unsigned int windowWidth, unsigned int windowHeight) +{ + if (mInit) return true; + mSrcViewportSize = glm::ivec2(windowWidth, windowHeight); + mSrcViewportSizeFloat = glm::vec2((float)windowWidth, (float)windowHeight); + + // Framebuffer + const unsigned int num_bloom_mips = 6; // TODO: Play around with this value + bool status = mFBO.Init(windowWidth, windowHeight, num_bloom_mips); + if (!status) { + std::cerr << "Failed to initialize bloom FBO - cannot create bloom renderer!\n"; + return false; + } + + // Shaders + mDownsampleShader = new Shader("6.new_downsample.vs", "6.new_downsample.fs"); + mUpsampleShader = new Shader("6.new_upsample.vs", "6.new_upsample.fs"); + + // Downsample + mDownsampleShader->use(); + mDownsampleShader->setInt("srcTexture", 0); + glUseProgram(0); + + // Upsample + mUpsampleShader->use(); + mUpsampleShader->setInt("srcTexture", 0); + glUseProgram(0); + + return true; +} + +void BloomRenderer::Destroy() +{ + mFBO.Destroy(); + delete mDownsampleShader; + delete mUpsampleShader; +} + +void BloomRenderer::RenderDownsamples(unsigned int srcTexture) +{ + const std::vector& mipChain = mFBO.MipChain(); + + mDownsampleShader->use(); + mDownsampleShader->setVec2("srcResolution", mSrcViewportSizeFloat); + if (mKarisAverageOnDownsample) { + mDownsampleShader->setInt("mipLevel", 0); + } + + // Bind srcTexture (HDR color buffer) as initial texture input + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, srcTexture); + + // Progressively downsample through the mip chain + for (int i = 0; i < (int)mipChain.size(); i++) + { + const bloomMip& mip = mipChain[i]; + glViewport(0, 0, mip.size.x, mip.size.y); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, mip.texture, 0); + + // Render screen-filled quad of resolution of current mip + renderQuad(); + + // Set current mip resolution as srcResolution for next iteration + mDownsampleShader->setVec2("srcResolution", mip.size); + // Set current mip as texture input for next iteration + glBindTexture(GL_TEXTURE_2D, mip.texture); + // Disable Karis average for consequent downsamples + if (i == 0) { mDownsampleShader->setInt("mipLevel", 1); } + } + + glUseProgram(0); +} + +void BloomRenderer::RenderUpsamples(float filterRadius) +{ + const std::vector& mipChain = mFBO.MipChain(); + + mUpsampleShader->use(); + mUpsampleShader->setFloat("filterRadius", filterRadius); + + // Enable additive blending + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE); + glBlendEquation(GL_FUNC_ADD); + + for (int i = (int)mipChain.size() - 1; i > 0; i--) + { + const bloomMip& mip = mipChain[i]; + const bloomMip& nextMip = mipChain[i-1]; + + // Bind viewport and texture from where to read + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, mip.texture); + + // Set framebuffer render target (we write to this texture) + glViewport(0, 0, nextMip.size.x, nextMip.size.y); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, nextMip.texture, 0); + + // Render screen-filled quad of resolution of current mip + renderQuad(); + } + + // Disable additive blending + //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + + glUseProgram(0); +} + +void BloomRenderer::RenderBloomTexture(unsigned int srcTexture, float filterRadius) +{ + mFBO.BindForWriting(); + + this->RenderDownsamples(srcTexture); + this->RenderUpsamples(filterRadius); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + // Restore viewport + glViewport(0, 0, mSrcViewportSize.x, mSrcViewportSize.y); +} + +GLuint BloomRenderer::BloomTexture() +{ + return mFBO.MipChain()[0].texture; +} + +GLuint BloomRenderer::BloomMip_i(int index) +{ + const std::vector& mipChain = mFBO.MipChain(); + int size = (int)mipChain.size(); + return mipChain[(index > size-1) ? size-1 : (index < 0) ? 0 : index].texture; +} + + + + + int main() { // glfw: initialize and configure @@ -87,9 +366,6 @@ int main() Shader shaderBlur("6.old_blur.vs", "6.old_blur.fs"); Shader shaderBloomFinal("6.bloom_final.vs", "6.bloom_final.fs"); - Shader shaderDownsample("6.new_downsample.vs", "6.new_downsample.fs"); - Shader shaderUpsample("6.new_upsample.vs", "6.new_upsample.fs"); - // load textures // ------------- unsigned int woodTexture = loadTexture(FileSystem::getPath("resources/textures/wood.png").c_str(), true); // note that we're loading the texture as an SRGB texture @@ -174,6 +450,11 @@ int main() shaderBloomFinal.setInt("scene", 0); shaderBloomFinal.setInt("bloomBlur", 1); + // bloom renderer + // -------------- + BloomRenderer bloomRenderer; + bloomRenderer.Init(SCR_WIDTH, SCR_HEIGHT); + // render loop // ----------- while (!glfwWindowShouldClose(window)) @@ -275,6 +556,7 @@ int main() if (programChoice < 1 || programChoice > 3) { programChoice = 1; } bloom = (programChoice == 1) ? false : true; + bool horizontal = true; // 2.A) bloom is disabled // ---------------------- @@ -287,7 +569,7 @@ int main() // ------------------------------------------------------ else if (programChoice == 2) { - bool horizontal = true, first_iteration = true; + bool first_iteration = true; unsigned int amount = 10; shaderBlur.use(); for (unsigned int i = 0; i < amount; i++) @@ -307,7 +589,7 @@ int main() // ------------------------------------------------------------------- else if (programChoice == 3) { - + bloomRenderer.RenderBloomTexture(colorBuffers[1], bloomFilterRadius); } // 3. now render floating point color buffer to 2D quad and tonemap HDR colors to default framebuffer's (clamped) color range @@ -317,13 +599,16 @@ int main() glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, colorBuffers[0]); glActiveTexture(GL_TEXTURE1); + if (programChoice == 1) { + glBindTexture(GL_TEXTURE_2D, 0); // trick to bind invalid texture "0", we don't care either way! + } if (programChoice == 2) { glBindTexture(GL_TEXTURE_2D, pingpongColorbuffers[!horizontal]); } else if (programChoice == 3) { - glBindTexture(GL_TEXTURE_2D, bloomRenderer.Get...) + glBindTexture(GL_TEXTURE_2D, bloomRenderer.BloomTexture()); } - shaderBloomFinal.setInt("programChoice", programChoice)); + shaderBloomFinal.setInt("programChoice", programChoice); shaderBloomFinal.setFloat("exposure", exposure); renderQuad(); @@ -335,6 +620,7 @@ int main() glfwPollEvents(); } + bloomRenderer.Destroy(); glfwTerminate(); return 0; }