以文本方式查看主题

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


--  作者:一分之千
--  发布时间:10/22/2007 6:30:00 PM

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

第二十三二十四课源码下载


第二十三课


按此在新窗口浏览图片球面映射:

这一个将教会你如何把环境纹理包裹在你的3D模型上,让它看起来象反射了周围的场景一样。

  
   
   
球体环境映射是一个创建快速金属反射效果的方法,但它并不像真实世界里那么精确!我们从18课的代码开始来创建这个教程,教你如何创建这种效果。
在我们开始之间,看一下红宝书中的介绍。它定义球体环境映射为一幅位于无限远的图像,把它映射到球面上。

在Photoshop中创建一幅球体环境映射图。

首先,你需要一幅球体环境映射图,用来把它映射到球体上。在Photoshop中打开一幅图并选择所有的像素,创建它的一个复制。
接着,我们把图像变为2的幂次方大小,一般为128x128或256x256。
最后使用扭曲(distort)滤镜,并应用球体效果。然后把它保存为*.bmp文件。

我们并没有添加任何全局变量,只是把纹理组的大小变为6,以保存6幅纹理。

  
   

GLuint texture[6];        // 保存6幅纹理

   
下面我们要做的就是载入这些纹理  
   

int LoadGLTextures()        
{
 int Status=FALSE;       

 AUX_RGBImageRec *TextureImage[2];      // 创建纹理的保存空间

 memset(TextureImage,0,sizeof(void *)*2);                // 清空为0

 // 载入*.bmp图像
 if ((TextureImage[0]=LoadBMP("Data/BG.bmp")) &&    // 背景图
  (TextureImage[1]=LoadBMP("Data/Reflect.bmp")))   // 反射图(球形纹理图)
 {
  Status=TRUE;       

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

  for (int loop=0; loop<=1; loop++)
  {
   // 创建临近点过滤纹理图
   glBindTexture(GL_TEXTURE_2D, texture[loop]);   // 创建纹理0和1
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
   glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
    0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);

   // 创建线形过滤纹理图
   glBindTexture(GL_TEXTURE_2D, texture[loop+2]);  // 创建纹理2,3
   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);

   // 创建线形Mipmap纹理图
   glBindTexture(GL_TEXTURE_2D, texture[loop+4]);  // 创建纹理4,5
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
   gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
    GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
  }
  for (loop=0; loop<=1; loop++)
  {
         if (TextureImage[loop])      // 如果图像存在则清除
      {
           if (TextureImage[loop]->data)   
        {
             free(TextureImage[loop]->data); 
     }
     free(TextureImage[loop]);  
   }
  }
 }

 return Status;    
}

   
我们对立方体的绘制代码做了一些小的改动,把法线的范围从[-1,1]缩放到[-0.5,0.5]。如果法向量太大的话,会产生一些块状效果,影响视觉效果。  
   

GLvoid glDrawCube()
{
  glBegin(GL_QUADS);
  // 前面
  glNormal3f( 0.0f, 0.0f, 0.5f);
  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,-0.5f);
  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, 0.5f, 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,-0.5f, 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( 0.5f, 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(-0.5f, 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();
}

   
在初始化OpenGL中,我们添加一些新的函数来使用球体纹理映射。
下面的代码让OpenGL自动为我们计算使用球体映射时,顶点的纹理坐标。  
   

 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);   // 设置s方向的纹理自动生成
 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);   // 设置t方向的纹理自动生成
   
我们几乎完成了所有的工作!接下来要做的就是就是绘制渲染,我删除了一些二次几何体,因为它们的视觉效果并不好。当然我们需要OpenGL为这些几何体自动生成坐标,接着选择球体映射纹理并绘制几何体。最后把OpenGL状态设置正常模式。  
   

int DrawGLScene(GLvoid)
{
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   
 glLoadIdentity();       // 重置视口

 glTranslatef(0.0f,0.0f,z);

 glEnable(GL_TEXTURE_GEN_S);      // 自动生成s方向纹理坐标
 glEnable(GL_TEXTURE_GEN_T);      // 自动生成t方向纹理坐标

 glBindTexture(GL_TEXTURE_2D, texture[filter+(filter+1)]);  // 绑定纹理
 glPushMatrix();
 glRotatef(xrot,1.0f,0.0f,0.0f);
 glRotatef(yrot,0.0f,1.0f,0.0f);
 switch(object)
 {
 case 0:
  glDrawCube();
  break;
 case 1:
  glTranslatef(0.0f,0.0f,-1.5f);     // 创建圆柱
  gluCylinder(quadratic,1.0f,1.0f,3.0f,32,32);   
  break;
 case 2:
  gluSphere(quadratic,1.3f,32,32);     // 创建球
  break;
 case 3:
  glTranslatef(0.0f,0.0f,-1.5f);     // 创建圆锥
  gluCylinder(quadratic,1.0f,0.0f,3.0f,32,32);   
  break;
 };

 glPopMatrix();
 glDisable(GL_TEXTURE_GEN_S);      // 禁止自动生成纹理坐标
 glDisable(GL_TEXTURE_GEN_T);     

 xrot+=xspeed;
 yrot+=yspeed;
 return TRUE;        // 成功返回
}

   
最后我们使用空格来切换各个不同的几何体  
   

    if (keys[' '] && !sp)
    {
     sp=TRUE;
     object++;
     if(object>3)
      object=0;
    }

   
我们成功了!现在你可以使用环境映射纹理做一些非常棒的特效了。我想做一个立方体环境映射的例子,但我现在的显卡不支持这种特效,所以只有等到以后了。

谢谢,并祝你好运!


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

--  
Lesson 23
   
Sphere Environment Mapping is a quick way to add a reflection to a metallic or reflective object in your scene. Although it is not as accurate as real life or as a Cube Environment Map, it is a whole lot faster! We'll be using the code from lesson eighteen (Quadrics) for the base of this tutorial. Also we're not using any of the same texture maps, we're going to use one sphere map, and one background image.

Before we start... The "red book" defines a Sphere map as a picture of the scene on a metal ball from infinite distance away and infinite focal point. Well that is impossible to do in real life. The best way I have found to create a good sphere map image without using a Fish eye lens is to use Adobe's Photoshop program.

Creating a Sphere Map In Photoshop:

First you will need a picture of the environment you want to map onto the sphere. Open the picture in Adobe Photoshop and select the entire image. Copy the image and create a new PSD (Photoshop Format) the new image should be the same size as the image we just copied. Paste a copy of the image into the new window we've created. The reason we make a copy is so Photoshop can apply its filters. Instead of copying the image you can select mode from the drop down menu and choose RGB mode. All of the filters should then be available.

Next we need to resize the image so that the image dimensions are a power of 2. Remember that in order to use an image as a texture the image needs to be 128x128, 256x256, etc. Under the image menu, select image size, uncheck the constraint proportions checkbox, and resize the image to a valid texture size. If your image is 100X90, it's better to make the image 128x128 than 64x64. Making the image smaller will lose alot of detail.

The last thing we do is select the filter menu, select distort and apply a spherize modifier. You should see that the center of the picture is blown up like a balloon, now in normal sphere maps the outer area will be blackened out, but it doesn't really matter. Save a copy of the image as a .BMP and you're ready to code!

We don't add any new global variables this time but we do modify the texture array to hold 6 textures.   
   

GLuint texture[6];        // Storage For 6 Textures

   
The next thing I did was modify the LoadGLTextures() function so we can load in 2 bitmaps and create 3 filters. (Like we did in the original texturing tutorials). Basically we loop through twice and create 3 textures each time using a different filtering mode. Almost all of this code is new or modified.   
   

int LoadGLTextures()        // Load Bitmaps And Convert To Textures
{
 int Status=FALSE;       // Status Indicator

 AUX_RGBImageRec *TextureImage[2];     // Create Storage Space For The Texture

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

 // Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit
 if ((TextureImage[0]=LoadBMP("Data/BG.bmp")) &&    // Background Texture
  (TextureImage[1]=LoadBMP("Data/Reflect.bmp")))   // Reflection Texture (Spheremap)
 {
  Status=TRUE;       // Set The Status To TRUE

  glGenTextures(6, &texture[0]);     // Create Three Textures

  for (int loop=0; loop<=1; loop++)
  {
   // Create Nearest Filtered Texture
   glBindTexture(GL_TEXTURE_2D, texture[loop]);  // Gen Tex 0 And 1
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
   glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
    0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);

   // Create Linear Filtered Texture
   glBindTexture(GL_TEXTURE_2D, texture[loop+2]);  // Gen Tex 2, 3 And 4
   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);

   // Create MipMapped Texture
   glBindTexture(GL_TEXTURE_2D, texture[loop+4]);  // Gen Tex 4 and 5
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
   gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
    GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
  }
  for (loop=0; loop<=1; 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
}

   
We'll modify the cube drawing code a little. Instead of using 1.0 and -1.0 for the normal values, we'll use 0.5 and -0.5. By changing the value of the normal, you can zoom the reflection map in and out. If the normal value is high, the image being reflected will be bigger, and may appear blocky. By reducing the normal value to 0.5 and -0.5 the reflected image is zoomed out a bit so that the image reflecting off the cube isn't all blocky looking. Setting the normal value too low will create undesirable results.   
   

