以文本方式查看主题

-  计算机科学论坛  (http://bbs.xml.org.cn/index.asp)
--  『 C/C++编程思想 』  (http://bbs.xml.org.cn/list.asp?boardid=61)
----  [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 17-lesson 18  (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=54042)


--  作者:一分之千
--  发布时间:10/18/2007 5:35:00 PM

--  [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 17-lesson 18

第十七十八课源码


第十七课


按此在新窗口浏览图片2D 图像文字:

在这一课中,你将学会如何使用四边形纹理贴图把文字显示在屏幕上。你将学会如何把256个不同的文字从一个256x256的纹理图像中分别提取出来,并为每一个文字创建一个显示列表,接着创建一个输出函数来创建任意你希望的文字。

  
   
   
本教程由NeHe和Giuseppe D'Agata提供。
我知道每个人都或许厌恶字体。目前为止我写的文字教程不仅能显示文字,还能显示3D文字,有纹理贴图的文字,以及处理变量。但是当你将你的作品移植到不支持位图或是轮廓字体的机器上会发生什么事呢?

由于Giuseppe D'Agata我们有了另一篇字体教程。你还会问什么?如果你记得在第一篇字体教程中我提到使用纹理在屏幕上绘制文字。通常当你使用纹理绘制文字时你会调用你最喜欢的图像处理程序,选择一种字体,然后输入你想显示的文字或段落。然后你保存位图并把它作为纹理读入到你的程序里。对一个需要很多文字或是文字在不停变化的程序来说这么做效率并不高。

本教程只使用有一个纹理来显示任意256个不同的字符。记住平均一个字符只有16个像素宽,大概16个像素高。如果你使用标准的256x256的纹理那么很明显你可以放入交叉的16个文字(即一个X),且最多16行16列。如果你需要一个更详细的解释:纹理是256个像素宽,一个字符是16个像素宽,256除以16得16:)

现在让我们来创建一个2D纹理字体demo!这课的程序基于第一课的代码。在程序的第一段,我们包括数学(math)和标准输入输出库(stdio)。我们需要数学库来使用正弦和余弦函数在屏幕上移动我们的文字,我们需要标准输入输出库来保证在我们制作纹理前要使用的位图实际存在。

  
   
   
我们将要加入一个变量base来指向我们的显示列表。我们还加入texture[2]来保存我们将要创建的两个纹理。Texture 1将是字体纹理,texture 2将是用来创建简单3D物体的凹凸纹理。
我们加入用来执行循环的变量loop。最后我们加入用来绕屏幕移动文字和旋转3D物体的cnt1和cnt2。

  
   

GLuint base;   // 绘制字体的显示列表的开始位置
GLuint texture[2];  // 保存字体纹理
GLuint loop;   // 通用循环变量

GLfloat cnt1;   // 字体移动计数器1
GLfloat cnt2;   // 字体移动计数器2

   
接下来是读取纹理代码。这跟前面纹理影射教程中的一模一样。  
   
   
下面的代码同样对之前教程的代码改动很小。如果你不清楚下面每行的用途,回头复习一下。
注意TextureImage[ ]将保存2个rgb图像记录。复查处理读取或存储纹理的纹理很重要。一个错误的数字可能导致内存溢出或崩溃!  
   

int LoadGLTextures()       // 载入位图(调用上面的代码)并转换成纹理
{
 int Status=FALSE;       // 状态指示器
 AUX_RGBImageRec *TextureImage[2];     // 创建纹理的存储空间

   
下一行十分重要。如果你用别的数字替换2将发生严重问题。再查一次!这个数字应该与你在设置TextureImages[ ]时的数字相匹配。
我们将读取的纹理是font.bmp 和bumps.bmp。第二个纹理可用任何你想用的纹理替换。我不是特别有创造性,所以我使的纹理可能有些单调。

  
   

memset(TextureImage,0,sizeof(void *)*2);     // 将指针设为 NULL

 if ((TextureImage[0]=LoadBMP("Data/Font.bmp")) &&   // 载入字体图像
  (TextureImage[1]=LoadBMP("Data/Bumps.bmp")))   // 载入纹理图像
 {
  Status=TRUE;      // 将 Status 设为 TRUE

   
另一十分重要,要检查两遍的行。我无法开始告诉你我收到多少email问“为什么我只看到一个纹理,或为什么我的纹理是全白的!?!”通常问题都出在这行。如果你用1替换2,那么将只创建一个纹理,第二个纹理将显示为全白。如果你用3替换2,你的程序可能崩溃!
你应该只调用glGenTextures()一次。调用glGenTextures()后你应该创建你的所有纹理。我曾见过有人在每创建一个纹理前都加上一行glGenTextures()。这通常导致新建的纹理覆盖了你之前创建的。决定你需要创建多少个纹理是个好主意,调用glGenTextures()一次,然后创建所有的纹理。把glGenTextures()放进循环是不明智的,除非你有自己的理由。

  
   

glGenTextures(2, &texture[0]);       // 创建纹理

  for (loop=0; loop<2; loop++)     // 循环设置所有的纹理
  {
   // 生成所有纹理
   glBindTexture(GL_TEXTURE_2D, texture[loop]);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
   glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
  }
 }

   
下面的几行代码检查我们读取的位图数据是否在内存里。如果是,释放内存。注意我们还要检查并释放rgb图像记录。如果我们使用了3个不同的图像来创建纹理,我们要检查并释放3个rgb图像记录。  
   

 for (loop=0; loop<2; loop++)
  {
         if (TextureImage[loop])      // 纹理是否存在
   {
    if (TextureImage[loop]->data)   // 纹理图像是否存在
    {
     free(TextureImage[loop]->data);  // 释放纹理图像占用的内存
    }
    free(TextureImage[loop]);    // 释放图像结构
   }
  }
 return Status;        // 返回 Status
}

   
现在我们将创建字体。我将以同样的细节来解释这段代码。这并没那么复杂,但是有些数学要了解,我知道不是每个人都喜欢数学。  
   

GLvoid BuildFont(GLvoid)        // 创建我们的字符显示列表
{

   
下面两个变量将用来保存字体纹理中每个字的位置。cx将用来保存纹理中水平方向的位置,cy将用来保存纹理中竖直方向的位置。
  
   

float cx;         // 字符的X坐标
 float cy;        // 字符的Y坐标
   
接着我们告诉OpenGL我们要建立256个显示列表。变量base将指向第一个显示列表的位置。第二个显示列表将是base+1,第三个是base+2,以此类推。
下面的第二行代码选择我们的字体纹理(texture[0])。
  
   

 base=glGenLists(256);       // 创建256个显示列表
 glBindTexture(GL_TEXTURE_2D, texture[0]);     // 选择字符图象

   
现在我们开始循环。循环间创建所有的256个字符,每个存在它自己的显示列表里。   
   

 for (loop=0; loop<256; loop++)      // 循环256个显示列表
 {

   
下面的第一行或许看上去让人有点困惑。%符号表示loop除以16的余数。cx将我们通过字体纹理从左至右移动。你将注意到在后面的代码中我们用1减去cy从而从上到下而不是从下到上移动我们。%符号很难解释,但我将尝试去解释。

我们真正关心的是(loop%16)。/16只是将结果转化为纹理坐标。所以如果loop等于16,cx将等于16/16的余数也就是0。但cy将等于16/16也就是1。所以我们将下移一个字符的高度,且我们将不往右移。如果loop等于17,cx将等于17/16也就是1.0625。余数0.625也等于1/16。意味着我们将右移一个字符。cy将仍是1因为我们只关心小数点左边的数字。18/16将右移2个字符,但仍下移一个字符。如果loop是32,cx将再次等于0,因为32除以16没有余数,但cy将等于2。因为小数点左边的数字现在是2,将下移2个字符。这么讲清楚吗?
  
   

  cx=float(loop%16)/16.0f;     // 当前字符的X坐标
  cy=float(loop/16)/16.0f;     // 当前字符的Y坐标

   
Ok。现在我们通过从字体纹理中依据cx和cy的值选择一个单独的字符创建了2D字体。在下面的行里我们给base的值加上loop,若不这么做,每个字都将建在第一个显示列表里。我们当然不想要那样的事发生,所以通过给base加上loop,我们创建的每个字都被存在下个可用的显示列表里。  
   

  glNewList(base+loop,GL_COMPILE);    //开始创建显示列表

   
现在我们已选择了我们要创建的显示列表,我们创建字符。这是通过绘制四边形,然后给他贴上字体纹理中的单个字符的纹理来完成的。  
   

   glBegin(GL_QUADS);     // 使用四边形显示每一个字符

   
cx和cy应该保存一个从0.0到1.0的非常小的浮点数。如果cx和cy同时为0,下面第一行的代码将为:glTexCoord2f(0.0f,1-0.0f-0.0625f)。记得0.0625正是我们纹理的1/16,或者说是一个字符的宽/高。下面的纹理坐标将是我们纹理的左下角。
注意我们使用glVertex2i(x,y)而不是glVertex3f(x,y,z)。我们的字体是2D字体,所以我们不需要z值。因为我们使用的是正交投影,我们不需要移进屏幕。在一个正交投影平面绘图你所需的是指定x和y坐标。因为我们的屏幕是以像素形式从0到639(宽)从0到479(高),我们既不需用浮点数也不用负数:)

我们设置正交投影屏幕的方式是,(0,0)将是屏幕的左下角,(640,480)是屏幕的右上角。x轴上0是屏幕的左边界,639是右边界。y轴上0时下便捷,479是上便捷。基本上我们避免了负坐标。对那些不在乎透视,更愿意同像素而不是单元打交道的人来说更方便:)
  
   

    glTexCoord2f(cx,1-cy-0.0625f);  // 左下角的纹理坐标
    glVertex2i(0,0);    // 左下角的坐标

   
下一个纹理坐标现在是上个纹理坐标右边1/16(刚好一个字符宽)。所以这将是纹理的右下角。
  
   

    glTexCoord2f(cx+0.0625f,1-cy-0.0625f); // 右下角的纹理坐标
    glVertex2i(16,0);    // 右下角的坐标

   
第三个纹理坐标在我们的字符的最右边,但上移了纹理的1/16(刚好一个字符高)。这将是一个单独字符的右上角。  
   

    glTexCoord2f(cx+0.0625f,1-cy);  // 右上角的纹理坐标
    glVertex2i(16,16);    // 右上角的坐标

   
最后我们左移来设置字符左上角的最后一个纹理坐标。   
   

    glTexCoord2f(cx,1-cy);   // 左上角的纹理坐标
    glVertex2i(0,16);    // 左上角的坐标
   glEnd();      // 四边形字符绘制完成

   
最终,我们右移了10个像素,置于纹理的右边。如果我们不平移,文字将被绘制到各自的上面。由于我们的字体太窄,我们不想右移16个像素。如果那样的话,每个字之间将有很大间隔。只移动10个像素去除了间隔。   
   

   glTranslated(10,0,0);     // 绘制完一个字符,向右平移16个单位
  glEndList();       // 字符显示列表结束
 }         // 循环建立256个显示列表
}

   
下面这段代码与我们在其它字体教程中用来在程序退出前释放显示列表的相同。所有自base开始的256个显示列表都将被销毁(这样做很好!)。   
   

GLvoid KillFont(GLvoid)        
{
 glDeleteLists(base,256);      // 从内存中删除256个显示列表
}

   
下一段代码将完成绘图。一切都几乎是新的,所以我将尽可能详细的解释每一行。一个小提示:很多都可加入这段代码,像是变量的支持,字体大小、间距的调整,和很多为恢复到我们决定打印前的状况所做的检查。
glPrint()有三个参数。第一个是屏幕上x轴上的位置(从左至右的位置),下一个是y轴上的位置(从下到上...0是底部,越往上越大)。然后是字符串(我们想打印的文字),最后是一个叫做set的变量。如果你看过Giuseppe D'Agata制作的位图,你会注意到有两个不同的字符集。第一个字符集是普通的,第二个是斜体的。如果set为0,第一个字符集被选中。若set为1则选择第二个字符集。
  
   

GLvoid glPrint(GLint x, GLint y, char *string, int set)    // 绘制字符
{

   
我们要做的第一件事是确保set的值非0即1。如果set大于1,我们将使它等于1。   
   

 if (set>1)        // 如果字符集大于1
 {
  set=1;        // 设置其为1
 }

   
现在我们选择字体纹理。我们这么做是防止在我们决定往屏幕上输出东西时选择了不同的纹理。   
   

 glBindTexture(GL_TEXTURE_2D, texture[0]);    // 绑定为字体纹理

   
现在我们禁用深度测试。我这么做是因为混合的效果会更好。如果你不禁用深度测试,文字可能会被什么东西挡住,或得不到正确的混合效果。如果你不打算混合文字(那样文字周围的黑色区域就不会显示)你可以启用深度测试。   
   

 glDisable(GL_DEPTH_TEST);      // 禁止深度测试

   
下面几行十分重要!我们选择投影矩阵。之后使用一个叫做glPushMatrix()的命令。glPushMatrix存储当前矩阵(投影)。有些像计算器的存储按钮。  
   

 glMatrixMode(GL_PROJECTION);      // 选择投影矩阵
 glPushMatrix();        // 保存当前的投影矩阵

   
现在我们保存了投影矩阵,重置矩阵并设置正交投影屏幕。第一和第三个数字(0)表示屏幕的底边和左边。如果愿意我们可以将屏幕的左边设为-640,但如果不需要我们为什么要设负数呢。第二和第四个数字表示屏幕的上边和右边。将这些值设为你当前使用的分辨率是明智的做法。我们不需要用到深度,所以我们将z值设为-1与1。  
   

 glLoadIdentity();        // 重置投影矩阵
 glOrtho(0,640,0,480,-1,1);       // 设置正投影的可视区域

   
现在我们选择模型视点矩阵,用glPushMatrix()保存当前设置。然后我们重置模型视点矩阵以便在正交投影视点下工作。   
   

 glMatrixMode(GL_MODELVIEW);       // 选择模型变换矩阵
 glPushMatrix();        // 保存当前的模型变换矩阵
 glLoadIdentity();        // 重置模型变换矩阵

   
在保存了透视参数,设置了正交投影屏幕后,现在我们可以绘制文字了。我们从移动到绘制文字的位置开始。我们使用 glTranslated()而不是glTranslatef()因为我们处理的是像素,所以浮点值并不重要。毕竟,你不可能用半个像素:)   
   

 glTranslated(x,y,0);       // 把字符原点移动到(x,y)位置

   
下面这行选择我们要使用的字符集。如果我们想使用第二个字符集,我们在当前的显示列表基数上加上128(128时我们256个字符的一半)。通过加上128,我们跳过了头128个字符。  
   

 glListBase(base-32+(128*set));      // 选择字符集

   
现在剩下的就是在屏幕上绘制文字了。我们同其它字体教程一样来完成这步。我们使用glCallLists()。strlen(string)是字符串的长度(我们想绘制多少字符),GL_UNSIGNED_BYTE意味着每个字符被表示为一个无符号字节(一个字节是一个从0到255的值)。最后,字符串保存我们想打印的文字。   
   

 glCallLists(strlen(string),GL_BYTE,string);     // 把字符串写入到屏幕

   
现在我们所要做的是恢复透视视图。我们选择投影矩阵并用glPopMatrix()恢复我们先前用glPushMatrix()保存的设置。用相反的顺序恢复设置很重要。
  
   

 glMatrixMode(GL_PROJECTION);      // 选择投影矩阵
 glPopMatrix();        // 设置为保存的矩阵

   
现在我们选择模型视点矩阵,做相同的工作。我们使用glPopMatrix()恢复模型视点矩阵到我们设置正交投影显示之前。   
   

 glMatrixMode(GL_MODELVIEW);       // 选择模型矩阵
 glPopMatrix();        // 设置为保存的矩阵

   
最后,我们启用深度测试。如果你没有在上面的代码中关闭深度测试,你不需要这行。   
   

 glEnable(GL_DEPTH_TEST);      // 启用深度测试
}

   
我们没有修改ReSizeGLScene(),所以我们直接跳到InitGL()。
  
   

