Çoklu Işık Kaynakları
Önceki bölümlerde OpenGL’de aydınlatma hakkında birçok şey öğrendik. Phong shader, materyaller, aydınlatma haritaları ve farklı ışık türleri gibi konulara değindik. Bu bölümde, önceki bilgileri birleştirerek 6 aktif ışık kaynağına sahip tam aydınlatılmış bir sahne oluşturacağız. Güneş benzeri bir ışık kaynağını yönlendirilmiş ışık (directional light) olarak simüle edeceğiz, sahne boyunca 4 noktasal ışık (point light) yerleştireceğiz ve ayrıca bir el feneri (flashlight) ekleyeceğiz.
Bir sahnede birden fazla ışık kaynağı kullanırken, aydınlatma hesaplamalarını GLSL fonksiyonlarına kapsüllemek (encapsulate) istiyoruz. Bunun sebebi, birden fazla ışık türüyle çalışırken her biri farklı hesaplamalar gerektirdiğinden kodun hızla karmaşık hale gelmesidir. Eğer tüm bu hesaplamaları yalnızca ana fonksiyon (main function) içinde yapmaya çalışırsak, kod hızla anlaşılması zor bir hale gelir.
GLSL’de fonksiyonlar, C fonksiyonları ile benzer şekilde çalışır. Bir fonksiyon adı, bir dönüş tipi (return type) ve eğer fonksiyon ana fonksiyondan önce tanımlanmamışsa kod dosyasının üst kısmında bir prototip tanımlaması yapmamız gerekir. Yönlendirilmiş ışık (directional light), noktasal ışık (point light) ve spot ışık (spotlight) olmak üzere her bir ışık türü için ayrı fonksiyonlar oluşturacağız.
Bir sahnede birden fazla ışık kaynağı kullanırken genellikle şu yaklaşım izlenir: Her shader’ın çıktı rengini temsil eden tek bir renk vektörümüz olur. Sahnedeki her ışık kaynağı, shader’ın çıktısına olan katkısını hesaplayarak bu renk vektörüne ekler. Yani her ışık, sahnede kendi bireysel etkisini hesaplar ve nihai çıktı rengine katkıda bulunur. Genel yapı aşağıdaki gibi olacaktır:
Gerçek kod uygulamaya göre farklılık gösterebilir, ancak genel yapı aynı kalır. Her bir ışık kaynağının etkisini hesaplayan fonksiyonlar tanımlarız ve bu fonksiyonların ürettiği renk değerlerini bir çıktı renk vektörüne ekleriz. Örneğin, iki ışık kaynağı bir fragmente yakınsa, bu ışıkların birleşik katkısı, tek bir ışık kaynağı tarafından aydınlatılan bir fragmente kıyasla daha parlak bir görüntü oluşturacaktır.
Yönlendirilmiş Işık (Directional Light)
Fragment shader içinde, bir yönlendirilmiş ışığın ilgili fragment'e olan katkısını hesaplayan bir fonksiyon tanımlamak istiyoruz. Bu fonksiyon, belirli parametreleri alarak hesaplanan yönlendirilmiş ışık rengini döndürecektir.
Öncelikle, bir yönlendirilmiş ışık kaynağı için minimum düzeyde gerekli değişkenleri belirlememiz gerekiyor. Bu değişkenleri DirLight adlı bir struct içinde saklayabilir ve bir uniform olarak tanımlayabiliriz. Struct içindeki değişkenler, önceki bölümden hatırlayacağınız kavramlardır:
Daha sonra, dirLight uniform değişkenini aşağıdaki prototipe sahip bir fonksiyona aktarabiliriz:
Tıpkı C ve C++'da olduğu gibi, bir fonksiyonu çağırmak istediğimizde (bu durumda main fonksiyonunun içinde), fonksiyonun çağrıldığı satırdan önce bir yerde tanımlanmış olması gerekir. Ancak, bu durumda fonksiyonları main fonksiyonunun altında tanımlamayı tercih ettiğimiz için bu gereklilik geçerli olmaz. Bu nedenle, C'de olduğu gibi, fonksiyon prototiplerini main fonksiyonunun üzerinde bir yerde tanımlarız.
Gördüğünüz gibi, bu fonksiyonun hesaplamalarını gerçekleştirebilmesi için bir DirLight struct ve iki vektör gereklidir. Önceki bölümü başarıyla tamamladıysanız, bu fonksiyonun içeriği sizin için sürpriz olmayacaktır.
Temelde, önceki bölümdeki kodu kopyaladık ve yönlendirilmiş ışığın katkı vektörünü hesaplamak için fonksiyon argümanları olarak verilen vektörleri kullandık. Ortaya çıkan ambient, diffuse ve specular katkıları, tek bir renk vektörü olarak döndürülür.
Noktasal Işık (Point Light)
Yönlendirilmiş ışıklara benzer şekilde, bir noktasal ışığın belirli bir shader çıktısına (fragment) olan katkısını hesaplayan bir fonksiyon tanımlamak istiyoruz. Bu hesaplamaya ışığın zayıflaması (attenuation) da dahil edilmelidir.
Yönlendirilmiş ışıklarda olduğu gibi, bir noktasal ışık için gerekli tüm değişkenleri içeren bir struct tanımlamak istiyoruz:
Gördüğünüz gibi, sahnemizde kaç tane noktasal ışık (point light) olacağını belirlemek için GLSL’de bir ön işlemci direktifi (pre-processor directive) kullandık. Daha sonra bu NR_POINT_LIGHTS sabitini kullanarak PointLight struct’larından oluşan bir dizi (array) oluşturduk.
GLSL’de diziler, C dizileriyle (C arrays) aynıdır ve köşeli parantezler ([ ]) kullanılarak tanımlanır. Şu anda 4 adet PointLight struct oluşturduk ve bunları veriyle doldurmamız gerekiyor.
Noktasal ışık fonksiyonunun prototipi ise şu şekildedir:
Fonksiyon, gerekli tüm verileri argüman olarak alır ve bu belirli noktasal ışığın (point light) shader çıktısına (fragment) yaptığı renk katkısını temsil eden bir vec3 değeri döndürür.
Yine, akıllı bir kopyala-yapıştır işlemiyle aşağıdaki fonksiyon elde edilir:
Bu işlevselliği böyle bir fonksiyon içine almak, birden fazla noktasal ışık için aydınlatmayı hesaplamayı kolaylaştırır ve kod tekrarını önler. Ana fonksiyon (main function) içinde, noktasal ışık dizisi (point light array) üzerinde dönen bir döngü oluşturarak her noktasal ışık için CalcPointLight fonksiyonunu çağırabiliriz.
Hepsini Bir Araya Getirelim
Artık yönlendirilmiş ışık (directional light) ve noktasal ışık (point light) için fonksiyonları tanımladığımıza göre, bunları ana fonksiyon (main function) içinde birleştirebiliriz.
Her ışık türü, çıktı rengine kendi katkısını ekler ve tüm ışık kaynakları işlenene kadar bu süreç devam eder. Sonuç olarak elde edilen renk, sahnedeki tüm ışık kaynaklarının birleşik etkisini içerir.
CalcSpotLight fonksiyonunu oluşturmayı ise okuyucuya bir alıştırma olarak bırakıyoruz.
Bu yaklaşımda, ışık türü fonksiyonlarına yayılmış birçok yinelenen hesaplama bulunmaktadır (örneğin, yansıma vektörünün --reflect vector-- hesaplanması, diffuse ve specular terimlerinin belirlenmesi ve materyal dokularının örneklenmesi gibi). Bu nedenle, burada optimizasyon için bir alan bulunmaktadır.
Yönlendirilmiş ışık (directional light) struct'ı için uniform değerlerini ayarlamak oldukça tanıdık gelmeli, ancak noktasal ışıkların (point lights) uniform değerlerini nasıl ayarlayacağınızı merak ediyor olabilirsiniz. Çünkü noktasal ışık uniform değişkeni, aslında bir PointLight struct dizisidir. Bu konuya daha önce değinmemiştik.
Neyse ki, bu işlem oldukça basittir. Bir struct’ın uniform değerlerini ayarlamak ile struct dizisinin uniform değerlerini ayarlamak temelde aynı mantıkla çalışır. Ancak bu sefer, uniform’un konumunu sorgularken uygun indis değerini de belirtmemiz gerekir:
Burada, pointLights dizisindeki ilk PointLight struct'ını indeksleyerek, içsel olarak constant değişkeninin konumunu alıyoruz ve bu değeri 1.0 olarak ayarlıyoruz.
Ayrıca, 4 noktasal ışık (point light) için birer konum vektörü tanımlamamız gerektiğini de unutmayalım. Bu ışıkları sahneye daha iyi yaymak için farklı konumlara yerleştireceğiz.
Bunun için, noktasal ışıkların (point lights) konumlarını içeren başka bir glm::vec3 dizisi tanımlayacağız:
Daha sonra, pointLights dizisinden ilgili PointLight struct’ını indeksleyerek, position özelliğini az önce tanımladığımız konumlardan biriyle ayarlıyoruz. Ayrıca, artık sadece 1 değil, 4 ışık küpü (light cube) çizdiğinizden emin olun. Bunun için, her ışık objesi için ayrı bir model matrisi oluşturmanız gerekir, tıpkı önceden konteynerler için yaptığımız gibi.
Eğer sahneye bir el feneri (flashlight) de eklerseniz, tüm ışık kaynaklarının birleşik etkisi aşağıdaki gibi görünecektir:
Gördüğünüz gibi, gökyüzünde güneş benzeri bir global ışık kaynağı bulunuyor, sahneye dağılmış 4 noktasal ışık (point light) yerleştirilmiş ve oyuncunun bakış açısından görülebilen bir el feneri (flashlight) mevcut. Oldukça etkileyici görünüyor, değil mi?
Son uygulamanın tüm kaynak koduna buradan ulaşabilirsiniz.
Görselde, önceki bölümlerde kullandığımız varsayılan ışık özellikleriyle ayarlanmış tüm ışık kaynakları gösteriliyor. Ancak, bu değerlerle oynayarak oldukça ilginç sonuçlar elde edebilirsiniz.
Sanatçılar ve seviye tasarımcıları (level designers), genellikle büyük bir editör içinde tüm bu ışık değişkenlerini ayarlayarak ışıklandırmanın çevreyle uyumlu olmasını sağlarlar. Basit ortamımızı kullanarak bile, yalnızca ışıkların özelliklerini değiştirerek oldukça ilginç görseller oluşturabilirsiniz:
Ayrıca, aydınlatmayı daha iyi yansıtmak için arka plan temizleme rengini (clear color) değiştirdik. Sadece birkaç ışıklandırma parametresini ayarlayarak tamamen farklı atmosferler oluşturabileceğinizi görebilirsiniz.
Bu noktaya kadar, OpenGL'de aydınlatma konusunda oldukça iyi bir anlayışa sahip olmuş olmalısınız. Şimdiye kadar öğrendiğiniz bilgilerle, ilgi çekici ve görsel olarak zengin ortamlar ve atmosferler oluşturabilirsiniz.
Tüm farklı ışık değerleriyle oynamayı deneyin ve kendi benzersiz atmosferlerinizi oluşturun!
Alıştırmalar
Son görseldeki farklı atmosferleri, ışığın öznitelik değerlerini değiştirerek yeniden yaratabilir misiniz? çözüm.
Orijinal Kaynak: Multiple lights
Çeviri: Nezihe Sözen
Last updated
Was this helpful?