以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- [推荐]NeHe OpenGL教程(中英文版附带VC++源码)Lesson 19-lesson 20 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=54072) |
-- 作者:一分之千 -- 发布时间:10/19/2007 11:55:00 AM -- Lesson 19 Welcome to Tutorial 19. You've learned alot, and now you want to play. I will introduce one new command in this tutorial... The triangle strip. It's very easy to use, and can help speed up your programs when drawing alot of triangles. In this tutorial I will teach you how to make a semi-complex Particle Engine. Once you understand how particle engines work, creating effects such as fire, smoke, water fountains and more will be a piece of cake! I have to warn you however! Until today I had never written a particle engine. I had this idea that the 'famous' particle engine was a very complex piece of code. I've made attempts in the past, but usually gave up after I realized I couldn't control all the points without going crazy. You might not believe me when I tell you this, but this tutorial was written 100% from scratch. I borrowed no ones ideas, and I had no technical information sitting in front of me. I started thinking about particles, and all of a sudden my head filled with ideas (brain turning on?). Instead of thinking about each particle as a pixel that had to go from point 'A' to point 'B', and do this or that, I decided it would be better to think of each particle as an individual object responding to the environment around it. I gave each particle life, random aging, color, speed, gravitational influence and more. Soon I had a finished project. I looked up at the clock and realized aliens had come to get me once again. Another 4 hours gone! I remember stopping now and then to drink coffee and blink, but 4 hours... ? So, although this program in my opinion looks great, and works exactly like I wanted it to, it may not be the proper way to make a particle engine. I don't care personally, as long as it works well, and I can use it in my projects! If you are the type of person that needs to know you're conforming, then spend hours browsing the net looking for information. Just be warned. The few code snippits you do find may appear cryptic :) This tutorial uses the base code from lesson 1. There is alot of new code however, so I'll rewrite any section of code that contains changes (makes it easier to understand). Using the code from lesson 1, we'll add 5 new lines of code at the top of our program. The first line (stdio.h) allows us to read data from files. It's the same line we've added to previous tutorials the use texture mapping. The second line defines how many particles were going to create and display on the screen. Define just tells our program that MAX_PARTICLES will equal whatever value we specify. In this case 1000. The third line will be used to toggle 'rainbow mode' off and on. We'll set it to on by default. sp and rp are variables we'll use to prevent the spacebar or return key from rapidly repeating when held down. #include <windows.h> // Header File For Windows #define MAX_PARTICLES 1000 // Number Of Particles To Create ( NEW ) HDC hDC=NULL; // Private GDI Device Context bool keys[256]; // Array Used For The Keyboard Routine The variables xspeed and yspeed allow us to control the direction of the tail. xspeed will be added to the current speed a particle is travelling on the x axis. If xspeed is a positive value our particle will be travelling more to the right. If xspeed is a negative value, our particle will travel more to the left. The higher the value, the more it travels in that direction. yspeed works the same way, but on the y axis. The reason I say 'MORE' in a specific direction is because other factors affect the direction our particle travels. xspeed and yspeed help to move the particle in the direction we want. Finally we have the variable zoom. We use this variable to pan into and out of our scene. With particle engines, it's nice to see more of the screen at times, and cool to zoom in real close other times. float slowdown=2.0f; // Slow Down Particles Finally, we set aside storage space for one texture (the particle texture). I decided to use a texture rather than OpenGL points for a few reasons. The most important reason is because points are not all that fast, and they look pretty blah. Secondly, textures are way more cool :) You can use a square particle, a tiny picture of your face, a picture of a star, etc. More control! GLuint loop; // Misc Loop Variable We start off with the boolean variable active. If this variable is TRUE, our particle is alive and kicking. If it's FALSE our particle is dead or we've turned it off! In this program I don't use active, but it's handy to include. The variables life and fade control how long the particle is displayed, and how bright the particle is while it's alive. The variable life is gradually decreased by the value stored in fade. In this program that will cause some particles to burn longer than others. typedef struct // Create A Structure For Particle float r; // Red Value float x; // X Position float xi; // X Direction float xg; // X Gravity } particles particle[MAX_PARTICLES]; // Particle Array (Room For Particle Info) static GLfloat colors[12][3]= // Rainbow Of Colors LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc AUX_RGBImageRec *LoadBMP(char *Filename) // Loads A Bitmap Image File=fopen(Filename,"r"); // Check To See If The File Exists int LoadGLTextures() // Load Bitmaps And Convert To Textures AUX_RGBImageRec *TextureImage[1]; // Create Storage Space For The Texture memset(TextureImage,0,sizeof(void *)*1); // Set The Pointer To NULL if (TextureImage[0]=LoadBMP("Data/Particle.bmp")) // Load Particle Texture glBindTexture(GL_TEXTURE_2D, texture[0]); if (TextureImage[0]) // If Texture Exists GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // Resize And Initialize The GL Window glViewport(0, 0, width, height); // Reset The Current Viewport glMatrixMode(GL_PROJECTION); // Select The Projection Matrix // Calculate The Aspect Ratio Of The Window glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix int InitGL(GLvoid) // All Setup For OpenGL Goes Here glShadeModel(GL_SMOOTH); // Enables Smooth Shading After we've made the particle active, we give it life. I doubt the way I apply life, and fade the particles is the best way, but once again, it works good! Full life is 1.0f. This also gives the particle full brightness. for (loop=0;loop<MAX_PARTICLES;loop++) // Initials All The Textures particle[loop].fade=float(rand()%100)/1000.0f+0.003f; // Random Fade Speed Some quick examples: 900*(12/900)=12. 1000*(12/1000)=12, etc. particle[loop].r=colors[loop*(12/MAX_PARTICLES)][0]; // Select Red Rainbow Color We'll end up with either a positive or negative random value. This value will be used to move the particle in a random direction at a random speed. particle[loop].xi=float((rand()%50)-26.0f)*10.0f; // Random Speed On X Axis particle[loop].xg=0.0f; // Set Horizontal Pull To Zero We reset the Modelview Matrix only once. We'll position the particles using the glVertex3f() command instead of using tranlations, that way we don't alter the modelview matrix while drawing our particles. int DrawGLScene(GLvoid) // Where We Do All The Drawing for (loop=0;loop<MAX_PARTICLES;loop++) // Loop Through All The Particles if (particle[loop].active) // If The Particle Is Active float x=particle[loop].x; // Grab Our Particle X Position // Draw The Particle Using Our RGB Values, Fade The Particle Based On It's Life glBegin(GL_TRIANGLE_STRIP); // Build Quad From A Triangle Strip So the first triangle is drawn using vertices 0, 1 and 2. If you look at the picture you'll see that vertex points 0, 1 and 2 do indeed make up the first triangle (top right, top left, bottom right). The second triangle is drawn using vertices 2, 1 and 3. Again, if you look at the picture, vertices 2, 1 and 3 create the second triangle (bottom right, top left, bottom left). Notice that both triangles are drawn with the same winding (counter-clockwise orientation). I've seen quite a few web sites that claim every second triangle is wound the opposite direction. This is not the case. OpenGL will rearrange the vertices to ensure that all of the triangles are wound the same way! There are two good reasons to use triangle strips. First, after specifying the first three vertices for the initial triangle, you only need to specify a single point for each additional triangle. That point will be combined with 2 previous vertices to create a triangle. Secondly, by cutting back the amount of data needed to create a triangle your program will run quicker, and the amount of code or data required to draw an object is greatly reduced. Note: The number of triangles you see on the screen will be the number of vertices you specify minus 2. In the code below we have 4 vertices and we see two triangles. glTexCoord2d(1,1); glVertex3f(x+0.5f,y+0.5f,z); // Top Right glEnd(); // Done Building Triangle Strip That's also why multiplying the start values by 10.0f made the pixels move alot faster, creating an explosion. We use the same formula for the y and z axis to move the particle around on the screen. particle[loop].x+=particle[loop].xi/(slowdown*1000); // Move On The X Axis By X Speed Lets say our moving speed was 10 and our resistance was 1. Each time our particle was drawn resistance would act on it. So the second time it was drawn, resistance would act, and our moving speed would drop from 10 to 9. This causes the particle to slow down a bit. The third time the particle is drawn, resistance would act again, and our moving speed would drop to 8. If the particle burns for more than 10 redraws, it will eventually end up moving the opposite direction because the moving speed would become a negative value. The resistance is applied to the y and z moving speed the same way it's applied to the x moving speed. particle[loop].xi+=particle[loop].xg; // Take Pull On X Axis Into Account particle[loop].life-=particle[loop].fade; // Reduce Particles Life By 'Fade' if (particle[loop].life<0.0f) // If Particle Is Burned Out particle[loop].life=1.0f; // Give It New Life particle[loop].x=0.0f; // Center On X Axis Also notice that I add xspeed to the x axis moving speed, and yspeed to the y axis moving speed. This gives us control over what direction the particles move later in the program. particle[loop].xi=xspeed+float((rand()%60)-32.0f); // X Axis Speed And Direction If you don't understand how I got the value of 1.0f for the red intensity if col is 0, I'll explain in a bit more detail. Look at the very top of the program. Find the line: static GLfloat colors[12][3]. Notice there are 12 groups of 3 number. The first of the three number is the red intensity. The second value is the green intensity and the third value is the blue intensity. [0], [1] and [2] below represent the 1st, 2nd and 3rd values I just mentioned. If col is equal to 0, we want to look at the first group. 11 is the last group (12th color). particle[loop].r=colors[col][0]; // Select Red From Color Table // If Number Pad 8 And Y Gravity Is Less Than 1.5 Increase Pull Upwards // If Number Pad 2 And Y Gravity Is Greater Than -1.5 Increase Pull Downwards // If Number Pad 6 And X Gravity Is Less Than 1.5 Increase Pull Right // If Number Pad 4 And X Gravity Is Greater Than -1.5 Increase Pull Left if (keys[VK_TAB]) // Tab Key Causes A Burst int WINAPI WinMain( HINSTANCE hInstance, // Instance // Ask The User Which Screen Mode They Prefer // Create Our OpenGL Window if (fullscreen) // Are We In Fullscreen Mode ( ADD ) while(!done) // Loop That Runs Until done=TRUE The line below checks to see if the + key on the number pad is being pressed. If it is and slowdown is greater than 1.0f we decrease slowdown by 0.01f. This causes the particles to move faster. Remember in the code above when I talked about slowdown and how it affects the speed at which the particles travel. if (keys[VK_ADD] && (slowdown>1.0f)) slowdown-=0.01f; // Speed Up Particles if (keys[VK_SUBTRACT] && (slowdown<4.0f)) slowdown+=0.01f; // Slow Down Particles if (keys[VK_PRIOR]) zoom+=0.1f; // Zoom In if (keys[VK_NEXT]) zoom-=0.1f; // Zoom Out if (keys[VK_RETURN] && !rp) // Return Key Pressed If the spacebar was pressed or rainbow is on and delay is greater than 25, the color will be changed! if ((keys[' '] && !sp) || (rainbow && (delay>25))) // Space Or Rainbow Mode if (keys[' ']) rainbow=false; // If Spacebar Is Pressed Disable Rainbow Mode sp=true; // Set Flag Telling Us Space Is Pressed if (col>11) col=0; // If Color Is To High Reset It if (!keys[' ']) sp=false; // If Spacebar Is Released Clear Flag For example. Say our particle had a moving speed of 5 on the x axis and 0 on the y axis. If we decreased xspeed until it was -10, we would be moving at a speed of -10 (xspeed) + 5 (original moving speed). So instead of moving at a rate of 10 to the right we'd be moving at a rate of -5 to the left. Make sense? Anyways. The line below checks to see if the up arrow is being pressed. If it is, yspeed will be increased. This will cause our particles to move upwards. The particles will move at a maximum speed of 200 upwards. Anything faster than that doesn't look to good. // If Up Arrow And Y Speed Is Less Than 200 Increase Upward Speed // If Down Arrow And Y Speed Is Greater Than -200 Increase Downward Speed // If Right Arrow And X Speed Is Less Than 200 Increase Speed To The Right // If Left Arrow And X Speed Is Greater Than -200 Increase Speed To The Left delay++; // Increase Rainbow Mode Color Cycling Delay Counter if (keys[VK_F1]) // Is F1 Being Pressed? Thanks to Richard Nutman for suggesting that the particles be positioned with glVertex3f() instead of resetting the Modelview Matrix and repositioning each particle with glTranslatef(). Both methods are effective, but his method will reduce the amount of work the computer has to do before it draws each particle, causing the program to run even faster. Thanks to Antoine Valentim for suggesting triangle strips to help speed up the program and to introduce a new command to this tutorial. The feedback on this tutorial has been great, I appreciate it! I hope you enjoyed this tutorial. If you had any problems understanding it, or you've found a mistake in the tutorial please let me know. I want to make the best tutorials available. Your feedback is important! Jeff Molofee (NeHe) |
-- 作者:一分之千 -- 发布时间:10/19/2007 11:59:00 AM -- 第二十课 到目前为止你已经学会如何使用alpha混合,把一个透明物体渲染到屏幕上了,但有的使用它看起来并不是那么的复合你的心意。使用蒙板技术,将会按照你蒙板的位置精确的绘制。 由于以上原因,我们需要使用“掩模”。使用“掩模”需要两个步骤,首先我们在场景上放置黑白相间的纹理,白色代表透明部分,黑色代表不透明部分。接着我们使用一种特殊的混合方式,只有在黑色部分上的纹理才会显示在场景中。 我只重写那些改变的地方,如果你做好了学习的准备,我们就上路吧。 bool masking=TRUE; // 是否使用“掩模” GLuint texture[5]; // 保存5个纹理标志 GLfloat roll; // 滚动纹理 int LoadGLTextures() if ((TextureImage[0]=LoadBMP("Data/logo.bmp")) && // 加载纹理0 for (loop=0; loop<5; loop++) // 循环加载5个纹理 int DrawGLScene(GLvoid) 向前面几课一样,我们假定四边形面对我们,并把纹理坐标(0,0)绑定到左下角,(1,0)绑定到右下角,(1,1)绑定到右上角。给定这些设置,你应该能猜到四边形中间对应的纹理坐标为(0.5,0.5),但你自己并没有设置此处的纹理坐标!OpenGL为你做了计算。 在这一课里,我们通过设置纹理坐标达到一种滚动纹理的目的。纹理坐标是被归一化的,它的范围从0.0-1.0,值0被映射到纹理的一边,值1被映射到纹理的另一边。超过1的值,纹理可以按照不同的方式被映射,这里我们设置为它将回绕道另一边并重复纹理。例如如果使用这样的映射方式,纹理坐标(0.3,0.5)和(1.3,0.5)被映射到同一个纹理坐标。在这一课里,我们将尝试一种无缝填充的效果。
我们使用roll变量去设置纹理坐标,当它为0时,它把纹理的左下角映射到四边形的左下角。当它大于0时,把纹理的左上角映射到四边形的左下角,看起来的效果就是纹理沿四边形向上滚动。 glBindTexture(GL_TEXTURE_2D, texture[0]); // 选择Logo纹理 glEnable(GL_BLEND); // 启用混合 if (masking) // 是否启用“掩模” glBlendFunc(GL_DST_COLOR,GL_ZERO); // 使用黑白“掩模”混合屏幕颜色 if (scene) glTranslatef(0.0f,0.0f,-1.0f); // 移入屏幕一个单位 if (masking) // “掩模”是否打开 glBindTexture(GL_TEXTURE_2D, texture[3]); // 选择第二个“掩模”纹理 如果我们没有使用“掩模”,我们的图像将与屏幕颜色混合。 glBlendFunc(GL_ONE, GL_ONE); // 把纹理2复制到屏幕 else if (masking) // “掩模”是否打开 glBindTexture(GL_TEXTURE_2D, texture[1]); // 选择第一个“掩模”纹理 如果我们没有使用“掩模”,我们的图像将与屏幕颜色混合。 glBlendFunc(GL_ONE, GL_ONE); // 把纹理1复制到屏幕 glEnable(GL_DEPTH_TEST); // 启用深度测试 roll+=0.002f; // 增加纹理滚动变量 return TRUE; // 成功返回 if (keys[' '] && !sp) // 空格键是否被按下? if (!keys[' ']) // 如果空格键释放,记录下来 if (keys['M'] && !mp) // M键是否被按下 if (!keys['M']) // 如果M键释放,记录下来 谢谢Rob Santa的想法和例子程序,我从没想到过这种方法。 我希望你喜欢这个教程,如果你在理解上有任何问题或找到了任何错误,请我知道,我想做最好的教程,你的反馈是非常重要的。 |
-- 作者:一分之千 -- 发布时间:10/19/2007 12:02:00 PM -- Lesson 20 Welcome to Tutorial 20. The bitmap image format is supported on just about every computer, and just about every operating system. Not only is it easy to work with, it's very easy to load and use as a texture. Up until now, we've been using blending to place text and other images onto the screen without erasing what's underneath the text or image. This is effective, but the results are not always pretty. Most the time a blended texture blends in too much or not enough. When making a game using sprites, you don't want the scene behind your character shining through the characters body. When writing text to the screen you want the text to be solid and easy to read. That's where masking comes in handy. Masking is a two step process. First we place a black and white image of our texture on top of the scene. The white represents the transparent part of our texture. The black represents the solid part of our texture. Because of the type of blending we use, only the black will appear on the scene. Almost like a cookie cutter effect. Then we switch blending modes, and map our texture on top of the black cut out. Again, because of the blending mode we use, the only parts of our texture that will be copied to the screen are the parts that land on top of the black mask. I'll rewrite the entire program in this tutorial aside from the sections that haven't changed. So if you're ready to learn something new, let's begin! #include <windows.h> // Header File For Windows HDC hDC=NULL; // Private GDI Device Context We set up storage space for 5 textures using the variable texture[5]. loop is our generic counter variable, we'll use it a few times in our program to set up textures, etc. Finally we have the variable roll. We'll use roll to roll the textures across the screen. Creates a neat effect! We'll also use it to spin the object in scene 2. bool keys[256]; // Array Used For The Keyboard Routine GLuint texture[5]; // Storage For Our Five Textures GLfloat roll; // Rolling Texture LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration For WndProc In the code below we create storage space for 5 images. We clear the space and load in all 5 bitmaps. We loop through each image and convert it into a texture for use in our program. The textures are stored in texture[0-4]. int LoadGLTextures() // Load Bitmaps And Convert To Textures if ((TextureImage[0]=LoadBMP("Data/logo.bmp")) && // Logo Texture for (loop=0; loop<5; loop++) // Loop Through All 5 Textures The Init code is fairly bare bones. We load in our textures, set the clear color, set and enable depth testing, turn on smooth shading, and enable texture mapping. Simple program so no need for a complex init :) int InitGL(GLvoid) // All Setup For OpenGL Goes Here glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Clear The Background Color To Black int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing Revised Description by Jonathan Roy: Remember that OpenGL is a vertex-based graphic system. Most of the parameters you set are recorded as attributes of a particular vertex. Texture coordinate is one such attribute. You simply specify appropriate texture coordinates for each vertex of a polygon, and OpenGL automatically fills in the surface between the vertices with the texture, through a process known as interpolation. Interpolation is a standard geometric technique that lets OpenGL determine how a given parameter varies between vertices just by knowing the value that parameter takes at the vertices themselves. Like in the previous lessons, we pretend we are facing the quad and assign texture coordinates as follows: (0.0, 0.0) to the bottom-left corner, (0.0, 1.0) to the top-left corner, (1.0, 0.0) to the bottom-right, and (1.0, 1.0) to the top-right. Now, given these settings, can you tell what texture coordinates correspond to the quad's middle point? That's right, (0.5, 0.5). But no where in the code did you specify that coordinate, did you? When it draws the quad, OpenGL computes it for you. And the real magic is that it does so whatever the position, size, or orientation of the polygon! In this lesson we add another interesting twist by assigning texture coordinates with values other than 0.0 and 1.0. Texture coordinates are said to be normalized. Value 0.0 maps to one edge of the texture, while value 1.0 maps to the opposite edge, spanning the full width or height of the texture image in a one unit step, regardless of the polygon's size or the image's size in pixels (which we therefore don't have to worry about when doing texture mapping, and that makes life a whole lot easier). Above 1.0, the mapping simply wraps around at the other edge and the texture repeats. In other words, texture coordinate (0.3, 0.5) for instance, maps to the exact same pixel in the texture image as coordinate (1.3, 0.5), or as (12.3, -2.5). In this lesson, we achieve a tiling effect by specifying value 3.0 instead of 1.0, effectively repeating the texture nine times (3x3 tiling) over the surface of the quad. Additionally, we use the roll variable to translate (or slide) the texture over the surface of the quad. A value of 0.0 for roll, which is added to the vertical texture coordinate, means that texture mapping on the bottom edge of the quad begins at the bottom edge of the texture image, as shown in the figure on the left. When roll equals 0.5, mapping on the bottom edge of the quad begins halfway up in the image (see figure on the right). Rolling textures can be used to create great effects such as moving clouds, words spinning around an object, etc. glBindTexture(GL_TEXTURE_2D, texture[0]); // Select Our Logo Texture glEnable(GL_BLEND); // Enable Blending if (masking) // Is Masking Enabled? The blend command below does the following: The Destination color (screen color) will be set to black if the section of our mask that is being copied to the screen is black. This means that sections of the screen that the black portion of our mask covers will turn black. Anything that was on the screen under the mask will be cleared to black. The section of the screen covered by the white mask will not change. glBlendFunc(GL_DST_COLOR,GL_ZERO); // Blend Screen Color With Zero (Black) if (scene) // Are We Drawing The Second Scene? After we translate into the screen, we rotate from 0-360 degrees depending on the value of roll. If roll is 0.0 we will be rotating 0 degrees. If roll is 1.0 we will be rotating 360 degrees. Fairly fast rotation, but I didn't feel like creating another variable just to rotate the image in the center of the screen. :) glTranslatef(0.0f,0.0f,-1.0f); // Translate Into The Screen One Unit if (masking) // Is Masking On? after drawing our mask to the screen a solid black copy of our final texture will appear on the screen. The final result will look as if someone took a cookie cutter and cut the shape of our final texture out of the screen, leaving an empty black space. glBindTexture(GL_TEXTURE_2D, texture[3]); // Select The Second Mask Texture Notice that we select the second image after selecting the final blending mode. This selects our colored image (the image that our second mask is based on). Also notice that we draw this image right on top of the mask. Same texture coordinates, same vertices. If we don't lay down a mask, our image will still be copied to the screen, but it will blend with whatever was on the screen. glBlendFunc(GL_ONE, GL_ONE); // Copy Image 2 Color To The Screen else // Otherwise if (masking) // Is Masking On? glBindTexture(GL_TEXTURE_2D, texture[1]); // Select The First Mask Texture glBlendFunc(GL_ONE, GL_ONE); // Copy Image 1 Color To The Screen glEnable(GL_DEPTH_TEST); // Enable Depth Testing roll+=0.002f; // Increase Our Texture Roll Variable return TRUE; // Everything Went OK The first thing you will notice different in the WinMain() code is the Window title. It's now titled "NeHe's Masking Tutorial". Change it to whatever you want :) 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[' '] && !sp) // Is Space Being Pressed? if (!keys[' ']) // Has Spacebar Been Released? if (keys['M'] && !mp) // Is M Being Pressed? if (!keys['M']) // Has M Been Released? if (keys[VK_F1]) // Is F1 Being Pressed? This is the mask -> This is the image -> Eric Desrosiers pointed out that you can also check the value of each pixel in your bitmap while you load it. If you want the pixel transparent you can give it an alpha value of 0. For all the other colors you can give them an alpha value of 255. This method will also work but requires some extra coding. The current tutorial is simple and requires very little extra code. I'm not blind to other techniques, but when I write a tutorial I try to make the code easy to understand and easy to use. I just wanted to point out that there are always other ways to get the job done. Thanks for the feedback Eric. In this tutorial I have shown you a simple, but effective way to draw sections of a texture to the screen without using the alpha channel. Normal blending usually looks bad (textures are either transparent or they're not), and texturing with an alpha channel requires that your images support the alpha channel. Bitmaps are convenient to work with, but they do not support the alpha channel this program shows us how to get around the limitations of bitmap images, while demonstrating a cool way to create overlay type effects. Thanks to Rob Santa for the idea and for example code. I had never heard of this little trick until he pointed it out. He wanted me to point out that although this trick does work, it takes two passes, which causes a performance hit. He recommends that you use textures that support the alpha channel for complex scenes. I hope you enjoyed this tutorial. If you had any problems understanding it, or you've found a mistake in the tutorial please let me know. I want to make the best tutorials available. Your feedback is important! Jeff Molofee (NeHe) |
-- 作者:ririyeye -- 发布时间:1/8/2010 10:39:00 AM -- 不看, 后悔 |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
312.500ms |