SpriteKit Advanced - Kuidas luua 2,5D mängu (III osa)

Sissejuhatus

See artikkel räägib Raft Challenge'i visuaalide täiustamisest, rakendades GPU varjureid kaadrite jaoks. See selgitab algoritme ja võimalikke puudusi GLSL-i kasutamisel SpriteKitis.

Lugejal peaks olema põhiline kogemus fragmentide varjurite kirjutamisel. Arutasime neid selle sarja 1. ja 2. osas.

Probleem

Pärast mängu beetaetappi sisenemist saime tagasisidet erinevatelt inimestelt. Kuulsime sageli, et graafika oli hea, aga ka staatiline, mis pikas perspektiivis tekitas igavust.

Minu vahetu reaktsioon oli selline: “Nad ütlesid, et see on staatiline? Nii et lisame kogu asja liigutamiseks pisut tuult! ”Pärast seda mõtlesime probleemile rohkem.

Sellised tohutud objektid nagu puud ei saa kaadrit kaadrisse animeerida, sest see tooks kaasa mäluprobleeme. Kaalusime väikeste animeeritud objektide, nagu loomad, lisamist. Kuid see muudaks stseenigraafiku veelgi keerukamaks. Ja sellel oleks tundmatu jõudluse mõju.

Minu tehtud lahendus oli kogu metsa animeerimine fragmentide abil. Tahtsin luua tuule efekti.

Idee oli rakendada sprite tekstuurile horisontaalset moonutust tugevusega, mis oleks võrdeline vahemaaga pagasiruumi alusest. See tugevus on ka aja jooksul muutunud ja seda on mõjutanud stseeni sügavus.

Selle lahenduse muud plussid:

  • lihtne integreerimine
    See on sama lihtne kui olemasoleva objekti atribuutide täitmine
  • etendus
  • tohutu paindlikkus

Siin on allikas (GLSL):

tühine pea (tühine)
{
    float horizonAbsoluteOffset = 0,64; // 1
    float distanceFromTrunksBase = abs (v_tex_coord [1] - horizonAbsoluteOffset); // 2
    float maxDivergence = segu (0,0,1,0, distanceFromTrunksBase) * 0,038; // 3
    ujukitegur = sin (u_time * 2 + (attrDepth * 1,3)); // 4
    vec2 deltaUV = vec2 (maxDivergence * tegur, 0); // 5
    
    gl_FragColor = texture2D (u_texture, v_tex_coord + deltaUV); // 6
}
  1. See ujuk hoiab kõigi šahtide aluseid vertikaalselt
     - See väärtus on omane meie tekstuurile
  2. Arvutame kauguse praeguse proovivõtupunkti ja ülaltoodud väärtuse vahel
     - see väärtus on väiksem kui 1,0 ja võib olla negatiivne
  3. Arvutame maksimaalse erinevuse
     - Maagiline number lõpus oli katse-eksituse meetodil läbi viidud
  4. Arvutame muutuva tugevuse ja tuule suuna
     - Sin funktsioon on hea alus, kuna see tagastab ennustatavad väärtused (-1 kuni 1)
     - See on ka pidev funktsioon
     - Viimane tähendab, et võime argumendiks tuua mis tahes prügi ja see töötab endiselt
     - Sel juhul on prügi praegune aeg pluss praeguse sprite sügavus
     - Animatsiooni kujundamiseks lisatakse maagilised numbrid
  5. Deltavektor on loodud
     - Maksimaalne erinevus, mis on korrutatud teguriga, läheb X-asendisse, kui Y-le jääb 0.
  6. See joon võtab värvi konkreetsest tekstuuri punktist ja väljastab selle ekraanile
     - Lisades vtexcoordiga meie praegusele positsioonile delta, muudame punkti, millest proovivõtja värvi väärtuse eraldab

Tulemus:

Pange tähele, et ka vee peegeldused liiguvad. Põhjus on see, et puud ja peegeldused on osa samast spriteist ja tekstuurist. Pole nõidus siin.

Udu parandamine

Kas on veel midagi, mida saaksime teha? Noh, kui me ei suuda midagi uut leiutada, saame alati midagi olemasolevat paremaks muuta. Meie disainer ütles üks kord tagasi, et kaugemal asuvatel puudel peaks olema ühtlane värv, et uduga paremini sulanduda.

Ülaltoodud pilt on peaaegu iseenesestmõistetav. Varem olen maininud “sügavuse” kohta. Igal metsakihil on atribuut attrDepth. See tähistab mägede (0,0) ja vaataja (6,0) vahelist kaugust.

Näpistagem seda udu!

