九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

GLFW 简单入门学习

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

概要

    实际学习使用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干点什么了。

这里将原来学OpenGL中的教程的例子《Win32 OpenGL 编程(1)Win32下的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来工作呢?

 

 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com

分类:  图形技术 
标签:  GLFW 

Posted By 九天雁翎 at 九天雁翎的博客 on 2010年07月15日

前一篇: 站在巨人的肩膀上开发游戏(5) -- 打砖块游戏制作续 后一篇: SDL 简单入门学习