Textury a světla v XNA

4. díl - Textury a světla v XNA

Tomáš Herceg       24.02.2008       VB.NET, XNA, Grafika       14164 zobrazení

V tomto díle přidáme na náš terén textury a naučíme se pracovat se světly. Při tom si vysvětlíme, co jsou to normály a jak je jednoduše (i když ne přesně) spočítat, aby nám světlo odvětlovalo plochy správně.

V minulých třech dílech jsme se ponořili do základů XNA frameworku, vysvětlili si základní pojmy, naučili jsme se pracovat s 2D a 3D grafikou a v neposlední řadě jsme provedli již poměrně složitější rozložení terénu na trojúhelníky a jeho vykreslení. Dnes se naučíme pracovat s texturami a ukážeme si, jak namapovat na náš terén nějakou texturu a jak terén nasvítit tak, aby vypadal dobře.

Načtení textury a příprava pro renderování

Poněkud netradičně začneme programovat hned. Stáhněte si tuto texturu trávy a přidejte ji do projektu.

 Textura trávy

Dále otevřete třídu Landscape a nahoru do deklarací přidejte vlastnost Texture, která bude vracet a nastavovat aktuální texturu terénu:

     Private _texture As Texture2D                   ' textura terénu
Public Property Texture() As Texture2D
Get
Return _texture
End Get
Set(ByVal value As Texture2D)
_texture = value
End Set
End Property

Mohli bychom udělat místo vlastnosti public proměnnou, ale zvykněte si na cokoliv, co má být viditelné ven, používat Property. Nikdy nevíte, jestli za týden nebudete potřebovat při nastavení hodnoty této proměnné spustit nějakou proceduru nebo provést nějaký kód. Když máte vlastnost, jde to hladce zařídit, stačí přidat příslušný kód do sekce Set, resp. Get, pokud chceme před vrácením hodnoty něco ošetřit.

Aby se textura aplikovala při vykreslování, do procedury Draw přidejte nahoru za nastavení matic tyto dva řádky (ještě před cyklus):

         ' zapnout texturování
effect.Texture = Texture
effect.TextureEnabled =
True

Tím jsme nastavili efektu, který terén vykresluje, naši texturu a řekli jsme mu, že ji má použít. Dále smažte z této metody tento řádek, nebudeme již chtít kreslit jen mřížku, ale celou plochu terénu:

          device.RenderState.FillMode = FillMode.WireFrame

A dovnitř metody LoadAllContent uvnitř třídy Game přidejte tento řádek (měli byste sami jednoznačně rozhodnout, jestli před inicializaci terénu nebo až za ní):

          land.Texture = Content.Load(Of Texture2D)("Content\grass")

Mapování textur

V minulém díle jsme pomocí složité opičárny sestavili terén a nyní bychom jej chtěli pokrýt texturou trávy tak, aby dobře vypadal. A k tomu se pojí termín mapování, je to vlastně stanovení, jak se má texturou terén potáhnout. Když například vytapetujete stěnu v obýváku, vzor se opakuje. A podobně je to s texturou - pokud bychom náš obrázek trávy roztáhli na celou plochu terénu, při pohledu zblízka bychom viděli tohle, což moc hezky nevypadá:

Textura roztažená po celém terénu

Budeme tedy chtít, aby se naše textura několikrát zopakovala. Samozřejmě musíme mít texturu, která na sebe navazuje, spousta textur při opakování vytváří znatelné hranice, které jsou nežádoucí:

Opakovaná nenavazující textura

Naše textura trávy ale při opakování navazuje a vypadá pěkně, proto ji můžeme na celou plochu terénu použít 8 x 8 krát. Samozřejmě textury nemusíme mapovat rovnoměrně, můžeme je někde roztáhnout více a jinde zase "smrsknout", to už je na nás. Samotné mapování určíme pomocí souřadnic mapování textur (obvykle se pro ně používají písmena U a V). Jak to funguje je vidět na obrázku:

