1. 程式人生 > >GLFW 簡單入門學習

GLFW 簡單入門學習

概要

    實際學習使用GLFW建立視窗,並繪製圖形。作為比較,可以參考一篇關於SDL的類似文章

前言

    使用SDL時,對其使用的簡單印象非常深刻,假如沒有效率上的原因,(SDL據說效率也不差)我想,即使是做一般的遊戲引擎都可以考慮用SDL來實現。現在嘗試使用一下GLFW( http://www.glfw.org/),GLFW在國內並不是很出名,第一次聽說也是從Orx的作者iarwain那裡,SDL那篇文章已經說過了,因為SDL給我的感覺非常好,我很難想象GLFW會超過它,但是,平時想真的自己使用OpenGL的時候,有個框架可以使用也挺不錯的,而且可以不使用glut,使得真的需要進一步做成產品時,不至於像使用glut那樣受到限制。另外,GLFW使用的是zlib的協議,比SDL更加自由。

實際的使用

    現在的最新版GLFW是2.6,我下載的是原始碼包,下載後,support目錄有VS的工程,有兩個projects,一個是編譯成lib,(因為GLFW使用的是zlib協議,所以這也是允許的)一個是編譯成dll。我為了減少編譯和連結的時間,使用dll。編譯成庫以後,使用時,只用包含一個頭檔案即可,只需要一個頭檔案。。。。。
    其實,假如你原因,GLFW的檔案並不是很多,全部包含進自己的工程也可以。(這也不違反協議)而且,我注意到support目錄下有很多有意思的東西,包括通過lua,basic,組合語言來使用GLFW和OpenGL的例子,也算是很有意思了。因為GLFW的介面比較少,所以,事實上,做一個語言的繫結工作的工作量不是太大,而且提供的都是C介面,這讓語言的繫結更加簡單,只要該語言支援C語言介面,而且有了OpenGL的繫結。雖然GLFW沒有提供Python繫結,不過我感覺也不難做。呵呵,這些都是題外話了。
    編譯後建立工程,這些都沒有什麼好說的了,不過第一次編譯一個使用了glfwInit函式的小工程,竟然報連結錯誤,我很奇怪,網上查了後,發現需要自己定義一個GLFW_DLL的巨集,這點倒是比較奇怪。見

此貼 。心理有點不快,這點在官方的user guide中也沒有提及。

建立視窗

    一直記得在Windows下使用OpenGL有多麼的麻煩,見《Win32 OpenGL 程式設計(1)Win32下的OpenGL程式設計必須步驟 》,那甚至值得用一篇論文來專門描述。使用GLFW呢?
    也還算簡單,主要是呼叫glfwOpenWindow這個函式,引數比較多,但是還算容易理解。當我按照網上少有的教程 ,(這點也體現了越流行的東西越好用的道理,SDL的教程太豐富了,其中很好的也很多,我很容易就上手了。GLFW就沒有那麼好了,我聯想到自己用Orx的經歷,更加發現如此。)開始嘗試執行起一個程式時,我發現一個問題,我建立自己的主迴圈時,(GLFW也沒有內部為你控制)儘管加入了對鍵盤的響應,但是還是沒有用,視窗仍然死在那裡。
類似這樣:
//錯誤示範,切勿模仿
int

 _tmain(int  argc, _TCHAR* argv[])
{
  if (!glfwInit()) {
    printf("GLFW init error." );
  }

  if  (!glfwOpenWindow(800 , 600 , 6 , 6 , 6 , 0 , 32 , 0 , GLFW_WINDOW) ) {
    glfwTerminate();
    exit(1 );
  }
  glfwSetWindowTitle("The GLFW Window" );

  // this just loops as long as the program runs
  while (true ) {
    if  (glfwGetKey(GLFW_KEY_ESC) == GLFW_PRESS) {
      break ;
    }

    glfwSleep(0.05 );
  }

  glfwTerminate();

    return  0 ;
}

我不得其解,後來感覺主迴圈可能需要加入事件的輪詢吧,而且還發現了GLFW中有類似的機制,pollevent的函式也在那裡,但是這個教程為什麼沒有用卻可以呢?後來在GLFW的文件中找到這麼一句:

 If it is not desirable that glfwPollEvents is called implicitly from glfwSwapBuffers, call glfwDisable
with the argument GLFW_AUTO_POLL_EVENTS.

    我就暈了,glfwSwapBuffers竟然隱含著呼叫了poolEvents。。。。。。。。。。我無語。因為我暫時沒有繪製圖形,所以從網上的教程中去掉了此句,這正是問題所在,在glfwSleep(0.05 ); 前新增glfwSwapBuffers後,問題解決。再次感嘆,好的介面容易讓人用對,壞的介面反之。。。。。。。不要去說是用某個東西前需要將文件全看了話,那是狡辯,好的語言,API設計,應該讓人僅僅看了一部分,也能正常的工作和使用,即使是想當然的撞大運程式設計方式,也應該讓人撞對才對,這才符合最小驚訝原則。。。。。。

    用到這裡,我得感嘆一句,GLFW的user guide寫的真的不咋地,和reference沒有區別,狂列舉API,卻缺少實際的例子,這和reference有啥區別,user guide應該就是寫來讓人快速掌握的。

OpenGL使用

有了上述的東西,基本上OpenGL的環境已經搭好了,可以嘗試用OpenGL乾點什麼了。


#include "stdafx.h"
#include "stdlib.h"
#include "gl/glfw.h"
#define WINDOW_WIDTH ( 800 )
#define WINDOW_HEIGHT ( 600 )

//OpenGL初始化開始
void  SceneInit(int  w,int  h)
{
  glClearColor(0.0f , 0.0f , 0.0f , 0.0f );      // 黑色背景
  glColor3f(1.0f , 1.0f , 1.0f );

  glShadeModel(GL_FLAT);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-50.0f , 50.0f , -50.0f , 50.0f , -1.0f , 1.0f );
}

