Wydajny system oświetlenia 2D oparty na płytkach

Jaki jest najskuteczniejszy sposób na oświetlenie silnika bazującego na płytkach w Javie?
Czy byłoby to umieszczenie czarnego tła za kafelkami i zmiana Alfy płytek?
A może postawić czarny pierwszy plan I zmienić Alfę? Albo coś jeszcze?

To jest przykład takiego oświetlenia jaki chcę:
http://i.stack.imgur.com/F5Lzo.png

Author: bricklore, 2013-08-30

3 answers

Można to osiągnąć na wiele sposobów. Poświęć trochę czasu, zanim podejmiesz ostateczną decyzję. Krótko podsumuję kilka technik, których możesz użyć, i podam kod w końcu.


Twarde Oświetlenie

Jeśli chcesz stworzyć efekt oświetlenia o twardej krawędzi (jak przykładowy obraz), niektóre podejścia przychodzą mi do głowy:

przykład twardego światła

Szybko i brudno (jak sugerowałeś)

  • użyj czarnego tła
  • Ustaw wartości alfa płytek zgodnie do ich wartość

Problem polega na tym, że nie można ani rozjaśnić płytki, ani zmienić koloru światła . Oba te aspekty zwykle sprawiają, że oświetlenie w grach wygląda dobrze.

Drugi zestaw płytek

  • użyj drugiego zestawu (czarny/kolorowy) płytek
  • połóż je na głównych płytkach
  • ustaw wartość alfa nowych płytek w zależności od tego, jak silny powinien być nowy kolor tam.

Takie podejście ma taki sam efekt jak pierwsze z tą zaletą, że teraz możesz pokolorować nakładkę w innym kolorze niż czarny, co pozwala zarówno na kolorowe światła, jak i na wykonywanie podświetleń.

Przykład: Twarde Światło z czarnymi kafelkami przykład

Chociaż jest to łatwe, problem w tym, że jest to naprawdę bardzo nieefektywny sposób. (Dwa renderowane kafelki na kafelek, ciągłe ponowne kolorowanie, wiele operacji renderowania itp.)

[[6]}Bardziej Efektywne Podejścia (Twarde i / lub miękkie oświetlenie)

Patrząc na twój przykład, wyobrażam sobie, że światło zawsze pochodzi z określonego źródła (znak, Pochodnia itp.)

  • dla każdego rodzaju światła (duża latarka, Mała Latarka, oświetlenie postaci) Utwórz obraz, który przedstawia specyficzne zachowanie oświetlenia względem płytki źródłowej (Maska świetlna). Może coś takiego na latarkę (Biała jest alfa):

centered light mask

  • dla każdej płytki, która jest źródłem światła, renderujesz Ten obraz w pozycji źródła jako nakładkę.
  • aby dodać odrobinę jasnego koloru, możesz użyć np. 10% nieprzezroczystego pomarańczowego zamiast pełnego Alfa.

Wyniki

obraz Maska Twarde Światło wynik

Dodanie miękkiego światła

Miękkie światło to nic wielkiego teraz, po prostu użyj więcej szczegółów w masce świetlnej w porównaniu do płytek. Używając tylko 15% alfa w obszarze zwykle czarnym, możesz dodać efekt słabego wzroku, gdy kafelek nie jest świecony: {]}

miękkie światło

Możesz nawet łatwo osiągnąć bardziej złożone formy oświetleniowe (stożki itp.) po prostu zmieniając obraz maski.

Wiele źródeł światła

W przypadku łączenia wielu źródeł światła, takie podejście prowadzi do problemu: Rysowanie dwóch masek, które się przecinają, może się anulować: [4]}

anulowanie maski

Chcemy, żeby dodali swoje światła zamiast je odejmować. Unikanie problemu:
  • Odwróć wszystkie maski światła (przy czym alfa jest ciemna obszary, nieprzezroczyste są lekkie)
  • Renderuj wszystkie te maski świetlne w tymczasowy obraz, który ma takie same wymiary jak widok [17]}
  • odwróć i wyrenderuj nowy obraz (tak jakby był jedyną maską świetlną) na całej scenerii.

To by skutkowało czymś podobnym do tego: wynik obraz

Kod metody odwrócenia maski

Zakładając, że najpierw wyrenderujesz wszystkie kafelki w BufferedImage , Podam kod wskazujący, który przypomina ostatnio pokazaną metodę (tylko obsługa skali szarości).