image

Na tomto obbrázku jsem označil vrcholy a vypsal k nim jejich souřadnice [U; V], které definují namapování textury. Pokud tedy na šířce terénu chceme texturu "vtěsnat" 8x, musíme souřadnice vrcholů U rovnoměrně osadit hodnotami od 0 do 8. Je nutno ještě zdůraznit, že každý vrchol musí mít souřadnice mapování nastavené. Je jasné, že většina vrcholů nebude mít tyto souřadnice celočíselné.

Otevřete si tedy metodu PrepareVertexBuffer a dovnitř cyklů hned pod řádek, kde nastavujeme pozici, vložte navíc řádek:

         .TextureCoordinate = New Vector2(x / width * 8, y / height * 8)

Hodnoty proměnných x a y jsou v rozmezí od 0 do width, resp. height. Pokud tedy x vydělíme hodnotou width, dostaneme výsledek v rozsahu od 0 do 1, což by se nám hodilo v případě, že chceme texturu roztáhnout na celou plochu terénu. Když tuto hodnotu ještě vynásobíme osmi, bude v rozsahu od 0 do 8, což přesně potřebujeme. x skáče rovnoměrně, takže i tyto výsledné souřadnice budou rozmístěny rovnoměrně. To samé platí i pro souřadnice y. Pokud změníte ve třídě Game vytváření matice view podle následující ukázky, uvidíte terén s pěkně vypadajícím mapováním textury.

    Private view As Matrix = Matrix.CreateLookAt(New Vector3(32, 12, 16), New Vector3(32, 8, 32), Vector3.Up)

Terén s opakující se texturou

Textura terénu je již lepší, ale stále ještě naší scéně mnoho chybí. Je to zcela jistě osvětlení, chtělo by to ztmavit některé stěny a některé zase rozjasnit, aby bylo na terénu něco vidět.

Normály

Abychom mohli používat světla, musíme každému vrcholu přidat ještě normálu. Normála plochy je vektor, který vede kolmo na tuto rovinu. Můžete si to představit jednoduše jako když na desku stolu postavíte kolmo tužku - tužka udává směr normály desky stolu. Pokud světlo dopadá na nějakou plochu, je potřeba znát normálu, protože pomocí ní můžeme spočítat, kolik světla na stůl dopadá. Pokud světlo na stůl dopadá šikmo, je osvícen méně, pokud na něj dopadá kolmo, je osvícen nejvíce, a pokud náhodou na stůl světlo svítí z druhé strany (zespoda), není horní deska osvětlená tímto světlem vůbec. Pro představu je zde jednoduchý obrázek:

image

Normála je tlustá modrá šipka, červené šipky udávají směr světla. Vidíme, že když světlo dopadá šikmo, plocha je středně tmavá, pokud světlo dopadá kolmo, plocha je nejsvětlejší, a pokud ukazuje normála na druhou stranu, plocha není osvícená a je na ní stín. Výhodou normály je, že jednoznačně určuje směr natočení plochy.

V XNA nemají normálu plochy, ale vrcholy (informace o plochách jako takových nemáme). Vzhledem k tomu, že vrchol leží vždy na rozhraní několika ploch, musíme podle okolních vrcholů (které určují směr naklonení plochy) dopočítat normálu. Toto počítání normály ve vrcholu není matematicky přesné, ale nám bude prozatím stačit. Jak na to ukazuje následující obrázek:

Zjištění normály

Chceme určit směr normály prostředního vrcholu. Vezmeme si sousední vrcholy napravo a nalevo a spojíme je (zajímá nás jen vektor této spojnice), dále si vezmeme sousední vrcholy vpředu a vzadu a provedeme s nimi to samé (zjistíme též vektor jejich spojnice). Tyto dva vektory nám určují jednu velkou plochu, jejíž normála nám poslouží jako hledaný výsledek. Je to jen přibližné řešení, ale je velmi jednoduché a dává poměrně dobré výsledky.

