以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 25-lesson 26 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=54177) |
-- 作者:一分之千 -- 发布时间:10/22/2007 7:14:00 PM -- [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 25-lesson 26 在这一课中,你将学会如何从文件加载3D模型,并且平滑的从一个模型变换为另一个模型。 GLfloat xrot,yrot,zrot, // X, Y & Z 轴的旋转角度 int key=1; // 物体的标识符 typedef struct typedef struct // 物体结构 int maxver; // 最大的顶点数 void objallocate(OBJECT *k,int n) void objfree(OBJECT *k) void readstr(FILE *f,char *string) // 读取一行字符 void objload(char *name,OBJECT *k) // 从文件加载一个模型 filein = fopen(name, "rt"); // 打开文本文件,供读取 for (int i=0;i<ver;i++) // 循环所有的顶点 k->points[i].x = rx; // 保存当前顶点的x坐标 if(ver>maxver) maxver=ver; // 记录最大的顶点数 VERTEX calculate(int i) // 计算第i个顶点每次变换的位移 GLvoid ReSizeGLScene(GLsizei width, GLsizei height) int InitGL(GLvoid) maxver=0; // 初始化最大顶点数为0 objallocate(&morph4,486); // 为第四个模型分配内存资源 objload("data/sphere.txt",&helper); return TRUE; // 初始化完成,成功返回 void DrawGLScene(GLvoid) xrot+=xspeed; yrot+=yspeed; zrot+=zspeed; // 根据旋转速度,增加旋转角度 GLfloat tx,ty,tz; // 顶点临时变量 glBegin(GL_POINTS); // 点绘制开始 glColor3f(0,1,1); // 设置颜色 // 如果启用变形则把变形步数增加 objfree(&morph1); // 释放模型1内存 BOOL CreateGLWindow() LRESULT CALLBACK WndProc() if(keys[VK_PRIOR]) // PageUp键是否被按下 if(keys[VK_NEXT]) // PageDown键是否被按下 if(keys[VK_DOWN]) // 下方向键是否被按下 if(keys[VK_UP]) // 上方向键是否被按下 if(keys[VK_RIGHT]) // 右方向键是否被按下 if(keys[VK_LEFT]) // 左方向键是否被按下 if (keys['Z']) // Z键是否被按下 if (keys['W']) // W键是否被按下 if (keys['S']) // S键是否被按下 if (keys['D']) // D键是否被按下 if (keys['A']) // A键是否被按下 if (keys['1'] && (key!=1) && !morph) // 如果1被按下,则变形到模型1 |
-- 作者:一分之千 -- 发布时间:10/22/2007 7:20:00 PM -- Lesson 25 Welcome to yet another exciting tutorial! This time we will focus on the effect rather than the graphics, although the final result is pretty cool looking! In this tutorial you will learn how to morph seamlessly from one object to another. Similar to the effect I use in the dolphin demo. Although there are a few catches. First thing to note is that each object must have the same amount of points. Very rare to luck out and get 3 object made up of exactly the same amount of vertices, but it just so happens, in this tutorial we have 3 objects with exactly the same amount of points :) Don't get me wrong, you can use objects with different values, but the transition from one object to another is odd looking and not as smooth. You will also learn how to read object data from a file. Similar to the format used in lesson 10, although it shouldn't be hard to modify the code to read .ASC files or some other text type data files. In general, it's a really cool effect, a really cool tutorial, so lets begin! We start off as usual. Including all the required header files, along with the math and standard input / output headers. Notice we don't include glaux. That's because we'll be drawing points rather than textures in this tutorial. After you've got the tutorial figured out, you can try playing with Polygons, Lines, and Textures! #include <windows.h> // Header File For Windows HDC hDC=NULL; // Device Context Handle bool keys[256]; // Key Array The variable key is a variable that I have included to make sure the user doesn't try to morph from the first shape back into the first shape. This would be pretty pointless and would cause a delay while the points were trying to morph to the position they're already in. step is a counter variable that counts through all the steps specified by steps. If you increase the value of steps it will take longer for the object to morph, but the movement of the points as they morph will be smoother. Once step is equal to steps we know the morphing has been completed. The last variable morph lets our program know if it should be morphing the points or leaving them where they are. If it's TRUE, the object is in the process of morphing from one shape to another. GLfloat xrot,yrot,zrot, // X, Y & Z Rotation int key=1; // Used To Make Sure Same Morph Key Is Not Pressed typedef struct // Structure For 3D Points The variable points will reference a single VERTEX (x, y and z values). This allows us to grab the x, y or z value of any point using points[{point we want to access}].{x, y or z}. The name of this structure is... you guessed it... OBJECT! typedef struct // Structure For An Object The variable maxver will be used to keep track of the maximum number of variables used in any of the objects. If one object only had 5 points, another had 20, and the last object had 15, the value of maxver would be equal to the greatest number of points used. So maxver would be equal to 20. After we define maxver we can define the objects. morph1, morph2, morph3, morph4 & helper are all defined as an OBJECT. *sour & *dest are defined as OBJECT* (pointer to an object). The object is made up of verticies (VERTEX). The first 4 morph{num} objects will hold the 4 objects we want to morph to and from. helper will be used to keep track of changes as the object is morphed. *sour will point to the source object and *dest will point to the object we want to morph to (destination object). int maxver; // Will Eventually Hold The Maximum Number Of Vertices LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration The line inside the { }'s allocates the memory for object k's points. A point is an entire VERTEX (3 floats). The memory allocated is the size of VERTEX (3 floats) multiplied by the number of points (n). So if there were 10 points (n=10) we would be allocating room for 30 floating point values (3 floats * 10 points). void objallocate(OBJECT *k,int n) // Allocate Memory For Each Object void objfree(OBJECT *k) // Frees The Object (Releasing The Memory) We start off be creating a do / while loop. fgets() will read up to 255 characters from our file f and store the characters at *string. If the line read is blank (carriage return \n), the loop will start over, attempting to find a line with text. The while() statement checks for blank lines and if found starts over again. After the string has been read in we return. void readstr(FILE *f,char *string) // Reads A String From File (f) We start off with an integer variable called ver. ver will hold the number of vertices used to build the object. The variables rx, ry & rz will hold the x, y & z values of each vertex. The variable filein is the pointer to our file structure, and oneline[ ] will be used to hold 255 characters of text. We open the file name for read in text translated mode (meaning CTRL-Z represents the end of a line). Then we read in a line of text using readstr(filein,oneline). The line of text will be stored in oneline. After we have read in the text, we scan the line of text (oneline) for the phrase "Vertices: {some number}{carriage return}. If the text is found, the number is stored in the variable ver. This number is the number of vertices used to create the object. If you look at the object text files, you'll see that the first line of text is: Vertices: {some number}. After we know how many vertices are used we store the results in the objects verts variable. Each object could have a different value if each object had a different number of vertices. The last thing we do in this section of code is allocate memory for the object. We do this by calling objallocate({object name},{number of verts}). void objload(char *name,OBJECT *k) // Loads Object From File (name) filein = fopen(name, "rt"); // Opens The File For Reading Text In Translated Mode Next we read in a line of text. This will be the first line of valid text underneath the "Vertices: {some number}" line. What we should end up reading is a line with floating point values for x, y & z. The line is analyzed with sscanf() and the three floating point values are extracted and stored in rx, ry and rz. for (int i=0;i<ver;i++) // Loops Through The Vertices The line k->points[i].x=rx can be broken down like this: rx is the value on the x axis for one of the points. So if i is equal to 0, what we are saying is: The x axis of point 1 (point[0].x) in our object (k) equals the x axis value we just read from the file (rx). The other two lines set the y & z axis values for each point in our object. We loop through all the vertices. If there are not enough vertices, an error might occur, so make sure the text at the beginning of the file "Vertices: {some number}" is actually the number of vertices in the file. Meaning if the top line of the file says "Vertices: 10", there had better be 10 Verticies (x, y and z values)! After reading in all of the verticies we close the file, and check to see if the variable ver is greater than the variable maxver. If ver is greater than maxver, we set maxver to equal ver. That way if we read in one object and it has 20 verticies, maxver will become 20. If we read in another object, and it has 40 verticies, maxver will become 40. That way we know how many vertices our largest object has. k->points[i].x = rx; // Sets Objects (k) points.x Value To rx if(ver>maxver) maxver=ver; // If ver Is Greater Than maxver Set maxver Equal To ver What the code below does is calculates a new position for each point when morphing is enabled. The number of the point to calculate is stored in i. The results will be returned in the VERTEX calculate. The first variable we create is a VERTEX called a. This will give a an x, y and z value. Lets look at the first line. The x value of the VERTEX a equals the x value of point[i] (point[i].x) in our SOURCE object minus the x value of point[i] (point[i].x) in our DESTINATION object divided by steps. So lets plug in some numbers. Lets say our source objects first x value is 40 and our destination objects first x value is 20. We already know that steps is equal to 200! So that means that a.x=(40-20)/200... a.x=(20)/200... a.x=0.1. What this means is that in order to move from 40 to 20 in 200 steps, we need to move by 0.1 units each calculation. To prove this calculation, multiply 0.1 by 200, and you get 20. 40-20=20 :) We do the same thing to calculate how many units to move on both the y axis and the z axis for each point. If you increase the value of steps the movements will be even more fine (smooth), but it will take longer to morph from one position to another. VERTEX calculate(int i) // Calculates Movement Of Points During Morphing GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // Resize And Initialize The GL Window int InitGL(GLvoid) // All Setup For OpenGL Goes Here Next well load in 3 objects. The first object is a sphere. The data for the sphere is stored in the file sphere.txt. The data will be loaded into the object named morph1. We also load a torus, and a tube into objects morph2 and morph3. maxver=0; // Sets Max Vertices To 0 By Default After allocating the space, we create a loop that assigns a random x, y and z value to each point. The random value will be a floating point value from +7 to -7. (14000/1000=14... minus 7 gives us a max value of +7... if the random number is 0, we have a minimum value of 0-7 or -7). objallocate(&morph4,486); // Manually Reserver Ram For A 4th 468 Vertice Object (morph4) After all of the objects are loaded, we set the source and destination objects (sour and dest) to equal morph1, which is the sphere. This way everything starts out as a sphere. objload("data/sphere.txt",&helper); // Load sphere.txt Object Into Helper (Used As Starting Point) return TRUE; // Initialization Went OK We start off normal. Clear the screen, depth buffer and reset the modelview matrix. Then we position the object on the screen using the values stored in cx, cy and cz. Rotations are done using xrot, yrot and zrot. The rotation angle is increased based on xpseed, yspeed and zspeed. Finally 3 temporary variables are created tx, ty and tz, along with a new VERTEX called q. void DrawGLScene(GLvoid) // Here's Where We Do All The Drawing xrot+=xspeed; yrot+=yspeed; zrot+=zspeed; // Increase xrot,yrot & zrot by xspeed, yspeed & zspeed GLfloat tx,ty,tz; // Temp X, Y & Z Variables We create a loop to loop through all the vertices. You could use maxver, but because every object has the same number of vertices we'll use morph1.verts. Inside the loop we check to see if morph is TRUE. If it is we calculate the movement for the current point (i). q.x, q.y and q.z will hold the results. If morph is false, q.x, q.y and q.z will be set to 0 (preventing movement). the points in the helper object are moved based on the results of we got from calculate(i). (remember earlier that we calculated a point would have to move 0.1 unit to make it from 40 to 20 in 200 steps). We adjust the each points value on the x, y and z axis by subtracting the number of units to move from helper. The new helper point is stored in tx, ty and tz. (t{x/y/z}=helper.points[i].{x/y/z}). glBegin(GL_POINTS); // Begin Drawing Points We then darken the color a little, and move 2 steps in the direction we just calculated instead of one. This moves the point to the newly calculated position, and then moves it again in the same direction. So if it was travelling left at 0.1 units, the next dot would be at 0.2 units. After calculating 2 positions ahead we draw the second point. Finally we set the color to dark blue, and calculate even further ahead. This time using our example we would move 0.4 units to the left instead of 0.1 or 0.2. The end result is a little tail of particles following as the dots move. With blending, this creates a pretty cool effect! glEnd() tells OpenGL we are done drawing points. glColor3f(0,1,1); // Set Color To A Bright Shade Of Off Blue If morph is false or step is greater than or equal to steps (200), morph is set to FALSE, the sour (source) object is set to equal the dest (destination) object, and step is set back to 0. This tells the program that morphing is not happening or it has just finished. // If We're Morphing And We Haven't Gone Through All 200 Steps Increase Our Step Counter GLvoid KillGLWindow(GLvoid) // Properly Kill The Window if (fullscreen) // Are We In Fullscreen Mode? if (hRC) // Do We Have A Rendering Context? if (!wglDeleteContext(hRC)) // Are We Able To Delete The RC? if (hDC && !ReleaseDC(hWnd,hDC)) // Are We Able To Release The DC if (hWnd && !DestroyWindow(hWnd)) // Are We Able To Destroy The Window? if (!UnregisterClass("OpenGL",hInstance)) // Are We Able To Unregister Class BOOL CreateGLWindow() // Creates The GL Window LRESULT CALLBACK WndProc() // Handle For This Window 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 page down is pressed we decrease zspeed. This causes the object to spin faster on the z axis in a negative direction. If the down arrow is pressed we increase xspeed. This causes the object to spin faster on the x axis in a positive direction. If the up arrow is pressed we decrease xspeed. This causes the object to spin faster on the x axis in a negative direction. If the right arrow is pressed we increase yspeed. This causes the object to spin faster on the y axis in a positive direction. If the left arrow is pressed we decrease yspeed. This causes the object to spin faster on the y axis in a negative direction. if(keys[VK_PRIOR]) // Is Page Up Being Pressed? if(keys[VK_NEXT]) // Is Page Down Being Pressed? if(keys[VK_DOWN]) // Is Page Up Being Pressed? if(keys[VK_UP]) // Is Page Up Being Pressed? if(keys[VK_RIGHT]) // Is Page Up Being Pressed? if(keys[VK_LEFT]) // Is Page Up Being Pressed? if (keys['Q']) // Is Q Key Being Pressed? if (keys['Z']) // Is Z Key Being Pressed? if (keys['W']) // Is W Key Being Pressed? if (keys['S']) // Is S Key Being Pressed? if (keys['D']) // Is D Key Being Pressed? if (keys['A']) // Is A Key Being Pressed? Pressing keys 2, 3, and 4 does the same thing. If 2 is pressed we set dest to morph2, and we set key to equal 2. Pressing 3, sets dest to morph3 and key to 3. By setting key to the value of the key we just pressed on the keyboard, we prevent the user from trying to morph from a sphere to a sphere or a cone to a cone! if (keys['1'] && (key!=1) && !morph) // Is 1 Pressed, key Not Equal To 1 And Morph False? if (keys[VK_F1]) // Is F1 Being Pressed? // Shutdown Piotr's code is new and refreshing. I hope that after reading through this tutorial you have a better understanding on how to store and load object data from a file, and how to manipulate the data to create cool GL effects in your own programs! The .html for this tutorial took 3 days to write. If you notice any mistakes please let me know. Alot of it was written late at night, meaning a few mistakes may have crept in. I want these tutorials to be the best they can be. Feedback is appreciated! RabidHaMsTeR released a demo called "Morph" before this tutorial was written that shows off a more advanced version of this effect. You can check it out yourself at http://homepage.ntlworld.com/fj.williams/PgSoftware.html. Piotr Cieslak Jeff Molofee (NeHe) |
-- 作者:一分之千 -- 发布时间:10/22/2007 7:34:00 PM -- 第二十六课 在这一课中你将学会如何创建镜面显示效果,它使用剪裁平面,蒙板缓存等OpenGL中一些高级的技巧。 static GLfloat LightAmb[] = {0.7f, 0.7f, 0.7f, 1.0f}; // 环境光 GLUquadricObj *q; // 使用二次几何体创建球 GLfloat xrot = 0.0f; // X方向的旋转角度 GLuint texture[3]; // 使用三个纹理 GLvoid ReSizeGLScene(GLsizei width, GLsizei height) AUX_RGBImageRec *LoadBMP(char *Filename) int LoadGLTextures() // 载入*.bmp文件,并转化为纹理 int InitGL(GLvoid) // 初始化OpenGL glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb); q = gluNewQuadric(); // 创建一个二次几何体 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); // 设置球纹理映射 return TRUE; // 初始化完成,成功返回 void DrawObject() // 绘制我们的球 glBindTexture(GL_TEXTURE_2D, texture[2]); // 设置为环境纹理 gluSphere(q, 0.35f, 32, 16); // 绘制球体,并混合 glDisable(GL_TEXTURE_GEN_S); // 让OpenGL回到默认的属性 void DrawFloor() glTexCoord2f(0.0f, 0.0f); // 左上 glTexCoord2f(1.0f, 0.0f); // 右上 glTexCoord2f(1.0f, 1.0f); // 右下 int DrawGLScene(GLvoid) // 设置剪切平面 glLoadIdentity(); glColorMask(0,0,0,0); 下面我们来解释蒙板测试函数的含义: 常量 含义 接下来我们解释glStencilOp函数,它用来根据比较结果修改蒙板缓存区中的值,它的函数原形为: zfail zpass 具体的操作包括以下几种 当完成了以上操作后我们绘制一个地面,当然现在你什么也看不到,它只是把覆盖地面的蒙板缓存区中的相应位置设为1。 glEnable(GL_STENCIL_TEST); // 启用蒙板缓存 glEnable(GL_DEPTH_TEST); //启用深度测试 glEnable(GL_CLIP_PLANE0); // 使用剪切平面 glLightfv(GL_LIGHT0, GL_POSITION, LightPos); // 设置灯光0 glLightfv(GL_LIGHT0, GL_POSITION, LightPos); glEnable(GL_LIGHTING); // 使用光照 xrot += xrotspeed; // 更新X轴旋转速度 void ProcessKeyboard() if (keys['A']) zoom +=0.05f; if (keys[VK_PRIOR]) height +=0.03f; GLvoid KillGLWindow(GLvoid) static PIXELFORMATDESCRIPTOR pfd= 1, // 使用蒙板缓存 ProcessKeyboard(); // 处理按键相应 |
-- 作者:一分之千 -- 发布时间:10/22/2007 7:39:00 PM -- Lesson 26 Welcome to another exciting tutorial. The code for this tutorial was written by Banu Cosmin. The tutorial was of course written by myself (NeHe). In this tutorial you will learn how to create EXTREMELY realistic reflections. Nothing fake here! The objects being reflected will not show up underneath the floor or on the other side of a wall. True reflections! A very important thing to note about this tutorial: Because the Voodoo 1, 2 and some other cards do not support the stencil buffer, this demo will NOT run on those cards. It will ONLY run on cards that support the stencil buffer. If you're not sure if your card supports the stencil buffer, download the code, and try running the demo. Also, this demo requires a fairly decent processor and graphics card. Even on my GeForce I notice there is a little slow down at times. This demo runs best in 32 bit color mode! As video cards get better, and processors get faster, I can see the stencil buffer becoming more popular. If you have the hardware and you're ready to reflect, read on! The first part of the code is fairly standard. We include all necessary header files, and set up our Device Context, Rendering Context, etc. #include <windows.h> // Header File For Windows HDC hDC=NULL; // Private GDI Device Context bool keys[256]; // Array Used For The Keyboard Routine // Light Parameters We then make room for our 3 textures with texture[3], and define WndProc(). GLUquadricObj *q; // Quadratic For Drawing A Sphere GLfloat xrot = 0.0f; // X Rotation GLuint texture[3]; // 3 Textures LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // Resize And Initialize The GL Window AUX_RGBImageRec *LoadBMP(char *Filename) // Loads A Bitmap Image int LoadGLTextures() // Load Bitmaps And Convert To Textures int InitGL(GLvoid) // All Setup For OpenGL Goes Here After we have set the light up we can enable it with glEnable(GL_LIGHT0). Even though the light is enabled, you will not see it until we enable lighting with the last line of code. Note: If we wanted to turn off all lights in a scene we would use glDisable(GL_LIGHTING). If we wanted to disable just one of our lights we would use glDisable(GL_LIGHT{0-7}). This gives us alot of control over the lighting and what lights are on and off. Just remember if GL_LIGHTING is disabled, you will not see lights! glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb); // Set The Ambient Lighting For Light0 glEnable(GL_LIGHT0); // Enable Light 0 The fourth and fifth lines tell OpenGL to use the Sphere Mapping algorithm to generate the texture coordinates. This allows us to sphere map the quadratic object. q = gluNewQuadric(); // Create A New Quadratic glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); // Set Up Sphere Mapping return TRUE; // Initialization Went OK We set the color to full intensity white and bind to our BALL texture (the ball texture is a series of red, white and blue stripes). After selecting our texture, we draw a Quadratic Sphere with a radius of 0.35f, 32 slices and 16 stacks (up and down). void DrawObject() // Draw Our Ball After doing all that, we redraw the sphere, disable sphere mapping and disable blending. The final result is a reflection that almost looks like bright points of light mapped to the beach ball. Because we enable sphere mapping, the texture is always facing the viewer, even as the ball spins. We blend so that the new texture doesn't cancel out the old texture (a form of multitexturing). glBindTexture(GL_TEXTURE_2D, texture[2]); // Select Texture 3 (2) gluSphere(q, 0.35f, 32, 16); // Draw Another Sphere Using New Texture void DrawFloor() // Draws The Floor glTexCoord2f(0.0f, 0.0f); // Top Left Of Texture glTexCoord2f(1.0f, 0.0f); // Top Right Of Texture glTexCoord2f(1.0f, 1.0f); // Bottom Right Of Texture We start off by clearing the screen (GL_COLOR_BUFFER_BIT) to our default clear color (off blue). The depth (GL_DEPTH_BUFFER_BIT) and stencil (GL_STENCIL_BUFFER_BIT) buffers are also cleared. Make sure you include the stencil buffer code, it's new and easy to overlook! It's important to note when we clear the stencil buffer, we are filling it with 0's. After clearing the screen and buffers, we define our clipping plane equation. The plane equation is used for clipping the reflected image. The equation eqr[]={0.0f,-1.0f, 0.0f, 0.0f} will be used when we draw the reflected image. As you can see, the value for the y-plane is a negative value. Meaning we will only see pixels if they are drawn below the floor or at a negative value on the y-axis. Anything drawn above the floor will not show up when using this equation. More on clipping later... read on. int DrawGLScene(GLvoid) // Draw Everything // Clip Plane Equations We start off by resetting the modelview matrix. Which of course starts all drawing in the center of the screen. We then translate down 0.6f units (to add a small perspective tilt to the floor) and into the screen based on the value of zoom. To better explain why we translate down 0.6f units, I'll explain using a simple example. If you were looking at the side of a piece of paper at exactly eye level, you would barely be able to see it. It would more than likely look like a thin line. If you moved the paper down a little, it would no longer look like a line. You would see more of the paper, because your eyes would be looking down at the page instead of directly at the edge of the paper. glLoadIdentity(); // Reset The Modelview Matrix If the red value of glColorMask({red},{green},{blue},{alpha}) was set to GL_TRUE, and all of the other values were 0 (GL_FALSE), the only color that would show up on the screen is red. If the value for red was 0 (GL_FALSE), but the other values were all GL_TRUE, every color except red would be drawn to the screen. We don't want anything drawn to the screen at the moment, with all of the values set to 0 (GL_FALSE), colors will not be drawn to the screen. glColorMask(0,0,0,0); // Set Color Mask We start off by enabling stencil testing. Once stencil testing has been enabled, we are able to modify the stencil buffer. It's very hard to explain the commands below so please bear with me, and if you have a better explanation, please let me know. In the code below we set up a test. The line glStencilFunc(GL_ALWAYS, 1, 1) tells OpenGL what type of test we want to do on each pixel when an object is drawn to the screen. GL_ALWAYS just tells OpenGL the test will always pass. The second parameter (1) is a reference value that we will test in the third line of code, and the third parameter is a mask. The mask is a value that is ANDed with the reference value and stored in the stencil buffer when the test is done. A reference value of 1 ANDed with a mask value of 1 is 1. So if the test goes well and we tell OpenGL to, it will place a one in the stencil buffer (reference&mask=1). Quick note: Stencil testing is a per pixel test done each time an object is drawn to the screen. The reference value ANDed with the mask value is tested against the current stencil value ANDed with the mask value. The third line of code tests for three different conditions based on the stencil function we decided to use. The first two parameters are GL_KEEP, and the third is GL_REPLACE. The first parameter tells OpenGL what to do if the test fails. Because the first parameter is GL_KEEP, if the test fails (which it can't because we have the funtion set to GL_ALWAYS), we would leave the stencil value set at whatever it currently is. The second parameter tells OpenGL what do do if the stencil test passes, but the depth test fails. In the code below, we eventually disable depth testing so this parameter can be ignored. The third parameter is the important one. It tells OpenGL what to do if the test passes! In our code we tell OpenGL to replace (GL_REPLACE) the value in the stencil buffer. The value we put into the stencil buffer is our reference value ANDed with our mask value which is 1. After setting up the type of testing we want to do, we disable depth testing and jump to the code that draws our floor. In simple english I will try to sum up everything that the code does up until now... We tell OpenGL not to draw any colors to the screen. This means that when we draw the floor, it wont show up on the screen. BUT... each spot on the screen where the object (our floor) should be if we could see it will be tested based on the type of stencil testing we decide to do. The stencil buffer starts out full of 0's (empty). We want to set the stencil value to 1 wherever our object would have been drawn if we could see it. So we tell OpenGL we don't care about testing. If a pixel should have been drawn to the screen, we want that spot marked with a 1. GL_ALWAYS does exactly that. Our reference and mask values of 1 make sure that the value placed into the stencil buffer is indeed going to be 1! As we invisibly draw, our stencil operation checks each pixel location, and replaces the 0 with a 1. glEnable(GL_STENCIL_TEST); // Enable Stencil Buffer For "marking" The Floor So now that we know the ball reflection will only be drawn where the floor should be, it's time to draw the reflection! We enable depth testing, and set the color mask back to all ones (meaning all the colors will be drawn to the screen). Instead of using GL_ALWAYS for our stencil function we are going to use GL_EQUAL. We'll leave the reference and mask values at 1. For the stencil operation we will set all the parameters to GL_KEEP. In english, any object we draw this time around will actually appear on the screen (because the color mask is set to true for each color). As long as stencil testing is enabled pixels will ONLY be drawn if the stencil buffer has a value of 1 (reference value ANDed with the mask, which is 1 EQUALS (GL_EQUAL) the stencil buffer value ANDed with the mask, which is also 1). If the stencil value is not 1 where the current pixel is being drawn it will not show up! GL_KEEP just tells OpenGL not to modify any values in the stencil buffer if the test passes OR fails! glEnable(GL_DEPTH_TEST); // Enable Depth Testing After we enable clipping plane0 (usually you can have from 0-5 clipping planes), we define the plane by telling it to use the parameters stored in eqr. We push the matrix (which basically saves the position of everything on the screen) and use glScalef(1.0f,-1.0f,1.0f) to flip the object upside down (creating a real looking reflection). Setting the y value of glScalef({x},{y},{z}) to a negative value forces OpenGL to render opposite on the y-axis. It's almost like flipping the entire screen upside down. When position an object at a positive value on the y-axis, it will appear at the bottom of the screen instead of at the top. When you rotate an object towards yourself, it will rotate away from you. Everything will be mirrored on the y-axis until you pop the matrix or set the y value back to 1.0f instead of -1.0f using glScalef({x},{y},{z}). glEnable(GL_CLIP_PLANE0); // Enable Clip Plane For Removing Artifacts We then move up or down on the y-axis to the value specified by height. Translations are mirrored, so if the value of height is 5.0f, the position we translate to will be mirrored (-5.0f). Positioning the reflected image under the floor, instead of above the floor! After position our reflected ball, we rotate the ball on both the x axis and y axis, based on the values of xrot and yrot. Keep in mind that any rotations on the x axis will also be mirrored. So if the real ball (ball above the floor) is rolling towards you on the x-axis, it will be rolling away from you in the reflection. After positioning the reflected ball and doing our rotations we draw the ball by calling DrawObject(), and pop the matrix (restoring things to how they were before we drew the ball). Popping the matrix all cancels mirroring on the y-axis. We then disable our clipping plane (plane0) so that we are not stuck drawing only to the bottom half of the screen, and last, we disable stencil testing so that we can draw to other spots on the screen other than where the floor should be. Note that we draw the reflected ball before we draw the floor. I'll explain why later on. glLightfv(GL_LIGHT0, GL_POSITION, LightPos); // Set Up Light0 We enable blending, disable lighting, and set the alpha value to 80% using the command glColor4f(1.0f,1.0f,1.0f,0.8f). The blending mode is set up using glBlendFunc(), and the semi transparent floor is drawn over top of the reflected ball. If we drew the floor first and then the reflected ball, the effect wouldn't look very good. By drawing the ball and then the floor, you can see a small amount of coloring from the floor mixed into the coloring of the ball. If I was looking into a BLUE mirror, I would expect the reflection to look a little blue. By rendering the ball first, the reflected image looks like it's tinted the color of the floor. glLightfv(GL_LIGHT0, GL_POSITION, LightPos); // Set Up Light0 Position We don't need blending anymore so we disable blending. If we didn't disable blending, the colors from the floor would mix with the colors of our 'real' ball when it was floating over top of the floor. We don't want the 'real' ball to look like the reflection so we disable blending. We are not going to clip the actual ball. If the real ball goes through the floor, we should see it come out the bottom. If we were using clipping the ball wouldn't show up after it went through the floor. If you didn't want to see the ball come through the floor, you would set up a clipping equation that set the Y value to +1.0f, then when the ball went through the floor, you wouldn't see it (you would only see the ball when it was drawn on at a positive value on the y-axis. For this demo, there's no reason we shouldn't see it come through the floor. We then translate up or down on the y-axis to the position specified by height. Only this time the y-axis is not mirrored, so the ball travels the opposite direction that the reflected image travels. If we move the 'real' ball down the reflected ball will move up. If we move the 'real' ball up, the reflected ball will move down. We rotate the 'real' ball, and again, because the y-axis is not mirrored, the ball will spin the opposite direction of the reflected ball. If the reflected ball is rolling towards you the 'real' ball will be rolling away from you. This creates the illusion of a real reflection. After positioning and rotating the ball, we draw the 'real' ball by calling DrawObject(). glEnable(GL_LIGHTING); // Enable Lighting Before we return TRUE, we do a glFlush(). This tells OpenGL to render everything left in the GL pipeline before continuing, and can help prevent flickering on slower video cards. xrot += xrotspeed; // Update X Rotation Angle By xrotspeed The next 2 lines check to see if you are pressing the 'A' or 'Z' keys. Pressing 'A' will zoom you in closer to the ball and pressing 'Z' will zoom you away from the ball. Pressing 'PAGE UP' will increase the value of height moving the ball up, and pressing 'PAGE DOWN' will decrease the value of height moving the ball down (closer to the floor). void ProcessKeyboard() // Process Keyboard Results if (keys['A']) zoom +=0.05f; // 'A' Key Pressed ... Zoom In if (keys[VK_PRIOR]) height +=0.03f; // Page Up Key Pressed Move Ball Up GLvoid KillGLWindow(GLvoid) // Properly Kill The Window BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag) fullscreen=fullscreenflag; // Set The Global Fullscreen Flag hInstance = GetModuleHandle(NULL); // Grab An Instance For Our Window if (!RegisterClass(&wc)) // Attempt To Register The Window Class if (fullscreen) // Attempt Fullscreen Mode? // Try To Set Selected Mode And Get Results. NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar if (fullscreen) // Are We Still In Fullscreen Mode? // Create The Window static PIXELFORMATDESCRIPTOR pfd= // pfd Tells Windows How We Want Things To Be 1, // Use Stencil Buffer ( * Important * ) if (!(hDC=GetDC(hWnd))) // Did We Get A Device Context? if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) // Did Windows Find A Matching Pixel Format? if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // Are We Able To Set The Pixel Format? if (!(hRC=wglCreateContext(hDC))) // Are We Able To Get A Rendering Context? if(!wglMakeCurrent(hDC,hRC)) // Try To Activate The Rendering Context ShowWindow(hWnd,SW_SHOW); // Show The Window if (!InitGL()) // Initialize Our Newly Created GL Window return TRUE; // Success LRESULT CALLBACK WndProc( HWND hWnd, // Handle For This Window 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 ProcessKeyboard(); // Processed Keyboard Presses // Shutdown Banu Cosmin (Choko) Jeff Molofee (NeHe) |
-- 作者:lamb0145 -- 发布时间:4/10/2010 10:42:00 AM -- 好东西,谢谢分享。。。 |
-- 作者:tingweide -- 发布时间:7/13/2011 10:39:00 AM -- 太感谢楼主了,那三个txt文档真的好强大 |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
359.863ms |