Giriş
Bu dokümanın temelini oluşturduğu için önce Three.js'ye giriş bölümünü okuyun.
Bugün gölgelendiriciler hakkında konuşmak istiyorum. WebGL harika bir araç. Daha önce de söylediğim gibi Three.js (ve diğer kitaplıklar) zorlukları sizin için soyutlama konusunda harika bir iş çıkarıyor. Ancak bazen belirli bir efekt elde etmek veya ekranda görünen bu harika şeylerin nasıl ortaya çıktığını biraz daha ayrıntılı olarak incelemek isteyebilirsiniz. Bu durumda, gölgelendiriciler neredeyse kesinlikle denklemin bir parçası olacaktır. Son eğitimdeki temel bilgilerden biraz daha zorlu konulara geçmek isteyebilirsiniz.
Three.js'nin gölgelendiriciyi çalıştırma konusunda işimizin büyük bir kısmını hallettiği için bu kitaplığı kullandığınızı varsayarak ilerleyeceğim. Önce gölgelendiricilerin bağlamını, ardından bu belgenin ilerleyen bölümlerinde gelişmiş adımları öğreneceksiniz.
1. İki Gölgelendiricimiz
WebGL, Fixed Pipeline'ın kullanımını sunmaz. Bu, WebGL'nin, öğelerinizi kutudan çıkarır çıkarmaz oluşturabileceğiniz herhangi bir araç sunmadığı anlamına gelir. Ancak daha güçlü olan ve anlaşılması ve kullanılması daha zor olan Programlanabilir İşlem Hattı'nı sunar. Kısacası, Programlanabilir Ardışık Düzen, köşe noktalarının ve benzerlerinin ekranda oluşturulmasından programcı olarak sizin sorumlu olduğunuz anlamına gelir. Gölgelendiriciler bu işlem hattının bir parçasıdır ve iki türü vardır:
- Vertex gölgelendiriciler
- Parça gölgelendiricileri
İkisinin de tek başına hiçbir anlamı olmadığını kabul edeceğinizi düşünüyorum. Bu uygulamaların ikisinin de tamamen grafik kartınızın GPU'sunda çalıştığını bilmeniz gerekir. Bu, yapabildiğimiz her şeyi onlara yük aktarmak, CPU'muzu ise diğer işleri yapmaya bırakmak istediğimiz anlamına gelir. Modern bir GPU, gölgelendiricilerin gerektirdiği işlevler için büyük ölçüde optimize edilmiştir. Bu nedenle, GPU'yu kullanabilmek harika bir avantajdır.
2. Vertex Shaders
Küre gibi standart bir temel şekil alın. Köşelerden oluşur, değil mi? Köşe gölgelendiricisine bu köşelerin her biri sırayla verilir ve köşe gölgelendirici bunları değiştirebilir. Her biriyle ne yapacağı köşe gölgelendiricinin sorumluluğundadır ancak bir sorumluluğu vardır: Bir noktada gl_Position adlı bir şeyi ayarlaması gerekir. Bu, ekrandaki köşenin son konumu olan 4 boyutlu bir kayan nokta vektörüdür. Bu süreç, 3 boyutlu bir konumun (x, y, z değerlerine sahip bir köşe) 2 boyutlu bir ekrana yerleştirilmesi veya yansıtılmasıyla ilgili olduğundan oldukça ilginçtir. Neyse ki Three.js gibi bir şey kullanıyorsak gl_Position'ı çok fazla zorlanmadan ayarlamanın kısa bir yolu var.
3. Parça gölgelendiriciler
Yani, köşeleri olan nesnemiz var ve bunları 2D ekrana yansıttık. Peki kullandığımız renkler ne olacak? Peki ya doku ve ışıklandırma? Parça gölgelendiricinin amacı tam olarak budur. Köşe gölgelendiricisine çok benzeyen parça gölgelendiricinin de yalnızca bir zorunlu görevi vardır: Parçamızın son rengi olan başka bir 4 boyutlu kayan nokta vektörü olan gl_FragColor değişkenini ayarlaması veya atması gerekir.
Peki, parça nedir? Üçgen oluşturan üç köşe düşünün. Bu üçgenin içindeki her pikselin çizilmesi gerekir. Parça, bu üç köşe tarafından söz konusu üçgendeki her pikseli çizmek amacıyla sağlanan verilerdir. Bu nedenle parçalar, bileşen köşe noktalarından enterpolasyonlu değerler alır. Bir köşe kırmızı, komşusu ise mavi renkteyse renk değerlerinin kırmızıdan mora ve maviye doğru değiştiğini görürüz.
4. Shader Değişkenleri
Üç değişken bildirimi yapabilirsiniz: Uniforms, Attributes ve Varyings. Bu diller, diğer yaygın dillerle eşleşmediği için kafa karıştırıcı olabilir. Bunları şu şekilde düşünebilirsiniz:
Uniforms, hem köşe gölgelendiricilere hem de parça gölgelendiricilere gönderilir ve oluşturulan çerçevenin tamamında aynı kalan değerleri içerir. Bunun iyi bir örneği ışığın konumu olabilir.
Özellikler, tek tek köşelere uygulanan değerlerdir. Özellikler yalnızca köşe gölgelendiricisi tarafından kullanılabilir. Örneğin, her bir köşe farklı bir renge sahip olabilir. Özellikler, köşelerle bire bir ilişkiye sahiptir.
Varying'ler, köşe gölgelendiricisinde tanımlanan ve parça gölgelendiricisiyle paylaşmak istediğimiz değişkenlerdir. Bunu yapmak için hem köşe gölgelendiricide hem de parça gölgelendiricide aynı tür ve ada sahip bir değişken değişkeni bildirdiğimizden emin oluruz. Bunun klasik bir kullanımı, aydınlatma hesaplamalarında kullanılabileceği için bir köşe noktasının normalidir.
Daha sonra, bu üç türün nasıl uygulandığını anlamanız için hepsini kullanırız.
Hayır, oluşturabileceğiniz en basit gölgelendiricilerden birine göz atın.
5. Bonjourno World
İşte tepe noktası gölgelendiricilerinin "Merhaba Dünya" örneği:
/**
* Multiply each vertex by the model-view matrix
* and the projection matrix (both provided by
* Three.js) to get a final vertex position
*/
void main() {
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(position,1.0);
}
Aynı durum parça gölgelendiricisi için de geçerlidir:
/**
* Set the color to a lovely pink.
* Note that the color is a 4D Float
* Vector, R,G,B and A and each part
* runs from 0.0 to 1.0
*/
void main() {
gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);
}
Ancak çok da karmaşık değil, değil mi?
Köşe gölgelendiricisinde, Three.js tarafından birkaç tek tip gönderilir. Bu iki tek tip, Model-View Matrix ve Projection Matrix olarak adlandırılan 4 boyutlu matrislerdir. Bunların tam olarak nasıl çalıştığını bilmeniz gerekmez. Ancak yapabiliyorsanız her zaman işlerin nasıl yürüdüğünü anlamak en iyisidir. Kısa versiyonu, köşe noktasının 3D konumunun ekrandaki nihai 2D konumuna nasıl yansıtıldığıdır.
Three.js bunları shader kodunuzun en üstüne eklediğinden, bu konuda endişelenmenize gerek kalmaz. Bu nedenle, bunları örnek snippet'e dahil etmedim. Aslında ışık verileri, köşe renkleri ve köşe normalleri gibi çok daha fazla şey ekler. Bu işlemi Three.js olmadan yapıyorsanız tüm bu tek tip değişkenleri ve özellikleri kendiniz oluşturup ayarlamanız gerekir. Gerçek hikaye.
6. MeshShaderMaterial kullanma
Tamam, bir gölgelendirici ayarladık ancak bunu Three.js ile nasıl kullanacağız?
/**
* Assume we have jQuery to hand and pull out
* from the DOM the two snippets of text for
* each of our shaders
*/
var shaderMaterial = new THREE.MeshShaderMaterial({
vertexShader: $('vertexshader').text(),
fragmentShader: $('fragmentshader').text()
});
Ardından Three.js, bu materyali verdiğiniz ağa bağlı gölgelendiricilerinizi derleyip çalıştırır. Gerçekten de daha kolay bir yöntem yoktur. Muhtemelen evet, ancak tarayıcınızda çalışan 3D'den bahsediyoruz. Bu nedenle, belirli bir karmaşıklık beklediğinizi tahmin ediyoruz.
MeshShaderMaterial'ımıza iki özellik daha ekleyebiliriz: üniforma ve özellikler. Bunlar hem vektörleri, hem tam sayıları hem de kayan noktalı sayıları alabilir ancak daha önce de belirttiğim gibi tekdüzenler tüm çerçeve veya tüm köşeler için aynıdır. Bu nedenle tek değerler olma eğilimindedirler. Ancak özellikler, köşe başına değişkenlerdir. Bu nedenle, dizi olmaları beklenir. Özellikler dizisindeki değer sayısı ile ağdaki köşe sayısı arasında bire bir ilişki olmalıdır.
7. Sonraki Adımlar
Şimdi biraz zaman ayırarak animasyon döngüsü, köşe özellikleri ve tek tip ekleyeceğiz. Ayrıca, köşe gölgelendiricinin parça gölgelendiriciye bazı veriler gönderebilmesi için değişken bir değişken de ekleyeceğiz. Sonuç olarak pembe olan küremiz yukarıdan ve yandan aydınlatılmış gibi görünecek ve titreyecek. Bu biraz kafa karıştırıcı olsa da üç değişken türünü ve bunların birbirleriyle ve temel geometriyle ilişkisini iyi bir şekilde anlamanıza yardımcı olacağını umuyoruz.
8. Sahte ışık
Şimdi renklendirmeyi, düz renkli bir nesne olmayacak şekilde güncelleyin. Three.js'nin ışıklandırmayı nasıl ele aldığını inceleyebiliriz ancak bunun ihtiyacımız olandan daha karmaşık olduğunu anlayacağınızı düşünüyoruz. Bu nedenle, ışıklandırmayı taklit edeceğiz. Three.js'nin bir parçası olan muhteşem gölgelendiricileri ve Chris Milk ile Google'ın Rome adlı harika WebGL projesindeki gölgelendiricileri mutlaka incelemelisiniz. Gölgelendiricilerimize geri dönelim. Her bir tepe noktasını Fragment Gölgelendirici'ye normal şekilde sağlamak için Vertex Gölgelendirici'mizi güncelleyeceğiz. Bu işlem, aşağıdakilerde değişiklik yapılarak gerçekleştirilir:
// create a shared variable for the
// VS and FS containing the normal
varying vec3 vNormal;
void main() {
// set the vNormal value with
// the attribute value passed
// in by Three.js
vNormal = normal;
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(position,1.0);
}
ve Fragment Shader'da aynı değişken adını ayarlayacağız ve ardından köşe normalinin nokta çarpımını, kürenin üstünden ve sağından parlayan bir ışığı temsil eden bir vektörle kullanacağız. Bunun sonucunda, 3D paketindeki yönlü ışığa benzer bir efekt elde ederiz.
// same name and type as VS
varying vec3 vNormal;
void main() {
// calc the dot product and clamp
// 0 -> 1 rather than -1 -> 1
vec3 light = vec3(0.5,0.2,1.0);
// ensure it's normalized
light = normalize(light);
// calculate the dot product of
// the light to the vertex normal
float dProd = max(0.0, dot(vNormal, light));
// feed into our frag color
gl_FragColor = vec4(dProd, dProd, dProd, 1.0);
}
Dolayısıyla, iç çarpımın işe yaramasının nedeni, iki vektör verildiğinde bu iki vektörün ne kadar "benzer" olduğunu gösteren bir sayı vermesidir. Normalleştirilmiş vektörler tam olarak aynı yöne işaret ediyorsa 1 değeri elde edilir. Zıt yönleri gösteriyorsa -1 puan alırsınız. Bu sayıyı alıp aydınlatmamıza uyguluyoruz. Bu nedenle, sağ üstteki bir köşe 1'e yakın veya eşit bir değere (tamamen aydınlatılmış) sahip olurken, yan taraftaki bir köşe 0'a yakın bir değere ve arka taraftaki köşe -1'e yakın bir değere sahip olur. Negatif değerleri 0'a sabitleriz ancak sayıları girdiğinizde gördüğümüz temel aydınlatma elde edilir.
Sırada ne var? Belki de bazı köşe noktası konumlarıyla oynamayı deneyebilirsiniz.
9. Özellikler
Şimdi yapmamız gereken şey, bir özellik kullanarak her bir tepe noktasına rastgele bir sayı eklemek. Bu sayıyı, köşeyi normali boyunca itmek için kullanırız. Sonuç olarak, sayfayı her yenilediğinizde değişen tuhaf bir dikenli top elde edersiniz. Henüz animasyonlu olmayacak (bu özellik daha sonra eklenecek) ancak sayfayı birkaç kez yenilediğinizde rastgele oluşturulduğunu göreceksiniz.
Özelliği köşe gölgelendiricisine ekleyerek başlayın:
attribute float displacement;
varying vec3 vNormal;
void main() {
vNormal = normal;
// push the displacement into the three
// slots of a 3D vector so it can be
// used in operations with other 3D
// vectors like positions and normals
vec3 newPosition = position +
normal *
vec3(displacement);
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(newPosition,1.0);
}
Nasıl görünüyor?
Aslında pek farklı değil. Bunun nedeni, özelliğin MeshShaderMaterial'da ayarlanmamış olmasıdır. Bu nedenle, gölgelendirici bunun yerine sıfır değeri kullanır. Şu anda bir yer tutucu gibi. Bir saniye içinde JavaScript'teki MeshShaderMaterial'e özelliği ekleyeceğiz ve Three.js, ikisini bizim için otomatik olarak bağlayacak.
Ayrıca, orijinal özellik tüm özellikler gibi salt okunur olduğundan güncellenen konumu yeni bir vec3 değişkenine atamam gerektiği de belirtilmelidir.
10. MeshShaderMaterial'ı güncelleme
MeshShaderMaterial'ımızı, yer değiştirmemizi sağlamak için gereken özellikle hemen güncelleyin. Hatırlatma: Özellikler, köşe başına değerlerdir. Bu nedenle, küremizde köşe başına bir değer gerekir. Aşağıdaki gibi:
var attributes = {
displacement: {
type: 'f', // a float
value: [] // an empty array
}
};
// create the material and now
// include the attributes property
var shaderMaterial = new THREE.MeshShaderMaterial({
attributes: attributes,
vertexShader: $('#vertexshader').text(),
fragmentShader: $('#fragmentshader').text()
});
// now populate the array of attributes
var vertices = sphere.geometry.vertices;
var values = attributes.displacement.value
for(var v = 0; v < vertices.length; v++) {
values.push(Math.random() * 30);
}
Şimdi bozulmuş bir küre görüyoruz ancak asıl güzel olan, tüm yer değiştirmenin GPU'da gerçekleşiyor olması.
11. Animasyon yap
Bunu kesinlikle animasyonlu hale getirmeliyiz. Bunu nasıl yapıyoruz? Bu durumda iki şeyi halletmemiz gerekiyor:
- Her karede ne kadar yer değiştirme uygulanacağını animasyonla göstermek için kullanılan bir tekdüzen. -1 ile 1 arasında çalıştıkları için bu işlemde sinüs veya kosinüs kullanabiliriz.
- JS'de animasyon döngüsü
Üniformayı hem MeshShaderMaterial'a hem de Vertex Shader'a ekleyeceğiz. Öncelikle Vertex Shader:
uniform float amplitude;
attribute float displacement;
varying vec3 vNormal;
void main() {
vNormal = normal;
// multiply our displacement by the
// amplitude. The amp will get animated
// so we'll have animated displacement
vec3 newPosition = position +
normal *
vec3(displacement *
amplitude);
gl_Position = projectionMatrix *
modelViewMatrix *
vec4(newPosition,1.0);
}
Ardından MeshShaderMaterial'ı güncelleriz:
// add a uniform for the amplitude
var uniforms = {
amplitude: {
type: 'f', // a float
value: 0
}
};
// create the final material
var shaderMaterial = new THREE.MeshShaderMaterial({
uniforms: uniforms,
attributes: attributes,
vertexShader: $('#vertexshader').text(),
fragmentShader: $('#fragmentshader').text()
});
Gölgelendiricilerimiz şu an için tamamlandı. Ancak şu anda geriye doğru bir adım atmış gibi görünüyoruz. Bunun temel nedeni, genlik değerimizin 0 olması ve bunu yer değiştirme ile çarptığımız için hiçbir değişikliğin görülmemesidir. Ayrıca, animasyon döngüsünü de ayarlamadığımız için 0 değerinin başka bir değere değiştiğini hiç görmüyoruz.
JavaScript'imizde artık oluşturma çağrısını bir işlevin içine sarmamız ve ardından bunu çağırmak için requestAnimationFrame'i kullanmamız gerekiyor. Ayrıca, üniformanın değerini de güncellememiz gerekir.
var frame = 0;
function update() {
// update the amplitude based on
// the frame value
uniforms.amplitude.value = Math.sin(frame);
frame += 0.1;
renderer.render(scene, camera);
// set up the next call
requestAnimFrame(update);
}
requestAnimFrame(update);
Sonuç
Hepsi bu kadar! Artık bu animasyonun, tuhaf (ve biraz da halüsinasyonlu) bir şekilde titreştiğini görebilirsiniz.
Gölgeleyicilerle ilgili ele alabileceğimiz daha birçok konu var. Bu girişin size yardımcı olacağını umuyoruz. Artık gördüğünüzde gölgelendiricileri anlayabilir ve kendi gölgelendiricilerinizi oluşturabilirsiniz.