Jak zjistit vektory spojnic? Vcelku jednoduše - vektor z bodu A do bodu B zjistíme tak, že odečteme souřadnice bodu A od souřadnic bodu B (zvlášť X, Y a Z). První vektor má souřadnici X rovnou dvěma (vrcholy jsou od sebe vzdálené 1 jednotku), Y souřadnici rovnou rozdílu výšek vrcholu vpravu a vlevo a Z souřadnici 0, protože oba dva sousední vrcholy ji mají stejnou. Obdobně zjistíme vektor druhé spojnice - X bude 0, Y rozdíl výšek a Z bude-2.

Pokud známe dva vektory a chceme k nim vypočítat třetí, který je kolmý na oba z nich, můžeme použít tzv. vektorový součin (možná znáte ze střední školy). Záleží na pořadí, v jakém vektory vynásobíme (existuje něco jako pravidlo utahování šroubu, které pořadí určí, ale tím vás nebudu zatěžovat), pokud dáme pořadí opačné, bude vektor ukazovat přesně na druhou stranu, tedy dolů (kolmost na oba dva výchozí se samozřejmě zachová). Samotný vektorový součin už za nás udělá XNA, má na to totiž funkci Vector3.Cross. Vrcholům, které jsou na kraji oblasti a chyběl by jim nějaký soused, nastavíme normálu na hodnotu Vector3.Up, což je vektor, který míří kolmo nahoru.

Přidání informací o normále do vertexů

Pro informace o vrcholu používáme datový typ VertexPositionTexture, který se nám ale nyní nehodí. Vzhledem k tomu, že je tento typ uveden na více místech, je nejrychlejší provést hromadné nahrazení. Vstupte tedy do třídy Landscape a stiskněte Ctrl-H. Vyplňte dialog podle obrázku a klikněte na tlačítko ReplaceAll.

Nahrazení

Nyní vstupte opět do metody PrepareVertexBuffer a za přiřazení souřadnic mapování textur přidejte ještě tento blok, který spočítá normálu vnitřním bodům a krajním ji nastaví přímo nahoru:

         If x > 0 And x < width - 1 And y > 0 And y < height - 1 Then
Dim v1 As New Vector3(2, heights(x + 1, y) - heights(x - 1, y), 0)
Dim v2 As New Vector3(0, heights(x, y + 1) - heights(x, y - 1), 2)
.Normal = Vector3.Cross(v2, v1)
.Normal.Normalize()
Else
.Normal = Vector3.Up
End If

Podmínka se tedy splní, pokud nejsme na kraji mapy. V takovém případě spočítáme vektory v1 a v2, což jsou vektory spojnic sousedů. Normála našeho vertexu pak bude rovna Vector3.Cross(v2, v1), což je vektorový součin našich vektorů. Nakonec na normálu zavoláme ještě funkci Normalize, aby se vektor zkrátil na délku 1 (směr zůstane zachován), protože s ním pak může XNA rychleji pracovat.

Světlo

Nyní v naší scéně zapneme světlo. Najděte metodu Draw v třídě Landscape a přidejte do ní za nastavení matic a textury (před aplikování efektu) tento kód. Hned si vysvětlíme, co co znamená:

         ' osvětlení scény
effect.LightingEnabled = True
effect.DirectionalLight0.Enabled = True
effect.DirectionalLight0.Direction = New Vector3(15, -7, -18) ' směr světla
effect.DirectionalLight0.DiffuseColor = New Vector3(1, 1, 0.7) ' barva světla
effect.DirectionalLight0.SpecularColor = New Vector3(0.5, 0.5, 0.32) ' nasvícené plochy
effect.AmbientLightColor = New Vector3(0.7, 0.7, 0.7) ' stín (tam, kam nejde světlo)

