IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

NeHe Productions: OpenGL Lesson #20

Date de publication : 31/03/2006 , Date de mise à jour : 31/03/2006

Par Jeff Molofee ( NeHe ) (Autres articles)
 

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
#include <math.h>								 // Header File For Windows Math Library
#include <stdio.h>								 // Header File For Standard Input/Output
#include <gl\gl.h>								 // Header File For The OpenGL32 Library
#include <gl\glu.h>								 // Header File For The GLu32 Library
#include <gl\glaux.h>								 // Header File For The Glaux Library
HDC		hDC=NULL;							 // Private GDI Device Context
HGLRC		hRC=NULL;							 // Permanent Rendering Context
HWND		hWnd=NULL;							 // Holds Our Window Handle
HINSTANCE	hInstance;							 // Holds The Instance Of The Application
We'll be using 7 global variables in this program. masking is a boolean variable (TRUE / FALSE) that will keep track of whether or not masking is turned on of off. mp is used to make sure that the 'M' key isn't being held down. sp is used to make sure that the 'Spacebar' isn't being held down and the variable scene will keep track of whether or not we're drawing the first or second scene.

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
bool	active=TRUE;								 // Window Active Flag Set To TRUE By Default
bool	fullscreen=TRUE;							 // Fullscreen Flag Set To Fullscreen Mode By Default
bool	masking=TRUE;								 // Masking On/Off
bool	mp;									 // M Pressed?
bool	sp;									 // Space Pressed?
bool	scene;									 // Which Scene To Draw
GLuint	texture[5];								 // Storage For Our Five Textures
GLuint	loop;									 // Generic Loop Variable
GLfloat	roll;									 // Rolling Texture
LRESULT	CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);				 // Declaration For WndProc
The load bitmap code hasn't changed. It's the same as it was in lesson 6, etc.

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
{
	int Status=FALSE;							 // Status Indicator
	AUX_RGBImageRec *TextureImage[5];					 // Create Storage Space For The Texture Data
	memset(TextureImage,0,sizeof(void *)*5);				 // Set The Pointer To NULL
	if ((TextureImage[0]=LoadBMP("Data/logo.bmp")) &&			 // Logo Texture
	 (TextureImage[1]=LoadBMP("Data/mask1.bmp")) &&			 // First Mask
	 (TextureImage[2]=LoadBMP("Data/image1.bmp")) &&			 // First Image
	 (TextureImage[3]=LoadBMP("Data/mask2.bmp")) &&			 // Second Mask
	 (TextureImage[4]=LoadBMP("Data/image2.bmp")))			 // Second Image
	{
		Status=TRUE;							 // Set The Status To TRUE
		glGenTextures(5, &texture[0]);					 // Create Five Textures
		for (loop=0; loop<5; loop++)					 // Loop Through All 5 Textures
		{
			glBindTexture(GL_TEXTURE_2D, texture[loop]);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
			glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
				0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
		}
	}
	for (loop=0; loop<5; loop++)						 // Loop Through All 5 Textures
	{
		if (TextureImage[loop])						 // If Texture Exists
		{
			if (TextureImage[loop]->data)				 // If Texture Image Exists
			{
				free(TextureImage[loop]->data);			 // Free The Texture Image Memory
			}
			free(TextureImage[loop]);				 // Free The Image Structure
		}
	}
	return Status;								 // Return The Status
}
The ReSizeGLScene() code hasn't changed so we'll skip over it.

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
{
	if (!LoadGLTextures())							 // Jump To Texture Loading Routine
	{
		return FALSE;							 // If Texture Didn't Load Return FALSE
	}
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);					 // Clear The Background Color To Black
	glClearDepth(1.0);							 // Enables Clearing Of The Depth Buffer
	glEnable(GL_DEPTH_TEST);						 // Enable Depth Testing
	glShadeModel(GL_SMOOTH);						 // Enables Smooth Color Shading
	glEnable(GL_TEXTURE_2D);						 // Enable 2D Texture Mapping
	return TRUE;								 // Initialization Went OK
}
Now for the fun stuff. Our drawing code! We start off the same as usual. We clear the background color and the depth buffer. Then we reset the modelview matrix, and translate into the screen 2 units so that we can see our scene.
int DrawGLScene(GLvoid)								 // Here's Where We Do All The Drawing
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);			 // Clear The Screen And The Depth Buffer
	glLoadIdentity();							 // Reset The Modelview Matrix
	glTranslatef(0.0f,0.0f,-2.0f);						 // Move Into The Screen 5 Units