//這裡進行所有的繪圖工作
void  SceneShow(GLvoid) {
  // 旋轉角度

  static  float  fSpin = 0.0f ;

  fSpin += 2.0f ;

  if (fSpin > 360.0f ) {
    fSpin -= 360.0f ;
  }

  glClear(GL_COLOR_BUFFER_BIT);
  glPushMatrix();

  // 旋轉矩形的主要函式
  glRotatef(fSpin, 0.0f , 0.0f , 1.0f );
  glRectf(-25.0 , -25.0 , 25.0 , 25.0 );
  glPopMatrix();

  // 交換緩衝區
  glfwSwapBuffers();
}  

int  _tmain(int  argc, _TCHAR* argv[])
{
  if (!glfwInit()) {
    printf("GLFW init error." );
  }

  if  (!glfwOpenWindow(WINDOW_WIDTH, WINDOW_HEIGHT, 6 , 6 , 6 , 0 , 32 , 0 , GLFW_WINDOW) ) {
    glfwTerminate();
    exit(1 );
  }
  glfwSetWindowTitle("The GLFW Window" );

  SceneInit(WINDOW_WIDTH, WINDOW_HEIGHT);

  // this just loops as long as the program runs
  while (true ) {
    if  (glfwGetKey(GLFW_KEY_ESC) == GLFW_PRESS) {
      break ;
    }

    SceneShow();
    glfwSleep(0.05 );
  }

  glfwTerminate();

    return  0 ;
}

執行正常,總的來說在GLFW中搭建一個OpenGL的環境還是很簡單的吧,起碼比Windows下使用Win32 API來使用OpenGL要簡單的多,並且,這還是跨平臺的。(知道和很多人說跨平臺就是廢話)


真的用GLFW使用過OpenGL後,發現其實還算是比較簡單,我的那些話也有些苛責了。雖然我一向很喜歡真的瞭解底層,比如Win32 API,比如cocoa,但是,能夠有個跨平臺的庫,為我將這些東西都管理起來,就算我要寫什麼,那也會簡單很多。另外,雖然SDL也能做到這些,但是相對於GLFW對於OpenGL的直接支援,(SDL中其實也可以)我感覺用起來還是更加親切一些。

圖形的顯示

    這才是最終的目的。