GLvoid glDrawCube()
{
  glBegin(GL_QUADS);
  // Front Face
  glNormal3f( 0.0f, 0.0f, 0.5f);
  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
  glNormal3f( 0.0f, 0.0f,-0.5f);
  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
  glNormal3f( 0.0f, 0.5f, 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);
  // Bottom Face
  glNormal3f( 0.0f,-0.5f, 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);
  // Right Face
  glNormal3f( 0.5f, 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);
  // Left Face
  glNormal3f(-0.5f, 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();
}

   
Now in InitGL we add two new function calls, these two calls set the texture generation mode for S and T to Sphere Mapping. The texture coordinates S, T, R & Q relate in a way to object coordinates x, y, z and w. If you are using a one-dimensional texture (1D) you will use the S coordinate. If your texture is two dimensional, you will use the S & T coordinates.

So what the following code does is tells OpenGL how to automatically generate the S and T coordinates for us based on the sphere-mapping formula. The R and Q coordinates are usually ignored. The Q coordinate can be used for advanced texture mapping extensions, and the R coordinate may become useful once 3D texture mapping has been added to OpenGL, but for now we will ignore the R & Q Coords. The S coordinate runs horizontally across the face of our polygon, the T coordinate runs vertically across the face of our polygon.   
   

 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);   // Set The Texture Generation Mode For S To Sphere Mapping ( NEW )
 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);   // Set The Texture Generation Mode For T To Sphere Mapping ( NEW )

   
We're almost done! All we have to do is set up the rendering, I took out a few of the quadratic objects because they didn't work well with environment mapping. The first thing we need to do is enable texture generation. Then we select the reflective texture (sphere map) and draw our object. After all of the objects you want sphere-mapped have been drawn, you will want to disable texture generation, otherwise everything will be sphere mapped. We disable sphere-mapping before we draw the background scene (we don't want the background sphere mapped). You will notice that the bind texture commands may look fairly complex. All we're doing is selecting the filter to use when drawing our sphere map or the background image.   
   

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);

 glEnable(GL_TEXTURE_GEN_S);      // Enable Texture Coord Generation For S ( NEW )
 glEnable(GL_TEXTURE_GEN_T);      // Enable Texture Coord Generation For T ( NEW )

 glBindTexture(GL_TEXTURE_2D, texture[filter+(filter+1)]);  // This Will Select A Sphere Map
 glPushMatrix();
 glRotatef(xrot,1.0f,0.0f,0.0f);
 glRotatef(yrot,0.0f,1.0f,0.0f);
 switch(object)
 {
 case 0:
  glDrawCube();
  break;
 case 1:
  glTranslatef(0.0f,0.0f,-1.5f);     // Center The Cylinder
  gluCylinder(quadratic,1.0f,1.0f,3.0f,32,32);   // A Cylinder With A Radius Of 0.5 And A Height Of 2
  break;
 case 2:
  gluSphere(quadratic,1.3f,32,32);    // Sphere With A Radius Of 1 And 16 Longitude/Latitude Segments
  break;
 case 3:
  glTranslatef(0.0f,0.0f,-1.5f);     // Center The Cone
  gluCylinder(quadratic,1.0f,0.0f,3.0f,32,32);   // Cone With A Bottom Radius Of .5 And Height Of 2
  break;
 };

 glPopMatrix();
 glDisable(GL_TEXTURE_GEN_S);      // Disable Texture Coord Generation ( NEW )
 glDisable(GL_TEXTURE_GEN_T);      // Disable Texture Coord Generation ( NEW )

 glBindTexture(GL_TEXTURE_2D, texture[filter*2]);   // This Will Select The BG Texture ( NEW )
 glPushMatrix();
  glTranslatef(0.0f, 0.0f, -24.0f);
  glBegin(GL_QUADS);
   glNormal3f( 0.0f, 0.0f, 1.0f);
   glTexCoord2f(0.0f, 0.0f); glVertex3f(-13.3f, -10.0f,  10.0f);
   glTexCoord2f(1.0f, 0.0f); glVertex3f( 13.3f, -10.0f,  10.0f);
   glTexCoord2f(1.0f, 1.0f); glVertex3f( 13.3f,  10.0f,  10.0f);
   glTexCoord2f(0.0f, 1.0f); glVertex3f(-13.3f,  10.0f,  10.0f);
  glEnd();

 glPopMatrix();

 xrot+=xspeed;
 yrot+=yspeed;
 return TRUE;        // Keep Going
}

   
The last thing we have to do is update the spacebar section of code to reflect (No Pun Intended) the changes we made to the Quadratic objects being rendered. (We removed the discs)   
   

    if (keys[' '] && !sp)
    {
     sp=TRUE;
     object++;
     if(object>3)
      object=0;
    }

   
We're done! Now you can do some really impressive things with Environment mapping like making an almost accurate reflection of a room! I was planning on showing how to do Cube Environment Mapping in this tutorial too but my current video card does not support cube mapping. Maybe in a month or so after I buy a GeForce 2 :) Also I taught myself environment mapping (mostly because I couldnt find too much information on it) so if anything in this tutorial is inaccurate, Email Me or let NeHe know.

Thanks, and Good Luck!


--  作者:一分之千
--  发布时间:10/22/2007 6:45:00 PM

--  
第二十四课


按此在新窗口浏览图片扩展,剪裁和TGA图像文件的加载:

在这一课里,你将学会如何读取你显卡支持的OpenGL的扩展,并在你指定的剪裁区域把它显示出来。

  
   
   
这个教程有一些难度,但它会让你学到很多东西。我听到很多朋友问我扩展方面的内容和怎样找到它们。这个教程将交给你这
一切。
我将教会你怎样滚动屏幕的一部分和怎样绘制直线,最重要的是从这一课起,我们将不使用AUX库,以及*.bmp文件。我将告诉你如何使用Targa(TGA)图像文件。因为它简单并且支持alpha通道,它可以使你更容易的创建酷的效果。
接下来我们要做的第一件事就是不包含glaux.h头文件和glaux.lib库。另外,在使用glaux库时,经常会发生一些可疑的警告,现在我们可以测定告别它了。
  
   

#include <stdarg.h>        // 处理可变参数的函数的头文件
#include <string.h>        // 处理字符串的头文件

   
接下来我们添加一些变量,第一个为滚动参数。第二给变量记录扩展的个数,swidth和sheight记录剪切矩形的大小。base为字体显示列表的开始值。  
   

int  scroll;         // 用来滚动屏幕
int  maxtokens;        // 保存扩展的个数
int  swidth;         // 剪裁宽度
int  sheight;         // 剪裁高度

GLuint  base;         // 字符显示列表的开始值

   
现在我们创建一个数据结构用来保存TGA文件,接着我们使用这个结构来加载纹理。  
   

typedef struct          // 创建加载TGA图像文件结构
{
 GLubyte *imageData;        // 图像数据指针
 GLuint bpp;         // 每个数据所占的位数(必须为24或32)
 GLuint width;         // 图像宽度
 GLuint height;         // 图像高度
 GLuint texID;         // 纹理的ID值
} TextureImage;          // 结构名称

TextureImage textures[1];        // 保存一个纹理

   
这个部分的代码将要加载一个TGA文件并把它转换为纹理。必须注意的是这部分代码只能加载24/32位的不压缩的TGA文件。
这个函数包含两个参数,一个保存载入的图像,一个为将载入的文件名。
TGA文件包含一个12个字节的文件头,载入图像后,我们用type来设置图像中像素格式在OpenGL中的对应。如果是24位的图像我们使用GL_RGB,如果是32位的图像我们使用GL_RGBA。  
   

bool LoadTGA(TextureImage *texture, char *filename)     // 把TGA文件加载入内存
{
 GLubyte  TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0};   // 无压缩的TGA文件头
 GLubyte  TGAcompare[12];      // 保存读入的文件头信息
 GLubyte  header[6];      // 保存最有用的图像信息,宽,高,位深
 GLuint  bytesPerPixel;      // 记录每个颜色所占用的字节数
 GLuint  imageSize;      // 记录文件大小
 GLuint  temp;       // 临时变量
 GLuint  type=GL_RGBA;      // 设置默认的格式为GL_RGBA,即32位图像

   
下面这个函数读取TGA文件,并记录文件信息。TGA文件格式如下所示:

Tga图像格式
无颜色表 rgb 图像

