Stencil Testing (Şablon Testi)
Fragment shader bir fragment'ı işledikten sonra, derinlik testi gibi fragment'ları atabilen stencil test adı verilen bir işlem gerçekleştirilir. Ardından kalan fragment'lar derinlik testine tabi tutulur; OpenGL burada da ek fragment'lar atabilir. Stencil test, stencil buffer adı verilen ayrı bir buffer'ın içeriğine dayanır; render sırasında bu buffer'ı güncelleyerek ilginç efektler üretebilirsiniz.
Stencil buffer, piksel başına genellikle 8 bit stencil değeri içerir; bu da piksel başına toplam 256 farklı stencil değeri demektir. Bu stencil değerlerini istediğimiz gibi ayarlayabiliriz; belirli bir fragment belirli bir stencil değerine sahipse o fragment'ı atabilir veya geçirebiliriz.
Her pencere kütüphanesinin sizin için bir stencil buffer oluşturması gerekir. GLFW bunu otomatik olarak yapar, dolayısıyla GLFW'ye açıkça söylememiz gerekmez. Ancak diğer pencere kütüphaneleri varsayılan olarak stencil buffer oluşturmayabilir; bu nedenle kütüphanenizin belgelerini kontrol ettiğinizden emin olun.
Bir stencil buffer'ın basit örneği şöyle gösterilebilir (pikseller ölçekli değil):

