3. díl - SDL - Základy vykreslování

C a C++ SDL SDL - Základy vykreslování

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V dnešním díle se dozvíme něco o vykreslování pomocí knihovny SDL. Řekneme si základní pojmy a poté vykreslíme obdélník, se kterým budeme posouvat po obrazovce.

SDL_Rect

Předtím, než se vrhneme na vykreslování, zmíním strukturu SDL_Rect. Již z názvu vyplývá, že reprezentuje obdélník. Má jen čtyři atributy, které jsou x a y (určení pozice levého horního rohu), a dále w a h, které určují šířku a výšku obdélníku. SDL_Rect se používá často k určení zdrojového nebo cílového obdélníku vykreslování. Vždy, když nechceme vykreslit barvou celé okno, použijeme SDL_Rect.

SDL_Renderer, SDL_Texture a SDL_Surface

Jedná se o tři struktury, které jsou svým významem velmi podobné, ale nejsou shodné. Protože budeme potřebovat všechny tři, každou si popíšeme.

SDL_Renderer je struktura, která se stará o samotné vykreslování. Zpravidla je svázaná s určitým SDL_Window, do kterého vykresluje (může být svázán i s SDL_Surface). S SDL_Renderer jsou spojeny funkce vykreslující jednotlivé elementy (SDL_RenderDraw­Point, SDL_RenderDraw­Line, SDL_RenderDraw­Rect), ale také funkce, které obsah kopírují (SDL_RenderCopy). Nejprve budeme využívat funkce, které vykreslují pouze určité elementy, v dalším díle se podíváme na funkce, které obraz kopírují. Vlastně se jedná o prostředníka, skrze kterého budeme vykreslovat.

SDL_Texture je struktura, která ukládá obraz - informace o jednotlivých pixelech. Je hardwarově specifická a je uložena v paměti grafické karty. Zde se dostáváme k hardwarově-akcelerovanému vykreslování. Samotné operace, které s ní můžeme dělat, jsou značně omezené, užívá se především jako zdroj pro další vykreslování. Do textury můžeme například uložit obrázek pozadí. Při každém vykreslení poté první zkopírujeme tuto texturu do výsledného obrazu, a teprve poté začneme vykreslovat jednotlivé prvky scény.

SDL_Surface má podobnou funkci jako SDL_Texture, ale v tomto případě se jedná o strukturu softwarovou. Je uložena v RAM paměti a můžeme s ní více pracovat. V proměnných x a y má uložené své rozměry, ukazatel pixels poté obsahuje adresu samotných pixelů. Doporučuji ale pixely neupravovat ručně. Ve většině případů to ani není nutné. SDL poskytuje několik funkcí, které nám dovolují s SDL_Surface manipulovat. Mezi nejdůležitější bych zařadil SDL_FillRect, která vykreslí obdélník. Další velmi důležitou funkcí je SDL_BlitSurface, která zkopíruje data z jedné SDL_Surface do druhé. Poslední zajímavou funkcí, kterou bych zmínil, je SDL_SetColorKey. Ta nastaví barvu, která se nebude vykreslovat. Většina obrázků (pozadí, postavy – setkáme se s nimi později) má určitou barvu pozadí. Pokud tuto barvu ve funkci nastavíme, nebude se vykreslovat. Například následující obrázek má červenou barvu pozadí (získáno zde).

Postava s pozadím

Spojitost mezi SDL_Surface, SDL_Texture a SDL_Renderer

Ačkoliv každá struktura slouží k jinému účelu, jistá spojitost mezi nimi je. Řekněme si o pár funkcích, které budeme potřebovat.

Z SDL_Surface můžeme vytvořit SDL_Texture pomocí funkce SDL_CreateTex­tureFromSurfa­ce, která převede SDL_Surface na SDL_Texture. Tím dostaneme obraz, který byl v SDL_Surface do paměti grafické karty, která s ním může pracovat rychleji. Využívá se toho v případech, kdy vykreslujeme něco složitého, ale už to nepotřebujeme překreslovat. Nejprve vytvoříme SDL_Surface, kde vše vykreslíme, a poté to převedeme do SDL_Texture.

Můžeme také vytvořit SDL_Renderer, který bude navázaný na určitý SDL_Surface. Využijeme k tomu funkci SDL_CreateSof­twareRenderer, která SDL_Renderer vytvoří. Všechny operace se poté budou provádět na SDL_Surface, kterou jsme předali v parametru. Pokud budeme chtít, aby byl SDL_Renderer navázaný na určitý SDL_Texture, využijeme funkce SDL_SetRenderer­Target. V tuto chvíli bude SDL_Renderer vykreslovat na SDL_Texture. Nesmíme ale zapomenout, že SDL_Renderer musí být vytvořen s flagem SDL_RENDERER_TARGETTEXTURE.

