Unity LOD Sistemi

Yayınlandı: 17 Ekim 2020 yasirkula tarafından Oyun Tasarımı, UNITY 3D içinde

Hepinize merhabalar,

Bu derste, Unity‘nin LOD (Level of Detail) sistemini göreceğiz. Bu sistem, yüksek poligonlu bir modelin kameradan uzaklaştıkça daha az poligon ile ekrana çizilmesini sağlayarak oyunun performansını artırır.

Hızlıca derse başlayalım…

A) LOD Group Component’i

Unity’de LOD sistemi, LOD Group component’i üzerinden çalışır. Bu component neyin nesiymiş bir bakalım:

Bu component temel olarak birkaç LOD bölmesinden oluşur. Varsayılan olarak bu bölmeler LOD 0, LOD 1, LOD 2 ve Culled‘dır. Component’in atandığı objenin kameraya olan uzaklığı değiştikçe, bir bölmeden diğerine geçiş yapılır. Aynı anda sadece 1 bölme aktif olabilir. Örneğin üstteki resimde, kamera LOD 1 bölmesine denk gelmektedir ve bu yüzden sadece LOD 1 bölmesi aktiftir. Kamera ikonunun altında yazan %34, objenin ekranın yaklaşık % kaçını kapladığını ifade etmektedir. Eğer kamera ikonunu sağa-sola sürüklerseniz, Scene panelindeki kamera objeye yakınlaşır veya uzaklaşır. LOD bölmelerini test etmenin en hızlı yolu budur.

LOD bölmelerinden birine tıklarsanız, altta Renderers listesi çıkar. Bu listeye sadece Renderer component’ine sahip objeleri ekleyebilirsiniz ve eklediğiniz objeler, LOD Group component’inin atandığı objenin child objeleri olmak zorundadır (veya component’in atandığı objenin kendisi). Peki Renderers listesine bir obje eklememiz ne işe yarar? O objenin sadece o LOD bölmesi aktifken ekrana çizilmesini sağlar:

  • diyelim sadece LOD 0 bölmesine A objesini eklerseniz, kamera LOD 0 harici bir bölmenin içerisindeyken (LOD 1, LOD 2 veya Culled) A objesi ekrana çizilmez
  • sadece LOD 1 bölmesine A objesini eklerseniz, kamera LOD 1 harici bir bölmenin içerisindeyken (LOD 0, LOD 2 veya Culled) A objesi ekrana çizilmez
  • hem LOD 0 hem de LOD 1 bölmelerine A objesini eklerseniz, kamera bu iki bölme dışında bir bölmenin içerisindeyken (LOD 2 veya Culled) A objesi ekrana çizilmez
  • hiçbir LOD bölmesine A objesini eklemezseniz, A objesi LOD Group component’inden etkilenmez ve daima ekrana çizilir

Culled bölmesine hiçbir obje ekleyemezsiniz. Kamera Culled bölmesinin içerisindeyken, diğer LOD bölmelerindeki objelerin hiçbiri ekrana çizilmez.

Bir LOD bölmesine sağ tıklayıp Delete diyerek, o bölmeyi silebilirsiniz. Veya sağ tıklayıp Insert Before diyerek, o bölmenin soluna yeni bir bölme ekleyebilirsiniz. İki bölmenin birleştiği kenarı sürükleyerek, bu bölmelerin kapladığı alanı değiştirebilirsiniz. Üstteki örnekte, obje ekranın yaklaşık %60 veya üzerini kaplıyorsa LOD 0 bölmesi aktiftir, %30 veya üzerini kaplıyorsa LOD 1 bölmesi aktiftir, %10 veya üzerini kaplıyorsa LOD 2 bölmesi aktiftir, %10’dan daha az yer kaplıyorsa da Culled bölmesi aktiftir.

Component’in nasıl çalıştığını göstermek amaçlı, sahnemde boş bir GameObject oluşturup içine bir Sphere bir de Cube objesi attım. Ardından boş GameObject’ime LOD Group component’i verip bölmelerini şu şekilde doldurdum:

Kamera ikonunu sürüklediğimde LOD’nin önizlemesi şu şekilde oluyor:

Gördüğünüz üzere, LOD 0 bölmesi aktifken Sphere objesi ekrana çizilir, Cube objesi ekrana çizilmez; LOD 1 bölmesi aktifken de Cube objesi ekrana çizilir, Sphere objesi ekrana çizilmez. Culled bölmesi aktifken her iki obje de ekrana çizilmez.

Bölmeler arası geçişin küt diye olduğunu görebilirsiniz. Bu geçişi daha da yumuşak yapmak için Fade Mode değişkeni bulunmakta. Bu değişkene dersin ilerleyen kısımlarında bakacağız.

En sonunda gelelim LOD Group component’inin yüksek poligonlu objeyi ekrana nasıl daha az poligon ile çizebildiğine. Burada Unity’nin yaptığı sihirli bir şey yok:

  1. elinizdeki yüksek poligonlu modelin daha düşük poligonlu versiyon(lar)ını siz oluşturup Unity’e ekliyorsunuz
  2. hem yüksek poligonlu modeli hem de düşük poligonlu model(ler)i aynı objenin child’ı yapıyorsunuz
  3. o objeye LOD Group component’i veriyorsunuz
  4. LOD Group’un LOD 0 bölmesine yüksek poligonlu objeyi, LOD 1 bölmesine daha düşük poligonlu modeli, LOD 2 bölmesine daha daha düşük poligonlu modeli koyuyorsunuz (veya LOD 2 bölmesini siliyorsunuz)
  5. artık kameranın objeye olan uzaklığına göre, LOD Group component’i vasıtasıyla yüksek poligonlu model veya daha düşük poligonlu model ekrana çiziliyor

Neyse ki bu süreç illa bu kadar uzun olmak zorunda değil. “LOD Modellerini Oluşturmak” kısmında göreceğimiz üzere, LOD Group component’inin kurulumu çoğunlukla otomatik olarak yapılıyor.