真正的點陣圖的顯示,在OpenGL中都不是那麼容易的,需要掌握一堆的東西。見《Win32 OpenGL程式設計(15) 點陣圖顯示 》《Win32 OpenGL程式設計(16) 紋理貼圖 》,那是因為OpenGL中實際完全對圖形的顯示沒有直接的支援。聽起來有些奇怪。。。實際的意思就是,OpenGL的API完全不理解點陣圖,png圖的含義。(雖然在上述15中提到一些bmp的操作介面,但是很遺憾的,實際的使用中都是使用紋理貼圖,即16中提到的東西) 在GLFW中呢?我看到GLFW有對圖形操作的介面。可是遺憾的是僅支援TGA,連BMP都不支援,不知道這種取捨是為啥,一般而言,我感覺,支援bmp的話,是最基礎的。
這裡還是用《SDL 簡單入門學習 》中的那個龍圖。
其中,圖形檔案操作的介面分兩種,這裡只看OpenGL常用的使用紋理貼圖的方式。用到的API名字叫glfwLoadTexture2D。先顯示個tga試一下。
程式碼的主要部分還是OpenGL,所以可以參考《Win32 OpenGL程式設計(16) 紋理貼圖 》中的程式碼,僅僅借用了glfw的glfwLoadTexture2D函式而已。主要程式碼如下:

#include "stdafx.h"
#include "stdlib.h"
#include "gl/glfw.h"
#define WINDOW_WIDTH ( 800 )
#define WINDOW_HEIGHT ( 600 )

GLuint gTexName;
//OpenGL初始化開始
void  SceneInit(int  w,int  h) {
  glClearColor(0.0f , 0.0f , 0.0f , 0.0f );      // 黑色背景
  glColor3f(1.0f , 1.0f , 1.0f );

  glShadeModel(GL_FLAT);

  glGenTextures(1  , &gTexName);
  glBindTexture(GL_TEXTURE_2D, gTexName);

  if  ( !glfwLoadTexture2D("dragon.tga" , GLFW_BUILD_MIPMAPS_BIT) ) {
    printf("glfw load the file failed." );
  }

  // Use trilinear interpolation for minification
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
    GL_LINEAR_MIPMAP_LINEAR );
  // Use bilinear interpolation for magnification

  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
    GL_LINEAR );

  // Enable texturing
  glEnable( GL_TEXTURE_2D );
}

//這裡進行所有的繪圖工作
void  SceneShow(GLvoid) {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glBindTexture(GL_TEXTURE_2D, gTexName);

  glBegin(GL_QUADS);
  glTexCoord2f(0.0  , 0.0  ); glVertex3f(-1.0  , -1.0  , 0.0  );
  glTexCoord2f(1.0  , 0.0  ); glVertex3f(1.0  , -1.0  , 0.0  );
  glTexCoord2f(1.0  , 1.0  ); glVertex3f(1.0  , 1.0  , 0.0  );
  glTexCoord2f(0.0  , 1.0  ); glVertex3f(-1.0  , 1.0  , 0.0  );

  glEnd();

  // 交換緩衝區
  glfwSwapBuffers();
}  

int  _tmain(int  argc, _TCHAR* argv[])
{
  if (!glfwInit()) {
    printf("GLFW init error." );
  }

  if  (!glfwOpenWindow(WINDOW_WIDTH, WINDOW_HEIGHT, 6 , 6 , 6 , 0 , 32 , 0 , GLFW_WINDOW) ) {
    glfwTerminate();
    exit(1 );
  }
  glfwSetWindowTitle("The GLFW Window" );

  SceneInit(WINDOW_WIDTH, WINDOW_HEIGHT);

  // this just loops as long as the program runs
  while (true ) {
    if  (glfwGetKey(GLFW_KEY_ESC) == GLFW_PRESS) {
      break ;
    }

    SceneShow();
    glfwSleep(0.05 );
  }

  glfwTerminate();

    return  0 ;
}

感覺相對於影象顯示來說,glfw並沒有如SDL那般省事,因為畢竟還是全程使用OpenGL,對比原來OpenGL中顯示點陣圖的程式碼來說,僅僅是沒有呼叫Windows API了而已,僅僅是多了跨平臺的特性而已,並沒有簡化工作,而且,glfw同樣的,也沒有內建png圖形的支援。雖然說tga在3D中用的非常多,主要是因為無失真壓縮,但是2D中,我還是喜歡使用png,因為小的多。當然,一旦使用png,無可避免的會需要使用libpng/zlib,所以GLFW為了保持自身的簡單,沒有做這樣的工作吧,相對來說tga的解壓就要簡單太多了。
另外,GLFW還有glfwReadImage函式可以將tga圖直接讀入記憶體,然後獲取到圖形的相關資訊的辦法(上面就沒有辦法獲取到圖形的寬高)。但是使用上都已經差不多了。