Zároveň stojí za pozornost, že SDL_RenderCopy přijímá jako parametr SDL_Texture. Z toho plyne, že není možné vykreslovat na obrazovku z SDL_Surface. Pokud budeme potřebovat, aby se SDL_Surface vykreslila na obrazovku, musíme z ní vytvořit SDL_Texture a následně vykreslit přes SDL_Renderer.

Poslední věc, kterou zmíním, je možnost získat SDL_Surface ze SDL_Window. Poté jakákoliv úprava provedená na tento SDL_Surface bude provedena také na obrazovce. Jediná věc, na kterou nesmíme zapomenout, je zavolat funkci SDL_UpdateWin­dowSurface. Ta zobrazí všechny změny, které jsme provedli. Proč vlastně máme dva způsoby vykreslování? Struktura SDL_Surface je softwarová. Všechny operace provádí procesor. Na rozdíl od toho SDL_Renderer a SDL_Texture jsou struktury hardwarově závislé a operace probíhají v grafické kartě. To znamená, že jsou rychlejší a efektivnější. Proto bychom měli používat hlavně hardwarově-akcelerované funkce, které tak nezatěžují procesor a jsou na grafické kartě provedeny rychleji.

Zobrazení vykreslení

Poslední část teorie, kterou budeme potřebovat, je funkce pro zobrazení vykreslení. Obraz nemůže být zároveň zobrazen a měnit se – nemůžeme do něj vykreslovat. Obraz je proto v grafické kartě uložen dvakrát. Jeden je ten, který se zobrazuje, a druhý ten, na který vykreslujeme. Tato technika se nazývá double buffering. Obrazy se můžou řadit i do fronty (triple buffering), ale SDL pro to podporu nemá. Není ovšem těžké tuto techniku naimplementovat. O tyto prostory se stará sám SDL_Renderer. Ona zmiňovaná funkce je SDL_RenderPre­sent. Ta udělá pouze to, že zkopíruje obraz, který jsme měnili, a vloží jej na místo obrazu, který je zobrazen na obrazovce. Poté můžeme dál měnit první obraz, aniž bychom narušovali zobrazování aktuálního obrazu. Pokud budeme chtít docílit vyšší efektivity, můžeme vytvořit SDL_Renderer s flagem SDL_RENDERER_PRESENTVSYNC, který bude synchronizovat zmíněnou operaci tak, aby odpovídala obnovovací frekvenci monitoru (zpravidla 60Hz).

Vykreslení obdélníku

Nejprve budeme potřebovat obdélník, který budeme vykreslovat. Vytvoříme si tedy SDL_Rect a nastavíme jeho proměnné na libovolnou velikost. Poté vytvoříme nový SDL_Renderer pro okno. Tyto dvě operace provedeme ještě před vstupem do hlavní smyčky, jinak bychom je vytvářeli v každém průchodu a by bylo zbytečné..

SDL_Rect* rect = new SDL_Rect;
rect->x = rect->y = rect->w = rect->h = 100;
SDL_Renderer* renderer = SDL_CreateRenderer(MainWindow,-1,0);

Ze vstupu budeme zachytávat stisk kláves. Pro začátek nám bude stačit reagovat pouze na šipky. Šipkou nahoru obdélník posuneme nahoru a obdobně pro ostatní směry. Jestliže se jednalo o stisk klávesy, bude tato struktura uložena v atributu key, v ní se podíváme do informací o klávesách (keysym) a konkrétně nás bude zajímat kód klávesy (sym). V něm jsou uloženy hodnoty z SDL_Keycode. My budeme reagovat pouze v případě, kdy se jednalo o šipky (SDLK_RIGHT, SDLK_LEFT, SDLK_UP, SDLK_DOWN). Výsledný kód pro vstup bude vypadat následovně:

while (SDL_PollEvent(event))                            //načtení události z fronty
{
        if (event->type == SDL_QUIT)                    //manipulace s událostí
                End = true;                             //ukončení aplikace
        else if (event->type == SDL_KEYDOWN)            //reagování na stisk klávesy
        {
                switch (event->key.keysym.sym)
                {
                case SDLK_RIGHT:
                        rect->x += 1; break;
                case SDLK_LEFT:
                        rect->x -= 1; break;
                case SDLK_UP:
                        rect->y -= 1; break;
                case SDLK_DOWN:
                        rect->y += 1; break;
                }
        }
}

Nakonec ještě obdélník vykreslíme. Nejprve nastavíme barvu, jakou budeme obdélník vykreslovat. Já si zvolil červenou. Barvu nastavíme funkcí SDL_SetRender­DrawColor. Druhý až čtvrtý parametr slouží k nastavení barvy klasicky ve formátu RGB, poslední je alfa kanál – tedy průhlednost. Všechny tyto parametry přijímají hodnotu 0 až 255. Obdélník vykreslíme funkcí SDL_RenderFillRec­t. Nakonec musíme aktualizovat obraz, proto použijeme funkci SDL_RenderPresent. To je vše, nyní program spustíme.

SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
SDL_RenderFillRect(renderer, rect);
SDL_RenderPresent(renderer);
Vykresleni bez pozadí

Proč nám obdélník nemizí? Protože vždy vykreslíme nový obdélník, do již existujícího obrazu. Pokud chceme docílit toho, aby nám obdélník zmizel, budeme muset celou obrazovku překreslit. V tomto případě ovšem nevyužijeme funkci SDL_RenderFillRect, ale SDL_RenderClear, která přemaluje celou plochu. Proto opět nastavíme barvu (SDL_SetRenderDrawColor). Zvolíme například na modrou a poté zavoláme SDL_RenderClear. Konečný kód pro vykreslení je následovný:

SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
SDL_RenderFillRect(renderer, rect);
SDL_RenderPresent(renderer);
Vykreslení obdelníku

Obdélník mizí a pozadí máme modré. Přesně jak jsme chtěli.

To je pro tento díl vše. Příště se podíváme na práci s obrázky, práci s texturami a kopírování bloků paměti.


 

Stáhnout

Staženo 258x (3.34 MB)

 

  Aktivity (1)

Článek pro vás napsal patrik.valkovic
Avatar
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu.

Jak se ti líbí článek?
Celkem (3 hlasů) :
55555


 


Miniatura
Všechny články v sekci
SDL
Miniatura
Následující článek
SDL - Práce s obrázky

 

 

Komentáře
Zobrazit starší komentáře (5)

Avatar
rikenbekr
Člen
Avatar
rikenbekr:

Takže to co mi dělá problemy :D

Odpovědět 30.8.2015 9:14
In world without fences and walls, who needs Gates and Windows?
Avatar
rikenbekr
Člen
Avatar
rikenbekr:

Mám problém po pokusu zkompilovat tento zdroják gcc vypíše to pod tím.
Proč to nefunguje?
Předchozí příklady fungovali.

#include <stdio.h>
#include <SDL2/SDL.h>

int main(void)
{
        SDL_Init(SDL_INIT_VIDEO);

        SDL_Window *window;
        SDL_Event event;
        SDL_Renderer* renderer;
        SDL_Rect *p_rect;
        SDL_Rect rect;

        p_rect = &rect;

        window = SDL_CreateWindow("SDL3", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_FULLSCREEN);

        rect.x = 100;
        rect.y = 100;
        rect.w = 200;
        rect.h = 100;

        renderer = SDL_CreateRenderer(window, -1, 0);

        while(SDL_PollEvent(&event)){
                if(event.type == SDL_QUIT){
                        SDL_Quit();
                        return 0;
                }
                else if(event.type == SDL_KEYDOWN){
                        switch(event.key.keysym.sym){
                        case SDLK_DOWN :
                                        rect.x-=1;
                                        break;
                        case SDLK_UP :
                                        rect.x+=1;
                                        break;
                        case SDLK_LEFT :
                                        rect.y-=1;
                                        break;
                        case SDLK_RIGHT :
                                        rect.y+=1;
                                        break;
                        default :
                                        break;
                        }
                        SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
                        SDL_RenderClear(renderer);
                        SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
                        SDL_RenderFillRect(renderer, p_rect);
                        SDL_RenderPresent(renderer);
                }
                else
                        ;
        }
        return 0;
}



ikar@henryposer:~$ cd programovani/c_c++/c/pokusy/SDL/itnetwork3
ikar@henryposer:~/programovani/c_c++/c/pokusy/SDL/itnetwork3$ gcc SDL_render.c `sdl-config --libs --cflags` -o SDL_render
/tmp/cckkpUVS.o: In function `main':
SDL_render.c:(.text+0x3b): undefined reference to `SDL_CreateWindow'
SDL_render.c:(.text+0x71): undefined reference to `SDL_CreateRenderer'
SDL_render.c:(.text+0x117): undefined reference to `SDL_SetRenderDrawColor'
SDL_render.c:(.text+0x123): undefined reference to `SDL_RenderClear'
SDL_render.c:(.text+0x144): undefined reference to `SDL_SetRenderDrawColor'
SDL_render.c:(.text+0x157): undefined reference to `SDL_RenderFillRect'
SDL_render.c:(.text+0x163): undefined reference to `SDL_RenderPresent'
collect2: error: ld returned 1 exit status
ikar@henryposer:~/programovani/c_c++/c/pokusy/SDL/itnetwork3$
Editováno 25.9.2015 11:25
Odpovědět 25.9.2015 11:25
In world without fences and walls, who needs Gates and Windows?
Avatar
patrik.valkovic
Šéfredaktor
Avatar
Odpovídá na rikenbekr
patrik.valkovic:

Napadají mě dvě věci. První je, že nemáš nareferencované knihovny (SDL2.lib, SDL2main.lib) a proto je GCC nemůže najít.
Ze zápisu to vypadá, že se jedná o konzolovou aplikaci. Před tím, než začneš volat funkce SDL musíš zavolat SDL_SetMainRe­ady(int,char**). Ve druhém případě zvol výstup aplikace na okno a main nahraď int SDL_main(int paramn,char** params). Nicméně v obou případech bys měl předat parametry, které se předávají při spuštění (int,char**).
Snad ti to pomůže.

Odpovědět 25.9.2015 11:31
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
rikenbekr
Člen
Avatar
Odpovídá na patrik.valkovic
rikenbekr:

No bylo to tím že místo sdl-config --libs --cflags má být sdl**2**-config --libs --cflags .
Je to program pro konzoli protože : a) snáže přídu na to co je špatně
b) SDL_main z nějakého důvodu nefungovala
SDL_SetMainReady() tam sice nebyla, ale zjistil jsem že na funkčnost tohoto programu nemá vliv.

Ve druhém případě zvol výstup aplikace na okno

Nechápu jak a kde?
Pouze tím že místo main() bude SDL_main()

Odpovědět 25.9.2015 13:55
In world without fences and walls, who needs Gates and Windows?
Avatar
patrik.valkovic
Šéfredaktor
Avatar
Odpovídá na rikenbekr
patrik.valkovic:

Nevím jak je to u GNN, ale ve VS (jak bylo popisování v prvním díle) může nastavit SubSystem na Console, Window a Library. Podle toho se taky aplikace chová. Pokud jsi nechal SubSystem jako Console a chtěl jsi použít SDL_main, tak to nefunguje, protože OS explicitně nevolá funkci int main, ale (napříkald pro Windows) HRESULT WINMAIN(....).

Odpovědět 25.9.2015 14:03
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
rikenbekr
Člen
Avatar
Odpovídá na patrik.valkovic
rikenbekr:

Co je gnn?
Když to píšu v geditu a překládám v konzoli pomocí gcc, jak mám zajistit aby SDL_main() fungovalo?

Odpovědět 25.9.2015 14:43
In world without fences and walls, who needs Gates and Windows?
Avatar
patrik.valkovic
Šéfredaktor
Avatar
Odpovídá na rikenbekr
patrik.valkovic:

promiň, to jsem se upsal, myslel jsem samozřejmě GCC.
To nevím, gedit jsem nikdy nepoužíval, projdi si nastavení projektu, někde to tam musí být.

Odpovědět 25.9.2015 16:23
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
rikenbekr
Člen
Avatar
rikenbekr:

Gedit je textový editor.
Takový lepší poznámkový blok v linuxu.

Odpovědět 25.9.2015 17:34
In world without fences and walls, who needs Gates and Windows?
Avatar
Libor Šimo (libcosenior):

patrik.valkovic
Rýchlosť pohybu obdĺžnika sa podľa všetkého robí v tejto časti:

case SDLK_DOWN :
                rect.x-=1;
                break;
case SDLK_UP :
                rect.x+=1;
                break;
case SDLK_LEFT :
                rect.y-=1;
                break;
case SDLK_RIGHT :
                rect.y+=1;
                break;
default :
                break;
}

Skúšal som zmeniť 1 až na 5 a už to nebol úplne pekný pohyb, aj keď rýchlosť sa mi zdala dobrá. Dá sa to aj nejak inakšie? Tá jednička mi prišla veľmi pomalá.

Odpovědět 28.12.2016 10:09
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
patrik.valkovic
Šéfredaktor
Avatar
Odpovídá na Libor Šimo (libcosenior)
patrik.valkovic:

Ve framu můžeš obdelník posunout pouze o celé pixely. Teoreticky můžeš mít nějak transition funkci, která ti vypočítá, o kolik se má obdelník posunout (v jednom snímku o pixel, ve druhém o dva), ale to jsem nechtěl v tomto tutoriálu více rozvíjet. Základ bylo nějaké vykreslení a nějaké zachytání vstupů.

Odpovědět  +1 28.12.2016 17:54
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 15. Zobrazit vše