Stencil buffer önce sıfırlarla temizlenir; ardından 1'lerden oluşan açık bir dikdörtgen stencil buffer'a yazılır. Sahnenin fragment'ları yalnızca o fragment'ın stencil değerinin 1 olduğu yerlerde render edilir (diğerleri atılır).
Stencil buffer işlemleri, fragment'ları render ettiğimiz her yerde stencil buffer'ı belirli değerlere ayarlamamıza olanak tanır. Render sırasında stencil buffer içeriğini değiştirerek buffer'a yazarız. Aynı ya da ilerleyen frame'lerde bu değerleri okuyarak belirli fragment'ları atabiliriz. Stencil buffer'ı kullanarak pek çok farklı şey yapabilirsiniz; ancak genel akış şu şekildedir:
Stencil buffer'a yazmayı etkinleştir.
Nesneleri render et; stencil buffer içeriğini güncelle.
Stencil buffer'a yazmayı devre dışı bırak.
(Diğer) nesneleri render et; bu sefer stencil buffer içeriğine göre belirli fragment'ları at.
Stencil buffer sayesinde, sahnedeki diğer nesnelerin fragment'larını baz alarak istediğimiz fragment'ları atabiliriz.
Stencil test'i GL_STENCIL_TEST ile etkinleştirebilirsiniz. O andan itibaren tüm render çağrıları stencil buffer'ı bir şekilde etkiler:
Her iterasyonda, renk ve depth buffer'da olduğu gibi, stencil buffer'ı da temizlemeniz gerektiğini unutmayın:
Derinlik testindeki glDepthMask fonksiyonuna benzer olarak stencil buffer için de eşdeğer bir fonksiyon vardır. glStencilMask, buffer'a yazılmadan önce stencil değeriyle AND işlemine girecek bir bit maskesi belirlememize olanak tanır. Varsayılan maske tüm bitleri 1 olduğundan çıktıyı etkilemez; 0x00 yaparsak ise buffer'a yazılan tüm stencil değerleri 0 olur — derinlik testindeki glDepthMask(GL_FALSE) ile eşdeğerdir:
Pratikte çoğunlukla 0x00 veya 0xFF stencil maskı kullanılır; ancak özel bit maskeleri tanımlanabildiğini bilmek faydalıdır.
Stencil Fonksiyonları
Derinlik testine benzer biçimde, stencil testinin ne zaman geçeceğini ya da başarısız olacağını ve stencil buffer'ı nasıl etkileyeceğini belirli ölçüde kontrol edebiliriz. Stencil testing'i yapılandırmak için iki fonksiyon kullanılır: glStencilFunc ve glStencilOp.
glStencilFunc(GLenum func, GLint ref, GLuint mask) üç parametre alır:
func: Fragment'ın geçip geçmeyeceğini ya da atılıp atılmayacağını belirleyen karşılaştırma fonksiyonu. Sakladığı stencil değeri ileglStencilFunc'ınrefdeğeri karşılaştırılarak uygulanır. Seçenekler:GL_NEVER,GL_LESS,GL_LEQUAL,GL_GREATER,GL_GEQUAL,GL_EQUAL,GL_NOTEQUAL,GL_ALWAYS. Anlamları derinlik testi fonksiyonlarıyla benzerdir.ref: Stencil testi için referans değeri belirtir. Stencil buffer içeriği bu değerle karşılaştırılır.mask: Karşılaştırılmadan önce hem referans değeri hem de saklanan stencil değeriyleANDişlemi yapılacak bir maske belirtir. Başlangıçta tümü1olarak ayarlanmıştır.
Başlangıçta gösterdiğimiz basit stencil örneğinde fonksiyon şöyle ayarlanırdı:
Bu, OpenGL'e şunu söyler: bir fragment'ın stencil değeri referans değere (1) eşit (GL_EQUAL) olduğunda fragment testi geçer ve çizilir; aksi takdirde atılır.
Ancak glStencilFunc yalnızca OpenGL'in stencil buffer içeriğine göre fragment'ları geçirip geçirmeyeceğini ya da atıp atmayacağını açıklar; buffer'ı gerçekte nasıl güncelleyeceğimizi değil. İşte bunun için glStencilOp devreye girer.
glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass) her seçenek için ne yapılacağını belirtmemizi sağlayan üç parametre içerir:
sfail: Stencil testi başarısız olursa yapılacak işlem.dpfail: Stencil testi geçer ancak depth testi başarısız olursa yapılacak işlem.dppass: Hem stencil hem de depth testi geçerse yapılacak işlem.
Her seçenek için aşağıdaki işlemlerden birini seçebilirsiniz:
GL_KEEP
Mevcut stencil değeri korunur.
GL_ZERO
Stencil değeri 0 olarak ayarlanır.
GL_REPLACE
Stencil değeri, glStencilFunc ile ayarlanan referans değeriyle değiştirilir.
GL_INCR
Maksimum değerin altındaysa stencil değeri 1 artırılır.
GL_INCR_WRAP
GL_INCR ile aynıdır; ancak maksimum değer aşılırsa 0'a döner.
GL_DECR
Minimum değerin üzerindeyse stencil değeri 1 azaltılır.
GL_DECR_WRAP
GL_DECR ile aynıdır; ancak 0'ın altına düşerse maksimum değere döner.
GL_INVERT
Mevcut stencil buffer değerini bitsel olarak tersine çevirir.
Varsayılan olarak glStencilOp, (GL_KEEP, GL_KEEP, GL_KEEP) biçiminde ayarlıdır; yani hangi test sonucu çıkarsa çıksın stencil buffer değerleri değişmez. Buffer'a yazmak istiyorsanız en az bir parametre için farklı bir işlem seçmelisiniz.
glStencilFunc ve glStencilOp birlikte kullanıldığında, stencil buffer'ı ne zaman ve nasıl güncelleyeceğimizi ile buffer içeriğine göre fragment'ları ne zaman geçireceğimizi ya da atacağımızı tam olarak belirleyebiliriz.
Object Outlining (Nesne Dış Hattı Çizimi)
Önceki bölümleri okuyarak stencil testing'i tam anlamıyla kavrayamadıysanız endişelenmeyin; şimdi yalnızca stencil buffer kullanılarak hayata geçirilebilen çok kullanışlı bir özelliği göstereceğiz: object outlining (nesne dış hat çizimi).