int InitGL(GLvoid)        // 此处开始对OpenGL进行所有设置
{

   
我们跳到创建纹理的代码。如果由于某种原因创建纹理失败了,我们返回FALSE。这将让我们的程序知道发生了一个错误从而关闭程序。  
   

 if (!LoadGLTextures())       // 调用纹理载入子例程
 {
  return FALSE;       // 如果未能载入,返回FALSE
 }

   
如果没有错,我们跳到创建字体的代码。在创建字体时不会出什么错所以我们省略了错误检查。
  
   

 BuildFont();        // 创建字符显示列表

   
现在我们做通常的GL设置。我们将背景色设为黑色,将深度清为1.0。我们选择一个深度测试模式和一个混合模式。我们启用平滑着色,最后启用2维纹理映射。  
   

 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);     // 黑色背景
 glClearDepth(1.0);        // 设置深度缓存
 glDepthFunc(GL_LEQUAL);       // 所作深度测试的类型
 glBlendFunc(GL_SRC_ALPHA,GL_ONE);      // 设置混合因子
 glShadeModel(GL_SMOOTH);       // 启用阴影平滑
 glEnable(GL_TEXTURE_2D);       // 启用纹理映射
 return TRUE;        // 初始化成功
}

   
下面这段代码将完成绘图。我们先绘制3D物体最后绘制文字,这样文字将显示在3D物体上面,而不会被3D物体遮住。我之所以加入一个3D物体是为了演示透视投影和正交投影可同时使用。  
   

