SpriteKit Advanced - Kuinka rakentaa 2,5D-peli (osa III)

intro

Tämä artikkeli käsittelee Raft Challengen visuaalien parantamista soveltamalla GPU: n varjostimia still-maisemiin. Se selittää algoritmit ja mahdolliset sudenkuopat, kun käytetään GLSL: tä SpriteKitissä.

Lukijalla tulisi olla peruskokemus fragmenttivarjostimien kirjoittamisesta. Keskustelemme näistä tämän sarjan osissa 1 ja 2.

Ongelma

Pelin siirtymisen jälkeen beetavaiheeseen saimme palautetta useilta ihmisiltä. Kuulimme usein, että grafiikat olivat hyviä, mutta myös staattisia, mikä pitkällä aikavälillä johti tylsyyteen.

Välitön reaktio oli: “He sanoivat sen olevan staattinen? Joten lisäämme jonkin verran tuulta liikuttaaksesi koko asiaa! ”Sen jälkeen ajattelimme enemmän ongelmaa.

Tällaiset valtavat esineet, kuten puut, eivät voi animoida kehyksittäin, koska se johtaisi muistiongelmiin. Harkitsimme pienten animoitujen esineiden, kuten eläinten, lisäämistä. Mutta se vaikeuttaisi kohtauskuvaa vielä enemmän. Ja sillä olisi tuntematon suorituskykyvaikutus.

Ratkaisu, jonka keksin, oli animoida koko metsä fragmenttivarjostimilla. Halusin luoda tuulen vaikutuksen.

Ajatuksena oli soveltaa vaakavääristymää spritein tekstuuriin lujuudella, joka on verrannollinen etäisyyteen rungon pohjasta. Tuo vahvuus on myös muuttunut ajan myötä, ja siihen on vaikuttanut kohtauksen ”syvyys”.

Tämän ratkaisun muut edut:

  • helppo integrointi
    Se on niin yksinkertaista kuin olemassa olevan objektin ominaisuuksien täyttäminen
  • esitys
  • valtava joustavuus

Tässä on lähde (GLSL):

tyhjä pää (tyhjä)
{
    float horizonAbsoluteOffset = 0,64; // 1
    float distanceFromTrunksBase = abs (v_tex_coord [1] - horizonAbsoluteOffset); // 2
    float maxDivergence = sekoitus (0,0,1,0, distanceFromTrunksBase) * 0,038; // 3
    kelluvuuskerroin = synti (u_time * 2 + (attrDepth * 1,3)); // 4
    vec2 deltaUV = vec2 (maxDivergenssikerroin, 0); // 5
    
    gl_FragColor = texture2D (u_texture, v_tex_coord + deltaUV); // 6
}
  1. Tämä kelluke pitää kaikkien runkojen tukikohtien pystyasennon
     - Tämä arvo on ominainen tekstuurillemme
  2. Laskemme etäisyyden nykyisen näytteenottopisteen ja yllä olevan arvon välillä
     - Tämä arvo on alle 1,0 ja voi olla negatiivinen
  3. Laskemme maksimihajonnan
     - Maaginen numero lopussa koettiin erehdyksen kautta
  4. Laskemme muuttuvan voimakkuuden ja tuulen suunnan
     - Sin-funktio on hyvä perusta, koska se palauttaa ennustettavat arvot (-1: 1)
     - Se on myös jatkuva toiminto
     - Jälkimmäinen tarkoittaa, että voimme laittaa kaikki roskat argumentiksi ja se toimii edelleen
     - Tässä tapauksessa ”roska” on nykyinen aika plus nykyisen sprite ”syvyys”
     - Maagiset numerot lisätään animaation muotoiluun
  5. Deltavektori luodaan
     - Suurin poikkeama kerrottuna kertoimella menee X-asemaan, kun Y: llä on 0.
  6. Tämä rivi vie värin tietystä pisteestä tekstuurissa ja tulostaa sen näytölle
     - Lisäämällä delta nykyiseen sijaintiimme vtexcoordilla, me muutamme pistettä, josta näytteenottaja purkaa väriarvon

Tulos:

Huomaa, että myös heijastukset vedessä liikkuvat. Tämä johtuu siitä, että puut ja heijastukset ovat osa samaa spriteä ja rakennetta. Ei noidanhalua täällä.

Paranna sumua

Voimmeko tehdä muuta? No, jos emme pysty keksimään mitään uutta, voimme aina parantaa jotain olemassa olevaa. Suunnittelijamme sanoi kerran sitten, että kauempana olevien puiden pitäisi olla kiinteitä värejä sulautuakseen paremmin sumun kanssa.

Yllä oleva kuva on melkein itsestään selvä. Aiemmin olen maininnut syvyydestä. Jokaisella metsäkerroksella on ominaisuus attrDepth. Se edustaa vuorten (0,0) ja katsojan (6,0) välistä etäisyyttä.

Siirretään tämä sumu!

