以文本方式查看主题 - 中文XML论坛 - 专业的XML技术讨论区 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 31-lesson 32 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=54209) |
-- 作者:一分之千 -- 发布时间:10/23/2007 12:55:00 PM -- [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 31-lesson 32 第三十一课 你知道大名鼎鼎的Milkshape3D建模软件么,我们将加载它的模型,当然你可以加载任何你认为不错的模型。 这课中使用的模型是从Milkshape3D中提取出来的,Milkshape3D是一个非常好的建模软件,它包含了自己的文件格式,所以你能很容易去分析和理解。 但是文件格式并不能使你加载一个模型,你必须自己定义一个结构去保存数据,接着把数据读入那个结构,我们将告诉你如何定义这样一个结构。 模型的定义在model.h中,好吧我们开始吧: // 顶点结构 // 顶点的个数和数据 // 三角形结构 // 使用的三角形 // 网格结构 // 使用的网格 // 材质属性 // 使用的纹理 我们创建了一个新类MilkshapeModel,它是从Model继承而来的。 bool MilkshapeModel::loadModelData( const char *filename ) inputFile.seekg( 0, ios::end ); byte *pBuffer = new byte[fileSize]; const byte *pPtr = pBuffer; if ( strncmp( pHeader->m_ID, "MS3D000000", 10 ) != 0 ) if ( pHeader->m_version < 3 || pHeader->m_version > 4 ) int nVertices = *( word* )pPtr; int i; int nTriangles = *( word* )pPtr; for ( i = 0; i < nTriangles; i++ ) int nGroups = *( word* )pPtr; word nTriangles = *( word* )pPtr; char materialIndex = *( char* )pPtr; m_pMeshes[i].m_materialIndex = materialIndex; int nMaterials = *( word* )pPtr; reloadTextures(); delete[] pBuffer; return true; void Model::reloadTextures() void Model::draw() // 按网格分组绘制 int materialIndex = m_pMeshes[i].m_materialIndex; if ( m_pMaterials[materialIndex].m_texture > 0 ) glBegin( GL_TRIANGLES ); for ( int k = 0; k < 3; k++ ) glNormal3fv( pTri->m_vertexNormals[k] ); if ( texEnabled ) Model *pModel = NULL; // 定义一个指向模型类的指针 pModel = new MilkshapeModel(); pModel->reloadTextures(); int DrawGLScene(GLvoid) glRotatef(yrot,0.0f,1.0f,0.0f); //绘制模型 yrot+=1.0f; |
-- 作者:一分之千 -- 发布时间:10/23/2007 12:56:00 PM -- Lesson 31 Model Rendering Tutorial by Brett Porter (brettporter@yahoo.com) The source for this project has been extracted from PortaLib3D, a library I have written to enable users to do things like displaying models with very little extra code. But so that you can trust such a library, you should understand what it is doing, so this tutorial aims to help with that. The portions of PortaLib3D included here retain my copyright notices. This doesn't mean they can't be used by you - it means that if you cut-and-paste the code into your project, you have to give me proper credit. That's all. If you choose to read, understand, and re-implement the code yourself (and it is what you are encouraged to do if you are not actually using the library. You don't learn anything with cut-and-paste!), then you free yourself of that obligation. Let's face it, the code is nothing special. Ok, let's get onto something more interesting! OpenGL Base Code The OpenGL base code is in Lesson32.cpp. Mostly it came from Lesson 6, with a small modification to the loading of textures and the drawing routine. The changes will be discussed later. Milkshape 3D The model I use in this example is from Milkshape 3D. The reason I use this is because it is a damn fine modelling package, and it includes its file-format so it is easy to parse and understand. My next plan is to implement an Anim8or (http://www.anim8or.com) file reader because it is free and of course a 3DS reader. However, the file format, while it will be described briefly here, is not the major concern for loading a model. You must create your own structures that are suitable to store the data, and then read the file into that. So first, let's describe the structures required for a model. Model Data Structures These model data structures come from the class Model in Model.h. First, and most important, we need vertices: // Vertex Structure // Vertices Used Next we need to group these vertices into triangles: // Triangle Structure // Triangles Used The next structure we have in a model is a mesh. A mesh is a group of triangles that all have the same material applied to them. The collection of meshes make up the entire model. The mesh structure is as follows: // Mesh // Meshes Used // Material Properties // Materials Used The Code - Loading the Model Now, on to loading the model. You will notice there is a pure virtual function called loadModelData, which takes the filename of the model as an argument. What happens is we create a derived class, MilkshapeModel, which implements this function, filling in the protected data structures mentioned above. Lets look at that function now: bool MilkshapeModel::loadModelData( const char *filename ) inputFile.seekg( 0, ios::end ); byte *pBuffer = new byte[fileSize]; const byte *pPtr = pBuffer; if ( strncmp( pHeader->m_ID, "MS3D000000", 10 ) != 0 ) if ( pHeader->m_version < 3 || pHeader->m_version > 4 ) int nVertices = *( word* )pPtr; int i; int nTriangles = *( word* )pPtr; for ( i = 0; i < nTriangles; i++ ) int nGroups = *( word* )pPtr; word nTriangles = *( word* )pPtr; char materialIndex = *( char* )pPtr; m_pMeshes[i].m_materialIndex = materialIndex; int nMaterials = *( word* )pPtr; reloadTextures(); delete[] pBuffer; return true; So at this point, the protected member variables of the Model class are filled with the model information. You'll note also that this is the only code in MilkshapeModel because it is the only code specific to Milkshape3D. Now, before the model can be rendered, it is necessary to load the textures for each of its materials. This is done with the following code: void Model::reloadTextures() The Code - Drawing the Model Now we can start the code to draw the model! This is not difficult at all now that we have a careful arrangement of the data structures in memory. void Model::draw() Now we loop through each of the meshes and draw them individually: // Draw By Group int materialIndex = m_pMeshes[i].m_materialIndex; if ( m_pMaterials[materialIndex].m_texture > 0 ) glBegin( GL_TRIANGLES ); for ( int k = 0; k < 3; k++ ) glNormal3fv( pTri->m_vertexNormals[k] ); if ( texEnabled ) The only other code of interest in the Model class is the constructor and destructor. These are self explanatory. The constructor initializes all members to 0 (or NULL for pointers), and the destructor deletes the dynamic memory for all of the model structures. You should note that if you call the loadModelData function twice for one Model object, you will get memory leaks. Be careful! The final topic I will discuss here is the changes to the base code to render using the new Model class, and where I plan to go from here in a future tutorial introducing skeletal animation. Model *pModel = NULL; // Holds The Model Data pModel = new MilkshapeModel(); pModel->reloadTextures(); Finally there is a new DrawGLScene function: int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing glRotatef(yrot,0.0f,1.0f,0.0f); pModel->draw(); yrot+=1.0f; To make it a bit more interesting, the scene gradually rotates around the y-axis with glRotatef. Finally, the model is drawn with its draw member function. It is drawn centered at the origin (assuming it was modelled around the origin in Milkshape 3D!), so If you want to position or rotate or scale it, simply call the appropriate GL functions before drawing it. Voila! To test it out - try making your own models in Milkshape (or use its import function), and load them instead by changing the line in WinMain. Or add them to the scene and draw several models! What Next? In a future tutorial for NeHe Productions, I will explain how to extend this class structure to incorporate skeletal animation. And if I get around to it, I will write more loader classes to make the program more versatile. The step to skeletal animation is not as large as it may seem, although the math involved is much more tricky. If you don't understand much about matrices and vectors, now is the time to read up them! There are several resources on the web that can help you out. See you then! Some information about Brett Porter: Born in Australia, he studied at the University of Wollongong, recently graduating with a BCompSc and a BMath. He began programming in BASIC 12 years ago on a Commodore 64 "clone" called the VZ300, but soon moved up to Pascal, Intel assembly, C++ and Java. During the last few years 3D programming has become an interest and OpenGL has become his graphics API of choice. For more information visit his homepage at: http://rsn.gamedev.net A follow up to this tutorial on Skeletal Animation can be found on Brett's homepage. Visit the link above! Brett Porter Jeff Molofee (NeHe) |
-- 作者:一分之千 -- 发布时间:10/23/2007 1:00:00 PM -- 32 ke 这又是一个小游戏,交给的东西会很多,慢慢体会吧 我确信会有批评,但我非常乐观对这课! 我已在从深度里选择和排序物体这个主题里找到快乐! 一些需要注意的代码. 我仅仅会在lesson32.cpp里讨论. 有一些不成熟的改动在 NeHeGL 代码里. 最重要的改动是我加入鼠标支持在 WindowProc(). 我也加入 int mouse_x, mouse_y 在存鼠标运动. 在 NeHeGL.h 以下两条代码被加入: extern int mouse_x; & extern int mouse_y; 课程用到的材质是用 Adobe Photoshop 做的. 每个 .TGA 文件是32位图片有一个alpha 通道. 若你不确信自已能在一个图片加入alpha通道, 找一本好书,上网,或读 Adobe Photoshop帮助. 全部的过程非常相似,我做了透明图在透明图课程. 调入你物体在 Adobe Photoshop (或一些其它图形处理程序,且支持alpha 通道). 用选择颜色工具选你图片的背景. 复制选区. 新建一个图. 粘贴生成新文件. 取消图片选择,你图的背景应是黑色. 使周围是白色. 选全部图复制. 回到最初的图且建一个alpha 通道. 粘贴黑和白透明图你就完成建立alpha通道.存图片为32位t .TGA文件. 使确定保存透明背景是选中的,保存! 如以往我希望你喜欢这课程. 我感兴趣你对他的想法. 若你有些问题或你发现一些问题,告诉我. 我匆忙的完成这课程 所以若你发现哪部分很难懂,给我发些邮件,然后我会用不同的方式或更详细的解释! #include <windows.h> #pragma comment( lib, "opengl32.lib" ) // 在链接时连接Opengl32.lib库 #ifndef CDS_FULLSCREEN void DrawTargets(); GL_Window* g_window; // 用户定义的变量 typedef int (*compfn)(const void*, const void*); // 定义用来比较的函数 变量frame 是用来存我们爆炸动画的周期. 每一帧改变增加一个爆炸材质. 在这课有更多在不久. 保存单个物体的移动方向, 我们用变量 dir. 一个dir 能有4 个值: 0 - 物体左移, 1 - 物体右移, 2 - 物体上移 和最后 3 - 物体下移 texid 能是从0到4的数. 0 表示是蓝面材质, 1 是水桶材质, 2 是靶子的材质 , 3 是 可乐的材质 和 4 是 花瓶 材质. 最近在调入材质的代码, 你将看到先前5种材质来自目标图片. x 和 y 两者都用来记屏模上物体的位置. x 表示物体在 x-轴, y 表示物体在 y-轴. 物体在z-轴上的旋转是记在变量spin. 在以后的代码, 我们将加或减spin基数在旅行的方向上. 最后, distance 保存我们物体到屏幕的距离. 距离是极端重要的变量, 我们将用他来计算屏幕的左右两边, 而且在对象关闭之前排序物体,画出物体的距离.
struct objects { typedef struct // 新建一个结构 TextureImage textures[10]; // 定义10个材质 objects object[30]; // 定义 30 个物体
struct dimensions { // 物体维数 // 每个物体的大小: 蓝面, 水桶, 靶子, 可乐, 瓶子 bool LoadTGA(TextureImage *texture, char *filename) // 调入一个TGA 文件到内存 FILE *file = fopen(filename, "rb"); // 打开 TGA 文件 if( file==NULL || // 文件是否已存在 ? texture->width = header[1] * 256 + header[0]; // 定义 TGA 宽 if( texture->width <=0 || // 若 宽<=0 texture->bpp = header[4]; // 取 TGA 的位每象素 (24 或 32) texture->imageData=(GLubyte *)malloc(imageSize); // 分配 内存 为 TGA 数据 if( texture->imageData==NULL || // 这个内存是否存在? fclose(file); // 关掉文件 for(GLuint i=0; i<int(imageSize); i+=bytesPerPixel) // 在图象数据里循环 fclose (file); // 关掉文件 // 建立一个贴图材质从以上数据 glBindTexture(GL_TEXTURE_2D, texture[0].texID); // 绑定我们的材质 if (texture[0].bpp==24) // 若 TGA 是24 位的 glTexImage2D(GL_TEXTURE_2D, 0, type, texture[0].width, texture[0].height, 0, type, GL_UNSIGNED_BYTE, texture[0].imageData); return true; // 材质建立成功, 返回正确
GLvoid BuildFont(GLvoid) // 建立我们字体显示列表 glNewList(base+loop,GL_COMPILE); // 开始建立一个列表 GLvoid glPrint(GLint x, GLint y, const char *string, ...) // 输出在屏慕的位置 if (string == NULL) // 若文字为空 va_start(ap, string); // 解析字符串 glBindTexture(GL_TEXTURE_2D, textures[9].texID); // 选择我们字体材质 int Compare(struct objects *elem1, struct objects *elem2) // 比较 函数 在给完随机的距离之后, 我们给物体一个随机的 y . 我们不想物体低于 -1.5f, 否则他将低于大地, 且我们不想物体高于3.0f. 所以留在我们的区间的随机数不能高于4.5f (-1.5f+4.5f=3.0f). 去计算 x 位置, 用一些狡猾的数学. 用我们的距离减去15.0f . 除以2 减5*level. 再 减随机数(0.0f 到5) 乘level. 减 5*level rndom(0.0f to 5*level) 这是最高级. 选一个方向. 使事情简单明白x, 写一个快的例子. 距离是 -30.0f ,当前级是 1: object[num].x=((-30.0f-15.0f)/2.0f)-(5*1)-float(rand()%(5*1)); 开始在屏模上移 10 个单位 , 距离是 -30.0f. 其实是 -40.0f.用透视的代码在 NeHeGL.cpp 文件. GLvoid InitObject(int num) // 初始化一个物体 object[num].x=float(rand()%int(-30.0f-10.0f))+((-30.0f-10.0f)/2.0f); object[num].x=float(rand()%int(-40.0f)+(-40.0f)/2.0f); object[num].x=float(15 {assuming 15 was returned))+(-20.0f); object[num].x=15.0f-20.0f; object[num].x=-5.0f; 下面设y. 我们想水桶从天上下来. 我人不想穿过云. 所以我们设 y 为 4.5f. 刚在去的下面一点. if (object[num].texid==2) // 靶子 理由是简单的... Z 缓冲区防止 OpenGL 从已画好的混合东西再画象素. 这就是为什么会发生物体画在透明混合之后而不再显示出来. 为什么你最后看到的是一个四边形与物体重叠... 很不好看! 我们已知道每个物体的深度. 因而在初始化一个物体之后, 我们能通过把物体排序,而用qsort 函数(快速排序sort),来解决这个问题 . 通过物体排序, 我们能确信第一个画的是最远的物体. 这意味着当我们画物体时, 起始于第一个物体, 物体通过用距离将被先画. 紧挨着那个物体(晚一会儿画) 将看到先前的物体在他们的后面, 再将适度的混合! 这文中的这行线注释是我在 MSDN 里发现这些代码,在网上花时间查找之后找到的解答 . 他们工作的很好,允许各种的排序结构. qsort 传送 4 个参数. 第一个参数指向物体数组 (被排序的数组d). 第二个参数是我们想排序数组的个数... 当然,我们想所有的排序的物体普遍的被显示(各个level). 第三个参数规定物体结构的大不, 第四个参数指向我们的 Compare() 函数. 大概有更好的排序结构的方法, 但是 qsort() 工作起来... 快速方便,简单易用! 这个是重要的知识点, 若你们想用 glAlphaFunc() 和 glEnable(GL_ALPHA_TEST), 排序是没必要的. 然而, 用Alpha 功能你被限制在完全透明或完全白底混合, 没有中间值. 用 Blendfunc()排序用一些更多的工作,但他顾及半透明物体. // 排序物体从距离:我们物体数组的开始地址 *** MSDN 代码修改为这个 TUT ***// 各种的数按// 各自的要素的// 指针比较的函数 BOOL Initialize (GL_Window* window, Keys* keys) // 任何 OpenGL 从这初始化 srand( (unsigned)time( NULL ) ); // 使随机化事件 if ((!LoadTGA(&textures[0],"Data/BlueFace.tga")) || // 调入蓝面材质 glBlendFunc() 是很重要的一行代码. 我们设混合函数(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA). 这些加上alpha变量的屏幕上的混合物体存在物体的材质. 在设置混合模式之后, 我们激活blending(混合). 然后我们打开 2D 材质贴图, 最后,打开 GL_CULL_FACE. 这是去除每个物体的后面( 没有一点浪费在一些我们看不到的循环 ). 画一些四边形逆时针卷动 ,因而精致而适当的面片. 早先的教程我谈论使用glAlphaFunc()代替alpha 混合. 若你想用Alpha 函数, 注释出的两行混合代码和不注释的两行在glEnable(GL_BLEND)之下. 你也能注释出qsort()函数在 InitObject() 部分里的代码. 程序应该运行ok,但sky 材质将不在这. 因为sky的材质已是一个alpha 变量0.5f. 当早在我说关于Alpha函数, 我提及它只工作在alpha 变量0 或 1. 若你想它出现,你将不得不修改sky的材质alpha 通道! 再则, 若你决定用Alpha 函数代替, 你不得排序物体.两个方法都有好处! 再下而是从SGI 网站的快速引用: "alpha 函数丢弃细节,代替画他们在结构缓冲器里. 因此排序原来的物体不是必须的 (除了一些其它像混合alpha模式是打开的). 不占优势的是象素必须完全白底或完全透明".
BuildFont(); // 建立我们的字体显示列表 glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 黑色背景 for (int loop=0; loop<30; loop++) // 循环在 30 个物体Objects return TRUE; // 返回正确 (设初值成功) void Deinitialize (void) // 任何user 结束初始化从这 void Selection(void) // 这是选择正确 if (game) // 游戏是否结束? glGetIntegerv(GL_VIEWPORT, viewport) 取当前视点存在viewport[]. 最初,等于 OpenGL 窗口维数. glSelectBuffer(512, buffer) 说 OpenGL 用这个内存. // 这是设视点的数组在屏幕窗口的位置 glInitNames(); // 设名字堆栈 glMatrixMode(GL_PROJECTION); // 选投影矩阵 // 这是建一个矩阵使鼠标在屏幕缩放 之后, 打开回到发射矩阵, 从堆栈中弹出矩阵. 之扣打开回到modelview 矩阵. 最后一行,回到渲染模式 因而物体画的很真实的在屏幕上. hits 将采集gluPickMatrix()所需要取渲染的物体数 . // 应用透视矩阵 if (hits > 0) // 若有大于0个 Hits for (int loop = 1; loop < hits; loop++) // 循环所有检测到的物体 if (kills>level*5) // 已有新的级? void Update(DWORD milliseconds) // 这里用来更新 if (g_keys->keyDown[' '] && game) // 按下空格键? game=FALSE; //设game为false roll-=milliseconds*0.00005f; // 云的旋转 for (int loop=0; loop<level; loop++) // 循环所有的物体 if (object[loop].rot==1) if (object[loop].rot==2) if (object[loop].dir==1) if (object[loop].dir==0) if (object[loop].dir==2) if (object[loop].dir==3) // 如果到达左边界,你没有击中,则增加丢失的目标数 //如果到达左边界,你没有击中,则方向变为向下 void Object(float width,float height,GLuint texid) // 画物体用需要的宽,高,材质 void Explosion(int num) // 画爆炸动画的1帧 glBindTexture(GL_TEXTURE_2D, textures[5].texID); // 选择爆炸的纹理 void DrawTargets(void) // 画靶子 glLoadName(loop); // 给物体新名字 if (object[loop].hit) // 若物体已被点击 void Draw(void) // 画我们的现场 glPushMatrix(); glTexCoord2f(1.5f,roll+1.0f); glVertex3f( 28.0f,+7.0f,-50.0f); glTexCoord2f(1.5f,roll+1.0f); glVertex3f( 28.0f,+7.0f,0.0f); glBindTexture(GL_TEXTURE_2D, textures[6].texID); // 大地材质 if (miss>9) // 我们已丢失 10 个物体? if (game) // 游戏是否结束? glMatrixMode(GL_PROJECTION); glFlush(); 要点: 这是很重要的,称为glTexImage2D 你设为两种格式国际 GL_RGBA. 否则 alpha blending 将不工作! |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
156.250ms |