偏移 长度 描述 32位常用图像文件各个字节的值
0 1 指出图像信息字段的长度,其取值范围是 0 到 255 ,当它为 0 时表示没有图像的信息字段。 0
1 1 是否使用颜色表,0 表示没有颜色表,1 表示颜色表存在 0
2 1 该字段总为 2。图像类型码,tga一共有6种格式,2表示无颜色表 rgb 图像 2
3 5 颜色表规格,总为0。 0
4 0
5 0
6 0
7 0
8       10           图像规格说明 开始
8 2 图像 x 坐标起始位置,一般为0 0
9
10 2 图像 y 坐标起始位置,一般为0 0
11
12 2 图像宽度,以像素为单位 256
13
14 2 图像高度,以像素为单位 256
15
16 1 图像每像素存储占用位(bit)数 32
17 1 图像描述符字节
bits 3-0 - 每像素对应的属性位的位数,对于 TGA 24,该值为 0
bit 4 - 保留,必须为 0
bit 5 - 屏幕起始位置标志,0 = 原点在左下角,1 = 原点在左上角
一般这个字节设为0x00即可

00100000(2)
18  可变 图像数据域
这里存储了(宽度)x(高度)个像素,每个像素中的 rgb 色值该色值包含整数个字节  ...

如果一切顺利,读取文件后关闭文件。
  
   

 FILE *file = fopen(filename, "rb");      // 打开一个TGA文件

 if( file==NULL ||       // 文件存在么?
  fread(TGAcompare,1,sizeof(TGAcompare),file)!=sizeof(TGAcompare) || // 是否包含12个字节的文件头?
  memcmp(TGAheader,TGAcompare,sizeof(TGAheader))!=0  || // 是否是我们需要的格式?
  fread(header,1,sizeof(header),file)!=sizeof(header))   // 如果是读取下面六个图像信息
 {
  if (file == NULL)       // 文件不存在返回错误
   return false;       
  else
  {
   fclose(file);      // 关闭文件返回错误
   return false;       
  }
 }

   
下面的代码记录文件的宽度和高度,并判断文件是否为24位/32位TGA文件。  
   

 texture->width  = header[1] * 256 + header[0];     // 记录文件高度
 texture->height = header[3] * 256 + header[2];     // 记录文件宽度

 if( texture->width <=0 ||      // 宽度是否小于0
  texture->height <=0 ||      // 高度是否小于0
  (header[4]!=24 && header[4]!=32))      // TGA文件是24/32位?
 {
  fclose(file);        // 如果失败关闭文件,返回错误
  return false;        
 }

   
下面的代码记录文件的位深和加载它需要的内存大小  
   

 texture->bpp = header[4];       // 记录文件的位深
 bytesPerPixel = texture->bpp/8;       // 记录每个象素所占的字节数
 imageSize = texture->width*texture->height*bytesPerPixel;    // 计算TGA文件加载所需要的内存大小

   
下面的代码为图像数据分配内存并载入它  
   

 texture->imageData=(GLubyte *)malloc(imageSize);    // 分配内存去保存TGA数据

 if( texture->imageData==NULL ||      // 系统是否分配了足够的内存?
  fread(texture->imageData, 1, imageSize, file)!=imageSize)  // 是否成功读入内存?
 {
  if(texture->imageData!=NULL)     // 是否有数据被加载
   free(texture->imageData);     // 如果是,则释放载入的数据

  fclose(file);       // 关闭文件
  return false;       // 返回错误
 }

   
TGA文件中,颜色的存储顺序为BGR,而OpenGL中颜色的顺序为RGB,所以我们需要交换每个象素中R和B的值。如果一切顺利,TGA文件中的图像数据将按照OpenGL的要求存储在内存中了。  
   

 for(GLuint i=0; i<int(imageSize); i+=bytesPerPixel)    // 循环所有的像素
 {         // 交换R和B的值
  temp=texture->imageData[i];      
  texture->imageData[i] = texture->imageData[i + 2];  
  texture->imageData[i + 2] = temp;    
 }

 fclose (file);        // 关闭文件

   
下面的代码创建一个纹理,并设置过滤方式为线性  
   

 // 创建纹理
 glGenTextures(1, &texture[0].texID);      // 创建纹理,并记录纹理ID

 glBindTexture(GL_TEXTURE_2D, texture[0].texID);    // 绑定纹理
 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  // 设置过滤器为线性过滤
 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
   
判断图像的位数是否为24,如果是则设置类型为GL_RGB  
   

 if (texture[0].bpp==24)        // 是否为24位图像?
 {
  type=GL_RGB;        // 如果是设置类型为GL_RGB
 }

   
下面的代码在OpenGL中创建一个纹理  
   

 glTexImage2D(GL_TEXTURE_2D, 0, type, texture[0].width, texture[0].height, 0, type, GL_UNSIGNED_BYTE, texture[0].imageData);

 return true;         // 纹理绑定完成,成功返回
}

   
下面的代码是从图像创建字体的典型的方法,这些代码将包含在后面的课程中,以显示文字。
只有一个不同的地方,纹理0用来保存字符图像。  
   

GLvoid BuildFont(GLvoid)        // 创建字体显示列表
{
 base=glGenLists(256);       // 创建256个显示列表
 glBindTexture(GL_TEXTURE_2D, textures[0].texID);    // 绑定纹理
 for (int loop1=0; loop1<256; loop1++)     // 循环创建256个显示列表
 {
  float cx=float(loop1%16)/16.0f;     // 当前字符的X位置
  float cy=float(loop1/16)/16.0f;     // 当前字符的Y位置

  glNewList(base+loop1,GL_COMPILE);     // 开始创建显示列表
   glBegin(GL_QUADS);      // 创建一个四边形用来包含字符图像
    glTexCoord2f(cx,1.0f-cy-0.0625f);   // 左下方纹理坐标
    glVertex2d(0,16);     // 左下方坐标
    glTexCoord2f(cx+0.0625f,1.0f-cy-0.0625f);  // 右下方纹理坐标
    glVertex2i(16,16);     // 右下方坐标
    glTexCoord2f(cx+0.0625f,1.0f-cy-0.001f);  // 右上方纹理坐标
    glVertex2i(16,0);     // 右上方坐标
    glTexCoord2f(cx,1.0f-cy-0.001f);   // 左上方纹理坐标
    glVertex2i(0,0);     // 左上方坐标
   glEnd();       // 四边形创建完毕
   glTranslated(14,0,0);     // 向右移动14个单位
  glEndList();       // 结束创建显示列表
 }         
}

   
下面的函数用来删除显示字符的显示列表  
   

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

   
glPrint函数只有一点变化,我们在Y轴方向把字符拉长一倍  
   

GLvoid glPrint(GLint x, GLint y, int set, const char *fmt, ...)
{
 char text[1024];       // 保存我们的字符
 va_list ap;        // 指向第一个参数

 if (fmt == NULL)        // 如果要显示的字符为空则返回
  return;         

 va_start(ap, fmt);        // 开始分析参数,并把结果写入到text中
     vsprintf(text, fmt, ap);       
 va_end(ap);         

 if (set>1)        // 如果字符集大于1则使用第二个字符集
 {
  set=1;         
 }

 glEnable(GL_TEXTURE_2D);       // 使用纹理映射
 glLoadIdentity();        // 重置视口矩阵
 glTranslated(x,y,0);       // 平移到(x,y,0)处
 glListBase(base-32+(128*set));      // 选择字符集

 glScalef(1.0f,2.0f,1.0f);       // 沿Y轴放大一倍

 glCallLists(strlen(text),GL_UNSIGNED_BYTE, text);    // 把字符写入到屏幕
 glDisable(GL_TEXTURE_2D);       // 禁止纹理映射
}

   
窗口改变大小的函数使用正投影,把视口范围设置为(0,0)-(640,480)  
   

GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
{
 swidth=width;         // 设置剪切矩形为窗口大小
 sheight=height;         
 if (height==0)         // 防止高度为0时,被0除
 {
  height=1;        
 }
 glViewport(0,0,width,height);       // 设置窗口可见区
 glMatrixMode(GL_PROJECTION);       
 glLoadIdentity();        
 glOrtho(0.0f,640,480,0.0f,-1.0f,1.0f);      // 设置视口大小为640x480
 glMatrixMode(GL_MODELVIEW);       
 glLoadIdentity();      
}

   
初始化操作非常简单,我们载入字体纹理,并创建字符显示列表,如果顺利,则成功返回。  
   

int InitGL(GLvoid)    
{
 if (!LoadTGA(&textures[0],"Data/Font.TGA"))      // 载入字体纹理
 {
  return false;        // 载入失败则返回
 }

 BuildFont();         // 创建字体

 glShadeModel(GL_SMOOTH);        // 使用平滑着色
 glClearColor(0.0f, 0.0f, 0.0f, 0.5f);      // 设置黑色背景
 glClearDepth(1.0f);        // 设置深度缓存中的值为1
 glBindTexture(GL_TEXTURE_2D, textures[0].texID);     // 绑定字体纹理

 return TRUE;         // 成功返回
}

   
绘制代码几乎是全新的:),token为一个指向字符串的指针,它将保存OpenGL扩展的全部字符串,cnt纪录扩展的个数。
接下来清楚背景,并显示OpenGL的销售商,实现它的公司和当前的版本。  
   

int DrawGLScene(GLvoid)   
{
 char *token;         // 保存扩展字符串
 int cnt=0;         // 纪录扩展字符串的个数

 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);     // 清楚背景和深度缓存

 glColor3f(1.0f,0.5f,0.5f);        // 设置为红色
 glPrint(50,16,1,"Renderer");       
 glPrint(80,48,1,"Vendor");      
 glPrint(66,80,1,"Version");      
   
下面的代码显示OpenGL实现方面的相关信息,完成之后我们用蓝色在屏幕的下方写上“NeHe Productions”,当然你可以使用任何你想使用的字符,比如"DancingWind Translate"。  
   

 glColor3f(1.0f,0.7f,0.4f);       // 设置为橘黄色
 glPrint(200,16,1,(char *)glGetString(GL_RENDERER));    // 显示OpenGL的实现组织
 glPrint(200,48,1,(char *)glGetString(GL_VENDOR));    // 显示销售商
 glPrint(200,80,1,(char *)glGetString(GL_VERSION));    // 显示当前版本

 glColor3f(0.5f,0.5f,1.0f);       // 设置为蓝色
 glPrint(192,432,1,"NeHe Productions");     // 在屏幕的底端写上NeHe Productions字符串
   
现在我们绘制显示扩展名的白色线框方块,并用一个更大的白色线框方块把所有的内容包围起来。  
   

 glLoadIdentity();        // 重置模型变换矩阵
 glColor3f(1.0f,1.0f,1.0f);       // 设置为白色
 glBegin(GL_LINE_STRIP);     
  glVertex2d(639,417);       
  glVertex2d(  0,417);       
  glVertex2d(  0,480);       
  glVertex2d(639,480);      
  glVertex2d(639,128);     
 glEnd();        
 glBegin(GL_LINE_STRIP);     
  glVertex2d(  0,128);      
  glVertex2d(639,128);       
  glVertex2d(639,  1);      
  glVertex2d(  0,  1);       
  glVertex2d(  0,417);      
 glEnd();         
   
glScissor函数用来设置剪裁区域,如果启用了GL_SCISSOR_TEST,绘制的内容只能在剪裁区域中显示。
下面的代码设置窗口的中部为剪裁区域,并获得扩展名字符串。  
   

 glScissor(1 ,int(0.135416f*sheight),swidth-2,int(0.597916f*sheight)); // 定义剪裁区域
 glEnable(GL_SCISSOR_TEST);       // 使用剪裁测试

 char* text=(char*)malloc(strlen((char *)glGetString(GL_EXTENSIONS))+1);  // 为保存OpenGL扩展的字符串分配内存空间
 strcpy (text,(char *)glGetString(GL_EXTENSIONS));    // 返回OpenGL扩展字符串

   
下面我们创建一个循环,循环显示每个扩展名,并纪录扩展名的个数  
   

 token=strtok(text," ");        // 按空格分割text字符串,并把分割后的字符串保存在token中
 while(token!=NULL)         // 如果token不为NULL
 {
  cnt++;         // 增加计数器
  if (cnt>maxtokens)        // 纪录最大的扩展名数量
  {
   maxtokens=cnt;       
  }

   
现我们已经获得第一个扩展名,下一步我们把它显示在屏幕上。
我们已经显示了三行文本,它们在Y轴上占用了3*32=96个像素的宽度,所以我们显示的第一个行文本的位置是(0,96),一次类推第i行文本的位置是(0,96+(cnt*32)),但我们需要考虑当前滚动过的位置,默认为向上滚动,所以我们得到显示第i行文本的位置为(0,96+(cnt*32)=scroll)。

当然它们不会都显示出来,记得我们使用了剪裁,只显示(0,96)-(0,96+32*9)之间的文本,其它的都被剪裁了。

更具我们上面的讲解,显示的第一个行如下:
1 GL_ARB_multitexture
  
   

  glColor3f(0.5f,1.0f,0.5f);      // 设置颜色为绿色
  glPrint(0,96+(cnt*32)-scroll,0,"%i",cnt);    // 绘制第几个扩展名

  glColor3f(1.0f,1.0f,0.5f);      // 设置颜色为黄色
  glPrint(50,96+(cnt*32)-scroll,0,token);    // 输出第i个扩展名

   
当我们显示完所有的扩展名,我们需要检查一下是否已经分析完了所有的字符串。我们使用strtok(NULL," ")函数代替strtok(text," ")函数,把第一个参数设置为NULL会检查当前指针位置到字符串末尾是否包含" "字符,如果包含返回其位置,否则返回NULL。
我们举例说明上面的过程,例如字符串"GL_ARB_multitexture GL_EXT_abgr GL_EXT_bgra",它是以空格分割字符串的,第一次调用strtok("text"," ")返回text的首位置,并在空格" "的位置加入一个NULL。以后每次调用,删除NULL,返回空格位置的下一个位置,接着搜索下一个空格的位置,并在空格的位置加入一个NULL。直道返回NULL。

返回NULL时循环停止,表示已经显示完所有的扩展名。
  
   

  token=strtok(NULL," ");       // 查找下一个扩展名
 }

   
下面的代码让OpenGL返回到默认的渲染状态,并释放分配的内存资源  
   

 glDisable(GL_SCISSOR_TEST);        // 禁用剪裁测试

 free (text);         // 释放分配的内存

   
下面的代码让OpenGL完成所有的任务,并返回TRUE  
   

 glFlush();         // 执行所有的渲染命令
 return TRUE;         // 成功返回
}

   
KillGLWindow函数基本没有变化,唯一改变的是需要删除我们创建的字体   
   

 KillFont();         // 删除字体

   
CreateGLWindow(), 和 WndProc() 函数保持不变

在WinMain()函数中我们需要加入新的按键控制   
   
   
下面的代码检查向上的箭头是否被按下,如果scroll大于0,我们把它减少2
  
   

    if (keys[VK_UP] && (scroll>0))    // 向上的箭头是否被按下?
    {
     scroll-=2;     // 如果是,减少scroll的值
    }

   
如果向下的箭头被按住,并且scroll小于32*(maxtoken-9),则增加scroll的值,32是每一个字符的高度,9是可以显示的行数。  
   

    if (keys[VK_DOWN] && (scroll<32*(maxtokens-9)))  // 向下的箭头是否被按住
    {
     scroll+=2;     // 如果是,增加scroll的值
    }

   
我希望你觉得这个教程有趣,学完了这个教程你应该知道如何获得你的显卡的发售商的名称,实现OpenGL的组织和你的显卡所使用的OpenGL的版本。进一步,你应该知道你的显卡支持的扩展的名称,并熟练的使用剪切矩形和加载TGA图像。
如果你发现任何问题,请让我知道。我想做最好的教程,你的反馈对我很重要。


--  作者:一分之千
--  发布时间:10/22/2007 6:51:00 PM

--  
Lesson 24
   
This tutorial is far from visually stunning, but you will definitely learn a few new things by reading through it. I have had quite a few people ask me about extensions, and how to find out what extensions are supported on a particular brand of video card. This tutorial will teach you how to find out what OpenGL extensions are supported on any type of 3D video card.

I will also teach you how to scroll a portion of the screen without affecting any of the graphics around it using scissor testing. You will also learn how to draw line strips, and most importantly, in this tutorial we will drop the AUX library completely, along with Bitmap images. I will show you how to use Targa (TGA) images as textures. Not only are Targa files easy to work with and create, they support the ALPHA channel, which will allow you to create some pretty cool effects in future projects!

The first thing you should notice in the code below is that we no longer include the glaux header file (glaux.h). It is also important to note that the glaux.lib file can also be left out! We're not working with bitmaps anymore, so there's no need to include either of these files in our project.

Also, using glaux, I always received one warning message. Without glaux there should be zero errors, zero warnings.   
   

#include <windows.h>        // Header File For Windows
#include <stdio.h>        // Header File For Standard Input / Output
#include <stdarg.h>        // Header File For Variable Argument Routines
#include <string.h>        // Header File For String Management
#include <gl\gl.h>        // Header File For The OpenGL32 Library
#include <gl\glu.h>        // Header File For The GLu32 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

   
The first thing we need to do is add some variables. The first variable scroll will be used to scroll a portion of the screen up and down. The second variable maxtokens will be used to keep track of how many tokens (extensions) are supported by the video card.

base is used to hold the font display list.

swidth and sheight are used to grab the current window size. We use these two variable to help us calculate the scissor coordinates later in the code.   
   

int  scroll;         // Used For Scrolling The Screen
int  maxtokens;        // Keeps Track Of The Number Of Extensions Supported
int  swidth;         // Scissor Width
int  sheight;        // Scissor Height