int DrawGLScene(GLvoid)        // 从这里开始进行所有的绘制
{
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // 清除屏幕和深度缓存
 glLoadIdentity();        // 重置当前的模型观察矩阵

   
我们选择bumps.bmp纹理来创建简单的小3D物体。为了看见3D物体,我们往屏幕内移动5个单位。我们绕z轴旋转45度。这将使我们的四边形顺时针旋转45度,让我们的四边形看起来更像钻石而不是矩形。  
   

 glBindTexture(GL_TEXTURE_2D, texture[1]);     // 设置为图像纹理
 glTranslatef(0.0f,0.0f,-5.0f);      // 移入屏幕5个单位
 glRotatef(45.0f,0.0f,0.0f,1.0f);      // 沿Z轴旋转45度

   
在旋转45度后,我们让物体同时绕x轴和y轴旋转cnt1x30度。这使我们的物体象在一个点上旋转的钻石那样旋转。  
   

 glRotatef(cnt1*30.0f,1.0f,1.0f,0.0f);     //  沿(1,1,0)轴旋转30度

   
我们关闭混合(我们希望3D物体看上去像实心的),设置颜色为亮白色。然后我们绘制一个单独的用了纹理映像的四边形。  
   

 glDisable(GL_BLEND);       // 关闭混合
 glColor3f(1.0f,1.0f,1.0f);       //设置颜色为白色
 glBegin(GL_QUADS);        // 绘制纹理四边形
  glTexCoord2d(0.0f,0.0f);     
  glVertex2f(-1.0f, 1.0f);     
  glTexCoord2d(1.0f,0.0f);     
  glVertex2f( 1.0f, 1.0f);     
  glTexCoord2d(1.0f,1.0f);     
  glVertex2f( 1.0f,-1.0f);     
  glTexCoord2d(0.0f,1.0f);     
  glVertex2f(-1.0f,-1.0f);     
 glEnd();        
   
在画完第一个四边形后,我们立即同时绕x轴和y轴旋转90度。然后我们画下一个四边形,。第二个四边形从第一个四边形的中间切过去,来形成一个好看的形状。  
   

 glRotatef(90.0f,1.0f,1.0f,0.0f);     //  沿(1,1,0)轴旋转90度
 glBegin(GL_QUADS);       // 绘制第二个四边形,与第一个四边形垂直
  glTexCoord2d(0.0f,0.0f);     
  glVertex2f(-1.0f, 1.0f);     
  glTexCoord2d(1.0f,0.0f);     
  glVertex2f( 1.0f, 1.0f);     
  glTexCoord2d(1.0f,1.0f);     
  glVertex2f( 1.0f,-1.0f);     
  glTexCoord2d(0.0f,1.0f);     
  glVertex2f(-1.0f,-1.0f);     
 glEnd();        
   
在绘制完有纹理贴图的四边形后,我们开启混合并绘制文字。   
   

 glEnable(GL_BLEND);       // 启用混合操作
 glLoadIdentity();        // 重置视口

   
我们使用同其它字体教程一样的生成很棒的颜色的代码。颜色会随着文字的移动而逐渐改变。
  
   

 // 根据字体位置设置颜色
 glColor3f(1.0f*float(cos(cnt1)),1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)));

   
我们来绘制文字。我们仍然使用glPrint()。第一个参数是x坐标,第二个是y坐标,第三个("NeHe")是要绘制的文字,最后一个是使用的字符集(0-普通,1-斜体)。
正如你猜的,我们使用SIN和COS连同计数器cnt1和cnt2来移动文字。如果你不清楚SIN和COS的作用,阅读之前的教程。
  
   

 glPrint(int((280+250*cos(cnt1))),int(235+200*sin(cnt2)),"NeHe",0); 

 glColor3f(1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)),1.0f*float(cos(cnt1)));
 glPrint(int((280+230*cos(cnt2))),int(235+200*sin(cnt1)),"OpenGL",1); 
   
我们将屏幕底部作者名字的颜色设为深蓝色和白色。然后用亮白色文字再次绘制他的名字。亮白色文字是有点偏蓝色的文字。这创造出一种附有阴影的样子。(如果混合没打开则没有这种效果)。   
   

 glColor3f(0.0f,0.0f,1.0f);      
 glPrint(int(240+200*cos((cnt2+cnt1)/5)),2,"Giuseppe D'Agata",0); 

 glColor3f(1.0f,1.0f,1.0f);      
 glPrint(int(242+200*cos((cnt2+cnt1)/5)),2,"Giuseppe D'Agata",0); 
   
我们所做的最后一件事是以不同的速率递增我们的计数器。这使得文字移动,3D物体自转。   
   

 cnt1+=0.01f;        // 增加计数器值
 cnt2+=0.0081f;        // 增加计数器值
 return TRUE;        // 成功返回
}

   
KillGLWindow(), CreateGLWindow()和WndProc()的代码都没有更改,所以我们跳过它们。  
   
   
如下所示,最后要做的是在KillGLWindow()的最后添加KillFont()。添加这行很重要,它在我们退出程序前将所有的清除干净。   
   

KillFont();      // 删除字体

   
我认为现在我可以正式说我的网站已经把所有绘制文字的方法教给大家了{笑}。总之,我认为我的教程很不错。这课的代码可在任何能运行OpenGL的电脑上运行,它很容易使用,且这样绘制文字对系统的资源消耗很少。
我要感谢这篇教程的原作者Giuseppe D'Agata。我做了大量的修改,并将它转变为新式的代码,但要是没有他寄给我这份代码我是不会完成这篇教程的。他的代码有更多的选项,像是改变文字间距等等。但我用很cool的3D物体来弥补了{笑}。

我希望你们喜欢这篇教程。若有什么问题,给我或Giuseppe D'Agata发email。

Giuseppe D'Agata

Jeff Molofee (NeHe)



--  作者:一分之千
--  发布时间:10/18/2007 5:37:00 PM

--  
Lesson 17
   
This tutorial brought to you by NeHe & Giuseppe D'Agata...

I know everyones probably sick of fonts. The text tutorials I've done so far not only display text, they display 3D text, texture mapped text, and can handle variables. But what happens if you're porting your project to a machine that doesn't support Bitmap or Outline fonts?

Thanks to Giuseppe D'Agata we have yet another font tutorial. What could possibly be left you ask!? If you remember in the first Font tutorial I mentioned using textures to draw letters to the screen. Usually when you use textures to draw text to the screen you load up your favorite art program, select a font, then type the letters or phase you want to display. You then save the bitmap and load it into your program as a texture. Not very efficient for a program that require alot of text, or text that continually changes!

This program uses just ONE texture to display any of 256 different characters on the screen. Keep in mind your average character is just 16 pixels wide and roughly 16 pixels tall. If you take your standard 256x256 texture it's easy to see that you can fit 16 letters across, and you can have a total of 16 rows up and down. If you need a more detailed explanation: The texture is 256 pixels wide, a character is 16 pixels wide. 256 divided by 16 is 16 :)

So... Lets create a 2D textured font demo! This program expands on the code from lesson 1. In the first section of the program, we include the math and stdio libraries. We need the math library to move our letters around the screen using SIN and COS, and we need the stdio library to make sure the bitmaps we want to use actually exist before we try to make textures out of them.   
   

#include <windows.h>  // Header File For Windows
#include <math.h>  // Header File For Windows Math Library  ( ADD )
#include <stdio.h>  // Header File For Standard Input/Output ( ADD )
#include <gl\gl.h>  // Header File For The OpenGL32 Library
#include <gl\glu.h>  // Header File For The GLu32 Library
#include <gl\glaux.h>  // Header File For The GLaux Library

HDC  hDC=NULL; // Private GDI Device Context
HGLRC  hRC=NULL; // Permanent Rendering Context
HWND  hWnd=NULL; // Holds Our Window Handle
HINSTANCE hInstance; // Holds The Instance Of The Application

bool keys[256];  // Array Used For The Keyboard Routine
bool active=TRUE;  // Window Active Flag Set To TRUE By Default
bool fullscreen=TRUE; // Fullscreen Flag Set To Fullscreen Mode By Default

   
We're going to add a variable called base to point us to our display lists. We'll also add texture[2] to hold the two textures we're going to create. Texture 1 will be the font texture, and texture 2 will be a bump texture used to create our simple 3D object.

We add the variable loop which we will use to execute loops. Finally we add cnt1 and cnt2 which we will use to move the text around the screen and to spin our simple 3D object.   
   

