_fpeti_ 2012.02.09. 03:16

Videókártyán számolt magasságtérkép árnyék.

 Van nekem egy magasságtérkép szerkesztő progim, nem egy nagy szám, van pár funkciója, többek között árnyékot is tud számolni, majd elmenti png-be. Eredetileg az árnyékot cpu-n számoltam, elég sok háromszög-sugár ütközés volt benne, és kb 10 percig tartott, mire egy 512x512 heightmap-en kiszámolt egy 2048x2048-as árnyéktérképet. Ráadásul nem is volt túl szép, még blur-ozni is kellett. Gondoltam, kipróbálom mi lenne ha gpu-n tolnánk a sugarakat, csináltam már afféle godray alapú árnyékokat 2d-s játékmotorban, ide pont jó lenne.

Elég egyszerű a dolog, kell hozzá 2 rendertarget textúra, az egyikbe készül a magasságtérkép, a másikba a shadowmap.
Az első mérete elég ha akkora, ahány cellából a térkép áll. Ebbe bele kell renderelni a hieghtmap-et úgy, hogy a legmagasabb pontja legyen 1, a legmélyebb pontja pedig 0. Ez nagyon könnyű, még projekciós mátrix sem kell hozzá. Csak -1 +1, és 1 -1 közé kell konvertálni a heightmap vecktorok xz komponensét, (vertex shaderben) majd a PS-nek átadva az y koordinátát azt elosztja a magasságtérkép legnagyobb magasságával, ezzel 0-1 közé konvertálja azt. (ha a lehet a vektor.y negatív, akkor még attól relatívvá kell tenni)

 

Kb ennyi a shader kód:

 

float2            origo; // hülye név csak a cellaszám felét adja meg

float2            zproj; // ’.x’ a minimum y, .y a himap magassága abszolút

 

void vs_himap(

            // input data

            float4 pos              :POSITION,

            float3 n                :NORMAL,

            float4 color0           :COLOR0,

            float4 color1           :COLOR1,

 

            // output

            out float4 opos              :POSITION,

            out float opixelheight  :TEXCOORD0)

{

      opos.xy = (pos.xz/origo);// world coord to x: -1 -> +1 y: +1 -> -1

      opos.zw = float2(0,1);

      opixelheight = pos.y;   // ps needz it

}

 

 

// simple diffuse with one color

void ps_himap(          float pixelheight :TEXCOORD0,

                        out   float4 oColor           :COLOR)

{

      oColor = (pixelheight-zproj.x) / zproj.y;

}

 

Most ebből számoltassam már ki a árnyékot, mégpedig úgy, hogy minden pixel koordinátájából a nap fele megnézünk egy csomó pixelt, és ha magasabb, mint a sugár y értéke ott, akkor árnyékol, csökkentsük a végső árnyékpixel-intenzitást. Ez gyakorlatilag egy csomó ’texture-fetch’ (pixel szín kiolvasás textúrából), ami talán a leglassabb, amit a gpu-n lehet. Egyébként ezt a módszert használják a pálmafa-levélen átszűrődő fénycsíkok rajzolására is a Crysis-ben például.

Kód:

float3            Invlightdir;

float2            HalfPixel;

 

texture Thimap;         // heightmap

 

// Himap

sampler Shimap = sampler_state

{

Texture     = <Thimap>;

MinFilter   = POINT;

MagFilter   = POINT;

MipFilter   = POINT;

AddressU    = CLAMP;

AddressV    = CLAMP;

};
void vs_shadowmap(

            float4 pos              :POSITION,

 

            // output

            out float4 opos         :POSITION,

            out float2 texcoord     :TEXCOORD0)

{

      opos = pos;

      // -1 -> +1 +1->-1 map to 0-1,0-1

      texcoord.x = pos.x*0.5 + 0.5;

      texcoord.y = (-pos.y)*0.5 + 0.5;

 

      texcoord+=HalfPixel;

}

 

 