GLuint  base;         // Base Display List For The Font

   
Now we create a structure to hold the TGA information once we load it in. The first variable imageData will hold a pointer to the data that makes up the image. bpp will hold the bits per pixel used in the TGA file (this value should be 24 or 32 bits depending on whether or not there is an alpha channel). The third variable width will hold the width of the TGA image. height will hold the height of the image, and texID will be used to keep track of the textures once they are built. The structure will be called TextureImage.

The line just after the structure (TextureImage textures[1]) sets aside storage for the one texture that we will be using in this program.   
   

typedef struct          // Create A Structure
{
 GLubyte *imageData;        // Image Data (Up To 32 Bits)
 GLuint bpp;         // Image Color Depth In Bits Per Pixel
 GLuint width;         // Image Width
 GLuint height;         // Image Height
 GLuint texID;         // Texture ID Used To Select A Texture
} TextureImage;          // Structure Name

TextureImage textures[1];        // Storage For One Texture

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

   
Now for the fun stuff! This section of code will load in a TGA file and convert it into a texture for use in the program. One thing to note is that this code will only load 24 or 32 bit uncompressed TGA files. I had a hard enough time making the code work with both 24 and 32 bit TGA's :) I never said I was a genious. I'd like to point out that I did not write all of this code on my own. Alot of the really good ideas I got from reading through random sites on the net. I just took all the good ideas and combined them into code that works well with OpenGL. Not easy, not extremely difficult!

We pass two parameters to this section of code. The first parameter points to memory that we can store the texture in (*texture). The second parameter is the name of the file that we want to load (*filename).

The first variable TGAheader[ ] holds 12 bytes. We'll compare these bytes with the first 12 bytes we read from the TGA file to make sure that the file is indeed a Targa file, and not some other type of image.

TGAcompare will be used to hold the first 12 bytes we read in from the TGA file. The bytes in TGAcompare will then be compared with the bytes in TGAheader to make sure everything matches.

header[ ] will hold the first 6 IMPORTANT bytes from the header file (width, height, and bits per pixel).

The variable bytesPerPixel will store the result after we divide bits per pixel by 8, leaving us with the number of bytes used per pixel.

imageSize will store the number of bytes required to make up the image (width * height * bytes per pixel).

temp is a temporary variable that we will use to swap bytes later in the program.

The last variable type is a variable that I use to select the proper texture building params depending on whether or not the TGA is 24 or 32 bit. If the texture is 24 bit we need to use GL_RGB mode when we build the texture. If the TGA is 32 bit we need to add the Alpha component, meaning we have to use GL_RGBA (By default I assume the image is 32 bit by default that is why type is GL_RGBA).   
   

bool LoadTGA(TextureImage *texture, char *filename)     // Loads A TGA File Into Memory
{
 GLubyte  TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0};   // Uncompressed TGA Header
 GLubyte  TGAcompare[12];       // Used To Compare TGA Header
 GLubyte  header[6];       // First 6 Useful Bytes From The Header
 GLuint  bytesPerPixel;       // Holds Number Of Bytes Per Pixel Used In The TGA File
 GLuint  imageSize;       // Used To Store The Image Size When Setting Aside Ram
 GLuint  temp;        // Temporary Variable
 GLuint  type=GL_RGBA;       // Set The Default GL Mode To RBGA (32 BPP)

   
The first line below opens the TGA file for reading. file is the handle we will use to point to the data within the file. the command fopen(filename, "rb") will open the file filename, and "rb" tells our program to open it for [r]eading in [b]inary mode!

The if statement has a few jobs. First off it checks to see if the file contains any data. If there is no data, NULL will be returned, the file will be closed with fclose(file), and we return false.

If the file contains information, we attempt to read the first 12 bytes of the file into TGAcompare. We break the line down like this: fread will read sizeof(TGAcompare) (12 bytes) from file into TGAcompare. Then we check to see if the number of bytes read is equal to sizeof(TGAcompare) which should be 12 bytes. If we were unable to read the 12 bytes into TGAcompare the file will close and false will be returned.

If everything has gone good so far, we then compare the 12 bytes we read into TGAcompare with the 12 bytes we have stored in TGAheader. If the bytes do not match, the file will close, and false will be returned.

Lastly, if everything has gone great, we attempt to read 6 more bytes into header (the important bytes). If 6 bytes are not available, again, the file will close and the program will return false.   
   

 FILE *file = fopen(filename, "rb");      // Open The TGA File

 if( file==NULL ||        // Does File Even Exist?
  fread(TGAcompare,1,sizeof(TGAcompare),file)!=sizeof(TGAcompare) || // Are There 12 Bytes To Read?
  memcmp(TGAheader,TGAcompare,sizeof(TGAheader))!=0  || // Does The Header Match What We Want?
  fread(header,1,sizeof(header),file)!=sizeof(header))   // If So Read Next 6 Header Bytes
 {
  if (file == NULL)       // Did The File Even Exist? *Added Jim Strong*
   return false;       // Return False
  else
  {
   fclose(file);       // If Anything Failed, Close The File
   return false;       // Return False
  }
 }

   
If everything went ok, we now have enough information to define some important variables. The first variable we want to define is width. We want width to equal the width of the TGA file. We can find out the TGA width by multiplying the value stored in header[1] by 256. We then add the lowbyte which is stored in header[0].

The height is calculated the same way but instead of using the values stored in header[0] and header[1] we use the values stored in header[2] and header[3].

After we have calculated the width and height we check to see if either the width or height is less than or equal to 0. If either of the two variables is less than or equal to zero, the file will be closed, and false will be returned.

We also check to see if the TGA is a 24 or 32 bit image. We do this by checking the value stored at header[4]. If the value is not 24 or 32 (bit), the file will be closed, and false will be returned.

In case you have not realized. A return of false will cause the program to fail with the message "Initialization Failed". Make sure your TGA is an uncompressed 24 or 32 bit image!   
   

 texture->width  = header[1] * 256 + header[0];     // Determine The TGA Width (highbyte*256+lowbyte)
 texture->height = header[3] * 256 + header[2];     // Determine The TGA Height (highbyte*256+lowbyte)

 if( texture->width <=0 ||      // Is The Width Less Than Or Equal To Zero
  texture->height <=0 ||      // Is The Height Less Than Or Equal To Zero
  (header[4]!=24 && header[4]!=32))     // Is The TGA 24 or 32 Bit?
 {
  fclose(file);        // If Anything Failed, Close The File
  return false;        // Return False
 }

   
Now that we have calculated the image width and height we need to calculate the bits per pixel, bytes per pixel and image size.

The value in header[4] is the bits per pixel. So we set bpp to equal header[4].

If you know anything about bits and bytes, you know that 8 bits makes a byte. To figure out how many bytes per pixel the TGA uses, all we have to do is divide bits per pixel by 8. If the image is 32 bit, bytesPerPixel will equal 4. If the image is 24 bit, bytesPerPixel will equal 3.

To calculate the image size, we multiply width * height * bytesPerPixel. The result is stored in imageSize. If the image was 100x100x32 bit our image size would be 100 * 100 * 32/8 which equals 10000 * 4 or 40000 bytes!   
   

 texture->bpp = header[4];       // Grab The TGA's Bits Per Pixel (24 or 32)
 bytesPerPixel = texture->bpp/8;      // Divide By 8 To Get The Bytes Per Pixel
 imageSize = texture->width*texture->height*bytesPerPixel;   // Calculate The Memory Required For The TGA Data

   
Now that we know how many bytes our image is going to take, we need to allocate some memory. The first line below does the trick. imageData will point to a section of ram big enough to hold our image. malloc(imagesize) allocates the memory (sets memory aside for us to use) based on the amount of ram we request (imageSize).

The "if" statement has a few tasks. First it checks to see if the memory was allocated properly. If not, imageData will equal NULL, the file will be closed, and false will be returned.

If the memory was allocated, we attempt to read the image data from the file into the allocated memory. The line fread(texture->imageData, 1, imageSize, file) does the trick. fread means file read. imageData points to the memory we want to store the data in. 1 is the size of data we want to read in bytes (we want to read 1 byte at a time). imageSize is the total number of bytes we want to read. Because imageSize is equal to the total amount of ram required to hold the image, we end up reading in the entire image. file is the handle for our open file.

After reading in the data, we check to see if the amount of data we read in is the same as the value stored in imageSize. If the amount of data read and the value of imageSize is not the same, something went wrong. If any data was loaded, we will free it. (release the memory we allocated). The file will be closed, and false will be returned.   
   

 texture->imageData=(GLubyte *)malloc(imageSize);    // Reserve Memory To Hold The TGA Data

 if( texture->imageData==NULL ||      // Does The Storage Memory Exist?
  fread(texture->imageData, 1, imageSize, file)!=imageSize)  // Does The Image Size Match The Memory Reserved?
 {
  if(texture->imageData!=NULL)      // Was Image Data Loaded
   free(texture->imageData);     // If So, Release The Image Data

  fclose(file);        // Close The File
  return false;        // Return False
 }

   
