WebGL'dagi Klasterli Forward Rendering texnikasini o'rganing. Bu real vaqtda yuzlab dinamik yorug'lik manbalarini render qilish uchun kuchli usul. Asosiy tushunchalar va optimallashtirish strategiyalarini bilib oling.
Ishlash samaradorligini ochish: WebGL Klasterli Forward Rendering va yorug'likni indekslash optimizatsiyasiga chuqur sho'ng'ish
Vebdagi real vaqtda ishlaydigan 3D grafika dunyosida ko'plab dinamik yorug'lik manbalarini render qilish har doim ishlash samaradorligi uchun jiddiy muammo bo'lib kelgan. Dasturchi sifatida biz boyroq va yanada qamrab oluvchi sahnalarni yaratishga intilamiz, ammo har bir qo'shimcha yorug'lik manbai hisoblash xarajatlarini eksponensial ravishda oshirib, WebGL imkoniyatlarini chegarasiga olib kelishi mumkin. An'anaviy renderlash usullari ko'pincha qiyin tanlovga majbur qiladi: ishlash samaradorligi uchun vizual sifatni qurbon qilish yoki pastroq kadrlar tezligini qabul qilish. Ammo ikkala dunyoning eng yaxshi tomonlariga ega bo'lishning bir yo'li bo'lsa-chi?
Bu yerda Klasterli Forward Rendering (Clustered Forward Rendering), shuningdek, Forward+ nomi bilan ham tanilgan texnika yordamga keladi. Bu kuchli usul an'anaviy forward renderingning soddaligi va material moslashuvchanligini deferred shadingning yorug'lik samaradorligi bilan birlashtirgan murakkab yechimni taklif etadi. U bizga interaktiv kadr tezligini saqlab qolgan holda yuzlab, hatto minglab dinamik yorug'lik manbalari bo'lgan sahnalarni render qilish imkonini beradi.
Ushbu maqola WebGL kontekstida Klasterli Forward Rendering texnikasini keng qamrovli o'rganishni taqdim etadi. Biz ko'rish frustumini qismlarga bo'lishdan tortib yorug'likni kesishgacha bo'lgan asosiy tushunchalarni tahlil qilamiz va eng muhim optimallashtirishga - yorug'likni indekslash ma'lumotlar quvuriga (light indexing data pipeline) katta e'tibor qaratamiz. Bu CPU'dan GPU'ning fragment sheyderiga qaysi yorug'liklar ekranning qaysi qismlariga ta'sir qilishini samarali tarzda uzatuvchi mexanizmdir.
Rendering maydoni: Forward va Deferred
Klasterli rendering nima uchun bunchalik samarali ekanligini tushunish uchun avvalo undan oldingi usullarning cheklovlarini tushunishimiz kerak.
An'anaviy Forward Rendering
Bu eng sodda renderlash yondashuvidir. Har bir obyekt uchun vertex sheyder uning uchlarini qayta ishlaydi va fragment sheyder har bir piksel uchun yakuniy rangni hisoblaydi. Yorug'likka kelganda, fragment sheyder odatda sahnadagi har bir yorug'lik manbasini aylanib chiqadi va uning hissasini jamlaydi. Asosiy muammo uning yomon masshtablanishidir. Hisoblash qiymati taxminan (Fragmentlar soni) x (Yorug'lik manbalari soni) ga proportsionaldir. Atigi bir necha o'nlab yorug'lik manbalari bilan ishlash samaradorligi keskin tushib ketishi mumkin, chunki har bir piksel keraksiz ravishda har bir yorug'likni, hatto milya uzoqlikdagi yoki devor orqasidagi yorug'liklarni ham tekshiradi.
Deferred Shading
Deferred Shading aynan shu muammoni hal qilish uchun ishlab chiqilgan. U ikki bosqichli jarayonda geometriyani yorug'likdan ajratadi:
- Geometriya bosqichi: Sahnaning geometriyasi G-buffer deb nomlanuvchi bir nechta to'liq ekranli teksturalarga render qilinadi. Bu teksturalar har bir piksel uchun pozitsiya, normal va material xususiyatlari (masalan, albedo, g'adir-budurlik) kabi ma'lumotlarni saqlaydi.
- Yorug'lik bosqichi: To'liq ekranli to'rtburchak chiziladi. Har bir piksel uchun fragment sheyder sirt xususiyatlarini tiklash uchun G-bufferdan namuna oladi va keyin yorug'likni hisoblaydi. Asosiy afzalligi shundaki, yorug'lik har bir piksel uchun faqat bir marta hisoblanadi va uning dunyo pozitsiyasiga qarab qaysi yorug'liklar ta'sir qilishini aniqlash oson.
Ko'p yorug'lik manbalari bo'lgan sahnalar uchun yuqori samarali bo'lsa-da, deferred shadingning o'ziga yarasha kamchiliklari bor, ayniqsa WebGL uchun. U G-buffer tufayli yuqori xotira o'tkazuvchanligini talab qiladi, shaffoflik bilan ishlashda qiyinchiliklarga duch keladi (bu alohida forward rendering bosqichini talab qiladi) va MSAA kabi anti-aliasing texnikalaridan foydalanishni murakkablashtiradi.
O'rta yo'l uchun dalil: Forward+
Klasterli Forward Rendering nafis murosani taqdim etadi. U forward renderingning bir bosqichli tabiati va material moslashuvchanligini saqlab qoladi, lekin har bir fragment uchun yorug'lik hisob-kitoblari sonini keskin kamaytirish uchun oldindan qayta ishlash bosqichini o'z ichiga oladi. U og'ir G-bufferni chetlab o'tadi, bu esa uni xotira uchun qulayroq va shaffoflik hamda MSAA bilan to'g'ridan-to'g'ri mos keladigan qiladi.
Klasterli Forward Renderingning asosiy tushunchalari
Klasterli renderingning markaziy g'oyasi qaysi yorug'liklarni tekshirishimiz haqida aqlliroq bo'lishdir. Har bir piksel har bir yorug'likni tekshirish o'rniga, biz ekranning bir qismiga ta'sir qilishi mumkin bo'lgan yorug'liklarni oldindan aniqlab olishimiz va o'sha hududdagi piksellarning faqat o'sha yorug'liklarni tekshirishini ta'minlashimiz mumkin.
Bunga kameraning ko'rish frustumini klasterlar (yoki plitkalar) deb ataladigan kichikroq hajmlardan iborat 3D to'rga bo'lish orqali erishiladi.
Umumiy jarayonni to'rtta asosiy bosqichga bo'lish mumkin:
- 1. Klaster to'rini yaratish: Ko'rish frustumini qismlarga ajratuvchi 3D to'rni aniqlash va qurish. Bu to'r ko'rish fazosida qat'iy belgilangan va kamera bilan birga harakatlanadi.
- 2. Yorug'likni tayinlash (Kesish): To'rdagi har bir klaster uchun uning ta'sir hajmi bilan kesishadigan barcha yorug'lik manbalari ro'yxatini aniqlash. Bu muhim kesish (culling) bosqichidir.
- 3. Yorug'likni indekslash: Bu bizning diqqat markazimizdagi qism. Biz yorug'likni tayinlash bosqichi natijalarini GPU'ga samarali yuborilishi va fragment sheyder tomonidan o'qilishi mumkin bo'lgan ixcham ma'lumotlar tuzilmasiga joylashtiramiz.
- 4. Soyalash (Shading): Asosiy renderlash bosqichida fragment sheyder avval qaysi klasterga tegishli ekanligini aniqlaydi. Keyin u yorug'likni indekslash ma'lumotlaridan foydalanib, o'sha klaster uchun tegishli yorug'liklar ro'yxatini oladi va yorug'lik hisob-kitoblarini *faqat* o'sha kichik yorug'liklar to'plami uchun bajaradi.
Chuqur sho'ng'ish: Klaster to'rini qurish
Texnikaning asosi yaxshi tuzilgan to'rdir. Bu yerda qilingan tanlovlar ham kesish samaradorligiga, ham ishlash unumdorligiga bevosita ta'sir qiladi.
To'r o'lchamlarini aniqlash
To'r o'zining X, Y va Z o'qlari bo'yicha o'lchamlari (masalan, 16x9x24 klasterlar) bilan aniqlanadi. O'lchamlarni tanlash murosa hisoblanadi:
- Yuqori o'lcham (Ko'proq klasterlar): Aniqroq va puxtaroq yorug'likni kesishga olib keladi. Har bir klasterga kamroq yorug'lik tayinlanadi, bu fragment sheyder uchun kamroq ish degani. Biroq, bu CPU'dagi yorug'likni tayinlash bosqichining qo'shimcha yukini va klaster ma'lumotlar tuzilmalarining xotira hajmini oshiradi.
- Past o'lcham (Kamroq klasterlar): CPU tomonidagi va xotiradagi qo'shimcha yukni kamaytiradi, ammo qo'polroq kesishga olib keladi. Har bir klaster kattaroq bo'ladi, shuning uchun u ko'proq yorug'lik manbalari bilan kesishadi, bu esa fragment sheyderda ko'proq ishga olib keladi.
Keng tarqalgan amaliyot X va Y o'lchamlarini ekran tomonlari nisbatiga bog'lashdir, masalan, ekranni 16x9 plitkaga bo'lish. Z o'lchami ko'pincha sozlash uchun eng muhim hisoblanadi.
Logarifmik Z-bo'laklash: Muhim optimizatsiya
Agar biz frustum chuqurligini (Z o'qi) chiziqli bo'laklarga bo'lsak, perspektiv proyeksiyaga oid muammoga duch kelamiz. Katta miqdordagi geometrik tafsilotlar kameraga yaqin joyda jamlangan, uzoqdagi obyektlar esa juda kam pikselni egallaydi. Chiziqli Z-bo'linish kameraga yaqin joyda (aniqlik eng zarur bo'lgan joyda) katta va noaniq klasterlarni va uzoqda esa kichik, isrofgarchilikka olib keladigan klasterlarni yaratadi.
Yechim logarifmik (yoki eksponensial) Z-bo'laklashdir. Bu kameraga yaqin joyda kichikroq, aniqroq klasterlarni va uzoqlashgan sari progressiv ravishda kattaroq klasterlarni yaratadi, klaster taqsimotini perspektiv proyeksiyaning ishlash tarziga moslashtiradi. Bu har bir klaster uchun bir xil miqdordagi fragmentlarni ta'minlaydi va ancha samaraliroq kesishga olib keladi.
Yaqin tekislik `n` va uzoq tekislik `f` berilgan holda, jami `N` bo'lakdan i-chi bo'lak uchun `z` chuqurligini hisoblash formulasini quyidagicha ifodalash mumkin:
z_i = n * (f/n)^(i/N)Ushbu formula ketma-ket bo'laklar chuqurliklari nisbatining doimiy bo'lishini ta'minlaydi va kerakli eksponensial taqsimotni yaratadi.
Masalaning yuragi: Yorug'likni kesish va indekslash
Sehr aynan shu yerda sodir bo'ladi. Bizning to'rimiz aniqlangandan so'ng, qaysi yorug'liklar qaysi klasterlarga ta'sir qilishini aniqlab, so'ngra bu ma'lumotni GPU uchun paketlashimiz kerak. WebGL'da bu yorug'likni kesish mantig'i odatda CPU'da JavaScript yordamida yorug'lik yoki kamera harakatlanadigan har bir kadr uchun bajariladi.
Yorug'lik-klaster kesishish testlari
Jarayon konseptual jihatdan sodda: har bir yorug'likni aylanib chiqib, uni har bir klaster chegaralovchi hajmi bilan kesishishini sinab ko'rish. Klaster uchun chegaralovchi hajmning o'zi ham bir frustumdir. Umumiy testlarga quyidagilar kiradi:
- Nuqtali yorug'liklar: Sferalar sifatida qaraladi. Test sfera-frustum kesishishi hisoblanadi.
- Proyektor yorug'liklari: Konuslar sifatida qaraladi. Test konus-frustum kesishishi bo'lib, bu ancha murakkab.
- Yo'naltirilgan yorug'liklar: Bular ko'pincha hamma narsaga ta'sir qiladi deb hisoblanadi, shuning uchun ular odatda alohida ko'rib chiqiladi va kesish jarayoniga kiritilmaydi.
Ushbu testlarni samarali bajarish muhimdir. Ushbu bosqichdan so'ng, bizda, ehtimol JavaScript massivlar massivida quyidagicha xaritalash bo'ladi: clusterLights[clusterId] = [lightId1, lightId2, ...].
Ma'lumotlar tuzilmasi muammosi: CPU'dan GPU'ga
Ushbu har bir klaster uchun yorug'lik ro'yxatini fragment sheyderiga qanday o'tkazamiz? Biz shunchaki o'zgaruvchan uzunlikdagi massivni o'tkaza olmaymiz. Sheyder bu ma'lumotlarni izlash uchun oldindan aytib bo'ladigan usulga muhtoj. Aynan shu yerda Global yorug'liklar ro'yxati va Yorug'lik indekslari ro'yxati yondashuvi yordamga keladi. Bu bizning murakkab ma'lumotlar tuzilmamizni GPU uchun qulay teksturalarga tekislashning nafis usuli.
Biz ikkita asosiy ma'lumotlar tuzilmasini yaratamiz:
- Klaster ma'lumotlari to'ri teksturasi: Bu 3D tekstura (yoki 3D ni taqlid qiluvchi 2D tekstura) bo'lib, unda har bir teksel bizning to'rimizdagi bitta klasterga to'g'ri keladi. Har bir teksel ikkita muhim ma'lumotni saqlaydi:
- Siljish (offset): Bu bizning ikkinchi ma'lumotlar tuzilmamizda (Global yorug'liklar ro'yxati) ushbu klaster uchun yorug'liklar boshlanadigan boshlang'ich indeks.
- Sanoq (count): Bu ushbu klasterga ta'sir qiladigan yorug'liklar soni.
- Global yorug'liklar ro'yxati teksturasi: Bu barcha klasterlar uchun barcha yorug'lik indekslarining birlashtirilgan ketma-ketligini o'z ichiga olgan oddiy 1D ro'yxat (2D teksturada saqlanadi).
Ma'lumotlar oqimini vizualizatsiya qilish
Keling, oddiy stsenariyni tasavvur qilaylik:
- 0-klasterga [5, 12] indeksli yorug'liklar ta'sir qiladi.
- 1-klasterga [8, 5, 20] indeksli yorug'liklar ta'sir qiladi.
- 2-klasterga [7] indeksli yorug'lik ta'sir qiladi.
Global yorug'liklar ro'yxati: [5, 12, 8, 5, 20, 7, ...]
Klaster ma'lumotlari to'ri:
- 0-klaster uchun teksel:
{ offset: 0, count: 2 } - 1-klaster uchun teksel:
{ offset: 2, count: 3 } - 2-klaster uchun teksel:
{ offset: 5, count: 1 }
WebGL va GLSL'da amalga oshirish
Endi tushunchalarni kod bilan bog'laymiz. Amalga oshirish kesish va ma'lumotlarni tayyorlash uchun JavaScript qismini va soyalash uchun GLSL qismini o'z ichiga oladi.
Ma'lumotlarni GPU'ga uzatish (JavaScript)
CPU'da yorug'likni kesishni amalga oshirgandan so'ng, sizda klaster to'ri ma'lumotlari (siljish/sanoq juftliklari) va global yorug'liklar ro'yxati bo'ladi. Ularni har bir kadrda GPU'ga yuklash kerak.
- Klaster ma'lumotlarini paketlash va yuklash: Klaster ma'lumotlaringiz uchun `Float32Array` yoki `Uint32Array` yarating. Har bir klaster uchun siljish va sanoqni teksturaning RG kanallariga joylashtirishingiz mumkin. Ushbu ma'lumotlar bilan tekstura yaratish uchun `gl.texImage2D` yoki yangilash uchun `gl.texSubImage2D` dan foydalaning. Bu sizning Klaster ma'lumotlari to'ri teksturangiz bo'ladi.
- Global yorug'liklar ro'yxatini yuklash: Xuddi shunday, yorug'lik indekslaringizni `Uint32Array` ga tekislang va uni boshqa teksturaga yuklang.
- Yorug'lik xususiyatlarini yuklash: Barcha yorug'lik ma'lumotlari (pozitsiya, rang, intensivlik, radius va hk) sheyderdan tez, indekslangan qidiruvlar uchun katta teksturada yoki Uniform Buffer Object (UBO) da saqlanishi kerak.
Fragment Sheyder mantig'i (GLSL)
Fragment sheyder - bu ishlash samaradorligining o'sishiga erishiladigan joy. Mana qadamma-qadam mantiq:
1-qadam: Fragmentning klaster indeksini aniqlash
Avvalo, joriy fragment qaysi klasterga tushishini bilishimiz kerak. Bu uning ko'rish fazosidagi pozitsiyasini talab qiladi.
// To'r haqida ma'lumot beruvchi uniformlar
uniform vec3 u_gridDimensions; // masalan, vec3(16.0, 9.0, 24.0)
uniform vec2 u_screenDimensions;
uniform float u_nearPlane;
uniform float u_farPlane;
// Ko'rish fazosi chuqurligidan Z-bo'lak indeksini olish funksiyasi
float getClusterZIndex(float viewZ) {
// viewZ manfiy, uni musbatga aylantiring
viewZ = -viewZ;
// CPU'da ishlatgan logarifmik formulamizning teskarisi
float slice = floor(log(viewZ / u_nearPlane) / log(u_farPlane / u_nearPlane) * u_gridDimensions.z);
return slice;
}
// 3D klaster indeksini olish uchun asosiy mantiq
vec3 getClusterIndex() {
// Ekran koordinatalaridan X va Y indeksini olish
float clusterX = floor(gl_FragCoord.x / u_screenDimensions.x * u_gridDimensions.x);
float clusterY = floor(gl_FragCoord.y / u_screenDimensions.y * u_gridDimensions.y);
// Fragmentning ko'rish fazosidagi Z pozitsiyasidan (v_viewPos.z) Z indeksini olish
float clusterZ = getClusterZIndex(v_viewPos.z);
return vec3(clusterX, clusterY, clusterZ);
}
2-qadam: Klaster ma'lumotlarini olish
Klaster indeksidan foydalanib, biz Klaster ma'lumotlari to'ri teksturasidan ushbu fragmentning yorug'lik ro'yxati uchun siljish va sanoqni olish uchun namuna olamiz.
uniform sampler2D u_clusterTexture; // Siljish va sanoqni saqlovchi tekstura
// ... main() ichida ...
vec3 clusterIndex = getClusterIndex();
// Agar kerak bo'lsa, 3D indeksni 2D tekstura koordinatasiga tekislang
vec2 clusterTexCoord = ...;
vec2 lightData = texture2D(u_clusterTexture, clusterTexCoord).rg;
int offset = int(lightData.x);
int count = int(lightData.y);
3-qadam: Tsiklda aylanish va yorug'likni jamlash
Bu oxirgi qadam. Biz qisqa, chegaralangan tsiklni bajaramiz. Har bir iteratsiyada biz Global yorug'liklar ro'yxatidan yorug'lik indeksini olamiz, so'ngra o'sha indeksdan foydalanib yorug'likning to'liq xususiyatlarini olamiz va uning hissasini hisoblaymiz.
uniform sampler2D u_globalLightIndexTexture;
uniform sampler2D u_lightPropertiesTexture; // UBO yaxshiroq bo'lardi
vec3 finalColor = vec3(0.0);
for (int i = 0; i < count; i++) {
// 1. Qayta ishlanadigan yorug'lik indeksini oling
int lightIndex = int(texture2D(u_globalLightIndexTexture, vec2(float(offset + i), 0.0)).r);
// 2. Ushbu indeks yordamida yorug'lik xususiyatlarini oling
Light currentLight = getLightProperties(lightIndex, u_lightPropertiesTexture);
// 3. Ushbu yorug'likning hissasini hisoblang
finalColor += calculateLight(currentLight, surfaceProperties, viewDir);
}
Va tamom! Yuzlab marta ishlaydigan tsikl o'rniga, endi bizda sahnaning o'sha qismidagi yorug'lik zichligiga qarab 5, 10 yoki 30 marta ishlashi mumkin bo'lgan tsikl mavjud, bu esa ishlash samaradorligini sezilarli darajada oshiradi.
Ilg'or optimizatsiyalar va kelajakdagi mulohazalar
- CPU va Hisoblash (Compute): WebGL'da bu texnikaning asosiy zaif nuqtasi shundaki, yorug'likni kesish CPU'da JavaScript'da sodir bo'ladi. Bu bir oqimli va har bir kadrda GPU bilan ma'lumotlar sinxronizatsiyasini talab qiladi. WebGPU ning paydo bo'lishi o'yin qoidalarini o'zgartiradi. Uning hisoblash sheyderlari butun klaster qurish va yorug'likni kesish jarayonini GPU'ga yuklash imkonini beradi, bu uni parallel va bir necha barobar tezroq qiladi.
- Xotirani boshqarish: Ma'lumotlar tuzilmalaringiz tomonidan ishlatiladigan xotiraga e'tiborli bo'ling. 16x9x24 to'r (3,456 klaster) va har bir klaster uchun maksimal, aytaylik, 64 yorug'lik uchun global yorug'liklar ro'yxati potentsial ravishda 221,184 indeksni saqlashi mumkin. To'ringizni sozlash va har bir klaster uchun yorug'liklarning realistik maksimal miqdorini belgilash juda muhimdir.
- To'rni sozlash: To'r o'lchamlari uchun yagona sehrli raqam yo'q. Optimal konfiguratsiya sizning sahna tarkibingiz, kamera harakati va maqsadli qurilmaga bog'liq. Eng yuqori ishlash samaradorligiga erishish uchun profiling qilish va turli xil to'r o'lchamlari bilan tajriba o'tkazish juda muhim.
Xulosa
Klasterli Forward Rendering shunchaki akademik qiziqishdan ko'ra ko'proq narsadir; bu real vaqtda veb-grafikadagi jiddiy muammoning amaliy va kuchli yechimidir. Ko'rish fazosini aqlli ravishda qismlarga bo'lish va yuqori darajada optimallashtirilgan yorug'likni kesish va indekslash bosqichini bajarish orqali u yorug'lik soni va fragment sheyder xarajatlari o'rtasidagi bevosita bog'liqlikni uzadi.
Garchi u CPU tomonida an'anaviy forward renderingga qaraganda ko'proq murakkablik keltirsa-da, ishlash samaradorligidagi yutuq juda katta bo'lib, to'g'ridan-to'g'ri brauzerda boyroq, dinamikroq va vizual jihatdan jozibali tajribalarni yaratishga imkon beradi. Uning muvaffaqiyatining o'zagi samarali yorug'likni indekslash quvurida - murakkab fazoviy muammoni GPU'dagi oddiy, chegaralangan tsiklga aylantiradigan ko'prikda yotadi.
Veb-platforma WebGPU kabi texnologiyalar bilan rivojlanib borar ekan, Klasterli Forward Rendering kabi usullar yanada qulayroq va samaraliroq bo'lib boradi, bu esa mahalliy (native) va veb-asosidagi 3D ilovalar o'rtasidagi chegaralarni yanada xiralashtiradi.