Dokular

Nesnelerimize daha fazla ayrıntı eklemek için her köşe noktasında renkleri kullanabileceğimizi öğrendik. Bununla birlikte, daha fazla gerçekçilik elde etmek için birçok köşe noktasına sahip olmamız gerekir, böylece çok fazla renk belirleyebiliriz. Her model çok daha fazla köşe noktasına ve her köşe noktası için bir renk özniteliğine ihtiyaç duyduğundan, bu önemli miktarda ek yük gerektirir.

Sanatçıların ve programcıların genel olarak tercih ettikleri şey doku kullanmaktır. Doku, bir nesneye ayrıntı eklemek için kullanılan iki boyutlu görüntüdür (1B ve 3B dokular bile vardır). Bir dokuyu 3B evinizin üzerinde düzgün bir şekilde katlanmış güzel bir tuğla görüntüsüne sahip bir kağıt parçası olarak düşünün, böylece evinizin taş bir dış cephesi olacaktır. Tek bir görüntüye çok fazla ayrıntı ekleyebildiğimiz için, nesnenin fazla ayrıntılı olduğu yanılsamasını fazladan köşe noktası belirtmek zorunda kalmadan verebiliriz.

İmgeler dışında, dokular gölgelendiricilere gönderilmek üzere geniş bir veri koleksiyonunu saklarken de kullanılabilir, ancak bunu farklı bir konu başlığı altında değerlendireceğiz.

Aşağıda, önceki bölümde sahnelediğimiz üçgene eşlenmiş bir tuğla duvarın doku görüntüsünü göreceksiniz.

Bir dokuyu üçgene eşlemek için, üçgenin her köşe noktasına, dokunun hangi kısmına karşılık geldiğini söylememiz gerekir. Dolayısıyla her köşe noktasında, doku görüntüsünün hangi kısmından örnek alınacağını belirleyen bir doku koordinatı bulunmalıdır. Parça interpolasyonu (ing. fragment interpolation) diğer parçalar için gerisini halledecektir..

Doku koordinatları x ve y ekseninde 0 ile 1 arasında değişir (2B doku imgelerini kullandığımızı unutmayın). Doku koordinatlarını kullanarak doku rengini elde etmeye örnekleme (ing. sampling) denir. Doku koordinatları, doku imgesinin sol alt köşesi için (0,0) ile başlar ve doku imgesinin sağ üst köşesi için (1,1) olur. Aşağıdaki imge doku koordinatlarını üçgene nasıl eşlediğimizi göstermektedir:

Üçgen için 3 doku koordinat noktası belirleriz. Üçgenin sol alt tarafının, dokumuzun sol alt tarafına karşılık gelmesini istiyoruz, bu nedenle üçgenin sol alt köşesi için (0,0) doku koordinatını kullanıyoruz. Aynısı (1,0) doku koordinatına sahip sağ alt taraf için de geçerlidir. Üçgenin tepesi, doku imgesinin üst merkezine karşılık gelmelidir, bu nedenle (0.5,1.0)’ı doku koordinatı olarak alırız. Köşe nokta gölgelendiricisine yalnızca 3 doku koordinatını geçirmeliyiz, bu daha sonra her bir parça için tüm doku koordinatlarını düzgün bir şekilde interpole eden parça gölgelendiricisine geçirir.

Sonuçta elde edilen doku koordinatları şöyle görünür:

float texCoords[] = {
    0.0f, 0.0f,  // sol alt köşe noktası
    1.0f, 0.0f,  // sağ alt köşe noktası
    0.5f, 1.0f   // merkez tepe köşe noktası
};

Doku örneklemesinin esnek bir yorumu vardır ve birçok farklı şekilde yapılabilir. Dolayısıyla OpenGL’e dokularını nasıl örneklemesi gerektiğini söylemek bizim işimiz.

Doku Örtüleme (ing.Texture Wrapping)