The first line below selects the 'logo' texture. We'll map the texture to the screen using a quad. We specify our four texture coordinates along with our four vertices.

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
	glBegin(GL_QUADS);							 // Start Drawing A Textured Quad
		glTexCoord2f(0.0f, -roll+0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);	 // Bottom Left
		glTexCoord2f(3.0f, -roll+0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);	 // Bottom Right
		glTexCoord2f(3.0f, -roll+3.0f); glVertex3f( 1.1f, 1.1f, 0.0f);	 // Top Right
		glTexCoord2f(0.0f, -roll+3.0f); glVertex3f(-1.1f, 1.1f, 0.0f);	 // Top Left
	glEnd();								 // Done Drawing The Quad
Anyways... back to reality. Now we enable blending. In order for this effect to work we also have to disable depth testing. It's very important that you do this! If you do not disable depth testing you probably wont see anything. Your entire image will vanish!
	glEnable(GL_BLEND);							 // Enable Blending
	glDisable(GL_DEPTH_TEST);						 // Disable Depth Testing
The first thing we do after we enable blending and disable depth testing is check to see if we're going to mask our image or blend it the old fashioned way. The line of code below checks to see if masking is TRUE. If it is we'll set up blending so that our mask gets drawn to the screen properly.
	if (masking)								 // Is Masking Enabled?
	{
If masking is TRUE the line below will set up blending for our mask. A mask is just a copy of the texture we want to draw to the screen but in black and white. Any section of the mask that is white will be transparent. Any sections of the mask that is black will be SOLID.

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)
	}
Now we check to see what scene to draw. If scene is TRUE we will draw the second scene. If scene is FALSE we will draw the first scene.
	if (scene)								 // Are We Drawing The Second Scene?
	{
We don't want things to be too big so we translate one more unit into the screen. This reduces the size of our objects.

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
		glRotatef(roll*360,0.0f,0.0f,1.0f);				 // Rotate On The Z Axis 360 Degrees
We already have the rolling logo on the screen and we've rotated the scene on the Z axis causing any objects we draw to be rotated counter-clockwise, now all we have to do is check to see if masking is on. If it is we'll draw our mask then our object. If masking is off we'll just draw our object.
		if (masking)							 // Is Masking On?
		{
If masking is TRUE the code below will draw our mask to the screen. Our blend mode should be set up properly because we had checked for masking once already while setting up the blending. Now all we have to do is draw the mask to the screen. We select mask 2 (because this is the second scene). After we have selected the mask texture we texture map it onto a quad. The quad is 1.1 units to the left and right so that it fills the screen up a little more. We only want one texture to show up so our texture coordinates only go from 0.0 to 1.0.

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
			glBegin(GL_QUADS);					 // Start Drawing A Textured Quad
				glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);	 // Bottom Left
				glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);	 // Bottom Right
				glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f, 1.1f, 0.0f);	 // Top Right
				glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f, 1.1f, 0.0f);	 // Top Left
			glEnd();						 // Done Drawing The Quad
		}
Now that we have drawn our mask to the screen it's time to change blending modes again. This time we're going to tell OpenGL to copy any part of our colored texture that is NOT black to the screen. Because the final texture is an exact copy of the mask but with color, the only parts of our texture that get drawn to the screen are parts that land on top of the black portion of the mask. Because the mask is black, nothing from the screen will shine through our texture. This leaves us with a very solid looking texture floating on top of the screen.

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
		glBindTexture(GL_TEXTURE_2D, texture[4]);			 // Select The Second Image Texture
		glBegin(GL_QUADS);						 // Start Drawing A Textured Quad
			glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);	 // Bottom Left
			glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);	 // Bottom Right
			glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f, 1.1f, 0.0f);	 // Top Right
			glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f, 1.1f, 0.0f);	 // Top Left
		glEnd();							 // Done Drawing The Quad
	}