GLuint base;   // Base Display List For The Font
GLuint texture[2];  // Storage For Our Font Texture
GLuint loop;   // Generic Loop Variable

GLfloat cnt1;   // 1st Counter Used To Move Text & For Coloring
GLfloat cnt2;   // 2nd Counter Used To Move Text & For Coloring

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);    // Declaration For WndProc

   
Now for the texture loading code. It's exactly the same as it was in the previous texture mapping tutorials.   
   

AUX_RGBImageRec *LoadBMP(char *Filename)     // Loads A Bitmap Image
{
 FILE *File=NULL;       // File Handle
 if (!Filename)        // Make Sure A Filename Was Given
 {
  return NULL;       // If Not Return NULL
 }
 File=fopen(Filename,"r");      // Check To See If The File Exists
 if (File)        // Does The File Exist?
 {
  fclose(File);       // Close The Handle
  return auxDIBImageLoad(Filename);    // Load The Bitmap And Return A Pointer
 }
 return NULL;        // If Load Failed Return NULL
}

   
The follwing code has also changed very little from the code used in previous tutorials. If you're not sure what each of the following lines do, go back and review.

Note that TextureImage[ ] is going to hold 2 rgb image records. It's very important to double check code that deals with loading or storing our textures. One wrong number could result in a memory leak or crash!   
   

int LoadGLTextures()        // Load Bitmaps And Convert To Textures
{
 int Status=FALSE;       // Status Indicator
 AUX_RGBImageRec *TextureImage[2];     // Create Storage Space For The Textures

   
The next line is the most important line to watch. If you were to replace the 2 with any other number, major problems will happen. Double check! This number should match the number you used when you set up TextureImages[ ].

The two textures we're going to load are font.bmp (our font), and bumps.bmp. The second texture can be replaced with any texture you want. I wasn't feeling very creative, so the texture I decided to use may be a little drab.   
   

 memset(TextureImage,0,sizeof(void *)*2);    // Set The Pointer To NULL

 if ((TextureImage[0]=LoadBMP("Data/Font.bmp")) &&   // Load The Font Bitmap
  (TextureImage[1]=LoadBMP("Data/Bumps.bmp")))   // Load The Texture Bitmap
 {
  Status=TRUE;       // Set The Status To TRUE

   
Another important line to double check. I can't begin to tell you how many emails I've received from people asking "why am I only seeing one texture, or why are my textures all white!?!". Usually this line is the problem. If you were to replace the 2 with a 1, only one texture would be created and the second texture would appear all white. If you replaced the 2 with a 3 you're program may crash!

You should only have to call glGenTextures() once. After glGenTextures() you should generate all your textures. I've seen people put a glGenTextures() line before each texture they create. Usually they causes the new texture to overwrite any textures you've already created. It's a good idea to decide how many textures you need to build, call glGenTextures() once, and then build all the textures. It's not wise to put glGenTextures() inside a loop unless you have a reason to.   
   

  glGenTextures(2, &texture[0]);     // Create Two Texture

  for (loop=0; loop<2; loop++)     // Loop Through All The Textures
  {
   // Build All The Textures
   glBindTexture(GL_TEXTURE_2D, texture[loop]);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
   glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
  }
 }

   
The following lines of code check to see if the bitmap data we loaded to build our textures is using up ram. If it is, the ram is freed. Notice we check and free both rgb image records. If we used 3 different images to build our textures, we'd check and free 3 rgb image records.   
   

 for (loop=0; loop<2; loop++)
  {
         if (TextureImage[loop])      // If Texture Exists
   {
    if (TextureImage[loop]->data)   // If Texture Image Exists
    {
     free(TextureImage[loop]->data);  // Free The Texture Image Memory
    }
    free(TextureImage[loop]);   // Free The Image Structure
   }
  }
 return Status;        // Return The Status
}

   
Now we're going to build our actual font. I'll go through this section of code in some detail. It's not really that complex, but there's a bit of math to understand, and I know math isn't something everyone enjoys.   
   

GLvoid BuildFont(GLvoid)       // Build Our Font Display List
{

   
The following two variable will be used to hold the position of each letter inside the font texture. cx will hold the position from left to right inside the texture, and cy will hold the position up and down.   
   

 float cx;        // Holds Our X Character Coord
 float cy;        // Holds Our Y Character Coord

   
Next we tell OpenGL we want to build 256 display lists. The variable base will point to the location of the first display list. The second display list will be base+1, the third will be base+2, etc.

The second line of code below selects our font texture (texture[0]).   
   

 base=glGenLists(256);       // Creating 256 Display Lists
 glBindTexture(GL_TEXTURE_2D, texture[0]);    // Select Our Font Texture

   
Now we start our loop. The loop will build all 256 characters, storing each character in it's own display lists.   
   

 for (loop=0; loop<256; loop++)      // Loop Through All 256 Lists
 {

   
The first line below may look a little puzzling. The % symbol means the remainder after loop is divided by 16. cx will move us through the font texture from left to right. You'll notice later in the code we subtract cy from 1 to move us from top to bottom instead of bottom to top. The % symbol is fairly hard to explain but I will make an attempt.

All we are really concerned about is (loop%16) the /16.0f just converts the results into texture coordinates. So if loop was equal to 16... cx would equal the remained of 16/16 which would be 0. but cy would equal 16/16 which is 1. So we'd move down the height of one character, and we wouldn't move to the right at all. Now if loop was equal to 17, cx would be equal to 17/16 which would be 1.0625. The remainder .0625 is also equal to 1/16th. Meaning we'd move 1 character to the right. cy would still be equal to 1 because we are only concerned with the number to the left of the decimal. 18/16 would gives us 2 over 16 moving us 2 characters to the right, and still one character down. If loop was 32, cx would once again equal 0, because there is no remained when you divide 32 by 16, but cy would equal 2. Because the number to the left of the decimal would now be 2, moving us down 2 characters from the top of our font texture. Does that make sense?   
   

  cx=float(loop%16)/16.0f;     // X Position Of Current Character
  cy=float(loop/16)/16.0f;     // Y Position Of Current Character


   
Whew :) Ok. So now we build our 2D font by selecting an individual character from our font texture depending on the value of cx and cy. In the line below we add loop to the value of base if we didn't, every letter would be built in the first display list. We definitely don't want that to happen so by adding loop to base, each character we create is stored in the next available display list.   
   

  glNewList(base+loop,GL_COMPILE);    // Start Building A List

   
Now that we've selected the display list we want to build, we create our character. This is done by drawing a quad, and then texturing it with just a single character from the font texture.   
   

   glBegin(GL_QUADS);     // Use A Quad For Each Character

   
cx and cy should be holding a very tiny floating point value from 0.0f to 1.0f. If both cx and cy were equal to 0 the first line of code below would actually be: glTexCoord2f(0.0f,1-0.0f-0.0625f). Remember that 0.0625 is exactly 1/16th of our texture, or the width / height of one character. The texture coordinate below would be the bottom left point of our texture.

Notice we are using glVertex2i(x,y) instead of glVertex3f(x,y,z). Our font is a 2D font, so we don't need the z value. Because we are using an Ortho screen, we don't have to translate into the screen. All you have to do to draw to an Ortho screen is specify an x and y coordinate. Because our screen is in pixels from 0 to 639 and 0 to 479, we don't have to use floating point or negative values either :)

The way we set up our Ortho screen, (0,0) will be at the bottom left of our screen. (640,480) will be the top right of the screen. 0 is the left side of the screen on the x axis, 639 is the right side of the screen on the x axis. 0 is the bottom of the screen on the y axis and 479 is the top of the screen on the y axis. Basically we've gotten rid of negative coordinates. This is also handy for people that don't care about perspective and prefer to work with pixels rather than units :)   
   

    glTexCoord2f(cx,1-cy-0.0625f);   // Texture Coord (Bottom Left)
    glVertex2i(0,0);    // Vertex Coord (Bottom Left)

   
The next texture coordinate is now 1/16th to the right of the last texture coordinate (exactly one character wide). So this would be the bottom right texture point.   
   

    glTexCoord2f(cx+0.0625f,1-cy-0.0625f);  // Texture Coord (Bottom Right)
    glVertex2i(16,0);    // Vertex Coord (Bottom Right)

   
The third texture coordinate stays at the far right of our character, but moves up 1/16th of our texture (exactly the height of one character). This will be the top right point of an individual character.   
   

    glTexCoord2f(cx+0.0625f,1-cy);   // Texture Coord (Top Right)
    glVertex2i(16,16);    // Vertex Coord (Top Right)

   
Finally we move left to set our last texture coordinate at the top left of our character.   
   

    glTexCoord2f(cx,1-cy);    // Texture Coord (Top Left)
    glVertex2i(0,16);    // Vertex Coord (Top Left)
   glEnd();      // Done Building Our Quad (Character)

   
