Bonjour à tous ! :)
J'en profite de passer par là pour que vous m'aidiez à résoudre un petit problème.
Voilà, j'aimerai faire, via SFML donc, du Texture Painting (ou Texture Splatting, ou Weight Mapping, ou Alpha Mapping, ou je sais pas quel nom supplémentaire on donne à cette technique !), c'est-à-dire, pour ceux qui n'aurai pas cerné la chose, texturer un terrain, mais pas de manière linéaire (Pas de tiles), plutôt en combinant des textures comme ceci par exemple : (http://oi45.tinypic.com/mkxax2.jpg) (Bon là c'est en 3D mais vous cernez l'idée, et puis ça existe en 2D aussi).
Mais voilà hélas ! Y'a toujours un hélas, et c'est bien là qu'est l'os !!!
J'ai eu beau essayer tout est n'importe quoi pour réaliser cet effet, j'ai joué avec des textures combinées, via du sf::BlendMode, à des textures en nuances de gris pour la répartition, mais non ! Ça fonctionne pas ! Soit les textures se mélangent (Dans le sens où leurs couleurs se multiplient), soit y'a la bonne répartition de l'une des textures, mais tout le reste de la scène est noir, ou blanc, soit rien du tout du tout ! :(
J'abdique, je voudrais donc savoir si, au moins, c'est possible avec la SFML (>= 2.0), et si oui, comment est-ce qu'on fait ??? (Je veux pas du code, ça je me débrouille, mais juste une explication, parce que même avec les rares trucs que j'ai trouvé sur Internet, ça marche pas comme il faut...)
Franchement, merci pour les futures réponses !
EDIT : J'en ai marre de passer des jours et des jours sur un problème, et de trouver la solution pure et dure à la minute où j'ai posté sur votre forum !!! Ce forum est magique, merci Laurent ! :P
Bah si t'as la solution donne la. :p
En fait, c'est pas dur !
Admettons que tu as deux textures :
(http://esforces.com/maptutorial/MapTutImages/Grass1.jpg) | (http://www.hiveworkshop.com/forums/resource_images/3/skins_2573_preview.jpg) |
On va dire ici que la texture de base de ton terrain (La principale) est l'herbe, et que tu veux mettre des bouts de sable un peu partout.
Pour réaliser ça, il te faut ce qu'on appelle une alpha map (En fait ça a genre plein de noms), qui va permettre, en nuances de gris, de représenter la répartition de la-dite texture, avec comme base : Noir = Rien, Blanc = Ta texture, Gris = Mix
Par exemple, pour un terrain 200x200, on va dire que, pour la texture de sable, j'ai cette alpha map :
(http://img4.hostingpics.net/pics/835757alpha.png)
Donc, tu vois, au final, je suis sensé avoir de l'herbe sur ton mon terrain, sauf vers le centre où on doit avoir un banc de sable.
Pour réaliser cette effet, donc, il faut les objets SFML suivants :
Deux sf::Texture pour l'herbe et le sable.
Deux autres, une pour l'alpha-map, une autre pour son inverse (J'expliquerai plus loin).
Quatre sf::RectangleShape (ou sf::Sprite) pour représenter les calques de terrain (Un contiendra seulement l'herbe, l'autre seulement le sable), les alpha-map.
Deux sf::Sprite pour avoir le calque du sable final, et le sol texturé final à afficher.
Deux sf::RenderTexture pour composer avec ces différents objets via sf::BlendMode.
Voyons du côté du code :
sf::Texture t_herbe, t_sable, t_alphamap, t_alphamap_inv;
sf::RenderTexture r_sol, r_sable;
sf::RectangleShape herbe, sable, alphamap, alphamap_inv;
sf::Sprite sable_final, sol_final;
Ensuite, tu fais tes chargements de fichiers :
t_herbe.loadFromFile("herbe.png");
t_herbe.setRepeated(true);
t_sable.loadFromFile("sable.png");
t_sable.setRepeated(true);
t_alphamap.loadFromFile("alpha.png");
t_alphamap_inv.loadFromFile("alpha_inv.png");
Puis tu fais les réglages sur les sf::RenderTexture :
r_sol.create(200, 200);
r_sol.clear(sf::Color::White);
r_sable.create(200, 200);
r_sable.clear(sf::Color::White);
et sur les sf::RectangleShape :
herbe.setSize(sf::Vector2f(200, 200));
herbe.setTexture(&t_herbe);
/* (IDEM pour les autres) */
Et puis maintenant, on se met au boulot !
D'abord, tu fais le rendu de ta texture de base (ici, l'herbe), en entier.
r_sol.draw(herbe, sf::BlendMultiply);
Puis tu dégages toutes les parties où le sable doit être (C'est là qu'intervient l'alpha-map inversée, en gros ça te fais du masking), avec de la multiplication de texture (Le noir efface, le blanc conserve).
r_sol.draw(alphamap_inv, sf::BlendMultiply);
Tu actualises la sf::RenderTexture (Pour éviter les inversions d'axe) :
r_sol.display();
Avec mon exemple, ma sf::RenderTexture contient, à présent, quelque chose comme ça :
(http://img4.hostingpics.net/pics/497184maskedgrass.png)
Ensuite, tu fais la même chose pour le sable :
r_sable.draw(sable, sf::BlendMultiply);
r_sable.draw(alphamap, sf::BlendMultiply);
r_sable.display();
Ce qui te donne quelque chose comme ça :
(http://img4.hostingpics.net/pics/167666maskedsand.png)
Maintenant, il reste plus qu'à composer, via les sf::Sprite.
sable_final.setTexture(r_sable.getTexture());
sol_final.setTexture(r_sol.getTexture())
r_sol.draw(sable_final, sf::BlendAdd);
r_sol.display();
Et puis bah il te reste plus qu'à afficher le tout dans ta boucle principale, c'est-à-dire seulement le sf::Sprite sol_final !
Tu as donc :
(http://img4.hostingpics.net/pics/164723final.png)
Et voilà ! :)
Après, c'est peut-être bordélique, et puis j'explique pas bien, et si c'est pas très optimisé, je remercie d'avance les personnes qui pourraient aider à l'amélioration de la chose !
P.S. : Tu peux très bien faire ça avec plus de deux textures, il suffit à chaque fois de faire du masking sur les textures selon laquelle superpose laquelle.
P.S. 2 : Au lieu de charger une autre alpha-map pré-inversée, tu peux l'inverser dans le programme, j'imagine que c'est possible avec la SFML sans aucun problème.
Bonjour,
Il n'est pas utile d'appliquer l'alphamap en négatif sur l'herbe, cela crée une couronne grise autour du sable.
Il me semble plus approprié d'utiliser l'alpha map en tant que calque alpha pour le sable. On peut le faire au début du programme grâce une sf::Image dans laquelle on change l'alpha des pixels et que l'on charge ensuite dans une sf::Texture mais c'est probablement un peu lent et ça demande de faire une Texture par alpha map et par terrain.
Avec un sf::Shader, je ne suis pas expert mais ça devrait ressembler à ça :
uniform sampler2D texture;
uniform sampler2D alpha;
void main(void)
{
gl_FragColor = gl_Color * texture2D(texture, gl_TexCoord[0].xy);
gl_FragColor.w *= texture2D(alpha, gl_TexCoord[0].xy).x;
}
(edit: code corrigé)
Pour chaque pixel, on multiplie l'alpha du sable avec la nuance de gris de l'alpha map.
Du code pour le tester :
#include <SFML/Graphics.hpp>
int main()
{
sf::Texture sandTex, grassTex, alphaTex;
sandTex.loadFromFile("sand.jpg");
grassTex.loadFromFile("grass.jpg");
alphaTex.loadFromFile("alpha.png");
sf::Sprite sand(sandTex), grass(grassTex);
sf::Shader alpha;
alpha.loadFromFile("alpha.frag", sf::Shader::Fragment);
alpha.setParameter("texture", sf::Shader::CurrentTexture);
alpha.setParameter("alpha", alphaTex);
sf::RenderWindow window(sf::VideoMode(256, 256), "sfml window");
while(window.isOpen())
{
sf::Event event;
while(window.pollEvent(event))
if(event.type == sf::Event::Closed)
window.close();
window.clear();
window.draw(grass);
window.draw(sand, &alpha);
window.display();
}
return 0;
}
Mais donc, du coup, avec ce shader que tu proposes, ci je comprends bien, il faut que l'alphamap soit transparente, avec des tâches plus ou moins opaques de blanc, ou alors on conserve la nuance de gris avec le code Noir = Rien, Blanc = Plein comme ce que j'ai ?
On utilise des nuances de gris. D'ailleurs je vois que j'ai oublié quelque chose dans mon shader: Il faut multiplier la transparence du pixel par un scalaire bien sûr, pas par un vec4.
Pour obtenir ce scalaire, on peut choisir n'importe quel canal de l'alphamap entre rouge, vert et bleu puisqu'elle est composée de nuances de gris. J'ai choisi ici le rouge.
uniform sampler2D texture;
uniform sampler2D alpha;
void main(void)
{
gl_FragColor = gl_Color * texture2D(texture, gl_TexCoord[0].xy);
gl_FragColor.w *= texture2D(alpha, gl_TexCoord[0].xy).x;
}
En effet gl_TexCoord[0].xy donne la position normalisée du pixel en cours de traitement dans la texture de sable. Pour avoir la position correspondante dans la texture de l'alphamap, on peut multiplier ce vecteur par le rapport des tailles des textures. Ce n'est peut être pas la meilleure façon de procéder, je ne suis pas expert, mais ça marche bien.
uniform sampler2D texture;
uniform vec2 textureSize;
uniform sampler2D alpha;
uniform vec2 alphaSize;
void main(void)
{
gl_FragColor = gl_Color * texture2D(texture, gl_TexCoord[0].xy);
gl_FragColor.w *= texture2D(alpha, gl_TexCoord[0].xy * textureSize / alphaSize).x;
}
Il ne faut pas oublier de donner les valeurs correspondantes aux vecteurs textureSize et alphaSize à l'aide de la méthode setParameter du sf::Shader.