Kenar düzeltme

Rendering yolculuğunuzun bir yerinde, muhtemelen modellerinizin kenarlarında testere benzeri tırtıklı desenlerle karşılaşmışsınızdır. Bu pürüzlü kenarların ortaya çıkmasının nedeni, pikselleştiricinin vertex verisini sahnenin arkasındaki gerçek parçalara nasıl dönüştürdüğüdür. Bu pürüzlü kenarların nasıl göründüğüne dair bir örnek, basit bir küp çizilirken de zaten görülebilir:

Hemen görünmese de, küpün kenarlarına daha yakından bakarsanız pürüzlü bir desen görürsünüz. Yakınlaştırırsak:

Bu, bir uygulamanın nihai versiyonunda istediğimiz bir şey değil. Bir kenarı oluşturan piksel oluşumlarını açıkça görmenin bu etkisine aliasing (tr. örtüşme) adı verilir. Daha düzgün kenarlar üreterek bu örtüşme davranışıyla savaşan, anti-aliasing (tr. kenar düzeltme) teknikleri adı verilen birkaç teknik vardır.

İlk başta, sahneyi oluşturmak için geçici olarak çok daha yüksek çözünürlüklü bir işleme arabelleği kullanan super sample anti-aliasing, SSAA (tr. Süper Örnek Kenar Düzeltme) adı verilen bir tekniğimiz vardı. Ardından, tam sahne oluşturulduğunda, çözünürlük normal çözünürlüğe geri döner. Bu ekstra çözünürlük, bu pürüzlü kenarları önlemek için kullanıldı. Takma ad sorununa bir çözüm sağlasa da, normalden çok daha fazla parça çizmemiz gerektiğinden büyük bir performans dezavantajı ile geldi. Bu teknik bu nedenle sadece kısa bir zafer anına sahipti.

Bu teknik, çok örnekli kenar yumuşatma veya SSAA'nın arkasındaki kavramlardan ödünç alan ve çok daha verimli bir yaklaşım uygulayan MSAA adı verilen daha modern bir tekniği doğurdu. Bu bölümde, OpenGL'de yerleşik olarak bulunan bu MSAA tekniğini kapsamlı bir şekilde tartışacağız.

Multisampling (tr. Çoklu Örnekleme)

Çoklu örneklemenin ne olduğunu ve örtüşme sorununu çözmek için nasıl çalıştığını anlamak için önce OpenGL'nin rasterleştiricisinin iç işleyişini biraz daha derinlemesine incelememiz gerekiyor.

Pikselleştirici, son işlenmiş köşeleriniz ile fragment shader arasındaki tüm algoritmaların ve işlemlerin birleşimidir. Pikselleştirici, tek bir ilkele ait tüm köşeleri alır ve bunu bir dizi parçaya dönüştürür. Vertex koordinatları teorik olarak herhangi bir koordinata sahip olabilir, ancak fragmanlar ekranınızın çözünürlüğüne bağlı oldukları için olamaz. Köşe koordinatları ve parçalar arasında neredeyse hiçbir zaman bire bir eşleme olmayacak, bu nedenle rasterleştirici bir şekilde her bir köşe noktasının hangi parça/ekran koordinatına ulaşacağını belirlemelidir.

Burada, her pikselin merkezinin, bir pikselin üçgen tarafından kapsanıp kapsanmadığını belirlemek için kullanılan bir örnek nokta içerdiği bir ekran pikselleri ızgarası görüyoruz. Kırmızı numune noktaları üçgen tarafından kapsanır ve bu kapsanan piksel için bir parça oluşturulur. Üçgen kenarlarının bazı kısımları hala belirli ekran piksellerine girse de, pikselin örnek noktası üçgenin iç kısmı tarafından kaplanmadığından bu piksel herhangi bir parça gölgelendiricisinden etkilenmeyecektir.

Muhtemelen şu anda takma adının kökenini anlayabilirsiniz. Üçgenin tam işlenmiş versiyonu ekranınızda şöyle görünecektir:

Sınırlı sayıda ekran pikseli nedeniyle, bazı pikseller bir kenar boyunca işlenecek ve bazıları olmayacaktır. Sonuç olarak, daha önce gördüğümüz pürüzlü kenarlara yol açan düzgün olmayan kenarları olan ilkelleri oluşturuyoruz.

