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:

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:

  1. elmentjük az aktuális DepthStencilBuffer-t, hogy majd visszatudjuk állítani a rajzolás végén
  2. megadjuk a RenderTarget példányunkat rajzolási felületnek
  3. átállítjuk a használt DepthStencilBuffer-t
  4. megszokott módon törlünk
  5. megszokott módon rajzolunk
  6. visszaállítjuk a DepthStencilBuffer-t
  7. visszaállítjuk a frame buffer-t rajzolási felületnek
  8. 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:

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:

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: