RenderTarget-ek
Sok egyszerű és szinte minden öszetettebb effekthez szükség van arra, hogy a színtér képét az alap nézetünktől különböző módon is legeneráljuk, és azt felhasználjuk a végleges képhez. Ilyenek például a tükrök, árnyéktérképek, úgynevezett post-processing effektek.
Ezek megvalósításához kellenek a RenderTarget-ek. A rajzolás alapértelmezett esetben az úgynevezett frame buffer-be történik, és ez kerül majd megjelenítésre. A frame buffer helyett a rajzolás történhet egy RenderTarget-be is, aminek a tartalmát a további rajzolások során textúraként érhetjük él.
Előfeltétel
Innen letölthető a kiinduló program a gyakorlat feladataihoz.
Közös feladat
Egészítsük ki a fenti programot egy "képernyővel", ami a pilótafülkéből látottakat mutatja!
Ehhez a következőkre lesz szükségünk:
- egy textúra koordinátákat is használó quad-ra,
- egy textúrára, amit majd rá lehet feszíteni, és pont a fülkéből látottak vannak rajta.
Ilyen textúrát egy RenderTarget segítségével készíthetünk.
A következő új adattagokra lesz szükségünk a fő osztályunkban:
RenderTarget2D cockpit; Texture2D cockpitTexture; DepthStencilBuffer cockpitDS;
A RenderTarget2D az az osztály, aminek a példányaival megadhatjuk, hogy hova történjen a rajzolás, és tartalma majd egy Texture2D-ből lekérdezhető. Mivel nem a frame buffer-be rajzolunk, ezért ehhez külön mélységi és stencil buffert hozunk létre.
Az fenti háromból csak a cockpit-et és a cockpitDS-t kell létrehozunk, a cockpitTexture-t a RenderTarget-től kapjuk majd meg.
A létehozásokat az Initialize() metódusban végezük:
protected override void Initialize() { base.Initialize(); //... cockpit = new RenderTarget2D(GraphicsDevice, 512, 512, 1, SurfaceFormat.Rgba64); cockpitDS = new DepthStencilBuffer(GraphicsDevice, 512, 512, GraphicsDevice.DepthStencilBuffer.Format); //... }
Mindkét konstruktornak első három paramétere: a használt grafikus eszközt megadó objektum, a szélesség és a magasság pixelben. Mivel a DepthStencilBuffer-t a RenderTarget2D-vel szeretnénk használni, ezért ezek mérete azonos kell legyen.
A RenderTarget2D konstruktor következő paramétere az úgy nevezett mip-szintek száma, és végül a kívánt formátum. Most nem akarunk több szintet használni, és formátumnak egy R,G,B,A értékeket komponensenként két byte-on tároló formátumot választunk.
A DepthStencilBuffer konstruktor utolsó paramétere adja meg, hogy milyen legyen a buffer formátuma. Általában a frame buffer-hez tartozó DepthStencilBuffer formátuma megfelelő.
A RenderTarget-be rajzoláshoz hozzunk létre egy új metódust:
private void RenderToTexture() { DepthStencilBuffer oldDS = GraphicsDevice.DepthStencilBuffer; GraphicsDevice.SetRenderTarget(0, cockpit); GraphicsDevice.DepthStencilBuffer = cockpitDS; graphics.GraphicsDevice.Clear(Color.CornflowerBlue); room.JustDraw(); GraphicsDevice.DepthStencilBuffer = oldDS; GraphicsDevice.SetRenderTarget(0, null); cockpitTexture = cockpit.GetTexture(); }
Ennek lépései:
- elmentjük az aktuális DepthStencilBuffer-t, hogy majd visszatudjuk állítani a rajzolás végén
- megadjuk a RenderTarget példányunkat rajzolási felületnek
- átállítjuk a használt DepthStencilBuffer-t
- megszokott módon törlünk
- megszokott módon rajzolunk
- visszaállítjuk a DepthStencilBuffer-t
- visszaállítjuk a frame buffer-t rajzolási felületnek
- lekérdezzük a RenderTarget-től annak képét egy textúrába
A 3. és a 6. lépés között minden rajzolási művelet a megszokott módon történik, és a 8. lépésben kapott textúrát a többi textúrával teljesen azonos módon használhatjuk.
A textúra megjelenítéséhez egy textúrázott quad-ot fogunk használni. Mivel a csak a képet akarjuk megjeleníteni, ezért elég egy BasicEffect kikapcsolt megvilágítással. A kész quad osztály letölthető innen.
Adjunk hozzá egy új Quad adattagot az osztályunkhoz, és példányosítsuk azt a konstruktorban. Mivel a Quad a DrawableGameComponent-ből származik, regisztráljuk a Components-be.
Quad quad; public Game1() { //... quad = new Quad(this); Components.Add(quad); }
A quad mérete legyen negyed képernyőnyi, és toljuk el az egyik sarokba. Ezeket a transzformációkat a World mátrixban adjuk meg, a View és Projection legyen egységmátrix. A mátrixokat az Initialize() metódusban állítjuk be.
quad.World = Matrix.CreateScale(0.25f, 0.25f, 1) * Matrix.CreateTranslation(-0.73f, -0.73f, 0); quad.View = quad.Projection = Matrix.Identity;
A Quad osztály ezeket a mátrixokat közvetlenül továbbadja a BasicEffect-jének.
Hogy a nézetet a pilótafülkébe helyezzük szükségünk lesz:
- a repülőt elhelyező mátrixra, mert ennek inverze a repülő rendszerének az origójába visz,
- egy transzformációra, ami a repülő origójából a pilótafülkébe viszi a nézetet, és
- egy mátrixra, amiben eltároljuk az eredeti nézetet, hogy ne kelljen mindig újraszámolni.
Hogy le tudjuk kérdezni a repülőt elhelyező mátrixot, egészítsük ki a Room osztályt a következővel:
public Matrix PlaneWorld { get { return planeWorld; } }
Adjunk két új mátrixot a Game1 osztályhoz:
Matrix view; Matrix planeViewCorrection;
A view-ba tároljuk el az eredetileg használt nézeti transzformációnkat, a planeViewCorrection-t meg állítsuk be a "grafikustól kapott adatok alapján":
protected override void Initialize() { //... view = Matrix.CreateLookAt(camera, new Vector3(0, 35, 0), Vector3.Up); room.View = view; //... planeViewCorrection = Matrix.CreateTranslation(0, -0.7f, -1.7f) * Matrix.CreateRotationY(MathHelper.Pi); }
Ezek után a teljes rajzolás menete:
protected override void Draw(GameTime gameTime) { room.View = Matrix.Invert(room.PlaneWorld) * planeViewCorrection; RenderToTexture(); room.View = view; quad.Texture = cockpitTexture; base.Draw(gameTime); }
Mivel a Room és a Quad is DrawableGameComponent, ezért nem kell külön meghívni a Draw metódusukat.
Önálló feladatok
1. [] Rajzolj ki még egy repülőt, ami üldözi az elsőt, és ennek a "belső nézetét" is jelenítsd meg!
Tippek:
- Ha ugyanazt a modellt használod, akkor elég egy új planeWorld mátrix a rajzolásához.
- Felhasználhatod a Quad-ot többször is, de akkor ne regisztráld a Components-be. Ilyenkor a kódban direkt meg kell hívnod az Initialize(), Update(), Draw() metódusokat.
- Ugyanazt a RenderTarget-et is felhasználhatod többször. Erre példa pszeudokóddal:
room.View = egyik_nezet; RenderToTexture(); quad.World = egyik_pozicio; quad.Texture = cockpitTexture; quad.Draw(); room.View = masik_nezet; RenderToTexture(); quad.World = masik_pozicio; quad.Texture = cockpitTexture; quad.Draw(); room.View = eredeti_nezet;
2. [] Cseréld le a Quad BasicEffect-jét egy igazi effektre, ami szépiában rajzol!
Tippek:
- A perpixel.fx-ből érdemes készíteni egy új effektet: a vertex shader-t le kell egyszerűsíteni, mert most nem használunk se normál vektorokat, se a world_pos-t.
- A pixel shader-ben egy textúra olvasásra van szükség, és a kapott értékek átszámítására a következő képlet alapján:
R' = (R × 0.393 + G × 0.769 + B × 0.189);
G' = (R × 0.349 + G × 0.686 + B × 0.168);
B' = (R × 0.272 + G × 0.534 + B × 0.131);