__vakio vec3 colorLightMountains = vec3 (0,847, 0,91, 0,8);
__vakio vec3 colorDarkMountains = vec3 (0,729, 0,808, 0,643);
tyhjä pää (tyhjä)
{
    // saada väri
    vec4 väri = texture2D (u_texture, v_tex_coord);
    kelluva alfa = väri.a; // 1
    //sumu
    vec3 outputColor = vec3 (väri.rgb);
    if (attrDepth <1,0) {// 2
        outputColor = colorLightMountains;
        alfa = min (attrDepth, alfa);
    } else if (attrDepth <2.0) {// 3
        outputColor = sekoita (colorLightMountains, colorDarkMountains, attrDepth - 1,0);
    } else if (attrDepth <= 3.0) {// 4
        outputColor = sekoita (colorDarkMountains, color.rgb, attrDepth - 2.0);
    }
    
    gl_FragColor = vec4 (outputColor, 1.0) * alfa; // 5
}

Yllä oleva koodi on melko suoraviivainen, joten keskityn vain tärkeimpiin asioihin:

  1. Pura alfa tekstuurista.
  2. Kaukainen vaihe
    Kun metsä on kauimpana mahdollista, siinä on valovuorien väri ja 0 alfaa
     Kun se liikkuu lähemmäksi, se ilmenee lisäämällä alfa-arvoa syvyyteen == 1,0
  3. Keskipitkä etäisyys
    Väri siirtyy kohti Tummaisia ​​vuoria, kun sprite lähestyy katsojaa.
  4. Lähietäisyys
    Väri on sekoitus Tummajen vuorten ja alkuperäisen tekstuurivärin välillä
    Luonnollisesti mitä lähempänä se on, sitä normaalia se näyttää
  5. Siirrä lopullinen väri lähtöön käyttämällä alussa uutettua alfaa

Jälleen tulos:

Yhdistämällä molemmat vaikutukset

Parasta, mitä pidän shaderista, on niiden joustavuus. Molempien tehosteiden yhdistäminen ei ole mahdollista vain uhraamatta mitään. On jopa suositeltavaa tehdä niin.

Shaderien yhdistäminen vähentää vetopuheluita ja se lisää kehyksenopeutta.

__vakio vec3 colorLightMountains = vec3 (0,847, 0,91, 0,8);
__vakio vec3 colorDarkMountains = vec3 (0,729, 0,808, 0,643);
tyhjä pää (tyhjä)
{
    //tuuli
    float horizonAbsoluteOffset = 0,64;
    float distanceFromTrunksBase = abs (v_tex_coord [1] - horizonAbsoluteOffset);
    float maxDivergence = sekoitus (0,0,1,0, distanceFromTrunksBase) * 0,038;
    kelluvuuskerroin = synti (u_time * 2 + (attrDepth * 1,3));
    vec2 deltaUV = vec2 (maxDivergenssikerroin, 0);
    
    // saada väri
    vec4 väri = texture2D (u_texture, v_tex_coord + deltaUV);
    kelluva alfa = väri.a;
    //sumu
    vec3 outputColor = vec3 (väri.rgb);
    if (attrDepth <1,0) {
        outputColor = colorLightMountains;
        alfa = min (attrDepth, alfa);
    } else if (attrDepth <2.0) {
        outputColor = sekoita (colorLightMountains, colorDarkMountains, attrDepth - 1,0);
    } else if (attrDepth <= 3.0) {
        outputColor = sekoita (colorDarkMountains, color.rgb, attrDepth - 2.0);
    }
    
    // lähtö
    gl_FragColor = vec4 (outputColor, 1.0) * alfa;
}

Lopputulos:

sudenkuoppia

Ei ruusua ilman piikkiä.

  • Shaderien käyttäminen useilla isoilla spriteillä alfa-kanavalla voi aiheuttaa näkyvän kuvanopeuden laskun.
  • Sama GPU saattaa antaa 60 kuvaa sekunnissa iPhonessa, mutta vain 20 kuvaa sekunnissa iPadissa, jossa on enemmän pikseliä
    Testaa koodiasi usein eri laitteilla, erityisesti verkkokalvonäytöillä
  • Laitteen suorituskykyä koodilla ei voida arvioida luotettavalla tavalla
    Suorita pelisi useilla fyysisillä laitteilla ja lista valkoisella listalla ne, jotka pystyvät ajamaan varjostimia kunnollisella suorituskyvyllä
    Laitteiden erottamiseksi voit käyttää UIDevice-Hardware.m -sovellusta
  • Osittain läpinäkyvä tekstuurisi menettää värin ja muuttuu harmaaksi? Googlen esivalmistettu alfa!
  • Varo SKTextureAtlases-sovellusten käyttöä, jos muutat koordinaatteja kuten tuulen esimerkissä
    Atlas-sukupolven aikana XCode saattaa pyöriä ja siirtää joitain kuvioita.
    Koodista on mahdotonta havaita tällaista poikkeavuutta, tai ainakaan en tiedä miten
  • Joillekin spriteille saatat saada tekstuurin vaihtuneilla X- ja Y-koordinaateilla!
  • Voit vääntyä vahingossa täysin erilaiseen tekstuuriin!

Yhteenveto

Olemme oppineet kuinka käyttää fragmenttivarjostimia tuulen ja sumun luomiseen. Kun kirjoitat omaa GLSL-koodiasi, tuotat varmasti monia näyttöaiheisia esineitä. Jotkut heistä ovat ärsyttäviä ja toiset hilpeitä. Muista, että joillakin heistä voi olla potentiaalia tulla ominaisuudeksi!

Tietoja kirjoittajasta: Kamil Ziętek on iOS-kehittäjä osoitteessa www.allinmobile.co