1、圖形設備與相機
在Camera類的成員函數中,setGraphicContext()函數的工作是設置相機對應的圖形設備對象,換句話說,下面要介紹的GraphicsContext類就是圖形設備對象的載體。用一句話來描述的話,GraphicsContext是任意圖形子系統的抽象層接口,它提供了統一的圖形設備操作函數,用來實現渲染結果和底層設備的交互;同時它還具有平台無關性,因而將OSG的渲染過程與操作系統平台剝離開來,使兩者相互獨立。用戶即可以將渲染的內容傳遞給Windows或者X11的窗口與像素緩存對象,也可以自定義一個支持OpenGL的圖形設備,並將結果反映在其上。
圖形設備對象的主要工作是提供場景渲染結果的載體,這個載體可以顯示緩存,進而繪制到一個圖像窗口中,也可以是其他特殊的緩存對象,從而實現復雜的渲染和圖像多次曝光等功能,創建一個圖像設備不能簡單地使用new運算符,因為GraphicContext類是一個不能被實例化的抽象類(這個體現在valid()等一大批純虛函數上);通常應當使用createContext()靜態函數,自動根據當前的用戶環境和特性參數traits,構建一個平台相關的圖形設備對象。
- 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,那么一個簡單的渲染窗口的實現過程如下。
- HDC hdc = GetDC(hwnd);
- HGLRC hrc = wglCreateContext(hdc);
- ----
- 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窗口的基本步驟如下。
- HWND pbufferHwnd = reinterpret_cast<HWND>(wglCreatePbufferARB(hdc, ---));
- HDC pbufferHdc = wglGetPbufferDCARB(reinterpret_cast<HRBUFFERARB>(pbufferHwnd));
- HGLRC pbufferHrc = wglCreateContext(pbufferHdc);
- ----
- wglMakeCurrent(pbufferHdc, pbufferHrc);
- ----
- glBindTexture(----);
- 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的基本流程如下:
- /* 創建FBO對象那個*/
- GLuint fboID;
- glGenFramebuffersEXT(1, &fboID);
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboID);
- /*綁定FBO與一個二維紋理對象textureID*/
- glFramebufferTexture3DEXT(GL_FRAMEBUFFER_EXT,
- GL_COLOR_ATTACHMENT0_EXT,
- GL_TEXTURE_2D, textureId, 0);
- /*渲染子場景到紋理*/
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
- drawSubScene();
- glBindFrameBufferEXT(GL_FRAMEBUFFER_EXT, 0);
而OSG中則直接使用Camera類實現了對於FBO、PBuffer和讀取幀緩存3種紋理烘焙方式的支持。
