Comment puis-je obtenir un effet d'éclairage 2D fluide?

18

Je fais un jeu basé sur des tuiles 2D dans XNA.

Actuellement, ma foudre ressemble à ceci .

Comment puis-je le faire ressembler à ça ?

Au lieu que chaque bloc ait sa propre teinte, il a une superposition lisse.

Je suppose une sorte de shader et je transmets les valeurs d'éclairage des tuiles environnantes au shader, mais je suis un débutant avec les shaders, donc je ne suis pas sûr.

Mon éclairage actuel calcule la lumière, puis la transmet à a SpriteBatchet dessine avec le paramètre de teinte. Chaque tuile a un Colorqui est calculé avant de dessiner dans mon algorithme d'éclairage, qui est utilisé pour la teinte.

Voici un exemple sur la façon dont je calcule actuellement l'éclairage (je le fais aussi à gauche, à droite et en bas, mais je suis vraiment fatigué de faire cette image par image ...)

entrez la description de l'image ici

Donc, obtenir et dessiner la lumière jusqu'à présent ne pose aucun problème !

J'ai vu d'innombrables tutoriels sur le brouillard de guerre et l'utilisation de superpositions circulaires en dégradé pour créer un éclair doux, mais j'ai déjà une belle méthode pour attribuer une valeur d'éclair à chaque tuile, il suffit de la lisser entre elles.

Donc pour revoir

  • Calculer l'éclairage (terminé)
  • Dessiner des tuiles (Terminé, je sais que je devrai le modifier pour le shader)
  • Carreaux d'ombre (Comment passer des valeurs et appliquer un "dégradé")
Cyral
la source

Réponses:

6

Que diriez-vous quelque chose comme ça?

Ne dessinez pas votre éclairage en teintant vos sprites de tuiles. Dessinez vos tuiles non éclairées sur une cible de rendu, puis dessinez les lumières des tuiles sur une deuxième cible de rendu, représentant chacune sous la forme d'un rectangle en niveaux de gris couvrant la zone de la tuile. Pour rendre la scène finale, utilisez un shader pour combiner les deux cibles de rendu, assombrissant chaque pixel du premier en fonction de la valeur de la seconde.

Cela produira exactement ce que vous avez maintenant. Cela ne vous aide pas, alors changeons-le un peu.

Modifiez les dimensions de votre cible de rendu lightmap de sorte que chaque mosaïque soit représentée par un seul pixel , plutôt que par une zone rectangulaire. Lors de la composition de la scène finale, utilisez un état d'échantillonneur avec filtrage linéaire. Sinon, laissez tout le reste inchangé.

En supposant que vous avez correctement écrit votre shader, le lightmap devrait être effectivement "agrandi" pendant la composition. Cela vous donnera un bel effet de dégradé gratuitement via l'échantillonneur de texture du périphérique graphique.

Vous pouvez également couper le shader et le faire plus simplement avec un BlendState `` assombrissant '', mais je devrais l'expérimenter avant de pouvoir vous donner les détails.

MISE À JOUR

J'ai eu un peu de temps aujourd'hui pour me moquer de ça. La réponse ci-dessus reflète mon habitude d'utiliser des shaders comme première réponse à tout, mais dans ce cas, ils ne sont pas réellement nécessaires et leur utilisation complique inutilement les choses.

Comme je l'ai suggéré, vous pouvez obtenir exactement le même effet en utilisant un BlendState personnalisé. Plus précisément, ce BlendState personnalisé:

BlendState Multiply = new BlendState()
{
    AlphaSourceBlend = Blend.DestinationAlpha,
    AlphaDestinationBlend = Blend.Zero,
    AlphaBlendFunction = BlendFunction.Add,
    ColorSourceBlend = Blend.DestinationColor,
    ColorDestinationBlend = Blend.Zero,
    ColorBlendFunction = BlendFunction.Add
}; 

L'équation de mélange est

result = (source * sourceBlendFactor) blendFunction (dest * destBlendFactor)

Donc, avec notre BlendState personnalisé, cela devient

result = (lightmapColor * destinationColor) + (0)

Ce qui signifie qu'une couleur source de blanc pur (1, 1, 1, 1) conservera la couleur de destination, une couleur source de noir pur (0, 0, 0, 1) assombrira la couleur de destination en noir pur, et tout une nuance de gris entre les deux assombrira la couleur de destination d'une quantité moyenne.

Pour mettre cela en pratique, faites d'abord tout ce que vous devez faire pour créer votre lightmap:

var lightmap = GetLightmapRenderTarget();

Ensuite, dessinez simplement votre scène non éclairée directement dans le backbuffer comme vous le feriez normalement:

spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
/* draw the world here */
spriteBatch.End();

Puis dessinez la lightmap en utilisant le BlendState personnalisé:

var offsetX = 0; // you'll need to set these values to whatever offset is necessary
var offsetY = 0; // to align the lightmap with the map tiles currently being drawn
var width = lightmapWidthInTiles * tileWidth;
var height = lightmapHeightInTiles * tileHeight;

spriteBatch.Begin(SpriteSortMode.Immediate, Multiply);
spriteBatch.Draw(lightmap, new Rectangle(offsetX, offsetY, width, height), Color.White);
spriteBatch.End();

Cela multipliera la couleur de destination (tuiles non éclairées) par la couleur source (lightmap), assombrissant les tuiles non éclairées de manière appropriée et créant un effet de dégradé à la suite de la mise à l'échelle de la texture lightmap jusqu'à la taille nécessaire.

