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

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

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

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

    第二十五二十六课源码


    第二十五课


    按此在新窗口浏览图片变形和从文件中加载3D物体:

    在这一课中,你将学会如何从文件加载3D模型,并且平滑的从一个模型变换为另一个模型。

      
       
       
    欢迎来到这激动人心的一课,在这一课里,我们将介绍模型的变形。需要注意的是各个模型必须要有相同的顶点,才能一一对应,并应用变形。
    在这一课里,我们同样要教会你如何从一个文件中读取模型数据。
    文件开始的部分和前面一样,没有任何变化。  
       
       
    我们结下来添加几个旋转变量,用来记录旋转的信息。并使用cx,cy,cz设置物体在屏幕上的位置。
    变量key用来记录当前的模型,step用来设置相邻变形之间的中间步骤。如step为200,则需要200次,才能把一个物体变为另一个物体。
    最后我们用一个变量来设置是否使用变形。  
       

    GLfloat  xrot,yrot,zrot,        // X, Y & Z 轴的旋转角度
      xspeed,yspeed,zspeed,       // X, Y & Z 轴的旋转速度
      cx,cy,cz=-15;        // 物体的位置

    int  key=1;         // 物体的标识符
    int  step=0,steps=200;        // 变换的步数
    bool  morph=FALSE;        // 是否使用变形

       
    下面的结构定义一个三维顶点  
       

    typedef struct     
    {
     float x, y, z;       
    } VERTEX;       
       
    下面的结构使用顶点来描述一个三维物体  
       

    typedef struct          // 物体结构
    {
    int  verts;         // 物体中顶点的个数
    VERTEX  *points;         // 包含顶点数据的指针
    } OBJECT;          
       
    maxver用来记录各个物体中最大的顶点数,如一个物体使用5个顶点,另一个物体使用20个顶点,那么物体的顶点个数为20。
    结下来定义了四个我们使用的模型物体,并把相邻模型变形的中间状态保存在helper中,sour保存原模型物体,dest保存将要变形的模型物体。  
       

    int  maxver;         // 最大的顶点数
    OBJECT  morph1,morph2,morph3,morph4,      // 我们的四个物体
      helper,*sour,*dest;        // 帮助物体,原物体,目标物体

       
    WndProc()函数没有变化  
       
       
    下面的函数用来为模型分配保存顶点数据的内存空间  
       

    void objallocate(OBJECT *k,int n)
    {           
     k->points=(VERTEX*)malloc(sizeof(VERTEX)*n);     // 分配n个顶点的内存空间
    }          
       
    下面的函数用来释放为模型分配的内存空间  
       

    void objfree(OBJECT *k)   
    {
     free(k->points);        
    }

       
    下面的代码用来读取文件中的一行。
    我们用一个循环来读取字符,最多读取255个字符,当遇到'\n'回车时,停止读取并立即返回。  
       

    void readstr(FILE *f,char *string)       // 读取一行字符
    {
     do          
     {
      fgets(string, 255, f);      // 最多读取255个字符
     } while ((string[0] == '/') || (string[0] == '\n'));    // 遇到回车则停止读取
     return;         // 返回
    }

       
    下面的代码用来加载一个模型文件,并为模型分配内存,把数据存储进去。  
       

    void objload(char *name,OBJECT *k)       // 从文件加载一个模型
    {
     int ver;        // 保存顶点个数
     float rx,ry,rz;        // 保存模型位置
     FILE *filein;        // 打开的文件句柄
     char oneline[255];       // 保存255个字符

     filein = fopen(name, "rt");       // 打开文本文件,供读取
               
     readstr(filein,oneline);       // 读入一行文本
     sscanf(oneline, "Vertices: %d\n", &ver);     // 搜索字符串"Vertices: ",并把其后的顶点数保存在ver变量中
     k->verts=ver;        // 设置模型的顶点个数
     objallocate(k,ver);       // 为模型数据分配内存

       
    下面的循环,读取每一行(即每个顶点)的数据,并把它保存到内存中?/td>   
       

     for (int i=0;i<ver;i++)        // 循环所有的顶点
     {
      readstr(filein,oneline);       // 读取一行数据
      sscanf(oneline, "%f %f %f", &rx, &ry, &rz);     // 把顶点数据保存在rx,ry,rz中

      k->points[i].x = rx;       // 保存当前顶点的x坐标
      k->points[i].y = ry;       // 保存当前顶点的y坐标
      k->points[i].z = rz;       // 保存当前顶点的z坐标
     }
     fclose(filein);         // 关闭文件

     if(ver>maxver) maxver=ver;        // 记录最大的顶点数
    }          
       
    下面的函数根据设定的间隔,计算第i个顶点每次变换的位移  
       

    VERTEX calculate(int i)         // 计算第i个顶点每次变换的位移
    {
     VERTEX a;        
     a.x=(sour->points[i].x-dest->points[i].x)/steps;    
     a.y=(sour->points[i].y-dest->points[i].y)/steps;    
     a.z=(sour->points[i].z-dest->points[i].z)/steps;    
     return a;         
    }           
       
    ReSizeGLScene()函数没有变化  
       

    GLvoid ReSizeGLScene(GLsizei width, GLsizei height)

       
    下面的函数完成初始化功能,它设置混合模式为半透明  
       

    int InitGL(GLvoid)   
    {
     glBlendFunc(GL_SRC_ALPHA,GL_ONE);      // 设置半透明混合模式
     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);     // 设置清除色为黑色
     glClearDepth(1.0);        // 设置深度缓存中值为1
     glDepthFunc(GL_LESS);       // 设置深度测试函数
     glEnable(GL_DEPTH_TEST);       // 启用深度测试
     glShadeModel(GL_SMOOTH);       // 设置着色模式为光滑着色
     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);   
       
    下面的代码用来加载我们的模型物体  
       

     maxver=0;         // 初始化最大顶点数为0
     objload("data/sphere.txt",&morph1);      // 加载球模型
     objload("data/torus.txt",&morph2);      // 加载圆环模型
     objload("data/tube.txt",&morph3);      // 加载立方体模型

       
    第四个模型不从文件读取,我们在(-7,-7,-7)-(7,7,7)之间随机生成模型点,它和我们载如的模型都一样具有486个顶点。  
       

     objallocate(&morph4,486);       // 为第四个模型分配内存资源
     for(int i=0;i<486;i++)       // 随机设置486个顶点
     {
      morph4.points[i].x=((float)(rand()%14000)/1000)-7;   
      morph4.points[i].y=((float)(rand()%14000)/1000)-7;   
      morph4.points[i].z=((float)(rand()%14000)/1000)-7;   
     }

       
    初始化中间模型为球体,并把原和目标模型都设置为球  
       

     objload("data/sphere.txt",&helper);
     sour=dest=&morph1;        

     return TRUE;         // 初始化完成,成功返回
    }

       
    下面是具体的绘制代码,向往常一样我们先设置模型变化,以便我们更好的观察。  
       

    void DrawGLScene(GLvoid)
    {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // 清空缓存
     glLoadIdentity();        // 重置模型变换矩阵
     glTranslatef(cx,cy,cz);       // 平移和旋转
     glRotatef(xrot,1,0,0);        
     glRotatef(yrot,0,1,0);       
     glRotatef(zrot,0,0,1);        

     xrot+=xspeed; yrot+=yspeed; zrot+=zspeed;     // 根据旋转速度,增加旋转角度

     GLfloat tx,ty,tz;        // 顶点临时变量
     VERTEX q;         // 保存中间计算的临时顶点
       
    接下来我们来绘制模型中的点,如果启用了变形,则计算变形的中间过程点。  
       

     glBegin(GL_POINTS);        // 点绘制开始
      for(int i=0;i<morph1.verts;i++)      // 循环绘制模型1中的每一个顶点
      {         
       if(morph) q=calculate(i); else q.x=q.y=q.z=0;    // 如果启用变形,则计算中间模型
       helper.points[i].x-=q.x;     
       helper.points[i].y-=q.y;     
       helper.points[i].z-=q.z;     
       tx=helper.points[i].x;      // 保存计算结果到x,y,z变量中
       ty=helper.points[i].y;      
       tz=helper.points[i].z;     
       
    为了让动画开起来流畅,我们一共绘制了三个中间状态的点。让变形过程从蓝绿色向蓝色下一个状态变化。  
       

       glColor3f(0,1,1);      // 设置颜色
       glVertex3f(tx,ty,tz);     // 绘制顶点
       glColor3f(0,0.5f,1);     // 把颜色变蓝一些
       tx-=2*q.x; ty-=2*q.y; ty-=2*q.y;    // 如果启用变形,则绘制2步后的顶点
       glVertex3f(tx,ty,tz);      
       glColor3f(0,0,1);      // 把颜色变蓝一些
       tx-=2*q.x; ty-=2*q.y; ty-=2*q.y;    // 如果启用变形,则绘制2步后的顶点
       glVertex3f(tx,ty,tz);      
      }         
     glEnd();         // 绘制结束

       
    最后如果启用了变形,则增加递增的步骤参数,然后绘制下一个点。  
       

     // 如果启用变形则把变形步数增加
     if(morph && step<=steps)step++; else { morph=FALSE; sour=dest; step=0;}
     return TRUE; // 一切OK
    }

       
    KillGLWindow() 函数基本没有变化,只是添加释放5个模型内存的代码  
       

    objfree(&morph1);        // 释放模型1内存
     objfree(&morph2);        // 释放模型2内存
     objfree(&morph3);        // 释放模型3内存
     objfree(&morph4);        // 释放模型4内存
     objfree(&helper);        // 释放模型5内存

       
    CreateGLWindow() 函数没有变化  
       

    BOOL CreateGLWindow()  

    LRESULT CALLBACK WndProc()

       
    在WinMain()函数中,我们添加了一些键盘控制的函数  
       

        if(keys[VK_PRIOR])      // PageUp键是否被按下
         zspeed+=0.01f;     // 按下增加绕z轴旋转的速度

        if(keys[VK_NEXT])      // PageDown键是否被按下
         zspeed-=0.01f;     // 按下减少绕z轴旋转的速度

        if(keys[VK_DOWN])      // 下方向键是否被按下
         xspeed+=0.01f;     // 按下增加绕x轴旋转的速度

        if(keys[VK_UP])      // 上方向键是否被按下
         xspeed-=0.01f;     // 按下减少绕x轴旋转的速度

        if(keys[VK_RIGHT])      // 右方向键是否被按下
         yspeed+=0.01f;     // 按下增加沿y轴旋转的速度

        if(keys[VK_LEFT])      // 左方向键是否被按下
         yspeed-=0.01f;     // 按下减少沿y轴旋转的速度
        if (keys['Q'])      // Q键是否被按下
         cz-=0.01f;      // 是则向屏幕里移动

        if (keys['Z'])      // Z键是否被按下
         cz+=0.01f;      // 是则向屏幕外移动

        if (keys['W'])      // W键是否被按下
         cy+=0.01f;      // 是则向上移动

        if (keys['S'])      // S键是否被按下
         cy-=0.01f;      // 是则向下移动

        if (keys['D'])      // D键是否被按下
         cx+=0.01f;      // 是则向右移动

        if (keys['A'])      // A键是否被按下
         cx-=0.01f;      // 是则向左移动
       
    1,2,3,4键用来设置变形的目标模型  
       

        if (keys['1'] && (key!=1) && !morph)   // 如果1被按下,则变形到模型1
        {
         key=1;      
         morph=TRUE;    
         dest=&morph1;     
        }
        if (keys['2'] && (key!=2) && !morph)   // 如果2被按下,则变形到模型1
        {
         key=2;      
         morph=TRUE;     
         dest=&morph2;     
        }
        if (keys['3'] && (key!=3) && !morph)   // 如果3被按下,则变形到模型1
        {
         key=3;      
         morph=TRUE;     
         dest=&morph3;     
        }
        if (keys['4'] && (key!=4) && !morph)   // 如果4被按下,则变形到模型1
        {
         key=4;      
         morph=TRUE;     
         dest=&morph4;     
        }

       
    我希望你能喜欢这个教程,相信你已经学会了变形动画。
    Piotr Cieslak 的代码非常的新颖,希望通过这个教程你能知道如何从文件中加载三维模型。
    这份教程化了我三天的时间,如果有什么错误请告诉我。


       收藏   分享  
    顶(0)
      




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

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

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客2
    发贴心情 
    Lesson 25
       
    Welcome to yet another exciting tutorial! This time we will focus on the effect rather than the graphics, although the final result is pretty cool looking! In this tutorial you will learn how to morph seamlessly from one object to another. Similar to the effect I use in the dolphin demo. Although there are a few catches. First thing to note is that each object must have the same amount of points. Very rare to luck out and get 3 object made up of exactly the same amount of vertices, but it just so happens, in this tutorial we have 3 objects with exactly the same amount of points :) Don't get me wrong, you can use objects with different values, but the transition from one object to another is odd looking and not as smooth.

    You will also learn how to read object data from a file. Similar to the format used in lesson 10, although it shouldn't be hard to modify the code to read .ASC files or some other text type data files. In general, it's a really cool effect, a really cool tutorial, so lets begin!

    We start off as usual. Including all the required header files, along with the math and standard input / output headers. Notice we don't include glaux. That's because we'll be drawing points rather than textures in this tutorial. After you've got the tutorial figured out, you can try playing with Polygons, Lines, and Textures!   
       

    #include <windows.h>        // Header File For Windows
    #include <math.h>        // Math Library Header File
    #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

    HDC  hDC=NULL;        // Device Context Handle
    HGLRC  hRC=NULL;        // Rendering Context Handle
    HWND  hWnd=NULL;        // Window Handle
    HINSTANCE hInstance;        // Instance Handle

    bool  keys[256];        // Key Array
    bool  active=TRUE;        // Program's Active
    bool  fullscreen=TRUE;       // Default Fullscreen To True

       
    After setting up all the standard variables, we will add some new variables. xrot, yrot and zrot will hold the current rotation values for the x, y and z axes of the onscreen object. xspeed, yspeed and zspeed will control how fast the object is rotating on each axis. cx, cy and cz control the position of the object on the screen (where it's drawn left to right cx, up and down cy and into and out of the screen cz)

    The variable key is a variable that I have included to make sure the user doesn't try to morph from the first shape back into the first shape. This would be pretty pointless and would cause a delay while the points were trying to morph to the position they're already in.

    step is a counter variable that counts through all the steps specified by steps. If you increase the value of steps it will take longer for the object to morph, but the movement of the points as they morph will be smoother. Once step is equal to steps we know the morphing has been completed.

    The last variable morph lets our program know if it should be morphing the points or leaving them where they are. If it's TRUE, the object is in the process of morphing from one shape to another.   
       

    GLfloat  xrot,yrot,zrot,        // X, Y & Z Rotation
      xspeed,yspeed,zspeed,       // X, Y & Z Spin Speed
      cx,cy,cz=-15;        // X, Y & Z Position

    int  key=1;         // Used To Make Sure Same Morph Key Is Not Pressed
    int  step=0,steps=200;       // Step Counter And Maximum Number Of Steps
    bool  morph=FALSE;        // Default morph To False (Not Morphing)

       
    Now we create a structure to keep track of a vertex. The structure will hold the x, y and z values of any point on the screen. The variables x, y & z are all floating point so we can position the point anywhere on the screen with great accuracy. The structure name is VERTEX.   
       

    typedef struct          // Structure For 3D Points
    {
     float x, y, z;        // X, Y & Z Points
    } VERTEX;          // Called VERTEX

       
    We already have a structure to keep track of vertices, and we know that an object is made up of many vertices so lets create an OBJECT structure. The first variable verts is an integer value that will hold the number of vertices required to make up an object. So if our object has 5 points, the value of verts will be equal to 5. We will set the value later in the code. For now, all you need to know is that verts keeps track of how many points we use to create the object.

    The variable points will reference a single VERTEX (x, y and z values). This allows us to grab the x, y or z value of any point using points[{point we want to access}].{x, y or z}.

    The name of this structure is... you guessed it... OBJECT!   
       

    typedef struct          // Structure For An Object
    {
    int  verts;         // Number Of Vertices For The Object
    VERTEX  *points;        // One Vertice (Vertex x,y & z)
    } OBJECT;          // Called OBJECT

       
    Now that we have created a VERTEX structure and an OBJECT structure we can define some objects.

    The variable maxver will be used to keep track of the maximum number of variables used in any of the objects. If one object only had 5 points, another had 20, and the last object had 15, the value of maxver would be equal to the greatest number of points used. So maxver would be equal to 20.

    After we define maxver we can define the objects. morph1, morph2, morph3, morph4 & helper are all defined as an OBJECT. *sour & *dest are defined as OBJECT* (pointer to an object). The object is made up of verticies (VERTEX). The first 4 morph{num} objects will hold the 4 objects we want to morph to and from. helper will be used to keep track of changes as the object is morphed. *sour will point to the source object and *dest will point to the object we want to morph to (destination object).   
       

    int  maxver;         // Will Eventually Hold The Maximum Number Of Vertices
    OBJECT  morph1,morph2,morph3,morph4,      // Our 4 Morphable Objects (morph1,2,3 & 4)
      helper,*sour,*dest;        // Helper Object, Source Object, Destination Object

       
    Same as always, we declare WndProc().   
       

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

       
    The code below allocates memory for each object, based on the number of vertices we pass to n. *k will point to the object we want to allocate memory for.

    The line inside the { }'s allocates the memory for object k's points. A point is an entire VERTEX (3 floats). The memory allocated is the size of VERTEX (3 floats) multiplied by the number of points (n). So if there were 10 points (n=10) we would be allocating room for 30 floating point values (3 floats * 10 points).   
       

    void objallocate(OBJECT *k,int n)       // Allocate Memory For Each Object
    {           // And Defines points
     k->points=(VERTEX*)malloc(sizeof(VERTEX)*n);     // Sets points Equal To VERTEX * Number Of Vertices
    }           // (3 Points For Each Vertice)

       
    The following code frees the object, releasing the memory used to create the object. The object is passed as k. The free command tells our program to release all the points used to make up our object (k).   
       

    void objfree(OBJECT *k)         // Frees The Object (Releasing The Memory)
    {
     free(k->points);        // Frees Points
    }

       
    The code below reads a string of text from a file. The pointer to our file structure is passed to *f. The variable string will hold the text that we have read in.

    We start off be creating a do / while loop. fgets() will read up to 255 characters from our file f and store the characters at *string. If the line read is blank (carriage return \n), the loop will start over, attempting to find a line with text. The while() statement checks for blank lines and if found starts over again.

    After the string has been read in we return.   
       

    void readstr(FILE *f,char *string)       // Reads A String From File (f)
    {
     do          // Do This
     {
      fgets(string, 255, f);       // Gets A String Of 255 Chars Max From f (File)
     } while ((string[0] == '/') || (string[0] == '\n'));    // Until End Of Line Is Reached
     return;          // Return
    }

       
    Now we load in an object. *name points to the filename. *k points to the object we wish to load data into.

    We start off with an integer variable called ver. ver will hold the number of vertices used to build the object.

    The variables rx, ry & rz will hold the x, y & z values of each vertex.

    The variable filein is the pointer to our file structure, and oneline[ ] will be used to hold 255 characters of text.

    We open the file name for read in text translated mode (meaning CTRL-Z represents the end of a line). Then we read in a line of text using readstr(filein,oneline). The line of text will be stored in oneline.

    After we have read in the text, we scan the line of text (oneline) for the phrase "Vertices: {some number}{carriage return}. If the text is found, the number is stored in the variable ver. This number is the number of vertices used to create the object. If you look at the object text files, you'll see that the first line of text is: Vertices: {some number}.

    After we know how many vertices are used we store the results in the objects verts variable. Each object could have a different value if each object had a different number of vertices.

    The last thing we do in this section of code is allocate memory for the object. We do this by calling objallocate({object name},{number of verts}).   
       

    void objload(char *name,OBJECT *k)       // Loads Object From File (name)
    {
     int ver;         // Will Hold Vertice Count
     float rx,ry,rz;        // Hold Vertex X, Y & Z Position
     FILE *filein;        // Filename To Open
     char oneline[255];        // Holds One Line Of Text (255 Chars Max)

     filein = fopen(name, "rt");       // Opens The File For Reading Text In Translated Mode
               // CTRL Z Symbolizes End Of File In Translated Mode
     readstr(filein,oneline);       // Jumps To Code That Reads One Line Of Text From The File
     sscanf(oneline, "Vertices: %d\n", &ver);     // Scans Text For "Vertices: ".  Number After Is Stored In ver
     k->verts=ver;         // Sets Objects verts Variable To Equal The Value Of ver
     objallocate(k,ver);        // Jumps To Code That Allocates Ram To Hold The Object

       
    We know how many vertices the object has. We have allocated memory, now all that is left to do is read in the vertices. We create a loop using the variable i. The loop will go through all the vertices.

    Next we read in a line of text. This will be the first line of valid text underneath the "Vertices: {some number}" line. What we should end up reading is a line with floating point values for x, y & z.

    The line is analyzed with sscanf() and the three floating point values are extracted and stored in rx, ry and rz.   
       

     for (int i=0;i<ver;i++)        // Loops Through The Vertices
     {
      readstr(filein,oneline);      // Reads In The Next Line Of Text
      sscanf(oneline, "%f %f %f", &rx, &ry, &rz);    // Searches For 3 Floating Point Numbers, Store In rx,ry & rz

       
    The following three lines are hard to explain in plain english if you don't understand structures, etc, but I'll try my best :)

    The line k->points[i].x=rx can be broken down like this:

    rx is the value on the x axis for one of the points.
    points[i].x is the x axis position of point[i].
    If i is 0 then were are setting the x axis value of point 1, if i is 1, we are setting the x axis value of point 2, and so on.
    points[i] is part of our object (which is represented as k).

    So if i is equal to 0, what we are saying is: The x axis of point 1 (point[0].x) in our object (k) equals the x axis value we just read from the file (rx).

    The other two lines set the y & z axis values for each point in our object.

    We loop through all the vertices. If there are not enough vertices, an error might occur, so make sure the text at the beginning of the file "Vertices: {some number}" is actually the number of vertices in the file. Meaning if the top line of the file says "Vertices: 10", there had better be 10 Verticies (x, y and z values)!

    After reading in all of the verticies we close the file, and check to see if the variable ver is greater than the variable maxver. If ver is greater than maxver, we set maxver to equal ver. That way if we read in one object and it has 20 verticies, maxver will become 20. If we read in another object, and it has 40 verticies, maxver will become 40. That way we know how many vertices our largest object has.   
       

      k->points[i].x = rx;       // Sets Objects (k) points.x Value To rx
      k->points[i].y = ry;       // Sets Objects (k) points.y Value To ry
      k->points[i].z = rz;       // Sets Objects (k) points.z Value To rz
     }
     fclose(filein);         // Close The File

     if(ver>maxver) maxver=ver;       // If ver Is Greater Than maxver Set maxver Equal To ver
    }           // Keeps Track Of Highest Number Of Vertices Used

       
    The next bit of code may look a little intimidating... it's NOT :) I'll explain it so clearly you'll laugh when you next look at it.

    What the code below does is calculates a new position for each point when morphing is enabled. The number of the point to calculate is stored in i. The results will be returned in the VERTEX calculate.

    The first variable we create is a VERTEX called a. This will give a an x, y and z value.

    Lets look at the first line. The x value of the VERTEX a equals the x value of point[i] (point[i].x) in our SOURCE object minus the x value of point[i] (point[i].x) in our DESTINATION object divided by steps.

    So lets plug in some numbers. Lets say our source objects first x value is 40 and our destination objects first x value is 20. We already know that steps is equal to 200! So that means that a.x=(40-20)/200... a.x=(20)/200... a.x=0.1.

    What this means is that in order to move from 40 to 20 in 200 steps, we need to move by 0.1 units each calculation. To prove this calculation, multiply 0.1 by 200, and you get 20. 40-20=20 :)

    We do the same thing to calculate how many units to move on both the y axis and the z axis for each point. If you increase the value of steps the movements will be even more fine (smooth), but it will take longer to morph from one position to another.   
       

    VERTEX calculate(int i)         // Calculates Movement Of Points During Morphing
    {
     VERTEX a;         // Temporary Vertex Called a
     a.x=(sour->points[i].x-dest->points[i].x)/steps;    // a.x Value Equals Source x - Destination x Divided By Steps
     a.y=(sour->points[i].y-dest->points[i].y)/steps;    // a.y Value Equals Source y - Destination y Divided By Steps
     a.z=(sour->points[i].z-dest->points[i].z)/steps;    // a.z Value Equals Source z - Destination z Divided By Steps
     return a;         // Return The Results
    }           // This Makes Points Move At A Speed So They All Get To Their

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

    GLvoid ReSizeGLScene(GLsizei width, GLsizei height)     // Resize And Initialize The GL Window

       
    In the code below we set blending for translucency. This allows us to create neat looking trails when the points are moving.   
       

    int InitGL(GLvoid)         // All Setup For OpenGL Goes Here
    {
     glBlendFunc(GL_SRC_ALPHA,GL_ONE);      // Set The Blending Function For Translucency
     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);      // This Will Clear The Background Color To Black
     glClearDepth(1.0);        // Enables Clearing Of The Depth Buffer
     glDepthFunc(GL_LESS);        // The Type Of Depth Test To Do
     glEnable(GL_DEPTH_TEST);       // Enables Depth Testing
     glShadeModel(GL_SMOOTH);       // Enables Smooth Color Shading
     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);    // Really Nice Perspective Calculations

       
    We set the maxver variable to 0 to start off. We haven't read in any objects so we don't know what the maximum amount of vertices will be.

    Next well load in 3 objects. The first object is a sphere. The data for the sphere is stored in the file sphere.txt. The data will be loaded into the object named morph1. We also load a torus, and a tube into objects morph2 and morph3.   
       

     maxver=0;         // Sets Max Vertices To 0 By Default
     objload("data/sphere.txt",&morph1);      // Load The First Object Into morph1 From File sphere.txt
     objload("data/torus.txt",&morph2);      // Load The Second Object Into morph2 From File torus.txt
     objload("data/tube.txt",&morph3);      // Load The Third Object Into morph3 From File tube.txt

       
    The 4th object isn't read from a file. It's a bunch of dots randomly scattered around the screen. Because we're not reading the data from a file, we have to manually allocate the memory by calling objallocate(&morph4,468). 468 means we want to allocate enough space to hold 468 vertices (the same amount of vertices the other 3 objects have).

    After allocating the space, we create a loop that assigns a random x, y and z value to each point. The random value will be a floating point value from +7 to -7. (14000/1000=14... minus 7 gives us a max value of +7... if the random number is 0, we have a minimum value of 0-7 or -7).   
       

     objallocate(&morph4,486);       // Manually Reserver Ram For A 4th 468 Vertice Object (morph4)
     for(int i=0;i<486;i++)        // Loop Through All 468 Vertices
     {
      morph4.points[i].x=((float)(rand()%14000)/1000)-7;   // morph4 x Point Becomes A Random Float Value From -7 to 7
      morph4.points[i].y=((float)(rand()%14000)/1000)-7;   // morph4 y Point Becomes A Random Float Value From -7 to 7
      morph4.points[i].z=((float)(rand()%14000)/1000)-7;   // morph4 z Point Becomes A Random Float Value From -7 to 7
     }

       
    We then load the sphere.txt as a helper object. We never want to modify the object data in morph{1/2/3/4} directly. We modify the helper data to make it become one of the 4 shapes. Because we start out displaying morph1 (a sphere) we start the helper out as a sphere as well.

    After all of the objects are loaded, we set the source and destination objects (sour and dest) to equal morph1, which is the sphere. This way everything starts out as a sphere.   
       

     objload("data/sphere.txt",&helper);      // Load sphere.txt Object Into Helper (Used As Starting Point)
     sour=dest=&morph1;        // Source & Destination Are Set To Equal First Object (morph1)

     return TRUE;         // Initialization Went OK
    }

       
    Now for the fun stuff. The actual rendering code :)

    We start off normal. Clear the screen, depth buffer and reset the modelview matrix. Then we position the object on the screen using the values stored in cx, cy and cz.

    Rotations are done using xrot, yrot and zrot.

    The rotation angle is increased based on xpseed, yspeed and zspeed.

    Finally 3 temporary variables are created tx, ty and tz, along with a new VERTEX called q.   
       

    void DrawGLScene(GLvoid)        // Here's Where We Do All The Drawing
    {
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // Clear The Screen And The Depth Buffer
     glLoadIdentity();        // Reset The View
     glTranslatef(cx,cy,cz);        // Translate The The Current Position To Start Drawing
     glRotatef(xrot,1,0,0);        // Rotate On The X Axis By xrot
     glRotatef(yrot,0,1,0);        // Rotate On The Y Axis By yrot
     glRotatef(zrot,0,0,1);        // Rotate On The Z Axis By zrot

     xrot+=xspeed; yrot+=yspeed; zrot+=zspeed;     // Increase xrot,yrot & zrot by xspeed, yspeed & zspeed

     GLfloat tx,ty,tz;        // Temp X, Y & Z Variables
     VERTEX q;         // Holds Returned Calculated Values For One Vertex

       
    Now we draw the points and do our calculations if morphing is enabled. glBegin(GL_POINTS) tells OpenGL that each vertex that we specify will be drawn as a point on the screen.

    We create a loop to loop through all the vertices. You could use maxver, but because every object has the same number of vertices we'll use morph1.verts.

    Inside the loop we check to see if morph is TRUE. If it is we calculate the movement for the current point (i). q.x, q.y and q.z will hold the results. If morph is false, q.x, q.y and q.z will be set to 0 (preventing movement).

    the points in the helper object are moved based on the results of we got from calculate(i). (remember earlier that we calculated a point would have to move 0.1 unit to make it from 40 to 20 in 200 steps).

    We adjust the each points value on the x, y and z axis by subtracting the number of units to move from helper.

    The new helper point is stored in tx, ty and tz. (t{x/y/z}=helper.points[i].{x/y/z}).   
       

     glBegin(GL_POINTS);        // Begin Drawing Points
      for(int i=0;i<morph1.verts;i++)      // Loop Through All The Verts Of morph1 (All Objects Have
      {         // The Same Amount Of Verts For Simplicity, Could Use maxver Also)
       if(morph) q=calculate(i); else q.x=q.y=q.z=0;   // If morph Is True Calculate Movement Otherwise Movement=0
       helper.points[i].x-=q.x;     // Subtract q.x Units From helper.points[i].x (Move On X Axis)
       helper.points[i].y-=q.y;     // Subtract q.y Units From helper.points[i].y (Move On Y Axis)
       helper.points[i].z-=q.z;     // Subtract q.z Units From helper.points[i].z (Move On Z Axis)
       tx=helper.points[i].x;      // Make Temp X Variable Equal To Helper's X Variable
       ty=helper.points[i].y;      // Make Temp Y Variable Equal To Helper's Y Variable
       tz=helper.points[i].z;      // Make Temp Z Variable Equal To Helper's Z Variable

       
    Now that we have the new position calculated it's time to draw our points. We set the color to a bright bluish color, and the draw the first point with glVertex3f(tx,ty,tz). This draws a point at the newly calculated position.

    We then darken the color a little, and move 2 steps in the direction we just calculated instead of one. This moves the point to the newly calculated position, and then moves it again in the same direction. So if it was travelling left at 0.1 units, the next dot would be at 0.2 units. After calculating 2 positions ahead we draw the second point.

    Finally we set the color to dark blue, and calculate even further ahead. This time using our example we would move 0.4 units to the left instead of 0.1 or 0.2. The end result is a little tail of particles following as the dots move. With blending, this creates a pretty cool effect!

    glEnd() tells OpenGL we are done drawing points.   
       

       glColor3f(0,1,1);      // Set Color To A Bright Shade Of Off Blue
       glVertex3f(tx,ty,tz);      // Draw A Point At The Current Temp Values (Vertex)
       glColor3f(0,0.5f,1);      // Darken Color A Bit
       tx-=2*q.x; ty-=2*q.y; ty-=2*q.y;    // Calculate Two Positions Ahead
       glVertex3f(tx,ty,tz);      // Draw A Second Point At The Newly Calculate Position
       glColor3f(0,0,1);      // Set Color To A Very Dark Blue
       tx-=2*q.x; ty-=2*q.y; ty-=2*q.y;    // Calculate Two More Positions Ahead
       glVertex3f(tx,ty,tz);      // Draw A Third Point At The Second New Position
      }         // This Creates A Ghostly Tail As Points Move
     glEnd();         // Done Drawing Points

       
    The last thing we do is check to see if morph is TRUE and step is less than steps (200). If step is less than 200, we increase step by 1.

    If morph is false or step is greater than or equal to steps (200), morph is set to FALSE, the sour (source) object is set to equal the dest (destination) object, and step is set back to 0. This tells the program that morphing is not happening or it has just finished.   
       

     // If We're Morphing And We Haven't Gone Through All 200 Steps Increase Our Step Counter
     // Otherwise Set Morphing To False, Make Source=Destination And Set The Step Counter Back To Zero.
     if(morph && step<=steps)step++; else { morph=FALSE; sour=dest; step=0;}
    }

       
    The KillGLWindow() code hasn't changed much. The only real difference is that we free all of the objects from memory before we kill the windows. This prevents memory leaks, and is good practice ;)   
       

    GLvoid KillGLWindow(GLvoid)        // Properly Kill The Window
    {
     objfree(&morph1);        // Jump To Code To Release morph1 Allocated Ram
     objfree(&morph2);        // Jump To Code To Release morph2 Allocated Ram
     objfree(&morph3);        // Jump To Code To Release morph3 Allocated Ram
     objfree(&morph4);        // Jump To Code To Release morph4 Allocated Ram
     objfree(&helper);        // Jump To Code To Release helper Allocated Ram

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

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

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

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

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

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

       
    The CreateGLWindow() and WndProc() code hasn't changed. So I'll skip over it.   
       

    BOOL CreateGLWindow()         // Creates The GL Window

    LRESULT CALLBACK WndProc()        // Handle For This Window

       
    In WinMain() there are a few changes. First thing to note is the new caption on the title bar :)   
       

    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("Piotr Cieslak & NeHe's Morphing Points 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 && keys[VK_ESCAPE])     // Active?  Was There A Quit Received?
       {
        done=TRUE;      // ESC or DrawGLScene Signaled A Quit
       }
       else        // Not Time To Quit, Update Screen
       {
        DrawGLScene();      // Draw The Scene (Don't Draw When Inactive 1% CPU Use)
        SwapBuffers(hDC);     // Swap Buffers (Double Buffering)

       
    The code below watches for key presses. By now you should understand the code fairly easily. If page up is pressed we increase zspeed. This causes the object to spin faster on the z axis in a positive direction.

    If page down is pressed we decrease zspeed. This causes the object to spin faster on the z axis in a negative direction.

    If the down arrow is pressed we increase xspeed. This causes the object to spin faster on the x axis in a positive direction.

    If the up arrow is pressed we decrease xspeed. This causes the object to spin faster on the x axis in a negative direction.

    If the right arrow is pressed we increase yspeed. This causes the object to spin faster on the y axis in a positive direction.

    If the left arrow is pressed we decrease yspeed. This causes the object to spin faster on the y axis in a negative direction.   
       

        if(keys[VK_PRIOR])     // Is Page Up Being Pressed?
         zspeed+=0.01f;     // Increase zspeed

        if(keys[VK_NEXT])     // Is Page Down Being Pressed?
         zspeed-=0.01f;     // Decrease zspeed

        if(keys[VK_DOWN])     // Is Page Up Being Pressed?
         xspeed+=0.01f;     // Increase xspeed

        if(keys[VK_UP])      // Is Page Up Being Pressed?
         xspeed-=0.01f;     // Decrease xspeed

        if(keys[VK_RIGHT])     // Is Page Up Being Pressed?
         yspeed+=0.01f;     // Increase yspeed

        if(keys[VK_LEFT])     // Is Page Up Being Pressed?
         yspeed-=0.01f;     // Decrease yspeed

       
    The following keys physically move the object. 'Q' moves it into the screen, 'Z' moves it towards the viewer, 'W' moves the object up, 'S' moves it down, 'D' moves it right, and 'A' moves it left.   
       

        if (keys['Q'])      // Is Q Key Being Pressed?
         cz-=0.01f;      // Move Object Away From Viewer

        if (keys['Z'])      // Is Z Key Being Pressed?
         cz+=0.01f;      // Move Object Towards Viewer

        if (keys['W'])      // Is W Key Being Pressed?
         cy+=0.01f;      // Move Object Up

        if (keys['S'])      // Is S Key Being Pressed?
         cy-=0.01f;      // Move Object Down

        if (keys['D'])      // Is D Key Being Pressed?
         cx+=0.01f;      // Move Object Right

        if (keys['A'])      // Is A Key Being Pressed?
         cx-=0.01f;      // Move Object Left

       
    Now we watch to see if keys 1 through 4 are pressed. If 1 is pressed and key is not equal to 1 (not the current object already) and morph is false (not already in the process of morphing), we set key to 1, so that our program knows we just selected object 1. We then set morph to TRUE, letting our program know it's time to start morphing, and last we set the destination object (dest) to equal object 1 (morph1).

    Pressing keys 2, 3, and 4 does the same thing. If 2 is pressed we set dest to morph2, and we set key to equal 2. Pressing 3, sets dest to morph3 and key to 3.

    By setting key to the value of the key we just pressed on the keyboard, we prevent the user from trying to morph from a sphere to a sphere or a cone to a cone!   
       

        if (keys['1'] && (key!=1) && !morph)   // Is 1 Pressed, key Not Equal To 1 And Morph False?
        {
         key=1;      // Sets key To 1 (To Prevent Pressing 1 2x In A Row)
         morph=TRUE;     // Set morph To True (Starts Morphing Process)
         dest=&morph1;     // Destination Object To Morph To Becomes morph1
        }
        if (keys['2'] && (key!=2) && !morph)   // Is 2 Pressed, key Not Equal To 2 And Morph False?
        {
         key=2;      // Sets key To 2 (To Prevent Pressing 2 2x In A Row)
         morph=TRUE;     // Set morph To True (Starts Morphing Process)
         dest=&morph2;     // Destination Object To Morph To Becomes morph2
        }
        if (keys['3'] && (key!=3) && !morph)   // Is 3 Pressed, key Not Equal To 3 And Morph False?
        {
         key=3;      // Sets key To 3 (To Prevent Pressing 3 2x In A Row)
         morph=TRUE;     // Set morph To True (Starts Morphing Process)
         dest=&morph3;     // Destination Object To Morph To Becomes morph3
        }
        if (keys['4'] && (key!=4) && !morph)   // Is 4 Pressed, key Not Equal To 4 And Morph False?
        {
         key=4;      // Sets key To 4 (To Prevent Pressing 4 2x In A Row)
         morph=TRUE;     // Set morph To True (Starts Morphing Process)
         dest=&morph4;     // Destination Object To Morph To Becomes morph4
        }

       
    Finally we watch to see if F1 is pressed if it is we toggle from Fullscreen to Windowed mode or Windowed mode to Fullscreen mode!   
       

        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("Piotr Cieslak & NeHe's Morphing Points Tutorial",640,480,16,fullscreen))
         {
          return 0;    // Quit If Window Was Not Created
         }
        }
       }
      }
     }

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

       
    I hope you have enjoyed this tutorial. Although it's not an incredibly complex tutorial, you can learn alot from the code! The animation in my dolphin demo is done in a similar way to the morphing in this demo. By playing around with the code you can come up with some really cool effects. Dots turning into words. Faked animation, and more! You may even want to try using solid polygons or lines instead of dots. The effect can be quite impressive!

    Piotr's code is new and refreshing. I hope that after reading through this tutorial you have a better understanding on how to store and load object data from a file, and how to manipulate the data to create cool GL effects in your own programs! The .html for this tutorial took 3 days to write. If you notice any mistakes please let me know. Alot of it was written late at night, meaning a few mistakes may have crept in. I want these tutorials to be the best they can be. Feedback is appreciated!

    RabidHaMsTeR released a demo called "Morph" before this tutorial was written that shows off a more advanced version of this effect. You can check it out yourself at http://homepage.ntlworld.com/fj.williams/PgSoftware.html.

    Piotr Cieslak

    Jeff Molofee (NeHe)

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

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

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

    第二十六课


    按此在新窗口浏览图片剪裁平面,蒙板缓存和反射:

    在这一课中你将学会如何创建镜面显示效果,它使用剪裁平面,蒙板缓存等OpenGL中一些高级的技巧。

      
       
       
    欢迎来到另一个激动人心的课程,这课的代码是Banu Cosmin所写,当然教程还是我自己写的。在这课里,我将教你创建真正的反射,基于物理的。
    由于它将用到蒙板缓存,所以需要耗费一些资源。当然随着显卡和CPU的发展,这些都不是问题了,好了让我们开始吧!
      
       
       
    下面我们设置光源的参数  
       

    static GLfloat LightAmb[] = {0.7f, 0.7f, 0.7f, 1.0f};    // 环境光
    static GLfloat LightDif[] = {1.0f, 1.0f, 1.0f, 1.0f};    // 漫射光
    static GLfloat LightPos[] = {4.0f, 4.0f, 6.0f, 1.0f};    // 灯光的位置
       
    下面用二次几何体创建一个球,并设置纹理  
       

    GLUquadricObj *q;        // 使用二次几何体创建球

    GLfloat  xrot  =  0.0f;      // X方向的旋转角度
    GLfloat  yrot  =  0.0f;      // Y方向的旋转角的
    GLfloat  xrotspeed =  0.0f;       // X方向的旋转速度
    GLfloat  yrotspeed =  0.0f;       // Y方向的旋转速度
    GLfloat  zoom  = -7.0f;      // 移入屏幕7个单位
    GLfloat  height  =  2.0f;      // 球离开地板的高度

    GLuint  texture[3];       // 使用三个纹理
       
    ReSizeGLScene() 和LoadBMP() 没有变化  
       

    GLvoid ReSizeGLScene(GLsizei width, GLsizei height)

    AUX_RGBImageRec *LoadBMP(char *Filename)

       
    下面的代码载入纹理  
       

    int LoadGLTextures()        // 载入*.bmp文件,并转化为纹理
    {
        int Status=FALSE;        
        AUX_RGBImageRec *TextureImage[3];      // 创建三个图象
        memset(TextureImage,0,sizeof(void *)*3);     
        if ((TextureImage[0]=LoadBMP("Data/EnvWall.bmp")) &&    // 载入地板图像
            (TextureImage[1]=LoadBMP("Data/Ball.bmp")) &&     // 载入球图像
            (TextureImage[2]=LoadBMP("Data/EnvRoll.bmp")))     // 载入强的图像
     {
      Status=TRUE;       
      glGenTextures(3, &texture[0]);     // 创建纹理
      for (int loop=0; loop<3; loop++)     // 循环设置三个纹理参数
      {
       glBindTexture(GL_TEXTURE_2D, texture[loop]);
       glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
       glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
       glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
      }
      for (loop=0; loop<3; loop++)     
      {
       if (TextureImage[loop])     
       {
        if (TextureImage[loop]->data)   
        {
         free(TextureImage[loop]->data);  
        }
        free(TextureImage[loop]);   
       }
      }
     }
     return Status;        // 成功返回
    }

       
    一个新的函数glClearStencil被加入到初始化代码中,它用来设置清空操作后蒙板缓存中的值。其他的操作保持不变。  
       

    int InitGL(GLvoid)        // 初始化OpenGL
    {
     if (!LoadGLTextures())      // 载入纹理
     {
      return FALSE;       
     }
     glShadeModel(GL_SMOOTH);      
     glClearColor(0.2f, 0.5f, 1.0f, 1.0f);     
     glClearDepth(1.0f);       
     glClearStencil(0);       // 设置蒙板值
     glEnable(GL_DEPTH_TEST);      
     glDepthFunc(GL_LEQUAL);      
     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);   
     glEnable(GL_TEXTURE_2D);      // 使用2D纹理
       
    下面的代码用来启用光照  
       

     glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb);   
     glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDif);    
     glLightfv(GL_LIGHT0, GL_POSITION, LightPos);   
     glEnable(GL_LIGHT0); 
     glEnable(GL_LIGHTING);

       
    下面的代码使用二次几何体创建一个球体,在前面的教程中都已经详纤,这里不再重复。  
       

     q = gluNewQuadric();       // 创建一个二次几何体
     gluQuadricNormals(q, GL_SMOOTH);      // 使用平滑法线
     gluQuadricTexture(q, GL_TRUE);      // 使用纹理

     glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);    // 设置球纹理映射
     glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);   

     return TRUE;        // 初始化完成,成功返回
    }

       
    下面的代码绘制我们的球  
       

    void DrawObject()        // 绘制我们的球
    {
     glColor3f(1.0f, 1.0f, 1.0f);     // 设置为白色
     glBindTexture(GL_TEXTURE_2D, texture[1]);    // 设置为球的纹理
     gluSphere(q, 0.35f, 32, 16);     // 绘制球
       
    绘制完一个白色的球后,我们使用环境贴图来绘制另一个球,把这两个球按alpha混合起来。  
       

     glBindTexture(GL_TEXTURE_2D, texture[2]);    // 设置为环境纹理
     glColor4f(1.0f, 1.0f, 1.0f, 0.4f);     // 使用alpha为40%的白色
     glEnable(GL_BLEND);      // 启用混合
     glBlendFunc(GL_SRC_ALPHA, GL_ONE);     // 把原颜色的40%与目标颜色混合
     glEnable(GL_TEXTURE_GEN_S);      // 使用球映射
     glEnable(GL_TEXTURE_GEN_T);      

     gluSphere(q, 0.35f, 32, 16);     // 绘制球体,并混合

     glDisable(GL_TEXTURE_GEN_S);     // 让OpenGL回到默认的属性
     glDisable(GL_TEXTURE_GEN_T);      
     glDisable(GL_BLEND);       
    }

       
    绘制地板  
       

    void DrawFloor()   
    {
     glBindTexture(GL_TEXTURE_2D, texture[0]);    // 选择地板纹理,地板由一个长方形组成
     glBegin(GL_QUADS);       
      glNormal3f(0.0, 1.0, 0.0);     
      glTexCoord2f(0.0f, 1.0f);     // 左下
      glVertex3f(-2.0, 0.0, 2.0);     

      glTexCoord2f(0.0f, 0.0f);     // 左上
      glVertex3f(-2.0, 0.0,-2.0);     

      glTexCoord2f(1.0f, 0.0f);     // 右上
      glVertex3f( 2.0, 0.0,-2.0);     

      glTexCoord2f(1.0f, 1.0f);     // 右下
      glVertex3f( 2.0, 0.0, 2.0);    
     glEnd();        
    }

       
    现在到了我们绘制函数的地方,我们将把所有的模型结合起来创建一个反射的场景。
    向往常一样先把各个缓存清空,接着定义我们的剪切平面,它用来剪切我们的图像。这个平面的方程为equ[]={0,-1,0,0},向你所看到的它的法线是指向-y轴的,这告诉我们你只能看到y轴坐标小于0的像素,如果你启用剪切功能的话。
    关于剪切平面,我们在后面会做更多的讨论。继续吧:)  
       

    int DrawGLScene(GLvoid)
    {
     // 清除缓存
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

     // 设置剪切平面
     double eqr[] = {0.0f,-1.0f, 0.0f, 0.0f};    
       
    下面我们把地面向下平移0.6个单位,因为我们的眼睛在y=0的平面,如果不平移的话,那么看上去平面就会变为一条线,为了看起来更真实,我们平移了它。  
       

     glLoadIdentity();       
     glTranslatef(0.0f, -0.6f, zoom);     // 平移和缩放地面

       
    下面我们设置了颜色掩码,在默认情况下所有的颜色都可以写入,即在函数glColorMask中,所有的参数都被设为GL_TRUE,如果设为零表示这部分颜色不可写入。现在我们不希望在屏幕上绘制任何东西,所以把参数设为0。  
       

     glColorMask(0,0,0,0);

       
    下面来设置蒙板缓存和蒙板测试。
    首先我们启用蒙板测试,这样就可以修改蒙板缓存中的值。

    下面我们来解释蒙板测试函数的含义:
    当你使用glEnable(GL_STENCIL_TEST)启用蒙板测试之后,蒙板函数用于确定一个颜色片段是应该丢弃还是保留(被绘制)。蒙板缓存区中的值与参考值ref进行比较,比较标准是func所指定的比较函数。参考值和蒙板缓存区的值都可以与掩码进行为AND操作。蒙板测试的结果还导致蒙板缓存区根据glStencilOp函数所指定的行为进行修改。
    func的参数值如下:

    常量 含义
    GL_NEVER 从不通过蒙板测试
    GL_ALWAYS 总是通过蒙板测试
    GL_LESS 只有参考值<(蒙板缓存区的值&mask)时才通过
    GL_LEQUAL 只有参考值<=(蒙板缓存区的值&mask)时才通过
    GL_EQUAL 只有参考值=(蒙板缓存区的值&mask)时才通过
    GL_GEQUAL 只有参考值>=(蒙板缓存区的值&mask)时才通过
    GL_GREATER 只有参考值>(蒙板缓存区的值&mask)时才通过
    GL_NOTEQUAL 只有参考值!=(蒙板缓存区的值&mask)时才通过

    接下来我们解释glStencilOp函数,它用来根据比较结果修改蒙板缓存区中的值,它的函数原形为:
    void glStencilOp(GLenum sfail, GLenum zfail, GLenum zpass),各个参数的含义如下:
    sfail
    当蒙板测试失败时所执行的操作

    zfail
    当蒙板测试通过,深度测试失败时所执行的操作

    zpass
    当蒙板测试通过,深度测试通过时所执行的操作

    具体的操作包括以下几种
    常量 描述
    GL_KEEP 保持当前的蒙板缓存区值
    GL_ZERO 把当前的蒙板缓存区值设为0
    GL_REPLACE 用glStencilFunc函数所指定的参考值替换蒙板参数值
    GL_INCR 增加当前的蒙板缓存区值,但限制在允许的范围内
    GL_DECR 减少当前的蒙板缓存区值,但限制在允许的范围内
    GL_INVERT 将当前的蒙板缓存区值进行逐位的翻转

    当完成了以上操作后我们绘制一个地面,当然现在你什么也看不到,它只是把覆盖地面的蒙板缓存区中的相应位置设为1。
      
       

     glEnable(GL_STENCIL_TEST);    // 启用蒙板缓存
     glStencilFunc(GL_ALWAYS, 1, 1);   // 设置蒙板测试总是通过,参考值设为1,掩码值也设为1
     glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);  // 设置当深度测试不通过时,保留蒙板中的值不变。如果通过则使用参考值替换蒙板值
     glDisable(GL_DEPTH_TEST);    // 禁用深度测试
     DrawFloor();     // 绘制地面

       
    我们现在已经在蒙板缓存区中建立了地面的蒙板了,这是绘制影子的关键,如果想知道为什么,接着向后看吧:)
    下面我们启用深度测试和绘制颜色,并相应设置蒙板测试和函数的值,这种设置可以使我们在屏幕上绘制而不改变蒙板缓存区的值。
      
       

     glEnable(GL_DEPTH_TEST);      //启用深度测试
     glColorMask(1,1,1,1);      // 可以绘制颜色
     glStencilFunc(GL_EQUAL, 1, 1);     //下面的设置指定当我们绘制时,不改变蒙板缓存区的值
     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);  
       
    下面的代码设置并启用剪切平面,使得只能在地面的下方绘制  
       

     glEnable(GL_CLIP_PLANE0);      // 使用剪切平面
     glClipPlane(GL_CLIP_PLANE0, eqr);     // 设置剪切平面为地面,并设置它的法线为向下
     glPushMatrix();       // 保存当前的矩阵
      glScalef(1.0f, -1.0f, 1.0f);    // 沿Y轴反转

       
    由于上面已经启用了蒙板缓存,则你只能在蒙板中值为1的地方绘制,反射的实质就是在反射屏幕的对应位置在绘制一个物体,并把它放置在反射平面中。下面的代码完成这个功能  
       

      glLightfv(GL_LIGHT0, GL_POSITION, LightPos);   // 设置灯光0
      glTranslatef(0.0f, height, 0.0f);    
      glRotatef(xrot, 1.0f, 0.0f, 0.0f);    
      glRotatef(yrot, 0.0f, 1.0f, 0.0f);    
      DrawObject();      // 绘制反射的球
     glPopMatrix();       // 弹出保存的矩阵
     glDisable(GL_CLIP_PLANE0);      // 禁用剪切平面
     glDisable(GL_STENCIL_TEST);      // 关闭蒙板

       
    下面的代码绘制地面,并把地面颜色和反射的球颜色混合,使其看起来像反射的效果。  
       

     glLightfv(GL_LIGHT0, GL_POSITION, LightPos);
     glEnable(GL_BLEND);       // 启用混合
     glDisable(GL_LIGHTING);       // 关闭光照
     glColor4f(1.0f, 1.0f, 1.0f, 0.8f);      // 设置颜色为白色
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);    // 设置混合系数
     DrawFloor();        // 绘制地面

       
    下面的代码在距地面高为height的地方绘制一个真正的球  
       

     glEnable(GL_LIGHTING);       // 使用光照
     glDisable(GL_BLEND);       // 禁用混合
     glTranslatef(0.0f, height, 0.0f);      // 移动高位height的位置
     glRotatef(xrot, 1.0f, 0.0f, 0.0f);      // 设置球旋转的角度
     glRotatef(yrot, 0.0f, 1.0f, 0.0f);     
     DrawObject();        // 绘制球

       
    下面的代码用来处理键盘控制等常规操作  
       

     xrot += xrotspeed;       // 更新X轴旋转速度
     yrot += yrotspeed;       // 更新Y轴旋转速度
     glFlush();       // 强制OpenGL执行所有命令
     return TRUE;       // 成功返回
    }

       
    下面的代码处理键盘控制,上下左右控制球的旋转。PageUp/Pagedown控制球的上下。A,Z控制球离你的远近。  
       

    void ProcessKeyboard()        
    {
     if (keys[VK_RIGHT]) yrotspeed += 0.08f;    
     if (keys[VK_LEFT]) yrotspeed -= 0.08f;    
     if (keys[VK_DOWN]) xrotspeed += 0.08f;    
     if (keys[VK_UP]) xrotspeed -= 0.08f;   

     if (keys['A'])  zoom +=0.05f;     
     if (keys['Z'])  zoom -=0.05f;     

     if (keys[VK_PRIOR]) height +=0.03f;     
     if (keys[VK_NEXT]) height -=0.03f;     
    }

       
    KillGLWindow() 函数没有任何改变  
       

    GLvoid KillGLWindow(GLvoid)       

       
    CreateGLWindow()函数基本没有改变,只是添加了以行启用蒙板缓存  
       

     static PIXELFORMATDESCRIPTOR pfd=
     {
      sizeof(PIXELFORMATDESCRIPTOR),     
      1,        
      PFD_DRAW_TO_WINDOW |      
      PFD_SUPPORT_OPENGL |     
      PFD_DOUBLEBUFFER,      
      PFD_TYPE_RGBA,      
      bits,        
      0, 0, 0, 0, 0, 0,      
      0,        
      0,       
      0,        
      0, 0, 0, 0,       
      16,       
       
    下面就是在这个函数中唯一改变的地方,记得把0变为1,它启用蒙板缓存。  
       

      1,        // 使用蒙板缓存
      0,        
      PFD_MAIN_PLANE,       
      0,        
      0, 0, 0        
     };

       
    WinMain()函数基本没有变化,只是加上以行键盘控制的处理函数  
       

         ProcessKeyboard();   // 处理按键相应

       
    我真的希望你能喜欢这个教程,我清楚地知道我想做的每一件事,以及如何一步一步实现我心中想创建的效果。但把它表达出来又是另一回事,当你坐下来并实际的去向那些从来没听到过蒙板缓存的人解释这一切时,你就会清楚了。好了,如果你有什么不清楚的,或者有更好的建议,请让我知道,我想些最好的教程,你的反馈很重要!

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

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

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客4
    发贴心情 
    Lesson 26
       
    Welcome to another exciting tutorial. The code for this tutorial was written by Banu Cosmin. The tutorial was of course written by myself (NeHe). In this tutorial you will learn how to create EXTREMELY realistic reflections. Nothing fake here! The objects being reflected will not show up underneath the floor or on the other side of a wall. True reflections!

    A very important thing to note about this tutorial: Because the Voodoo 1, 2 and some other cards do not support the stencil buffer, this demo will NOT run on those cards. It will ONLY run on cards that support the stencil buffer. If you're not sure if your card supports the stencil buffer, download the code, and try running the demo. Also, this demo requires a fairly decent processor and graphics card. Even on my GeForce I notice there is a little slow down at times. This demo runs best in 32 bit color mode!

    As video cards get better, and processors get faster, I can see the stencil buffer becoming more popular. If you have the hardware and you're ready to reflect, read on!

    The first part of the code is fairly standard. We include all necessary header files, and set up our Device Context, Rendering Context, etc.   
       

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

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

       
    Next we have the standard variables to keep track of key presses (keys[ ]), whether or not the program is active (active), and if we should use fullscreen mode or windowed mode (fullscreen).   
       

    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

       
    Next we set up our lighting variables. LightAmb[ ] will set our ambient light. We will use 70% red, 70% green and 70% blue, creating a light that is 70% bright white. LightDif[ ] will set the diffuse lighting (the amount of light evenly reflected off the surface of our object). In this case we want to reflect full intensity light. Lastly we have LightPos[ ] which will be used to position our light. In this case we want the light 4 units to the right, 4 units up, and 6 units towards the viewer. If we could actually see the light, it would be floating in front of the top right corner of our screen.   
       

    // Light Parameters
    static GLfloat LightAmb[] = {0.7f, 0.7f, 0.7f, 1.0f};    // Ambient Light
    static GLfloat LightDif[] = {1.0f, 1.0f, 1.0f, 1.0f};    // Diffuse Light
    static GLfloat LightPos[] = {4.0f, 4.0f, 6.0f, 1.0f};    // Light Position

       
    We set up a variable called q for our quadratic object, xrot and yrot to keep track of rotation. xrotspeed and yrotspeed control the speed our object rotates at. zoom is used to zoom in and out of the scene (we start at -7 which shows us the entire scene) and height is the height of the ball above the floor.

    We then make room for our 3 textures with texture[3], and define WndProc().   
       

    GLUquadricObj *q;        // Quadratic For Drawing A Sphere

    GLfloat  xrot  =  0.0f;     // X Rotation
    GLfloat  yrot  =  0.0f;     // Y Rotation
    GLfloat  xrotspeed =  0.0f;     // X Rotation Speed
    GLfloat  yrotspeed =  0.0f;     // Y Rotation Speed
    GLfloat  zoom  = -7.0f;     // Depth Into The Screen
    GLfloat  height  =  2.0f;     // Height Of Ball From Floor

    GLuint  texture[3];       // 3 Textures

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

       
    The ReSizeGLScene() and LoadBMP() code has not changed so I will skip over both sections of code.   
       

    GLvoid ReSizeGLScene(GLsizei width, GLsizei height)    // Resize And Initialize The GL Window

    AUX_RGBImageRec *LoadBMP(char *Filename)     // Loads A Bitmap Image

       
    The load texture code is pretty standard. You've used it many times before in the previous tutorials. We make room for 3 textures, then we load the three images, and create linear filtered textures from the image data. The bitmap files we use are located in the DATA directory.   
       

    int LoadGLTextures()        // Load Bitmaps And Convert To Textures
    {
        int Status=FALSE;        // Status Indicator
        AUX_RGBImageRec *TextureImage[3];      // Create Storage Space For The Textures
        memset(TextureImage,0,sizeof(void *)*3);     // Set The Pointer To NULL
        if ((TextureImage[0]=LoadBMP("Data/EnvWall.bmp")) &&   // Load The Floor Texture
            (TextureImage[1]=LoadBMP("Data/Ball.bmp")) &&    // Load the Light Texture
            (TextureImage[2]=LoadBMP("Data/EnvRoll.bmp")))    // Load the Wall Texture
     {
      Status=TRUE;       // Set The Status To TRUE
      glGenTextures(3, &texture[0]);     // Create The Texture
      for (int loop=0; loop<3; loop++)    // Loop Through 5 Textures
      {
       glBindTexture(GL_TEXTURE_2D, texture[loop]);
       glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
       glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
       glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
      }
      for (loop=0; loop<3; loop++)     // Loop Through 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
    }

       
    A new command called glClearStencil is introduced in the init code. Passing 0 as a parameter tells OpenGL to disable clearing of the stencil buffer. You should be familiar with the rest of the code by now. We load our textures and enable smooth shading. The clear color is set to an off blue and the clear depth is set to 1.0f. The stencil clear value is set to 0. We enable depth testing, and set the depth test value to less than or equal to. Our perspective correction is set to nicest (very good quality) and 2d texture mapping is enabled.   
       

    int InitGL(GLvoid)        // All Setup For OpenGL Goes Here
    {
     if (!LoadGLTextures())       // If Loading The Textures Failed
     {
      return FALSE;       // Return False
     }
     glShadeModel(GL_SMOOTH);      // Enable Smooth Shading
     glClearColor(0.2f, 0.5f, 1.0f, 1.0f);     // Background
     glClearDepth(1.0f);       // Depth Buffer Setup
     glClearStencil(0);       // Clear The Stencil Buffer To 0
     glEnable(GL_DEPTH_TEST);      // Enables Depth Testing
     glDepthFunc(GL_LEQUAL);       // The Type Of Depth Testing To Do
     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);   // Really Nice Perspective Calculations
     glEnable(GL_TEXTURE_2D);      // Enable 2D Texture Mapping

       
    Now it's time to set up light 0. The first line below tells OpenGL to use the values stored in LightAmb for the Ambient light. If you remember at the beginning of the code, the rgb values of LightAmb were all 0.7f, giving us a white light at 70% full intensity. We then set the Diffuse light using the values stored in LightDif and position the light using the x,y,z values stored in LightPos.

    After we have set the light up we can enable it with glEnable(GL_LIGHT0). Even though the light is enabled, you will not see it until we enable lighting with the last line of code.

    Note: If we wanted to turn off all lights in a scene we would use glDisable(GL_LIGHTING). If we wanted to disable just one of our lights we would use glDisable(GL_LIGHT{0-7}). This gives us alot of control over the lighting and what lights are on and off. Just remember if GL_LIGHTING is disabled, you will not see lights!   
       

     glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb);    // Set The Ambient Lighting For Light0
     glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDif);    // Set The Diffuse Lighting For Light0
     glLightfv(GL_LIGHT0, GL_POSITION, LightPos);    // Set The Position For Light0

     glEnable(GL_LIGHT0);       // Enable Light 0
     glEnable(GL_LIGHTING);       // Enable Lighting

       
    In the first line below, we create a new quadratic object. The second line tells OpenGL to generate smooth normals for our quadratic object, and the third line tells OpenGL to generate texture coordinates for our quadratic. Without the second and third lines of code, our object would use flat shading and we wouldn't be able to texture it.

    The fourth and fifth lines tell OpenGL to use the Sphere Mapping algorithm to generate the texture coordinates. This allows us to sphere map the quadratic object.   
       

     q = gluNewQuadric();       // Create A New Quadratic
     gluQuadricNormals(q, GL_SMOOTH);     // Generate Smooth Normals For The Quad
     gluQuadricTexture(q, GL_TRUE);      // Enable Texture Coords For The Quad

     glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);   // Set Up Sphere Mapping
     glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);   // Set Up Sphere Mapping

     return TRUE;        // Initialization Went OK
    }

       
    The code below will draw our object (which is a cool looking environment mapped beach ball).

    We set the color to full intensity white and bind to our BALL texture (the ball texture is a series of red, white and blue stripes).

    After selecting our texture, we draw a Quadratic Sphere with a radius of 0.35f, 32 slices and 16 stacks (up and down).   
       

    void DrawObject()        // Draw Our Ball
    {
     glColor3f(1.0f, 1.0f, 1.0f);      // Set Color To White
     glBindTexture(GL_TEXTURE_2D, texture[1]);    // Select Texture 2 (1)
     gluSphere(q, 0.35f, 32, 16);      // Draw First Sphere

       
    After drawing the first sphere, we select a new texture (EnvRoll), set the alpha value to 40% and enable blending based on the source alpha value. glEnable(GL_TEXTURE_GEN_S) and glEnable(GL_TEXTURE_GEN_T) enables sphere mapping.

    After doing all that, we redraw the sphere, disable sphere mapping and disable blending.

    The final result is a reflection that almost looks like bright points of light mapped to the beach ball. Because we enable sphere mapping, the texture is always facing the viewer, even as the ball spins. We blend so that the new texture doesn't cancel out the old texture (a form of multitexturing).   
       

     glBindTexture(GL_TEXTURE_2D, texture[2]);    // Select Texture 3 (2)
     glColor4f(1.0f, 1.0f, 1.0f, 0.4f);     // Set Color To White With 40% Alpha
     glEnable(GL_BLEND);       // Enable Blending
     glBlendFunc(GL_SRC_ALPHA, GL_ONE);     // Set Blending Mode To Mix Based On SRC Alpha
     glEnable(GL_TEXTURE_GEN_S);      // Enable Sphere Mapping
     glEnable(GL_TEXTURE_GEN_T);      // Enable Sphere Mapping

     gluSphere(q, 0.35f, 32, 16);      // Draw Another Sphere Using New Texture
              // Textures Will Mix Creating A MultiTexture Effect (Reflection)
     glDisable(GL_TEXTURE_GEN_S);      // Disable Sphere Mapping
     glDisable(GL_TEXTURE_GEN_T);      // Disable Sphere Mapping
     glDisable(GL_BLEND);       // Disable Blending
    }

       
    The code below draws the floor that our ball hovers over. We select the floor texture (EnvWall), and draw a single texture mapped quad on the z-axis. Pretty simple!   
       

    void DrawFloor()        // Draws The Floor
    {
     glBindTexture(GL_TEXTURE_2D, texture[0]);    // Select Texture 1 (0)
     glBegin(GL_QUADS);       // Begin Drawing A Quad
      glNormal3f(0.0, 1.0, 0.0);     // Normal Pointing Up
      glTexCoord2f(0.0f, 1.0f);     // Bottom Left Of Texture
      glVertex3f(-2.0, 0.0, 2.0);     // Bottom Left Corner Of Floor

      glTexCoord2f(0.0f, 0.0f);     // Top Left Of Texture
      glVertex3f(-2.0, 0.0,-2.0);     // Top Left Corner Of Floor

      glTexCoord2f(1.0f, 0.0f);     // Top Right Of Texture
      glVertex3f( 2.0, 0.0,-2.0);     // Top Right Corner Of Floor

      glTexCoord2f(1.0f, 1.0f);     // Bottom Right Of Texture
      glVertex3f( 2.0, 0.0, 2.0);     // Bottom Right Corner Of Floor
     glEnd();        // Done Drawing The Quad
    }

       
    Now for the fun stuff. Here's where we combine all the objects and images to create our reflective scene.

    We start off by clearing the screen (GL_COLOR_BUFFER_BIT) to our default clear color (off blue). The depth (GL_DEPTH_BUFFER_BIT) and stencil (GL_STENCIL_BUFFER_BIT) buffers are also cleared. Make sure you include the stencil buffer code, it's new and easy to overlook! It's important to note when we clear the stencil buffer, we are filling it with 0's.

    After clearing the screen and buffers, we define our clipping plane equation. The plane equation is used for clipping the reflected image.

    The equation eqr[]={0.0f,-1.0f, 0.0f, 0.0f} will be used when we draw the reflected image. As you can see, the value for the y-plane is a negative value. Meaning we will only see pixels if they are drawn below the floor or at a negative value on the y-axis. Anything drawn above the floor will not show up when using this equation.

    More on clipping later... read on.   
       

    int DrawGLScene(GLvoid)        // Draw Everything
    {
     // Clear Screen, Depth Buffer & Stencil Buffer
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

     // Clip Plane Equations
     double eqr[] = {0.0f,-1.0f, 0.0f, 0.0f};    // Plane Equation To Use For The Reflected Objects

       
    So we have cleared the screen, and defined our clipping planes. Now for the fun stuff!

    We start off by resetting the modelview matrix. Which of course starts all drawing in the center of the screen. We then translate down 0.6f units (to add a small perspective tilt to the floor) and into the screen based on the value of zoom. To better explain why we translate down 0.6f units, I'll explain using a simple example. If you were looking at the side of a piece of paper at exactly eye level, you would barely be able to see it. It would more than likely look like a thin line. If you moved the paper down a little, it would no longer look like a line. You would see more of the paper, because your eyes would be looking down at the page instead of directly at the edge of the paper.   
       

     glLoadIdentity();       // Reset The Modelview Matrix
     glTranslatef(0.0f, -0.6f, zoom);     // Zoom And Raise Camera Above The Floor (Up 0.6 Units)

       
    Next we set the color mask. Something new to this tutorial! The 4 values for color mask represent red, green, blue and alpha. By default all the values are set to GL_TRUE.

    If the red value of glColorMask({red},{green},{blue},{alpha}) was set to GL_TRUE, and all of the other values were 0 (GL_FALSE), the only color that would show up on the screen is red. If the value for red was 0 (GL_FALSE), but the other values were all GL_TRUE, every color except red would be drawn to the screen.

    We don't want anything drawn to the screen at the moment, with all of the values set to 0 (GL_FALSE), colors will not be drawn to the screen.   
       

     glColorMask(0,0,0,0);       // Set Color Mask

       
    Now even more fun stuff... Setting up the stencil buffer and stencil testing!

    We start off by enabling stencil testing. Once stencil testing has been enabled, we are able to modify the stencil buffer.

    It's very hard to explain the commands below so please bear with me, and if you have a better explanation, please let me know. In the code below we set up a test. The line glStencilFunc(GL_ALWAYS, 1, 1) tells OpenGL what type of test we want to do on each pixel when an object is drawn to the screen.

    GL_ALWAYS just tells OpenGL the test will always pass. The second parameter (1) is a reference value that we will test in the third line of code, and the third parameter is a mask. The mask is a value that is ANDed with the reference value and stored in the stencil buffer when the test is done. A reference value of 1 ANDed with a mask value of 1 is 1. So if the test goes well and we tell OpenGL to, it will place a one in the stencil buffer (reference&mask=1).

    Quick note: Stencil testing is a per pixel test done each time an object is drawn to the screen. The reference value ANDed with the mask value is tested against the current stencil value ANDed with the mask value.

    The third line of code tests for three different conditions based on the stencil function we decided to use. The first two parameters are GL_KEEP, and the third is GL_REPLACE.

    The first parameter tells OpenGL what to do if the test fails. Because the first parameter is GL_KEEP, if the test fails (which it can't because we have the funtion set to GL_ALWAYS), we would leave the stencil value set at whatever it currently is.

    The second parameter tells OpenGL what do do if the stencil test passes, but the depth test fails. In the code below, we eventually disable depth testing so this parameter can be ignored.

    The third parameter is the important one. It tells OpenGL what to do if the test passes! In our code we tell OpenGL to replace (GL_REPLACE) the value in the stencil buffer. The value we put into the stencil buffer is our reference value ANDed with our mask value which is 1.

    After setting up the type of testing we want to do, we disable depth testing and jump to the code that draws our floor.

    In simple english I will try to sum up everything that the code does up until now...

    We tell OpenGL not to draw any colors to the screen. This means that when we draw the floor, it wont show up on the screen. BUT... each spot on the screen where the object (our floor) should be if we could see it will be tested based on the type of stencil testing we decide to do. The stencil buffer starts out full of 0's (empty). We want to set the stencil value to 1 wherever our object would have been drawn if we could see it. So we tell OpenGL we don't care about testing. If a pixel should have been drawn to the screen, we want that spot marked with a 1. GL_ALWAYS does exactly that. Our reference and mask values of 1 make sure that the value placed into the stencil buffer is indeed going to be 1! As we invisibly draw, our stencil operation checks each pixel location, and replaces the 0 with a 1.   
       

     glEnable(GL_STENCIL_TEST);      // Enable Stencil Buffer For "marking" The Floor
     glStencilFunc(GL_ALWAYS, 1, 1);      // Always Passes, 1 Bit Plane, 1 As Mask
     glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);    // We Set The Stencil Buffer To 1 Where We Draw Any Polygon
              // Keep If Test Fails, Keep If Test Passes But Buffer Test Fails
              // Replace If Test Passes
     glDisable(GL_DEPTH_TEST);      // Disable Depth Testing
     DrawFloor();        // Draw The Floor (Draws To The Stencil Buffer)
              // We Only Want To Mark It In The Stencil Buffer

       
    So now we have an invisible stencil mask of the floor. As long as stencil testing is enabled, the only places pixels will show up are places where the stencil buffer has a value of 1. All of the pixels on the screen where the invisible floor was drawn will have a stencil value of 1. Meaning as long as stencil testing is enabled, the only pixels that we will see are the pixels that we draw in the same spot our invisible floor was defined in the stencil buffer. The trick behind creating a real looking reflection that reflects in the floor and nowhere else!

    So now that we know the ball reflection will only be drawn where the floor should be, it's time to draw the reflection! We enable depth testing, and set the color mask back to all ones (meaning all the colors will be drawn to the screen).

    Instead of using GL_ALWAYS for our stencil function we are going to use GL_EQUAL. We'll leave the reference and mask values at 1. For the stencil operation we will set all the parameters to GL_KEEP. In english, any object we draw this time around will actually appear on the screen (because the color mask is set to true for each color). As long as stencil testing is enabled pixels will ONLY be drawn if the stencil buffer has a value of 1 (reference value ANDed with the mask, which is 1 EQUALS (GL_EQUAL) the stencil buffer value ANDed with the mask, which is also 1). If the stencil value is not 1 where the current pixel is being drawn it will not show up! GL_KEEP just tells OpenGL not to modify any values in the stencil buffer if the test passes OR fails!   
       

     glEnable(GL_DEPTH_TEST);      // Enable Depth Testing
     glColorMask(1,1,1,1);       // Set Color Mask to TRUE, TRUE, TRUE, TRUE
     glStencilFunc(GL_EQUAL, 1, 1);      // We Draw Only Where The Stencil Is 1
              // (I.E. Where The Floor Was Drawn)
     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);     // Don't Change The Stencil Buffer

       
    Now we enable the mirrored clipping plane. This plane is defined by eqr, and only allows object to be drawn from the center of the screen (where the floor is) down to the bottom of the screen (any negative value on the y-axis). That way the reflected ball that we draw can't come up through the center of the floor. That would look pretty bad if it did. If you don't understand what I mean, remove the first line below from the source code, and move the real ball (non reflected) through the floor. If clipping is not enabled, you will see the reflected ball pop out of the floor as the real ball goes into the floor.

    After we enable clipping plane0 (usually you can have from 0-5 clipping planes), we define the plane by telling it to use the parameters stored in eqr.

    We push the matrix (which basically saves the position of everything on the screen) and use glScalef(1.0f,-1.0f,1.0f) to flip the object upside down (creating a real looking reflection). Setting the y value of glScalef({x},{y},{z}) to a negative value forces OpenGL to render opposite on the y-axis. It's almost like flipping the entire screen upside down. When position an object at a positive value on the y-axis, it will appear at the bottom of the screen instead of at the top. When you rotate an object towards yourself, it will rotate away from you. Everything will be mirrored on the y-axis until you pop the matrix or set the y value back to 1.0f instead of -1.0f using glScalef({x},{y},{z}).   
       

     glEnable(GL_CLIP_PLANE0);      // Enable Clip Plane For Removing Artifacts
              // (When The Object Crosses The Floor)
     glClipPlane(GL_CLIP_PLANE0, eqr);     // Equation For Reflected Objects
     glPushMatrix();        // Push The Matrix Onto The Stack
      glScalef(1.0f, -1.0f, 1.0f);     // Mirror Y Axis

       
    The first line below positions our light to the location specified by LightPos. The light should shine on the bottom right of the reflected ball creating a very real looking light source. The position of the light is also mirrored. On the real ball (ball above the floor) the light is positioned at the top right of your screen, and shines on the top right of the real ball. When drawing the reflected ball, the light is positioned at the bottom right of your screen.

    We then move up or down on the y-axis to the value specified by height. Translations are mirrored, so if the value of height is 5.0f, the position we translate to will be mirrored (-5.0f). Positioning the reflected image under the floor, instead of above the floor!

    After position our reflected ball, we rotate the ball on both the x axis and y axis, based on the values of xrot and yrot. Keep in mind that any rotations on the x axis will also be mirrored. So if the real ball (ball above the floor) is rolling towards you on the x-axis, it will be rolling away from you in the reflection.

    After positioning the reflected ball and doing our rotations we draw the ball by calling DrawObject(), and pop the matrix (restoring things to how they were before we drew the ball). Popping the matrix all cancels mirroring on the y-axis.

    We then disable our clipping plane (plane0) so that we are not stuck drawing only to the bottom half of the screen, and last, we disable stencil testing so that we can draw to other spots on the screen other than where the floor should be.

    Note that we draw the reflected ball before we draw the floor. I'll explain why later on.   
       

      glLightfv(GL_LIGHT0, GL_POSITION, LightPos);   // Set Up Light0
      glTranslatef(0.0f, height, 0.0f);    // Position The Object
      glRotatef(xrot, 1.0f, 0.0f, 0.0f);    // Rotate Local Coordinate System On X Axis
      glRotatef(yrot, 0.0f, 1.0f, 0.0f);    // Rotate Local Coordinate System On Y Axis
      DrawObject();       // Draw The Sphere (Reflection)
     glPopMatrix();        // Pop The Matrix Off The Stack
     glDisable(GL_CLIP_PLANE0);      // Disable Clip Plane For Drawing The Floor
     glDisable(GL_STENCIL_TEST);      // We Don't Need The Stencil Buffer Any More (Disable)

       
    We start off this section of code by positioning our light. The y-axis is no longer being mirrored so drawing the light this time around will position it at the top of the screen instead of the bottom right of the screen.

    We enable blending, disable lighting, and set the alpha value to 80% using the command glColor4f(1.0f,1.0f,1.0f,0.8f). The blending mode is set up using glBlendFunc(), and the semi transparent floor is drawn over top of the reflected ball.

    If we drew the floor first and then the reflected ball, the effect wouldn't look very good. By drawing the ball and then the floor, you can see a small amount of coloring from the floor mixed into the coloring of the ball. If I was looking into a BLUE mirror, I would expect the reflection to look a little blue. By rendering the ball first, the reflected image looks like it's tinted the color of the floor.   
       

     glLightfv(GL_LIGHT0, GL_POSITION, LightPos);    // Set Up Light0 Position
     glEnable(GL_BLEND);       // Enable Blending (Otherwise The Reflected Object Wont Show)
     glDisable(GL_LIGHTING);       // Since We Use Blending, We Disable Lighting
     glColor4f(1.0f, 1.0f, 1.0f, 0.8f);     // Set Color To White With 80% Alpha
     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);   // Blending Based On Source Alpha And 1 Minus Dest Alpha
     DrawFloor();        // Draw The Floor To The Screen

       
    Now we draw the 'real' ball (the one that floats above the floor). We disabled lighting when we drew the floor, but now it's time to draw another ball so we will turn lighting back on.

    We don't need blending anymore so we disable blending. If we didn't disable blending, the colors from the floor would mix with the colors of our 'real' ball when it was floating over top of the floor. We don't want the 'real' ball to look like the reflection so we disable blending.

    We are not going to clip the actual ball. If the real ball goes through the floor, we should see it come out the bottom. If we were using clipping the ball wouldn't show up after it went through the floor. If you didn't want to see the ball come through the floor, you would set up a clipping equation that set the Y value to +1.0f, then when the ball went through the floor, you wouldn't see it (you would only see the ball when it was drawn on at a positive value on the y-axis. For this demo, there's no reason we shouldn't see it come through the floor.

    We then translate up or down on the y-axis to the position specified by height. Only this time the y-axis is not mirrored, so the ball travels the opposite direction that the reflected image travels. If we move the 'real' ball down the reflected ball will move up. If we move the 'real' ball up, the reflected ball will move down.

    We rotate the 'real' ball, and again, because the y-axis is not mirrored, the ball will spin the opposite direction of the reflected ball. If the reflected ball is rolling towards you the 'real' ball will be rolling away from you. This creates the illusion of a real reflection.

    After positioning and rotating the ball, we draw the 'real' ball by calling DrawObject().   
       

     glEnable(GL_LIGHTING);       // Enable Lighting
     glDisable(GL_BLEND);       // Disable Blending
     glTranslatef(0.0f, height, 0.0f);     // Position The Ball At Proper Height
     glRotatef(xrot, 1.0f, 0.0f, 0.0f);     // Rotate On The X Axis
     glRotatef(yrot, 0.0f, 1.0f, 0.0f);     // Rotate On The Y Axis
     DrawObject();        // Draw The Ball

       
    The following code rotates the ball on the x and y axis. By increasing xrot by xrotspeed we rotate the ball on the x-axis. By increasing yrot by yrotspeed we spin the ball on the y-axis. If xrotspeed is a very high value in the positive or negative direction the ball will spin quicker than if xrotspeed was a low value, closer to 0.0f. Same goes for yrotspeed. The higher the value, the faster the ball spins on the y-axis.

    Before we return TRUE, we do a glFlush(). This tells OpenGL to render everything left in the GL pipeline before continuing, and can help prevent flickering on slower video cards.   
       

     xrot += xrotspeed;       // Update X Rotation Angle By xrotspeed
     yrot += yrotspeed;       // Update Y Rotation Angle By yrotspeed
     glFlush();        // Flush The GL Pipeline
     return TRUE;        // Everything Went OK
    }

       
    The following code will watch for key presses. The first 4 lines check to see if you are pressing one of the 4 arrow keys. If you are, the ball is spun right, left, down or up.

    The next 2 lines check to see if you are pressing the 'A' or 'Z' keys. Pressing 'A' will zoom you in closer to the ball and pressing 'Z' will zoom you away from the ball.

    Pressing 'PAGE UP' will increase the value of height moving the ball up, and pressing 'PAGE DOWN' will decrease the value of height moving the ball down (closer to the floor).   
       

    void ProcessKeyboard()        // Process Keyboard Results
    {
     if (keys[VK_RIGHT]) yrotspeed += 0.08f;    // Right Arrow Pressed (Increase yrotspeed)
     if (keys[VK_LEFT]) yrotspeed -= 0.08f;    // Left Arrow Pressed (Decrease yrotspeed)
     if (keys[VK_DOWN]) xrotspeed += 0.08f;    // Down Arrow Pressed (Increase xrotspeed)
     if (keys[VK_UP]) xrotspeed -= 0.08f;    // Up Arrow Pressed (Decrease xrotspeed)

     if (keys['A'])  zoom +=0.05f;     // 'A' Key Pressed ... Zoom In
     if (keys['Z'])  zoom -=0.05f;     // 'Z' Key Pressed ... Zoom Out

     if (keys[VK_PRIOR]) height +=0.03f;     // Page Up Key Pressed Move Ball Up
     if (keys[VK_NEXT]) height -=0.03f;     // Page Down Key Pressed Move Ball Down
    }

       
    The KillGLWindow() code hasn't changed, so I'll skip over it.   
       

    GLvoid KillGLWindow(GLvoid)       // Properly Kill The Window

       
    You can skim through the following code. Even though only one line of code has changed in CreateGLWindow(), I have included all of the code so it's easier to follow through the tutorial.   
       

    BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
    {
     GLuint  PixelFormat;      // Holds The Results After Searching For A Match
     WNDCLASS wc;       // Windows Class Structure
     DWORD  dwExStyle;      // Window Extended Style
     DWORD  dwStyle;      // Window Style

     fullscreen=fullscreenflag;      // Set The Global Fullscreen Flag

     hInstance  = GetModuleHandle(NULL);   // Grab An Instance For Our Window
     wc.style  = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;  // Redraw On Size, And Own DC For Window
     wc.lpfnWndProc  = (WNDPROC) WndProc;    // WndProc Handles Messages
     wc.cbClsExtra  = 0;      // No Extra Window Data
     wc.cbWndExtra  = 0;      // No Extra Window Data
     wc.hInstance  = hInstance;     // Set The Instance
     wc.hIcon  = LoadIcon(NULL, IDI_WINLOGO);   // Load The Default Icon
     wc.hCursor  = LoadCursor(NULL, IDC_ARROW);   // Load The Arrow Pointer
     wc.hbrBackground = NULL;      // No Background Required For GL
     wc.lpszMenuName  = NULL;      // We Don't Want A Menu
     wc.lpszClassName = "OpenGL";     // Set The Class Name

     if (!RegisterClass(&wc))      // Attempt To Register The Window Class
     {
      MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

     if (fullscreen)        // Attempt Fullscreen Mode?
     {
      DEVMODE dmScreenSettings;     // Device Mode
      memset(&dmScreenSettings,0,sizeof(dmScreenSettings));  // Makes Sure Memory's Cleared
      dmScreenSettings.dmSize=sizeof(dmScreenSettings);  // Size Of The Devmode Structure
      dmScreenSettings.dmPelsWidth = width;   // Selected Screen Width
      dmScreenSettings.dmPelsHeight = height;   // Selected Screen Height
      dmScreenSettings.dmBitsPerPel = bits;    // Selected Bits Per Pixel
      dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

      // Try To Set Selected Mode And Get Results.  NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar
      if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
      {
       // If The Mode Fails, Offer Two Options.  Quit Or Use Windowed Mode
       if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
       {
        fullscreen=FALSE;    // Windowed Mode Selected.  Fullscreen = FALSE
       }
       else
       {
        // Pop Up A Message Box Letting User Know The Program Is Closing
        MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
        return FALSE;     // Return FALSE
       }
      }
     }

     if (fullscreen)        // Are We Still In Fullscreen Mode?
     {
      dwExStyle=WS_EX_APPWINDOW;     // Window Extended Style
      dwStyle=WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;  // Windows Style
      ShowCursor(FALSE);      // Hide Mouse Pointer
     }
     else
     {
      dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;   // Window Extended Style
      dwStyle=WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;// Windows Style
     }

     // Create The Window
     if (!(hWnd=CreateWindowEx( dwExStyle,    // Extended Style For The Window
         "OpenGL",    // Class Name
         title,     // Window Title
         dwStyle,    // Window Style
         0, 0,     // Window Position
         width, height,    // Selected Width And Height
         NULL,     // No Parent Window
         NULL,     // No Menu
         hInstance,    // Instance
         NULL)))     // Dont Pass Anything To WM_CREATE
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

     static PIXELFORMATDESCRIPTOR pfd=     // pfd Tells Windows How We Want Things To Be
     {
      sizeof(PIXELFORMATDESCRIPTOR),     // Size Of This Pixel Format Descriptor
      1,        // Version Number
      PFD_DRAW_TO_WINDOW |      // Format Must Support Window
      PFD_SUPPORT_OPENGL |      // Format Must Support OpenGL
      PFD_DOUBLEBUFFER,      // Must Support Double Buffering
      PFD_TYPE_RGBA,       // Request An RGBA Format
      bits,        // Select Our Color Depth
      0, 0, 0, 0, 0, 0,      // Color Bits Ignored
      0,        // No Alpha Buffer
      0,        // Shift Bit Ignored
      0,        // No Accumulation Buffer
      0, 0, 0, 0,       // Accumulation Bits Ignored
      16,        // 16Bit Z-Buffer (Depth Buffer)

       
    The only change in this section of code is the line below. It is *VERY IMPORTANT* you change the value from 0 to 1 or some other non zero value. In all of the previous tutorials the value of the line below was 0. In order to use Stencil Buffering this value HAS to be greater than or equal to 1. This value is the number of bits you want to use for the stencil buffer.   
       

      1,        // Use Stencil Buffer ( * Important * )
      0,        // No Auxiliary Buffer
      PFD_MAIN_PLANE,       // Main Drawing Layer
      0,        // Reserved
      0, 0, 0        // Layer Masks Ignored
     };

     if (!(hDC=GetDC(hWnd)))       // Did We Get A Device Context?
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

     if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))    // Did Windows Find A Matching Pixel Format?
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

     if(!SetPixelFormat(hDC,PixelFormat,&pfd))    // Are We Able To Set The Pixel Format?
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

     if (!(hRC=wglCreateContext(hDC)))     // Are We Able To Get A Rendering Context?
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

     if(!wglMakeCurrent(hDC,hRC))      // Try To Activate The Rendering Context
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

     ShowWindow(hWnd,SW_SHOW);      // Show The Window
     SetForegroundWindow(hWnd);      // Slightly Higher Priority
     SetFocus(hWnd);        // Sets Keyboard Focus To The Window
     ReSizeGLScene(width, height);      // Set Up Our Perspective GL Screen

     if (!InitGL())        // Initialize Our Newly Created GL Window
     {
      KillGLWindow();       // Reset The Display
      MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
      return FALSE;       // Return FALSE
     }

     return TRUE;        // Success
    }

       
    WndProc() has not changed, so we will skip over it.   
       

    LRESULT CALLBACK WndProc( HWND hWnd,     // Handle For This Window
        UINT uMsg,     // Message For This Window
        WPARAM wParam,     // Additional Message Information
        LPARAM lParam)     // Additional Message Information

       
    Nothing new here. Typical start to WinMain().   
       

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

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

       
    The only real big change in this section of the code is the new window title to let everyone know the tutorial is about reflections using the stencil buffer. Also notice that we pass the resx, resy and resbpp variables to our window creation procedure instead of the usual 640, 480 and 16.   
       

     // Create Our OpenGL Window
     if (!CreateGLWindow("Banu Octavian & NeHe's Stencil & Reflection Tutorial", resx, resy, resbpp, 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)      // Program Active?
       {
        if (keys[VK_ESCAPE])    // Was Escape Pressed?
        {
         done=TRUE;    // ESC Signalled A Quit
        }
        else      // Not Time To Quit, Update Screen
        {
         DrawGLScene();    // Draw The Scene
         SwapBuffers(hDC);   // Swap Buffers (Double Buffering)

       
    Instead of checking for key presses in WinMain(), we jump to our keyboard handling routine called ProcessKeyboard(). Notice the ProcessKeyboard() routine is only called if the program is active!   
       

         ProcessKeyboard();   // Processed Keyboard Presses
        }
       }
      }
     }

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

       
    I really hope you've enjoyed this tutorial. I know it could use a little more work. It was one of the more difficult tutorials that I have written. It's easy for me to understand what everything is doing, and what commands I need to use to create cool effects, but when you sit down and actually try to explain things keeping in mind that most people have never even heard of the stencil buffer, it's tough! If you notice anything that could be made clearer or if you find any mistakes in the tutorial please let me know. As always, I want this tutorial to be the best it can possibly be, your feedback is greatly appreciated.

    Banu Cosmin (Choko)

    Jeff Molofee (NeHe)

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

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/22 19:39:00
     
     lamb0145 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:1
      积分:58
      门派:XML.ORG.CN
      注册:2010/4/9

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给lamb0145发送一个短消息 把lamb0145加入好友 查看lamb0145的个人资料 搜索lamb0145在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看lamb0145的博客5
    发贴心情 
    好东西,谢谢分享。。。
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2010/4/10 10:42:00
     
     tingweide 美女呀,离线,快来找我吧!
      
      
      等级:大一新生
      文章:1
      积分:56
      门派:XML.ORG.CN
      注册:2011/7/13

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给tingweide发送一个短消息 把tingweide加入好友 查看tingweide的个人资料 搜索tingweide在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看tingweide的博客6
    发贴心情 
    太感谢楼主了,那三个txt文档真的好强大
    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2011/7/13 10:39:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/4/28 9:01:17

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

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