Doku koordinatları genellikle (0,0) ila (1,1) arasında değişir, ancak bu aralığın dışındaki koordinatları belirtirsek ne olur? OpenGL’in varsayılan davranışı doku imgelerini tekrarlamaktır (temel olarak float nokta doku koordinatının tamsayı bölümünü yok sayıyoruz), ancak OpenGL’in sunduğu daha fazla seçenek var:

  • GL_REPEAT: Dokular için varsayılan davranıştır. Doku imgesini tekrarlar.

  • GL_MIRRORED_REPEAT: GL_REPEAT ile aynıdır ancak imgeyi her tekrarında yansıtmaktadır.

  • GL_CLAMP_TO_EDGE: Koordinatları 0 ile 1 arasında sıkıştırır. Sonuç, daha yüksek koordinatların kenara kenetlenerek gerilmiş bir örüntüdür.

  • GL_CLAMP_TO_BORDER: Aralık dışındaki koordinatlara kullanıcı tanımlı bir kenarlık rengi verilir.

Varsayılan aralığın dışında doku koordinatları kullanılırken seçeneklerin her biri farklı bir görsel çıktıya sahiptir. Örnek bir doku imgesinde bunların nasıl göründüğüne bakalım:

Yukarıda belirtilen seçeneklerin her biri, glTexParameter* işleviyle koordinat ekseni (s, t, r; x, y, z’ye eşdeğer) başına ayarlanabilir:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

İlk argüman doku hedefini belirtir; 2B dokularla çalışıyoruz, bu yüzden doku hedefi GL_TEXTURE_2D olmaktadır. İkinci argüman, hangi seçeneği belirlemek istediğimizi ve hangi doku ekseni olduğunu söylemektedir. WRAP seçeneğini yapılandırmak ve hem S hem de T ekseni için belirtiyoruz. Son argüman, istediğimiz doku örtüleme modundan geçmemizi gerektiriyor ve bu durumda OpenGL, doku örtüleme seçeneğini GL_MIRRORED_REPEAT ile o anda aktif olan dokuya ayarlayacaktır.

GL_CLAMP_TO_BORDER seçeneğini seçersek, bir kenarlık rengi de belirtmeliyiz. Bu, kenarlığın renk değerinin bir float dizisine geçtiğimiz seçenek olarak gl_TexTURE_BORDER_COLOR ile glTexParameter işlevinin fv değeri kullanılarak yapılır:

float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); 

Doku Filteleme (ing. Texture Filtering)

Doku koordinatları çözünürlüğe bağlı değildir, ancak herhangi bir float değeri olabilir. Bu nedenle OpenGL, doku koordinatını eşleştirmek için hangi doku pikselinin (texel olarak da bilinir) olduğunu bulmalıdır. Bu, özellikle çok büyük bir nesneye ve düşük çözünürlüklü bir dokuya sahipseniz önemlidir. Muhtemelen şimdiye kadar OpenGL’in bu doku filtreleme için de seçenekleri olduğunu tahmin ettiniz. Birkaç seçenek var, ancak şimdilik en önemli seçenekleri tartışacağız: GL_NEAREST ve GL_LINEAR.

GL_NEAREST (en yakın komşu filtreleme olarak da bilinir), OpenGL’in varsayılan doku filtreleme yöntemidir. GL_NEAREST olarak ayarlandığında, OpenGL merkez doku koordinatına en yakın olan pikseli seçer. Aşağıda, doku koordinatını temsil eden 4 piksel görebilirsiniz. Sol üst doku hücresi (ing. texel) merkezi doku koordinatına en yakın olanıdır ve bu nedenle örneklenen renk olarak seçilir:

GL_LINEAR (doğrusal filtreleme olarak da bilinir), doku koordinatının komşu metinlerinden interpolasyonlu bir değer alır ve metinler arasındaki bir renge yakınsar. Doku koordinatından bir doku hücresinin merkezine olan mesafe ne kadar küçük olursa, doku hücresinin renginin örneklenen renge o kadar katkısı bulunur. Aşağıda, komşu piksellerin karışık bir renginin döndüğünü görebiliriz:

Fakat böyle bir doku filtreleme yönteminin görsel etkisi nedir? Büyük bir nesne üzerinde düşük çözünürlüklü bir doku kullanırken bu yöntemlerin nasıl çalıştığını görelim (bu nedenle doku yukarı doğru ölçeklendirilir):

