電腦遊戲製作開發設計論壇 首頁 電腦遊戲製作開發設計論壇
任何可以在PC上跑的遊戲都可以討論,主要以遊戲之製作開發為主軸,希望讓台灣的遊戲人有個討論、交流、教學、經驗傳承的園地
 
 常見問題常見問題   搜尋搜尋   會員列表會員列表   會員群組會員群組   會員註冊會員註冊 
 個人資料個人資料   登入檢查您的私人訊息登入檢查您的私人訊息   登入登入 

Google
GLSL&GLUT 從環境設定開始的基礎教學(06) - Frame Buffer Object(FBO)

 
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式高級班:DirectX、OpenGL及各種圖型函式庫
上一篇主題 :: 下一篇主題  
發表人 內容
Director
偶而上來逛逛的過客


註冊時間: 2013-11-04
文章: 13

381.66 果凍幣

發表發表於: 2013-12-16, PM 2:37 星期一    文章主題: GLSL&GLUT 從環境設定開始的基礎教學(06) - Frame Buffer Object(FBO) 引言回覆

引言回覆:

 ※2015/7/5補充:
  其實已經過很久了,GLee似乎已經沒再繼續更新,我在寫了這篇教學不久後也改用了GLEW取代Glee,它們的功能及函式名稱其實是幾乎一樣的,安裝方式同其他函式庫
  所以只需要將glee.h及glee.lib改為glew.h及glew.lib就好,這是下載的地方,抓Binaries那個http://glew.sourceforge.net/。
  lib用 lib\Release\Win32 裡面的。



 前陣子忙著做繪圖引擎的光影和FXAA的優化,這篇稍晚了些 Confused

 我們這次要做的東西是這個!


 這樣可能看不出是什麼東西,實際上這個正方形裡面畫的是一個周圍場景的動畫(程式中是一直旋轉),你可以想像是一個攝影機那樣,而正方形裡就是攝影機傳來的畫面。

 這個運用可以拿來做出遊戲中監視器的效果,它叫做Frame Buffer Object,下稱FBO。

 當然它可以做的事情絕不只是這個,像是影片剪輯軟體中的濾鏡功能、遊戲裡的反鋸齒、全景泛光等等,大都可以透過這個搭配特定Shader來實現,是個很實用的好東西。

 關於Frame Buffer我不會提太多,畢竟我這系列是想以實作的觀點來跟大家研究GLSL,請多見諒 Very Happy

引言回覆:

 那我們來簡單談談FBO,我們都知道畫面中的每一禎都是資訊經過計算後,送到Frame Buffer Processor,再送到畫面上來的。

 而FBO最經常被拿來用的做法就是,把Frame Buffer Processor裡的資料截下來,然後存成材質再來做演算。

 什麼意思呢?

 大家可以想像成,在vertex的資料處理完之後,畫出來的圖,在把他們搬到螢幕上之前,我們還有機會針對整個畫面調整。

 像是把畫面弄模糊、變清晰等等,各式各樣的濾鏡功能都是在這樣情形下運作的,當然大家熟悉的抗鋸齒AA(anti-aliasing)也是這樣實作的。

 又或者結合我上一篇說的cubemap,他可以從反射物體的位置,取到動態的cubemap材質,從而做出可以反射移動中物體畫面的反射物。

 它的用途非常非常多,像我自己就做過3D遊戲中的控制面板、監視器、全景泛光、抗鋸齒等等運用,有機會的話我今後會多紀錄一些,但還是推薦大家上原文網站查找,資料量真的不可比擬。

 那今天我們就來做個簡易攝影機和屏幕吧!



 那麼簡單說明下步驟:
  Step1: 創建兩張全新的材質,一個深度一個色彩(用來儲存從Frame Buffer Processor拿到的資料)
  Step2: 創建一個新的FBO,然後將剛剛創建的材質綁上去
  Step3: 布置環境,把想要的畫面渲染到FBO的材質上
  Step4: 把每一禎渲染出的材質貼在一個方形上,done!

 開始看程式碼吧 Very Happy

引言回覆:
Step1


 首先要做的是創建新材質,如何創建呢? 我們寫個函式來製作吧!

代碼:
unsigned int createTexture( int w, int h, bool isDepth )
{
   unsigned int textureID;

   glGenTextures( 1, &textureID );      // 向OpenGL登錄一張材質
   glBindTexture( GL_TEXTURE_2D, textureID );      // 下面開始做材質設定
   glTexImage2D( GL_TEXTURE_2D, 0,      // 首參數是你要什麼樣的材質(GL_TEXTURE_2D), 第二參數level可以不管
      isDepth ? GL_DEPTH_COMPONENT : GL_RGBA8,   // 再來是根據引數進來的isDepth來判斷我們要深度還是色彩材質
      w, h, 0,                  // 材質長寬, border可以不管
      isDepth ? GL_DEPTH_COMPONENT : GL_RGBA,      // 同上, 但有區別, 可參考, http://www.opengl.org/sdk/docs/man/xhtml/glTexImage2D.xml
      GL_FLOAT, NULL );            // 色彩資料存在float格式中, 最後我們不是導入image檔, 沒有圖像資料所以田NULL

   // 材質設定算是OpenGL初學的知識我就不提了
   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );

   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER );
   glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER );

   // 簡易的錯誤回報
   int error;
   error = glGetError();
   if( glGetError() != 0 ){
      std::cout << "Error happened while loading the texture: " << gluErrorString(error) << std::endl;
   }
   glBindTexture( GL_TEXTURE_2D, 0 );

   // 最後回傳生成好的材質ID
   return textureID;
}


 大多都如註解 Very Happy

 再來就是宣告兩張新材質囉。
代碼:
GLuint renderTex, depthTex;

void Initialize()
{
   ...
   ...

   renderTex = createTexture( WINDOW_SIZE_W, WINDOW_SIZE_H, false );
   depthTex = createTexture( WINDOW_SIZE_W, WINDOW_SIZE_H, true );

   ...
   ...
}


 第一步就到這邊。




引言回覆:
Step2


 OK! 那我們再來看要如何建立一個新的FBO。
代碼:
GLuint FBO;

void Initialize()
{
   ...
   ...

   glGenFramebuffers( 1, &FBO );   // 登錄一個Frame Buffer Object
   glBindFramebuffer( GL_FRAMEBUFFER, FBO );   // 然後使用它, 下面選擇要存資料的材質
   // GL_COLOR_ATTACHMENT0, 表示用來存色彩資料
   // GL_TEXTURE_2D, 就是材質的性質(剛剛創建的是2D材質)
   // level可以不管
   glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTex, 0 );
   // GL_DEPTH_ATTACHMENT, 表是用來存深度資料
   glFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTex, 0 );
   glBindFramebuffer( GL_FRAMEBUFFER, 0 );      // 一般來說, FBO不使用的時候就會使用"0"來關閉

   ...
   ...
}


 FBO的建立沒什麼困難的,也沒有太多參數可以提 Very Happy



引言回覆:
Step3


 很快的我們就跳到的渲染的階段了,這邊要有的概念很簡單,被包在:
代碼:
glBindFramebuffer( GL_FRAMEBUFFER, FBO );

 以及
代碼:
glBindFramebuffer( GL_FRAMEBUFFER, 0 );

 之間的所有畫面,都會渲染到剛剛綁的那兩張材質上面!

 你想要讓攝影機顯現出什麼畫面,你就先把你的視野移動到那個畫面,記下那時候的攝影機狀態(view matrix),然後到FBO下面完整的畫一遍那個畫面,這樣畫面就會存到renderTex了!

 以我的例子,我設定了一個新的camera,並且讓它每一禎旋轉1度,最後把這個camera看的到畫面畫到正方形上面,我再從別的camera來看這塊正方形,這就是攝影機了!

 於是首先要做的就是佈置場景,調整你想要在螢幕上看到的視野(view matrix)
代碼:
float angle = 0.0f;

void Display()
{
   angle += 1.0f;
   if( angle >= 360 )
      angle -= 360;

   glMatrixMode( GL_MODELVIEW );
   glLoadIdentity();

   glBindFramebuffer( GL_FRAMEBUFFER, FBO );   // 使用FBO
   // 綁定色彩資料材質
   glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTex, 0 );
   // 綁定深度資料材質
   glFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTex, 0 );
   // 我不想讓keyboard影響到攝影機的視野, 所以傳了一個假的keyboard狀態給newCamera
   bool tmpKB[256] = {false};
   newCamera.lookAt( newCamera.getPitch(), angle );
   newCamera.Control( tmpKB, CAM_NO_MOUSE_INPUT );
   // 畫點東西
   drawSkybox();
   glClear( GL_DEPTH_BUFFER_BIT );

   newCamera.UpdateCamera();
   // 然後結束FBO
   glBindFramebuffer( GL_FRAMEBUFFER, 0 );


 到這裡為止,我就完成了我想要看到的畫面,就是一個一直旋轉的攝影機看到的東西。



引言回覆:
Step4


 然後最後一步,你只要向平常一樣把renderTex當作一張材質貼上正方形就好!

