OpenGL 离屏渲染

OpenGL 离屏渲染(Off-screen Rendering),短答案:

  • eglCreatePbufferSurface()
  • glReadPixels()

TLDR

EGL 1.5 的 2 个扩展

定义了一个独立于具体窗口系统(如:X11, Wayland, Windows)的抽象设备类别 EGLDeviceEXT。大致而言,一个 EGLDeviceEXT object 代表一个 GPU 设备。各个具体的窗口系统(X11, Wayland...)及 EGLDeviceEXT 在 EGL 规范中统称"platform"。指定 platform,调用 eglGetPlatformDisplay() 函数可创建针对此 platform 的 EGLDisplay object,进一步创建 EGLSurface 和 EGLContext object,供 OpenGL 渲染。

基于 EGLDeviceEXT 的应用程序,不依赖于平台的窗口系统 API,例如,不需要在 X11 或 Wayland 图形环境下运行,适合 headless 系统下的图像渲染,且利用了 OpenGL 提供的硬件加速。

下面按先后顺序介绍用到的 API。

首先需检查 EGL 实现是否支持前述扩展。调用 EGL 函数 eglQueryString() 并提供 EGL_NO_DISPLAY 参数可查询 EGL 支持的全部扩展。如下:

C 复制代码
const char *s = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (NULL == strstr(s, "EGL_EXT_platform_device") || NULL == strstr(s, "EGL_EXT_device_base"))
{
    printf("EGL extension EXT_platform_device not supported\n");
    exit(-1);
}

要使用 EGL 扩展,程序需包含 <EGL/eglext.h> 头文件,并调用 eglGetProcAddress() 函数获取扩展函数的函数指针/地址。EGL_EXT_device_base 扩展中定义了一个 eglQueryDevicesEXT() 函数是后面需要用到的,必须先获取其函数地址,如下:

C 复制代码
#include <EGL/egl.h>
#include <EGL/eglext.h>

PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT;

...

eglQueryDevicesEXT = (PFNEGLQUERYDEVICESEXTPROC)eglGetProcAddress("eglQueryDevicesEXT");

接下来可调用此函数获取支持的 EGLDeviceEXT object:

C 复制代码
EGLDeviceEXT egl_device;
{
    EGLint num;
    eglQueryDevicesEXT(0, NULL, &num);
        
    if (0 == num)
    {
        ...
    }

    eglQueryDevicesEXT(1, &egl_device, &num);
}

然后通过 EGLDeviceEXT object 创建 EGLDisplay object,并初始化:

C 复制代码
EGLDisplay egl_disp = eglGetPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, egl_device, NULL);
if (EGL_NO_DISPLAY == egl_disp)
{
    ...
}

if (!eglInitialize(egl_disp, NULL, NULL))
{
    ...
}

接下来创建 pbuffer surface。Pbuffer surface 不同于 window surface,用于 off screen 渲染。通常 pbuffer 用作 OpenGL texture object,但也可以直接将其复制到应用程序地址空间,例如,保存为图片文件。创建 pbuffer surface 代码如下:

C 复制代码
#define WINDOW_WIDTH 480
#define WINDOW_HEIGHT 480

...

EGLConfig egl_config;
{

    const EGLint attr_list[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //
        EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,       //
        EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,   //
        EGL_RED_SIZE, 5,                         //
        EGL_GREEN_SIZE, 6,                       //
        EGL_BLUE_SIZE, 5,                        //
        EGL_DEPTH_SIZE, 8,                       //
        EGL_NONE,                                //
    };

    EGLint num;
    if (!eglChooseConfig(egl_disp, attr_list, &egl_config, 1, &num) || 1 != num)
    {
        ...
    }
}

EGLSurface egl_surf;
{
    EGLint attr_list[] = {
        EGL_WIDTH, WINDOW_WIDTH,   //
        EGL_HEIGHT, WINDOW_HEIGHT, //
        EGL_NONE,                  //
    };
    egl_surf = eglCreatePbufferSurface(egl_disp, egl_config, attr_list);
    if (EGL_NO_SURFACE == egl_surf)
    {
        ...
    }
}

特别留意上面代码第 10, 11 行。

最后,创建 EGLContext object,并将其设为当前:

C 复制代码
EGLContext egl_context;
{
    EGLint attr_list[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2, //
        EGL_NONE,                      //
    };
    egl_context = eglCreateContext(egl_disp, egl_config, EGL_NO_CONTEXT, attr_list);
    if (EGL_NO_CONTEXT == egl_context)
    {
        ...
    }
}

eglMakeCurrent(egl_disp, egl_surf, egl_surf, egl_context);

至此,EGL 初始化完毕,可以进行 OpenGL 渲染,并调用 OpenGL glReadPixels() 函数从 pbuffer 中将图像/像素数据复制到 CPU 地址空间:

C 复制代码
GLubyte buf[3 * WINDOW_WIDTH * WINDOW_HEIGHT];
glReadPixels(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, buf);

完整的示例程序代码在 gitlab.com/sihokk/lear...

相关推荐
扶尔魔ocy7 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行并进行态势标绘(四)
qt·opengl·osg
:mnong8 天前
Web 三维可视化技术对比:ParaView Web vs 本项目 wasm + Three.js
前端·webgl·opengl·opencl·科学可视化
扶尔魔ocy9 天前
【qt openGL入门笔记】OSG实现显示飞机模型飞行的点迹(二)
qt·opengl·osg
扶尔魔ocy9 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行(三)
opengl·osg
扶尔魔ocy10 天前
【qt openGL入门笔记】OSG实现飞机模型在网格图上飞行
qt·opengl·osg
扶尔魔ocy14 天前
【qt openGL入门笔记】FPS相机
qt·opengl
扶尔魔ocy14 天前
qt openGL入门笔记】FPS相机和轨道相机
qt·opengl
扶尔魔ocy16 天前
【qt openGL入门笔记】基础光照
qt·opengl
扶尔魔ocy16 天前
【qt openGL入门笔记】坐标系统和摄像机
qt·opengl
茉莉玫瑰花茶19 天前
OpenGL [ 纹理 ]
开发语言·c++·opengl