You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于GLUT与GLEW的OpenGL屏幕捕获:如何获取位图头?

解决GLUT+GLEW环境下填充BITMAPINFOHEADER实现OpenGL视频捕获的问题

不用先把帧存成BMP再提取头信息!你可以直接从当前OpenGL窗口的参数和帧缓冲特性来填充BITMAPINFOHEADER结构体,全程不需要中间文件。下面一步步给你讲怎么搞:

逐个字段填充说明

BITMAPINFOHEADER的每个字段都可以通过OpenGL/GLUT的现有接口直接获取或计算,不需要依赖外部文件:

  • biSize:固定为sizeof(BITMAPINFOHEADER),这是结构体自身的大小,Windows需要这个来识别结构体类型。
  • biWidth:就是你的GLUT窗口宽度,用glutGet(GLUT_WINDOW_WIDTH)直接获取。
  • biHeight:这里要注意OpenGL帧缓冲的坐标是从左下角开始的,而Windows BMP默认是从左上角开始的。所以把这个值设为窗口高度的负数(比如-glutGet(GLUT_WINDOW_HEIGHT)),这样BITMAP会自动按从上到下的顺序存储像素,和OpenGL帧缓冲的读取结果匹配,避免后续还要翻转图像。
  • biPlanes:固定为1,Windows的BITMAP规范里这个字段始终是1,不用改。
  • biBitCount:对应你glReadPixels使用的像素格式:
    • 如果用GL_RGBA读取32位像素,设为32;
    • 如果用GL_RGB读取24位像素,设为24;
      这个必须和你读取像素的格式严格对应,否则颜色会错乱。
  • biCompression:设为BI_RGB,表示不压缩,因为我们从OpenGL读的是原始像素数据,不需要压缩处理。
  • biSizeImage:计算整个帧的字节数,公式是每行字节数 × 帧高度的绝对值。其中每行字节数需要对齐到4字节(Windows BITMAP的要求),计算方式是:((biWidth × biBitCount + 31) / 32) × 4。
  • biXPelsPerMeter / biYPelsPerMeter:视频生成不需要这个参数,直接设为0就行。
  • biClrUsed / biClrImportant:如果是24/32位真彩色,这两个都设为0,因为不需要调色板。

完整代码示例

下面是填充BITMAPINFOHEADER和读取OpenGL帧的代码片段:

#include <GL/glew.h>
#include <GL/glut.h>
#include <cstring>
#include <algorithm> // 用于std::swap

// 填充BITMAPINFOHEADER
BITMAPINFOHEADER initBitmapHeader() {
    BITMAPINFOHEADER header{};
    int winWidth = glutGet(GLUT_WINDOW_WIDTH);
    int winHeight = glutGet(GLUT_WINDOW_HEIGHT);

    header.biSize = sizeof(BITMAPINFOHEADER);
    header.biWidth = winWidth;
    header.biHeight = -winHeight; // 负数值匹配OpenGL帧缓冲方向
    header.biPlanes = 1;
    header.biBitCount = 32; // 我们用32位RGBA格式
    header.biCompression = BI_RGB;

    // 计算每行对齐后的字节数
    int bytesPerRow = ((winWidth * header.biBitCount + 31) / 32) * 4;
    header.biSizeImage = bytesPerRow * winHeight;

    // 剩余字段设为0
    header.biXPelsPerMeter = 0;
    header.biYPelsPerMeter = 0;
    header.biClrUsed = 0;
    header.biClrImportant = 0;

    return header;
}

// 捕获单帧并转换为BITMAP格式
unsigned char* captureOpenGLFrame(const BITMAPINFOHEADER& header) {
    int winWidth = glutGet(GLUT_WINDOW_WIDTH);
    int winHeight = glutGet(GLUT_WINDOW_HEIGHT);
    unsigned char* pixelData = new unsigned char[header.biSizeImage];

    // 从OpenGL读取像素(RGBA格式)
    glReadPixels(0, 0, winWidth, winHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixelData);

    // 转换RGBA为BGRA(Windows BITMAP的32位格式是BGRA)
    for (int i = 0; i < header.biSizeImage; i += 4) {
        std::swap(pixelData[i], pixelData[i + 2]); // 交换R和B通道
    }

    return pixelData;
}

和AVI类结合的注意事项

  1. 初始化时调用一次SetBitmapHeader:在你创建AVI写入器实例后,只需要把上面初始化好的BITMAPINFOHEADER传给SetBitmapHeader一次,不需要每一帧都调用。
  2. 帧率设置:记得在AVI初始化时设置好正确的帧率(比如30fps),这样生成的视频播放速度才正常。
  3. 内存管理:每次捕获帧后,把像素数据传给AVI类的写入函数后,要记得释放pixelData的内存,避免内存泄漏。
  4. 关于GetActiveView():那个函数是MFC框架里的,而你用的是GLUT,完全不需要它——我们自己填充的BITMAPINFOHEADER已经包含了所有需要的信息。

这样你就可以直接把OpenGL的帧数据传给AVI类,不需要中间存BMP文件啦!


内容的提问来源于stack exchange,提问作者anako

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:24:11