这里主要讨论 Bloom效果引发的各种问题,及其解决方案。
记录几个高斯模糊的做法:
https://www.shadertoy.com/view/XdfGDH
https://www.shadertoy.com/view/ltScRG
https://gamedev.stackexchange.com/questions/166182/my-single-pass-gaussian-blur-looks-awful
https://github.com/Jam3/glsl-fast-gaussian-blur
原作者的做法这样的:
注意原作者的贴图从头到尾都不是抗锯齿(MSAA),所以操作起来一路顺手。而且场景也简单就几个贴图。
梁大大已经告诉我MSAA是废品,让我考虑FXAA。
而我的做法是:
hdrFBO里的纹理是多重采样纹理。我一开始想的是直接用FBO一步传递到一个postprocessing的buffer中处理。
也就是渲染只分2部分,一部分渲染场景,第二部分把这个渲染的2个buffer传递到postprocessing先测试。
遇到的问题就是:
1,hdrFBO多重采样纹理FBO渲染场景完成之后必须拷贝到post processing 的FBO.
2,hdrFBO多重采样FBO的第一个场景纹理复制过去到post processing没问题。
3,hdrFBO多重采样的第二个Texture(也就是第二个color attachment)是个多重采样纹理,要传递到post的材质,需要这么传:
4,顺便认识到MSAA的这种设计,直接限制了代码,让代码可以写的更烂
vec4 textureMSAA(sampler2DMS tex,vec2 TexCoords){ ivec2 texSize = textureSize(tex); vec4 mtex=vec4(1); for(int i = 0; i < 32; ++i) { mtex += texelFetch(tex, ivec2(TexCoords*texSize ), i); } mtex = mtex / 32.0f; return mtex; }
#version 450 core #extension GL_ARB_shading_language_include : require #include "/shaders/common/utils.glsl" #include "/shaders/common/postprocess.glsl" out vec4 FragColor; in vec2 TexCoords; uniform sampler2D sceneImage; uniform sampler2DMS brightImage; uniform float exposure; void main() { vec4 scene = texture(sceneImage,TexCoords); vec4 mtex = textureMSAA(brightImage,TexCoords); vec3 result = toneMapping(scene.rgb, exposure); FragColor = vec4(result,1.0f); }
到这里顺便复习下以前做过的相关的Framebuffer和这个Quad post processing的关系。为什么有时候postprocessing需要个framebuffer?为什么有时候不要:
接下来现在用2中方案实现:思路如下:
实际中别用第一个方案,比较垃圾,直接第二个,把multisample color attachment 转换成普通的GL_TEXTURE_2D。然后就各种后期效果就行。
多采样代码,注意里面的32 samplers,是在一开始设置framebuffer,renderbuffer就要提前设置好的,平常应该用unifrom int samplers从C++传入:
vec4 textureMSAA(sampler2DMS tex,vec2 TexCoords){ ivec2 texSize = textureSize(tex); vec4 mtex=vec4(1); for(int i = 0; i < 32; ++i) { mtex += texelFetch(tex, ivec2(TexCoords*texSize ), i); } mtex = mtex / 32.0f; return mtex; }
转换的伪代码就是:
// 2020 4 6 // This shader for convert multisampled buffer with multi attachements // to basis GL_TEXTURE_2D // frag file export two attachents, and input sampler2dMS texture #version 450 core #extension GL_ARB_shading_language_include : require #include "/shaders/common/utils.glsl" in vec2 TexCoords; layout (location = 0 ) out vec4 colorAttament0; layout (location = 1 ) out vec4 colorAttament1; layout (location = 2 ) out vec4 colorAttament2; layout (location = 3 ) out vec4 colorAttament3; layout (location = 4 ) out vec4 colorAttament4; layout (location = 5 ) out vec4 colorAttament5; layout (location = 6 ) out vec4 colorAttament6; // ... and you can export more texture , C++ code need: // static FrameBufferAttachments<GL_RGBA16F,6> convertFBO; // for study opengl, writing with hard code // for best should use a uniform texture count : uniform int texCount; // and: uniform sampler2dMS mtexs[texCount] uniform sampler2DMS mtex0; uniform sampler2DMS mtex1; uniform sampler2DMS mtex2; uniform sampler2DMS mtex3; uniform sampler2DMS mtex4; uniform sampler2DMS mtex5; uniform sampler2DMS mtex6; void main() { colorAttament0 = textureMSAA(mtex0, TexCoords); colorAttament1 = textureMSAA(mtex1, TexCoords); colorAttament2 = textureMSAA(mtex2, TexCoords); colorAttament3 = textureMSAA(mtex3, TexCoords); colorAttament4 = textureMSAA(mtex4, TexCoords); colorAttament5 = textureMSAA(mtex5, TexCoords); colorAttament6 = textureMSAA(mtex6, TexCoords); }
CP36:
逻辑:
int display_w, display_h; glfwGetFramebufferSize(frameWindow->getWindow(), &display_w, &display_h); glm::mat4 view = camera->GetViewMatrix(); glm::mat4 projection = glm::perspective(glm::radians(camera->fov),float(SRC_WIDTH) / float(SRC_HEIGHT),0.1f, 1000.0f); // object world transformation glm::mat4 model = glm::mat4(1.0f); // per-frame time logic // -------------------- float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; ImGui::Begin("Distance Light Params"); ImGui::ColorEdit3("color", color); ImGui::SliderFloat("lightIntensity",&intensity,0,10.0f); ImGui::SliderFloat("shadowNear",&shadowNear,1,10.0f); ImGui::SliderFloat("shadowFar",&shadowFar,100,800.0f); distanceLight.setShadowNearFarPlane(shadowNear,shadowFar); // ----------------------------------- RENDER SHADOW MAP ----------------------------------- // 1. render depth of scene to texture (from light's perspective) glViewport(0, 0, distanceLight.shadowPtr->width, distanceLight.shadowPtr->height); glBindFramebuffer(GL_FRAMEBUFFER, distanceLight.shadowPtr->fbo); glClear(GL_DEPTH_BUFFER_BIT); //distanceLight.setDir(glm::vec3()) distanceLight.renderUseDepthShader(); // Render SceneAssembly in depth shadow shader RenderScene(model,distanceLight.shadowPtr->lightView,distanceLight.shadowPtr->lightProjection,&distanceLight.shadowPtr->shader); // ----------------------------------- RENDER SHADOW MAP ----------------------------------- glBindFramebuffer(GL_FRAMEBUFFER, 0); ImGui::End(); // 2.---------------render scene as normal ,draw scene as normal in multisampled buffers--------------- glViewport(0, 0, display_w, display_w/2); hdrMultiSamplerFBO.update(display_w,display_w/2); // update render buffer UpdateMultiSampledRenderBufferObject(display_w, display_w/2,multiRBO,samplers); glBindFramebuffer(GL_FRAMEBUFFER, hdrMultiSamplerFBO.fbo); // BIND TO MULTI sampled fbo hdrMultiSamplerFBO.drawBuffers(); ClearAllBufferColor(); glEnable(GL_DEPTH_TEST); RenderScene(model,view,projection); RenderGridAndGnomon(view,projection); // render distance light in multiFBO distanceLight.setLightIntensity(intensity); distanceLight.shader.use(); distanceLight.shader.setMatrix(view,projection); distanceLight.setLightColor(glm::vec3(color[0] ,color[1],color[2] )); distanceLight.draw(); // 2.---------------render scene as normal ,draw scene as normal in multisampled buffers--------------- // 3----------------------------- Render Scene to ConvertFBO ------------------------ glViewport(0, 0, display_w, display_w/2); convertFBO.update(display_w, display_w/2); convertPostProcess.update(display_w,display_w/2); convertFBO.drawBuffers(); glBindFramebuffer(GL_FRAMEBUFFER, convertFBO.fbo);; // render quad in convert FBO glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); convertPostProcess.shader.use(); glActiveTexture(hdrMultiSamplerFBO.texChannels[0]); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, hdrMultiSamplerFBO.textures[0]); glActiveTexture(hdrMultiSamplerFBO.texChannels[1]); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, hdrMultiSamplerFBO.textures[1]); convertPostProcess.shader.setInt("tex0",hdrMultiSamplerFBO.texUnits[0]); convertPostProcess.shader.setInt("tex1",hdrMultiSamplerFBO.texUnits[1]); /* // This code verty simple, and it's do not use texture unit glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, hdrMultiSamplerFBO.textures[0]); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, hdrMultiSamplerFBO.textures[1]); convertPostProcess.shader.setInt("tex0",0); convertPostProcess.shader.setInt("tex1",1); */ convertPostProcess.draw(); // 3----------------------------- Render Scene to ConvertFBO ------------------------ // 4 ------------- RENDER TO Post processing FBO ----------------- #if 1 // use the convert FBO texture for final processing FBO toneMapping.update(display_w,display_w/2); glBindFramebuffer(GL_FRAMEBUFFER, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); toneMapping.shader.use(); /* // // This code use texture unit glActiveTexture(convertFBO.texChannels[0]); glBindTexture(GL_TEXTURE_2D,convertFBO.textures[0]); toneMapping.shader.setInt("sceneImage", convertFBO.texUnits[0]); glActiveTexture(convertFBO.texChannels[1]); glBindTexture(GL_TEXTURE_2D,convertFBO.textures[1]); toneMapping.shader.setInt("brightImage", convertFBO.texUnits[1]); */ //This code very simple, and it's do not use texture unit glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D,convertFBO.textures[0]); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D,convertFBO.textures[1]); toneMapping.shader.setInt("sceneImage", 0); toneMapping.shader.setInt("brightImage", 1); toneMapping.shader.setFloat("exposure",4.0f); toneMapping.draw(); #else toneMapping.update(display_w,display_w/2); CopyFrameBuffer(hdrMultiSamplerFBO.fbo, toneMapping.FBO, display_w, display_w / 2); glBindFramebuffer(GL_FRAMEBUFFER, 0); ClearAllBufferColor(); toneMapping.shader.use(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D,toneMapping.FBOTexture); toneMapping.shader.setInt("sceneImage", 0); glActiveTexture(hdrMultiSamplerFBO.texChannels[1]); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE,hdrMultiSamplerFBO.textures[1]); toneMapping.shader.setInt("brightImage", hdrMultiSamplerFBO.texUnits[1]); toneMapping.shader.setFloat("exposure",4.0f); toneMapping.draw(); #endif
init:
// framebuffer static MultiSampledFrameBufferAttachments<GL_RGBA16F,2> hdrMultiSamplerFBO; // multisampler with two attachment static GLuint multiRBO; // renderbuffer static DrawPostProcessingQuad convertPostProcess; static FrameBufferAttachments<GL_RGBA16F,2> convertFBO; // multisampler convert to GL_TEXTURE_2D two attachment //
cout << "---------------- STARTING HDR Sampled FrameBuffer ----------- "; // Create MultiSampler Framebuffer with two attached texture hdrMultiSamplerFBO.initialize(SRC_WIDTH,SRC_HEIGHT,samplers); hdrMultiSamplerFBO.setUpTexChannels(SceneAssembly::currentActiveTextureChannel,SceneAssembly::currentTextureLoaderIndex); hdrMultiSamplerFBO.drawBuffers(); hdrMultiSamplerFBO.debug(); cout << "---------------- ENDING HDR Sampled FrameBuffer ----------- "; CreateMultiSampledRenderBufferObject(SRC_WIDTH, SRC_HEIGHT, multiRBO, samplers); cout << " ---------------- STARTING Convert FrameBuffer ----------- "; // Create Convert Framebuffer with two attached texture convertFBO.initialize(SRC_WIDTH,SRC_HEIGHT); convertFBO.setUpTexChannels(SceneAssembly::currentActiveTextureChannel,SceneAssembly::currentTextureLoaderIndex); convertFBO.drawBuffers(); convertFBO.debug(); cout << "next "; cout << "---------------- ENDING Convert FrameBuffer ----------- ";
CP_37:
接下来就是one pass的高斯模糊 bloom。
vec3 toneMapping(vec3 hdrColor ,float exposure){ const float gamma = 2.2; // 曝光色调映射 vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure); // Gamma校正 mapped = pow(mapped, vec3(1.0 / gamma)); return mapped; } // https://www.youtube.com/watch?v=01xAJ5giuu0 // https://zhuanlan.zhihu.com/p/21983679 vec3 ReinhardMapping(vec3 hdrColor) { vec3 mapped = hdrColor / (1.0f +hdrColor); mapped = pow(mapped, vec3(1.0 / 2.2f)); return mapped; } vec3 ACESToneMapping(vec3 color,float exposure) { const float gamma = 2.2; const float A = 2.51f; const float B = 0.03f; const float C = 2.43f; const float D = 0.59f; const float E = 0.14f; color *= exposure; vec3 mapped = (color * (A * color + B)) / (color * (C * color + D) + E); mapped = pow(mapped, vec3(1.0/gamma)); return mapped; } //precision mediump float; float normpdf(in float x, in float sigma) { return 0.39894*exp(-0.5*x*x/(sigma*sigma))/sigma; } vec3 gaussianBlur(sampler2D tex, vec2 texCoords){ //https://www.shadertoy.com/view/XdfGDH //declare stuff const int mSize = 30; const int kSize = (mSize-1)/2; float kernel[mSize]; vec3 final_colour = vec3(0.0); //create the 1-D kernel float sigma = 7.0; float Z = 0.0; for (int j = 0; j <= kSize; ++j) { kernel[kSize+j] = kernel[kSize-j] = normpdf(float(j), sigma); } //get the normalization factor (as the gaussian has been clamped) for (int j = 0; j < mSize; ++j) { Z += kernel[j]; } //read out the texels for (int i=-kSize; i <= kSize; ++i) { for (int j=-kSize; j <= kSize; ++j) { //https://gamedev.stackexchange.com/questions/166182/my-single-pass-gaussian-blur-looks-awful final_colour += kernel[kSize+j]*kernel[kSize+i]* texture(tex, (texCoords+vec2(float(i),float(j))/textureSize(tex, 0).xy) ).rgb; } } return final_colour/(Z*Z); }
#version 450 core #extension GL_ARB_shading_language_include : require #include "/shaders/common/utils.glsl" #include "/shaders/common/postprocess.glsl" out vec4 FragColor; in vec2 TexCoords; uniform sampler2D sceneImage; uniform sampler2D brightImage; uniform float exposure; void main() { vec3 scene = texture(sceneImage,TexCoords).rgb; vec3 brightColor = texture(brightImage,TexCoords).rgb; vec3 blur = gaussianBlur(brightImage,TexCoords).rgb; scene+= blur*3.0; //scene = toneMapping(scene, exposure) ; scene = ACESToneMapping(scene,exposure); FragColor = vec4( scene , 1.0f); }
CP_38:
two pass blur:
bool horizontal = true, first_iteration = true; unsigned int amount = 400; pingpongBlurShader.use(); for (unsigned int i = 0; i < amount; i++) { glBindFramebuffer(GL_FRAMEBUFFER, pingpongPost[horizontal].FBO); pingpongBlurShader.setInt("horizontal", 1); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, first_iteration ? convertFBO.textures[1] : pingpongPost[!horizontal].FBOTexture); // bind texture of other framebuffer (or scene if first iteration) pingpongPost[0].draw(pingpongBlurShader); horizontal = !horizontal; if (first_iteration) first_iteration = false; }