LOD Group component’inin altındaki butonlardan bahsederek bu bölümü tamamlayacak olursam:

  • Recalculate Bounds: objenin ekranda ne kadar yer kapladığı %’sini hesaplamak için kullanılan dikdörtgensel alanın tekrar hesaplanmasını sağlıyormuş ama ben henüz bu butonun aktif olduğunu görmedim. Aktif görürseniz basın gitsin
  • Recalculate Lightmap Scale: LOD Group’a eklenen Renderer component’lerinin, Inspector’larındaki Scale In Lightmap değerlerini güncellemeye yarar. Bu hesaplama, o LOD bölmesinin ekranda kapladığı % alanla doğru orantılı olarak yapılır; böylece LOD 0 bölmesindeki objelerin Scale In Lightmap değeri, LOD 1 bölmesindekilere göre daha büyük olur. Scale In Lightmap değişkeni, sadece obje static ise ve sahnede lightmap açık ise (Baked Global Illumination veya Real Global Illumination) gözükür. Bu değer ne kadar yüksek olursa, o Renderer lightmap Texture’unda o kadar çok yer kaplar. LOD 1 ve LOD 2 bölmeleri aktifken kamera objeden hatırı sayılır derecede uzak olacağı için, bu bölmelerdeki objelerin boş yere Texture’da büyük yer işgal etmesine gerek yoktur

B) LOD Modellerini Oluşturmak

LOD sisteminin kullanacağı yüksek poligonlu ve düşük poligonlu modelleri Unity’e sizin vermeniz lazım; maalesef Unity yüksek poligonlu modelin düşük poligonlu varyasyonunu otomatik olarak oluşturmuyor. Bu iş için 2 alternatifiniz var: LOD modellerini bir 3D modelleme programında elle oluşturmak (Blender, 3DS Max, Maya vb.) veya hazır bir plugin kullanmak.

B.1) 3D Modelleme Programı Kullanmak

Bu yöntemde, yüksek poligonlu modeli kendi elinizle düşük poligonlu varyasyonlara çevireceğiniz için, en kaliteli sonucu bu yöntem ile elde edersiniz. Ancak en azından bir 3D modelleme programının nasıl kullanıldığını bilmeniz lazım.

Yapmanız gereken tek şey, oluşturduğunuz LOD modellerin isimlerini _LOD0, _LOD1, _LOD2 vs. takılarla bitirmek. En yüksek poligonlu modelinizin ismi _LOD0 ile biterken, biraz daha az poligonlu modelinizin ismi _LOD1 ile, ondan daha az poligonlu modelinizin ismi de _LOD2 ile bitmeli. Örneğin ben Blender’da maymun modelinin farklı poligonlara sahip 3 varyasyonunu oluşturdum:

Benim yaptığım LOD0 1000 poligon, LOD1 500 poligon ve LOD2 de 250 poligondan oluşuyor. İstersem LOD3 ve LOD4 diye daha az poligonlu varyasyonlarını da oluşturabilirdim ama bu kadar poligon için o kadar çok LOD modeli gereksiz olur; zaten bir yerden sonra da LOD modellerin sayısı arttıkça, bu Unity’i daha çok yormaya başlar. O yüzden olabildiğince az LOD modeli kullanmaya çalışın.

Blender’dan modeli FBX olarak export alıp Unity’e attığımda, Unity bu model asset’ine otomatik olarak bir LOD Group component’i atıyor:

Kamera ikonunu sürükleyerek LOD Group’u test edecek olursam:

Yukarıdaki animasyonda, LOD modelleri arasındaki geçiş biraz fazla belli oluyor çünkü ben bu geçişi rahatça görebilmemiz için, kayıt alırken LOD 0 bölmesinin kapladığı alanı küçültüp LOD 2 bölmesinin kapladığı alanı artırdım. Normal şartlarda, model ekranda daha ufak bir yer kaplarken LOD modelleri arasında geçiş yapılacağı için, bu geçiş bu kadar bariz olmaz.

Dışarıdan import ettiğiniz 3D modellerin LOD Group component’inde Upload to Importer butonu yer alır. Eğer LOD Group component’inde bir değişiklik yaparsanız, bu butona tıklayarak yaptığınız değişiklikleri 3D model asset’inin kendisine uygulayabilirsiniz (bunu, yaptığınız bir değişikliği bir prefab’a uygulamak gibi düşünebilirsiniz). 3D model asset’ine uyguladığınız değişiklikler, o modelin kullanıldığı her yere otomatik olarak uygulanır.

B.2) Hazır Plugin Kullanmak

LOD modelleri oluşturmak için Asset Store’da azımsanmayacak sayıda ücretli asset var. Yalan olmasın bu konuda ücretsiz asset bulmak gerçekten zor. Benim gözüme şu plugin çarptı ve bu derste onu tanıtmak istedim: https://github.com/Whinarn/UnityMeshSimplifier

Plugin’i kurmak için, şu linkteki zip arşivini indirin. Ardından arşivin içindeki UnityMeshSimplifier-master klasörünü, Unity projenizin olduğu konumdaki Assets klasörünün içine çıkarın. Eski bir Unity sürümü kullanıyorsanız, kodun bir kısmı hata verecek. Bu hataları adım adım çözecek olursak:

  • tüm script’lerdeki [MethodImpl(MethodImplOptions.AggressiveInlining)] satırlarını //[MethodImpl(MethodImplOptions.AggressiveInlining)] ile değiştirin. Bunun için, Visual Studio’da CTRL+Shift+F kombinasyonunu kullanıp gelen pencereyi şu şekilde doldurabilir ve sağ alttaki Replace All butonuna tıklayabilirsiniz:
  • tüm nameof(blabla)‘ları "blabla" olarak değiştirin. Bunun için, Visual Studio’da CTRL+Shift+F penceresindeki “Use regular expressions” seçeneğini işaretleyip kutucukları şu şekilde doldurabilirsiniz (üstteki kutucukta nameof\(([a-zA-Z0-9]*)\), alttaki kutucukta ise "$1" yazmakta):
  • Type 'System.Reflection.PropertyInfo' does not contain a definition for 'GetMethod' hatası alıyorsanız, o satırı meshCanAccessMethodInfo = canAccessProperty.GetMethod;‘dan meshCanAccessMethodInfo = canAccessProperty.GetGetMethod(true);‘ya çevirin
  • 'UnityEditor.SerializedObject': type used in a using statement must be implicitly convertible to 'System.IDisposable' hatası aldığınız satırları using (var serializedObject = new SerializedObject(lodGeneratorHelper)) formatından var serializedObject = new SerializedObject(lodGeneratorHelper); formatına çevirin
  • The best overloaded method match for 'UnityEngine.Mesh.SetUVs' has some invalid arguments ve 'UnityEngine.Matrix4x4' does not contain a definition for 'Rotate' hatalarını çözmek için, plugin’in klasöründen Tests alt klasörünü silmeniz yeterli