Finally, we translate 10 pixels to the right, placing us to the right of our texture. If we didn't translate, the letters would all be drawn on top of eachother. Because our font is so narrow, we don't want to move 16 pixels to the right. If we did, there would be big spaces between each letter. Moving by just 10 pixels eliminates the spaces.   
   

   glTranslated(10,0,0);     // Move To The Right Of The Character
  glEndList();       // Done Building The Display List
 }         // Loop Until All 256 Are Built
}

   
The following section of code is the same code we used in our other font tutorials to free the display list before our program quits. All 256 display lists starting at base will be deleted. (good thing to do!).   
   

GLvoid KillFont(GLvoid)        // Delete The Font From Memory
{
 glDeleteLists(base,256);      // Delete All 256 Display Lists
}

   
The next section of code is where all of our drawing is done. Everything is fairly new so I'll try to explain each line in great detail. Just a small note: Alot can be added to this code, such as variable support, character sizing, spacing, and alot of checking to restore things to how they were before we decided to print.

glPrint() takes three parameters. The first is the x position on the screen (the position from left to right). Next is the y position on the screen (up and down... 0 at the bottom, bigger numbers at the top). Then we have our actual string (the text we want to print), and finally a variable called set. If you have a look at the bitmap that Giuseppe D'Agata has made, you'll notice there are two different character sets. The first character set is normal, and the second character set is italicized. If set is 0, the first character set is selected. If set is 1 or greater the second character set is selected.   
   

GLvoid glPrint(GLint x, GLint y, char *string, int set)    // Where The Printing Happens
{

   
The first thing we do is make sure that set is either 0 or 1. If set is greater than 1, we'll make it equal to 1.   
   

 if (set>1)        // Is set Greater Than One?
 {
  set=1;        // If So, Make Set Equal One
 }

   
Now we select our Font texture. We do this just in case a different texture was selected before we decided to print something to the screen.   
   

 glBindTexture(GL_TEXTURE_2D, texture[0]);    // Select Our Font Texture

   
Now we disable depth testing. The reason I do this is so that blending works nicely. If you don't disable depth testing, the text may end up going behind something, or blending may not look right. If you have no plan to blend the text onto the screen (so that black spaces do not show up around our letters) you can leave depth testing on.   
   

 glDisable(GL_DEPTH_TEST);      // Disables Depth Testing

   
The next few lines are VERY important! We select our Projection Matrix. Right after that, we use a command called glPushMatrix(). glPushMatrix stores the current matrix (projection). Kind of like the memory button on a calculator.   
   

 glMatrixMode(GL_PROJECTION);      // Select The Projection Matrix
 glPushMatrix();        // Store The Projection Matrix

   
Now that our projection matrix has been stored, we reset the matrix and set up our Ortho screen. The first and third numbers (0) represent the bottom left of the screen. We could make the left side of the screen equal -640 if we want, but why would we work with negatives if we don't need to. The second and fourth numbers represent the top right of the screen. It's wise to set these values to match the resolution you are currently in. There is no depth so we set the z values to -1 & 1.   
   

 glLoadIdentity();       // Reset The Projection Matrix
 glOrtho(0,640,0,480,-1,1);       // Set Up An Ortho Screen

   
Now we select our modelview matrix, and store it's current settings using glPushMatrix(). We then reset the modelview matrix so we can work with it using our Ortho view.   
   

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

   
With our perspective settings saved, and our Ortho screen set up, we can now draw our text. We start by translating to the position on the screen that we want to draw our text at. We use glTranslated() instead of glTranslatef() because we are working with actual pixels, so floating point values are not important. After all, you can't have half a pixel :)   
   

 glTranslated(x,y,0);       // Position The Text (0,0 - Bottom Left)

   
The line below will select which font set we want to use. If we want to use the second font set we add 128 to the current base display list (128 is half of our 256 characters). By adding 128 we skip over the first 128 characters.   
   

 glListBase(base-32+(128*set));      // Choose The Font Set (0 or 1)

   
Now all that's left for us to do is draw the letters to the screen. We do this exactly the same as we did in all the other font tutorials. We use glCallLists(). strlen(string) is the length of our string (how many characters we want to draw), GL_BYTE means that each character is represented by a byte (a byte is any value from 0 to 255). Finally, string holds the actual text we want to print to the screen.   
   

 glCallLists(strlen(string),GL_BYTE,string);    // Write The Text To The Screen

   
All we have to do now is restore our perspective view. We select the projection matrix and use glPopMatrix() to recall the settings we previously stored with glPushMatrix(). It's important to restore things in the opposite order you stored them in.   
   

 glMatrixMode(GL_PROJECTION);      // Select The Projection Matrix
 glPopMatrix();        // Restore The Old Projection Matrix

   
Now we select the modelview matrix, and do the same thing. We use glPopMatrix() to restore our modelview matrix to what it was before we set up our Ortho display.   
   

 glMatrixMode(GL_MODELVIEW);      // Select The Modelview Matrix
 glPopMatrix();        // Restore The Old Projection Matrix

   
Finally, we enable depth testing. If you didn't disable depth testing in the code above, you don't need this line.   
   

 glEnable(GL_DEPTH_TEST);      // Enables Depth Testing
}

   
Nothing has changed in ReSizeGLScene() so we'll skip right to InitGL().   
   

int InitGL(GLvoid)        // All Setup For OpenGL Goes Here
{

   
We jump to our texture building code. If texture building fails for any reason, we return FALSE. This lets our program know that an error has occurred and the program gracefully shuts down.   
   

 if (!LoadGLTextures())       // Jump To Texture Loading Routine
 {
  return FALSE;       // If Texture Didn't Load Return FALSE
 }

   
If there were no errors, we jump to our font building code. Not much can go wrong when building the font so we don't bother with error checking.   
   

 BuildFont();        // Build The Font

   
Now we do our normal GL setup. We set the background clear color to black, the clear depth to 1.0. We choose a depth testing mode, along with a blending mode. We enable smooth shading, and finally we enable 2D texture mapping.   
   

 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);     // Clear The Background Color To Black
 glClearDepth(1.0);       // Enables Clearing Of The Depth Buffer
 glDepthFunc(GL_LEQUAL);       // The Type Of Depth Test To Do
 glBlendFunc(GL_SRC_ALPHA,GL_ONE);     // Select The Type Of Blending
 glShadeModel(GL_SMOOTH);      // Enables Smooth Color Shading
 glEnable(GL_TEXTURE_2D);      // Enable 2D Texture Mapping
 return TRUE;        // Initialization Went OK
}

   
The section of code below will create our scene. We draw the 3D object first and the text last so that the text appears on top of the 3D object, instead of the 3D object covering up the text. The reason I decide to add a 3D object is to show that both perspective and ortho modes can be used at the same time.   
   

int DrawGLScene(GLvoid)        // Here's Where We Do All The Drawing
{
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   // Clear The Screen And The Depth Buffer
 glLoadIdentity();       // Reset The Modelview Matrix

   
We select our bumps.bmp texture so that we can build our simple little 3D object. We move into the screen 5 units so that we can see the 3D object. We rotate on the z axis by 45 degrees. This will rotate our quad 45 degrees clockwise and makes our quad look more like a diamond than a square.   
   

 glBindTexture(GL_TEXTURE_2D, texture[1]);    // Select Our Second Texture
 glTranslatef(0.0f,0.0f,-5.0f);      // Move Into The Screen 5 Units
 glRotatef(45.0f,0.0f,0.0f,1.0f);     // Rotate On The Z Axis 45 Degrees (Clockwise)

   
After we have done the 45 degree rotation, we spin the object on both the x axis and y axis based on the variable cnt1 times 30. This causes our object to spin around as if the diamond is spinning on a point.   
   

 glRotatef(cnt1*30.0f,1.0f,1.0f,0.0f);     // Rotate On The X & Y Axis By cnt1 (Left To Right)

   
We disable blending (we want the 3D object to appear solid), and set the color to bright white. We then draw a single texture mapped quad.   
   

 glDisable(GL_BLEND);       // Disable Blending Before We Draw In 3D
 glColor3f(1.0f,1.0f,1.0f);      // Bright White
 glBegin(GL_QUADS);       // Draw Our First Texture Mapped Quad
  glTexCoord2d(0.0f,0.0f);     // First Texture Coord
  glVertex2f(-1.0f, 1.0f);     // First Vertex
  glTexCoord2d(1.0f,0.0f);     // Second Texture Coord
  glVertex2f( 1.0f, 1.0f);     // Second Vertex
  glTexCoord2d(1.0f,1.0f);     // Third Texture Coord
  glVertex2f( 1.0f,-1.0f);     // Third Vertex
  glTexCoord2d(0.0f,1.0f);     // Fourth Texture Coord
  glVertex2f(-1.0f,-1.0f);     // Fourth Vertex
 glEnd();        // Done Drawing The First Quad

   
Immediately after we've drawn the first quad, we rotate 90 degrees on both the x axis and y axis. We then draw another quad. The second quad cuts through the middle of the first quad, creating a nice looking shape.   
   

 glRotatef(90.0f,1.0f,1.0f,0.0f);     // Rotate On The X & Y Axis By 90 Degrees (Left To Right)
 glBegin(GL_QUADS);       // Draw Our Second Texture Mapped Quad
  glTexCoord2d(0.0f,0.0f);     // First Texture Coord
  glVertex2f(-1.0f, 1.0f);     // First Vertex
  glTexCoord2d(1.0f,0.0f);     // Second Texture Coord
  glVertex2f( 1.0f, 1.0f);     // Second Vertex
  glTexCoord2d(1.0f,1.0f);     // Third Texture Coord
  glVertex2f( 1.0f,-1.0f);     // Third Vertex
  glTexCoord2d(0.0f,1.0f);     // Fourth Texture Coord
  glVertex2f(-1.0f,-1.0f);     // Fourth Vertex
 glEnd();        // Done Drawing Our Second Quad

   
After both texture mapped quads have been drawn, we enable enable blending, and draw our text.   
   

 glEnable(GL_BLEND);       // Enable Blending
 glLoadIdentity();       // Reset The View

   
We use the same fancy coloring code from our other text tutorials. The color is changed gradually as the text moves across the screen.   
   

 // Pulsing Colors Based On Text Position
 glColor3f(1.0f*float(cos(cnt1)),1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)));

   