Nejprve na efektu zapneme vlastnost LightningEnabled, aby zapnul podporu světel. Vidíme, že můžeme použít 3 různá directional lights (směrová světla). Budeme pracovat jen s prvním z nich - effect.DirectionalLight0. Zapneme jej nastavením vlastnosti Enabled na True. Dále mu nastavíme směr (svazek paprsků tohoto světla se nerozšiřuje, jako třeba světlo lampičky; je vhodné jej použít na světlo hodně vzdáleného zdroje, např. Slunce). DiffuseColor je barva tohoto světla (skládáme jí pomocí složek RGB spektra, akorát místo celočíselných hodnot od 0 do 255 dáváme desetinné hodnoty od 0 do 1), v tomto případě tedy skoro bílá, trošku nažloutlá. SpecularColor je barva nejnasvícenějších ploch, těch, kam světlo dopadá nejkolměji. Chceme, aby nasvícené plochy byly trochu ozářené, takže barvu nastavíme trochu střídměji (asi tak na polovinu).

Nakonec musíme celému efektu nastavit barvu AmbientLightColor. Standardně je nastavena na černou a jedná se o barvu globálního světla, které se použije, pokud na plochu nedopadá světlo žádné. Ve skutečném světě i ve stínu, kam sluneční paprsky nedopadají, není úplná tma, je tam jen šero. Proto my tuto barvu nastavíme na světle šedou, aby nám plochy hor odvrácené od Slunce nezčernaly moc, to by nevypadalo hezky. Kdyžtak si s hodnotami světel zkuste trochu pohrát, tak nejlépe zjistíte, co která dělá.

A to by bylo pro dnešek vše, výsledek vypadá takto:

image

Nalevo vidíme velmi světlé plochy, tam se projevilo SpecularLight, máme tam také spoustu zastíněných ploch, kam sluneční paprsky nedopadají.

V příštím díle se naučíme pracovat s klávesnicí a myší, do naší hry přidáme nám již dobře známého jednorožce, na kterém si trochu zajezdíme.

 

hodnocení článku

1 bodů / 1 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Všechny díly tohoto seriálu

6. Trocha matematiky pro 3D 11.07.2008
5. Klávesnice a myš v XNA 30.03.2008
4. Textury a světla v XNA 24.02.2008
3. Generování terénu 20.02.2008
2. Vykreslujeme 3D model v XNA 17.02.2008
1. Seznámení s XNA frameworkem 14.02.2008

 

Mohlo by vás také zajímat

Řešené příklady v ASP.NET - díl 1.: Aplikace pro zamlouvání sedadel (část 1)

V této části vytvoříme databázi, napíšeme základní infrastrukturu a nakonfigurujeme přihlašování uživatelů pomocí knihovny Altairis Web Security.

Windows Presentation Foundation (WPF) - díl 5.: Device independent pixels

Co je to DPI a proč by vás to mělo zajímat? Jak se aplikují automatické transformace při změně této hodnoty? Článek je prvním pohledem tohoto seriálu do transformací a pozičního systému WPF.

Práce s časovými pásmy a letním časem v aplikaci a databázi - díl 2.: DateTime v .NET Frameworku

Práce se strukturou DateTime v .NET Frameworku lze využívat pro práci s lokálním časem i časem v jiných časových pásmech. Tento díl se věnuje základnímu popisu a konverzím mezi lokálním a UTC časovým údajem.

 

 

Nový příspěvek

 

Diskuse: Textury a světla v XNA

Prosím vás o pomoc s triedou Landscape. Ja to robím v jazyku C#. Terén sa mi vykreslí ale je celý zelený. Mohli by ste mi prosím vás pomôcť?

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

ahoj

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Textury a světla v XNA

Především bych chtěl poděkovat za bezva tutory!

