středa 6. listopadu 2013

Oheň ve 2D

V příspěvku si ukážeme dvě techniky, kterými můžeme docílit zajímavého efektu hoření. Přidáme též implementaci v C++ s použitím knihovny SDL s nástavbou QuickCG.

Základní myšlenka

Na začátku je třeba vygenerovat paletu barev, kterou použijeme pro náš oheň. Vybereme 300 odstínů, které budou plynule přecházet od bílé přes oranžovou, červenou až do černé.



Poté vyplníme spodní řádek obrázku náhodně černou a bílou barvou a odshora dolů začneme ostatní pixely počítat dle pravidel uvedených níže. Budeme používat následující pojmenování pixelů



Implementace

Na začátku deklarujeme proměnné a definujeme konstanty. Také vytvoříme okno.
#include <cmath>

#include "quickcg.h"

using namespace QuickCG;

const int POCET_BAREV = 300;

const int OBRAZOVKA_SIRKA = 400;
const int OBRAZOVKA_VYSKA = 400;

const int CERNA_R = 0;
const int CERNA_G = 0;
const int CENRA_B = 0;

const int CERVENA_R = 255;
const int CERVENA_G = 0;
const int CERVENA_B = 0;

const int ORANZOVA_R = 255;
const int ORANZOVA_G = 170;
const int ORANZOVA_B = 0;

const int BILA_R = 255;
const int BILA_G = 255;
const int BILA_B = 255;

const int PALIVO = 25;

int main(int argc, char *argv[])
{
    ColorRGB barva;
    Uint32 ohen[OBRAZOVKA_SIRKA][OBRAZOVKA_VYSKA];
    Uint32 buffer[OBRAZOVKA_SIRKA][OBRAZOVKA_VYSKA];
    Uint32 paleta[POCET_BAREV];
    float inkrement1;
    float inkrement2;
    int novaBarva;
    int nahodneCislo;

    //inicializuj obrazovku
    screen(OBRAZOVKA_SIRKA, OBRAZOVKA_VYSKA, 0, "ohen");
[/sourcecode]
Teď si vynulujeme pole s budoucími hodnotami ohně.
[sourcecode language='c++']
    //nastav výchozí hodnoty pro oheň
    for(int x = 0; x < w; x++)
    {
        for(int y = 0; y < h; y++)
        {
            ohen[x][y] = 0;
        }
    }
Čas na definování palety. První třetina bude přechod od černé do červené, druhá od červené do oranžové a třetí od oranžové do bílé.
    //barvy 0 - 99
    barva.g = CERVENA_G;
    barva.b = CERVENA_B;

    inkrement1 = (float)CERVENA_R / (float)99;

    for(int i = 0; i < 100; i++)
    {
        barva.r = ceil(inkrement1 * i);
        paleta[i] = RGBtoINT(barva);
    }

    //barvy 100 - 199
    barva.r = ORANZOVA_R;
    barva.b = ORANZOVA_B;

    inkrement1 = (float)ORANZOVA_G / (float)99;

    for(int i = 100; i < 200; i++)
    {
        barva.g = ceil(inkrement1 * (i - 99));
        paleta[i] = RGBtoINT(barva);
    }

    //barvy 200 - 299
    barva.r = BILA_R;

    inkrement1 = (float)(BILA_G - ORANZOVA_G) / (float)99;
    inkrement2 = (float)(BILA_B) / (float)99;

    for(int i = 200; i < 300 - 1; i++)
    {
        barva.g = ORANZOVA_G + ceil(inkrement1 * (i - 199));
        barva.b = ceil(inkrement2 * (i - 199));
        paleta[i] = RGBtoINT(barva);
    }
Vše je připraveno pro spuštění animace. Pokračujme tedy smyčkou a spouštěcím mechanismem. Na začátku musíme vygenerovat první řádek dle jednoduchého předpisu. Pokud náhodné číslo v intervalu <0> bude 0, pak na dané pozici dáme černý pixel. Pokud 1 tak bílý a pokud cokoliv jiného necháme ho beze změny.
    while(!done())
    {
        //nastav palivo
        for(int x = 0; x < w; x++)
        {
            nahodneCislo = rand() % PALIVO;

            if (nahodneCislo == 1)
            {
                ohen[x][h - 1] = 299;
            }
            else if (nahodneCislo == 0)
            {
                ohen[x][h - 1] = 0;
            }
        }
Následující část představuje samotný výpočet. Vložte kód jedné z následujících technik.
...
Zbývá už jen definování bufferu a jeho vykreslení na obrazovku.
        //nastav buffer pro vykreslení
        for(int x = 0; x < w; x++)
        {
            for(int y = 0; y < h - 1; y++)
                {
                    buffer[x][y] = paleta[ohen[x][y]];
                }
        }

        //vykresli buffer
        drawBuffer(buffer[0]);
        redraw();
    }

    return 0;
}

Technika první

Pro každý pixel vezmeme tři, které leží bezprostředně pod ním. Z těchto tří uděláme aritmetický průměr a výslednou hodnotu uložíme do počítaného pixelu. \[ I_n=(I_a + I_b + I_c)/3 \]



Následuje kód této techniky.
        //vypočítej hodnoty pixelů na obrazovce
        for(int y = 0; y < h - 1; y++)
        {
            for(int x = 1; x < w - 1; x++)
            {
                novaBarva = (ohen[x-1][y+1] + ohen[x][y+1] + ohen[x+1][y+1]) / 3;

                if (novaBarva > 0)
                {
                    novaBarva = novaBarva - 1;
                }

                ohen[x][y] = novaBarva;
            }
        }

Technika druhá

Pro každý pixel vezmeme hodnotu tří, které leží pod ním, stejně jako v předchozím případě a přidáme i původní hodnotu počítaného. Vydělíme čtyřmi a výsledek použijeme. \[ I_n=(I_a + I_b + I_c + I_n)/4 \]



        //vypočítej hodnoty pixelů na obrazovce
        for(int y = 0; y < h - 1; y++)
        {
            for(int x = 1; x < w - 1; x++)
            {
                novaBarva = (ohen[x-1][y+1] + ohen[x][y+1] + ohen[x+1][y+1]  + ohen[x][y]) / 4;

                if (novaBarva > 0)
                {
                    novaBarva = novaBarva - 1;
                }

                ohen[x][y] = novaBarva;
            }
        }

Dodatek

Všimněte si, že je nutné odečíst z indexu barvy 1, pokud je to možné. Tímto simulujeme postupné slábnutí plamenů, protože čím menší hodnotu barva má, tím jde více do černa. Můžete zkusit co se stane, když jedničku odečítat nebudete.

Literatura a odkazy

Videa a článek co mě inspirovaly
http://www.youtube.com/watch?v=NSI4KKjYS_w - porovnání obou přístupů
http://www.youtube.com/watch?v=_SzpMBOp1mE - jednoduchá implementace spolu s mluveným komentářem, appletem a zdrojovými kódy
http://www.youtube.com/watch?v=iezD8B1ym3w - vylepšení implementace z předchozího videa
http://www.academictutorials.com/graphics/graphics-fire-effect.asp - další z možností jak naprogramovat oheň

Zdrojový kód
Stahujte zde.

Žádné komentáře:

Okomentovat