If the data was loaded properly, things are going good :) All we have to do now is swap the Red and Blue bytes. In OpenGL we use RGB (red, green, blue). The data in a TGA file is stored BGR (blue, green, red). If we didn't swap the red and blue bytes, anything in the picture that should be red would be blue and anything that should be blue would be red.

The first thing we do is create a loop (i) that goes from 0 to imageSize. By doing this, we can loop through all of the image data. Our loop will increase by steps of 3 (0, 3, 6, 9, etc) if the TGA file is 24 bit, and 4 (0, 4, 8, 12, etc) if the image is 32 bit. The reason we increase by steps is so that the value at i is always going to be the first byte ([b]lue byte) in our group of 3 or 4 bytes.

Inside the loop, we store the [b]lue byte in our temp variable. We then grab the red byte which is stored at texture->imageData[i+2] (Remember that TGAs store the colors as BGR[A]. B is i+0, G is i+1 and R is i+2) and store it where the [b]lue byte used to be.

Lastly we move the [b]lue byte that we stored in the temp variable to the location where the [r]ed byte used to be (i+2), and we close the file with fclose(file).

If everything went ok, the TGA should now be stored in memory as usable OpenGL texture data!   
   

 for(GLuint i=0; i<int(imageSize); i+=bytesPerPixel)    // Loop Through The Image Data
 {          // Swaps The 1st And 3rd Bytes ('R'ed and 'B'lue)
  temp=texture->imageData[i];      // Temporarily Store The Value At Image Data 'i'
  texture->imageData[i] = texture->imageData[i + 2];   // Set The 1st Byte To The Value Of The 3rd Byte
  texture->imageData[i + 2] = temp;     // Set The 3rd Byte To The Value In 'temp' (1st Byte Value)
 }

 fclose (file);         // Close The File

   
Now that we have usable data, it's time to make a texture from it. We start off by telling OpenGL we want to create a texture in the memory pointed to by &texture[0].texID.

It's important that you understand a few things before we go on. In the InitGL() code, when we call LoadTGA() we pass it two parameters. The first parameter is &textures[0]. In LoadTGA() we don't make reference to &textures[0]. We make reference to &texture[0] (no 's' at the end). When we modify &texture[0] we are actually modifying textures[0]. texture[0] assumes the identity of textures[0]. I hope that makes sense.

So if we wanted to create a second texture, we would pass the parameter &textures[1]. In LoadTGA() any time we modified texture[0] we would be modifying textures[1]. If we passed &textures[2], texture[0] would assume the identity of &textures[2], etc.

Hard to explain, easy to understand. Of course I wont be happy until I make it really clear :) Last example in english using an example. Say I had a box. I called it box #10. I gave it to my friend and asked him to fill it up. My friend could care less what number it is. To him it's just a box. So he fills what he calls "just a box". He gives it back to me. To me he just filled Box #10 for me. To him he just filled a box. If I give him another box called box #11 and say hey, can you fill this. He'll again think of it as just "box". He'll fill it and give it back to me full. To me he's just filled box #11 for me.

When I give LoadTGA &textures[1] it thinks of it as &texture[0]. It fills it with texture information, and once it's done I am left with a working textures[1]. If I give LoadTGA &textures[2] it again thinks of it as &texture[0]. It fills it with data, and I'm left with a working textures[2]. Make sense :)

Anyways... On to the code! We tell LoadTGA() to build our texture. We bind the texture, and tell OpenGL we want it to be linear filtered.   
   

 // Build A Texture From The Data
 glGenTextures(1, &texture[0].texID);      // Generate OpenGL texture IDs

 glBindTexture(GL_TEXTURE_2D, texture[0].texID);     // Bind Our Texture
 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  // Linear Filtered
 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  // Linear Filtered

   
Now we check to see if the TGA file was 24 or 32 bit. If the TGA was 24 bit, we set the type to GL_RGB. (no alpha channel). If we didn't do this, OpenGL would try to build a texture with an alpha channel. The alpha information wouldn't be there, and the program would probably crash or give an error message.   
   

 if (texture[0].bpp==24)        // Was The TGA 24 Bits
 {
  type=GL_RGB;        // If So Set The 'type' To GL_RGB
 }

   
Now we build our texture, the same way we've always done it. But instead of putting the type in ourselves (GL_RGB or GL_RGBA), we substitute the variable type. That way if the program detected that the TGA was 24 bit, the type will be GL_RGB. If our program detected that the TGA was 32 bit, the type would be GL_RGBA.

After the texture has been built, we return true. This lets the InitGL() code know that everything went ok.   
   

 glTexImage2D(GL_TEXTURE_2D, 0, type, texture[0].width, texture[0].height, 0, type, GL_UNSIGNED_BYTE, texture[0].imageData);

 return true;         // Texture Building Went Ok, Return True
}

   
The code below is our standard build a font from a texture code. You've all seen this code before if you've gone through all the tutorials up until now. Nothing really new here, but I figured I'd include the code to make following through the program a little easier.

Only real difference is that I bind to textures[0].texID. Which points to the font texture. Only real difference is that .texID has been added.   
   

GLvoid BuildFont(GLvoid)        // Build Our Font Display List
{
 base=glGenLists(256);        // Creating 256 Display Lists
 glBindTexture(GL_TEXTURE_2D, textures[0].texID);    // Select Our Font Texture
 for (int loop1=0; loop1<256; loop1++)      // Loop Through All 256 Lists
 {
  float cx=float(loop1%16)/16.0f;      // X Position Of Current Character
  float cy=float(loop1/16)/16.0f;      // Y Position Of Current Character

  glNewList(base+loop1,GL_COMPILE);     // Start Building A List
   glBegin(GL_QUADS);      // Use A Quad For Each Character
    glTexCoord2f(cx,1.0f-cy-0.0625f);   // Texture Coord (Bottom Left)
    glVertex2d(0,16);     // Vertex Coord (Bottom Left)
    glTexCoord2f(cx+0.0625f,1.0f-cy-0.0625f);  // Texture Coord (Bottom Right)
    glVertex2i(16,16);     // Vertex Coord (Bottom Right)
    glTexCoord2f(cx+0.0625f,1.0f-cy-0.001f);  // Texture Coord (Top Right)
    glVertex2i(16,0);     // Vertex Coord (Top Right)
    glTexCoord2f(cx,1.0f-cy-0.001f);   // Texture Coord (Top Left)
    glVertex2i(0,0);     // Vertex Coord (Top Left)
   glEnd();       // Done Building Our Quad (Character)
   glTranslated(14,0,0);      // Move To The Right Of The Character
  glEndList();        // Done Building The Display List
 }          // Loop Until All 256 Are Built
}

   
KillFont is still the same. We created 256 display lists, so we need to destroy 256 display lists when the program closes.   
   

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

   
The glPrint() code has only changed a bit. The letters are all stretched on the y axis. Making the letters very tall. I've explained the rest of the code in other tutorials. The stretching is accomplished with the glScalef(x,y,z) command. We leave the ratio at 1.0 on the x axis, we double the size on the y axis (2.0), and we leave it at 1.0 on the z axis.   
   

GLvoid glPrint(GLint x, GLint y, int set, const char *fmt, ...)    // Where The Printing Happens
{
 char text[1024];        // Holds Our String
 va_list ap;         // Pointer To List Of Arguments

 if (fmt == NULL)        // If There's No Text
  return;         // Do Nothing

 va_start(ap, fmt);        // Parses The String For Variables
     vsprintf(text, fmt, ap);       // And Converts Symbols To Actual Numbers
 va_end(ap);         // Results Are Stored In Text

 if (set>1)         // Did User Choose An Invalid Character Set?
 {
  set=1;         // If So, Select Set 1 (Italic)
 }

 glEnable(GL_TEXTURE_2D);       // Enable Texture Mapping
 glLoadIdentity();        // Reset The Modelview Matrix
 glTranslated(x,y,0);        // Position The Text (0,0 - Top Left)
 glListBase(base-32+(128*set));       // Choose The Font Set (0 or 1)

 glScalef(1.0f,2.0f,1.0f);       // Make The Text 2X Taller

 glCallLists(strlen(text),GL_UNSIGNED_BYTE, text);    // Write The Text To The Screen
 glDisable(GL_TEXTURE_2D);       // Disable Texture Mapping
}

   
ReSizeGLScene() sets up an ortho view. Nothing really new. 0,1 is the top left of the screen. 639,480 is the bottom right. This gives us exact screen coordinates in 640 x 480 resolution. Notice that we set the value of swidth to equal the windows current width, and we set the value of sheight to equal the windows current height. Whenever the window is resized or moved, sheight and swidth will be updated.   
   

