Video Oyunlarında Outline Çizme Yöntemleri
(ameye.dev)- Unity’de nesnelerin etrafına outline çizmek için kullanılan başlıca yöntemler rim efekti, vertex genişletme, blur buffer, Jump Flood algoritması ve edge detection’dır; kalite, performans ve kurulum maliyeti açısından birbirlerinden farklı ödünler sunarlar
- Rim efekti, normal ile bakış yönünün iç çarpımıyla Fresnel’i yaklaşıklar ve kenarın iç tarafını vurgular; ancak yuvarlak modeller ile keskin hatlı modellerde sonuçlar belirgin biçimde farklı olabilir
- Vertex genişletme, kopyalanmış mesh’i arkadan büyüterek dış kontur oluşturur; clip space’te işlendiğinde ekrandaki genişliği daha tutarlı tutmak mümkündür
- Blur buffer ve Jump Flood algoritması, silüeti bir buffer’a çizip sonra genişletme yaklaşımını kullanır; ilki yumuşak çizgilerde, ikincisi ise çok geniş outline’larda güçlüdür
- Edge detection, depth, normal ve renk buffer’larındaki süreksizlikleri tüm ekranda bularak çizgiler üretir; ancak istenmeyen çizgileri azaltmak için threshold ve modülasyonun ince ayarlanması gerekir
Outline rendering neden kullanılır?
- Outline’lar oyunlarda görsel stil oluşturmak veya nesne vurgulama/seçim gibi oynanış yardımcıları için kullanılır
- Sable, çizgi roman benzeri bir stil için outline kullanır
- The Last of Us, oyuncu gizlilik moduna girdiğinde düşmanları vurgulamak için outline kullanır
- Beş tekniğin her biri performans, görsel kalite ve manuel kurulum miktarı arasında farklı ödünler sunar
Rim efekti: Fresnel yaklaşımıyla iç kenarı vurgulama
- Rim efekti, Fresnel etkisini yaklaşıklar ve nesnenin rim/kenar kısmına çizgi render eder
- Hesaplama, normalize edilmiş normal vektörü
Nile normalize edilmiş bakış yönüVarasındaki iç çarpıma dayanır1.0 - saturate(dot(N, V))değeri, efekt şiddetini üretmek içinPkuvvetine yükseltilir- Fiziksel olarak doğru Fresnel’den çok, outline için kullanılan bir yaklaşıma daha yakındır
- Unity uygulamasında özel bir shader, outline’ın genişliğini, kuvvetini, yumuşaklığını ve rengini kontrol eder
- Örnek uygulama
smoothstep,lerp,_OutlineWidth,_OutlineSoftness,_OutlinePower,_OutlineColorkullanır
- Örnek uygulama
- Bu yöntem her zaman iç çizgi olarak görünür; nesnenin dış konturunda görünmez
- Yuvarlak ve pürüzsüz nesnelerde iyi çalışır, ancak küp veya karmaşık modellerde çizgi genişliği düzensiz olabilir ya da outline gibi görünmeyebilir
Vertex genişletme: kopya mesh’i büyüterek dış çizgi oluşturma
- Vertex genişletme, özgün nesnenin veya mesh’in kopyasını tekrar render eder; özgün nesnenin arkasında vertex’leri genişletip daha büyük bir form oluşturur ve bunu tek renkle render eder
-
Genişletme yönünü seçme
- Vertex’lerin hangi yöne taşındığı outline kalitesini büyük ölçüde etkiler
- Vertex konumu yönünde taşındığında mesh şişirilmiş gibi görünür
- Local space’te vertex konumu, nesnenin merkezinden vertex’e giden vektör gibi kullanılabilir
- Normalize edilmezse merkezden daha uzaktaki vertex’ler daha fazla hareket eder
normalize(positionOS) * widthkullanmak object space’te daha dengeli bir hareket sağlar- Normal vektörü yönünde taşımak, küre veya kapsül gibi pürüzsüz kenarlı nesnelerde iyi sonuç verir
- Küp gibi keskin kenarlı nesnelerde outline’da boşluklar oluşabilir
- Vertex color da genişletme yönü olarak kullanılabilir
- Bu, özel normal’ler oluşturup bunları mesh’in vertex color kanalında saklama yöntemidir
- Bir küpe küresel, yumuşak normal’ler vertex color olarak bake edilirse daha iyi outline elde edilebilir
- Dezavantajı, her mesh için özel normal oluşturmayı gerektiren manuel kurulumdur; bu süreç script ile otomatikleştirilebilir
-
Genişletme space’ini seçme
- Shader’ın vertex aşamasında vertex koordinatları object space’te başlar ve MVP matrisi üzerinden clip space’e dönüştürülür
- Akış object/model/local space → world space → camera/view space → clip space → screen space → viewport/window space şeklindedir
- Object space içinde genişletme yapılırsa MVP dönüşümü henüz uygulanmadığından outline bozulabilir
- Model matrisi uygulanırken ölçekleme sorunları oluşur
- Clip space’ten screen space’e geçerken perspective division nedeniyle perspektif küçülmesi oluşur
- 3D uzaydaki hareketin bir kısmı kamera yönüne olur ve ekrandaki outline genişliğine katkı sağlamaz
- Clip space içinde genişletme yapılırsa ekranda aynı genişliğe yakın, temiz outline’lar üretilebilir
- Vertex konumu ve normal clip space’e dönüştürüldükten sonra yalnızca
x,ykoordinatları değiştirilir - Ekranın yatay ve dikey boyutlarına bölerek en-boy oranı düzeltilir
- Clip space’teki
wile çarpılır; böylece sonraki perspective division outline genişliği üzerinde net bir etki yaratmaz - 1 genişlik biriminin ekrandaki 1 piksele karşılık gelmesi için
width * 2ile çarpılır - Clip space yöntemi de mesh normal’lerine bağımlıdır
- Özel normal kullanılmazsa keskin kenarlarda boşluklar oluşabilir
- Normal yanlış ayarlanıp ters yöne bakarsa outline vertex’i de ters yönde hareket eder ve boşluk oluşur
- İlgili açıklamayı creating an outline in clip space içinde daha ayrıntılı görebilirsiniz
-
Masking
- Kopya mesh’te yalnızca dışarı taşan outline bölümü görünmelidir
- Yaygın yöntem, kopya mesh’in front-facing geometry’sini cull edip outline’ı backface ile oluşturmaktır
- Depth test için less than or equal to kullanılır; böylece backface yalnızca outline konumunda görünür
- Diğer yöntem, kopya mesh’in özgün nesnenin önünde görünmesini engellemek için stencil mask kullanmaktır
- Bu durumda culling gerekmez
- Nesne içinde hiç çizgi oluşmaz
- İki nesne üst üste gelirse outline yalnızca iki nesnenin çevresinde görünür
Blur buffer: silüeti blur ile genişletme
- Blur buffer yöntemi, nesne silüetini bir buffer’a render eder, ardından blur ile genişletir ve sonucu outline rendering için kullanır
-
Silüet buffer’ı
- İlk adım silüet buffer’ı oluşturmaktır
- Her nesne, tek renk çıktı veren bir shader ile texture’a render edilir
- Tüm silüetler beyaz render edilirse, sonunda istenen outline rengiyle çarpılarak tek renge dönüştürülebilir
- Nesne başına farklı renkte outline gerekiyorsa her silüet belirli bir renkle render edilebilir
-
Blur pass
- Blur pass, silüet buffer’ını genişletmek için kullanılır
- Genellikle box blur veya Gaussian blur kullanılır
- Performansı iyileştirmek için blur’dan önce silüet buffer’ı küçültülebilir
- Blur pass, her pikselin çevresindeki birçok pikselin ortalamasını veya ağırlıklı ortalamasını hesapladığı için maliyetlidir
- Blur’un 2 pass olarak uygulanması önerilir
- Ayrılabilir filtreler olan box blur ve Gaussian blur’da algoritma karmaşıklığı
O(N²)seviyesindenO(2N)seviyesine indirilebilir - Önce dikey yönde blur uygulanır, ardından sonuç yatay yönde tekrar blur’lanarak nihai sonuç üretilir
- Outline genişliği, blur shader’ındaki
_KernelSizeparametresiyle kontrol edilir
-
Outline pass ve masking
- Blur pass sonrasında bulanıklaştırılmış silüet özgün sahneyle compositing yapılarak outline oluşturulur
- Blur buffer, yumuşak veya parlayan outline için uygundur
- Blur sonucuna step işlemi uygulanırsa sert outline da render edilebilir
- Vertex genişletme yönteminde olduğu gibi, outline’ın yalnızca geometry arkasında render edilmesi için stencil mask kullanılabilir
- Diğer yöntemlere göre performans etkisi daha yüksek olabilir
Jump Flood algoritması: çok geniş outline’ları işleme
- Dördüncü yöntem, outline render etmek için Jump Flood algoritması kullanır
- Avantajı, çok geniş outline’ları makul bir performans maliyetiyle render edebilmesidir
- Ayrıntılı açıklama Ben Golus’un The Quest for Very Wide Outlines yazısına yönlendirir
Edge detection: tüm ekranda süreksizlikleri bulma
- Edge detection yöntemi, tam ekran pass’te sahnedeki süreksizlikleri bulup çizgiler çizer
- Süreksizlikler depth buffer değerlerinde, normal vektörlerinde, albedo renginde veya render sürecinde kullanılabilecek başka verilerde algılanabilir
-
Roberts cross
- Roberts cross operator, diyagonal piksel farklarının kareleri toplamını hesaplayan bir diferansiyel operatördür
- Gerçek uygulamada, edge’leri algılamak için özgün görüntüye kernel convolution uygulanır
xyönü veyyönü için 2 kernel kullanılır- Kernel boyutu
2 x 2’dir - Bir pikselin çevresinde yalnızca 4 sample gerekir
- Basit bir operatördür, ancak iyi sonuçlar verebilir
-
Sobel operator
- Sobel operator da
xyönü veyyönü için 2 kernel kullanır - Sobel kernel boyutu
3 x 3’tür ve bir pikselin çevresindeki 9 sample’ı kullanır - Sobel filtrelerinin çalışma biçimi blog post on Sobel filters içinde daha ayrıntılı görülebilir
- Sobel operator da
-
Süreksizlik kaynakları
- Yaygın yöntem, render pipeline’ın sahne için ürettiği depth texture, normal texture ve color texture içinde süreksizlik aramaktır
- Edge detection pass bu texture’ları sample eder ve yukarıda belirtilen operatörlerle süreksizlikleri algılar
- Bu şekilde üretilen edge’ler, üç buffer’dan birinde bulunan süreksizlik nedeniyle çizilebilir
- Bu yöntem, ilgili buffer’a yazan tüm nesnelere outline uyguladığı için nesne bazında kontrolü düşüktür
- Birden fazla süreksizlik kaynağına izin vermek daha sağlam bir outline sistemi oluşturabilir
- Bazı edge’ler üç kaynağın tamamında algılanır
- Birçok edge yalnızca belirli bir kaynağın katkısıyla algılanır
- Her kaynak için farklı ağırlıklar ve threshold’lar verilebilir; böylece outline’ın görsel sonucu kontrol edilebilir
-
Edge detection modülasyonu
- Süreksizlik buffer’ına yalnızca edge detection operatörü uygulamak, artifactsız sonuçlar elde etmeyi zorlaştırabilir
- Depth buffer birçok render pipeline’da doğrusal olmayan şekilde uygulanır
- Kameraya yakın iki nesne 1 m uzaktaysa aralarındaki depth farkı, uzaktaki iki nesne 1 m uzaktayken oluşan farktan daha büyüktür
- Bunu telafi etmek için depth süreksizliği algılama threshold’u depth buffer’ın kendisiyle modüle edilebilir
- Yakın geometry’nin edge olarak algılanmadan önce daha büyük bir depth değeri süreksizliğine ihtiyaç duyması gerekir
- Küçük grazing angle’larda istenmeyen edge’ler de oluşabilir
- Normal vektörü
Nile bakış yönüVarasındaki iç çarpımdan üretilen Fresnel maskesiyle modülasyon yapılabilir - Bu maske, rim efekti yönteminde kullanılanla aynı türdendir
- Başka modülasyon teknikleri de kullanılabilir, ancak seçim hedeflenen görsel etkiye göre değişir
-
Özel süreksizlik kaynakları
- Outline shader’ına özel süreksizlik kaynakları da sağlanabilir
- Bunlar render sürecinde doğrudan oluşturulan render texture’lardır ve outline üretiminde kullanılacak özel verileri taşır
- Hangi nesnelerin özel buffer’a yazılacağını doğrudan kontrol edebildiğiniz için hangi nesnelerin outline alacağını da kontrol edebilirsiniz
- Örnek olarak, mesh’in vertex color’ı texture’a render edilerek süreksizlik kaynağı oluşturulabilir
- Diğer yöntemler arasında yüzeyleri world position’a göre renklendirmek veya depth buffer ile normal buffer bilgilerini birleştiren özel bir buffer oluşturmak yer alır
- Ek bilgiye Linework section map üzerinden ulaşabilirsiniz
1 yorum
Hacker News yorumları
Bağlantısı verilen Jump Flood Algorithm yazısı gerçekten iyiydi: https://bgolus.medium.com/the-quest-for-very-wide-outlines-b...
Piksel/texel düzeyinde kullanılabilecek çeşitli yaklaşımları düşünmek ilginç; burada da signed distance field (SDF) pek çok işi üstlenen akıllı bir yöntem olarak kullanılıyor.
İstenen genişlikte dış çizgiyi doğrusal zamanda üretme sonucu, büyük genişliklerde kaba kuvvet hesaplamayla karşılaştırınca etkileyici.
İster vektör tabanlı olsun, ister Inigo Quilez’in çalışmaları gibi fonksiyon tabanlı, ister yazıdaki gibi texel/voxel tabanlı raster bir yöntem olsun, SDF’yi şiddetle öneririm.
Houdini de raster SDF’yi iyi destekliyor ve olgun bir SDF araç setine sahip; ücretsiz sürümüyle bile bakmaya değer.
GPU’da çalıştırmak için rastgele erişimli yazma, yani compute shader gerekmesi gibi bir dezavantajı var.
CPU ile sorun yoksa birkaç uygulama mevcut.
JavaScript: https://parmanoir.com/distance/
C: https://github.com/983/df
C++: https://github.com/opencv/opencv/blob/4.x/modules/imgproc/sr...
Python: https://github.com/pymatting/pymatting/blob/afd2dec073cb08b8...
Çünkü hem kalitesi iyi hem de titreşen dış çizgi gibi mesafe tabanlı efektleri render edebiliyor.
Bu 3D çizgi çizme aracı da SDF’yi küçük bir dokuya yazıp çalışma zamanında örnekliyor: https://x.com/alexanderameye/status/1663523972485357569
SDF gerçekten güçlü.
Aynı yöntemi radiance cascades’e kadar genişletirseniz daha da hızlanabilir; bu da oldukça ilginç.
Bir gün stilize 3D grafikleri bir Ar-Ge projesi olarak derinlemesine incelemek istiyorum
Son dönemde epey ilerleme oldu ama hâlâ kolayca toplanabilecek çok meyve var gibi görünüyor
Kamera uzaklaşırken toon rendering uygulanmış 3D modellerin ayrıntılarının nasıl azaltılacağını ve daha stilize bir görünümle daha az stilize bir görünüm arasında nasıl doğal geçiş yapılacağını merak ediyorum
Elle çizilmiş 2D animasyonlardaki suluboya arka planları 3D sahneler olarak ikna edici biçimde render etmek mümkün mü; ekran uzayında fırça izlerini ve kâğıt dokusunu nasıl yumuşakça anime etmek gerektiği de bir sorun
Duman, alev, ağaç, çimen, çamur, yağmur, tüy ve su gibi öğelerin stilize 3D oyunlarda nasıl temsil edilmesi gerektiği de açık bir konu
Elle çizilmiş animasyonda olduğu gibi, mevcut kamera açısından daha iyi görünmesi için modeli ince biçimde deforme etme işinin serbest kameralı oyunlarda otomatikleştirilip otomatikleştirilemeyeceğini de merak ediyorum
Stilize bir renderer’a uygun ideal mesh düzenleyicisi ve arka plan düzenleyicisi nasıl görünmeli; fiziksel olarak doğru 3D yüzeyler ve rig’ler gerçekten gerekli mi, yoksa daha soyut biçimde tanımlanabilir mi, bunlar da büyük sorular
Basit 3D modellerden retro piksel art render ederek prosedürel 2D oyunlar yapılabilir mi, bu da ilginç; iki mesh tesadüfen kesiştiğinde stilizasyon kullanarak bu kesişimi daha az göze batar hâle getirmek de mümkün olabilir
Sadece sorular bile on kişinin kariyerini doldurabilir ama bence bu tam tersine iyi bir şey
Kuwahara filtresi de çoğu kişi için yeterince inandırıcı
Stilize renderer’lar için düzenleme araçları konusunda Blender + Rigify + shape key’ler + biraz driver sihri benim ihtiyaçlarıma yetmişti
Blender’da texturing zahmetli ama hobi düzeyinde katlanılabilir; fotogerçekçi olmayan rendering üzerinde daha fazla kontrol gerekiyorsa DillonGoo Studio’nun fork’u daha iyi olabilir: https://www.dillongoostudios.com/gooengine
3D modellerden piksel art üretmeyi animasyonları/modelleri düşük çözünürlükte render ederek denemiştim; sonuç fena değildi ama deneme yanılma gerektirdi
Piksel titremesi gibi şeyleri gidermek için son işlemeyi daha sofistike yapan örnekler de olduğunu hatırlıyorum
Düşük çözünürlük: https://x.com/Navy_Green/status/1525564342975995904
Stabilizasyon: https://x.com/Navy_Green/status/1693820282245431540
Kariyerime VR uygulamalarıyla başladım, sonra pazar daha iyi olduğu için kısa süre içinde web geliştirmeye geçtim
Böyle yazıları görünce o alanı özlüyorum
3D grafikler, çarpışma ve shader’lar ile uğraşmanın başka alanlarda bulunması zor, sihirli bir tarafı vardı
Aslında bir dünya kurup fiziği yeniden üretmek gibi; matematik de diğer programlama alanlarına göre çok daha pratik ve çok daha sık karşına çıkıyor
İş tatmini çok daha yüksek, konuların derinliği ve tavanı da daha yüksek
Dördüncü yılımdayım ve web geliştirmede olduğu gibi hiç sıkılma duvarına çarpmadım
Pazartesi işe gelip engine editor’ı açmak, üzerinde çalıştığım küçük dünyanın render edildiğini izlerken sırada hangi harika özelliği ekleyeceğimi düşünmek gibisi yok
Buna karşılık teknik sanat alanında çıktıları emip sinir ağlarına yedirirlerse ilginç ve tatmin edici kısım ortadan kalkar; profesyonel işlerde geriye mevcut sıkıcı işler kalır ya da hatta daha da artar
Tüm beceri setinin değeri de düşer; teknoloji sektöründekiler bu işi bizzat yapanlardan daha iyi biliyormuş gibi davranır ve Silikon Vadisi şirketlerinin, veriyi aslında üreten insanlara ödeme yapmadan F fiyatına C- sonuçlar çıkarıp pazarı bozmasına sevinmediğiniz için sizi suçlar
Artılarını eksilerini tartınca web geliştirmeye geçme kararının doğru olduğunu düşünüyorum
Kendi oyunum Astral Divide’da yazıda olmayan bir teknik geliştirmiştim.
Blurred Buffer’a benziyor ama blur pass yapmıyor; antialiasing’in oluşturduğu sınır çizgilerini kullanıyor.
Saydam siyah bir arka plan üzerine nesneyi opak beyaz olarak çiziyor, fragment shader’da da alfa kanalı tamamen opak ya da tamamen saydam olmayan pikselleri sabit kodlanmış bir eşikle filtreliyorum.
Sonuç yeterince iyi, performans maliyeti düşük ve uygulaması da çok basit.
Eskiden video düzenleme yazılımında kırpma aracı geliştirirken, düzenleme sırasında kırpılan alanın dışını geçici olarak bulanıklaştırmam gerekiyordu.
Yine pahalı bir blur pass eklemek istemediğim için sadece mipmap bias’ını değiştirip düşük çözünürlüklü dokunun render edilmesini sağladım; texture filtering de işi bedavaya halletti.
PowerPoint’teki benzer kırpma bulanıklığı efektiyle karşılaştırınca neredeyse aynıydı; hatta PowerPoint’te hafif bir renk bantlaşması vardı, benim uygulamamda yoktu.
Benzer şekilde, tam çözünürlüklü antialiasing’in olmadığı bir durumda, tuvalin büyük kısmı döndürülebilen 2D dikdörtgenlerden oluştuğu için kenarlarda basamaklanma görünüyordu.
Tam ekran antialiasing’i açmak yerine tüm dikdörtgenleri biraz büyütüp UV koordinatlarını orantılı olarak küçülttüm; böylece görünen kenarlar gerçek 3D dikdörtgenin içine geldi ve yine texture filtering bunu bedavaya çözdü.
Yoksa alfayı koruyup yalnızca rengi ayarlayarak dış çizginin silik ama basamaklı olmamasını mı sağlıyorsun?
Notlar harika.
Yakın zamanda kenar algılama yaklaşımlarını araştırırken Mars First Logistics geliştiricisinin yaptığı iyi bir yönteme denk geldim: https://www.reddit.com/r/Unity3D/comments/taq2ou/improving_e...
Yazı da harika, sonuç da çok iyi görünüyor: https://ameye.dev/notes/rendering-outlines/edge-detection/co...
Hollanda çizgi romanı Franka’dan bir sahne gibi görünüyor.
Gerçekten çok iyi bir yazı ve okuma deneyimi de mükemmel.
Zor kavramları herkesin anlayabileceği dille açıklıyor; diyagramlar ve örnekler iyi, boşluklar ve tipografi sayesinde okunabilirlik de üst düzey.
Mevcut temayı yapmanıza neyin vesile olduğunu ve mühendis odaklı bir yayın platformu kurmayı hiç düşünüp düşünmediğinizi merak ediyorum.
Yazılımda technical art açık ara ilk aşkım.
Godot’ta post-processing efektleri için compute shader pipeline’ı daha kolay hale gelse keşke.
Mevcut compositor plugin yapısında epey boilerplate var.
Bu depo Godot post-processing için iyi bir örnek: https://github.com/sphynx-owner/JFA_driven_motion_blur_demo
Bu efekti ilk kez Dreamcast’teki Wacky Races’te gördüğümü hatırlıyorum.
O zamanlar bu efekti ilk kullanan oyunun o olduğu ve geliştiricilerin fiilen bunu icat ettiği yönünde çok tanıtım yapılıyordu.
Bunun gerçekten doğru mu yoksa pazarlama abartısı mı olduğunu bilmiyorum ama bir oyuncu olarak ilk deneyimim kesinlikle o zamandı.
Dreamcast, dürüst olmak gerekirse bu efekti yüksek kalitede gerçek zamanlı işleyebilen ilk donanımdı.
Dreamcast oyunu Looney Tunes: Space Race’in cel shading efektini geliştirdim; Dreamcast geliştirme kitini aldığımız ilk hafta hemen yapmıştım.
Wacky Racers’ı yapan Infogrames Sheffield, bizim uygulamanın erken bir sürümünü görüp kendi oyunlarına benzer bir efekt ekledi.
Güzel görünüyordu ama üretimin sonlarına doğru eklendiği için bizim oyundaki gibi o efekte göre optimize edilmemişti.
Jet Grind Radio ekibi de aynı efekti bağımsız olarak geliştirdi ve bizden önce piyasaya çıkardı.
Algoritma tam olarak aynıydı ama kullanım biçimi farklıydı: onlar düzensiz, geniş ve girintili çıkıntılı dış çizgileri bilinçli olarak benimsedi; Sheffield ve biz ise daha düzgün, geleneksel bir görsel stile uydurmak için bu özellikle mücadele ediyorduk.
Yaklaşık bir yıl sonra Xbox’taki Dragons Lair 3D’de birileri kenar algılamalı cel shading’i gerçek zamanlı çalıştırmanın yolunu bulmuş gibi görünüyor.
Bu yaklaşımı Dreamcast’te prototip olarak denemiştim ama oyunu çalıştırırken birden fazla karaktere aynı anda uygulamak için performans hiç yeterli değildi.
Xbox daha güçlü olduğu için mi, yoksa algoritma daha akıllı olduğu için mi bilmiyorum; ama sonuç inkâr edilemez.
Gerçek el çizimi bir çizgi film gibi görünen oyun yapmak istiyorsanız, kişisel olarak bunun hâlâ en yüksek kaliteli yöntem olduğunu düşünüyorum.
Bir gün tekrar uygulamak için bir bahane bulmak istiyorum; artık performansın sorun olmayacağını sanıyorum.
Epey büyük bir tesadüf.
Shader programlama ve 3D rendering’e mutlaka başlamam lazım.
Böyle yazılar gerçekten harika; pratikte shader yazabiliyor olmak isterdim.