Çoklu örneklemenin yaptığı şey, üçgenin kapsamını belirlemek için tek bir örnekleme noktası kullanmak değil, birden fazla örnek noktası kullanmaktır (adını nereden aldığını tahmin edin). Her pikselin merkezinde tek bir örnek noktası yerine, genel bir desene 4 alt örnek yerleştireceğiz ve bunları piksel kapsamını belirlemek için kullanacağız.

Görselin sol tarafı, normalde bir üçgenin kapsamını nasıl belirleyeceğimizi gösterir. Bu belirli piksel, bir fragment shader çalıştırmaz (ve bu nedenle boş kalır), çünkü örnek noktası üçgen tarafından kaplanmamıştır. Görselinb sağ tarafında, her pikselin 4 örnek nokta içerdiği çok örnekli bir sürüm gösterilmektedir. Burada örnek noktalardan sadece 2 tanesinin üçgeni kapladığını görebiliriz.

Örnek noktalarının miktarı, bize daha iyi kapsama hassasiyeti sağlayan daha fazla örnek ile istediğimiz herhangi bir sayıda olabilir.

Çoklu örneklemenin ilginç hale geldiği yer burasıdır. 2 alt örneğin üçgen tarafından kapsandığını belirledik, bu nedenle bir sonraki adım bu belirli piksel için bir renk belirlemektir. İlk tahminimiz, kapsanan her alt örnek için parça gölgelendiriciyi çalıştırmamız ve daha sonra her bir alt örneğin renklerinin piksel başına ortalamasını almamız olacaktır. Bu durumda, parça gölgelendiriciyi her bir alt örnekte enterpolasyonlu tepe verisi üzerinde iki kez çalıştırır ve elde edilen rengi bu örnek noktalarında saklardık. Bu (neyse ki) çalışma şekli değil, çünkü bu, çoklu örneklemeden çok daha fazla parça gölgelendirici çalıştırmamız gerektiği anlamına gelir, bu da performansı büyük ölçüde azaltır.

MSAA'nın gerçekte nasıl çalıştığı, üçgenin kaç tane alt örneği kapsadığına bakılmaksızın, parça gölgelendiricisinin piksel başına (her ilkel için) yalnızca bir kez çalıştırılmasıdır; parça gölgelendirici, pikselin merkezine enterpolasyonlu tepe noktası verileriyle çalışır. MSAA daha sonra alt örnek kapsamını belirlemek için daha büyük bir derinlik/kalıp arabelleği kullanır. Kapsanan alt örneklerin sayısı, piksel renginin çerçeve arabelleğine ne kadar katkıda bulunduğunu belirler. Önceki görüntüde 4 numuneden sadece 2'si kaplanmış olduğundan, üçgenin renginin yarısı çerçeve arabelleği rengiyle (bu durumda açık renk) karıştırılarak açık mavimsi bir renk elde edilir.

Sonuç, tüm ilkel kenarların artık daha yumuşak bir desen ürettiği daha yüksek çözünürlüklü bir arabellektir (daha yüksek çözünürlüklü derinlik/kalıp ile). Önceki üçgenin kapsamını belirlediğimizde çoklu örneklemenin nasıl göründüğünü görelim:

Çoklu örneklemenin ilginç hale geldiği yer burasıdır. 2 alt örneğin üçgen tarafından kapsandığını belirledik, bu nedenle bir sonraki adım bu belirli piksel için bir renk belirlemektir. İlk tahminimiz, kapsanan her alt örnek için parça gölgelendiriciyi çalıştırmamız ve daha sonra her bir alt örneğin renklerinin piksel başına ortalamasını almamız olacaktır. Bu durumda, parça gölgelendiriciyi her bir alt örnekte enterpolasyonlu tepe verisi üzerinde iki kez çalıştırır ve elde edilen rengi bu örnek noktalarında saklardık. Bu (neyse ki) çalışma şekli değil, çünkü bu, çoklu örneklemeden çok daha fazla parça gölgelendirici çalıştırmamız gerektiği anlamına gelir, bu da performansı büyük ölçüde azaltır.

