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.

circle-info

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 örneği

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:

  1. Stencil buffer'a yazmayı etkinleştir.

  2. Nesneleri render et; stencil buffer içeriğini güncelle.

  3. Stencil buffer'a yazmayı devre dışı bırak.

  4. (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 ile glStencilFunc'ın ref değ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ğeriyle AND işlemi yapılacak bir maske belirtir. Başlangıçta tümü 1 olarak 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:

İşlem
Açıklama

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 örneği

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:

  1. Stencil yazmayı etkinleştir.

  2. Dış hattı çizilecek nesneleri çizmeden önce stencil op'u GL_ALWAYS olarak ayarla; nesnelerin fragment'larının render edildiği her yere stencil buffer'a 1 yaz.

  3. Nesneleri render et.

  4. Stencil yazmayı ve depth testing'i devre dışı bırak.

  5. Her nesneyi küçük miktarda büyüt.

  6. Tek (çerçeve) renk çıkaran farklı bir fragment shader kullan.

  7. Nesneleri yeniden çiz; ancak yalnızca fragment'larının stencil değerleri 1'e eşit olmadığı yerlerde.

  8. Depth testing'i yeniden etkinleştir ve stencil func'ı GL_KEEP olarak 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ümdekiarrow-up-right 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 sonucu

Object outlining algoritmasının tam kaynak kodunu buradanarrow-up-right bulabilirsiniz.

circle-info

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-testingarrow-up-right Türkçe çeviri: OpenGL Öğrenin projesi

Orijinal Kaynak: Stencil testingarrow-up-right

Last updated