GL_NEAREST, dokuyu oluşturan pikselleri açıkça görebildiğimiz bloke edilmiş örüntülerle sonuçlanırken GL_LINEAR, tek tek piksellerin daha az görünür olduğu daha pürüzsüz bir örüntü üretir. GL_LINEAR daha gerçekçi bir çıktı üretmektedir, ancak bazı geliştiriciler 8 bitlik bir görünümü tercih eder ve sonuç olarak GL_NEAREST’i seçer.

Doku filtreleme, büyütme ve küçültme işlemleri yukarı veya aşağı ölçeklendirme sırasında ayarlanabilir, örneğin dokular aşağı doğru ölçeklendiğinde en yakın komşu filtrelemeyi; yükseltilmiş dokular için doğrusal filtrelemeyi kullanabilirsiniz. Bu nedenle glTexParameter* ile her iki seçenek için de filtreleme yöntemini belirtmemiz gerekiyor. Kod, örtüleme yöntemini ayarlamaya benzer olmalıdır:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Mipmap

Her biri eklenmiş bir dokuya sahip binlerce nesne içeren geniş bir odamız olduğunu düşünün. Uzakta, izleyiciye yakın nesnelerle aynı yüksek çözünürlüklü dokuya sahip nesneler olacaktır. Nesneler uzak olduğundan ve muhtemelen sadece birkaç parça ürettiğinden, OpenGL parçasının doğru renk değerini yüksek çözünürlüklü dokudan almakta zorluk çeker, çünkü dokunun büyük bir kısmını kapsayan parça için bir doku rengi seçmelidir. Küçük nesneler üzerinde yüksek çözünürlüklü dokular kullanmak için bellek israfından bahsetmiyorum bile…

Bu sorunu çözmek için OpenGL, her bir sonraki dokunun bir öncekine göre iki kat daha küçük olduğu doku imgelerinin bir koleksiyonu olan mipmap adlı bir kavram kullanır. Mipmap’ların arkasındaki fikrin anlaşılması kolaydır. İzleyiciden belirli bir mesafe eşiğinden sonra, OpenGL nesneye olan mesafeye en uygun farklı bir mipmap dokusu kullanır. Nesne uzak olduğunda, daha küçük çözünürlük kullanıcı tarafından fark edilmeyecektir. Ayrıca, mipmap’lerin performans için de iyi oldukları da avantajdır. Mipmap biçimine getirilmiş bir dokunun neye benzediğine daha yakından bakalım:

Her doku imgesi için mipmap’li dokular koleksiyonu oluşturmayı elle yapmak zahmetlidir, neyse ki OpenGL, bir doku oluşturduktan sonra tek bir glGenerateMipmaps çağrısı ile tüm işleri yapabilir. Daha sonraki bölümde bu işlevin kullanımını göreceksiniz.

Sahneleme sırasında mipmap seviyeleri arasında geçiş yaparken OpenGL, iki mipmap katmanı arasında görünen keskin kenarlar gibi bazı yapay öğeler gösterebilir. Tıpkı normal doku filtrelemesinde olduğu gibi, mipmap seviyeleri arasında geçiş yapmak için NEAREST ve LINEAR filtreleme kullanarak mipmap düzeyleri arasında filtreleme yapmak da mümkündür. Mipmap düzeyleri arasındaki filtreleme yöntemini belirtmek için orijinal filtreleme yöntemlerini aşağıdaki dört seçenekten biriyle değiştirebiliriz:

  • GL_NEAREST_MIPMAP_NEAREST: piksel boyutuna uyacak en yakın mipmap’i alır ve doku örneklemesi için en yakın komşu interpolasyonunu kullanır.

  • GL_LINEAR_MIPMAP_NEAREST: en yakın mipmap seviyesini ve lineer interpolasyonu kullanarak örnekleri alır.

  • GL_NEAREST_MIPMAP_LINEAR: en yakın komşu interpolasyonu yoluyla bir pikselin ve örneklerin boyutuna en yakın eşleşen iki mipmap arasında doğrusal olarak interpolasyon gerçekleştirir.

  • GL_LINEAR_MIPMAP_LINEAR: en yakın iki mipmap arasında doğrusal olarak interpolasyon yapar ve dokuyu doğrusal interpolasyon yoluyla örnekler.

