Oyun geliştirme, görselleştirme veya simülasyon projelerinizde tekrarlayan ve sıkıcı dokular yerine dinamik, benzersiz ve organik yüzeyler mi arıyorsunuz? WGPU kullanarak prosedürel Voronoi dokuları oluşturmak, projelerinize yeni bir soluk getirecek ve görsel zenginliği artıracaktır. Bu rehberde, Voronoi dokularının temelinden başlayıp modern WebGPU API’si ile adım adım nasıl uygulanacağını, ileri düzey teknikleri ve gerçek dünya kullanım senaryolarını detaylı bir şekilde inceleyeceğiz.
Görsel dünyalarımızda çeşitlilik ve gerçekçilik yaratmak, kullanıcı deneyimini doğrudan etkileyen kritik bir unsurdur. Statik, tekrar eden dokular genellikle “yapay” bir his bırakırken, prosedürel olarak üretilen dokular her zaman taze, benzersiz ve dinamik bir görünüm sunar. Peki, bu dinamizmi nasıl yakalayabiliriz? İşte burada prosedürel doku üretimi devreye giriyor. Özellikle Voronoi dokuları, doğal dünyadaki hücre yapıları, çatlaklar, su yüzeyindeki köpükler, deriler veya taş yüzeyler gibi birçok karmaşık deseni şaşırtıcı bir doğrulukla taklit edebilir.
Voronoi diyagramları, bir düzlemi belirli “çekirdek” noktalarına olan uzaklığa göre bölgelere ayırır. Her nokta, kendisine diğer tüm çekirdek noktalardan daha yakın olan alanın tek sahibidir. Bu geometrik yapı, görsel olarak farklı hücreler ve bu hücrelerin kenarlarını oluşturan çizgilerle karakterize edilir. Bu basit kural, şaşırtıcı derecede karmaşık ve estetik olarak hoş dokuların temelini oluşturur. Örneğin, bir çatlak dokusu oluşturmak istediğinizde, Voronoi hücrelerinin kenarları bu çatlakları doğal bir şekilde taklit edebilir. Ya da bir taş yüzeyindeki mineral dağılımını simüle etmek için, her hücre farklı bir renk veya parlaklık değeri alabilir. Bu esneklik, Voronoi’yi grafik programcıları ve tasarımcılar için vazgeçilmez bir araç haline getirir.
Geleneksel doku haritalama yöntemleri genellikle önceden çizilmiş veya fotoğraflanmış resimlere dayanır. Bu yöntemler, bellekte yer kaplar ve tekrarlayan desenler oluşturma eğilimindedir, bu da büyük ölçekli veya sonsuz dünyalarda görsel yorgunluğa yol açabilir. Buna karşılık, prosedürel dokular, bir algoritma aracılığıyla anında hesaplandığı için bellekte çok daha az yer kaplar ve isteğe bağlı olarak sonsuz varyasyonlar üretebilir. Ayrıca, bu dokuların parametrelerini değiştirerek anında farklı görünümler elde edebiliriz, bu da tasarımcılara inanılmaz bir esneklik sunar. Bu nedenle, oyun motorlarında, mimari görselleştirmelerde ve hatta bilimsel simülasyonlarda prosedürel yaklaşımlar giderek daha fazla tercih edilmektedir.
WebGPU (WGPU), bu tür karmaşık grafik işlemlerini web tarayıcılarında bile modern GPU donanımının tam gücünü kullanarak gerçekleştirmemizi sağlayan yeni nesil bir web standardıdır. WebGL’e kıyasla daha düşük seviyeli ve performans odaklı bir API sunan WGPU, prosedürel doku üretimini daha verimli ve daha kolay hale getirir. Bu rehber boyunca, Voronoi dokularının arkasındaki matematiksel temelleri ve WGPU’nun sunduğu imkanları bir araya getirerek, tarayıcınızda çalışan, tamamen dinamik ve görsel açıdan zengin Voronoi dokuları nasıl oluşturacağınızı öğreneceksiniz. Şimdi, bu heyecan verici yolculuğa başlamadan önce, Voronoi dokularının temelini daha derinlemesine anlayalım.
Voronoi Dokuları Nedir ve Nasıl Çalışır? Temel Kavramlar
Voronoi diyagramları, matematiksel olarak tanımlanmış, büyüleyici ve estetik açıdan zengin yapılardır. Bir Voronoi diyagramı oluşturmak için, öncelikle bir düzlem üzerinde bir dizi “çekirdek nokta” veya “site” (points, seeds) belirlenir. Bu noktalar, diyagramın temelini oluşturur. Ardından, düzlemdeki her bir noktayı, kendisine en yakın olan çekirdek noktaya göre bir hücreye atayarak düzlemi bölgelere ayırırız. Yani, her bir hücre (Voronoi hücresi), içinde bulunan çekirdek noktasına, diğer tüm çekirdek noktalardan daha yakın olan tüm noktaların kümesidir. Bu bölgelerin sınırları, komşu çekirdek noktaların orta dikmeleri üzerinde oluşur ve bu sınırlar, Voronoi kenarları olarak adlandırılır. Bu kenarların kesiştiği noktalara ise Voronoi köşeleri denir. Bu basit ama güçlü prensip, doğal görünümlü ve karmaşık desenler yaratmak için harika bir temel sağlar.
Bu diyagramların güzelliği, rastgeleliğin ve düzenin birleşmesinden gelir. Çekirdek noktaların rastgele dağılımı, organik ve tahmin edilemez bir yapı oluştururken, her noktanın en yakın çekirdeğe atanması işlemi, belirli bir düzeni ve sürekliliği garanti eder. Bu sayede, çatlamış toprak, hayvan postları, sabun köpükleri, böcek kanatları veya gezegen yüzeyindeki kraterler gibi doğal fenomenleri taklit etmek için kullanılabilir. Hatta mimari tasarımlarda, şehir planlamasında ve hatta veri analizinde de kendine yer bulmuştur. Görsel olarak, Voronoi dokuları genellikle hücrelerin kenarları boyunca koyu renkler ve hücre içlerinde daha açık renklerle temsil edilir, bu da “çatlak” veya “mozaik” bir etki yaratır.
WGSL (WebGPU Shading Language) gibi bir gölgelendirici dilinde Voronoi dokusu hesaplaması yaparken, genellikle her piksel için aşağıdaki adımlar izlenir:
- Çekirdek Noktaları Belirleme: Genellikle bir ızgara sistemi içinde, her ızgara hücresi için birkaç rastgele nokta oluşturulur. Bu rastgele noktaların konumu, genellikle bir hash fonksiyonu veya basit bir rastgele sayı üreteci kullanılarak belirlenir. Bu, daha küçük bir alanda hesaplama yaparak büyük ölçekli, tekrarlamayan dokular elde etmemizi sağlar. Örneğin, mevcut piksel koordinatına dayalı bir hash değeri kullanarak çekirdek noktalarının yerini “sabit” ancak “rastgele” hale getirebiliriz.
- Uzaklık Hesaplama: Mevcut pikselin konumu ile etrafındaki potansiyel çekirdek noktaları arasındaki Öklid uzaklıkları hesaplanır. Bu, basitçe iki nokta arasındaki mesafeyi veren
sqrt(dx*dx + dy*dy)formülü ile yapılır. - En Yakın Noktayı Bulma: Hesaplanan uzaklıklar arasından en küçüğü belirlenir. Bu en küçük uzaklık, pikselin ait olduğu Voronoi hücresinin kenarına ne kadar yakın olduğunu gösterir. Bu değeri doğrudan pikselin rengi olarak kullanmak, hücre kenarlarının daha koyu, merkezlerinin daha açık olduğu bir görüntü oluşturur. Bu en küçük uzaklık, F1 olarak adlandırılan temel Voronoi gürültü değeridir.
- İkinci En Yakın Noktayı Bulma (Opsiyonel): Daha karmaşık ve ilginç varyasyonlar için, sadece en yakın noktayı değil, aynı zamanda ikinci en yakın noktayı da bulabiliriz (F2). F2 – F1 farkını kullanmak, hücrelerin iç kısımlarının daha homojen olduğu, ancak kenarların çok daha belirgin ve “çatlak” göründüğü bir doku oluşturur. Bu, birçok doğal malzeme dokusunu taklit etmek için özellikle kullanışlıdır.
Bu hesaplamalar, GPU’nun paralel işlem gücü sayesinde her piksel için eş zamanlı olarak milyonlarca kez çok hızlı bir şekilde gerçekleştirilebilir. Bu, gerçek zamanlı olarak dinamik dokular üretmemize olanak tanır. WGPU ile bu işlemleri nasıl entegre edeceğimizi ve WGSL’de nasıl kodlayacağımızı bir sonraki bölümde daha detaylı inceleyeceğiz. Prosedürel Voronoi dokuları, basit kurallardan doğan karmaşıklığın mükemmel bir örneğidir ve modern grafik uygulamaları için güçlü bir araçtır.
WGPU’ya Giriş: Neden Tarayıcıda GPU Programlama Geleceğimiz?
WebGPU (WGPU), modern web tarayıcıları için geliştirilen yeni nesil bir grafik ve hesaplama API’sidir. Geleneksel olarak web tabanlı 3D grafikler için kullanılan WebGL’nin yerini alması hedeflenen WGPU, daha güçlü, daha esnek ve güncel GPU donanımlarının sunduğu özelliklere daha doğrudan erişim sağlayan bir platform sunar. Peki, WGPU’yu bu kadar özel kılan nedir ve neden bu kadar önemli?
İlk olarak, WGPU, modern grafik API’leri olan Vulkan, Metal ve DirectX 12’den ilham alarak tasarlanmıştır. Bu, geliştiricilere düşük seviyeli GPU kontrolü imkanı sunar, bu da daha iyi performans ve daha ince optimizasyonlar yapma potansiyeli anlamına gelir. WebGL, eski ve daha kısıtlı bir API olan OpenGL ES’e dayanıyordu ve bu nedenle modern GPU mimarilerinden tam olarak yararlanamıyordu. WGPU ile birlikte, GPU donanımının özelliklerini çok daha verimli bir şekilde kullanabiliriz, bu da özellikle yüksek performans gerektiren uygulamalar (oyunlar, bilimsel simülasyonlar, 3D modelleme araçları) için büyük bir avantajdır.
İkinci olarak, WGPU sadece grafik oluşturma (rendering) yetenekleriyle sınırlı değildir; aynı zamanda genel amaçlı GPU hesaplamaları (compute shaders) için de güçlü bir arayüz sağlar. Bu, Voronoi doku üretimi gibi karmaşık algoritmaları doğrudan GPU üzerinde çalıştırmak için kritik öneme sahiptir. Hesaplama gölgelendiricileri sayesinde, CPU’nun yükünü hafifleterek ve GPU’nun paralel işlem gücünden faydalanarak büyük veri kümeleri üzerinde inanılmaz hızda işlemler gerçekleştirebiliriz. Bu, sadece doku üretimi değil, aynı zamanda fizik simülasyonları, yapay zeka çıkarımları ve veri analizi gibi birçok alanda da kapıları açar.
Üçüncü olarak, WGPU’nun bellek yönetimi ve kaynak bağlama (bind groups) modelleri, geliştiricilere daha fazla kontrol ve daha az hata payı sunar. WebGL’de kaynak yönetimi biraz daha karmaşık ve hata eğilimli olabilirken, WGPU’nun açık ve yapılandırılmış yaklaşımı, performansı artırırken geliştirme sürecini de kolaylaştırır. Örneğin, farklı türdeki verileri (uniforms, storage buffers, textures) GPU’ya aktarmak ve shader’lar içinde bunlara erişmek için bind grupları kullanılır, bu da kaynak yönetimini çok daha düzenli hale getirir.
Son olarak, WGPU, Rust programlama dilinde geliştirilen wgpu-rs kütüphanesi aracılığıyla ve web’de JavaScript/TypeScript ile kullanılabilir. Bu platformlar arası uyumluluk, geliştiricilerin aynı mantığı ve shader kodunu farklı ortamlarda yeniden kullanmasına olanak tanır. Web ortamında ise, doğrudan tarayıcı API’si olarak sunulması, herhangi bir eklenti veya özel kurulum gerektirmeden modern GPU yeteneklerine erişim anlamına gelir. Bu, özellikle etkileşimli web uygulamaları ve web tabanlı oyunlar için devrim niteliğindedir.
Özetle, WGPU sadece WebGL’nin bir sonraki versiyonu değil, aynı zamanda web’de GPU programlamanın geleceğidir. Gelişmiş performansı, esnekliği, genel amaçlı hesaplama yetenekleri ve platformlar arası uyumluluğu ile geliştiricilere, tarayıcıda daha önce hayal bile edilemeyen görsel ve hesaplama gücü sunar. Bu güçlü API sayesinde, prosedürel Voronoi dokularını oluşturmak gibi karmaşık görevler artık çok daha erişilebilir ve verimli hale gelmiştir. Şimdi, WGPU ortamını nasıl kuracağımızı inceleyelim.
WGPU Ortamını Kurma: Temel Bileşenler Nelerdir?
WGPU ile prosedürel Voronoi dokuları oluşturmaya başlamadan önce, temel bir WGPU ortamını kurmamız gerekiyor. Bu, genellikle bir HTML sayfası, JavaScript dosyası ve tarayıcınızın WGPU API’sini desteklemesiyle başlar. Modern tarayıcılar (Chrome, Firefox, Edge, Safari) WGPU desteğini kademeli olarak sunmaktadır. Temel olarak, bir WGPU uygulaması şu bileşenlerden oluşur:
- HTML Canvas: GPU çıktısını göstereceğimiz ana yüzeydir.
- GPUAdapter: Mevcut GPU donanımını temsil eder.
- GPUDevice: GPU ile etkileşim kurmak için kullanılan mantıksal bir aygıt.
- GPUCanvasContext: Canvas ile GPU arasında bir bağlantı kurar.
- GPURenderPipeline: Grafik çizim işleminin tüm aşamalarını tanımlar (shader’lar, düzenler, çıktı formatları).
- GPUBufferlar: Vertex verileri, indeks verileri, uniform değişkenler gibi GPU’ya gönderilecek verileri içerir.
- GPUTexture: Doku verilerini saklar ve işler.
Şimdi, minimum bir WGPU kurulumunun nasıl görüneceğine dair genel bir yapıya göz atalım. Unutmayın, bu sadece Voronoi dokusunu oluşturmak için gerekli olan temel çerçevedir. Tam bir uygulama çok daha fazla detay içerecektir.
Öncelikle, HTML dosyamızda bir canvas elementine ihtiyacımız var:
WGPU Voronoi Doku Oluşturucu
Ardından, main.js dosyamızda WGPU'yu başlatacak kodları yazmaya başlayabiliriz. Bu kısımda, temel WGPU nesnelerini oluşturacak ve render döngüsünü ayarlayacağız.
async function initWGPU() {
if (!navigator.gpu) {
alert("Tarayıcınız WebGPU'yu desteklemiyor!");
return;
}
const canvas = document.getElementById("voronoiCanvas");
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const context = canvas.getContext("webgpu");
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format: presentationFormat,
alphaMode: "opaque",
});
// Vertex buffer'ı oluşturma (Ekranı kaplayan bir dörtgen için)
const vertices = new Float32Array([
-1.0, -1.0, // Sol alt
1.0, -1.0, // Sağ alt
-1.0, 1.0, // Sol üst
1.0, 1.0, // Sağ üst
]);
const vertexBuffer = device.createBuffer({
label: "Ekran Dörtgeni Vertex Buffer",
size: vertices.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true,
});
new Float32Array(vertexBuffer.getMappedRange()).set(vertices);
vertexBuffer.unmap();
// Shader kodunu burada tanımlayacağız (Sonraki bölümlerde).
// ...
// Render pipeline'ı ve bind grupları burada oluşturulacak.
// ...
function renderLoop() {
// Render komutları burada verilecek.
// ...
requestAnimationFrame(renderLoop);
}
renderLoop();
}
initWGPU();
Yukarıdaki JavaScript kodu, WGPU'nun temelini atar: canvas'ı alır, bir GPU adaptörü ve aygıtı ister, ardından canvas bağlamını yapılandırır. Ayrıca, tüm ekranı kaplayacak basit bir dörtgenin köşe verilerini içeren bir vertex buffer oluştururuz. Bu dörtgen, Voronoi dokusunu üzerine çizeceğimiz yüzey olacaktır. renderLoop fonksiyonu ise grafiklerin sürekli olarak güncellenmesini sağlayacak ana döngüdür. Bu temel yapı üzerine, WGSL shader kodumuzu ve render pipeline'ımızı inşa edeceğiz. Bu kurulum, WGPU ile etkileşim kurmak için gerekli olan minimum çerçeveyi sunar ve Voronoi dokusu üretiminin kalbine inmemize olanak tanır.
Voronoi Shader'ı Yazmak: Adım Adım Yaklaşım
WGPU'daki Voronoi doku üretiminin kalbi, WGSL (WebGPU Shading Language) ile yazdığımız gölgelendirici kodudur. Bu gölgelendirici, her pikselin rengini hesaplayarak Voronoi desenini oluşturur. Şimdi adım adım bu shader'ı nasıl oluşturacağımıza bakalım.
Rastgele Noktalar Nasıl Belirlenir?
Voronoi'nin temelini oluşturan çekirdek noktaları, her bir pikselin bulunduğu "ızgara hücresi" içinde rastgele olarak oluşturulur. Ancak bu rastgelelik, her çalıştırmada aynı dokuyu elde etmek için deterministik olmalıdır. Bunu sağlamak için, pikselin dünya koordinatlarına dayalı bir hash fonksiyonu kullanırız. Bu hash fonksiyonu, her bir ızgara hücresi için benzersiz, ancak sabit bir "rastgele" değer üretir. Bu sayede, aynı koordinatlardaki pikseller her zaman aynı çekirdek noktalarını görür.
İşte basit bir hash fonksiyonu ve rastgele nokta oluşturma mantığı:
// Basit bir hash fonksiyonu. Daha karmaşık ve kaliteli hash fonksiyonları da kullanılabilir.
fn hash(p: vec2f) -> vec2f {
let p_floor = floor(p);
let p_fract = fract(p);
let h = vec2f(dot(p_floor, vec2f(127.1, 311.7)),
dot(p_floor, vec2f(269.5, 183.3)));
return fract(sin(h) * 43758.5453) * 2.0 - 1.0; // [-1, 1] aralığında rastgele değerler
}
// Bir ızgara hücresi içinde rastgele bir nokta üretir
fn get_cell_point(cell_coord: vec2f, seed: f32) -> vec2f {
// cell_coord'u kullanarak deterministik bir rastgelelik elde edelim.
let random_offset = hash(cell_coord + seed);
return cell_coord + (random_offset * 0.5 + 0.5); // [0, 1] aralığında ofset ekler
}
Uzaklık Hesaplama: En Yakın Noktayı Bulma Algoritması
Şimdi, mevcut piksel koordinatımız için en yakın çekirdek noktayı bulmamız gerekiyor. Bu, pikselin çevresindeki 3x3 (veya daha büyük) ızgara hücrelerindeki tüm çekirdek noktalarını kontrol etmeyi gerektirir. Her bir çekirdek noktası için pikselden olan mesafeyi hesaplayacak ve en kısa olanı seçeceğiz.
fn voronoi_distance(uv: vec2f, scale: f32, seed: f32) -> f32 {
let scaled_uv = uv * scale;
let current_cell_coord = floor(scaled_uv);
var min_dist = 100000.0; // Çok büyük bir başlangıç değeri
// 3x3 komşu hücreleri kontrol et
for (var y_offset: i32 = -1; y_offset <= 1; y_offset++) {
for (var x_offset: i32 = -1; x_offset <= 1; x_offset++) {
let neighbor_cell_coord = current_cell_coord + vec2f(f32(x_offset), f32(y_offset));
let cell_point = get_cell_point(neighbor_cell_coord, seed);
let dist = distance(scaled_uv, cell_point);
min_dist = min(min_dist, dist);
}
}
return min_dist;
}
Bu fonksiyon, verilen UV koordinatlarını belirli bir ölçekte büyütür, mevcut hücre koordinatını bulur ve çevresindeki 9 hücredeki her bir çekirdek nokta için uzaklığı hesaplar. En küçük uzaklık, bu pikselin Voronoi hücresinin kenarına olan mesafesini temsil eder (F1 değeri).
Renk Değerlerini Belirleme: Çeşitli Voronoi Görünümleri
En yakın mesafeyi bulduktan sonra, bu mesafeyi kullanarak pikselin rengini belirleyebiliriz. İşte bazı yaygın yaklaşımlar:
- F1 Gürültüsü:
min_distdeğerini doğrudan renk olarak kullanmak, hücre kenarlarının koyu, merkezlerinin açık olduğu temel bir Voronoi deseni verir. - F2-F1 Gürültüsü: İkinci en yakın mesafeyi (F2) bulup F2-F1 farkını almak, hücrelerin iç kısımlarının daha düzgün, kenarların ise daha belirgin ve "çatlak" göründüğü bir doku oluşturur. Bu genellikle en popüler Voronoi doku tipidir.
F2-F1 yaklaşımını entegre edelim:
fn voronoi_noise(uv: vec2f, scale: f32, seed: f32) -> f32 {
let scaled_uv = uv * scale;
let current_cell_coord = floor(scaled_uv);
var min_dist1 = 100000.0;
var min_dist2 = 100000.0; // İkinci en yakın mesafeyi tutar
for (var y_offset: i32 = -1; y_offset <= 1; y_offset++) {
for (var x_offset: i32 = -1; x_offset <= 1; x_offset++) {
let neighbor_cell_coord = current_cell_coord + vec2f(f32(x_offset), f32(y_offset));
let cell_point = get_cell_point(neighbor_cell_coord, seed);
let dist = distance(scaled_uv, cell_point);
if (dist < min_dist1) {
min_dist2 = min_dist1; // Eski min_dist1, yeni min_dist2 olur
min_dist1 = dist; // dist, yeni min_dist1 olur
} else if (dist < min_dist2) {
min_dist2 = dist;
}
}
}
return min_dist2 - min_dist1; // F2-F1 farkını döndür
}
Bu temel WGSL kodunu bir fragman gölgelendiriciye (fragment shader) entegre ederek, her piksel için yukarıdaki hesaplamaları çalıştırırız. Fragment shader'ın ana fonksiyonu aşağıdaki gibi görünecektir:
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) uv: vec2f,
}
// Uniform değişkenleri (GPU'dan shader'a gönderilen sabitler)
@group(0) @binding(0)
var uniforms: struct {
resolution: vec2f,
time: f32,
scale: f32,
seed: f32,
};
@fragment
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
let uv = input.uv; // Ekran koordinatları (0,0) - (1,1) aralığında
let voronoi_val = voronoi_noise(uv, uniforms.scale, uniforms.seed + uniforms.time * 0.05); // Zamanla değişen seed
// Değeri normalize edip renk olarak kullan
let color_val = smoothstep(0.0, 0.6, voronoi_val); // Görsel iyileştirme için smoothstep
return vec4f(color_val, color_val, color_val, 1.0); // Gri tonlamalı çıktı
}
Burada uniforms struct'ı aracılığıyla JavaScript'ten shader'a çözünürlük, zaman, ölçek ve seed gibi değerleri iletiyoruz. smoothstep fonksiyonu, renk geçişlerini daha yumuşak hale getirerek görsel olarak daha hoş bir sonuç elde etmemizi sağlar. Bu WGSL kod parçacıkları, WGPU ile prosedürel Voronoi dokusu üretmenin temelini oluşturur. Şimdi, bu shader'ı WGPU render pipeline'ımıza nasıl entegre edeceğimizi ve dokuyu ekrana nasıl çizeceğimizi görelim.
WGPU Pipeline Oluşturma ve Dokuyu Render Etme: Görselleştirme Adımları
Voronoi gölgelendirici kodumuzu WGSL'de yazdıktan sonra, bu kodu WGPU ortamında çalıştıracak bir render pipeline oluşturmamız ve dokuyu ekrana çizmemiz gerekiyor. Bu süreç, birkaç temel adımdan oluşur: gölgelendirici modülü oluşturma, bind grup düzenlerini ve bind gruplarını tanımlama, render pipeline'ı yapılandırma ve son olarak çizim komutlarını kaydetme ve yürütme.
Gölgelendirici Modülü ve Pipeline Düzenleri
İlk olarak, WGSL kodumuzu bir GPUShaderModule nesnesine dönüştürmemiz gerekir. Bu modül, GPU'nun doğrudan anlayacağı bir formata çevrilir. Ardından, gölgelendiricimizin nasıl veri alacağını (uniform değişkenler, dokular vb.) belirten GPUBindGroupLayout ve GPUPipelineLayout nesnelerini oluştururuz.
// ... initWGPU fonksiyonunun içinde ...
const shaderCode = // WGSL hash ve voronoi_noise fonksiyonları buraya gelecek
// ... (Yukarıdaki WGSL kodunu buraya yapıştırın) ...
struct VertexOutput {
@builtin(position) position: vec4f,
@location(0) uv: vec2f,
}
@vertex
fn vertex_main(@location(0) position: vec2f) -> VertexOutput {
var output: VertexOutput;
output.position = vec4f(position, 0.0, 1.0);
// [-1, 1] aralığındaki position'ı [0, 1] aralığına çevir
output.uv = (position * 0.5) + 0.5;
return output;
}
@group(0) @binding(0)
var uniforms: struct {
resolution: vec2f,
time: f32,
scale: f32,
seed: f32,
};
@fragment
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
let uv = input.uv;
let voronoi_val = voronoi_noise(uv, uniforms.scale, uniforms.seed + uniforms.time * 0.05);
let color_val = smoothstep(0.0, 0.6, voronoi_val);
return vec4f(color_val, color_val, color_val, 1.0);
} ;
const shaderModule = device.createShaderModule({
label: "Voronoi Shader",
code: shaderCode,
});
const bindGroupLayout = device.createBindGroupLayout({
label: "Uniform Bind Group Layout",
entries: [{
binding: 0,
visibility: GPUShaderStage.FRAGMENT, // Sadece fragment shader'da görülecek
buffer: { type: "uniform" },
}],
});
const pipelineLayout = device.createPipelineLayout({
label: "Render Pipeline Layout",
bindGroupLayouts: [bindGroupLayout],
});
Render Pipeline Oluşturma
Render pipeline, vertex ve fragment gölgelendiricilerimizi, hangi formatta veri beklediğimizi (vertex buffer'lar için), hangi çıktı formatını kullanacağımızı (canvas formatı) ve diğer render ayarlarını bir araya getirir. Bu, GPU'ya çizim işlemi için tam bir talimat setidir.
const renderPipeline = device.createRenderPipeline({
label: "Voronoi Render Pipeline",
layout: pipelineLayout,
vertex: {
module: shaderModule,
entryPoint: "vertex_main",
buffers: [{ // Ekranı kaplayan dörtgenin vertex formatı
arrayStride: 2 * 4, // 2 float * 4 byte/float
attributes: [{
shaderLocation: 0,
offset: 0,
format: "float32x2",
}],
}],
},
fragment: {
module: shaderModule,
entryPoint: "fragment_main",
targets: [{
format: presentationFormat,
}],
},
primitive: {
topology: "triangle-strip", // Dörtgeni çizmek için
},
});
Uniform Buffer ve Bind Grubu Oluşturma
Şimdi, shader'ımızda tanımladığımız uniforms struct'ına veri göndermek için bir GPUBuffer oluşturmamız ve bu buffer'ı bir GPUBindGroup'a bağlamamız gerekiyor. Bu, zaman, ölçek ve çözünürlük gibi değerleri JavaScript'ten WGSL shader'ına aktarmamızı sağlar.
const uniformBufferSize = (2 + 1 + 1) * 4; // resolution (vec2f) + time (f32) + scale (f32) + seed (f32) = 4 * 4 byte = 16 byte
const uniformBuffer = device.createBuffer({
label: "Uniform Buffer",
size: uniformBufferSize,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
const bindGroup = device.createBindGroup({
label: "Uniform Bind Group",
layout: bindGroupLayout,
entries: [{
binding: 0,
resource: { buffer: uniformBuffer },
}],
});
Render Döngüsü ve Çizim Komutları
renderLoop fonksiyonumuzun içinde, her karede güncellenmesi gereken uniform değerlerini yazarız ve ardından çizim komutlarını kaydederiz.
let startTime = performance.now();
const voronoiScale = 15.0; // Voronoi ölçeği
const voronoiSeed = Math.random() * 1000.0; // Başlangıç seed değeri
function renderLoop() {
const currentTime = performance.now();
const elapsedTime = (currentTime - startTime) / 1000.0; // Saniye cinsinden
// Uniform verilerini güncelle
const uniformsData = new Float32Array([
canvas.width, canvas.height, // resolution
elapsedTime, // time
voronoiScale, // scale
voronoiSeed, // seed
]);
device.queue.writeBuffer(uniformBuffer, 0, uniformsData);
const commandEncoder = device.createCommandEncoder();
const textureView = context.getCurrentTexture().createView();
const renderPassEncoder = commandEncoder.beginRenderPass({
label: "Render Pass",
colorAttachments: [{
view: textureView,
clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
loadOp: "clear",
storeOp: "store",
}],
});
renderPassEncoder.setPipeline(renderPipeline);
renderPassEncoder.setBindGroup(0, bindGroup);
renderPassEncoder.setVertexBuffer(0, vertexBuffer);
renderPassEncoder.draw(4, 1, 0, 0); // 4 köşe (triangle-strip ile 2 üçgen oluşturur)
renderPassEncoder.end();
device.queue.submit([commandEncoder.finish()]);
requestAnimationFrame(renderLoop);
}
Bu adımlar tamamlandığında, web sayfanızda dinamik olarak oluşturulmuş bir Voronoi dokusu görmeye başlayacaksınız. renderLoop içindeki elapsedTime değişkeni sayesinde, doku zamanla hafifçe değişebilir, bu da daha dinamik bir görsel deneyim sunar. voronoiScale ve voronoiSeed gibi değişkenleri değiştirerek farklı doku görünümleri elde edebilirsiniz. Bu yapı, WGPU'nun modern grafik boru hattını kullanarak prosedürel doku üretimi için nasıl bir temel oluşturduğumuzu açıkça göstermektedir.
Gelişmiş Teknikler ve Gerçek Dünya Uygulamaları: Voronoi Dokularını Zenginleştirme
Temel bir Voronoi dokusunu WGPU ile oluşturmayı öğrendiğimize göre, şimdi bu tekniği daha da ileri taşıyacak gelişmiş yöntemlere ve gerçek dünya kullanım senaryolarına göz atalım. Prosedürel Voronoi dokuları, sadece soyut desenler olmaktan öte, birçok karmaşık görsel efekti taklit etmek için kullanılabilir.
Pertürbasyon ve Renk Paletleri: Daha Organik Görünümler
Temel Voronoi deseni genellikle geometrik ve düzgün hücrelerden oluşur. Ancak, doğal dünyadaki desenler genellikle daha "organik" ve "düzensizdir". Bu düzensizliği eklemek için "pertürbasyon" adı verilen bir teknik kullanabiliriz. Bu, Voronoi çekirdek noktalarının konumlarını, başka bir gürültü fonksiyonu (örneğin Perlin veya Simplex gürültüsü) kullanarak hafifçe kaydırmaktır. Bu kaydırma, hücre kenarlarının dalgalı, çatlaklı veya daha az düzgün görünmesini sağlar, bu da onu kaya, ahşap veya yıpranmış yüzeyler gibi malzemeleri taklit etmek için mükemmel kılar.
// ... önceki hash ve voronoi_noise fonksiyonları ...
// Örnek bir Perlin gürültüsü fonksiyonu (Basitliği için basitleştirilmiş)
fn perlin_noise(p: vec2f) -> f32 {
let p_floor = floor(p);
let p_fract = fract(p);
let n00 = hash(p_floor);
let n10 = hash(p_floor + vec2f(1.0, 0.0));
let n01 = hash(p_floor + vec2f(0.0, 1.0));
let n11 = hash(p_floor + vec2f(1.0, 1.0));
let u = p_fract * p_fract * (3.0 - 2.0 * p_fract); // Smoothstep
let res = mix(mix(n00.x, n10.x, u.x), mix(n01.x, n11.x, u.x), u.y);
return res;
}
@fragment
fn fragment_main(input: VertexOutput) -> @location(0) vec4f {
let uv_orig = input.uv;
// Perlin gürültüsü ile UV koordinatlarını pertürbe et
let perturbation = vec2f(perlin_noise(uv_orig * 10.0 + uniforms.time * 0.1),
perlin_noise(uv_orig * 10.0 + uniforms.time * 0.1 + 123.45));
let uv_perturbed = uv_orig + perturbation * 0.05; // Pertürbasyon miktarını ayarla
let voronoi_val = voronoi_noise(uv_perturbed, uniforms.scale, uniforms.seed + uniforms.time * 0.05);
let color_val = smoothstep(0.0, 0.6, voronoi_val);
// Renk paleti kullanımı (Örnek: Kahverengi tonları)
let color_a = vec3f(0.2, 0.1, 0.05); // Koyu kahverengi
let color_b = vec3f(0.8, 0.6, 0.4); // Açık kahverengi
let final_color = mix(color_a, color_b, color_val);
return vec4f(final_color, 1.0);
}
Ayrıca, tek bir gri tonlamalı değer yerine, belirli renk paletleri kullanarak dokulara daha fazla hayat katabiliriz. mix fonksiyonunu kullanarak, Voronoi değerine göre iki veya daha fazla renk arasında geçiş yapabiliriz. Bu, çatlakların farklı bir renkte, hücre içlerinin farklı bir renkte olmasını sağlayarak daha zengin ve görsel olarak çekici sonuçlar verir.
Farklı Voronoi Fonksiyonları ve Hacimsel Dokular
Daha önce F1 ve F2-F1 varyasyonlarını gördük. Ancak Voronoi gürültüsünün başka çeşitleri de mevcuttur. Örneğin, F2, F3-F2, F4-F3 gibi değerler kullanarak farklı estetik sonuçlar elde edilebilir. Her bir varyasyon, belirli doğal desenleri daha iyi taklit edebilir. Örneğin, F2-F1 hücre kenarlarını vurgularken, F1 daha yumuşak bir hücre yapısı gösterir. Ayrıca, Voronoi dokusunu 2D düzlem yerine 3D uzayda hesaplayarak hacimsel dokular (3D gürültü) oluşturmak da mümkündür. Bu, bulutlar, duman veya kaya oluşumları gibi hacimsel materyalleri modellemek için kullanılabilir.
Gerçek Dünya Senaryoları ve Vaka Analizleri
-
Oyunlarda Arazi ve Kaya Dokuları: Minecraft gibi voksel tabanlı oyunlarda veya diğer 3D oyunlarda, Voronoi gürültüsü arazi oluşumları için kullanılabilir. Örneğin, bir gezegen yüzeyindeki kraterleri veya lav akışlarını simüle etmek için F2-F1 Voronoi gürültüsünü rakım verilerine uygulayabilirsiniz.
Uzman İpucu: Oyunlarda Voronoi dokularını sadece görsel değil, aynı zamanda oynanış mekanikleri için de kullanabilirsiniz. Örneğin, hücre kenarları boyunca daha zorlu araziler veya düşman spawn noktaları belirleyebilirsiniz.
- Bilimsel Görselleştirmelerde Hücre Yapısı Modelleme: Biyolojik simülasyonlarda, Voronoi diyagramları hücrelerin veya dokuların büyüme ve bölünme modellerini görselleştirmek için doğal bir yoldur. Her hücre, belirli bir merkezin etki alanını temsil edebilir. Bu sayede, hücrelerin birbirlerine göre nasıl konumlandığını ve etkileşimde bulunduğunu dinamik olarak gösterebilirsiniz.
- Mimari Tasarım ve Malzeme Prototipleme: Mimarlar, cephe tasarımlarında veya iç mekanlarda benzersiz desenler oluşturmak için prosedürel Voronoi dokularından ilham alabilirler. Örneğin, bir binanın cam panellerinin veya bir duvarın yüzey kaplamasının rastgele ama düzenli desenlerini oluşturmak için kullanılabilir. Ayrıca, fayans desenleri veya mozaik efektler için de idealdir.
- Animasyon ve Görsel Efektler (VFX): Akışkan simülasyonlarında su yüzeyindeki köpükler veya çarpışma efektlerindeki parçacık dağılımları gibi birçok dinamik efekt, Voronoi gürültüsü ile gerçekçi bir şekilde oluşturulabilir. Zaman parametresini değiştirerek dokunun sürekli olarak evrimleşmesini sağlayabilirsiniz.
WGPU'nun sağladığı performans ve esneklik sayesinde, bu gelişmiş teknikleri ve gerçek dünya uygulamalarını doğrudan web tarayıcınızda veya yerel masaüstü uygulamalarınızda (Rust ile wgpu-rs kullanarak) hayata geçirebilirsiniz. Prosedürel Voronoi dokuları, yaratıcılığınızı ve teknik becerilerinizi birleştirerek sınırsız görsel potansiyel sunar.
Sonuç: Prosedürel Voronoi Dokularıyla Sınırsız Yaratıcılık
Bu makale boyunca, WGPU kullanarak prosedürel Voronoi dokularının nasıl oluşturulacağını, temel prensiplerinden ileri düzey tekniklerine ve gerçek dünya uygulamalarına kadar detaylı bir şekilde inceledik. Başlangıçta Voronoi diyagramlarının ne olduğunu ve neden dinamik dokuların görsel projelerimiz için vazgeçilmez olduğunu anlamakla yola çıktık. Ardından, WGPU'nun modern GPU programlama dünyasında sunduğu avantajları ve temel kurulum adımlarını ele aldık. En önemlisi, WGSL kullanarak Voronoi gürültüsünü hesaplayan shader kodunu adım adım inşa ettik ve bu shader'ı WGPU render pipeline'ına nasıl entegre edeceğimizi öğrendik.
Prosedürel doku üretimi, sanatçılara ve geliştiricilere sonsuz sayıda benzersiz ve organik desen oluşturma özgürlüğü sunar. WGPU'nun gücüyle birleşen Voronoi dokuları, sadece statik görüntüler değil, aynı zamanda dinamik ve etkileşimli görsel deneyimler yaratmak için de kullanılabilir. Pertürbasyon teknikleri, renk paletleri ve farklı Voronoi varyasyonları ile dokularınızı daha da zenginleştirme potansiyeli, hayal gücünüzle sınırlıdır. İster oyun geliştiriyor, ister bilimsel verileri görselleştiriyor, isterse mimari tasarımlar yapın, prosedürel Voronoi dokuları projelerinize derinlik ve gerçekçilik katacaktır. Bu teknikleri kullanarak, dijital dünyalara yeni bir soluk getirebilir ve kullanıcılarınıza unutulmaz görsel deneyimler sunabilirsiniz.
Sıkça Sorulan Sorular (SSS)
- WGPU nedir ve WebGL'den farkı nedir?
- WGPU (WebGPU), web tarayıcıları için tasarlanmış yeni nesil bir grafik ve hesaplama API'sidir. WebGL'e kıyasla daha düşük seviyeli GPU kontrolü, modern GPU API'leri (Vulkan, Metal, DirectX 12) ile daha fazla uyumluluk, daha iyi performans ve genel amaçlı GPU hesaplamaları (compute shaders) için destek sunar. Bu sayede, daha karmaşık ve verimli grafik uygulamaları geliştirmeye olanak tanır.
- WGSL (WebGPU Shading Language) nedir?
- WGSL, WGPU için özel olarak tasarlanmış bir gölgelendirici dilidir. C benzeri bir sözdizimine sahiptir ve modern GPU'ların özelliklerinden tam olarak yararlanmak için optimize edilmiştir. Fragment ve vertex gölgelendiricileri gibi GPU üzerinde çalışan programları yazmak için kullanılır.
- Voronoi dokularını oluştururken performansı nasıl optimize edebilirim?
- Performansı optimize etmek için birkaç yöntem bulunmaktadır:
- Daha Az Komşu Hücre Kontrolü: Genellikle 3x3 komşu hücre kontrolü yeterlidir, ancak ölçek çok küçükse veya daha yüksek hücre yoğunluğu gerekiyorsa 5x5 veya daha büyük bir alanı kontrol etmek gerekebilir. Gereksiz hücreleri kontrol etmekten kaçının.
- Daha Hızlı Hash Fonksiyonları: Karmaşık matematiksel işlemler içeren hash fonksiyonları yerine daha hafif ve hızlı çalışan hash fonksiyonları kullanın.
- Detay Seviyesi (LOD): Uzak nesneler için daha düşük çözünürlüklü veya daha az karmaşık Voronoi hesaplamaları kullanarak performansı artırabilirsiniz.
- Uniform Veri Optimizasyonu: Shader'a gönderilen uniform verilerin boyutunu ve güncellenme sıklığını minimize edin.
- Sadece 2D Voronoi mi oluşturabilirim, yoksa 3D de mümkün mü?
- Evet, hem 2D hem de 3D Voronoi dokuları oluşturmak mümkündür. Bu makalede 2D bir düzlem üzerinde Voronoi dokusu oluşturmaya odaklandık. Ancak, WGSL shader'ındaki matematiksel hesaplamaları 3 boyutlu vektörler ve komşu küpler (3x3x3) üzerinde çalışacak şekilde genişleterek hacimsel (3D) Voronoi dokuları da üretebilirsiniz. Bu, bulutlar, sis veya diğer hacimsel efektler için kullanışlıdır.
- Oluşturduğum Voronoi dokularını başka projelerde nasıl kullanabilirim?
- WGPU ile oluşturduğunuz Voronoi dokusunu bir
GPUTextureolarak alıp, bu dokuyu daha sonra başka bir shader içinde örnekleyerek (sampling) kullanabilirsiniz. Örneğin, 3D bir modelin malzemesine normal harita veya difüz harita olarak uygulayabilirsiniz. Ayrıca, canvas'ı bir görüntü olarak kaydederek (canvas.toDataURL()) veyaOffscreenCanvaskullanarak dokuyu bir dosyaya aktarabilirsiniz. Bu sayede, doku başka oyun motorlarında veya grafik uygulamalarında da kullanılabilir.