新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 计算机科学论坛计算机技术与应用『 C/C++编程思想 』 → [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 37-lesson  38 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 21055 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 37-lesson  38 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客楼主
    发贴心情 [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 37-lesson  38

    第三十七 三十八课源码


    第三十七课


    按此在新窗口浏览图片卡通映射:

    什么是卡通了,一个轮廓加上少量的几种颜色。使用一维纹理映射,你也可以实现这种效果。

      
       
       
    看到人们仍然e-mail我请求在文章中使用我方才在GameDev.net上写的源代码,还看到文章的第二版(在那每一个API附带源码)不是在中途完成之前连贯的结束。我已经把这篇指南一并出租给了NeHe(这实际上是写文章的最初意图)因此你们所有的OpenGL领袖可以玩转它。对模型的选择表示抱歉,但是我最近一直在玩Quake 2。
    注释:这篇文章的源代码可以在这里找到:
    http://www.gamedev.net/reference/programming/features/celshading.
    这篇指南实际上并不解释原理,仅仅解释代码。在上面的连接中可以发现为什么它能工作。现在不断地大声抱怨STOP E-MAILING ME REQUESTS FOR SOURCE CODE!!!!
    首先,我们需要包含一些额外的头文件。第一个(math.h)我们可以使用sqrtf (square root)函数,第二个用来访问文件。
      
       

    #include <math.h>      
    #include <stdio.h>      

       
    现在我们将定义一些结构体来帮助我们存贮我们的数据(保存好几百浮点数组)。第一个是tagMATRIX结构体。如果你仔细地看,你将看到我们正象包含一个十六个浮点数的1维数组~一个2维4×4数族一样存储那个矩阵。这下至OpenGL存储它的矩阵的方式。如果我们使用4x4数组,这些值将发生错误的顺序。
      
       

    typedef struct tagMATRIX     // 保存OpenGL矩阵的结构体
    {
     float Data[16];     // 由于OpenGL的矩阵的格式我们使用[16
    }
    MATRIX;

       
    第二是向量的类。 仅存储X,Y和Z的值  
       

    typedef struct tagVECTOR     // 存储一个单精度向量的结构体
    {
     float X, Y, Z;     // 向量的分量
    }
    VECTOR;

       
    第三,我们持有顶点的结构。每一个顶点仅需要它的法线和位置(没有纹理的现行纵坐标)信息。它们必须以这样的次序被存放,否则当它停止装载文件的事件将发生严重的错误(我发现艰难的情形:(教我分块出租我的代码。)。
      
       

    typedef struct tagVERTEX     // 存放单一顶点的结构
    {
     VECTOR Nor;     // 顶点法线
     VECTOR Pos;     // 顶点位置
    }
    VERTEX;

       
    最后是多边形的结构。我知道这是存储顶点的愚蠢的方法,要不是它完美工作的简单的缘故。通常我愿意使用一个顶点数组,一个多边形数组,和包括一个在多边形中的3个顶点的指数,但这比较容易显示你想干什么。  
       

    typedef struct tagPOLYGON     // 存储单一多边形的结构
    {
     VERTEX Verts[3];     // 3个顶点结构数组
    }
    POLYGON;

       
    优美简单的材料也在这里了。为每一个变量的一个解释考虑那个注释。  
       

    bool  outlineDraw = true;    // 绘制轮廓的标记
    bool  outlineSmooth = false;    // Anti-Alias 线段的标记
    float  outlineColor[3] = { 0.0f, 0.0f, 0.0f };  // 线段的颜色
    float  outlineWidth = 3.0f;    // 线段的宽度

    VECTOR  lightAngle;     // 灯光的方向
    bool  lightRotate = false;    // 是否我们旋转灯光的标记

    float  modelAngle = 0.0f;    // 模型的Y轴角度
    bool     modelRotate = false;     // 旋转模型的标记

    POLYGON  *polyData = NULL;     // 多边形数据
    int  polyNum  = 0;    // 多边形的编号

    GLuint  shaderTexture[1];     // 存储纹理ID

       
    这是得到的再简单不过的模型文件格式。 最初的少量字节存储在场景中的多边形的编号,文件的其余是tagPOLYGON结构体的一个数组。正因如此,数据在没有任何需要去分类到详细的顺序的情况下被读出。  
       

    BOOL ReadMesh ()      // 读“model.txt” 文件
    {
     FILE *In = fopen ("Data\\model.txt", "rb");  // 打开文件

     if (!In)
      return FALSE;    // 如果文件没有打开返回 FALSE

     fread (&polyNum, sizeof (int), 1, In);  // 读文件头,多边形的个数

     polyData = new POLYGON [polyNum];   // 分配内存

     fread (&polyData[0], sizeof (POLYGON) * polyNum, 1, In);// 把所有多边形的数据读入

     fclose (In);     // 关闭文件

     return TRUE;     // 工作完成
    }

       
    一些基本的数学函数而已。DotProduct计算2个向量或平面之间的角,Magnitude函数计算向量的长度,Normalize函数缩放向量到一个单位长度。
      
       

    inline float DotProduct (VECTOR &V1, VECTOR &V2)  //计算两个向量之间的角度
    {
     return V1.X * V2.X + V1.Y * V2.Y + V1.Z * V2.Z;  
    }

    inline float Magnitude (VECTOR &V)    // 计算向量的长度
    {
     return sqrtf (V.X * V.X + V.Y * V.Y + V.Z * V.Z); 
    }

    void Normalize (VECTOR &V)     // 创建一个单位长度的向量
    {
     float M = Magnitude (V);    

     if (M != 0.0f)     // 确保我们没有被0隔开
     {
      V.X /= M;     
      V.Y /= M;
      V.Z /= M;
     }
    }

       
    这个函数利用给定的矩阵旋转一个向量。请注意它仅旋转这个向量——与向量的位置相比它算不了什么。它用来当旋转法线确保当我们在计算灯光时它们停留在正确的方向上。  
       

    void RotateVector (MATRIX &M, VECTOR &V, VECTOR &D)  // 利用提供的矩阵旋转一个向量
    {
     D.X = (M.Data[0] * V.X) + (M.Data[4] * V.Y) + (M.Data[8]  * V.Z); 
     D.Y = (M.Data[1] * V.X) + (M.Data[5] * V.Y) + (M.Data[9]  * V.Z); 
     D.Z = (M.Data[2] * V.X) + (M.Data[6] * V.Y) + (M.Data[10] * V.Z); 
    }

       
    引擎的第一个主要的函数…… 初始化,按所说的精确地做。我已经砍掉了在注释中不再需要的代码段。  
       

    // 一些GL 初始代码和用户初始化从这里开始
    BOOL Initialize (GL_Window* window, Keys* keys)
    {

       
    这3个变量用来装载着色文件。在文本文件中为了单一的线段线段包含了空间,虽然shaderData存储了真实的着色值。你可能奇怪为什么我们的96个值被32个代替了。好了,我们需要转换greyscale 值为RGB以便OpenGL能使用它们。我们仍然可以以greyscale存储这些值,但向上负载纹理时我们至于R,G和B成分仅仅使用同一值。  
       

     char Line[255];      // 255个字符的存储量
     float shaderData[32][3];     // 96个着色值的存储量
     g_window = window; g_keys  = keys;
     FILE *In = NULL;      // 文件指针

       
    当绘制线条时,我们想要确保很平滑。初值被关闭,但是按“2”键,它可以被toggled on/off。  
       

     glShadeModel (GL_SMOOTH);    // 使用色彩阴影平滑
     glDisable (GL_LINE_SMOOTH);    // 线条平滑初始化不可用

     glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 提高计算精度  
     glClearColor (0.7f, 0.7f, 0.7f, 0.0f);  // 设置为灰色背景 
     glClearDepth (1.0f);    // 设置深度缓存值
       glEnable (GL_DEPTH_TEST);  // 启用深度测试
       glDepthFunc (GL_LESS);  // 设置深度比较函数
     glShadeModel (GL_SMOOTH);  // 启用反走样
       glDisable (GL_LINE_SMOOTH);

    glEnable (GL_CULL_FACE);    // 启用剔除多边形功能

       
    我们使 OpenGL灯光不可用因为我们自己做所以的灯光计算。  
       

     glDisable (GL_LIGHTING);    // 使 OpenGL 灯光不可用

       
    这里是我们装载阴影文件的地方。它简单地以32个浮点值ASCII码存放(为了轻松修改),每一个在separate线上。  
       

     In = fopen ("Data\\shader.txt", "r");   // 打开阴影文件

     if (In)       // 检查文件是否打开
     {
      for (i = 0; i < 32; i++)    // 循环32次
      {
       if (feof (In))    // 检查文件是否结束
        break;

       fgets (Line, 255, In);   // 获得当前线条

       
    这里我们转换 greyscale 值为 RGB, 正象上面所描述的。  
       

       // 从头到尾复制这个值
       shaderData[i][0] = shaderData[i][1] = shaderData[i][2] = atof (Line);
      }

      fclose (In);     // 关闭文件
     }

     else
      return FALSE;     
       
    现在我们向上装载这个纹理。同样它清楚地规定,不要使用任何一种过滤在纹理上否则它看起来奇怪,至少可以这样说。GL_TEXTURE_1D被使用因为它是值的一维数组。  
       

     glGenTextures (1, &shaderTexture[0]);   // 获得一个自由的纹理ID

     glBindTexture (GL_TEXTURE_1D, shaderTexture[0]);  // 绑定这个纹理。 从现在开始它变为一维

     // 使用邻近点过滤
     glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

     // 设置纹理
     glTexImage1D (GL_TEXTURE_1D, 0, GL_RGB, 32, 0, GL_RGB , GL_FLOAT, shaderData);

       
    现在调整灯光方向。我已经使得它向下指向Z轴正方向,这意味着它将正面碰撞模型  
       

     lightAngle.X = 0.0f;     
     lightAngle.Y = 0.0f;     
     lightAngle.Z = 1.0f;    

     Normalize (lightAngle);

       
    读取Mesh文件,并返回  
       

     return ReadMesh ();     // 读取Mesh文件,并返回
    }

       
    与上面的函数相对应…… 卸载,删除由Initalize 和 ReadMesh 创建的纹理和多边形数据。  
       

    void Deinitialize (void)
    {
     glDeleteTextures (1, &shaderTexture[0]);  // 删除阴影纹理

     delete [] polyData;    // 删除多边形数据
    }

       
    主要的演示循环。所有这些用来处理输入和更新角度。控制如下:
    <SPACE> =锁定旋转

    1 = 锁定轮廓绘制
    2 = 锁定轮廓 anti-aliasing

    <UP> =增加线宽
    <DOWN> = 减小线宽
      
       

    void Update (DWORD milliseconds)    // 这里执行动作更新
    {
     if (g_keys->keyDown [' '] == TRUE)   // 空格是否被按下
     {
      modelRotate = !modelRotate;   // 锁定模型旋转开/关

      g_keys->keyDown [' '] = FALSE;
     }

     if (g_keys->keyDown ['1'] == TRUE)   // 1是否被按下
     {
      outlineDraw = !outlineDraw;   // 切换是否绘制轮廓线

      g_keys->keyDown ['1'] = FALSE;
     }

     if (g_keys->keyDown ['2'] == TRUE)   // 2是否被按下
     {
      outlineSmooth = !outlineSmooth;  // 切换是否使用反走样

      g_keys->keyDown ['2'] = FALSE;
     }

     if (g_keys->keyDown [VK_UP] == TRUE)   // 上键增加线的宽度
     {
      outlineWidth++;     

      g_keys->keyDown [VK_UP] = FALSE;
     }

     if (g_keys->keyDown [VK_DOWN] == TRUE)  // 下减少线的宽度
     {
      outlineWidth--;     

      g_keys->keyDown [VK_DOWN] = FALSE;
     }

     if (modelRotate)     // 是否旋转
      modelAngle += (float) (milliseconds) / 10.0f; // 更新旋转角度
    }

       
    你一直在等待的函数。Draw 函数做每一件事情——计算阴影的值,着色网孔,着色轮廓,等等,这是它作的。  
       

    void Draw (void)
    {

       
    TmpShade用来存储当前顶点的色度值。所有顶点数据同时被计算,意味着我们只需使用我们能继续使用的单个的变量。
    TmpMatrix, TmpVector 和 TmpNormal同样被用来计算顶点数据,TmpMatrix在函数开始时被调整一次并一直保持到Draw函数被再次调用。TmpVector 和 TmpNormal则相反,当另一个顶点被处理时改变。
      
       

     float TmpShade;      // 临时色度值

     MATRIX TmpMatrix;      // 临时 MATRIX 结构体
     VECTOR TmpVector, TmpNormal;    // 临时 VECTOR结构体

       
    清除缓冲区矩阵数据  
       

     glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //  清除缓冲区
     glLoadIdentity ();     // 重置矩阵

       
    首先检查我们是否想拥有平滑的轮廓。如果是,我们就打开anti-alaising 。否则把它关闭。简单!  
       

     if (outlineSmooth)     // 检查我们是否想要 Anti-Aliased 线条
     {
      glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); // 启用它们
      glEnable (GL_LINE_SMOOTH);   
     }

     else      // 否则不启用
      glDisable (GL_LINE_SMOOTH);  
       
    然后我们设置视口。我们反向移动摄象机2个单元,之后以一定角度旋转模型。注:由于我们首先移动摄象机,这个模型将在现场旋转。如果我们以另一种方法做,模型将绕摄象机旋转。
    我们之后从OpenGL中取最新创建的矩阵并把它存储在 TmpMatrix。
      
       

     glTranslatef (0.0f, 0.0f, -2.0f);   // 移入屏幕两个单位
     glRotatef (modelAngle, 0.0f, 1.0f, 0.0f);  // 绕Y轴旋转这个模型

     glGetFloatv (GL_MODELVIEW_MATRIX, TmpMatrix.Data); // 获得产生的矩阵

       
    戏法开始了。首先我们启用一维纹理,然后启用着色纹理。这被OpenGL用来当作一个look-up表格。我们之后调整模型的颜色(白色)我选择白色是因为它亮度高并且描影法比其它颜色好。我建议你不要使用黑色:)
      
       

     // 卡通渲染代码
     glEnable (GL_TEXTURE_1D);    // 启用一维纹理
     glBindTexture (GL_TEXTURE_1D, shaderTexture[0]); // 锁定我们的纹理

     glColor3f (1.0f, 1.0f, 1.0f);    // 调整模型的颜色

       
    现在我们开始绘制那些三角形。尽管我们看到在数组中的每一个多边形,然后旋转它的每一个顶点。第一步是拷贝法线信息到一个临时的结构中。因此我们能旋转法线,但仍然保留原来保存的值(没有精确降级)。
      
       

     glBegin (GL_TRIANGLES);     // 告诉 OpenGL 我们即将绘制三角形

      for (i = 0; i < polyNum; i++)   // 从头到尾循环每一个多边形
      {
       for (j = 0; j < 3; j++)   // 从头到尾循环每一个顶点
       {
        TmpNormal.X = polyData[i].Verts[j].Nor.X; // 用当前顶点的法线值填充TmpNormal结构
        TmpNormal.Y = polyData[i].Verts[j].Nor.Y; 
        TmpNormal.Z = polyData[i].Verts[j].Nor.Z;

       
    第二,我们通过初期从OpenGL中攫取的矩阵来旋转这个法线。我们之后规格化因此它并不全部变为螺旋形。  
       

        // 通过矩阵旋转
        RotateVector (TmpMatrix, TmpNormal, TmpVector);

        Normalize (TmpVector);  // 规格化这个新法线

       
    第三,我们获得那个旋转的法线的点积灯光方向(称为lightAngle,因为我忘了从我的旧的light类中改变它)。我们之后约束这个值在0——1的范围。(从-1到+1)
      
       

        // 计算色度值
        TmpShade = DotProduct (TmpVector, lightAngle);

        if (TmpShade < 0.0f)
         TmpShade = 0.0f; // 如果负值约束这个值到0

       
    第四,对于OpenGL我们象忽略纹理坐标一样忽略这个值。阴影纹理与一个查找表一样来表现(色度值正成为指数),这是(我认为)为什么1D纹理被创造主要原因。对于OpenGL我们之后忽略这个顶点位置,并不断重复,重复。至此我认为你已经抓到了概念。  
       

        glTexCoord1f (TmpShade); // 规定纹理的纵坐标当作这个色度值
        // 送顶点
        glVertex3fv (&polyData[i].Verts[j].Pos.X);
          }
      }

     glEnd ();      // 告诉OpenGL 完成绘制

     glDisable (GL_TEXTURE_1D);    // 1D 纹理不可用

       
    现在我们转移到轮廓之上。一个轮廓能以“它的相邻的边,一边为可见,另一边为不可见”定义。在OpenGL中,这是深度测试被规定小于或等于(GL_LEQUAL)当前值的地方,并且就在那时所有前面的面被精选。我们同样也要混合线条,以使它看起来不错:)
    那么,我们使混合可用并规定混合模式。我们告诉OpenGL与着色线条一样着色backfacing多边形,并且规定这些线条的宽度。我们精选所有前面多边形,并规定测试深度小于或等于当前的Z值。在这个线条的的颜色被规定后,我们从头到尾循环每一个多边形,绘制它的顶点。我们仅需忽略顶点位置,而不是法线或着色值因为我们需要的仅仅是轮廓。
      
       

     // 轮廓代码
     if (outlineDraw)      // 检查看是否我们需要绘制轮廓
     {
      glEnable (GL_BLEND);    // 使混合可用
      // 调整混合模式
      glBlendFunc (GL_SRC_ALPHA ,GL_ONE_MINUS_SRC_ALPHA);

      glPolygonMode (GL_BACK, GL_LINE);   // 绘制轮廓线
      glLineWidth (outlineWidth);    // 调整线宽

      glCullFace (GL_FRONT);    // 剔出前面的多边形

      glDepthFunc (GL_LEQUAL);    // 改变深度模式

      glColor3fv (&outlineColor[0]);   // 规定轮廓颜色

      glBegin (GL_TRIANGLES);    // 告诉OpenGL我们想要绘制什么

       for (i = 0; i < polyNum; i++)  // 从头到尾循环每一个多边形
       {
        for (j = 0; j < 3; j++)  // 从头到尾循环每一个顶点
        {
         // 送顶点
         glVertex3fv (&polyData[i].Verts[j].Pos.X);
        }
       }

      glEnd ();      // 告诉 OpenGL我们已经完成

       
    这样以后,我们就把它规定为以前的状态,然后退出  
       

      glDepthFunc (GL_LESS);    // 重置深度测试模式

      glCullFace (GL_BACK);    // 重置剔出背面多边形

      glPolygonMode (GL_BACK, GL_FILL);   // 重置背面多边形绘制方式

      glDisable (GL_BLEND);    //  混合不可用
     }
    }

       
    你现在看到Cel-Shading并非那样难。当然技术可以提高非常多。一个好的例子是游戏XIII http://www.nvidia.com/object/game_xiii.html,它使你认为你在一个卡通世界里。如果你想在卡通透视技术里达到更深层次,你可以浏览这本书实时透视这一章“Non-Photorealistic Rendering”。如果你更喜欢在WEB上读论文,在这里可以发现一大堆联接列表:http://www.red3d.com/cwr/npr/


       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/25 9:43:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客2
    发贴心情 
    Lesson 37
       
    Cel-Shading By Sami "MENTAL" Hamlaoui

    Seeing as people still e-mail me asking for source code to the article I wrote on GameDev.net a while ago, and seeing as the 2nd version of that article (with source for every API out there) isn't even close to being halfway finished, I've hacked together this tutorial for NeHe (that was actually going to be the original intention of the article) so all of you OpenGL gurus can play around with it. Sorry for the choice of model, but I've been playing Quake 2 extensivly recently... :)

    Note: The original article for this code can be found at: http://www.gamedev.net/reference/programming/features/celshading.

    This tutorial doesn't actually explain the theory, just the code. WHY it works can be found at the above link. Now for crying out loud STOP E-MAILING ME REQUESTS FOR SOURCE CODE!!!!

    Enjoy :).

    First of all, we need to include a few extra header files. The first one (math.h) is so we can use the sqrtf (square root) function, and the second (stdio.h) is for file access.   
       

    #include <math.h>      // Header File For The Math Library
    #include <stdio.h>      // Header File For The Standard I/O Library

       
    Now we are going to define a few structures to help store our data (saves having hundreds of arrays of floats). The first one is the tagMATRIX structure. If you look closely, you will see that we are storing the matrix as a 1D array of 16 floats as opposed to a 2D 4x4 array. This is down to how OpenGL stores it's matrices. If we used 4x4, the values would come out in the wrong order.   
       

    typedef struct tagMATRIX     // A Structure To Hold An OpenGL Matrix
    {
     float Data[16];      // We Use [16] Due To OpenGL's Matrix Format
    }
    MATRIX;

       
    Second up is the vector class. This simply stores a value for X, Y and Z.   
       

    typedef struct tagVECTOR     // A Structure To Hold A Single Vector
    {
     float X, Y, Z;      // The Components Of The Vector
    }
    VECTOR;

       
    Third, we have the vertex structure. Each vertex only needs it's normal and position (no texture co-ordinates). They MUST be stored in this order, or else when it comes to loading the file things will go horribly wrong (I found out the hard way :(. That'll teach me to hack my code to pieces.).   
       

    typedef struct tagVERTEX     // A Structure To Hold A Single Vertex
    {
     VECTOR Nor;      // Vertex Normal
     VECTOR Pos;      // Vertex Position
    }
    VERTEX;

       
    Finally, the polygon structure. I know this is a stupid way of storing vertexes, but for the sake of simplicity it works perfectly. Usually I would use an array of vertexes, an array of polygons, and contain the Index number of the 3 verts in the polygon structure, but this is easier to show you what's going on.   
       

    typedef struct tagPOLYGON     // A Structure To Hold A Single Polygon
    {
     VERTEX Verts[3];     // Array Of 3 VERTEX Structures
    }
    POLYGON;

       
    Pretty simple stuff here too. Look at the comments for an explaination of each variable.   
       

    bool  outlineDraw = true;    // Flag To Draw The Outline
    bool  outlineSmooth = false;   // Flag To Anti-Alias The Lines
    float  outlineColor[3] = { 0.0f, 0.0f, 0.0f };  // Color Of The Lines
    float  outlineWidth = 3.0f;    // Width Of The Lines

    VECTOR  lightAngle;     // The Direction Of The Light
    bool  lightRotate = false;   // Flag To See If We Rotate The Light

    float  modelAngle = 0.0f;    // Y-Axis Angle Of The Model
    bool     modelRotate = false;   // Flag To Rotate The Model

    POLYGON  *polyData = NULL;    // Polygon Data
    int  polyNum  = 0;    // Number Of Polygons

    GLuint  shaderTexture[1];    // Storage For One Texture

       
    This is as simple as model file formats get. The first few bytes store the number of polygons in the scene, and the rest of the file is an array of tagPOLYGON structures. Because of this, the data can be read in without any need to sort it into any particular order.   
       

    BOOL ReadMesh ()      // Reads The Contents Of The "model.txt" File
    {
     FILE *In = fopen ("Data\\model.txt", "rb");  // Open The File

     if (!In)
      return FALSE;     // Return FALSE If File Not Opened

     fread (&polyNum, sizeof (int), 1, In);   // Read The Header (i.e. Number Of Polygons)

     polyData = new POLYGON [polyNum];   // Allocate The Memory

     fread (&polyData[0], sizeof (POLYGON) * polyNum, 1, In);// Read In All Polygon Data

     fclose (In);      // Close The File

     return TRUE;      // It Worked
    }

       
    Some basic math functions now. The DotProduct calculates the angle between 2 vectors or planes, the Magnitude function calculates the length of the vector, and the Normalize function reduces the vector to a unit length of 1.   
       

    inline float DotProduct (VECTOR &V1, VECTOR &V2)  // Calculate The Angle Between The 2 Vectors
    {
     return V1.X * V2.X + V1.Y * V2.Y + V1.Z * V2.Z;  // Return The Angle
    }

    inline float Magnitude (VECTOR &V)    // Calculate The Length Of The Vector
    {
     return sqrtf (V.X * V.X + V.Y * V.Y + V.Z * V.Z); // Return The Length Of The Vector
    }

    void Normalize (VECTOR &V)     // Creates A Vector With A Unit Length Of 1
    {
     float M = Magnitude (V);    // Calculate The Length Of The Vector

     if (M != 0.0f)      // Make Sure We Don't Divide By 0
     {
      V.X /= M;     // Normalize The 3 Components
      V.Y /= M;
      V.Z /= M;
     }
    }

       
    This function rotates a vector using the matrix provided. Please note that it ONLY rotates the vector - it has nothing to do with the position of the vector. This is used when rotating normals to make sure that they stay pointing in the right direction when we calculate the lighting.   
       

    void RotateVector (MATRIX &M, VECTOR &V, VECTOR &D)  // Rotate A Vector Using The Supplied Matrix
    {
     D.X = (M.Data[0] * V.X) + (M.Data[4] * V.Y) + (M.Data[8]  * V.Z); // Rotate Around The X Axis
     D.Y = (M.Data[1] * V.X) + (M.Data[5] * V.Y) + (M.Data[9]  * V.Z); // Rotate Around The Y Axis
     D.Z = (M.Data[2] * V.X) + (M.Data[6] * V.Y) + (M.Data[10] * V.Z); // Rotate Around The Z Axis
    }

       
    The first major function of the engine, Initialize does exactly what is says. I've cut out a few lines of code as they are not needed in the explaination.   
       

    // Any GL Init Code & User Initialiazation Goes Here
    BOOL Initialize (GL_Window* window, Keys* keys)
    {

       
    These 3 variables are used to load the shader file. Line contains space for a single line in the text file, while shaderData stores the actual shader values. You may be wondering why we have 96 values instead of 32. Well, we need to convert the greyscale values to RGB so that OpenGL can use them. We can still store the values as greyscale, but we will simply use the same value for the R, G and B components when uploading the texture.   
       

     char Line[255];      // Storage For 255 Characters
     float shaderData[32][3];    // Storate For The 96 Shader Values

     FILE *In = NULL;     // File Pointer

       
    When drawing the lines, we want to make sure that they are nice and smooth. Initially this value is turned off, but by pressing the "2" key, it can be toggled on/off.   
       

     glShadeModel (GL_SMOOTH);    // Enables Smooth Color Shading
     glDisable (GL_LINE_SMOOTH);    // Initially Disable Line Smoothing

     glEnable (GL_CULL_FACE);    // Enable OpenGL Face Culling

       
    We disable OpenGL lighting because we do all of the lighting calculations ourself.   
       

     glDisable (GL_LIGHTING);    // Disable OpenGL Lighting

       
    Here is where we load the shader file. It is simply 32 floating point values stored as ASCII (for easy modification), each one on a seperate line.   
       

     In = fopen ("Data\\shader.txt", "r");   // Open The Shader File

     if (In)       // Check To See If The File Opened
     {
      for (i = 0; i < 32; i++)   // Loop Though The 32 Greyscale Values
      {
       if (feof (In))    // Check For The End Of The File
        break;

       fgets (Line, 255, In);   // Get The Current Line

       
    Here we convert the greyscale value into RGB, as described above.   
       

       // Copy Over The Value
       shaderData[i][0] = shaderData[i][1] = shaderData[i][2] = atof (Line);
      }

      fclose (In);     // Close The File
     }

     else
      return FALSE;     // It Went Horribly Horribly Wrong

       
    Now we upload the texture. At it clearly states, do not use any kind of filtering on the texture or else it will look odd, to say the least. GL_TEXTURE_1D is used because it is a 1D array of values.   
       

     glGenTextures (1, &shaderTexture[0]);   // Get A Free Texture ID

     glBindTexture (GL_TEXTURE_1D, shaderTexture[0]); // Bind This Texture. From Now On It Will Be 1D

     // For Crying Out Loud Don't Let OpenGL Use Bi/Trilinear Filtering!
     glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

     // Upload
     glTexImage1D (GL_TEXTURE_1D, 0, GL_RGB, 32, 0, GL_RGB , GL_FLOAT, shaderData);

       
    Now set the lighting direction. I've got it pointing down positive Z, which means it's going to hit the model face-on.   
       

     lightAngle.X = 0.0f;     // Set The X Direction
     lightAngle.Y = 0.0f;     // Set The Y Direction
     lightAngle.Z = 1.0f;     // Set The Z Direction

     Normalize (lightAngle);     // Normalize The Light Direction

       
    Load in the mesh from file (described above).   
       

     return ReadMesh ();     // Return The Value Of ReadMesh
    }

       
    The opposite of the above function, Deinitalize deletes the texture and polygon data created by Initalize and ReadMesh.   
       

    void Deinitialize (void)     // Any User DeInitialization Goes Here
    {
     glDeleteTextures (1, &shaderTexture[0]);  // Delete The Shader Texture

     delete [] polyData;     // Delete The Polygon Data
    }

       
    The main demo loop. All this does is process the input and update the angle. Controls are as follows:

    <SPACE> = Toggle rotation

    1 = Toggle outline drawing

    2 = Toggle outline anti-alaising

    <UP> = Increase line width

    <DOWM> = Decrease line width   
       

    void Update (DWORD milliseconds)    // Perform Motion Updates Here
    {
     if (g_keys->keyDown [' '] == TRUE)   // Is the Space Bar Being Pressed?
     {
      modelRotate = !modelRotate;   // Toggle Model Rotation On/Off

      g_keys->keyDown [' '] = FALSE;
     }

     if (g_keys->keyDown ['1'] == TRUE)   // Is The Number 1 Being Pressed?
     {
      outlineDraw = !outlineDraw;   // Toggle Outline Drawing On/Off

      g_keys->keyDown ['1'] = FALSE;
     }

     if (g_keys->keyDown ['2'] == TRUE)   // Is The Number 2 Being Pressed?
     {
      outlineSmooth = !outlineSmooth;   // Toggle Anti-Aliasing On/Off

      g_keys->keyDown ['2'] = FALSE;
     }

     if (g_keys->keyDown [VK_UP] == TRUE)   // Is The Up Arrow Being Pressed?
     {
      outlineWidth++;     // Increase Line Width

      g_keys->keyDown [VK_UP] = FALSE;
     }

     if (g_keys->keyDown [VK_DOWN] == TRUE)   // Is The Down Arrow Being Pressed?
     {
      outlineWidth--;     // Decrease Line Width

      g_keys->keyDown [VK_DOWN] = FALSE;
     }

     if (modelRotate)     // Check To See If Rotation Is Enabled
      modelAngle += (float) (milliseconds) / 10.0f; // Update Angle Based On The Clock
    }

       
    The function you've all been waiting for. The Draw function does everything - calculates the shade values, renders the mesh, renders the outline, and, well that's it really.   
       

    void Draw (void)
    {

       
    TmpShade is used to store the shader value for the current vertex. All vertex data is calculate at the same time, meaning that we only need to use a single variable that we can just keep reusing.

    The TmpMatrix, TmpVector and TmpNormal structures are also used to calculate the vertex data. TmpMatrix is set once at the start of the function and never changed until Draw is called again. TmpVector and TmpNormal on the other hand, change when another vertex is processed.   
       

     float TmpShade;      // Temporary Shader Value

     MATRIX TmpMatrix;     // Temporary MATRIX Structure
     VECTOR TmpVector, TmpNormal;    // Temporary VECTOR Structures

       
    Lets clear the buffers and matrix data.   
       

     glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Buffers
     glLoadIdentity ();     // Reset The Matrix

       
    The first check is to see if we want to have smooth outlines. If so, then we turn on anti-alaising. If not, we turn it off. Simple!   
       

     if (outlineSmooth)     // Check To See If We Want Anti-Aliased Lines
     {
      glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); // Use The Good Calculations
      glEnable (GL_LINE_SMOOTH);   // Enable Anti-Aliasing
     }

     else       // We Don't Want Smooth Lines
      glDisable (GL_LINE_SMOOTH);   // Disable Anti-Aliasing

       
    We then setup the viewport. We move the camera back 2 units, and then rotate the model by the angle. Note: because we moved the camera first, the model will rotate on the spot. If we did it the other way around, the model would rotate around the camera.

    We then grab the newly created matrix from OpenGL and store it in TmpMatrix.   
       

     glTranslatef (0.0f, 0.0f, -2.0f);   // Move 2 Units Away From The Screen
     glRotatef (modelAngle, 0.0f, 1.0f, 0.0f);  // Rotate The Model On It's Y-Axis

     glGetFloatv (GL_MODELVIEW_MATRIX, TmpMatrix.Data); // Get The Generated Matrix

       
    The magic begins. We first enable 1D texturing, and then enable the shader texture. This is to be used as a look-up table by OpenGL. We then set the color of the model (white). I chose white because it shows up the highlights and shading much better then other colors. I suggest that you don't use black :)   
       

     // Cel-Shading Code
     glEnable (GL_TEXTURE_1D);    // Enable 1D Texturing
     glBindTexture (GL_TEXTURE_1D, shaderTexture[0]); // Bind Our Texture

     glColor3f (1.0f, 1.0f, 1.0f);    // Set The Color Of The Model

       
    Now we start drawing the triangles. We look though each polygon in the array, and then in turn each of it's vertexes. The first step is to copy the normal information into a temporary structure. This is so we can rotate the normals, but still keep the original values preserved (no precision degradation).   
       

     glBegin (GL_TRIANGLES);     // Tell OpenGL That We're Drawing Triangles

      for (i = 0; i < polyNum; i++)   // Loop Through Each Polygon
      {
       for (j = 0; j < 3; j++)   // Loop Through Each Vertex
       {
        TmpNormal.X = polyData[i].Verts[j].Nor.X; // Fill Up The TmpNormal Structure With The
        TmpNormal.Y = polyData[i].Verts[j].Nor.Y; // Current Vertices' Normal Values
        TmpNormal.Z = polyData[i].Verts[j].Nor.Z;

       
    Second, we rotate the normal by the matrix grabbed from OpenGL earlier. We then normalize this so it doesn't go all screwy.   
       

        // Rotate This By The Matrix
        RotateVector (TmpMatrix, TmpNormal, TmpVector);

        Normalize (TmpVector);  // Normalize The New Normal

       
    Third, we get the dot product of the rotated normal and light direction (called lightAngle, because I forgot to change it from my old light class). We then clamp the value to the range 0-1 (from -1 to +1).   
       

        // Calculate The Shade Value
        TmpShade = DotProduct (TmpVector, lightAngle);

        if (TmpShade < 0.0f)
         TmpShade = 0.0f; // Clamp The Value to 0 If Negative

       
    Forth, we pass this value to OpenGL as the texture co-ordinate. The shader texture acts as a lookup table (the shader value being the index), which is (i think) the main reason why 1D textures were invented. We then pass the vertex's position to OpenGL, and repeat. And Repeat. And Repeat. And I think you get the idea.   
       

        glTexCoord1f (TmpShade); // Set The Texture Co-ordinate As The Shade Value
        // Send The Vertex Position
        glVertex3fv (&polyData[i].Verts[j].Pos.X);
          }
      }

     glEnd ();      // Tell OpenGL To Finish Drawing

     glDisable (GL_TEXTURE_1D);    // Disable 1D Textures

       
    Now we move onto the outlines. An outline can be defined as "an edge where one polygon is front facing, and the other is backfacing". In OpenGL, it's where the depth test is set to less than or equal to (GL_LEQUAL) the current value, and when all front faces are being culled. We also blend the lines in, to make it look nice :)

    So, we enable blending and set the blend mode. We tell OpenGL to render backfacing polygons as lines, and set the width of those lines. We cull all front facing polygons, and set the depth test to less than or equal to the current Z value. After this the color of the line is set, and we loop though each polygon, drawing its vertices. We only need to pass the vertex position, and not the normal or shade value because all we want is an outline.   
       

     // Outline Code
     if (outlineDraw)     // Check To See If We Want To Draw The Outline
     {
      glEnable (GL_BLEND);    // Enable Blending
      // Set The Blend Mode  
      glBlendFunc (GL_SRC_ALPHA ,GL_ONE_MINUS_SRC_ALPHA);

      glPolygonMode (GL_BACK, GL_LINE);  // Draw Backfacing Polygons As Wireframes
      glLineWidth (outlineWidth);   // Set The Line Width

      glCullFace (GL_FRONT);    // Don't Draw Any Front-Facing Polygons

      glDepthFunc (GL_LEQUAL);   // Change The Depth Mode

      glColor3fv (&outlineColor[0]);   // Set The Outline Color

      glBegin (GL_TRIANGLES);    // Tell OpenGL What We Want To Draw

       for (i = 0; i < polyNum; i++)  // Loop Through Each Polygon
       {
        for (j = 0; j < 3; j++)  // Loop Through Each Vertex
        {
         // Send The Vertex Position
         glVertex3fv (&polyData[i].Verts[j].Pos.X);
        }
       }

      glEnd ();     // Tell OpenGL We've Finished

       
    After this, we just set everything back to how it was before, and exit.   
       

      glDepthFunc (GL_LESS);    // Reset The Depth-Testing Mode

      glCullFace (GL_BACK);    // Reset The Face To Be Culled

      glPolygonMode (GL_BACK, GL_FILL);  // Reset Back-Facing Polygon Drawing Mode

      glDisable (GL_BLEND);    // Disable Blending
     }
    }

       
    Sami Hamlaoui (MENTAL)

    Jeff Molofee (NeHe)

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/25 9:43:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客3
    发贴心情 

    第三十八课


    按此在新窗口浏览图片从资源文件中载入图像:

    如何把图像数据保存到*.exe程序中,使用Windows的资源文件吧,它既简单又实用。

      
       
       
    欢迎来到NeHe教程第38课。离上节课的写作已经有些时日了,加上写了一整天的code,也许笔头已经开始生锈了 :)
    现在你已经学会了如何做方格贴图,如何读入bitmap及各种光栅图像...那么如何做三角形贴图,又如何在.exe文件中体现你的纹理呢?

    我每每被问及这两个问题,可是一旦你看到他们是多么简单,你就会大骂自己居然没有想到过 :)

    我不会事无巨细地解释每一个细节,只需给你一些抓图,就明白了。我将基于最新的code,请在主页"NeHeGL I Basecode"下或者这张网页最下面下载。

    首先,我们把图像加载入资源文件。我向大家已经知道怎么做了,只是,你忽略了几步,于是值得到一些无用的资源文件。里面有bitmap文件,却无法使用。

    还记得吧?我们使用Visual C++ 6.0 做的。如果你使用其它工具,这页教材关于资源的部分(尤其是那些图)完全不适用。

    * 暂时你只能用24bit BMP 图像。如果读8bit BMP文件要写很多额外的code。我很希望听到你们谁有更小的/更好的loader。我这里的读入8bit 和 24bit BMP 的code实在臃肿。用LoadImage就可以。


    按此在新窗口浏览图片


    打开文件,点击“插入”菜单,选“资源”


    按此在新窗口浏览图片
    然后选择你要插入的资源类型BITMAP文件,单击"插入"

    按此在新窗口浏览图片
    然后是文件窗口,进入DATA目录,选中三个图形文件(用Ctrl啦)然后点“读入”。注意文件类型是否正确。
    按此在新窗口浏览图片
    接下来会弹出三次警告(一个文件一次),说读入正确,但该文件不能被浏览或编辑,因为它有多于256种颜色。没什么的!
    按此在新窗口浏览图片


    一旦所有图形都调入,将会出现一个列表。每个图分配有一个ID,每个ID都是IDB_BITMAP打头的,然后数字1-3。你要是懒得改,就不用管它了。不过我们还都比较勤快!


    按此在新窗口浏览图片


    右健单击每个ID,选"属性",然后重命名,使之与文件名匹配。就像我图片上那样。


    按此在新窗口浏览图片


    接下来,选“文件--〉全部保存”。你刚刚创建一个新的资源文件,所以Windows会问你取什么名字。你随便拉,也可以叫"lesson38.rc" , 然后保存。
    到此为止,你有了一个资源文件,里面全是保存在硬盘上的Bitmap 图形文件,要使用这些文件,你还需要完成一系列步骤。


    按此在新窗口浏览图片


    接下来该把资源文件加到你自己的项目里面了。选“项目--〉添加到项目--〉文件”


    按此在新窗口浏览图片


    选择resorce.h文件和资源文件Lesson38.rc(用Ctrl)


    按此在新窗口浏览图片


    最后确认资源文件Lesson38.rc放入RESOURCE FILES文件夹。就像上面图片里那样,点击并拖入RESOURCE FILES文件夹就好了。
    移动之后选“文件--〉全部保存”,然后文件部分就好了。好多的图阿:)

    然后我们开始code的部分。下面一段最重要的一行是#include "resource.h".没有这行,编译的时候就会有无数未定义变量的错误。resource.h文件定义了资源文件里的对象。所以要从IDB_BUTTERFLY1里面读取数据的话,最好include这个头文件 !

        
       

    #include <windows.h>   
    #include <gl\gl.h>          
    #include <gl\glu.h>          
    #include <gl\glaux.h>          
    #include "NeHeGL.h"         
    #include "resource.h"          // 资源文件的头文件

    #pragma comment( lib, "opengl32.lib" )        
    #pragma comment( lib, "glu32.lib" )        
    #pragma comment( lib, "glaux.lib" )        

    GL_Window* g_window;
    Keys*  g_keys;

       
    下面一段第一行分配三个纹理所需空间,接下来的结构体用于保存关于约50个在屏幕上运动的物体的信息。
    tex将跟踪每个物体所用纹理,x是物体的x坐标,y是y坐标,z,z坐标,yi是一个随机数用来控制物体下落速度,
    spinz用来控制沿z轴的旋转,spinzi是另一个随机树,记录旋转速度。flap用来控制物体的翅膀(一会在解释这个)随机数fi控制翅膀拍打的速度。
      
       

    // 定义三个保存纹理变量的ID
    GLuint texture[3];           // 保存三个纹理

    struct object           // 定义一个物体
    {
     int   tex;          // 纹理值
     float x;           // 位置
     float y;          
     float z;          
     float yi;           // 速度
     float spinz;          // 沿Z轴旋转的角度和速度
     float spinzi;          
     float flap;          // 是否翻转三角形
     float fi;          
    };

    object obj[50];           // 创建50个物体

       
    下面一段代码是物体obj[loop]的初始化,loop从0到49(表示50个物体中的一个)。首先是随机纹理从0到2表示
    一个随机着色的蝴蝶。x坐标随机的取-17.0f到+17.0f之间的值,y取18.0f,也就是从屏幕的上面一点点开始,
    这样一开始时看不到物体的。z也是-10.0f到-40.f之间的随机数,spinzi取-1.0f到1.0f之间。flap取翅膀中心
    位置,为0.0f。最后拍打速度fi和下落速度yi也是随机的。
      
       

    void SetObject(int loop)         // 循环设置50个物体
    {
     obj[loop].tex=rand()%3;        // 纹理
     obj[loop].x=rand()%34-17.0f;       // 位置
     obj[loop].y=18.0f;         
     obj[loop].z=-((rand()%30000/1000.0f)+10.0f);      
     obj[loop].spinzi=(rand()%10000)/5000.0f-1.0f;      // 旋转
     obj[loop].flap=0.0f;         
     obj[loop].fi=0.05f+(rand()%100)/1000.0f;      
     obj[loop].yi=0.001f+(rand()%1000)/10000.0f;      
    }

       
    这回该到了最有意思的地方了。从资源文件中读入bitmap,转为纹理。hBMP是指向这个bitmap文件的指针,
    它将告诉我们的程序从哪里读取数据。BMP是一个bitmap结构体,我们把从资源文件中读取的数据保存在里面。
    第三行是告诉我们的程序我们将使用哪些ID:IDB_BUTTERFLY1,IDB_BUTTERFLY2,IDB_BUTTERFLY3。要用更多
    的图像的话,只需增加资源文件中的图像,并在Texture[]中增加新的ID。
      
       

    void LoadGLTextures()          // 资源文件中读入bitmap,转为纹理
    {
     HBITMAP hBMP;          // 位图句柄
     BITMAP BMP;          // 位图结构

     // 纹理句柄
     byte Texture[]={ IDB_BUTTERFLY1, IDB_BUTTERFLY2, IDB_BUTTERFLY3 };

       
    下面一行使用sizeof(Texture)来计算要创建多少个纹理。我们有3个ID,也就是3个纹理。  
       

     glGenTextures(sizeof(Texture), &texture[0]);       // 创建三个纹理
     for (int loop=0; loop<sizeof(Texture); loop++)      // 循环载入所有的位图
     {

       
    LoadImage需要如下参数:GetModuleHandle(NULL)-指向实例的句柄,MAKEINTRESOURCE(Texture[loop])-把Texture[loop]从整型转为一个资
    源值,也就是要读的图形文件。IMAGE_BITMAP-告诉我们要读的是一个bitmap文件。
    接下来两个参数(0,0)是读入图像的高度和宽度像素数,使用默认大小就设为0。
    最后一个参数(LR_CREATEDIBSECTION)返回DIB section bitmap??这是一个没有保存颜色信息的bitmap。也正是我们需要的。
    hBMP 指向从LoadImage()读入的bitmap数据。
      
       

      hBMP=(HBITMAP)LoadImage(GetModuleHandle(NULL),MAKEINTRESOURCE(Texture[loop]), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);

       
    检查指针hBMP是否有效,即指向有用数据。如果没有指向任何数据,也可以弹出错误提示。
    如果数据存在,用GetObject()从hBMP取得数据(sizeof(BMP))并存储在BMP中。
    glPixelStorei告诉OpenGL这些数据是以word alignments存储的,也就是每像素4字节。
    绑定纹理,设置滤波方式为GL_LINEAR_MIPMAP_LINEAR(又好又光滑),然后生成纹理。
    注意到我们使用BMP.bmWidth和BMP.bmHeight获取图像的高度和宽度。并用GL_BGR_EXT交换红蓝,实际使用的资源数据是从BMP.bmBits中取得的

    最后删除bitmap对象,释放所有与之相联系的系统资源空间。
      
       

      if (hBMP)         // 位图是否存在
      {         // 存在
       GetObject(hBMP,sizeof(BMP), &BMP);     // 获得位图
       glPixelStorei(GL_UNPACK_ALIGNMENT,4);    // 以四字节方式对其内存
       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_MIPMAP_LINEAR);
      // 创建纹理
       gluBuild2DMipmaps(GL_TEXTURE_2D, 3, BMP.bmWidth, BMP.bmHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, BMP.bmBits);
       DeleteObject(hBMP);      // 删除位图对象
      }
     }
    }

       
    init code没有什么新鲜的,只是增加了LoadGLTextures()调用上面的code。清屏的颜色是黑色,不进行深度检测,这样比较快。启用纹理映
    射和混色效果。
      
       

    BOOL Initialize (GL_Window* window, Keys* keys)       // 初始化
    {
     g_window = window;
     g_keys  = keys;

     LoadGLTextures();          //载入纹理

     glClearColor (0.0f, 0.0f, 0.0f, 0.5f);       // 设置背景
     glClearDepth (1.0f);         
     glDepthFunc (GL_LEQUAL);        
     glDisable(GL_DEPTH_TEST);         // 启用深度测试
     glShadeModel (GL_SMOOTH);        
     glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);    
     glEnable(GL_TEXTURE_2D);         // 启用2D纹理
     glBlendFunc(GL_ONE,GL_SRC_ALPHA);        // 使用混合
     glEnable(GL_BLEND);         
       
    初始化所有的物体  
       

     for (int loop=0; loop<50; loop++)
     {
      SetObject(loop);       
     }

     return TRUE;          // 成功返回
    }

    void Deinitialize (void)         
    {
    }

    void Update (DWORD milliseconds)         // 更新,执行动画
    {
     if (g_keys->keyDown [VK_ESCAPE] == TRUE)       // 按ESC退出
     {
      TerminateApplication (g_window);      
     }

     if (g_keys->keyDown [VK_F1] == TRUE)        // 按F1切换显示模式
     {
      ToggleFullscreen (g_window);       
     }
    }

       
    接下来看看绘制代码。在这部分我将讲解如何用尽可能简单的方式将一个图像映到两个三角形上。有些人认为有理由相信,一个图像到三角形
    上的单一映射是不可能的。
    实际上,你可以轻而易举地将图像映到任何形状的区域内。使得图像与边界匹配或者完全不考虑形式。根本没关系的。(译者:我想作者的意
    思是,从长方形到三角形的解析影射是不存在的,但不考虑那么多的话,任意形状之间的连续影射总是可以存在的。他说的使纹理与边界匹配
    ,大概是指某一种参数化的方法,简单地说使得扭曲最小。)
    首先清屏,循环润色50个蝴蝶对象。
      
       

    void Draw (void)          // 绘制场景
    {
     glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);     

     for (int loop=0; loop<50; loop++)       
     {

       
    调用glLoadIdentify()重置投影矩阵,然后选择对象的纹理。用glTranslatef()为蝴蝶定位,然后沿x轴旋转45度。使之向观众微略倾斜,这
    样比较有立体感。最后沿z轴旋转,蝴蝶就旋转下落了。
      
       

      glLoadIdentity ();        // 重置矩阵
      glBindTexture(GL_TEXTURE_2D, texture[obj[loop].tex]);    // 绑定纹理
      glTranslatef(obj[loop].x,obj[loop].y,obj[loop].z);    // 绘制物体
      glRotatef(45.0f,1.0f,0.0f,0.0f);      
      glRotatef((obj[loop].spinz),0.0f,0.0f,1.0f);     
       
    其实到三角形上的映射和到方形上并没有很大区别。只是你只有三个定点,要小心一点。
    下面的code中,我们将会值第一个三角形。从一个设想的方形的右上角开始,到左上角,再到左下角。润色的结果像下面这样:

    按此在新窗口浏览图片

    注意半个蝴蝶出现了。另外半个出现在第二个三角形里。同样地将三个纹理坐标与顶点坐标非别对应,这给出充分的信息定义一个三角形上的映射。
      
       

      glBegin(GL_TRIANGLES); 

       glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);   
       glTexCoord2f(0.0f,1.0f); glVertex3f(-1.0f, 1.0f, obj[loop].flap); 
       glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);   
       
    下面的code润色另一半。同上,只是我们的三角变成了从右上到左下,再到右下。

    按此在新窗口浏览图片


    第一个三角形的第二点和第二个三角形的第三点(也就是翅膀的尖端)在z方向往复运动(即z=-1.0f和1.0f之间),两个三角形沿着蝴蝶的身
    体折叠起来,产生拍打的效果,简易可行。
      
       


       glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);   
       glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);   
       glTexCoord2f(1.0f,0.0f); glVertex3f( 1.0f,-1.0f, obj[loop].flap); 

      glEnd();    

       
    下面一段通过从obj[loop].y中递减obj[loop].yi使蝴蝶自上而下运动。spinz值递增spinzi(可正可负)flap递增fi.fi的正负取决于翅膀向
    上还是向下运动。
      
       

      //移动,选择图像
      obj[loop].y-=obj[loop].yi;       
      obj[loop].spinz+=obj[loop].spinzi;      
      obj[loop].flap+=obj[loop].fi;       

       
    当蝴蝶向下运行时,需要检查是否越出屏幕,如果是,就调用SetObject(loop)来给蝴蝶赋新的纹理,新下落速度等。   
       

      if (obj[loop].y<-18.0f)        //判断是否超出了屏幕,如果是重置它
      {
       SetObject(loop);       
      }

       
    翅膀拍打的时候,还要检查flap是否小于-1.0f或大于1.0f,如果是,令fi=-fi,以改变运动方向。Sleep(15)是用来减缓运行速度,每帧15毫
    秒。在我朋友的机器上,这让蝴蝶疯狂的飞舞。不过我懒得改了:)
      
       

      if ((obj[loop].flap>1.0f) || (obj[loop].flap<-1.0f))    
      {
       obj[loop].fi=-obj[loop].fi;
      }
     }

     Sleep(15);          

     glFlush ();         
    }

       
    希望你在这一课学的开心。也希望通过这一课,从资源文件里读取纹理,和三角形映射的过程变得比较容易理解。我花五分钟又冲读了一遍,
    感觉还好。如果你还有什么问题,尽管问。我希望我的讲义尽可能好,因此期待您的任何回应。

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/25 9:44:00
     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客4
    发贴心情 
    Lesson 38
       
    Welcome to the 38th NeHe Productions Tutorial. It's been awhile since my last tutorial, so my writing may be a little rusty. That and the fact that I've been up for almost 24 hours working on the code :)

    So you know how to texture map a quad, and you know how to load bitmap images, tga's, etc. So how the heck do you texture map a Triangle? And what if you want to hide your textures in the .EXE file?

    The two questions I'm asked on a daily basis will soon be answered, and once you see how easy it is, you'll wonder why you never thought of the solution :)

    Rather than trying to explain everything in great detail I'm going to include a few screenshots, so you know exactly what it is I'm talking about. I will be using the latest basecode. You can download the code from the main page under the heading "NeHeGL I Basecode" or you can download the code at the end of this tutorial.

    The first thing we need to do is add the images to the resource file. Alot of you have already figured out how to do this, unfortunately, you miss a few steps along the way and end up with a useless resource file filled with bitmaps that you can't use.

    Remember, this tutorial was written in Visual C++ 6.0. If you're using anything other than Visual C++, the resource portion of this tutorial wont make sense (especially the screenshots).

    * Currently you can only use 24 bit BMP images. There is alot of extra code to load 8 bit BMP files. I'd love to hear from anyone that has a tiny / optimized BMP loader. The code I have right now to load 8 and 24 bit BMP's is a mess. Something that uses LoadImage would be nice.

    按此在新窗口浏览图片


    Open the project and click INSERT on the main menu. Once the INSERT menu has opened, select RESOURCE.


    按此在新窗口浏览图片

    You are now asked what type of resource you wish to import. Select BITMAP and click the IMPORT button.

    按此在新窗口浏览图片


    A file selection box will open. Browse to the DATA directory, and highlight all three images (Hold down the CTRL key while selecting each image). Once you have all three selected click the IMPORT button. If You do not see the bitmap files, make sure FILES OF TYPE at the bottom says ALL FILE (*.*).

    按此在新窗口浏览图片


    A warning will pop up three times (once for each image you imported). All it's telling you is that the image was imported fine, but the picture can't be viewed or edited because it has more than 256 colors. Nothing to worry about!

    按此在新窗口浏览图片


    Once all three images have been imported, a list will be displayed. Each bitmap has been assigned an ID. Each ID starts with IDB_BITMAP and then a number from 1 - 3. If you were lazy, you could leave the ID's and jump to the code. Lucky we're not lazy!

    按此在新窗口浏览图片


    Right click each ID, and select PROPERTIES. Rename each ID so that it matches the name of the original bitmap file. See the picture if you're not sure what I mean.


    按此在新窗口浏览图片

    Once you are done, select FILE from the main menu and then SAVE ALL because you have just created a new resource file, windows will ask you what you want to call the file. You can save the file with the default filename or you can rename it to lesson38.rc. Once you have decided on a name click SAVE.

    This is the point that most people make it to. You have a resource file. It's full of Bitmap images and it's been saved to the Hard Drive. To use the images, you need to complete a few more steps.


    按此在新窗口浏览图片

    The next thing you need to do is add the resource file to your current project. Select PROJECT from the main menu, ADD TO PROJECT, and then FILES.


    按此在新窗口浏览图片

    Select the resource.h file, and the resource file (Lesson38.rc). Hold down control to select more than one file, or add each file individually.


    按此在新窗口浏览图片

    The last thing to do is make sure the resource file (Lesson38.rc) was put in the RESOURCE FILES folder. As you can see in the picture above, it was put in the SOURCE FILES folder. Click it with your mouse and drag it down to the RESOURCE FILES folder.

    Once the resource file has been moved select FILE from the main menu and SAVE ALL. The hard part has been done! ...Way too many pictures :)

    So now we start on the code! The most important line in the section of code below is #include "resource.h". Without this line, you will get a bunch of undeclared identifier errors when you try to compile the code. The resource.h file declares the objects inside the resource file. So if you want to grab data from IDB_BUTTERFLY1 you had better remember to include the header file!   
       

    #include <windows.h>          // Header File For Windows
    #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
    #include "NeHeGL.h"          // Header File For NeHeGL
    #include "resource.h"          // Header File For Resource (*IMPORTANT*)

    #pragma comment( lib, "opengl32.lib" )        // Search For OpenGL32.lib While Linking
    #pragma comment( lib, "glu32.lib" )        // Search For GLu32.lib While Linking
    #pragma comment( lib, "glaux.lib" )        // Search For GLaux.lib While Linking

    #ifndef CDS_FULLSCREEN          // CDS_FULLSCREEN Is Not Defined By Some
    #define CDS_FULLSCREEN 4         // Compilers. By Defining It This Way,
    #endif            // We Can Avoid Errors

    GL_Window* g_window;
    Keys*  g_keys;

       
    The first line below sets aside space for the three textures we're going to make.

    The structure will be used to hold information about 50 different objects that we'll have moving around the screen.

    tex will keep track of which texture to use for the object. x is the xposition of the object, y is the y position of the object, z is the objects position on the z-axis, yi will be a random number used to control how fast the object falls. spinz will be used to rotate the object on it's z-axis as it falls, spinzi is another random number used to control how fast the object spins. flap will be used to control the objects wings (more on this later) and fi is a random value that controls how fast the wings flap.

    We create 50 instances of obj[ ] based on the object structure.   
       

    // User Defined Variables
    GLuint texture[3];          // Storage For 3 Textures

    struct object           // Create A Structure Called Object
    {
     int   tex;          // Integer Used To Select Our Texture
     float x;          // X Position
     float y;          // Y Position
     float z;          // Z Position
     float yi;          // Y Increase Speed (Fall Speed)
     float spinz;          // Z Axis Spin
     float spinzi;          // Z Axis Spin Speed
     float flap;          // Flapping Triangles :)
     float fi;          // Flap Direction (Increase Value)
    };

    object obj[50];           // Create 50 Objects Using The Object Structure

       
    The bit of code below assigns random startup values to object (obj[ ]) loop. loop can be any value from 0 - 49 (any one of the 50 objects).

    We start off with a random texture from 0 to 2. This will select a random colored butterfly.

    We assign a random x position from -17.0f to +17.0f. The starting y position will be 18.0f, which will put the object just above the screen so we can't see it right off the start.

    The z position is also a random value from -10.0f to -40.0f. The spinzi value is a random value from -1.0f to 1.0f. flap is set to 0.0f (which will be the center position for the wings).

    Finally, the flap speed (fi) and fall speed (yi) are also given a random value.   
       

    void SetObject(int loop)         // Sets The Initial Value Of Each Object (Random)
    {
     obj[loop].tex=rand()%3;         // Texture Can Be One Of 3 Textures
     obj[loop].x=rand()%34-17.0f;        // Random x Value From -17.0f To 17.0f
     obj[loop].y=18.0f;         // Set y Position To 18 (Off Top Of Screen)
     obj[loop].z=-((rand()%30000/1000.0f)+10.0f);      // z Is A Random Value From -10.0f To -40.0f
     obj[loop].spinzi=(rand()%10000)/5000.0f-1.0f;      // spinzi Is A Random Value From -1.0f To 1.0f
     obj[loop].flap=0.0f;         // flap Starts Off At 0.0f;
     obj[loop].fi=0.05f+(rand()%100)/1000.0f;      // fi Is A Random Value From 0.05f To 0.15f
     obj[loop].yi=0.001f+(rand()%1000)/10000.0f;      // yi Is A Random Value From 0.001f To 0.101f
    }

       
    Now for the fun part! Loading a bitmap from the resource file and converting it to a texture.

    hBMP is a pointer to our bitmap file. It will tell our program where to get the data from. BMP is a bitmap structure that we can fill with data from our resource file.

    We tell our program which ID's to use in the third line of code. We want to load IDB_BUTTEFLY1, IDB_BUTTEFLY2 and IDB_BUTTERFLY3. If you wish to add more images, add the image to the resource file, and add the ID to Texture[ ].   
       

    void LoadGLTextures()          // Creates Textures From Bitmaps In The Resource File
    {
     HBITMAP hBMP;          // Handle Of The Bitmap
     BITMAP BMP;          // Bitmap Structure

     // The ID Of The 3 Bitmap Images We Want To Load From The Resource File
     byte Texture[]={ IDB_BUTTERFLY1, IDB_BUTTERFLY2, IDB_BUTTERFLY3 };

       
    The line below uses sizeof(Texture) to figure out how many textures we want to build. We have 3 ID's in Texture[ ] so the value will be 3. sizeof(Texture) is also used for the main loop.   
       

     glGenTextures(sizeof(Texture), &texture[0]);      // Generate 3 Textures (sizeof(Texture)=3 ID's)
     for (int loop=0; loop<sizeof(Texture); loop++)      // Loop Through All The ID's (Bitmap Images)
     {

       
    LoadImage takes the following parameters: GetModuleHandle(NULL) - A handle to an instance. MAKEINTRESOURCE(Texture[loop]) - Converts an Integer Value (Texture[loop]) to a resource value (this is the image to load). IMAGE_BITMAP - Tells our program that the resource to load is a bitmap image.

    The next two parameters (0,0) are the desired height and width of the image in pixels. We want to use the default size so we set both to 0.

    The last parameter (LR_CREATEDIBSECTION) returns a DIB section bitmap, which is a bitmap without all the color information stored in the data. Exactly what we need.

    hBMP points to the bitmap data that is loaded by LoadImage( ).   
       

      hBMP=(HBITMAP)LoadImage(GetModuleHandle(NULL),MAKEINTRESOURCE(Texture[loop]), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);

       
    Next we check to see if the pointer (hBMP) actually points to data. If you wanted to add error checking, you could check hBMP and pop up a messagebox if there's no data.

    If data exists, we use GetObject( ) to grab all of the data (sizeof(BMP)) from hBMP and store it in our BMP (bitmap structure).

    glPixelStorei tells OpenGL that the data is stored in word alignments (4 bytes per pixel).

    We then bind to our texture, set the filtering to GL_LINEAR_MIPMAP_LINEAR (nice and smooth), and generate the texture.

    Notice that we use BMP.bmWidth and BMP.bmHeight to get the height and width of the bitmap. We also have to swap the Red and Blue colors using GL_BGR_EXT. The actual resource data is retreived from BMP.bmBits.

    The last step is to delete the bitmap object freeing all system resources associated with the object.   
       

      if (hBMP)         // Does The Bitmap Exist?
      {          // If So...
       GetObject(hBMP,sizeof(BMP), &BMP);     // Get The Object
                // hBMP: Handle To Graphics Object
                // sizeof(BMP): Size Of Buffer For Object Information
                // Buffer For Object Information
       glPixelStorei(GL_UNPACK_ALIGNMENT,4);     // Pixel Storage Mode (Word Alignment / 4 Bytes)
       glBindTexture(GL_TEXTURE_2D, texture[loop]);    // Bind Our Texture
       glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);  // Linear Filtering
       glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); // Mipmap Linear Filtering

       // Generate Mipmapped Texture (3 Bytes, Width, Height And Data From The BMP)
       gluBuild2DMipmaps(GL_TEXTURE_2D, 3, BMP.bmWidth, BMP.bmHeight, GL_BGR_EXT, GL_UNSIGNED_BYTE, BMP.bmBits);
       DeleteObject(hBMP);       // Delete The Bitmap Object
      }
     }
    }

       
    Nothing really fancy in the init code. We add LoadGLTextures() to call the code above. The screen clear color is black. Depth testing is disabled (cheap way to blend). We enable texture mapping, then set up and enable blending.   
       

    BOOL Initialize (GL_Window* window, Keys* keys)       // Any GL Init Code & User Initialiazation Goes Here
    {
     g_window = window;
     g_keys  = keys;

     // Start Of User Initialization
     LoadGLTextures();         // Load The Textures From Our Resource File

     glClearColor (0.0f, 0.0f, 0.0f, 0.5f);       // Black Background
     glClearDepth (1.0f);         // Depth Buffer Setup
     glDepthFunc (GL_LEQUAL);        // The Type Of Depth Testing (Less Or Equal)
     glDisable(GL_DEPTH_TEST);        // Disable Depth Testing
     glShadeModel (GL_SMOOTH);        // Select Smooth Shading
     glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);     // Set Perspective Calculations To Most Accurate
     glEnable(GL_TEXTURE_2D);        // Enable Texture Mapping
     glBlendFunc(GL_ONE,GL_SRC_ALPHA);       // Set Blending Mode (Cheap / Quick)
     glEnable(GL_BLEND);         // Enable Blending

       
    We need to initialize all 50 objects right off the start so they don't appear in the middle of the screen or all in the same location. The loop below does just that.   
       

     for (int loop=0; loop<50; loop++)       // Loop To Initialize 50 Objects
     {
      SetObject(loop);        // Call SetObject To Assign New Random Values
     }

     return TRUE;          // Return TRUE (Initialization Successful)
    }

    void Deinitialize (void)         // Any User DeInitialization Goes Here
    {
    }

    void Update (DWORD milliseconds)        // Perform Motion Updates Here
    {
     if (g_keys->keyDown [VK_ESCAPE] == TRUE)      // Is ESC Being Pressed?
     {
      TerminateApplication (g_window);      // Terminate The Program
     }

     if (g_keys->keyDown [VK_F1] == TRUE)       // Is F1 Being Pressed?
     {
      ToggleFullscreen (g_window);       // Toggle Fullscreen Mode
     }
    }

       
    Now for the drawing code. In this section I'll attempt to explain the easiest way to texture map a single image across two triangles. For some reason everyone seems to think it's near impossible to texture an image to a triangle.

    The truth is, you can texture an image to any shape you want. With very little effort. The image can match the shape or it can be a completely different pattern. It really doesn't matter.

    First things first... we clear the screen and set up a loop to render all 50 of our butterflies (objects).   
       

    void Draw (void)          // Draw The Scene
    {
     glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);     // Clear Screen And Depth Buffer

     for (int loop=0; loop<50; loop++)       // Loop Of 50 (Draw 50 Objects)
     {

       
    We call glLoadIdentity( ) to reset the modelview matrix. Then we select the texture that was assigned to our object (obj[loop].tex).

    We position the butterfly using glTranslatef() then rotate the buttefly 45 degrees on it's X axis. This tilts the butterfly a little more towards the viewer so it doesn't look like a flat 2D object.

    The final rotation spins the butterfly on it's z-axis which makes it spin as it falls down the screen.   
       

      glLoadIdentity ();        // Reset The Modelview Matrix
      glBindTexture(GL_TEXTURE_2D, texture[obj[loop].tex]);    // Bind Our Texture
      glTranslatef(obj[loop].x,obj[loop].y,obj[loop].z);    // Position The Object
      glRotatef(45.0f,1.0f,0.0f,0.0f);      // Rotate On The X-Axis
      glRotatef((obj[loop].spinz),0.0f,0.0f,1.0f);     // Spin On The Z-Axis

       
    Texturing a triangle is not all that different from texturing a quad. Just because you only have 3 vertices doesn't mean you can't texture a quad to your triangle. The only difference is that you need to be more aware of your texture coordinates.

    In the code below, we draw the first triangle. We start at the top right corner of an invisible quad. We then move left until we get to the top left corner. From there we go to the bottom left corner.

    The code below will render the following image:


    按此在新窗口浏览图片

    Notice that half the buttefly is rendered on the first triangle. The other half is rendered on the second triangle. The texture coordinates match up with the vertex coordinates and although there are only 3 texture coordinates, it's still enough information to tell OpenGL what portion of the image needs to be mapped to the triangle.   
       

      glBegin(GL_TRIANGLES);        // Begin Drawing Triangles
       // First Triangle
       glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);   // Point 1 (Top Right)
       glTexCoord2f(0.0f,1.0f); glVertex3f(-1.0f, 1.0f, obj[loop].flap); // Point 2 (Top Left)
       glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);   // Point 3 (Bottom Left)

       
    The code below renders the second half of the triangle. Same technique as above, but this time we render from the top right to the bottom left, then over to the bottom right.


    按此在新窗口浏览图片

    The second point of the first triangle and the third point of the second triangle move back and forth on the z-axis to create the illusion of flapping. What's really happening is that point is moving from -1.0f to 1.0f and then back, which causes the two triangles to bend in the center where the butterflies body is.

    If you look at the two pictures you will notice that points 2 and 3 are the tips of the wings. Creates a very nice flapping effect with minimal effort.   
       

       // Second Triangle
       glTexCoord2f(1.0f,1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);   // Point 1 (Top Right)
       glTexCoord2f(0.0f,0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);   // Point 2 (Bottom Left)
       glTexCoord2f(1.0f,0.0f); glVertex3f( 1.0f,-1.0f, obj[loop].flap); // Point 3 (Bottom Right)

      glEnd();         // Done Drawing Triangles

       
    The following bit of code moves the butterfly down the screen by subtracting obj[loop].yi from obj[loop].y. The butterfly spinz value is increased by spinzi (which can be a negative or positive value) and the wings are increased by fi. fi can also be a negative or positive direction depending on the direction we want the wings to flap.   
       

      obj[loop].y-=obj[loop].yi;       // Move Object Down The Screen
      obj[loop].spinz+=obj[loop].spinzi;      // Increase Z Rotation By spinzi
      obj[loop].flap+=obj[loop].fi;       // Increase flap Value By fi

       
    After moving the butterfly down the screen, we need to see if it's gone past the bottom of the screen (no longer visible). if it has, we call SetObject(loop) to assign the butterfly a new texture, new fall speed, etc.   
       

      if (obj[loop].y<-18.0f)        // Is Object Off The Screen?
      {
       SetObject(loop);       // If So, Reassign New Values
      }

       
    To make the wings flap, we check to see if the flap value is greater than or less than 1.0f and -1.0f. If the wing is greater than or less than those values, we change the flap direction by making fi=-fi.

    So if the wings were going up, and they hit 1.0f, fi will become a negative value which will make the wings go down.

    Sleep(15) has been added to slow the program down by 15 milliseconds. It ran insanely fast on a friends machine, and I was too lazy to modify the code to take advantage of the timer :)   
       

      if ((obj[loop].flap>1.0f) || (obj[loop].flap<-1.0f))    // Time To Change Flap Direction?
      {
       obj[loop].fi=-obj[loop].fi;      // Change Direction By Making fi = -fi
      }
     }

     Sleep(15);          // Create A Short Delay (15 Milliseconds)

     glFlush ();          // Flush The GL Rendering Pipeline
    }

       
    I hope you enjoyed the tutorial. Hopefully it makes loading textures from a resource a lot easier to understand, and texturing triangles a snap. I've reread this tutorial about 5 times now, and it seems easy enough, but if you're still having problems, let me know. As always, I want the tutorials to be the best that they can be, so feedback is greatly appreciated!

    Jeff Molofee (NeHe)

    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/25 9:45:00
     
     yqdrrjhan 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:3
      积分:70
      门派:XML.ORG.CN
      注册:2006/3/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给yqdrrjhan发送一个短消息 把yqdrrjhan加入好友 查看yqdrrjhan的个人资料 搜索yqdrrjhan在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看yqdrrjhan的博客5
    发贴心情 
    lz好强,瞻仰中~~~~
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/26 10:16:00
     
     yqdrrjhan 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:3
      积分:70
      门派:XML.ORG.CN
      注册:2006/3/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给yqdrrjhan发送一个短消息 把yqdrrjhan加入好友 查看yqdrrjhan的个人资料 搜索yqdrrjhan在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看yqdrrjhan的博客6
    发贴心情 
    lz能发些关于dx的教程吗,十分感谢~~~~
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/26 10:19:00
     
     长风万里 美女呀,离线,快来找我吧!
      
      
      等级:大一新生
      文章:3
      积分:77
      门派:W3CHINA.ORG
      注册:2007/10/28

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给长风万里发送一个短消息 把长风万里加入好友 查看长风万里的个人资料 搜索长风万里在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看长风万里的博客7
    发贴心情 
    谢谢你的教程~~~不知道还有会下面的lesson吗?
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/28 5:14:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2025/7/20 12:29:07

    本主题贴数7,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    230.469ms