Tıpkı doku filtrelemede olduğu gibi, filtreleme yöntemini glTexParameteri kullanarak yukarıda belirtilen 4 yöntemden birine ayarlayabiliriz:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Yaygın bir hata olarak, mipmap filtreleme seçeneklerinden birini büyütme filtresi olarak ayarlamak örnek verilebilir. Mipmap’ların esas olarak dokular küçültüldüğünde kullanıldığı için bunun herhangi bir etkisi yoktur. Doku büyütmede mipmap kullanmaz ve mipmap filtreleme seçeneği verildiğinde OpenGL GL_INVALID_ENUM hata kodunu oluşturur.

Doku Yaratma ve Yükleme

Dokuları kullanmak için ilk yapmamız gereken şey, bunları uygulamamıza yüklemektir. Doku imgeleri, her biri kendi yapısı ve veri sırasına sahip düzinelerce dosya biçiminde saklanabilir. Peki bu imgeleri uygulamamıza nasıl alabiliriz? Çözümlerden biri, örneğin .PNG kullanmak istediğimiz bir dosya biçimini seçmek ve imge biçimini geniş bir bayt dizisine dönüştürmek için kendi imge yükleyicimizi yazmak olacaktır. Kendi imge yükleyicinizi yazmak çok zor değildir, ancak yine de hantaldır ve daha fazla dosya biçimini desteklemek istediğinizde ne olacak? Desteklemek istediğiniz her biçim için bir imge yükleyici yazmanız gerekir.

Başka bir çözüm ve muhtemelen iyi bir çözüm, birkaç popüler formatı destekleyen ve bizim için tüm zor işleri yapan bir imge yükleme kütüphanesi kullanmaktır. stb_image.h gibi bir kütüphane.

stb_image.h

stb_image.h, Sean Barrett tarafından oluşturulmuş en popüler dosya formatlarını yükleyebilen ve projelerinize kolayca entegre edilebilen çok popüler tek başlıklı imge yükleme kütüphanesidir. stb_image.h buradan indirilebilir. Tek başlık dosyasını indirin, projenize stb_image.h olarak ekleyin ve aşağıdaki kodu ekleyerek bir C ++ dosyası oluşturun:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

STB_IMAGE_IMPLEMENTATION önişlemcisini tanımlayarak başlık dosyası yalnızca ilgili tanım kaynak kodunu içerecek şekilde değiştirilir, başlık dosyasını etkili bir şekilde bir .cpp dosyasına dönüştürür. Şimdi stb_image.h dosyasını programınızın herhangi bir yerine dahil edin ve derleyin.

Aşağıdaki doku bölümleri için ahşap bir konteynerin imgesini kullanacağız. stb_image.h ile bir imge yüklemek için stbi_load işlevini kullanıyoruz:

int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0); 

İşlev girdi olarak önce bir imge dosyasının konumunu alır. Daha sonra, stb_image.h'nin elde edilen imgesinin genişliği, yüksekliği ve renk kanalı sayısı ile dolduracağı ikinci, üçüncü ve dördüncü argümanı olarak üç adet int değer vermenizi bekler. Daha sonra doku oluşturmak için imgenin genişliğine ve yüksekliğine ihtiyacımız vardır.

Bir doku oluşturma

OpenGL' deki önceki nesnelerden herhangi biri gibi, dokulara da bir ID ile başvurulur; bir tane oluşturalım:

unsigned int texture;
glGenTextures(1, &texture);

glGenTextures işlevi önce kaç tane doku oluşturmak istediğimizi girdi olarak alır ve bunları ikinci argümanı olarak verilen unsigned int dizisinde saklar (bizim örneğimizde tek bir unsigned int). Diğer nesneler gibi, onu bağlamamız gerekir, böylece sonraki doku komutları o anda bağlı olan dokuyu yapılandıracaktır:

glBindTexture(GL_TEXTURE_2D, texture); 

Artık doku bağlandığına göre, önceden yüklenmiş imge verilerini kullanarak bir doku oluşturmaya başlayabiliriz. Dokular glTexImage2D ile oluşturulur:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);

