Blending (Harmanlama)
OpenGL'de Blending, nesnelerde şeffaflık (transparency) uygulamak için kullanılan yaygın bir tekniktir. Şeffaflık, nesnelerin (veya bir kısmının) düz bir renge sahip olmayıp hem nesnenin kendi rengini hem de arkasındaki diğer nesnelerin renklerini farklı yoğunluklarda bir araya getirmesidir. Renkli bir cam pencere şeffaf bir nesnedir; camın kendine has bir rengi vardır, ancak oluşan renk camın arkasındaki tüm nesnelerin renklerini de içerir. Adı da buradan gelir; çeşitli piksel renklerini (farklı nesnelerden) tek bir renge harmanlıyoruz (blend). Şeffaflık böylece nesnelerin içinden bakmamızı sağlar.

Şeffaf nesneler tamamen şeffaf olabilir (tüm renkleri geçirir) ya da kısmen şeffaf (renkleri geçirir, aynı zamanda kendi renklerinden bir kısmını da gösterir). Bir nesnenin şeffaflık miktarı, renginin alpha değeri tarafından tanımlanır. Alpha renk değeri, bir renk vektörünün artık çok tanıdık olan 4. bileşenidir. Bu bölüme kadar her zaman bu 4. bileşeni 1.0 olarak tuttuk; bu da nesnenin 0.0 şeffaflığa sahip olduğu anlamına gelir. 0.0 alpha değeri tamamen şeffaf bir nesne üretir. 0.5 alpha değeri, nesnenin renginin %50 kendi renginden, %50 ise arkasındaki nesnelerin renklerinden oluştuğunu gösterir.
Şimdiye kadar kullandığımız dokular 3 renk bileşeninden oluşuyordu: kırmızı, yeşil ve mavi. Ancak bazı dokular her texel için bir alpha değeri içeren gömülü bir alpha kanalına da sahiptir. Bu alpha değeri, dokunun hangi bölümlerinin şeffaf olduğunu ve ne kadar şeffaf olduğunu kesin olarak bildirir. Örneğin, şu pencere dokusunun cam kısmında 0.25, köşelerinde 0.0 alpha değeri bulunmaktadır. Cam kısmı normalde tamamen kırmızı olurdu; ancak %75 şeffaflığa sahip olduğundan, sayfanın arka planını büyük ölçüde gösterir ve bu da onu çok daha az kırmızı gösterir:

Bu pencereli dokuyu yakında depth testing bölümündeki sahneye ekleyeceğiz; ancak önce tamamen şeffaf veya tamamen opak piksellerle şeffaflık uygulamanın daha kolay bir tekniğini ele alacağız.
Fragment Atmak (Discarding Fragments)
Bazı efektler kısmi şeffaflığa aldırış etmez; doku renk değerine göre ya bir şey göstermek ya da hiç göstermemek ister. Çim düşünün: az çabayla çim gibi bir şey oluşturmak için genellikle 2D bir dörtgen üzerine çim dokusu yapıştırıp onu sahneye yerleştirirsiniz. Ancak çim tam olarak 2D bir kare şeklinde değildir; bu nedenle yalnızca çim dokusunun bazı bölümlerini göstermek, diğerlerini yok saymak istersiniz.
Aşağıdaki doku tam olarak böyle bir dokudur: ya tamamen opak (1.0 alpha değeri) ya da tamamen şeffaf (0.0 alpha değeri) — arada hiçbir şey yok. Çim olmayan yerlerde, görüntü kendi rengi yerine sayfanın arka plan rengini göstermektedir:

Sahneye bitki örtüsü eklerken dikdörtgen bir çim görüntüsü değil, yalnızca gerçek çimi görmek ve görüntünün geri kalanından geçmek istiyoruz. Dokunun şeffaf bölümlerini gösteren fragment'ları atmak (discard), onları renk buffer'ına kaydetmemek istiyoruz.
Bunu yapmadan önce alpha değerleri olan dokuların nasıl yükleneceğini öğrenmemiz gerekiyor. Alpha değerlerine sahip dokuları yüklemek için değiştirmemiz gereken pek bir şey yok. stb_image, mevcutsa bir görüntünün alpha kanalını otomatik olarak yükler; ancak doku oluşturma işleminde OpenGL'e dokunun artık bir alpha kanalı kullandığını söylememiz gerekiyor:
Ayrıca fragment shader'da dokunun tüm 4 renk bileşenini aldığınızdan emin olun; yalnızca RGB bileşenlerini değil:
Şeffaf doku yüklemeyi öğrendiğimize göre, derinlik testi bölümünde tanıtılan temel sahneye bu çim yapraklarından birkaç tane ekleyerek test edebiliriz. Birkaç glm::vec3 vektörü ekleyeceğimiz küçük bir vector dizisi oluşturuyoruz:
Her çim nesnesi, üzerine çim dokusu yapıştırılmış tek bir dörtgen olarak render edilir. Zemini ve iki küpü render ettikten sonra çim yapraklarını render ediyoruz:
Uygulamayı çalıştırmak şuna benzer görünecektir:

Bu, OpenGL'in varsayılan olarak alpha değerleriyle ne yapacağını bilmemesinden kaynaklanmaktadır. Bunları shader'ların yardımıyla kendimiz yapmamız gerekir. GLSL, bir kez çağrıldığında fragment'ın daha fazla işlenmeyeceğini ve renk buffer'ına geçmeyeceğini garanti eden discard komutunu sunar. Bu komut sayesinde bir fragment'ın alpha değerinin belirli bir eşiğin altında olup olmadığını kontrol edebilir, öyleyse fragment'ı hiç işlenmemiş gibi atabiliriz:
Burada örneklenen doku renginin 0.1 eşiğinin altında bir alpha değeri içerip içermediğini kontrol ediyor ve varsa fragment'ı atıyoruz. Bu fragment shader, yalnızca (neredeyse) tamamen şeffaf olmayan fragment'ları render etmemizi sağlar. Artık şöyle görünmeli:

Dokuları kenarlarda örneklerken OpenGL, kenarlık değerlerini dokunun bir sonraki tekrar eden değeriyle interpolate eder (sarma parametrelerini varsayılan olarak GL_REPEAT olarak ayarladığımızdan). Bu genellikle sorun olmaz; ancak şeffaf değerler kullandığımızda dokunun üst kısmı, şeffaf değerini alt kenarlığın opak renk değeriyle interpolate eder. Bunun sonucu, dokulu dörtgeninizin etrafına sarılmış hafif yarı şeffaf renkli bir kenarlık görebilirsiniz. Bunu önlemek için, tekrar etmesini istemediğiniz alpha dokuları kullandığınızda doku sarma yöntemini GL_CLAMP_TO_EDGE olarak ayarlayın:
Kaynak kodu buradan bulabilirsiniz.
Blending (Harmanlama)
Fragment atma bizi iyi bir noktaya getirdi; ancak yarı şeffaf görüntüler render etme esnekliği sunmuyor: ya fragment'ı render ediyoruz ya da tamamen atıyoruz. Farklı şeffaflık seviyelerinde görüntüler render etmek için blending'i etkinleştirmemiz gerekiyor. OpenGL'in diğer işlevleri gibi GL_BLEND ile etkinleştirebiliriz:
Blending'i etkinleştirdiğimize göre, OpenGL'e nasıl blend yapacağını söylememiz gerekiyor.
OpenGL'de blending şu denklemle gerçekleşir:
Cˉresult=Cˉsource⋅Fsource+Cˉdestination⋅Fdestination
C_source: Kaynak renk vektörü. Fragment shader'ın renk çıktısıdır.
C_destination: Hedef renk vektörü. Halihazırda renk buffer'ında saklanan renk vektörüdür.
F_source: Kaynak faktör değeri. Alpha değerinin kaynak renk üzerindeki etkisini belirler.
F_destination: Hedef faktör değeri. Alpha değerinin hedef renk üzerindeki etkisini belirler.
Fragment shader çalıştıktan ve tüm testler geçtikten sonra bu blend denklemi, fragment'ın renk çıktısı ve renk buffer'ında mevcut olan değer üzerine uygulanır. Kaynak ve hedef renkler OpenGL tarafından otomatik olarak ayarlanır; kaynak ve hedef faktörler ise bizim seçeceğimiz bir değere ayarlanabilir.
Basit bir örnekle başlayalım:

Kırmızı karenin üstüne yarı şeffaf yeşil kare çizmek istiyoruz. Kırmızı kare hedef renk (renk buffer'ında zaten var), yeşil kareyi ise kırmızı karenin üstüne çiziyoruz.
Faktör değerlerini ne olarak ayarlarız? En azından yeşil kareyi alpha değeriyle çarpmak istiyoruz; bu nedenle kaynak faktörünü (F_src) kaynak renk vektörünün alpha değeri olan 0.6 olarak ayarlıyoruz. Hedef karenin geri kalan alpha değeri kadar katkıda bulunmasını istiyoruz; yeşil kare sonuca %60 katkıda bulunuyorsa kırmızı karenin %40 katkıda bulunmasını isteyebiliriz, yani 1.0 - 0.6. Bu nedenle hedef faktörü (F_destination) kaynak renk vektörünün alpha değerinin (1 eksi) olarak ayarlıyoruz. Denklem şöyle olur:
Cˉresult=0.01.00.00.6⋅0.6+1.00.00.01.0⋅(1−0.6)
Sonuç, birleşik karenin fragment'larının %60 yeşil ve %40 kırmızı içeren bir renk içerdiğidir:

Elde edilen renk daha sonra renk buffer'ına saklanarak önceki rengin yerini alır.
Peki OpenGL'e bu faktörleri nasıl kullanacağını söyleriz? Bunun için glBlendFunc adında bir fonksiyon bulunmaktadır.
glBlendFunc(GLenum sfactor, GLenum dfactor) fonksiyonu kaynak ve hedef faktör için seçenekleri ayarlayan iki parametre bekler. Sabit renk vektörünün ayrıca glBlendColor fonksiyonu aracılığıyla ayarlanabileceğini unutmayın. En yaygın seçenekler:
GL_ZERO
Faktör 0'a eşittir.
GL_ONE
Faktör 1'e eşittir.
GL_SRC_COLOR
Faktör kaynak renk vektörüne eşittir.
GL_ONE_MINUS_SRC_COLOR
Faktör 1 - C_source'a eşittir.
GL_DST_COLOR
Faktör hedef renk vektörüne eşittir.
GL_ONE_MINUS_DST_COLOR
Faktör 1 - C_destination'a eşittir.
GL_SRC_ALPHA
Faktör kaynak renk vektörünün alpha bileşenine eşittir.
GL_ONE_MINUS_SRC_ALPHA
Faktör 1 - alpha (kaynak) değerine eşittir.
GL_DST_ALPHA
Faktör hedef renk vektörünün alpha bileşenine eşittir.
GL_ONE_MINUS_DST_ALPHA
Faktör 1 - alpha (hedef) değerine eşittir.
GL_CONSTANT_COLOR
Faktör sabit renk vektörüne eşittir.
GL_ONE_MINUS_CONSTANT_COLOR
Faktör 1 - C_constant'a eşittir.
GL_CONSTANT_ALPHA
Faktör sabit renk vektörünün alpha bileşenine eşittir.
GL_ONE_MINUS_CONSTANT_ALPHA
Faktör 1 - alpha (sabit) değerine eşittir.
İki karenin blending sonucunu elde etmek için kaynak faktör olarak kaynak renk vektörünün alpha'sını, hedef faktör olarak ise 1 - alpha'yı kullanmak istiyoruz:
RGB ve alpha kanalları için ayrı ayrı seçenekler ayarlamak da mümkündür (glBlendFuncSeparate ile):
Bu fonksiyon, RGB bileşenlerini önceki gibi ayarlar; ancak yalnızca elde edilen alpha bileşeninin kaynak alpha değerinden etkilenmesine olanak tanır.
OpenGL, denklemdeki kaynak ve hedef parça arasındaki operatörü değiştirmemize de izin vererek daha fazla esneklik sunar. glBlendEquation(GLenum mode) bunu ayarlamamıza olanak tanır:
GL_FUNC_ADD: varsayılan; her iki rengi birbirine ekler: C_result = Src + Dst.GL_FUNC_SUBTRACT: her iki rengi birbirinden çıkarır: C_result = Src - Dst.GL_FUNC_REVERSE_SUBTRACT: her iki rengi çıkarır ama sırayı tersine çevirir: C_result = Dst - Src.GL_MIN: her iki rengin bileşen bazında minimumunu alır: C_result = min(Dst, Src).GL_MAX: her iki rengin bileşen bazında maksimumunu alır: C_result = max(Dst, Src).
Genellikle GL_FUNC_ADD çağrısını atlayabiliriz çünkü bu, çoğu işlem için tercih edilen blend denklemdir.
Yarı Şeffaf Dokuları Render Etmek
Blending'in OpenGL'de nasıl çalıştığını anladığımıza göre birkaç yarı şeffaf pencere ekleyerek bilgimizi test edelim. Bu bölümün başındaki sahneyi kullanıyoruz; ancak bu kez çim dokusu yerine bölümün başındaki şeffaf pencere dokusunu kullanıyoruz.
İlk olarak, başlatma sırasında blending'i etkinleştirip uygun blend fonksiyonunu ayarlıyoruz:
Blending etkinleştirildiğinzden artık fragment atmaya gerek yok; bu nedenle fragment shader'ı orijinal versiyonuna sıfırlayabiliriz:
Bu kez OpenGL, bir fragment render ederken FragColor'ın alpha değerine göre mevcut fragment rengini renk buffer'ındaki fragment rengiyle birleştirecektir. Pencerenin cam kısmı yarı şeffaf olduğundan bu pencereden bakarak sahnenin geri kalanını görebilmeliyiz.

Ancak daha yakından bakarsanız bir şeylerin ters gittiğini fark edebilirsiniz. Ön pencerenin şeffaf kısımları arka plandaki pencereleri kapatıyor. Neden?
Bunun nedeni, depth testing'in blending ile birleştirildiğinde biraz hileli çalışmasıdır. Depth buffer'a yazarken depth test, fragment'ın şeffaf olup olmadığına bakmaz; şeffaf kısımlar da diğer değerler gibi depth buffer'a yazılır. Bunun sonucunda arka plandaki pencereler, şeffaflığı göz ardı edilerek herhangi bir opak nesne gibi depth testine tabi tutulur. Şeffaf kısım arkasındaki pencereleri gösterse de depth test onları atar.
Bu nedenle pencereleri istediğimiz gibi render edemeyiz ve depth buffer'ın tüm sorunları çözmesini bekleyemeyiz. Pencerelerin arkalarındakileri göstermesi için önce arka plandaki pencereleri çizmemiz gerekir; yani pencereleri en uzaktan en yakına manuel olarak sıralayıp buna göre kendimiz çizmemiz gerekir.
Çim yaprakları gibi tamamen şeffaf nesnelerde, blend yerine şeffaf fragment'ları atmak seçeneğimiz var; bu da bu baş ağrılarından bazılarını ortadan kaldırır (depth sorunları olmaz).
Sırayı Bozmayın
Birden fazla nesne için blending'in çalışması için en uzak nesneyi önce, en yakın nesneyi sonra çizmemiz gerekir. Normal opak nesneler yine depth buffer kullanılarak çizilebilir ve sıralanmaları gerekmez. Ancak önce (sıralanmış) şeffaf nesneleri çizmeden önce çizildiklerinden emin olmamız gerekir. Opak ve şeffaf nesnelerden oluşan bir sahneyi çizerken genel yaklaşım şöyledir:
Önce tüm opak nesneleri çiz.
Tüm şeffaf nesneleri sırala.
Sıralı şeffaflık sırasında tüm şeffaf nesneleri çiz.
Şeffaf nesneleri sıralamanın bir yolu, kameranın bakış açısından nesneye uzaklığı almaktır. Bu, kameranın konum vektörü ile nesnenin konum vektörü arasındaki mesafeyi alarak yapılabilir. Bu mesafeyi, STL kütüphanesinin map veri yapısında karşılık gelen konum vektörüyle birlikte saklıyoruz. map, değerlerini anahtarlarına göre otomatik olarak sıralar:
Sonuç, her pencere konumunu en düşük mesafeden en yüksek mesafeye kadar distance anahtar değerine göre saklayan sıralı bir kap nesnesidir.
Render ederken haritanın değerlerini her birini ters sırada (en uzaktan en yakına) alıyoruz ve karşılık gelen pencereleri doğru sırada render ediyoruz:
map'ten ters yineleyici alarak her birini ters sırada yineliyoruz ve her pencere dördüyü ilgili pencere konumuna öteliyoruz. Bu görece basit sıralama yaklaşımı önceki sorunu çözüyor ve sahne artık şöyle görünüyor:

Sıralamalı tam kaynak kodu buradan bulabilirsiniz.
Nesneleri mesafelerine göre sıralama yaklaşımı bu spesifik senaryo için iyi çalışsa da dönmeler, ölçekleme veya diğer dönüşümleri dikkate almaz; garip şekilli nesneler için basit bir konum vektöründen farklı bir ölçüt gerekebilir.
Sahnenizde nesneleri sıralama, sahne türüne büyük ölçüde bağlı olan zorlu bir iştir; bunun üstüne gerektirdiği ekstra işlem gücü de cabası. Opak ve şeffaf nesneleri tamamen render etmek o kadar kolay değil. Order Independent Transparency gibi daha gelişmiş teknikler var; ancak bunlar bu bölümün kapsamı dışında. Şimdilik nesnelerinizi normal blend ile sahneye eklemek zorunda kalacaksınız; ancak dikkatli olur ve sınırlamaları bilirseniz oldukça iyi blending uygulamaları elde edebilirsiniz.
Orijinal kaynak: LearnOpenGL – Advanced-OpenGL/Blending Türkçe çeviri: OpenGL Öğrenin projesi
Orijinal Kaynak: Blending
Last updated