Wie kann ich einen gleichmäßigen 2D-Lichteffekt erzielen?

18

Ich mache ein 2D-Kachel-basiertes Spiel in XNA.

Derzeit ist mein Blitz aussieht wie dieses .

Wie kann ich es so aussehen bekommen diese ?

Anstatt dass jeder Block einen eigenen Farbton hat, hat er eine glatte Überlagerung.

Ich gehe von einer Art Shader aus und übergebe die Beleuchtungswerte für die umgebenden Kacheln an den Shader. Ich bin jedoch ein Anfänger mit Shadern, daher bin ich mir nicht sicher.

Meine aktuelle Beleuchtung berechnet das Licht, übergibt es dann an a SpriteBatchund zeichnet mit dem Parameter „Farbton“. Jede Fliese hat eine Color, die vor dem Zeichnen in meinem Beleuchtungsalgorithmus berechnet wird, der für den Farbton verwendet wird.

Hier ist ein Beispiel, wie ich derzeit die Beleuchtung berechne (ich mache das auch von links, rechts und unten, aber ich habe es wirklich satt, dies Bild für Bild zu tun ...)

Bildbeschreibung hier eingeben

Das Licht so weit zu holen und zu zeichnen ist also kein Problem !

Ich habe unzählige Tutorials zum Thema Nebel des Krieges gesehen und Verlaufs-Kreis-Überlagerungen verwendet, um einen gleichmäßigen Blitz zu erzeugen, aber ich habe bereits eine gute Methode, um jeder Kachel einen Blitzwert zuzuweisen. Es muss nur zwischen ihnen geglättet werden.

Also zur Überprüfung

  • Beleuchtung berechnen (Fertig)
  • Kacheln zeichnen (Fertig, ich weiß, dass ich es für den Shader ändern muss)
  • Schattenkacheln (Übergeben von Werten und Anwenden eines "Farbverlaufs")
Cyral
quelle

Antworten:

6

Wie wäre es mit so etwas?

Zeichnen Sie nicht Ihre Beleuchtung, indem Sie Ihre Fliesensprites tönen. Zeichnen Sie Ihre nicht beleuchteten Kacheln auf ein Render-Ziel und zeichnen Sie dann die Kachel-Lichter auf ein zweites Render-Ziel, wobei jedes als Graustufen-Rechteck den Bereich der Kachel abdeckt. Verwenden Sie zum Rendern der endgültigen Szene einen Shader, um die beiden Renderziele zu kombinieren, und verdunkeln Sie jedes Pixel des ersten entsprechend dem Wert des zweiten.

Dies wird genau das produzieren, was Sie jetzt haben. Das hilft dir nicht, also lass es uns ein bisschen ändern.

Ändern Sie die Abmessungen Ihres Lightmap-Render-Ziels entsprechend Kachel durch ein einzelnes Pixel und nicht durch einen rechteckigen Bereich dargestellt wird. Verwenden Sie beim Zusammenstellen der endgültigen Szene einen Sampler-Status mit linearer Filterung. Ansonsten alles gleich lassen.

Vorausgesetzt, Sie haben Ihren Shader korrekt geschrieben, sollte die Lightmap beim Compositing effektiv "vergrößert" werden. Dadurch erhalten Sie über den Textur-Sampler des Grafikgeräts kostenlos einen schönen Verlaufseffekt.

Möglicherweise können Sie den Shader auch ausschneiden und dies einfacher mit einem "verdunkelnden" BlendState tun, aber ich muss damit experimentieren, bevor ich Ihnen die Details geben kann.

AKTUALISIEREN

Ich hatte heute einige Zeit, um dies tatsächlich zu verspotten. Die obige Antwort spiegelt meine Gewohnheit wider, Shader als erste Antwort auf alles zu verwenden, aber in diesem Fall sind sie eigentlich nicht notwendig und ihre Verwendung erschwert die Dinge unnötig.

Wie ich bereits angedeutet habe, können Sie mit einem benutzerdefinierten BlendState genau denselben Effekt erzielen. Speziell dieser benutzerdefinierte BlendState:

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

