Jak uzyskać płynny efekt oświetlenia 2D?

18

Tworzę grę opartą na kafelkach 2D w XNA.

Obecnie mój piorun wygląda to .

Jak mogę dostać to wyglądać jak ten ?

Zamiast każdego bloku ma własny odcień, ma gładką nakładkę.

Zakładam jakiś moduł cieniujący i przekazuję do modułu cieniującego wartości oświetlenia dla sąsiadujących kafelków, ale jestem początkującym z modułami cieniującymi, więc nie jestem pewien.

Moje obecne oświetlenie oblicza światło, a następnie przekazuje je do a SpriteBatchi rysuje za pomocą parametru odcienia. Każdy kafelek ma wartość Colorobliczoną przed narysowaniem w moim algorytmie oświetlenia, który jest używany dla odcienia.

Oto przykład tego, jak obecnie obliczam oświetlenie (robię to również od lewej, prawej i od dołu, ale naprawdę mam dość robienia tego klatka po klatce ...)

wprowadź opis zdjęcia tutaj

Tak naprawdę zdobycie i narysowanie światła do tej pory nie stanowi problemu !

Widziałem niezliczoną ilość samouczków na temat mgły wojny i używania gradientowych okrągłych nakładek do tworzenia gładkiej błyskawicy, ale już mam dobrą metodę przypisywania każdej płytce wartości błyskawicy, wystarczy ją wygładzić między nimi.

Więc do przeglądu

  • Oblicz oświetlenie (gotowe)
  • Draw Tiles (Gotowe, wiem, że będę musiał go zmodyfikować dla modułu cieniującego)
  • Cieniowanie kafelków (Jak przekazywać wartości i stosować „gradient”)
Cyral
źródło

Odpowiedzi:

6

Co powiesz na coś takiego?

Nie rysuj swojego oświetlenia, przyciemniając duszki kafelkowe. Narysuj nieoświetlone kafelki na celu renderowania, a następnie narysuj światła kafelków na drugim celu renderowania, reprezentując każdy z nich jako prostokąt w skali szarości pokrywający obszar kafelka. Aby wyrenderować ostatnią scenę, użyj modułu cieniującego do połączenia dwóch celów renderowania, przyciemniając każdy piksel pierwszego zgodnie z wartością drugiego.

To da dokładnie to, co masz teraz. To ci nie pomaga, więc zmieńmy to trochę.

Zmień wymiary celu renderowania lightmapy, tak aby każdy kafelek był reprezentowany przez pojedynczy piksel , a nie prostokątny obszar. Podczas komponowania końcowej sceny użyj stanu próbnika z filtrowaniem liniowym. W przeciwnym razie pozostaw wszystko inne bez zmian.

Zakładając, że poprawnie napisałeś moduł cieniujący, lightmap powinien być skutecznie „powiększany” podczas komponowania. Dzięki temu uzyskasz ładny efekt gradientu za darmo za pomocą próbnika tekstur urządzenia graficznego.

Być może będziesz w stanie wyciąć moduł cieniujący i zrobić to prościej z „zaciemniającym” stanem BlendState, ale musiałbym z nim poeksperymentować, zanim przedstawię ci szczegóły.

AKTUALIZACJA

Miałem dzisiaj trochę czasu, żeby sobie z tego wyśmiewać. Powyższa odpowiedź odzwierciedla mój nawyk używania shaderów jako mojej pierwszej odpowiedzi na wszystko, ale w tym przypadku nie są one faktycznie konieczne, a ich użycie niepotrzebnie komplikuje rzeczy.

Jak zasugerowałem, możesz osiągnąć dokładnie ten sam efekt za pomocą niestandardowego BlendState. W szczególności ten niestandardowy stan BlendState:

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

Równanie mieszania jest

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

Dzięki naszemu niestandardowemu programowi BlendState tak się stanie

result = (lightmapColor * destinationColor) + (0)

Co oznacza, że ​​kolor źródłowy czystej bieli (1, 1, 1, 1) zachowa kolor docelowy, kolor źródłowy czystej czerni (0, 0, 0, 1) przyciemni kolor docelowy do czystej czerni i dowolne odcień szarości pomiędzy nimi przyciemni docelowy kolor o średnią wartość.

Aby zastosować to w praktyce, najpierw zrób wszystko, co musisz zrobić, aby stworzyć lightmap:

var lightmap = GetLightmapRenderTarget();

Następnie po prostu narysuj nieoświetloną scenę bezpośrednio w buforze backbuff, jak zwykle:

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

Następnie narysuj lightmap za pomocą niestandardowego 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();

Spowoduje to pomnożenie koloru docelowego (nieoświetlone płytki) przez kolor źródłowy (lightmap), odpowiednie przyciemnienie nieoświetlonych płytek i stworzenie efektu gradientu w wyniku skalowania tekstury lightmap do wymaganego rozmiaru.

