Planetarna pokrajina. Okoljski problemi naravne krajine

AlexWIN32 20. avgust 2017 ob 13:36

Planetarna pokrajina

  • API,
  • C++
  • Razvoj igre
  • Vadnica

Težko je trditi, da je pokrajina sestavni del večine računalniških iger na prostem. Tradicionalna metoda izvajanja sprememb v reliefu površine, ki obkroža igralca, je naslednja - vzamemo mrežo, ki je ravnina, in za vsako primitivo v tej mreži naredimo premik vzdolž normale na to ravnino za določeno vrednost temu primitivcu. Preprosto povedano, imamo enokanalno teksturo 256 x 256 slikovnih pik in ravno mrežo. Za vsako primitivo vzamemo vrednost iz teksture na podlagi njenih koordinat na ravnini. Zdaj preprosto premaknemo koordinate primitive vzdolž normale na ravnino za dobljeno vrednost (slika 1)

Slika 1 zemljevid višin + ravnina = pokrajina

1. Sektor

Očitno ni pametno zgraditi pokrajine za celotno kroglo naenkrat - večina je ne bo vidna. Zato moramo ustvariti določeno minimalno območje prostora - določen primitiv, ki bo sestavljal relief vidnega dela krogle. Imenoval ga bom sektor. Kako ga lahko dobimo? Poglejte torej sliko 2a. Zelena celica je naš sektor. Nato bomo zgradili šest mrež, od katerih je vsaka stran kocke (slika 2b). Zdaj pa normalizirajmo koordinate primitivov, ki tvorijo mreže (slika 2c).


Slika 2

Kot rezultat smo dobili kocko, ki je projicirana na kroglo, kjer je sektor območje na eni od njenih ploskev. Zakaj to deluje? Upoštevajte poljubno točko na mreži kot vektor iz izhodišča. Kaj je vektorska normalizacija? To je transformacija danega vektorja v vektor v isti smeri, vendar z enotsko dolžino. Postopek je naslednji: najprej poiščemo dolžino vektorja v evklidski metriki po Pitagorovem izreku

Nato razdelite vsako komponento vektorja s to vrednostjo

Sedaj pa se vprašajmo, kaj je krogla? Krogla je niz točk, ki so enako oddaljene od dane točke. Parametrična enačba krogle izgleda takole

Kjer so x0, y0, z0 koordinate središča krogle, R pa njen polmer. V našem primeru je središče krogle izhodišče, polmer pa je enak ena. Zamenjajmo znane vrednosti in vzamemo koren dveh strani enačbe. Izkazalo se je naslednje

Dobesedno zadnja transformacija nam pove naslednje: "Da bi pripadal krogli, mora biti dolžina vektorja enaka ena." To smo dosegli z normalizacijo.

Kaj pa, če ima krogla poljubno središče in polmer? Točko, ki mu pripada, lahko najdete z naslednjo enačbo

Kjer je pS točka na krogli, C je središče krogle, pNorm je predhodno normaliziran vektor in R je polmer krogle. Preprosto povedano, tukaj se zgodi, da se "premikamo od središča krogle proti točki na mreži na razdalji R." Ker ima vsak vektor enotno dolžino, so na koncu vse točke enako oddaljene od središča krogle za razdaljo njenega polmera, zaradi česar enačba krogle velja.

2. Upravljanje

Dobiti moramo skupino sektorjev, ki so potencialno vidni z vidika. Toda kako to narediti? Recimo, da imamo na neki točki kroglo s središčem. Imamo tudi sektor, ki se nahaja na krogli, in točko P, ki se nahaja v prostoru blizu krogle. Zdaj pa zgradimo dva vektorja - enega usmerimo od središča krogle do središča sektorja, drugega - od središča krogle do točke gledanja. Poglejte sliko 3 - sektor je lahko viden le, če je absolutna vrednost kota med tema vektorjema manjša od 90 stopinj.


Slika 3 a - kot manjši od 90 - sektor je potencialno viden. b - kot večji od 90 - sektor ni viden

Kako dobiti ta kot? Če želite to narediti, morate uporabiti skalarni produkt vektorjev. Za tridimenzionalni primer se izračuna na naslednji način:

Skalarni produkt ima distribucijsko lastnost:

Prej smo definirali enačbo za dolžino vektorja - zdaj lahko rečemo, da je dolžina vektorja enaka korenu iz pikasti izdelek tega vektorja nase. Ali obratno - skalarni produkt vektorja samega je enak kvadratu njegove dolžine.

Zdaj pa poglejmo kosinusni zakon. Ena od njegovih dveh formulacij je videti tako (slika 4):


Slika 4 kosinusni zakon

Če vzamemo a in b za dolžini naših vektorjev, potem je kot alfa tisto, kar iščemo. Toda kako dobimo vrednost c? Poglejte: če od b odštejemo a, dobimo vektor, usmerjen od a proti b, in ker je vektor označen samo s smerjo in dolžino, lahko njegov začetek grafično lociramo na koncu vektorja a. Iz tega lahko rečemo, da je c enak dolžini vektorja b - a. Tako, uspelo nam je

Izrazimo kvadrate dolžin kot skalarne produkte

Odprimo oklepaje z uporabo lastnosti distribucije

Pa malo skrajšajmo

Končno, če obe strani enačbe delimo z minus dva, dobimo

To je še ena lastnost skalarnega produkta. V našem primeru moramo vektorje normalizirati tako, da bodo njihove dolžine enake ena. Ni nam treba izračunati kota – dovolj je vrednost kosinusa. Če je manjša od nič, potem lahko mirno rečemo, da nas ta panoga ne zanima

3. Mreža

Čas je, da razmislimo o tem, kako narisati primitive. Kot sem že rekel, je sektor glavna komponenta v našem diagramu, zato bomo za vsak potencialno viden sektor narisali mrežo, katere primitivi bodo oblikovali pokrajino. Vsako njegovo celico je mogoče prikazati z dvema trikotnikoma. Ker ima vsaka celica sosednje ploskve, se vrednosti večine oglišč trikotnika ponavljajo v dveh ali več celicah. Da bi se izognili podvajanju podatkov v medpomnilniku vozlišč, napolnimo medpomnilnik indeksa. Če so uporabljeni indeksi, potem z njihovo pomočjo grafični cevovod določi, kateri primitiv v medpomnilniku vozlišč naj obdela. (Slika 5) Topologija, ki sem jo izbral, je seznam trikotnikov (D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST)


Slika 5 Vizualni prikaz indeksov in primitivov

Ustvarjanje ločenega medpomnilnika vozlišč za vsak sektor je predrago. Veliko bolj učinkovito je uporabiti en medpomnilnik s koordinatami v mrežnem prostoru, tj. x je stolpec in y je vrstica. Toda kako od njih dobiti točko na krogli? Sektor je kvadratna ploskev z začetkom v določeni točki S. Vsi sektorji imajo enako dolžino robov – recimo temu SLen. Mreža pokriva celotno območje sektorja in ima tudi enako število vrstic in stolpcev, tako da lahko za iskanje dolžine roba celice sestavimo naslednjo enačbo

Kjer je Сlen dolžina roba celice, je MSize število vrstic ali stolpcev mreže. Oba dela razdelite na MSize in dobite CLen


Slika 6 Vizualni prikaz oblikovanja točke na mreži

Da dobimo točko na krogli, uporabimo prej izpeljano enačbo

4. Višina

Vse, kar smo dosegli do te točke, je malo podobno pokrajini. Čas je, da dodamo tisto, kar bo naredilo tako - višinsko razliko. Predstavljajmo si, da imamo kroglo enotskega polmera s središčem v izhodišču in niz točk (P0, P1, P2 ... PN), ki se nahajajo na tej krogli. Vsako od teh točk lahko predstavimo kot enotski vektor iz izhodišča. Zdaj pa si predstavljajte, da imamo niz vrednosti, od katerih je vsaka dolžina določenega vektorja (slika 7).

Te vrednosti bom shranil v dvodimenzionalno teksturo. Najti moramo povezavo med koordinatami slikovnih pik teksture in vektorjem točke na krogli. Pa začnimo.

Poleg kartezičnega lahko točko na krogli opišemo tudi s sferičnim koordinatnim sistemom. V tem primeru bodo njegove koordinate sestavljene iz treh elementov: kota azimuta, polarnega kota in vrednosti najkrajše razdalje od izvora do točke. Azimutni kot je kot med osjo X in projekcijo žarka iz izhodišča v točko na ravnini XZ. Zavzame lahko vrednosti od nič do 360 stopinj. Polarni kot je kot med osjo Y in žarkom od izhodišča do točke. Lahko se imenuje tudi zenit ali normalen. Sprejema vrednosti od nič do 180 stopinj. (glej sliko 8)


Sl.8 Sferične koordinate

Za pretvorbo iz kartezične v sferično uporabljam naslednje enačbe (predvidevam, da je os Y navzgor):

Kjer je d razdalja do točke, a je polarni kot, b je azimutni kot. Parameter d lahko opišemo tudi kot "dolžino vektorja od izhodišča do točke" (kot je razvidno iz enačbe). Če uporabimo normalizirane koordinate, se lahko izognemo delitvi pri iskanju polarnega kota. Pravzaprav, zakaj potrebujemo te kote? Če vsakega od njih delimo z njegovim največjim obsegom, dobimo koeficiente od nič do ena in jih uporabimo za vzorčenje iz teksture v senčniku. Pri pridobivanju koeficienta za polarni kot je treba upoštevati četrtino, v kateri se nahaja kot. "Toda vrednost izraza z / x ni definirana, ko je x enak nič," pravite. Poleg tega bom rekel, da ko je z enak nič, bo kot enak nič ne glede na vrednost x.

Dodajmo nekaj posebnih primerov za te vrednosti. Imamo normalizirane koordinate (normalno) - dodajmo več pogojev: če je vrednost X normale enaka nič in je vrednost Z večja od nič - potem je koeficient 0,25, če je X enak nič in je Z manjši od nič - potem je bo 0,75. Če je vrednost Z enaka nič in je X manjši od nič, bo v tem primeru koeficient enak 0,5. Vse to lahko enostavno preverimo na krogu. Toda kaj storiti, če je Z nič in je X večji od nič - navsezadnje bosta v tem primeru tako 0 kot 1 pravilni? Predstavljajmo si, da smo izbrali 1 - no, vzemimo sektor z najmanjšim azimutnim kotom 0 in največjim 90 stopinj. Zdaj pa poglejmo prve tri točke v prvi vrstici mreže, ki prikazuje ta sektor. Za prvo točko smo izpolnili pogoj in nastavili koordinato teksture X na 1. Očitno je, da za naslednji dve točki ta pogoj ne bo izpolnjen - vogali zanje so v prvi četrtini in kot rezultat dobimo nekaj takega nastavite - (1,0, 0,05, 0,1). Toda za sektor s koti od 270 do 360 za zadnja tri oglišča v isti vrsti bo vse pravilno - pogoj za zadnje oglišče bo deloval in dobili bomo niz (0,9, 0,95, 1,0). Če kot rezultat izberemo nič, bomo dobili sklope (0,0, 0,05, 0,1) in (0,9, 0,95, 0,0) - v vsakem primeru bo to privedlo do precej opaznih površinskih popačenj. Torej uporabimo naslednje. Vzemimo središče sektorja, nato normaliziramo njegovo središče in ga tako premaknemo v kroglo. Zdaj pa izračunajmo skalarni produkt normaliziranega središča in vektorja (0, 0, 1). Formalno gledano je ta vektor normalen na ravnino XY in z izračunom njegovega pikčastega produkta z normaliziranim središčnim vektorjem sektorja lahko razumemo, na kateri strani ravnine je središče. Če je manjši od nič, je sektor za ravnino in potrebujemo vrednost 1. Če je skalarni produkt večji od nič, je sektor pred ravnino in bo zato mejna vrednost 0. (glej Slika 9)


Sl.9 Problem izbire med 0 in 1 za koordinate teksture

Tukaj je koda za pridobivanje teksturnih koordinat iz sferičnih. Prosimo, upoštevajte - zaradi napak pri izračunu ne moremo preveriti normalnih vrednosti za enakost na nič, namesto tega moramo primerjati njihove absolutne vrednosti z neko mejno vrednostjo (na primer 0,001)

