zoukankan      html  css  js  c++  java
  • 【OpenGL】学习笔记#5

    一、帧缓冲

    什么是帧缓冲?可以理解为GPU在渲染前预先准备的一个区域,之后将把它渲染成屏幕上的像素。但是,帧缓冲本身并不储存数据,仅仅储存指向数据的指针。所以,帧缓冲需要绑定几个缓冲区,我们特殊地称它们为附件:颜色附件,深度缓冲附件,模板缓冲附件。需要注意的是,一个完整的帧缓冲必须包括一个颜色附件。

    除了这种分类之外,附件还可以分为纹理附件和渲染缓冲对象(RBO,Render Buffer Object)。其中,纹理附件就是颜色附件,RBO分为深度缓冲附件和模板缓冲附件。让我们来列一个树形图。

    帧缓冲--[纹理附件]--颜色附件

         |

         [渲染缓冲附件]--深度缓冲附件

               |

               --模板缓冲附件

    为什么要讲到帧缓冲呢?因为帧缓冲是后期处理相当重要的部分。例如阴影,模糊,反相等后期处理都要依靠帧缓冲来实现。打个比方,帧缓冲就好像是拍出来的一张照片,可以让我们PS。

    此处的PS便是着色器。OpenGL提供了可以让我们编辑的帧缓冲,好伟大!我们可以把一张帧缓冲保存的2D纹理或深度缓冲获得为一个句柄,在后面直接使用,塞进着色器里。

    说了这么多,来看看代码吧,让我们以用着色器编辑一个帧缓冲为例子:

    首先生成并绑定一个帧缓冲:

    GLuint FramebufferName = 0;
        glGenFramebuffers(1, &FramebufferName);
        glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

    生成一个纹理:

    GLuint renderedTexture;
        glGenTextures(1, &renderedTexture);

    状态机不解释:

    glBindTexture(GL_TEXTURE_2D, renderedTexture);

    由于帧缓冲必须包含一个颜色附件,所以接下来对之前绑定的纹理填充一个空的图像(最后一个参数0代表空,empty):

    glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, windowWidth, windowHeight, 0,GL_RGB, GL_UNSIGNED_BYTE, 0);

    可  怜  的  过  滤(性能upup),设置过滤模式:

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    由于我们要渲染一个3D模型,所以要进行深度测试,所以来生成并绑定一个渲染缓冲区,将要作为我们的RBO:

    GLuint depthrenderbuffer;
        glGenRenderbuffers(1, &depthrenderbuffer);
        glBindRenderbuffer(GL_RENDERBUFFER, depthrenderbuffer);

    由于OpenGL只知道这是个渲染缓冲区,对其中的数据格式和大小全然不知,所以我们得告诉它,使用glRenderbufferStorage来指定RBO的数据格式和大小,此处指定为深度缓冲区:

    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, windowWidth, windowHeight);

    对于此函数,第一个参数必须填GL_RENDERBUFFER,第二个指定了类型为深度缓冲,还可以是GL_RGB,GL_RGBA,剩下的两个参数指定了数据长宽,这里设置为屏幕大小。

    接下来开始为帧缓冲绑定附件,将此深度缓冲绑定为深度附件:

    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthrenderbuffer);

    第二个参数指定了类型为深度附件。

    然后绑定纹理附件,这个比较特殊,先看代码:

    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, renderedTexture, 0);

    可以看到,第二个参数并不是指定的类型,而是GL_COLOR_ATTACHMENT0,这意味着什么呢,这意味着颜色附件可以有多个,0代表的是颜色附件的位置。同时我们也发现RBO只能有一个,因为它直接指定了类型。

    所以,我们就需要指定接下来渲染时需要渲染到哪些颜色附件上,这次我们只渲染到GL_COLOR_ATTACHMENT0上:

    GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0};
        glDrawBuffers(1, DrawBuffers);

    不需要多说了吧。

    终于,帧缓冲的前置操作完成了。这时需要检查你的帧缓冲是否完整,否则会有很可怕的错误:

    if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            return false;

    经常check是好习惯呦~

    由于我们要把帧缓冲的图像经过处理绘制到屏幕上,所以一会渲染的时候将要分两个步骤:第一步,按照往常的操作渲染,但是在渲染前把渲染目标绑定到我们自定义的帧缓冲上,此为离屏渲染。然后,通过颜色附件获得渲染得到的深度缓冲和纹理,再把它们传到着色器里处理,作为2D纹理渲染到一个屏幕大小的四边形上。

    那么,下面开始定义四边形:

    static const GLfloat g_quad_vertex_buffer_data[] = { 
            -1.0f, -1.0f, 0.0f,
             1.0f, -1.0f, 0.0f,
            -1.0f,  1.0f, 0.0f,
            -1.0f,  1.0f, 0.0f,
             1.0f, -1.0f, 0.0f,
             1.0f,  1.0f, 0.0f,
        };
    
        GLuint quad_vertexbuffer;
        glGenBuffers(1, &quad_vertexbuffer);
        glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(g_quad_vertex_buffer_data), g_quad_vertex_buffer_data, GL_STATIC_DRAW);

    定义顶点缓冲,里面包括的是一个2D充满屏幕的四边形的顶点,注意此时顶点依然是3维的格式,只不过Z=0,相当于2D。

    好了,一切都准备好了,下面进入主循环:

    首先把渲染目标绑定到我们定义的帧缓冲里:

    glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

    这里是渲染操作...

    glUseProgram(programID);

    (这里使用通常的着色器)

    这里是渲染操作...

    然后重头戏来了,开始渲染四边形,先把渲染目标绑定到默认帧缓冲:这个帧缓冲将会直接被GPU提取渲染:

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    这里是渲染操作...

    glUseProgram(quad_programID);

    (使用第二个着色器,这个着色器只负责渲染一个2D纹理并稍稍处理)

    这里是渲染操作...

    怎么样,这样就完成了呢,小朋友们学会了吗?(狗头)

    咳咳,说了这么多,第二个着色器长啥样呢,仅仅渲染一个2D纹理的着色器,来看吧:

    #version 330 core
    
    // Input vertex data, different for all executions of this shader.
    layout(location = 0) in vec3 vertexPosition_modelspace;
    
    // Output data ; will be interpolated for each fragment.
    out vec2 UV;
    
    void main(){
        gl_Position =  vec4(vertexPosition_modelspace,1);
        UV = (vertexPosition_modelspace.xy+vec2(1,1))/2.0;
    }

    出人意料的简单呢......仅仅传出一个UV坐标。不过,这里要注意,我们的顶点坐标xy的范围是[-1,1],而UV坐标uv的范围是[0,1],所以需要(xy+(1,1))/2来转换。

    看片元着色器:

    #version 330 core
    
    in vec2 UV;
    
    out vec3 color;
    
    uniform sampler2D renderedTexture;
    uniform float time;
    
    void main(){
        color = texture( renderedTexture, UV + 0.005*vec2( sin(time+1024.0*UV.x),cos(time+768.0*UV.y)) ).xyz ;
    }

    用时间来做一个偏移量,不多说了吧。

    那么看看结果吧:

    像素会随着时间的变化慢慢移动喔!

  • 相关阅读:
    RHEL6.x 删除Oracle11g
    通过IPV6上QQ及自建IPV6代理的方法
    Carrier frequency and EARFCN
    FDD LTE frequency band allocations
    Cmake参考资料
    sigal & mq_notify
    哈夫曼编码与解码
    哈夫曼数与哈夫曼编码
    算法做题顺序
    我的大学
  • 原文地址:https://www.cnblogs.com/dudujerry/p/13573649.html
Copyright © 2011-2022 走看看