Wiele masek świetlnych dla np. latarki i odtwarzacza można połączyć w następujący sposób:

public BufferedImage combineMasks(BufferedImage[] images)
{
    // create the new image, canvas size is the max. of all image sizes
    int w, h;

    for (BufferedImage img : images)
    {
        w = img.getWidth() > w ? img.getWidth() : w;
        h = img.getHeight() > h ? img.getHeight() : h;
    }

    BufferedImage combined = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

    // paint all images, preserving the alpha channels
    Graphics g = combined.getGraphics();

    for (BufferedImage img : images)
        g.drawImage(img, 0, 0, null);

    return combined;
}

Końcowa maska jest tworzona i nakładana tą metodą:

public void applyGrayscaleMaskToAlpha(BufferedImage image, BufferedImage mask)
{
    int width = image.getWidth();
    int height = image.getHeight();

    int[] imagePixels = image.getRGB(0, 0, width, height, null, 0, width);
    int[] maskPixels = mask.getRGB(0, 0, width, height, null, 0, width);

    for (int i = 0; i < imagePixels.length; i++)
    {
        int color = imagePixels[i] & 0x00ffffff; // Mask preexisting alpha

        // get alpha from color int
        // be careful, an alpha mask works the other way round, so we have to subtract this from 255
        int alpha = (maskPixels[i] >> 24) & 0xff;
        imagePixels[i] = color | alpha;
    }

    image.setRGB(0, 0, width, height, imagePixels, 0, width);
}

Jak zauważono, jest to prymitywny przykład. Wdrożenie mieszania kolorów może być nieco bardziej pracochłonne.

 24
Author: bricklore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-27 12:03:55

Raytracing może być najprostszym podejściem.

  • możesz zapisać, które kafelki były widoczne (używane do automatycznego mapowania, używane do "zapamiętaj mapę będąc oślepionym", może do minimapy itp.)
  • pokazujesz tylko to , co widzisz-może Potwór ze ściany lub wzgórza blokuje twój widok, wtedy raytracing zatrzymuje się w tym momencie
  • odległe "jarzące się obiekty" lub inne źródła światła (pochodnie) mogą być widoczne, nawet jeśli własne źródło światła nie sięga zbyt daleko.
  • the długość twojego promienia będzie używana do sprawdzania ilości światła (blaknięcie światła)
  • może masz specjalny czujnik (ESP, gold/food detection), który będzie używany do znajdowania przedmiotów, które nie są w Twoim widoku? raytrace też może pomóc ^^