Then we draw our text. We still use glPrint(). The first parameter is the x position. The second parameter is the y position. The third parameter ("NeHe") is the text to write to the screen, and the last parameter is the character set to use (0 - normal, 1 - italic).

As you can probably guess, we swing the text around the screen using COS and SIN, along with both counters cnt1 and cnt2. If you don't understand what SIN and COS do, go back and read the previous text tutorials.   
   

 glPrint(int((280+250*cos(cnt1))),int(235+200*sin(cnt2)),"NeHe",0); // Print GL Text To The Screen

 glColor3f(1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)),1.0f*float(cos(cnt1)));
 glPrint(int((280+230*cos(cnt2))),int(235+200*sin(cnt1)),"OpenGL",1); // Print GL Text To The Screen

   
We set the color to a dark blue and write the author's name at the bottom of the screen. We then write his name to the screen again using bright white letters. The white letters are a little to the right of the blue letters. This creates a shadowed look. (if blending wasn't enabled the effect wouldn't work).   
   

 glColor3f(0.0f,0.0f,1.0f);      // Set Color To Red
 glPrint(int(240+200*cos((cnt2+cnt1)/5)),2,"Giuseppe D'Agata",0); // Draw Text To The Screen

 glColor3f(1.0f,1.0f,1.0f);      // Set Color To White
 glPrint(int(242+200*cos((cnt2+cnt1)/5)),2,"Giuseppe D'Agata",0); // Draw Offset Text To The Screen

   
The last thing we do is increase both our counters at different rates. This causes the text to move, and the 3D object to spin.   
   

 cnt1+=0.01f;        // Increase The First Counter
 cnt2+=0.0081f;        // Increase The Second Counter
 return TRUE;        // Everything Went OK
}

   
The code in KillGLWindow(), CreateGLWindow() and WndProc() has not changed so we'll skip over it.   
   

int WINAPI WinMain( HINSTANCE hInstance,    // Instance
   HINSTANCE hPrevInstance,    // Previous Instance
   LPSTR  lpCmdLine,    // Command Line Parameters
   int  nCmdShow)    // Window Show State
{
 MSG  msg;       // Windows Message Structure
 BOOL done=FALSE;       // Bool Variable To Exit Loop

 // Ask The User Which Screen Mode They Prefer
 if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
 {
  fullscreen=FALSE;      // Windowed Mode
 }

   
The title of our Window has changed.   
   

 // Create Our OpenGL Window
 if (!CreateGLWindow("NeHe & Giuseppe D'Agata's 2D Font Tutorial",640,480,16,fullscreen))
 {
  return 0;       // Quit If Window Was Not Created
 }

 while(!done)        // Loop That Runs While done=FALSE
 {
  if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))   // Is There A Message Waiting?
  {
   if (msg.message==WM_QUIT)    // Have We Received A Quit Message?
   {
    done=TRUE;     // If So done=TRUE
   }
   else       // If Not, Deal With Window Messages
   {
    TranslateMessage(&msg);    // Translate The Message
    DispatchMessage(&msg);    // Dispatch The Message
   }
  }
  else        // If There Are No Messages
  {
   // Draw The Scene.  Watch For ESC Key And Quit Messages From DrawGLScene()
   if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) // Active?  Was There A Quit Received?
   {
    done=TRUE;     // ESC or DrawGLScene Signalled A Quit
   }
   else       // Not Time To Quit, Update Screen
   {
    SwapBuffers(hDC);    // Swap Buffers (Double Buffering)
   }
  }
 }

 // Shutdown

   
The last thing to do is add KillFont() to the end of KillGLWindow() just like I'm showing below. It's important to add this line. It cleans things up before we exit our program.   
   

 if (!UnregisterClass("OpenGL",hInstance))  // Are We Able To Unregister Class
 {
  MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
  hInstance=NULL;     // Set hInstance To NULL
 }

 KillFont();      // Destroy The Font
}

   
I think I can officially say that my site now teaches every possible way to write text to the screen {grin}. All in all, I think this is a fairly good tutorial. The code can be used on any computer that can run OpenGL, it's easy to use, and writing text to the screen using this method requires very little processing power.

I'd like to thank Giuseppe D'Agata for the original version of this tutorial. I've modified it heavily, and converted it to the new base code, but without him sending me the code I probably wouldn't have written the tutorial. His version of the code had a few more options, such as spacing the characters, etc, but I make up for it with the extremely cool 3D object {grin}.

I hope everyone enjoys this tutorial. If you have questions, email Giuseppe D'Agata or myself.

Giuseppe D'Agata

Jeff Molofee (NeHe)  


--  作者:一分之千
--  发布时间:10/18/2007 5:42:00 PM

--  

第18课


按此在新窗口浏览图片二次几何体:

利用二次几何体,你可以很容易的创建球,圆盘,圆柱和圆锥。

  
   
   
二次曲面是一种画复合对象的方法,这种方法通常并不需要很多的三角形。我们将要使用第七课的代码。我们将要增加7个变量以及修改纹理以增加一些变化 :  
   

bool    sp;      // 空格键是否按下

int part1;      // 圆盘的起始角度
int part2;      // 圆盘的结束角度
int p1=0;      // 增量1
int p2=1;      // 增量1
GLUquadricObj *quadratic;     // 二次几何体
GLuint  object=0;      // 二次几何体标示符

   
好了我们现在开始搞InitGL()函数。我们打算增加3行代码用来初始化我们的二次曲面。这3行代码将在你使1号光源有效后增加,但是要在返回之前。第一行代码将初始化二次曲面并且创建一个指向改二次曲面的指针,如果改二次曲面不能被创建的话,那么该指针就是NULL。第二行代码将在二次曲面的表面创建平滑的法向量,这样当灯光照上去的时候将会好看些。另外一些可能的取值是:GLU_NONE和GLU_FLAT。最后我们使在二次曲面表面的纹理映射有效。  
   

 quadratic=gluNewQuadric();    // 创建二次几何体
 gluQuadricNormals(quadratic, GLU_SMOOTH);  // 使用平滑法线
 gluQuadricTexture(quadratic, GL_TRUE);  // 使用纹理
   
现在我决定在本课里保留立方体,这样你可以看到纹理是如何映射到二次曲面对象上的。而且我打算将绘制立方体的代码定义为一个单独的函数,这样我们在定义函数Draw()的时候它将会变的不那么凌乱。每个人都应该记住这些代码:  
   

GLvoid glDrawCube()     // 绘制立方体
{
  glBegin(GL_QUADS);   
  // 前面
  glNormal3f( 0.0f, 0.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); 
  glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f); 
  // 后面
  glNormal3f( 0.0f, 0.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); 
  glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); 
  // 上面
  glNormal3f( 0.0f, 1.0f, 0.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); 
  glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f); 
  // 下面
  glNormal3f( 0.0f,-1.0f, 0.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); 
  glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f); 
  // 右面
  glNormal3f( 1.0f, 0.0f, 0.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); 
  glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f); 
  // 左面
  glNormal3f(-1.0f, 0.0f, 0.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); 
  glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f); 
 glEnd();     
}

   
接下来就是场景绘制函数了,在这里我只写一个简单的例子。并且当我绘制一个部分的盘子的时候,我将使用一个静态变量(一个局部的变量,该变量可以保留他的值不论你任何时候调用他)来表达一个非常酷的效果。为了清晰起见我将要重写DrawGLScene函数。
   你们将会注意到当我讨论这些正在使用的参数时我忽略了当前函数的第一个参数(quadratic)。这个参数将被除立方体外的所有对象使用。所以当我讨论这些参数的时候我忽略了它。  
   

