Planētu ainava. Dabas ainavas vides problēmas

AlekssWIN32 2017. gada 20. augustā plkst. 13:36

Planētu ainava

  • API,
  • C++
  • Spēļu izstrāde
  • Apmācība

Ir grūti apgalvot, ka ainava ir neatņemama sastāvdaļa lielākajā daļā āra datorspēļu. Tradicionālā metode spēlētāju apkārtējās virsmas reljefa izmaiņu ieviešanai ir šāda - mēs ņemam sietu, kas ir plakne, un katram primitīvam šajā tīklā veicam nobīdi pa normālu uz šo plakni par noteiktu vērtību. šim primitīvam. Vienkārši izsakoties, mums ir viena kanāla 256 x 256 pikseļu tekstūra un plakana acs. Katram primitīvam mēs ņemam vērtību no faktūras, pamatojoties uz tās koordinātām plaknē. Tagad mēs vienkārši nobīdām primitīvas koordinātas pa normālu uz plakni par iegūto vērtību (1. att.)

1. att. augstuma karte + plakne = ainava

1. Nozare

Acīmredzot nav prātīgi veidot ainavu uzreiz visai sfērai – lielākā daļa nebūs redzama. Tāpēc mums ir jāizveido noteikta minimāla telpas platība - noteikts primitīvs, kas veidos sfēras redzamās daļas reljefu. Es to saukšu par nozari. Kā mēs to varam iegūt? Tāpēc skatiet 2.a attēlu. Zaļā šūna ir mūsu nozare. Tālāk mēs izveidosim sešus režģus, no kuriem katrs ir kuba skaldne (2.b att.). Tagad normalizēsim to primitīvu koordinātas, kas veido tīklus (2.c att.).


2. att

Rezultātā mēs saņēmām kubu, kas projicēts uz sfēras, kur sektors ir apgabals vienā no tā virsmām. Kāpēc tas darbojas? Apsveriet patvaļīgu punktu režģī kā vektoru no sākuma. Kas ir vektora normalizācija? Šī ir dotā vektora transformācija vektorā tajā pašā virzienā, bet ar garuma vienību. Process ir šāds: vispirms mēs atrodam vektora garumu Eiklīda metrikā saskaņā ar Pitagora teorēmu

Pēc tam sadaliet katru vektora komponentu ar šo vērtību

Tagad pajautāsim sev, kas ir sfēra? Sfēra ir punktu kopa, kas atrodas vienādā attālumā no noteiktā punkta. Sfēras parametriskais vienādojums izskatās šādi

Kur x0, y0, z0 ir sfēras centra koordinātas, un R ir tās rādiuss. Mūsu gadījumā sfēras centrs ir izcelsme, un rādiuss ir vienāds ar vienu. Aizstāsim zināmās vērtības un ņem sakni no vienādojuma divām pusēm. Izrādās sekojošais

Burtiski pēdējā transformācija mums saka: "Lai piederētu sfērai, vektora garumam jābūt vienādam ar vienu." Tas ir tas, ko mēs panācām, normalizējot.

Ko darīt, ja sfērai ir patvaļīgs centrs un rādiuss? Tam piederošo punktu var atrast, izmantojot šādu vienādojumu

Kur pS ir sfēras punkts, C ir sfēras centrs, pNorm ir iepriekš normalizētais vektors un R ir sfēras rādiuss. Vienkārši izsakoties, tas, kas šeit notiek, ir "mēs virzāmies no sfēras centra uz režģa punktu attālumā R". Tā kā katram vektoram ir garuma vienība, galu galā visi punkti atrodas vienādā attālumā no sfēras centra tās rādiusa attālumā, kas padara sfēras vienādojumu patiesu.

2. Vadība

Mums ir jāiegūst nozaru grupa, kas ir potenciāli redzama no skatpunkta. Bet kā to izdarīt? Pieņemsim, ka mums kādā brīdī ir sfēra ar centru. Mums ir arī sektors, kas atrodas uz sfēras, un punkts P, kas atrodas telpā netālu no sfēras. Tagad konstruēsim divus vektorus – vienu virzītu no sfēras centra uz sektora centru, otru – no sfēras centra uz skata punktu. Apskatiet 3. attēlu - sektors var būt redzams tikai tad, ja leņķa absolūtā vērtība starp šiem vektoriem ir mazāka par 90 grādiem.


3. att. a - leņķis mazāks par 90 - sektors ir potenciāli redzams. b - leņķis lielāks par 90 - sektors nav redzams

Kā iegūt šo leņķi? Lai to izdarītu, jums jāizmanto vektoru skalārais reizinājums. Trīsdimensiju gadījumā to aprēķina šādi:

Skalāram reizinājumam ir sadales īpašība:

Iepriekš mēs definējām vektora garuma vienādojumu - tagad mēs varam teikt, ka vektora garums ir vienāds ar punktu produktsšī vektora uz sevi. Vai arī otrādi - vektora skalārais reizinājums ar sevi ir vienāds ar tā garuma kvadrātu.

Tagad apskatīsim kosinusu likumu. Viens no diviem tā formulējumiem izskatās šādi (4. attēls):


4. att. kosinusu likums

Ja mēs pieņemam, ka a un b ir mūsu vektoru garumi, tad alfa leņķis ir tas, ko mēs meklējam. Bet kā mēs iegūstam c vērtību? Paskatieties: ja mēs atņemam a no b, mēs iegūstam vektoru, kas vērsts no a uz b, un, tā kā vektoru raksturo tikai virziens un garums, mēs varam grafiski noteikt tā sākumu vektora a beigās. No tā mēs varam teikt, ka c ir vienāds ar vektora b - a garumu. Tātad, mēs to izdarījām

Izteiksim garumu kvadrātus kā skalārus reizinājumus

Atvērsim iekavas, izmantojot sadales īpašību

Nedaudz saīsināsim

Visbeidzot, sadalot abas vienādojuma puses ar mīnus divi, mēs iegūstam

Šī ir vēl viena skalārā reizinājuma īpašība. Mūsu gadījumā mums ir jānormalizē vektori, lai to garumi būtu vienādi ar vienu. Mums nav jāaprēķina leņķis - pietiek ar kosinusu. Ja tas ir mazāks par nulli, tad varam droši teikt, ka šī nozare mūs neinteresē

3. Režģis

Ir pienācis laiks domāt par to, kā zīmēt primitīvus. Kā jau teicu iepriekš, sektors ir mūsu diagrammas galvenā sastāvdaļa, tāpēc katram potenciāli redzamajam sektoram mēs uzzīmēsim sietu, kura primitīvi veidos ainavu. Katru no tās šūnām var attēlot, izmantojot divus trīsstūrus. Tā kā katrai šūnai ir blakus esošās skaldnes, vairuma trijstūra virsotņu vērtības tiek atkārtotas divās vai vairākās šūnās. Lai izvairītos no datu dublēšanās virsotņu buferī, aizpildīsim indeksa buferi. Ja tiek izmantoti indeksi, tad ar to palīdzību grafikas konveijers nosaka, kurš primitīvs virsotņu buferī tam jāapstrādā. (5. att.) Topoloģija, kuru es izvēlējos, ir trijstūra saraksts (D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST)


5. att. Indeksu un primitīvu vizuālais attēlojums

Atsevišķa virsotņu bufera izveide katram sektoram ir pārāk dārga. Daudz efektīvāk ir izmantot vienu buferi ar koordinātām režģa telpā, t.i., x ir kolonna un y ir rinda. Bet kā no viņiem var dabūt punktu sfērā? Sektors ir kvadrātveida laukums ar sākumu noteiktā punktā S. Visiem sektoriem ir vienāds malas garums – sauksim to par SLen. Režģis aptver visu sektora laukumu, un tajā ir arī vienāds rindu un kolonnu skaits, tāpēc, lai atrastu šūnas malas garumu, mēs varam izveidot šādu vienādojumu

Kur СLen ir šūnas malas garums, MSize ir režģa rindu vai kolonnu skaits. Sadaliet abas daļas pēc MSize un iegūstiet CLen


6. att. Režģa punkta veidošanās vizuālais attēlojums

Lai iegūtu punktu uz sfēras, mēs izmantojam vienādojumu, kas iegūts iepriekš

4. Augstums

Viss, ko esam sasnieguši līdz šim, maz atgādina ainavu. Ir pienācis laiks pievienot to, kas to padarīs par tādu - augstuma starpību. Iedomāsimies, ka mums ir vienības rādiusa sfēra ar centru, kas atrodas sākumā, kā arī punktu kopa (P0, P1, P2... PN), kas atrodas uz šīs sfēras. Katru no šiem punktiem var attēlot kā vienības vektoru no sākuma. Tagad iedomājieties, ka mums ir vērtību kopa, no kurām katra ir noteikta vektora garums (7. att.).

Es glabāšu šīs vērtības divdimensiju tekstūrā. Mums jāatrod sakarība starp tekstūras pikseļu koordinātām un punkta vektoru uz sfēras. Sāksim.

Papildus Dekarta punktam sfēras punktu var aprakstīt arī, izmantojot sfērisku koordinātu sistēmu. Šajā gadījumā tā koordinātas sastāvēs no trim elementiem: azimuta leņķa, polārā leņķa un īsākā attāluma vērtības no sākuma līdz punktam. Azimuta leņķis ir leņķis starp X asi un stara projekciju no sākuma līdz punktam uz XZ plaknes. Tas var ņemt vērtības no nulles līdz 360 grādiem. Polārais leņķis ir leņķis starp Y asi un staru no sākuma līdz punktam. To var saukt arī par zenītu vai normālu. Pieņem vērtības no nulles līdz 180 grādiem. (skat. 8. att.)


8. att. Sfēriskas koordinātas

Lai konvertētu no Dekarta uz sfērisku, es izmantoju šādus vienādojumus (es pieņemu, ka Y ass ir uz augšu):

Kur d ir attālums līdz punktam, a ir polārais leņķis, b ir azimuta leņķis. Parametru d var aprakstīt arī kā "vektora garumu no sākuma līdz punktam" (kā redzams no vienādojuma). Ja mēs izmantojam normalizētas koordinātas, mēs varam izvairīties no dalīšanas, atrodot polāro leņķi. Patiesībā, kāpēc mums ir vajadzīgi šie leņķi? Izdalot katru no tiem ar maksimālo diapazonu, mēs iegūstam koeficientus no nulles līdz vienam un izmantojam tos, lai ņemtu paraugu no tekstūras ēnotājā. Iegūstot polārā leņķa koeficientu, jāņem vērā ceturksnis, kurā atrodas leņķis. "Bet izteiksmes z / x vērtība nav definēta, ja x ir vienāda ar nulli," jūs sakāt. Turklāt es teikšu, ka tad, kad z ir vienāds ar nulli, leņķis būs nulle neatkarīgi no x vērtības.

Pievienosim šīm vērtībām dažus īpašus gadījumus. Mēs esam normalizējuši koordinātas (normālas) - pievienosim vairākus nosacījumus: ja normāles X vērtība ir nulle un Z vērtība ir lielāka par nulli - tad koeficients ir 0,25, ja X ir nulle un Z ir mazāks par nulli - tad tas ir būs 0,75. Ja Z vērtība ir nulle un X ir mazāka par nulli, tad šajā gadījumā koeficients būs vienāds ar 0,5. To visu var viegli pārbaudīt lokā. Bet ko darīt, ja Z ir nulle un X ir lielāks par nulli – galu galā šajā gadījumā pareizi būs gan 0, gan 1? Iedomāsimies, ka esam izvēlējušies 1 – nu, ņemsim sektoru ar minimālo azimuta leņķi 0 un maksimālo 90 grādiem. Tagad apskatīsim pirmās trīs virsotnes režģa pirmajā rindā, kas parāda šo sektoru. Pirmajai virsotnei izpildījām nosacījumu un faktūras koordinātu X uzstādījām uz 1. Acīmredzot nākamajām divām virsotnēm šis nosacījums nebūs izpildīts - tām stūri ir pirmajā ceturksnī un rezultātā iegūstam kaut ko līdzīgu šim. komplekts - (1,0, 0,05, 0,1). Bet sektoram ar leņķiem no 270 līdz 360 pēdējām trim virsotnēm vienā rindā viss būs pareizi - nostrādās nosacījums pēdējai virsotnei, un mēs iegūsim kopu (0.9, 0.95, 1.0). Ja kā rezultātu izvēlamies nulli, mēs iegūsim kopas (0,0, 0,05, 0,1) un (0,9, 0,95, 0,0) - jebkurā gadījumā tas radīs diezgan ievērojamus virsmas kropļojumus. Tātad piemērosim sekojošo. Ņemsim sektora centru, tad normalizēsim tā centru, tādējādi pārvietojot to uz sfēru. Tagad aprēķināsim normalizētā centra un vektora skalāro reizinājumu (0, 0, 1). Formāli runājot, šis vektors ir normāls XY plaknei, un, aprēķinot tā punktu reizinājumu ar normalizētā sektora centra vektoru, mēs varam saprast, kurā plaknes pusē atrodas centrs. Ja tas ir mazāks par nulli, tad sektors atrodas aiz plaknes un mums vajadzīga vērtība 1. Ja skalārais reizinājums ir lielāks par nulli, tad sektors atrodas plaknes priekšā un tāpēc robežvērtība būs 0. ( sk. 9. att.)


9. att. Tekstūras koordinātu izvēles problēma starp 0 un 1

Šeit ir kods tekstūras koordinātu iegūšanai no sfēriskām. Lūdzu, ņemiet vērā - aprēķinu kļūdu dēļ mēs nevaram pārbaudīt normālās vērtības vienādībai ar nulli, tā vietā mums ir jāsalīdzina to absolūtās vērtības ar kādu sliekšņa vērtību (piemēram, 0,001)

