zoukankan      html  css  js  c++  java
  • NeHe OpenGL教程 第二十四课:扩展

    转自【翻译】NeHe OpenGL 教程

    前言

    声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改。对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢。

    NeHe OpenGL第二十四课:扩展

    扩展,剪裁和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的值
        }
    原文及其个版本源代码下载:

    http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=24

     
     
  • 相关阅读:
    Java学习的第二十八天
    Java学习的第二十七天
    第五章 作用域闭包
    第4章提升
    第3章函数作用域和块级作用域
    Vant中List列表下拉加载更多
    获取当前时间
    js比较两个时间的大小
    边框引起页面抖动
    计算两个数的百分比,保留两位小数
  • 原文地址:https://www.cnblogs.com/arxive/p/6239499.html
Copyright © 2011-2022 走看看