int DrawGLScene(GLvoid)      
{
  //...
// 这部分是新增加的
 switch(object)      // 绘制哪一种二次几何体
 {
 case 0:       // 绘制立方体
  glDrawCube();     
  break;      

   
我们创建的第2个对象是一个圆柱体。参数1(1.0F)是圆柱体的底面半径,参数2(1.0F)是圆柱体的饿顶面半径,参数3(3.0F)是圆柱体的高度。参数4(32)是纬线(环绕Z轴有多少细分),参数5(32)是经线(沿着Z轴有多少细分)。细分越多该对象就越细致。我们可以用增加细分的方法来增加对象的多边形数。因此你可以牺牲速度换回质量(以时间换质量),大多数的时候我们都可以很容易的找到一个合适的“度”。
  
   

 case 1:       // 绘制圆柱体
  glTranslatef(0.0f,0.0f,-1.5f);   
  gluCylinder(quadratic,1.0f,1.0f,3.0f,32,32); 
  break;      
   
对象3将会创建一个CD样子的盘子。参数1(0.5F)是盘子的内圆半径,该参数可以为0,则表示在盘子中间没孔,内圆半径越大孔越大。参数2(1.5F)表示外圆半径,这个参数必须比内圆半径大。参数3(32)是组成该盘子的切片的数量,这个数量可以想象成披萨饼中的切片的数量。切片越多,外圆边缘就越平滑。最后一个参数(32)是组成盘子的环的数量。环很像唱片上的轨迹,一环套一环。这些环从内圆半径细分到外圆半径。再说一次,细分越多,速度越慢。  
   

 case 2:       // 绘制圆盘
  gluDisk(quadratic,0.5f,1.5f,32,32);  
  break;      
   
我们的第4个对象我知道你们为描述它耗尽精力。就是球。绘制球将会变的非常简单。参数1是球的半径。如果你无法理解半径/直径等等的话,可以理解成物体中心到物体外部的距离,在这里我们使用1.3F作为半径。接下来两个参数就是细分了,和圆柱体一样,参数2是纬线,参数3是经线。细分越多球看起来就越平滑,通常球需要多一些的细分以使他们看起来平滑。
  
   

 case 3:       // 绘制球
  gluSphere(quadratic,1.3f,32,32);  
  break;      
   
我们创建的第4个对象使用与我们曾经创建的圆柱体一样的命令来创建,如果你还记得的话,我们可以通过控制参数2和参数3来控制顶面半径和地面半径。因此我们可以使顶面半径为0来绘制一个圆锥体,顶面半径为0将会在顶面上创建一个点。因此在下面的代码中,我们使顶面半径等于0,这将会创建一个点,同时也就创建了我们的圆锥。  
   

 case 4:       // 绘制圆锥
  glTranslatef(0.0f,0.0f,-1.5f);   
  gluCylinder(quadratic,1.0f,0.0f,3.0f,32,32); 
  break;      
   
我们的第6个对象将被gluPartialDisk函数创建。我们打算创建的这个对象使用了一些命令,这些命令在我们创建对象之前,你将会清楚的看到。但是命令gluPartialDisk拥有两个新的参数。第5个参数是我们想要绘制的部分盘子的开始角度,参数6是旋转角,也就是转过的角度。我们将要增加旋转角,这将引起盘子沿顺时针方向缓慢的被绘制在屏幕上。一旦旋转角达到360度我们将开始增加开始角度,这样盘子看起来就想是被逐渐的抹去一样。我们将重复这些过程。  
   

 case 5:       // 绘制部分圆盘
  part1+=p1;     
  part2+=p2;     

  if(part1>359)     
  {
   p1=0;     
   part1=0;    
   p2=1;     
   part2=0;    
  }
  if(part2>359)     
  {
   p1=1;     
   p2=0;     
  }
  gluPartialDisk(quadratic,0.5f,1.5f,32,32,part1,part2-part1); 
  break;      
 };

  //...
}

   
In the KillGLWindow() section of code, we need to delete the quadratic to free up system resources. We do this with the command gluDeleteQuadratic.   
   

GLvoid KillGLWindow(GLvoid)     
{
 gluDeleteQuadric(quadratic);    // 删除二次几何体

   
在最后,我给出键盘输入代码。仅仅增加一些对剩余键的检查。
  
   

    if (keys[' '] && !sp)  // 空格是否按下
    {
     sp=TRUE;   // 是,则绘制下一种二次几何体
     object++;  
     if(object>5)  
      object=0; 
    }
    if (!keys[' '])   // 空格是否释放
    {
     sp=FALSE;   // 记录这个状态
    }

   
这就是全部了。现在你可以在OpenGL中绘制二次曲面了。



--  作者:一分之千
--  发布时间:10/18/2007 7:11:00 PM

--  
Lesson 18
   
Quadrics

Quadrics are a way of drawing complex objects that would usually take a few FOR loops and some background in trigonometry.

We'll be using the code from lesson seven. We will add 7 variables and modify the texture to add some variety :)   
   

#include <windows.h>     // Header File For Windows
#include <stdio.h>     // Header File For Standard Input/Output
#include <gl\gl.h>     // Header File For The OpenGL32 Library
#include <gl\glu.h>     // Header File For The GLu32 Library
#include <gl\glaux.h>     // Header File For The GLaux Library

HDC  hDC=NULL;    // Private GDI Device Context
HGLRC  hRC=NULL;    // Permanent Rendering Context
HWND  hWnd=NULL;    // Holds Our Window Handle
HINSTANCE hInstance;    // Holds The Instance Of The Application

bool keys[256];     // Array Used For The Keyboard Routine
bool active=TRUE;     // Window Active Flag Set To TRUE By Default
bool fullscreen=TRUE;    // Fullscreen Flag Set To Fullscreen Mode By Default
bool light;      // Lighting ON/OFF
bool lp;      // L Pressed?
bool fp;      // F Pressed?
bool    sp;      // Spacebar Pressed? ( NEW )

int part1;      // Start Of Disc ( NEW )
int part2;      // End Of Disc  ( NEW )
int p1=0;      // Increase 1  ( NEW )
int p2=1;      // Increase 2  ( NEW )

GLfloat xrot;      // X Rotation
GLfloat yrot;      // Y Rotation
GLfloat xspeed;      // X Rotation Speed
GLfloat yspeed;      // Y Rotation Speed

GLfloat z=-5.0f;     // Depth Into The Screen

GLUquadricObj *quadratic;    // Storage For Our Quadratic Objects ( NEW )

GLfloat LightAmbient[]=  { 0.5f, 0.5f, 0.5f, 1.0f }; // Ambient Light Values
GLfloat LightDiffuse[]=  { 1.0f, 1.0f, 1.0f, 1.0f }; // Diffuse Light Values
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f }; // Light Position

GLuint filter;      // Which Filter To Use
GLuint texture[3];     // Storage for 3 textures
GLuint  object=0;     // Which Object To Draw ( NEW )

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc

   
Okay now move down to InitGL(), We're going to add 3 lines of code here to initialize our quadratic. Add these 3 lines after you enable light1 but before you return true. The first line of code initializes the Quadratic and creates a pointer to where it will be held in memory. If it can't be created it returns 0. The second line of code creates smooth normals on the quadratic so lighting will look great. Other possible values are GLU_NONE, and GLU_FLAT. Last we enable texture mapping on our quadratic. Texture mapping is kind of awkward and never goes the way you planned as you can tell from the crate texture.   
   

 quadratic=gluNewQuadric();   // Create A Pointer To The Quadric Object ( NEW )
 gluQuadricNormals(quadratic, GLU_SMOOTH); // Create Smooth Normals ( NEW )
 gluQuadricTexture(quadratic, GL_TRUE);  // Create Texture Coords ( NEW )

   
Now I decided to keep the cube in this tutorial so you can see how the textures are mapped onto the quadratic object. I decided to move the cube into its own function so when we write the draw function it will appear more clean. Everybody should recognize this code. =P   
   