Böylece koddaki tüm hatalar gitmiş olmalı. Son olarak, kodda ufak bir iyileştirme yapalım. Bu iyileştirme, plugin’in oluşturacağı model asset’lerinin vertex (köşe) ve triangle (üçgen) sayılarını Inspector’da görebilmemize yarayacak. Yapmanız gereken tek şey, LODGenerator script’indeki .mesh" (tırnak işareti ile birlikte) gördüğünüz yerleri, .asset" olarak değiştirmek. Bu dersi yazdığım esnada, kodun sadece 704. ve 708. satırlarında bu değişiklik gerekiyordu.

Bu plugin’in kullanımı oldukça basit. Sahnedeki hangi obje için LOD modelleri oluşturmak istiyorsanız, o objeye bir LOD Generator Helper component’i ekliyorsunuz:

Buradaki ayarlardan bahsedecek olursam:

  • Fade Mode: dersin ilerleyen kısmında göreceğiz
  • Auto Collect Renderers: seçili olursa, mevcut objedeki ve onun child’larındaki tüm Renderer’lar için LOD modelleri oluşturulur. Tüm child Renderer’lar için LOD oluşturmak istemiyorsanız, bu seçeneği kapatın. Bu durumda, alttaki Level 1, Level 2 ve Level 3 kısımlarına Renderers listeleri eklenecek. Sadece A objesi için LOD modelleri oluşturulmasını istiyorsanız, o objeyi her üç Level’in de Renderers listesine ekleyebilirsiniz
  • Simplification Options: buradaki ayarların çoğunu bilmiyorum, dokümantasyonda bunların çoğundan bahsetmiyor. Ben açıkçası bunları olduğu gibi bırakıyorum. Dokümantasyonda yazdığına göre, Max Iteration Count ve Agressiveness arttıkça, oluşan LOD modellerin kalitesi artıyormuş ama ben bir keresinde Agressiveness’ı 7’den 10’a çektiğimde, LOD modelin kalitesi artacağı yerde azaldı; o yüzden burada deneme-yanılma yapmak isteyebilirsiniz (Generate LODs). Eğer LOD modellerin anatomisi orijinal modele göre çok bozuluyorsa, Preserve Surface Curvature seçeneğini açmayı deneyebilirsiniz
  • Override Save Assets Path: oluşturulan LOD modellerin nereye kaydedileceğini belirler. Bu seçenek kapalı olursa, modeller UMS_LODs diye bir klasörün içine kaydedilir
  • Level 1‘in karşılığı LOD 0, Level 2‘nin LOD 1 ve Level 3‘ün de LOD 2‘dir. İsterseniz Create Level ile yeni bir LOD bölmesi oluşturabilirsiniz. Screen Relative Transition Height, o bölmenin % kaçtan sonra aktif olacağını belirler. Quality ise, o bölme için oluşturulacak LOD modelinin, orijinal modele kıyasla ne kadar kaliteli olacağını belirler. Bu değeri ne kadar azaltırsanız, oluşan modelin poligon sayısı da o kadar az olur. Bu component’teki en önemli değişken belki de Quality’dir. Level 2 ve Level 3’ün Quality değerlerini, oluşan LOD modellerinin poligon sayıları ve kaliteleri arasında tatlı bir denge bulana kadar deneme-yanılma yoluyla değiştirmelisiniz (Generate LODs)
  • Settings: o LOD bölmesi için kullanılacak Renderer component’inin Inspector’daki ayarlarını önceden belirlemeye yarar (gölge düşürüp düşürmeyeceği gibi). Combine Meshes hariç buradaki tüm ayarları sonradan da Renderer component’i üzerinden değiştirebilirsiniz. Eğer Combine Meshes’ı işaretlerseniz ve bu LOD bölmesine birden çok Renderer ekli ise, plugin o objelerin mesh’lerini birleştirerek tek mesh yapmaya çalışır (sadece bu bölme için)

Ayarlarla işiniz bitince, Generate LODs butonuna tıklayarak LOD modellerini oluşturabilirsiniz. Orijinal model(ler)in karmaşıklığına bağlı olarak, bu süreç biraz vakit alabilir. İşlem tamamlandıktan sonra, LOD Generator Helper component’i şu şekle bürünecek:

Destroy LODs butonuna tıklayarak, oluşturulan LOD modellerini silip tüm değişiklikleri otomatik olarak geri alabilirsiniz. İşte deneme yanılma böyle oluyor: ayarları değiştirip Generate LODs butonuna tıklıyorsunuz, oluşan LOD modellerini beğenmezseniz Destroy LODs diyerek ayarlar menüsüne geri dönüyorsunuz ve süreci tekrarlıyorsunuz.

LOD Generator Helper, otomatik olarak LOD Group component’i oluşturur. Eğer oluşan LOD Group component’inin LOD bölmelerinde bir değişiklik yaparsanız, Copy Visibility Changes butonuna tıklayarak bu değişiklikleri LOD Generator Helper component’ine de uygulayabilirsiniz. Böylece Destroy LODs yapıp tekrar Generate LODs yapsanız bile, bölmelerde yaptığınız değişiklikler kaybolmaz.

Bu plugin her zaman güzel sonuç vermeyebiliyor; sonuçta ücretsiz bir plugin ve hiç insan eli değmeden modelin poligon sayısını düşürmeye çalışıyor. Ama ben genelde kabul edilebilir sonuçlar alıyorum. Örneğin:



C) Quality Settings Ayarları

LOD Group component’i, Edit-Project Settings-Quality‘deki şu 2 ayardan etkilenir:

  • Lod Bias: LOD Group’ta kameranın bulunduğu % kısım, bu değer ile çarpılır. Bu yüzden bu değer ne kadar büyük olursa, yüksek LOD bölmeleri o kadar çok ekranda kalır. Örneğin kamerayı hiç hareket ettirmeden Lod Bias’ı 1’den 2’ye çektiğimde, LOD Group şu şekilde güncellenmekte:
  • Maximum LOD Level: LOD Group’taki en yüksek LOD bölmesinin hangisi olacağını belirler. Örneğin değerini 1 yaparsanız, kamera LOD 0 bölmesinin üzerinde olsa bile, LOD 1 bölmesi aktif olur

D) LOD Modelleri Arası Geçişi Yumuşatmak

