This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. Read this tutorial in English
Bu öğreticide renk düzenleme (color grading) yapan bir tam ekran son işlem efekti (post effect) oluşturacağız. Görüntü oluşturmak için kullanılan temel işleme (rendering) yöntemi bulanıklaştırma, izler, parlama, renk ayarları gibi çeşitli son işlem efektlerine de uygulanabilir.
Defold düzenleyicisini kullanmayı bildiğiniz ve GL gölgelendiricileri (shader) ile Defold işleme hattı (rendering pipeline) hakkında temel bilginiz olduğu varsayılır. Bu konuları öğrenmeniz gerekiyorsa Gölgelendirici kılavuzumuza ve İşleme kılavuzuna göz atın.
Varsayılan işleme betiğiyle (render script), her görsel bileşen (component; sprite bileşeni, karo haritası, parçacık efekti, GUI vb.) doğrudan grafik kartının kare arabelleğine (frame buffer) işlenir. Ardından donanım, grafiklerin ekranda görünmesini sağlar. Bir bileşenin piksellerinin asıl çizimini bir GL gölgelendirici programı (shader program) yapar. Defold, her bileşen türü için piksel verilerini değiştirmeden ekrana çizen varsayılan bir gölgelendirici programıyla gelir. Normalde istediğiniz davranış budur: görüntüleriniz ekranda ilk tasarlandıkları hâliyle görünmelidir.
Bir bileşenin gölgelendirici programını, piksel verilerini değiştiren veya program yoluyla tamamen yeni piksel renkleri oluşturan bir programla değiştirebilirsiniz. Shadertoy öğreticisi bunu nasıl yapacağınızı öğretir.
Şimdi oyununuzun tamamını siyah beyaz işlemek istediğinizi varsayalım. Olası çözümlerden biri, her bileşen türünün kendi gölgelendirici programını, piksel renklerinin doygunluğunu kaldıracak şekilde değiştirmektir. Defold şu anda 6 yerleşik materyal ve 6 köşe ve parça gölgelendiricisi program çiftiyle geldiğinden bu epey çalışma gerektirir. Ayrıca daha sonraki her değişikliğin veya efekt eklemesinin de her gölgelendirici programına uygulanması gerekir.
Çok daha esnek bir yaklaşım, işlemeyi iki ayrı adımda yapmaktır:

Bu yöntemle, oluşan görsel verileri ekrana ulaşmadan önce okuyabilir ve değiştirebiliriz. Yukarıdaki 2. adıma gölgelendirici programları ekleyerek kolayca tam ekran efektler elde edebiliriz. Bunun Defold’da nasıl kurulacağını görelim.
Yerleşik işleme betiğini değiştirip yeni işleme işlevini eklememiz gerekiyor. Varsayılan işleme betiği iyi bir başlangıç noktasıdır; bu nedenle onu kopyalayarak başlayın:
grade.render dosyasını açın ve Script özelliğini “/main/grade.render_script” olarak ayarlayın.

game.project dosyasını açın ve Render ayarını “/main/grade.render” olarak ayarlayın.

