OpenGL如何使用多次采样渲染到纹理

时间:2016-09-30 21:32:40

标签: opengl glsl framebuffer render-to-texture multisampling

实现一些效果,我最终得到1个与1个纹理相关联的帧缓冲区,它保存了我的最终场景。然后将此纹理应用于全屏四边形。

结果是我所期望的效果,但是我注意到场景上的边缘因此呈现,并不是平滑的 - 可能是因为在渲染到帧缓冲传递期间不采用多次采样,就像我直接渲染到屏幕缓冲区时一样。

所以我的问题是

如何对此最终纹理应用/使用多次采样,以使其内容显示平滑边缘?

  

编辑:我已经删除了我的代码的原始版本,正在使用   经典的FrameBuffer + Texture不是多重采样的。以下是最新的,   以下是评论中的建议。

     

现在也是,我将专注于让glBlitFramebuffer方法起作用!

所以我的代码现在是这样的:

// 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( ... );

结果输出是正确的,这意味着我可以看到具有所需发光效果的场景......但没有明显的多次采样! :(

注意:我开始怀疑,如果我在正确的阶段使用多次采样 - 我将在此进行实验 - 但是在我第一次渲染初始3D场景时,我应该使用它最初的FBO? (我在评论中提到的那些,我不想在这里发帖以避免混淆:s)

我在最后一个阶段之前添加了更详细的评论。解析帧缓冲区。

2 个答案:

答案 0 :(得分:0)

您具有:“第5步。最后将纹理0和纹理3相加(保留最后的模糊结果)-为此,我们创建了一个多采样帧缓冲区。”但是这种方式的多重采样仅适用于全屏四边形。

“如果我在正确的阶段使用多重采样”,那么答案是“否”,则在渲染场景时需要在另一个阶段使用多重采样。

我使用帧缓冲区(用于渲染场景的采样是多采样),两个输出纹理(用于颜色信息和用于稍后将模糊以实现发光的高光)和乒乓帧缓冲区的设置非常相似。我还使用glBlitFramebuffer解决方案(每个颜色附件也使用2个blit调用,每个都会使用自己的纹理),还没有找到任何使其直接渲染到具有附加纹理的帧缓冲区中的方法。

如果您需要一些代码,这是对我有用的解决方案(尽管它在C#中):

// ----------------------------
// 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 ...

答案 1 :(得分:0)

我自己刚刚实现了绽放效果,在生成的图像上面临相同的锯齿边缘并面临完全相同的问题。因此在这里分享我的经验。

当您使用 OpenGL 渲染线条时会发生锯齿 - 例如三角形或多边形的边缘,因为 OpenGL 使用非常简单(但快速)的算法在屏幕上绘制“对角线”(或简单地放置非直线)线。

话虽如此,如果您想要消除某些东西的锯齿 - 那将是 3D 形状,而不是纹理 - 毕竟它只是一个普通图像。

题外话:为了修复图像上的锯齿,您将应用类似的技术,但您需要找出图像上“边缘”的位置,然后遵循相同的方法每个“边缘”像素的算法。 “边缘”(在引号中),因为从图像的角度来看,它们只是普通像素,而边缘只是我们人类附加到这些像素的额外上下文。

除此之外,带有两个图像附件的东西实际上是一个很好的优化 - 您不需要将整个场景两次渲染到不同的帧缓冲区。但是您将付出代价,将数据从每个多采样帧缓冲区附件复制到单独的非多采样纹理以进行后期处理。

有点跑题:在性能方面,我认为这是完全相同的(或在一个非常小的阈值内) - 将整个场景渲染两次,到两个单独的帧缓冲区和两个单独的多- 采样附件(作为后处理的输入),然后将它们分别复制到两个单独的非多重采样纹理。

因此,在将(任何)后处理应用于多采样场景之前的最后一步是将每个多采样渲染结果转换为非多采样纹理 - 这样您的着色器使用普通的 sampler2D

应该是类似的:

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);

假设您要将场景渲染到一个帧缓冲区中的两个附件,那么您需要从这些多采样附件中的每一个复制到非多采样纹理,并相应地将它们用于附加渲染和模糊。

如果您不介意凌乱的代码和将 globjects 用于 OpenGL API 抽象,那么这是我的 entire bloom solution with anti-aliasing

还有一些截图:

no framebuffers and no post-processing

before anti-aliasing

aliasing on longer distances

anti-aliasing

anti-aliasing on longer distances

第一张截图没有使用帧缓冲区来渲染,所以线条非常流畅。

第二个屏幕截图是绽放效果的第一个实现(可用作 separate CMake project)。

在较远距离上锯齿更明显,因此第三张屏幕截图显示的场景更多一些 - 边缘看起来真的像楼梯。

最后两张截图显示了应用了抗锯齿的泛光效果。

请注意,灯笼的纹理分辨率较低,因此出现锯齿状线条,而纸张的边缘则通过抗锯齿处理变得平滑。