Cole Campbell
źródło
Wydaje się to dość proste. Zobaczę, co mogę zrobić później. Próbowałem wcześniej czegoś prostszego (zrenderowałem mapę świetlną nad wszystkim innym i użyłem modułu cieniującego rozmycie, ale nie mogłem sprawić, by renderTarget był „przezroczysty”, miał purpurową nakładkę, ale chciałem, aby przejrzał warstwę płytki
aktualizacyjnej
Po ustawieniu celu renderowania na urządzeniu wywołaj device.Clear (Color.Transparent);
Cole Campbell
Chociaż w tym przypadku warto wskazać, że twoja mapa świetlna powinna być prawdopodobnie wyczyszczona na białą lub czarną, przy czym te są odpowiednio pełnym światłem i całkowitą ciemnością.
Cole Campbell
Myślę, że to właśnie próbowałem wcześniej, ale wciąż było fioletowe, więc przyjrzę się temu jutro, kiedy będę miał trochę czasu, aby nad tym popracować.
Cyral
Prawie wszystko się udało, ale zmienia kolor z czarnego na biały, zamiast czarnego na przezroczysty. Część shadera jest tym, co mnie pomyliło, jak napisać jedną, aby przyciemnić oryginalną nieoświetloną scenę do sceny nowej.
Cyral
10

Dobra, tutaj jest jedna bardzo prosta metoda stworzenia prostej i płynnej błyskawicy 2D, renderowanej w trzech przejściach:

  • Pass 1: świat gry bez błyskawicy.
  • Pass 2: Błyskawica bez świata
  • Kombinacja przebiegów 1. i 2. wyświetlanych na ekranie.

W przełęczy 1 narysujesz wszystkie duszki i teren.

W pasażu 2 narysujesz drugą grupę duszków, które są źródłami światła. Powinny wyglądać podobnie do tego:
wprowadź opis zdjęcia tutaj
Zainicjuj cel renderowania za pomocą czerni i narysuj na nim te duszki za pomocą mieszania maksymalnego lub addytywnego.

W pasażu 3 łączysz dwa poprzednie podania. Istnieje wiele różnych sposobów ich łączenia. Ale najprostszą i najmniej artystyczną metodą jest połączenie ich ze sobą za pomocą Multiply. Wyglądałoby to tak:
wprowadź opis zdjęcia tutaj

API-Beast
źródło
Chodzi o to, że mam już system oświetlenia, a światła punktowe nie działają tutaj, ponieważ niektóre obiekty potrzebują światła, a niektóre nie.
Cyral,
2
To trudniejsze. Musisz użyć ray-cast, aby uzyskać cienie. Teraria używa dużych bloków dla swojego terenu, więc nie ma problemu. Możesz użyć nieprecyzyjnego rzutowania promieniami, ale nie wyglądałoby to lepiej niż terraria i może pasować nawet mniej, jeśli nie zablokujesz grafiki. Zaawansowana i dobrze wyglądająca technika znajduje się w tym artykule: gamedev.net/page/resources/_/technical/…
API-Beast
2

Drugi opublikowany link wygląda jak mgła wojny , jest na to kilka innych pytań

Wygląda mi to na teksturę , w której „kasujesz” fragmenty czarnej nakładki (ustawiając alfa tych pikseli na 0), gdy gracz porusza się do przodu.

Powiedziałbym, że ta osoba musiała użyć pędzla „wymazać” w miejscu, w którym badał gracz.

Bobobobo
źródło
1
To nie jest mgła wojny, oblicza błyskawicę w każdej klatce. Gra, którą wskazałem, jest podobna do Terrarii.
Cyral
Miałem na myśli to, że można go wdrożyć podobnie jak mgła wojny
bobobobo
2

Lekkie wierzchołki (rogi między płytkami) zamiast płytek. Mieszaj oświetlenie na każdej płytce w oparciu o cztery wierzchołki.

Wykonaj testy linii wzroku dla każdego wierzchołka, aby określić, jak jest ono oświetlone (możesz albo blok zatrzymać światło lub zmniejszyć światło, np. Policzyć ile przecięć każdego wierzchołka w połączeniu z odległością, aby obliczyć wartość światła zamiast używać czysty binarny test widoczny / niewidoczny).

Podczas renderowania kafelka wyślij wartość światła dla każdego wierzchołka do GPU. Możesz łatwo użyć modułu cieniującego fragmenty, aby pobrać interpolowaną wartość światła dla każdego fragmentu w ikonce kafelka, aby płynnie oświetlić każdy piksel. Minęło już tyle czasu, odkąd dotknąłem GLSL, że nie czułbym się komfortowo, podając prawdziwą próbkę kodu, ale w pseudo-kodu byłoby to tak proste, jak:

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

Moduł cieniujący wierzchołki musi po prostu przekazać wartości wejściowe do modułu cieniującego fragmenty, nic nawet skomplikowanego.

Otrzymasz jeszcze płynniejsze oświetlenie z większą liczbą wierzchołków (powiedzmy, jeśli każda płytka jest renderowana jako cztery sub-płytki), ale może to nie być warte wysiłku w zależności od jakości, której szukasz. Spróbuj najpierw w prostszy sposób.

Sean Middleditch
źródło
1
line-of sight tests to each vertex? To będzie drogie.
ashes999
Możesz zoptymalizować za pomocą sprytu. W przypadku gry 2D dość szybko można wykonać zaskakującą liczbę testów promieni na ścisłej siatce. Zamiast prostego testu LOS można „przepłynąć” (nie mogę przez całe życie wymyślić właściwego terminu w tej chwili; noc piwa) zamiast wartości promieni nad wierzchołkami, zamiast wykonywać testy promieniowe.
Sean Middleditch
Korzystanie z testów linii wzroku skomplikowałoby to jeszcze bardziej, już wymyśliłem oświetlenie. Teraz, kiedy wysyłam 4 wierzchołki do modułu cieniującego, jak mogę to zrobić? Wiem, że nie czujesz się komfortowo, dając prawdziwą próbkę kodu, ale gdybym mógł uzyskać trochę więcej informacji, byłoby miło. Zredagowałem też pytanie, podając więcej informacji.
Cyral