Die Mischungsgleichung lautet

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

So wird das mit unserem benutzerdefinierten BlendState

result = (lightmapColor * destinationColor) + (0)

Dies bedeutet, dass eine Quellfarbe in reinem Weiß (1, 1, 1, 1) die Zielfarbe beibehält, eine Quellfarbe in reinem Schwarz (0, 0, 0, 1) die Zielfarbe zu reinem Schwarz verdunkelt und alle dazwischen liegender Grauton verdunkelt die Zielfarbe um einen mittleren Betrag.

Um dies in die Praxis umzusetzen, müssen Sie zunächst alle erforderlichen Schritte ausführen, um Ihre Lightmap zu erstellen:

var lightmap = GetLightmapRenderTarget();

Dann ziehen Sie Ihre unbeleuchtete Szene einfach wie gewohnt direkt in den Backbuffer:

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

Dann zeichne die Lightmap mit dem benutzerdefinierten BlendState:

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();

Dadurch wird die Zielfarbe (nicht beleuchtete Kacheln) mit der Quellfarbe (Lightmap) multipliziert, und nicht beleuchtete Kacheln werden entsprechend abgedunkelt. Dadurch wird ein Verlaufseffekt erzeugt, da die Lightmap-Textur auf die erforderliche Größe skaliert wird.

Cole Campbell
quelle
Das scheint ziemlich einfach, ich werde später sehen, was ich tun kann. Ich habe zuvor etwas Simlares ausprobiert (ich habe die Lightmap über alles andere gerendert und einen Blur-Shader verwendet, aber ich konnte das renderTarget nicht "transparent" machen, es hatte eine violette Überlagerung, aber ich wollte, dass es bis zur eigentlichen Kachelebene durchschaut)
Cyral
Rufen Sie nach dem Festlegen des Renderziels auf dem Gerät device.Clear (Color.Transparent) auf.
Cole Campbell
In diesem Fall sollten Sie jedoch darauf hinweisen, dass Ihre Lightmap wahrscheinlich entweder in Weiß oder in Schwarz gelöscht werden sollte, wobei es sich um volles Licht bzw. völlige Dunkelheit handelt.
Cole Campbell
Ich denke, das ist es, was ich zuvor versucht habe, aber es war immer noch lila. Ich werde es morgen untersuchen, wenn ich etwas Zeit habe, daran zu arbeiten.
Cyral
Fast hat das alles geklappt, aber es verblasst von schwarz nach weiß, anstatt von schwarz nach transparent. Der Shader-Teil ist, worüber ich verwirrt bin, wie man einen schreibt, um die ursprüngliche unbeleuchtete Szene gegen die der neuen abzudunkeln.
Cyral
10

Okay, hier ist eine sehr einfache Methode, um einen einfachen und gleichmäßigen 2D-Blitz zu erstellen, der in drei Durchgängen gerendert wird:

  • Pass 1: Die Spielwelt ohne Blitz.
  • Pass 2: Der Blitz ohne die Welt
  • Kombination des 1. und 2. Durchlaufs, der auf dem Bildschirm angezeigt wird.

In Pass 1 zeichnest du alle Sprites und das Terrain.

In Durchgang 2 zeichnen Sie eine zweite Gruppe von Sprites, die die Lichtquellen sind. Sie sollten ungefähr so ​​aussehen:
Bildbeschreibung hier eingeben
Initialisieren Sie das Render-Ziel mit Schwarz und zeichnen Sie diese Sprites mit Maximum oder Additiv-Überblendung darauf.

In Durchgang 3 kombinieren Sie die beiden vorherigen Durchgänge. Es gibt verschiedene Möglichkeiten, wie sie kombiniert werden können. Die einfachste und am wenigsten künstlerische Methode besteht darin, sie über Multiplizieren zu mischen. Das würde so aussehen:
Bildbeschreibung hier eingeben

API-Biest
quelle
Die Sache ist, ich habe bereits ein Beleuchtungssystem und Punktlichter funktionieren hier nicht, weil einige Objekte Licht benötigen und andere nicht.
Cyral
2
Nun, das ist schwieriger. Du musst einen Raycast machen, um Schatten zu bekommen. Teraria benutzt große Blöcke für ihr Terrain, so dass das kein Problem ist. Sie könnten ungenaues Raycasting verwenden, aber das würde nicht besser aussehen als Terrarien und könnte noch weniger passen, wenn Sie keine Grafiken blockieren. Für eine fortgeschrittene und gut aussehende Technik gibt es diesen Artikel: gamedev.net/page/resources/_/technical/…
API-Beast
2

Der zweite Link, den Sie gepostet haben, sieht aus wie eine Art Nebel des Krieges. Dazu gibt es noch ein paar andere Fragen

Das sieht für mich wie eine Textur aus , bei der Sie Bits der schwarzen Überlagerung "löschen" (indem Sie das Alpha dieser Pixel auf 0 setzen), wenn der Player vorwärts geht.

Ich würde sagen, die Person muss einen Pinsel vom Typ "Löschen" verwendet haben, den der Spieler erkundet hat.

Bobobobo
quelle
1
Es ist kein Nebel des Krieges. Es berechnet den Blitz für jedes Bild. Das Spiel, auf das ich hingewiesen habe, ähnelt Terraria.
Cyral
Was ich damit gemeint habe, ist, dass es ähnlich wie Nebel des Krieges implementiert werden könnte
Bobobobo
2

Leichte Eckpunkte (Ecken zwischen Kacheln) anstelle von Kacheln. Mischen Sie die Beleuchtung über jede Kachel anhand ihrer vier Eckpunkte.

Führen Sie jeweils Sichtprüfungen durch Scheitelpunkt durch, um festzustellen, wie beleuchtet er ist (Sie können entweder festlegen, dass ein Block das gesamte Licht anhält oder das Licht verringert, z reiner binärer sichtbarer / nicht sichtbarer Test).

Senden Sie beim Rendern einer Kachel den Lichtwert für jeden Scheitelpunkt an die GPU. Mit einem Fragment-Shader können Sie problemlos den interpolierten Lichtwert für jedes Fragment im Sprite der Kachel verwenden, um jedes Pixel gleichmäßig zu beleuchten. Es ist schon lange genug her, dass ich GLSL angerührt habe, um ein echtes Codebeispiel zu geben, aber in Pseudocode wäre es so einfach wie:

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;
}

Der Vertex-Shader muss lediglich die Eingabewerte an den Fragment-Shader weitergeben, was auch nur annähernd kompliziert ist.

Sie erhalten eine noch gleichmäßigere Beleuchtung mit mehr Scheitelpunkten (z. B. wenn jede Kachel als vier Unterkacheln gerendert wird), aber das lohnt sich möglicherweise nicht, abhängig von der Qualität, die Sie anstreben. Versuchen Sie es zuerst auf einfachere Weise.

Sean Middleditch
quelle
1
line-of sight tests to each vertex? Das wird teuer.
Asche999
Sie können mit etwas Cleverness optimieren. Für ein 2D-Spiel können Sie relativ schnell eine überraschende Anzahl von Strahlentests gegen ein strenges Raster durchführen. Anstelle eines geraden LOS-Tests könnte man auch die Lichtwerte über den Scheitelpunkten "fließen" (ich kann mir im Moment nicht den richtigen Begriff vorstellen; Biernacht), anstatt auch Strahlentests durchzuführen.
Sean Middleditch
Die Verwendung von Sichtlinien-Tests würde dies noch komplizierter machen. Ich habe bereits herausgefunden, wie die Beleuchtung funktioniert. Wie kann ich nun die 4 Verticies an den Shader senden? Ich weiß, Sie fühlen sich nicht wohl, wenn Sie wirklich ein Codebeispiel geben, aber wenn ich ein bisschen mehr Informationen bekommen könnte, wäre das nett. Ich habe auch die Frage mit mehr Infos bearbeitet.
Cyral