都到了這個地步了,不顯示一下GLFW對OpenGL的強力支援,所以做3D比較方便都不像話了。這裡套用原來的程式碼。見《Win32 OpenGL程式設計(16) 紋理貼圖 》。

//OpenGL初始化開始
void  SceneInit(int  w,int  h) {
  glClearColor(0.0f , 0.0f , 0.0f , 0.0f );      // 黑色背景
  glColor3f(1.0f , 1.0f , 1.0f );

  glViewport(0  ,0  ,WINDOW_WIDTH,WINDOW_HEIGHT);                      // Reset The Current Viewport

  glMatrixMode(GL_PROJECTION);                        // Select The Projection Matrix
  glLoadIdentity();                                   // Reset The Projection Matrix

  // Calculate The Aspect Ratio Of The Window
  gluPerspective(45.0f  ,(GLfloat)WINDOW_WIDTH/(GLfloat)WINDOW_HEIGHT,0.1f  ,100.0f  );

  glMatrixMode(GL_MODELVIEW);                         // Select The Modelview Matrix
  glLoadIdentity();                                   // Reset The Modelview Matrix


  glGenTextures(1  , &gTexName);
  glBindTexture(GL_TEXTURE_2D, gTexName);

  if  ( !glfwLoadTexture2D("dragon.tga" , GLFW_BUILD_MIPMAPS_BIT) ) {
    printf("glfw load the file failed." );
  }

  // Use trilinear interpolation for minification
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
    GL_LINEAR_MIPMAP_LINEAR );
  // Use bilinear interpolation for magnification

  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
    GL_LINEAR );

  glEnable(GL_DEPTH_TEST);
  // Enable texturing
  glEnable( GL_TEXTURE_2D );
}

//這裡進行所有的繪圖工作
void  SceneShow(GLvoid) {
  static  float  xrot = 0.0f ,yrot = 0.0f ,zrot = 0.0f ;
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();                                   // Reset The View
  glTranslatef(0.0f  ,0.0f  ,-5.0f  );

  glRotatef(xrot,1.0f  ,0.0f  ,0.0f  );
  glRotatef(yrot,0.0f  ,1.0f  ,0.0f  );
  glRotatef(zrot,0.0f  ,0.0f  ,1.0f  );

  glBindTexture(GL_TEXTURE_2D, gTexName);

  glBegin(GL_QUADS);
  // Front Face
  glTexCoord2f(0.0f  , 0.0f  ); glVertex3f(-1.0f  , -1.0f  ,  1.0f  );
  glTexCoord2f(1.0f  , 0.0f  ); glVertex3f( 1.0f  , -1.0f  ,  1.0f  );
  glTexCoord2f(1.0f  , 1.0f  ); glVertex3f( 1.0f  ,  1.0f  ,  1.0f  );
  glTexCoord2f(0.0f  , 1.0f  ); glVertex3f(-1.0f  ,  1.0f  ,  1.0f  );
  // Back Face
  glTexCoord2f(1.0f  , 0.0f  ); glVertex3f(-1.0f  , -1.0f  , -1.0f  );
  glTexCoord2f(1.0f  , 1.0f  ); glVertex3f(-1.0f  ,  1.0f  , -1.0f  );
  glTexCoord2f(0.0f  , 1.0f  ); glVertex3f( 1.0f  ,  1.0f  , -1.0f  );
  glTexCoord2f(0.0f  , 0.0f  ); glVertex3f( 1.0f  , -1.0f  , -1.0f  );
  // Top Face
  glTexCoord2f(0.0f  , 1.0f  ); glVertex3f(-1.0f  ,  1.0f  , -1.0f  );
  glTexCoord2f(0.0f  , 0.0f  ); glVertex3f(-1.0f  ,  1.0f  ,  1.0f  );
  glTexCoord2f(1.0f  , 0.0f  ); glVertex3f( 1.0f  ,  1.0f  ,  1.0f  );
  glTexCoord2f(1.0f  , 1.0f  ); glVertex3f( 1.0f  ,  1.0f  , -1.0f  );
  // Bottom Face
  glTexCoord2f(1.0f  , 1.0f  ); glVertex3f(-1.0f  , -1.0f  , -1.0f  );
  glTexCoord2f(0.0f  , 1.0f  ); glVertex3f( 1.0f  , -1.0f  , -1.0f  );
  glTexCoord2f(0.0f  , 0.0f  ); glVertex3f( 1.0f  , -1.0f  ,  1.0f  );
  glTexCoord2f(1.0f  , 0.0f  ); glVertex3f(-1.0f  , -1.0f  ,  1.0f  );
  // Right face
  glTexCoord2f(1.0f  , 0.0f  ); glVertex3f( 1.0f  , -1.0f  , -1.0f  );
  glTexCoord2f(1.0f  , 1.0f  ); glVertex3f( 1.0f  ,  1.0f  , -1.0f  );
  glTexCoord2f(0.0f  , 1.0f  ); glVertex3f( 1.0f  ,  1.0f  ,  1.0f  );
  glTexCoord2f(0.0f  , 0.0f  ); glVertex3f( 1.0f  , -1.0f  ,  1.0f  );
  // Left Face
  glTexCoord2f(0.0f  , 0.0f  ); glVertex3f(-1.0f  , -1.0f  , -1.0f  );
  glTexCoord2f(1.0f  , 0.0f  ); glVertex3f(-1.0f  , -1.0f  ,  1.0f  );
  glTexCoord2f(1.0f  , 1.0f  ); glVertex3f(-1.0f  ,  1.0f  ,  1.0f  );
  glTexCoord2f(0.0f  , 1.0f  ); glVertex3f(-1.0f  ,  1.0f  , -1.0f  );
  glEnd();

  xrot+=0.3f  ;
  yrot+=0.2f  ;
  zrot+=0.4f  ;


  // 交換緩衝區
  glfwSwapBuffers();
}  