U tohoto čtvrtého mám trochu problém s texturou. Vykreslení "síťové" krajiny funguje. Pokud ale přidám texturu, tak se mi krajina vykreslí jednobarevně, ale nikoliv s uloženou texturou. Resp. to vypadá, že si vezme vždy jednu barvu z textury a tou to celé potáhne... :(

Nevíte v čem je problém?

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Mam stejny problem :(

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Sice celkem starý dotaz, ale určitě to třeba někomu pomůže.

To že máte celou plochu pokrytou jednou barvou musí být způsobeno špatnými UV souřadnicemi. Takže bych se podíval na vektor, kterým plníte TextureCoordinate ... je dost možné, že ho plníte samými nulovými vektory, protože pořádně neovládáte programovací jazyk a neumíte správně dělit :-) Můj osobní tip.

S pozdravem

Tomáš Kubík

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Diskuse: Textury a světla v XNA

        If x > 0 And x < width - 1 And y > 0 And y < height - 1 Then
             Dim v1 As New Vector3(2, heights(x + 1, y) - heights(x - 1, y), 0)
             Dim v2 As New Vector3(0, heights(x, y + 1) - heights(x, y - 1), 2)
            .Normal = Vector3.Cross(v2, v1)
            .Normal.Normalize()
         Else
             .Normal = Vector3.Up
         End If

i zde u slova .normal napíše "normal" is not a member of "Microsoft.xna.framework.graphics.vertexpositiontexture"

a taki mi dost chybí odkaz na stažení.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Zapomněl jste změnit někde VertexPositionTexture na název typu vertexu, který jsme si kdesi nadeklarovali.

Odkazna stažení nebude, lidi pak články nečtou a jenom si stáhnou ukázkový příklad.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

děkuji za radu.vyskytla se další chyba.

Protected Overrides Sub LoadContent()
        ' Zkompilovat a načíst všechen herní obsah
        _vbContentManager.CompileContent(True)
        _vbContentManager.LoadAllContent()
        ' >> Zde následuje kód pro načtení herního obsahu
        land = New landscape(Me.GraphicsDevice)

        land.LoadTerrain(Content.Load(Of Texture2D)("Content\heightmap"), 25)

        land.Texture = Content.Load(Of Texture2D)("Content\grass")


    End Sub

u land.Texture píše ""texture" is not a member of "Windowsgame1.landscape""

a v metodě Draw u

effect.Texture = Texture

'Texture' is a type and cannot be used as an expression.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

mám stejný problém

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Evidentně jste zapomněli přidat vlastnost Texture do třídy Landscape. Je to nahoře v článku, hned pod obrázkem s texturou trávy.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Děkuji, už to jde jak má.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

ja se zajímám o blender

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Textury a světla v XNA

měl bych takový dotaz, v této kapitole vše ok až do "Přidání informací o normále do vertexů"...jakmile totiž zaměním ony dva datové tipy, scéna se vykresluje asi jako dvě nebo tři plochy přes sebe naházené (alespoň to tak vypadá) zkoušel jsem to zmenšit ale má to pořád stejný tvar. Můžete pls někdo pomoci? Diky

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Textury a světla v XNA

Další díl by byl super. Mohl bys v něm popsat pohyb kamery, fyziku, vykreslování ploch, použití mapy a tak..., prostě pohled s první osoby :-) Jinak ty předchozí jsou vyborně napsaný, pochopí je začátečník i pogramátor, který začíná s 3D. Jen tak dál.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Textury a světla v XNA

Zdravim, tento serial je uplne skvely, a velmi sa tesim na dalsi diel, lenze ten jaksi neprichadza, mohli by ste nam potvrdit ci je vobec v priprave, alebo s nim vobec nemame pocitat? V ziadnom pripade na vas nechcem tlacit, len to ticho ma trochu znepokojuje, ale chapem ze casu je malo.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Další díl bude, snad jej stihnu napsat během tohoto víkendu, ale nic neslibuji, protože nemám moc času.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět

Diskuse: Textury a světla v XNA

Zdar kdy bude dalsi dil? Tento serial je vynikajici, uz se tesim.

nahlásit spamnahlásit spam -1 / 1 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback