Geometry Shader

Vertex ve fragment shader arasında geometry shader adı verilen isteğe bağlı bir shader aşaması bulunur. Geometry shader, bir nokta veya üçgen gibi tek bir primitive oluşturan bir dizi vertex alır. Geometry shader daha sonra bu vertex'leri uygun gördüğü şekilde dönüştürerek bir sonraki shader aşamasına gönderir. Geometry shader'ı ilginç kılan şey, orijinal primitive'i (vertex kümesini) tamamen farklı primitive'lere dönüştürebilmesi ve başlangıçta verilenden daha fazla vertex üretebilmesidir.

Doğrudan bir örnek göstererek başlayalım:

#version 330 core
layout (points) in;
layout (line_strip, max_vertices = 2) out;

void main() {    
    gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0); 
    EmitVertex();

    gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0);
    EmitVertex();
    
    EndPrimitive();
}

Geometry shader'ın başında, vertex shader'dan aldığımız input primitive türünü belirtmemiz gerekir. Bunu in anahtar sözcüğünün önünde bir layout belirteci bildirerek yaparız. Bu input layout niteleyicisi aşağıdaki primitive değerlerinden herhangi birini alabilir:

  • points: GL_POINTS primitive'leri çizilirken (1).

  • lines: GL_LINES veya GL_LINE_STRIP çizilirken (2).

  • lines_adjacency: GL_LINES_ADJACENCY veya GL_LINE_STRIP_ADJACENCY (4).

  • triangles: GL_TRIANGLES, GL_TRIANGLE_STRIP veya GL_TRIANGLE_FAN (3).

  • triangles_adjacency: GL_TRIANGLES_ADJACENCY veya GL_TRIANGLE_STRIP_ADJACENCY (6).

Geometry shader'ın çıkaracağı primitive türünü de belirtmemiz gerekir; bunu out anahtar sözcüğünün önündeki layout belirteci ile yaparız. Output layout niteleyicisi birkaç primitive değeri alabilir:

  • points

  • line_strip

  • triangle_strip

Yalnızca bu 3 output belirteci ile input primitive'lerden neredeyse istediğimiz her şekli oluşturabiliriz. Geometry shader ayrıca üreteceği maksimum vertex sayısını belirlememizi de ister; bu sayıyı out anahtar sözcüğünün layout niteleyicisi içinde ayarlayabiliriz.

circle-info

Line strip nedir merak ediyorsanız: line strip, minimum 2 noktayla bir dizi noktayı birbirine bağlar ve aralarında sürekli bir çizgi oluşturur. Her ek nokta, yeni nokta ile önceki nokta arasında yeni bir çizgiye yol açar.

Line strip primitive

Önceki shader aşamasından anlamlı çıkışlar üretmek için bir yola ihtiyacımız var. GLSL bize dahili olarak şuna benzeyen gl_in adında bir yerleşik değişken sunar:

Bu, önceki bölümde tartıştığımız bir interface block olarak bildirilmiştir; en ilginci, vertex shader'ın çıkışı olarak ayarladığımız vektörü içeren gl_Position'dır. Bir dizi olarak bildirildiğine dikkat edin; çünkü çoğu render primitive'i birden fazla vertex içerir. Geometry shader, bir primitive'in tüm vertex'lerini input olarak alır.

Vertex shader aşamasından gelen vertex verisiyle, EmitVertex ve EndPrimitive adı verilen iki geometry shader fonksiyonu kullanarak yeni veri üretebiliriz. Geometry shader, output olarak belirttiğiniz primitive'lerden en az birini üretmenizi bekler.

EmitVertex'i her çağırdığımızda, o an gl_Position'a ayarlanmış olan vektör output primitive'e eklenir. EndPrimitive çağrıldığında, bu primitive için yayımlanan tüm vertex'ler belirtilen output render primitive'de birleştirilir. Bu örnek, orijinal vertex konumundan küçük bir offset kadar ötelenen iki vertex yayımlar; ardından EndPrimitive çağırarak 2 vertex'li tek bir line strip oluşturur.

Sonuç olarak bu geometry shader, bir nokta primitive'ini input alır ve bu noktayı merkez kabul eden yatay bir çizgi primitive'i üretir.


Geometry Shader Kullanımı

Geometry shader'ın kullanımını göstermek için normalize edilmiş cihaz koordinatlarında z-düzleminde 4 nokta çizeceğimiz basit bir sahne oluşturacağız:

Vertex shader:

Fragment shader tüm noktalar için yeşil rengi doğrudan çıkarır:

VAO ve VBO oluşturup glDrawArrays ile çiziriz. Sonuç, sahneye karanlık bir arka planda 4 yeşil nokta:

4 nokta

Öğrenme amaçlı önce bir pass-through geometry shader oluşturalım; bu shader bir nokta primitive'ini input alır ve değiştirmeden sonraki shader'a iletir:

Geometry shader, tıpkı vertex ve fragment shader gibi derlenmeli ve programa bağlanmalıdır; bu sefer GL_GEOMETRY_SHADER shader türüyle:


Ev İnşa Edelim

Geometry shader'ı daha yaratıcı kullanarak her nokta konumunda bir ev çizeceğiz. Bunun için geometry shader çıkışını triangle_strip olarak ayarlayıp üç üçgen çizeceğiz: kare evin iki si ve çatı için biri.

OpenGL'de triangle strip, daha az vertex ile üçgenleri daha verimli çizmek için kullanılır. İlk üçgen çizildikten sonra her yeni vertex ilk üçgenin yanına bir üçgen daha ekler: her 3 bitişik vertex bir üçgen oluşturur:

Triangle strip

Triangle strip'i geometry shader'ın çıkışı olarak kullanarak, doğru sırada 3 bitişik üçgen üretip ev şeklini oluşturabiliriz:

Ev diyagramı

Bu, şu geometry shader'a dönüşür:

Bu geometry shader 5 vertex üretir ve ortaya çıkan primitive rasterize edilir; fragment shader tüm triangle strip üzerinde çalışır. Her çizdiğimiz nokta için yeşil bir ev elde edilir:

Yeşil evler

Her evin tek bir konum noktasından oluşturulduğunu görebilirsiniz. Biraz daha renkli yapmak için vertex shader'a per-vertex renk bilgisi ekleyelim ve interface block aracılığıyla geometry shader'a iletelim:

Vertex shader:

Ve geometry shader'da aynı interface block'u farklı bir instance adıyla tanımlarız:

Geometry shader bir dizi vertex'i input aldığından, vertex shader'dan gelen input verisi dizi olarak temsil edilir. Fragment shader için bir output renk vektörü de tanımlarız:

Fragment shader yalnızca tek bir (interpolated) renk beklediğinden, fColor bir dizi değil tek bir vektördür. Bir vertex yayımlandığında, o vertex çıkış değeri olarak fColor'da saklanan son değeri taşır:

Tüm evlerin artık kendi rengi var:

Renkli evler

Kış havasına getirmek için çatı vertex'ine farklı bir renk verebiliriz:

Sonuç:

Karlı çatılı evler

Kaynak kodunu buradanarrow-up-right bulabilirsiniz.


Patlayan Nesneler

Şimdi biraz ileri gidelim ve nesneleri patlatalım! Her üçgeni kendi normal vektörü yönünde kısa bir süre boyunca öteliyoruz. Etkisi tüm nesnenin üçgenlerinin patlamış gibi görünmesidir; bu efektin sırt çantası modelindeki görünümü:

Patlama efekti

Böyle bir geometry shader efektinin güzel yanı, nesnenin karmaşıklığından bağımsız olarak tüm nesneler üzerinde çalışmasıdır.

Her vertex'i üçgenin normal vektörü yönünde ötelemek için önce bu normal vektörü hesaplamamız gerekir. Erişimimizdeki 3 vertex'i kullanarak üçgenin yüzeyine dik vektörü hesaplarız; bu işlemi çapraz çarpım (cross product) ile yaparız. Bunu yapan geometry shader fonksiyonu:

Ardından bu normal vektörü ve bir vertex konum vektörünü alan bir explode fonksiyonu oluştururuz:

sin fonksiyonu time uniform değişkenini argüman olarak alır ve [-1.0, 1.0] arasında bir değer döndürür. Nesneyi içine çöktürmemek için bunu [0,1] aralığına dönüştürürüz. Elde edilen değer normal vektörü ölçeklemek için kullanılır ve direction vektörü konum vektörüne eklenir.

Model yükleyicimizle yüklenen bir model için explode efektinin tam geometry shader'ı:

OpenGL kodunuzda time uniform'unu ayarlamayı unutmayın:


Normal Vektörleri Görselleştirmek

Geometry shader'ın gerçekten faydalı bir kullanımına geçelim: herhangi bir nesnenin normal vektörlerini görselleştirmek. Işıklandırma shader'larını programlarken yanlış normal vektörlerinden kaynaklanan garip görsel çıktılarla karşılaşabilirsiniz. Normal vektörlerin doğru olup olmadığını belirlemenin harika bir yolu onları görselleştirmektir; işte tam da bu amaç için geometry shader son derece kullanışlı bir araçtır.

Fikir şöyle: önce sahneyi geometry shader olmadan normal şekilde çiziyoruz, ardından geometry shader aracılığıyla oluşturulan normal vektörlerini görüntüleyerek ikinci bir kez çiziyoruz. Geometry shader, input olarak bir üçgen primitive alır ve her vertex için normal yönünde 3 hatla 3 normal vektörünü görüntüler:

Bu sefer kendi normal vektörümüzü oluşturmak yerine model tarafından sağlanan vertex normal'larını kullanan bir geometry shader yazıyoruz. Normal'ları ölçekleme ve döndürmeler nedeniyle normal matrisiyle dönüştürüyoruz:

Normal vektörlerini görüntüleyen geometry shader:

Fragment shader normal vektörlerini tek renk çizgi olarak gösterir:

Modelinizi önce normal shader'larla, ardından özel normal-görselleştirme shader'ıyla render ettiğinizde şuna benzer bir sonuç elde edersiniz:

Normal vektörleri görselleştirme

Sırt çantamız biraz tüylü görünse de bu, bir modelin normal vektörlerinin gerçekten doğru olup olmadığını belirlemenin çok kullanışlı bir yöntemini sunuyor. Bunun gibi geometry shader'ların nesnelere tüy/yaprak eklemek için de kullanılabileceğini hayal edebilirsiniz.

Kaynak kodu buradaarrow-up-right.

Last updated