//norma - punkta normalizētās koordinātes, kurām iegūstam tekstūras koordinātas //nobīde - sektora centra normalizētas koordinātas, pie kuras norma pieder //zeroTreshold - sliekšņa vērtība (0,001) float2 GetTexCoords(float3 norma, float3 nobīde) ( peldēt 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 >else if(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)(if(punkts(float3(0.0f, 0.0f, 1.0f), nobīde))< 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); }
else if(norm.x

0.0f && norm.z

Es došu virsotņu ēnotāja starpposma versiju

//startPos - kuba virsmas sākums //vec1, vec2 - kuba virsmas virziena vektori //režģisSolis - šūnas izmērs //sideSize - sektora malas garums //GetTexCoords() - pārvērš sfēriskās koordinātas tekstūras koordinātēs VOut ProcessVertex(VIn ievade) ( 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); output.texCoords = tc;

5. Apgaismojums

Tas pats attiecas uz kosinusa vērtību -1, tikai šajā gadījumā vektori norāda pretējos virzienos. Izrādās, jo tuvāk kolinearitātes stāvoklim atrodas normālvektors un vektors gaismas avotam, jo ​​augstāks ir tās virsmas apgaismojuma koeficients, kurai normāls pieder. Tas arī pieņem, ka virsmu nevar apgaismot, ja tās normāls ir vērsts pretējā virzienā pret avotu - tāpēc es izmantoju tikai pozitīvas kosinusa vērtības.

Es izmantoju paralēlo avotu, tāpēc tā pozīciju var neievērot. Vienīgais, kas jāpatur prātā, ir tas, ka mēs izmantojam gaismas avota vektoru. Tas ir, ja staru virziens ir (1,0, -1,0, 0) - mums jāizmanto vektors (-1,0, 1,0, 0). Vienīgais, kas mums ir grūti, ir parastais vektors. Aprēķināt parasto plaknei ir vienkārši - mums ir jāražo vektora produkts divi vektori, kas to apraksta. Ir svarīgi atcerēties, ka vektora reizinājums ir pretkomutatīvs – jāņem vērā faktoru secība. Mūsu gadījumā mēs varam iegūt trijstūra normālu, zinot tā virsotņu koordinātas acu telpā, šādi (Lūdzu, ņemiet vērā, ka es neņemu vērā p.x un p.y robežgadījumus)

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 = normalzie(cross(v1, v2));
Bet tas vēl nav viss. Lielākā daļa acs virsotņu pieder četrām plaknēm vienlaikus. Lai iegūtu pieņemamu rezultātu, jums jāaprēķina vidējais normāls šādi:

Na = normalizēt (n0 + n1 + n2 + n3)
Šīs metodes ieviešana GPU ir diezgan dārga - mums ir nepieciešami divi posmi, lai aprēķinātu normālos rādītājus un tos vidējotu. Turklāt efektivitāte atstāj daudz vēlamo. Pamatojoties uz to, es izvēlējos citu metodi - izmantot parasto karti (10. att.)


10. att. Parasta karte

Darbības princips ar to ir tāds pats kā ar augstuma karti - mēs pārveidojam acs virsotnes sfēriskās koordinātas tekstūras koordinātes un veicam atlasi. Bet mēs nevarēsim tieši izmantot šos datus - galu galā mēs strādājam ar sfēru, un virsotnei ir savs normāls, kas jāņem vērā. Tāpēc kā TBN bāzes koordinātas izmantosim parastos kartes datus. Kas ir pamats? Šeit ir piemērs jums. Iedomājieties, ka esat astronauts un sēžat uz bākas kaut kur kosmosā. Jūs saņemat ziņojumu no KC: “Jums ir jāpārvietojas no bākas 1 metru pa kreisi, 2 metrus uz augšu un 3 metrus uz priekšu.” Kā to var izteikt matemātiski? (1, 0, 0) * 1 + (0, 1, 0) * 2 + (0, 0, 1) * 3 = (1,2,3). IN matricas formaŠo vienādojumu var izteikt šādi:

Tagad iedomājieties, ka jūs arī sēžat uz bākas, tikai tagad viņi jums raksta no vadības centra: “Mēs jums tur nosūtījām virziena vektorus - jums jāpārvietojas 1 metrs pa pirmo vektoru, 2 metri pa otro un 3 metri pa trešais." Jauno koordinātu vienādojums būs šāds:

Eksplodētais apzīmējums izskatās šādi:

Vai matricas formā:

Tātad, matrica ar vektoriem V1, V2 un V3 ir bāze, un vektors (1,2,3) ir koordinātas šīs bāzes telpā.

Tagad iedomāsimies, ka jums ir vektoru kopa (bāze M) un jūs zināt, kur atrodaties attiecībā pret bāku (punkts P). Jums ir jānoskaidro savas koordinātas šīs bāzes telpā – cik tālu jums jāpārvietojas pa šiem vektoriem, lai nonāktu tajā pašā vietā. Iedomāsimies vajadzīgās koordinātas (X)

Ja P, M un X būtu skaitļi, mēs vienkārši abas vienādojuma puses dalītu ar M, bet ak vai... Iesim citu ceļu - pēc apgrieztās matricas īpašības

Kur es ir identitātes matrica. Mūsu gadījumā tas izskatās šādi

Ko tas mums dod? Mēģiniet reizināt šo matricu ar X, un jūs iegūsit

Ir arī nepieciešams precizēt, ka matricas reizināšanai ir asociatīvā īpašība

Mēs diezgan likumīgi varam uzskatīt vektoru par 3 x 1 matricu

Ņemot vērā visu iepriekš minēto, mēs varam secināt, ka, lai vienādojuma labajā pusē iegūtu X, mums ir nepieciešams pareizā secībā reiziniet abas puses ar apgriezto M matricu

Šis rezultāts mums būs vajadzīgs vēlāk.

Tagad atgriezīsimies pie mūsu problēmas. Es izmantošu ortonormālo bāzi - tas nozīmē, ka V1, V2 un V3 ir viens otram ortogonāli (veido 90 grādu leņķi) un tiem ir garuma vienība. V1 būs pieskares vektors, V2 būs bitangens vektors, un V3 būs normāls. Tradicionālajā DirectX transponētajā formā matrica izskatās šādi:

Kur T ir pieskares vektors, B ir bitangentes vektors un N ir normāls. Atradīsim viņus. Ar parasto ir vieglāk būtībā viss tās ir punkta normalizētās koordinātas. Bitangences vektors ir vienāds ar normālā un pieskares vektora krustojumu. Visgrūtāk būs ar pieskares vektoru. Tas ir vienāds ar apļa pieskares virzienu punktā. Apskatīsim šo brīdi. Vispirms atradīsim XZ plaknes vienības riņķa punkta koordinātas kādam leņķim a

Apļa pieskares virzienu šajā punktā var atrast divos veidos. Apļa punkta vektors un pieskares vektors ir ortogonāli - tāpēc, tā kā funkcijas sin un cos ir periodiskas, mēs varam vienkārši pievienot leņķim a pi/2 un iegūt vēlamo virzienu. Saskaņā ar pi/2 pārvietojuma īpašību:

Mēs saņēmām šādu vektoru:

Varam izmantot arī diferenciāciju – sīkāk skatīt 3.pielikumu. Tātad 11.attēlā var redzēt sfēru, kurai ir uzbūvēts pamats katrai virsotnei. Zilie vektori apzīmē normālus, sarkanie - pieskares vektorus, zaļie - bitangentus vektorus.


11. att. Sfēra ar TBN bāzēm katrā virsotnē. Sarkanie - pieskares vektori, zaļie - bitangenti vektori, zilie vektori - normālie

Esam sakārtojuši pamatu – tagad dabūsim normālu karti. Lai to izdarītu, mēs izmantosim Sobel filtru. Sobel filtrs aprēķina attēla spilgtuma gradientu katrā punktā (rupji runājot, spilgtuma maiņas vektoru). Filtra princips ir tāds, ka katram pikselim un tā kaimiņiem šīs matricas dimensijā ir jāpiemēro noteikta vērtību matrica, ko sauc par “kodolu”. Pieņemsim, ka mēs apstrādājam pikseli P ar kodolu K. Ja tas neatrodas attēla malā, tad tam ir astoņi kaimiņi - augšējais kreisais, augšējais, augšējais labais utt. Sauksim tos par tl, t, tb, l, r, bl, b, br. Tātad K kodola lietošana šim pikselim ir šāda:

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)

Šo procesu sauc par "konvolūciju". Sobel filtrs izmanto divus kodolus, lai aprēķinātu vertikālo un horizontālo gradientu. Apzīmēsim tos kā Kx un Ku:

Pamats ir – var sākt to īstenot. Vispirms mums jāaprēķina pikseļa spilgtums. Es izmantoju pāreju no RGB krāsu modeļa uz YUV modeli PAL sistēmai:

Taču, tā kā mūsu attēls sākotnēji ir pelēktoņos, šo darbību var izlaist. Tagad mums ir nepieciešams “sabrukt” oriģinālo attēlu ar Kx un Ky kodoliem. Tādējādi mēs iegūsim gradienta X un Y komponentus. Arī šī vektora normālā vērtība var būt ļoti noderīga - mēs to neizmantosim, bet attēliem, kas satur normalizētas gradienta normālās vērtības, ir vairāki noderīgi pielietojumi. Ar normalizāciju es domāju šādu vienādojumu

Kur V ir vērtība, kuru mēs normalizējam, Vmin un Vmax ir šo vērtību diapazons. Mūsu gadījumā minimālās un maksimālās vērtības tiek izsekotas ģenerēšanas procesā. Šeit ir Sobel filtra ieviešanas piemērs:

Float SobelFilter::Get GrayscaleData(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)); atgriešanās (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); }
Jāsaka, ka Sobel filtram ir lineāras atdalāmības īpašība, tāpēc šo metodi var optimizēt.

Grūtā daļa ir beigusies - atliek vien ierakstīt gradienta virziena X un Y koordinātas parastās kartes pikseļu R un G kanālos Z koordinātei izmantoju vienu. Es arī izmantoju 3D koeficientu vektoru, lai pielāgotu šīs vērtības. Šis ir piemērs normālas kartes ar komentāriem ģenerēšanai:

//ImageProcessing::ImageData Attēls — sākotnējais attēls. Konstruktors satur pikseļu formātu un attēla datus ImageProcessing::SobelFilter sobelFilter; sobelFilter.Init(Attēls); sobelFilter.NormaliseDirections() = false; sobelFilter.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::vektors dstImgPixels(dstImageSize); for(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);
Tagad es sniegšu piemēru parastas kartes izmantošanai ēnotā:

//texCoords - tekstūras koordinātas, kuras ieguvām, izmantojot 4. punktā aprakstīto metodi //normalL - virsotne normāls //lightDir - vektors uz gaismas avotu //Ld - gaismas avota krāsa //Kd - materiāla krāsa apgaismota virsma float4 normColor = mainNormalTex parauga līmenis(galvenaisNormalTexSampler, texCoords, 0); //pārnes vērtību no apgabala uz [-1.0, 1.0] un normalizē rezultātu float3 normalT = normalize(2.0f * mainNormColor.rgb - 1.0f); //pārtulkot X tekstūras koordinātu no apgabala uz peldošo ang = texCoords.x * 3.141592f * 2.0f; float3 tangenss; tangente.x = -sin(ang); tangent.y = 0,0f; tangente.z = cos(ang); float3 bitangents = normalizēt(cross(normalL, tangent)); float3x3 tbn = float3x3(tangenss, bitangens, normālsL); float3 resNormal = mul(normālsT, tbn); float diff = piesātināts(punkts(resNormal, lightDir.xyz)); float4 resColor = Ld * Kd * diff;

6. Detalizācijas līmenis

Nu, tagad mūsu ainava ir izgaismota! Var lidot uz Mēnesi - pievienot augstuma karti, iestatīt materiāla krāsu, ielādēt sektorus, iestatīt režģa izmēru uz (16, 16) un... Jā, kaut kas ir par mazu - ielikšu (256 , 256) - ak, kaut kas visu palēnina , un kāpēc liela detalizācija attālos sektoros? Turklāt, jo tuvāk novērotājs atrodas planētai, jo mazāk sektoru viņš var redzēt. Jā... mums vēl daudz jāstrādā! Vispirms izdomāsim, kā nogriezt nevajadzīgos sektorus. Noteicošā vērtība šeit būs novērotāja augstums no planētas virsmas - jo augstāks tas ir, jo vairāk sektoru viņš var redzēt (12. att.)


12. att. Novērotāja augstuma atkarība no apstrādāto sektoru skaita

Mēs atrodam augstumu šādi - mēs izveidojam vektoru no novērotāja stāvokļa līdz sfēras centram, aprēķinām tā garumu un atņemam no tā sfēras rādiusa vērtību. Iepriekš es teicu, ka, ja vektora skalārais reizinājums pēc novērotāja un vektora ar sektora centru ir mazāks par nulli, tad šis sektors mūs neinteresē - tagad nulles vietā izmantosim vērtību, kas lineāri atkarīga no augstums. Pirmkārt, definēsim mainīgos — tātad mums būs minimālā un maksimālā vērtība punktu reizinājumam un minimālā un maksimālā augstuma vērtība. Izveidosim šādu vienādojumu sistēmu

Tagad izteiksim A otrajā vienādojumā

Aizstāsim A no otrā vienādojuma ar pirmo

Izteiksim B no pirmā vienādojuma

Aizstāt B no pirmā vienādojuma ar otro

Tagad aizstāsim mainīgos funkcijā

Un mēs saņemsim

Kur Hmin un Hmax ir minimālās un maksimālās augstuma vērtības, Dmin un Dmax ir skalārās reizinājuma minimālās un maksimālās vērtības. Šo problēmu var atrisināt dažādi – skatīt 4. pielikumu.

Tagad mums ir jāsaprot detalizācijas līmeņi. Katrs no tiem noteiks punktveida produkta diapazonu. Pseidokodā sektora piederības noteiktam līmenim noteikšanas process izskatās šādi:

Pārejiet cauri visiem sektoriem, aprēķiniet vektora skalāro reizinājumu pēc novērotāja un vektora pēc sektora centra, ja skalārais reizinājums ir mazāks par iepriekš aprēķināto minimālo slieksni, pārejiet uz nākamo sektoru, pārejiet cauri līmeņiem detalizēti, ja skalārais reizinājums ir šim līmenim noteiktajās robežās, pievienojiet sektoru šim līmenim, cikla beigas pa detalizācijas līmeņiem cikla beigas visiem sektoriem
Mums ir jāaprēķina vērtību diapazons katram līmenim. Pirmkārt, izveidosim divu vienādojumu sistēmu

Atrisinot to, mēs saņemam

Izmantojot šos koeficientus, mēs definējam funkciju

Kur Rmax ir skalārās reizinājuma diapazons (D(H) - Dmin), Rmin ir minimālais diapazons, ko nosaka līmenis. Es izmantoju vērtību 0,01. Tagad mums ir jāatņem rezultāts no Dmax

Izmantojot šo funkciju, mēs iegūsim apgabalus visiem līmeņiem. Šeit ir piemērs:

Const float dotArea = dotRange.maxVal - dotRange.minVal; const float Rmax = dotArea, Rmin = 0,01f; peldēt lodsCnt = lods.size(); pludiņš A = Rmax; pludiņš B = powf(Rmin / Rmax, 1.0f / (lodsCnt - 1.0f)); for(izmērs_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;
Tagad mēs varam noteikt, kādam sektora detalizācijas līmenim pieder (13. att.).


13. att. Sektoru krāsu diferenciācija atbilstoši detalizācijas līmeņiem

Tālāk jums ir jāizdomā režģu izmērs. Saglabāt katram līmenim savu sietu būs ļoti dārgi – daudz efektīvāk ir mainīt viena sieta detaļu lidojuma laikā, izmantojot teselāciju. Lai to izdarītu, mums papildus parastajiem virsotņu un pikseļu ēnotājiem ir jāievieš arī korpusa un domēna ēnotāji. Hull ēnotā galvenais uzdevums ir sagatavot kontrolpunktus. Tas sastāv no divām daļām – galvenās funkcijas un funkcijas, kas aprēķina kontrolpunkta parametrus. Ir jānorāda vērtības šādiem atribūtiem:

domēns
sadalīšana
izvades topoloģija
izvades kontroles punkti
patchconstantfunc
Šis ir korpusa ēnotāja piemērs trīsstūra flīzēšanai:

Struct PatchData ( peldošās malas : SV_TessFactor; peldēt iekšpusē: SV_InsideTessFactor; ); PatchData GetPatchData(InputPatch Patch, uint PatchId: SV_PrimitiveID) ( PatchData izvade; float tessFactor = 2.0f; output.edges = tessFactor; output.edges = tessFactor; output.edges = tessFactor; output.inside = tessFactor; atgriešanās procesa izvade; ) VIn(InPatchutchu Patch, uint PointId: SV_OutputControlPointID, uint PatchId: SV_PrimitiveID) (atgriezt ielāpu; )
Redziet, galvenais darbs tiek veikts programmā GetPatchData (). Tās uzdevums ir noteikt teselācijas koeficientu. Mēs par to runāsim vēlāk, tagad pāriesim uz domēna ēnotāju. Tas saņem kontroles punktus no korpusa ēnotāja un koordinātas no tessellatora. Jaunā pozīcijas vai faktūras koordinātu vērtība dalīšanas ar trijstūriem gadījumā jāaprēķina, izmantojot šādu formulu

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

Kur C1, C2 un C3 ir kontroles punktu vērtības, F ir tesellatora koordinātas. Arī Domain shader ir jāiestata domēna atribūts, kura vērtība atbilst Hull ēnotā norādītajai. Šeit ir domēna ēnotāja piemērs:

Cbuffer buff0: register(b0) ( matrica worldViewProj; ) struct PatchData ( peldošās malas : SV_TessFactor; peldēt iekšā: SV_InsideTessFactor; ); PIN ProcessDomain(PatchData Patch, float3 Koords: 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; atgriešanas izvade;

Virsotnes ēnotāja loma šajā gadījumā ir samazināta līdz minimumam - man tas vienkārši “nodod” datus uz nākamo posmu.

Tagad mums ir jāīsteno kaut kas līdzīgs. Mūsu galvenais uzdevums ir aprēķināt teselācijas koeficientu vai, precīzāk, attēlot tā atkarību no novērotāja augstuma. Atkal izveidosim vienādojumu sistēmu

Atrisinot to tāpat kā iepriekš, mēs iegūstam
Kur Tmin un Tmax ir minimālais un maksimālais teselācijas koeficients, Hmin un Hmax ir novērotāja minimālās un maksimālās augstuma vērtības. Mans minimālais teselācijas koeficients ir viens. maksimums tiek iestatīts atsevišķi katram līmenim

(piemēram, 1, 2, 4, 16).

Ņemsim vienādojuma abu pušu decimālo logaritmu

Atbilstoši logaritmu īpašībai vienādojumu varam pārrakstīt šādi

Tagad viss, kas mums jādara, ir sadalīt abas puses ar baļķi(2)

Bet tas vēl nav viss. X ir aptuveni 2,58. Tālāk jums ir jāatiestata daļēja daļa un jāpalielina divi līdz iegūtā skaitļa pakāpei. Šeit ir kods detalizācijas līmeņu tesellācijas faktoru aprēķināšanai

Float h = kamera->GetHeight(); const DiapazonsF &hR = augstumsRange; for(LodsStorage::Lod &lod: lods)( //atvasināts no sistēmas //A + B * Hmax = Lmin //A + B * Hmin = Lmax //un iegūstot A, tad aizstāšanu B otrajā vienādības pludiņā mTf = (peldēt )lod.GetMaxTessFactor( float tessFactor = 1.0f + (mTf - 1.0f) * ((h - hR.maxVal) / (hR.minVal - hR.maxVal) (1.0f, mTf)); = pow(2.0f, floor(log(tessFactor) / log(2)));

7. Troksnis

Apskatīsim, kā mēs varam palielināt ainavas detalizāciju, nemainot augstuma kartes izmēru. Man ienāk prātā mainīt augstuma vērtību uz vērtību, kas iegūta no gradienta trokšņa faktūras. Koordinātas, pēc kurām mēs veiksim paraugu, būs N reizes lielākas nekā galvenās. Izlases laikā tiks izmantots spoguļadresācijas veids (D3D11_TEXTURE_ADDRESS_MIRROR) (skat. 14. att.).


14. att. Sfēra ar augstuma karti + sfēra ar trokšņu karti = sfēra ar galīgo augstumu

Šajā gadījumā augstumu aprēķina šādi:

//float2 tc1 - tekstūras koordinātas, kas iegūtas no normalizētā punkta, kā //aprakstīts iepriekš //texCoordsScale - tekstūras koordinātu reizinātājs. Manā gadījumā tas ir vienāds ar vērtību 300 //mainHeightTex, mainHeightTexSampler - augstuma kartes tekstūra //distHeightTex, distHeightTexSampler - gradienta trokšņa tekstūra //maxTerrainHeight - maksimālais ainavas augstums. Manā gadījumā 0,03 float2 tc2 = tc1 * texCoordsScale; float4 mainHeighTexColor = mainHeightTex.SampleLevel(galvenaisHeightTexSampler, tc1, 0); float4 distHeighTexColor = distHeightTex.SampleLevel(distHeightTexSampler, tc2, 0); pludiņa augstums = (mainHeighTexColor.x + distHeighTexColor.x) * maxTerrainHeight;
Līdz šim periodiskums ir izteikts ievērojami, taču, pievienojot apgaismojumu un tekstūru, situācija mainīsies uz labo pusi. Kas ir gradienta trokšņa tekstūra? Aptuveni runājot, tas ir nejaušu vērtību režģis. Izdomāsim, kā saskaņot režģa izmērus ar tekstūras izmēriem. Pieņemsim, ka mēs vēlamies izveidot trokšņu faktūru ar izmēru 256 x 256 pikseļi. Tas ir vienkārši, ja režģa izmēri sakrīt ar faktūras izmēriem, mēs iegūsim kaut ko līdzīgu baltajam troksnim televizorā. Bet ko tad, ja mūsu režģa izmēri ir, teiksim, 2 x 2? Atbilde ir vienkārša – izmantojiet interpolāciju. Viena lineārās interpolācijas formula izskatās šādi:

Šis ir ātrākais, bet tajā pašā laikā mums visnepiemērotākais variants. Labāk ir izmantot kosinusa interpolāciju:

Bet mēs nevaram vienkārši interpolēt vērtības diagonāles malās (šūnas apakšējā kreisajā un augšējā labajā stūrī). Mūsu gadījumā interpolācija būs jāpiemēro divas reizes. Iedomāsimies vienu no režģa šūnām. Tam ir četri stūri – sauksim tos par V1, V2, V3, V4. Tāpat šīs šūnas iekšpusē būs sava divdimensiju koordinātu sistēma, kur punkts (0, 0) atbilst V1 un punkts (1, 1) V3 (skat. 15.a att.). Lai iegūtu vērtību ar koordinātām (0,5, 0,5), vispirms ir jāiegūst divas X-interpolētas vērtības - starp V1 un V4 un starp V2 un V3, un visbeidzot Y-interpolē starp šīm vērtībām (Zīm. 15b).

Šeit ir piemērs:

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)


15. att. a - režģa šūnas attēls ar koordinātām V1, V2, V3 un V4. b - divu interpolāciju secība, izmantojot šūnu kā piemēru

Tagad rīkojamies šādi – katram trokšņa faktūras pikselim ņemam interpolēto vērtību 2x2 režģim, pēc tam pievienojiet tai 4x4 režģa interpolēto vērtību, kas reizināta ar 0,5, pēc tam 8x8 režģim, kas reizināta ar 0,25 utt. līdz noteiktai robežai – to sauc par saskaitīšanas oktāvām (16. att.). Formula izskatās šādi:


16. att. Oktāvu pievienošanas piemērs

Šeit ir īstenošanas piemērs:

For(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);
Arī V1, V2, V3 un V4 summu var iegūt no pašas vērtības un tās kaimiņiem, piemēram:

Float GetSmoothValue(const Point2 &Coords) ( peldošie stūri = (GetValue((Coords.x - 1, Coords.y - 1)) + GetValue((Coords.x + 1, Coords.y - 1)) + GetValue((Coords) .x - 1, Coords.y + 1)) + GetValue((Coords.x + 1, Coords.y + 1))) / 16.0f; peldošās malas = (GetValue((Coords.x - 1, Coords.y )) + GetValue((Coords.x + 1, Coords.y)) + GetValue((Coords.x, Coords.y - 1)) + GetValue((Coords.x, Coords.y + 1))) / 8.0 f peldēšanas centrs = GetValue(Coords) / 4.0f atgriešanās centrs + malas + stūri;
un izmantojiet šīs vērtības interpolācijai. Lūk, pārējais kods:

Float GetInterpolatedValue(const Point2 &TopLeftCoord, const Point2 &BottomRightCoord, float XFactor, float YFactor) ( Point2 tlCoords(TopLeftCoord.x, TopLeftCoord.y); Point2 trCoords(BottomRightCoord.BottomRightCoord.BottomRightCoord.) Coord.x, BottomRightCoord .y);TopLeftCoord.y MOTHVALUS) : BRCOORDS (BRCOORDS) = MATH :: Cosinterpolation (BL, BR, XFACTOR)
Apakšnodaļas noslēgumā es gribu teikt, ka viss, ko esmu aprakstījis līdz šim, ir nedaudz atšķirīga Perlin trokšņa realizācija no kanoniskā.
Esam sakārtojuši augstumus – tagad skatīsimies, ko darīt ar normāliem. Tāpat kā galvenajā augstuma kartē, mums ir jāģenerē parasta karte no trokšņa faktūras. Tad ēnotā mēs vienkārši pievienojam parasto no galvenās kartes ar parasto no trokšņa faktūras. Man jāsaka, ka tas nav pilnīgi pareizi, bet tas dod pieņemamu rezultātu. Šeit ir piemērs:
//float2 texCoords1 - tekstūras koordinātas, kas iegūtas no normalizētā punkta, kā aprakstīts iepriekš //mainNormalTex, mainNormalTexSampler - galvenā normālā karte //distNormalTex, distNormalTexSampler - gradienta trokšņu parastā karte float2 texCoords2 = texCoords1 * ScaleCoord; float4 mainNormColor = mainNormalTex.SampleLevel(galvenaisNormalTexSampler, 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. Aparatūras instalēšana

Veiksim optimizāciju. Tagad sektoru zīmēšanas cikls pseidokodā izskatās šādi

Aprēķiniet cilpu pa visiem sektoriem, aprēķiniet vektora skalāro reizinājumu ar punktu un vektoru pēc sektora centra, ja tas ir lielāks par nulli, iestatiet S vērtību ēnotāja datos, iestatiet V1 vērtību ēnotājā dati, iestatiet V2 vērtību ēnotāja datos, zīmējiet tīklu, nosacījuma beigas, cilpas beigas visos sektoros
Šīs pieejas veiktspēja ir ārkārtīgi zema. Ir vairākas optimizācijas iespējas – katrai kuba plaknei var izveidot četrkoku, lai nerēķinātu katra sektora skalāro reizinājumu. Varat arī atjaunināt V1 un V2 vērtības nevis katram sektoram, bet gan sešām kuba plaknēm, kurām tās pieder. Izvēlējos trešo variantu - Instancing. Īsumā par to, kas tas ir. Pieņemsim, ka vēlaties uzzīmēt mežu. Jums ir koka modelis, un jums ir arī transformācijas matricu kopa - koka pozīcijas, iespējama mērogošana vai rotācija. Var izveidot vienu buferi, kurā būs visu koku virsotnes, kas pārvērstas pasaules telpā – tas ir labs variants, mežs neskraida pa karti. Bet ja vajag īstenot pārvērtības – teiksim, koki vējā šūpojas. To var izdarīt - kopēt modeļa virsotņu datus N reizes vienā buferī, virsotņu datiem pievienojot koka indeksu (no 0 līdz N). Tālāk mēs atjauninām transformācijas matricu masīvu un nododam to ēnotājam kā mainīgo. Ēnotājā mēs atlasām vajadzīgo matricu pēc koka indeksa. Kā izvairīties no datu dublēšanās? Sākumā es vēlos vērst jūsu uzmanību uz to, ka šīs virsotnes var savākt no vairākiem buferiem. Lai aprakstītu virsotni, struktūras D3D11_INPUT_ELEMENT_DESC laukā InputSlot ir jānorāda avota indekss. To var izmantot, ieviešot morfējošu sejas animāciju — pieņemsim, ka jums ir divi virsotņu buferi, kuros ir divi sejas stāvokļi, un jūs vēlaties šīs vērtības lineāri interpolēt. Lūk, kā aprakstīt virsotni:

D3D11_INPUT_ELEMENT_DESC desc = ( /*part1*/ ("POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0), ("NORMAL", 0, DXGI_3FORMAT_3, DXGI_3 1_INPUT_PER_VERTEX_DATA, 0), ("TEXCOORD", 2 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) )
ēnotā virsotne jāapraksta šādi:

Struktūra VIn ( float3 position1: POSITION0; float3 normal1: NORMAL0; float2 tex1: TEXCOORD0; float3 position2: POSITION1; float3 normal2: NORMAL1; float2 tex2: TEXCOORD1; )
tad jūs vienkārši interpolējat vērtības

Float3 res = lerp(ievade.pozīcija1, ievade.pozīcija2, faktors);
Kāpēc es to saku? Atgriezīsimies pie koku piemēra. Mēs savāksim virsotni no diviem avotiem - pirmajā būs atrašanās vieta lokālajā telpā, faktūras koordinātas un norma, otrajā būs transformācijas matrica četru četrdimensiju vektoru veidā. Virsotnes apraksts izskatās šādi:

D3D11_INPUT_ELEMENT_DESC desc = ( /*part1*/ ("POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0), ("NORMAL", 0, DXGI_3FORMAT_3, DXGI_3 1_INPUT_PER_VERTEX_DATA, 0), ("TEXCOORD", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0), /*part2*/ ("WORLD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, _PERD_11, D ", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 16, D3D11_INPUT_PER_INSTANCE_DATA , 1), ("WORLD", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 32, D3D11_INPUT_PER_INSTANCE_DATA, 1), ("WORLD", 3, DXGI_FORMAT_R32G32B32A32A32_FLOAT,1,_DATA)8,
Lūdzu, ņemiet vērā, ka otrajā daļā lauks InputSlotClass ir vienāds ar D3D11_INPUT_PER_INSTANCE_DATA un lauks InstanceDataStepRate ir vienāds ar vienu (īsu lauka InstanceDataStepRate aprakstu skatiet 1. pielikumā). Šajā gadījumā savācējs izmantos visa bufera datus no avota ar tipu D3D11_INPUT_PER_VERTEX_DATA katram elementam no avota ar tipu D3D11_INPUT_PER_INSTANCE_DATA. Šajā gadījumā ēnotā šīs virsotnes var aprakstīt šādi:

Struktūra VIn ( float3 posL: POSITION; float3 normalL: NORMAL; float2 tex: TEXCOORD; row_major float4x4 world: WORLD; );
Izveidojot otru buferi ar atribūtiem D3D11_USAGE_DYNAMIC un D3D11_CPU_ACCESS_WRITE, mēs varam to atjaunināt no CPU puses. Šāda veida ģeometrija ir jāzīmē, izmantojot DrawInstanced() vai DrawIndexedInstanced() izsaukumus. Ir arī izsaukumi DrawInstancedIndirect() un DrawIndexedInstancedIndirect() - par tiem skatiet 2. pielikumu.

Tālāk ir sniegts buferu iestatīšanas un funkcijas DrawIndexedInstanced() izmantošanas piemērs:

//vb - virsotņu buferis //tb - "instance" buferis //ib - indeksa buferis //vertexSize - elementa lielums virsotnes buferī //instanceSize - elementa lielums "instance" buferī //indicesCnt - indeksu skaits //instancesCnt - "gadījumu" skaits std::vector buferi = (vb, tb); std::vektors soļi = (virsotnesSize, instanceSize); std::vektors nobīdes = (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(indexesCnt, instancesCnt, 0, 0, 0);
Tagad beidzot atgriezīsimies pie mūsu tēmas. Sektoru var aprakstīt ar punktu plaknē, kurai tas pieder, un diviem vektoriem, kas apraksta šo plakni. Tāpēc maksimums sastāvēs no diviem avotiem. Pirmais ir režģa telpas koordinātas, otrais ir sektora dati. Virsotnes apraksts izskatās šādi:

Std::vektors meta = ( //koordinātas režģa telpā ("POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0) //pirmās virsmas vektors ("TEXCOORD", 0, DXGPER_FORMAT_B3,1 DATI, 1), / /otrās virsmas vektors ("TEXCOORD", 1, DXGI_FORMAT_R32G32B32_FLOAT, 1, 12, D3D11_INPUT_PER_INSTANCE_DATA, 1), //virsmas sākums ("TEXCOORD", 2, DXGI_FORMAT_R32G32B, INSTUTD_12B,2.1 ANCE_DATA, 1) )
Ņemiet vērā, ka es izmantoju 3D vektoru, lai saglabātu režģa telpas koordinātas (z koordinātas netiek izmantotas)

9. Frustum izkaušana

Vēl viens svarīgs optimizācijas komponents ir redzamības piramīdas apgriešana (Frustum izkaušana). Redzamības piramīda ir ainas apgabals, ko kamera "redz". Kā to uzbūvēt? Pirmkārt, atcerieties, ka punkts var atrasties četrās koordinātu sistēmās – lokālajā, pasaules, skatu un projekcijas koordinātu sistēmās. Pāreja starp tām tiek veikta caur matricām – pasauli, sugu un projekcijas matricu, un transformācijām jānotiek secīgi – no lokālās uz pasauli, no pasaules uz sugu un visbeidzot no sugas uz projekcijas telpu. Visas šīs transformācijas var apvienot vienā, reizinot šīs matricas.

Mēs izmantojam perspektīvo projekciju, kas ietver tā saukto “viendabīgo dalīšanu” - pēc vektora (Px, Py, Pz, 1) reizināšanas ar projekcijas matricu, tās komponentes jāsadala ar šī vektora W komponenti. Pēc pārejas uz projekcijas telpu un vienmērīgu dalījumu punkts nonāk NDC telpā. NDC telpa ir trīs koordinātu kopa x, y, z, kur x un y pieder [-1, 1], un z - (Jāsaka, ka OpenGL parametri nedaudz atšķiras).

Tagad ķersimies pie mūsu problēmas risināšanas. Manā gadījumā piramīda atrodas skata telpā. Mums ir vajadzīgas sešas plaknes, kas to raksturo (17.a att.). Plakni var aprakstīt, izmantojot normālu un punktu, kas pieder šai plaknei. Pirmkārt, iegūstam punktus - šim NDC telpā mēs ņemam šādu koordinātu kopu:

Std::vektors PunktiN = ( (-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) );
Paskatieties, pirmajos četros punktos z vērtība ir 0 - tas nozīmē, ka tie pieder pie tuvākās apgriešanas plaknes, pēdējos četros z ir vienāds ar 1 - tie pieder tālākajai apgriešanas plaknei. Tagad šie punkti ir jāpārvērš, lai redzētu vietu. Bet kā?

Atcerieties piemēru par astronautu — tātad šeit ir tas pats. Mums ir jāreizina punkti ar apgrieztās projekcijas matricu. Tiesa, pēc tam vēl ir jāsadala katra ar tās koordinātu W. Rezultātā iegūstam nepieciešamās koordinātas (17.b att.). Tagad apskatīsim normālus - tiem jābūt vērstiem piramīdas iekšpusē, tāpēc mums ir jāizvēlas nepieciešamā secība vektora reizinājuma aprēķināšanai.

Matrix4x4 invProj = Matrix4x4::Inverse(kamera->GetProjMatrix()); std::vektors PunktiV; for(const Point4F &pN: pointsN)( Point4F pV = invProj.Transform(pN); pV /= pV.w; pointsV.push_back(Cast (pV)); ) plaknes = (punktiV, punktiV, punktiV); //plaknes plaknēm = (punktiV, punktiV, punktiV); //tālās plaknes plaknes = (punktiV, punktiV, punktiV); //kreisās plaknes plaknes = (punktiV, punktiV, punktiV); //labās plaknes plaknes = (punktiV, punktiV, punktiV); //augšējās plaknes plaknes = (punktiV, punktiV, punktiV); //apakšējās plaknes plaknes.normāls *= -1.0f; plaknes.normāls *= -1.0f;


17. att. Redzamības piramīda

Piramīda ir uzbūvēta – laiks to izmantot. Mēs nezīmējam tos sektorus, kas neietilpst piramīdas iekšpusē. Lai noteiktu, vai kāds sektors atrodas redzamības piramīdas iekšpusē, pārbaudīsim norobežojošo sfēru, kas atrodas šī sektora centrā. Tas nedod precīzus rezultātus, bet šajā gadījumā Es neredzu neko sliktu tajā, ka tiek uzzīmēti vairāki papildu sektori. Sfēras rādiusu aprēķina šādi:

Kur TR ir sektora augšējais labais stūris, BL ir apakšējais kreisais stūris. Tā kā visiem sektoriem ir vienāda platība, pietiek vienreiz aprēķināt rādiusu.

Kā mēs varam noteikt, vai sektoru raksturojošā sfēra atrodas redzamības piramīdas iekšpusē? Vispirms mums ir jānosaka, vai sfēra krustojas ar plakni un, ja nē, kurā tās pusē tā atrodas. Novedīsim vektoru uz sfēras centru

Kur P ir punkts plaknē un S ir sfēras centrs. Tagad aprēķināsim šī vektora skalāro reizinājumu un plaknes normālu. Orientāciju var noteikt, izmantojot punktveida reizinājuma zīmi - kā jau iepriekš minēts, ja tā ir pozitīva, tad sfēra atrodas plaknes priekšā, ja negatīva, tad lode atrodas aizmugurē. Atliek noteikt, vai sfēra šķērso plakni. Ņemsim divus vektorus - N (normāls vektors) un V. Tagad konstruēsim vektoru no N līdz V - sauksim to par K. Tātad jāatrod tāds garums N, lai tas veidotu 90 grādu leņķi ar K (formāli). runājot, lai N un K būtu ortogonāli). Oki doki, paskaties uz 18.a attēlu - no taisnleņķa trijstūra īpašībām mēs to zinām

Mums jāatrod kosinuss. Izmantojot iepriekš minēto punktu produkta īpašību

Sadaliet abas puses ar |V|*|N| un saņemam

Izmantosim šo rezultātu:

Kopš |V| ir tikai skaitlis, mēs varam to samazināt par |V|, un tad mēs iegūstam

Tā kā vektors N ir normalizēts, pēdējais solis ir vienkārši reizināt to ar iegūto vērtību, pretējā gadījumā vektors ir jānormalizē - šajā gadījumā galīgais vienādojums izskatās šādi:

Kur D ir mūsu jaunais vektors. Šo procesu sauc par “Vektoru projekciju” (18.b att.). Bet kāpēc mums tas ir vajadzīgs? Mēs zinām, ka vektoru nosaka tā garums un virziens un tas nekādā veidā nemainās atkarībā no tā stāvokļa - tas nozīmē, ka, ja pozicionēsim D tā, lai tas norādītu uz S, tad tā garums būs vienāds ar minimālo attālumu no S uz plakni (18.c att.)


18. att. a N projekcija uz V, b Projicētā N garuma vizuāls attēlojums attiecībā pret punktu, c Vizuāls displejs
projicētā N garums, kas piemērots sfērai, kuras centrs ir S

Tā kā mums nav nepieciešams projicēts vektors, mums vienkārši jāaprēķina tā garums. Ņemot vērā, ka N ir vienības vektors, mums ir jāaprēķina tikai V un N punktu reizinājums. Saliekot visu kopā, mēs beidzot varam secināt, ka sfēra šķērso plakni, ja vektora punktu reizinājuma vērtība ir ar vektora centru. sfēra un plaknes norma ir lielāka par nulli un mazāka par šīs sfēras rādiusa vērtību.

Lai apgalvotu, ka sfēra atrodas redzamības piramīdas iekšpusē, mums jāpārliecinās, ka tā vai nu šķērso kādu no plaknēm, vai atrodas katras no tām priekšā. Jautājumu var uzdot savādāk – ja sfēra nekrustojas un atrodas aiz vismaz vienas plaknes, tā noteikti atrodas ārpus redzamības piramīdas. To mēs darīsim. Lūdzu, ņemiet vērā, ka es pārnesu sfēras centru uz to pašu telpu, kurā atrodas piramīda - skata telpā.

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

10. Plaisas

Vēl viena problēma, kas mums jāatrisina, ir plaisas detaļu līmeņu robežās (19. att.).


19. att. ainavas plaisu demonstrācija

Pirmkārt, mums ir jānosaka tās nozares, kas atrodas uz detalizācijas līmeņu robežas. No pirmā acu uzmetiena šķiet, ka tas ir resursietilpīgs uzdevums - galu galā nozaru skaits katrā līmenī pastāvīgi mainās. Bet, ja izmantojat blakus datus, risinājums kļūst daudz vienkāršāks. Kas ir blakus dati? Skaties, katram sektoram ir četri kaimiņi. Atsauču kopums uz tiem — vai tie būtu norādes vai indeksi — ir blakus dati. Ar viņu palīdzību mēs varam viegli noteikt, kurš sektors atrodas uz robežas – vienkārši pārbaudiet, kuram līmenim pieder tās kaimiņi.

Nu, meklēsim katra sektora kaimiņus. Un atkal, mums nav jāpārskata visi sektori. Iedomāsimies, ka strādājam ar sektoru ar X un Y koordinātām režģa telpā.

Ja tas nepieskaras kuba malai, tā kaimiņu koordinātas būs šādas:

Augšējais kaimiņš — (X, Y — 1)
Apakšējais kaimiņš — (X, Y + 1)
Kreisais kaimiņš — (X — 1, Y)
Labais kaimiņš — (X + 1, Y)

Ja sektors pieskaras kādai malai, tad ievietojam speciālā traukā. Pēc visu sešu skaldņu apstrādes tajā būs visi kuba robežsektori. Tieši šajā konteinerā mums ir jāveic meklēšana. Aprēķināsim katra sektora malas iepriekš:

Struct SectorEdges ( CubeSectors::Sector *īpašnieks; typedef std::pair Mala; sektoriEdges; //borderSectors - konteiners ar apmales sektoriem priekš(CubeSectors::Sector &sec: borderSectors)( // katrā sektorā ir divi vektori, kas apraksta kuba virsmu // kuram tas pieder Vector3 v1 = sec.vec1 * sec.sideSize ; Vector3 v2 = sec.vec2 * sec.sideSize - sektora sākums SectorEdges.owner = secEdges.edges = (sec.startPos + v1); , sec.startPos + v2, sec.startPos + v2 + v1 = (sec.startPos + v1, sec.startPos + v2 + v1);
Tālāk seko pati meklēšana

For(SectorEdges &edgs: sektoriEdges) for(size_t e = 0; e< 4; e++) if(edgs.owner->blakus[e] == nullptr) FindSectorEdgeAdjacency(edgs, (AdjacencySide)e, sektoriEdges);
Funkcija FindSectorEdgeAdjacency() izskatās šādi

Tukšie kuba sektori::FindSectorEdgeAdjacency(Sector Edges &Sector, CubeSectors::AdjacencySide Side, std::vector &Neibs) ( SectorEdges::Edge &e = Sector.edges; for(SectorEdges &edgs2: Neibs)( if(edgs2.owner == Sector.owner) turpināt; 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->blakus = edgs2.owner;
edgs2.owner->adjacency[e] = sektora īpašnieks;

atgriešanās;
) ) ))

Std::vektors Lūdzu, ņemiet vērā, ka mēs atjaunojam divu sektoru - meklētā (Sector) un atrastā kaimiņa - blakus datus.
Pēc tam, kad esam sadalījuši sektorus pēc detalizācijas līmeņiem, mēs katram sektoram nosakām blakus esošos tesellācijas koeficientus:

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

for(Sector *s: sektori)( int32_t tessFacor = s->GetTessFactor(); s->GetBorderTessFactor() = ( GetNeibTessFactor(s, Sector::ADJ_BOTTOM, tessFacor, lastLod), GetFNeictor,ADJs, GetFNeictor,ADJs tessFacor, lastLod), GetNeibTessFactor(s, Sector::ADJ_TOP, tessFacor, lastLod), GetNeibTessFactor(s, Sector::ADJ_RIGHT, tessFacor, lastLod) ) )< TessFactor) ? (float)neibTessFactor: 0.0f; }
Funkcija, kas meklē blakus esošo teselācijas faktoru:

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

Ja atgriežam nulli, tad kaimiņš Sides pusē mūs neinteresē. Ļaujiet man lēkt uz priekšu un teikt, ka mums ir jānovērš plaisas no līmeņa sāniem ar augstu teselācijas koeficientu.
Tagad pāriesim pie ēnotāja. Ļaujiet man atgādināt, ka vispirms mums ir jāiegūst režģa koordinātas, izmantojot tessellatora koordinātas. Tad šīs koordinātas tiek pārveidotas par punktu kuba virspusē, šis punkts tiek normalizēts - un tagad mums ir punkts uz sfēras:

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 = normalizēt(planePos);
Vispirms mums ir jānoskaidro, vai virsotne pieder režģa pirmajai vai pēdējai rindai, vai arī pirmajai vai pēdējai kolonnai - šajā gadījumā virsotne pieder sektora malai. Bet ar to nepietiek - mums ir jānosaka, vai virsotne pieder LOD robežai. Lai to izdarītu, mēs izmantojam informāciju par blakus esošajiem sektoriem vai drīzāk to tesellācijas līmeņiem: Float4 bTf = Tri.borderTessFactor; bool isEdge = (bTf.x != 0.0f && p.y == 0.0f) || //apakšā (bTf.y != 0.0f && p.x == 0.0f) || //pa kreisi (bTf.z != 0.0f && p.y == gridSize.y) || //augšpusē (bTf.w != 0,0f && p.x == gridSize.x) //pa labi- faktiski novēršot plaisas. Apskatiet 20. attēlu. Sarkanā līnija ir virsotnes virsotne, kas pieder otrajam detalizācijas līmenim. Divas zilās līnijas ir trešā detalizācijas līmeņa malas. Mums ir nepieciešams, lai V3 piederētu sarkanajai līnijai - tas ir, gulēt uz otrā līmeņa malas. Tā kā augstumi V1 un V2 ir vienādi abiem līmeņiem, V3 var atrast ar lineāru interpolāciju starp tiem


20. att. Plaisu veidojošo virsmu demonstrēšana līniju veidā

Pagaidām mums nav ne V1 un V2, ne koeficienta F. Vispirms jāatrod punkta V3 indekss. Tas ir, ja acs izmērs ir 32x32 un teselācijas koeficients ir četri, tad šis indekss būs no nulles līdz 128 (32 * 4). Mums jau ir koordinātas režģa telpā p - ietvaros šis piemērs tie var būt, piemēram, (15,5, 16). Lai iegūtu indeksu, viena no p koordinātām jāreizina ar teselācijas koeficientu. Mums ir nepieciešams arī sejas sākums un virziens uz tās beigām - vienu no sektora stūriem.

Float edgeVertInd = 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)( // apakšējā malaVertInd = 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; .tessFactor; edgeVec = Tri.vec2; startPos = Tri.startPos + Tri.vec1 * (gridStep.x * gridSize.x);
Tālāk mums jāatrod V1 un V2 indeksi. Iedomājieties, ka jums ir skaitlis 3. Jums jāatrod divi tuvākie skaitļi, kas ir divu reizinātāji. Lai to izdarītu, jūs aprēķināt atlikumu, dalot trīs ar diviem - tas ir vienāds ar vienu. Tad jūs atņemat vai pievienojat šo atlikumu trīs un iegūstat vēlamo rezultātu. Tas pats ar indeksiem, bet divu vietā mums būs detalizācijas līmeņu tesellācijas koeficientu attiecība. Tas ir, ja trešā līmeņa koeficients ir 16, bet otrā ir 2, tad attiecība būs vienāda ar 8. Tagad, lai iegūtu augstumus, vispirms ir jāiegūst atbilstošie punkti uz sfēras, normalizējot. punkti uz sejas. Mēs jau esam sagatavojuši malas sākumu un virzienu - atliek tikai aprēķināt vektora garumu no V1 līdz V2. Tā kā sākotnējās režģa šūnas malas garums ir gridStep.x, mums nepieciešamais garums ir gridStep.x / Tri.tessFactor. Tad no sfēras punktiem mēs iegūsim augstumu, kā aprakstīts iepriekš.

Float GetNeibHeight(float3 EdgeStartPos, float3 EdgeVec, float VecLen, float3 NormOffset) ( float3 neibPos = EdgeStartPos + EdgeVec * VecLen; neibPos = normalize(neibPos); return GetHeight(neibPos, NormOffset) = flotOffset /. tessFactor; uint tessRatio = (uint)tessFactor / (uint)neibTessFactor; uint ind = (uint)edgeVertInd % tessRatio; uint leftNeibInd = (uint)edgeVertInd — ind; peldēt pa kreisiNeibHeight = GetNeibHeight(startPos, edgeVec, vertOffset * leftNeibInd, normOffset); uint rightNeibInd = (uint)edgeVertInd + ind; peldēt rightNeibHeight = GetNeibHeight(startPos, edgeVec, vertOffset * rightNeibInd, normOffset);
Nu, pēdējā sastāvdaļa ir koeficients F. Mēs to iegūstam, dalot atlikušo daļu ar koeficientu attiecību (ind) ar koeficientu attiecību (tessRatio)

Pludināšanas koeficients = (float)ind / (float)tessRatio;
Pēdējais posms ir augstumu lineāra interpolācija un jaunas virsotnes iegūšana

Float avgHeight = lerp(kreisaisNeibAugstums, pa labiNeibAugstums, faktors); posL = sphPos * (sphereRadius + avgHeight);
Plaisa var parādīties arī tur, kur robežojas sektori ar malu faktūras koordinātām, kas vienādas ar 1 vai 0. Šajā gadījumā es ņemu vidējo vērtību starp abu koordinātu augstumiem.

Float GetHeight(float2 TexCoords) ( float2 texCoords2 = TexCoords Scale; peldēt mHeight = mainHeightTex.SampleLevel(mainHeightTexSampler, TexCoords, 0).x; pludināt dAugstums(distample.S , 0).x; atgriešanās (mHeight + dHeight) * maxTerrainHeight ) peldēt GetHeight(float3 SphPos, float3 NormOffset) ( float2 texCoords1 = GetTexCoords(SphPos, NormOffset); peldēšanas augstums = GetHeight(texCoords1); if(texCoords = Augstums1. ( pludiņš2(0.0f, texCoords.y)); return lerp(height, height2, 0.5f )else if(texCoords1.x == 0.0f)( float height2 = GetHeight(float2(1.0f, texCoords1.y)) ) ; return lerp(augstums, augstums2, 0,5f);

11. GPU apstrāde

Pārcelsim sektora apstrādi uz GPU. Mums būs divi Compute ēnotāji – pirmais veiks apgriešanu pa redzamības piramīdu un noteiks detalizācijas līmeni, otrs saņems robežu teselācijas koeficientus, lai novērstu plaisas. Sadalījums divos posmos ir nepieciešams, jo, tāpat kā CPU gadījumā, mēs nevaram pareizi noteikt sektoru kaimiņus, kamēr mēs neveicam atzarošanu. Tā kā abi ēnotāji izmantos LOD datus un strādās ar sektoriem, es ieviesu divus vispārējā struktūra: Sektors un Lod

Struktūras sektors ( float3 vec1, vec2; float3 startPos; float3 normCenter; int blakus; float borderTessFactor; int lod; ); struct Lod ( RangeF dotRange; float tessFactor; float padding; float4 color; );
Mēs izmantosim trīs galvenos buferus - ievades (satur sākotnējo informāciju par sektoriem), starpposma (tajā ir dati no sektoriem, kas iegūti pirmā posma rezultātā) un galīgo (tiks nodoti zīmēšanai). Ievades bufera dati nemainīsies, tāpēc D3D11_BUFFER_DESC struktūras laukā Usage ir saprātīgi izmantot vērtību D3D11_USAGE_IMMUTABLE. Tajā vienkārši ierakstīsim visu sektoru datus ar vienīgo atšķirību, ka blakus datiem izmantosim sektoru indeksus , nevis norādes uz tiem. Detalizētības indeksa līmenim un robežu teselācijas koeficientiem iestatiet vērtības uz nulli:

Statiskā konst. size_t sektora lielums =(3. vektora) + //vec1 lielums(3. vektora) + //vec22. (Punkta3F) izmērs + //norma(Punkta3F) centra lielums + //StartPos izmērs(4.punkts) + //(4. vektora) blakus lielums + //borderTessFactor sizeof(int32_t);//lod size_t sektorisDataSize = sektori.GetSectors().izmērs() * sektora izmērs; std::vektors sektoriDati(sectorsDataSize); char* ptr = const Sector* firstPtr = §ors.GetSectors(); for(const Sector &sec: sektori)( 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, Vector4()); Utils::AddToStream (ptr, 0); ). D,//mis c karodziņi sektora izmērs) ; //struktūras baita solis< dotRange.minVal || dotVal >Tagad daži vārdi par starpposma buferi. Tam būs divas lomas – izeja pirmajam ēnotājam un ievade otrajam, tāpēc laukā BindFlags norādīsim vērtību D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE. Tam arī izveidosim divus skatus - UnorderedAccessView, kas ļaus ēnotājam tajā ierakstīt sava darba rezultātu un ShaderResourceView, ar kuru kā ievadi izmantosim buferi. Tā izmērs būs tāds pats kā iepriekš izveidotajam ievades buferim< 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() * sektora izmērs,//Bufera lielums miscFlags, D3D11_USAGE_DEFAULT,//lietojums 0,//CPU piekļuves karodziņi D3D11_RESOURCE_MISCizeRE/BUFFER_MISC_Sektors); /structure byte solis intermediateUAW = Utils::DirectX::CreateUnorderedAccessView(intermediateData, D3D11_BUFFER_UAV(0, sektori.GetSectors().size(), 0)); intermediateSRV = Utils::DirectX::CreateShaderResourceView(intermediateData, D3D11_BUFFEREX_SRV(0, sektori.GetSectors().size(), 0));<= lod.dotRange.maxVal) sector.lod = l + 1; } outputData = sector; }
Pēc punktu reizinājuma aprēķināšanas pārbaudām, vai sektors atrodas potenciāli redzamajā reģionā. Tālāk mēs precizējam tā redzamības faktu, izmantojot IsVisible() izsaukumu, kas ir identisks iepriekš parādītajam Frustum::TestSphere() izsaukumam. Funkcijas darbība ir atkarīga no worldView, sphereRadius, frustumPlanesPosV un frustumPlanesNormalsV mainīgajiem, kuru vērtības ir iepriekš jānodod ēnotājam. Tālāk mēs definējam detalizācijas pakāpi. Lūdzu, ņemiet vērā, ka mēs norādām līmeņa indeksu, sākot no viena - tas ir nepieciešams, lai otrajā posmā atmestu tos sektorus, kuru detalizācijas līmenis ir vienāds ar nulli.

Tagad mums ir jāsagatavo buferi otrajam posmam. Mēs vēlamies izmantot buferi kā izvadi Compute shader un ievadi tessellatoram — šim nolūkam laukā BindFlags jānorāda vērtība D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_VERTEX_BUFFER. Mums būs jāstrādā ar bufera datiem tieši, tāpēc laukā MiscFlags norādīsim vērtību D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS Lai parādītu šādu buferi, laukā Karogi izmantosim vērtību DXGI_FORMAT_R32_TYPELESS, bet laukā NumElements norādīsim visu. buferis dalīts ar četriem

Size_t instancesByteSize = instanceByteSize * sektori.GetSectors().size(); outputData = Utils::DirectX::CreateBuffer(instancesByteSize, D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_DEFAULT, 0, D3D11_RESOURCE_IEOW_BUFFERV_ALLC_BUFFERV); D3D11_BUFFER_UAV uavParams = (0, instancesByteSize / 4, D3D11_BUFFER_UAV_FLAG_RAW); outputUAW = Utils::DirectX::CreateUnorderedAccessView(outputData, uavParams, DXGI_FORMAT_R32_TYPELESS);
Mums ir nepieciešams arī skaitītājs. Ar tās palīdzību mēs ēnotā adresējam atmiņu un izmantojam tās galīgo vērtību DrawIndexedInstanced() izsaukuma argumentā instanceCount. Es ieviesu skaitītāju kā 16 baitu buferi. Tāpat, veidojot kartējumu lauka D3D11_BUFFER_UAV laukā Karogi, es izmantoju vērtību D3D11_BUFFER_UAV_FLAG_COUNTER

Skaitītājs = 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::IzveidotUnorderedAccessView(skaitītājs, uavParams);
Ir pienācis laiks iedot kodu otrajam ēnotājam

(ptr, Vector4()); ievaddati: register(t0); RWByteAddressBufera izvaddati: register(u0); RWStructuredBuffer skaitītājs: register(u1);
void Process(int3 TId: SV_DispatchThreadID) ( int ind = TId.x; sektora sektors = ievaddati; if(sector.lod != 0)( sektor.borderTessFactor = GetNeibTessFactor(sector, 0); //Bottom sektorsF.bordersFNeii (sektors, 1); //Kreisais sektors (c * dataSize + 0, asuint(sektors.startPos.x)); .startPos.z)); outputData.Store(c * dataSize + 12, asuint(sector.vec1.x)); (c * dataSize + 20, asuint(sektors.vec1.z)); .vec2.y)); outputData.Store(c * dataSize + 32, asuint(sector.vec2.z));

outputData.Store(c * dataSize + 40, asuint(sector.borderTessFactor));
outputData.Store(c * dataSize + 44, asuint(sector.borderTessFactor)); outputData.Store(c * dataSize + 48, asuint(sector.borderTessFactor)); Informāciju par metodēm DrawIndexedInstancedIndirect() un CopyStructureCount() skatiet 2. pielikumā.

12. Kamera

Jūs noteikti zināt, kā izveidot vienkāršas FPS (First Person Shooter) kameras modeli. Es sekoju šādam scenārijam:
  • 1. No diviem leņķiem es iegūstu virziena vektoru
  • 2. izmantojot virziena vektoru un vektoru (0, 1, 0) es iegūstu pamatu
  • 3. Atbilstoši virziena vektoram un vaktoram pa labi, kas iegūti 2. darbībā, es mainu kameras pozīciju

Mūsu gadījumā situācija ir nedaudz sarežģītāka - pirmkārt, jāpārvietojas attiecībā pret planētas centru, otrkārt, konstruējot bāzi, vektora (0, 1, 0) vietā jāizmanto normāls sfēra tajā punktā, kur mēs šobrīd atrodamies. Lai sasniegtu vēlamos rezultātus, izmantošu divas bāzes. Saskaņā ar pirmo, pozīcija mainīsies, otrā aprakstīs kameras orientāciju. Bāzes ir savstarpēji atkarīgas, bet es vispirms aprēķināju pozīcijas bāzi, tāpēc sākšu ar to. Pieņemsim, ka mums ir sākotnējās pozīcijas bāze (pDir, pUp, pRight) un virziena vektors vDir, pa kuru mēs vēlamies pārvietoties kādu attālumu. Pirmkārt, mums jāaprēķina vDir projekcijas uz pDir un pRight. Saskaitot tos, iegūstam atjauninātu virziena vektoru (21. att.).


21. att. ProjDir iegūšanas vizuālais process

Kur P ir kameras pozīcija, mF un mS ir koeficienti, kas nozīmē, cik daudz mums ir jāvirzās uz priekšu vai uz sāniem.

Mēs nevaram izmantot PN kā jaunu kameras pozīciju, jo PN nepieder pie sfēras. Tā vietā mēs atrodam sfēras normālu punktā PN, un šī norma būs augšupejošā vektora jaunā vērtība. Tagad mēs varam izveidot atjauninātu bāzi

Vector3 nUp = Vector3::Normalize(PN - spherePos); Vector3 nDir = projDir Vector3 nRight = Vector3::Normalize(Vector3::Cross(pUp, pDir))
kur sfēraPos ir sfēras centrs.

Mums ir jāpārliecinās, ka katrs no tā vektoriem ir ortogonāls pārējiem diviem. Saskaņā ar krustojuma produktu īpašību nRight atbilst šim nosacījumam. Atliek to pašu sasniegt nUp un nDir. Lai to izdarītu, projicējiet nDir uz nUp un atņemiet iegūto vektoru no nDir (22. att.)


22. att. nDir ortogonalizācija attiecībā pret nUp

Mēs varētu darīt to pašu ar nUp, bet tad tas mainītu virzienu, kas mūsu gadījumā ir nepieņemami. Tagad mēs normalizējam nDir un iegūstam atjauninātu ortonormālo virzienu bāzi.

Otrais galvenais posms ir orientācijas bāzes izveide. Galvenā grūtība ir virziena vektora iegūšana. vispiemērotākais risinājums ir punktu ar polāro leņķi a, azimuta leņķi b un attālumu no sākuma, kas vienāds ar vienu, pārvērst no sfēriskām koordinātām uz Dekarta. Tikai tad, ja mēs veiksim šādu pāreju punktam ar polāro leņķi, kas vienāds ar nulli, mēs iegūsim vektoru, kas skatās uz augšu. Tas mums nav pilnībā piemērots, jo mēs palielināsim leņķus un pieņemsim, ka šāds vektors skatīsies uz priekšu. Vienkārši pārbīdot leņķi par 90 grādiem, problēma tiks atrisināta, taču elegantāk ir izmantot leņķa maiņas noteikumu, kas nosaka, ka

Darīsim tā. Rezultātā mēs iegūstam sekojošo

Kur a ir polārais leņķis, b ir azimuta leņķis.

Šis rezultāts mums nav pilnībā piemērots - mums ir jākonstruē virziena vektors attiecībā pret pozīcijas bāzi. Pārrakstīsim vDir vienādojumu:

Viss ir kā astronautiem – tik daudz šajā virzienā, tik daudz tajā virzienā. Tagad vajadzētu būt acīmredzamam, ka, aizstājot standarta bāzes vektorus ar pDir, pUp un pRight, mēs iegūsim vajadzīgo virzienu. Tāpat kā šis

Jūs varat attēlot vienu un to pašu matricas reizināšanas formā

Vektors vUp sākotnēji būs vienāds ar pUp. Aprēķinot vUp un vDir krustojumu, mēs iegūstam vRight

Tagad mēs pārliecināsimies, ka vUp ir ortogonāls pārējiem bāzes vektoriem. Princips ir tāds pats kā strādājot ar nDir

Esam sakārtojuši pamatus – atliek vien aprēķināt kameras pozīciju. Tas ir darīts šādi

Ja spherePos ir sfēras centrs, sphereRadius ir sfēras rādiuss un augstums ir augstums virs sfēras virsmas. Šeit ir aprakstītās kameras darbības kods:

Float MoveFactor = 0.0f, sideFactor = 0.0f, heightFactor = 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 , [&]() (augstuma koeficients = -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 = poz + (newDir * moveFactor + pRight * sideFactor) * Tf * pUp = Vector3::Normalize(pDir = Vector3::Normalize(pDir - pUp * Vector3::Dot,); pDir)); poz = sfēraPoss + pUp * (sfēraRādiuss != 0,0 f)( augstums = Math::Saturate(augstums + augstumsFactor * Tf * ātrums, augstumsRangs); poz = spherePos + pUp * (); sphereRadius + augstums ) DirectInput::MouseState mState = DirectInput::GetInsance()->GetMouseDelta( ); if(mState.x != 0 || mState.y != 0 || MoveFactor != 0.0f || sideFactor != 0.0f)( if(mState.x != 0) leņķi.x = leņķi.x + mState .x / 80.0f; if(mState.y != 0) leņķi.y = Math::Saturate(leņķi.y + mState.y / 80.0f, diapazonsF(-Pi * 0,499f, Pi * 0,499f)); vDir = Vector3::Normalize(pRight * sinf(leņķi.x) * cosf(leņķi.y) + pUp * -sinf(leņķi.y) + pDir * cosf(leņķi.x) * cosf(leņķi.y)); vUp = pUp = Vector3::Normalize(Vector3::Cross(vUp, vDir)); (((vRight, 0.0f), (vUp, 0.0f), (vDir, 0.0f), (poz, 1.0f)));
Ņemiet vērā, ka mēs atiestatām angles.x pēc pozīcijas bāzes atjaunināšanas. Tas ir ļoti svarīgi. Iedomāsimies, ka mēs vienlaikus mainām skata leņķi un pārvietojamies pa sfēru. Vispirms mēs projicēsim virziena vektoru uz pDir un pRight, iegūsim nobīdi (newPos) un atjaunināsim pozīcijas bāzi, pamatojoties uz to. Otrais nosacījums arī darbosies, un mēs sāksim atjaunināt orientācijas bāzi. Bet tā kā pDir un pRight jau ir mainīti atkarībā no vDir, tad bez azimuta leņķa atiestatīšanas (leņķi.x) rotācija būs “stīvāka”.

Secinājums

Es pateicos lasītājam par jūsu interesi par rakstu. Ceru, ka tajā ietvertā informācija viņam bija pieejama, interesanta un noderīga. Ierosinājumus un komentārus var sūtīt man pa pastu. [aizsargāts ar e-pastu] vai atstājiet kā komentārus.

Es novēlu jums panākumus!

1. pielikums

laukā InstanceDataStepRate ir informācija par to, cik reižu zīmēt D3D11_INPUT_PER_VERTEX_DATA datus vienam D3D11_INPUT_PER_INSTANCE_DATA elementam. Mūsu piemērā viss ir vienkāršs - viens pret vienu. "Bet kāpēc mums vairākas reizes jāzīmē viena un tā pati lieta?" - tu jautā. Saprātīgs jautājums. Armēnijas radio atbild – pieņemsim, ka mums ir 99 trīs dažādu krāsu bumbiņas. Virsotni varam aprakstīt šādi:

UINT krāsasRate = 99 / 3; std::vektors meta = (("POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0), ("NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT), 0, 12 ORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0), ("WORLD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1), ("WORLD", 1_BBLOG_21, DX23 6, D3D11_INPUT_PER_INSTANCE_DATA, 1), ("WORLD", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 32, D3D11_INPUT_PER_INSTANCE_DATA, 1), ("WORLD", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 48, D3_D11C,_1 XGI_FORMAT_R32G32B32A32_FLOAT, 2, 0, D3D11_INPUT_PER_INSTANCE_DATA, coloursRate), );
Lūdzu, ņemiet vērā, ka virsotne tiek savākta no trim avotiem, un pēdējā dati tiek atjaunināti reizi 33 “gadījumos”. Rezultātā mēs iegūsim 33 pirmās krāsas gadījumus, vēl 33 otrās krāsas utt. Tagad izveidosim buferus. Turklāt, tā kā krāsas nemainīsies, mēs varam izveidot buferi ar krāsām ar karogu D3D11_USAGE_IMMUTABLE. Tas nozīmē, ka pēc bufera inicializācijas tikai GPU būs tikai lasīšanas piekļuve saviem datiem. Šeit ir kods buferu izveidei:

MatricesTb = Utils::DirectX::CreateBuffer(izmērs(Matrix4x4) * 99, D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE); colorTb = Utils::DirectX::CreateBuffer(krāsas, D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_IMMUTABLE, 0);
tad, ja nepieciešams, atjauninām buferi ar matricām (es izmantoju savas bibliotēkas funkcijas - ceru, ka viss būs skaidrs)

Utils::DirectX::Map (matricesTb, [&](Matrix4x4 *Data) ( //vispirms buferī ierakstām datus par pirmās krāsas bumbiņām //pēc tam par otro utt. Lūdzu, ņemiet vērā, ka ir jābūt //datu atbilstībai krāsām nodrošināts bufera datu ģenerēšanas stadijā ));
Piekļuvi datiem ēnotā var ieviest tādā pašā veidā, kā es aprakstīju iepriekš


2. pielikums

Atšķirībā no DrawIndexedInstanced(), izsaucot DrawIndexedInstancedIndirect(), kā arguments tiek izmantots buferis, kas satur visu informāciju, ko izmantojat, lai izsauktu DrawIndexedInstanced(). Turklāt šis buferis ir jāizveido ar karogu D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS. Šeit ir bufera izveides piemērs:

//indicesCnt - indeksu skaits, ko vēlamies parādīt //instancesCnt - "instances" skaits, ko vēlamies parādīt std::vector args = ( indexesCnt, //IndexCountPerInstance instancesCnt, //InstanceCount 0, //StartIndexLocation 0,//BaseVertexLocation 0//StartInstanceLocation ); D3D11_BUFFER_DESC bd = (); bd.Usage = 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; ID3D11Buferis* buferis; D3D11_APAKŠRESURSA_DATI initData = (); initData.pSysMem = HR(DeviceKeeper::GetDevice()->CreateBuffer(&bd, &initData, &buffer)); piemērs DrawIndexedInstancedIndirect(): DeviceKeeper::GetDeviceContext()->DrawIndexedInstancedIndirect(indirectArgs, 0);
kā otro argumentu mēs nododam nobīdi baitos no bufera sākuma, no kura mums jāsāk nolasīt datus. Kā to var izmantot? Piemēram, ieviešot neredzamo ģeometrijas izgriezumu GPU. Kopumā hronoloģija ir šāda – vispirms Compute shader aizpildām AppendStructuredBuffer, kurā ir redzami ģeometrijas dati. Pēc tam, izmantojot CopyStructureCount(), mēs iestatām gadījumu skaitu, ko vēlamies parādīt, līdz ierakstu skaitam šajā buferī un izsaucam DrawIndexedInstancedIndirect()


3. pielikums

Pieņemsim, ka x koordinātas vērtība ir vienāda ar funkcijas X rezultātu ar argumentu a, un z koordinātas vērtība ir funkcijas Z rezultāts ar tādu pašu argumentu:

Tagad mums ir jāaprēķina katras funkcijas atvasinājums. Būtībā funkcijas atvasinājums dotais punkts vienāds ar funkciju vērtību izmaiņu ātrumu tieši šajā punktā. Saskaņā ar trigonometrisko funkciju diferencēšanas noteikumiem:

Kas galu galā dod mums tādu pašu rezultātu:

Kāpēc mēs varam izmantot ātruma vērtības kā virziena vektora sastāvdaļas? Tā es to saprotu. Iedomājieties, ka mums ir vektora funkcija (ja t >= 0):

Aprēķināsim atvasinājumu X koordinātei

Tagad par Y

Mēs esam ieguvuši, ka ātruma vektors ir vienāds ar (2, 3), tagad atradīsim sākumpunktu

Rezultātā funkciju P(t) varam izteikt šādi:

Ko vienkāršos vārdos var raksturot kā "punkts pārvietojas no sākuma ar koordinātām (3, 2) uz t virzienā (2, 3)." Tagad ņemsim citu piemēru:

Vēlreiz aprēķināsim atvasinājumu X koordinātei

Un par Y koordinātu

Tagad ātruma vektors mainās atkarībā no argumenta. Šajā gadījumā vienkāršos vārdos situāciju var raksturot šādi: "Punkts pārvietojas no sākuma ar koordinātām (3, 2), un tā kustības virziens pastāvīgi mainās."


4. pielikums

Definēsim funkciju F(H), kas ņems augstumu apgabalā un atgriezīs vērtību no 0 līdz 1, kur F(Hmin) = 0 un F(Hmax) = 1. Vienādojumu sistēmas risināšana

es saņēmos

Rezultātā funkcija F iegūst formu

Tagad mums ir nepieciešama funkcija, kas ņem augstuma koeficientu no 0 līdz 1 un atgriež minimālo punktu produkta vērtību. Jāņem vērā, ka jo tuvāk novērotājs atrodas virsmai, jo lielāka tā ir. Tā rezultātā tiek iegūts šāds vienādojums:

Atvērsim iekavas

Vienkāršosim un iegūsim

Tagad mēs izsakām D(F(H)) un iegūstam

Tagi:

  • Directx11
  • reljefa renderēšana
Pievienojiet atzīmes

Zemes evolūcijas laikā izmaiņas sauszemes ainavu izskatā bija reakcija uz dabas apstākļu pārveidi. Visa ģeogrāfiskā aploksnes daudzveidība, kas pazīstama kā ģeosistēmas, ainavas vai dabiskie kompleksi, atspoguļo dažādu temperatūras un mitruma izpausmju rezultātus, kas savukārt ir pakļauti radiācijas līdzsvaram.

Šīs dažāda ranga dinamiskās sistēmas, ko raksturo integritāte, to veidojošo elementu īpaša mijiedarbība un darbība, produktivitāte un izskats, kopā veido ģeogrāfisko apvalku un attiecas uz to kā veseluma daļas. Tiem ir savs dabas (dabas resursu) potenciāls, kura mērījumi ļauj sarindot ģeosistēmas un pētīt to izmaiņas. Šo struktūru vienojošais princips ir vielu un enerģijas plūsmu apmaiņa, to daļēja uzkrāšana un patēriņš. Tādējādi enerģijas un masas apmaiņa ģeogrāfiskā apvalka ietvaros kalpo par pamatu tās diferenciācijai, un tās izmaiņas atspoguļojas zemes virsmas izskatā. Šis process nodrošina Zemes moderno ģeogrāfisko zonējumu un zonalitāti un specifisku ainavu dažādību ar dažādas organizācijas pakāpes.

Tomēr ģeogrāfiskā apvalka evolūcijas laikā izmaiņas tās zemes sistēmās bija saistītas arī ar dziļiem procesiem un parādībām, kas daļēji izpaudās virspusē (vulkānisma zonas, seismiskums, kalnu apbūve utt.). Tajā pašā laikā, līdz ar tiešām izmaiņām ainavu litogēnajā bāzē un ģeogrāfiskajā apvalkā kopumā, pēdējā saņēma papildu vielu un enerģiju, kas atspoguļojās tā atsevišķo komponentu un sistēmas darbībā kopumā. Šī “komplementaritāte” (reizēm, iespējams, nozīmīga) izpaudās ne tikai kvantitatīvi, globālajā matērijas un enerģijas apritē, bet arī atsevišķu komponentu kvalitatīvās izmaiņās. Zemes degazācijas procesu loma un to enerģijas-masu apmaiņa ar atmosfēru un hidrosfēru vēl nav pietiekami pētīta. Tikai no 20. gadsimta vidus. parādījās informācija par mantijas materiāla materiālo sastāvu un tās kvantitatīvajām īpašībām.

V. I. Bgatova pētījumi atklāja, ka atmosfēras skābeklis ir ne tik daudz fotosintētiskas, cik dziļas izcelsmes. Vispārpieņemtā oglekļa cikla shēma dabā ir jākoriģē, piegādājot tās savienojumus no zemes zarnām, jo ​​īpaši vulkānu izvirdumu laikā. Acīmredzot ne mazāks daudzums vielas nonāk ūdens čaulā zemūdens izvirdumu laikā, īpaši izplatīšanās zonās, vulkānisko salu lokos un atsevišķos karstajos punktos. Kopējais ikgadējais oglekļa savienojumu daudzums, kas no zemes dzīlēm nonāk okeānā un atmosfērā, ir salīdzināms ar ikgadējo karbonātu veidošanās masu ūdenstilpēs un, acīmredzot, pārsniedz sauszemes augu organiskā oglekļa uzkrāšanās apjomu.

Dabiskajai klimata sasilšanai un tās antropogēnajai pastiprināšanai vajadzētu izraisīt ģeogrāfisko zonu un jostu robežu maiņu un veicināt atsevišķu ainavu modifikāciju.

Taču cilvēku sabiedrības attīstība un tās vajadzību un spēju paplašināšanās noved pie dažāda mēroga dabas kompleksu mākslīgas pārstrukturēšanas un kultūrainavu veidošanās, kas ietekmē ģeogrāfiskās aploksnes funkcionēšanu, izjaucot dabisko gaitu. Starp šīm sekām visredzamākās ir šādas:

Ņemiet vērā, ka piesārņojošo vielu ikgadējo emisiju summēšana nav teorētiski un praktiski pilnībā pamatota, jo, nonākot ģeogrāfiskajā vidē, tās tiek asimilētas, transformējas viena otras ietekmē un funkcionē atšķirīgi. Ir svarīgi analizēt katru galveno antropogēno izdalīšanos, ņemot vērā tās reakcijas ar esošajiem savienojumiem.

Ģeogrāfiskā apvalka vai tā daļu enerģijas izmaiņas izraisa ģeosistēmas iekšējās struktūras un darbības procesu un ar to saistīto parādību pārstrukturēšanu. Šis process ir sarežģīts, un to regulē vairāki tiešie un atgriezeniskās saites savienojumi (9.4. att.). Antropogēnā ietekme uz ģeogrāfisko vidi izraisa izmaiņas vides sastāvā un stāvoklī, izjauc dzīvās vielas kvantitatīvo un kvalitatīvo sastāvu (līdz mutācijām), pārveido esošās enerģijas, masas un mitruma apmaiņas sistēmas. Tomēr pašlaik pieejamie pierādījumi liecina, ka antropogēnās izmaiņas nav būtiski atspoguļotas ģeogrāfiskajā aploksnē. Tā pastāvēšanas relatīvo līdzsvaru un attīstības ilgtspēju galvenokārt nodrošina dabiski cēloņi, kuru mērogs pārsniedz cilvēka ietekmi. No tā neizriet, ka pati ģeogrāfiskā aploksne vienmēr pārvarēs pieaugošo antropogēno spiedienu. Iejaukšanās dabā jāregulē no to izpausmju lietderības viedokļa - cilvēces labā un bez būtiska kaitējuma dabiskajai videi. Koncepcijas, kas tiek izstrādātas šajā virzienā, sauc par ilgtspējīgu (līdzsvarotu) attīstību. Tiem jābūt balstītiem uz vispārīgiem ģeoloģiskiem modeļiem un pašreizējā stāvokļa un ģeogrāfiskās aploksnes attīstības iezīmēm.

Nobeigumā pievērsīsimies jaunajam apgalvojumam, ka mūsdienu ģeogrāfiskais apvalks kļūst antroposfēra, vai daļa no topošajiem noosfēra.Ņemiet vērā, ka jēdziens “noosfēra” lielākoties ir filozofisks. Cilvēka ietekme uz vidi un atkritumproduktu iesaistīšanās tajā ir nenoliedzama parādība. Ir svarīgi saprast, ka visbiežāk cilvēks maina savu dzīvotni nevis apzināti, bet gan ar neparedzamām sekām. Turklāt šīs realizācijas nav vērstas uz visām ģeogrāfiskās aploksnes sastāvdaļām, bet tikai uz cilvēkiem nepieciešamajām sastāvdaļām (mežs, augsne, izejvielas utt.). Līdz ar to ir tikai pārmaiņu kabatas, kaut arī dažkārt ļoti nozīmīgas un nopietnas, un, lai gan cilvēka aktivitāte palielinās, daba joprojām attīstās galvenokārt dabas procesu ietekmē. Līdz ar to šobrīd būtu jārunā par atsevišķām ģeogrāfiskās aploksnes zonām, kur dabiskā vide ir būtiski mainījusies un attīstās cilvēka regulētu procesu ietekmē.

Rīsi. 9.4. Dažas atsauksmes par globālo klimatu

Zemes evolūcijas laikā zemes ainavu izskata izmaiņas bija reakcija uz dabas apstākļu pārveidi. Visa ģeogrāfiskā aploksnes daudzveidība, kas pazīstama kā ģeosistēmas, ainavas vai dabiskie kompleksi, atspoguļo dažādu temperatūras un mitruma izpausmju rezultātus, kas savukārt ir pakļauti radiācijas līdzsvaram.

Šīs dažāda ranga dinamiskās sistēmas, ko raksturo integritāte, to veidojošo elementu īpaša mijiedarbība un darbība, produktivitāte un izskats, kopā veido ģeogrāfisko apvalku un attiecas uz to kā veseluma daļas. Tiem ir savs dabas (dabas resursu) potenciāls, kura mērījumi ļauj sarindot ģeosistēmas un pētīt to izmaiņas. Šo struktūru vienojošais princips ir vielu un enerģijas plūsmu apmaiņa, to daļēja uzkrāšana un patēriņš. Tādējādi enerģijas un masas apmaiņa ģeogrāfiskā apvalka ietvaros kalpo par pamatu tās diferenciācijai, un tās izmaiņas atspoguļojas zemes virsmas izskatā. Šis process nodrošina Zemes moderno ģeogrāfisko zonējumu un zonalitāti un specifisku ainavu dažādību ar dažādas organizācijas pakāpes.

Tomēr ģeogrāfiskā apvalka evolūcijas laikā izmaiņas tās zemes sistēmās bija saistītas arī ar dziļiem procesiem un parādībām, kas daļēji izpaudās virspusē (vulkānisma zonas, seismiskums, kalnu apbūve utt.). Tajā pašā laikā, līdz ar tiešām izmaiņām ainavu litogēnajā bāzē un ģeogrāfiskajā apvalkā kopumā, pēdējā saņēma papildu vielu un enerģiju, kas atspoguļojās tā atsevišķo komponentu un sistēmas darbībā kopumā. Šī “komplementaritāte” (reizēm, iespējams, nozīmīga) izpaudās ne tikai kvantitatīvi, globālajā matērijas un enerģijas apritē, bet arī atsevišķu komponentu kvalitatīvās izmaiņās. Zemes degazācijas procesu loma un to enerģijas-masu apmaiņa ar atmosfēru un hidrosfēru vēl nav pietiekami pētīta. Tikai no 20. gadsimta vidus. parādījās informācija par mantijas materiāla materiālo sastāvu un tās kvantitatīvajām īpašībām.

V. I. Bgatova pētījumi atklāja, ka atmosfēras skābeklis ir ne tik daudz fotosintētiskas, cik dziļas izcelsmes. Vispārpieņemtā oglekļa cikla shēma dabā ir jākoriģē, piegādājot tās savienojumus no zemes zarnām, jo ​​īpaši vulkānu izvirdumu laikā. Acīmredzot ne mazāks daudzums vielas nonāk ūdens čaulā zemūdens izvirdumu laikā, īpaši izplatīšanās zonās, vulkānisko salu lokos un atsevišķos karstajos punktos. Kopējais ikgadējais oglekļa savienojumu daudzums, kas no zemes dzīlēm nonāk okeānā un atmosfērā, ir salīdzināms ar ikgadējo karbonātu veidošanās masu ūdenstilpēs un, acīmredzot, pārsniedz sauszemes augu organiskā oglekļa uzkrāšanās apjomu.

Dabiskajai klimata sasilšanai un tās antropogēnajai pastiprināšanai vajadzētu izraisīt ģeogrāfisko zonu un jostu robežu maiņu un veicināt atsevišķu ainavu modifikāciju.

Taču cilvēku sabiedrības attīstība un tās vajadzību un spēju paplašināšanās noved pie dažāda mēroga dabas kompleksu mākslīgas pārstrukturēšanas un kultūrainavu veidošanās, kas ietekmē ģeogrāfiskās aploksnes funkcionēšanu, izjaucot dabisko gaitu. Starp šīm sekām visredzamākās ir šādas:

1) Rezervuāru un apūdeņošanas sistēmu izveide maina virsmas albedo, siltuma un mitruma apmaiņas režīmu, kas, savukārt, ietekmē gaisa temperatūru un mākoņainību.

2) Zemes pārvēršana par lauksaimniecības zemi vai veģetācijas iznīcināšana (masveida mežu izciršana) maina albedo un termiskos apstākļus, izjauc vielu ciklu, jo samazinās fotosintēzes aktīvās virsmas. Mēroga ziņā nozīmīgākā ietekme bija neapstrādātu zemju un papuvju masveida attīstībai, kad tika uzarti un apsēti daudzi miljoni hektāru zaļo ganību un papuvju. Zemes virsmas absorbcijas spējas palielināšanās, tās nelīdzenuma un augsnes un veģetācijas seguma nepārtrauktības pārtraukšana mainīja radiācijas līdzsvaru, izraisīja gaisa masu cirkulācijas transformāciju un palielināja vēju, kas izraisīja putekļu vētras un samazināšanos. atmosfēras caurspīdīgumā. Pārveidojumu rezultāts bija stabilu produktīvu ainavu pārnešana uz nestabilām, pastiprinoties pārtuksnešošanās procesiem un riskam zemes izmantošanā.

3) Virszemes noteces pārdale (plūsmas regulēšana, aizsprostu un ūdenskrātuvju izveide) visbiežāk noved pie apkārtējo teritoriju pārpurvošanās. Tajā pašā laikā mainās pamatvirsmas albedo, palielinās mitrums, miglas biežums, mākoņainība un gaisa caurlaidība, kas izjauc dabisko siltuma un masas pārnesi starp zemes virsmu un atmosfēru. Ūdens plūsmas aizsprostošanās un purvainu telpu veidošanās maina augu pakaišu sadalīšanās raksturu, kas izraisa papildu siltumnīcefekta gāzu (oglekļa dioksīda, metāna u.c.) daudzuma iekļūšanu atmosfērā, mainot tās sastāvu un caurspīdīgumu.

4) Hidroenerģijas būvju izveidošana uz upēm, aizsprostojumi, veidojot visu gadu krītoša ūdens kaskādēm, maina upju gada režīmu, izjauc ledus situāciju, transportējamo nogulumu sadalījumu un pārveido upes-atmosfēras sistēmu. Neaizsalstošās ūdenskrātuves ar pastāvīgu miglu un iztvaikošanu no ūdens virsmas (arī ziemā) ietekmē temperatūras gaitu, ūdens masu cirkulāciju, laika apstākļu pasliktināšanos un dzīvo organismu dzīvesvietas maiņu. Hidroelektrostacijas ietekme uz lielas upes(Jeņiseja, Angara, Kolima, Volga u.c.) ir jūtama desmitiem kilometru lejup pa straumi un uz visām aizsprostotajām rezervuāru daļām, un vispārējās klimata izmaiņas aptver simtiem kvadrātkilometru. Lēnā upju nogulumu pieplūde un to pārdale izraisa ģeomorfoloģisko procesu traucējumus un upju grīvu un krastu iznīcināšanu. ūdens baseini(piemēram, Nīlas deltas un Vidusjūras piekrastes dienvidaustrumu daļas iznīcināšana pēc Asuānas dambja uzbūvēšanas un tā pārtveršanas ievērojamai daļai cieto nogulumu, ko nes upe).

5) Meliorācijas darbi, ko pavada lielu telpu nosusināšana, izjauc esošo siltuma, mitruma un apmaiņas režīmu un veicina negatīvu atgriezeniskās saites cilpu veidošanos ainavu transformācijas laikā. Tādējādi purvaino sistēmu pāržūšana vairākos reģionos (Polesje, Novgorodas apgabals, Irtišas apgabals) izraisīja dabiskā veģetācijas seguma bojāeju un deflācijas procesu rašanos, kas pat pietiekama mitruma apgabalos veidoja mainīgas smiltis. Rezultātā palielinājās atmosfēras putekļainība, pieauga virsmas raupjums, mainījās vēja režīms.

6) Zemes virsmas nelīdzenuma palielināšanās dažādu būvju (ēkas, raktuvju un izgāztuvju, rūpniecisko noliktavu uc) būvniecības laikā izraisa vēja apstākļu, putekļu līmeņa un laika un klimatisko īpašību izmaiņas.

7) Dažādi piesārņotāji, kas milzīgos daudzumos nonāk visā dabiskajā vidē, izmaina, pirmkārt, gaisa, ūdens, virsmas veidojumu uc materiāla sastāvu un enerģētiskās kapacitātes. Šīs izmaiņas dabas aģentos nosaka to veikto dabisko procesu transformāciju, kā arī dažādas mijiedarbības ar apkārtējo vidi un citiem dabas faktoriem.

Ņemiet vērā, ka piesārņojošo vielu ikgadējo emisiju summēšana nav teorētiski un praktiski pilnībā pamatota, jo, nonākot ģeogrāfiskajā vidē, tās tiek asimilētas, transformējas viena otras ietekmē un funkcionē atšķirīgi. Ir svarīgi analizēt katru galveno antropogēno izdalīšanos, ņemot vērā tās reakcijas ar esošajiem savienojumiem.

Ģeogrāfiskā apvalka vai tā daļu enerģijas izmaiņas izraisa ģeosistēmas iekšējās struktūras un darbības procesu un ar to saistīto parādību pārstrukturēšanu. Šis process ir sarežģīts, un to regulē vairāki tiešie un atgriezeniskās saites savienojumi (9.4. att.). Antropogēnā ietekme uz ģeogrāfisko vidi izraisa izmaiņas vides sastāvā un stāvoklī, izjauc dzīvās vielas kvantitatīvo un kvalitatīvo sastāvu (līdz mutācijām), pārveido esošās enerģijas, masas un mitruma apmaiņas sistēmas. Tomēr pašlaik pieejamie pierādījumi liecina, ka antropogēnās izmaiņas nav būtiski atspoguļotas ģeogrāfiskajā aploksnē. Tā pastāvēšanas relatīvo līdzsvaru un attīstības ilgtspēju galvenokārt nodrošina dabiski cēloņi, kuru mērogs pārsniedz cilvēka ietekmi. No tā neizriet, ka pati ģeogrāfiskā aploksne vienmēr pārvarēs pieaugošo antropogēno spiedienu. Iejaukšanās dabā jāregulē no to izpausmju lietderības viedokļa - cilvēces labā un bez būtiska kaitējuma dabiskajai videi. Koncepcijas, kas tiek izstrādātas šajā virzienā, sauc par ilgtspējīgu (līdzsvarotu) attīstību. Tiem jābūt balstītiem uz vispārīgiem ģeoloģiskiem modeļiem un pašreizējā stāvokļa un ģeogrāfiskās aploksnes attīstības iezīmēm.

Nobeigumā pievērsīsimies jaunajam apgalvojumam, ka mūsdienu ģeogrāfiskais apvalks kļūst antroposfēra, vai daļa no topošajiem noosfēra.Ņemiet vērā, ka jēdziens “noosfēra” lielākoties ir filozofisks. Cilvēka ietekme uz vidi un atkritumproduktu iesaistīšanās tajā ir nenoliedzama parādība. Ir svarīgi saprast, ka visbiežāk cilvēks maina savu dzīvotni nevis apzināti, bet gan ar neparedzamām sekām. Turklāt šīs realizācijas nav vērstas uz visām ģeogrāfiskās aploksnes sastāvdaļām, bet tikai uz cilvēkiem nepieciešamajām sastāvdaļām (mežs, augsne, izejvielas utt.). Līdz ar to ir tikai pārmaiņu kabatas, kaut arī dažkārt ļoti nozīmīgas un nopietnas, un, lai gan cilvēka aktivitāte palielinās, daba joprojām attīstās galvenokārt dabas procesu ietekmē. Līdz ar to šobrīd būtu jārunā par atsevišķām ģeogrāfiskās aploksnes zonām, kur dabiskā vide ir būtiski mainījusies un attīstās cilvēka regulētu procesu ietekmē.

Rīsi. 9.4. Dažas atsauksmes par globālo klimatu

Drošības jautājumi

Kādas parādības tiek klasificētas kā globālas izmaiņas ģeogrāfiskajā aploksnē?

Kāda ir globālo pārmaiņu specifika 20. gadsimta beigās un 21. gadsimta sākumā?

Kas ir siltumnīcas efekts un kādas ir tā sekas?

Kas ir izplatīta problēmaģeogrāfiskā apvalka antropogenizācija?

Kāda ir klimata sasilšanas problēma?

Kādas ir naftas piesārņojuma briesmas?

Kas ir globālā vides krīze, kā un kur tā izpaužas?

Ko nozīmē optimistiski un pesimistiski uzskati par planētas Zeme attīstību?

Kāda ietekme polārais ledus ir ietekme uz ģeogrāfisko aploksni?

Kādas ir sauszemes ainavas izmaiņas?

LITERATŪRA

Alpatijevs A. M. Dabiskās vides attīstība, transformācija un aizsardzība. - L., 1983. gads.

Balandins R.K., Bondarevs L.G. Daba un civilizācija. - M., 1988. gads.

Bioloģiskā indikācija antropoekoloģijā. - L., 1984. gads.

Bitkaeva L.Kh., Nikolajevs V.A. Terek Sands ainavas un antropogēnā pārtuksnešošanās. - M., 2001. gads.

Bokovs V.A., Luščiks A.V. Vides drošības pamati. - Simferopole, 1998. gads.

Vernadskis V.I. Biosfēra un noosfēra. - M., 1989. gads.

20. gadsimta beigu ģeogrāfiskās problēmas / Rep. ed. Ju P. Seļiverstovs. - Sanktpēterburga, 1998. gads.

Ģeogrāfija un vide / Atbildīgs. ed. N. S. Kasimovs, S. M. Malhazova. - M., 2000. gads.

Globālās izmaiņas dabiskajā vidē (klimats un ūdens režīms) / Rep. ed. N.S. Kasimovs. - M., 2000. gads.

Globālās un reģionālās klimata pārmaiņas un to dabiskās un sociāli ekonomiskās sekas / Atbildīgs. ed. V.M. Kotļakovs. - M., 2000. gads.

Globāli vides problēmas uz 21. gadsimta sliekšņa / Rep. ed. F.T. Janšina. - M., 1998. gads.

Govoruško S.M. Dabas procesu ietekme uz cilvēka darbību. - Vladivostoka, 1999. gads.

Golubevs G.N.Ģeoekoloģija. - M., 1999. gads.

Gorškovs V.G. Dzīves ilgtspējības fiziskie un bioloģiskie pamati. - M., 1995. gads.

Gorškovs SP.Ģeoekoloģijas konceptuālie pamati. - Smoļenska, 1998.

Grigorjevs A.A. Ekoloģiskās mācības pagātnē un tagadnē. - L., 1991. gads.

Grigorjevs A. A., Kondratjevs K. Ja. Ekodinamika un ģeopolitika. - T. 11. Vides katastrofas. - Sanktpēterburga, 2001. gads.

Gumiļovs L.N. Zemes etnoģenēze un biosfēra. - L., 1990. gads.

Daņilovs A.D., Korols I.L. Atmosfēras ozons - sajūtas un realitāte. - L., 1991. gads.

Doto L. Planēta Zeme ir apdraudēta. - M., 1988. gads.

Zaļetajevs V.S. Ekoloģiski destabilizēta vide. Sauso zonu ekosistēmas mainīgā hidroloģiskajā režīmā. - M., 1989. gads.

Zeme un cilvēce. Globālās problēmas/ Valstis un tautas. - M., 1985. gads.

Zubakovs V.A. Ecogea - Māja Zeme. Īsumā par nākotni. Ekogeju koncepcijas kontūras par izeju no globālās vides krīzes. - Sanktpēterburga, 1999. gads.

Zubakovs V.A. Māja Zeme. Ekoģeozofiskā pasaules skatījuma kontūras. (Uzturēšanas stratēģijas zinātniskā izstrāde). - Sanktpēterburga, 2000. gads.

Isačenko A.G. Dabiskās vides optimizācija. - M., 1980. gads.

Isačenko A.G. Krievijas ekoloģiskā ģeogrāfija. - Sanktpēterburga, 2001. gads.

Kondratjevs K. Ja. Globālais klimats. - M., 1992. gads.

Kotļakovs V. M. Zinātne. Sabiedrība. Vide. - M., 1997. gads.

Kotļakovs V.M., Grosvalds M.G., Lorijs K. Pagātnes klimats no ledus lokšņu dzīlēm. - M., 1991. gads.

Lavrovs S.B., Sdasjuks G.V.Šī kontrastējošā pasaule. - M., 1985. gads.

Vide / Red. A. M. Rjabčikova. - M., 1983. gads.

Ģeoekoloģijas pamati / Red. V. G. Moračevskis. - Sanktpēterburga, 1994. gads.

Petrovs K. M. Izpostīto zemju atjaunošanas dabiskie procesi. - Sanktpēterburga, 1996. gads.

Ekoloģijas problēmas Krievijā / Atbildīgais. ed. V. I. Daņilovs-Daņiljans, V. M. Kotļakovs. - M., 1993. gads.

Krievija apkārtējā pasaulē: 1998. Analītiskais krājums / Red. ed. N.N. Moisejeva, S.A. Stepanova. - M., 1998. gads.

Rouns S. Ozona krīze. Negaidītu globālu draudu piecpadsmit gadu evolūcija. - M., 1993. gads.

Krievijas Ģeogrāfijas biedrība: jaunas idejas un ceļi / Rep. ed. A.O.Brinkens, S.B.Lavrovs, Seļiverstovs. - Sanktpēterburga, 1995. gads.

Seliverstovs J. P. Globālā vides riska problēma // Krievijas Ģeogrāfijas biedrības ziņas. - 1994. - Izdevums. 2.

Seliverstovs J. P. Dabas antropogenizācija un vides krīzes problēma // Vestnik St. Petersburg. Universitāte. - 1995. - Ser. 7. - jautājums. 2.

Seliverstovs J. P. Planētu vides krīze: cēloņi un realitāte // Vestnik St. Petersburg. Universitāte. - 1995. - Ser. 7. - jautājums. 4.

Forteskjū Dž. Vides ģeoķīmija. - M., 1985. gads.

Ekoloģiskā alternatīva / Red. ed. M.Ja. Lemeševa. - M., 1990. gads.

Vides prasības ilgtspējīgai attīstībai Krievijā / Red. V.T.Puļajeva.-L., 1996.g.

Vides problēmas: kas notiek, kas ir vainīgs un ko darīt? / Red. V.I.Daņilovs-Daņiljans. - M., 1997. gads.

Janšins A.L., Melua A.I. Mācības no vides krīzēm. - M., 1991. gads.

Zemes virsma nepaliek nemainīga. Mūsu planētas pastāvēšanas miljonu gadu laikā tās izskatu pastāvīgi ietekmējuši dažādi dabas spēki. Izmaiņas, kas notiek uz Zemes virsmas, izraisa gan iekšējie spēki, gan tas, kas notiek atmosfērā.

Tādējādi zemes garozas kustības rezultātā izveidojās kalni. Akmeņu masas tika izspiestas virspusē, sasmalcinātas un salauztas, kā rezultātā veidojās dažāda veida kalni. Laikam ejot, lietus un sals drupināja kalnus, veidojot atsevišķas klintis un ielejas.

Daži kalni veidojās vulkānu izvirdumu rezultātā. Izkusušais iezis kārtu pēc slāņa burbuļoja uz Zemes virsmas caur garozas caurumiem, līdz beidzot parādījās kalns. Vezuvs Itālijā ir vulkāniskas izcelsmes kalns.

Vulkāniskie kalni var veidoties arī zem ūdens. Piemēram, Havaju salas ir vulkānisko kalnu virsotnes.

Saule, vējš un ūdens izraisa pastāvīgu akmeņu iznīcināšanu. Šo procesu sauc par eroziju. Bet tas var ietekmēt ne tikai akmeņus. Tādējādi ledus, vēja un ūdens izraisītā erozija izskalo zemes augsni.

Vietās, kur tie ieslīd jūrā, ledāji sagriež līdzenumus, veidojot ielejas un fjordus – šaurus un līkumotus jūras līčus.

laikā veidojās fiordi ledus laikmets kad kontinentus klāja bieza ledus un sniega kārta.

Šis ledus savukārt izraisīja ledāju veidošanos, kas ir lēnas ledus upes.

Slīdot no kalniem ielejās, savu ceļu lauza ledāji, kuru ledus biezums dažkārt sasniedza vairākus desmitus metru. Viņu kustības spēks bija ļoti liels.

Sākumā pa ledāju taku veidojās šauras aizas, tad ledāja zvērīgais spēks tās palielināja, paverot ceļu lejup. Pamazām šī telpa kļuva dziļāka un plašāka.

Pēc ledus laikmeta beigām ledus un sniegs sāka kust. Ledumam kūstot, pieauga upju platums. Tajā pašā laikā jūras līmenis cēlās. Tādējādi upju vietā izveidojās fiordi.

Fjordu krasti parasti ir akmeņainas nogāzes, dažkārt sasniedzot 1000 metru (3000 pēdu) augstumu.

Daži fiordi ir tik dziļi, ka kuģi var pārvietoties pa tiem.

Liels skaits fiordu atrodas Somijas un Grenlandes krastos. Bet visskaistākie fjordi ir Norvēģijā. Garākais fiords ir arī Norvēģijā. To sauc par Sognefjordu. Tā garums ir 180 kilometri (113 jūdzes).

Kad ledus kūst, morēnas — klinšu fragmentu sakrājumi — paliek aiz muguras un veido zigzaga formas kalnu virsotnes. Upes iegremdē gravas irdenās klintīs un dažviet milzīgus kanjonus (dziļas upju ielejas ar stāvām pakāpju nogāzēm), piemēram, Lielais kanjons Arizonā (ASV). Tā garums ir 349 kilometri.

Lietus un vēji ir īsti tēlnieki un izgrebj īstas skulpturālas grupas un dažādas figūras. Austrālijā ir tā sauktās Vēja klintis, un netālu no Krasnojarskas atrodas akmens stabi. Abi veidojušies vēja erozijas rezultātā.

Zemes virsmas erozija nebūt nav nekaitīgs process. Ik gadu, pateicoties tai, pazūd daudzi desmiti hektāru aramzemes. Upēs tiek ienests liels daudzums auglīgas augsnes, kuras veidošanās dabiskos apstākļos ilgst simtiem gadu. Tāpēc cilvēki visos iespējamos veidos cenšas cīnīties ar eroziju.

Šīs cīņas galvenais virziens ir augsnes erozijas novēršana. Ja uz augsnes nav veģetācijas seguma, tad vējš un ūdens viegli aiznes auglīgo slāni un zeme kļūst neauglīga. Tāpēc vietās ar intensīvu vēju tiek izmantotas saudzējošas zemes apstrādes metodes, piemēram, aršana bez plātnēm.

Turklāt notiek cīņa ar gravām. Šim nolūkam upju krasti tiek apstādīti ar dažādiem augiem un nostiprinātas nogāzes. Jūras un upju piekrastē, kur notiek spēcīga krasta erozija, tiek izveidota speciāla grants izgāztuve un ierīkoti aizsargdambji, lai novērstu smilšu pārvietošanos.