Bu, birkaç parametreye sahip büyük bir işlevdir, bu nedenle adım adım ilerleyeceğiz:

  • İlk argüman doku hedefini belirtir; bunu GL_TEXTURE_2D olarak ayarlamak, bu işlemin o anda bağlı olan doku nesnesinde aynı hedef için bir doku üreteceği anlamına gelir (böylece GL_TEXTURE_1D veya GL_TEXTURE_3D hedeflerine bağlı dokular etkilenmeyecektir).

  • İkinci argüman, her bir mipmap seviyesini manuel olarak ayarlamak istiyorsak, doku oluşturmayı istediğimiz mipmap seviyesini belirtir, ancak bunu 0 olarak bırakacağız.

  • Üçüncü argüman OpenGL' e dokuyu ne tür bir formatta saklamak istediğimizi söyler. İmgemiz sadece RGBdeğerlerine sahip olduğundan dokuyu RGBdeğerleriyle saklayacağız.

  • 4. ve 5. bağımsız değişken, elde edilen dokunun genişliğini ve yüksekliğini ayarlar. İmgeyi yüklerken bunları saklamıştık, bu sayede ilgili değişkenleri kullanacağız.

  • Bir sonraki argüman her zaman 0 olmalıdır (tecrübelerimize göre ).

  • 7. ve 8. bağımsız değişken, kaynak imgenin biçimini ve veri türünü belirtir. İmgeyi RGB değerleriyle yükledik ve char(bayt) olarak sakladık, böylece ilgili değerleri ileteceğiz

  • Son argüman gerçek imge verileridir.

Dokuyu ve karşılık gelen mipmap'leri oluşturduktan sonra, imge belleğini temizlemek iyi bir alışkanlıktır:

stbi_image_free(data);

Bir doku oluşturma sürecinin tamamı aşağıdaki gibidir:

unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// set the texture wrapping/filtering options (on the currently bound texture object)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// load and generate the texture
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
    std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);

Dokuları Uygulama

Gelecek bölümler için, Merhaba Üçgen eğitselinin son bölümünde glDrawElements ile çizilen dikdörtgen şeklini kullanacağız. OpenGL'e dokuyu nasıl örnekleyeceğini bildirmeliyiz, böylece köşe nokta verilerini doku koordinatlarıyla güncelleriz:

float vertices[] = {
    // positions          // colors           // texture coords
     0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // top right
     0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // bottom right
    -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // bottom left
    -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // top left 
};

Ek bir köşe noktası özniteliği eklediğimizden, OpenGL'e yeni köşe noktası formatını tekrar bildirmemiz gerekiyor:

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);

Önceki iki köşe noktası özniteliğinin stride parametresini de 8* sizeof (float) olarak ayarlamamız gerektiğini unutmayın.

Ardından, doku koordinatlarını bir köşe noktası özniteliği olarak kabul etmek ve ardından koordinatları parça gölgelendiriciye iletmek için köşe nokta gölgelendiricisini değiştirmemiz gerekir:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 ourColor;
out vec2 TexCoord;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
}

Parça gölgelendirici TexCoord çıktı değişkenini, bir girdi değişkeni olarak kabul etmelidir.

Parça gölgelendiricinin ayrıca doku nesnesine erişimi olmalıdır, ancak doku nesnesini parça gölgelendiricisine nasıl geçiririz? GLSL, sampler (tr. örnekleyici) adı verilen doku nesneleri için, istediğimiz doku türünü son ek olarak alan yerleşik bir veri türüne sahiptir; sampler1D, sampler3D veya bizim durumumuzda sampler2D. Daha sonra, dokuya atayacağımız tek tip bir sampler2D bildirerek parça gölgelendiriciye bir doku ekleyebiliriz.

#version 330 core
out vec4 FragColor;
  
in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D ourTexture;

void main()
{
    FragColor = texture(ourTexture, TexCoord);
}

Bir dokunun rengini örneklemek için, ilk argüman olarak bir doku örneği ve ikinci argüman olarak karşılık gelen doku koordinatını alan GLSL' in yerleşik doku işlevini kullanırız. Doku işlevi, önceden ayarladığımız doku parametrelerini kullanarak karşılık gelen renk değerini örnekler. Bu parça gölgelendiricinin çıktısı daha sonra (interpolasyonlu) doku koordinatındaki dokunun (filtrelenmiş) rengidir.