GLvoid glDrawCube()     // Draw A Cube
{
  glBegin(GL_QUADS);   // Start Drawing Quads
  // Front Face
  glNormal3f( 0.0f, 0.0f, 1.0f);  // Normal Facing Forward
  glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f); // Bottom Left Of The Texture and Quad
  glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f); // Bottom Right Of The Texture and Quad
  glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f); // Top Right Of The Texture and Quad
  glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f); // Top Left Of The Texture and Quad
  // Back Face
  glNormal3f( 0.0f, 0.0f,-1.0f);  // Normal Facing Away
  glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Bottom Right Of The Texture and Quad
  glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f); // Top Right Of The Texture and Quad
  glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f); // Top Left Of The Texture and Quad
  glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Bottom Left Of The Texture and Quad
  // Top Face
  glNormal3f( 0.0f, 1.0f, 0.0f);  // Normal Facing Up
  glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f); // Top Left Of The Texture and Quad
  glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f); // Bottom Left Of The Texture and Quad
  glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f); // Bottom Right Of The Texture and Quad
  glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f); // Top Right Of The Texture and Quad
  // Bottom Face
  glNormal3f( 0.0f,-1.0f, 0.0f);  // Normal Facing Down
  glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Top Right Of The Texture and Quad
  glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Top Left Of The Texture and Quad
  glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f); // Bottom Left Of The Texture and Quad
  glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f); // Bottom Right Of The Texture and Quad
  // Right face
  glNormal3f( 1.0f, 0.0f, 0.0f);  // Normal Facing Right
  glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f); // Bottom Right Of The Texture and Quad
  glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f); // Top Right Of The Texture and Quad
  glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f); // Top Left Of The Texture and Quad
  glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f); // Bottom Left Of The Texture and Quad
  // Left Face
  glNormal3f(-1.0f, 0.0f, 0.0f);  // Normal Facing Left
  glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); // Bottom Left Of The Texture and Quad
  glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f); // Bottom Right Of The Texture and Quad
  glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f); // Top Right Of The Texture and Quad
  glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f); // Top Left Of The Texture and Quad
 glEnd();     // Done Drawing Quads
}

   
Next is the DrawGLScene function, here I just wrote a simple if statement to draw the different objects. Also I used a static variable (a local variable that keeps its value everytime it is called) for a cool effect when drawing the partial disk. I'm going to rewrite the whole DrawGLScene function for clarity.

You'll notice that when I talk about the parameters being used I ignore the actual first parameter (quadratic). This parameter is used for all the objects we draw aside from the cube, so I ignore it when I talk about the parameters.   
   

int DrawGLScene(GLvoid)      // Here's Where We Do All The Drawing
{
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
 glLoadIdentity();     // Reset The View
 glTranslatef(0.0f,0.0f,z);    // Translate Into The Screen

 glRotatef(xrot,1.0f,0.0f,0.0f);    // Rotate On The X Axis
 glRotatef(yrot,0.0f,1.0f,0.0f);    // Rotate On The Y Axis

 glBindTexture(GL_TEXTURE_2D, texture[filter]);  // Select A Filtered Texture

 // This Section Of Code Is New ( NEW )
 switch(object)      // Check object To Find Out What To Draw
 {
 case 0:       // Drawing Object 1
  glDrawCube();     // Draw Our Cube
  break;      // Done

   
The second object we create is going to be a Cylinder. The first parameter (1.0f) is the radius of the cylinder at base (bottom). The second parameter (1.0f) is the radius of the cylinder at the top. The third parameter ( 3.0f) is the height of the cylinder (how long it is). The fouth parameter (32) is how many subdivisions there are "around" the Z axis, and finally, the fifth parameter (32) is the amount of subdivisions "along" the Z axis. The more subdivisions there are the more detailed the object is. By increase the amount of subdivisions you add more polygons to the object. So you end up sacrificing speed for quality. Most of the time it's easy to find a happy medium.   
   

 case 1:       // Drawing Object 2
  glTranslatef(0.0f,0.0f,-1.5f);   // Center The Cylinder
  gluCylinder(quadratic,1.0f,1.0f,3.0f,32,32); // Draw Our Cylinder
  break;      // Done

   
The third object we create will be a CD shaped disc. The first parameter (0.5f) is the inner radius of the disk. This value can be zero, meaning there will be no hole in the middle. The larger the inner radius is, the bigger the hole in the middle of the disc will be. The second parameter (1.5f) is the outer radius. This value should be larger than the inner radius. If you make this value a little bit larger than the inner radius you will end up with a thing ring. If you make this value alot larger than the inner radius you will end up with a thick ring. The third parameter (32) is the number of slices that make up the disc. Think of slices like the slices in a pizza. The more slices you have, the smoother the outer edge of the disc will be. Finally the fourth parameter (32) is the number of rings that make up the disc. The rings are are similar to the tracks on a record. Circles inside circles. These ring subdivide the disc from the inner radius to the outer radius, adding more detail. Again, the more subdivisions there are, the slow it will run.   
   

 case 2:       // Drawing Object 3
  gluDisk(quadratic,0.5f,1.5f,32,32);  // Draw A Disc (CD Shape)
  break;      // Done

   
Our fourth object is an object that I know many of you have been dying to figure out. The Sphere! This one is quite simple. The first parameter is the radius of the sphere. In case you're not familiar with radius/diameter, etc, the radius is the distance from the center of the object to the outside of the object. In this case our radius is 1.3f. Next we have our subdivision "around" the Z axis (32), and our subdivision "along" the Z axis (32). The more subdivisions you have the smoother the sphere will look. Spheres usually require quite a few subdivisions to make them look smooth.   
   

 case 3:       // Drawing Object 4
  gluSphere(quadratic,1.3f,32,32);  // Draw A Sphere
  break;      // Done

   
Our fifth object is created using the same command that we used to create a Cylinder. If you remember, when we were creating the Cylinder the first two parameters controlled the radius of the cylinder at the bottom and the top. To make a cone it makes sense that all we'd have to do is make the radius at one end Zero. This will create a point at one end. So in the code below, we make the radius at the top of the cylinder equal zero. This creates our point, which also creates our cone.   
   

 case 4:       // Drawing Object 5
  glTranslatef(0.0f,0.0f,-1.5f);   // Center The Cone
  gluCylinder(quadratic,1.0f,0.0f,3.0f,32,32); // A Cone With A Bottom Radius Of .5 And A Height Of 2
  break;      // Done

   
Our sixth object is created with gluPartialDisc. The object we create using this command will look exactly like the disc we created above, but with the command gluPartialDisk there are two new parameters. The fifth parameter (part1) is the start angle we want to start drawing the disc at. The sixth parameter is the sweep angle. The sweep angle is the distance we travel from the current angle. We'll increase the sweep angle, which causes the disc to be slowly drawn to the screen in a clockwise direction. Once our sweep hits 360 degrees we start to increase the start angle. the makes it appear as if the disc is being erased, then we start all over again!   
   

 case 5:       // Drawing Object 6
  part1+=p1;     // Increase Start Angle
  part2+=p2;     // Increase Sweep Angle

  if(part1>359)     // 360 Degrees
  {
   p1=0;     // Stop Increasing Start Angle
   part1=0;    // Set Start Angle To Zero
   p2=1;     // Start Increasing Sweep Angle
   part2=0;    // Start Sweep Angle At Zero
  }
  if(part2>359)     // 360 Degrees
  {
   p1=1;     // Start Increasing Start Angle
   p2=0;     // Stop Increasing Sweep Angle
  }
  gluPartialDisk(quadratic,0.5f,1.5f,32,32,part1,part2-part1); // A Disk Like The One Before
  break;      // Done
 };

 xrot+=xspeed;      // Increase Rotation On X Axis
 yrot+=yspeed;      // Increase Rotation On Y Axis
 return TRUE;      // Keep Going
}

   
In the KillGLWindow() section of code, we need to delete the quadratic to free up system resources. We do this with the command gluDeleteQuadratic.   
   

GLvoid KillGLWindow(GLvoid)     // Properly Kill The Window
{
 gluDeleteQuadric(quadratic);    // Delete Quadratic - Free Resources

   
Now for the final part, they key input. Just add this where we check the rest of key input.   
   

    if (keys[' '] && !sp)  // Is Spacebar Being Pressed?
    {
     sp=TRUE;  // If So, Set sp To TRUE
     object++;  // Cycle Through The Objects
     if(object>5)  // Is object Greater Than 5?
      object=0; // If So, Set To Zero
    }
    if (!keys[' '])   // Has The Spacebar Been Released?
    {
     sp=FALSE;  // If So, Set sp To FALSE
    }

   
Thats all! Now you can draw quadrics in OpenGL. Some really impressive things can be done with morphing and quadrics. The animated disc is an example of simple morphing.

Everyone if you have time go check out my website, TipTup.Com 2000.

GB Schmick (TipTup)

Jeff Molofee (NeHe)


--  作者:ririyeye
--  发布时间:1/8/2010 10:37:00 AM

--  
深入
W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
2,765.625ms