OpenGL 使用 PBO 高速复制屏幕图像到内存或者纹理中
如果你想给游戏做个截图功能,或者想把屏幕图像弄成一个纹理,你就非常需要 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(, &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,每个像素3字节
glBufferData(GL_ARRAY_BUFFER, BUFFER_SIZE, NULL, GL_STREAM_COPY); // 这样我们的缓冲区就已经初始化好了,它现在已经有一块可用的内存
// 随时可以用 glMapBuffer 来访问
// 初始化完了那么解绑吧
glBindBuffer(GL_ARRAY_BUFFER, ); // 创建1个纹理,等会把屏幕复制到这个纹理
glGenTextures(, &Texture);
// 初始化纹理,不多解释了
glBindTexture(GL_TEXTURE_2D, Texture);
// 这里data参数传NULL和上面缓冲区一样,GL仅仅给纹理分配内存而已
// ScreenWide和ScreenTall是屏幕的宽度和高度
// 格式用RGB,因为屏幕不需要透明通道,所以纹理的像素数据大小是和上面的缓冲区大小一样的
glTexImage2D(GL_TEXTURE_2D, , GL_RGB, ScreenWide, ScreenTall, , 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, );
} void draw()
{
// 假装这里画了游戏场景 // 首先我们要把缓冲区绑定到 GL_PIXEL_PACK_BUFFER 这个地方
glBindBuffer(GL_PIXEL_PACK_BUFFER, Buffer);
// 这个函数会判断 GL_PIXEL_PACK_BUFFER 这个地方有没有绑定一个缓冲区,如果有,那就把数据写入到这个缓冲区里
// 前4个参数就是要读取的屏幕区域,不多解释
// 格式是RGB,类型是BYTE,每个像素3字节
// 如果GL_PIXEL_PACK_BUFFER有绑定缓冲区,最后一个参数就作为偏移值来使用,这里不啰嗦没用的东西了。
// 传NULL就行
glReadPixels(, , 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, ); // 接着我们演示一下把缓冲区中的像素数据传给纹理
// 首先我们把缓冲区绑定到 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, , , , ScreenWide, ScreenTall, GL_RGB, GL_UNSIGNED_BYTE, NULL);
// 完事了把GL_PIXEL_UNPACK_BUFFER这个地方的缓冲区解绑掉,以免别的函数误操作
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, ); // 这时候我们已经更新了纹理,我们可以把纹理画出来看看 // 假装这里有绘制纹理的代码
}
代码就这些了。如果你想了解上面用到的函数的详细信息,一定要去看看 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还没有绘制

OpenGL 使用 PBO 高速复制屏幕图像到内存或者纹理中的更多相关文章
- [转]OpenGL 使用 PBO 高速复制屏幕图像到内存或者纹理中
如果你想给游戏做个截图功能,或者想把屏幕图像弄成一个纹理,你就非常需要 PBO 了 通常情况下,你想把屏幕图像的像素数据读到内存需要用 glReadPixels 然后 pixels 参数传进去一块内存 ...
- PowerShell定时抓取屏幕图像
昨天的博文写了定时记录操作系统行为,其实说白了就是抓取了击键的记录和对应窗口的标题栏,而很多应用程序标题栏又包含当时记录的文件路径和文件名,用这种方式可以大致记录操作了哪些程序,打开了哪些文 ...
- C# 图像处理:复制屏幕到内存中,拷屏操作
/// <summary> /// 复制屏幕到内存中 /// </summary> /// <returns>返回内存流</returns> publi ...
- C# 截取屏幕图像
#region 截取屏幕图像 private static Bitmap GetScreenCapture() { Rectangle tScreenRect = , , Screen.Primary ...
- ##DAY3 自定义视图、视图控制器、视图控制器指定视图、loadView、 viewDidLoad、MVC、屏幕旋转、内存警告
##DAY3 自定义视图.视图控制器.视图控制器指定视图.loadView. viewDidLoad.MVC.屏幕旋转.内存警告 #pragma mark ———————自定义视图的步骤 —————— ...
- 实现把dgv里的数据完整的复制到一张内存表
/// <summary> /// 方法实现把dgv里的数据完整的复制到一张内存表 /// </summary> /// <param name="dgv&qu ...
- NeHe OpenGL教程 第十三课:图像字体
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- 在屏幕上搜索图片并返回图片所在位置的坐标的AutoHotkey脚本源代码(类似大漠插件)
;~ 在屏幕上搜索图片并返回图片所在位置的坐标的AutoHotkey脚本源代码(类似大漠插件) ; https://www.autohotkey.com/boards/viewtopic.php?t ...
- 【OpenGL】使用FreeType库加载字体并在GL中绘制文字
FreeType用起来比较麻烦,这里写了一份简单的示例代码,仅供参考. 实现了FT库生成字符位图,并上传到GL纹理. 实现了字符位图缓存功能,多个字符图像保存在同一个纹理中. 实现了简单的字体管理框架 ...
随机推荐
- Linux命令——find/grep
这两个命令写起来会很多,这里只简单的写一些东西,加深自己的印象. 一.find find命令主要作用是沿着文件层次结构向下遍历,匹配符合条件的文件,并执行相应的操作. 1)命令格式 find [参数] ...
- POJ3301 Texas Trip 计算几何、随机化贪心
传送门--Vjudge 三分写法似乎有问题,可以去Udebug上看Morass的\(666\)个测试点的数据,我的乱搞有很多比正解答案小,但还是能在SPOJ和POJ过,可见数据之水. 可以对正方形的角 ...
- eclipse取消空格、等号、分号自动录入
默认eclipse中按空格.等号.分号等键时,会将提示框中的文字输入到编辑内容中,但是很多时候我们并不希望录入,可如下设置. 1.打开 Eclipse -> Window -> Perfe ...
- SqlServer 案例:已有汽车每日行驶里程数据,计算其每日增量
需求说明 某公司某项业务,需要获得用户每日行车里程数.已知能获得该车每日提交的总里程数,如何通过 T-SQL 来获得其每日增量里程? 解决方案 首选需要对数据进行编号,利用开窗函数 OVER() 实现 ...
- Java各厂对外的优质博客
1.美团:https://tech.meituan.com/ 2.极客学院:http://wiki.jikexueyuan.com/list/java/
- 分布式监控系统Zabbix-3.0.3-完整安装记录(3)-监控nginx,php,memcache,Low-level discovery磁盘IO
前段时间在公司IDC服务器上部署了zabbix3.0.3监控系统,除了自带的内存/带宽/CPU负载等系统资源监控模板以及mysql监控模板外,接下来对诸如nginx.php.memcache.磁盘IO ...
- C_数据结构_栈
# include <stdio.h> # include <malloc.h> # include <stdlib.h> typedef struct Node ...
- PairWork-电梯调度程序结对编程【附加题】
1 接口改进 1) 之前判断电梯是否闲置的函数不太好理解,重新修改了,如下所示: //是否停顿状态(停止的以及开门间隔>=0) public bool IsIdle { get { return ...
- Week 7 迭代总结
写在前面: 本次我为团队博客写了一篇总结,深刻总结了我们组发生的问题以及将来要做的事情.有兴趣请移步http://www.cnblogs.com/Buaa-software Week 7 Alpha轮 ...
- 《Linux内核分析》 第八节 进程的切换和一般的执行过程
张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux内核分析 第八 ...