If scene was FALSE, we will draw the first scene (my favorite).
	else									 // Otherwise
	{
We start off by checking to see if masking is TRUE of FALSE, just like in the code above.
		if (masking)							 // Is Masking On?
		{
If masking is TRUE we draw our mask 1 to the screen (the mask for scene 1). Notice that the texture is rolling from right to left (roll is added to the horizontal texture coordinate). We want this texture to fill the entire screen that is why we never translated further into the screen.
			glBindTexture(GL_TEXTURE_2D, texture[1]);		 // Select The First Mask Texture
			glBegin(GL_QUADS);					 // Start Drawing A Textured Quad
				glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);	 // Bottom Left
				glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);	 // Bottom Right
				glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f, 1.1f, 0.0f);	 // Top Right
				glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f, 1.1f, 0.0f);	 // Top Left
			glEnd();						 // Done Drawing The Quad
		}
Again we enable blending and select our texture for scene 1. We map this texture on top of it's mask. Notice we roll this texture as well, otherwise the mask and final image wouldn't line up.
		glBlendFunc(GL_ONE, GL_ONE);					 // Copy Image 1 Color To The Screen
		glBindTexture(GL_TEXTURE_2D, texture[2]);			 // Select The First Image Texture
		glBegin(GL_QUADS);						 // Start Drawing A Textured Quad
			glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f, 0.0f);	 // Bottom Left
			glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f, 0.0f);	 // Bottom Right
			glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f, 1.1f, 0.0f);	 // Top Right
			glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f, 1.1f, 0.0f);	 // Top Left
		glEnd();							 // Done Drawing The Quad
	}
Next we enable depth testing, and disable blending. This prevents strange things from happening in the rest of our program :)
	glEnable(GL_DEPTH_TEST);						 // Enable Depth Testing
	glDisable(GL_BLEND);							 // Disable Blending