代碼:
   // 要來畫新的東西, 記得重置視野的matrix
   glLoadIdentity();

   MainCamera.Control( stateKeyboard, CAM_NO_MOUSE_INPUT );

   drawSkybox();
   glClear( GL_DEPTH_BUFFER_BIT );

   MainCamera.UpdateCamera();

   glm::mat4 modelViewMatrix =
      glm::rotate( -MainCamera.getPitch(), 1.0f, 0.0f, 0.0f ) *
      glm::rotate( -MainCamera.getYaw(), 0.0f, 1.0f, 0.0f ) *
      glm::translate( MainCamera.getLocation().x, MainCamera.getLocation().y, MainCamera.getLocation().z );
   glm::vec4 worldLightPosition = modelViewMatrix * glm::vec4( lightPosition.x, lightPosition.y, lightPosition.z, 1.0f );

   mainShader->useShader();
   glActiveTexture( GL_TEXTURE0 );
   // 全部跟上一章一樣, 有改的只有這邊, 貼上剛剛渲染的renderTex
   glBindTexture( GL_TEXTURE_2D, renderTex );
   glUniform1i( glGetUniformLocation( mainShader->getProgramID(), "tex" ), 0 );
   glUniform3f( glGetUniformLocation( mainShader->getProgramID(), "lightPosition" ), worldLightPosition.x, worldLightPosition.y, worldLightPosition.z );
   glUniform4f( glGetUniformLocation( mainShader->getProgramID(), "lambient" ), 0.4, 0.4, 0.4, 1.0 );
   glUniform4f( glGetUniformLocation( mainShader->getProgramID(), "ldiffuse" ), 0.7, 0.7, 0.7, 1.0 );
   glUniform4f( glGetUniformLocation( mainShader->getProgramID(), "lspecular" ), 1.0, 1.0, 1.0, 1.0 );   

   glBegin(GL_QUADS);
   glNormal3f( 0, 0, 1 );
   glTexCoord2f( 1, 1 );
   glVertex3f(1, 1, -5);
   glTexCoord2f( 0, 1 );
   glVertex3f(-1, 1, -5);
   glTexCoord2f( 0, 0 );
   glVertex3f(-1, -1, -5);
   glTexCoord2f( 1, 0 );
   glVertex3f(1, -1, -5);
   glEnd();
   mainShader->delShader();   

   glutSwapBuffers();
}


 只要把myTex改成renderTex你就可以把剛剛FBO截下的材質貼到正方形上!

 之後若有機會提到濾鏡的實作,概念就是把這張renderTex經過Shader處理過後再貼上,很簡單的道理,之後有機會再談囉 Very Happy



 最後留下本章範例程式碼。



ch6.rar
 描述:
本章節範例程式碼

下載
 檔名:  ch6.rar
 附件大小:  106.71 KB
 下載次數:  共 747 次

回頂端
檢視會員個人資料 發送私人訊息 發送電子郵件 參觀發表人的個人網站
JeffreyMt
稍嫌羞澀的路人


註冊時間: 2015-08-13
文章: 1
來自: France
7.08 果凍幣

發表發表於: 2015-9-10, PM 11:37 星期四    文章主題: GLSLLUT 06 Frame Buffer ObjectFBO 引言回覆

I am trying to modify the animation of Skeleton.Nif, but when I try to export, I get this error.

Error in Anim Buffer: Frame Out of Range 31 not in 1,20

anu clue of its meaning and how to workaround it?
回頂端
檢視會員個人資料 發送私人訊息 發送電子郵件 參觀發表人的個人網站 AIM Address 雅虎訊息通
Takenaka
稍嫌羞澀的路人


註冊時間: 2016-01-04
文章: 1

7.58 果凍幣

發表發表於: 2016-1-4, PM 9:34 星期一    文章主題: 引言回覆

感謝樓主,本人是今年開始才學習OPENGL的新人....

我只有基本的java知識,還有真的是微不足道C++知識(我同時只學習了C++大約3星期...)

因為自己不能明白學校的教學內容,所以來此學習C++,請多指教!
回頂端
檢視會員個人資料 發送私人訊息
從之前的文章開始顯示:   
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式高級班:DirectX、OpenGL及各種圖型函式庫 所有的時間均為 台灣時間 (GMT + 8 小時)
1頁(共1頁)

 
前往:  
無法 在這個版面發表文章
無法 在這個版面回覆文章
無法 在這個版面編輯文章
無法 在這個版面刪除文章
無法 在這個版面進行投票
可以 在這個版面附加檔案
可以 在這個版面下載檔案


Powered by phpBB © 2001, 2005 phpBB Group
正體中文語系由 phpbb-tw 維護製作