zoukankan      html  css  js  c++  java
  • OpenGL 使用 PBO 高速复制屏幕图像到内存或者纹理中

    https://www.cnblogs.com/crsky/p/7870835.html

    如果你想给游戏做个截图功能,或者想把屏幕图像弄成一个纹理,你就非常需要 PBO 了

    通常情况下,你想把屏幕图像的像素数据读到内存需要用 glReadPixels 然后 pixels 参数传进去一块内存地址

    这样做是非常非常不好的,因为 glReadPixels 会把屏幕图像的像素数据从显卡的显存复制到内存条,这个过程就非常非常的慢,特别是数据量大的时候

    然后如果你要把像素数据再用 glTexImage2D 传到纹理,数据就又要从内存条复制到显存,这个过程也是非常非常慢的,特别是数据量大的时候

    那么有没有一种办法,让我们可以通过一个内存指针,直接访问显存的数据呢?当然是有的,那就是 OpenGL 的 Array Buffer

    这个东西中文叫做 数组缓冲区 也可以直接省略成 缓冲区 因为它就是显存里的一块内存,所以我们下文就叫 缓冲区 吧

    你可以用 glMapBuffer 得到它的内存指针,然后就可以为所欲为了,另外,OpenGL 很多用来返回数据的函数,都可以把数据写到缓冲区里,而不是复制到内存条。

    就比如说 glReadPixels 原本你是要传一个内存指针进去的,但是有了缓冲区,它就可以把数据复制到缓冲区里而不是复制到内存条

    因为,屏幕的像素数据是在显存里的,缓冲区也是在显存里的,所以,显存->复制数据->显存 速度就比 显存->复制数据->内存条 快非常非常的多

    然后我们直接用 glMapBuffer 来获取缓冲区的内存地址,就能访问到复制好的屏幕像素数据了,接着该干嘛干嘛。

    而且,OpenGL 的一些函数可以把数据写入到缓冲区里,还有些函数也可以从缓冲区里读取数据来用,比如,glTexImage2D 什么的,如果你很聪明,你已经知道接下来要干嘛了

    假如我们上一步把屏幕的像素数据读取到缓冲区里了,我们就可以直接用 glTexImage2D、glTexSubImage2D 什么的函数把缓冲区里的数据传给纹理了

    这样我们就把屏幕图像存储在纹理了,然后干嘛干嘛,并且这个过程完全不关内存条的事,所以速度也是非常非常的快

    当然我们需要用来操作Buffer的函数。你也可以用GLEW库来偷懒

    复制代码
    #include "glext.h"
    
    PFNGLBINDBUFFERPROC glBindBuffer = NULL;
    PFNGLBUFFERDATAPROC glBufferData = NULL;
    PFNGLGENBUFFERSPROC glGenBuffers = NULL;
    PFNGLMAPBUFFERPROC glMapBuffer = NULL;
    PFNGLUNMAPBUFFERPROC glUnmapBuffer = NULL;
    
    void gl_init()
    {
        glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer");
        glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData");
        glGenBuffers = (PFNGLGENBUFFERSPROC)wglGetProcAddress("glGenBuffers");
        glMapBuffer = (PFNGLMAPBUFFERPROC)wglGetProcAddress("glMapBuffer");
        glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)wglGetProcAddress("glUnmapBuffer");
    }
    复制代码

    那么上面我们已经BB了一堆,接下来就开始写代码了

    复制代码
    GLuint Buffer;
    GLuint Texture;
    
    void init()
    {
        // 创建1个缓冲区
        glGenBuffers(1, &Buffer);
        
        // 缓冲区刚创建出来的时候还没有分配内存,所以我们要初始化一下它
        // 先绑定..
        glBindBuffer(GL_ARRAY_BUFFER, Buffer);
        
        // 这一步十分重要,第2个参数指定了这个缓冲区的大小,单位是字节,一定要注意
        //  然后第3个参数是初始化用的数据,如果你传个内存指针进去,这个函数就会把你的
        //  数据复制到缓冲区里,我们这里一开始并不需要什么数据,所以传个NULL就行了
        //  GL内部会给缓冲区分配内存,然后什么都不干,第4个参数可以优化显存效率,指定
        //  缓冲区中的数据读写频繁程度,如果缓冲区中的数据不经常读写,可以传入 GL_STATIC_****
        //  这样GL会把缓冲区放在内存数据不经常变动的区域,如果要经常读写缓冲区中的数据,可以传
        //  别的值,具体参考 @https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/ 吧
        // 注意这里的 BUFFER_SIZE 我们假设要复制整个屏幕的像素数据,格式为RGB就行了,那么
        //  大小就是 屏幕宽度×屏幕高度×3,每个像素1字节
        glBufferData(GL_ARRAY_BUFFER, BUFFER_SIZE, NULL, GL_STREAM_COPY);
        
        // 这样我们的缓冲区就已经初始化好了,它现在已经有一块可用的内存
        //  随时可以用 glMapBuffer 来访问
        // 初始化完了那么解绑吧
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        
        
        // 创建1个纹理,等会把屏幕复制到这个纹理
        glGenTextures(1, &Texture);
        // 初始化纹理,不多解释了
        glBindTexture(GL_TEXTURE_2D, Texture);
        // 这里data参数传NULL和上面缓冲区一样,GL仅仅给纹理分配内存而已
        //  ScreenWide和ScreenTall是屏幕的宽度和高度
        // 格式用RGB,因为屏幕不需要透明通道,所以纹理的像素数据大小是和上面的缓冲区大小一样的
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, ScreenWide, ScreenTall, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        // 初始化完了解绑
        glBindTexture(GL_TEXTURE_2D, 0);
    }
    
    void draw()
    {
        // 假装这里画了游戏场景
        
        
        // 首先我们要把缓冲区绑定到 GL_PIXEL_PACK_BUFFER 这个地方
        glBindBuffer(GL_PIXEL_PACK_BUFFER, Buffer);
        // 这个函数会判断 GL_PIXEL_PACK_BUFFER 这个地方有没有绑定一个缓冲区,如果有,那就把数据写入到这个缓冲区里
        // 前4个参数就是要读取的屏幕区域,不多解释
        //  格式是RGB,类型是BYTE,每个像素1字节
        // 如果GL_PIXEL_PACK_BUFFER有绑定缓冲区,最后一个参数就作为偏移值来使用,这里不啰嗦没用的东西了。
        //  传NULL就行
        glReadPixels(0, 0, ScreenWide, ScreenTall, GL_RGB, GL_UNSIGNED_BYTE, NULL);
        // 好了我们已经成功把屏幕的像素数据复制到了缓冲区里
        
        // 这时候,你可以用 glMapBuffer 得到缓冲区的内存指针,来读取里面的像素数据,保存到图片文件
        // 完成截图
        /******
        
        // 注意glMapBuffer的第1个参数不一定要是GL_PIXEL_PACK_BUFFER,你可以把缓冲区绑定到比如上面init函数的GL_ARRAY_BUFFER
        //  然后这里也传GL_ARRAY_BUFFER,由于懒得再绑定一次,就接着用上面绑定的GL_PIXEL_PACK_BUFFER吧
        void *data = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_WRITE);
        if (data)
        {
            WriteTGA("screenshot.tga", ScreenWide, ScreenTall, data);
            glUnmapBuffer(GL_PIXEL_PACK_BUFFER);    // 不要忘了解除Map
        }
        
        ******/
        
        // 完事了把GL_PIXEL_PACK_BUFFER这个地方的缓冲区解绑掉,以免别的函数误操作
        qglBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
        
    
        
        // 接着我们演示一下把缓冲区中的像素数据传给纹理
        //  首先我们把缓冲区绑定到 GL_PIXEL_UNPACK_BUFFER 这个地方。这里注意啊!GL_PIXEL_PACK_BUFFER 和 GL_PIXEL_UNPACK_BUFFER 是不同的!
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, Buffer);
        // 绑定纹理
        glBindTexture(GL_TEXTURE_2D, Texture);
        // 这个函数会判断 GL_PIXEL_UNPACK_BUFFER 这个地方有没有绑定一个缓冲区
        //   如果有,就从这个缓冲区读取数据,而不是data参数指定的那个内存
        // 前面参数很简单就不解释了,最后一个参数和上面glReadPixels同理,传NULL就行
        // 这样glTexSubImage2D就会从我们的缓冲区中读取数据了
        // 这里为什么要用glTexSubImage2D呢,因为如果用glTexImage2D,glTexImage2D会销毁纹理内存重新申请,glTexSubImage2D就仅仅只是更新纹理中的数据
        //  这就提高了速度,并且优化了显存的利用率
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ScreenWide, ScreenTall, GL_RGB, GL_UNSIGNED_BYTE, NULL);
        // 完事了把GL_PIXEL_UNPACK_BUFFER这个地方的缓冲区解绑掉,以免别的函数误操作
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
        
        // 这时候我们已经更新了纹理,我们可以把纹理画出来看看
        
        // 假装这里有绘制纹理的代码
    }
    复制代码

    代码就这些了。如果你想了解上面用到的函数的详细信息,一定要去看看 https://www.khronos.org/registry/OpenGL-Refpages/gl2.1 这个网页

    补充:GL_PIXEL_PACK_BUFFER 和 GL_PIXEL_UNPACK_BUFFER 统称为 PBO(Pixel Buffer Object)因为这俩就是用来搞像素的,所以就叫 Pixel buffer 了呗

    缓冲区什么东西都可以存,可不止像素,具体请搜索其它资料吧 

    这是纹理的样子:

    纹理中没有看到HUD是因为我读取屏幕像素的时候HUD还没有绘制

  • 相关阅读:
    HDU 2433 Travel (最短路,BFS,变形)
    HDU 2544 最短路 (最短路,spfa)
    HDU 2063 过山车 (最大匹配,匈牙利算法)
    HDU 1150 Machine Schedule (最小覆盖,匈牙利算法)
    290 Word Pattern 单词模式
    289 Game of Life 生命的游戏
    287 Find the Duplicate Number 寻找重复数
    283 Move Zeroes 移动零
    282 Expression Add Operators 给表达式添加运算符
    279 Perfect Squares 完美平方数
  • 原文地址:https://www.cnblogs.com/jukan/p/9213338.html
Copyright © 2011-2022 走看看