OSG图形设备接口GraphicsContext

时间:2020-11-28 15:32:32

1、图形设备与相机

在Camera类的成员函数中,setGraphicContext()函数的工作是设置相机对应的图形设备对象,换句话说,下面要介绍的GraphicsContext类就是图形设备对象的载体。用一句话来描述的话,GraphicsContext是任意图形子系统的抽象层接口,它提供了统一的图形设备操作函数,用来实现渲染结果和底层设备的交互;同时它还具有平台无关性,因而将OSG的渲染过程与操作系统平台剥离开来,使两者相互独立。用户即可以将渲染的内容传递给Windows或者X11的窗口与像素缓存对象,也可以自定义一个支持OpenGL的图形设备,并将结果反映在其上。

图形设备对象的主要工作是提供场景渲染结果的载体,这个载体可以显示缓存,进而绘制到一个图像窗口中,也可以是其他特殊的缓存对象,从而实现复杂的渲染和图像多次曝光等功能,创建一个图像设备不能简单地使用new运算符,因为GraphicContext类是一个不能被实例化的抽象类(这个体现在valid()等一大批纯虚函数上);通常应当使用createContext()静态函数,自动根据当前的用户环境和特性参数traits,构建一个平台相关的图形设备对象。

  1. osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::creteGraphicsContext(traits)

2、窗口与像素缓存(Pixel Buffer)

      Windows下的每一个窗口都附带了一个设备环境(Device Context,DC)。当需要在窗口中直接进行二维图像绘制时,可以使用Windows图形设备接口函数(GDI)来完成操作。如果希望在某个Windows的窗口中实现三维场景的渲染,则需要将一个OpenGL渲染环境(Rendering Context, RC)的标识与此窗口的设备环境相关联;如果当前的OpenGL指令都要输出到某一个窗口,还应当指定该窗口的渲染环境为“当前渲染环境”。假设已知窗口句柄为hwnd,那么一个简单的渲染窗口的实现过程如下。

  1. HDC hdc = GetDC(hwnd);
  2. HGLRC hrc = wglCreateContext(hdc);
  3. ----
  4. wglMakeCurrent(hdc, hrc);

对于一个OpenGL而言,参与三维绘制的窗口可以有多个,但是一个渲染环境只能与一个窗口的设备环境相关联;并且任意时刻都只能有一个渲染环境被指定为当前环境。

而对于OSG来说,有关窗口及其渲染环境的操作都是由GraphicsContext的派生类osgViewer::GraphicsWindow来完成的。有关这个类及其“平台相关”子类的具体实现,参见后文“人机交互与图形设备接口”的内容。

像素缓存(Pixel Buffer, PBuffer)是一种较新的OpenGL扩展功能,用于实现离屏渲染(Off-screen Rendering)以及渲染到纹理(又称纹理烘焙,Render to Texture)。简单地说, PBuffer机制将本来渲染到显示缓存的场景数据换向输出到一处用户缓存中,进而可以将渲染数据绑定到纹理图片,甚至直接取出进行处理。将场景渲染的数据绑定到一张纹理图片的动作称为“纹理烘焙”;而使用着色器进行逐顶点或逐像素的数学运算,通过glReadPixels()等函数将渲染到纹理的结果重新取出,并加以储存和重新运用的过程,则属于通用GPU计算(General-purpose Computing on Graphics Process Units, GPGPU)的范畴。

OpenGL的像素缓存可以理解成一个建立在已有窗口上的一个虚拟窗口设备,它同样需要创建一个“窗口”句柄,为这个句柄分配设备环境,并且为设备环境关联渲染环境。由此得到的PBuffer设备可以像普通窗口一样被操作,但是它还允许将这个“窗口”绑定到指定的纹理对象,从而将渲染到该“窗口”的场景内容烘焙到纹理上。假设已知一个实际窗口的设备环境hdc,在其基础上构建一个PBuffer窗口的基本步骤如下。

  1. HWND pbufferHwnd = reinterpret_cast<HWND>(wglCreatePbufferARB(hdc, ---));
  2. HDC pbufferHdc = wglGetPbufferDCARB(reinterpret_cast<HRBUFFERARB>(pbufferHwnd));
  3. HGLRC pbufferHrc = wglCreateContext(pbufferHdc);
  4. ----
  5. wglMakeCurrent(pbufferHdc, pbufferHrc);
  6. ----
  7. glBindTexture(----);
  8. wglBindTexImageARB(reinterpret_cast<HPBUFFERARB>(phbufferHwnd), ---);

