Related
The goal:
Effectively read and write to the same texture, like how Shadertoy does their buffers.
The setup:
I have a basic feedback system with 2 textures each connected to a framebuffer. As I render to frame buffer 1, I bind Texture 2 for sampling in the shader. Then, as I render to frame buffer 2, I bind texture 1 for sampling, and repeat. Finally, I output texture 1 to the whole screen with the default frame buffer and a sperate shader.
The issue:
This almost works as intended as I'm able to read from the texture in the shader and also output to it, creating the desired feedback loop.
The problem is that the frame buffers do not clear completely to black it seems.
To test, I made a simple trailing effect.
In shadertoy, the trail completely disappears as intended:
Live in shadertoy
But in my app, the trail begins to disappear, but leaves a small amount behind:
My thoughts are I'm not clearing the frame buffers correctly or I am not using GLFW's double buffering correctly in this instance. I've tried every combination of clearing the framebuffers but I must be missing something here.
The code:
Here is the trailing effect shader with a moving circle (Same as above images)
#version 330
precision highp float;
uniform sampler2D samplerA; // Texture sampler
uniform float uTime; // current execution time
uniform vec2 uResolution; // resolution of window
void main()
{
vec2 uv = gl_FragCoord.xy / uResolution.xy; // Coordinates from 0 - 1
vec3 tex = texture(samplerA, uv).xyz;// Read ping pong texture that we are writing to
vec2 pos = .3*vec2(cos(uTime), sin(uTime)); // Circle position (circular motion around screen)
vec3 c = mix(vec3(1.), vec3(0), step(.0, length(uv - pos)-.07)); // Circle color
tex = mix(c, tex, .981); // Replace some circle color with the texture color
gl_FragColor = vec4(tex, 1.0); // Output to texture
}
Frame buffer and texture creation:
// -- Generate frame buffer 1 --
glGenFramebuffers(1, &frameBuffer1);
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer1);
// Generate texture 1
glGenTextures(1, &texture1);
// Bind the newly created texture
glBindTexture(GL_TEXTURE_2D, texture1);
// Create an empty image
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1920, 1080, 0, GL_RGBA, GL_FLOAT, 0);
// Nearest filtering, for sampling
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// Attach output texture to frame buffer
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture1, 0);
// -- Generate frame buffer 2 --
glGenFramebuffers(1, &frameBuffer2);
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer2);
// Generate texture 2
glGenTextures(1, &texture2);
// Bind the newly created texture
glBindTexture(GL_TEXTURE_2D, texture2);
// Create an empty image
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1920, 1080, 0, GL_RGBA, GL_FLOAT, 0);
// Nearest filtering, for sampling
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// Attach texture 2 to frame buffer 2
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture2, 0);
Main loop:
while(programIsRunning){
// Draw scene twice, once to frame buffer 1 and once to frame buffer 2
for (int i = 0; i < 2; i++)
{
// Start trailing effect shader program
glUseProgram(program);
glViewport(0, 0, platform.windowWidth(), platform.windowHeight());
// Write to frame buffer 1
if (i == 0)
{
// Bind and clear frame buffer 1
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer1);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Bind texture 2 for sampler
glActiveTexture(GL_TEXTURE0 + 0);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(uniforms.samplerA, 0);
}
else // Write to frame buffer 2
{
// Bind and clear frame buffer 2
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer2);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Bind texture 1 for sampler
glActiveTexture(GL_TEXTURE0 + 0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(uniforms.samplerA, 0);
}
// Render to screen
glDrawArrays(GL_TRIANGLES, 0, 6);
}
// Start screen shader program
glUseProgram(screenProgram);
// Bind default frame buffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, platform.windowWidth(), platform.windowHeight());
// Bind texture 1 for sampler (binding texture 2 should be the same?)
glActiveTexture(GL_TEXTURE0 + 0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(uniforms.samplerA, 0);
// Draw final rectangle to screen
glDrawArrays(GL_TRIANGLES, 0, 6);
// Swap glfw buffers
glfwSwapBuffers(platform.window());
}
If this is an issue with clearing I would really like to know why. Changing which frame buffer gets cleared doesn't seem to change anything.
I will keep experimenting in the meantime.
Thank you!
The problem is that you are creating a texture with too little precision for your exponential moving average computations to ultimately discretize to zero.
In your call to:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1920, 1080, 0, GL_RGBA, GL_FLOAT, 0);
you are using the unsized internal format GL_RGBA (third argument), which will very likely ultimately result in the GL_RGBA8 internal format actually being used. So, all channels will have a precision of 8 bits.
You probably believed that using GL_FLOAT as the argument for the type parameter results in a 32-bit floating-point texture being allocated: It does not. The type parameter is used to indicate to OpenGL how it should interpret your data (last parameter of the function) when/if you actually specify data to be uploaded. You use 0/NULL so the type parameter really does not influence the call, as there is no memory to be interpreted as float values to be uploaded.
So, your texture will have a precision of 8 bits per channel and therefore each channel can hold at most 256 different values.
Given that in your shown RGB image the RGB value is 24 for each channel, we can do the math how OpenGL gets to this value and why it won't get any lower than that:
First, let's do another round of your exponential moving average between (0, 0, 0) and (24, 24, 24)/255 with a factor of your 0.981:
d = (24, 24, 24)/255 * 0.981
If we had infinite precision, this value d would be 0.09232941176.
Now, let's see what RGB value within the representable range [0, 255] this comes close to: 0.09232941176 * 255 = 23.5439999988.
So, this value is actually (when correctly rounded to the nearest representable value within the [0, 255] discretization) 24 again. And that's where it stays.
In order to fix this, you likely need to use a higher precision internal texture format, such as GL_RGBA32F (which is actually what ShaderToy itself uses).
Implementing some effect, I end up with 1 frame buffer associated to 1 texture, which holds my final scene. This texture is then applied on a fullscreen quad.
The result is what I expect as far as the effect goes, but I noticed that edges on the scene thus rendered, weren't smooth - presumably, because multi-sampling did not apply during render-to-framebuffer passes, as it does when I render directly to the screen buffer.
So my question is
How can I apply/use multi-sampling on this final texture, so that its content shows smooth edges?
EDIT: I have removed the original version of my code here, which was using
a classic FrameBuffer + Texture not multi-sampled. Below is the lastest,
following suggestions in the comments.
For now also, I'll focusing on getting the glBlitFramebuffer approach to work!
So my code now goes like so:
// Unlike before, finalTexture is multi-sampled, thus created like this:
glGenFramebuffers(1, &finalFrame);
glGenTextures(1, &finalTexture);
glBindFramebuffer(GL_FRAMEBUFFER, finalFrame);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, finalTexture);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA, w, h, GL_TRUE);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D_MULTISAMPLE,
finalTexture,
0);
// Alternative using a render buffer instead of a texture.
//glGenRenderbuffers(1, &finalColor);
//glBindRenderbuffer(GL_RENDERBUFFER, finalColor);
//glRenderbufferStorageMultisample(GL_RENDERBUFFER, 8, GL_RGBA, w, h);
//glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, finalColor);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Then I introduced a new frame buffer to resolve the multi-sampling:
// This one's not multi-sampled.
glGenFramebuffers(1, &resolveFrame);
glGenTextures(1, &resolveTexture);
glBindFramebuffer(GL_FRAMEBUFFER, resolveFrame);
glBindTexture(GL_TEXTURE_2D, resolveTexture);
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_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);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
resolveTexture,
0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Now a lot of code to produce a glowing effect, things like:
// 1. Generate 1 frame buffer with 2 color attachments (textures) - no multisampling
// 2. Render the 3D scene to it:
// - texture 0 receives the entire scene
// - texture 1 receives glowing objects only
// 3. Generate 2 frame buffers with 1 color attachment (texture) each - no multisampling
// - we can call them Texture 2 and texture 3
// 4. Ping-pong Render a fullscreen textured quad on them
// - On the first iteration we use texture 1
// - Then On each following iteration we use one another's texture (3,2,3...)
// - Each time we apply a gaussian blur
// 5. Finally sum texture 0 and texture 3 (holding the last blur result)
// - For this we create a multi-sampled frame buffer:
// - Created as per code here above: finalFrame & **finalTexture**
// - To produce the sum, we draw a full screen texured quad with 2 sampler2D:
// - The fragment shader then computes texture0+texture3 on each pixel
// - finalTexture now holds the scene as I expect it to be
// Then I resolve the multi-sampled texture into a normal one:
glBindFramebuffer(GL_READ_FRAMEBUFFER, finalFrame);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFrame);
glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// And the last stage: render onto the screen:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, resolveTexture);
drawFullScreenQuad( ... );
The resulting output is correct, meaning that I can see the scene with the desired glowing effect... But no apparent multi-sampling! :(
Note: I am starting to wonder, if I am using multi-sampling at the right stage - I will be experimenting on this - but any chance I should use it when rendering the initial 3D scene for the first time, on the initial FBOs? (the ones I refer to in the comments and I didn't want to post here to avoid confusion :s)
I added more detailed comments on what's going on before this last stage with final & resolve frame buffers.
You have: "step 5. Finally sum texture 0 and texture 3 (holding the last blur result) - For this we create a multi-sampled frame buffer". But this way multisampling will only apply to fullscreen quad.
"if I am using multi-sampling at the right stage" so the answer to your question is no, you need to use multisampling on another stage when you render a scene.
I have very similar setup with framebuffers (that one which is used to render the scene is multisampled) two output textures (for color info and for highlights which will later be blurred to achieve glow) and ping-pong framebuffers. I also use glBlitFramebuffer solution (also I use 2 blit calls for each color attachment, each one will go in own texture), have not found any way of making it render directly into framebuffer with attached texture.
If you want some code, this is solution that worked for me (it is in C# though):
// ----------------------------
// Initialization
int BlitFrameBufferHandle = GL.GenFramebuffer();
GL.BindFramebuffer(FramebufferTarget.Framebuffer, BlitFrameBufferHandle);
// need to setup this for 2 color attachments:
GL.DrawBuffers(2, new [] {DrawBuffersEnum.ColorAttachment0, DrawBuffersEnum.ColorAttachment1});
// create texture 0
int ColorTextureHandle0 = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, ColorTextureHandle0);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear); // can use nearest for min and mag filter also
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
// for HRD use PixelInternalFormat.Rgba16f and PixelType.Float. Otherwise PixelInternalFormat.Rgba8 and PixelType.UnsignedByte
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba16f, Width, Height, 0, PixelFormat.Rgba, PixelType.Float, IntPtr.Zero);
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, ColorTextureHandle0, 0);
// create texture 1
int ColorTextureHandle1 = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, ColorTextureHandle1);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba16f, Width, Height, 0, PixelFormat.Rgba, PixelType.Float, IntPtr.Zero);
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment1, TextureTarget.Texture2D, ColorTextureHandle1, 0);
// check FBO error
var error = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
if (error != FramebufferErrorCode.FramebufferComplete) {
throw new Exception($"OpenGL error: Framwbuffer status {error.ToString()}");
}
int FrameBufferHandle = GL.GenFramebuffer();
GL.BindFramebuffer(FramebufferTarget.Framebuffer, FrameBufferHandle);
// need to setup this for 2 color attachments:
GL.DrawBuffers(2, new [] {DrawBuffersEnum.ColorAttachment0, DrawBuffersEnum.ColorAttachment1});
// render buffer 0
int RenderBufferHandle0 = GL.GenRenderbuffer();
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, RenderBufferHandle0);
GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, 8, RenderbufferStorage.Rgba16f, Width, Height);
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, RenderbufferTarget.Renderbuffer, RenderBufferHandle0);
// render buffer 1
int RenderBufferHandle1 = GL.GenRenderbuffer();
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, RenderBufferHandle1);
GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, 8, RenderbufferStorage.Rgba16f, Width, Height);
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment1, RenderbufferTarget.Renderbuffer, RenderBufferHandle1);
// depth render buffer
int DepthBufferHandle = GL.GenRenderbuffer();
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, DepthBufferHandle);
GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, 8, RenderbufferStorage.DepthComponent24, Width, Height);
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, RenderbufferTarget.Renderbuffer, DepthBufferHandle);
// check FBO error
var error = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
if (error != FramebufferErrorCode.FramebufferComplete) {
throw new Exception($"OpenGL error: Framwbuffer status {error.ToString()}");
}
// unbind FBO
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
// ----------------------------
// Later for each frame
GL.BindFramebuffer(FramebufferTarget.Framebuffer, FrameBufferHandle);
// render scene ...
// blit data from FrameBufferHandle to BlitFrameBufferHandle
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, FrameBufferHandle);
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, BlitFrameBufferHandle);
// blit color attachment0
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
GL.BlitFramebuffer(
0, 0, Width, Height,
0, 0, Width, Height,
ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest
);
// blit color attachment1
GL.ReadBuffer(ReadBufferMode.ColorAttachment1);
GL.DrawBuffer(DrawBufferMode.ColorAttachment1);
GL.BlitFramebuffer(
0, 0, Width, Height,
0, 0, Width, Height,
ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest
);
// after that use textures ColorTextureHandle0 and ColorTextureHandle1 to render post effects using ping-pong framebuffers ...
Just implemented a bloom effect myself, faced the same aliased edges on the resulting image and faced the exactly same issues. Hence sharing my experience here.
Aliasing happens when you render the lines with OpenGL - e.g. edges of a triangle or a polygon, since OpenGL draws "diagonal" (or simply put non-straight) lines on the screen using quite simple (yet fast) algorithms.
That being said, if you want to anti-alias something - that would be a 3D shape, not a texture - it is just a plain image after all.
Off-topic: in order to fix aliasing on an image you would apply the similar technique, but you would need to figure out where the "edges" are on the image and then follow the same algorithm per "edge" pixel. "Edge" (in quotes) since they are just ordinary pixels from the image perspective and being an edge is just extra context we humans attach to those pixels.
With that out of our way, the thing with two image attachments is actually a nice optimization - you do not need to render your entire scene twice to different framebuffers. But you will pay the price of copying the data from each multi-sampled framebuffer attachment to a separate non-multisampled texture for post-processing.
A bit off-topic: performance-wise, I think this is exactly the same (or within a very small threshold) - rendering an entire scene twice, to two separate framebuffers with two separate multi-sampled attachments (as inputs for the post-processing) and then copying each of them separately to two separate non-multisampled textures.
So the last step before you can apply your (any) post-processing to the multi-sampled scene is to convert each multi-sampled render result to non-multisampled texture - so that your shaders work with plain sampler2D.
It would be something similar to this:
glBindFramebuffer(GL_READ_FRAMEBUFFER, bloomFBOWith2MultisampledAttachments);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, temporaryFBOWith1NonMultisampledAttachment);
// THIS IS IMPORTANT
glReadBuffer(GL_COLOR_ATTACHMENT0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glBlitFramebuffer(0, 0, windowWidth, windowHeight, 0, 0, windowWidth, windowHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
// bloomFBOWith2MultisampledAttachments is still bound
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, blurFramebuffer1);
// THIS IS IMPORTANT
glReadBuffer(GL_COLOR_ATTACHMENT1);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glBlitFramebuffer(0, 0, windowWidth, windowHeight, 0, 0, windowWidth, windowHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
Given you are rendering your scene to two attachments in one framebuffer, you will then need to copy from each of those multi-sampled attachments to non-multi-sampled textures and use them for additive rendering and blurring, correspondingly.
If you don't mind messy code and the use of globjects for OpenGL APIs abstraction, here's my entire bloom solution with anti-aliasing.
And few screenshots:
The first screenshot does not use a framebuffer to render to, so the lines are really smooth.
The second screenshot is the first implementation of a bloom effect (available as a separate CMake project).
Aliasing is more visible on longer distances, so the third screenshots shows a bit more of a scene - the edges look really stairs-like.
The last two screenshots show the bloom effect with anti-aliasing applied.
Note how lantern only has somewhat low-resolution texture, hence aliased lines, whilst the paper has its edges smoothed out by anti-aliasing.
I have implemented a deferred rendering and am trying to use multisample textures for anti aliasing.
I render the scene into a FBO with multisample textures, use glBlit to create regular textures in a second FBO and finally bind the texture to the lighting shader that produces the final image.
// draw to textures
mMultiGeometryFBO->bind();
glViewport(0,0,mWidth,mHeight);
glEnable(GL_DEPTH_TEST);
glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT );
// calling all modules to draw to FBO
for(auto r : mRenderer)
r->renderMaterial(camera);
glBindFramebuffer(GL_READ_FRAMEBUFFER, mMultiGeometryFBO->fbo());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mGeometryFBO->fbo());
glReadBuffer(GL_COLOR_ATTACHMENT0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glBlitFramebuffer(0, 0, mWidth, mHeight,
0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_LINEAR);
glReadBuffer(GL_COLOR_ATTACHMENT1);
glDrawBuffer(GL_COLOR_ATTACHMENT1);
glBlitFramebuffer(0, 0, mWidth, mHeight,
0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glReadBuffer(GL_COLOR_ATTACHMENT2);
glDrawBuffer(GL_COLOR_ATTACHMENT2);
glBlitFramebuffer(0, 0, mWidth, mHeight,
0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
// draw to screen
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_DEPTH_BUFFER_BIT);
mSkybox->renderMaterial(camera);
mShader->use();
mShader->setTexture("tDiffuse", mDiffuseColor, 0);
mShader->setTexture("tNormal", mNormals, 1);
mShader->setTexture("tMaterial", mMaterialParams, 2);
mShader->setTexture("tDepth", mDepthBuffer, 3);
mShader->setTexture("tLights", mLightColor, 4);
mQuad->draw();
This produces a visible line at the horizon (between geometry and skybox).
The color is the clear color. Only clearing the depth reduces the problem when moving. Rendering the SkyBox to the FBO before rendering the geometry produces less visible artifacts, but the line is still there.
Edit: forgot the picture
Resolving the multisample target before the lighting pass does not make sense, conceptually. What you will get is that the values in your gbuffers will be averaged at the edges of objects. This is especially bad for the normal directions. Think about it: If you have a pixel which contains 50% of your ground plane, and 50% of your sky, you will get a normal direction which is (normal_ground + normal_sky)/2. This is totally different from calculating the final color of each of this parts with their original normal and mixing the resulting colors.
If you want to do multisampling with deferred rendering, you have to use the multisampling target for the lighting, and will have to enable per sample shading and actually access and light each sample individually, and only blit the final result to a non-multisampled target. However, that will be exorbitantly expensive. You especially lose the benefits of multisampling vs. supersampling.
I don't know if there are some neat tricks trick to still work with multisampling in a more efficient way, but the usual approach is to not use multisampling at all and doing the anti-aliasing via some image-based postprocessing pass.
I have an image in OpenGL that I am attempting to apply a simple HSB filter to. The user selects a hue value, I shade the image appropriately, display it, and everyone is happy. The problem I am running into is that the code I have inherited that worked on a previous system (Solaris, presuming OpenGL 2.1) does not work on our current system (RHEL 5, OpenGL 3.0).
Right now, the image appears in grey-scale, no matter what saturation is set to. However, brightness does seem to be acting appropriately. The relevant code has been reproduced below:
// imageData - unsigned char[3*width*height]
// (red|green|blue)Channel - unsigned char[width*height]
// brightnessBias - float in range [-1/3,1/3]
// hsMatrix - float[4][4] Described by algorithm from
// http://www.graficaobscura.com/matrix/index.html
// (see Hue Rotation While Preserving Luminance)
glDrawPixels(width, height, format, GL_UNSIGNED_BYTE, imageData);
// Split into RGB channels
glReadPixels(0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, redChannel);
glReadPixels(0, 0, width, height, GL_GREEN, GL_UNSIGNED_BYTE, greenChannel);
glReadPixels(0, 0, width, height, GL_BLUE, GL_UNSIGNED_BYTE, blueChannel);
// Redraw and blend RGB channels with scaling and bias
glPixelZoom(1.0, 1.0);
glRasterPos2i(0, height);
glPixelTransferf(GL_RED_BIAS, brightnessBias);
glPixelTransferf(GL_GREEN_BIAS, brightnessBias);
glPixelTransferf(GL_BLUE_BIAS, brightnessBias);
glDisable(GL_BLEND);
glPixelTransferf(GL_RED_SCALE, hsMatrix[0][0]);
glPixelTransferf(GL_GREEN_SCALE, hsMatrix[1][0]);
glPixelTransferf(GL_BLUE_SCALE, hsMatrix[2][0]);
glDrawPixels(width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, redChannel);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
glPixelTransferf(GL_RED_SCALE, hsMatrix[0][1]);
glPixelTransferf(GL_GREEN_SCALE, hsMatrix[1][1]);
glPixelTransferf(GL_BLUE_SCALE, hsMatrix[2][1]);
glDrawPixels(width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, greenChannel);
glPixelTransferf(GL_RED_SCALE, hsMatrix[0][2]);
glPixelTransferf(GL_GREEN_SCALE, hsMatrix[1][2]);
glPixelTransferf(GL_BLUE_SCALE, hsMatrix[2][2]);
glDrawPixels(width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, blueChannel);
// Reset pixel transfer parameters
glDisable(GL_BLEND);
glPixelTransferf(GL_RED_SCALE, 1.0f);
glPixelTransferf(GL_GREEN_SCALE, 1.0f);
glPixelTransferf(GL_BLUE_SCALE, 1.0f);
glPixelTransferf(GL_RED_BIAS, 0.0f);
glPixelTransferf(GL_GREEN_BIAS, 0.0f);
glPixelTransferf(GL_BLUE_BIAS, 0.0f);
The brightness control works as intended, however, when the glPixelTransferf(GL_*_SCALE) calls are left in, the image is displayed in greyscale. Compounding all of this is the fact that I have no prior experience with OpenGL, so I find a lot of links for what I presume are more modern techniques that I simply can't make sense of.
EDIT:
I believe the theory behind what was being done was a hack at doing the matrix multiplication through the draw calls, because GL_LUMINANCE treats the one value as the value for all three components, so if you follow the components through that drawing, you expect
// After glDrawPixels(..., redChannel)
new_red = red*hsMatrix[0][0]
new_green = red*hsMatrix[1][0]
new_blue = red*hsMatrix[2][0]
// After glDrawPixels(..., greenChannel)
new_red = red*hsMatrix[0][0] + green*hsMatrix[0][1]
new_green = red*hsMatrix[1][0] + green*hsMatrix[1][1]
new_blue = red*hsMatrix[2][0] + green*hsMatrix[2][1]
// After glDrawPixels(..., blueChannel)
new_red = red*hsMatrix[0][0] + green*hsMatrix[0][1] + blue*hsMatrix[0][2]
new_green = red*hsMatrix[1][0] + green*hsMatrix[1][1] + blue*hsMatrix[1][2]
new_blue = red*hsMatrix[2][0] + green*hsMatrix[2][1] + blue*hsMatrix[2][2]
Because it was turning out greyscale anyway and from a similar-ish example, I had thought that I might have needed to do the glPixelTransfer calls before calling glDrawPixels, but that was amazingly slow.
Wow, what the hell is that ?!
For your question, I'd replace GL_LUMINANCE in your 3 glDrawPixels by GL_RED, GL_GREEN and GL_BLUE respectively.
However :
glPixelTransfer is bad
glDrawPixels is bad
Is there a single reason why you're not using a super-simple fragment shader to do the conversion ? It's a simple matrix multiplication, and you're under ogl3.0...
Create a texture from imageData, this needs to be done only once.
Make a shader that reads the color from the texture, multiply it by the color conversion matrix, and display it
Bind the computed color matrix
Draw a fullscreen quad. Even an 5 year old card will get 500 fps out of this.
I have an FBO object with a color and depth attachment which I render to and then read from using glReadPixels() and I'm trying to add to it multisampling support.
Instead of glRenderbufferStorage() I'm calling glRenderbufferStorageMultisampleEXT() for both the color attachment and the depth attachment. The frame buffer object seem to have been created successfully and is reported as complete.
After rendering I'm trying to read from it with glReadPixels(). When the number of samples is 0 i.e. multisampling disables it works perfectly and I get the image I want. when I set the number of samples to something else, say 4, the frame buffer is still constructed OK but glReadPixels() fails with an INVALID_OPERATION
Anyone have an idea what could be wrong here?
EDIT: The code of glReadPixels:
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, ptr);
where ptr points to an array of width*height uints.
I don't think you can read from a multisampled FBO with glReadPixels(). You need to blit from the multisampled FBO to a normal FBO, bind the normal FBO, and then read the pixels from the normal FBO.
Something like this:
// Bind the multisampled FBO for reading
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, my_multisample_fbo);
// Bind the normal FBO for drawing
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, my_fbo);
// Blit the multisampled FBO to the normal FBO
glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
//Bind the normal FBO for reading
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, my_fbo);
// Read the pixels!
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
You can't read the multisample buffer directly with glReadPixels since it would raise an GL_INVALID_OPERATION error. You need to blit to another surface so that the GPU can do a downsample. You could blit to the backbuffer, but there is the problem of the "pixel owner ship test". It is best to make another FBO. Let's assume you made another FBO and now you want blit. This requires GL_EXT_framebuffer_blit. Typically, when your driver supports GL_EXT_framebuffer_multisample, it also supports GL_EXT_framebuffer_blit, for example the nVidia Geforce 8 series.
//Bind the MS FBO
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, multisample_fboID);
//Bind the standard FBO
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, fboID);
//Let's say I want to copy the entire surface
//Let's say I only want to copy the color buffer only
//Let's say I don't need the GPU to do filtering since both surfaces have the same dimension
glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
//--------------------
//Bind the standard FBO for reading
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboID);
glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
Source: GL EXT framebuffer multisample