Finally all we have left to do is increase the value of roll. If roll is greater than 1.0 we subtract 1.0. This prevents the value of roll from getting to high.
	roll+=0.002f;								 // Increase Our Texture Roll Variable
	if (roll>1.0f)								 // Is Roll Greater Than One
	{
		roll-=1.0f;							 // Subtract 1 From Roll
	}
	return TRUE;								 // Everything Went OK
}
The KillGLWindow(), CreateGLWindow() and WndProc() code hasn't changed so we'll skip over it.

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
			HINSTANCE	hPrevInstance,				 // Previous Instance
			LPSTR		lpCmdLine,				 // Command Line Parameters
			int		nCmdShow)				 // Window Show State
{
	MSG	msg;								 // Windows Message Structure
	BOOL	done=FALSE;							 // Bool Variable To Exit Loop
	 // Ask The User Which Screen Mode They Prefer
	if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
	{
		fullscreen=FALSE;						 // Windowed Mode
	}
	 // Create Our OpenGL Window
	if (!CreateGLWindow("NeHe's Masking Tutorial",640,480,16,fullscreen))
	{
		return 0;							 // Quit If Window Was Not Created
	}
	while(!done)								 // Loop That Runs While done=FALSE
	{
		if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))			 // Is There A Message Waiting?
		{
			if (msg.message==WM_QUIT)				 // Have We Received A Quit Message?
			{
				done=TRUE;					 // If So done=TRUE
			}
			else							 // If Not, Deal With Window Messages
			{
				TranslateMessage(&msg);				 // Translate The Message
				DispatchMessage(&msg);				 // Dispatch The Message
			}
		}
		else								 // If There Are No Messages
		{
			 // Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
			if ((active && !DrawGLScene()) || keys[VK_ESCAPE])	 // Active? Was There A Quit Received?
			{
				done=TRUE;					 // ESC or DrawGLScene Signalled A Quit
			}
			else							 // Not Time To Quit, Update Screen
			{
				SwapBuffers(hDC);				 // Swap Buffers (Double Buffering)
Now for our simple key handling code. We check to see if the spacebar is being pressed. If it is, we set the sp variable to TRUE. If sp is TRUE, the code below will not run a second time until the spacebar has been released. This keeps our program from flipping back and forth from scene to scene very rapidly. After we set sp to TRUE, we toggle the scene. If it was TRUE, it becomes FALSE, if it was FALSE it becomes TRUE. In our drawing code above, if scene is FALSE the first scene is drawn. If scene is TRUE the second scene is drawn.
				if (keys[' '] && !sp)				 // Is Space Being Pressed?
				{
					sp=TRUE;				 // Tell Program Spacebar Is Being Held
					scene=!scene;				 // Toggle From One Scene To The Other
				}
The code below checks to see if we have released the spacebar (if NOT ' '). If the spacebar has been released, we set sp to FALSE letting our program know that the spacebar is NOT being held down. By setting sp to FALSE the code above will check to see if the spacebar has been pressed again, and if so the cycle will start over.
				if (!keys[' '])					 // Has Spacebar Been Released?
				{
					sp=FALSE;				 // Tell Program Spacebar Has Been Released
				}
The next section of code checks to see if the 'M' key is being pressed. If it is being pressed, we set mp to TRUE, telling our program not to check again until the key is released, and we toggle masking from TRUE to FALSE or FALSE to TRUE. If masking is TRUE, the drawing code will turn on masking. If it is FALSE masking will be off. If masking is off, the object will be blended to the screen using the old fashioned blending we've been using up until now.
				if (keys['M'] && !mp)				 // Is M Being Pressed?
				{
					mp=TRUE;				 // Tell Program M Is Being Held
					masking=!masking;			 // Toggle Masking Mode OFF/ON
				}
The last bit of code checks to see if we've stopped pressing 'M'. If we have, mp becomes FALSE letting the program know that we are no longer holding the 'M' key down. Once the 'M' key has been released, we are able to press it once again to toggle masking on or off.
				if (!keys['M'])					 // Has M Been Released?
				{
					mp=FALSE;				 // Tell Program That M Has Been Released
				}
Like all the previous tutorials, make sure the title at the top of the window is correct.
				if (keys[VK_F1])				 // Is F1 Being Pressed?
				{
					keys[VK_F1]=FALSE;			 // If So Make Key FALSE
					KillGLWindow();				 // Kill Our Current Window
					fullscreen=!fullscreen;			 // Toggle Fullscreen / Windowed Mode
					 // Recreate Our OpenGL Window
					if (!CreateGLWindow("NeHe's Masking Tutorial",640,480,16,fullscreen))
					{
						return 0;			 // Quit If Window Was Not Created
					}
				}
			}
		}
	}
	 // Shutdown
	KillGLWindow();								 // Kill The Window
	return (msg.wParam);							 // Exit The Program
}
Creating a mask isn't to hard. A little time consuming. The best way to make a mask if you already have your image made is to load your image into an art program or a handy program like infranview, and reduce it to a gray scale image. After you've done that, turn the contrast way up so that gray pixels become black. You can also try turning down the brightness, etc. It's important that the white is bright white, and the black is pure black. If you have any gray pixels in your mask, that section of the image will appear transparent. The most reliable way to make sure your mask is a perfect copy of your image is to trace over the image with black. It's also very important that your image has a BLACK background and the mask has a WHITE background! If you create a mask and notice a square shape around your texture, either your white isn't bright enough (255 or FFFFFF) or your black isn't true black (0 or 000000). Below you can see an example of a mask and the image that goes over top of the mask. The image can be any color you want as long as the background is black. The mask must have a white background and a black copy of your image.

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 )




Valid XHTML 1.1!Valid CSS!

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Nehe Gamedev.net. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.