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

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

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

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

    第四十一 四十二课 源码  


    第四十一课


    按此在新窗口浏览图片体积雾气

    把雾坐标绑定到顶点,你可以在雾中漫游,体验一下吧。

      
       

      
    这一课我们将介绍体积雾,为了运行这个程序,你的显卡必须支持扩展"GL_EXT_fot_coord"。  

      

    #include <windows.h>        
    #include <gl\gl.h>       
    #include <gl\glu.h>        
    #include <math.h>        

    #include "NeHeGL.h"       

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

    GL_Window* g_window;      
    Keys*  g_keys;        

      
    下面的代码设置雾的颜色和摄像机在Z方向的深度  

      

    GLfloat fogColor[4] = {0.6f, 0.3f, 0.0f, 1.0f};     // 雾的颜色
    GLfloat camz;         // 摄像机在Z方向的深度

       
    下面变量GL_FOG_COORDINATE_SOURCE_EXT和GL_FOG_COORDINATE_EXT具有初值,他们在glext.h文件中被定义,这里我们必须感谢Lev Povalahev,它创建了这个文件。如果你想编译你的代码,你必须设置这个值。
    为了使用glFogCoordfExt,我们需要定义这个函数的指针,并在程序运行时把它指向显卡中的函数。
      
       

    // 使用FogCoordfEXT它需要的变量
    #define GL_FOG_COORDINATE_SOURCE_EXT 0x8450     // 从GLEXT.H得到的值
    #define GL_FOG_COORDINATE_EXT  0x8451     

    typedef void (APIENTRY * PFNGLFOGCOORDFEXTPROC) (GLfloat coord);  // 声明雾坐标函数的原形

    PFNGLFOGCOORDFEXTPROC glFogCoordfEXT = NULL;     // 设置雾坐标函数指针为NULL

    GLuint texture[1];       // 纹理

       
    Nehe的原文介绍了Ipicture的接口,它不是我们这一课的重点,故我还是使用以前的方法加载纹理。  
       
       
    下面的代码用来检测用户的显卡是否支持EXT_fog_coord扩展,这段代码只有在你获得OpenGL渲染描述表后才能调用,否则你将获得一个错误。
    首先,我们创建一个字符串,来描述我们需要的扩展。接着我们分配一块内存,用来保存显卡支持的扩展,它可以通过glGetString函数获得。接着我们检测是否含有需要的扩展,如果不存在,则返回false,如存在我们把函数的指针指向这个扩展。  
       

    int Extension_Init()
    {
     char Extension_Name[] = "EXT_fog_coord";

     // 返回扩展字符串
     char* glextstring=(char *)malloc(strlen((char *)glGetString(GL_EXTENSIONS))+1);
     strcpy (glextstring,(char *)glGetString(GL_EXTENSIONS));  

     if (!strstr(glextstring,Extension_Name))    // 查找是否有我们想要的扩展
      return FALSE;       

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

     // 获得函数的指针
     glFogCoordfEXT = (PFNGLFOGCOORDFEXTPROC) wglGetProcAddress("glFogCoordfEXT");

     return TRUE;
    }

       
    下面的代码初始化OpenGL,并设置雾气的参数。  
       

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

     // 初始化扩展
     if (!Extension_Init())       
      return FALSE;       

     if (!BuildTexture("data/wall.bmp", texture[0]))    // 创建纹理
      return FALSE;       

     glEnable(GL_TEXTURE_2D);      
     glClearColor (0.0f, 0.0f, 0.0f, 0.5f);     
     glClearDepth (1.0f);       
     glDepthFunc (GL_LEQUAL);      
     glEnable (GL_DEPTH_TEST);      
     glShadeModel (GL_SMOOTH);      
     glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);   
       
    下面的代码设置雾气的属性
    最后一个设置为雾气基于顶点的坐标,这运行我们把雾气放置在场景中的任意地方。
      
       

     glEnable(GL_FOG);       
     glFogi(GL_FOG_MODE, GL_LINEAR);      
     glFogfv(GL_FOG_COLOR, fogColor);     
     glFogf(GL_FOG_START,  1.0f);      
     glFogf(GL_FOG_END,    0.0f);      
     glHint(GL_FOG_HINT, GL_NICEST);      
     glFogi(GL_FOG_COORDINATE_SOURCE_EXT, GL_FOG_COORDINATE_EXT);  //设置雾气是基于顶点的坐标的

     camz = -19.0f;        

     return TRUE;       
    }
       
    下面的代码绘制具体的场景  
       

    void Draw (void)
    {
     glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   
     glLoadIdentity ();       

     glTranslatef(0.0f, 0.0f, camz);      

       
    下面的代码绘制四边形组成的墙,并设置每个顶点的纹理坐标和雾坐标  
       

     glBegin(GL_QUADS);       //后墙
       glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-2.5f,-2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 2.5f,-2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 2.5f, 2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-2.5f, 2.5f,-15.0f);
     glEnd();

     glBegin(GL_QUADS);       // 地面
       glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-2.5f,-2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 2.5f,-2.5f,-15.0f);
      glFogCoordfEXT(1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 2.5f,-2.5f, 15.0f);
      glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-2.5f,-2.5f, 15.0f);
     glEnd();

     glBegin(GL_QUADS);       // 天花板
      glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-2.5f, 2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 2.5f, 2.5f,-15.0f);
      glFogCoordfEXT(1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 2.5f, 2.5f, 15.0f);
      glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-2.5f, 2.5f, 15.0f);
     glEnd();

     glBegin(GL_QUADS);       // 右墙
      glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 2.5f,-2.5f, 15.0f);
      glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 2.5f, 2.5f, 15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 2.5f, 2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 2.5f,-2.5f,-15.0f);
     glEnd();

     glBegin(GL_QUADS);       // 左墙
       glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-2.5f,-2.5f, 15.0f);
      glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-2.5f, 2.5f, 15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-2.5f, 2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-2.5f,-2.5f,-15.0f);
     glEnd();

     glFlush ();                                                                                                
    }

       
    希望你喜欢这一课,如果你愿意的话创建更漂亮的体积雾吧。


       收藏   分享  
    顶(0)
      




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

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

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

      
    Welcome to another fun filled tutorial. This time I will attempt to explain Volumetric Fog using the glFogCoordf Extension. In order to run this demo, your video card must support the "GL_EXT_fog_coord" extension. If you are not sure if your card supports this extension, you have two options... 1) download the VC++ source code, and see if it runs. 2) download lesson 24, and scroll through the list of extensions supported by your video card.

    This tutorial will introduce you to the NeHe IPicture code which is capable of loading BMP, EMF, GIF, ICO, JPG and WMF files from your computer or a web page. You will also learn how to use the "GL_EXT_fog_coord" extension to create some really cool looking Volumetric Fog (fog that can float in a confined space without affecting the rest of the scene).

    If this tutorial does not work on your machine, the first thing you should do is check to make sure you have the latest video driver installed. If you have the latest driver and the demo still does not work... you might want to purchase a new video card. A low end GeForce 2 will work just fine, and should not cost all that much. If your card doesn't support the fog extension, who's to say what other extensions it will not support?

    For those of you that can't run the demo, and feel excluded... keep the following in mind: Every single day I get at least 1 email requesting a new tutorial. Many of the tutorials requested are already online! People don't bother reading what is already online and end up skipping over the topic they are most interested in. Other tutorials are too complex and would require weeks worth of programming on my end. Finally, there are the tutorials that I could write, but usually avoid because I know they will not run on all cards. Now that cards such as the GeForce are cheap enough that anyone with an allowance could afford one, I can no longer justify not writing the tutorials. Truthfully, if your video card only supports basic extensions, you are missing out! And if I continue to skip over topics such as Extensions, the tutorials will lag behind!

    With that said... lets attack some code!!!

    The code starts off very similar to the old basecode, and almost identical to the new NeHeGL basecode. The only difference is the extra line of code to include the OLECTL header file. This header must be included if you want the IPICTURE code to function. If you exclude this line, you will get errors when trying to use IPicture, OleLoadPicturePath and IID_IPicture.

    Just like the NeHeGL basecode, we use #pragma comment ( lib, ... ) to automatically include the required library files! Notice we no longer need to include the glaux library (I'm sure many of you are cheering right now).

    The next three lines of code check to see if CDS_FULLSCREEN is defined. If it is not (which it isn't in most compilers), we give it a value of 4. I know many of you have emailed me to ask why you get errors when trying to compile code using CDS_FULLSCREEN in DEV C++. Include these three lines and you will not get the error!   

      

    #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 <olectl.h>        // Header File For The OLE Controls Library (Used In BuildTexture)
    #include <math.h>        // Header File For The Math Library  (Used In BuildTexture)

    #include "NeHeGL.h"        // Header File For NeHeGL

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

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

    GL_Window* g_window;       // Window Structure
    Keys*  g_keys;        // Keyboard


      
    In the following code, we set the color of our fog. In this case we want it to be a dark orange color. A little red (0.6f) mixed with even less green (0.3f) will give us the color we desire.

    The floating point variable camz will be used later in the code to position our camera inside a long and dark hallway! We will move forwards and backwards through the hallway by translating on the Z-Axis before we draw the hallway.   

      

    // User Defined Variables
    GLfloat fogColor[4] = {0.6f, 0.3f, 0.0f, 1.0f};     // Fog Colour
    GLfloat camz;         // Camera Z Depth

       
    Just like CDS_FULLSCREEN has a predefined value of 4... the variables GL_FOG_COORDINATE_SOURCE_EXT and GL_FOG_COORDINATE_EXT also have predefined values. As mentioned in the comments, the values were taken from the GLEXT header file. A file that is freely available on the net. Huge thanks to Lev Povalahev for creating such a valuable header file! These values must be set if you want the code to compile! The end result is that we have two new enumerants available to us (GL_FOG_COORDINATE_SOURCE_EXT & GL_FOG_COORDINATE_EXT).

    To use the function glFogCoordfExt we need to declare a function prototype typedef that match the extension’s entry point. Sounds complex, but it is not all that bad. In English... we need to tell our program the number of parameters and the the type of each parameter accepted by the function glFogCoordfEXT. In this case... we are passing one parameter to this function and it is a floating point value (a coordinate).

    Next we have to declare a global variable of the type of the function prototype typedef. This is the first step to creating our new function (glFogCoordfEXT). It is global so that we can use the command anywhere in our code. The name we use should match the actual extension name exactly. The actual extension name is glFogCoordfEXT and the name we use is also glFogCoordfEXT.

    Once we use wglGetProcAddress to assign the function variable the address of the OpenGL driver’s extension function, we can call glFogCoordfEXT as if it was a normal function. More on this later!

    The last line prepares things for our single texture.

    So what we have so far...

    We know that PFNGLFOGCOORDFEXTPROC takes one floating point value (GLfloat coord)

    Because glFogCoordfEXT is type PFNGLFOGCOORDFEXTPROC it's safe to say glFogCoordfEXT takes one floating point value... Leaving us with glFogCoordfEXT(GLfloat coord).

    Our function is defined, but will not do anything because glFogCoordfEXT is NULL at the moment (we still need to attach glFogCoordfEXT to the Address of the OpenGL driver's extension function).

    Really hope that all makes sense... it's very simple when you already know how it works... but describing it is extremely difficult (at least for me it is). If anyone would like to rewrite this section of text using simple / non complicated wording, please send me an email! The only way I could explain it better is through images, and at the moment I am in a rush to get this tutorial online!   
       

    // Variables Necessary For FogCoordfEXT
    #define GL_FOG_COORDINATE_SOURCE_EXT 0x8450     // Value Taken From GLEXT.H
    #define GL_FOG_COORDINATE_EXT  0x8451     // Value Taken From GLEXT.H

    typedef void (APIENTRY * PFNGLFOGCOORDFEXTPROC) (GLfloat coord);  // Declare Function Prototype

    PFNGLFOGCOORDFEXTPROC glFogCoordfEXT = NULL;     // Our glFogCoordfEXT Function

    GLuint texture[1];        // One Texture (For The Walls)

       
    Now for the fun stuff... the actual code that turns an image into a texture using the magic of IPicture :)

    This function requires a pathname (path to the actual image we want to load... either a filename or a Web URL) and a texture ID (for example ... texture[0]).

    We need to create a device context for our temporary bitmap. We also need a place to store the bitmap data (hbmpTemp), a connection to the IPicture Interface, variables to store the path (file or URL). 2 variables to store the image width, and 2 variables to store the image height. lwidth and lheight store the actual image width and height. lwidthpixels and lheightpixels stores the width and height in pixels adjusted to fix the video cards maximum texture size. The maximum texture size will be stored in glMaxTexDim.   
       

    int BuildTexture(char *szPathName, GLuint &texid)    // Load Image And Convert To A Texture
    {
     HDC  hdcTemp;      // The DC To Hold Our Bitmap
     HBITMAP  hbmpTemp;      // Holds The Bitmap Temporarily
     IPicture *pPicture;      // IPicture Interface
     OLECHAR  wszPath[MAX_PATH+1];     // Full Path To Picture (WCHAR)
     char  szPath[MAX_PATH+1];     // Full Path To Picture
     long  lWidth;       // Width In Logical Units
     long  lHeight;      // Height In Logical Units
     long  lWidthPixels;      // Width In Pixels
     long  lHeightPixels;      // Height In Pixels
     GLint  glMaxTexDim ;      // Holds Maximum Texture Size

       
    The next section of code takes the filename and checks to see if it's a web URL or a file path. We do this by checking to see if the filename contains http://. If the filename is a web URL, we copy the name to szPath.

    If the filename does not contain a URL, we get the working directory. If you had the demos saved to C:\wow\lesson41 and you tried to load data\wall.bmp the program needs to know the full path to the wall.bmp file not just that the bmp file is saved in a folder called data. GetCurrentDirectory will find the current path. The location that has both the .EXE and the data folder.

    If the .exe was stored at c:\wow\lesson41... The working directory would return "c:\wow\lesson41". We need to add \\ to the end of the working directory along with data\wall.bmp. The \\ represents a single \. So if we put it all together we end up with c:\wow\lesson41 plus a \ plus data\wall.bmp... or c:\wow\lesson41\data\wall.bmp. Make sense?   
       

     if (strstr(szPathName, "http://"))     // If PathName Contains http:// Then...
     {
      strcpy(szPath, szPathName);     // Append The PathName To szPath
     }
     else         // Otherwise... We Are Loading From A File
     {
      GetCurrentDirectory(MAX_PATH, szPath);    // Get Our Working Directory
      strcat(szPath, "\\");      // Append "\" After The Working Directory
      strcat(szPath, szPathName);     // Append The PathName
     }

       
    So we have the full pathname stored in szPath. Now we need to convert the pathname from ASCII to Unicode so that OleLoadPicturePath understands the path name. The first line of code below does this for us. The result is stored in wszPath.

    CP_ACP means ANSI Codepage. The second parameter specifies the handling of unmapped characters (in the code below we ignore this parameter). szPath is the wide-character string to be converted. The 4th parameter is the width of the wide-character string. If this value is set to -1, the string is assumed to be NULL terminated (which it is). wszPath is where the translated string will be stored and MAX_PATH is the maximum size of our file path (256 characters).

    After converting the path to Unicode, we attempt to load the image using OleLoadPicturePath. If everything goes well, pPicture will point to the image data and the result code will be stored in hr.

    If loading fails, the program will exit.   
       

     MultiByteToWideChar(CP_ACP, 0, szPath, -1, wszPath, MAX_PATH);  // Convert From ASCII To Unicode
     HRESULT hr = OleLoadPicturePath(wszPath, 0, 0, 0, IID_IPicture, (void**)&pPicture);

     if(FAILED(hr))        // If Loading Failed
      return FALSE;       // Return False

       
    Now we need to create a temporary device context. If all goes well, hdcTemp will hold the compatible device context. If the program is unable to get a compatible device context pPicture is released, and the program exits.   
       

     hdcTemp = CreateCompatibleDC(GetDC(0));     // Create The Windows Compatible Device Context
     if(!hdcTemp)        // Did Creation Fail?
     {
      pPicture->Release();      // Decrements IPicture Reference Count
      return FALSE;       // Return False (Failure)
     }

       
    Now it's time to query the video card and find out what the maximum texture dimension supported is. This code is important because it will attempt to make the image look good on all video cards. Not only will it resize the image to a power of 2 for you. It will make the image fit in your video cards memory. This allows you to load images with any width or height. The only drawback is that users with bad video cards will loose alot of detail when trying to view high resolution images.

    On to the code... we use glGetIntegerv(...) to get the maximum texture dimension (256, 512, 1024, etc) supported by the users video card. We then check to see what the actual image with is. pPicture->get_width(&lwidth) is the images width.

    We use some fancy math to convert the image width to pixels. The result is stored in lWidthPixels. We do the same for the height. We get the image height from pPicture and store the pixel value in lHeightPixels.   
       

     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glMaxTexDim);   // Get Maximum Texture Size Supported

     pPicture->get_Width(&lWidth);      // Get IPicture Width (Convert To Pixels)
     lWidthPixels = MulDiv(lWidth, GetDeviceCaps(hdcTemp, LOGPIXELSX), 2540);
     pPicture->get_Height(&lHeight);      // Get IPicture Height (Convert To Pixels)
     lHeightPixels = MulDiv(lHeight, GetDeviceCaps(hdcTemp, LOGPIXELSY), 2540);

       
    Next we check to see if the image width in pixels is less than the maximum width supported by the video card.

    If the image width in pixels is less than the maximum width supported, we resize the image to a power of two based on the current image width in pixels. We add 0.5f so that the image is always made bigger if it's closer to the next size up. For example... If our image width was 400 and the video card supported a maximum width of 512... it would be better to make the width 512. If we made the width 256, the image would loose alot of it's detail.

    If the image size is larger than the maximum width supported by the video card, we set the image width to the maximum texture size supported.

    We do the same for the image height. The final image width and height will be stored in lWidthPixels and lHeightPixels.   
       

     // Resize Image To Closest Power Of Two
     if (lWidthPixels <= glMaxTexDim)     // Is Image Width Less Than Or Equal To Cards Limit
      lWidthPixels = 1 << (int)floor((log((double)lWidthPixels)/log(2.0f)) + 0.5f);
     else         // Otherwise  Set Width To "Max Power Of Two" That The Card Can Handle
      lWidthPixels = glMaxTexDim;

     if (lHeightPixels <= glMaxTexDim)     // Is Image Height Greater Than Cards Limit
      lHeightPixels = 1 << (int)floor((log((double)lHeightPixels)/log(2.0f)) + 0.5f);
     else         // Otherwise  Set Height To "Max Power Of Two" That The Card Can Handle
      lHeightPixels = glMaxTexDim;

       
    Now that we have the image data loaded and we know the height and width we want to make the image, we need to create a temporary bitmap. bi will hold our bitmap header information and pBits will hold the actual image data. We want the bitmap we create to be a 32 bit bitmap with a width of lWidthPixels and a height of lHeightPixels. We want the image encoding to be RGB and the image will have just one bitplane.   
       

     // Create A Temporary Bitmap
     BITMAPINFO bi = {0};      // The Type Of Bitmap We Request
     DWORD  *pBits = 0;      // Pointer To The Bitmap Bits

     bi.bmiHeader.biSize  = sizeof(BITMAPINFOHEADER);  // Set Structure Size
     bi.bmiHeader.biBitCount  = 32;     // 32 Bit
     bi.bmiHeader.biWidth  = lWidthPixels;    // Power Of Two Width
     bi.bmiHeader.biHeight  = lHeightPixels;   // Make Image Top Up (Positive Y-Axis)
     bi.bmiHeader.biCompression = BI_RGB;    // RGB Encoding
     bi.bmiHeader.biPlanes  = 1;     // 1 Bitplane

       
    Taken from the MSDN: The CreateDIBSection function creates a DIB that applications can write to directly. The function gives you a pointer to the location of the bitmap's bit values. You can let the system allocate the memory for the bitmap.

    hdcTemp is our temporary device context. bi is our Bitmap Info data (header information). DIB_RGB_COLORS tells our program that we want to store RGB data, not indexes into a logical palette (each pixel will have a red, green and blue value).

    pBits is where the image data will be stored (points to the image data). the last two parameters can be ignored.

    If for any reason the program was unable to create a temporary bitmap, we clean things up and return false (which exits the program).

    If things go as planned, we end up with a temporary bitmap. We use SelectObject to attach the bitmap to the temporary device context.   
       

     // Creating A Bitmap This Way Allows Us To Specify Color Depth And Gives Us Imediate Access To The Bits
     hbmpTemp = CreateDIBSection(hdcTemp, &bi, DIB_RGB_COLORS, (void**)&pBits, 0, 0);

     if(!hbmpTemp)        // Did Creation Fail?
     {
      DeleteDC(hdcTemp);      // Delete The Device Context
      pPicture->Release();      // Decrements IPicture Reference Count
      return FALSE;       // Return False (Failure)
     }

     SelectObject(hdcTemp, hbmpTemp);     // Select Handle To Our Temp DC And Our Temp Bitmap Object

       
    Now we need to fill our temporary bitmap with data from our image. pPicture->Render will do this for us. It will also resize the image to any size we want (in this case... lWidthPixels by lHeightPixels).

    hdcTemp is our temporary device context. The first two parameters after hdcTemp are the horizontal and vertical offset (the number of blank pixels to the left and from the top). We want the image to fill the entire bitmap, so we select 0 for the horizontal offset and 0 for the vertical offset.

    The fourth parameter is the horizontal dimension of destination bitmap and the fifth parameter is the vertical dimension. These parameters control how much the image is stretched or compressed to fit the dimensions we want.

    The next parameter (0) is the horizontal offset we want to read the source data from. We draw from left to right so the offset is 0. This will make sense once you see what we do with the vertical offset (hopefully).

    the lHeight parameter is the vertical offset. We want to read the data from the bottom of the source image to the top. By using an offset of lHeight, we move to the very bottom of the source image.

    lWidth is the amount to copy in the source picture. We want to copy all of the date horizontally in the source image. lWidth covers all the data from left to right.

    The second last parameter is a little different. It's a negative value. Negative lHeight to be exact. What this means is that we want to copy all of the data vertically, but we want to start copying from the bottom to the top. That way the image is flipped as it's copied to the destination bitmap.

    The last parameter is not used.   
       

     // Render The IPicture On To The Bitmap
     pPicture->Render(hdcTemp, 0, 0, lWidthPixels, lHeightPixels, 0, lHeight, lWidth, -lHeight, 0);

       
    So now we have a new bitmap with a width of lWidthPixels and a height of lHeightPixels. The new bitmap has been flipped right side up.

    Unfortunately the data is stored in BGR format. So we need to swap the Red and Blue pixels to make the bitmap and RGB image. At the same time, we set the alpha value to 255. You can change this value to anything you want. This demo does not use alpha so it has no effect in this tutorial!   
       

     // Convert From BGR To RGB Format And Add An Alpha Value Of 255
     for(long i = 0; i < lWidthPixels * lHeightPixels; i++)   // Loop Through All Of The Pixels
     {
      BYTE* pPixel = (BYTE*)(&pBits[i]);    // Grab The Current Pixel
      BYTE  temp = pPixel[0];     // Store 1st Color In Temp Variable (Blue)
      pPixel[0] = pPixel[2];     // Move Red Value To Correct Position (1st)
      pPixel[2] = temp;      // Move Temp Value To Correct Blue Position (3rd)
      pPixel[3] = 255;      // Set The Alpha Value To 255
     }

       
    Finally, after all of that work, we have a bitmap image that can be used as a texture. We bind to texid, and generate the texture. We want to use linear filtering for both the min and mag filters (looks nice).

    We get the image data from pBits. When generating the texture, we use lWidthPixels and lHeightPixels one last time to set the texture width and height.

    After the 2D texture has been generated, we can clean things up. We no longer need the temporary bitmap or the temporary device context. Both of these are deleted. We can also release pPicture... YAY!!!   
       

     glGenTextures(1, &texid);      // Create The Texture

     // Typical Texture Generation Using Data From The Bitmap
     glBindTexture(GL_TEXTURE_2D, texid);     // Bind To The Texture ID
     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);  // (Modify This For The Type Of Filtering You Want)
     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);  // (Modify This For The Type Of Filtering You Want)

     // (Modify This If You Want Mipmaps) 
     glTexImage2D(GL_TEXTURE_2D, 0, 3, lWidthPixels, lHeightPixels, 0, GL_RGBA, GL_UNSIGNED_BYTE, pBits);

     DeleteObject(hbmpTemp);       // Delete The Object
     DeleteDC(hdcTemp);       // Delete The Device Context

     pPicture->Release();       // Decrements IPicture Reference Count

     return TRUE;        // Return True (All Good)
    }

       
    The following code checks to see if the users video card support the EXT_fog_coord extension. This code can ONLY be called after your OpenGL program has a Rendering Context. If you try to call it before you set up the window, you will get errors.

    The first thing we do is create a string with the name of our extension.

    We then allocate enough memory to hold the list of OpenGL extensions supported by the users video card. The list of supported extensions is retreived with the command glGetString(GL_EXTENSIONS). The information returned is copied into glextstring.

    Once we have the list of supported extensions we use strstr to see if our extension (Extension_Name) is in the list of supported extensions (glextstring).

    If the extension is not supported, FALSE is returned and the program ends. If everything goes ok, we free glextstring (we no longer need the list of supported extensions).   
       

    int Extension_Init()
    {
     char Extension_Name[] = "EXT_fog_coord";

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

     if (!strstr(glextstring,Extension_Name))    // Check To See If The Extension Is Supported
      return FALSE;       // If Not, Return FALSE

     free(glextstring);       // Free Allocated Memory

       
    At the very top of this program we defined glFogCoordfEXT. However, the command will not work until we attach the function to the actual OpenGL extension. We do this by giving glFogCoordfEXT the address of the OpenGL Fog Extension. When we call glFogCoordfEXT, the actual extension code will run, and will receive the parameter passed to glFogCoordfEXT.

    Sorry, this is one of them bits of code that is very hard to explain in simple terms (at least for me).   
       

     // Setup And Enable glFogCoordEXT
     glFogCoordfEXT = (PFNGLFOGCOORDFEXTPROC) wglGetProcAddress("glFogCoordfEXT");

     return TRUE;
    }

       
    This section of code is where we call the routine to check if the extension is supported, load our texture, and set up OpenGL.

    By the time we get to this section of code, our program has an RC (rendering context). This is important because you need to have a rendering context before you can check if an extension is supported by the users video card.

    So we call Extension_Init( ) to see if the card supports the extension. If the extension is not supported, Extension_Init( ) returns false and the check fails. This will cause the program to end. If you wanted to display some type of message box you could. Currently the program will just fail to run.

    If the extension is supported, we attempt to load our wall.bmp texture. The ID for this texture will be texture[0]. If for some reason the texture does not load, the program will end.

    Initialization is simple. We enable 2D texture mapping. We set the clear color to black. The clear depth to 1.0f. We set depth testing to less than or equal to and enable depth testing. The shademodel is set to smooth shading, and we select nicest for our perspective correction.   
       

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

     // Start Of User Initialization
     if (!Extension_Init())       // Check And Enable Fog Extension If Available
      return FALSE;       // Return False If Extension Not Supported

     if (!BuildTexture("data/wall.bmp", texture[0]))    // Load The Wall Texture
      return FALSE;       // Return False If Loading Failed

     glEnable(GL_TEXTURE_2D);      // Enable Texture Mapping
     glClearColor (0.0f, 0.0f, 0.0f, 0.5f);     // Black Background
     glClearDepth (1.0f);       // Depth Buffer Setup
     glDepthFunc (GL_LEQUAL);      // The Type Of Depth Testing
     glEnable (GL_DEPTH_TEST);      // Enable Depth Testing
     glShadeModel (GL_SMOOTH);      // Select Smooth Shading
     glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);   // Set Perspective Calculations To Most Accurate

       
    Now for the fun stuff. We need to set up the fog. We start off by enabling fog. The rendering mode we use is linear (nice looking). The fog color is set to fogColor (orange).

    We then need to set the fog start position. This is the least dense section of fog. To make things simple, we will use 1.0f as the least dense value (FOG_START). We will use 0.0f as the most dense area of fog (FOG_END).

    According to all of the documentation I have read, setting the fog hint to GL_NICEST causes the fog to be rendered per pixel. Using GL_FASTEST will render the fog per vertex. I personally do not see a difference.

    The last glFogi(...) command tells OpenGL that we want to set our fog based on vertice coordinates. This allows us to position the fog anywhere in our scene without affecting the entire scene (cool!).

    We set the starting camz value to -19.0f. The actual hallways is 30 units in length. So -19.0f moves us almost the beginning of the hallway (the hallway is rendered from -15.0f to +15.0f on the Z axis).   
       

     // Set Up Fog
     glEnable(GL_FOG);       // Enable Fog
     glFogi(GL_FOG_MODE, GL_LINEAR);      // Fog Fade Is Linear
     glFogfv(GL_FOG_COLOR, fogColor);     // Set The Fog Color
     glFogf(GL_FOG_START,  1.0f);      // Set The Fog Start (Least Dense)
     glFogf(GL_FOG_END,    0.0f);      // Set The Fog End (Most Dense)
     glHint(GL_FOG_HINT, GL_NICEST);      // Per-Pixel Fog Calculation
     glFogi(GL_FOG_COORDINATE_SOURCE_EXT, GL_FOG_COORDINATE_EXT);  // Set Fog Based On Vertice Coordinates

     camz = -19.0f;        // Set Camera Z Position To -19.0f

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

       
    This section of code is called whenever a user exits the program. There is nothing to clean up so this section of code remains empty!   
       

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

       
    Here is where we handle the keyboard interaction. Like all previous tutorials, we check to see if the ESC key is pressed. If it is, the application is terminated.

    If the F1 key is pressed, we toggle from fullscreen to windowed mode or from windowed mode to fullscreen.

    The other two keys we check for are the up and down arrow keys. If the UP key is pressed and the value of camz is less than 14.0f we increase camz. This will move the hallway towards the viewer. If we went past 14.0f, we would go right through the back wall. We don't want this to happen :)

    If the DOWN key is pressed and the value of camz is greater than -19.0f we decrease camz. This will move the hallway away from the viewer. If we went past -19.0f, the hallway would be too far into the screen and you would see the entrance to the hallway. Again... this wouldn't be good!

    the value of camz is increased and decreased based on the number of milliseconds that have passed divided by 100.0f. This should force the program to run at the same speed on all types of processors.   
       

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

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

     if (g_keys->keyDown [VK_UP] && camz<14.0f)    // Is UP Arrow Being Pressed?
      camz+=(float)(milliseconds)/100.0f;    // Move Object Closer (Move Forwards Through Hallway)

     if (g_keys->keyDown [VK_DOWN] && camz>-19.0f)    // Is DOWN Arrow Being Pressed?
      camz-=(float)(milliseconds)/100.0f;    // Move Object Further (Move Backwards Through Hallway)
    }

       
    I'm sure you are dying to get the rendering, but we still have a few things to do before we draw the hallway. First off we need to clear the screen and the depth buffer. We reset the modelview matrix and translate into the screen based on the value stored in camz.

    By increasing or decreasing the value of camz, the hallway will move closer or further away from the viewer. This will give the impression that the viewer is moving forward or backward through the hall... Simple but effective!   
       

    void Draw (void)
    {
     glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   // Clear Screen And Depth Buffer
     glLoadIdentity ();       // Reset The Modelview Matrix

     glTranslatef(0.0f, 0.0f, camz);      // Move To Our Camera Z Position

       
    The camera is positioned, so now it is time to render the first quad. This will be the BACK wall (the wall at the end of the hallway).

    We want this wall to be in the thickest of the fog. If you look at the Init section of code, you will see that GL_FOG_END is the most dense section of fog... and it has a value of 0.0f.

    Fog is applied the same way you apply texture coordinates. GL_FOG_END has the most fog, and GL_FOG_END has a value of 0.0f. So for our first vertex we pass glFogCoordfEXT a value of 0.0f. This will give the bottom (-2.5f on the Y-Axis) left (-2.5f on the X-Axis) vertex a fog density of 0.0f.

    We assign 0.0f to the other 3 glFogCoordfEXT vertices as well. We want all 4 points (way in the distance) to be in dense fog.

    Hopefully by now you understand texture mapping coordinates and glVertex coordinates. I shouldn't have to explain these :)   
       

     glBegin(GL_QUADS);       // Back Wall
       glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-2.5f,-2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 2.5f,-2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 2.5f, 2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-2.5f, 2.5f,-15.0f);
     glEnd();

       
    So we have a texture mapped back wall in very dense fog. Now we will draw the floor. it's a little different, but once you spot the pattern it will all become very clear to you!

    Like all quads, the floor has 4 points. However the Y value is always -2.5f. The left vertex is -2.5f, the right vertex is 2.5f, and the floor runs from -15.0f on the Z-Axis to +15.0f on the Z-Axis.

    We want the section of floor way in the distance to have the most fog. So once again we give these glFogCoordfEXT vertices a value of 0.0f. Notice that any vertex drawn at -15.0f has a glFogCoordfEXT value of 0.0f...?

    The sections of floor closest the viewer (+15.0f) will have the least amount of fog. GL_START_FOG is the least dense fog and has a value of 1.0f. So for these points we will pass a value of 1.0f to glFogCoordfEXT.

    What you should see if you run the program is really dense fog on the floor near the back and light fog up close. The fog is not dense enough to fill the entire hallway. It actually dies out halfway down the hall, even though GL_START_FOG is 1.0f.   
       

     glBegin(GL_QUADS);       // Floor
       glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-2.5f,-2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 2.5f,-2.5f,-15.0f);
      glFogCoordfEXT(1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 2.5f,-2.5f, 15.0f);
      glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-2.5f,-2.5f, 15.0f);
     glEnd();

       
    The roof is drawn exactly the same way the floor was drawn, with the only difference being that the roof is drawn on the Y-Axis at 2.5f.   
       

     glBegin(GL_QUADS);       // Roof
      glFogCoordfEXT(0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-2.5f, 2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 2.5f, 2.5f,-15.0f);
      glFogCoordfEXT(1.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 2.5f, 2.5f, 15.0f);
      glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-2.5f, 2.5f, 15.0f);
     glEnd();

       
    The right wall is also drawn the same way. Except the X-Axis is always 2.5f. The furthest points on the Z-Axis are still set to glFogCoordfEXT(0.0f) and the closest points on the z-Axis are still set to glFogCoordfEXT(1.0f).   
       

     glBegin(GL_QUADS);       // Right Wall
      glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f( 2.5f,-2.5f, 15.0f);
      glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f( 2.5f, 2.5f, 15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 2.5f, 2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 2.5f,-2.5f,-15.0f);
     glEnd();

       
    Hopefully by now you understand how things work. Anything in the distance will have more fog, and should be set to a value of 0.0f. Anything up close should be set to 1.0f.

    Of course you can always play around with the GL_FOG_START and GL_FOG_END values to see how they affect the scene.

    The effect does not look convincing if you swap the values. The illusion is created by the back wall being completely orange! The effect looks best in dead ends or tight corners where the player can not get behind the fog!

    If you plan to use this type of fog in a 3D engine you may want to adjust the START and END values based on where the player is standing, and which direction they are facing the fog from!   
       

     glBegin(GL_QUADS);       // Left Wall
       glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-2.5f,-2.5f, 15.0f);
      glFogCoordfEXT(1.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-2.5f, 2.5f, 15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(-2.5f, 2.5f,-15.0f);
      glFogCoordfEXT(0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(-2.5f,-2.5f,-15.0f);
     glEnd();

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

       
    I really hope you enjoy this tutorial. It was created over a period of 3 days... 4 hours a day. Most of the time was spent writing the text you are currently reading.

    I wanted to make a 3D room with fog in one corner of the room. Unfortunately, I had very little time to work on the code.

    Even though the hallway in this tutorial is very simple, the actual fog effect is quite cool! Modifying the code for use in projects of your own should take very little effort.

    It is important to note that this is just ONE of many different ways to create volumetric fog. The same effect can be recreated using blending, particles, masks, etc.

    This tutorials shows you how to use the glFogCoordfEXT... It's fast, looks great and is very easy to use!

    If you modify the view so you can see outside the hallway, you will see that the fog is contained inside the hallway!

    As always... if you find mistakes in this tutorial let me know. If you think you can describe a section of code better (my wording is not always clear), send me an email!

    A lot of the text was written late at night, and although it's not an excuse, my typing gets a little worse as I get more sleepy. Please email me if you find duplicate words, spelling mistakes, etc.

    The original idea for this tutorial was sent to me a long time ago. Since then I have lost the original email. To the person that sent this idea in... Thank You!

    Jeff Molofee (NeHe)

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

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

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

    第四十二课


    按此在新窗口浏览图片多重视口

    画中画效果,很酷吧。使用视口它变得很简单,但渲染四次可会大大降低你的显示速度哦:)

      
       
       
    欢迎来到充满趣味的另一课。这次我将向你展示怎样在单个窗口内显示多个视口。这些视口在窗口模式下能正确的调整大小。其中有两个窗口起用了光照。窗口之一用的是正交投影而其他三个则是透视投影。为了保持教程的趣味性,在本例子中我们同样需要学习迷宫代码,怎么渲染到一张纹理以及怎么得到当前窗口的分辨率。
    一旦你明白了本教程,制作分屏游戏以及多视图的3D程序就很简单了。接下来,让我们投入到代码中来吧!!!
    你可以利用最近的NeHeGL或者IPicture代码作为主要基本代码。我们需要看的第一个文件就是NeHeGL.cpp,其中有三节代码已经被修改了。我将只列出那些被修改了的代码。
    第一个且最重要的被修改了的代码就是ReshapeGL()函数。这是我们设置屏幕(主视口)分辨率的地方。现在所有的主视口设置都在画循环里完成了。因此这儿所有我们能做的就是设置我们的主窗口。  
       

    void ReshapeGL (int width, int height)        // 当窗口移动或者大小改变时重新调整窗口
    {
     glViewport (0, 0, (GLsizei)(width), (GLsizei)(height));     // 重置当前视口
    }

       
    下一步我们添加一些代码用于监视擦除窗口背景的Windows消息(WM_ERASEBKGND).如果它被调用,我们截取它并返回0,这样就阻止了窗口背景被擦除,并让我们自己来调整主窗口大小,这样就没有了我们以前常见的那种恼人的闪烁。如果你还不明白我的意思,删掉 case WM_ERASEBKGND: 和 return 0; 你自己比较就能知道有何不同。  
       

    LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
     DWORD  tickCount;        // 保存当前的时间
     __int64  timer;         // 记录时间

     // 返回窗口结构
     GL_Window* window = (GL_Window*)(GetWindowLong (hWnd, GWL_USERDATA));

     switch (uMsg)          // 处理消息
     {
      case WM_ERASEBKGND:        // 检测Windows是否去擦除背景
       return 0;        // 跳过直接返回

       
    在WinMain函数中,我们需要修改窗口标题并设置分辨率至1024x768.如果由于某种原因你的显示器不能支持到1024x768,你可以设置低一点的分辨率,但是牺牲了一些细节。  
       

     window.init.width  = 1024;       // 宽
     window.init.height  = 768;       // 高

       
    现在该是对lesson42.cpp文件动手术的时候了(主要代码)...
    我们以包含标准头文件和库文件作为开始吧.  
       

    #include <windows.h>          
    #include <gl\gl.h>          
    #include <gl\glu.h>          

    #include "NeHeGL.h"         

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

    GL_Window* g_window;         
    Keys*  g_keys;        
       
    然后我们声明一些我们打算在整个程序中都要用到的全局变量。
    mx和my纪录了当前所在迷宫中的房间。每个房间都被墙隔开(因此房间都是2个单元大小的部分)。
    with和height是用来建立纹理需要的。它也是迷宫的宽和高。让迷宫和贴图的大小一致的原因是使迷宫中的象素和纹理中的象素一一对应。我倾向于把宽和高都设成256,尽管这要花更长的时间来建立迷宫。
    如果你的显卡能支持处理大型贴图。可以试着以2次幂增加这个值(256, 512, 1023)。确保这个值不至于太大。如果这个主窗口的宽度有1024个象素,并且每个视口的大小都是主窗口的一半,相应的你应该设置你的贴图宽度也是窗口宽度的一半。如果你使贴图宽度为1024象素,但你的视口大小只有512,空间不足于容纳贴图中所有得象素,这样每两个象素就会重叠在一起。贴图的高度也作同样处理:高度是窗口高度的1/2. 当然你还必须四舍五入到2的幂。  
       

    int mx,my;           // 循环变量

    const width = 128;          // 迷宫大小
    const height = 128;          
       
    dong用来跟踪迷宫是否被建完,后面有这个更详细的解释。
    sp用来确认空格键是否处于按下状态。通过按空格健,迷宫就会被重置,然后程序将重新开始画一个新的迷宫。如果我们不去检测空格键是否处于按下状态,迷宫会在空格键按下的瞬间被重置很多次。这个值确保迷宫只被重置一次。  
       

    BOOL done;           // 迷宫是否被建完
    BOOL sp;          
       
    r[4]保存了4个随机的红色分量值,g[4]保存了4个随机的绿色分量值,b[4]保存了4个随机的兰色分量值。这些值赋给各个视口不同的颜色。第一个视口颜色为r[0],g[0],b[0]。请注意每一个颜色都是一个字节的值,而不是常用的浮点值。我这里用字节是因为产生0-255的随机值比产生0.0f-1.0f的浮点值更容易。
    tex_data指向我们的贴图数据。  
       

    BYTE r[4], g[4], b[4];          // 随机的颜色
    BYTE *tex_data;          // 保存纹理数据

       
    xrot,yrot和zrot是旋转3d物体用到的变量。
    最后,我们声明一个二次曲面物体,这样我们可以用gluCylinder和gluSphere来画圆柱和球体,这比手工绘制这些物体容易多了。  
       

    GLfloat xrot, yrot, zrot;         // 旋转物体

    GLUquadricObj *quadric;         // 二次几何体对象
       
    下面的小段代码设置纹理中位置dmx,dmy的颜色值为纯白色。tex_data是指向我们的纹理数据的指针。每一个象素都由3字节组成(1字节红色分量,1字节绿色分量,一字节兰色分量). 红色分量的偏移为0,我们要修改的象素的在纹理数据中的偏移为dmx(象素的x坐标)加上dmy(象素y坐标)与贴图宽度的乘积,最后的结果乘3(3字节每象素)。
    下面第一行代码设置red(0)颜色分量为255, 第二行设置green(1)颜色分量为255,最后一行设置blue(2)颜色分量为255,最后的结果为在dmx,dmy处的象素颜色为白色。  
       

    void UpdateTex(int dmx, int dmy)        // 更新纹理
    {
     tex_data[0+((dmx+(width*dmy))*3)]=255;      // 设置颜色为白色
     tex_data[1+((dmx+(width*dmy))*3)]=255;       
     tex_data[2+((dmx+(width*dmy))*3)]=255;       
    }

       
    重置有相当多的工作量。它清空纹理,给每一个视口设置随机颜色,删除迷宫中的墙并为迷宫的生成设置新的随机起点。
    第一行代码清空tex_data指向的贴图数据。我们需要清空width(贴图宽)*height(贴图高)*3(红,绿,兰)。 (代码已经够清楚了,呜呼,干吗要翻译这段?) 清空内存空间就是设置所有的字节为0。如果3个颜色分量都清零,那么整个贴图就完全变黑了!  
       

    void Reset (void)          
    {
     ZeroMemory(tex_data, width * height *3);      // 清空纹理数据

       
    现在我们来给每一个视口设置随机的颜色。对于不了解这些的人来说,这里的随机并不是真正那种随机! 如果你写了一个简单的程序来打印出10个随机数字,每次你运行程序,你都会得到同样的10个数字。为了使事情(看起来)更加随机,我们可以设置随机数种子。同样的,如果你设置种子为1,你总是会得到同样的结果。然而,如果我们设置srand为开机后当前时钟计数(这可能是任意的数),我们的程序每次运行都会有不同的结果。
    我们有四个视口,因此我们需要从0-3的循环来处理。我们给每一个颜色(red,green,blue)从128-255中间的随机值。要加128的目的是需要更亮的颜色。最小值为0,最大值为255,而128则表示大约有50%的亮度。   
       

     srand(GetTickCount());         // 初始化随机向量

     for (int loop=0; loop<4; loop++)       // 循环随机生成颜色
     {
      r[loop]=rand()%128+128;        
      g[loop]=rand()%128+128;        
      b[loop]=rand()%128+128;       
     }

       
    下一步,我们设置一个随机的起点。我们的起点必须是一个房间。在纹理中每两个象素就是一个房间。为确保起点是房间而不是墙,我们在0至贴图宽度一半的范围内挑选一个数,并和2相乘。通过这种方法我们只能得到如0,2,6,8之类的数,也就是说我们总是得到一个随机的房间,决不会着陆到一堵墙上如1,3,5,7,9等等。  
       

     mx=int(rand()%(width/2))*2;        
     my=int(rand()%(height/2))*2;        
    }

       
    初始化的第一行代码非常重要。它分配了足够的内存来保存我们的纹理(width*height*3). 如果你不分配内存,你很可能使你的系统崩溃。  
       

    BOOL Initialize (GL_Window* window, Keys* keys)       //初始化
    {
     tex_data=new BYTE[width*height*3];       // 分配保存纹理的空间

     g_window = window;        
     g_keys  = keys;        
       
    一分配完内存,我们就调用Reset()函数,Reset会清空贴图,设置所需颜色,并为迷宫选取随机起点。
    一旦所有的东西都设置好了。我们建立我们的初始纹理。前两个纹理参数将纹理坐标截断在 [0,1]范围内,当把一个单独的图像映射到一个物体上时,这种方式可以避免缠绕时人为因素的影响(?本句翻译不爽,请指正). 为了看到CLAMP参数的重要性,可以移掉这两行代码看看。如果没有Clamping,你会注意到在纹理的顶部和右边的细小线条。这些线条的出现是因为线性过滤想使整个纹理平滑,包括纹理边界。如果一个靠近边界的点被画了,在纹理的对边上就会出现一条线。
    我们打算用线性过滤来使纹理变的更平滑一点。 用什么类型的过滤是由你来决定的。如果它使程序跑起来很慢,那就换成过滤类型为GL_NEAREST
    最后,我们利用tex_data数据(并没有利用alpha通道)建立了一个二维的RGB纹理。  
       

     Reset();          // 重置纹理贴图

     // 设置纹理参数
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, tex_data);

       
    我们设置用于清空颜色缓冲区的颜色为黑色,清空深度缓冲区的值为1.0f. 设置深度函数为less than或者equal to, 然后激活深度测试。
    激活GL_COLOR_MATERIAL可以让你在激活光照的情况下用glColor函数给物体上色。这个方法又称为颜色追踪, 常常是性能杀手的glMaterial的代替品。我收到?矶鄀mail问如何修改物体的颜色...,希望这些信息对这个有帮助!对于那些发email问我为什么纹理的颜色如此怪异或者问纹理颜色受当前glColor影响的人,请确认一下你没有激活GL_COLOR_MATERIAL.
    *多谢James Trotter对GL_COLOR_MATERIAL功能的解释。我曾说过它会对你的纹理上色...实际上,它是对你的物体上色。
    最后我们激活2维纹理映射。  
       

     glClearColor (0.0f, 0.0f, 0.0f, 0.0f);       
     glClearDepth (1.0f);         

     glDepthFunc (GL_LEQUAL);        
     glEnable (GL_DEPTH_TEST);        

     glEnable(GL_COLOR_MATERIAL);

     glEnable(GL_TEXTURE_2D);

       
    下面的代码建立了一个二次曲面物体并得到指向它的指针。一旦我们有这个指针后,我们设置它的法线类型为平滑类型,然后要求生成纹理坐标。这样我们的光照才能正确的工作,并且我们的纹理能自动的映射到二次曲面物体。  
       

     quadric=gluNewQuadric();        
     gluQuadricNormals(quadric, GLU_SMOOTH);     
     gluQuadricTexture(quadric, GL_TRUE);      
       
    Light0被激活,但是如果我们不激活光照,它不会起任何作用。Light0是预定义的灯光,方向指向屏幕内。如果你不喜欢的话,可以手工自己设置  
       

     glEnable(GL_LIGHT0);        

     return TRUE;
    }

       
    只要你分配了内存,记住释放它是很重要的。不管你在全屏切换或者程序退出时,下面的代码都将释放了tex_data的内存空间。  
       

    void Deinitialize (void)         
    {
     delete [] tex_data;        
    }

       
    大部分迷宫建设,以及键盘检测,旋转处理等工作都是在Update()函数中完成的。
    我们需要设置一个变量dir, 用它来表示记录随机的向上,向右,向下或向左值。
    我们需要检测空格键是否被按下,如果是,并且不处于按下状态,我们就重置迷宫。如果按键被释放,我们设置sp为false,这样程序就知道它不再是按下状态。  
       

    void Update (float milliseconds)        // 更新各个参数
    {
     int dir;         // 保存当前的方向

     if (g_keys->keyDown [VK_ESCAPE])       // 处理键盘信息
      TerminateApplication (g_window);      

     if (g_keys->keyDown [VK_F1])       
      ToggleFullscreen (g_window);       

     if (g_keys->keyDown [' '] && !sp)       
     {
      sp=TRUE;         
      Reset();         
     }

     if (!g_keys->keyDown [' '])        
      sp=FALSE;         
       
    xrot,yrot和zrot通过和一些小浮点数相乘而随着时间的消逝而增加。这样我们可以让物体绕x轴,y轴和z轴旋转。每个变量都增加不同的值使旋转好看一点  
       

     xrot+=(float)(milliseconds)*0.02f;       
     yrot+=(float)(milliseconds)*0.03f;       
     zrot+=(float)(milliseconds)*0.015f;       
       
    下面的代码用来检测我们是否画完了迷宫。我们开始设置done值为true, 然后循环检查每一个房间去看是否需要增加一面墙,如果有一间房还有被访问到,我们设置done为false.
    如果tex_data[(x + (width*y))*3]的值为0, 我们就明白这个房间还没被访问到,而且没有在里面没有画一个象素。如果这儿有一个象素,那么它的值为255。我们只需要检查它的颜色红色分量值。因为我们知道这个值只能为0(空)或者255(更新过)  
       

     done=TRUE;          // 循环所有的纹理素,如果为0则表示没有绘制完所有的迷宫,返回
     for (int x=0; x<width; x+=2)        
     {
      for (int y=0; y<height; y+=2)       
      {
       if (tex_data[((x+(width*y))*3)]==0)     
        done=FALSE;      
      }
     }

       
    检查完所有的房间之后,如果done为true.那么迷宫就算建完了,SetWindowsText就会改变窗口的标题。我们改变标题为"迷宫建造完成!"。然后我们停顿5000毫秒使看这个例子的人有时间来看标题栏上的字(如果在全屏状态,他们会看到动画停顿了)。  
       

     if (done)          //如果完成停止五秒后重置
     {
      SetWindowText(g_window->hWnd,"Lesson 42: Multiple Viewports... 2003 NeHe Productions... Maze Complete!");
      Sleep(5000);
      SetWindowText(g_window->hWnd,"Lesson 42: Multiple Viewports... 2003 NeHe Productions... Building Maze!");
      Reset();
     }

       
    下面的代码也许让人看着糊涂,但其实并不难懂。我们检查当前房间的右边房间是否被访问过或者是否当前位置的右边是迷宫的右边界(当前房间右边的房间就不存在),同样检查左边的房间是否访问过或者是否达到左边界。其它方向也作如此检查。
    如果房间颜色的红色分量的值为255,就表示这个房间已经被访问过了(因为它已经被函数UpdateTex更新过)。如果mx(当前x坐标)小于2, 就表示我们已经到了迷宫最左边不能再左了。
    如果往四个方向都不能移动了或以已经到了边界,就给mx和my一个随机值,然后检查这个值对应点是否被访问,如果没有,我们就重新寻找一个新的随机变量,直到该变量对应的单元早已经被访问。因为需要从旧的路径中分叉出新的路径,所以我们必须保持搜素知道发觉有一老的路径可以从那里开始新的路径。
    为了使代码尽量简短,我没有打算去检查mx-2是否小于0。如果你想有100%的错误检测,你可以修改这段代码阻止访问不属于当前贴图的内存。  
       

     // 检测是否走过这里
     if (((tex_data[(((mx+2)+(width*my))*3)]==255) || mx>(width-4)) && ((tex_data[(((mx-2)+(width*my))*3)]==255) || mx<2) &&
      ((tex_data[((mx+(width*(my+2)))*3)]==255) || my>(height-4)) && ((tex_data[((mx+(width*(my-2)))*3)]==255) || my<2))
     {
      do         
      {
       mx=int(rand()%(width/2))*2;      
       my=int(rand()%(height/2))*2;     
      }
      while (tex_data[((mx+(width*my))*3)]==0);     
     }           
       
    下面这行代码赋给dir变量0-3之间的随机值,这个值告诉我们该往右,往上,往左还是往下画迷宫。
    在得到随机的方向之后,我们检查dir的值是否为0(往右移),如果是并且我们不在迷宫的右边界,然后检查当前房间的右边房间,如果没被访问,我们就调用UpdateTex(mx+1,my)在两个房间之间建立一堵墙,然后mx增加2移到新的房间.  
       

     dir=int(rand()%4);         // 随机一个走向

     if ((dir==0) && (mx<=(width-4)))       // 向右走,更新数据
     {
      if (tex_data[(((mx+2)+(width*my))*3)]==0)     
      {
       UpdateTex(mx+1,my);       
       mx+=2;         
      }
     }

       
    如果dir的值为1(往下),并且我们不在迷宫底部,我们检查当前房间的下面房间是否被访问过。如果没被访问过,我们就在两个房间(当前房间和当前房间下面的房间)建立一堵墙。然后my增加2移到新的房间.  
       

     if ((dir==1) && (my<=(height-4)))       //  向下走,更新数据
     {
      if (tex_data[((mx+(width*(my+2)))*3)]==0)     
      {
       UpdateTex(mx,my+1);      
       my+=2;        
      }
     }

       
    如果dir的值为2(向左)并且我们不在左边界,我们就检查左边的房间是否被访问,如果没被访问,我们也在两个房间(当前房间和左边的房间)之间建立一堵墙,然后mx减2移到新的房间.  
       

     if ((dir==2) && (mx>=2))        // 向左走,更新数据
     {
      if (tex_data[(((mx-2)+(width*my))*3)]==0)     
      {
       UpdateTex(mx-1,my);      
       mx-=2;         
      }
     }

       
    如果dir的值为3并且不在迷宫的最顶部,我们检?榈鼻胺考涞纳厦媸欠癖环梦剩绻挥校蛟诹礁龇考?(当前房间和当前房间上面个房间)之间建立一堵墙,然后my增加2移到新的房间。  
       

     if ((dir==3) && (my>=2))        // 向上走,更新数据
     {
      if (tex_data[((mx+(width*(my-2)))*3)]==0)     
      {
       UpdateTex(mx,my-1);       
       my-=2;         
      }
     }

       
    移到新的房间后,我们必须标志当前房间为正在访问状态。我们通过调用以当前位置mx, my为参数的UpdateTex()函数来达到这个目的。  
       

     UpdateTex(mx,my);         // 更新纹理
    }

       
    这段代码我们开始讲一些新的东西...我们必须知道当前窗口的大小以便正确的调整视口的大小。为了的到当前窗口的宽和高,我们需要获取窗口上下左右坐标值。得到这些值后我们通过窗口右边的坐标减去左边的坐标得到宽度值。底部坐标减去顶部坐标得到窗口的高度值。
    我们用RECT结构来得到窗口的那些值。RECT保存了一个矩形的坐标。也即矩形的左,右,顶部,底部的坐标。
    为获取窗口的屏幕坐标,我们用GetClientRect()函数。我们传进去的第一个参数是当前窗口的句柄。第二个参数是一个结构用于保存返回的窗口位置信息.  
       

    void Draw (void)          // 绘制
    {
     RECT rect;          // 保存长方形坐标

     GetClientRect(g_window->hWnd, &rect);       // 获得窗口大小
     int window_width=rect.right-rect.left;       
     int window_height=rect.bottom-rect.top;      
       
    我们在每一帧都需要更新纹理并且要在映射纹理之前更新。更新纹理最快的方法是用命令glTexSubImage2D(). 它能把内存中的纹理的全部或部分和屏幕中的物体建立映射。下面的代码我们表明用的??2维纹理,纹理细节级别为0,没有x方向(0)或y方向(0)的偏移,我们需要利用整张纹理的每一部分,图像为GL_RGB类型,对应的数据类型为GL_UNSIGNED_BYTE. tex_data是我们需要映射的具体数据。
    这是一个非非常快的不用重建纹理而更新纹理的方法。同样需要注意的是这个命令不会为你建立一个纹理。你必须在更新纹理前把纹理建立好。  
       

     // 设置更新的纹理
     glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, tex_data);

       
    这行代码非常重要,它将清空整个屏幕。
    ……
    一次性清空整个屏幕,然后在画每一个视口前清空它们的深度存非常重要。  
       

     glClear (GL_COLOR_BUFFER_BIT);        
       
    现在是主画循环。我们要画四个视口,所以建立了一个0到3的循环。
    首先要做的事是设置用glColor3ub(r,g,b)设置当前视口的颜色。这对某些人来说不太熟悉,它跟glColor3f(r,g,b)几乎一样但是用无符号字节代替浮点数为参数。记住早些时候我说过参省一个0-255的随机颜色值会更容易。好在已经有了该命令设置正确颜色所需要的值。
    glColor3f(0.5f,0.5f,0.5f)是指颜色中红,绿,蓝具有50%的亮度值。glColor3ub(127,127,127)同样也表示同样的意思。
    如果loop的值为0,我们将选择r[0],b[0],b[[0],如果loop指为1, 我们选用r[1],g[1],b[1]. 这样,每个场景都有自个的随机颜色。  
       

     for (int loop=0; loop<4; loop++)       // 循环绘制4个视口
     {
      glColor3ub(r[loop],g[loop],b[loop]);      
       
    在画之前首先要做的是设置当前视口,如果loop值为0,我们画第一个视口。我们想把第一个视口放在屏幕的左半部分(0),并且在屏幕的上半部分(window_height/2).视口的宽度为当前主窗口的一半(window_width/2), 高度也为主窗口高度的一半(window_height/2).

    如果主窗口为1024x768, 结果就是一个起点坐标为0,384,宽512,高384的视口。

    这个视口看起来象下面这张图

    按此在新窗口浏览图片

    设置完视口后,我们选择当前矩阵为投影矩阵,重置它并设置为2D平行投影视图。我们需要以平行投影视图来填充整个视口,因此我们给左边的值为0,右边的值为window_width/2(跟视口一样),同样给底部的值赋为window_height/2,顶部的值为0. 这样给了视口同样的高度。

    这个平行投影视图的左上角的坐标为0,0,右下角坐标为window_width/2,window_height/2.
      
       

      if (loop==0)         // 绘制左上角的视口
      {
       // 设置视口区域
       glViewport (0, window_height/2, window_width/2, window_height/2);
       glMatrixMode (GL_PROJECTION);      
       glLoadIdentity ();       
       gluOrtho2D(0, window_width/2, window_height/2, 0);
      }

       
    如果loop的值为1, 我们是在画第二个视口了。它在屏幕的右上部分。宽度和高度都跟前一个视图一样。唯一不同的是glViewport()函数的第一个参数为window_width/2.这告诉程序视口起点是从窗口左起一半的地方。

    第二个视口看起来象下面这样:

    按此在新窗口浏览图片


    同样的,我们设置当前矩阵为投影矩阵并重置它。但这次我们设置透视投影参数为FOV为45度,并且近截面值为0.1f,远截面值为500.0f  
       

      if (loop==1)         // 绘制右上角视口
      {
       
       glViewport (window_width/2, window_height/2, window_width/2, window_height/2);
       glMatrixMode (GL_PROJECTION);     
       glLoadIdentity ();     
       gluPerspective( 45.0, (GLfloat)(width)/(GLfloat)(height), 0.1f, 500.0 );
      }

       
    I如果loop值为2,我们画第三个视口。它将在主窗口的右下部分。宽度和高度与第二个视口一样。跟第二个视口不同的是glViewport()函数的第二个参数为0.这告诉程序我们想让视口位于主窗口的右下部分。

    第三个视口看起来如下:


    [IMG]http://www.owlei.com/DancingWind/Pic/viewport3.jpg[/IMG

    透视视图的设置同第二个视图。  
       

      if (loop==2)         // 绘制右下角视口
      {
       glViewport (window_width/2, 0, window_width/2, window_height/2);
       glMatrixMode (GL_PROJECTION);      
       glLoadIdentity ();      
       gluPerspective( 45.0, (GLfloat)(width)/(GLfloat)(height), 0.1f, 500.0 );
      }

       
    如果loop等于3,我们就画最后一个视口(第四个视口)。它将位于窗口的左下部分。宽度和高度跟前几次设置一样。唯一跟第三个视口不同的是glViewport()的第一个参数为0.这告诉程序视口将在主窗口的左下部分。

    第四个视口看起来如下:

    按此在新窗口浏览图片


    透视投影视图设置同第二个视口。  
       

      if (loop==3)         // 绘制右下角视口
      {
       glViewport (0, 0, window_width/2, window_height/2);
       glMatrixMode (GL_PROJECTION);      
       glLoadIdentity ();       
       gluPerspective( 45.0, (GLfloat)(width)/(GLfloat)(height), 0.1f, 500.0 );
      }

       
    下面的代码选择模型视图矩阵为当前矩阵真,并重置它。然后清空深度缓存。我们在每个视口画之前清空深度缓存。注意到我们没有清除屏幕颜色,只是深度缓存!如果你没有清除深度缓存,你将看到物体的部分消失了,等等,很明显不美观!  
       

      glMatrixMode (GL_MODELVIEW);       
      glLoadIdentity ();       

      glClear (GL_DEPTH_BUFFER_BIT);

       
    我们要画的第一副图为一个平坦的2维纹理方块。这个方块是在平行投影模式下画的,并且将会覆盖整个视口。因为我们用了平行投影投影模式,这儿没有第三维了,因此没必要在z轴进行变换。
    记住我们第一个视口的左上角坐标维0,0,右下部分坐标为window_width/2,window_height/2.这意味我们的四边形的右上坐标为window_width/2,0,左上坐标为0,0,左下坐标为0,window_height/2.右下坐标为window_width/2,window_height/2. 请注意在平行投影投影模式下,我们能在象素级别上处理而不是单元级别(决定于我们的视口设置)  
       

      if (loop==0)         // 绘制左上角的视图
      {
       glBegin(GL_QUADS);       
        glTexCoord2f(1.0f, 0.0f); glVertex2i(window_width/2, 0              );
        glTexCoord2f(0.0f, 0.0f); glVertex2i(0,              0              );
        glTexCoord2f(0.0f, 1.0f); glVertex2i(0,              window_height/2);
        glTexCoord2f(1.0f, 1.0f); glVertex2i(window_width/2, window_height/2);
       glEnd();       
      }

       
    第二个要画的图像是一个带光照的平滑球体。第二个视图是带透视的,因此我们首先必须做的是往屏幕里平移14个单位,然后在x,y,z轴旋转物体。
    我们激活光照,画球体,然后关闭光照。这个球体半径为4个单元长度,围绕z轴的细分度为32,沿z轴的细分度也为32. 如果你还在犯迷糊,可以试着改变stacks或者slices的值为更小。通过减小stacks/slices的值,你就减少了球体的平滑度。
    纹理坐标是自动产生的!  
       

      if (loop==1)         // 绘制右上角的视图
      {
       glTranslatef(0.0f,0.0f,-14.0f);      

       glRotatef(xrot,1.0f,0.0f,0.0f);     
       glRotatef(yrot,0.0f,1.0f,0.0f);      
       glRotatef(zrot,0.0f,0.0f,1.0f);    

       glEnable(GL_LIGHTING);       
       gluSphere(quadric,4.0f,32,32);     
       glDisable(GL_LIGHTING);      
      }

       
    要画的第三幅图跟第一幅一样。但是是带透视的。它贴到屏幕有一定的角度并且有旋转。
    我们把它往屏幕里移动2个单位。然后往后倾斜那个方块45度角。这让方块的顶部远离我们,而方块的底部则更靠近我们。
    然后在z轴方向上旋转方块。画方块时,我们需要手工设置贴图坐标。  
       

      if (loop==2)         // 绘制右下角的视图
      {
       glTranslatef(0.0f,0.0f,-2.0f);      
       glRotatef(-45.0f,1.0f,0.0f,0.0f);     
       glRotatef(zrot/1.5f,0.0f,0.0f,1.0f);     

       glBegin(GL_QUADS);       
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, 0.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, 0.0f);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 0.0f);
       glEnd();       
      }

       
    如果我们在画第四副图,我们往屏幕里移动7个单位。然后把物体绕x,y,z轴旋转。
    我们激活光照给物体一些不错的阴影效果,然后在z轴上平移-2个单位。我们这样做的原因是让物体绕自己的中心旋转而不是绕某一端。这圆柱体两端宽1.5个单位。长度为4个单位并且绕轴上细分32个面片,沿轴细分16个面片。
    为了能绕中心旋转,我们需要平移柱体长度的一半,4的一半也即是2。
    在平移,旋转,然后再平移之后,我们画圆柱体,之后关闭光照。  
       

      if (loop==3)         // 绘制左下角的视图
      {
       glTranslatef(0.0f,0.0f,-7.0f);      
       glRotatef(-xrot/2,1.0f,0.0f,0.0f);     
       glRotatef(-yrot/2,0.0f,1.0f,0.0f);     
       glRotatef(-zrot/2,0.0f,0.0f,1.0f);     

       glEnable(GL_LIGHTING);       
       glTranslatef(0.0f,0.0f,-2.0f);      
       gluCylinder(quadric,1.5f,1.5f,4.0f,32,16);   
       glDisable(GL_LIGHTING);       
      }
     }

       
    最后要做的事就是清空渲染管道。  
       

     glFlush ();         
    }

       
    希望这个教程能解答所有你在做多视口中碰到的任何问题。代码并不难懂。它几乎跟标准的基本代码没什么区别。我们唯一真正修改的是视口设置是在画的主循环中。在所有视口画之前清空一次屏幕,然后清空各自深度缓存。
    你可以用这些代码来在各自的视口中显示各种各样的图片,或在多视图中显示特定的物体。要做什么起决于你自己

    我希望你们喜欢这个教程...如果你发现代码中的任何错误,或者你感觉你能让这个教程更好,请通知我(同样的,如果你看过我的翻译,发现有不当之处,请通知我)

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

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

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客4
    发贴心情 
    Lesson: 42
       
    Welcome to another fun filled tutorial. This time I will show you how to display multiple viewports in a single window. The viewports will resize correctly in windowed mode. Two of the windows use lighting. One of the windows is Ortho and three are Perspective. To keep the tutorial exciting, you will also learn about the maze code used in this demo, how rendering to a texture (yet again) and how to get the current windows dimensions.

    Once you understand this tutorial, making split screen games or 3D applications with multiple views should be a snap! With that said, let dive into the code!!!

    You can use either the latest NeHeGL code or the IPicture code as the main basecode. The first file we need to look at is the NeHeGL.cpp file. Three sections of code have been modified. I will list just the sections of code that have changed.

    The first and most important thing that has changed is ReshapeGL( ). This is where we used to set up the screen dimensions (our main viewport). All of the main viewport setup is done in our main drawing loop now. So all we do here is set up the main window.   
       

    void ReshapeGL (int width, int height)        // Reshape The Window When It's Moved Or Resized
    {
     glViewport (0, 0, (GLsizei)(width), (GLsizei)(height));     // Reset The Current Viewport
    }

       
    Next we add some code to watch for the Window Message Erase Background (WM_ERASEBKGND). If it's called, we intercept it and return 0. This prevents the background from being erased, and allows us to resize our main window without all the annoying flicker you would usually see. If you are not sure what I mean, remove: case WM_ERASEBKGND: and return 0; and you can compare for yourself.   
       

    // Process Window Message Callbacks
    LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
     DWORD  tickCount;        // Holds Current Tick Count
     __int64  timer;         // Used For The Tick Counter

     // Get The Window Context
     GL_Window* window = (GL_Window*)(GetWindowLong (hWnd, GWL_USERDATA));

     switch (uMsg)          // Evaluate Window Message
     {
      case WM_ERASEBKGND:        // Check To See If Windows Is Trying To Erase The Background
       return 0;        // Return 0 (Prevents Flickering While Resizing A Window)

       
    In WinMain we need to modify the window title and crank the resolution up to 1024x768. If your monitor for some reason will not support 1024x768, you can drop down to a lower resolution and sacrifice some of the detail.   
       

    // Program Entry (WinMain)
    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
     Application   application;      // Application Structure
     GL_Window   window;       // Window Structure
     Keys    keys;       // Key Structure
     BOOL    isMessagePumpActive;     // Message Pump Active?
     MSG    msg;       // Window Message Structure

     // Fill Out Application Data
     application.className = "OpenGL";       // Application Class Name
     application.hInstance = hInstance;       // Application Instance

     // Fill Out Window
     ZeroMemory (&window, sizeof (GL_Window));      // Make Sure Memory Is Zeroed
     window.keys   = &keys;      // Window Key Structure
     window.init.application  = &application;      // Window Application

     // Window Title
     window.init.title  = "Lesson 42: Multiple Viewports... 2003 NeHe Productions... Building Maze!";

     window.init.width  = 1024;       // Window Width
     window.init.height  = 768;       // Window Height
     window.init.bitsPerPixel = 32;       // Bits Per Pixel
     window.init.isFullScreen = TRUE;       // Fullscreen? (Set To TRUE)

       
    Now it's time to modify lesson42.cpp (the main code)...

    We start off by including the standard list of header and library files.   
       

    #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 "NeHeGL.h"          // Header File For NeHeGL

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

    GL_Window* g_window;         // Window Structure
    Keys*  g_keys;          // Keyboard

       
    We then set up any global variables that we intend to use throughout the program.

    mx and my keep track of which room in the maze we are currently in. Each room is separated by a wall (so rooms are 2 units apart).

    width and height are used to build our texture. It is also the width and height of the maze. The reason we make the maze and the texture the same size is so that each pixel drawn in the maze is one pixel in the texture. I like width and height set to 256, although it takes longer to build the maze.

    If your video card can handle large textures, try increasing the values by a power of 2 (256, 512, 1024). Make sure you do not increase the values too much. If the main window is 1024 pixels wide, and each viewport is half the size of the main window, the widest you should make your texture is: Width Of The Window / 2. If you make your texture 1024 pixels wide, but your viewport size is only 512, every second pixel will overlap because there is not enough room to fit all the pixels of the texture in the viewport. The same goes for the texture height. It should be: Height Of The Window / 2. Of course you have to round down to the nearest power of 2.   
       

    // User Defined Variables
    int mx,my;           // General Loops (Used For Seeking)

    const width = 128;          // Maze Width  (Must Be A Power Of 2)
    const height = 128;          // Maze Height (Must Be A Power Of 2)

       
    done will be used to keep track of when the maze has been completed. More about this later.

    sp is used to check if the spacebar is being held down. By pressing space, the maze is reset, and the program starts drawing a new maze. If we don't check to see if the spacebar is being held, the maze resets many times during the split second that the spacebar is pressed. This variable makes sure that the maze is only reset once.   
       

    BOOL done;           // Flag To Let Us Know When It's Done
    BOOL sp;           // Spacebar Pressed?

       
    r[4] will hold 4 random values for red, g[4] will hold 4 random values for green and b[4] will hold 4 random values for blue. These values will be used to assign a different color to each viewport. The first viewports color will be r[0],g[0],b[0]. Take note that each color will be a byte value and not a float value like most of you are used to using. The reason I use a byte is because it's easier to assign a random value from 0 to 255 than it is a value from 0.0f to 1.0f.

    tex_data points to our texture data.   
       

    BYTE r[4], g[4], b[4];         // Random Colors (4 Red, 4 Green, 4 Blue)
    BYTE *tex_data;          // Holds Our Texture Data

       
    xrot, yrot and zrot will be used to rotate our 3D objects.

    Finally, we set up a quadric object so we can draw a cylinder and sphere using gluCylinder and gluSphere. Much easier than drawing the objects manually.   
       

    GLfloat xrot, yrot, zrot;         // Use For Rotation Of Objects

    GLUquadricObj *quadric;          // The Quadric Object

       
    The following bit of code will set a pixel in our texture at location dmx, dmy to bright white. tex_data is the pointer to our texture data. Each pixel is made up of 3 bytes (1 for red, 1 for green and 1 for blue). The offset for red is 0. And the location of the pixel we want to modify is dmx (the x position) plus dmy (the y position) multiplied by the width of our texture, with the end result multiplied by 3 (3 bytes per pixel).

    The first line below sets the red (0) color to 255. The second line sets the green (1) color to 255 and the last line sets the blue (2) color to 255. The end result is a bright white pixel at dmx,dmy.   
       

    void UpdateTex(int dmx, int dmy)        // Update Pixel dmx, dmy On The Texture
    {
     tex_data[0+((dmx+(width*dmy))*3)]=255;       // Set Red Pixel To Full Bright
     tex_data[1+((dmx+(width*dmy))*3)]=255;       // Set Green Pixel To Full Bright
     tex_data[2+((dmx+(width*dmy))*3)]=255;       // Set Blue Pixel To Full Bright
    }

       
    Reset has quite a few jobs. It clears our texture, assigns some random colors to each viewport, resets all the walls in the maze and assigns a new random starting point for the maze generation.

    The first line of code does the clearing. tex_data points to our texture data. We need to clear width (width of our texture) multiplied by height (height of our texture) multiplied by 3 (red, green, blue). Clearing this memory sets all all bytes to 0. If all 3 color values are set to 0, our texture will be completely black!   
       

    void Reset (void)          // Reset The Maze, Colors, Start Point, Etc
    {
     ZeroMemory(tex_data, width * height *3);      // Clear Out The Texture Memory With 0's

       
    No we need to assign a random color to each view port. For those of you that do not already know this, random is not really all that random! If you made a simple program to print 10 random digits. Each time you ran the program, you would get the exact same digits. In order to make things more random (to appear more random) we can set the random seed. Again, if we set the seed to 1, we would always get the same numbers. However, if we set srand to our current tick count (which could be any number), we end up getting different numbers every time the program is run.

    We have 4 viewports, so we need to make a loop from 0 to 3. We assign each color (red, green, blue) a random value from 128 to 255. The reason I add 128 is because I want bright colors. With a min value of 0 and a max value of 255, 128 is roughly 50% brightness.   
       

     srand(GetTickCount());         // Try To Get More Randomness

     for (int loop=0; loop<4; loop++)       // Loop So We Can Assign 4 Random Colors
     {
      r[loop]=rand()%128+128;        // Pick A Random Red Color (Bright)
      g[loop]=rand()%128+128;        // Pick A Random Green Color (Bright)
      b[loop]=rand()%128+128;        // Pick A Random Blue Color (Bright)
     }

       
    Next we assign a random starting point. We must start in a room. Every second pixel in the texture is a room. To make sure we start in a room and not on a wall, we pick a random number from 0 to half the width of the texture and multiply it by 2. That way the only numbers we can get are 0, 2, 4, 6, 8, etc. Which means we will always get a random room and never end up landing on a wall which would be 1, 3, 5, 7, 9, etc.   
       

     mx=int(rand()%(width/2))*2;        // Pick A New Random X Position
     my=int(rand()%(height/2))*2;        // Pick A New Random Y Position
    }

       
    The first line of initialization is very important. It allocates enough memory to hold our text (width*height*3). If you do not allocate memory, you will more than likely crash your system!   
       

    BOOL Initialize (GL_Window* window, Keys* keys)       // Any GL Init Code & User Initialiazation Goes Here
    {
     tex_data=new BYTE[width*height*3];       // Allocate Space For Our Texture

     g_window = window;        // Window Values
     g_keys  = keys;         // Key Values

       
    Right after we allocate memory for our texture, we call Reset( ). Reset will clear the texture, set up our colors, and pick a random starting point for our maze.

    Once everything has been reset, we need to create our initial texture. The first 2 texture parameters CLAMP our texture to the range [0,1]. This prevents wrapping artifacts when mapping a single image onto an object. To see why it's important to clamp the texture, try removing the 2 lines of code. Without clamping, you will notice a thin line at the top of the texture and on the right side of the texture. The lines appear because linear filtering tries to smooth the entire texture, including the borders. If pixels is drawn to close to a border, a line appears on the opposite side of the texture.

    We are going to use linear filtering to make things look a little smoother. It's up to you what type of filtering you use. If it runs really slow, try changing the filtering to GL_NEAREST.

    Finally, we build an RGB 2D texture using tex_data (the alpha channel is not used).   
       

     Reset();          // Call Reset To Build Our Initial Texture, Etc.

     // Start Of User Initialization
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, tex_data);

       
    We set our clear color to black and the clear depth to 1.0f. We set our depth function to less than or equal to, and then enable depth testing.

    Enabling GL_COLOR MATERIAL let's you color your objects, with glColor, when lighting is enabled. This method is called color tracking, and is often used instead of performance-draining calls to glMaterial. I get alot of emails asking how to change the color of an object... hope the information is useful! For those of you that have emailed me asking why textures in your projects are weird colors or tinted with the current glColor( )... Make sure you do not have GL_COLOR_MATERIAL enabled!

    * Thanks to James Trotter for the correct explanation on how GL_COLOR_MATERIAL works. I had said it lets you color your textures... However, it actually lets you color objects.

    Finally we enable 2D texture mapping.   
       

     glClearColor (0.0f, 0.0f, 0.0f, 0.0f);       // Black Background
     glClearDepth (1.0f);         // Depth Buffer Setup

     glDepthFunc (GL_LEQUAL);        // The Type Of Depth Testing
     glEnable (GL_DEPTH_TEST);        // Enable Depth Testing

     glEnable(GL_COLOR_MATERIAL);        // Enable Color Material (Allows Us To Tint Textures)

     glEnable(GL_TEXTURE_2D);        // Enable Texture Mapping

       
    The following code creates a pointer to our quadric object. Once we have the pointer we set the Normals to smooth, and we ask for texture coordinates. By doing this, lighting will work properly, and our texture will be mapped to any quadric object automatically!   
       

     quadric=gluNewQuadric();        // Create A Pointer To The Quadric Object
     gluQuadricNormals(quadric, GLU_SMOOTH);       // Create Smooth Normals
     gluQuadricTexture(quadric, GL_TRUE);       // Create Texture Coords

       
    Light 0 is enabled, however it will not do anything until we enable lighting. Light 0 for those of you that do not already know is a predefined light that points directly into the screen. Handy if you don't feel like setting the light up yourself.   
       

     glEnable(GL_LIGHT0);         // Enable Light0 (Default GL Light)

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

       
    Whenever you allocate memory, it's important to deallocate it. The line of code below deletes the memory whenever you toggle fullscreen / windowed mode or whenever the program exits.   
       

    void Deinitialize (void)         // Any User DeInitialization Goes Here
    {
     delete [] tex_data;         // Delete Our Texture Data (Freeing Up Memory)
    }

       
    Update( ) is where most of the maze creation is done, along with watching for keypresses, rotation, etc.

    We need to set up a variable called dir. We will use this variable to randomly travel up, right, down or left.

    We watch to see if the spacebar is pressed. If it is, and it's not being held down, we reset the maze. If the keyboard is released, we set sp to FALSE so that our program knows it is no longer being held down.   
       

    void Update (float milliseconds)        // Perform Motion Updates Here
    {
     int dir;          // Will Hold Current Direction

     if (g_keys->keyDown [VK_ESCAPE])       // Is ESC Being Pressed?
      TerminateApplication (g_window);      // Terminate The Program

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

     if (g_keys->keyDown [' '] && !sp)       // Check To See If Spacebar Is Pressed
     {
      sp=TRUE;         // If So, Set sp To TRUE (Spacebar Pressed)
      Reset();         // If So, Call Reset And Start A New Maze
     }

     if (!g_keys->keyDown [' '])        // Check To See If Spacebar Has Been Released
      sp=FALSE;         // If So, Set sp To FALSE (Spacebar Released)

       
    xrot, yrot and zrot are increased by the number of milliseconds that have passed multiplied by some small floating point number. This allows us to rotate objects on the x-axis, y-axis and z-axis. Each variable increases by a different amount to make the rotation a little nicer to watch.   
       

     xrot+=(float)(milliseconds)*0.02f;       // Increase Rotation On The X-Axis
     yrot+=(float)(milliseconds)*0.03f;       // Increase Rotation On The Y-Axis
     zrot+=(float)(milliseconds)*0.015f;       // Increase Rotation On The Z-Axis

       
    The code below checks to see if we are done drawing the maze. We start off by setting done to TRUE. We then loop through every single room to see if any of the rooms still need a wall knocked out. If any of the rooms have not been visited we set done to FALSE.

    If tex_data[((x+(width*y))*3)] equals zero, we know that room has not been visited yet, and does not have a pixel drawn in it yet. If there was a pixel, the value would be 255. We only check the red pixel value, because we know the red value will either be 0 (empty) or 255 (updated).   
       

     done=TRUE;          // Set done To True
     for (int x=0; x<width; x+=2)        // Loop Through All The Rooms
     {
      for (int y=0; y<height; y+=2)       // On X And Y Axis
      {
       if (tex_data[((x+(width*y))*3)]==0)     // If Current Texture Pixel (Room) Is Blank
        done=FALSE;       // We Have To Set done To False (Not Finished Yet)
      }
     }

       
    After checking all of the rooms, if done is still TRUE, we know that the maze is complete. SetWindowsText will change the title of a window. We change the title so that it says "Maze Complete!". We then pause for 5000 milliseconds so that the person watching the demo has time to read the title bar (or if they are in fullscreen, they see that the animation has stopped). After 5000 milliseconds, we change the title back so that it says "Building Maze!" and we reset the maze (starting the entire process over).   
       

     if (done)          // If done Is True Then There Were No Unvisited Rooms
     {
      // Display A Message At The Top Of The Window, Pause For A Bit And Then Start Building A New Maze!
      SetWindowText(g_window->hWnd,"Lesson 42: Multiple Viewports... 2003 NeHe Productions... Maze Complete!");
      Sleep(5000);
      SetWindowText(g_window->hWnd,"Lesson 42: Multiple Viewports... 2003 NeHe Productions... Building Maze!");
      Reset();
     }

       
    The following code might look confusing but it's really not that hard to understand. We check to see if the room to the right of the current room has been visited or if our current location is too close to the far right side of the maze (there are no more rooms to the right). We check if the room to the left has been visited or we are to close to the left size of the maze (no more rooms to the left). We check if the room below us has been visited or if we are too far down (no more rooms below us) and finally we check to see if the room above us has been visited or if we are to close to the top (no more rooms above).

    If the red pixel value of a room equals 255 we know that room has been visited (because it has been updated with UpdateTex). If mx (current x position) is less than 2 we know that we are almost to the far left of the screen and can not go any further left.

    If we are trapped or we are to close to a border, we give mx and my random values. We then check to see if the pixel at that location is has already been visited. If it has not, we grab new random mx, my values until we find a cell that has already been visited. We want new paths to branch off old paths which is why we need to keep searching until we find an old path to launch from.

    To keep the code to a minimum, I don't bother checking if mx-2 is less than 0. If you want 100% error checking, you can modify this section of code to prevent checking memory that does not belong to the current texture.   
       

     // Check To Make Sure We Are Not Trapped (Nowhere Else To Move)
     if (((tex_data[(((mx+2)+(width*my))*3)]==255) || mx>(width-4)) && ((tex_data[(((mx-2)+(width*my))*3)]==255) || mx<2) &&
      ((tex_data[((mx+(width*(my+2)))*3)]==255) || my>(height-4)) && ((tex_data[((mx+(width*(my-2)))*3)]==255) || my<2))
     {
      do          // If We Are Trapped
      {
       mx=int(rand()%(width/2))*2;      // Pick A New Random X Position
       my=int(rand()%(height/2))*2;      // Pick A New Random Y Position
      }
      while (tex_data[((mx+(width*my))*3)]==0);     // Keep Picking A Random Position Until We Find
     }           // One That Has Already Been Tagged (Safe Starting Point)

       
    The first line below assigns dir with a random value from 0 to 3. We will use this value to tell our maze to draw right, up, left, down.

    after we get a random direction, we check to see if the value of dir is equal to 0 (move right). if it is and we are not already at the far right side of the maze, we check the room to the right of the current room. If the room to the right has not been visited, we knock out the wall between the two room with UpdateTex(mx+1,my) and then we move to the new room by increasing mx by 2.   
       

     dir=int(rand()%4);         // Pick A Random Direction

     if ((dir==0) && (mx<=(width-4)))       // If The Direction Is 0 (Right) And We Are Not At The Far Right
     {
      if (tex_data[(((mx+2)+(width*my))*3)]==0)     // And If The Room To The Right Has Not Already Been Visited
      {
       UpdateTex(mx+1,my);       // Update The Texture To Show Path Cut Out Between Rooms
       mx+=2;         // Move To The Right (Room To The Right)
      }
     }

       
    If the value of dir is 1 (down) and we are not at the very bottom, we check to see if the room below has been visited. If it has not been visited, we knock out the wall between the two rooms (current room and room below it) and then move to the new room by increasing my by 2.   
       

     if ((dir==1) && (my<=(height-4)))       // If The Direction Is 1 (Down) And We Are Not At The Bottom
     {
      if (tex_data[((mx+(width*(my+2)))*3)]==0)     // And If The Room Below Has Not Already Been Visited
      {
       UpdateTex(mx,my+1);       // Update The Texture To Show Path Cut Out Between Rooms
       my+=2;         // Move Down (Room Below)
      }
     }

       
    If the value of dir is 2 (left) and we are not at the far left, we check to see if the room to the left has been visited. If it has not been visited, we knock out the wall between the two rooms (current room and room to the left) and then move to the new room by decreasing mx by 2.   
       

     if ((dir==2) && (mx>=2))        // If The Direction Is 2 (Left) And We Are Not At The Far Left
     {
      if (tex_data[(((mx-2)+(width*my))*3)]==0)     // And If The Room To The Left Has Not Already Been Visited
      {
       UpdateTex(mx-1,my);       // Update The Texture To Show Path Cut Out Between Rooms
       mx-=2;         // Move To The Left (Room To The Left)
      }
     }

       
    If the value of dir is 3 (up) and we are not at the very top, we check to see if the room above has been visited. If it has not been visited, we knock out the wall between the two rooms (current room and room above it) and then move to the new room by decreasing my by 2.   
       

     if ((dir==3) && (my>=2))        // If The Direction Is 3 (Up) And We Are Not At The Top
     {
      if (tex_data[((mx+(width*(my-2)))*3)]==0)     // And If The Room Above Has Not Already Been Visited
      {
       UpdateTex(mx,my-1);       // Update The Texture To Show Path Cut Out Between Rooms
       my-=2;         // Move Up (Room Above)
      }
     }

       
    After moving to the new room, we need to mark it as being visited. We do this by calling UpdateTex( ) with the current mx, my position.   
       

     UpdateTex(mx,my);         // Update Current Room
    }

       
    We will start this section of code off with something new... We need to know how large the current window is in order to resize the viewports correctly. To get the current window width and height, we need to grab the left value of the window, the right value of the window, the top of the window and the bottom of the window. After we have these values we can calculate the width by subtracting the left value of the window from the right value. We can get the height by subtracting the top of the window from the bottom of the window.

    We can get the left, right, top and bottom values by using RECT. RECT holds the coordinates of a rectangle. The left, right, top and bottom coordinates to be exact.

    To grab the coordinates for our screen, we use GetClientRect( ). The first parameter we pass is our current window handle. The second parameter is the structure that will hold the information returned (rect).   
       

    void Draw (void)          // Our Drawing Routine
    {
     RECT rect;          // Holds Coordinates Of A Rectangle

     GetClientRect(g_window->hWnd, &rect);       // Get Window Dimensions
     int window_width=rect.right-rect.left;       // Calculate The Width (Right Side-Left Side)
     int window_height=rect.bottom-rect.top;       // Calculate The Height (Bottom-Top)

       
    We need to update the texture every frame and we want it updated before we draw the textured scenes. The fastest way to update a texture is to use the command glTexSubImage2D( ). glTexSubImage2D will map all or part of a texture in memory to an object on the screen. In the code below we tell it we are using a 2D texture. The level of detail number is 0, we do not want an x (0) or y (0) offset. We want to use the entire width of the texture and the entire height. The data is GL_RGB format, and it's type is GL_UNSIGNED_BYTE. tex_data is the data we want to map.

    This is a very fast way to use updated texture data without having to rebuild the texture. It's also important to note that this command will not BUILD a texture. You have to create a texture before you can use this command to update it!   
       

     // Update Our Texture... This Is The Key To The Programs Speed... Much Faster Than Rebuilding The Texture Each Time
     glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, tex_data);

       
    This line of code is very important. It will clear the entire screen. Because we only want to clear the screen BEFORE ALL 4 viewports are drawn, and before each viewport is drawn, we need to clear before the main loop that draws the 4 viewports! Also notice that we are not clearing the depth buffer at the moment. It will be cleared on it's own before drawing each scene! It's VERY important that you clear the screen once, and then clear the depth buffer before drawing each viewport.   
       

     glClear (GL_COLOR_BUFFER_BIT);        // Clear Screen

       
    Now for the main drawing loop. We want to draw 4 different viewports, so we create a loop from 0 to 3.

    The first thing we do is set the color of the current viewport using glColor3ub(r,g,b). This may be new to a few of you. It just like glColor3f(r,g,b) but it uses unsigned bytes instead of floating point values. Remember earlier that I said it was easier to assign a random value from 0 to 255 as a color. Well now that we have such large values for each color this is the command we need to use to set the colors properly.

    glColor3f(0.5f,0.5f,0.5f) is 50% brightness for red, green and blue. glColor3ub(127,127,127) is also 50% brightness for red, green, blue.

    If loop is 0, we would be selecting r[0],g[0],b[0]. If loop is 1, we would be selecting the colors stored in r[1],g[1],b[1]. That way, each scene has it's own random color.   
       

     for (int loop=0; loop<4; loop++)       // Loop To Draw Our 4 Views
     {
      glColor3ub(r[loop],g[loop],b[loop]);      // Assign Color To Current View

       
    The first thing we need to do before we can draw anything is set up the current viewport. If loop equals 0, we are drawing the first viewport. We want this viewport on the left half of the screen (0), and halfway up the screen (window_height/2). We want the width of the viewport to be half the width of the main window (window_width/2) and we want the height to be half the height of the main window (window_height/2).

    If the main window is 1024x768, we would end up with a viewport at 0,384 with a width of 512 and a height of 384.

    This viewport would look like this:

    按此在新窗口浏览图片

    After setting up the viewport, we select the projection matrix, reset it and then set up our 2D Ortho view. We want the Ortho view to fill the entire viewport. So we give it a left value of 0 and a right value of window_width/2 (same width as the viewport). We also assign it a bottom value of window_height/2 and a top value of 0. This gives us the same height as the viewport.

    The top left of our Ortho view will be 0,0. The bottom right of our Ortho view will be window_width/2, window_height/2.   
       

      if (loop==0)         // If We Are Drawing The First Scene
      {
       // Set The Viewport To The Top Left.  It Will Take Up Half The Screen Width And Height
       glViewport (0, window_height/2, window_width/2, window_height/2);
       glMatrixMode (GL_PROJECTION);      // Select The Projection Matrix
       glLoadIdentity ();       // Reset The Projection Matrix
       // Set Up Ortho Mode To Fit 1/4 The Screen (Size Of A Viewport)
       gluOrtho2D(0, window_width/2, window_height/2, 0);
      }

       
    If loop equals 1, we are drawing the second viewport. It will be on the right half of the screen, and halfway up the screen (main window). The width and height will be the same as the first viewport. The only thing different is the first parameter of glViewport( ) is window_width/2. This tells our program that we want the viewport to start halfway from the left side of the main window.

    The second viewport would look like this:

    按此在新窗口浏览图片

    Again, we select the projection matrix and reset it, but this time we set up a perspective view with a 45 degree field of view and near value of 0.1f and a far value of 500.0f.   
       

      if (loop==1)         // If We Are Drawing The Second Scene
      {
       // Set The Viewport To The Top Right.  It Will Take Up Half The Screen Width And Height
       glViewport (window_width/2, window_height/2, window_width/2, window_height/2);
       glMatrixMode (GL_PROJECTION);      // Select The Projection Matrix
       glLoadIdentity ();       // Reset The Projection Matrix
       // Set Up Perspective Mode To Fit 1/4 The Screen (Size Of A Viewport)
       gluPerspective( 45.0, (GLfloat)(width)/(GLfloat)(height), 0.1f, 500.0 );
      }

       
    If loop equals 2, we are drawing the third viewport. It will be on the bottom right half of the main window. The width and height will be the same as the first and second viewports. The only thing different from the second viewport is the second parameter of glViewport( ) is now 0. This tells our program that we want the viewport to start at the bottom right half of the main window.

    The third viewport would look like this:

    按此在新窗口浏览图片

    We set up a perspective view exactly the same way we did for the second viewport.   
       

      if (loop==2)         // If We Are Drawing The Third Scene
      {
       // Set The Viewport To The Bottom Right.  It Will Take Up Half The Screen Width And Height
       glViewport (window_width/2, 0, window_width/2, window_height/2);
       glMatrixMode (GL_PROJECTION);      // Select The Projection Matrix
       glLoadIdentity ();       // Reset The Projection Matrix
       // Set Up Perspective Mode To Fit 1/4 The Screen (Size Of A Viewport)
       gluPerspective( 45.0, (GLfloat)(width)/(GLfloat)(height), 0.1f, 500.0 );
      }

       
    If loop equals 3, we are drawing the last viewport (viewport 4). It will be on the bottom left half of the main window. The width and height will be the same as the first, second and third viewports. The only thing different from the third viewport is the first parameter of glViewport( ) is now 0. This tells our program that we want the viewport to start at the bottom left half of the main window.

    The fourth viewport would look like this:

    按此在新窗口浏览图片

    We set up a perspective view exactly the same way we did for the second viewport.   
       

      if (loop==3)         // If We Are Drawing The Fourth Scene
      {
       // Set The Viewport To The Bottom Left.  It Will Take Up Half The Screen Width And Height
       glViewport (0, 0, window_width/2, window_height/2);
       glMatrixMode (GL_PROJECTION);      // Select The Projection Matrix
       glLoadIdentity ();       // Reset The Projection Matrix
       // Set Up Perspective Mode To Fit 1/4 The Screen (Size Of A Viewport)
       gluPerspective( 45.0, (GLfloat)(width)/(GLfloat)(height), 0.1f, 500.0 );
      }

       
    The following code selects the modelview matrix, resets it, then clears the depth buffer. We clear the depth buffer for each viewport drawn. Notice we are not clearing the screen color. Just the Depth Buffer! If you do not clear the depth buffer, you will see portions of objects disappear, etc. Definitely not pretty!   
       

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

      glClear (GL_DEPTH_BUFFER_BIT);       // Clear Depth Buffer

       
    The first image we draw is a flat 2D textured quad. The quad is drawn in ortho mode, and will fill the entire viewport. Because we are using ortho mode, there is no 3rd dimension, so there is no need to translate on the z-axis.

    Remember that the top left of the first viewport is 0,0 and the bottom right is window_width/2, window_height/2. So that means that the top right of our quad is at window_width/2, 0. The top left is at 0,0, the bottom left is at 0, window_height/2 and the bottom right is at window_width/2, window_height/2. Notice in ortho mode, we can actually work with pixels rather than units (depending on how we set the viewport up).   
       

      if (loop==0)         // Are We Drawing The First Image?  (Original Texture... Ortho)
      {
       glBegin(GL_QUADS);       // Begin Drawing A Single Quad
        // We Fill The Entire 1/4 Section With A Single Textured Quad.
        glTexCoord2f(1.0f, 0.0f); glVertex2i(window_width/2, 0              );
        glTexCoord2f(0.0f, 0.0f); glVertex2i(0,              0              );
        glTexCoord2f(0.0f, 1.0f); glVertex2i(0,              window_height/2);
        glTexCoord2f(1.0f, 1.0f); glVertex2i(window_width/2, window_height/2);
       glEnd();        // Done Drawing The Textured Quad
      }

       
    The second image we draw is a smooth sphere with lighting. The second viewport is perspective, so the first thing we need to do is move into the screen 14 units. We then rotate our object on the x-axis, y-axis and z-axis.

    We enable lighting, draw our sphere and then disable lighting. The sphere has a radius of 4 units with 32 slices and 32 stacks. If you feel like playing around, try changing the number of stacks or slices to a lower number. By reducing the number of stacks / slices, you reduce the smoothness of the sphere.

    Texture coordinates are automatically generated!   
       

      if (loop==1)         // Are We Drawing The Second Image?  (3D Texture Mapped Sphere... Perspective)
      {
       glTranslatef(0.0f,0.0f,-14.0f);      // Move 14 Units Into The Screen

       glRotatef(xrot,1.0f,0.0f,0.0f);      // Rotate By xrot On The X-Axis
       glRotatef(yrot,0.0f,1.0f,0.0f);      // Rotate By yrot On The Y-Axis
       glRotatef(zrot,0.0f,0.0f,1.0f);      // Rotate By zrot On The Z-Axis

       glEnable(GL_LIGHTING);       // Enable Lighting
       gluSphere(quadric,4.0f,32,32);      // Draw A Sphere
       glDisable(GL_LIGHTING);       // Disable Lighting
      }

       
    The third image drawn is the same as the first image, but it's drawn with perspective, It's tilted at an angle and it rotates (oh yay!).

    We move 2 units into the screen and then tilt the quad back 45 degrees. This makes the top of the quad further away from us, and the bottom of the quad closer towards us!

    We then rotate on the z-axis to get the quad spinning and draw the quad. We need to set the texture coordinates manually.   
       

      if (loop==2)         // Are We Drawing The Third Image?  (Texture At An Angle... Perspective)
      {
       glTranslatef(0.0f,0.0f,-2.0f);      // Move 2 Units Into The Screen
       glRotatef(-45.0f,1.0f,0.0f,0.0f);     // Tilt The Quad Below Back 45 Degrees.
       glRotatef(zrot/1.5f,0.0f,0.0f,1.0f);     // Rotate By zrot/1.5 On The Z-Axis

       glBegin(GL_QUADS);       // Begin Drawing A Single Quad
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, 0.0f);
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, 0.0f);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f);
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 0.0f);
       glEnd();        // Done Drawing The Textured Quad
      }

       
    If we are drawing the 4th image, we move 7 units into the screen. We then rotate the object on the x-axis, y-axis and z-axis.

    We enable lighting to give the object some nice shading and then we translate -2 units on the z-axis. The reason we do this is so that our object rotates around it's center point rather than rotating around one of the ends. The cylinder is 1.5 units wide on one end, 1.5 unit wide on the other end, it has a length of 4 units and is made up of 32 slices (panels around) and 16 stacks (length panels).

    In order to rotate around the center we need to translate half the length. Half of 4 is 2!

    After translating, rotating and then translating some more, we draw the cylinder and then disable lighting.   
       

      if (loop==3)         // Are We Drawing The Fourth Image?  (3D Texture Mapped Cylinder... Perspective)
      {
       glTranslatef(0.0f,0.0f,-7.0f);      // Move 7 Units Into The Screen
       glRotatef(-xrot/2,1.0f,0.0f,0.0f);     // Rotate By -xrot/2 On The X-Axis
       glRotatef(-yrot/2,0.0f,1.0f,0.0f);     // Rotate By -yrot/2 On The Y-Axis
       glRotatef(-zrot/2,0.0f,0.0f,1.0f);     // Rotate By -zrot/2 On The Z-Axis

       glEnable(GL_LIGHTING);       // Enable Lighting
       glTranslatef(0.0f,0.0f,-2.0f);      // Translate -2 On The Z-Axis (To Rotate Cylinder Around The Center, Not An End)
       gluCylinder(quadric,1.5f,1.5f,4.0f,32,16);    // Draw A Cylinder
       glDisable(GL_LIGHTING);       // Disable Lighting
      }
     }

       
    The last thing we do is flush the rendering pipeline.   
       

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

       
    Hopefully this tutorial answers any questions you may have had about multiple viewports. The code is not all that hard to understand. The code is almost identical to the standard basecode. The only thing that has really changed is the viewport setup is now done in the main drawing loop, the screen is cleared once before the viewports are drawn, and the depth buffer is cleared on it's own.

    You can use the code to display a variety of images all running in their own viewport, or you could use the code to display multiple views of a certain object. What you do with this code is up to you.

    I hope you guys enjoy the tutorial... If you find any mistakes in the code, or you feel you can make this tutorial even better, let me know.

    Jeff Molofee (NeHe)

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

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/31 20:38:00
     
     GoogleAdSense射手座1984-11-30
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/4/19 22:25:48

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

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