//norma - normalizirane koordinate točke, za katero dobimo teksturne koordinate //offset - normalizirane koordinate središča sektorja, ki mu norma pripada //zeroTreshold - vrednost praga (0,001) float2 GetTexCoords(float3 norma, float3 offset) ( float tX = 0,0f, tY = 0,0f; bool normXIsZero = abs(norm.x)< zeroTreshold; bool normZIsZero = abs(norm.z) < zeroTreshold; if(normXIsZero || normZIsZero){ if(normXIsZero && norm.z >0,0f) tX = 0,25f;< 0.0f && normZIsZero) tX = 0.5f; else if(normXIsZero && norm.z < 0.0f) tX = 0.75f; else if(norm.x >sicer če (norm.x< 0.0f) tX = 1.0f; else tX = 0.0f; } }else{ tX = atan(norm.z / norm.x); if(norm.x < 0.0f && norm.z >0.0f && normZIsZero)( če(pika(float3(0.0f, 0.0f, 1.0f), odmik)< 0.0f && norm.z < 0.0f) tX += 3.141592; else if(norm.x >0,0f) tX += 3,141592;< 0.0f) tX = 3.141592 * 2.0f + tX; tX = tX / (3.141592 * 2.0f); } tY = acos(norm.y) / 3.141592; return float2(tX, tY); }
sicer če (norm.x

0,0f && norm.z

Podal bom vmesno različico vertex shaderja

//startPos - začetek ploskve kocke //vec1, vec2 - vektorji smeri ploskve kocke //gridStep - velikost celice //sideSize - dolžina roba sektorja //GetTexCoords() - pretvori sferične koordinate v koordinate teksture VOut ProcessVertex(VIn vhod) ( float3 planePos = startPos + vec1 * input.netPos.x * gridStep.x + vec2 * input.netPos.y * gridStep.y; float3 sphPos = normalize(planePos); normalize(startPos + (vec1 + vec2) * sideSize * 0,5f); float2 tc (sphPos, normOffset); float height = mainHeightTexSampler, 0).x; posL = sphPos + height (posL, 1.0f), worldViewProj); output.texCoords = tc; vrni izhod;

5. Razsvetljava

Enako velja za vrednost kosinusa -1, le da sta v tem primeru vektorja usmerjena v nasprotni smeri. Izkaže se, da čim bližje sta vektor normale in vektor na vir svetlobe stanju kolinearnosti, tem večji je koeficient osvetljenosti površine, ki ji normala pripada. Predpostavlja tudi, da površine ni mogoče osvetliti, če njena normala kaže v nasprotni smeri od vira - zato uporabljam samo pozitivne vrednosti kosinusa.

Uporabljam vzporedni vir, zato lahko njegov položaj zanemarim. Upoštevati je treba le to, da uporabljamo vektor za vir svetlobe. Se pravi, če je smer žarkov (1.0, -1.0, 0) - moramo uporabiti vektor (-1.0, 1.0, 0). Edina stvar, ki je za nas težka, je normalni vektor. Izračun normale na ravnino je preprost - proizvesti moramo vektorski izdelek dva vektorja, ki ga opisujeta. Pomembno si je zapomniti, da je vektorski produkt antikomutativen - upoštevati morate vrstni red faktorjev. V našem primeru lahko dobimo normalo na trikotnik, če poznamo koordinate njegovih oglišč v mrežnem prostoru, kot sledi (prosimo, upoštevajte, da ne upoštevam mejnih primerov za p.x in p.y)

Float3 p1 = GetPosOnSphere(p); float3 p2 = GetPosOnSphere(float2(p.x + 1, p.y)); float3 p3 = GetPosOnSphere(float2(p.x, p.y + 1)); float3 v1 = p2 - p1; float3 v2 = p3 - p1; float3 n = normalie(cross(v1, v2));
A to še ni vse. Večina oglišč mreže pripada štirim ravninam hkrati. Da bi dobili sprejemljiv rezultat, morate izračunati povprečno normalno vrednost na naslednji način:

Na = normaliziraj (n0 + n1 + n2 + n3)
Implementacija te metode na GPE je precej draga - potrebujemo dve stopnji za izračun normalnih vrednosti in njihovo povprečje. Poleg tega učinkovitost pušča veliko želenega. Na podlagi tega sem izbral drugo metodo - uporabo običajnega zemljevida (slika 10).


Slika 10 Normalni zemljevid

Načelo dela z njim je enako kot z zemljevidom višin - sferične koordinate mrežnega vrha pretvorimo v teksturne in naredimo izbor. Vendar teh podatkov ne bomo mogli uporabiti neposredno - navsezadnje delamo s kroglo in oglišče ima svojo normalo, ki jo je treba upoštevati. Zato bomo kot koordinate baze TBN uporabili podatke normalne karte. Kaj je osnova? Tukaj je primer za vas. Predstavljajte si, da ste astronavt in sedite na svetilniku nekje v vesolju. Od MCC-ja prejmete sporočilo: "Od svetilnika se morate premakniti 1 meter v levo, 2 metra navzgor in 3 metre naprej." Kako se to lahko izrazi matematično? (1, 0, 0) * 1 + (0, 1, 0) * 2 + (0, 0, 1) * 3 = (1,2,3). IN matrična oblika To enačbo lahko izrazimo na naslednji način:

Zdaj pa si predstavljajte, da tudi vi sedite na svetilniku, le da vam zdaj iz nadzornega centra pišejo: »tam smo vam poslali vektorje smeri - po prvem vektorju se morate premakniti 1 meter, po drugem 2 metra in po drugem 3 metre tretji." Enačba za nove koordinate bo:

Razčlenjeni zapis je videti takole:

Ali v matrični obliki:

Torej je matrika z vektorji V1, V2 in V3 baza, vektor (1,2,3) pa koordinate v prostoru te baze.

Predstavljajmo si zdaj, da imate nabor vektorjev (osnova M) in veste, kje ste glede na svetilnik (točka P). Ugotoviti morate svoje koordinate v prostoru te osnove - kako daleč se morate premakniti po teh vektorjih, da končate na istem mestu. Predstavljajmo si zahtevane koordinate (X)

Če bi bili P, M in X števila, bi preprosto delili obe strani enačbe z M, a žal... Gremo v drugo smer - glede na lastnost inverzne matrike

Kjer je I matrica identitete. V našem primeru je videti takole

Kaj nam to daje? Poskusite pomnožiti to matriko z X in dobili boste

Prav tako je treba pojasniti, da ima matrično množenje lastnost asociativnosti

Vektor lahko povsem upravičeno obravnavamo kot matriko 3 x 1

Ob upoštevanju vsega zgoraj navedenega lahko sklepamo, da moramo, če želimo dobiti X na desni strani enačbe, v pravem vrstnem redu pomnožite obe strani z inverzno M matriko

Ta rezultat bomo potrebovali kasneje.

Zdaj pa se vrnimo k našemu problemu. Uporabil bom ortonormirano osnovo - to pomeni, da so V1, V2 in V3 pravokotni drug na drugega (tvorijo kot 90 stopinj) in imajo enotsko dolžino. V1 bo tangentni vektor, V2 bo bitangentni vektor, V3 pa normala. V tradicionalni obliki, preneseni v DirectX, je matrika videti takole:

Kjer je T tangentni vektor, B bitangentni vektor in N normala. Poiščimo jih. Z navadnim je lažje v glavnem vse to so normalizirane koordinate točke. Bitangensni vektor je enak navzkrižnemu produktu normale in tangentnega vektorja. Najtežje bo s tangentnim vektorjem. Je enaka smeri tangente na krožnico v točki. Poglejmo ta trenutek. Najprej poiščimo koordinate točke na enotskem krogu v ravnini XZ za nek kot a

Smer tangente na krožnico v tej točki lahko najdemo na dva načina. Vektor na točko na krožnici in tangentni vektor sta pravokotna - torej, ker sta funkciji sin in cos periodični - lahko preprosto prištejemo pi/2 kotu a in dobimo želeno smer. Glede na lastnost premika pi/2:

Dobili smo naslednji vektor:

Lahko uporabimo tudi diferenciacijo - glej dodatek 3 za več podrobnosti, na sliki 11 lahko vidite kroglo, za katero je bila zgrajena osnova za vsako vozlišče. Modri ​​vektorji označujejo normale, rdeči - tangentne vektorje, zeleni - bitangentne vektorje.


Sl.11 Krogla z bazami TBN na vsakem oglišču. Rdeči - tangentni vektorji, zeleni - bitangentni vektorji, modri vektorji - normale

Razvrstili smo osnovo - zdaj dobimo običajni zemljevid. Za to bomo uporabili Sobelov filter. Sobelov filter izračuna gradient svetlosti slike na vsaki točki (grobo rečeno, vektor spremembe svetlosti). Načelo filtra je, da morate uporabiti določeno matriko vrednosti, ki se imenuje "Jedro", za vsako slikovno piko in njene sosede znotraj dimenzije te matrike. Recimo, da obdelamo piksel P z jedrom K. Če ni na robu slike, ima osem sosedov - zgoraj levo, zgoraj, zgoraj desno itd. Imenujmo jih tl, t, tb, l, r, bl, b, br. Torej je uporaba jedra K za to slikovno piko naslednja:

Pn = tl * K(0, 0) + t * K(0,1) + tb * K(0,2) +
          l * K(1, 0) + P * K(1,1) + r * K(1,2) +
          bl * K(2, 0) + b * K(2,1) + br * K(2,2)

Ta proces se imenuje "konvolucija". Sobelov filter uporablja dve jedri za izračun navpičnega in vodoravnega gradienta. Označimo ju kot Kx in Ku:

Osnova je tam - lahko jo začnete izvajati. Najprej moramo izračunati svetlost piksla. Za sistem PAL uporabljam pretvorbo iz barvnega modela RGB v model YUV:

Ker pa je naša slika na začetku v sivinah, lahko ta korak preskočimo. Zdaj moramo izvirno sliko "zrušiti" z jedri Kx in Ky. To nam bo dalo komponente X in Y gradienta. Normalna vrednost tega vektorja je lahko tudi zelo uporabna - ne bomo je uporabili, vendar imajo slike, ki vsebujejo normalizirane normalne vrednosti gradienta, nekaj uporabnih uporab. Z normalizacijo mislim na naslednjo enačbo

Kjer je V vrednost, ki jo normaliziramo, sta Vmin in Vmax razpon teh vrednosti. V našem primeru se minimalne in maksimalne vrednosti spremljajo med procesom generiranja. Tukaj je primer izvedbe Sobelovega filtra:

Float SobelFilter::GetGrayscaleData(const Point2 &Coords) ( Point2 coords; coords.x = Math::Saturate(Coords.x, RangeI(0, image.size.width - 1)); coords.y = Math::Saturate( Coords.y, RangeI(0, image.size.height - 1)); int32_t offset = (coords.y * image.size.width + coords.x) * image.pixelSize; const uint8_t *pixel = &image.pixels; return (image.pixelFormat == PXL_FMT_R8) ? dirYVr, magNormVr; for(int32_t y = 0; y< image.size.height; y++) for(int32_t x = 0; x < image.size.width; x++){ float tl = GetGrayscaleData({x - 1, y - 1}); float t = GetGrayscaleData({x , y - 1}); float tr = GetGrayscaleData({x + 1, y - 1}); float l = GetGrayscaleData({x - 1, y }); float r = GetGrayscaleData({x + 1, y }); float bl = GetGrayscaleData({x - 1, y + 1}); float b = GetGrayscaleData({x , y + 1}); float br = GetGrayscaleData({x + 1, y + 1}); float dirX = -1.0f * tl + 0.0f + 1.0f * tr + -2.0f * l + 0.0f + 2.0f * r + -1.0f * bl + 0.0f + 1.0f * br; float dirY = -1.0f * tl + -2.0f * t + -1.0f * tr + 0.0f + 0.0f + 0.0f + 1.0f * bl + 2.0f * b + 1.0f * br; float magNorm = sqrtf(dirX * dirX + dirY * dirY); int32_t ind = y * image.size.width + x; dirXData = dirX; dirYData = dirY; magNData = magNorm; dirXVr.Update(dirX); dirYVr.Update(dirY); magNormVr.Update(magNorm); } if(normaliseDirections){ for(float &dirX: dirXData) dirX = (dirX - dirXVr.minVal) / (dirXVr.maxVal - dirXVr.minVal); for(float &dirY: dirYData) dirY = (dirY - dirYVr.minVal) / (dirYVr.maxVal - dirYVr.minVal); } for(float &magNorm: magNData) magNorm = (magNorm - magNormVr.minVal) / (magNormVr.maxVal - magNormVr.minVal); }
Povedati je treba, da ima Sobelov filter lastnost linearne ločljivosti, zato je to metodo mogoče optimizirati.

Težji del je končan - vse, kar ostane, je zapisati koordinate X in Y smeri gradienta v R in G kanale normalnih slikovnih pik zemljevida. Za koordinato Z uporabljam eno. Za prilagajanje teh vrednosti uporabljam tudi 3D vektor koeficientov. Sledi primer generiranja običajnega zemljevida s komentarji:

//ImageProcessing::ImageData Slika - izvirna slika. Konstruktor vsebuje slikovno obliko in slikovne podatke ImageProcessing::SobelFilter sobelFilter; sobelFilter.Init(Slika); sobelFilter.NormaliseDirections() = false; sobaFilter.Process(); const auto &resX =sobelFilter.GetFilteredData(ImageProcessing::SobelFilter::SOBEL_DIR_X); const auto &resY =sobelFilter.GetFilteredData(ImageProcessing::SobelFilter::SOBEL_DIR_Y); ImageProcessing::ImageData destImage = (DXGI_FORMAT_R8G8B8A8_UNORM, Image.size); size_t dstImageSize = Image.size.width * Image.size.height * destImage.pixelSize; std::vector dstImgPixels(dstImageSize); za (int32_t d = 0; d< resX.size(); d++){ //используем вектор настроечных коэффициентов. У меня он равен (0.03, 0.03, 1.0) Vector3 norm = Vector3::Normalize({resX[d] * NormalScalling.x, resY[d] * NormalScalling.y, 1.0f * NormalScalling.z}); Point2 coords(d % Image.size.width, d / Image.size.width); int32_t offset = (coords.y * Image.size.width + coords.x) * destImage.pixelSize; uint8_t *pixel = &dstImgPixels; //переводим значения из области [-1.0, 1.0] в а затем в область pixel = (0.5f + norm.x * 0.5f) * 255.999f; pixel = (0.5f + norm.y * 0.5f) * 255.999f; pixel = (0.5f + norm.z * 0.5f) * 255.999f; } destImage.pixels = &dstImgPixels; SaveImage(destImage, OutFilePath);
Zdaj bom dal primer uporabe običajnega zemljevida v senčniku:

//texCoords - koordinate teksture, ki smo jih pridobili z metodo, opisano v odstavku 4 //normalL - normala oglišča //lightDir - vektor na svetlobni vir //Ld - barva svetlobnega vira //Kd - barva materiala osvetljena površina float4 normColor = mainNormalTex SampleLevel(mainNormalTexSampler, texCoords, 0); //prenesi vrednost iz območja v [-1.0, 1.0] in normaliziraj rezultat float3 normalT = normalize(2.0f * mainNormColor.rgb - 1.0f); //prevedi koordinato teksture X iz območja v lebdečo ang = texCoords.x * 3.141592f * 2.0f; float3 tangenta; tangenta.x = -sin(ang); tangenta.y = 0,0f; tangenta.z = cos(ang); float3 bitangent = normalize(cross(normalL, tangent)); float3x3 tbn = float3x3(tangenta, bitangenta, normalaL); float3 resNormal = mul(normalT, tbn); float diff = saturate(dot(resNormal, lightDir.xyz)); float4 resColor = Ld * Kd * diff;

6. Raven podrobnosti

No, zdaj je naša pokrajina osvetljena! Lahko poletite na Luno - dodajte zemljevid višin, nastavite barvo materiala, naložite sektorje, nastavite velikost mreže na (16, 16) in ... Ja, nekaj je premajhno - bom dal ( 256, 256) - oh, nekaj upočasnjuje vse in zakaj visoke podrobnosti v oddaljenih sektorjih? Še več, bližje kot je opazovalec planetu, manj sektorjev lahko vidi. Ja... čaka nas še veliko dela! Najprej ugotovimo, kako odstraniti nepotrebne sektorje. Odločilna vrednost tukaj bo višina opazovalca od površine planeta - višja kot je, več sektorjev lahko vidi (slika 12)


Slika 12 Odvisnost višine opazovalca od števila obdelanih sektorjev

Višino najdemo na naslednji način - sestavimo vektor od položaja opazovalca do središča krogle, izračunamo njegovo dolžino in od tega odštejemo vrednost polmera krogle. Prej sem rekel, da če je skalarni produkt vektorja opazovalca in vektorja središča sektorja manjši od nič, potem nas ta sektor ne zanima - zdaj bomo namesto nič uporabili vrednost, ki je linearno odvisna od višina. Najprej definirajmo spremenljivke – tako bomo imeli najmanjšo in največjo vrednost za pikčasti produkt ter najmanjšo in največjo vrednost za višino. Sestavimo naslednji sistem enačb

Zdaj pa izrazimo A v drugi enačbi

Zamenjajmo A iz druge enačbe v prvo

Izrazimo B iz prve enačbe

Zamenjajte B iz prve enačbe v drugo

Zdaj pa zamenjajmo spremenljivke v funkciji

In prejeli bomo

Kjer sta Hmin in Hmax najmanjša in največja vrednost višine, sta Dmin in Dmax najmanjša in največja vrednost skalarnega produkta. To težavo je mogoče rešiti drugače - glej Dodatek 4.

Zdaj moramo razumeti ravni podrobnosti. Vsak od njih bo določil obseg pikčastega produkta. V psevdo kodi je postopek ugotavljanja, ali sektor pripada določeni ravni, videti takole:

Krožite skozi vse sektorje, izračunajte skalarni produkt vektorja s strani opazovalca in vektorja s središčem sektorja, če je skalarni produkt manjši od prej izračunanega najnižjega praga, pojdite na naslednji sektor, krožite skozi ravni podrobnosti, če je skalarni produkt v mejah, določenih za to raven, dodajte sektor tej ravni, konec cikla po stopnjah podrobnosti konec cikla za vse sektorje
Izračunati moramo obseg vrednosti za vsako raven. Najprej sestavimo sistem dveh enačb

Ko smo ga rešili, dobimo

S pomočjo teh koeficientov definiramo funkcijo

Kjer je Rmax razpon skalarnega produkta (D(H) - Dmin), je Rmin najmanjši razpon, določen z ravnjo. Uporabljam vrednost 0,01. Zdaj moramo rezultat odšteti od Dmax

S to funkcijo dobimo območja za vse nivoje. Tukaj je primer:

Const float dotArea = dotRange.maxVal - dotRange.minVal; const float Rmax = dotArea, Rmin = 0,01f; float lodsCnt = lods.size(); float A = Rmax; float B = powf(Rmin / Rmax, 1,0f / (lodsCnt - 1,0f)); za (velikost_t g = 0; g< lods.size(); g++){ lods[g].dotRange.minVal = dotRange.maxVal - A * powf(B, g); lods[g].dotRange.maxVal = dotRange.maxVal - A * powf(B, g + 1); } lods.dotRange.maxVal = 1.0f;
Zdaj lahko določimo, kateri stopnji podrobnosti pripada sektor (slika 13).


Slika 13 Barvno razlikovanje sektorjev glede na ravni podrobnosti

Nato morate ugotoviti velikost mrež. Shranjevanje lastne mreže za vsako raven bo zelo drago - veliko bolj učinkovito je spreminjanje podrobnosti ene mreže sproti s pomočjo teselacije. Da bi to naredili, moramo poleg običajnih senčil oglišč in slikovnih pik implementirati tudi senčnike trupa in domene. V senčniku Hull je glavna naloga priprava kontrolnih točk. Sestavljen je iz dveh delov - glavne funkcije in funkcije, ki izračunava parametre kontrolne točke. Določiti morate vrednosti za naslednje atribute:

domena
pregrajevanje
izhodna topologija
izhodne kontrolne točke
patchconstantfunc
Tukaj je primer senčila Hull za trikotno popločanje:

Struct PatchData (lebdeči robovi: SV_TessFactor; lebdeči znotraj: SV_InsideTessFactor;); PatchData GetPatchData(InputPatch Patch, uint PatchId: SV_PrimitiveID) ( PatchData output; float tessFactor = 2.0f; output.edges = tessFactor; output.edges = tessFactor; output.edges = tessFactor; output.inside = tessFactor; return output; ) VIn ProcessHull(InputPatch Patch, uint PointId: SV_OutputControlPointID, uint PatchId: SV_PrimitiveID) ( return Patch; )
Vidite, glavno delo je opravljeno v GetPatchData(). Njegova naloga je ugotoviti faktor teselacije. O tem bomo govorili kasneje, zdaj pa pojdimo k senčniku domene. Prejema kontrolne točke iz Hull shaderja in koordinate iz tesselatorja. Novo vrednost koordinat položaja ali teksture v primeru delitve s trikotniki je treba izračunati po naslednji formuli

N = C1 * F.x + C2 * F.y + C3 * F.z

Kjer so C1, C2 in C3 vrednosti kontrolnih točk, so F koordinate teselatorja. Tudi v senčniku domene morate nastaviti atribut domene, katerega vrednost ustreza tisti, določeni v senčniku Hull. Tukaj je primer senčila domene:

Cbuffer buff0: register(b0) ( matrix worldViewProj; ) struct PatchData ( plavajoči robovi : SV_TessFactor; lebdeči znotraj: SV_InsideTessFactor; ); PIN ProcessDomain(PatchData Patch, float3 Coord: SV_DomainLocation, const OutputPatch
Tri) ( float3 posL = Tri.posL * Coord.x + Tri.posL * Coord.y + Tri.posL * Coord.z; float2 texCoords = Tri.texCoords * Coord.x + Tri.texCoords * Coord.y + Tri .texCoords * PIN output.posH = mul(float4(posL, 1.0f), output.normalW = Tri.normalW; vrni izhod;

Vloga vertex shaderja je v tem primeru zmanjšana na minimum - zame preprosto "prenese" podatke na naslednjo stopnjo.

Zdaj moramo izvesti nekaj podobnega. Naša primarna naloga je izračunati faktor teselacije oziroma natančneje izrisati njegovo odvisnost od višine opazovalca. Ponovno sestavimo sistem enačb

Če ga rešimo na enak način kot prej, dobimo
Kjer sta Tmin in Tmax najmanjši in največji koeficient teselacije, sta Hmin in Hmax najmanjša in največja vrednost višine opazovalca. Moj najmanjši koeficient teselacije je ena. maksimum je določen za vsako stopnjo posebej

(na primer 1, 2, 4, 16).

Vzemimo decimalni logaritem obeh strani enačbe

Glede na lastnost logaritmov lahko enačbo prepišemo takole

Zdaj moramo samo obe strani razdeliti z log(2)

A to še ni vse. X je približno 2,58. Nato morate ponastaviti delni del in dvigniti dve na potenco dobljenega števila. Tukaj je koda za izračun faktorjev teselacije za ravni podrobnosti

Float h = camera->GetHeight(); const RangeF &hR = višinaRange; for(LodsStorage::Lod &lod: lods)( //izpeljano iz sistema //A + B * Hmax = Lmin //A + B * Hmin = Lmax //in pridobivanje A in nato zamenjava B v drugi enakosti float mTf = (float )lod.MaxTessFactor(); float tessFactor = (mTf - 1.0f) * (hR.minVal - hR.maxVal)); (1.0f, mTf)); = pow(2.0f, floor(log(tessFactor) / log(2))));

7. Hrup

Poglejmo, kako lahko povečamo podrobnosti pokrajine, ne da bi spremenili velikost zemljevida višin. Kar mi pride na misel, je, da spremenim vrednost višine na vrednost, pridobljeno iz teksture gradientnega šuma. Koordinate, na katerih bomo vzorčili, bodo N-krat večje od glavnih. Pri vzorčenju bo uporabljen tip zrcalnega naslavljanja (D3D11_TEXTURE_ADDRESS_MIRROR) (glejte sliko 14).


Slika 14 Krogla z zemljevidom višine + krogla z zemljevidom šuma = krogla s končno višino

V tem primeru se višina izračuna na naslednji način:

//float2 tc1 - koordinate teksture, dobljene iz normalizirane točke, //kot je opisano prej //texCoordsScale - množitelj koordinat teksture. V mojem primeru je enaka vrednosti 300 //mainHeightTex, mainHeightTexSampler - tekstura zemljevida višin //distHeightTex, distHeightTexSampler - tekstura gradientnega šuma //maxTerrainHeight - največja višina pokrajine. V mojem primeru 0,03 float2 tc2 = tc1 * texCoordsScale; float4 mainHeightTexColor = mainHeightTex.SampleLevel(mainHeightTexSampler, tc1, 0); float4 distHeightTexColor = distHeightTex.SampleLevel(distHeightTexSampler, tc2, 0); plavajoča višina = (mainHeighTexColor.x + distHeighTexColor.x) * maxTerrainHeight;
Do sedaj je periodična narava močno izražena, vendar se bo z dodatkom osvetlitve in teksturiranja situacija spremenila na bolje. Kaj je gradientna tekstura šuma? Grobo rečeno, gre za mrežo naključnih vrednosti. Ugotovimo, kako uskladiti velikosti rešetk z velikostmi teksture. Recimo, da želimo ustvariti teksturo šuma 256 krat 256 slikovnih pik. Vse je preprosto, če dimenzije rešetke sovpadajo z dimenzijami teksture, bomo na televizorju dobili nekaj podobnega belemu šumu. Kaj pa, če ima naša rešetka dimenzije, recimo, 2 krat 2? Odgovor je preprost - uporabite interpolacijo. Ena formulacija linearne interpolacije izgleda takole:

To je najhitrejša, a hkrati najmanj primerna možnost za nas. Bolje je uporabiti interpolacijo na osnovi kosinusa:

Vendar ne moremo preprosto interpolirati med vrednostmi na robovih diagonale (spodnji levi in ​​zgornji desni kot celice). V našem primeru bo treba interpolacijo uporabiti dvakrat. Predstavljajmo si eno od mrežnih celic. Ima štiri vogale – poimenujmo jih V1, V2, V3, V4. Tudi znotraj te celice bo obstajal lasten dvodimenzionalni koordinatni sistem, kjer točka (0, 0) ustreza V1 in točka (1, 1) V3 (glej sliko 15a). Da bi dobili vrednost s koordinatami (0,5, 0,5), moramo najprej dobiti dve X-interpolirani vrednosti - med V1 in V4 ter med V2 in V3, in končno Y-interpolacijo med tema vrednostma (sl. 15b).

Tukaj je primer:

Float2 coords(0.5f, 0.5f) float4 P1 = lerp(V1, V4, coords.x); float4 P2 = lerp(V2, V3, coords.x); float4 P = lerp(P1, P2, coords.y)


Slika 15 a - Slika mrežne celice s koordinatami V1, V2, V3 in V4. b - Zaporedje dveh interpolacij na primeru celice

Zdaj naredimo naslednje - za vsak piksel teksture hrupa vzemimo interpolirano vrednost za mrežo 2x2, nato ji dodamo interpolirano vrednost za mrežo 4x4, pomnoženo z 0,5, nato za mrežo 8x8, pomnoženo z 0,25 itd. .do določene meje - to se imenuje adicijska oktava (slika 16). Formula izgleda takole:


Slika 16 Primer dodajanja oktav

Tukaj je primer izvedbe:

Za(int32_t x = 0; x< size.width; x++) for(int32_t y = 0; y < size.height; y++){ float val = 0.0f; Vector2 normPos = {(float)x / (float)(sideSize - 1), (float)y / (float)(sideSize - 1)}; for(int32_t o = 0; o < octavesCnt; o++){ float frequency = powf(2.0f, (float)(startFrequency + o)); float intencity = powf(intencityFactor, (float)o); Vector2 freqPos = normPos * frequency; Point2 topLeftFreqPos = Cast(freqPos);
Tudi za V1, V2, V3 in V4 lahko dobite vsoto iz same vrednosti in njenih sosedov takole:

Float GetSmoothValue(const Point2 &Coords) ( float koti = (GetValue((Coords.x - 1, Coords.y - 1)) + GetValue((Coords.x + 1, Coords.y - 1)) + GetValue((Koord .x - 1, Coords.y + 1)) + GetValue((Coords.x + 1, Coords.y + 1))) / 16.0f; plavajoče strani = (GetValue((Coords.x - 1, Coords.y) )) + GetValue((Koord.x + 1, Koord.y)) + GetValue((Koord.x, Koord.y - 1)) + GetValue((Koord.x, Koord.y + 1))) / 8.0 f; float center = GetValue(Coords) / 4.0f; vrni središče + stranice + vogali )
in uporabite te vrednosti za interpolacijo. Tukaj je preostanek kode:

Float GetInterpolatedValue(const Point2 &TopLeftCoord, const Point2 &BottomRightCoord, float XFactor, float YFactor) ( Point2 tlCoords(TopLeftCoord.x, TopLeftCoord.y); Point2 trCoords(BottomRightCoord.x, TopLeftCoord.y); Point2 brC oords(Bottom RightCoord.x, BottomRightCoords(TopLeftCoord.y); float tl = (useSmoothValues) : GetValue(tlCoords) ? ); Float Br = (USESMOTHVALUS) ? OTTOMVAL = MATH :: Kosinterpolacija (BL, BR, XFACTOR); topVal = Math::CosInterpolation(tl, tr, XFactor);
V zaključku pododdelka želim povedati, da je vse, kar sem opisal do te točke, nekoliko drugačna izvedba Perlinovega šuma od kanoničnega.
Razvrstili smo višine - zdaj pa poglejmo, kaj storiti z normali. Tako kot pri glavnem zemljevidu višin moramo iz teksture hrupa ustvariti normalni zemljevid. Nato v senčniku preprosto dodamo normalo iz glavnega zemljevida z normalko iz teksture šuma. Moram reči, da to ni povsem pravilno, vendar daje sprejemljiv rezultat. Tukaj je primer:
//float2 texCoords1 - koordinate teksture, pridobljene iz normalizirane točke, kot je opisano prej //mainNormalTex, mainNormalTexSampler - glavni normalni zemljevid //distNormalTex, distNormalTexSampler - normalni zemljevid hrupa gradienta float2 texCoords2 = texCoords1 * texCoordsScale; float4 mainNormColor = mainNormalTex.SampleLevel(mainNormalTexSampler, TexCoords1, 0); float4 distNormColor = distNormalTex.SampleLevel(distNormalTexSampler, TexCoords2, 0); float3 mainNormal = 2.0f * mainNormColor.rgb - 1.0f; float3 distNormal = 2.0f * distNormColor.rgb - 1.0f; float3 normal = normalize(mainNormal + distNormal);

8. Instanciranje strojne opreme

Naredimo optimizacijo. Zdaj je cikel risanja sektorjev v psevdokodi videti takole

Preletite vse sektorje, izračunajte skalarni produkt vektorja s točko in vektorja s središčem sektorja, če je večji od nič, nastavite vrednost S v podatkih senčila, nastavite vrednost V1 v senčniku podatki, nastavite vrednost V2 v podatkih shaderja, narišite mrežo, konec pogoja, konec zanke čez vse sektorje
Učinkovitost tega pristopa je izjemno nizka. Obstaja več možnosti optimizacije - za vsako ravnino kocke lahko zgradite štiridrevo, da ne izračunate skalarnega produkta za vsak sektor. Prav tako lahko posodobite vrednosti V1 in V2 ne za vsak sektor, ampak za šest ravnin kocke, ki jim pripadajo. Izbral sem tretjo možnost - instanciranje. Na kratko o tem, kaj je. Recimo, da želite narisati gozd. Imate drevesni model, imate pa tudi nabor transformacijskih matrik - položaje drevesa, morebitno skaliranje ali rotacijo. Ustvarite lahko en medpomnilnik, ki bo vseboval vrhove vseh dreves, pretvorjenih v svetovni prostor - to je dobra možnost, gozd ne teče po zemljevidu. Kaj pa, če morate izvesti transformacije - recimo drevesa, ki se zibljejo v vetru. To lahko storite - kopirajte podatke o točkah modela N-krat v en medpomnilnik in dodate indeks drevesa (od 0 do N) podatkom o točki. Nato posodobimo matriko transformacijskih matrik in jo kot spremenljivko posredujemo senčniku. V senčniku izberemo želeno matriko z drevesnim indeksom. Kako se lahko izognete podvajanju podatkov? Najprej bi vas rad opozoril na dejstvo, da je te točke mogoče zbrati iz več medpomnilnikov. Če želite opisati vozlišče, morate navesti izvorni indeks v polju InputSlot strukture D3D11_INPUT_ELEMENT_DESC. To je mogoče uporabiti pri izvajanju preoblikovanja obrazne animacije - recimo, da imate dva medpomnilnika vozlišč, ki vsebujeta dve stanji obraza, in želite linearno interpolirati ti vrednosti. Točko opišemo tako:

D3D11_INPUT_ELEMENT_DESC desc = ( /*del1*/ ("POZICIJA", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0), ("NORMALNO", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D 11_INPUT_PER_VERTEX_DATA, 0), ("TEXCOORD", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0), /*part2*/ ("POSITION", 1, DXGI_FORMAT_R32G32B32_FLOAT, 1, 0, D3D11_INPUT_PER_VERTEX_DATA, 0), ("NORMA" AL", 1, DXGI_FORMAT_R32G32B32_FLOAT, 1, 12 , D3D11_INPUT_PER_VERTEX_DATA, 0), ("TEXCOORD", 1, DXGI_FORMAT_R32G32B32_FLOAT, 1, 24, D3D11_INPUT_PER_VERTEX_DATA, 0) )
v senčniku mora biti oglišče opisano na naslednji način:

Struktura VIn ( float3 položaj1: POSITION0; float3 normal1: NORMAL0; float2 tex1: TEXCOORD0; float3 položaj2: POSITION1; float3 normal2: NORMAL1; float2 tex2: TEXCOORD1; )
potem samo interpolirate vrednosti

Float3 res = lerp(input.position1, input.position2, faktor);
Zakaj to govorim? Vrnimo se k primeru dreves. Verteks bomo zbirali iz dveh virov - prvi bo vseboval položaj v lokalnem prostoru, teksturne koordinate in normalo, drugi bo vseboval transformacijsko matriko v obliki štirih štiridimenzionalnih vektorjev. Opis vrha izgleda takole:

D3D11_INPUT_ELEMENT_DESC desc = ( /*del1*/ ("POZICIJA", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0), ("NORMALNO", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D 11_INPUT_PER_VERTEX_DATA, 0), ("TEXCOORD", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0), /*part2*/ ("SVET", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1), ("W ORLD", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 16, D3D11_INPUT_PER_INSTANCE_DATA , 1), ("SVET", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 32, D3D11_INPUT_PER_INSTANCE_DATA, 1), ("SVET", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 48, ANCE_DATA, 1), )
Upoštevajte, da je v drugem delu polje InputSlotClass enako D3D11_INPUT_PER_INSTANCE_DATA, polje InstanceDataStepRate pa je enako ena (za kratek opis polja InstanceDataStepRate glejte Dodatek 1). V tem primeru bo zbiralnik uporabil podatke celotnega medpomnilnika iz vira s tipom D3D11_INPUT_PER_VERTEX_DATA za vsak element iz vira s tipom D3D11_INPUT_PER_INSTANCE_DATA. V tem primeru lahko v senčniku te točke opišemo na naslednji način:

Struct VIn ( float3 posL: POSITION; float3 normalL: NORMAL; float2 tex: TEXCOORD; row_major float4x4 world: WORLD; );
Z ustvarjanjem drugega medpomnilnika z atributoma D3D11_USAGE_DYNAMIC in D3D11_CPU_ACCESS_WRITE ga lahko posodobimo s strani CPU. To vrsto geometrije morate narisati s klici DrawInstanced() ali DrawIndexedInstanced(). Obstajajo tudi klici DrawInstancedIndirect() in DrawIndexedInstancedIndirect() - o njih glejte Dodatek 2.

Tu je primer nastavljanja medpomnilnikov in uporabe funkcije DrawIndexedInstanced():

//vb - medpomnilnik za vozlišča //tb - medpomnilnik za "primerke" //ib - medpomnilnik za indekse //vertexSize - velikost elementa v medpomnilniku za vozlišča //instanceSize - velikost elementa v medpomnilniku za "primerke" //indicesCnt - število indeksov //instancesCnt - število "primerkov" std::vector medpomnilniki = (vb, tb); std::vector koraki = (Velikost oglišča, Velikost primerka); std::vector odmiki = (0, 0); deviceContext->IASetVertexBuffers(0,buffers.size(),&buffers,&strides,&offsets); deviceContext->IASetIndexBuffer(ib, DXGI_FORMAT_R32_UINT, 0); deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->DrawIndexedInstanced(indicesCnt, instancesCnt, 0, 0, 0);
Zdaj pa se končno vrnimo k naši temi. Sektor lahko opišemo s točko na ravnini, ki ji pripada, in dvema vektorjema, ki opisujeta to ravnino. Zato bo vrh sestavljen iz dveh virov. Prvi so koordinate prostora mreže, drugi pa sektorski podatki. Opis vrha izgleda takole:

Std::vector meta = ( //koordinate v mrežnem prostoru ("POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0) //prvi vektor obraza ("TEXCOORD", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, 0, UT_PER_INSTANCE_DATA , 1), / /drugi obrazni vektor ("TEXCOORD", 1, DXGI_FORMAT_R32G32B32_FLOAT, 1, 12, D3D11_INPUT_PER_INSTANCE_DATA, 1), //začetek obraza ("TEXCOORD", 2, DXGI_FORMAT_R32G32B32_FLOAT, 1, 24, D3D11_INPUT_PER_INST ANCE_DATA, 1) )
Upoštevajte, da uporabljam 3D vektor za shranjevanje koordinat mrežnega prostora (koordinata z se ne uporablja)

9. Odstrel frustuma

Druga pomembna komponenta optimizacije je izrezovanje piramide vidnosti (Frustum culling). Piramida vidnosti je območje prizora, ki ga kamera »vidi«. Kako ga zgraditi? Najprej si zapomnite, da je lahko točka v štirih koordinatnih sistemih - lokalnem, svetovnem, poglednem in projekcijskem koordinatnem sistemu. Prehajanje med njimi se izvaja preko matric – sveta, vrste in projekcijske matrike, preobrazbe pa morajo potekati zaporedno – iz lokalnega v svet, iz sveta v vrsto in nazadnje iz vrste v projekcijski prostor. Vse te transformacije je mogoče združiti v eno z množenjem teh matrik.

Uporabljamo perspektivno projekcijo, ki vključuje tako imenovano "enotno delitev" - po množenju vektorja (Px, Py, Pz, 1) s projekcijsko matriko je treba njegove komponente deliti s komponento W tega vektorja. Po prehodu v prostor projekcije in enotni delitvi se točka znajde v prostoru NDC. NDC prostor je niz treh koordinat x, y, z, kjer x in y pripadata [-1, 1], z pa - (Treba je povedati, da so v OpenGL parametri nekoliko drugačni).

Zdaj pa se lotimo reševanja našega problema. V mojem primeru se piramida nahaja v vidnem prostoru. Potrebujemo šest ravnin, ki jo opisujejo (slika 17a). Ravnino lahko opišemo z normalo in točko, ki tej ravnini pripada. Najprej dobimo točke - za to vzamemo naslednji niz koordinat v prostoru NDC:

Std::vector TočkeN = ((-1,0f, -1,0f, 0,0f, 1,0f), (-1,0f, 1,0f, 0,0f, 1,0f), (1,0f, 1,0f, 0,0f, 1,0f), (1,0 f, -1,0f, 0,0f, 1,0f), (-1,0f, -1,0f, 1,0f, 1,0f), (-1,0f, 1,0f, 1,0f, 1,0f), ( 1,0f, 1,0f , 1.0f, 1.0f), (1.0f, -1.0f, 1.0f, 1.0f) );
Poglejte, pri prvih štirih točkah je vrednost z 0 - to pomeni, da pripadajo bližnji odsečni ravnini, pri zadnjih štirih je z enak 1 - pripadajo oddaljeni odsečni ravnini. Zdaj je treba te točke pretvoriti v prostor za ogled. ampak kako?

Spomnite se primera z astronavtom - tako je tudi tukaj. Točke moramo pomnožiti z matriko inverzne projekcije. Res je, da moramo po tem še vedno vsakega od njih razdeliti na njegovo koordinato W. Posledično dobimo potrebne koordinate (slika 17b). Poglejmo zdaj normale - usmerjene morajo biti znotraj piramide, zato moramo izbrati potreben vrstni red za izračun vektorskega produkta.

Matrix4x4 invProj = Matrix4x4::Inverse(camera->GetProjMatrix()); std::vector TočkeV; for(const Point4F &pN: pointsN)( Point4F pV = invProj.Transform(pN); pV /= pV.w; pointsV.push_back(Cast (pV)); ) ravnine = (točkeV, točkeV, točkeV); //blizu ravninskih ravnin = (točkeV, točkeV, točkeV); //ravnine daljne ravnine = (točkeV, točkeV, točkeV); //leva ravnina ravnine = (točkeV, točkeV, točkeV); //desna ravnina ravnine = (točkeV, točkeV, točkeV); //ravnine zgornje ravnine = (točkeV, točkeV, točkeV); //ravnine spodnje ravnine.normal *= -1.0f; ravnine.normal *= -1,0f;


Slika 17 Piramida vidnosti

Piramida je zgrajena - čas je, da jo uporabite. Ne rišemo tistih sektorjev, ki ne spadajo v piramido. Da bi ugotovili, ali je sektor znotraj piramide vidnosti, bomo preverili mejno kroglo, ki se nahaja v središču tega sektorja. To ne daje natančnih rezultatov, vendar v tem primeru Ne vidim nič slabega, če je narisanih več dodatnih sektorjev. Polmer krogle se izračuna na naslednji način:

Kjer je TR zgornji desni kot sektorja, BL spodnji levi kot. Ker imajo vsi sektorji enako površino, je dovolj, da polmer izračunamo enkrat.

Kako lahko ugotovimo, ali je krogla, ki opisuje sektor, znotraj piramide vidnosti? Najprej moramo ugotoviti, ali krogla seka ravnino in, če ne, na kateri strani se nahaja. Postavimo vektor v središče krogle

Kjer je P točka na ravnini in S središče krogle. Zdaj pa izračunajmo skalarni produkt tega vektorja in normale ravnine. Orientacijo lahko določimo z uporabo znaka pikčastega produkta - kot smo že omenili, če je pozitivna, je krogla pred ravnino, če je negativna, potem je krogla zadaj. Ostaja ugotoviti, ali krogla seka ravnino. Vzemimo dva vektorja - N (normalni vektor) in V. Sedaj konstruirajmo vektor od N proti V - imenujemo ga K. Torej, najti moramo takšno dolžino N, da tvori kot 90 stopinj s K (formalno če govorimo, tako da sta bila N in K pravokotna). Oki doki, poglej sliko 18a - iz lastnosti pravokotnega trikotnika to vemo

Najti moramo kosinus. Uporaba prej omenjene lastnosti pikčastega produkta

Obe strani delite z |V|*|N| in dobimo

Uporabimo ta rezultat:

Ker |V| je samo število, lahko ga zmanjšamo za |V| in potem dobimo

Ker je vektor N normaliziran, je zadnji korak ta, da ga preprosto pomnožimo z dobljeno vrednostjo, sicer je treba vektor normalizirati - v tem primeru končna enačba izgleda takole:

Kjer je D naš novi vektor. Ta proces se imenuje "vektorska projekcija" (slika 18b). Toda zakaj potrebujemo to? Vemo, da je vektor določen z njegovo dolžino in smerjo in se na noben način ne spreminja glede na njegov položaj - to pomeni, da če postavimo D tako, da kaže na S, potem bo njegova dolžina enaka najmanjši razdalji od S na ravnino (slika 18c)


Slika 18 a Projekcija N na V, b Vizualni prikaz dolžine projicirane N glede na točko, c Vizualni prikaz
dolžina projicirane N kot uporabljena za kroglo s središčem v S

Ker ne potrebujemo projiciranega vektorja, moramo le izračunati njegovo dolžino. Glede na to, da je N enotski vektor, moramo izračunati samo pikčasti produkt V in N. Če vse skupaj sestavimo, lahko končno sklepamo, da krogla seka ravnino, če je vrednost pikčastega produkta vektorja s središčem krogla in normala na ravnino je večja od nič in manjša od vrednosti polmera te krogle.

Da bi trdili, da je krogla znotraj piramide vidnosti, se moramo prepričati, da bodisi seka eno od ravnin bodisi je pred vsako od njih. Vprašanje lahko postavite drugače - če se krogla ne seka in se nahaja za vsaj eno od ravnin, je zagotovo zunaj piramide vidnosti. To bomo storili. Upoštevajte, da središče krogle prenesem v isti prostor, v katerem se nahaja piramida - v pogledni prostor.

Bool Frustum::TestSphere(const Point3F &Pos, float Radius, const Matrix4x4 &WorldViewMatrix) const ( Point3F posV = WorldViewMatrix.Transform(Pos); for(const Plane &pl: ravnine)( Vector3 toSphPos = posV - pl.pos; if(Vector3 ::Pika(toSphPos, pl.normal)< -Radius) return false; } return true; }

10. Razpoke

Druga težava, ki jo moramo rešiti, so razpoke na mejah nivojev podrobnosti (slika 19).


Slika 19 prikaz pokrajinskih razpok

Najprej moramo identificirati tiste sektorje, ki ležijo na meji ravni podrobnosti. Na prvi pogled se zdi, da je to naloga, ki zahteva veliko virov - navsezadnje se število sektorjev na vsaki ravni nenehno spreminja. Če pa uporabite podatke o sosednosti, postane rešitev veliko enostavnejša. Kaj so sosednji podatki? Poglejte, vsak sektor ima štiri sosede. Niz sklicev nanje - pa naj gre za kazalce ali indekse - so sosednji podatki. Z njihovo pomočjo zlahka ugotovimo, kateri sektor leži na meji – preverite le, kateri ravni pripadajo njegovi sosedje.

No, poiščimo sosede vsakega sektorja. In spet, ni nam treba brskati po vseh sektorjih. Predstavljajmo si, da delamo s sektorjem s koordinatama X in Y v mrežnem prostoru.

Če se ne dotika roba kocke, bodo koordinate njegovih sosedov naslednje:

Zgornji sosed - (X, Y - 1)
Spodnji sosed - (X, Y + 1)
Levi sosed - (X - 1, Y)
Desni sosed - (X + 1, Y)

Če se sektor dotika roba, ga postavimo v posebno posodo. Po obdelavi vseh šestih ploskev bo vsebovala vse robne sektorje kocke. V tem vsebniku moramo opraviti iskanje. Vnaprej izračunajmo robove za vsak sektor:

Struct SectorEdges ( CubeSectors::Sector *owner; typedef std::pair Edge; sektorjiRobovi; //borderSectors - vsebnik z obrobnimi sektorji za(CubeSectors::Sector &sec: borderSectors)( // vsak sektor vsebuje dva vektorja, ki opisujeta ploskev kocke // kateri pripada Vector3 v1 = sec.vec1 * sec.sideSize ; Vector3 v2 * sec.sideSize; //sec.startPos - začetek sektorja v lokalnem prostoru SectorEdges.owner = sec.startPos + v1); , sec.startPos + v2, sec.startPos + v1); sec.startPos + v1; .push_back(secEdges)
Sledi samo iskanje

For(SectorEdges &edgs: sectorsEdges) for(size_t e = 0; e< 4; e++) if(edgs.owner->adjacency[e] == nullptr) FindSectorEdgeAdjacency(edgs, (AdjacencySide)e, sectorsEdges);
Funkcija FindSectorEdgeAdjacency() izgleda takole

Void CubeSectors::FindSectorEdgeAdjacency(SectorEdges &Sector, CubeSectors::AdjacencySide Side, std::vector &Neibs) ( SectorEdges::Edge &e = Sector.edges; for(SectorEdges &edgs2: Neibs)( if(edgs2.owner == Sector.owner) continue; for(size_t e = 0; e< 4; e++){ SectorEdges::Edge &e2 = edgs2.edges[e]; if((Math::Equals(e.first, e2.first) && Math::Equals(e.second, e2.second)) || (Math::Equals(e.second, e2.first) && Math::Equals(e.first, e2.second))) { Sector.owner->sosedstvo = edgs2.owner;
edgs2.owner->adjacency[e] = Sector.owner;

vrnitev;
) ) ) )

Std::vector Upoštevajte, da posodobimo podatke o sosednostih za dva sektorja – iskanega (Sektor) in najdenega soseda.
Ko razdelimo sektorje po stopnjah podrobnosti, določimo sosednje koeficiente teselacije za vsak sektor:

For(LodsStorage::Lod &lod: lods)( const std::vector &sektorji = lod.GetSectors();
bool lastLod = lod.GetInd() == lods.GetCount() - 1;

for(Sektor *s: sektorji)( int32_t tessFacor = s->GetTessFactor(); s->GetBorderTessFactor() = ( GetNeibTessFactor(s, Sector::ADJ_BOTTOM, tessFacor, lastLod), GetNeibTessFactor(s, Sector::ADJ_LEFT, tessFacor, lastLod), GetNeibTessFactor(s, Sector::ADJ_TOP, tessFacor, lastLod), GetNeibTessFactor(s, Sector::ADJ_RIGHT, tessFacor, lastLod) ) )< TessFactor) ? (float)neibTessFactor: 0.0f; }
Funkcija, ki išče sosednji faktor teselacije:

Float Terrain::GetNeibTessFactor(Sector *Sec, Sector::AdjacencySide Side, int32_t TessFactor, bool IsLastLod) ( Sector *neib = Sec->GetAdjacency(); int32_t neibTessFactor = neib->GetTessFactor(); return (neibTessFactor

Če vrnemo nič, potem nas sosed na stranski strani ne zanima. Naj skočim naprej in povem, da moramo odpraviti razpoke s strani nivoja z visokim koeficientom teselacije.
Zdaj pa preidimo na senčnik. Naj vas spomnim, da moramo najprej pridobiti koordinate mreže s pomočjo koordinat teselatorja. Nato se te koordinate pretvorijo v točko na strani kocke, ta točka se normalizira - in zdaj imamo točko na krogli:

Float3 p = Tri.netPos * Coord.x + Tri.netPos * Coord.y + Tri.netPos * Coord.z; float3 planePos = Tri.startPos + Tri.vec1 * p.x * gridStep.x + Tri.vec2 * p.y * gridStep.y; float3 sphPos = normalize(planePos);
Najprej moramo ugotoviti, ali oglišče pripada prvi ali zadnji vrstici mreže ali prvemu ali zadnjemu stolpcu - v tem primeru oglišče pripada robu sektorja. Vendar to ni dovolj - ugotoviti moramo, ali oglišče pripada meji LOD. Za to uporabimo podatke o sosednjih sektorjih oziroma njihove ravni teselacije: Float4 bTf = Tri.borderTessFactor; bool isEdge = (bTf.x != 0,0f && p.y == 0,0f) || //spodaj (bTf.y != 0,0f && p.x == 0,0f) || //levo (bTf.z != 0.0f && p.y == gridSize.y) || //zgoraj (bTf.w != 0.0f && p.x == gridSize.x) //desno- dejansko odpravljanje razpok. Poglejte sliko 20. Rdeča črta je ploskev vrha, ki pripada drugi ravni podrobnosti. Dve modri črti sta robovi tretje stopnje podrobnosti. V3 potrebujemo, da pripada rdeči črti - torej leži na robu drugega nivoja. Ker sta višini V1 in V2 enaki za obe ravni, lahko V3 najdemo z linearno interpolacijo med njima


Sl. 20 Prikaz ploskev, ki tvorijo razpoko v obliki črt

Zaenkrat nimamo ne V1 in V2, ne koeficienta F. Najprej moramo najti indeks točke V3. To pomeni, da če ima mreža velikost 32 x 32 in koeficient teselacije štiri, bo ta indeks od nič do 128 (32 * 4). Koordinate v mrežnem prostoru p - znotraj že imamo ta primer lahko so na primer (15.5, 16). Če želite dobiti indeks, morate eno od koordinat p pomnožiti s koeficientom teselacije. Potrebujemo tudi začetek obraza in smer do njegovega konca - enega od vogalov sektorja.

Plavajoči robVertInd = 0,0f; float3 edgeVec = float3(0.0f, 0.0f, 0.0f); float3 startPos = float3(0.0f, 0.0f, 0.0f); uint neibTessFactor = 0; if(bTf.x != 0.0f && p.y == 0.0f)( // bottom edgeVertInd = p.x * Tri.tessFactor; edgeVec = Tri.vec1; startPos = Tri.startPos; neibTessFactor = (uint)Tri.borderTessFactor.x ; )else if(bTf.y != 0.0f && p.x == 0.0f)( // left edgeVertInd = p.y * Tri.tessFactor; edgeVec = Tri.vec2; startPos = Tri.startPos; neibTessFactor = (uint)Tri. borderTessFactor.y; )else if(bTf.z != 0.0f && p.y == gridSize.y)( // top edgeVertInd = p.x * Tri.tessFactor; edgeVec = Tri.vec1; startPos = Tri.startPos + Tri.vec2 * (gridStep.x * gridSize.x); neibTessFactor = (uint)Tri.borderTessFactor.z )else if(bTf.w != 0.0f && p.x == gridSize.x)( // right edgeVertInd = p.y * Tri .tessFactor; edgeVec = Tri.vec2; startPos = Tri.startPos + Tri.vec1 * (gridStep.x * gridSize.x);
Nato moramo najti indeksa za V1 in V2. Predstavljajte si, da imate številko 3. Najti morate dve najbližji števili, ki sta večkratnika dveh. Če želite to narediti, izračunajte ostanek pri deljenju tri z dva - enak je ena. Nato ta ostanek odštejete ali prištejete tri in dobite želeni rezultat. Enako z indeksi, le da bomo namesto dveh imeli razmerje koeficientov teselacije ravni podrobnosti. Se pravi, če ima tretja raven koeficient 16, druga pa 2, potem bo razmerje enako 8. Zdaj, da bi dobili višine, morate najprej pridobiti ustrezne točke na krogli z normalizacijo točke na obrazu. Začetek in smer roba smo že pripravili – preostalo je le še izračunati dolžino vektorja od V1 do V2. Ker je dolžina roba prvotne mrežne celice gridStep.x, je dolžina, ki jo potrebujemo, gridStep.x / Tri.tessFactor. Nato bomo iz točk na krogli dobili višino, kot je opisano prej.

Float GetNeibHeight(float3 EdgeStartPos, float3 EdgeVec, float VecLen, float3 NormOffset) ( float3 neibPos = EdgeStartPos + EdgeVec * VecLen; neibPos = normalize(neibPos); return GetHeight(neibPos, NormOffset); ) float vertOffset = gridStep .x / Tri. tessFactor; uint tessRatio = (uint)tessFactor / (uint)neibTessFactor; uint ind = (uint)edgeVertInd % tessRatio; uint leftNeibInd = (uint)edgeVertInd - ind; float leftNeibHeight = GetNeibHeight(startPos, edgeVec, vertOffset * leftNeibInd, normOffset); uint rightNeibInd = (uint)edgeVertInd + ind; float rightNeibHeight = GetNeibHeight(startPos, edgeVec, vertOffset * rightNeibInd, normOffset);
No, zadnja komponenta je faktor F. Dobimo ga tako, da preostanek deljenja z razmerjem koeficientov (ind) delimo z razmerjem koeficientov (tessRatio)

Float faktor = (float)ind / (float)tessRatio;
Končna faza je linearna interpolacija višin in pridobitev novega vrha

Float avgHeight = lerp(leftNeibHeight, rightNeibHeight, faktor); posL = sphPos * (polmer krogle + povprečna višina);
Razpoka se lahko pojavi tudi tam, kjer mejijo sektorji s koordinatami robne teksture, enakimi 1 ali 0. V tem primeru vzamem povprečno vrednost med višinama za obe koordinati:

Float GetHeight(float2 TexCoords) ( float2 texCoords2 = TexCoords * texCoordsScale; float mHeight = mainHeightTex.SampleLevel(mainHeightTexSampler, TexCoords, 0).x; float dHeight = distHeightTex.SampleLevel(distHeightTexSampler, texCo ords 2, 0).x; return (mHeight + dHeight) * maxTerrainHeight; ) float GetHeight(float3 SphPos, float3 NormOffset) ( float2 texCoords1 = GetTexCoords(SphPos, NormOffset); float height = GetHeight(texCoords1); if(texCoords1.x == 1.0f)( float height2 = GetHeight ( float2(0.0f, texCoords1.y)); return lerp(height, height2, 0.5f); else if(texCoords1.x == 0.0f)( float height2 = GetHeight(1.0f, texCoords1.y) vrni lerp(višina, višina2, 0,5f);

11. GPE obdelava

Prestavimo sektorsko obdelavo na GPE. Imeli bomo dva Compute shaderja - prvi bo izvajal izrezovanje vzdolž piramide vidnosti in določal stopnjo podrobnosti, drugi bo prejemal koeficiente mejne teselacije za odpravo razpok. Razdelitev na dve stopnji je nujna, ker tako kot v primeru CPU sektorjem ne moremo pravilno določiti sosedov, dokler ne izvedemo rezanja. Ker bosta oba senčila uporabljala podatke LOD in delala s sektorji, sem predstavil dva splošna struktura: Sektor in Lod

Strukturni sektor ( float3 vec1, vec2; float3 startPos; float3 normCenter; int sosednost; float borderTessFactor; int lod; ); struct Lod ( RangeF dotRange; float tessFactor; float padding; float4 color;);
Uporabili bomo tri glavne medpomnilnike - vhodni (vsebuje začetne informacije o sektorjih), vmesni (vsebuje podatke iz sektorjev, pridobljene kot rezultat prve stopnje) in končni (bo posredovan za risanje). Podatki vhodnega medpomnilnika se ne bodo spremenili, zato je smiselno uporabiti vrednost D3D11_USAGE_IMMUTABLE v polju Usage strukture D3D11_BUFFER_DESC. Vanj bomo preprosto zapisali podatke vseh sektorjev s to razliko, da bomo za podatke o sosednosti uporabili indekse sektorjev. , namesto kazalcev nanje. Za raven indeksa podrobnosti in koeficiente mejne teselacije nastavite vrednosti na nič:

Static const size_t sectorSize = sizeof(Vector3) + //vec1 sizeof(Vector3) + //vec2 sizeof(Point3F) + //normCenter sizeof(Point3F) + //startPos sizeof(Point4) + //sosednost sizeof(Vector4) + //borderTessFactor sizeof(int32_t);//lod size_t sectorsDataSize = sectors.GetSectors().size() * sectorSize; std::vector sektorjiData(sectorsDataSize); char* ptr = const Sektor* firstPtr = §ors.GetSectors(); for(const Sektor &sec: sektorji)( Utils::AddToStream (ptr, sec.GetVec1()); Utils::AddToStream (ptr, sec.GetVec2()); Utils::AddToStream (ptr, sec.GetStartPos()); (ptr, sec.GetStartPos()); (ptr, sec.GetStartPos()); Utils::AddToStream (ptr, sec.GetNormCenter());
Utils::AddToStream

(ptr, sec.GetAdjacency() - firstPtr);
Utils::AddToStream

(ptr, Vektor4()); Utils::AddToStream (ptr, 0); ) inputData = Utils::DirectX::CreateBuffer(§orsData,//Neobdelani podatkovni sektorjiDataSize,//Velikost medpomnilnika D3D11_BIND_SHADER_RESOURCE,//vezne zastavice D3D11_USAGE_IMMUTABLE,//uporaba 0,//CPU dostopne zastavice D3D11_RESOURCE_MISC_BUFFER_STRUCTU RDEČA,//mis c označuje velikost sektorja) ; //strukturni bajtni korak< dotRange.minVal || dotVal >Zdaj pa nekaj besed o vmesnem medpomnilniku. Imel bo dve vlogi - izhod za prvi senčnik in vnos za drugega, zato bomo v polju BindFlags podali vrednost D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE. Zanj bomo ustvarili tudi dva pogleda - UnorderedAccessView, ki bo omogočal, da shader vanj zapiše rezultat svojega dela, in ShaderResourceView, pri katerem bomo medpomnilnik uporabljali kot vhod. Njegova velikost bo enaka kot predhodno ustvarjen vhodni medpomnilnik< 4; l++){ Lod lod = lods[l]; if(dotVal >UINT miscFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE; intermediateData = Utils::DirectX::CreateBuffer(sectors.GetSectors().size() * sectorSize,//Velikost medpomnilnika miscFlags, D3D11_USAGE_DEFAULT,//uporaba 0,//CPE dostopne zastavice D3D11_RESOURCE_MISC_BUFFER_STRUCTURED,//razne zastavice sectorSize);/ /structure byte stride intermediateUAW = Utils::DirectX::CreateUnorderedAccessView(intermediateData, D3D11_BUFFER_UAV(0, sectors.GetSectors().size(), 0)); intermediateSRV = Utils::DirectX::CreateShaderResourceView(intermediateData, D3D11_BUFFEREX_SRV(0, sectors.GetSectors().size(), 0));<= lod.dotRange.maxVal) sector.lod = l + 1; } outputData = sector; }
Po izračunu pikčastega produkta preverimo, ali je sektor v potencialno vidnem območju. Nato razjasnimo dejstvo njegove vidnosti s klicem IsVisible(), ki je identičen prej prikazanemu klicu Frustum::TestSphere(). Delovanje funkcije je odvisno od spremenljivk worldView, sphereRadius, frustumPlanesPosV in frustumPlanesNormalsV, katerih vrednosti je treba vnaprej posredovati senčniku. Nato določimo stopnjo podrobnosti. Upoštevajte, da navajamo indeks ravni, ki se začne od ena - to je potrebno, da na drugi stopnji zavržemo tiste sektorje, katerih stopnja podrobnosti je enaka nič.

Zdaj moramo pripraviti pufre za drugo stopnjo. Medpomnilnik želimo uporabiti kot izhod za senčnik Compute in vhod za tesellator - za to moramo podati vrednost D3D11_BIND_UNORDERED_ACCESS | v polju BindFlags. D3D11_BIND_VERTEX_BUFFER. Delati bomo morali neposredno s podatki medpomnilnika, zato bomo v polju MiscFlags navedli vrednost D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS. Za prikaz takega medpomnilnika bomo v polju Flags uporabili vrednost DXGI_FORMAT_R32_TYPELESS, v polju NumElements pa bomo navedli celotno. medpomnilnik deljen s štiri

Size_t instancesByteSize = instanceByteSize * sectors.GetSectors().size(); outputData = Utils::DirectX::CreateBuffer(instancesByteSize, D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_DEFAULT, 0, D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS, 0); D3D11_BUFFER_UAV uavParams = (0, instancesByteSize / 4, D3D11_BUFFER_UAV_FLAG_RAW); outputUAW = Utils::DirectX::CreateUnorderedAccessView(outputData, uavParams, DXGI_FORMAT_R32_TYPELESS);
Potrebujemo tudi števec. Z njegovo pomočjo naslovimo pomnilnik v senčniku in uporabimo njegovo končno vrednost v argumentu instanceCount klica DrawIndexedInstanced(). Števec sem implementiral kot 16-bajtni medpomnilnik. Poleg tega sem pri ustvarjanju preslikave v polju Flags polja D3D11_BUFFER_UAV uporabil vrednost D3D11_BUFFER_UAV_FLAG_COUNTER

Counter = Utils::DirectX::CreateBuffer(sizeof(UINT), D3D11_BIND_UNORDERED_ACCESS, D3D11_USAGE_DEFAULT, 0, D3D11_RESOURCE_MISC_BUFFER_STRUCTURED, 4); D3D11_BUFFER_UAV uavParams = (0, 1, D3D11_BUFFER_UAV_FLAG_COUNTER); counterUAW = Utils::DirectX::CreateUnorderedAccessView(counter, uavParams);
Čas je, da damo kodo za drugi shader

(ptr, Vektor4()); vhodniPodatki: register(t0); Izhodni podatki RWByteAddressBuffer: register(u0); RWStructuredBuffer števec: register(u1);
void Process(int3 TId: SV_DispatchThreadID) ( int ind = TId.x; Sektorski sektor = vhodniPodatki; if(sector.lod != 0)( sektor.borderTessFactor = GetNeibTessFactor(sektor, 0); //Spodnji sektor.borderTessFactor = GetNeibTessFactor (sektor, 1); //Levi sektor.borderTessFactor(sektor, 2); //Zgornji sektor.borderTessFactor(sektor, 3); //Desni int c = counter.IncrementData.Store (c * dataSize + 0, asuint(sector.startPos.x)); outputData.Store(c * dataSize + 4, asuint(sector.startPos.y)); outputData.Store(c * dataSize + 8, asuint(sector .startPos.z)); outputData.Store(c * dataSize + 12, asuint(sector.vec1.x)); outputData.Store(c * dataSize + 16, asuint(sector.vec1.y)) ; outputData.Store (c * dataSize + 20, asuint(sector.vec1.z)); outputData.Store(c * dataSize + 24, asuint(sector.vec2.x)); outputData.Store(c * dataSize + 28, asuint(sector .vec2.y)); outputData.Store(c * dataSize + 32, asuint(sector.vec2.z)); outputData.Store(c * dataSize + 36, asuint(sector.borderTessFactor));

outputData.Store(c * dataSize + 40, asuint(sector.borderTessFactor));
outputData.Store(c * dataSize + 44, asuint(sector.borderTessFactor)); outputData.Store(c * dataSize + 48, asuint(sector.borderTessFactor)); Za metode DrawIndexedInstancedIndirect() in CopyStructureCount() si oglejte Dodatek 2

12. Kamera

Zagotovo veste, kako sestaviti model preproste FPS (First Person Shooter) kamere. Sledim temu scenariju:
  • 1. Iz dveh kotov dobim smerni vektor
  • 2. z uporabo smernega vektorja in vektorja (0, 1, 0) dobim osnovo
  • 3. V skladu z vektorjem smeri in vektorjem v desno, pridobljenim v koraku 2, spremenim položaj kamere

V našem primeru je situacija nekoliko bolj zapletena - prvič, premakniti se moramo glede na središče planeta, in drugič, pri gradnji osnove moramo namesto vektorja (0, 1, 0) uporabiti normalo krogla na točki, kjer smo zdaj. Doseči želene rezultate, uporabil bom dve bazi. Po prvem se bo spremenil položaj, drugi bo opisal usmerjenost kamere. Osnovi sta medsebojno odvisni, vendar najprej izračunam položajno osnovo, zato bom začel s tem. Predpostavimo, da imamo začetno osnovo položaja (pDir, pUp, pRight) in smerni vektor vDir, po katerem se želimo premakniti na določeno razdaljo. Najprej moramo izračunati projekcije vDir na pDir in pRight. Če jih seštejemo, dobimo posodobljen vektor smeri (slika 21).


Slika 21 Vizualni postopek pridobivanja projDir

Kjer je P položaj kamere, sta mF in mS koeficienta, ki pomenita, koliko se moramo premakniti naprej ali vstran.

PN ne moremo uporabiti kot novega položaja kamere, ker PN ne pripada sferi. Namesto tega najdemo normalo krogle v točki PN in ta normala bo nova vrednost vektorja navzgor. Zdaj lahko oblikujemo posodobljeno osnovo

Vector3 nUp = Vector3::Normalize(PN - spherePos); Vector3 nDir = projDir Vector3 nRight = Vector3::Normalize(Vector3::Cross(pUp, pDir))
kjer je spherePos središče krogle.

Prepričati se moramo, da je vsak njegov vektor pravokoten na druga dva. Glede na lastnost navzkrižnega produkta nRight izpolnjuje ta pogoj. Še vedno je treba doseči enako za nUp in nDir. To naredite tako, da projicirate nDir na nUp in odštejete dobljeni vektor od nDir (slika 22).


Slika 22 Ortogonalizacija nDir glede na nUp

Enako bi lahko storili z nUp, vendar bi potem spremenil smer, kar je v našem primeru nesprejemljivo. Zdaj normaliziramo nDir in dobimo posodobljeno osnovo ortonormirane smeri.

Druga ključna faza je izdelava orientacijske osnove. Glavna težava je pridobitev vektorja smeri. najprimernejša rešitev je pretvorba točke s polarnim kotom a, azimutnim kotom b in razdaljo od izhodišča, ki je enaka ena, iz sferičnih koordinat v kartezične. Le če izvedemo tak prehod za točko s polarnim kotom enakim nič, bomo dobili vektor, ki gleda navzgor. To za nas ni povsem primerno, saj bomo kote povečevali in predvidevali, da bo tak vektor gledal naprej. Preprost premik kota za 90 stopinj bo rešil težavo, vendar je bolj elegantno uporabiti pravilo premika kota, ki pravi, da

Naredimo tako. Kot rezultat dobimo naslednje

Kjer je a polarni kot, b je azimutni kot.

Ta rezultat za nas ni povsem primeren - zgraditi moramo vektor smeri glede na osnovo položaja. Prepišimo enačbo za vDir:

Vse je kot pri astronavtih – toliko v to smer, toliko v ono smer. Zdaj bi moralo biti očitno, da če zamenjamo standardne osnovne vektorje s pDir, pUp in pRight, bomo dobili smer, ki jo potrebujemo. Takole

Isto stvar lahko predstavite v obliki matričnega množenja

Vektor vUp bo na začetku enak pUp. Z izračunom navzkrižnega produkta vUp in vDir dobimo vRight

Sedaj se bomo prepričali, da je vUp pravokoten na preostale bazne vektorje. Načelo je enako kot pri delu z nDir

Osnove smo uredili – preostane nam le še izračun položaja kamere. To se naredi takole

Kjer je spherePos središče krogle, sphereRadius polmer krogle, višina pa višina nad površino krogle. Tukaj je delovna koda za opisano kamero:

Float moveFactor = 0,0f, stranski faktor = 0,0f, višinski faktor = 0,0f; DirectInput::GetInsance()->ProcessKeyboardDown(( (DIK_W, [&]())(moveFactor = 1.0f;)), (DIK_S, [&]())(moveFactor = -1.0f;)), (DIK_D , [ &]())(sideFactor = 1.0f;)), (DIK_A, [&]())(sideFactor = -1.0f;)), (DIK_Q, [&]())(heightFactor = 1.0f; )), (DIK_E, [&]())(heightFactor = -1.0f;)) )); if(moveFactor != 0.0f || sideFactor != 0.0f)( Vector3 newDir = Vector3::Normalize(pDir * Vector3::Dot(pDir, vDir) + pRight * Vector3::Dot(pRight, vDir)); Point3F newPos = (newDir * moveFactor + pRight * sideFactor) * Tf * pUp = Vector3::Normalize(newPos - spherePos); pDir = Vector3::Normalize(pDir - pUp * Vector3::Dot(pUp, pDir)); pos = spherePos + pUp * (heightFactor != 0.0 f)( height = Math::Saturate(height + heightFactor * Tf * speed, heightRange); pos = spherePos + pUp * ( sphereRadius + height) DirectInput::MouseState mState = DirectInput::GetInsance()->GetMouseDelta( ); if(mState.x != 0 || mState.y != 0 || moveFactor != 0.0f || sideFactor != 0.0f)( if(mState.x != 0) angles.x = angles.x + mState .x / 80.0f; if(mState.y != 0) angles.y = Math::Saturate(angles.y + mState.y / 80.0f, RangeF(-Pi * 0.499f, Pi * 0.499f)); vDir = Vector3::Normalize(pRight * sinf(angles.x) * cosf(angles.y) + pUp * -sinf(angles.y) + pDir * cosf(angles.x) * cosf(angles.y)); vUp = pUp; vRight = Vector3::Cross(vUp, vDir)); vUp = Vector3::Normalize(vUp - vDir * Vector3::Dot(vDir, vUp)); viewMatrix = Matrix4x4::Inverse (((vDesno, 0,0f), (vGor, 0,0f), (vDir, 0,0f), (poz, 1,0f)));
Upoštevajte, da ponastavimo angles.x, potem ko smo posodobili osnovo položaja. To je kritično. Predstavljajmo si, da hkrati spreminjamo zorni kot in se premikamo po krogli. Najprej bomo projicirali vektor smeri na pDir in pRight, pridobili odmik (newPos) in na njegovi podlagi posodobili osnovo položaja. Deloval bo tudi drugi pogoj in začeli bomo posodabljati orientacijsko osnovo. Toda ker sta bila pDir in pRight že spremenjena glede na vDir, bo brez ponastavitve kota azimuta (angles.x) rotacija bolj "strma"

Zaključek

Zahvaljujem se bralcu za zanimanje za članek. Upam, da so mu bile informacije v njem dostopne, zanimive in uporabne. Predloge in pripombe mi lahko posredujete po pošti. [e-pošta zaščitena] ali pustite kot komentarje.

Želim vam uspeh!

Dodatek 1

polje InstanceDataStepRate vsebuje informacije o tem, kolikokrat narisati podatke D3D11_INPUT_PER_VERTEX_DATA za en element D3D11_INPUT_PER_INSTANCE_DATA. V našem primeru je vse preprosto - ena proti ena. "Toda zakaj moramo večkrat narisati isto stvar?" - vprašate. Razumno vprašanje. Armenski radio odgovarja - recimo, da imamo 99 kroglic treh različnih barv. Točko lahko opišemo takole:

UINT colorsRate = 99 / 3; std::vector meta = (("POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0), ("NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0), ("TE XCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0), ("SVET", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1), ("SVET", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 16, D3D11_INPUT_PER_INSTANCE_DATA, 1), ("SVET", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 32, D3D11_INPUT_PER_INSTANCE_DATA, 1), ("SVET", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 48, D3D11_INPUT_PER_INSTANCE_DATA, 1), ("BARVA", 0 , DXGI_FORMAT_R32G32B32A32_FLOAT, 2, 0, D3D11_INPUT_PER_INSTANCE_DATA, colorsRate), );
Upoštevajte, da je vozlišče zbrano iz treh virov, podatki slednjega pa se posodobijo enkrat na vsakih 33 "primerkov". Kot rezultat bomo dobili 33 primerkov prve barve, še 33 drugih itd. Zdaj pa ustvarimo medpomnilnike. Poleg tega, ker se barve ne bodo spremenile, lahko ustvarimo medpomnilnik z barvami z zastavico D3D11_USAGE_IMMUTABLE. To pomeni, da bo imel po inicializaciji vmesnega pomnilnika samo GPE dostop do svojih podatkov samo za branje. Tukaj je koda za ustvarjanje medpomnilnikov:

MatricesTb = Utils::DirectX::CreateBuffer(sizeof(Matrix4x4) * 99, D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE); colorsTb = Utils::DirectX::CreateBuffer(barve, D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_IMMUTABLE, 0);
nato po potrebi posodobimo medpomnilnik z matricami (uporabljam funkcije svoje knjižnice - upam, da bo vse jasno)

Utils::DirectX::Map (matricesTb, [&](Matrix4x4 *Data) ( //najprej zapišemo podatke za kroglice prve barve v medpomnilnik //nato za drugo itd. Upoštevajte, da mora biti potrebna ujemanje //podatkov z barvami zagotovljeno na stopnji generiranja podatkov medpomnilnika ));
Dostop do podatkov v senčniku je mogoče izvesti na enak način, kot sem opisal prej


Dodatek 2

Za razliko od DrawIndexedInstanced() klicanje DrawIndexedInstancedIndirect() vzame kot argument medpomnilnik, ki vsebuje vse informacije, ki jih uporabite za klic DrawIndexedInstanced(). Poleg tega mora biti ta medpomnilnik ustvarjen z zastavico D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS. Tukaj je primer ustvarjanja medpomnilnika:

//indicesCnt - število indeksov, ki jih želimo prikazati //instancesCnt - število "instanc", ki jih želimo prikazati std::vector args = ( indeksiCnt, //IndexCountPerInstance instancesCnt, //InstanceCount 0, //StartIndexLocation 0,//BaseVertexLocation 0//StartInstanceLocation); D3D11_BUFFER_DESC bd = (); bd.Uporaba = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof(UINT) * args.size(); bd.BindFlags = 0; bd.CPUAccessFlags = 0; bd.MiscFlags = D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS; bd.StructureByteStride = 0; ID3D11Buffer* medpomnilnik; D3D11_SUBRESOURCE_DATA initData = (); initData.pSysMem = HR(DeviceKeeper::GetDevice()->CreateBuffer(&bd, &initData, &buffer)); primer klica DrawIndexedInstancedIndirect(): DeviceKeeper::GetDeviceContext()->DrawIndexedInstancedIndirect(indirectArgs, 0);
kot drugi argument posredujemo odmik v bajtih od začetka medpomnilnika, iz katerega moramo začeti brati podatke. Kako se lahko to uporabi? Na primer pri izvajanju izrezovanja nevidne geometrije na GPE. Na splošno je kronologija sledeča – najprej v Compute shaderju zapolnimo AppendStructuredBuffer, ki vsebuje podatke o vidni geometriji. Nato z uporabo CopyStructureCount() nastavimo število primerkov, ki jih želimo prikazati, na število vnosov v tem medpomnilniku in pokličemo DrawIndexedInstancedIndirect()


Dodatek 3

Predpostavimo, da je vrednost koordinate x enaka rezultatu funkcije X z argumentom a, vrednost koordinate z pa je rezultat funkcije Z z enakim argumentom:

Zdaj moramo izračunati odvod za vsako funkcijo. V bistvu je derivat funkcije v dano točko enaka hitrosti spreminjanja vrednosti funkcije točno na tej točki. Po pravilih za razlikovanje trigonometričnih funkcij:

Kar nam na koncu da enak rezultat:

Zakaj lahko uporabimo vrednosti hitrosti kot komponente vektorja smeri? Tako jaz to razumem. Predstavljajte si, da imamo vektorsko funkcijo (za t >= 0):

Izračunajmo odvod za koordinato X

Zdaj za Y

Ugotovili smo, da je vektor hitrosti enak (2, 3), zdaj pa poiščimo izhodišče

Kot rezultat lahko izrazimo funkcijo P(t) kot sledi:

Kar z enostavnimi besedami lahko opišemo kot "točka se premakne iz izhodišča s koordinatami (3, 2) v t v smeri (2, 3)." Zdaj pa vzemimo še en primer:

Ponovno izračunajmo odvod za koordinato X

In za koordinato Y

Zdaj se vektor hitrosti spreminja glede na argument. V tem primeru lahko situacijo preprosto opišemo na naslednji način: "Točka se premakne od izhodišča s koordinatami (3, 2) in smer njenega gibanja se nenehno spreminja."


Dodatek 4

Definirajmo funkcijo F(H), ki bo vzela višino v območju in vrnila vrednost med 0 in 1, kjer je F(Hmin) = 0 in F(Hmax) = 1. Reševanje sistema enačb

prejel sem

Posledično ima funkcija F obliko

Zdaj potrebujemo funkcijo, ki vzame faktor višine med 0 in 1 in vrne najmanjšo vrednost za pikčasti produkt. Upoštevati moramo, da bližje kot je opazovalec površini, večja je. Posledica tega je naslednja enačba:

Odprimo oklepaje

Poenostavimo in dobimo

Zdaj izrazimo D(F(H)) in dobimo

Oznake:

  • directx11
  • render terena
Dodajte oznake

Med razvojem Zemlje so bile spremembe v videzu kopenske pokrajine reakcija na preoblikovanje naravnih razmer. Vsa pestrost geografskega ovoja, znanega kot geosistemi, pokrajine oz naravni kompleksi, odraža rezultate različnih pojavov temperature in vlažnosti, ki so nato podvrženi ravnovesju sevanja.

Ti dinamični sistemi različnih rangov, za katere je značilna celovitost, posebna interakcija njihovih sestavnih elementov in delovanja, produktivnost in videz, skupaj tvorijo geografski ovoj in se nanj navezujejo kot na dele celote. Imajo svoj naravni (naravno virski) potencial, katerega meritve omogočajo rangiranje geosistemov in proučevanje njihovih sprememb. Poenotenje teh struktur je izmenjava tokov snovi in ​​energije, njihovo delno kopičenje in poraba. Izmenjava energije in mase znotraj geografskega ovoja je torej osnova za njegovo diferenciacijo, njene spremembe pa se odražajo v videzu zemeljskega površja. Ta proces zagotavlja sodobno geografsko conacijo in conskost Zemlje ter raznolikost specifičnih pokrajin različnih stopenj organiziranosti.

Toda med razvojem geografskega ovoja so bile spremembe v njegovih kopenskih sistemih povezane tudi z globinskimi procesi in pojavi, deloma izraženimi na površju (območja vulkanizma, seizmičnosti, gorovja itd.). Hkrati je ob neposrednih spremembah litogene podlage pokrajine in geografskega ovoja kot celote slednja dobila dodatno snov in energijo, kar se je odrazilo v delovanju njenih posameznih sestavin in sistema kot celote. Ta »komplementarnost« (včasih verjetno precejšnja) se ni kazala le kvantitativno, v globalnem kroženju snovi in ​​energije, ampak tudi v kvalitativnih spremembah posameznih komponent. Vloga procesov razplinjevanja Zemlje in njihove izmenjave energije in mase z atmosfero in hidrosfero še ni dovolj raziskana. Šele od sredine 20. stol. pojavile so se informacije o snovni sestavi snovi plašča in njenih kvantitativnih značilnostih.

Raziskava V.I.Gatova je pokazala, da atmosferski kisik ni toliko fotosinteznega izvora. Splošno sprejeto shemo cikla ogljika v naravi je treba popraviti z dobavo njegovih spojin iz črevesja zemlje, zlasti med vulkanskimi izbruhi. Očitno nič manjše količine snovi ne pridejo v vodno lupino med podvodnimi izbruhi, zlasti v conah širjenja, vulkanskih otočnih lokih in v posameznih vročih točkah. Skupna letna količina ogljikovih spojin, ki prihajajo iz podzemlja v ocean in ozračje, je primerljiva z maso letne tvorbe karbonata v vodnih telesih in očitno presega količino kopičenja organskega ogljika s kopenskimi rastlinami.

Naravno segrevanje podnebja in njegovo antropogeno intenzificiranje naj bi povzročilo premikanje meja geografskih pasov in con ter prispevalo k spreminjanju posameznih krajin.

Vendar pa razvoj človeške družbe ter širitev njenih potreb in zmožnosti vodi do umetnega prestrukturiranja naravnih kompleksov različnih velikosti in oblikovanja kulturnih krajin, ki vplivajo na delovanje geografskega ovoja in motijo ​​naravni potek. Med temi učinki so najbolj očitni naslednji:

Upoštevajte, da seštevanje letnih izpustov onesnaževal ni teoretično in praktično popolnoma utemeljeno, saj se ob vstopu v geografsko okolje asimilirajo, transformirajo pod vplivom drug drugega in delujejo različno. Pomembno je analizirati vsako večje antropogeno sproščanje ob upoštevanju njegovih reakcij z obstoječimi spojinami.

Sprememba energije geografske lupine ali njenih delov povzroči prestrukturiranje notranje strukture in procesov delovanja geosistema in z njim povezanih pojavov. Ta proces je kompleksen in je reguliran z več neposrednimi in povratnimi povezavami (slika 9.4). Antropogeni vplivi na geografsko okolje povzročajo spremembe v sestavi in ​​stanju okolja, motijo ​​kvantitativno in kakovostno sestavo žive snovi (do mutacij) ter spreminjajo obstoječe sisteme izmenjave energije, mase in vlage. Vendar trenutno razpoložljivi dokazi kažejo, da se antropogene spremembe bistveno ne odražajo v geografskem ovoju. Relativno uravnoteženost njenega obstoja in trajnost razvoja zagotavljajo predvsem naravni vzroki, katerih obseg presega človekov vpliv. Iz tega ne sledi, da bo geografski ovoj sam vedno premagoval vse večji antropogeni pritisk. Posege v naravo je treba urejati z vidika smotrnosti njihovih pojavnih oblik – v dobro človeštva in brez večje škode za naravno okolje. Koncepte, ki se razvijajo v tej smeri, imenujemo trajnostni (uravnoteženi) razvoj. Temeljiti morajo na splošnih geoloških vzorcih in značilnostih trenutnega stanja in razvoja geografskega ovoja.

Za zaključek se dotaknimo porajajoče se trditve, da sodobna geografska ovojnica postaja antroposfera, ali del nastajajočih noosfera. Upoštevajte, da je koncept "noosfere" v veliki meri filozofske narave. Vpliv človeka na okolje in vpletenost odpadkov vanj je nesporen pojav. Pomembno je razumeti, da človek najpogosteje spremeni svoj življenjski prostor ne zavestno, ampak zaradi nepredvidenih posledic. Poleg tega te izvedbe niso usmerjene v vse sestavine geografskega ovoja, temveč le v tiste, ki so za človeka potrebne (gozd, prst, surovine itd.). Tako obstajajo le žarišča sprememb, čeprav včasih zelo pomembne in resne, in čeprav se človeška dejavnost povečuje, se narava še vedno razvija predvsem pod vplivom naravnih procesov. Zato bi morali v tem trenutku govoriti o določenih območjih geografskega ovoja, kjer je naravno okolje bistveno spremenjeno in se razvija pod vplivom procesov, ki jih ureja človek.

riž. 9.4. Nekaj ​​povratnih informacij o globalnem podnebju

Med razvojem Zemlje je bila sprememba videza kopenske pokrajine reakcija na preoblikovanje naravnih razmer. Vsa pestrost geografskega ovoja, znanega kot geosistemi, pokrajine oz naravni kompleksi, odraža rezultate različnih pojavov temperature in vlažnosti, ki so nato podvrženi ravnovesju sevanja.

Ti dinamični sistemi različnih rangov, za katere je značilna celovitost, posebna interakcija njihovih sestavnih elementov in delovanja, produktivnost in videz, skupaj tvorijo geografski ovoj in se nanj navezujejo kot na dele celote. Imajo svoj naravni (naravno virski) potencial, katerega meritve omogočajo rangiranje geosistemov in proučevanje njihovih sprememb. Poenotenje teh struktur je izmenjava tokov snovi in ​​energije, njihovo delno kopičenje in poraba. Izmenjava energije in mase znotraj geografskega ovoja je torej osnova za njegovo diferenciacijo, njene spremembe pa se odražajo v videzu zemeljskega površja. Ta proces zagotavlja sodobno geografsko conacijo in conskost Zemlje ter raznolikost specifičnih pokrajin različnih stopenj organiziranosti.

Toda med razvojem geografskega ovoja so bile spremembe v njegovih kopenskih sistemih povezane tudi z globinskimi procesi in pojavi, deloma izraženimi na površju (območja vulkanizma, seizmičnosti, gorovja itd.). Hkrati je ob neposrednih spremembah litogene podlage pokrajine in geografskega ovoja kot celote slednja dobila dodatno snov in energijo, kar se je odrazilo v delovanju njenih posameznih sestavin in sistema kot celote. Ta »komplementarnost« (včasih verjetno precejšnja) se ni kazala le kvantitativno, v globalnem kroženju snovi in ​​energije, ampak tudi v kvalitativnih spremembah posameznih komponent. Vloga procesov razplinjevanja Zemlje in njihove izmenjave energije in mase z atmosfero in hidrosfero še ni dovolj raziskana. Šele od sredine 20. stol. pojavile so se informacije o snovni sestavi snovi plašča in njenih kvantitativnih značilnostih.

Raziskava V.I.Gatova je pokazala, da atmosferski kisik ni toliko fotosinteznega izvora. Splošno sprejeto shemo cikla ogljika v naravi je treba popraviti z dobavo njegovih spojin iz črevesja zemlje, zlasti med vulkanskimi izbruhi. Očitno nič manjše količine snovi ne pridejo v vodno lupino med podvodnimi izbruhi, zlasti v conah širjenja, vulkanskih otočnih lokih in v posameznih vročih točkah. Skupna letna količina ogljikovih spojin, ki prihajajo iz podzemlja v ocean in ozračje, je primerljiva z maso letne tvorbe karbonata v vodnih telesih in očitno presega količino kopičenja organskega ogljika s kopenskimi rastlinami.

Naravno segrevanje podnebja in njegovo antropogeno intenzificiranje naj bi povzročilo premikanje meja geografskih pasov in con ter prispevalo k spreminjanju posameznih krajin.

Vendar pa razvoj človeške družbe ter širitev njenih potreb in zmožnosti vodi do umetnega prestrukturiranja naravnih kompleksov različnih velikosti in oblikovanja kulturnih krajin, ki vplivajo na delovanje geografskega ovoja in motijo ​​naravni potek. Med temi učinki so najbolj očitni naslednji:

1) Ustvarjanje rezervoarjev in namakalnih sistemov spremeni površinski albedo, režim izmenjave toplote in vlage, kar posledično vpliva na temperaturo zraka in oblačnost.

2) Spreminjanje zemljišč v kmetijska zemljišča ali uničenje vegetacije (množično krčenje gozdov) spremeni albedo in toplotne razmere, moti kroženje snovi zaradi zmanjšanja aktivnih površin za fotosintezo. Najpomembnejši vpliv po obsegu je bil množični razvoj pragovitih in ledinskih zemljišč, ko je bilo preoranih in posejanih več milijonov hektarjev zelenih pašnikov in ledin. Povečanje absorpcijske sposobnosti zemeljskega površja, motnje njegove hrapavosti in kontinuitete talnega in vegetacijskega pokrova so spremenile sevalno bilanco, povzročile preobrazbo v kroženju zračnih mas in povečali vetrove, kar je povzročilo prašne nevihte in zmanjšanje v prosojnosti ozračja. Posledica preobrazb je bil prehod stabilnih produktivnih krajin v nestabilne s krepitvijo procesov dezertifikacije in tveganja pri rabi tal.

3) Prerazporeditev površinskega odtoka (regulacija pretoka, ustvarjanje jezov in zadrževalnikov) najpogosteje povzroči zamočvirjevanje okoliških območij. Ob tem se spreminja albedo podložnega površja, povečuje se vlaga, pogostost megle, oblačnost in prepustnost zraka, kar moti naravni prenos toplote in snovi med zemeljskim površjem in ozračjem. Zajezitev vodnega toka in nastanek zamočvirjenih prostorov spremenita naravo razgradnje rastlinskega opada, kar povzroči vnos dodatnih količin toplogrednih plinov (ogljikovega dioksida, metana itd.) v ozračje, ki spremeni njegovo sestavo in prosojnost.

4) Ustvarjanje hidroenergetskih objektov na rekah, zajezitev s tvorbo kaskad celoletne padajoče vode spreminja letni režim rek, moti stanje ledu, distribucijo prenosljivih sedimentov in preoblikuje sistem reka-atmosfera. Nezmrzovalni rezervoarji s stalno meglo in izhlapevanjem z vodne površine (tudi pozimi) vplivajo na potek temperatur, kroženje vodnih mas, poslabšanje vremenskih razmer in spreminjanje habitata živih organizmov. Vpliv hidroelektrarne na velike reke(Jenisej, Angara, Kolyma, Volga itd.) se čuti na desetine kilometrov dolvodno in na vseh zajezenih delih akumulacij, splošne spremembe podnebja pa zajemajo stotine kvadratnih kilometrov. Počasna oskrba z rečnimi sedimenti in njihova prerazporeditev povzročata motnje geomorfoloških procesov ter uničenje rečnih ustij in bregov vodni bazeni(na primer uničenje delte Nila in jugovzhodnega dela sredozemske obale po izgradnji Asuanskega jezu in njegovem prestrezanju pomembnega dela trdnih usedlin, ki jih prenaša reka).

5) Meliorativna dela, ki jih spremlja izsuševanje velikih prostorov, porušijo obstoječi režim toplote, vlage in izmenjave ter prispevajo k razvoju negativnih povratnih informacij med preoblikovanjem krajin. Tako je prekomerno izsušitev močvirnih sistemov v številnih regijah (Polesie, Novgorodska regija, Irtyška regija) povzročila odmrtje naravne vegetacije in nastanek deflacijskih procesov, ki so tudi na območjih z zadostno vlažnostjo oblikovali premikajoče se peske. . Posledično se je povečala zaprašenost ozračja, povečala se je hrapavost površja, spremenil se je vetrovni režim.

6) Povečanje hrapavosti zemeljske površine med gradnjo različnih objektov (zgradbe, rudniki in odlagališča, industrijska skladišča itd.) povzroči spremembe v vetrovnih razmerah, ravni prahu ter vremenskih in podnebnih značilnostih.

7) Različna onesnaževala, ki v velikih količinah vstopajo v vsa naravna okolja, spremenijo predvsem snovno sestavo in energijske zmogljivosti zraka, vode, površinskih formacij itd. Ta sprememba naravnih dejavnikov določa transformacijo naravnih procesov, ki jih izvajajo, kot tudi različne interakcije z okoljem, okoljem in drugimi naravnimi dejavniki.

Upoštevajte, da seštevanje letnih izpustov onesnaževal ni teoretično in praktično popolnoma utemeljeno, saj se ob vstopu v geografsko okolje asimilirajo, transformirajo pod vplivom drug drugega in delujejo različno. Pomembno je analizirati vsako večje antropogeno sproščanje ob upoštevanju njegovih reakcij z obstoječimi spojinami.

Sprememba energije geografske lupine ali njenih delov povzroči prestrukturiranje notranje strukture in procesov delovanja geosistema in z njim povezanih pojavov. Ta proces je kompleksen in je reguliran z več neposrednimi in povratnimi povezavami (slika 9.4). Antropogeni vplivi na geografsko okolje povzročajo spremembe v sestavi in ​​stanju okolja, motijo ​​kvantitativno in kakovostno sestavo žive snovi (do mutacij) ter spreminjajo obstoječe sisteme izmenjave energije, mase in vlage. Vendar trenutno razpoložljivi dokazi kažejo, da se antropogene spremembe bistveno ne odražajo v geografskem ovoju. Relativno uravnoteženost njenega obstoja in trajnost razvoja zagotavljajo predvsem naravni vzroki, katerih obseg presega človekov vpliv. Iz tega ne sledi, da bo geografski ovoj sam vedno premagoval vse večji antropogeni pritisk. Posege v naravo je treba urejati z vidika smotrnosti njihovih pojavnih oblik – v dobro človeštva in brez večje škode za naravno okolje. Koncepte, ki se razvijajo v tej smeri, imenujemo trajnostni (uravnoteženi) razvoj. Temeljiti morajo na splošnih geoloških vzorcih in značilnostih trenutnega stanja in razvoja geografskega ovoja.

Za zaključek se dotaknimo porajajoče se trditve, da sodobna geografska ovojnica postaja antroposfera, ali del nastajajočih noosfera. Upoštevajte, da je koncept "noosfere" v veliki meri filozofske narave. Vpliv človeka na okolje in vpletenost odpadkov vanj je nesporen pojav. Pomembno je razumeti, da človek najpogosteje spremeni svoj življenjski prostor ne zavestno, ampak zaradi nepredvidenih posledic. Poleg tega te izvedbe niso usmerjene v vse sestavine geografskega ovoja, temveč le v tiste, ki so za človeka potrebne (gozd, prst, surovine itd.). Tako obstajajo le žarišča sprememb, čeprav včasih zelo pomembne in resne, in čeprav se človeška dejavnost povečuje, se narava še vedno razvija predvsem pod vplivom naravnih procesov. Zato bi morali v tem trenutku govoriti o določenih območjih geografskega ovoja, kjer je naravno okolje bistveno spremenjeno in se razvija pod vplivom procesov, ki jih ureja človek.

riž. 9.4. Nekaj ​​povratnih informacij o globalnem podnebju

Varnostna vprašanja

Katere pojave uvrščamo med globalne spremembe geografskega ovoja?

Kakšne so posebnosti globalnih sprememb ob koncu 20. in v začetku 21. stoletja?

Kaj je učinek tople grede in kakšne so njegove posledice?

Kaj je pogosta težava antropogenizacija geografskega ovoja?

Kaj je problem s segrevanjem podnebja?

Kakšne so nevarnosti onesnaženja z nafto?

Kaj je svetovna okoljska kriza, kako in kje se kaže?

Kaj pomenijo optimistični in pesimistični pogledi na razvoj planeta Zemlje?

Kakšen vpliv polarni led vpliva na geografski ovoj?

Kaj so spremembe kopenske pokrajine?

LITERATURA

Alpatiev A. M. Razvoj, preoblikovanje in varstvo naravnega okolja. - L., 1983.

Balandin R.K., Bondarev L.G. Narava in civilizacija. - M., 1988.

Biološka indikacija v antropoekologiji. - L., 1984.

Bitkaeva L.Kh., Nikolaev V.A. Pokrajine in antropogena dezertifikacija Tereškega peska. - M., 2001.

Bokov V.A., Luščik A.V. Osnove okoljske varnosti. - Simferopol, 1998.

Vernadsky V.I. Biosfera in noosfera. - M., 1989.

Geografski problemi poznega 20. stoletja / Rep. izd. Yu. P. Seliverstov. - Sankt Peterburg, 1998.

Geografija in okolje / Odgovorno. izd. N. S. Kasimov, S. M. Malkhazova. - M., 2000.

Globalne spremembe v naravnem okolju (podnebje in vodni režim) / Rep. izd. N.S. Kasimov. - M., 2000.

Globalne in regionalne podnebne spremembe ter njihove naravne in družbenoekonomske posledice / Odg. izd. V. M. Kotljakov. - M., 2000.

Globalno okoljske težave na pragu 21. stoletja / Rep. izd. F. T. Janšina. - M., 1998.

Govoruško S. M. Vpliv naravnih procesov na človekovo dejavnost. - Vladivostok, 1999.

Golubev G.N. Geoekologija. - M., 1999.

Gorškov V.G. Fizikalni in biološki temelji trajnosti življenja. - M., 1995.

Gorškov SP. Konceptualni temelji geoekologije. - Smolensk, 1998.

Grigoriev A. A. Ekološke lekcije nekoč in sedanjosti. - L., 1991.

Grigoriev A. A., Kondratiev K. Ya. Ekodinamika in geopolitika. - T. 11. Okoljske katastrofe. - Sankt Peterburg, 2001.

Gumiljov L.N. Etnogeneza in biosfera Zemlje. - L., 1990.

Danilov A.D., Korol I.L. Atmosferski ozon - občutki in resničnost. - L., 1991.

Dotto L. Planet Zemlja je v nevarnosti. - M., 1988.

Zaletaev V.S. Ekološko destabilizirano okolje. Ekosistemi sušnih con v spremenljivem hidrološkem režimu. - M., 1989.

Zemlja in človeštvo. Globalni problemi/ Države in ljudstva. - M., 1985.

Zubakov V. A. Ecogea - hišna zemlja. Na kratko o prihodnosti. Obrisi ekogejevskega koncepta izhoda iz svetovne okoljske krize. - Sankt Peterburg, 1999.

Zubakov V. A. Hiša Zemlja. Obrisi ekogeozofskega pogleda na svet. (Znanstvena izdelava strategije vzdrževanja). - Sankt Peterburg, 2000.

Isačenko A. G. Optimizacija naravnega okolja. - M., 1980.

Isačenko A. G. Ekološka geografija Rusije. - Sankt Peterburg, 2001.

Kondratyev K. Ya. Globalno podnebje. - M., 1992.

Kotljakov V. M. Znanost. Družba. okolje. - M., 1997.

Kotljakov V.M., Groswald M.G., Lorius K. Pretekla podnebja iz globin ledenih plošč. - M., 1991.

Lavrov S.B., Sdasyuk G.V. Ta kontrastni svet. - M., 1985.

Okolje / ur. A. M. Rjabčikova. - M., 1983.

Osnove geoekologije / Ed. V. G. Moračevskega. - Sankt Peterburg, 1994.

Petrov K. M. Naravni procesi obnove opustošenih zemljišč. - Sankt Peterburg, 1996.

Problemi ekologije v Rusiji / Odgovorni. izd. V. I. Danilov-Danilyan, V. M. Kotlyakov. - M., 1993.

Rusija v svetu okoli nas: 1998. Analitična zbirka / Ed. izd. N.N. Moiseeva, S.A. Stepanova. - M., 1998.

Rown S. Ozonska kriza. Petnajstletna evolucija nepričakovane globalne grožnje. - M., 1993.

Rusko geografsko društvo: nove ideje in poti / Rep. izd. A.O.Brinken, S.B.Seliverstov. - Sankt Peterburg, 1995.

Seliverstov Yu. Problem globalnega okoljskega tveganja // Novice Ruskega geografskega društva. - 1994. - Št. 2.

Seliverstov Yu. Antropogenizacija narave in problem okoljske krize // Vestnik St. Petersburg. Univerza. - 1995. - Ser. 7. - Izd. 2.

Seliverstov Yu. Planetarna okoljska kriza: vzroki in realnosti // Vestnik St. Petersburg. Univerza. - 1995. - Ser. 7. - Izd. 4.

Fortescue J. Geokemija okolja. - M., 1985.

Ekološka alternativa / Ed. izd. M.Ya Lemesheva. - M., 1990.

Okoljski imperativi trajnostnega razvoja Rusije / Ed. V.T.Pulyaeva.-L., 1996.

Okoljski problemi: kaj se dogaja, kdo je kriv in kaj storiti? / Ed. V.I.Danilov-Danilyan. - M., 1997.

Yanshin A.L., Melua A.I. Lekcije iz okoljskih kriz. - M., 1991.

Površina Zemlje ne ostane nespremenjena. V milijonih letih obstoja našega planeta so na njegov videz nenehno vplivale različne naravne sile. Spremembe, ki se dogajajo na površju Zemlje, povzročajo tako notranje sile kot dogajanje v ozračju.

Tako so gore nastale kot posledica premikanja zemeljske skorje. Kamnite gmote so bile potisnjene na površje, drobljene in lomljene, zaradi česar so nastale različne vrste gora. Sčasoma sta dež in mraz zdrobila gore in ustvarila ločene pečine in doline.

Nekatere gore so nastale kot posledica vulkanskih izbruhov. Staljena kamnina je brbotala na zemeljsko površje skozi luknje v skorji, plast za plastjo, dokler se na koncu ni pojavila gora. Vezuv v Italiji je gora vulkanskega izvora.

Vulkanske gore lahko nastanejo tudi pod vodo. Na primer, Havajski otoki so vrhovi vulkanskih gora.

Sonce, veter in voda nenehno uničujejo kamnine. Ta proces se imenuje erozija. Vendar lahko vpliva ne le na kamnine. Tako erozija z ledom, vetrom in vodo izpira zemeljsko zemljo.

Na mestih, kjer zdrsnejo v morje, ledeniki prerežejo ravnine in oblikujejo doline in fjorde – ozke in vijugaste morske zalive.

Fjordi so nastali med ledena doba ko so bile celine prekrite z debelo plastjo ledu in snega.

Ta led pa je povzročil nastanek ledenikov, ki so počasne reke ledu.

Drseči z gora v doline so si utrli pot ledeniki, katerih debelina je včasih dosegla več deset metrov. Moč njihovega gibanja je bila zelo velika.

Sprva so se vzdolž poti ledenikov oblikovale ozke soteske, nato pa jih je pošastna sila ledenika povečala in odprla pot navzdol. Postopoma je ta prostor postajal vse globlji in širši.

Po koncu ledene dobe sta se led in sneg začela topiti. Ko se je led talil, se je širina rek povečevala. Hkrati se je dvigovala gladina morja. Tako so na mestu rek nastali fiordi.

Obale fjordov so običajno skalnata pobočja, ki včasih dosežejo višino 1000 metrov (3000 čevljev).

Nekateri fiordi so tako globoki, da se ladje lahko premikajo po njih.

Veliko število fiordov se nahaja na obalah Finske in Grenlandije. Toda najlepši fjordi so na Norveškem. Tudi najdaljši fiord je na Norveškem. Imenuje se Sognefjord. Njegova dolžina je 180 kilometrov (113 milj).

Ko se led stopi, ostanejo morene – kopičenja kamnitih drobcev – in tvorijo cikcakaste gorske vrhove. Reke v rahle kamnine klešejo grape, ponekod pa ogromne kanjone (globoke rečne doline s strmimi stopničastimi pobočji), kot je Grand Canyon v Arizoni (ZDA). V dolžino se razteza 349 kilometrov.

Deževje in vetrovi so pravi kiparji in izrezujejo prave kiparske skupine in različne figure. V Avstraliji so tako imenovane vetrovne pečine, nedaleč od Krasnojarska pa kamniti stebri. Oba sta nastala kot posledica vetrne erozije.

Erozija zemeljske površine še zdaleč ni neškodljiv proces. Vsako leto po njegovi zaslugi izgine več deset hektarjev obdelovalnih površin. V reke se odnese velika količina rodovitne zemlje, katere nastajanje v naravnih razmerah traja več sto let. Zato se ljudje na vse možne načine poskušajo boriti proti eroziji.

Glavna usmeritev tega boja je preprečevanje erozije tal. Če na tleh ni rastlinskega pokrova, veter in voda zlahka odneseta rodovitno plast in zemlja postane nerodovitna. Zato se na območjih z intenzivnimi vetrovi uporabljajo varčevalne metode obdelave zemlje, na primer oranje brez deske.

Poleg tega poteka boj proti grapam. V ta namen obrežja zasadimo z različnimi rastlinami in utrdimo pobočja. Na morskih in rečnih obalah, kjer prihaja do močnejše erozije obale, se naredi posebna deponija gramoza in postavijo zaščitne jezove, ki preprečujejo prenos peska.