Oyun artık değiştirebileceğimiz yeni bir işleme hattıyla çalışacak şekilde ayarlandı. Motorun işleme betiği kopyamızı kullandığını sınamak için oyununuzu çalıştırın, ardından işleme betiğinde görsel bir sonuç verecek bir değişiklik yapıp betiği yeniden yükleyin. Örneğin, karoların ve sprite bileşenlerinin çizimini devre dışı bırakabilir, ardından ⌘ + R tuşlarına basarak “bozuk” işleme betiğini çalışan oyunda yeniden yükleyebilirsiniz (hot reload):
...
render.set_projection(vmath.matrix4_orthographic(0, render.get_width(), 0, render.get_height(), -1, 1))
-- render.draw(self.tile_pred) -- <1>
render.draw(self.particle_pred)
render.draw_debug3d()
...
Bu basit sınamada sprite bileşenleri ve karolar kaybolursa oyunun sizin işleme betiğinizi çalıştırdığını anlarsınız. Her şey beklendiği gibi çalışıyorsa işleme betiğinde yaptığınız değişikliği geri alabilirsiniz.
Şimdi işleme betiğini, kare arabelleği yerine ekran dışı işleme hedefine çizecek şekilde değiştirelim. Önce işleme hedefini oluşturmamız gerekiyor:
function init(self)
self.tile_pred = render.predicate({"tile"})
self.gui_pred = render.predicate({"gui"})
self.text_pred = render.predicate({"text"})
self.particle_pred = render.predicate({"particle"})
self.clear_color = vmath.vector4(0, 0, 0, 1)
self.clear_color.x = sys.get_config_number("render.clear_color_red", 0)
self.clear_color.y = sys.get_config_number("render.clear_color_green", 0)
self.clear_color.z = sys.get_config_number("render.clear_color_blue", 0)
self.clear_color.w = sys.get_config_number("render.clear_color_alpha", 1)
self.view = vmath.matrix4()
local color_params = { format = graphics.TEXTURE_FORMAT_RGBA,
width = render.get_width(),
height = render.get_height() } -- <1>
local target_params = {[graphics.BUFFER_TYPE_COLOR0_BIT] = color_params }
self.target = render.render_target("original", target_params) -- <2>
end
Şimdi yalnızca özgün işleme kodunun başına ve sonuna şu şekilde render.set_render_target() çağrıları eklememiz gerekiyor:
function update(self)
render.set_render_target(self.target) -- <1>
render.set_depth_mask(true)
render.set_stencil_mask(0xff)
render.clear({[graphics.BUFFER_TYPE_COLOR0_BIT] = self.clear_color, [graphics.BUFFER_TYPE_DEPTH_BIT] = 1, [graphics.BUFFER_TYPE_STENCIL_BIT] = 0})
render.set_viewport(0, 0, render.get_width(), render.get_height()) -- <2>
render.set_view(self.view)
...
render.set_render_target(render.RENDER_TARGET_DEFAULT) -- <3>
end
render.draw() çağrısı ekran dışı işleme hedefimizin arabelleklerine çizecektir.update() içindeki tüm özgün çizim kodu olduğu gibi bırakılır.Yapmamız gerekenlerin hepsi bu. Oyunu şimdi çalıştırırsanız her şeyi işleme hedefine çizecektir. Ancak artık kare arabelleğine hiçbir şey çizmediğimiz için yalnızca siyah bir ekran göreceğiz.
İşleme hedefinin renk arabelleğindeki pikselleri ekrana çizmek için piksel verilerini doku olarak uygulayabileceğimiz bir şey hazırlamamız gerekiyor. Bu amaçla düz, kare biçiminde bir 3B model kullanacağız.
main.collection dosyasını açın ve “grade” adlı yeni bir oyun nesnesi (game object) oluşturun.grade” oyun nesnesine bir Model bileşeni ekleyin.builtins/assets/meshes içinde bulunan quad.gltf dosyasına ayarlayın.Oyun nesnesini ölçeklemeden başlangıç noktasında bırakın. Daha sonra, kareyi işlerken tüm ekranı dolduracak şekilde izdüşümünü alacağız. Ancak önce kare için bir materyal ve gölgelendirici programları gerekiyor:
grade.material adını verin.grade.vp adlı bir köşe gölgelendiricisi programı ve grade.fp adlı bir parça gölgelendiricisi programı oluşturun.CONSTANT_TYPE_VIEWPROJ türünde “view_proj” adlı bir Vertex constant ekleyin. Bu, kare köşeleri için köşe programında kullanılan görünüm ve izdüşüm matrisidir.original” adlı bir Sampler ekleyin. Bu, ekran dışı işleme hedefinin renk arabelleğinden pikselleri örneklemek için kullanılacaktır.“grade” adlı bir Tag ekleyin. Kareyi çizmek için işleme betiğinde bu etiketle eşleşen yeni bir işleme yüklemi (render predicate) oluşturacağız.

main.collection dosyasını açın, “grade” oyun nesnesindeki model bileşenini seçin ve Material özelliğini “/main/grade.material” olarak ayarlayın.

