holodepth

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ış

Özellik
MeshStandardMaterial
MeshToonMaterial
Görsel stil
Fotorealistik / gerçekçi
Stilize · anime · çizgi roman
Işık geçişi
Yumuşak ve kesintisiz
Keskin ve basamaklı
En iyi partneri
HDR çevre haritaları
Keskin ışık kaynakları (Directional)
Karmaşıklık
Yüksek (PBR hesaplamaları)
Düşük ( matematiksel eşikler)

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.

Cel-shading sahne · MeshToonMaterial

2 → çok sert (anime) · 4 → klasik toon · 5–6 → yarı-realistik

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).

initToonDemo · Toon cel-shading lab
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…100t·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.