LOD geçişinin yumuşak olması için, LOD modellerinin kullandığı shader’ın bunu desteklemesi gerekiyor. Maalesef ki Unity’nin hiçbir shader’ı varsayılan olarak yumuşak LOD geçişini desteklemiyor. Peşinen söyleyeyim; LOD modelleri arası yumuşak geçiş, mümkünse kullanmamanız gereken bir özellik (özellikle mobilde). Bunun başlıca 3 sebebi var:

  1. diyelim LOD 0 bölmesinden LOD 1 bölmesine geçiş yapılıyorsa, geçiş esnasında her iki LOD bölmesindeki modeller de aynı anda ekrana çizilmek zorundadır, bu da geçiş esnasında ekrandaki poligon sayısını artırır
  2. üstte de dediğim gibi, Unity’nin varsayılan shader’larının hiçbiri yumuşak geçişi desteklemiyor. İstediğiniz özelliklere sahip ve yumuşak geçiş destekleyen bir shader bulmakta zorlanabilir ve shader’ı kendiniz yazmak zorunda kalabilirsiniz
  3. geçişin yumuşak olabilmesi için, mantıken LOD modellerinin fade olması gerekiyor. Bu da, shader’ın transparent veya cutout olmak zorunda olması anlamına geliyor. Bu shader’lar, opak shader’lara göre GPU’yu daha çok zorlarlar

Artık neyle karşı karşıya olduğunuzu bildiğinize göre, yazıya devam edebiliriz. Yumuşak LOD geçişi için Google araması yaptığınızda, karşınıza gelen sonuçların çoğu sizi şu GitHub plugin’ine yönlendirecek: https://github.com/keijiro/CrossFadingLod. Bu shader’ları projenize eklemek için, şu linkteki zip arşivini indirin. Ardından arşivdeki CrossFadingLod-master\Assets klasöründe yer alan dosyaları, Unity projenizin olduğu konumdaki Assets klasöründe yer alan boş bir klasörün içine çıkarın.

Eğer Unity 2017 öncesi bir sürüm kullanıyorsanız, CrossFadingLodDither shader’ı undefined variable "UnityApplyDitherCrossFade" hatası verecek. Hatayı çözmek için, shader’daki şu 2 satırı bulun:

float2 vpos = IN.screenPos.xy / IN.screenPos.w * _ScreenParams.xy;
UnityApplyDitherCrossFade(vpos);

Ardından bu satırları silip yerine şu satırı eklemeniz yeterli: ApplyDitherCrossFade(ComputeDitherScreenPos(IN.screenPos));

Shader’daki hatayı çözdüğümüze göre, geriye yapmamız gereken 2 şey kaldı:

  1. LOD modellerinizin materyal(ler)inin shader’ını, Custom/CrossFadingLod (Dither) ile değiştirin
  2. LOD Group component’indeki Fade Mode değişkenini Cross Fade yapın. Karşınıza Animate Cross-fading kutucuğu çıkacak:
  • bu kutucuğu işaretlerseniz, diyelim LOD 0 bölmesinden LOD 1 bölmesine geçiş olduğu anda, yumuşak geçiş belirli bir süre içerisinde gerçekleşir. Bu sürenin kaç saniye olacağını, oyun esnasında LODGroup.crossFadeAnimationDuration değişkeni ile değiştirebilirsiniz:
  • Animate Cross-fading’i işaretlemezseniz, kamera LOD 0 bölmesinden LOD 1 bölmesine ne kadar yaklaşırsa, yumuşak geçiş o kadar ileriye sarılır. Geçiş esnasında kamera olduğu yerde sabit kalırsa, yumuşak geçiş de olduğu yerde duraklar. LOD bölmelerine tıklayıp Fade Transition Width değerini değiştirerek, yumuşak geçişin olacağı alanın, LOD bölmesinin genişliğinin % kaçına denk geleceğini ayarlayabilirsiniz (yumuşak geçiş için bu değer 0’dan büyük olmalı):

Dersin başındaki küre ve küp ile yaptığım örneğin Animate Cross-fading açıkkenki hali:

Lemur modelinin Animate Cross-fading açıkkenki hali:

Custom/CrossFadingLod (Dither) shader’ı, Standard shader’da olduğu gibi PBR ışıklandırma kullanır ve bu yüzden Standard shader kadar performansı etkiler. Keşke Mobile/Diffuse veya Legacy Shaders/Diffuse‘e LOD geçişi desteği verebilsek diyorsanız, CrossFadingLodDither shader’ını klonlayıp içeriğini şöyle değiştirebilirsiniz:

Shader "Custom/CrossFadingLod Diffuse (Dither)"
{
	Properties
	{
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="TransparentCutout" "Queue"="AlphaTest" }
		LOD 200
		
		CGPROGRAM

		#pragma multi_compile _ LOD_FADE_CROSSFADE
		#pragma surface surf Lambert
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input
		{
			float4 screenPos;
			float2 uv_MainTex;
		};

		fixed4 _Color;

		void surf(Input IN, inout SurfaceOutput o)
		{
			#ifdef LOD_FADE_CROSSFADE
			
			// Unity 2017 ve sonrası için
			float2 vpos = IN.screenPos.xy / IN.screenPos.w * _ScreenParams.xy;
			UnityApplyDitherCrossFade(vpos);
			
			// Unity 5 ve öncesi için
			//ApplyDitherCrossFade(ComputeDitherScreenPos(IN.screenPos));
			
			#endif
			// Albedo comes from a texture tinted by color
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

Ardından materyalinize Custom/CrossFadingLod Diffuse (Dither) shader’ını verebilirsiniz.

Eğer PBR ışıklandırma sizin için bir performans sıkıntısı değilse ama Custom/CrossFadingLod (Dither) shader’ının normal map, specular vs. gibi tüm Standard shader özelliklerini desteklemesini istiyorsanız, ufak bir ayar ile bunu yapmak mümkün. Bu ayar, sadece Unity 2017.1 ve sonrasında çalışmakta:

  • şu linkten, Unity sürümünüz için olan “Built in shaders“ı indirin (“Downloads (Win)” butonuna tıklayınca gelir): https://unity3d.com/get-unity/download/archive
  • indirdiğiniz zip’in içindeki DefaultResourcesExtra/Standard.shader‘ı (veya Specular istiyorsanız DefaultResourcesExtra/StandardSpecular.shader‘ı), Unity projenizin olduğu konumdaki Assets klasörüne çıkarın
  • Shader’ı açın ve Shader "Standard" satırını değiştirerek, shader’ın ismini değiştirin (örneğin Shader "Standard (LOD CrossFade)" yapın)
  • Shader’daki tüm //#pragma multi_compile _ LOD_FADE_CROSSFADE satırlarının comment’ini kaldırın
  • artık materyalinize Standard (LOD CrossFade) shader’ını verebilirsiniz

E) En Son LOD Seviyesinde Impostor Model Kullanmak

Impostor modeli, orijinal modelin sadece 2 üçgen kullanarak ekrana çizilmesi gibi düşünebilirsiniz. Bunun için, model pek çok farklı açıdan render alınır ve bu render’ların hepsi bir Texture sheet’te depolanır. Oyun esnasında, bu Texture sheet’teki render’lardan kameranın bakış açısına en yakın olanlar seçilir ve onlar ekrana çizilir.

Impostor model oluşturmak için Asset Store’da bir takım ücretli asset bulabilirsiniz (örnek). Biz bu derste, GitHub’daki ücretsiz bir plugin’i kullanacağız: https://github.com/xraxra/IMP. Başlamadan önce, plugin’in kısıtlamalarını bilmelisiniz:

  • minimum Unity 2017.3 gerekmekte
  • plugin ile gelen shader sadece Standard PBR ışıklandırma desteklediği için, mobil cihazlarda performansı çok iyi olmayabilir
  • shader’ın yaptığı hesaplamaların kompleksliğinden dolayı, OpenGL 3.0 desteklemeyen çoğu eski Android cihazda shader çalışmaz. O yüzden bu plugin’i mobil cihazlarda kullanmayı düşünüyorsanız, mutlaka hedeflediğiniz en eski cihazda plugin’i test edin
  • animasyonlu modellerin impostor modelini oluşturamazsınız
  • impostor model gölge düşürmez. Ancak zaten impostor modeller genel olarak objeye uzaktan bakarken kullanıldığı için, bu çok sıkıntı olmamalı

Plugin’i kurmak için, şu linkteki zip arşivini indirin. Ardından zip’in içindeki IMP-master\Assets\Plugins\IMP klasörünü, Unity projenizin olduğu konumdaki Assets klasörüne çıkarın. İsterseniz Example alt klasörünü silebilirsiniz çünkü bu klasör 80 MB olduğu için, projeye import edilmesi biraz vakit alıyor.

Plugin’in kullanımına gelecek olursak:

  • Impostor modelini oluşturmak istediğiniz orijinal modeli sahnenizde seçin. Eğer model Skinned Mesh Renderer kullanıyorsa, onu normal Mesh Renderer‘a çevirmelisiniz. Bunun için 3D model asset’inin bir klonunu oluşturup Inspector’da Rig sekmesinden Animation Type‘ı None yapabilirsiniz (daha sonra bu modeli sahnenize ekleyip seçin). Impostor modeli oluşturduktan sonra bu klon 3D model asset’ini silebilirsiniz
  • Edit-Graphics Emulation‘ın değerini No Emulation yapın
  • eğer modeliniz Custom/CrossFadingLod (Dither) shader’ını kullanıyorsa, onu geçici olarak Standard shader yapın. Aksi taktirde, modelin ışıklandırması doğru olmuyor. Impostor modeli oluşturduktan sonra bu değişikliği geri alabilirsiniz
  • Window-IMP penceresini açın:
  • buradaki ayarlardan bahsedecek olursam:
    • Resolution: modelin render’larından oluşturulacak olan Texture‘ların, kaç piksel genişliğinde olacaklarını belirler (plugin toplam 2 Texture oluşturmakta). Bu değer arttıkça, render’ın kalitesi artar ama aynı zamanda Texture’un kapladığı alan da artar. Burada minimum çözünürlük olarak 1024 piksel verebilirsiniz ama impostor modeli oluşturduktan sonra, oluşan Texture’ları elle seçip oradan çözünürlüğü daha da düşürebilirsiniz
    • Frames: modelin kaç farklı açıdan render alınacağını belirler. Bu değer ne kadar yüksek olursa, kamera modelin etrafında gezerken model o kadar düzgün durur. Ancak alınan render’ların kalitesi düşer çünkü aynı Texture çözünürlüğüne daha çok render’ı sıkıştırmaya çalışmış oluyoruz
    • Hemisphere: eğer impostor modele hiç aşağıdan bakmayacaksak, bu seçeneği işaretleyin. Böylece modelin render’ları aşağı yönden alınmaz ve haliyle, diğer yönlerden alınan render’lar için ayrılan yer daha çok olur ve render kalitesi artar
    • Custom Lighting Root: anladığım kadarıyla, alınan render’larda sadece belli bir ışığın modele vurmasını istiyorsanız, o ışığı buraya değer olarak verin. Impostor model oluşturulduktan sonra nedense ışık kendiliğinden kapanacak; bu yüzden render’dan sonra ışığı elle tekrar açmalısınız
    • Prefab Suffix: Impostor modelin ismi, bu takıyla biter
    • Create Unity Billboard: Unity’nin Billboard Renderer component’inde kullanılmaya hazır bir billboard asset de oluşturmaya yarar. Ancak ben test ederken, Billboard Renderer’ın konumunu değiştirsem bile, impostor model hep aynı yerde ekrana çiziliyordu. O yüzden ben bu seçeneği açmıyorum
  • ayarları istediğiniz gibi doldurduktan sonra, Preview Snapshot Locations butonuna tıklayarak, objenin hangi açılardan render alınacağını Scene panelinde görebilirsiniz:
  • Capture butonuna tıklayarak, impostor modeli oluşturabilirsiniz. Ayarlardaki parametrelerin değerlerine göre, bu işlem kısa da sürebilir uzun da sürebilir. Eğer orijinal obje bir 3D model asset’iyle veya bir prefab ile bağlantılıysa, impostor model o 3D model asset’inin olduğu klasörde oluşturulur. Aksi taktirde, plugin size impostor modeli hangi klasörde oluşturmak istediğinizi soracaktır
  • işlem bitince, impostor modelin asset’lerini ilgili klasörde bulacaksınız:
  • eğer buradaki ImposterBase ve ImposterPack Texture’ları sizde simsiyah ise, işlem düzgün olmamış demektir. Impostor modeli boş bir sahnede oluşturmayı ve/veya Custom Lighting Root‘a değer vermeyi deneyebilirsiniz. Hiçbir şey işe yaramazsa, şuradaki sorunu yaşıyorsunuz demektir ve bu sorunun çözümünü maalesef ben de bilmiyorum
  • plugin otomatik olarak Monkey_IMP prefab’ının bir klonunu sahnenize koyacaktır. Bu objenin Transform değerleri, orijinal objeninkiyle aynı olmayabilir. Onları elle aynı değere ayarlayabilirsiniz. Eğer Monkey_IMP objesine odaklandığınızda hiçbir şey göremiyorsanız, objenin materyalindeki Radius değerini artırmayı deneyin
  • Impostor modelde ilk yapmanızı önerdiğim değişiklik, materyalindeki Alpha Cutoff değerini ayarlamak olacak. Impostor modeli orijinal model ile aynı yere koyun ve iki modelin kenarları aşağı yukarı aynı hizaya gelene kadar, Alpha Cutoff değerini artırın

Maymun modeli için oluşturduğum impostor model:

Aynı sahnenin wireframe açık hali:

Gördüğünüz gibi, soldaki impostor model basit bir Quad ile ekrana çiziliyor (ben yukarıda 2 üçgen demiştim ama burada 4 üçgen kullanılıyor, yapacak bir şey yok ¯\_(ツ)_/¯ ).

Impostor modeli LOD sistemine dahil etmek istiyorsanız:

  • impostor modeli, orijinal modelin child objesi yapın
  • impostor modeli, orijinal model ile aynı pozisyona gelecek şekilde konumlandırın
  • LOD Group component’inin en son LOD düzeyinin Renderers listesine, impostor modeli ekleyin (eğer bu LOD düzeyinde başka bir model varsa, ya en sonda yeni bir LOD düzeyi oluşturun ya da eski LOD modelini Renderers listesinden çıkarıp GameObject’ini kapatın)

Maymun modelinin LOD 2’sini impostor model ile değiştirdikten sonra:


F) CullingGroup İle Koda LOD Uygulamak