MSAA'nın gerçekte nasıl çalıştığı, üçgenin kaç tane alt örneği kapsadığına bakılmaksızın, parça gölgelendiricisinin piksel başına (her ilkel için) yalnızca bir kez çalıştırılmasıdır; parça gölgelendirici, pikselin merkezine enterpolasyonlu tepe noktası verileriyle çalışır. MSAA daha sonra alt örnek kapsamını belirlemek için daha büyük bir derinlik/kalıp arabelleği kullanır. Kapsanan alt örneklerin sayısı, piksel renginin çerçeve arabelleğine ne kadar katkıda bulunduğunu belirler. Önceki görüntüde 4 numuneden sadece 2'si kaplanmış olduğundan, üçgenin renginin yarısı çerçeve arabelleği rengiyle (bu durumda açık renk) karıştırılarak açık mavimsi bir renk elde edilir.

Sonuç, tüm ilkel kenarların artık daha yumuşak bir desen ürettiği daha yüksek çözünürlüklü bir arabellektir (daha yüksek çözünürlüklü derinlik/kalıp ile). Önceki üçgenin kapsamını belirlediğimizde çoklu örneklemenin nasıl göründüğünü görelim:

Üçgenin sert kenarları artık gerçek kenar renginden biraz daha açık renklerle çevrelenmiştir, bu da uzak mesafeden bakıldığında kenarın pürüzsüz görünmesine neden olur.

Derinlik ve şablon değerleri alt örnek başına depolanır ve parça gölgelendiriciyi yalnızca bir kez çalıştırsak bile, tek bir pikselin üst üste binen birden çok üçgen olması durumunda renk değerleri de alt örnek başına depolanır. Derinlik testi için, tepe noktasının derinlik değeri, derinlik testi çalıştırılmadan önce her bir alt örneğe enterpolasyon yapılır ve şablon testi için, alt örnek başına şablon değerlerini saklarız. Bu, arabelleklerin boyutunun artık piksel başına alt örnek miktarı kadar artırıldığı anlamına gelir.

Şimdiye kadar tartıştığımız şey, çoklu örneklemeli kenar yumuşatmanın perde arkasında nasıl çalıştığına dair temel bir genel bakış. Rasterleştiricinin arkasındaki gerçek mantık biraz daha karmaşıktır, ancak bu kısa açıklama, çok örnekli kenar yumuşatma arkasındaki kavramı ve mantığı anlamak için yeterli olmalıdır; pratik yönleri incelemek için yeterlidir.

OpenGL' de MSAA

OpenGL'de MSAA kullanmak istiyorsak, piksel başına birden fazla örnek değeri depolayabilen bir tampon kullanmamız gerekir. Belirli bir miktarda çoklu örneği depolayabilen yeni bir tür arabelleğe ihtiyacımız var ve buna çoklu örnek arabelleği denir.

Çoğu pencereleme sistemi, varsayılan bir arabellek yerine bize çok örnekli bir arabellek sağlayabilir. GLFW ayrıca bize bu işlevi sağlar ve tek yapmamız gereken, pencereyi oluşturmadan önce glfwWindowHint'i çağırarak normal bir tampon yerine N örnekli çok örnekli bir tampon kullanmak istediğimize dair GLFW'ye ipucu vermektir:

glfwWindowHint(GLFW_SAMPLES, 4);

Şimdi glfwCreateWindow'u çağırdığımızda bir işleme penceresi oluşturuyoruz, ancak bu sefer ekran koordinatı başına 4 alt örnek içeren bir tamponla. Bu, arabellek boyutunun 4 artırıldığı anlamına gelir.

Artık GLFW'den çoklu örneklemeli tamponlar istediğimize göre, GL_MULTISAMPLE ile glEnable'ı çağırarak çoklu örneklemeyi etkinleştirmemiz gerekiyor. Çoğu OpenGL sürücüsünde, çoklu örnekleme varsayılan olarak etkinleştirilmiştir, bu nedenle bu çağrı biraz gereksizdir, ancak yine de etkinleştirmek genellikle iyi bir fikirdir. Bu şekilde tüm OpenGL uygulamaları çoklu örneklemeyi etkinleştirir.

glEnable(GL_MULTISAMPLE);  

Gerçek çoklu örnekleme algoritmaları, OpenGL sürücülerinizdeki rasterleştiricide uygulandığından, yapmamız gereken başka bir şey yok. Şimdi bu bölümün başından itibaren yeşil küpü oluşturacak olsaydık, daha düzgün kenarlar görmemiz gerekirdi:

Küp gerçekten çok daha pürüzsüz görünüyor ve aynı şey sahnenizde çizdiğiniz diğer nesneler için de geçerli. Bu basit örneğin kaynak kodunu burada bulabilirsiniz.

Off-screen MSAA

GLFW, çok örnekli arabelleklerin oluşturulmasıyla ilgilendiğinden, MSAA'yı etkinleştirmek oldukça kolaydır. Ancak kendi çerçeve arabelleklerimizi kullanmak istiyorsak, çok örnekli arabellekleri kendimiz oluşturmamız gerekir; şimdi çok örnekli arabellekler oluşturmaya özen göstermemiz gerekiyor.

Çerçeve arabellekleri için ek olarak işlev görecek çok örnekli arabellekler oluşturmanın iki yolu vardır: doku ekleri ve işleme arabelleği ekleri. Çerçeve arabellekleri bölümünde tartıştığımız gibi normal eklentilere oldukça benzer.

Multisampled texture attachments

Birden çok örnek noktasının depolanmasını destekleyen bir doku oluşturmak için, doku hedefi olarak GL_TEXTURE_2D_MULTISAPLE'yi kabul eden glTexImage2D yerine glTexImage2DMultisample kullanıyoruz:

glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGB, width, height, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); 

İkinci argüman, dokuda olmasını istediğimiz örnek sayısını belirler. Son argüman GL_TRUE olarak ayarlanırsa, görüntü her bir metin için aynı örnek konumlarını ve aynı sayıda alt örneği kullanacaktır.

Bir çerçeve arabelleğine çok örnekli bir doku eklemek için glFramebufferTexture2D kullanıyoruz, ancak bu sefer doku türü olarak GL_TEXTURE_2D_MULTISAMPLE ile:

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0); 

Halihazırda bağlı çerçeve arabelleği, artık doku görüntüsü biçiminde çok örnekli bir renk arabelleğine sahiptir.

Multisampled renderbuffer objects

Dokular gibi, çok örnekli bir oluşturma arabelleği nesnesi oluşturmak zor değildir. Hatta oldukça kolaydır, çünkü (şu anda bağlı) renderbuffer'ın bellek deposunu yapılandırırken tek yapmamız gereken glRenderbufferStorage'dan glRenderbufferStorageMultisample'a değiştirmektir:

glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height);  

Burada değişen tek şey, kullanmak istediğimiz örnek miktarını belirlediğimiz ekstra ikinci parametredir; Bu özel durumda 4.

Render to multisampled framebuffer

Çok örnekli bir çerçeve arabelleğine işleme yapmak basittir. Çerçeve arabelleği nesnesi bağlıyken herhangi bir şey çizdiğimizde, rasterleştirici tüm çok örnekli işlemlerle ilgilenecektir. Ancak, çok örnekli bir arabellek biraz özel olduğundan, arabelleği bir gölgelendiricide örneklemek gibi diğer işlemler için doğrudan kullanamayız.

Çok örnekli bir görüntü, normal bir görüntüden çok daha fazla bilgi içerir, bu nedenle yapmamız gereken görüntüyü küçültmek veya çözmektir. Çok örnekli bir çerçeve arabelleğinin çözümlenmesi genellikle, bir bölgeyi bir çerçeve arabelleğinden diğerine kopyalarken aynı zamanda çok örnekli arabellekleri de çözen glBlitFramebuffer aracılığıyla yapılır.

glBlitFramebuffer, 4 ekran alanı koordinatıyla tanımlanan belirli bir kaynak bölgeyi, yine 4 ekran alanı koordinatıyla tanımlanan belirli bir hedef bölgeye aktarır. Framebuffers bölümünden, GL_FRAMEBUFFER'a bağlanırsak, hem okuma hem de çekme framebuffer hedeflerine bağlandığımızı hatırlayabilirsiniz. Çerçeve arabelleklerini sırasıyla GL_READ_FRAMEBUFFER ve GL_DRAW_FRAMEBUFFER'a bağlayarak bu hedeflere ayrı ayrı da bağlanabiliriz. glBlitFramebuffer işlevi, hangisinin kaynak ve hangisinin hedef çerçeve arabelleği olduğunu belirlemek için bu iki hedeften okur. Daha sonra, görüntüyü varsayılan çerçeve arabelleğine şu şekilde blitleyerek çoklu örneklenmiş çerçeve arabelleği çıktısını gerçek ekrana aktarabiliriz:

glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampledFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); 

Daha sonra aynı uygulamayı yapacak olsaydık, aynı çıktıyı almalıyız: MSAA ile görüntülenen ve yine önemli ölçüde daha az pürüzlü kenarlar gösteren kireç yeşili bir küp:

Kaynak kodunu burada bulabilirsiniz.

Peki ya çoklu örneklemeli çerçeve arabelleğinin doku sonucunu son işleme gibi şeyler yapmak için kullanmak istersek? Parça gölgelendiricide çoklu örneklenmiş dokuları doğrudan kullanamayız. Ancak yapabileceğimiz şey, çoklu örneklemeli arabellek(ler)i, çoklu örneklenmemiş bir doku ekiyle farklı bir FBO'ya bölmektir. Daha sonra bu sıradan renk ek dokusunu, çoklu örnekleme yoluyla oluşturulan bir görüntüyü son işlemden sonra etkin bir şekilde işlemek için kullanırız. Bu, çok örnekli arabelleği çözümlemek için yalnızca bir ara çerçeve arabelleği nesnesi olarak işlev gören yeni bir FBO oluşturmamız gerektiği anlamına gelir; parça gölgelendiricide kullanabileceğimiz normal bir 2B doku. Bu işlem, sözde kodda biraz şöyle görünür:

unsigned int msFBO = CreateFBOWithMultiSampledAttachments();
// then create another FBO with a normal texture color attachment
[...]
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0);
[...]
while(!glfwWindowShouldClose(window))
{
    [...]
    
    glBindFramebuffer(msFBO);
    ClearFrameBuffer();
    DrawScene();
    // now resolve multisampled buffer(s) into intermediate FBO
    glBindFramebuffer(GL_READ_FRAMEBUFFER, msFBO);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO);
    glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    // now scene is stored as 2D texture image, so use that image for post-processing
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    ClearFramebuffer();
    glBindTexture(GL_TEXTURE_2D, screenTexture);
    DrawPostProcessingQuad();  
  
    [...] 
}

Daha sonra bunu çerçeve arabellekleri bölümünün son işleme koduna uygularsak, bir sahnenin dokusu üzerinde (neredeyse) pürüzlü kenarları olmayan her türlü harika son işleme efektini oluşturabiliriz. Gri tonlamalı bir son işleme filtresi uygulandığında, şöyle görünecektir:

Ekran dokusu yine normal (çoklu örneklenmemiş) bir doku olduğundan, kenar algılama gibi bazı işlem sonrası filtreler yeniden pürüzlü kenarlar oluşturacaktır. Buna uyum sağlamak için daha sonra dokuyu bulanıklaştırabilir veya kendi kenar yumuşatma algoritmanızı oluşturabilirsiniz.

Çoklu örneklemeyi ekran dışı işleme ile birleştirmek istediğimizde, bazı ekstra adımlara dikkat etmemiz gerektiğini görebilirsiniz. Çoklu örnekleme, sahnenizin görsel kalitesini önemli ölçüde artırdığından, adımlar ekstra çabaya değer. Çoklu örneklemeyi etkinleştirmenin, kullandığınız örnek sayısı arttıkça performansı belirgin şekilde azaltabileceğini unutmayın.

Özel Anti-Aliasing Algoritması

Çok örnekli bir doku görüntüsünü, önce onu çözmek yerine doğrudan bir parça gölgelendiriciye geçirmek mümkündür. GLSL bize, kendi özel örtüşme önleme algoritmalarımızı oluşturabilmemiz için alt örnek başına doku görüntüsünü örnekleme seçeneği sunar.

Alt örnek başına bir doku değeri elde etmek için doku tek tip örnekleyiciyi normal sampler2D yerine bir sampler2DMS olarak tanımlamanız gerekir:

uniform sampler2DMS screenTextureMS;  

texelFetch işlevini kullanarak numune başına renk değerini almak mümkündür:

vec4 colorSample = texelFetch(screenTextureMS, TexCoords, 3);  // 4th subsample

Burada özel anti-aliasing teknikleri oluşturmanın ayrıntılarına girmeyeceğiz, ancak bunlar bir tane oluşturmaya başlamanız için yeterli olabilir.

Orijinal Kaynak: Anti-aliasing

Last updated