Köşe gölgelendiricisi programı, temel şablondan oluşturulduğu hâliyle bırakılabilir:
// grade.vp
uniform mediump mat4 view_proj;
// positions are in world space
attribute mediump vec4 position;
attribute mediump vec2 texcoord0;
varying mediump vec2 var_texcoord0;
void main()
{
gl_Position = view_proj * vec4(position.xyz, 1.0);
var_texcoord0 = texcoord0;
}
Parça gölgelendiricisi programında, gl_FragColor değerini doğrudan örneklenen renk değerine ayarlamak yerine basit bir renk değişikliği yapalım. Bunu esas olarak buraya kadar her şeyin beklendiği gibi çalıştığından emin olmak için yapıyoruz:
// grade.fp
varying mediump vec4 position;
varying mediump vec2 var_texcoord0;
uniform lowp sampler2D original;
void main()
{
vec4 color = texture2D(original, var_texcoord0.xy);
// Desaturate the color sampled from the original texture
float grey = color.r * 0.3 + color.g * 0.59 + color.b * 0.11;
gl_FragColor = vec4(grey, grey, grey, 1.0);
}
Artık kare modelimiz, materyali ve gölgelendiricileriyle birlikte hazır. Yalnızca onu ekranın kare arabelleğine çizmemiz gerekiyor.
Kare modeli çizebilmek için işleme betiğine bir işleme yüklemi eklememiz gerekiyor. grade.render_script dosyasını açın ve init() işlevini düzenleyin:
function init(self)
self.tile_pred = render.predicate({"tile"})
self.gui_pred = render.predicate({"gui"})
self.text_pred = render.predicate({"text"})
self.particle_pred = render.predicate({"particle"})
self.grade_pred = render.predicate({"grade"}) -- <1>
...
end
grade.material dosyasında belirlediğimiz “grade” etiketiyle eşleşen yeni bir yüklem ekleyin.update() içinde işleme hedefinin renk arabelleği doldurulduktan sonra, kare modelin tüm ekranı doldurmasını sağlayan bir görünüm ve izdüşüm ayarlarız. Ardından işleme hedefinin renk arabelleğini karenin dokusu olarak kullanırız:
function update(self)
render.set_render_target(self.target)
...
render.set_render_target(render.RENDER_TARGET_DEFAULT)
render.clear({[graphics.BUFFER_TYPE_COLOR0_BIT] = self.clear_color}) -- <1>
render.set_viewport(0, 0, render.get_window_width(), render.get_window_height()) -- <2>
render.set_view(vmath.matrix4()) -- <3>
render.set_projection(vmath.matrix4())
render.enable_texture(0, self.target, graphics.BUFFER_TYPE_COLOR0_BIT) -- <4>
render.draw(self.grade_pred) -- <5>
render.disable_texture(0, self.target) -- <6>
end
render.clear() çağrısının ekranın kare arabelleğini değil, işleme hedefini etkilediğini unutmayın.grade.material dosyamızın 0 numaralı yuvasında “original” örnekleyicisi bulunduğundan, parça gölgelendiricisi işleme hedefinden örnekleme yapacaktır.grade.material materyalini kullanır; böylece kare çizilecektir.Şimdi oyunu çalıştırıp sonucu görelim:

Renkler, her biri rengin ne kadar kırmızı, yeşil veya maviden oluştuğunu belirleyen üç bileşen değeriyle ifade edilir. Siyahtan kırmızı, yeşil, mavi, sarı ve pembeye, oradan beyaza uzanan tüm renk tayfı bir küp biçimine sığdırılabilir:

Ekranda gösterilebilen her renk bu renk küpünde bulunabilir. Renk düzenlemenin temel fikri, böyle bir renk küpünü renklerini değiştirerek 3B bir arama tablosu (lookup table) olarak kullanmaktır.
Her piksel için:
Bunu parça gölgelendiricimizde yapabiliriz:

Open GL ES 2.0, 3B dokuları desteklemediğinden 3B renk küpünü temsil etmenin başka bir yolunu bulmamız gerekiyor. Bunun yaygın bir yolu, küpü Z ekseni (mavi) boyunca dilimlemek ve her dilimi iki boyutlu bir ızgarada yan yana yerleştirmektir. 16 dilimin her biri 16⨉16 piksellik bir ızgara içerir. Bunu, parça gölgelendiricisinde bir örnekleyiciyle okuyabileceğimiz bir dokuda saklarız:

Elde edilen doku 16 hücre (her mavi renk yoğunluğu için bir hücre) ve her hücre içinde X ekseni boyunca 16 kırmızı renk, Y ekseni boyunca da 16 yeşil renk içerir. Doku, 16 milyon renkli RGB renk uzayının tamamını yalnızca 4096 renkle, yani sadece 4 bit renk derinliğiyle temsil eder. Çoğu ölçüte göre bu berbat olsa da GL grafik donanımının bir özelliği sayesinde çok yüksek renk doğruluğunu geri kazanabiliriz. Nasıl olduğunu görelim.
Bir rengi bulmak için mavi bileşeni kontrol edip kırmızı ve yeşil değerlerin hangi hücreden alınacağını belirlemek gerekir. Doğru kırmızı-yeşil renk kümesine sahip hücreyi bulmanın formülü basittir:
cell = \left \lfloor{B \times (N - 1)} \right \rfloor
Burada B, 0 ile 1 arasındaki mavi bileşen değeri; N ise toplam hücre sayısıdır. Bizim durumumuzda hücre numarası 0–15 aralığında olacaktır. 0 numaralı hücre mavi bileşeni 0 olan tüm renkleri, 15 numaralı hücre ise mavi bileşeni 1 olan tüm renkleri içerir.
Örneğin, (0.63, 0.83, 0.4) RGB değeri, mavi değeri 0.4 olan tüm renklerin bulunduğu hücrede, yani 6 numaralı hücrede bulunur. Bunu bildikten sonra yeşil ve kırmızı değerlere göre son doku koordinatlarını bulmak basittir:

Kırmızı ve yeşil değerler olan (0, 0) değerlerini sol alt pikselin merkezinde, (1.0, 1.0) değerlerini ise sağ üst pikselin merkezinde kabul etmemiz gerektiğine dikkat edin.
Sol alt pikselin merkezinden başlayıp sağ üst pikselin merkezine kadar okumamızın nedeni, geçerli hücrenin dışındaki hiçbir pikselin örneklenen değeri etkilemesini istemememizdir. Aşağıdaki filtreleme açıklamasına bakın.
Dokunun bu belirli koordinatlarında örnekleme yaptığımızda, tam 4 pikselin ortasına denk geldiğimizi görürüz. Peki GL bu noktanın renk değeri olarak bize ne söyleyecek?

Yanıt, materyalde örnekleyicinin filtrelemesini nasıl belirlediğimize bağlıdır.
Örnekleyicinin filtrelemesi NEAREST ise GL, en yakın pikselin renk değerini döndürür (konum değeri aşağı yuvarlanır). Yukarıdaki durumda GL, (0.60, 0.80) konumundaki renk değerini döndürür. 4 bitlik arama dokumuz için bu, renk değerlerini toplamda yalnızca 4096 renge nicemleyeceğimiz anlamına gelir.
Örnekleyicinin filtrelemesi LINEAR ise GL, ara değerlemeyle (interpolation) hesaplanmış renk değerini döndürür. GL, örnekleme konumunun çevresindeki piksellere olan uzaklığa göre bir renk karışımı oluşturur. Yukarıdaki durumda GL, örnekleme noktasının çevresindeki 4 pikselin her birinden %25 içeren bir renk döndürür.
Böylece doğrusal filtreleme kullanarak renk nicemlemesini ortadan kaldırır ve oldukça küçük bir arama tablosundan çok iyi renk hassasiyeti elde ederiz.
Parça gölgelendiricisinde doku aramasını uygulayalım:
grade.material dosyasını açın.lut” adlı ikinci bir örnekleyici ekleyin.Filter min özelliğini FILTER_MODE_MIN_LINEAR, Filter mag özelliğini ise FILTER_MODE_MAG_LINEAR olarak ayarlayın.

Aşağıdaki arama tablosu dokusunu (lut16.png) indirip projenize ekleyin.

main.collection dosyasını açın ve lut doku özelliğini indirdiğiniz arama dokusuna ayarlayın.

Son olarak, renk arama desteğini ekleyebilmemiz için grade.fp dosyasını açın:
varying mediump vec4 position;
varying mediump vec2 var_texcoord0;
uniform lowp sampler2D original;
uniform lowp sampler2D lut; // <1>
#define MAXCOLOR 15.0 // <2>
#define COLORS 16.0
#define WIDTH 256.0
#define HEIGHT 16.0
void main()
{
vec4 px = texture2D(original, var_texcoord0.xy); // <3>
float cell = floor(px.b * MAXCOLOR); // <4>
float half_px_x = 0.5 / WIDTH; // <5>
float half_px_y = 0.5 / HEIGHT;
float x_offset = half_px_x + px.r / COLORS * (MAXCOLOR / COLORS);
float y_offset = half_px_y + px.g * (MAXCOLOR / COLORS); // <6>
vec2 lut_pos = vec2(cell / COLORS + x_offset, y_offset); // <7>
vec4 graded_color = texture2D(lut, lut_pos); // <8>
gl_FragColor = graded_color; // <9>
}
lut örnekleyicisini bildirin.px adıyla) örnekleyin.px değerinin mavi kanal değerine göre rengin hangi hücreden okunacağını hesaplayın.px değerinin kırmızı ve yeşil değerlerine göre doku üzerindeki X ve Y kaydırmalarını hesaplayın.Şu anda arama tablosu dokusu, aradığımız renk değerlerini olduğu gibi döndürüyor. Bu, oyunun özgün renkleriyle işlenmesi gerektiği anlamına gelir:

Buraya kadar her şeyi doğru yapmışız gibi görünüyor, ancak yüzeyin altında gizlenen bir sorun var. Degrade test dokusuna sahip bir sprite bileşeni eklediğimizde neler olduğuna bakın:

Mavi degradede gerçekten çirkin bantlaşmalar görülüyor. Bunun nedeni ne?
Mavi kanaldaki bantlaşma sorunu, GL’nin rengi dokudan okurken mavi kanalda herhangi bir ara değerleme yapamamasından kaynaklanır. Mavi renk değerine göre okunacak belirli bir hücreyi önceden seçeriz ve işlem burada biter. Örneğin, mavi kanal 0.400–0.466 aralığında herhangi bir değer içeriyorsa değerin tam olarak ne olduğu önemli değildir: son rengi her zaman mavi kanalın 0.400 olarak ayarlandığı 6 numaralı hücreden örnekleriz.
Mavi kanalda daha iyi çözünürlük elde etmek için ara değerlemeyi kendimiz uygulayabiliriz. Mavi değer iki komşu hücrenin değeri arasındaysa bu hücrelerin her ikisinden de örnekleme yapıp renkleri karıştırabiliriz. Örneğin, mavi değer 0.420 ise 6 numaralı hücreden ve 7 numaralı hücreden örnekleme yapıp ardından renkleri karıştırmalıyız.
Dolayısıyla iki hücreden okumalıyız:
cell_{low} = \left \lfloor{B \times (N - 1)} \right \rfloor
ve:
cell_{high} = \left \lceil{B \times (N - 1)} \right \rceil
Ardından bu hücrelerin her birinden renk değerlerini örnekleyip şu formüle göre renkler arasında doğrusal ara değerleme yaparız:
color = color_{low} \times (1 - C_{frac}) + color_{high} \times C_{frac}
Burada color~low~, daha düşük numaralı (en soldaki) hücreden örneklenen renk; color~high~ ise daha yüksek numaralı (en sağdaki) hücreden örneklenen renktir. GLSL’nin mix() işlevi bu doğrusal ara değerlemeyi bizim için yapar.
Yukarıdaki C~frac~ değeri, mavi kanal değerinin 0–15 renk aralığına ölçeklenmiş hâlinin kesirli kısmıdır:
C_{frac} = B \times (N - 1) - \left \lfloor{B \times (N - 1)} \right \rfloor
Bir değerin kesirli kısmını veren bir GLSL işlevi de vardır. Bu işlev frac() olarak adlandırılır. Parça gölgelendiricisindeki (grade.fp) son uygulama oldukça basittir:
varying mediump vec4 position;
varying mediump vec2 var_texcoord0;
uniform lowp sampler2D original;
uniform lowp sampler2D lut;
#define MAXCOLOR 15.0
#define COLORS 16.0
#define WIDTH 256.0
#define HEIGHT 16.0
void main()
{
vec4 px = texture2D(original, var_texcoord0.xy);
float cell = px.b * MAXCOLOR;
float cell_l = floor(cell); // <1>
float cell_h = ceil(cell);
float half_px_x = 0.5 / WIDTH;
float half_px_y = 0.5 / HEIGHT;
float r_offset = half_px_x + px.r / COLORS * (MAXCOLOR / COLORS);
float g_offset = half_px_y + px.g * (MAXCOLOR / COLORS);
vec2 lut_pos_l = vec2(cell_l / COLORS + r_offset, g_offset); // <2>
vec2 lut_pos_h = vec2(cell_h / COLORS + r_offset, g_offset);
vec4 graded_color_l = texture2D(lut, lut_pos_l); // <3>
vec4 graded_color_h = texture2D(lut, lut_pos_h);
// <4>
vec4 graded_color = mix(graded_color_l, graded_color_h, fract(cell));
gl_FragColor = graded_color;
}
cell değerinin kesirli kısmına göre renkleri doğrusal olarak karıştırın.Oyunu test dokusuyla yeniden çalıştırmak artık çok daha iyi sonuçlar verir. Mavi kanaldaki bantlaşma kaybolmuştur:

Pekâlâ, özgün oyun dünyasıyla tamamen aynı görünen bir şey çizmek için epey uğraştık. Ancak bu düzenek gerçekten harika bir şey yapmamızı sağlıyor. Sıkı durun!

lut16.png) uygulayın.lut16.png dokusunu renkleri ayarlanmış olanla değiştirin.
İşte bu!