CullingGroup sınıfı şöyle çalışıyor:

  • sisteme bir referans kamera (Main Camera diyelim) bir de referans pivot noktası (Pivot diyelim) veriyorsunuz
  • istediğiniz kadar miktarda hedef nokta belirliyorsunuz. Bu hedef noktaların 2 değişkeni bulunmakta: konum ve yarıçap. Yani bu hedef noktalar, 3D uzayda sizin belirlediğiniz noktalardaki, sizin belirlediğiniz yarıçapa sahip sanal küreleri temsil ediyor
  • son olarak da, sisteme LOD mesafeleri giriyorsunuz (10 metre ve 25 metre diyelim)

Bu ayarları yaptıktan sonra, hedef noktaların Main Camera ve Pivot’a olan ilişkileri şu şekilde değiştiğinde, sistem sizi otomatik olarak bilgilendiriyor:

  • hedef noktalardan bazıları Main Camera’nın görüş alanının dışına çıktığında veya görüş alanının içine girdiğinde
  • sahnede Occlusion Culling varsa, hedef noktalardan bazıları Occlusion Culling tarafından gizlendiğinde veya Occlusion Culling tarafından gizlenmesi sona erdiğinde
  • hedef noktaların Pivot’a olan uzaklıkları, girilen LOD mesafelerinin birinden öbürüne geçtiğinde (yani mesela bir hedef noktanın bir önceki frame’de Pivot’a uzaklığı 9 metre iken, bu frame’de pivot’a uzaklığı 11 metre olduğunda veya mesafe 25.1 metreden 24.8 metreye düştüğünde)

CullingGroup için örnek kullanım alanları sayacak olursak:

  • Kameranın görüş alanı dışındaki spawn noktalarını bularak düşmanları oralarda spawn etmek
  • Oyuncudan çok uzakta yer alan yapay zekaların, çok kompleks hesaplamalar yerine daha basit hesaplamalar yapmasını sağlamak
  • Sahnedeki binlerce waypoint arasından, oyuncunun 1m yarıçapında olanları bulmak

CullingGroup sistemini kodda şu şekilde ayarlıyoruz:

  • CullingGroup group = new CullingGroup(); şeklinde yeni bir CullingGroup tanımlayın
  • group.targetCamera ile, istediğiniz kamerayı sisteme referans kamera olarak tanımlayın
  • group.SetDistanceReferencePoint ile, istediğiniz Transform‘u veya Vector3 koordinatı sisteme Pivot nokta olarak tanımlayın (eğer sadece referans noktaların kameranın görüş alanında olup olmadığı ile ilgileniyorsanız, bu adıma gerek yok)
  • group.SetBoundingDistances ile, sisteme LOD mesafelerini tanımlayın (eğer sadece referans noktaların kameranın görüş alanında olup olmadığı ile ilgileniyorsanız, bu adıma gerek yok). Bu array’i float.PositiveInfinity (sonsuz sayı) ile bitirin. Örneğin 10m ve 25m için LOD mesafeleri tanımlayacaksanız şu array’i kullanın: new float[3] { 10f, 25f, float.PositiveInfinity };. Eğer array’i float.PositiveInfinity ile bitirmezseniz, en son LOD mesafesinden daha uzaktaki referans noktalar, sistem tarafından kameranın görüş alanı dışında (görünmez) varsayılır
  • referans noktaları tutan bir BoundingSphere[] array oluşturun (BoundingSphere’de position (konum) ve radius (yarıçap) değişkenleri bulunmaktadır). Eğer oyun esnasında yeni referans noktalar eklemeyi planlıyorsanız, array’in boyutunu maksimum hedeflediğiniz referans nokta boyutuna eşitleyin: CullingGroup sistemine array’deki ilk kaç elemanı referans nokta olarak hesaba katması gerektiğini söyleyebiliyorsunuz; boş yere her yeni referans nokta eklendiğinde array’i yeniden oluşturmayın
  • BoundingSphere[] array’ini group.SetBoundingSpheres ile sisteme tanıtın
  • array’deki ilk kaç elemanın referans nokta olarak kabul edilmesi gerektiğini, group.SetBoundingSphereCount ile sisteme söyleyin
  • group.onStateChanged event’ine kaydolun. Referans noktaların görünürlüğünde veya Pivot’a olan mesafesinde bir değişiklik olduğunda, değişen her referans nokta için bu fonksiyon ayrı ayrı çağrılır. Mesafe değişikliğinden kastım, referans noktanın bir LOD mesafesinden başka LOD mesafesine geçmesidir. Onun harici mesafe değişikliklerinde bu fonksiyon çağrılmaz
  • onStateChanged’e alternatif olarak:
    • group.IsVisible ile, belli bir index’teki referans noktanın şu anda görünür olup olmadığını bulabilirsiniz
    • group.GetDistance ile, belli bir index’teki referans noktanın hangi LOD mesafesi içerisinde olduğunu bulabilirsiniz
    • group.QueryIndices ile, hangi index’lerdeki referans noktaların görünür/görünmez olduğunu ve/veya belli bir LOD mesafesi içerisinde olduğunu topluca bulabilirsiniz. Bu fonksiyon bir int[] array parametre alır ve bulunan index’leri bu array’de depolar. Son olarak da bir int değer döndürür; bu da bulunan toplam index sayısını depolar (girdiğiniz int[] array’in ilk kaç elemanında sonuçların depolandığı). Dilerseniz, firstIndex parametresi ile ilk birkaç referans noktanın aramaya dahil olmamasını sağlayabilirsiniz. Eğer int[] array’inizin boyutu, referans noktaların boyutundan küçükse, sadece array’in boyutu kadar referans nokta aranır (yani [firstIndex, firstIndex+intArray.Length] aralığındaki referans noktalar aranır)
  • oyun esnasında referans noktaları değiştirseniz bile, BoundingSphere[] array’ini yeniden oluşturmadığınız sürece tekrar group.SetBoundingSpheres‘i çağırmanıza gerek yok
  • referans noktaların sayısı arttığında, bu değişikliği group.SetBoundingSphereCount ile tekrar sisteme söyleyin
  • bir referans noktayı silecekseniz, BoundingSphere[] array’ini ellemek ve group.SetBoundingSphereCount’ı tekrar çağırmak yerine, sadece group.EraseSwapBack fonksiyonunu çağırın
  • CullingGroup ile işiniz bittiğinde (örneğin sahne değiştiğinde), mutlaka group.Dispose fonksiyonunu çağırın. Bu fonksiyon, CullingGroup’un kullandığı RAM vb. sistem kaynaklarının tekrar kullanılabilir olmasını sağlar. Bu fonksiyonu çağırmayı unutursanız, oyununuz çökebilir(miş)