GLvoid ReSizeGLScene(GLsizei width, GLsizei height)     // Resize And Initialize The GL Window
{
 swidth=width;         // Set Scissor Width To Window Width
 sheight=height;         // Set Scissor Height To Window Height
 if (height==0)         // Prevent A Divide By Zero By
 {
  height=1;        // Making Height Equal One
 }
 glViewport(0,0,width,height);       // Reset The Current Viewport
 glMatrixMode(GL_PROJECTION);       // Select The Projection Matrix
 glLoadIdentity();        // Reset The Projection Matrix
 glOrtho(0.0f,640,480,0.0f,-1.0f,1.0f);      // Create Ortho 640x480 View (0,0 At Top Left)
 glMatrixMode(GL_MODELVIEW);       // Select The Modelview Matrix
 glLoadIdentity();        // Reset The Modelview Matrix
}

   
The init code is very minimal. We load our TGA file. Notice that the first parameter passed is &textures[0]. The second parameter is the name of the file we want to load. In this case, we want to load the Font.TGA file. If LoadTGA() returns false for any reason, the if statement will also return false, causing the program to quit with an "initialization failed" message.

If you wanted to load a second texture you could use the following code: if ((!LoadTGA(&textures[0],"image1.tga")) || (!LoadTGA(&textures[1],"image2.tga"))) { }

After we load the TGA (creating our texture), we build our font, set shading to smooth, set the background color to black, enable clearing of the depth buffer, and select our font texture (bind to it).

Lastly we return true so that our program knows that initialization went ok.   
   

int InitGL(GLvoid)         // All Setup For OpenGL Goes Here
{
 if (!LoadTGA(&textures[0],"Data/Font.TGA"))     // Load The Font Texture
 {
  return false;        // If Loading Failed, Return False
 }

 BuildFont();         // Build The Font

 glShadeModel(GL_SMOOTH);       // Enable Smooth Shading
 glClearColor(0.0f, 0.0f, 0.0f, 0.5f);      // Black Background
 glClearDepth(1.0f);        // Depth Buffer Setup
 glBindTexture(GL_TEXTURE_2D, textures[0].texID);    // Select Our Font Texture

 return TRUE;         // Initialization Went OK
}

   
The draw code is completely new :) we start off by creating a variable of type char called token. Token will hold parsed text later on in the code.

We have another variable called cnt. I use this variable both for counting the number of extensions supported, and for positioning the text on the screen. cnt is reset to zero every time we call DrawGLScene.

We clear the screen and depth buffer and then set the color to bright red (full red intensity, 50% green, 50% blue). at 50 on the x axis and 16 on the y axis we write teh word "Renderer". We also write "Vendor" and "Version" at the top of the screen. The reason each word does not start at 50 on the x axis is because I right justify the words (they all line up on the right side).   
   

int DrawGLScene(GLvoid)         // Here's Where We Do All The Drawing
{
 char *token;         // Storage For Our Token
 int cnt=0;         // Local Counter Variable

 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // Clear Screen And Depth Buffer

 glColor3f(1.0f,0.5f,0.5f);       // Set Color To Bright Red
 glPrint(50,16,1,"Renderer");       // Display Renderer
 glPrint(80,48,1,"Vendor");       // Display Vendor Name
 glPrint(66,80,1,"Version");       // Display Version

   
Now that we have text on the screen, we change the color to orange, and grab the renderer, vendor name and version number from the video card. We do this by passing GL_RENDERER, GL_VENDOR & GL_VERSION to glGetString(). glGetString will return the requested renderer name, vendor name and version number. The information returned will be text so we need to cast the return information from glGetString as char. All this means is that we tell the program we want the information returned to be characters (text). If you don't include the (char *) you will get an error message. We're printing text, so we need text returned. We grab all three pieces of information and write the information we've grabbed to the right of the previous text.

The information we get from glGetString(GL_RENDERER) will be written beside the red text "Renderer", the information we get from glGetString(GL_VENDOR) will be written to the right of "Vendor", etc.

I'd like to explain casting in more detail, but I'm not really sure of a good way to explain it. If anyone has a good explanation, send it in, and I'll modify my explanation.

After we have the renderer information, vendor information and version number written to the screen, we change the color to a bright blue, and write "NeHe Productions" at the bottom of the screen :) Of course you can change this to anything you want.   
   

 glColor3f(1.0f,0.7f,0.4f);       // Set Color To Orange
 glPrint(200,16,1,(char *)glGetString(GL_RENDERER));    // Display Renderer
 glPrint(200,48,1,(char *)glGetString(GL_VENDOR));    // Display Vendor Name
 glPrint(200,80,1,(char *)glGetString(GL_VERSION));    // Display Version

 glColor3f(0.5f,0.5f,1.0f);       // Set Color To Bright Blue
 glPrint(192,432,1,"NeHe Productions");      // Write NeHe Productions At The Bottom Of The Screen

   
Now we draw a nice white border around the screen, and around the text. We start off by resetting the modelview matrix. Because we've been printing text to the screen, and we might not be at 0,0 on the screen, it's a safe thing to do.

We then set the color to white, and start drawing our borders. A line strip is actually pretty easy to use. You tell OpenGL you want to draw a line strip with glBegin(GL_LINE_STRIP). Then we set the first vertex. Our first vertex will be on the far right side of the screen, and about 63 pixels up from the bottom of the screen (639 on the x axis, 417 on the y axis). Then we set the second vertex. We stay at the same location on the y axis (417), but we move to the far left side of the screen on the x axis (0). A line will be drawn from the right side of the screen (639,417) to the left side of the screen (0,417).

You need to have at least two vertices in order to draw a line (common sense). From the left side of the screen, we move down, right, and then straight up (128 on the y axis).

We then start another line strip, and draw a second box at the top of the screen. If you need to draw ALOT of connected lines, line strips can definitely cut down on the amount of code required as opposed to using regular lines (GL_LINES).   
   

 glLoadIdentity();        // Reset The ModelView Matrix
 glColor3f(1.0f,1.0f,1.0f);       // Set The Color To White
 glBegin(GL_LINE_STRIP);        // Start Drawing Line Strips (Something New)
  glVertex2d(639,417);       // Top Right Of Bottom Box
  glVertex2d(  0,417);       // Top Left Of Bottom Box
  glVertex2d(  0,480);       // Lower Left Of Bottom Box
  glVertex2d(639,480);       // Lower Right Of Bottom Box
  glVertex2d(639,128);       // Up To Bottom Right Of Top Box
 glEnd();         // Done First Line Strip
 glBegin(GL_LINE_STRIP);        // Start Drawing Another Line Strip
  glVertex2d(  0,128);       // Bottom Left Of Top Box
  glVertex2d(639,128);       // Bottom Right Of Top Box  
  glVertex2d(639,  1);       // Top Right Of Top Box
  glVertex2d(  0,  1);       // Top Left Of Top Box
  glVertex2d(  0,417);       // Down To Top Left Of Bottom Box
 glEnd();         // Done Second Line Strip

   
Now for something new. A wonderful GL command called glScissor(x,y,w,h). What this command does is creates almost what you would call a window. When GL_SCISSOR_TEST is enabled, the only portion of the screen that you can alter is the portion inside the scissor window. The first line below creates a scissor window starting at 1 on the x axis, and 13.5% (0.135...f) of the way from the bottom of the screen on the y axis. The scissor window will be 638 pixels wide (swidth-2), and 59.7% (0.597...f) of the screen tall.

In the next line we enable scissor testing. Anything we draw OUTSIDE the scissor window will not show up. You could draw a HUGE quad on the screen from 0,0 to 639,480, and you would only see the quad inside the scissor window, the rest of the screen would be unaffected. Very nice command!

The third line of code creates a variable called text that will hold the characters returned by glGetString(GL_EXTENSIONS). malloc(strlen((char *)glGetString(GL_EXTENSIONS))+1) allocates enough memory to hold the entire string returned +1 (so if the string was 50 characters, text would be able to hold all 50 characters).

The next line copies the GL_EXTENSIONS information to text. If we modify the GL_EXTENSIONS information directly, big problems will occur, so instead we copy the information into text, and then manipulate the information stored in text. Basically we're just taking a copy, and storing it in the variable text.   
   

 glScissor(1 ,int(0.135416f*sheight),swidth-2,int(0.597916f*sheight)); // Define Scissor Region
 glEnable(GL_SCISSOR_TEST);       // Enable Scissor Testing

 char* text=(char*)malloc(strlen((char *)glGetString(GL_EXTENSIONS))+1);  // Allocate Memory For Our Extension String
 strcpy (text,(char *)glGetString(GL_EXTENSIONS));    // Grab The Extension List, Store In Text

   
Now for something new. Lets pretend that after grabbing the extension information from the video card, the variable text had the following string of text stored in it... "GL_ARB_multitexture GL_EXT_abgr GL_EXT_bgra". strtok(TextToAnalyze,TextToFind) will scan through the variable text until it finds a " " (space). Once it finds a space, it will copy the text UP TO the space into the variable token. So in our little example, token would be equal to "GL_ARB_multitexture". The space is then replaced with a marker. More about this in a minute.

Next we create a loop that stops once there is no more information left in text. If there is no information in text, token will be equal to nothing (NULL) and the loop will stop.