__konstant vec3 colorLightMountains = vec3 (0,847, 0,91, 0,8);
__konstant vec3 colorDarkMountains = vec3 (0,729, 0,808, 0,643);
tühine pea (tühine)
{
    // saada värvi
    vec4 värv = texture2D (u_texture, v_tex_coord);
    ujuk alfa = värv.a; // 1
    // udu
    vec3 outputColor = vec3 (värv.rgb);
    if (attrDepth <1,0) {// 2
        outputColor = colorLightMountains;
        alfa = min (attrDepth, alfa);
    } else if (attrDepth <2.0) {// 3
        outputColor = mix (colorLightMountains, colorDarkMountains, attrDepth - 1,0);
    } else if (attrDepth <= 3.0) {// 4
        outputColor = mix (colorDarkMountains, color.rgb, attrDepth - 2.0);
    }
    
    gl_FragColor = vec4 (outputColor, 1.0) * alfa; // 5
}

Ülaltoodud kood on üsna sirgjooneline, nii et keskendun ainult kõige olulisematele asjadele:

  1. Ekstraheerige tekstuurist alfa.
  2. Kauge etapp
    Kui mets on võimalikult lai, on sellel Valgusmägede värv ja 0 alfa
     Lähemale liikudes ilmneb see alfa suurendamisel sügavuseni == 1,0
  3. Keskmine vahemaa
    Värv nihkub Pimedate mägede poole, kui sprite jõuab vaatajale lähemale.
  4. Lähedane vahemaa
    Värvus on segu tumedate mägede ja loodusliku tekstuuri värvi vahel
    Loomulikult, mida lähemal see on, seda normaalsem see välja näeb
  5. Viige lõplik värv väljundisse, kasutades alguses eraldatud alfat

Jällegi tulemus:

Mõlema efekti ühendamine

Parim asi, mis mulle varjutajate juures meeldib, on nende paindlikkus. Mõlemat efekti pole võimalik ühendada mitte ainult midagi ohverdamata. Seda on isegi soovitatav teha.

Shaderite ühendamine vähendab loosimiskõnesid ja see suurendab kaadrisagedust.

__konstant vec3 colorLightMountains = vec3 (0,847, 0,91, 0,8);
__konstant vec3 colorDarkMountains = vec3 (0,729, 0,808, 0,643);
tühine pea (tühine)
{
    // tuul
    float horizonAbsoluteOffset = 0,64;
    float distanceFromTrunksBase = abs (v_tex_coord [1] - horizonAbsoluteOffset);
    float maxDivergence = segu (0,0,1,0, distanceFromTrunksBase) * 0,038;
    ujukitegur = sin (u_time * 2 + (attrDepth * 1,3));
    vec2 deltaUV = vec2 (maxDivergence * tegur, 0);
    
    // saada värvi
    vec4 värv = tekstuur2D (u_texture, v_tex_coord + deltaUV);
    ujuk alfa = värv.a;
    // udu
    vec3 outputColor = vec3 (värv.rgb);
    if (attrDepth <1,0) {
        outputColor = colorLightMountains;
        alfa = min (attrDepth, alfa);
    } else if (attrDepth <2.0) {
        outputColor = mix (colorLightMountains, colorDarkMountains, attrDepth - 1,0);
    } muidu kui (attrDepth <= 3,0) {
        outputColor = mix (colorDarkMountains, color.rgb, attrDepth - 2.0);
    }
    
    // väljund
    gl_FragColor = vec4 (outputColor, 1.0) * alfa;
}

Lõpptulemus:

Lõksud

Ilma okkata pole roosi.

  • Shaderite kasutamine mitmel suurel spritil koos alfa-kanaliga võib põhjustada nähtavat kaadrisageduse langust.
  • Sama GPU võib anda iPhone'is 60 kaadrit sekundis, kuid suurema pikslite arvuga iPadis ainult 20 kaadrit sekundis
    Testige oma koodi sageli erinevates seadmetes, eriti võrkkesta kuvaritega iPadides
  • Koodi järgi pole seadme jõudluse hindamiseks usaldusväärset viisi
    Käitage oma mängu mitmel füüsilisel seadmel ja lisage valgesse nimekirja need seadmed, mis suudavad korraliku jõudlusega varjutajaid kasutada
    Seadmete eristamiseks võite kasutada UIDevice-Hardware.m
  • Teie osaliselt läbipaistev tekstuur kaotab värvi ja muutub halliks? Google'i esmaversioon alfaversioon!
  • Hoidke SKTextureAtlases'i kasutamisest, kui muudate koordinaate nagu tuule näites
    Atlase genereerimise ajal võib XCode mõnda tekstuuri pöörata ja liigutada.
    Sellist kõrvalekallet on koodist võimatu tuvastada või vähemalt ma ei tea, kuidas
  • Mõne spriidi jaoks võite saada tekstuuri, kus on vahetatud X ja Y koordinaadid!
  • Võite kogemata väänduda täiesti erinevaks alatekstuuriks!

Kokkuvõte

Oleme õppinud, kuidas kasutada fragmentide varje tuule ja udu tekitamiseks. Enda GLSL-koodi kirjutamisel toodete kindlasti paljusid kuvaelemente. Mõni neist on tüütu ja mõni lõbus. Pidage meeles, et mõnel neist võib olla potentsiaal funktsiooniks saada!

Autori kohta: Kamil Ziętek on iOS-i arendaja veebisaidil www.allinmobile.co