以文本方式查看主题 - 中文XML论坛 - 专业的XML技术讨论区 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 33-lesson 34 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=54271) |
-- 作者:一分之千 -- 发布时间:10/24/2007 4:51:00 PM -- [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 33-lesson 34
在这一课里,你将学会如何加载压缩和为压缩的TGA文件,由于它使用RLE压缩,所以非常的简单,你能很快地熟悉它的。 我们将从两个头文件开始。第一个文件控制纹理结构,在第二个里,结构和变量将为程序读取所用。 就像每个头文件那样,我们需要一些包含保护措施以防止文件被重复包含。 在文件的顶部加入这样几行程序: #ifndef __TEXTURE_H__ // 看看此头文件是否已经被包含 #endif // __TEXTURE_H__ 结束包含保护 在这个头文件中,我们将要加入完成每件工作所需的标准头文件。在#define __TGA_H__后添加如下几行: #pragma comment(lib, "OpenGL32.lib") // 链接 Opengl32.lib 我们将需要一块空间存储图像数据以及OpenGL生成纹理所需的类型。我们将要用到以下结构: typedef struct 接下来,看看另外两个结构,它们将在处理TGA文件的过程中使用。 typedef struct typedef struct TGAHeader tgaheader; // 用来存储我们的文件头 // 未压缩的TGA头 // 读取一个未压缩的文件 马上,我们就可以在文件开头包含我们刚刚建立的头文件。 #include "tga.h" // 包含我们刚刚建立的头文件 接下来,我们要做的事情是看看第一个函数,名为LoadTGA(…)。 // 读取一个TGA文件! 函数的前两行声明了一个文件指针,然后打开由“filename”参数指定的文件,它由函数的第二个指针传递进去。 FILE * fTGA; // 声明文件指针 if(fTGA == NULL) // 如果此处有错误 if(fread(&tgaheader, sizeof(TGAHeader), 1, fTGA) == 0) // 如果文件头附合未压缩的文件头格式 下面开始我们要做的第一件事,像往常一样,是函数头。 //读取未压缩的TGA文件 接下来我们试着再从文件中读取6个字节的内容,并且存储在tga.header中。如果他失败了,我们运行一些错误处理代码,并且返回false。 // 尝试继续读取6个字节的内容 texture->width = tga.header[1] * 256 + tga.header[0]; // 计算高度 // 确认所有的信息都是有效的 if(texture->bpp == 24) // 是24 bit图像吗? tga.bytesPerPixel = (tga.Bpp / 8); // 计算BPP 然后我们确认内存已经分配,并且它不是NULL。如果出现了错误,则运行错误处理代码。 // 分配内存 // 尝试读取所有图像数据 Steve Thomas补充:我已经编写了能稍微更快速读取TGA文件的代码。它涉及到仅用3个二进制操作将BGR转换到RGB的方法。 然后我们关闭文件,并且成功退出函数。 // 开始循环 fclose(fTGA); // 关闭文件 bool LoadCompressedTGA(Texture * texture, char * filename, FILE * fTGA) // 分配存储图像所需的内存空间 我们也需要存储当前所处的像素,以及我们正在写入的图像数据的字节,这样避免溢出写入过多的旧数据。 GLuint pixelcount = tga.Height * tga.Width; // 图像中的像素数 让我们将它分解为更多可管理的块。 首先我们声明一个变量来存储“块”头。块头指示接下来的段是RLE还是RAW,它的长度是多少。如果一字节头小于等于127,则它是一个RAW头。头的值是颜色数,是负数,在我们处理其它头字节之前,我们先读取它并且拷贝到内存中。这样我们将我们得到的值加1,然后读取大量像素并且将它们拷贝到ImageData中,就像我们处理未压缩型图像一样。如果头大于127,那么它是下一个像素值随后将要重复的次数。要获取实际重复的数量,我们将它减去127以除去1bit的的头标示符。然后我们读取下一个像素并且依照上述次数连续拷贝它到内存中。 do // 开始循环 if(chunkheader < 128) // 如果是RAW块 首先,我们读取并检验像素数据。单个像素的数据将被存储在colorbuffer变量中。然后我们将检查它是否为RAW头。如果是,我们需要添加一个到变量之中以获取头之后的像素总数。 // 开始像素读取循环 texture->imageData[currentbyte] = colorbuffer[2]; // 写“R”字节 else // 如果是RLE头 // 读取下一个像素 然后,我们将颜色值拷贝到图像数据中,预处理R和B的值交换。 随后,我们增加当前的字节数、当前像素,这样我们再次写入值时可以处在正确的位置。 // 开始循环 最后,我们关闭文件并返回成功。 while(currentpixel < pixelcount); // 是否有更多的像素要读取?开始循环直到最后 |
-- 作者:一分之千 -- 发布时间:10/24/2007 4:52:00 PM -- Lesson 33 Loading Uncompressed and RunLength Encoded TGA images By Evan "terminate" Pipho. I've seen lots of people ask around in #gamdev, the gamedev forums, and other places about TGA loading. The following code and explanation will show you how to load both uncompressed TGA files and RLE compressed files. This particular tutorial is geared toward OpenGL, but I plan to make it more universal in the future. We will begin with the two header files. The first file will hold our texture structure, the second, structures and variables used by the loading code. Like every header file we need some inclusion guards to prevent the file from being included multiple times. At the top of the file add these lines: #ifndef __TEXTURE_H__ // See If The Header Has Been Defined Yet #endif // __TEXTURE_H__ End Inclusion Guard Into this header file we we will insert the standard headers we will need for everything we do. Add the following lines after the #define __TGA_H__ command. #pragma comment(lib, "OpenGL32.lib") // Link To Opengl32.lib We will need a place to store image data and specifications for generating a texture usable by OpenGL. We will use the following structure. typedef struct Next come two more structures used during processing of the TGA file. typedef struct typedef struct TGAHeader tgaheader; // Used To Store Our File Header // Uncompressed TGA Header // Load An Uncompressed File Right off the bat we have to include the file we just made so at the top of the file put. #include "tga.h" // Include The Header We Just Made The next thing we see is the first function, which is called LoadTGA(...). // Load A TGA File! The first two lines of the function declare a file pointer and then open the file specified by "filename" which was passed to the function in the second parem for reading. FILE * fTGA; // Declare File Pointer if(fTGA == NULL) // If Here Was An Error // Attempt To Read The File Header // If The File Header Matches The Uncompressed Header First thing we come to, as always, is the function header. // Load An Uncompressed TGA! Next we try to read the next 6 bytes of the file, and store them in tga.header. If it fails, we run some error code, and return false. // Attempt To Read Next 6 Bytes texture->width = tga.header[1] * 256 + tga.header[0]; // Calculate Height // Make Sure All Information Is Valid if(texture->bpp == 24) // Is It A 24bpp Image? tga.bytesPerPixel = (tga.Bpp / 8); // Calculate The BYTES Per Pixel Then we check to make sure memory was allocated, and is not NULL. If there was an error, run error handling code. // Allocate Memory // Attempt To Read All The Image Data Steve Thomas Adds: I've got a little speedup in TGA loading code. It concerns switching BGR into RGB using only 3 binary operations. Instead of using a temp variable you XOR the two bytes 3 times. Then we close the file, and exit the function successfully. // Start The Loop fclose(fTGA); // Close The File bool LoadCompressedTGA(Texture * texture, char * filename, FILE * fTGA) // Allocate Memory To Store Image Data We also need to store which pixel we are currently on, and what byte of the imageData we are writing to, to avoid overflows and overwriting old data. We will allocate enough memory to store one pixel. GLuint pixelcount = tga.Height * tga.Width; // Number Of Pixels In The Image Lets break it down into more manageable chunks. First we declare a variable in order to store the chunk header. A chunk header dictates wheather the following section is RLE, or RAW, and how long it is. If the one byte header is less than or equal to 127, then it is a RAW header. The value of the header is the number of colors, minus one, that we read ahead and copy into memory, before we hit another header byte. So we add one to the value we get, and then read that many pixels and copy them into the ImageData, just like we did with the uncompressed ones. If the header is ABOVE 127, then it is the number of times that the next pixel value is repeated consequtively. To get the actual number of repetitions we take the value returned and subtract 127 to get rid of the one bit header identifier. Then we read the next one pixel and copy it the said number of times consecutively into the memory. On to the code. First we read the one byte header. do // Start Loop if(chunkheader < 128) // If The Chunk Is A 'RAW' Chunk First we read and verify the pixel data. The data for one pixel will be stored in the colorbuffer variable. Next we will check to see if it a RAW header. If it is, we need to add one to the value to get the total number of pixels following the header. // Start Pixel Reading Loop texture->imageData[currentbyte] = colorbuffer[2]; // Write The 'R' Byte else // If It's An RLE Header // Read The Next Pixel Then we copy the the color values into the image data, preforming the R and B value switch. Then we increment the current bytes, and current pixel, so we are in the right spot when we write the values again. // Start The Loop Last of all we close up the file and return success. while(currentpixel < pixelcount); // More Pixels To Read? ... Start Loop Over Evan Pipho (Terminate) Jeff Molofee (NeHe) |
-- 作者:一分之千 -- 发布时间:10/24/2007 4:53:00 PM -- 第三十四课 这一课将教会你如何从一个2D的灰度图创建地形 #define MAP_SIZE 1024 BYTE g_HeightMap[MAP_SIZE*MAP_SIZE]; // 保存高度数据 float scaleValue = 0.15f; // 地形的缩放比例 // 从*.raw文件中加载高度数据 // 打开文件 // 如果文件不能打开 // 读取文件数据到pHeightMap数组中 // 读取是否成功 // 如果不成功,提示错误退出 // 关闭文件 // 载入1024*1024的高度图道g_HeightMap数组中 LoadRawFile("Data/Terrain.raw", MAP_SIZE * MAP_SIZE, g_HeightMap); int Height(BYTE *pHeightMap, int X, int Y) // 下面的函数返回(x,y)点的高度 if(!pHeightMap) return 0; // 检测高度图是否存在,不存在则返回0 return pHeightMap[x + (y * MAP_SIZE)]; // 返回(x,y)的高度 void SetVertexColor(BYTE *pHeightMap, int x, int y) // 按高度设置顶点的颜色,越高的地方越亮 float fColor = -0.15f + (Height(pHeightMap, x, y ) / 256.0f); // 设置顶点的颜色 void RenderHeightMap(BYTE pHeightMap[]) // 根据高度图渲染输出地形 if(!pHeightMap) return; // 确认高度图存在 if(bRender) // 选择渲染模式 for ( X = 0; X < MAP_SIZE; X += STEP_SIZE ) // 设置顶点颜色 glVertex3i(x, y, z); // 调用OpenGL绘制顶点的命令 // 绘制(x,y+1)处的顶点 // 绘制(x+1,y+1)处的顶点 // 绘制(x+1,y)处的顶点 glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // 重置颜色 // 设置视点 RenderHeightMap(g_HeightMap); // 渲染高度图 return TRUE; case WM_LBUTTONDOWN: // 是否单击鼠标左键 |
-- 作者:一分之千 -- 发布时间:10/24/2007 4:54:00 PM -- Lesson 34 Welcome to another exciting tutorial! The code for this tutorial was written by Ben Humphrey, and is based on the GL framework from lesson 1. By now you should be a GL expert {grin}, and moving the code into your own base code should be a snap! This tutorial will teach you how to create cool looking terrain from a height map. For those of you that have no idea what a height map is, I will attempt a crude explanation. A height map is simply... displacement from a surface. For those of you that are still scratching your heads asking yourself "what the heck is this guy talking about!?!"... In english, our heightmap represents low and height points for our landscape. It's completely up to you to decide which shades represent low points and which shades represent high points. It's also important to note that height maps do not have to be images... you can create a height map from just about any type of data. For instance, you could use an audio stream to create a visual height map representation. If you're still confused... keep reading... it will all start to make sense as you go through the tutorial :) #include <windows.h> // Header File For Windows #pragma comment(lib, "opengl32.lib") // Link OpenGL32.lib Further down in the code you will notice bRender. If bRender is set to true (which it is by default), we will drawn solid polygons. If bRender is set to false, we will draw the landscape in wire frame. #define MAP_SIZE 1024 // Size Of Our .RAW Height Map ( NEW ) HDC hDC=NULL; // Private GDI Device Context bool keys[256]; // Array Used For The Keyboard Routine BYTE g_HeightMap[MAP_SIZE*MAP_SIZE]; // Holds The Height Map Data ( NEW ) float scaleValue = 0.15f; // Scale Value For The Terrain ( NEW ) LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // Resize And Initialize The GL Window // Loads The .RAW File And Stores It In pHeightMap // Open The File In Read / Binary Mode. // Check To See If We Found The File And Could Open It After reading in the data, we check to see if there were any errors. We store the results in result and then check result. If an error did occur, we pop up an error message. The last thing we do is close the file with fclose(pFile). // Here We Load The .RAW File Into Our pHeightMap Data Array // After We Read The Data, It's A Good Idea To Check If Everything Read Fine // Check If We Received An Error // Close The File int InitGL(GLvoid) // All Setup For OpenGL Goes Here // Here we read read in the height map from the .raw file and put it in our LoadRawFile("Data/Terrain.raw", MAP_SIZE * MAP_SIZE, g_HeightMap); // ( NEW ) return TRUE; // Initialization Went OK We check to make sure pHeightMap points to valid data, if not, we return 0. Otherwise, we return the value stored at x, y in our height map. By now, you should know that we have to multiply y by the width of the image MAP_SIZE to move through the data. More on this below! int Height(BYTE *pHeightMap, int X, int Y) // This Returns The Height From A Height Map Index if(!pHeightMap) return 0; // Make Sure Our Data Is Valid Now that we have the correct index, we will return the height at that index (data at x, y in our array). return pHeightMap[x + (y * MAP_SIZE)]; // Index Into Our Height Array And Return The Height void SetVertexColor(BYTE *pHeightMap, int x, int y) // This Sets The Color Value For A Particular Index float fColor = -0.15f + (Height(pHeightMap, x, y ) / 256.0f); // Assign This Blue Shade To The Current Vertex As always, we check to see if the height map (pHeightMap) contains data. If not, we return without doing anything. void RenderHeightMap(BYTE pHeightMap[]) // This Renders The Height Map As Quads if(!pHeightMap) return; // Make Sure Our Height Data Is Valid if(bRender) // What We Want To Render for ( X = 0; X < MAP_SIZE; X += STEP_SIZE ) // Set The Color Value Of The Current Vertex glVertex3i(x, y, z); // Send This Vertex To OpenGL To Be Rendered // Get The (X, Y, Z) Value For The Top Left Vertex // Set The Color Value Of The Current Vertex glVertex3i(x, y, z); // Send This Vertex To OpenGL To Be Rendered // Get The (X, Y, Z) Value For The Top Right Vertex // Set The Color Value Of The Current Vertex glVertex3i(x, y, z); // Send This Vertex To OpenGL To Be Rendered // Get The (X, Y, Z) Value For The Bottom Right Vertex // Set The Color Value Of The Current Vertex glVertex3i(x, y, z); // Send This Vertex To OpenGL To Be Rendered glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // Reset The Color The values of gluLookAt() are as follows: The first three numbers represent where the camera is positioned. So the first three values move the camera 212 units on the x-axis, 60 units on the y-axis and 194 units on the z-axis from our center point. The next 3 values represent where we want the camera to look. In this tutorial, you will notice while running the demo that we are looking a little to the left. We are also look down towards the landscape. 186 is to the left of 212 which gives us the look to the left, and 55 is lower than 60, which gives us the appearance that we are higher than the landscape looking at it with a slight tilt (seeing a bit of the top of it). The value of 171 is how far away from the camera the object is. The last three values tell OpenGL which direction represents up. Our mountains travel upwards on the y-axis, so we set the value on the y-axis to 1. The other two values are set at 0. gluLookAt can be very intimidating when you first use it. After reading the rough explanation above you may still be confused. My best advise is to play around with the values. Change the camera position. If you were to change the y position of the camera to say 120, you would see more of the top of the landscape, because you would be looking all the way down to 55. I'm not sure if this will help, but I'm going to break into one of my highly flamed real life "example" explanations :) Lets say you are 6 feet and a bit tall. Lets also assume your eyes are at the 6 foot mark (your eyes represent the camera - 6 foot is 6 units on the y-axis). Now if you were standing in front of a wall that was only 2 feet tall (2 units on the y-axis), you would be looking DOWN at the wall and would be able to see the top of the wall. If the wall was 8 feet tall, you would be looking UP at the wall and you would NOT see the top of the wall. The view would change depending on if you were looking up or down (if you were higher than or lower than the object you are looking at). Hope that makes a bit of sense! int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing // Position View Up Vector glScalef(scaleValue, scaleValue * HEIGHT_RATIO, scaleValue); RenderHeightMap(g_HeightMap); // Render The Height Map return TRUE; // Keep Going GLvoid KillGLWindow(GLvoid) // Properly Kill The Window BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag) LRESULT CALLBACK WndProc( HWND hWnd, // Handle For This Window return 0; // Return To The Message Loop case WM_SYSCOMMAND: // Intercept System Commands case WM_CLOSE: // Did We Receive A Close Message? case WM_LBUTTONDOWN: // Did We Receive A Left Mouse Click? case WM_KEYDOWN: // Is A Key Being Held Down? case WM_KEYUP: // Has A Key Been Released? case WM_SIZE: // Resize The OpenGL Window // Pass All Unhandled Messages To DefWindowProc int WINAPI WinMain( HINSTANCE hInstance, // Instance // Ask The User Which Screen Mode They Prefer // Create Our OpenGL Window while(!done) // Loop That Runs While done=FALSE if (keys[VK_F1]) // Is F1 Being Pressed? if (keys[VK_UP]) // Is The UP ARROW Being Pressed? if (keys[VK_DOWN]) // Is The DOWN ARROW Being Pressed? // Shutdown Once you understand how the code works, play around a little. One thing you could try doing is adding a little ball that rolls across the surface. You already know the height of each section of the landscape, so adding the ball should be no problem. Other things to try: Create the heightmap manually, make it a scrolling landscape, add colors to the landscape to represent snowy peaks / water / etc, add textures, use a plasma effect to create a constantly changing landscape. The possibilities are endless :) Hope you enjoyed the tut! You can visit Ben's site at: http://www.GameTutorials.com. Ben Humphrey (DigiBen) Jeff Molofee (NeHe) |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
191.406ms |