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

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

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

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


    第19 20课源码


    按此在新窗口浏览图片粒子系统:

    你是否希望创建爆炸,喷泉,流星之类的效果。这一课将告诉你如何创建一个简单的例子系统,并用它来创建一种喷射的效果。

      
       
       
    欢迎来到第十九课.你已经学习了很多知识,并且现在想自己来实践.我将在这讲解一个新命令... 三角形带(我的理解就是画很多三角形来组合成我们要的形状),它非常容易使用,当画很多三角形的时候能加快你程序的运行速度.在本课中,我将会教你该如何做一个半复杂的微粒程序.一旦您了解微粒程序的原理后,在创建例如:火,烟,喷泉等效果将是很轻松的事情.我必须警告你!直到今天我从未写一个真正的粒子程序.我想写一个"出名"的复杂的粒子程序.我尝试过,但在我了解我不能控制所有点变疯狂之后我放弃了!!!你也许不相信我要告诉你的,但这个课程从头到尾都是我自己的想法.开始我没有一点想法,并且没有任何技术数据放在我的面前.我开始考虑粒子,突然我的脑袋装满想法(脑袋开启了??):给予每个粒子生命,任意变化颜色,速度,重力影响等等.来适应环境的变化,把每个粒子看成单一的从这个点运动到另一个点的颗粒.很快我完成了这个项目.我看看时钟然后有个想法突然出现.四个小时过去了!我偶尔记得已经停止喝咖啡,眨眼,但是4个小时...?尽管这个程序我觉得很棒,并象我想象的那么严密的运行,但它不可能是最好的粒子引擎,这个我不关心,只要他运行好就可以.并且我能把它运行在我的项目中.如果你是那种想了解透彻的人,那么你要花费很多时间在网络上查找资料并弄明白它.在程序中有很多小的代码会看起来很模糊:)本课教程所用的部分代码来自于Lesson1.但有很多新的代码,因此我将重写一些发生代码变化的部分(使它更容易了解).
    下面用到的代码来自于Lesson6,我将会增加5行新的代码在我们程序的前面.第一行"stdio.h"允许我们读文件中的数据.它和我们以前用在纹理映射当中是一样的.第二行定义了一些我们要在屏幕上显示的粒子的数目.告诉程序MAX_PARTICLES在这里的数值为1000.第三条行将不断分离的彩色的粒子栓牢在一起,并设置为默认情况.sp和rp用来确定空格键和返回键是否有按住.
      
       

    #define MAX_PARTICLES 1000  // 定义最大的粒子数
    bool rainbow=true;   // 是否为彩虹模式
    bool sp;    // 空格键是否被按下
    bool rp;    // 回车键是否被按下

       
    下面四行是复杂的变量.变量slowdown控制粒子移动的快慢.数值愈高,移动越慢.数值越底,移动越快.如果数值降低,粒子将快速的移动!粒子的速度影响它们在荧屏中移动的距离.记住速度慢的粒子不会射很远的.变量xspeed和yspeed控制尾部的方向.xspeed将会增加粒子在x轴上速度.如果xspeed是正值粒子将会向右边移动多.如果xspeed负价值,粒子将会向左边移动多.那个值越高,就向那个方向移动比较多.yspeed工作相同的方法,但是在y轴上.因为有其它的因素影响粒子的运动,所以我要说"多".xspeed和yspeed有助于在我们想要的方向上移动粒子.最后是变量zoom,我们用该变量移入或移出我们的屏幕.在粒子引擎里,有时可看见更多的图象,而且当接近你时很酷
      
       

    float slowdown=2.0f;   // 减速粒子
    float xspeed;    // X方向的速度
    float yspeed;    // Y方向的速度
    float zoom=-40.0f;   // 沿Z轴缩放

       
    我们定义了一个复杂的循环变量叫做Loop.我们用这变量预先定义粒子并在屏幕中画粒子.col用来给予粒子不同的颜色.delay用来控制在彩虹模式中圆的颜色变化.最后,我们设定一个存储空间(粒子纹理).我用纹理而不用点的重要原因是,点的速度慢,而且挺麻烦的.其次纹理很酷:)你用一个正方形的粒子,一张你脸的小图片,一张星星的图片等等.很好控制!  
       

    GLuint loop;    // 循环变量
    GLuint col;    // 当前的颜色
    GLuint delay;    // 彩虹效果延迟

       
    好!现在是有趣的东西.下段程序描述单一粒子结构,这是我们给予粒子的属性.我们用布尔型变量active开始,如果为true,我们的粒子为活跃的.如果为false则粒子为死的,此时我们就删除它.在程序中我没有使用活跃的,因为它很好的实现.变量life和fade来控制粒子显示多久以及显示时候的亮度.随着life数值的降低fade的数值也相应降低.这将导致一些粒子比其他粒子燃烧的时间长.
      
       

    typedef struct      // 创建粒子数据结构
    {
     bool active;     // 是否激活
     float life;     // 粒子生命
     float fade;     // 衰减速度

       
    变量r,g和b用来表示粒子的红色强度,绿色强度和蓝色强度.当r的值变成1.0f时粒子将会很红,当三个变量全为1.0f时则粒子将变成白色.
      
       

     float r;     // 红色值
     float g;     // 绿色值
     float b;     // 蓝色值

       
    变量x.y和z控制粒子在屏幕上显示的位置.x表示粒子在x轴上的位置.y表示y轴上的位置.z表示粒子z轴上的位置
      
       

     float x;     // X 位置
     float y;     // Y 位置
     float z;     // Z 位置

       
    下面三个变量很重要.这三个变量控制粒子在每个轴上移动的快慢和方向.如果xi是负价粒子将会向左移动,正值将会向右移动.如果yi是负值粒子将会向下移动,正值将向上.最后,如果zi负值粒子将会向荧屏内部移动,正植将移向观察者.   
       

     float xi;     // X 方向
     float yi;     // Y 方向
     float zi;     // Z 方向

       
    最后,另外3个变量!每一个变量可被看成加速度.如果xg正值时,粒子将会被拉倒右边,负值将拉向左边.所以如果粒子向左移动(负的)而我们给它一个正的加速度,粒子速度将变慢.最后将向反方向移动(高中物理).yg拉下或拉上.zg拉进或拉出屏幕.
      
       

     float xg;     // X 方向重力加速度
     float yg;     // Y 方向重力加速度
     float zg;     // Z 方向重力加速度

       
    结构的名字为particles.  
       

    }
    particles;      // 粒子数据结构

       
    下面我们创建一个数组叫particle.数组存储MAX_PARTICLES个元素.也就是说我们创建1000(MAX_PARTICLES)个粒子,存储空间为每个粒子提供相应的信息
      
       

    particles particle[MAX_PARTICLES];    // 保存1000个粒子的数组

       
    在颜色数组上我们减少一些代码来存储12中不同的颜色.对每一个颜色从0到11我们存储亮红,亮绿,和亮蓝.下面的颜色表里包含12个渐变颜色从红色到紫罗兰色  
       

    static GLfloat colors[12][3]=    // 彩虹颜色
    {
     {1.0f,0.5f,0.5f},{1.0f,0.75f,0.5f},{1.0f,1.0f,0.5f},{0.75f,1.0f,0.5f},
     {0.5f,1.0f,0.5f},{0.5f,1.0f,0.75f},{0.5f,1.0f,1.0f},{0.5f,0.75f,1.0f},
     {0.5f,0.5f,1.0f},{0.75f,0.5f,1.0f},{1.0f,0.5f,1.0f},{1.0f,0.5f,0.75f}
    };

       
    这段代码调用前面的代码载入位图,与前面的代码相同,只是位图的名称不同。载入一符名为Particle.bmp的位图  
       

     if (TextureImage[0]=LoadBMP("Data/Particle.bmp")) // 载入粒子纹理

       
    窗口改变大小的代码和前面一样,不需要改变   
       
       
    我们使用光滑的阴影,清除背景为黑色,关闭深度测试,绑定并映射纹理.启用映射位图后我们选择粒子纹理。唯一的改变就是禁用深度测试和初始化粒子  
       

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

       
    下面代码初始化每个粒子.我们从活跃的粒子开始.如果粒子不活跃,它在荧屏上将不出现,无论它有多少life.当我们使粒子活跃之後,我们给它life.我怀疑给粒子生命和颜色渐变的是否是最好的方法,但当它运行一次后,效果很好!life满值是1.0f.这也给粒子完整的光亮.
      
       

     for (loop=0;loop<MAX_PARTICLES;loop++)    //初始化所有的粒子
     {
      particle[loop].active=true;     // 使所有的粒子为激活状态
      particle[loop].life=1.0f;     // 所有的粒子生命值为最大

       
    我们通过给定的值来设定粒子退色快慢.每次粒子被拉的时候life随着fade而减小.结束的数值将是0~99中的任意一个,然后平分1000份来得到一个很小的浮点数.最后我们把结果加上0.003f来使fade速度值不为0
      
       

      particle[loop].fade=float(rand()%100)/1000.0f+0.003f;  // 随机生成衰减速率

       
    既然粒子是活跃的,而且我们又给它生命,下面将给它颜色数值.一开始,我们就想每个粒子有不同的颜色.我怎么做才能使每个粒子与前面颜色箱里的颜色一一对应那?数学很简单,我们用loop变量乘以箱子中颜色的数目与粒子最大值(MAX_PARTICLES)的余数.这样防止最后的颜色数值大于最大的颜色数值(12).举例:900*(12/900)=12.1000*(12/1000)=12,等等
      
       

      particle[loop].r=colors[loop*(12/MAX_PARTICLES)][0];  // 粒子的红色颜色
      particle[loop].g=colors[loop*(12/MAX_PARTICLES)][1];  // 粒子的绿色颜色
      particle[loop].b=colors[loop*(12/MAX_PARTICLES)][2];  // 粒子的蓝色颜色

       
    现在设定每个粒子移动的方向和速度.我们通过将结果乘于10.0f来创造开始时的爆炸效果.我们将会以任意一个正或负值结束.这个数值将以任意速度,任意方向移动粒子.  
       

      particle[loop].xi=float((rand()%50)-26.0f)*10.0f;  // 随机生成X轴方向速度
      particle[loop].yi=float((rand()%50)-25.0f)*10.0f;  // 随机生成Y轴方向速度
      particle[loop].zi=float((rand()%50)-25.0f)*10.0f;  // 随机生成Z轴方向速度

       
    最后,我们设定加速度的数值.不像一般的加速度仅仅把事物拉下,我们的加速度能拉出,拉下,拉左,拉右,拉前和拉后粒子.开始我们需要强大的向下加速度.为了达到这样的效果我们将xg设为0.0f.在x方向没有拉力.我们设yg为-0.8f来产生一个向下的拉力.如果值为正则拉向上.我们不希望粒子拉近或远离我们,所以将zg设为0.0f
      
       

      particle[loop].xg=0.0f;      // 设置X轴方向加速度为0
      particle[loop].yg=-0.8f;      //  设置Y轴方向加速度为-0.8
      particle[loop].zg=0.0f;      //  设置Z轴方向加速度为0
     }

       
    现在为有趣的部分.下面的部分是我们从哪里拉粒子,检查加速度等等.你要明白它是怎么实现的,因此仔细的看:)我们重置Modelview巨阵.在画粒子位置的时候用glVertex3f()命令来代替tranlations,这样在我们画粒子的时候不会改变modelview巨阵
      
       

    int DrawGLScene(GLvoid)        // 绘制粒子
    {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // 以黑色背景清楚
     glLoadIdentity();        // 重置模型变换矩阵

       
    我们开始创建一个循环loop.这个环将会更新每一个粒子.   
       

     for (loop=0;loop<MAX_PARTICLES;loop++)     // 循环所有的粒子
     {

       
    首先我们做的事物是检查粒子是否活跃.如果不活跃,则不被更新.在这个程序中,它们始终活跃.但是在你自己的程序中,你可能想要使某粒子不活跃   
       

      if (particle[loop].active)     // 如果粒子为激活的
      {

       
    下面三个变量是我们确定x,y和z位置的暂时变量.注意:在z的位置上我们加上zoom以便我们的场景在以前的基础上再移入zoom个位置.particle[loop].x告诉我们要画的x的位置.particle[loop].y告诉我们要画的y的位置.particle[loop].z告诉我们要画的z的位置   
       

       float x=particle[loop].x;    // 返回X轴的位置
       float y=particle[loop].y;    // 返回Y轴的位置
       float z=particle[loop].z+zoom;   // 返回Z轴的位置

       
    既然知道粒子位置,就能给粒子上色.particle[loop].r保存粒子的亮红,particle[loop].g保存粒子的亮绿,particle[loop].b保存粒子的亮蓝.注意我用alpha为粒子生命.当粒子要燃尽时,它会越来越透明直到它最后消失.这就是为什么粒子的生命不应该超过1.0f.如果你想粒子燃烧时间长,可降低fade减小的速度
      
       

       // 设置粒子颜色
       glColor4f(particle[loop].r,particle[loop].g,particle[loop].b,particle[loop].life);

       
    我们有粒子的位置,并设置颜色了.所以现在我们来画我们的粒子.我们用一个三角形带来代替一个四边形这样使程序运行快一点.很多3D card画三角形带比画四边形要快的多.有些3D card将四边形分成两个三角形,而有些不.所以我们按照我们自己的想法来,所以我们来画一个生动的三角形带
      
       

       glBegin(GL_TRIANGLE_STRIP);    // 绘制三角形带

       
      从红宝书引述:三角形带就是画一连续的三角形(三个边的多角形)使用vertices V0,V1,V2,然后V2,V1,V3(注意顺序),然后V2,V3,V4等等.画三角形的顺序一样才能保证三角形带为相同的表面.要求方向是很重要的,例如:剔除,最少用三点来画当第一个三角形使用vertices0,1和2被画.如果你看图片你将会理解用顶点0,1和2构造第一个三角形(顶端右边,顶端左边,底部的右边).第二个三角形用点vertices2,1和3构造.再一次,如果你注意图片,点vertices2,1和3构造第二个三角形(底部右边,顶端左边,底部左边).注意:两个三角形画点顺序相同.我看到很多的网站要求第二个三角形反方向画.这是不对的.Opengl从新整理顶点来保证所有的三角形为同一方向!注意:你在屏幕上看见的三角形个数是你叙述的顶点的个数减2.在程序中在我们有4个顶点,所以我们看见二个三角形
      
       

        glTexCoord2d(1,1); glVertex3f(x+0.5f,y+0.5f,z);
        glTexCoord2d(0,1); glVertex3f(x-0.5f,y+0.5f,z);
        glTexCoord2d(1,0); glVertex3f(x+0.5f,y-0.5f,z);
        glTexCoord2d(0,0); glVertex3f(x-0.5f,y-0.5f,z);
       
    最后我们告诉Opengl我们画完三角形带
      
       

       glEnd();

       
    现在我们能移动粒子.下面公式可能看起来很奇怪,其实很简单.首先我们取得当前粒子的x位置.然后把x运动速度加上粒子被减速1000倍后的值.所以如果粒子在x轴(0)上屏幕中心的位置,运动值(xi)为x轴方向+10(移动我们为右),而slowdown等于1,我们移向右边以10/(1*1000)或 0.01f速度.如果增加slowdown值到2我们只移动0.005f.希望能帮助你了解slowdown如何工作.那也是为什么用10.0f乘开始值来叫象素移动快速,创造一个爆发效果.y和z轴用相同的公式来计算附近移动粒子
      
       

       particle[loop].x+=particle[loop].xi/(slowdown*1000); // 更新X坐标的位置
       particle[loop].y+=particle[loop].yi/(slowdown*1000); // 更新Y坐标的位置
       particle[loop].z+=particle[loop].zi/(slowdown*1000); // 更新Z坐标的位置

       
    在计算出下一步粒子移到那里,开始考虑重力和阻力.在下面的第一行,将阻力(xg)和移动速度(xi)相加.我们的移动速度是10和阻力是1.每时每刻粒子都在抵抗阻力.第二次画粒子时,阻力开始作用,移动速度将会从10掉到9.第三次画粒子时,阻力再一次作用,移动速度降低到8.如果粒子燃烧为超过10次重画,它将会最后结束,并向相反方向移动.因为移动速度会变成负值.阻力同样使用于y和z移动速度   
       

       particle[loop].xi+=particle[loop].xg;   // 更新X轴方向速度大小
       particle[loop].yi+=particle[loop].yg;   // 更新Y轴方向速度大小
       particle[loop].zi+=particle[loop].zg;   // 更新Z轴方向速度大小

       
    下行将粒子的生命减少.如果我们不这么做,粒子无法烧尽.我们用粒子当前的life减去当前的fade值.每粒子都有不同的fade值,因此他们全部将会以不同的速度烧尽
      
       

       particle[loop].life-=particle[loop].fade;  // 减少粒子的生命值

       
    现在我们检查当生命为零的话粒子是否活着   
       

       if (particle[loop].life<0.0f)     // 如果粒子生命值小于0
       {

       
    如果粒子是小时(烧尽),我们将会使它复原.我们给它全值生命和新的衰弱速度.  
       

        particle[loop].life=1.0f;    // 产生一个新的粒子
        particle[loop].fade=float(rand()%100)/1000.0f+0.003f; // 随机生成衰减速率

       
    我们也重新设定粒子在屏幕中心放置.我们重新设定粒子的x,y和z位置为零  
       

        particle[loop].x=0.0f;     // 新粒子出现在屏幕的中央
        particle[loop].y=0.0f;     
        particle[loop].z=0.0f;     
       
    在粒子从新设置之后,将给它新的移动速度/方向.注意:我增加最大和最小值,粒子移动速度为从50到60的任意值,但是这次我们没将移动速度乘10.我们这次不想要一个爆发的效果,而要比较慢地移动粒子.也注意我把xspeed和x轴移动速度相加,y轴移动速度和yspeed相加.这个控制粒子的移动方向.  
       

        particle[loop].xi=xspeed+float((rand()%60)-32.0f); // 随机生成粒子速度
        particle[loop].yi=yspeed+float((rand()%60)-30.0f); 
        particle[loop].zi=float((rand()%60)-30.0f);  
       
    最后我们分配粒子一种新的颜色.变量col保存一个数字从1到11(12种颜色),我们用这个变量去找红,绿,蓝亮度在颜色箱里面.下面第一行表示红色的强度,数值保存在colors[col][0].所以如果col是0,红色的亮度就是1.0f.绿色的和蓝色的值用相同的方法读取.如果你不了解为什么红色亮度为1.0f那col就为0.我将一点点的解释.看着程序的最前面.找到那行:static GLfloat colors[12][3].注意:12行3列.三个数字的第一行是红色强度.第二行是绿色强度而且第三行是蓝色强度.[0],[1]和[2]下面描述的1st,2nd和3rd就是我刚提及的.如果col等于0,我们要看第一个组.11 是最後一个组(第12种颜色).
      
       

        particle[loop].r=colors[col][0];   // 设置粒子颜色
        particle[loop].g=colors[col][1];   
        particle[loop].b=colors[col][2];   
       }

       
    下行描述加速度的数值是多少.通过小键盘8号键,我们增加yg(y 地心引力)值.这引起向上的力.如果这个程序在循环外面,那么我们必须生成另一个循环做相同的工作,因此我们最好放在这里
      
       

       // 如果小键盘8被按住,增加Y轴方向的加速度
       if (keys[VK_NUMPAD8] && (particle[loop].yg<1.5f)) particle[loop].yg+=0.01f;

       
    这行是产生相反的效果.通过2号键,减小yg值,引起向下的力
      
       

       // 如果小键盘2被按住,减少Y轴方向的加速度
       if (keys[VK_NUMPAD2] && (particle[loop].yg>-1.5f)) particle[loop].yg-=0.01f;

       
    现在更改向右的拉力.如果按下6号键时增加向右的拉力.
      
       

       // 如果小键盘6被按住,增加X轴方向的加速度
       if (keys[VK_NUMPAD6] && (particle[loop].xg<1.5f)) particle[loop].xg+=0.01f;

       
    最后如果4号键被按下则增加向左的拉力.这些按键给了我们很酷的结果.举例来说:你可以用粒子造一条向上设的水流.通过增加向下的引力可以形成泉水
      
       

       // 如果小键盘4被按住,减少X轴方向的加速度
       if (keys[VK_NUMPAD4] && (particle[loop].xg>-1.5f)) particle[loop].xg-=0.01f;

       
    我仅仅为乐趣增加了一些代码.我的兄弟产生很棒的效果:)通过按住tab键所有粒子都回到屏幕中心.所有的粒子在从新开始运动,再产生一个大的爆发.在粒子变弱之后,你最初的效果会再一次出现
      
       

       if (keys[VK_TAB])      // 按Tab键,使粒子回到原点
       {
        particle[loop].x=0.0f;     
        particle[loop].y=0.0f;     
        particle[loop].z=0.0f;     
        particle[loop].xi=float((rand()%50)-26.0f)*10.0f; // 随机生成速度
        particle[loop].yi=float((rand()%50)-25.0f)*10.0f; 
        particle[loop].zi=float((rand()%50)-25.0f)*10.0f; 
       }
      }
        }
     return TRUE;         // 绘制完毕成功返回
    }

       
    代码KillGLWindow(),CreateGLWindow()和WndProc()中没有改变,所以我们直接跳到WinMain().我将重写代码  
       
       
    我喜欢简单的代码.在一行上不想包含很多东西,所以使代码象一个清洁工:)下面的代码检查"+"是否被按下.如果它和slowdown一起实现则slowdown减少0.01f.粒子就可以较快速地移动.
      
       

        if (keys[VK_ADD] && (slowdown>1.0f)) slowdown-=0.01f;  // 按+号,加速粒子

       
    下面的代码检查"-"是否被按下.如果它和slowdown一起实现则slowdown增加0.01f.粒子就可以较慢速地移动.我实质的极限是4.0f,我不想它太慢的运动,你可以随你的要求改变最大最小值  
       

        if (keys[VK_SUBTRACT] && (slowdown<4.0f)) slowdown+=0.01f; // 按-号,减速粒子

       
    下面的代码检测Page Up是否被按下.如果是,则zoom增加.从而导致粒子靠近我们  
       

        if (keys[VK_PRIOR]) zoom+=0.1f;  // 按Page Up键,让粒子靠近视点

       
    下行代码检测Page Down是否别按下,如果是,则zoom减小.从而导师粒子离开我们
      
       

        if (keys[VK_NEXT]) zoom-=0.1f;  // 按Page Down,让粒子远离视点

       
    下面的代码检验enter键是否被按下.如果是,并且没有被一直按着,我们将让计算机把rp变为true,然后我们固定彩虹模式.如果彩虹模式为true,将其变成false.如果为false,将其变成true.最后一行检测enter是否被释放,如果释放rp为false并告诉计算机该键不被按下
      
       

        if (keys[VK_RETURN] && !rp)  // 按住回车键,切换彩虹模式
        {
         rp=true;   
         rainbow=!rainbow;  
        }
        if (!keys[VK_RETURN]) rp=false;  
       
    下面程序有点乱.第一行检查space键是否被按下并没有没一直按着.并检查彩虹模式是否开始运行,如果是,检查delay是否大于25.delay是我创建的显示彩虹效果的数值.如果你曾经改变颜色结构,粒子将显示不同颜色.通过创建一个delay,在颜色改变之前,一组粒子将是一种颜色.如果space按下,彩虹运行,delay值大于25则颜色改变
      
       

        if ((keys[' '] && !sp) || (rainbow && (delay>25))) // 空格键,变换颜色
        {

       
    下面行是为了当space按下则彩虹关掉而设置的.如果我们不关掉彩虹模式,颜色会继续变化直到enter再被按下.也就是说人们按下space来代替enter是想叫粒子颜色自己变化  
       

         if (keys[' ']) rainbow=false; 

       
    如果space键被按下,或者彩虹模式已开始,并且delay大于25,我们叫计算机知道space键被按下通过叫sp为true.然后我们将delay设定回0以便它能在到25.最后我们增加col的值以便它通过颜色箱里面改变成另一个颜色.  
       

         sp=true;   
         delay=0;   
         col++;    
       
    如果颜色值大于11,我们把它重新设为零.如果我们不重新设定为零,程序将去找第13颜色.而我们只有12种颜色!寻找不存在的颜色将会导致程序瘫痪  
       

         if (col>11) col=0;
        }

       
    最后如果space键不被按下,我们将sp设为false。  
       

        if (!keys[' ']) sp=false;  // 如果释放空格键,记录这个状态

       
    现在对粒子增加一些控制.还记得我们从开始定义的2变量么?一个xspeed,一个yspeed.在粒子燃尽之后,我们给它新的移动速度且把新的速度加入到xspeed和yspeed中.这样当粒子被创建时将影响粒子的速度. 举例来说:粒子在x轴上的速度为5在y轴上的速度为0.当我们减少xspeed到-10,我们将以-10(xspeed)+5(最初的移动速度)的速度移动.这样我们将以5的速度向左移动.明白了么??无论如何,下面的代码检测UP是否被按下.如果它,yspeed将增加这将引起粒子向上运动.最大速度不超过200.速度在快就不好看了
      
       

        // 按上增加粒子Y轴正方向的速度
        if (keys[VK_UP] && (yspeed<200)) yspeed+=1.0f;

       
    这行检查Down键是否被按下,如果它是,yspeed将减少.这将引起粒子向下运动.再一次,最大速度为200  
       

        // 按下减少粒子Y轴正方向的速度
        if (keys[VK_DOWN] && (yspeed>-200)) yspeed-=1.0f;

       
    现在我们检查Right键是否被按下.如果它是..xspeed将被增加.粒子将移到右边.最大速度为200  
       

        // 按右增加粒子X轴正方向的速度
        if (keys[VK_RIGHT] && (xspeed<200)) xspeed+=1.0f;

       
    最后我们检查Left键是否被按下.如果是...你猜....xspeed被减小,粒子开始向左移动.最大速度为200  
       

        // 按左减少粒子X轴正方向的速度
        if (keys[VK_LEFT] && (xspeed>-200)) xspeed-=1.0f;

       
    最后我们要增加delay的数值.像我在前面所说,delay是控制彩色变化的  
       

        delay++;   // 增加彩虹模式的颜色切换延迟

       
    在课程中,我试着把所有细节都讲清楚,并且简单的了解粒子系统.这个粒子系统能在游戏产生例如火,水,雪,爆炸,流行等效果.程序能简单的修改参数来实现新的效果(例:烟花效果)


       收藏   分享  
    顶(0)
      




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

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

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客2
    发贴心情 
    Lesson 19
       
    Welcome to Tutorial 19. You've learned alot, and now you want to play. I will introduce one new command in this tutorial... The triangle strip. It's very easy to use, and can help speed up your programs when drawing alot of triangles.

    In this tutorial I will teach you how to make a semi-complex Particle Engine. Once you understand how particle engines work, creating effects such as fire, smoke, water fountains and more will be a piece of cake!

    I have to warn you however! Until today I had never written a particle engine. I had this idea that the 'famous' particle engine was a very complex piece of code. I've made attempts in the past, but usually gave up after I realized I couldn't control all the points without going crazy.

    You might not believe me when I tell you this, but this tutorial was written 100% from scratch. I borrowed no ones ideas, and I had no technical information sitting in front of me. I started thinking about particles, and all of a sudden my head filled with ideas (brain turning on?). Instead of thinking about each particle as a pixel that had to go from point 'A' to point 'B', and do this or that, I decided it would be better to think of each particle as an individual object responding to the environment around it. I gave each particle life, random aging, color, speed, gravitational influence and more.

    Soon I had a finished project. I looked up at the clock and realized aliens had come to get me once again. Another 4 hours gone! I remember stopping now and then to drink coffee and blink, but 4 hours... ?

    So, although this program in my opinion looks great, and works exactly like I wanted it to, it may not be the proper way to make a particle engine. I don't care personally, as long as it works well, and I can use it in my projects! If you are the type of person that needs to know you're conforming, then spend hours browsing the net looking for information. Just be warned. The few code snippits you do find may appear cryptic :)

    This tutorial uses the base code from lesson 1. There is alot of new code however, so I'll rewrite any section of code that contains changes (makes it easier to understand).

    Using the code from lesson 1, we'll add 5 new lines of code at the top of our program. The first line (stdio.h) allows us to read data from files. It's the same line we've added to previous tutorials the use texture mapping. The second line defines how many particles were going to create and display on the screen. Define just tells our program that MAX_PARTICLES will equal whatever value we specify. In this case 1000. The third line will be used to toggle 'rainbow mode' off and on. We'll set it to on by default. sp and rp are variables we'll use to prevent the spacebar or return key from rapidly repeating when held down.   
       

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

    #define MAX_PARTICLES 1000  // Number Of Particles To Create ( NEW )

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

    bool keys[256];   // Array Used For The Keyboard Routine
    bool active=TRUE;   // Window Active Flag Set To TRUE By Default
    bool fullscreen=TRUE;  // Fullscreen Flag Set To Fullscreen Mode By Default
    bool rainbow=true;   // Rainbow Mode? ( ADD )
    bool sp;    // Spacebar Pressed? ( ADD )
    bool rp;    // Return Key Pressed? ( ADD )

       
    The next 4 lines are misc variables. The variable slowdown controls how fast the particles move. The higher the number, the slower they move. The lower the number, the faster they move. If the value is set to low, the particles will move way too fast! The speed the particles travel at will affect how they move on the screen. Slow particles will not shoot out as far. Keep this in mind.

    The variables xspeed and yspeed allow us to control the direction of the tail. xspeed will be added to the current speed a particle is travelling on the x axis. If xspeed is a positive value our particle will be travelling more to the right. If xspeed is a negative value, our particle will travel more to the left. The higher the value, the more it travels in that direction. yspeed works the same way, but on the y axis. The reason I say 'MORE' in a specific direction is because other factors affect the direction our particle travels. xspeed and yspeed help to move the particle in the direction we want.

    Finally we have the variable zoom. We use this variable to pan into and out of our scene. With particle engines, it's nice to see more of the screen at times, and cool to zoom in real close other times.   
       

    float slowdown=2.0f;   // Slow Down Particles
    float xspeed;    // Base X Speed (To Allow Keyboard Direction Of Tail)
    float yspeed;    // Base Y Speed (To Allow Keyboard Direction Of Tail)
    float zoom=-40.0f;   // Used To Zoom Out

       
    Now we set up a misc loop variable called loop. We'll use this to predefine the particles and to draw the particles to the screen. col will be use to keep track of what color to make the particles. delay will be used to cycle through the colors while in rainbow mode.

    Finally, we set aside storage space for one texture (the particle texture). I decided to use a texture rather than OpenGL points for a few reasons. The most important reason is because points are not all that fast, and they look pretty blah. Secondly, textures are way more cool :) You can use a square particle, a tiny picture of your face, a picture of a star, etc. More control!   
       

    GLuint loop;    // Misc Loop Variable
    GLuint col;    // Current Color Selection
    GLuint delay;    // Rainbow Effect Delay
    GLuint texture[1];   // Storage For Our Particle Texture

       
    Ok, now for the fun stuff. The next section of code creates a structure describing a single particle. This is where we give the particle certain characteristics.

    We start off with the boolean variable active. If this variable is TRUE, our particle is alive and kicking. If it's FALSE our particle is dead or we've turned it off! In this program I don't use active, but it's handy to include.

    The variables life and fade control how long the particle is displayed, and how bright the particle is while it's alive. The variable life is gradually decreased by the value stored in fade. In this program that will cause some particles to burn longer than others.   
       

    typedef struct      // Create A Structure For Particle
    {
     bool active;     // Active (Yes/No)
     float life;     // Particle Life
     float fade;     // Fade Speed

       
    The variables r, g and b hold the red intensity, green intensity and blue intensity of our particle. The closer r is to 1.0f, the more red the particle will be. Making all 3 variables 1.0f will create a white particle.   
       

     float r;     // Red Value
     float g;     // Green Value
     float b;     // Blue Value

       
    The variables x, y and z control where the particle will be displayed on the screen. x holds the location of our particle on the x axis. y holds the location of our particle on the y axis, and finally z holds the location of our particle on the z axis.   
       

     float x;     // X Position
     float y;     // Y Position
     float z;     // Z Position

       
    The next three variables are important. These three variables control how fast a particle is moving on specific axis, and what direction to move. If xi is a negative value our particle will move left. Positive it will move right. If yi is negative our particle will move down. Positive it will move up. Finally, if zi is negative the particle will move into the screen, and postive it will move towards the viewer.   
       

     float xi;     // X Direction
     float yi;     // Y Direction
     float zi;     // Z Direction

       
    Lastly, 3 more variables! Each of these variables can be thought of as gravity. If xg is a positive value, our particle will pull to the right. If it's negative our particle will be pulled to the left. So if our particle is moving left (negative) and we apply a positive gravity, the speed will eventually slow so much that our particle will start moving the opposite direction. yg pulls up or down and zg pulls towards or away from the viewer.   
       

     float xg;     // X Gravity
     float yg;     // Y Gravity
     float zg;     // Z Gravity

       
    particles is the name of our structure.   
       

    }
    particles;      // Particles Structure

       
    Next we create an array called particle. This array will store MAX_PARTICLES. Translated into english we create storage for 1000 (MAX_PARTICLES) particles. This storage space will store the information for each individual particle.   
       

    particles particle[MAX_PARTICLES];   // Particle Array (Room For Particle Info)

       
    We cut back on the amount of code required for this program by storing our 12 different colors in a color array. For each color from 0 to 11 we store the red intensity, the green intensity, and finally the blue intensity. The color table below stores 12 different colors fading from red to violet.   
       

    static GLfloat colors[12][3]=    // Rainbow Of Colors
    {
     {1.0f,0.5f,0.5f},{1.0f,0.75f,0.5f},{1.0f,1.0f,0.5f},{0.75f,1.0f,0.5f},
     {0.5f,1.0f,0.5f},{0.5f,1.0f,0.75f},{0.5f,1.0f,1.0f},{0.5f,0.75f,1.0f},
     {0.5f,0.5f,1.0f},{0.75f,0.5f,1.0f},{1.0f,0.5f,1.0f},{1.0f,0.5f,0.75f}
    };

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

       
    Our bitmap loading code hasn't changed.   
       

    AUX_RGBImageRec *LoadBMP(char *Filename)  // Loads A Bitmap Image
    {
     FILE *File=NULL;    // File Handle
     if (!Filename)     // Make Sure A Filename Was Given
     {
      return NULL;    // If Not Return NULL
     }

     File=fopen(Filename,"r");   // Check To See If The File Exists
     if (File)     // Does The File Exist?
     {
      fclose(File);    // Close The Handle
      return auxDIBImageLoad(Filename); // Load The Bitmap And Return A Pointer
     }
     return NULL;     // If Load Failed Return NULL
    }

       
    This is the section of code that loads the bitmap (calling the code above) and converts it into a textures. Status is used to keep track of whether or not the texture was loaded and created.   
       

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

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

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

       
    Our texture loading code will load in our particle bitmap and convert it to a linear filtered texture.   
       

     if (TextureImage[0]=LoadBMP("Data/Particle.bmp")) // Load Particle Texture
     {
      Status=TRUE;     // Set The Status To TRUE
      glGenTextures(1, &texture[0]);   // Create One Textures

      glBindTexture(GL_TEXTURE_2D, texture[0]);
      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[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
     }

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

       
    The only change I made to the resize code was a deeper viewing distance. Instead of 100.0f, we can now view particles 200.0f units into the screen.   
       

    GLvoid ReSizeGLScene(GLsizei width, GLsizei height)  // Resize And Initialize The GL Window
    {
     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

     // Calculate The Aspect Ratio Of The Window
     gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,200.0f);  ( MODIFIED )

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

       
    If you're using the lesson 1 code, replace it with the code below. I've added code to load in our texture and set up blending for our particles.   
       

    int InitGL(GLvoid)        // All Setup For OpenGL Goes Here
    {
     if (!LoadGLTextures())       // Jump To Texture Loading Routine
     {
      return FALSE;       // If Texture Didn't Load Return FALSE
     }

       
    We enable smooth shading, clear our background to black, disable depth testing, blending and texture mapping. After enabling texture mapping we select our particle texture.   
       

     glShadeModel(GL_SMOOTH);      // Enables Smooth Shading
     glClearColor(0.0f,0.0f,0.0f,0.0f);     // Black Background
     glClearDepth(1.0f);       // Depth Buffer Setup
     glDisable(GL_DEPTH_TEST);      // Disables Depth Testing
     glEnable(GL_BLEND);       // Enable Blending
     glBlendFunc(GL_SRC_ALPHA,GL_ONE);     // Type Of Blending To Perform
     glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);   // Really Nice Perspective Calculations
     glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);     // Really Nice Point Smoothing
     glEnable(GL_TEXTURE_2D);      // Enable Texture Mapping
     glBindTexture(GL_TEXTURE_2D,texture[0]);    // Select Our Texture

       
    The code below will initialize each of the particles. We start off by activating each particle. If a particle is not active, it won't appear on the screen, no matter how much life it has.

    After we've made the particle active, we give it life. I doubt the way I apply life, and fade the particles is the best way, but once again, it works good! Full life is 1.0f. This also gives the particle full brightness.   
       

     for (loop=0;loop<MAX_PARTICLES;loop++)     // Initials All The Textures
     {
      particle[loop].active=true;     // Make All The Particles Active
      particle[loop].life=1.0f;     // Give All The Particles Full Life

       
    We set how fast the particle fades out by giving fade a random value. The variable life will be reduced by fade each time the particle is drawn. The value we end up with will be a random value from 0 to 99. We then divide it by 1000 so that we get a very tiny floating point value. Finally we then add .003 to the final result so that the fade speed is never 0.   
       

      particle[loop].fade=float(rand()%100)/1000.0f+0.003f;  // Random Fade Speed

       
    Now that our particle is active, and we've given it life, it's time to give it some color. For the initial effect, we want each particle to be a different color. What I do is make each particle one of the 12 colors that we've built in our color table at the top of this program. The math is simple. We take our loop variable and multiply it by the number of colors in our color table divided by the maximum number of particles (MAX_PARTICLES). This prevents the final color value from being higher than our max number of colors (12).

    Some quick examples: 900*(12/900)=12. 1000*(12/1000)=12, etc.   
       

      particle[loop].r=colors[loop*(12/MAX_PARTICLES)][0];  // Select Red Rainbow Color
      particle[loop].g=colors[loop*(12/MAX_PARTICLES)][1];  // Select Red Rainbow Color
      particle[loop].b=colors[loop*(12/MAX_PARTICLES)][2];  // Select Red Rainbow Color

       
    Now we'll set the direction that each particle moves, along with the speed. We're going to multiply the results by 10.0f to create a spectacular explosion when the program first starts.

    We'll end up with either a positive or negative random value. This value will be used to move the particle in a random direction at a random speed.   
       

      particle[loop].xi=float((rand()%50)-26.0f)*10.0f;  // Random Speed On X Axis
      particle[loop].yi=float((rand()%50)-25.0f)*10.0f;  // Random Speed On Y Axis
      particle[loop].zi=float((rand()%50)-25.0f)*10.0f;  // Random Speed On Z Axis

       
    Finally, we set the amount of gravity acting on each particle. Unlike regular gravity that just pulls things down, our gravity can pull up, down, left, right, forward or backward. To start out we want semi strong gravity pulling downwards. To do this we set xg to 0.0f. No pull left or right on the x plane. We set yg to -0.8f. This creates a semi-strong pull downwards. If the value was positive it would pull upwards. We don't want the particles pulling towards or away from us so we'll set zg to 0.0f.   
       

      particle[loop].xg=0.0f;      // Set Horizontal Pull To Zero
      particle[loop].yg=-0.8f;     // Set Vertical Pull Downward
      particle[loop].zg=0.0f;      // Set Pull On Z Axis To Zero
     }
     return TRUE;        // Initialization Went OK
    }

       
    Now for the fun stuff. The next section of code is where we draw the particle, check for gravity, etc. It's important that you understand what's going on, so please read carefully :)

    We reset the Modelview Matrix only once. We'll position the particles using the glVertex3f() command instead of using tranlations, that way we don't alter the modelview matrix while drawing our particles.   
       

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

       
    We start off by creating a loop. This loop will update each one of our particles.   
       

     for (loop=0;loop<MAX_PARTICLES;loop++)     // Loop Through All The Particles
     {

       
    First thing we do is check to see if the particle is active. If it's not active, it wont be updated. In this program they're all active, all the time. But in a program of your own, you may want to make certain particles inactive.   
       

      if (particle[loop].active)     // If The Particle Is Active
      {

       
    The next three variables x, y and z are temporary variables that we'll use to hold the particles x, y and z position. Notice we add zoom to the z position so that our scene is moved into the screen based on the value stored in zoom. particle[loop].x holds our x position for whatever particle we are drawing (particle loop). particle[loop].y holds our y position for our particle and particle[loop].z holds our z position.   
       

       float x=particle[loop].x;    // Grab Our Particle X Position
       float y=particle[loop].y;    // Grab Our Particle Y Position
       float z=particle[loop].z+zoom;    // Particle Z Pos + Zoom

       
    Now that we have the particle position, we can color the particle. particle[loop].r holds the red intensity of our particle, particle[loop].g holds our green intensity, and particle[loop].b holds our blue intensity. Notice I use the particles life for the alpha value. As the particle dies, it becomes more and more transparent, until it eventually doesn't exist. That's why the particles life should never be more than 1.0f. If you need the particles to burn longer, try reducing the fade speed so that the particle doesn't fade out as fast.   
       

       // Draw The Particle Using Our RGB Values, Fade The Particle Based On It's Life
       glColor4f(particle[loop].r,particle[loop].g,particle[loop].b,particle[loop].life);

       
    We have the particle position and the color is set. All that we have to do now is draw our particle. Instead of using a textured quad, I've decided to use a textured triangle strip to speed the program up a bit. Most 3D cards can draw triangles alot faster than they can draw quads. Some 3D cards will convert the quad to two triangles for you, but some don't. So we'll do the work ourselves. We start off by telling OpenGL we want to draw a triangle strip.   
       

       glBegin(GL_TRIANGLE_STRIP);    // Build Quad From A Triangle Strip

       
    Quoted directly from the red book: A triangle strip draws a series of triangles (three sided polygons) using vertices V0, V1, V2, then V2, V1, V3 (note the order), then V2, V3, V4, and so on. The ordering is to ensure that the triangles are all drawn with the same orientation so that the strip can correctly form part of a surface. Preserving the orientation is important for some operations, such as culling. There must be at least 3 points for anything to be drawn.

    So the first triangle is drawn using vertices 0, 1 and 2. If you look at the picture you'll see that vertex points 0, 1 and 2 do indeed make up the first triangle (top right, top left, bottom right). The second triangle is drawn using vertices 2, 1 and 3. Again, if you look at the picture, vertices 2, 1 and 3 create the second triangle (bottom right, top left, bottom left). Notice that both triangles are drawn with the same winding (counter-clockwise orientation). I've seen quite a few web sites that claim every second triangle is wound the opposite direction. This is not the case. OpenGL will rearrange the vertices to ensure that all of the triangles are wound the same way!

    There are two good reasons to use triangle strips. First, after specifying the first three vertices for the initial triangle, you only need to specify a single point for each additional triangle. That point will be combined with 2 previous vertices to create a triangle. Secondly, by cutting back the amount of data needed to create a triangle your program will run quicker, and the amount of code or data required to draw an object is greatly reduced.

    Note: The number of triangles you see on the screen will be the number of vertices you specify minus 2. In the code below we have 4 vertices and we see two triangles.   
       

        glTexCoord2d(1,1); glVertex3f(x+0.5f,y+0.5f,z); // Top Right
        glTexCoord2d(0,1); glVertex3f(x-0.5f,y+0.5f,z); // Top Left
        glTexCoord2d(1,0); glVertex3f(x+0.5f,y-0.5f,z); // Bottom Right
        glTexCoord2d(0,0); glVertex3f(x-0.5f,y-0.5f,z); // Bottom Left

       
    Finally we tell OpenGL that we are done drawing our triangle strip.   
       

       glEnd();      // Done Building Triangle Strip

       
    Now we can move the particle. The math below may look strange, but once again, it's pretty simple. First we take the current particle x position. Then we add the x movement value to the particle divided by slowdown times 1000. So if our particle was in the center of the screen on the x axis (0), our movement variable (xi) for the x axis was +10 (moving us to the right) and slowdown was equal to 1, we would be moving to the right by 10/(1*1000), or 0.01f. If we increase the slowdown to 2 we'll only be moving at 0.005f. Hopefully that helps you understand how slowdown works.

    That's also why multiplying the start values by 10.0f made the pixels move alot faster, creating an explosion.

    We use the same formula for the y and z axis to move the particle around on the screen.   
       

       particle[loop].x+=particle[loop].xi/(slowdown*1000); // Move On The X Axis By X Speed
       particle[loop].y+=particle[loop].yi/(slowdown*1000); // Move On The Y Axis By Y Speed
       particle[loop].z+=particle[loop].zi/(slowdown*1000); // Move On The Z Axis By Z Speed

       
    After we've calculated where to move the particle to next, we have to apply gravity or resistance. In the first line below, we do this by adding our resistance (xg) to the speed we are moving at (xi).

    Lets say our moving speed was 10 and our resistance was 1. Each time our particle was drawn resistance would act on it. So the second time it was drawn, resistance would act, and our moving speed would drop from 10 to 9. This causes the particle to slow down a bit. The third time the particle is drawn, resistance would act again, and our moving speed would drop to 8. If the particle burns for more than 10 redraws, it will eventually end up moving the opposite direction because the moving speed would become a negative value.

    The resistance is applied to the y and z moving speed the same way it's applied to the x moving speed.   
       

       particle[loop].xi+=particle[loop].xg;   // Take Pull On X Axis Into Account
       particle[loop].yi+=particle[loop].yg;   // Take Pull On Y Axis Into Account
       particle[loop].zi+=particle[loop].zg;   // Take Pull On Z Axis Into Account

       
    The next line takes some life away from the particle. If we didn't do this, the particle would never burn out. We take the current life of the particle and subtract the fade value for that particle. Each particle will have a different fade value, so they'll all burn out at different speeds.   
       

       particle[loop].life-=particle[loop].fade;  // Reduce Particles Life By 'Fade'

       
    Now we check to see if the particle is still alive after having life taken from it.   
       

       if (particle[loop].life<0.0f)     // If Particle Is Burned Out
       {

       
    If the particle is dead (burnt out), we'll rejuvenate it. We do this by giving it full life and a new fade speed.   
       

        particle[loop].life=1.0f;    // Give It New Life
        particle[loop].fade=float(rand()%100)/1000.0f+0.003f; // Random Fade Value

       
    We also reset the particles position to the center of the screen. We do this by resetting the x, y and z positions of the particle to zero.   
       

        particle[loop].x=0.0f;     // Center On X Axis
        particle[loop].y=0.0f;     // Center On Y Axis
        particle[loop].z=0.0f;     // Center On Z Axis

       
    After the particle has been reset to the center of the screen, we give it a new moving speed / direction. Notice I've increased the maximum and minimum speed that the particle can move at from a random value of 50 to a value of 60, but this time we're not going to multiply the moving speed by 10. We don't want an explosion this time around, we want slower moving particles.

    Also notice that I add xspeed to the x axis moving speed, and yspeed to the y axis moving speed. This gives us control over what direction the particles move later in the program.   
       

        particle[loop].xi=xspeed+float((rand()%60)-32.0f); // X Axis Speed And Direction
        particle[loop].yi=yspeed+float((rand()%60)-30.0f); // Y Axis Speed And Direction
        particle[loop].zi=float((rand()%60)-30.0f);  // Z Axis Speed And Direction

       
    Lastly we assign the particle a new color. The variable col holds a number from 0 to 11 (12 colors). We use this variable to look of the red, green and blue intensities in our color table that we made at the beginning of the program. The first line below sets the red (r) intensity to the red value stored in colors[col][0]. So if col was 0, the red intensity would be 1.0f. The green and blue values are read the same way.

    If you don't understand how I got the value of 1.0f for the red intensity if col is 0, I'll explain in a bit more detail. Look at the very top of the program. Find the line: static GLfloat colors[12][3]. Notice there are 12 groups of 3 number. The first of the three number is the red intensity. The second value is the green intensity and the third value is the blue intensity. [0], [1] and [2] below represent the 1st, 2nd and 3rd values I just mentioned. If col is equal to 0, we want to look at the first group. 11 is the last group (12th color).   
       

        particle[loop].r=colors[col][0];   // Select Red From Color Table
        particle[loop].g=colors[col][1];   // Select Green From Color Table
        particle[loop].b=colors[col][2];   // Select Blue From Color Table
       }

       
    The line below controls how much gravity there is pulling upward. By pressing 8 on the number pad, we increase the yg (y gravity) variable. This causes a pull upwards. This code is located here in the program because it makes our life easier by applying the gravity to all of our particles thanks to the loop. If this code was outside the loop we'd have to create another loop to do the same job, so we might as well do it here.   
       

       // If Number Pad 8 And Y Gravity Is Less Than 1.5 Increase Pull Upwards
       if (keys[VK_NUMPAD8] && (particle[loop].yg<1.5f)) particle[loop].yg+=0.01f;

       
    This line has the exact opposite affect. By pressing 2 on the number pad we decrease yg creating a stronger pull downwards.   
       

       // If Number Pad 2 And Y Gravity Is Greater Than -1.5 Increase Pull Downwards
       if (keys[VK_NUMPAD2] && (particle[loop].yg>-1.5f)) particle[loop].yg-=0.01f;

       
    Now we modify the pull to the right. If the 6 key on the number pad is pressed, we increase the pull to the right.   
       

       // If Number Pad 6 And X Gravity Is Less Than 1.5 Increase Pull Right
       if (keys[VK_NUMPAD6] && (particle[loop].xg<1.5f)) particle[loop].xg+=0.01f;

       
    Finally, if the 4 key on the number pad is pressed, our particle will pull more to the left. These keys give us some really cool results. For example, you can make a stream of particles shooting straight up in the air. By adding some gravity pulling downwards you can turn the stream of particles into a fountain of water!   
       

       // If Number Pad 4 And X Gravity Is Greater Than -1.5 Increase Pull Left
       if (keys[VK_NUMPAD4] && (particle[loop].xg>-1.5f)) particle[loop].xg-=0.01f;

       
    I added this bit of code just for fun. My brother thought the explosion was a cool effect :) By pressing the tab key all the particles will be reset back to the center of the screen. The moving speed of the particles will once again be multiplied by 10, creating a big explosion of particles. After the particles fade out, your original effect will again reappear.   
       

       if (keys[VK_TAB])      // Tab Key Causes A Burst
       {
        particle[loop].x=0.0f;     // Center On X Axis
        particle[loop].y=0.0f;     // Center On Y Axis
        particle[loop].z=0.0f;     // Center On Z Axis
        particle[loop].xi=float((rand()%50)-26.0f)*10.0f; // Random Speed On X Axis
        particle[loop].yi=float((rand()%50)-25.0f)*10.0f; // Random Speed On Y Axis
        particle[loop].zi=float((rand()%50)-25.0f)*10.0f; // Random Speed On Z Axis
       }
      }
        }
     return TRUE;         // Everything Went OK
    }

       
    The code in KillGLWindow(), CreateGLWindow() and WndProc() hasn't changed, so we'll skip down to WinMain(). I'll rewrite the entire section of code to make it easier to follow through the code.   
       

    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 Particle Tutorial",640,480,16,fullscreen))
     {
      return 0;      // Quit If Window Was Not Created
     }

       
    This is our first change to WinMain(). I've added some code to check if the user decide to run in fullscreen mode or windowed mode. If they decide to use fullscreen mode, I change the variable slowdown to 1.0f instead of 2.0f. You can leave this bit code out if you want. I added the code to speed up fullscreen mode on my 3dfx (runs ALOT slower than windowed mode for some reason).   
       

     if (fullscreen)       // Are We In Fullscreen Mode ( ADD )
     {
      slowdown=1.0f;      // Speed Up The Particles (3dfx Issue) ( ADD )
     }

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

       
    I was a little sloppy with the next bit of code. Usually I don't include everything on one line, but it makes the code look a little cleaner :)

    The line below checks to see if the + key on the number pad is being pressed. If it is and slowdown is greater than 1.0f we decrease slowdown by 0.01f. This causes the particles to move faster. Remember in the code above when I talked about slowdown and how it affects the speed at which the particles travel.   
       

        if (keys[VK_ADD] && (slowdown>1.0f)) slowdown-=0.01f;  // Speed Up Particles

       
    This line checks to see if the - key on the number pad is being pressed. If it is and slowdown is less than 4.0f we increase the value of slowdown. This causes our particles to move slower. I put a limit of 4.0f because I wouldn't want them to move much slower. You can change the minimum and maximum speeds to whatever you want :)   
       

        if (keys[VK_SUBTRACT] && (slowdown<4.0f)) slowdown+=0.01f; // Slow Down Particles

       
    The line below check to see if Page Up is being pressed. If it is, the variable zoom is increased. This causes the particles to move closer to us.   
       

        if (keys[VK_PRIOR]) zoom+=0.1f;  // Zoom In

       
    This line has the opposite effect. By pressing Page Down, zoom is decreased and the scene moves futher into the screen. This allows us to see more of the screen, but it makes the particles smaller.   
       

        if (keys[VK_NEXT]) zoom-=0.1f;  // Zoom Out

       
    The next section of code checks to see if the return key has been pressed. If it has and it's not being 'held' down, we'll let the computer know it's being pressed by setting rp to true. Then we'll toggle rainbow mode. If rainbow was true, it will become false. If it was false, it will become true. The last line checks to see if the return key was released. If it was, rp is set to false, telling the computer that the key is no longer being held down.   
       

        if (keys[VK_RETURN] && !rp)  // Return Key Pressed
        {
         rp=true;   // Set Flag Telling Us It's Pressed
         rainbow=!rainbow;  // Toggle Rainbow Mode On / Off
        }
        if (!keys[VK_RETURN]) rp=false;  // If Return Is Released Clear Flag

       
    The code below is a little confusing. The first line checks to see if the spacebar is being pressed and not held down. It also check to see if rainbow mode is on, and if so, it checks to see if the variable delay is greater than 25. delay is a counter I use to create the rainbow effect. If you were to change the color ever frame, the particles would all be a different color. By creating a delay, a group of particles will become one color, before the color is changed to something else.

    If the spacebar was pressed or rainbow is on and delay is greater than 25, the color will be changed!   
       

        if ((keys[' '] && !sp) || (rainbow && (delay>25))) // Space Or Rainbow Mode
        {

       
    The line below was added so that rainbow mode would be turned off if the spacebar was pressed. If we didn't turn off rainbow mode, the colors would continue cycling until the return key was pressed again. It makes sense that if the person is hitting space instead of return that they want to go through the colors themselves.   
       

         if (keys[' ']) rainbow=false; // If Spacebar Is Pressed Disable Rainbow Mode

       
    If the spacebar was pressed or rainbow mode is on, and delay is greater than 25, we'll let the computer know that space has been pressed by making sp equal true. Then we'll set the delay back to 0 so that it can start counting back up to 25. Finally we'll increase the variable col so that the color will change to the next color in the color table.   
       

         sp=true;   // Set Flag Telling Us Space Is Pressed
         delay=0;   // Reset The Rainbow Color Cycling Delay
         col++;    // Change The Particle Color

       
    If the color is greater than 11, we reset it back to zero. If we didn't reset col to zero, our program would try to find a 13th color. We only have 12 colors! Trying to get information about a color that doesn't exist would crash our program.   
       

         if (col>11) col=0;  // If Color Is To High Reset It
        }

       
    Lastly if the spacebar is no longer being pressed, we let the computer know by setting the variable sp to false.   
       

        if (!keys[' ']) sp=false;  // If Spacebar Is Released Clear Flag

       
    Now for some control over the particles. Remember that we created 2 variables at the beginning of our program? One was called xspeed and one was called yspeed. Also remember that after the particle burned out, we gave it a new moving speed and added the new speed to either xspeed or yspeed. By doing that we can influence what direction the particles will move when they're first created.

    For example. Say our particle had a moving speed of 5 on the x axis and 0 on the y axis. If we decreased xspeed until it was -10, we would be moving at a speed of -10 (xspeed) + 5 (original moving speed). So instead of moving at a rate of 10 to the right we'd be moving at a rate of -5 to the left. Make sense?

    Anyways. The line below checks to see if the up arrow is being pressed. If it is, yspeed will be increased. This will cause our particles to move upwards. The particles will move at a maximum speed of 200 upwards. Anything faster than that doesn't look to good.   
       

        // If Up Arrow And Y Speed Is Less Than 200 Increase Upward Speed
        if (keys[VK_UP] && (yspeed<200)) yspeed+=1.0f;

       
    This line checks to see if the down arrow is being pressed. If it is, yspeed will be decreased. This will cause the particles to move downward. Again, a maximum downward speed of 200 is enforced.   
       

        // If Down Arrow And Y Speed Is Greater Than -200 Increase Downward Speed
        if (keys[VK_DOWN] && (yspeed>-200)) yspeed-=1.0f;

       
    Now we check to see if the right arrow is being pressed. If it is, xspeed will be increased. This will cause the particles to move to the right. A maximum speed of 200 is enforced.   
       

        // If Right Arrow And X Speed Is Less Than 200 Increase Speed To The Right
        if (keys[VK_RIGHT] && (xspeed<200)) xspeed+=1.0f;

       
    Finally we check to see if the left arrow is being pressed. If it is... you guessed it... xspeed is decreased, and the particles start to move left. Maximum speed of 200 enforced.   
       

        // If Left Arrow And X Speed Is Greater Than -200 Increase Speed To The Left
        if (keys[VK_LEFT] && (xspeed>-200)) xspeed-=1.0f;

       
    The last thing we need to do is increase the variable delay. Like I said above, delay is used to control how fast the colors change when you're using rainbow mode.   
       

        delay++;   // Increase Rainbow Mode Color Cycling Delay Counter

       
    Like all the previous tutorials, make sure the title at the top of the window is correct.   
       

        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 Particle Tutorial",640,480,16,fullscreen))
         {
          return 0; // Quit If Window Was Not Created
         }
        }
       }
      }
     }
     // Shutdown
     KillGLWindow();      // Kill The Window
     return (msg.wParam);     // Exit The Program
    }

       
    In this lesson, I have tried to explain in as much detail, all the steps required to create a simple but impressive particle system. This particle system can be used in games of your own to create effects such as Fire, Water, Snow, Explosions, Falling Stars, and more. The code can easily be modified to handle more parameters, and new effects (fireworks for example).

    Thanks to Richard Nutman for suggesting that the particles be positioned with glVertex3f() instead of resetting the Modelview Matrix and repositioning each particle with glTranslatef(). Both methods are effective, but his method will reduce the amount of work the computer has to do before it draws each particle, causing the program to run even faster.

    Thanks to Antoine Valentim for suggesting triangle strips to help speed up the program and to introduce a new command to this tutorial. The feedback on this tutorial has been great, I appreciate it!

    I hope you enjoyed this tutorial. If you had any problems understanding it, or you've found a mistake in the tutorial please let me know. I want to make the best tutorials available. Your feedback is important!

    Jeff Molofee (NeHe)

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

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

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

    第二十课

    按此在新窗口浏览图片蒙板:

    到目前为止你已经学会如何使用alpha混合,把一个透明物体渲染到屏幕上了,但有的使用它看起来并不是那么的复合你的心意。使用蒙板技术,将会按照你蒙板的位置精确的绘制。

      
       
       
    欢迎来到第20课的教程,*.bmp图像被给各种操作系统所支持,因为它简单,所以可以很轻松的作为纹理图片加载它。知道现在,我们在把图像加载到屏幕上时没有擦除背景色,因为这样简单高效。但是效果并不总是很好。
    大部分情况下,把纹理混合到屏幕,纹理不是太少就是太多。当使用精灵时,我不希望背景从精灵的缝隙中透出光来;但在显示文字时,你希望文字的间隙可以显示背景色。

    由于以上原因,我们需要使用“掩模”。使用“掩模”需要两个步骤,首先我们在场景上放置黑白相间的纹理,白色代表透明部分,黑色代表不透明部分。接着我们使用一种特殊的混合方式,只有在黑色部分上的纹理才会显示在场景中。

    我只重写那些改变的地方,如果你做好了学习的准备,我们就上路吧。
      
       
       
    在这个程序里,我们使用7个全局变量。变量masking为一个布尔值,标志是否使用“掩模”。变量mp标志键M是否按下,变量sp标志空格是否按下。
    接着我们创建保存5个纹理标志的数组,loop为循环变量。变量roll使得纹理沿屏幕滚动。
      
       

    bool masking=TRUE;     // 是否使用“掩模”
    bool mp;      // 键M是否按下
    bool sp;      // 空格是否按下
    bool scene;      // 绘制那一个场景

    GLuint texture[5];     // 保存5个纹理标志
    GLuint loop;      // 循环变量

    GLfloat roll;      // 滚动纹理

       
    加载纹理代码基本没变,只是这里我们需要加载5个纹理  
       

    int LoadGLTextures()      
    {
     int Status=FALSE;      
     AUX_RGBImageRec *TextureImage[5];    // 创建保存5个纹理的数据结构
     memset(TextureImage,0,sizeof(void *)*5);   // 初始化

     if ((TextureImage[0]=LoadBMP("Data/logo.bmp")) &&  // 加载纹理0
         (TextureImage[1]=LoadBMP("Data/mask1.bmp")) &&  // 加载掩模纹理1,作为“掩模”使用
         (TextureImage[2]=LoadBMP("Data/image1.bmp")) &&  // 加载纹理1
         (TextureImage[3]=LoadBMP("Data/mask2.bmp")) &&  // 加载掩模纹理2,作为“掩模”使用
         (TextureImage[4]=LoadBMP("Data/image2.bmp")))  // 加载纹理2
     {
      Status=TRUE;      
      glGenTextures(5, &texture[0]);    // 创建5个纹理

      for (loop=0; loop<5; loop++)    // 循环加载5个纹理
      {
       glBindTexture(GL_TEXTURE_2D, texture[loop]);
       glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
       glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
       glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
        0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
      }
     }
     for (loop=0; loop<5; loop++)     
     {
      if (TextureImage[loop])     
      {
       if (TextureImage[loop]->data)   
       {
        free(TextureImage[loop]->data);  
       }
       free(TextureImage[loop]);   
      }
     }
     return Status;      
    }

       
    改变窗口大小和初始化OpenGL的函数没有变化
      
       
       
    现在到了最有趣的绘制部分了,我们从清楚背景色开始,接着把物体移入屏幕2个单位。  
       

    int DrawGLScene(GLvoid)  
    {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   
     glLoadIdentity();       
     glTranslatef(0.0f,0.0f,-2.0f);      // 物体移入屏幕2个单位
       
    下面一行,我们选择'logo'纹理。我们将要通过四边形把纹理映射到屏幕,并按照顶点的顺序设置纹理坐标。
    Jonathan Roy说OpenGL是一个基于顶点的图形系统,大部分你设置的参数是作为顶点的属性而记录的,纹理坐标就是这样一种属性。你只要简单的设置各个顶点的纹理坐标,OpenGL就自动帮你把多边形内部填充纹理,通过一个插值的过程。

    向前面几课一样,我们假定四边形面对我们,并把纹理坐标(0,0)绑定到左下角,(1,0)绑定到右下角,(1,1)绑定到右上角。给定这些设置,你应该能猜到四边形中间对应的纹理坐标为(0.5,0.5),但你自己并没有设置此处的纹理坐标!OpenGL为你做了计算。

    在这一课里,我们通过设置纹理坐标达到一种滚动纹理的目的。纹理坐标是被归一化的,它的范围从0.0-1.0,值0被映射到纹理的一边,值1被映射到纹理的另一边。超过1的值,纹理可以按照不同的方式被映射,这里我们设置为它将回绕道另一边并重复纹理。例如如果使用这样的映射方式,纹理坐标(0.3,0.5)和(1.3,0.5)被映射到同一个纹理坐标。在这一课里,我们将尝试一种无缝填充的效果。

    我们使用roll变量去设置纹理坐标,当它为0时,它把纹理的左下角映射到四边形的左下角。当它大于0时,把纹理的左上角映射到四边形的左下角,看起来的效果就是纹理沿四边形向上滚动。  
       

     glBindTexture(GL_TEXTURE_2D, texture[0]);    // 选择Logo纹理
     glBegin(GL_QUADS);       // 绘制纹理四边形
      glTexCoord2f(0.0f, -roll+0.0f); glVertex3f(-1.1f, -1.1f,  0.0f); 
      glTexCoord2f(3.0f, -roll+0.0f); glVertex3f( 1.1f, -1.1f,  0.0f); 
      glTexCoord2f(3.0f, -roll+3.0f); glVertex3f( 1.1f,  1.1f,  0.0f); 
      glTexCoord2f(0.0f, -roll+3.0f); glVertex3f(-1.1f,  1.1f,  0.0f); 
     glEnd();  
       
    启用混合和禁用深度测试  
       

     glEnable(GL_BLEND);       // 启用混合
     glDisable(GL_DEPTH_TEST);       // 禁用深度测试

       
    接下来我们需要根据masking的值设置是否使用“掩模”,如果是,则需要设置相应的混合系数。  
       

     if (masking)        // 是否启用“掩模”
     {

       
    如果启用了“掩模”,我们将要设置“掩模”的混合系数。一个“掩模”只是一幅绘制到屏幕的纹理图片,但只有黑色和白色。白色的部分代表透明,黑色的部分代表不透明。
    下面这个混合系数使得,任何对应“掩模”黑色的部分会变为黑色,白色的部分会保持原来的颜色。
      
       

      glBlendFunc(GL_DST_COLOR,GL_ZERO);     // 使用黑白“掩模”混合屏幕颜色
     }

       
    现在我们检查绘制那一个层,如果为True绘制第二个层,否则绘制第一个层  
       

     if (scene) 
     {

       
    为了不使它看起来显得非常大,我们把它移入屏幕一个单位,并把它按roll变量的值进行旋转(沿Z轴)。  
       

      glTranslatef(0.0f,0.0f,-1.0f);     // 移入屏幕一个单位
      glRotatef(roll*360,0.0f,0.0f,1.0f);     // 沿Z轴旋转

       
    接下我们检查masking的值来绘制我们的对象  
       

      if (masking)       // “掩模”是否打开
      {

       
    如果“掩模打开”,我们会把掩模绘制到屏幕。当我们完成这个操作时,将会看到一个镂空的纹理出现在屏幕上。  
       

       glBindTexture(GL_TEXTURE_2D, texture[3]);  // 选择第二个“掩模”纹理
       glBegin(GL_QUADS);     // 开始绘制四边形
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f); 
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f); 
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f,  1.1f,  0.0f); 
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f,  1.1f,  0.0f); 
       glEnd();      
      }

       
    当我们把“掩模”绘制到屏幕上后,接着我们变换混合系数。这次我们告诉OpenGL把任何黑色部分对应的像素复制到屏幕,这样看起来纹理就像被镂空一样帖子屏幕上。
    注意,我们在变换了混合模式后在选择的纹理。

    如果我们没有使用“掩模”,我们的图像将与屏幕颜色混合。
      
       

      glBlendFunc(GL_ONE, GL_ONE);    // 把纹理2复制到屏幕
      glBindTexture(GL_TEXTURE_2D, texture[4]);   // 选择第二个纹理
      glBegin(GL_QUADS);      // 绘制四边形
       glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f); 
       glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f); 
       glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f,  1.1f,  0.0f); 
       glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f,  1.1f,  0.0f); 
      glEnd();      
     }

       
    绘制第一层图像  
       

     else         
     {

       
    如果“掩模打开”,我们会把掩模绘制到屏幕。当我们完成这个操作时,将会看到一个镂空的纹理出现在屏幕上。  
       

      if (masking)       // “掩模”是否打开
      {

       
    如果“掩模打开”,我们会把掩模绘制到屏幕。当我们完成这个操作时,将会看到一个镂空的纹理出现在屏幕上。  
       

       glBindTexture(GL_TEXTURE_2D, texture[1]);  // 选择第一个“掩模”纹理
       glBegin(GL_QUADS);     // 开始绘制四边形
        glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f); 
        glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f); 
        glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f,  1.1f,  0.0f); 
        glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f,  1.1f,  0.0f); 
       glEnd();      
      }

       
    当我们把“掩模”绘制到屏幕上后,接着我们变换混合系数。这次我们告诉OpenGL把任何黑色部分对应的像素复制到屏幕,这样看起来纹理就像被镂空一样帖子屏幕上。
    注意,我们在变换了混合模式后在选择的纹理。

    如果我们没有使用“掩模”,我们的图像将与屏幕颜色混合。
      
       

      glBlendFunc(GL_ONE, GL_ONE);     // 把纹理1复制到屏幕
      glBindTexture(GL_TEXTURE_2D, texture[2]);    // 选择第一个纹理
      glBegin(GL_QUADS);       // 开始绘制四边形
       glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f); 
       glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f); 
       glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f,  1.1f,  0.0f); 
       glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f,  1.1f,  0.0f); 
      glEnd();   
     }

       
    接下来启用深度测试,禁用混合。  
       

     glEnable(GL_DEPTH_TEST);       // 启用深度测试
     glDisable(GL_BLEND);       // 禁用混合

       
    最后增加roll变量,如果大于1,把它的值减1。  
       

     roll+=0.002f;        // 增加纹理滚动变量
     if (roll>1.0f)        // 大于1则减1
     {
      roll-=1.0f;      
     }

     return TRUE;        // 成功返回
    }

       
    函数KillGLWindow(), CreateGLWindow() 和 WndProc() 没有改变。
      
       
       
    接下来在wWinMain,我们添加键盘控制函数。我们检查空格是否按下,如果是则设置sp变量为TRUE,sp变量用来切换场景。  
       

        if (keys[' '] && !sp)    // 空格键是否被按下?
        {
         sp=TRUE;    
         scene=!scene;    // 是则切换场景
        }

       
    如果空格键释放,记录下来  
       

        if (!keys[' '])     // 如果空格键释放,记录下来
        {
         sp=FALSE;    
        }

       
    我们检查M键是否按下,如果是则设置mp变量为TRUE,sp变量用来切换是否使用“掩模”  
       

        if (keys['M'] && !mp)    // M键是否被按下
        {
         mp=TRUE;    
         masking=!masking;    // 是则切换“掩模”
        }

       
    如果M键释放,记录下来  
       

        if (!keys['M'])     // 如果M键释放,记录下来
        {
         mp=FALSE;    
        }

       
    Eric Desrosiers指出,你也可以在载入的时候测试*.bmp图像中的每一个像素,如果你你想要透明的结果,你可以把颜色的alpha设置为0。对于其他的颜色,你可以把alpha设置为1。这个方法也能达到同样的效果,但需要一些额外的代码。
    在这课里,我们给你演示了一个简单的例子,它能高效的绘制一部分纹理而不使用alpha值。

    谢谢Rob Santa的想法和例子程序,我从没想到过这种方法。

    我希望你喜欢这个教程,如果你在理解上有任何问题或找到了任何错误,请我知道,我想做最好的教程,你的反馈是非常重要的。

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

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

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客4
    发贴心情 
    Lesson 20
       
    Welcome to Tutorial 20. The bitmap image format is supported on just about every computer, and just about every operating system. Not only is it easy to work with, it's very easy to load and use as a texture. Up until now, we've been using blending to place text and other images onto the screen without erasing what's underneath the text or image. This is effective, but the results are not always pretty.

    Most the time a blended texture blends in too much or not enough. When making a game using sprites, you don't want the scene behind your character shining through the characters body. When writing text to the screen you want the text to be solid and easy to read.

    That's where masking comes in handy. Masking is a two step process. First we place a black and white image of our texture on top of the scene. The white represents the transparent part of our texture. The black represents the solid part of our texture. Because of the type of blending we use, only the black will appear on the scene. Almost like a cookie cutter effect. Then we switch blending modes, and map our texture on top of the black cut out. Again, because of the blending mode we use, the only parts of our texture that will be copied to the screen are the parts that land on top of the black mask.

    I'll rewrite the entire program in this tutorial aside from the sections that haven't changed. So if you're ready to learn something new, let's begin!   
       

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

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

       
    We'll be using 7 global variables in this program. masking is a boolean variable (TRUE / FALSE) that will keep track of whether or not masking is turned on of off. mp is used to make sure that the 'M' key isn't being held down. sp is used to make sure that the 'Spacebar' isn't being held down and the variable scene will keep track of whether or not we're drawing the first or second scene.

    We set up storage space for 5 textures using the variable texture[5]. loop is our generic counter variable, we'll use it a few times in our program to set up textures, etc. Finally we have the variable roll. We'll use roll to roll the textures across the screen. Creates a neat effect! We'll also use it to spin the object in scene 2.   
       

    bool keys[256];     // Array Used For The Keyboard Routine
    bool active=TRUE;     // Window Active Flag Set To TRUE By Default
    bool fullscreen=TRUE;    // Fullscreen Flag Set To Fullscreen Mode By Default
    bool masking=TRUE;     // Masking On/Off
    bool mp;      // M Pressed?
    bool sp;      // Space Pressed?
    bool scene;      // Which Scene To Draw

    GLuint texture[5];     // Storage For Our Five Textures
    GLuint loop;      // Generic Loop Variable

    GLfloat roll;      // Rolling Texture

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

       
    The load bitmap code hasn't changed. It's the same as it was in lesson 6, etc.

    In the code below we create storage space for 5 images. We clear the space and load in all 5 bitmaps. We loop through each image and convert it into a texture for use in our program. The textures are stored in texture[0-4].   
       

    int LoadGLTextures()       // Load Bitmaps And Convert To Textures
    {
     int Status=FALSE;      // Status Indicator
     AUX_RGBImageRec *TextureImage[5];    // Create Storage Space For The Texture Data
     memset(TextureImage,0,sizeof(void *)*5);   // Set The Pointer To NULL

     if ((TextureImage[0]=LoadBMP("Data/logo.bmp")) &&  // Logo Texture
         (TextureImage[1]=LoadBMP("Data/mask1.bmp")) &&  // First Mask
         (TextureImage[2]=LoadBMP("Data/image1.bmp")) &&  // First Image
         (TextureImage[3]=LoadBMP("Data/mask2.bmp")) &&  // Second Mask
         (TextureImage[4]=LoadBMP("Data/image2.bmp")))  // Second Image
     {
      Status=TRUE;      // Set The Status To TRUE
      glGenTextures(5, &texture[0]);    // Create Five Textures

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

       
    The ReSizeGLScene() code hasn't changed so we'll skip over it.

    The Init code is fairly bare bones. We load in our textures, set the clear color, set and enable depth testing, turn on smooth shading, and enable texture mapping. Simple program so no need for a complex init :)   
       

    int InitGL(GLvoid)        // All Setup For OpenGL Goes Here
    {
     if (!LoadGLTextures())       // Jump To Texture Loading Routine
     {
      return FALSE;       // If Texture Didn't Load Return FALSE
     }

     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);     // Clear The Background Color To Black
     glClearDepth(1.0);       // Enables Clearing Of The Depth Buffer
     glEnable(GL_DEPTH_TEST);      // Enable Depth Testing
     glShadeModel(GL_SMOOTH);      // Enables Smooth Color Shading
     glEnable(GL_TEXTURE_2D);      // Enable 2D Texture Mapping
     return TRUE;        // Initialization Went OK
    }

       
    Now for the fun stuff. Our drawing code! We start off the same as usual. We clear the background color and the depth buffer. Then we reset the modelview matrix, and translate into the screen 2 units so that we can see our scene.   
       

    int DrawGLScene(GLvoid)        // Here's Where We Do All The Drawing
    {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   // Clear The Screen And The Depth Buffer
     glLoadIdentity();       // Reset The Modelview Matrix
     glTranslatef(0.0f,0.0f,-2.0f);      // Move Into The Screen 5 Units

       
    The first line below selects the 'logo' texture. We'll map the texture to the screen using a quad. We specify our four texture coordinates along with our four vertices.

    Revised Description by Jonathan Roy: Remember that OpenGL is a vertex-based graphic system. Most of the parameters you set are recorded as attributes of a particular vertex. Texture coordinate is one such attribute. You simply specify appropriate texture coordinates for each vertex of a polygon, and OpenGL automatically fills in the surface between the vertices with the texture, through a process known as interpolation. Interpolation is a standard geometric technique that lets OpenGL determine how a given parameter varies between vertices just by knowing the value that parameter takes at the vertices themselves.

    Like in the previous lessons, we pretend we are facing the quad and assign texture coordinates as follows: (0.0, 0.0) to the bottom-left corner, (0.0, 1.0) to the top-left corner, (1.0, 0.0) to the bottom-right, and (1.0, 1.0) to the top-right. Now, given these settings, can you tell what texture coordinates correspond to the quad's middle point? That's right, (0.5, 0.5). But no where in the code did you specify that coordinate, did you? When it draws the quad, OpenGL computes it for you. And the real magic is that it does so whatever the position, size, or orientation of the polygon!

    In this lesson we add another interesting twist by assigning texture coordinates with values other than 0.0 and 1.0. Texture coordinates are said to be normalized. Value 0.0 maps to one edge of the texture, while value 1.0 maps to the opposite edge, spanning the full width or height of the texture image in a one unit step, regardless of the polygon's size or the image's size in pixels (which we therefore don't have to worry about when doing texture mapping, and that makes life a whole lot easier). Above 1.0, the mapping simply wraps around at the other edge and the texture repeats. In other words, texture coordinate (0.3, 0.5) for instance, maps to the exact same pixel in the texture image as coordinate (1.3, 0.5), or as (12.3, -2.5). In this lesson, we achieve a tiling effect by specifying value 3.0 instead of 1.0, effectively repeating the texture nine times (3x3 tiling) over the surface of the quad.

    Additionally, we use the roll variable to translate (or slide) the texture over the surface of the quad. A value of 0.0 for roll, which is added to the vertical texture coordinate, means that texture mapping on the bottom edge of the quad begins at the bottom edge of the texture image, as shown in the figure on the left. When roll equals 0.5, mapping on the bottom edge of the quad begins halfway up in the image (see figure on the right). Rolling textures can be used to create great effects such as moving clouds, words spinning around an object, etc.   
       

     glBindTexture(GL_TEXTURE_2D, texture[0]);    // Select Our Logo Texture
     glBegin(GL_QUADS);       // Start Drawing A Textured Quad
      glTexCoord2f(0.0f, -roll+0.0f); glVertex3f(-1.1f, -1.1f,  0.0f); // Bottom Left
      glTexCoord2f(3.0f, -roll+0.0f); glVertex3f( 1.1f, -1.1f,  0.0f); // Bottom Right
      glTexCoord2f(3.0f, -roll+3.0f); glVertex3f( 1.1f,  1.1f,  0.0f); // Top Right
      glTexCoord2f(0.0f, -roll+3.0f); glVertex3f(-1.1f,  1.1f,  0.0f); // Top Left
     glEnd();        // Done Drawing The Quad

       
    Anyways... back to reality. Now we enable blending. In order for this effect to work we also have to disable depth testing. It's very important that you do this! If you do not disable depth testing you probably wont see anything. Your entire image will vanish!   
       

     glEnable(GL_BLEND);       // Enable Blending
     glDisable(GL_DEPTH_TEST);      // Disable Depth Testing

       
    The first thing we do after we enable blending and disable depth testing is check to see if we're going to mask our image or blend it the old fashioned way. The line of code below checks to see if masking is TRUE. If it is we'll set up blending so that our mask gets drawn to the screen properly.   
       

     if (masking)        // Is Masking Enabled?
     {

       
    If masking is TRUE the line below will set up blending for our mask. A mask is just a copy of the texture we want to draw to the screen but in black and white. Any section of the mask that is white will be transparent. Any sections of the mask that is black will be SOLID.

    The blend command below does the following: The Destination color (screen color) will be set to black if the section of our mask that is being copied to the screen is black. This means that sections of the screen that the black portion of our mask covers will turn black. Anything that was on the screen under the mask will be cleared to black. The section of the screen covered by the white mask will not change.   
       

      glBlendFunc(GL_DST_COLOR,GL_ZERO);    // Blend Screen Color With Zero (Black)
     }

       
    Now we check to see what scene to draw. If scene is TRUE we will draw the second scene. If scene is FALSE we will draw the first scene.   
       

     if (scene)        // Are We Drawing The Second Scene?
     {

       
    We don't want things to be too big so we translate one more unit into the screen. This reduces the size of our objects.

    After we translate into the screen, we rotate from 0-360 degrees depending on the value of roll. If roll is 0.0 we will be rotating 0 degrees. If roll is 1.0 we will be rotating 360 degrees. Fairly fast rotation, but I didn't feel like creating another variable just to rotate the image in the center of the screen. :)   
       

      glTranslatef(0.0f,0.0f,-1.0f);     // Translate Into The Screen One Unit
      glRotatef(roll*360,0.0f,0.0f,1.0f);    // Rotate On The Z Axis 360 Degrees

       
    We already have the rolling logo on the screen and we've rotated the scene on the Z axis causing any objects we draw to be rotated counter-clockwise, now all we have to do is check to see if masking is on. If it is we'll draw our mask then our object. If masking is off we'll just draw our object.   
       

      if (masking)       // Is Masking On?
      {

       
    If masking is TRUE the code below will draw our mask to the screen. Our blend mode should be set up properly because we had checked for masking once already while setting up the blending. Now all we have to do is draw the mask to the screen. We select mask 2 (because this is the second scene). After we have selected the mask texture we texture map it onto a quad. The quad is 1.1 units to the left and right so that it fills the screen up a little more. We only want one texture to show up so our texture coordinates only go from 0.0 to 1.0.

    after drawing our mask to the screen a solid black copy of our final texture will appear on the screen. The final result will look as if someone took a cookie cutter and cut the shape of our final texture out of the screen, leaving an empty black space.   
       

       glBindTexture(GL_TEXTURE_2D, texture[3]);  // Select The Second Mask Texture
       glBegin(GL_QUADS);     // Start Drawing A Textured Quad
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f); // Bottom Left
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f); // Bottom Right
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f,  1.1f,  0.0f); // Top Right
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f,  1.1f,  0.0f); // Top Left
       glEnd();      // Done Drawing The Quad
      }

       
    Now that we have drawn our mask to the screen it's time to change blending modes again. This time we're going to tell OpenGL to copy any part of our colored texture that is NOT black to the screen. Because the final texture is an exact copy of the mask but with color, the only parts of our texture that get drawn to the screen are parts that land on top of the black portion of the mask. Because the mask is black, nothing from the screen will shine through our texture. This leaves us with a very solid looking texture floating on top of the screen.

    Notice that we select the second image after selecting the final blending mode. This selects our colored image (the image that our second mask is based on). Also notice that we draw this image right on top of the mask. Same texture coordinates, same vertices.

    If we don't lay down a mask, our image will still be copied to the screen, but it will blend with whatever was on the screen.   
       

      glBlendFunc(GL_ONE, GL_ONE);     // Copy Image 2 Color To The Screen
      glBindTexture(GL_TEXTURE_2D, texture[4]);   // Select The Second Image Texture
      glBegin(GL_QUADS);      // Start Drawing A Textured Quad
       glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f); // Bottom Left
       glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f); // Bottom Right
       glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f,  1.1f,  0.0f); // Top Right
       glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f,  1.1f,  0.0f); // Top Left
      glEnd();       // Done Drawing The Quad
     }

       
    If scene was FALSE, we will draw the first scene (my favorite).   
       

     else         // Otherwise
     {

       
    We start off by checking to see if masking is TRUE of FALSE, just like in the code above.   
       

      if (masking)       // Is Masking On?
      {

       
    If masking is TRUE we draw our mask 1 to the screen (the mask for scene 1). Notice that the texture is rolling from right to left (roll is added to the horizontal texture coordinate). We want this texture to fill the entire screen that is why we never translated further into the screen.   
       

       glBindTexture(GL_TEXTURE_2D, texture[1]);  // Select The First Mask Texture
       glBegin(GL_QUADS);     // Start Drawing A Textured Quad
        glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f); // Bottom Left
        glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f); // Bottom Right
        glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f,  1.1f,  0.0f); // Top Right
        glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f,  1.1f,  0.0f); // Top Left
       glEnd();      // Done Drawing The Quad
      }

       
    Again we enable blending and select our texture for scene 1. We map this texture on top of it's mask. Notice we roll this texture as well, otherwise the mask and final image wouldn't line up.   
       

      glBlendFunc(GL_ONE, GL_ONE);     // Copy Image 1 Color To The Screen
      glBindTexture(GL_TEXTURE_2D, texture[2]);   // Select The First Image Texture
      glBegin(GL_QUADS);      // Start Drawing A Textured Quad
       glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f); // Bottom Left
       glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f); // Bottom Right
       glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f,  1.1f,  0.0f); // Top Right
       glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f,  1.1f,  0.0f); // Top Left
      glEnd();       // Done Drawing The Quad
     }

       
    Next we enable depth testing, and disable blending. This prevents strange things from happening in the rest of our program :)   
       

     glEnable(GL_DEPTH_TEST);      // Enable Depth Testing
     glDisable(GL_BLEND);       // Disable Blending

       
    Finally all we have left to do is increase the value of roll. If roll is greater than 1.0 we subtract 1.0. This prevents the value of roll from getting to high.   
       

     roll+=0.002f;        // Increase Our Texture Roll Variable
     if (roll>1.0f)        // Is Roll Greater Than One
     {
      roll-=1.0f;       // Subtract 1 From Roll
     }

     return TRUE;        // Everything Went OK
    }

       
    The KillGLWindow(), CreateGLWindow() and WndProc() code hasn't changed so we'll skip over it.

    The first thing you will notice different in the WinMain() code is the Window title. It's now titled "NeHe's Masking Tutorial". Change it to whatever you want :)   
       

    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 Masking Tutorial",640,480,16,fullscreen))
     {
      return 0;       // Quit If Window Was Not Created
     }

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

       
    Now for our simple key handling code. We check to see if the spacebar is being pressed. If it is, we set the sp variable to TRUE. If sp is TRUE, the code below will not run a second time until the spacebar has been released. This keeps our program from flipping back and forth from scene to scene very rapidly. After we set sp to TRUE, we toggle the scene. If it was TRUE, it becomes FALSE, if it was FALSE it becomes TRUE. In our drawing code above, if scene is FALSE the first scene is drawn. If scene is TRUE the second scene is drawn.   
       

        if (keys[' '] && !sp)    // Is Space Being Pressed?
        {
         sp=TRUE;    // Tell Program Spacebar Is Being Held
         scene=!scene;    // Toggle From One Scene To The Other
        }

       
    The code below checks to see if we have released the spacebar (if NOT ' '). If the spacebar has been released, we set sp to FALSE letting our program know that the spacebar is NOT being held down. By setting sp to FALSE the code above will check to see if the spacebar has been pressed again, and if so the cycle will start over.   
       

        if (!keys[' '])     // Has Spacebar Been Released?
        {
         sp=FALSE;    // Tell Program Spacebar Has Been Released
        }

       
    The next section of code checks to see if the 'M' key is being pressed. If it is being pressed, we set mp to TRUE, telling our program not to check again until the key is released, and we toggle masking from TRUE to FALSE or FALSE to TRUE. If masking is TRUE, the drawing code will turn on masking. If it is FALSE masking will be off. If masking is off, the object will be blended to the screen using the old fashioned blending we've been using up until now.   
       

        if (keys['M'] && !mp)    // Is M Being Pressed?
        {
         mp=TRUE;    // Tell Program M Is Being Held
         masking=!masking;   // Toggle Masking Mode OFF/ON
        }

       
    The last bit of code checks to see if we've stopped pressing 'M'. If we have, mp becomes FALSE letting the program know that we are no longer holding the 'M' key down. Once the 'M' key has been released, we are able to press it once again to toggle masking on or off.   
       

        if (!keys['M'])     // Has M Been Released?
        {
         mp=FALSE;    // Tell Program That M Has Been Released
        }

       
    Like all the previous tutorials, make sure the title at the top of the window is correct.   
       

        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 Masking Tutorial",640,480,16,fullscreen))
         {
          return 0; // Quit If Window Was Not Created
         }
        }
       }
      }
     }
     // Shutdown
     KillGLWindow();      // Kill The Window
     return (msg.wParam);     // Exit The Program
    }

       
    Creating a mask isn't to hard. A little time consuming. The best way to make a mask if you already have your image made is to load your image into an art program or a handy program like infranview, and reduce it to a gray scale image. After you've done that, turn the contrast way up so that gray pixels become black. You can also try turning down the brightness, etc. It's important that the white is bright white, and the black is pure black. If you have any gray pixels in your mask, that section of the image will appear transparent. The most reliable way to make sure your mask is a perfect copy of your image is to trace over the image with black. It's also very important that your image has a BLACK background and the mask has a WHITE background! If you create a mask and notice a square shape around your texture, either your white isn't bright enough (255 or FFFFFF) or your black isn't true black (0 or 000000). Below you can see an example of a mask and the image that goes over top of the mask. the image can be any color you want as long as the background is black. The mask must have a white background and a black copy of your image.

    This is the mask ->  This is the image ->  

    Eric Desrosiers pointed out that you can also check the value of each pixel in your bitmap while you load it. If you want the pixel transparent you can give it an alpha value of 0. For all the other colors you can give them an alpha value of 255. This method will also work but requires some extra coding. The current tutorial is simple and requires very little extra code. I'm not blind to other techniques, but when I write a tutorial I try to make the code easy to understand and easy to use. I just wanted to point out that there are always other ways to get the job done. Thanks for the feedback Eric.

    In this tutorial I have shown you a simple, but effective way to draw sections of a texture to the screen without using the alpha channel. Normal blending usually looks bad (textures are either transparent or they're not), and texturing with an alpha channel requires that your images support the alpha channel. Bitmaps are convenient to work with, but they do not support the alpha channel this program shows us how to get around the limitations of bitmap images, while demonstrating a cool way to create overlay type effects.

    Thanks to Rob Santa for the idea and for example code. I had never heard of this little trick until he pointed it out. He wanted me to point out that although this trick does work, it takes two passes, which causes a performance hit. He recommends that you use textures that support the alpha channel for complex scenes.

    I hope you enjoyed this tutorial. If you had any problems understanding it, or you've found a mistake in the tutorial please let me know. I want to make the best tutorials available. Your feedback is important!

    Jeff Molofee (NeHe)

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

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/19 12:02:00
     
     ririyeye 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:7
      积分:81
      门派:XML.ORG.CN
      注册:2009/12/3

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给ririyeye发送一个短消息 把ririyeye加入好友 查看ririyeye的个人资料 搜索ririyeye在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看ririyeye的博客5
    发贴心情 
    不看, 后悔
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2010/1/8 10:39:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/11/24 20:49:36

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

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