6. createScene Fonksiyonunun İçine Tanımlanacak Diğer Fonksiyonlar
Şimdi iç içe iki tane for döngüsü yazacağız. Bu for döngüleri x ve y değişkenlerinin değerlerine göre çalışacak. Ve döngü boyunca terrainTanimlama fonksiyonu çağırılacak. Dikkat etmemiz gereken nokta şudur: bu x ve y değerleri koordinat eksenlerini belirtmiyor. terrainGrubu nesnemizin üye fonksiyonu olan setFilenameConvention fonksiyonunun ilk parametresine string türünde, Ders3Terrain değerini girmiştik. İşleyiş şöyle olacak, Ders3Terrain yazacak, alt tire “_” koyacak, for düngülerindeki x ve y değerlerinin durumunu kullanarak en sona bir sayı üretecek. Yani bu döngüdeki x ve y değerlerinin birinci görevi dosya ismi üretmektir. Örneğin: Ders3Terrain_00000000.dat şeklinde bir dosya oluşturacak. Bu dosyanın “00000000” bölümünü for döngüsündeki x ve y değerleriyle üretecek. Bu projede tek bir terrain’e sahip olduğumuz için bu döngü bir kere çalışacak. Yani terrainTanimlama fonksiyonumuzun değerleri x=0 ve y=0 şeklinde olacak. Yani, az sonra belirteceğimiz kod satırlarına alternatif olarak, hepsini silip yerine terrainTanimlama(0, 0) yazarsanız aynı işlemi yapmış olacağınızdan dolayı hiçbir hata almazsınız. Eğer mantıksal olarak for döngüsü ile terrainTanimlama fonksiyonu arasındaki ilişkiyi kuramıyorsanız, aklınıza nasıl oluyor da döngü bir kere çalıştırılıyor diye bir soru gelirse fazla düşünmeyin. Sonraki dökümanlarda terrain sistemine daha kapsamlı olarak değineceğiz. Şimdi createScene fonksiyonuna terrainTanimlama foksiyonunu for döngüsü içinde ekleyelim:
// terrainTanimlama fonksiyonunu for döngüsü içinde çalıştırıyoruz:
for (long x = 0; x <= 0; ++x)
for (long y = 0; y <= 0; ++y)
terrainTanimlama(x, y);
Sonraki adımsa ise tanımladığımız terrainleri aynı anda (eş zamanlı) olarak yükleyeceğiz. Biz şuan tek bir terrain tanımlamamıza rağmen, sanki birden çok terrain tanımlamış gibi davranacağız. Terrain/Terrainler yükleme işlemini terrainGrubu nesnesinin loadAllTerrains üye fonksiyonu yapar. Bu fonksiyonu kurarak, uygulamayı başlattığımızda sahneye her şeyi eşzamanlı olarak yükleyecek. Şimdi, createScene fonksiyonumuza şu satırları ekleyelim:
// Tanımladığımız terrain/terrainleri eş zamanlı olarak yüklüyoruz:
terrainGrubu->loadAllTerrains(true);
Sonraki adımsa ise tanımladığımız terrainleri aynı anda (eş zamanlı) olarak yükleyeceğiz. Biz şuan tek bir terrain tanımlamamıza rağmen, sanki birden çok terrain tanımlamış gibi davranacağız. Terrain/Terrainler yükleme işlemini terrainGrubu nesnesinin loadAllTerrains üye fonksiyonu yapar. Bu fonksiyonu kurarak, uygulamayı başlattığımızda sahneye her şeyi eşzamanlı olarak yükleyecek. Şimdi, createScene fonksiyonumuza şu satırları ekleyelim:
// Tanımladığımız terrain/terrainleri eş zamanlı olarak yüklüyoruz:
terrainGrubu->loadAllTerrains(true);
Ders3.h dosyasında boolean türünde terrainEklendi isimli bir değişken oluşturmuştuk. İlerleyen satırlarda bulunan kodlarda, terrainEklendi değişkenimizi true olarak belirleyeceğiz. Bu belirleme, belleğimize terrain modeli yüklendikten sonra gerçekleşecek. Yani terrain modeli belleğe yüklendiği zaman terrainEklendi değişkeni true olacak. Şimdi terrainEklendi değişkeninin durumuna göre terrainimizin BlendMap’lerini hesaplayacağız. İlk olarak terrainListeleyici adında bir iterator nesnesi oluşturuyoruz. Bu iterator nesnesinin, terrainGrubu’nun iterator’unun yerine geçmesini sağlıyoruz. hasMoreElements fonksiyonuyla terrainListeleyici’nin bir veya birden fazla terrain listeleyip listelemediğini soruyoruz. Eğer bir veya birden fazla terrain, terrainGrubu nesnesinde tanımlanmışsa hasMoreElements fonksiyonu, true değerini döndürecek. True değerinin dönmesiyle while döngüsü başlatılmış olacak. Ardından ornekTerrain isimli bir terrain nesnesi oluşturacağız. getNext fonksiyonunu kullanarak ornekTerrain nesnemizi, terrainListeleyici’deki terrainlere sırayla eşitleyeceğiz. Ama terrainListeleyici bir iterator olduğu için, terrainlerle ilgili, instance tanımıyla terrain nesnesine dönüştüreceğiz. Bu sayede terrainListeleyici’den elde ettiğimiz bu bilgiler, ornekTerrain ismindeki terrain nesnemize eşitlenebilecek. Son olarak da ornekTerrain, initBlendMaps fonksiyonunun parametresine yazarak, terrainListeleyici’nin bulabildiği tüm terrain’ler için BlendMap’leri hesaplayacağız. Şimdi bahsettiğimiz tüm bu işlemleri yapmak için createScene fonksiyonumuza şu satırları ekleyelim:
// initBlendMaps fonksiyonunu çağırmak için bir if koşulu oluşturuyoruz:
if (terrainEklendi) {
Ogre::TerrainGroup::TerrainIterator terrainListeleyici = terrainGrubu->getTerrainIterator();
while(terrainListeleyici.hasMoreElements()) {
Ogre::Terrain* ornekTerrain = terrainListeleyici.getNext()->instance; initBlendMaps(ornekTerrain);
}}
Tüm bu işlemlerin sonunda terrain tanımlamak için oluşturduğumuz geçici kaynakları sileceğiz. Peki, nedir bu geçici kaynaklar? İlerleyen satırlarda şuan eksik olan fonksiyonlarımızı da tanımlayıp projemizi derlediğimiz zaman, terrainimizin Der3Terrain_00000000.dat dosyası şeklinde kaydedildiğini göreceğiz. Bu kayıt işleminden sonra, bu dosyayı oluşturmak için kullandığımız terrain resmi, kaplamalar vb. şeylere ihtiyacımız kalmayacak. Bunlardan silmemizin mümkün olduğu, yani artık kullanılmayan, tüm kaynakları bellekten sileceğiz. Bu sayede projemiz daha az bellek kullandığı için daha hızlı çalışacak. Geçici olarak oluşturulan kaynakları silerek ulaşabildiğimiz en uygun bellek kullanım miktarıyla programımızı çalıştırmış olacağız. Şimdi bahsettiğimiz bu işlemi yapmak için createScene fonksiyonumuza şu satırları ekleyelim:
// Geçici olarak oluşturduğumuz kaynakları belleğimizden siliyoruz:
terrainGrubu->freeTemporaryResources();
Şuan createScene fonksiyonumuzla işimiz bitti. createScene fonksiyonumuzun son hali şu şekilde olmalı:
void Ders3::createScene(void) {
// Kameramızın konumunu, bakacağı yönü, yakın ve uzak kırpma mesafelerini ayarlıyoruz:
mCamera->setPosition(Ogre::Vector3(1683, 50, 2116));
mCamera->lookAt(Ogre::Vector3(1963, 50, 1660));
mCamera->setNearClipDistance(0.1);
mCamera->setFarClipDistance(50000);
// Eğer RenderSystem'imiz destekliyorsa uzak kırpma mesafesini sonsuz yapıyoruz:
if (mRoot->getRenderSystem()->getCapabilities()->hasCapability(Ogre::RSC_INFINITE_FAR_PLANE)) {
// 0, sıfır değerini girerek sonsuz olduğunu belirtiyoruz:
mCamera->setFarClipDistance(0); }
// Kaplama Filtremizi, Anisotropic Filtresi olarak ve seviyesini de 7 olarak ayarlıyoruz:
Ogre::MaterialManager::getSingleton().setDefaultTextureFiltering(Ogre::TFO_ANISOTROPIC);
Ogre::MaterialManager::getSingleton().setDefaultAnisotropy(7);
// DirectionalLight'ımızın yönünü belirlemek için kullanacağımız vektörümüzü ayarlayıp, normalize ediyoruz:
Ogre::Vector3 gunesYonu(0.55, -0.3, 0.75);
gunesYonu.normalise();
// gunes isimli ışık nesnemizi DirectionalLight olarak belirliyoruz ve yönünü, Diffuse ve Specular renklerini kuruyoruz:
Ogre::Light* gunes = mSceneMgr->createLight("gunesIsigi");
gunes->setType(Ogre::Light::LT_DIRECTIONAL);
gunes->setDirection(gunesYonu);
gunes->setDiffuseColour(Ogre::ColourValue::White);
gunes->setSpecularColour(Ogre::ColourValue(0.4, 0.4, 0.4));
// Sahnemizin AmbientLight'ını kuruyoruz:
mSceneMgr->setAmbientLight(Ogre::ColourValue(0.2, 0.2, 0.2));
// terrainAyarlari nesnemizi, dinamik bir nesne olarak belirliyoruz:
terrainAyarlari = OGRE_NEW Ogre::TerrainGlobalOptions();
// terrainGubu nesnemizi, dinamik bir nesne olarak; kaydedilirken aldığı dosya adını ve uzantısını;
// terrainimizin orjinini (burada terrain modelinin merkez noktası kastediliyor) belirliyoruz.
terrainGrubu = OGRE_NEW Ogre::TerrainGroup(mSceneMgr, Ogre::Terrain::ALIGN_X_Z, 513, 12000.0f);
terrainGrubu->setFilenameConvention(Ogre::String("Ders3Terrain"), Ogre::String("dat"));
terrainGrubu->setOrigin(Ogre::Vector3::ZERO);
// terrainYapilandirma fonksiyonunu, parametresini gunes olarak belirleyip çağırıyoruz.
terrainYapilandirma(gunes);
// terrainTanimlama fonksiyonunu for döngüsü içinde çalıştırıyoruz:
for (long x = 0; x <= 0; ++x)
for (long y = 0; y <= 0; ++y)
terrainTanimlama(0, 0);
// Tanımladığımız terrain/terrainleri eş zamanlı olarak yüklüyoruz:
terrainGrubu->loadAllTerrains(true);
// initBlendMaps fonksiyonunu çağırmak için bir if koşulu oluşturuyoruz:
if (terrainEklendi)
{ Ogre::TerrainGroup::TerrainIterator terrainListeleyici = terrainGrubu->getTerrainIterator();
while(terrainListeleyici.hasMoreElements()) {
Ogre::Terrain* ornekTerrain = terrainListeleyici.getNext()->instance;
initBlendMaps(ornekTerrain); } }
// Geçici olarak oluşturduğumuz kaynakları belleğimizden siliyoruz:
terrainGrubu->freeTemporaryResources(); }
Şimdi createScene fonksiyonunda yaptığımız işlemleri görselleştirelim. Kalın kırmızı oklar createScene fonksiyonunun içinden çağrılan fonksiyonlara gidiyor. Siyah oklar tanımlama işlemini temsil ediyor. Resmimiz şu şekilde:
7. terrainYapilandirma Fonksiyonu
Terrain oluştururken her zaman bir resim dosyasından faydalanırız. Bu resim dosyasını da az sonra getTerrainImage fonksiyonunda tanımlayacağız. Ama şimdi anlatacağımız fonksiyonun daha anlaşılır olması için kullanacağımız resim dosyasını, bu bölümde sizlerle paylaşıyoruz:
Yüklediğimiz bu resim dosyasından terrain oluşturulurken, resimdeki en beyaz yerler en yüksek, en siyah yerler en alçak yerler olarak tasarlanır. Bu siyah ve beyaz renk arasındaki renk geçişleri terrainimizdeki eğimli araziyi oluşturur.
Şimdi kuracağımız setMaxPixelError fonksiyonuyla resim dosyasından terrain modeli üretilirken ne kadar hassas davranılacağı belirteceğiz. setMaxPixelError fonksiyonu büyük bir değer almış ise arazı modelimiz oluşturulurken daha az yüzey kullanılır. Resim dosyamızdaki renk geçişlerinden hafif tonda olanları ihmal edilir. Ama setMaxPixelError fonksiyonuna küçük bir değer belirlersek, resim dosyasındaki en belirsiz tonlamalar bile hesaba katılarak, daha çok yüzeyden oluşan bir terrain modeli oluşturulur.
setMaxPixelError fonksiyonunun aldığı değer küçüldükçe, üretilen terrain modelindeki yüzey sayısı artacağından bellek kullanımı da oldukça artacaktır. Şimdi uygulama dosyamızın içindeki terrainYapilandirma fonksiyonunun içine şu satırları ekleyelim:
// Resimden terrain modeli oluşturma hassasiyetini kuruyoruz:
terrainAyarlari->setMaxPixelError(8);
Devam etmeden önce bir hatırlatma yapalım. createScene fonksiyonundayken bu fonksiyonu çağırırken parametresine gunes nesnesini atamıştık. Yani terrainYapilandirma(gunes) şeklinde yazmıştık. terrainYapilandirma fonksiyonumuzun tanımı, terrainYapilandirma(Ogre::Light* atananIsik) şeklindedir. atananIsik nesnesinin yerine gunes nesnesi geçecek. Aklınızda bulunsun.
Sırada LightMap için mesafe kurulumu var. LightMap, tıpkı terrain modelini hazırladığımız resim dosyamız gibi bir resim dosyadır. DirectionalLight’ın özelliklerine göre bir kaplama dosyası hazırlanır. Daha sonra bu kaplama dosyası terrain modelinin üstüne kaplanır. Ama amacı renklendirmek değil tonlama yapmaktır. Bu tonlama sayesinde aydınlık ve gölge olan bölgeler oluşturulur. Aşağıda bir terrain modelinin oluşturulduğu resim ve terrain modeli için hesaplanan LightMap örneği vardır:
OGRE, LightMap dosyasını kendisi üretir. Şimdi biz LightMap hesaplamasında kullanacağımız DirectionalLight’ın terrain’e olan uzaklığını terrainAyarlari üzerinden setCompositeMapDistance fonksiyonuyla belirleyeceğiz. Bu işlem için terrainYapilandirma fonksiyonumuzun içine şu satırları ekliyoruz:
// LightMap hazırlanırken kullanılacak DirectionalLight'ın, Terrain'e olan uzaklığını kuruyoruz:
terrainAyarlari->setCompositeMapDistance(3000);
Sonra LightMap işlemleri için kullanılacak DirectionalLight nesnesini terrainAyarlari nesnesine tanıtmamız lazım. DirectionalLight’ı kurarken sadece yön değerini kuruyorduk. Konum değeri kurmuyorduk. Bu nedenle LightMap için tanıtma işleminde DirectionalLight’ın yönünü almamız yeterli. terrainAyarlari nesnemizin üye fonksiyonu olan getLightMapDirection fonksiyonunu çağıracağız. Parametresinde de atananIsik nesnesinin üye fonksiyonu olan getDeviredDirection fonksiyonunu çağıracağız. createScene fonksiyonundaki terrainYapilandirma(gunes) tanımımızdan dolayı atananIsik nesnesi, gunes nesnesine gidecek. getDeviredDirection fonksiyonuyla da gunes nesnesinin yönünü belirleyen gunesYonu vektörü çağırılacak. Böylece LightMap için kullanacağımız DirectionalLight’ı tanımlamış olacağız. Bu işlemi yapmak için terrainYapilandirma fonksiyonumuzun içine şu satırları ekliyoruz:
// LightMap hazırlanırken kullanılacak DirectionalLight'ın yönünü belirliyoruz:
terrainAyarlari->setLightMapDirection(atananIsik->getDerivedDirection());
Oluşturulacak LightMap dosyasının AmbientLight tonlaması için SM’miz olan mSceneMgr nesnesinin AmbientLight’ını kullanacağız. Bu işlem için terrainAyarlari nesnesinin üye fonksiyonu olan setCompositeMapAmbient fonksiyonunu çağıracağız. Parametresine de mSceneMgr üzerinden getAmbientLight fonksiyonunu atayacağız. Bu işlemle, createScene fonksiyonunda tanımlamış olduğumuz, yani sahnemizde kullanılan AmbientLight’ı, LightMap işlemi için de atamış olacağız. Bu işlemi yapmak için terrainYapilandirma fonksiyonumuzun içine şu satırları ekliyoruz:
// LightMap hazırlanırken kullanılacak AmbientLight'ı belirliyoruz:
terrainAyarlari->setCompositeMapAmbient(mSceneMgr->getAmbientLight());
Şimdi de LightMap dosyasının aydınlatma rengini belirleyeceğiz. Yani, bir bakıma güneşimizin rengini belirleyeceğiz. gunes nesnemizin Diffuse rengi beyazdı. LightMap hazırlama işleminde de aynı rengi kullanacağız. Bunu yapmak için terrainAyarlari üzerinden setCompositeMapDiffuse fonksiyonunu çağıracağız. Parametresine de atananIsik nesnesi üzerinden getDiffuseColour fonksiyonunu gireceğiz. Bu işlemi yapmak için terrainYapilandirma fonksiyonumuzun içine şu satırları ekliyoruz:
// LightMap hazırlanırken güneş olarak kullanılacak olan ışığın rengini belirliyoruz:
terrainAyarlari->setCompositeMapDiffuse(atananIsik->getDiffuseColour());
Terrain oluştururken kullandığımız resmin, kaplamaların ayarlamalarını yapmamız gerekiyor. terrainAyarlari nesnesiyle OGRE’nin kendi üreteceği dosyaların, kullanacağını fonksiyonların düzenlemesini yapmıştık. terrainGrubu nesnesiyle ise OGRE’nin program dışından yükleyeceği dosyaları düzenleyeceğiz. Yapacağımız ilk işlem varsayılan(default) import ayarlarını belirlemektir. Belirler belirlemez terrainGrubu’nun kendinde varolan varsayılan import ayarlarını yükleyeceğiz. Bu işlem için terrainGrubu üzerinden getDefaultImportSettings fonksiyonunu kullanacağız. Belirlediğimiz bu varsayılan import ayarları terrainimiz oluşturulurken kullanılacak olan resmi ve birkaç ek ayarı daha düzenlemek içindir. Önceki satırlardan hatırlayacağınız üzere biz terrainimizi, terrain.png dosyasından oluşturmuştuk. Varsayılan import ayarları import işlemi sırasında terrain.png dosyasını düzenleyecektir.
Import ayarlarını düzenlemek için bir ImportData struct’u oluşturacağız. Bu struct’un ismi de varsayilanImportAyarlari olacak. Bir yapının üye fonksiyonlarına veya değişkenlerine erişirken, ok “->” yerine; nokta “.” kullanacağız. Şimdi değerleri belirlemeye geçelim. Belirleyeceğimiz ilk iki değer olan terrainSize ve worldSize, terrainGrubu nesnesini dinamik nesne olarak tanımlarken kullandığımız üçüncü ve dördüncü parametrelerin aynısıdır. inputScale değeri, resim dosyamızın türü ve kayıt özellikleriyle ilgilidir. Kullanacağımız terrain.png dosyası 8bpp olduğu için 600 değerini gireceğiz. İlerleyen dokümanlarda bu değerin nasıl belirlendiğiyle ilgili bilgi vereceğiz. Şimdilik üzerinde fazla durmayın, yazıp geçin.
OGRE, LOD sistemi için terrain modelini karesel onlarca parçaya ayırır. Bu parçaların örneğin dört tanesi birleşerek daha büyük bir parça oluşturur. Bu şekilde onlarca terrain parçası oluşturulur. Bizim kameramızın görüş alanına giren ilgili parça/parçalar renderlenir. Terrain modelinin bir kenarının kaç parçaya bölüneceğini minBatchSize belirler. Değer arttıkça terrain daha küçük parçalara bölünecektir. Yan yana olan, terrain parçalarının birleşerek oluşturacağı daha büyük karesel parçaları da maxBatchSize değeri belirler. maxBatchSize değeri tek sayı olmalıdır ve 65’e eşit veya 65’ten daha küçük olmalıdır. Biz minBatchSize değerini 33 olarak, maxBatchSize değerini 65 olarak kuracağız. Bu işlemleri yapmak için terrainYapilandirma fonksiyonumuzun içine şu satırları ekliyoruz:
// Default import ayarlarını import ettiğimiz terrain hazılama resmi için yapılandırıyoruz:
Ogre::Terrain::ImportData& varsayilanImportAyarlari = terrainGrubu->getDefaultImportSettings();
varsayilanImportAyarlari.terrainSize = 513;
varsayilanImportAyarlari.worldSize = 12000.0f;
varsayilanImportAyarlari.inputScale = 600;
// LOD Sistemi için oluşturulacak karesel modellerin özelliklerini belirtiyoruz:
varsayilanImportAyarlari.minBatchSize = 33;
varsayilanImportAyarlari.maxBatchSize = 65;
Terrain’in modelinin üzerine kaplayacağımız kaplamaları belirleme işlemine geldik. Öncelikle terraine kaç kaplama dosyası kaplayacağımızı belirteceğiz. Bu işlemi varsayilanImportAyarlari struct’umuzun, layerList fonksiyonu üzerinden resize fonksiyonu ile yapacağız. Örneğin 3 resim kullanacaksak, parametresine 3 değerini atayacağız. Bizim için 0, 1 ve 2 indeksleriyle çağrılan üç katman oluşturulacak. Bu katmanlara yine varsayilanImportAyarlari struct’umuz üzerinden layerList*katmanın indeksi+ şeklinde ulaşacağız. Kuracağımız ilk değer worldSize değeri, ki zaten bunu biliyorsunuz. İkinci değer olan ve textureNames üzerinden çalışan push_back ise bu katmana yükleyeceğimiz kaplama dosyalarını belirleyecek. Biz bir katman için iki kaplama dosyası yükleyeceğiz. Bu iki kaplama dosyasının barındırdığı toplam dört farklı resmi, OGRE’nin varsayılan material yönetici kullanarak bir kaplama resmi tanımlar. Örneğin kullanacağımız dirt_grayrocky_diffusespecular.dds dosyası, alpha kanalında specular haritası olmak üzere renk haritasını; dirt_grayrocky_normalheight.dds dosyası ise alpha kanalında height haritası(yükselti haritası) olmak üzere normal haritasını barındırır.
Şimdi bahsettiğimiz işlemleri yapmak için terrainYapilandirma fonksiyonumuzun içine şu satırları ekliyoruz:
// Kullanacağımız kaplamalar için katman sayısını belirliyoruz:
varsayilanImportAyarlari.layerList.resize(3);
// Birinci katmanın worldSize değerini ve kaplama dosyalarını tanımlıyoruz:
varsayilanImportAyarlari.layerList[0].worldSize = 100;
varsayilanImportAyarlari.layerList[0].textureNames.push_back("dirt_grayrocky_diffusespecular.dds");
varsayilanImportAyarlari.layerList[0].textureNames.push_back("dirt_grayrocky_normalheight.dds");
// İkinci katmanın worldSize değerini ve kaplama dosyalarını tanımlıyoruz:
varsayilanImportAyarlari.layerList[1].worldSize = 30;
varsayilanImportAyarlari.layerList[1].textureNames.push_back("grass_green-01_diffusespecular.dds");
varsayilanImportAyarlari.layerList[1].textureNames.push_back("grass_green-01_normalheight.dds");
// Üçüncü katmanın worldSize değerini ve kaplama dosyalarını tanımlıyoruz:
varsayilanImportAyarlari.layerList[2].worldSize = 200;
varsayilanImportAyarlari.layerList[2].textureNames.push_back("growth_weirdfungus-03_diffusespecular.dds");
varsayilanImportAyarlari.layerList[2].textureNames.push_back("growth_weirdfungus-03_normalheight.dds");