效果:


其實最後一個例子已經與介紹GLFW沒有關係了,新新增的部分純粹屬於OpenGL的內容,這裡參看原來的文章,這裡不多加解釋了。僅僅用於演示,當使用了OpenGL後,3D圖形的使用的方便。

小結

    GLFW無愧於其號稱的lightweight的OpenGL框架,的確是除了跨平臺必要做的事情都沒有做,所以一個頭檔案,很少量的API,就完成了任務。GLFW的開發目的是用於替代glut的,從程式碼和功能上來看,我想它已經完全的完成了任務,(雖然從歷史原因上考慮還沒有,畢竟紅寶書都還是用glut。。。)並且glfw還在持續的開發當中,雖然作者總說他很忙。
    作為與SDL(參考《SDL 簡單入門學習》 )比較而寫的兩篇文章。這裡也做個小結。相對來說,SDL真的將API做的很簡單,而且因為使用的人比較多,所以第3方擴充套件也做的很好,並且,碰到問題,你比較容易找到答案。假如是做2D應用,SDL真的已經非常不錯了。GLFW作為一個跨平臺的OpenGL框架,也出色的完成了任務,不過因為定位不同,封裝較少,所以在做一些基本任務的時候,因為OpenGL本身的複雜性,會複雜一些。同時,資料也少的多,所以我寫本文花費的時間比SDL那篇就要長了很多,說明學習週期也會長一些。。。。(但是個人感覺類似GLFW,SDL這樣的庫,學習週期基本為0。。。。比起編寫圖形或者遊戲的其他方面來說可以忽略不計)但是,同時的,GLFW封裝的少,也帶來更大的靈活性,我們可以還是自由的用OpenGL完成工作,也可以完成自己的進一步封裝,相當於GLFW將跨平臺的一些髒活累活都幹了,我們就剩最核心的渲染去自由發揮了。碰到一個任務,該怎麼在兩者之間選擇呢?出於簡單考慮,2D方面的東西用SDL做實在再合適和簡單不過了,但是假如想要學習OpenGL或者是做3D應用,GLFW的確是不錯的選擇。絕對比在glut上的投入要值,當然,其實因為glut實在太過簡單,其實出於教學/學習目的去看一下也實在沒有什麼投入,實際用OpenGL開發東西的時候,還是選擇用GLFW做吧。
    也許,下一步,我可以看看怎麼在SDL中使用OpenGL。在Windows下,SDL的預設渲染API使用的是D3D,不知道是否可以更改,並且完全使用OpenGL來工作呢?

原創文章作者保留版權 轉載請註明原作者 並給出連結