OSG中完整地封装了像素缓存的实现机制。正是由于pBuffer设备和窗口的类似之处,各个平台上的pBuffer类的实现同样都是由GraphicContext的派生类来完成的,包括核心库osgViewer下的PixelBufferWin32(Windows平台下的实现)、PixelBufferX11(Linux X11下的实现)和PixelBufferCarbon(Mac OS X下的实现)类。当将前文中提及的Traits::pbuffer参数设置为真时,系统就会根据当前系统平台的类别加载相应的PBuffer设备。当然直接使用createGraphicsContext()函数启动一个PBuffer也许没有太大的意义,更多的是在执行渲染到纹理功能时创建一个与之绑定的PBuffer设备。具体参看下一节的内容。

3、渲染到纹理(Rende to Texture)

上一节已经提到过,渲染到纹理(纹理烘焙)这一功能有两个主要作用——是实现场景离屏渲染之后的“后置处理”(Post-processing);二是实现多种不同场景的融合显示。

一个典型的例子如下所述:在一个房间中放置一台播放着精彩节目的电视,房间是主场景;而电视节目则属于另一个场景,它作为纹理被显示在电视屏幕的模型之上,因而成为了主场景的组成部分。重要的是,节目的播放、节目频道的替换,以及节目信号是否突然中断等,这些复杂的变故与主场景并没有直接关系,对于整个房间而言,那只是一幅不断更新着的纹理图片而已。

上一节介绍了像素缓存(PBuffer)这一常用的OpenGL机制,然而实现纹理烘焙的手段并不只有像素缓存一种而已。常用的渲染到纹理的手段包括直接复制帧缓存(Frame buffer)中的像素、使用像素缓存设备、以及使用使用帧缓存对象(Frame Buffer Object, FBO)3种。

  直接复制帧缓存(Frame buffer)中的像素: OpenGL中提供了多种从当前帧的缓存数据中生成二维纹理的方法。效率较低的例如使用glReadPixels()提取像素再传递给glTexImage2D();而效率较高的则使用glCopyTexImage2D()或者glCopyTexSubImage()函数,直接将显示缓存中的数据保存为纹理图片。但是无论怎样,这都是一种间接地“渲染到纹理”的方案,因而其中总是免不了一个“将数据复制到纹理”的步骤。

使用像素缓存设备 : 由此产生的一个优化方案就是使用像素缓存设备。正如之前介绍的那样,它省却了复制的过程,而是直接将子场景渲染到与之绑定的纹理中。因此PBuffer虽然可能在一些老式和低端的显卡上无法得到全面的支持,但依然足以取代直接复制帧缓存的做法,从而进一步提升了纹理烘焙的效率。

帧缓存对象: 然而PBuffer还是有一些无法令人忽视的问题,例如每一个PBuffer设备都必须建立一个自己的渲染环境(RC);它们各自有自己的像素格式、深度和模板缓存;对多个PBuffer进行切换和管理都十分困难。因此,一个新的解决方案诞生了,那就是帧缓存对象(Frame Buffer Object, FBO)扩展。

帧缓存的意义在于,它是一段2D数据的存储空间,保存了OpenGL渲染管线最终得到的像素数据。帧缓存的数据直接输出到窗口系统,即作为显示缓存使用,这种默认的缓存对象又称为“窗口系统支持”(Window-system-provided)的帧缓存。

如果将帧缓存的信息换向输出到一个虚拟窗口设备,并进而绑定到纹理对象,那么这就是之前所说的PBuffer的概念。如果另外定义一种不参与显示的,由“应用程序创建”(Application-created)的帧缓存,则称为帧缓存对象(FBO)。当FBO与一个纹理对象绑定时,它实现的即是“渲染到纹理”的操作;如果它与一处内存空间绑定,那么所执行的操作称为“离屏渲染”,我们可以随后取出该空间的内容,将其保存到图片或执行其他的后置处理。

FBO支持多达16个绑定通道,可以绑定渲染结果的多个颜色缓存值、深度缓存值以及模板缓存值到纹理或者自定义空间之上。FBO易于管理,多个FBO对象之间的切换也十分迅速,并且它还具有平台无关的特性(要知道PBuffer是平台相关的)。

OpenGL中定义和绑定FBO的基本流程如下:

  1. /* 创建FBO对象那个*/
  2. GLuint fboID;
  3. glGenFramebuffersEXT(1, &fboID);
  4. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboID);
  5. /*绑定FBO与一个二维纹理对象textureID*/
  6. glFramebufferTexture3DEXT(GL_FRAMEBUFFER_EXT,
  7. GL_COLOR_ATTACHMENT0_EXT,
  8. GL_TEXTURE_2D, textureId, 0);
  9. /*渲染子场景到纹理*/
  10. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
  11. drawSubScene();
  12. glBindFrameBufferEXT(GL_FRAMEBUFFER_EXT, 0);

而OSG中则直接使用Camera类实现了对于FBO、PBuffer和读取帧缓存3种纹理烘焙方式的支持。