Şimdi tek yapmanız gereken, glDrawElements öğesini çağırmadan önce dokuyu bağlamaktır ve dokuyu otomatik olarak parça gölgelendiricinin örnekleyicisine atayacaktır:

glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

Her şeyi doğru yaptıysanız, aşağıdaki imgeyi görmelisiniz:

Dikdörtgen tamamen beyaz veya siyahsa, muhtemelen bir hata yaptınız. Gölgelendirici log dosyalarınızı kontrol edin ve kodunuzu uygulamanın kaynak koduyla karşılaştırmayı deneyin.

Doku kodunuz çalışmıyorsa veya tamamen siyah görünüyorsa, okumaya devam edin ve çalışması gereken son kadar örneğe gidin. Bazı sürücülerde, örnekleyici uniform'a bu eğitselde daha ayrıntılı olarak tartışacağımız bir doku birimi atamak gerekir.

Ortaya çıkan doku rengini köşe renkleri ile de karıştırabiliriz. Elde edilen doku rengini, her iki rengi karıştırmak için parça gölgelendiricideki köşe rengi ile çarpıyoruz:

FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0); 

Sonuç, köşe noktasının rengiyle doku renginin bir karışımı olmalıdır:

Doku Birimleri

glUniform ile bir değer atamasak bile, sampler2D değişkeninin neden tek tip olduğunu muhtemelen merak ettiniz. glUniform1i kullanarak aslında doku örnekleyicisine bir konum değeri atayabiliriz, böylece bir parça gölgelendiricide aynı anda birden fazla doku ayarlayabiliriz. Bir dokunun bu konumu daha çok doku birimi olarak bilinir. Bir doku için varsayılan doku birimi, varsayılan etkin doku birimi olan 0'dır, bu nedenle önceki bölümde bir konum atamak zorunda kalmadık; tüm grafik sürücülerinin varsayılan doku birimi atamadığından önceki bölümün sizin için sahnelenmemiş olabileceğini unutmayın.

Doku birimlerinin temel amacı gölgelendiricilerimizde birden fazla doku kullanmamıza izin vermektir. Örnekleyicilere doku birimleri atayarak, önce ilgili doku birimini etkinleştirdiğimiz sürece aynı anda birden fazla dokuya bağlanabiliriz. Tıpkı glBindTexture gibi, kullanmak istediğimiz doku biriminden geçen glActiveTexture'ı kullanarak doku birimlerini etkinleştirebiliriz:

glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
glBindTexture(GL_TEXTURE_2D, texture);

Bir doku birimini etkinleştirdikten sonra, glBindTexture çağrısı bu dokuyu o anda etkin doku birimine bağlar. Doku birimi GL_TEXTURE0 her zaman varsayılan olarak etkindir, bu nedenle glBindTexture kullanırken önceki örnekte herhangi bir doku birimini etkinleştirmek zorunda değildik.

OpenGL, GL_TEXTURE0 ila GL_TEXTURE15 kullanarak etkinleştirebileceğiniz en az 16 doku birimine sahip olmalıdır. Sırasıyla tanımlanırlar, böylece GL_TEXTURE0 + 8 aracılığıyla GL_TEXTURE8'i de alabiliriz, bu da birkaç doku birimi üzerinde döngü yapmamız gerektiğinde yararlıdır.

Bununla birlikte, başka bir örnekleyiciyi kabul etmek için parça gölgelendiriciyi düzenlememiz gerekiyor. Bu şimdi nispeten daha basit olmalı:

#version 330 core
...

uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

Son çıktı rengi iki dokunun birleşimidir. GLSL’ in yerleşik mix işlevi giriş olarak iki değer alır ve üçüncü argümanına dayanarak aralarında doğrusal olarak interpolasyon yapar. Üçüncü değer 0.0 ise ilk girdiyi döndürür; 1.0 ise ikinci girdi değerini döndürür. 0.2 değeri ise, ilk girdi renginin %80'ini ve ikinci girdi renginin %20'sini döndürerek her iki dokumuzun da karışımını verir.