Jak to się robi łatwo?
  • narysuj linię od gracza do każdego punktu granicy mapy (za pomocą algorytmu Bresehhams http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm spacer wzdłuż tej linii (z twojego w tym momencie przerwij wyszukiwanie (a może wykonaj ostatnią iterację, aby zobaczyć, co Cię przebiło)
  • dla każdego punktu na linii Ustaw oświetlenie (może 100% dla odległości 1, 70% Dla odległości 2 i tak dalej) i oznacz kafelek Mapy jako odwiedzony

Może nie będziesz chodził po całej mapie, może wystarczy, jeśli ustawisz raytrace na widok 20x20? Uwaga: naprawdę trzeba chodzić wzdłuż granic viewport, jego nie jest wymagane do śledzenia każdy punkt.

Dodaję algorytm liniowy, aby uprościć Twoją pracę:

public static ArrayList<Point> getLine(Point start, Point target) {
    ArrayList<Point> ret = new ArrayList<Point>();
    int x0 =  start.x;
    int y0 =  start.y;

    int x1 = target.x;
    int y1 = target.y;

    int sx = 0;
    int sy = 0;

    int dx =  Math.abs(x1-x0);
    sx = x0<x1 ? 1 : -1;
    int dy = -1*Math.abs(y1-y0);
    sy = y0<y1 ? 1 : -1; 
    int err = dx+dy, e2; /* error value e_xy */

    for(;;){  /* loop */
        ret.add( new Point(x0,y0) );
        if (x0==x1 && y0==y1) break;
        e2 = 2*err;
        if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
        if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
    }

    return ret;
}

Zrobiłem to całe błyskawice jakiś czas temu, a * pathfindin nie krępuj się zadawać dalszych pytań

Appendum: może po prostu dodam małe algorytmy raytracingu ^^

Aby dostać się do Północnej i południowej granicy wystarczy użyć tego fragmentu:

for (int x = 0; x <map.WIDTH; x++){
    Point northBorderPoint = new Point(x,0);
    Point southBorderPoint = new Point(x,map.HEIGHT);

    rayTrace( getLine(player.getPos(), northBorderPoint), player.getLightRadius()) );
    rayTrace( getLine(player.getPos(), southBorderPoint, player.getLightRadius()) );
}

A raytrace działa tak:

private static void rayTrace(ArrayList<Point> line, WorldMap map, int radius) {

    //int radius = radius from light source     
    for (Point p: line){

        boolean doContinue = true;
        float d = distance(line.get(0), p);

        //caclulate light linear 100%...0% 
        float amountLight = (radius - d) / radius;  
        if (amountLight < 0 ){
            amountLight = 0;
        }

        map.setLight( p, amountLight );

        if ( ! map.isViewBlocked(p) ){ //can be blockeb dy wall, or monster  
            doContinue = false;
            break;
        }

    }
}
 11
Author: Martin Frank,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-12 12:47:15

[5]}zajmuję się tworzeniem gier niezależnych od około trzech lat. Sposób, w jaki bym to zrobił, to przede wszystkim użycie OpenGL, dzięki czemu można uzyskać wszystkie zalety mocy obliczeniowej graficznej GPU (mam nadzieję, że już to robisz). Załóżmy, że zaczniemy od wszystkich płytek w VBO, całkowicie oświetlonych. Teraz istnieje kilka opcji osiągnięcia tego, co chcesz. W zależności od złożoności systemu oświetleniowego można wybrać inne podejście.

  • Jeśli Twoje światło jest będzie okrągły wokół gracza, bez względu na to, czy przeszkody blokują światło w prawdziwym życiu, można wybrać dla algorytmu oświetlenia zaimplementowanego w Vertex shader. W cieniowaniu wierzchołków można obliczyć odległość wierzchołka od odtwarzacza i zastosować jakąś funkcję, która określa, jak jasne rzeczy powinny być w funkcji obliczonej odległości. Nie używaj Alfy, ale po prostu pomnóż kolor tekstury / płytki przez wartość oświetlenia.

  • Jeśli chcesz użyć niestandardowa Mapa świetlna (co jest bardziej prawdopodobne), sugerowałbym dodanie dodatkowego atrybutu wierzchołka, który określa jasność płytki. Zaktualizuj VBO w razie potrzeby. To samo podejście idzie tutaj: pomnóż piksel tekstury przez wartość światła. Jeśli wypełniasz światło rekurencyjnie z pozycji gracza jako punktu wyjścia, następnie aktualizować VBO za każdym razem gracz porusza.

  • Jeśli Twoja mapa świetlna zależy od tego, gdzie światło słoneczne uderza w twój poziom, możesz połączyć dwa rodzaje techniki oświetleniowe. Utwórz jeden atrybut wierzchołka dla jasności Słońca i inny atrybut wierzchołka dla światła emitowanego przez punkty świetlne (jak pochodnia trzymana przez gracza). Teraz możesz połączyć te dwie wartości w cieniowaniu wierzchołków. Przypuśćmy, że Twoje słońce wschodzi i zachodzi jak dzień i noc. Załóżmy, że jasność Słońca wynosi sun, co jest wartością między 0 a 1. Wartość ta może być przekazana do modułu cieniującego wierzchołki jako jednolita. Atrybut wierzchołka, który reprezentuje jasność Słońca to s, a dla światła emitowanego przez punkty świetlne to l. Następnie możesz obliczyć całkowite światło dla tej płytki w następujący sposób:

    tileBrightness = max(s * sun, l + flicker);
    

    Gdzie flicker (również vertex shader uniform) jest rodzajem funkcji falującej, która reprezentuje małe warianty jasności punktów świetlnych.
    Takie podejście sprawia, że scena jest dynamiczna bez konieczności ciągłego odtwarzania VBO. zaimplementowałem to podejście w projekcie proof-of-concept. Działa świetnie. Możesz sprawdzić jak to wygląda jak tutaj: http://www.youtube.com/watch?v=jTcNitp_IIo . zauważ, jak latarka miga o 0: 40 na filmie. Dzieje się tak dzięki temu, co tutaj wyjaśniłem.

 1
Author: Martijn Courteaux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-11 23:04:09