Pattern break · Toon / cel-shading
MeshToonMaterial — 3B dünyada çizgi film estetiği
Işığı yumuşatmak değil; net renk bloklarına bölmek
3B modellerinizin bir anime veya teknik bir el çizimi gibi görünmesini hiç istediniz mi? Gerçekçilik her zaman nihai hedef değildir. Bazen ihtiyacınız olan şey, ışığın pürüzsüz gradyanları yerine, bir fırça darbesi kadar keskin ve net renk bloklarıdır. Bu estetikte derinlik, yumuşak gölgelerden çok ışığın yüzey normaline göre düştüğü bantların sayısı ve konumuyla okunur.
MeshToonMaterial (cel-shading), tam bu noktada devreye girerek derinliği
stilize bir sanata dönüştürür.
PBR’de aynı sahne “fotoğraf gibi” kalırken, toon aynı geometriyi tasarım diline
çevirir — bu yüzden çoğu projede PBR ile karıştırılırken ışık ve post-processing hedefi açıkça
ayrılır.
Önkoşul: MeshPhysicalMaterial (cam / katmanlı PBR), MeshStandardMaterial (temel PBR), MeshLambertMaterial (sürekli ton kıyası için). Hızlı tablo: Standard vs. Toon. Kavramsal çerçeve: Materyal giriş. Sayfa sonunda özet ve komşu ders bağlantıları.
Işığın kırılma noktası: basamaklandırma
Geleneksel materyaller ışığı bir yüzeye yayarken binlerce ara ton üretir. Toon materyali bu tonları reddeder: ışık yoğunluğunu eşik değerlerine böler. Işığın vurduğu bölge tam aydınlık, gölgede kalan yer ise keskin bir sınırla koyulaşır. Bu, beynimize nesnenin “bilgisayar grafiği değil, illüstrasyon” olduğu sinyalini verir.
Shader düşüncesinde bu, genelde yüzey–ışık kosinüsünün (N·L) sürekli fonksiyonundan çok, eşiklenmiş veya örneklenmiş bir aralığa indirgenmesi demektir. Bu yüzden aynı toon materyali, farklı mesh yoğunluğunda farklı “keskinlikte” okunabilir: düşük poligonlu formlarda bantlar daha kaba, yüksek eğrilikte daha ince parçalanır — materyal tek başına değil, geometri + ışık yönü ile birlikte tasarım kararıdır.
Hızlı karşılaştırma: Standard vs. Toon
Özet bakış
Tablo “hangisi daha iyi?” sorusuna değil, hangi sözleşmeye hizmet ediyoruz? sorusuna cevap verir: ürün görselleştirmede PBR hakimdir; karakter, UI mascots veya eğitim içeriğinde toon sık sık ana dil olur. İkisini aynı karede birleştirirken izleyiciye “bu nesne fotografik, bu nesne illüstratif” mesajını bilinçli vermek gerekir — aksi halde sahne tutarsız hissedilir.
Performans algısı genelde toon lehinedir; fakat gradientMap, outline veya
post-process hat ile birlikte kullanıldığında toplam maliyet yine ölçülmelidir. Özet:
toon “ucuz PBR” değildir; farklı bir görsel sözleşmedir.
Gradyan haritalarının gücü
(gradientMap)
Kaç farklı “renk bandına” bölüneceğini siz belirlersiniz. Varsayılan iki tonlu görünümden sıkıldıysanız bir gradient map kullanın. Gradyan dokusunu yüklerken keskinliği korumak kritiktir: filtreleme renkleri karıştırırsa basamaklar kaybolur.
Dokunun yatay ekseni tipik olarak “ışık–gölge ekseni” olarak düşünülür: her dikey şerit, belirli bir parlaklık aralığına karşılık gelir. Bu nedenle doku çözünürlüğü çok düşükse bantlar kaba, çok yüksekse gereksiz bellek harcar — çoğu sahne için dar ve uzun bir gradyan haritası (ör. birkaç piksel genişliğinde çok satır) yeterlidir.
// Gradyan dokusunu yüklerken "keskinliği" korumak hayati önem taşır
const gradientMap = new THREE.TextureLoader().load('/textures/toon_step.png');
// ❌ YAYGIN HATA: Filtrelemeyi unutmak
// NearestFilter kullanmazsanız tarayıcı renkleri birbirine karıştırır ve efekt ölür.
gradientMap.minFilter = THREE.NearestFilter;
gradientMap.magFilter = THREE.NearestFilter;
gradientMap.generateMipmaps = false;
const toonMaterial = new THREE.MeshToonMaterial({
color: '#3399ff',
gradientMap: gradientMap
});
Üretimde gradyan haritasını dosyadan değil, veri dokusu veya küçük bir canvas ile üretmek
sık görülür; önemli olan colorSpace ayarının hedef tonlamayla uyumlu olması
ve mip üretiminin (çoğu zaman kapalı) basamakları sulandırmamasıdır. Aşağıdaki etkileşimli
örnekte prosedürel gradyan kullanıldığı için harici PNG şart değildir; yine de
NearestFilter disiplini aynen geçerlidir.
İnteraktif: küre, Lambert kıyas, küp
Toon: sürekli (continuous) gölgelendirme yerine
ayrık (discrete) ışık bantları üretir — eğitimde bu cümle altın
standarttır. Aynı tek DirectionalLight altında: büyük küre ve küp
MeshToonMaterial, ortadaki küçük küre MeshLambert (kıyas).
Işık yönünü kaydırınca toon’da bantlar kayar; Lambert’te ton yumuşar.
gradientMap prosedürel veri dokusu; minFilter /
magFilter = NearestFilter (keskin basamak). Harici PNG/HDR yok.
Önce sürgüleri ve onay kutularını oynatıp readout ile kısa geri
bildirimi okuyun; ardından figür altındaki özetten ışık yoğunluğu, gölge tipi ve hat
davranışını doğrulayın — böylece aynı bilgi iki kez üst üste binmez. Ölçüler
demo sabitleri
tablosunda diagram-toon-material.js ile eşlenir.
2 → çok sert (anime) · 4 → klasik toon · 5–6 → yarı-realistik
gradientMap kapalıyken hat otomatik kapanır ve bu seçenek devre dışı kalır.
Outline genelde toon shading (gradientMap) ile birlikte kullanılır.
Sol: pürüzsüz küre + toon → ayrık ışık bantları.
Orta: aynı renkte Lambert → sürekli ton (kıyas).
Sağ: küp + toon → yüzey başına düz bloklar. Ana ışık ~2.1, hafif soğuk
ambient ve exposure ile sahne okunaklı; gradientMap 8×128 +
NearestFilter ile basamaklar keskin örneklenir. Varsayılan gölge
BasicShadowMap; PCF yumuşatınca toon okuması zayıflar. Hat:
toon küre/küp altında ikinci mesh (BackSide + scale); gradientMap
kapalıyken hat kapanır (stil bütünlüğü). Lambert referansı çizgisiz kalır.
Demo sabitleri tablosu (küre · Lambert · küp)
initToonDemo · prosedürel gradientMap (DataTexture
8×128, basamak sayısı sürgüden), tek yönlü gölge üreten ışık, BasicShadowMap
varsayılan; hat için alt mesh (BackSide + ölçek).
| Sahne / rol | Parametre | Değer | Tür |
|---|---|---|---|
| Renderer | WebGLRenderer |
antialias: true · alpha: false ·
powerPreference: high-performance ·
setPixelRatio(min(dpr, 2)) · SRGBColorSpace · arka
plan
0x080d18 · ACESFilmicToneMapping · exposure
1.14 · gölge enabled · varsayılan
BasicShadowMap
|
🔒 + ↔ UI |
| Izgara | GridHelper |
boyut 14 · bölüm 28 · renkler
0x5a5890 / 0x222838 · y = -0.52
|
🔒 Sabit |
| Zemin | MeshLambertMaterial / PlaneGeometry |
18×18 · renk 0x141c2c ·
receiveShadow · rotation.x = -π/2 ·
y = -0.52
|
🔒 Sabit |
| Toon küre | MeshToonMaterial |
renk 0x72d0ff · SphereGeometry(0.92, 40, 32) · konum
(-0.52, 0.62, 0) · castShadow · hat alt mesh ölçek
1.038 · hat renk 0x050508 ·
BackSide
|
🔒 + ↔ UI |
| Lambert referans | MeshLambertMaterial |
aynı renk 0x72d0ff · emissive 0x061828
intensity 0.08 · yarıçap 0.34 · konum
(0.95, 0.48, 0.35) · castShadow
|
🔒 Sabit |
| Toon küp | MeshToonMaterial |
renk 0xffdc98 · BoxGeometry(0.68,0.68,0.68) · konum
(1.42, 0.52, -0.42) · dönüş (0.18, 0.52, 0.08) · hat
ölçek 1.024
|
🔒 + ↔ UI |
| Ortam ışığı | AmbientLight |
0xe8f0ff · 0.2 |
🔒 Sabit |
| Yönlü ışık | DirectionalLight |
0xfff8f5 · intensity 2.12 · castShadow ·
harita 1024² · bias -0.0002 ·
normalBias 0.03 · gölge kamera ±4.2 ·
near 0.5 far 22 · hedef (0.15, 0.35, 0) ·
konum: yörünge r = 7.8, y = 5.4, açı HTML
0…100 → t·2π
|
🔒 + ↔ UI |
| Gradyan dokusu | DataTexture |
boyut 8×128 · basamak clamp(2…8) ·
NearestFilter · generateMipmaps false ·
NoColorSpace (destekleniyorsa)
|
🔒 + ↔ UI |
| Kamera | PerspectiveCamera |
FOV 42 · yakın/uzak 0.08 / 60 · konum
(-0.15, 1.05, 4.1) · lookAt(0.2, 0.45, 0)
|
🔒 Sabit |
| OrbitControls | damping / mesafe | enableDamping · 0.07 · hedef
(0.2, 0.45, 0) · mesafe 2.4…14
|
🔒 Sabit |
| HTML · Işık yörünge | type="range" |
0…100 · adım 0.5 · varsayılan 32 |
🔒 + ↔ UI |
| HTML · Basamak | type="range" |
2…6 · adım 1 · varsayılan 4 |
🔒 + ↔ UI |
| HTML · Üç onay | checkbox |
gradientMap varsayılan açık · PCF gölge varsayılan kapalı · hat
varsayılan açık (gradient kapalıyken hat devre dışı) |
🔒 + ↔ UI |
Önemli kod kesitleri
Demo tek bir apply() yerine ışık/gölge, gradyan yeniden üretimi ve hat
görünürlüğünü ayıran birkaç fonksiyon kullanır; aşağıda akışın özü toplanmıştır.
function setLightOrbit() {
const t = lightRange ? Number(lightRange.value) / 100 : 0.35;
const phi = t * Math.PI * 2;
const r = 7.8;
const y = 5.4;
dir.position.set(Math.cos(phi) * r, y, Math.sin(phi) * r);
dir.target.updateMatrixWorld();
}
function applyLightAndShadow() {
setLightOrbit();
if (softShadowCb) {
renderer.shadowMap.type = softShadowCb.checked
? THREE.PCFSoftShadowMap
: THREE.BasicShadowMap;
}
updateReadout();
}
function rebuildGradientMap() {
const useGrad = !!(gradCb && gradCb.checked);
if (bandsRange) bandsRange.disabled = !useGrad;
const steps = bandsRange ? Number(bandsRange.value) : 4;
if (gradientTex) {
gradientTex.dispose();
gradientTex = null;
}
if (useGrad) {
gradientTex = makeBandGradientTexture(steps);
toonSphereMat.gradientMap = gradientTex;
toonCubeMat.gradientMap = gradientTex;
} else {
toonSphereMat.gradientMap = null;
toonCubeMat.gradientMap = null;
}
toonSphereMat.needsUpdate = true;
toonCubeMat.needsUpdate = true;
syncOutlineWithGradient();
}
function applyOutline() {
const gradOn = !!(gradCb && gradCb.checked);
const on = gradOn && !!(outlineCb && outlineCb.checked);
outlineSphere.visible = on;
outlineCube.visible = on;
updateReadout();
}
Yaygın hata: ışığın rengi ve sayısı
Çok renkli, çok kaynaklı ışık
Hata: Sahnede beş–altı farklı renkli ışık kullanmak.
Sonuç: Renk blokları birbirine girer; “çizgi film” hissi kaybolur, görüntü kirlenir.
Çözüm: Tek bir güçlü DirectionalLight (güneş) ve hafif bir
AmbientLight ile en temiz okumayı alırsınız.
Renkli rim veya çoklu dolgu ışıkları bazen “sahneyi zenginleştirir” sanılır; toon okumasında ise çoğu zaman bant sayısını ikiye katlar ve kontrolü kaybedersiniz. İstisna, bilinçli olarak “neon şehir” gibi ikinci bir stil katmanı hedeflemektir — o zaman da paleti ve ışık sayısını tasarım rehberiyle sınırlamak gerekir.
Demo, tek yönlü ana ışık + hafif ambient ile bu disiplini somutlar; PCF yumuşatmayı açtığınızda neden stilin zayıfladığını figür altındaki notla birlikte gözlemleyebilirsiniz.
Ne zaman kullanılmamalı?
- Mikro detay: Çok girinti–çıkıntılı yüzeylerde toon, küçük alanları siyah lekeler gibi gösterebilir.
- Yumuşak atmosfer: Sisli, dumanlı veya romantik yumuşak ışıkta bu materyalin keskinliği atmosferi bozabilir.
“Toon kullanmayalım” demek, teknik olarak mümkün olsa da bazen yanlış tasarruf olur: örneğin eğitimde veya oyun prototipinde okunabilirlik için bilinçli stilize seçim, fotorealistik gürültüden daha az GPU harcayabilir. Soru şudur: hedef kitle bu dili bekliyor mu? Beklemiyorsa Standard veya Physical ile devam etmek daha güvenlidir.
Ürün görselleştirmede (otomotiv, mobilya, mücevher) toon genelde yalnızca marka animasyonu veya AR filtreleri gibi ikincil yüzeylerde kullanılır; ana ürün görünümü PBR’de kalır.
Holodepth notu: gerçek anime etkisi
Çizgi film = materyal + hat
Tam bir “Ghibli” veya “Spider-Verse” hissi için yalnızca toon materyal yetmez: nesnelerin kenarlarına siyah hat (outline) eklemek gerekir. Inverted hull (mesh’i kopyalayıp ters yüz ve hafif ölçekle genişletme) veya Three.js OutlinePass (post-processing) yollarını araştırın. Stilize dünyalarda sınır, materyalden çoğu zaman sanatsal vizyondur.
HoloDepth üretiminde hat kararını erken verin: gerçek zamanlı outline genelde ek draw veya tam ekran geçiş maliyeti getirir; hangi nesnelerde hat zorunlu, hangilerinde yalnızca toon gölge yeterli, bunu sahne bazında listelemek revizyon maliyetini düşürür. Bir sonraki adımda normal vektörü doğrudan renge bağlayan MeshNormalMaterial ile “stilize ama tamamen farklı bir okuma” diline geçebilirsiniz.