Şimdi başka bir doku yüklemek ve yaratmak istiyoruz; artık adımlara daha aşina olmalısınız. glTexImage2D kullanarak başka bir doku nesnesi oluşturduğunuzdan, imgeyi yüklediğinizden ve son dokuyu oluşturduğunuzdan emin olun. İkinci doku için OpenGL öğrenirken sahip olduğunuz yüz ifadenizin bir imgesini kullanacağız:

unsigned char *data = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
if (data)
{
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
}

Şimdi alfa (saydamlık) kanalı içeren bir .png imgesi yüklediğimizi unutmayın. Bu, imge verilerinin GL_RGBA kullanarak da bir alfa kanalı içerdiğini belirtmemiz gerektiği anlamına gelir; aksi takdirde OpenGL imge verilerini yanlış yorumlayacaktır.

İkinci dokuyu (ve ilk dokuyu) kullanmak için, her iki dokuya da karşılık gelen doku birimini bağlayarak oluşturma yöntemini biraz değiştirmeniz gerekir:

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);

glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

Ayrıca glUniform1i kullanırken her örnekleyiciyi ayarlayarak OpenGL'e her gölgelendirici örnekleyicisinin hangi doku birimine ait olduğunu söylememiz gerekir. Bunu yalnızca bir kez ayarlamamız gerekir, böylece sahneleme döngüsüne girmeden önce bunu sağlamış oluruz:

ourShader.use(); // don't forget to activate the shader before setting uniforms!  
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // set it manually
ourShader.setInt("texture2", 1); // or with shader class
  
while(...) 
{
    [...]
}

Örnekleyicileri glUniform1i aracılığıyla ayarlayarak, her bir tek örnekleyicinin uygun doku birimine karşılık geldiğinden emin oluruz. Aşağıdaki sonucu elde etmeliyiz:

Muhtemelen dokunun ters çevrildiğini fark ettiniz. Bunun nedeni, OpenGL' in y-eksenindeki 0.0 koordinatının imgenin alt tarafında olmasını beklemesidir, ancak imgeler genellikle y-ekseninin üstündedir. Neyse ki bizim için, stb_image.h herhangi bir imge yüklemeden önce aşağıdaki ifadeyi ekleyerek imge yükleme sırasında y-eksenini çevirebilir:

stbi_set_flip_vertically_on_load(true); 

İmgeleri yüklerken stb_image.h öğesine y-eksenini çevirmesini söyledikten sonra aşağıdaki sonucu elde etmelisiniz:

Gülen yüzlü bir konteyner görürseniz, işleri doğru yaptınız demektir. Kaynak kodla karşılaştırabilirsiniz.

Alıştırmalar

Dokular konusunda daha iyi olmak için, diğer eğitsellere devam etmeden önce bu alıştırmaları yapmanız önerilir.

  • Yalnızca parça gölgelendiricisini değiştirerek gülen yüzün diğer/ters yöne bakmasını sağlayın: çözüm.

  • 0.0f ila 1.0f yerine 0.0f ila 2.0f aralığında doku koordinatları belirterek farklı doku örtüleme yöntemlerini deneyin. Kenarına sıkıştırılmış tek bir konteyner imgesinde 4 gülen yüz görüntüleyip görüntüleyemeyeceğinize bakın: çözüm, sonuç. Diğer örtüleme yöntemlerini de deneyin.

  • Dikdörtgen üzerindeki doku görüntüsünün yalnızca orta piksellerini, doku koordinatlarını değiştirerek tek tek piksellerin görünebileceği şekilde görüntülemeye çalışın. Pikselleri daha net görmek için doku filtreleme yöntemini GL_NEAREST olarak ayarlamayı deneyin:çözüm.

  • İki dokunun görülebileceği miktarı değiştirmek için mix işlevinin üçüncü parametresi olarak düzgün bir değişken kullanın. Konteyner veya gülen yüzün ne kadar görünür olacağını değiştirmek için yukarı ve aşağı ok tuşlarını kullanın: çözüm.

Orijinal Kaynak: Textures

Çeviri: Nezihe Sözen

Last updated