Cole Campbell
la source
Cela semble assez simple, je vois ce que je peux faire plus tard. J'ai essayé quelque chose de similaire auparavant (j'ai rendu le lightmap sur tout le reste et utilisé un shader de flou, mais je ne pouvais pas rendre le renderTarget "transparent", il avait une superposition violette, mais je voulais qu'il voie à travers la couche de tuiles actuall)
Cyral
Après avoir défini votre cible de rendu sur l'appareil, appelez device.Clear (Color.Transparent);
Cole Campbell
Bien que, dans ce cas, il convient de souligner que votre lightmap devrait probablement être effacé en blanc ou en noir, ceux qui sont respectivement en pleine lumière et en pleine obscurité.
Cole Campbell
Je pense que c'est ce que j'ai essayé auparavant, mais il était encore violet, eh bien je vais l'examiner demain quand j'aurai le temps de travailler dessus.
Cyral
Presque tout a été réglé, mais il passe du noir au blanc, au lieu du noir au transparent. La partie shader est ce sur quoi je suis confus, comment en écrire un pour atténuer la scène originale non éclairée à celle de la nouvelle.
Cyral
10

D'accord, voici une méthode très simple pour créer un éclair 2D simple et fluide, rendu en trois passes:

  • Pass 1: le monde du jeu sans foudre.
  • Pass 2: la foudre sans le monde
  • Combinaison des passes 1. et 2. qui s'affiche à l'écran.

Au col 1, vous dessinez tous les sprites et le terrain.

Dans le passage 2, vous dessinez un deuxième groupe de sprites qui sont les sources de lumière. Ils devraient ressembler à ceci:
entrez la description de l'image ici
initialisez la cible de rendu avec du noir et dessinez ces sprites dessus avec un mélange maximum ou additif.

Dans la passe 3, vous combinez les deux passes précédentes. Il existe plusieurs façons de les combiner. Mais la méthode la plus simple et la moins artistique est de les mélanger via Multiply. Cela ressemblerait à ceci:
entrez la description de l'image ici

API-Beast
la source
Le fait est que j'ai déjà un système d'éclairage, et les lumières ponctuelles ne fonctionnent pas ici à cause de certains objets qui ont besoin de lumière pour passer à travers et d'autres non.
Cyral
2
Eh bien, c'est plus délicat. Vous aurez besoin de lancer des rayons pour obtenir des ombres. Teraria utilise de gros blocs pour son terrain, ce n'est donc pas un problème. Vous pouvez utiliser un lancer de rayons imprécis, mais cela ne semblerait pas mieux que terraria et pourrait s'adapter encore moins si vous n'avez pas bloqué les graphiques. Pour une technique avancée et esthétique,
API-Beast
2

Le deuxième lien que vous avez publié ressemble à un brouillard de guerre en quelque sorte, il y a quelques autres questions à ce sujet

Cela me ressemble à une texture , où vous "effacez" les bits de la superposition noire (en définissant l'alpha de ces pixels à 0) lorsque le joueur avance.

Je dirais que la personne doit avoir utilisé un pinceau de type "effacer" là où le joueur a exploré.

bobobobo
la source
1
Ce n'est pas du brouillard de guerre, il calcule la foudre à chaque image. Le jeu que j'ai signalé est similaire à Terraria.
Cyral
Ce que je voulais dire, c'est qu'il pourrait être mis en œuvre de la même manière que le brouillard de guerre
bobobobo
2

Des sommets légers (coins entre les carreaux) au lieu de carreaux. Mélanger l'éclairage sur chaque tuile en fonction de ses quatre sommets.

Effectuez des tests de visibilité directe sur chaque sommet pour déterminer son allumage (vous pouvez soit faire bloquer un bloc de toute lumière ou diminuer la lumière, par exemple, compter le nombre d'intersections à chaque sommet combinées à la distance pour calculer une valeur de lumière plutôt que d'utiliser un test binaire pur visible / non visible).

Lors du rendu d'une tuile, envoyez la valeur lumineuse de chaque sommet au GPU. Vous pouvez facilement utiliser un fragment shader pour prendre la valeur de la lumière interpolée à chaque fragment dans le sprite de la tuile pour éclairer chaque pixel en douceur. Cela fait assez longtemps que je n'ai pas touché GLSL que je ne me sentirais pas à l'aise de donner un véritable échantillon de code, mais en pseudo-code, ce serait aussi simple que:

texture t_Sprite
vec2 in_UV
vec4 in_Light // coudl be one float; assuming you might want colored lights though

fragment shader() {
  output = sample2d(t_Sprite, in_UV) * in_Light;
}

Le vertex shader n'a qu'à transmettre les valeurs d'entrée au fragment shader, rien de compliqué même à distance.

Vous obtiendrez un éclairage encore plus doux avec plus de sommets (par exemple, si chaque tuile est rendue en quatre sous-tuiles), mais cela ne vaut peut-être pas la peine en fonction de la qualité que vous recherchez. Essayez-le d'abord de la manière la plus simple.

Sean Middleditch
la source
1
line-of sight tests to each vertex? Ça va coûter cher.
ashes999
Vous pouvez optimiser avec un peu d'intelligence. Pour un jeu 2D, vous pouvez effectuer un nombre surprenant de tests de rayons contre une grille stricte assez rapidement. Au lieu d'un test direct de LOS, vous pouvez "couler" (je ne peux pas pour l'instant penser au terme approprié pour l'instant; nuit de la bière) les valeurs lumineuses sur les sommets plutôt que de faire des tests de rayons également.
Sean Middleditch
L'utilisation de tests de ligne de vue compliquerait davantage cela, j'ai déjà compris l'éclairage. Maintenant, quand j'envoie les 4 sommets au shader, comment puis-je faire cela? Je sais que vous ne vous sentez pas à l'aise de donner un véritable exemple de code, mais si je pouvais obtenir un peu plus d'informations, ce serait bien. J'ai également édité la question avec plus d'informations.
Cyral