Placage de relief

A propos du code

Le code dans cet article a été écrit avec Code::Blocks et la SDL 2.
Vous pouvez trouver ici un guide pour installer ces logiciels.
Bien qu'il soit basé sur la SDL, je n'utilise pas ses fonctions directement. J'ai écrit une petite bibliothèque
avec quelques fonctions basiques pour faciliter la compréhension et la portabilité dans un autre langage.
Vous pouvez en apprendre plus sur cette bibliothèque ici.

Dans cet article j'ai utilisé les fonctions pour le format Targa que j'ai présentées ici.

Projecteur

Ici je vais vous montrer un petit effet 2D pour ajouter du relief à une image.
D'abord on va faire une lumière "projecteur".
On va avoir besoin de 2 images, une pour le fond:


Et une pour la lumière.


Dans le code, on va commencer par charger ces images.
		#define SCREEN_WIDTH    640
		#define SCREEN_HEIGHT   480

		int main(int argc, char* argv[])
		{
			// init the window
			gfx.init("Bump", SCREEN_WIDTH, SCREEN_HEIGHT);
			gfx.init2D();
			gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));
			int width, height;

			Color* background = tga.load("background.tga", &width, &height);
			Color* lightImage = tga.load("light.tga", &width, &height);
				
Puis on boucle sur chaque pixel et on récupère la lumière.
C'est une image en noir et blanc, alors on utilise seulement la composante rouge.
			while (sys.isQuitRequested() == false)
			{
				// draw the image
				for (int y = 0; y < SCREEN_HEIGHT; ++y)
					for (int x = 0; x < SCREEN_WIDTH; ++x)
					{
						// compute the light intensity
						int light = lightImage[y * SCREEN_WIDTH + x].r;
				
On recupère la couleur du fond, on la multiplie par la lumière, et on dessine le pixel.
						// get the background color
						Color col = background[y * SCREEN_WIDTH + x];

						// modulate the color by the light intensity
						col.r = col.r * light / 255;
						col.g = col.g * light / 255;
						col.b = col.b * light / 255;

						gfx.setPixel(x, y, col);
					}

		Télécharger le code source
		Télécharger l'exécutable pour Windows
				
Le reste du programme est classique. On gère les évènements et on termine la boucle principale.
Et voilà ce qu'on obtient:

Faire bouger la lumière

Maintenant on va faire bouger la lumière. De cette façon on verra mieux l'effet de relief.
J'ai choisi d'utiliser une courbe de Lissajous pour la bouger sur tout l'écran.
Alors en dehors de la boucle principale on va déclarer la position de la lumière et un angle.
		int lightPosX, lightPosY;
		float angle = 0.0f;
				
A l'intérieur de la boucle principale, on va calculer la position de la lumière
		// move the light
		lightPosX = 160 * sin(angle);
		lightPosY = 120 * sin(2.0f * angle);
		angle += 0.02f;
				
Et quand on lit le pixel de lumière, on va ajouter cette position et vérifier qu'on reste bien à l'intérieur de
l'image.
		int light = 0;
		int lightX = x + lightPosX;
		int lightY = y + lightPosY;

		if (lightX >= 0 && lightX < SCREEN_WIDTH &&
		    lightY >= 0 && lightY < SCREEN_HEIGHT)
		{
			light = lightImage[lightY * SCREEN_WIDTH + lightX].r;
		}

		Télécharger le code source
		Télécharger l'exécutable pour Windows
				

L'effet de relief

Maintenant on va ajouter une autre image qui va être utilisée pour perturber la position de la lumière.


Cette image est une carte de hauteurs. Le noir est le point le plus haut et la blanc est le plus bas.
Mais on verra qu'on peut inverser ces valeurs.
Bien entendu on doit charger cette image au début.
		Color* bump       = tga.load("bump.tga", &width, &height);
				
Maintenant pour chaque point de cette image, on a besoin d'avoir les "dérivées" suivant x et y.
Alors on va prendre la valeur du pixel courant, celle du pixel juste à droite et celle du pixel juste en-dessous.
La dérivée horizontale sera "pixel de droite" - "pixel courant".
Et la dérivée verticale sera "pixel en dessous" - "pixel courant".
		// compute the bump disturbance
		int deltaX = 0;
		int deltaY = 0;

		if (x < SCREEN_WIDTH - 1 && y < SCREEN_HEIGHT - 1)
		{
			int current = bump[ y      * SCREEN_WIDTH +  x     ].r;
			int right   = bump[ y      * SCREEN_WIDTH + (x + 1)].r;
			int down    = bump[(y + 1) * SCREEN_WIDTH +  x     ].r;

			deltaX = right - current;
			deltaY = down  - current;
		}
				
Puis on ajoute ces deltas à la position de la lumière.
		int lightX = x + lightPosX + deltaX;
		int lightY = y + lightPosY + deltaY;

		Télécharger le code source
		Télécharger l'exécutable pour Windows
				
Maintenant l'image semble sortir du bois.

Gravure sur bois

Maintenant, le relief sortant du bois n'est pas très réaliste.
Ca serait mieux si l'effet était inversé, comme si on avait gravé l'image dans le bois.
Pour réaliser ça on va simplement soustraire les deltas à la position de la lumière au lieu de les ajouter.
		int lightX = x + lightPosX - deltaX;
		int lightY = y + lightPosY - deltaY;

		Télécharger le code source
		Télécharger l'exécutable pour Windows
				
Et voilà à quoi ça ressemble:


Enfin pour avoir un effet encore plus marqué, on peut multiplier les deltas par 2.
		int lightX = x + lightPosX - deltaX * 2;
		int lightY = y + lightPosY - deltaY * 2;

		Télécharger le code source
		Télécharger l'exécutable pour Windows
				
Et maintenant ça a l'air vraiment profond.


Liens

Vidéo du dernier programme de cet article