Birkaç örnek ile CullingGroup’u iş üstünde görelim:

  • Kameranın görüş alanı dışındaki spawn noktalarını bularak düşmanları oralarda spawn etmek
using UnityEngine;

public class CullingGroupTest : MonoBehaviour
{
	private CullingGroup group;
	private BoundingSphere[] referansNoktalar;

	public Transform[] spawnNoktalari;

	private int[] gorunmezReferansNoktalar;

	private void Start()
	{
		// Yeni bir CullingGroup tanımla
		group = new CullingGroup();

		// CullingGroup'a referans kamerayı Main Camera olarak tanımla
		group.targetCamera = Camera.main;

		// Referans noktaları oluştur (yarıçap olarak ben 0.5m veriyorum)
		// Eğer spawn noktalarının sayısı oyun esnasında artabilirse, array'in boyutu maksimum
		// spawn noktası sayısı kadar olmalı. Bu örnekte böyle bir durum söz konusu değil
		referansNoktalar = new BoundingSphere[spawnNoktalari.Length];
		for( int i = 0; i < spawnNoktalari.Length; i++ )
			referansNoktalar[i] = new BoundingSphere( spawnNoktalari[i].position, 0.5f );

		// CullingGroup'a referans noktaları tanımla
		group.SetBoundingSpheres( referansNoktalar );
		group.SetBoundingSphereCount( spawnNoktalari.Length );
	}

	// Obje yok olduğunda bu fonksiyon otomatik olarak çağrılır
	private void OnDestroy()
	{
		// CullingGroup ile işimiz bitince mutlaka Dispose fonksiyonunu çağırmalıyız
		if( group != null )
		{
			group.Dispose();
			group = null;
		}
	}

	// Eğer spawn noktaları hareketli ise (pek rastlanan bir durum değil), onların referans noktalarını güncelle
	//private void Update()
	//{
	//	for( int i = 0; i < spawnNoktalari.Length; i++ )
	//		referansNoktalar[i].position = spawnNoktalari[i].position;
	//}

	// Kameranın görüş alanında olmayan spawn noktalarından rastgele birisini döndürür
	public Transform SpawnNoktasiBul()
	{
		// Sonuçları bu array'de depolayacağız
		if( gorunmezReferansNoktalar == null )
			gorunmezReferansNoktalar = new int[referansNoktalar.Length];

		// bool visible = false: kameranın görüş alanı dışındaki referans noktaları bul
		// int[] result = gorunmezReferansNoktalar: sonuçları bu array'de depolayacağız
		// int firstIndex = 0: ilk referans noktadan itibaren aramaya başla
		int bulunanReferansNoktaSayisi = group.QueryIndices( false, gorunmezReferansNoktalar, 0 );

		// Hiçbir referans nokta sorgumuzu sağlamıyorsa (hepsi kameranın görüş alanı içerisindeyse)
		if( bulunanReferansNoktaSayisi <= 0 )
			return null;

		// Bulunan görünmez referans noktalar arasından rastgele bir tanesini seç
		int rastgeleReferansNoktaIndex = gorunmezReferansNoktalar[Random.Range( 0, bulunanReferansNoktaSayisi )];

		// O referans noktaya karşılık gelen spawn noktasını döndür
		return spawnNoktalari[rastgeleReferansNoktaIndex];
	}
}
  • Oyuncudan çok uzakta yer alan yapay zekaların, çok kompleks hesaplamalar yerine daha basit hesaplamalar yapmasını sağlamak
using System.Collections.Generic;
using UnityEngine;

public class CullingGroupTest : MonoBehaviour
{
	private CullingGroup group;
	private BoundingSphere[] referansNoktalar;

	public Transform player;
	public List<Dusman> dusmanlar;

	private void Start()
	{
		// Yeni bir CullingGroup tanımla
		group = new CullingGroup();

		// CullingGroup'a referans kamerayı Main Camera olarak tanımla
		group.targetCamera = Camera.main;

		// CullingGroup'a Pivot'u Player'ın Transform'u olarak tanımla
		group.SetDistanceReferencePoint( player );

		// CullingGroup'a LOD mesafelerini tanımla. Bu örnekte ben 10m, 25m ve
		// sonsuz olarak tanımladım. Yani elimizde 3 LOD bölmesi var:
		// 0) [0m-10m]
		// 1) (10m-25m]
		// 2) (25m-Sonsuz]
		group.SetBoundingDistances( new float[3] { 10f, 25f, float.PositiveInfinity } );

		// Referans noktaları oluştur. Ben maksimum düşman sayısını 1000 olarak varsayıyorum
		referansNoktalar = new BoundingSphere[Mathf.Max( dusmanlar.Count, 1000 )];
		for( int i = 0; i < dusmanlar.Count; i++ )
			referansNoktalar[i] = new BoundingSphere( dusmanlar[i].transform.position, dusmanlar[i].yaricap );

		// CullingGroup'a referans noktaları tanımla
		group.SetBoundingSpheres( referansNoktalar );
		group.SetBoundingSphereCount( dusmanlar.Count );

		// Referans noktaların görünürlüğünde veya LOD mesafesinde bir değişiklik olduğunda,
		// ReferansNoktaDegisti fonksiyonunu çağır
		group.onStateChanged += ReferansNoktaDegisti;
	}