void ps_shadowmap(           float2 texcoord         :TEXCOORD0,

                             out   float4 oColor           :COLOR)

{

      const int ITER = 256;

      float3 texadd = Invlightdir;

      float basehi = tex2D(Shimap,texcoord).r;

      float3 tc = float3(texcoord.x,basehi,texcoord.y);

 

      float finalcolor = 1;

      for(int i=0;i<ITER;i++)

      {

            tc+=texadd;

            float h = tex2D(Shimap,tc.xz).r;

            if(h>tc.y) finalcolor -= 0.01; // soft shadow lesz

      }

      oColor = finalcolor;

      oColor.a = 1; // csak hogy lássuk

}

A vertex shader nem nagy vaszidszdasz, egy target méretű négyzetet rajzolunk, aminek a koordinátái már -1 +1 közé esnek, csak a textúra koordinátákat kell ebből kiszámolni – át is lehetne adni a VS-nek, de mindegy.

A lényeg a pixelshaderben van (ps_shadowmap). A texadd az a növekmény, amit minden iterációban hozzáadunk a pixelpozícióhoz (tc). Kicsit bonyinak tűnhet, de nem az. A tc.xz a textúrakoordináta, a tc.y pedig a magassága a pixelnek a hieghtmap textúra alapján. Így a texadd.xz a texcoord növekmény, a texadd.y pedig a sugár emelkedésének növekménye pixelenként. Igazából itt van a cica elásva. Ez az Invlightdir –ból jön, amit be kell állítani a hieghtmapnek megfelelően. Az x és z komponens az egy pixelre jutó textúrakoordináta növekmény, mondjuk 512x512 heightmap textúra esetén 1.f / 512. Az y pedig az egy pixelre jutó sugáremelkedés. Ezt úgy kapjuk meg, hogy a heigthmap maximum magasságával osztjuk a normált raydir y komponensét. Inkább beszéljen a kód:

 

VECTOR smallm = Lightdir; // normalized!

smallm.y /= heightmapysize;

float pixelnum = float(HieghtMap.GetTexSizeX());// heightmap textúra mérete (x==y nálam)

smallm.x /= pixelnum;   // osszuk le pixelméretre

smallm.z /= pixelnum;

 

S ezt a ’smallm’-et lehet beállítani az effect ’ float3 Invlightdir’ paraméterébe. S kész is. A kérdés csak az, megérte-e a hardvert használni? A szoftveres 10 percig fut – ez 2 EZREDMÁSODPERCIG! Szóval sokkal gyorsabb, és sokkal gyorsabban meg is lehet írni.

Pár apróság, hogy ne menjen el másnak erre egy este:
- Képernyőnél nagyobb rendertarget-et csak megfelelő méretű z bufferrel lehet használni dx9 alatt, ehhez a SetDepthStencilSurface() a kulcs.

 

- rendertarget formátuma: a hieghtmaphez én a kedvenc D3DFMT_R32F-t használom, ez pontos és talán elég elterjedt. A shadowmap-hez próbáltam egy 8 bites formátumot, a D3DFMT_L8-at, amit a kártyám támogat is a dxcapsviewer szerint, de érdekes módon x irányban széthúzta a kb 4 szeresére a textúrát, 32bit helyett 8 bitbe próbáltam renderelni, talán azért, de ez nem indok arra, hogy ne működjön. Mindegy, inkább D3DFMT_A8R8G8B8 lett, ami 4-szer annyit foglal, de úgyse kell sokáig, csak amíg kimentjük fileba. 

Végül egy kép, hogy mégis, a kék a magasságtérkép, a fehér a shadowmap.

esetleg egy részletesebb, 64 iterációval és nagyobb fényerőcsökkentéssel:
 

 

Szólj hozzá!

A bejegyzés trackback címe:

https://sortagamedev.blog.hu/api/trackback/id/tr784080830

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.