Object outlining adından da anlaşılacağı üzere, her nesnenin (ya da yalnızca birinin) etrafına küçük renkli bir çerçeve çizer. Bir strateji oyununda hangi birimlerin seçili olduğunu göstermek gibi durumlarda çok işe yarayan bir efekttir. Genel uygulama adımları şöyledir:
Stencil yazmayı etkinleştir.
Dış hattı çizilecek nesneleri çizmeden önce stencil op'u
GL_ALWAYSolarak ayarla; nesnelerin fragment'larının render edildiği her yere stencil buffer'a1yaz.Nesneleri render et.
Stencil yazmayı ve depth testing'i devre dışı bırak.
Her nesneyi küçük miktarda büyüt.
Tek (çerçeve) renk çıkaran farklı bir fragment shader kullan.
Nesneleri yeniden çiz; ancak yalnızca fragment'larının stencil değerleri
1'e eşit olmadığı yerlerde.Depth testing'i yeniden etkinleştir ve stencil func'ı
GL_KEEPolarak sıfırla.
Bu adımlar, stencil buffer'a nesnenin her fragment'ı için 1 değerini yazar. Çerçeveleri çizme sırası geldiğinde, yalnızca stencil testinin geçtiği (yani 1 olmayan) konumlarda büyütülmüş nesne sürümlerini render ederiz. Böylece büyütülmüş sürümlerin orijinal nesne fragment'larıyla örtüşen kısımları stencil buffer sayesinde etkili biçimde elenir.
Önce tek bir sabit renk çıkaran çok basit bir fragment shader oluşturuyoruz; shaderSingleColor olarak adlandırıyoruz:
Önceki bölümdeki sahneyi kullanarak iki konteynere object outlining ekleyeceğiz; zemini dışarıda bırakıyoruz. Önce zemini, sonra iki konteyneri (stencil buffer'a yazarken), ardından büyütülmüş konteynerleri (daha önce çizilmiş konteyner fragment'larının üzerine yazan bölümleri atarak) çizmek istiyoruz.
Önce stencil testing'i etkinleştiriyoruz:
Her frame'de stencil testlerinden herhangi biri başarılı veya başarısız olduğunda yapılacak işlemi belirliyoruz:
Testlerden biri başarısız olursa stencil buffer'daki değeri olduğu gibi bırakıyoruz. Hem stencil hem de derinlik testi geçtiğinde ise glStencilFunc ile belirlediğimiz referans değeriyle — ilerleyen kodda 1 yapacağız — saklanan stencil değerini değiştiriyoruz.
Frame başlangıcında stencil buffer'ı 0'lara temizliyor; konteynerler için çizilen her fragment'ta stencil buffer'ı 1'e güncelliyoruz:
GL_REPLACE stencil op fonksiyonunu kullanarak konteyner fragment'larının her birinin stencil buffer'ı 1 stencil değeriyle güncellediğinden emin oluyoruz. Fragment'lar her zaman stencil testini geçtiğinden, stencil buffer çizdiğimiz her yerde referans değeriyle güncelleniyor.
Stencil buffer artık konteynerlerin çizildiği yerlerde 1'lerle güncellendiğine göre, büyütülmüş konteynerleri uygun test fonksiyonu ile ve stencil buffer'a yazma devre dışı bırakılmış halde çiziyoruz:
Stencil fonksiyonunu GL_NOTEQUAL yaparak yalnızca stencil değeri 1 olmayan bölümleri çizeceğimizden, efektif olarak orijinal konteynerlerin dışında kalan piksel konumlarını görürsünüz. Derinlik testini de devre dışı bırakıyoruz; böylece büyütülmüş konteynerler (çerçeveler) zemin tarafından bastırılmaz. İşlem sonrasında derinlik testini yeniden etkinleştirmeyi unutmayın.
Sahnemizdeki tam object outlining rutini şuna benzer:
Stencil testing'in genel mantığını kavradıysanız bu kodu anlamak zor olmayacaktır. Anlamadıysanız önceki bölümleri dikkatle tekrar okuyun; somut bir örnek gördükten sonra her fonksiyonun ne işe yaradığı daha net oturacaktır.
Outlining algoritmasının sonucu şuna benzer:

Object outlining algoritmasının tam kaynak kodunu buradan bulabilirsiniz.
Her iki konteyner arasında çerçevelerin üst üste geldiğini göreceksiniz — bu genellikle istediğimiz efekttir (örneğin bir strateji oyununda 10 birimi aynı anda seçmek istiyorsak çerçevelerin birleşmesi tercih edilir). Her nesne için tam bağımsız bir çerçeve istiyorsanız, nesne başına stencil buffer'ı temizlemeniz ve depth buffer ile biraz yaratıcı olmanız gerekir.
Bu object outlining algoritması strateji oyunlarında seçili nesneleri vurgulamak için yaygın biçimde kullanılır ve bir Model sınıfına kolaylıkla entegre edilebilir; çerçeveli ya da çerçevesiz çizimi toggle etmek için basit bir boolean bayrağı yeterli olur. İsteyenler Gaussian Blur gibi post-processing filtreleriyle çerçevelere daha organik bir görünüm kazandırabilir.
Stencil testing bunların yanı sıra dikiz aynasına doku çizmek, aynaya sığacak şekilde maskeleme yapmak ya da shadow volume tekniğiyle gerçek zamanlı gölgeler render etmek gibi pek çok farklı amaç için de kullanılır. Stencil buffer, OpenGL araç kutusuna değerli bir eklenti olarak katılır.
Orijinal kaynak: LearnOpenGL – Advanced-OpenGL/Stencil-testing Türkçe çeviri: OpenGL Öğrenin projesi
Orijinal Kaynak: Stencil testing
Last updated