	// Obje yok olduğunda bu fonksiyon otomatik olarak çağrılır
	private void OnDestroy()
	{
		// CullingGroup ile işimiz bitince mutlaka Dispose fonksiyonunu çağırmalıyız
		if( group != null )
		{
			group.onStateChanged -= ReferansNoktaDegisti;
			group.Dispose();
			group = null;
		}
	}

	// Düşmanların hareketli olduğunu varsayıyorum, bu durumda referans noktaları sürekli güncelle
	private void Update()
	{
		// Kodu her 10 frame'de bir çalıştır, sürekli tüm düşmanların position'ına erişmek
		// performansı etkileyebilir
		if( Time.frameCount % 10 == 0 )
		{
			// Eğer düşmanların çoğu hareketli değilse, bu kodun daha iyi bir versiyonu, sadece
			// hareket halindeki düşmanlar için referans noktaları güncellemektir
			for( int i = 0; i < dusmanlar.Count; i++ )
				referansNoktalar[i].position = dusmanlar[i].position;
		}
	}

	// Sahneye yeni bir düşman eklendi, bu düşman için referans nokta ekle
	public void YeniDusmanEkle( Dusman dusman )
	{
		dusmanlar.Add( dusman );

		if( dusmanlar.Count <= referansNoktalar.Length )
		{
			// referansNoktalar'da hâlâ boş yer var
			referansNoktalar[dusmanlar.Count - 1] = new BoundingSphere( dusman.transform.position, dusman.yaricap );

			// Referans nokta sayısı değişikliğini CullingGroup'a bildir
			group.SetBoundingSphereCount( dusmanlar.Count );
		}
		else
		{
			// referansNoktalar'ın kapasitesini o kadar yüksek vermemize rağmen maalesef kapasite doldu,
			// mecburen array'i yeniden oluşturmalıyız (bu örnekte array'in kapasitesini 2'ye katlıyorum)
			System.Array.Resize( ref referansNoktalar, referansNoktalar.Length * 2 );

			referansNoktalar[dusmanlar.Count - 1] = new BoundingSphere( dusman.transform.position, dusman.yaricap );

			// Referans nokta array'i değişikliğini CullingGroup'a bildir
			group.SetBoundingSpheres( referansNoktalar );

			// Referans nokta sayısı değişikliğini CullingGroup'a bildir
			group.SetBoundingSphereCount( dusmanlar.Count );
		}
	}

	// Bir düşman öldü, bu düşmanın referans noktasını sil
	public void DusmaniSil( Dusman dusman )
	{
		// Düşmanın index'ini bul
		int dusmanIndex = dusmanlar.IndexOf( dusman );
		if( dusmanIndex < 0 )
		{
			// Düşman zaten listeye hiç eklenmemiş, garip...
			return;
		}

		// CullingGroup'a, bu index'teki referans noktayı silmesini söyle
		group.EraseSwapBack( dusmanIndex );

		// List'in en sonundaki elemanı, dusmanIndex'teki elemanın üzerine yaz
		// ve sonra en sondaki elemanı sil. Bunu yapmamızın 2 sebebi var:
		// 1: dusmanlar.RemoveAt( dusmanIndex );'a göre bu daha performanslı
		// 2: group.EraseSwapBack( dusmanIndex ); de arkaplanda bunu yapıyor,
		//    referans noktalar ile dusmanlar'ın senkronize olmasını sağlamalıyız
		dusmanlar[dusmanIndex] = dusmanlar[dusmanlar.Count - 1];
		dusmanlar.RemoveAt( dusmanlar.Count - 1 );
	}

	// Bir referans noktanın görünürlüğü veya LOD mesafesi değişti
	private void ReferansNoktaDegisti( CullingGroupEvent referansNokta )
	{
		//if( referansNokta.hasBecomeInvisible )
		//{ 
		//	// Referans nokta kameranın görüş alanı dışına çıkınca yapılacaklar
		//}

		//if( referansNokta.hasBecomeVisible )
		//{ 
		//	// Referans nokta kameranın görüş alanı içine girince yapılacaklar
		//}

		int lodMesafesi = referansNokta.currentDistance;
		int oncekiLodMesafesi = referansNokta.previousDistance;
		if( lodMesafesi != oncekiLodMesafesi )
		{
			// Referans noktanın LOD mesafesi değişince yapılacaklar
			Dusman dusman = dusmanlar[referansNokta.index];

			if( lodMesafesi == 0 ) // [0m-10m] aralığına girdi
				dusman.KarmasikYapayZekayaGec();
			else if( lodMesafesi == 1 ) // (10m-25m] aralığına girdi
				dusman.OrtaDuzeyYapayZekayaGec();
			else // (25m-Sonsuz] aralığına girdi
				dusman.BasitYapayZekayaGec();
		}
	}
}

Yukarıdaki örnek kodlarda kullanmadığım group.QueryIndices fonksiyonlarından bahsedecek olursam:

  • QueryIndices( int distanceIndex, int[] result, int firstIndex ): belli bir LOD mesafesi içerisindeki referans noktaları bulur (yapay zeka örneğinde, [0m-10m] için distanceIndex 0, (10m-25m] için 1, (25m-Sonsuz) için de 2 olmalı)
  • QueryIndices( bool visible, int distanceIndex, int[] result, int firstIndex ): görünür olan/olmayan (visible) ve belli bir LOD mesafesi içerisindeki referans noktaları bulur (bulunan noktalar, hem visible hem de distanceIndex koşullarını aynı anda sağlar)

Böylece bu dersi de noktaladık. Sonraki derslerde görüşmek üzere!

yorum
  1. Ivan dedi ki:

    Thank you! Very good article!!!

  2. Igrahiga dedi ki:

    Merhaba Yasir! Android SDK nedir acaba bende oyle bir hata cikiyor

  3. efsane dedi ki:

    hocam oyunu apk olarak build ediyorum telefona yüklüyorum oyunu açıyorum oyunu başlat butona basınca donuyor ve uygulama durdu hatası veriyor nedeni ne olabilir

Cevap Yazın

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.