We increase the counter variable (cnt) by one, and then check to see if the value in cnt is higher than the value of maxtokens. If cnt is higher than maxtokens we make maxtokens equal to cnt. That way if the counter hits 20, maxtokens will also equal 20. It's an easy way to keep track of the maximum value of cnt.   
   

 token=strtok(text," ");        // Parse 'text' For Words, Seperated By " " (spaces)
 while(token!=NULL)        // While The Token Isn't NULL
 {
  cnt++;         // Increase The Counter
  if (cnt>maxtokens)       // Is 'maxtokens' Less Than 'cnt'
  {
   maxtokens=cnt;       // If So, Set 'maxtokens' Equal To 'cnt'
  }

   
So we have stored the first extension from our list of extensions in the variable token. Next thing to do is set the color to bright green. We then print the variable cnt on the left side of the screen. Notice that we print at 0 on the x axis. This should erase the left (white) border that we drew, but because scissor testing is on, pixels drawn at 0 on the x axis wont be modified. The border can't be drawn over.

The variable is drawn on the far left side of the screen (0 on the x axis). We start drawing at 96 on the y axis. To keep all the text from drawing to the same spot on the screen, we add (cnt*32) to 96. So if we are displaying the first extension, cnt will equal 1, and the text will be drawn at 96+(32*1) (128) on the y axis. If we display the second extension, cnt will equal 2, and the text will be drawn at 96+(32*2) (160) on the y axis.

Notice I also subtract scroll. When the program first runs, scroll will be equal to 0. So our first line of text is drawn at 96+(32*1)-0. If you press the DOWN ARROW, scroll is increased by 2. If scroll was 4, the text would be drawn at 96+(32*1)-4. That means the text would be drawn at 124 instead of 128 on the y axis because of scroll being equal to 4. The top of our scissor window ends at 128 on the y axis. Any part of the text drawn from lines 124-127 on the y axis will not appear on the screen.

Same thing with the bottom of the screen. If cnt was equal to 11 and scroll was equal to 0, the text would be drawn at 96+(32*11)-0 which is 448 on the y axis. Because the scissor window only allows us to draw as far as line 416 on the y axis, the text wouldn't show up at all.

The final result is that we end up with a scrollable window that only allows us to look at 288/32 (9) lines of text. 288 is the height of our scissor window. 32 is the height of the text. By changing the value of scroll we can move the text up or down (offset the text).

The effect is similar to a movie projector. The film rolls by the lens, and all you see is the current frame. You don't see the area above or below the frame. The lens acts as a window similar to the window created by the scissor test.

After we have drawn the current count (cnt) to the screen, we change the color to yellow, move 50 pixels to the right on the x axis, and we write the text stored in the variable token to the screen.

Using our example above, the first line of text displayed on the screen should look like this:

1 GL_ARB_multitexture   
   

  glColor3f(0.5f,1.0f,0.5f);      // Set Color To Bright Green
  glPrint(0,96+(cnt*32)-scroll,0,"%i",cnt);    // Print Current Extension Number

   
After we have drawn the current count to the screen, we change the color to yellow, move 50 pixels to the right on the x axis, and we write the text stored in the variable token to the screen.

Using our example above, the first line of text displayed on the screen should look like this:

1   GL_ARB_multitexture   
   

  glColor3f(1.0f,1.0f,0.5f);      // Set Color To Yellow
  glPrint(50,96+(cnt*32)-scroll,0,token);     // Print The Current Token (Parsed Extension Name)

   
After we have displayed the value of token on the screen, we need to check through the variable text to see if any more extensions are supported. Instead of using token=strtok(text," ") like we did above, we replace text with NULL. This tells the command strtok to search from the last marker to the NEXT space in the string of text (text).

In our example above ("GL_ARB_multitexturemarkerGL_EXT_abgr GL_EXT_bgra") there will now be a marker after the text "GL_ARB_multitexture". The line below will start search FROM the marker to the next space. Everything from the marker to the next space will be stored in token. token should end up being "GL_EXT_abgr", and text will end up being "GL_ARB_multitexturemarkerGL_EXT_abgrmarkerGL_EXT_bgra".

Once strtok() has run out of text to store in token, token will become NULL and the loop will stop.   
   

  token=strtok(NULL," ");       // Search For The Next Token
 }

   
After all of the extensions have been parsed from the variable text we can disable scissor testing, and free the variable text. This releases the ram we were using to hold the information we got from glGetString(GL_EXTENSIONS).

The next time DrawGLScene() is called, new memory will be allocated. A fresh copy of the information returned by glGetStrings(GL_EXTENSIONS) will be copied into the variable text and the entire process will start over.   
   

 glDisable(GL_SCISSOR_TEST);       // Disable Scissor Testing

 free (text);         // Free Allocated Memory

   
The first line below isn't necessary, but I thought it might be a good idea to talk about it, just so everyone knows that it exists. The command glFlush() basically tells OpenGL to finish up what it's doing. If you ever notice flickering in your program (quads disappearing, etc). Try adding the flush command to the end of DrawGLScene. It flushes out the rendering pipeline. You may notice flickering if you're program doesn't have enough time to finish rendering the scene.

Last thing we do is return true to show that everything went ok.   
   

 glFlush();         // Flush The Rendering Pipeline
 return TRUE;         // Everything Went OK
}

   
The only thing to note in KillGLWindow() is that I have added KillFont() at the end. That way whenever the window is killed, the font is also killed.   
   

GLvoid KillGLWindow(GLvoid)        // Properly Kill The Window
{
 if (fullscreen)         // Are We In Fullscreen Mode?
 {
  ChangeDisplaySettings(NULL,0);      // If So Switch Back To The Desktop
  ShowCursor(TRUE);       // Show Mouse Pointer
 }

 if (hRC)         // Do We Have A Rendering Context?
 {
  if (!wglMakeCurrent(NULL,NULL))      // Are We Able To Release The DC And RC Contexts?
  {
   MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
  }

  if (!wglDeleteContext(hRC))      // Are We Able To Delete The RC?
  {
   MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
  }
  hRC=NULL;        // Set RC To NULL
 }

 if (hDC && !ReleaseDC(hWnd,hDC))      // Are We Able To Release The DC
 {
  MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
  hDC=NULL;        // Set DC To NULL
 }

 if (hWnd && !DestroyWindow(hWnd))      // Are We Able To Destroy The Window?
 {
  MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
  hWnd=NULL;        // Set hWnd To NULL
 }

 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();         // Kill The Font
}

   
CreateGLWindow(), and WndProc() are the same.

The first change in WinMain() is the title that appears at the top of the window. It should now read "NeHe's Extensions, Scissoring, Token & TGA Loading Tutorial"   
   

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
 }

 // Create Our OpenGL Window
 if (!CreateGLWindow("NeHe's Token, Extensions, Scissoring & TGA Loading 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
   {
    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)

    if (keys[VK_F1])     // Is F1 Being Pressed?
    {
     keys[VK_F1]=FALSE;    // If So Make Key FALSE
     KillGLWindow();     // Kill Our Current Window
     fullscreen=!fullscreen;    // Toggle Fullscreen / Windowed Mode
     // Recreate Our OpenGL Window
     if (!CreateGLWindow("NeHe's Token, Extensions, Scissoring & TGA Loading Tutorial",640,480,16,fullscreen))
     {
      return 0;    // Quit If Window Was Not Created
     }
    }

   
The code below checks to see if the up arrow is being pressed if it is, and scroll is greater than 0, we decrease scroll by 2. This causes the text to move down the screen.   
   

    if (keys[VK_UP] && (scroll>0))    // Is Up Arrow Being Pressed?
    {
     scroll-=2;     // If So, Decrease 'scroll' Moving Screen Down
    }

   
If the down arrow is being pressed and scroll is less than (32*(maxtokens-9)) scroll will be increased by 2, andd the text on the screen will scroll upwards.

32 is the number of lines that each letter takes up. Maxtokens is the total amount of extensions that your video card supports. We subtract 9, because 9 lines can be shown on the screen at once. If we did not subtract 9, we could scroll past the end of the list, causing the list to scroll completely off the screen. Try leaving the -9 out if you're not sure what I mean.   
   

    if (keys[VK_DOWN] && (scroll<32*(maxtokens-9)))  // Is Down Arrow Being Pressed?
    {
     scroll+=2;     // If So, Increase 'scroll' Moving Screen Up
    }
   }
  }
 }

 // Shutdown
 KillGLWindow();         // Kill The Window
 return (msg.wParam);        // Exit The Program
}

   
I hope that you found this tutorial interesting. By the end of this tutorial you should know how to read the vendor name, renderer and version number from your video card. You should also know how to find out what extensions are supported on any video card that supports OpenGL. You should know what scissor testing is, and how it can be used in OpenGL projects of your own, and lastly, you should know how to load TGA Images instead of Bitmap Images for use as textures.

If you find any problems with the tutorial, or you find the information to hard to understand, let me know. I want the tutorials to be the best they can be. Your feedback is important!

Jeff Molofee (NeHe)



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