Kamera

Önceki bölümde, görünüm matrisini ve sahne etrafında hareket etmek için görünüm matrisini nasıl kullanabileceğimizi tartıştık. OpenGL tek başına bir kamera kavramına aşina değildir, ancak sahnedeki tüm nesneleri ters yönde hareket ettirerek, hareket ettiğimiz yanılsamayı verebiliriz.

Bu bölümde OpenGL' de kamerayı nasıl kurabileceğimizi tartışacağız. 3-boyutlu bir sahnede özgürce hareket etmenizi sağlayan kamerayı inceleyeceğiz. Ayrıca klavye ve fare girdilerini tartışacağız ve özel bir kamera sınıfıyla eğitselimizi bitireceğiz.

Kamera/ Görünüm uzayı

Kamera/ görünüm uzayı hakkında konuşurken, sahnenin orijini olarak kameranın perspektifinden görüldüğü gibi tüm köşe koordinatlarından bahsediyoruz: görünüm matrisi tüm dünya koordinatlarını kameranın konumuna ve yönüne göre görünüm koordinatlarına dönüştürür. Bir kamerayı tanımlamak için dünya uzayındaki konumuna, baktığı yöne, sağa işaret bir vektöre ve kameradan yukarıya işaret eden bir vektöre ihtiyacımız var. Dikkatli bir okuyucu, aslında kameranın orijin noktası olarak 3 dikey birim eksenli bir koordinat sistemi oluşturacağımızı fark edebilir.

1. Kameranın konumu

Kamera konumunu elde etmek kolaydır. Kamera konumu aslında, dünya uzayında kameranın konumuna işaret eden bir vektördür. Kamerayı, önceki bölümde ayarladığımız konuma getirdik:

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);  

Pozitif z-ekseninin ekranınızdan size doğru olduğunu unutmayın. Eğer kameranın geriye doğru hareket etmesini istiyorsak, pozitif z-ekseni boyunca hareket ettiririz.

2. Kameranın yönü

Gerek duyulan bir sonraki vektör, kameranın yönü örn. hangi yöne işaret ettiğidir. Şimdilik kameranın sahnemizin orijinine işaret etmesine izin verdik: (0,0,0). İki vektörü birbirinden çıkarırsak, bu iki vektörün farkı olan bir vektör elde ettiğimizi hatırlıyor musunuz? Kamera konum vektörünü sahnenin başlangıç vektörününden çıkarmak bize istediğimiz yön vektörünü verecektir. Görünüm matrisinin koordinat sistemi için z- ekseninin pozitif olmasını istiyoruz ve OpenGL geleneğinde kamera negatif z- eksenini işaret ettiğinden yön vektörünü olumsuzlamak istiyoruz. Çıkarma sırasını değiştirirsek, kameranın pozitif z- eksenine işaret eden bir vektör elde ederiz:

glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);

Yön vektörü isimlendirmesi, seçilebilecek en iyi isim değildir, çünkü aslında hedeflediği şeyin ters yönünü göstermektedir.

3. Sağ eksen

İhtiyacımız olan bir sonraki vektör, kamera uzayının pozitif x-eksenini temsil eden sağ yönde bir vektördür. Sağ yön vektörü elde etmek için, önce (dünya uzayda) yukarıyı gösteren bir yukarı vektör belirterek küçük bir hile yaparız. Daha sonra, yukarı vektörde ve adım 2'deki yön vektöründe bir çapraz çarpım yaparız. Bir çapraz çarpım sonucu her iki vektöre dik bir vektör olduğundan, pozitif x- ekseninin yönünü gösteren bir vektör elde edeceğiz (eğer çapraz çarpım sırasını değiştirirsek, negatif x- ekseninde işaret eden bir vektör elde etmiş oluruz):

glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); 
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));

4. Yukarı eksen

Artık hem x- ekseni vektörüne hem de z- ekseni vektörüne sahip olduğumuza göre, kameranın pozitif y- eksenine işaret eden vektörü almak nispeten kolaylaştı: sağ ve yön vektörünün çapraz çarpımını alıyoruz:

glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);

Çapraz çarpım ve birkaç püf noktası yardımıyla, görünüm/ kamera uzayını oluşturan tüm vektörleri elde ettik. Daha matematik meraklısı okuyucular için ifade edecek olursak, bu işlem lineer cebirde Gram-Schmidt işlemi olarak bilinir. Artık kamera vektörlerini kullanarak, kamera oluşturmak için çok yararlı olduğu kanıtlanan bir LookAt matrisi oluşturabiliriz.

Look At

Matrislerle ilgili en iyi şey, 3 dikey (veya doğrusal olmayan) eksen kullanarak bir koordinat uzayı tanımladığınızda, bu 3 eksen ve bir çeviri vektörü ile bir matris oluşturabileceğiniz ve herhangi bir vektörü bu matrisle çarparak istenen koordinat uzayına dönüştürebileceğinizdir. LookAt matrisinin yaptığı da tam olarak budur ve şimdi 3 dikey eksene ve kamera uzayını tanımlamak için bir konum vektörüne sahibiz, kendi LookAt matrisimizi oluşturabiliriz:

LookAt=[RxRyRz0UxUyUz0DxDyDz00001][100Px010Py001Pz0001]LookAt = \begin{bmatrix} \color{red}{R_x} & \color{red}{R_y} & \color{red}{R_z} & 0 \\ \color{green}{U_x} & \color{green}{U_y} & \color{green}{U_z} & 0 \\ \color{blue}{D_x} & \color{blue}{D_y} & \color{blue}{D_z} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} 1 & 0 & 0 & -\color{purple}{P_x} \\ 0 & 1 & 0 & -\color{purple}{P_y} \\ 0 & 0 & 1 & -\color{purple}{P_z} \\ 0 & 0 & 0 & 1 \end{bmatrix}

R\color{red}R sağ vektör, U\color{green}U yukarı vektör, D\color{blue}D yön vektörü ve P\color{purple}P kameranın konum vektörüdür. Döndürme (sol matris) ve öteleme (sağ matris) parçalarının, dünyayı kameranın hareket etmesini istediğimiz yerin tersi yönde döndürmek ve ötelemek istediğimizden ters çevrildiğini (sırasıyla transpoze edildiğini ve olumsuzlandığını) unutmayın. Bu LookAt matrisini görünüm matrisimiz olarak kullanmak, tüm dünya koordinatlarını yeni tanımladığımız görünüm alanına etkili bir şekilde dönüştürür. LookAt matrisi tam olarak ismini taşır: belirli bir hedefe bakan bir görünüm matrisi oluşturur.

Neyse ki GLM zaten tüm bu işleri bizim için yapıyor. Sadece bir kamera konumu, bir hedef konum ve dünya uzayındaki yukarı yön vektörü temsil eden bir vektör (sağ yön vektörü hesaplamak için kullandığımız yukarı vektör) belirtmeliyiz. GLM daha sonra görünüm matrisimiz olarak kullanabileceğimiz LookAt matrisini oluşturur:

glm::mat4 view;
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), 
  		   glm::vec3(0.0f, 0.0f, 0.0f), 
  		   glm::vec3(0.0f, 1.0f, 0.0f));

glm :: LookAt işlevi, sırasıyla bir konum, hedef ve yukarı yön vektörü gerektirmektedir. Bu örnek, önceki bölümde oluşturduğumuzla aynı olan bir görünüm matrisi oluşturur.

Kullanıcı girdisine derinlemesine girmeden önce, kamerayı sahnemizin etrafında döndürerek biraz ürkek davranalım. Sahnenin hedefini (0,0,0) 'da tutarız. Daire üzerindeki bir noktayı temsil eden her kareyi x ve zkoordinatı oluşturmak için biraz trigonometri kullanıyoruz ve bunlardan kamera konumumuz için faydalanacağız. Zaman içinde x ve y koordinatlarını yeniden hesaplayarak, bir daire içindeki tüm noktaları geziyoruz ve böylece kamera sahne etrafında dönüyor. Bu daireyi önceden tanımlanmış bir yarıçapla büyütüyor ve GLFW' nin glfwGetTime işlevini kullanarak her bir kareyi yeni bir görünüm matrisi oluşturuyoruz:

const float radius = 10.0f;
float camX = sin(glfwGetTime()) * radius;
float camZ = cos(glfwGetTime()) * radius;
glm::mat4 view;
view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0));  

Kodu çalıştırdığınızda, aşağıda ekli bulunan videoda göründüğü gibi bir çıktı elde etmelisiniz.

Bu küçük kod parçası ile kamera artık zaman içinde sahnenin etrafında dönebilmektedir. LookAt matrisinin nasıl çalıştığını anlamak için yarıçap ve konum/ yön parametrelerini değiştirmeyi denemekten çekinmeyin. Ayrıca, tıkanırsanız kaynak kodunu kontrol edin.

Gezinti

Kamerayı bir sahnenin etrafında döndürmek eğlencelidir, ancak tüm hareketi kendimizin yapması çok daha eğlencelidir! Öncelikle bir kamera sistemi kurmamız gerekiyor, bu nedenle programımızda bazı kamera değişkenlerini tanımlamak yararlı olacaktır:

glm::vec3 cameraPos   = glm::vec3(0.0f, 0.0f,  3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp    = glm::vec3(0.0f, 1.0f,  0.0f);

Nihayet karşınızdaLookAt işlevi:

view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);

İlk olarak, kamera konumunu önceden tanımlanmış cameraPos değişkeni ile ayarladık. Yön, mevcut konum artı az önce tanımladığımız yön vektörüdür. Bu, hareket etmemize rağmen kameranın hedef yöne bakmaya devam etmesini sağlar. Haydi, bazı tuşlara bastığımızda cameraPos vektörünü güncelleyerek bu değişkenlerle oynayalım.

GLFW' nin klavye girişini yönetmek için bir processInput işlevi zaten tanımlıydı, bu yüzden birkaç ekstra tuş komutu ekleyelim:


void processInput(GLFWwindow *window)
{
    ...
    const float cameraSpeed = 0.05f; // adjust accordingly
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        cameraPos += cameraSpeed * cameraFront;
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        cameraPos -= cameraSpeed * cameraFront;
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}

WASD tuşlarından birine her bastığımızda, kameranın konumu buna göre güncellenir. İleri veya geri gitmek istiyorsak, yön vektörünü bir hız değerine göre ölçeklendirilmiş konum vektörüne ekler veya çıkarırız. Yanlara doğru hareket etmek istiyorsak, sağ yönlü bir vektör oluşturmak için bir çapraz çarpım yaparız ve buna göre sağ yönlü vektör boyunca hareket ederiz. Bu, kamerayı kullanırken bilindik strafe efekti oluşturur.

Şimdiye kadar, kamerayı bir şekilde hareket ettirebilmişsinizdir, sisteme özgü bir hızda olsa da, cameraSpeed ayarını yapmanız gerekebilir.

Hareket hızı

Mevcut durumda hareket hızı için sabit bir değer kullandık. Teorik olarak bu iyi görünüyor, ancak pratikte kullanıcıların makinelerinde farklı işlem gücü var ve bunun sonucu olarak bazı insanlar her saniye diğerlerinden daha fazla kare sahneleyebiliyorlar. Bir kullanıcı başka bir kullanıcıdan daha fazla kare oluşturduğunda processInput'u daha sık çağırır. Sonuç olarak, bazı insanlar sistemlerine bağlı olarak gerçekten hızlı ve bazıları da yavaş hareket ederler. Uygulamanızı gönderirken her türlü donanımda aynı şekilde çalıştığından emin olmak istersiniz.

Grafik uygulamaları ve oyunlar genellikle son kareyi oluşturmak için gereken süreyi saklayan bir deltatime değişkenini tutar. Daha sonra tüm hızları bu deltaTime değeri ile çarpar. Sonuç olarak, bir karede büyük bir deltaTime varsa, yani son karenin ortalamadan daha uzun sütüyorsa, bu karenin hızının da hepsini dengelemek için biraz daha yüksek olacağı anlamına gelir. Bu yaklaşımı kullanırken, çok hızlı veya yavaş bir bilgisayarınız varsa, kameranın hızı buna göre dengelenir, böylece her kullanıcı aynı deneyime sahip olur.

deltaTime değerini hesaplamak için 2 global değişkeni tutuyoruz:

float deltaTime = 0.0f;	// Time between current frame and last frame
float lastFrame = 0.0f; // Time of last frame

Daha sonra her karede daha sonra kullanılmak üzere yeni deltaTime değerini hesaplıyoruz:

float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;  

Artık deltaTime değişkenine sahip olduğumuza göre, hızları hesaplarken bunu da hesaplamalara dahil edebiliriz:

void processInput(GLFWwindow *window)
{
    float cameraSpeed = 2.5f * deltaTime;
    [...]
}

deltaTime kullandığımız için kamera şimdi saniyede 2.5 birim sabit bir hızda hareket edecektir. Önceki bölümle birlikte artık sahnede hareket etmek için çok daha pürüzsüz ve daha tutarlı bir kamera sistemine sahip olduk:

Ve şimdi herhangi bir sistemde çalışan ve eşit derecede hızlı görünen bir kameramız var. Yine, takıldığınız bir yer olursa kaynak kodunu kontrol edin. deltaTime değerinin, hareket ile ilgili herhangi bir değişim olduğunda sık sık geri döndüğünü göreceğiz.

Etrafı seyret

Hareket etmek için sadece klavye tuşlarını kullanmak o kadar da ilginç bir şey değildir. Özellikle de hareketi kısıtlı hale getiremediğimiz için. Fare burada devreye giriyor!

Sahnenin etrafına bakmak için kamera girdisini farenin girdisine göre değiştirmeliyiz. Bununla birlikte, fare döndürülmesine göre yön vektörünün değiştirilmesi biraz karmaşıktır ve bir miktar trigonemetri bilgisi gerektirir. Trigonemetriyi anlamadıysanız endişelenmeyin, kod bölümlerine geçip onları kendi programınıza yapıştırabilirsiniz; daha fazla bilgi edinmek istediğinizde geri dönebilirsiniz.

Euler açıları

Euler açıları, 1700'lerde Leonhard Euler tarafından tanımlanan 3B'deki herhangi bir dönüşü temsil edebilen değerlerdir. Üç adet Euler açısı vardır: meyil(ing. pitch), sapma (ing. yaw) ve yuvarlanma (ing. roll). Aşağıdaki görsel onlara temsili bir anlam vermektedir:

Meyil açısı, ilk resimde görüldüğü gibi ne kadar aşağı veya yukarı baktığımızı gösteren açıdır. İkinci resim, sola veya sağa baktığımız büyüklüğü temsil eden sapma açı değerini gösterir. Yuvarlanma açısı, uzay uçuş kameralarında en çok kullandığımız yuvarlanma/sallanma miktarını temsil eder. Euler açılarının her biri tek bir değerle ifade edilir ve bunların üçünün birleşimi ile herhangi bir döndürme vektörünü 3-boyutlu olarak hesaplayabiliriz.

Kamera sistemimiz için sadece sapma ve meyil değerlerini önemsiyoruz, bu nedenle burada yuvarlanma açı değerini tartışmayacağız. Bir meyil ve sapma değeri verildiğinde, bunları yeni bir yön vektörünü temsil eden 3- boyutlu vektöre dönüştürebiliriz. Sapma ve meyil değerlerini bir yön vektörüne dönüştürme işlemi biraz trigonemetri bilgisi gerektirir:

Hafızamızı tazeleyelim ve bir dik üçgeni (bir köşesi 90 derecelik bir açıya sahip olan) kontrol edelim:

Hipotenüsü 1 uzunluğunda tanımlarsak cos x/h=cos x/1=cos x\cos \ \color{red}x/\color{purple}h = \cos \ \color{red}x/\color{purple}1 = \cos\ \color{red}x ve karşı kernarın uzunluğunun da sin y/h=sin y/1=sin y\sin \ \color{green}y/\color{purple}h = \sin \ \color{green}y/\color{purple}1 = \sin\ \color{green}y olduğunu trigonometriden biliyoruz.Bu, bize verilen açıya bağlı olarak dik üçgenlerdeki hem x hem de y kenarlarındaki uzunluğu elde etmek için bazı genel formüller verir. Bunu yön vektörünün bileşenlerini hesaplamak için kullanalım.

Aynı üçgeni hayal edelim, ama şimdi bitişikteki üst perspektiften ve karşıt tarafların x ve z eksenine (y eksenine bakıyormuş gibi) paralel olarak bakıyoruz.

Sapma açısını x tarafından başlayarak saat yönünün tersine doğru görselleştirirsek, x tarafının uzunluğunun cos(yaw) ile ilişkili olduğunu görebiliriz. Benzer şekilde z tarafının uzunluğu sin(yaw) ilişkilidir.

Bu bilgiyi ve verilen bir sapma değerini alırsak, kamera yönü vektörü oluşturmak için bunları kullanabiliriz:

glm::vec3 direction;
direction.x = cos(glm::radians(yaw)); // Açıları önce radyana çevirdiğimizi unutmayın
direction.z = sin(glm::radians(yaw));

Bu, bir sapma değerinden bir 3- boyutlu yön vektörünü nasıl elde edebileceğimizi çözer, ancak meyilin de dahil edilmesi gerekir. Şimdi y-ekseni tarafına xz düzleminde oturuyormuşuz gibi bakalım:

Benzer şekilde, bu üçgenden yönün y-bileşeninin sin(pitch) değerine eşit olduğunu görebiliriz, bu yüzden şunu yazabiliriz:

direction.y = sin(glm::radians(pitch));  

Bununla birlikte, meyil üçgeninden, xz kenarlarının cos(pitch)tarafından etkilendiğini de görebiliriz, bu yüzden bunun da yön vektörünün bir parçası olduğundan emin olmalıyız. Bunu dahil ederek sapma ve meyil Euler açılarından çevrildiği şekliyle son yön vektörünü elde ederiz:

direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
direction.y = sin(glm::radians(pitch));
direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));

Bu bize sapma ve meyil değerlerini, etrafı seyretmek için kullanabileceğimiz 3- boyutlu bir yön vektörüne dönüştürmek için bir formül sunar.

Sahneyi oluşturduk, ve her şey negatif z- ekseni yönünde konumlandırıldı. Bununla birlikte, x ve z sapma açısı üçgenine bakarsak, 0'ın θ değerinin, kameranın yön vektörüne pozitif x- eksenine işaret ettiğini görürüz. Kameranın varsayılan olarak negatif z- eksenini gösterdiğinden emin olmak için, yaw'a saat yönünde 90 derece varsayılan bir değer verebiliriz. Pozitif açılar saat yönünün tersine doğrudur, böylece varsayılan yawdeğerini aşağıdaki gibi ayarladık:

yaw = -90.0f;

Muhtemelen şimdiye kadar merak ettiniz: bu sapma ve meyil değerlerini nasıl ayarlayabilir ve değiştirebiliriz?

Fare girdisi

Sapma ve meyil değerleri, yatay fare hareketinin sapması ve dikey fare hareketinin meyili etkilediği fare (veya kontrolcü/ joystick) hareketinden elde edilir. Anafikir, son karenin fare konumlarını saklamak ve mevcut karede fare değerlerinin ne kadar değiştiğini hesaplamaktır. Yatay veya dikey fark ne kadar yüksek olursa, pitch veya yaw değerini o kadar sık güncelleriz ve böylece kamera daha fazla hareket eder.

İlk olarak GLFW' ye imleci gizlemesi ve yakalaması gerektiğini söyleyeceğiz. Bir imlecin yakalanması, odaklanma olduğunda, fare imlecinin pencerenin merkezinde kalması anlamına gelir (uygulama odağı kaybetmediği veya uygulamadan çıkmadığı sürece). Bunu basit bir konfigürasyon çağrısı ile yapabiliriz:

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);  

Bu çağrıdan sonra, fareyi hareket ettirdiğimiz her yerde görünmez ve pencereden ayrılmaz. Bu bir FPS kamera sistemi için mükemmeldir.

Meyil ve sapma açı değerlerini hesaplamak için GLFW' ye fare hareketlerini dinlemesini söylememiz gerekir. Bunu, aşağıdaki prototiple bir işlev çağrısı oluşturarak yaparız:

void mouse_callback(GLFWwindow* window, double xpos, double ypos);

Burada xpos ve ypos mevcut fare konumlarını temsil eden değişkenlerdir. Fare her hareket ettiğinde geri çağırım işlevini GLFW' ye kaydettirir kaydetmez, mouse_callback işlevi çağrılır:

glfwSetCursorPosCallback(window, mouse_callback);  

Bu tarz kamera için fare girdisini kullanırken, kameranın yön vektörünü tam olarak hesaplayabilmek için atmamız gereken birkaç adım vardır:

  1. Son kareden bu yana farenin ofsetini hesaplayın.

  2. Ofset değerlerini kameranın yaw ve pitch değerlerine ekleyin.

  3. Minimum/ maksimum adım değerlerine bazı sınırlamalar ekleyin.

  4. Yön vektörünü hesaplayın.

İlk adım, son kareden bu yana farenin ofsetini hesaplamaktır. Öncelikle, ekranın ortasında olmaya başladığımız (ekran boyutu 800'e 600) son fare konumlarını uygulamada depolamamız gerekir:

float lastX = 400, lastY = 300;

Daha sonra farenin geri çağırım işlevinde, son ve geçerli kare arasındaki ofset hareketini hesaplıyoruz:

float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // y- koordinatları aşağıdan yukarıya doğru bir aralığa sahip olduğu için tersine çevrildi
lastX = xpos;
lastY = ypos;

const float sensitivity = 0.1f;
xoffset *= sensitivity;
yoffset *= sensitivity;

Ofset değerlerini bir hassasiyet değeri ile çarptığımızı unutmayın. Bu çarpmayı atlarsak, fare hareketi çok güçlü olur.

Sonra ofset değerlerini global olarak deklare edilen meyil ve sapma açı değerlerine ekliyoruz:

yaw   += xoffset;
pitch += yoffset;  

Üçüncü adımda, kullanıcıların tuhaf kamera hareketleri yapamayacağı şekilde kameraya bazı kısıtlamalar eklemek istiyoruz (ayrıca yön vektörü dünya yukarı yönüne paralel olduğunda LookAt' in ters dönmesine neden oluyor). Pitch, kullanıcıların 89 dereceden daha yüksek görünmeyeceği (90 derecede LookAt salto yapar) ve aynı zamanda -89 derecenin altında olmayacak şekilde kısıtlanmalıdır. Bu, kullanıcının gökyüzüne ya da ayağının altına bakabilmesini sağlar. Kısıtlama ihlal edildiğinde, Euler değeri kısıtlama değeri ile değiştirilir:

if(pitch > 89.0f)
  pitch =  89.0f;
if(pitch < -89.0f)
  pitch = -89.0f;

Kullanıcıyı yatay dönüşte kısıtlamak istemediğimiz için sapma açı değeri üzerinde herhangi bir kısıtlama koymadığımıza dikkat edin. Bununla birlikte, eğer isterseniz sapma açısına bir kısıtlama eklemek de kolaydır.

Dördüncü ve son adım, önceki bölümde de kullandığımız formülü kullanarak gerçek yön vektörünü hesaplamaktır:

glm::vec3 direction;
direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
direction.y = sin(glm::radians(pitch));
direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
cameraFront = glm::normalize(direction);

Hesaplanan yön vektörü daha sonra farenin hareketinden hesaplanan tüm dönüşleri içermektedir. cameraFront vektörü zaten GLM' in lookAt işlevine dahil edildiğinden, devam etmeye hazırız.

Kodu çalıştırırsanız, fare imlecinize ilk kez odaklandığında kameranın büyük bir sıçrama yaptığını görürsünüz. Bu ani sıçramanın nedeni, imleciniz pencereye girer girmez, fare geri çağırım işlevinin, farenizin ekrana girdiği konuma eşit bir xpos ve ypos konumu ile çağrılmasıdır. Bu genellikle ekranın merkezinden önemli ölçüde uzakta olan ve büyük ofsetlere ve dolayısıyla büyük bir hareket sıçramasına neden olan bir konumdur. İlk kez fare girdisi alıp almadığımızı kontrol etmek için genel bir bool değişkeni tanımlayarak bu sorunu aşabiliriz. İlk kez kullanıyorsanız, ilk fare konumlarını yeni xpos ve ypos değerlerine güncelliyoruz. Ortaya çıkan fare hareketleri, ofsetleri hesaplamak için farenin yeni girilen konum koordinatlarını kullanır:

if (firstMouse) // true olarak ilklendirilir
{
    lastX = xpos;
    lastY = ypos;
    firstMouse = false;
}

Son kod:

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }
  
    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos; 
    lastX = xpos;
    lastY = ypos;

    float sensitivity = 0.1f;
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    yaw   += xoffset;
    pitch += yoffset;

    if(pitch > 89.0f)
        pitch = 89.0f;
    if(pitch < -89.0f)
        pitch = -89.0f;

    glm::vec3 direction;
    direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
    direction.y = sin(glm::radians(pitch));
    direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
    cameraFront = glm::normalize(direction);
}  

Bir dönüş hareketi yapın ve şimdi 3-boyutlu sahnemizde özgürce hareket edebildiğimizi göreceksiniz!

Yakınlaştır

Kamera sistemine ek olarak biraz yakınlaştırma arayüzü de uygulayacağız. Önceki bölümde, görüş alanı veya fov'un sahneyi ne kadar görebildiğimizi büyük ölçüde tanımladığını söyledik. Görüş alanı küçüldüğünde, sahnenin izdüşürülen alanı küçülür. Daha küçük alan için aynı NDC' nin üzerine izdüşürülüyor ve yakınlaştırılıyor yanılsaması verir. Yakınlaştırmak için farenin kaydırma tekerleğini kullanacağız. Fare hareketi ve klavye girdisine benzer şekilde, fare kaydırma için bir geri çağırım işlevimiz mevcuttur:

void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    Zoom -= (float)yoffset;
    if (Zoom < 1.0f)
        Zoom = 1.0f;
    if (Zoom > 45.0f)
        Zoom = 45.0f; 
}

Kaydırma sırasında, y ofset değeri bize dikey olarak kaydırdığımız miktarı söyler. Scroll_callback işlevi çağrıldığında, global olarak bildirilen fov değişkeninin içeriğini değiştiririz. 45.0 varsayılan fov değeri olduğundan, zoom seviyesini 1.0 ile 45.0 arasında kısıtlamak istiyoruz.

Şimdi perspektif izdüşüm matrisini her bir karede GPU' ya yüklemeliyiz, ancak bu sefer fov değişkeni kendi görüş alanı olacak:

projection = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);  

Son olarak, kaydırma geri çağırma işlevini yazmayı unutmayın:

glfwSetScrollCallback(window, scroll_callback); 

Ve işte buyurun. 3-boyutlu bir ortamda serbest dolaşıma izin veren basit bir kamera sistemi uyguladık.

Biraz deneme yapmaktan çekinmeyin ve takılırsanız kodunuzu kaynak koduyla karşılaştırın.

Kamera sınıfı

Önümüzdeki bölümlerde, sahneleri kolayca elde edebilmek ve sonuçları her açıdan görmek için sürekli olarak bir kamera kullanacağız. Bununla birlikte, kamera kodu her bölümde önemli miktarda yer kaplayabileceğinden, ayrıntılarını biraz soyutlayacağız ve işin çoğunu bizim için bazı küçük eklemelerle yapan kendi kamera nesnesini yaratacağız. Gölgelendirici bölümünün aksine, kamera sınıfını oluştururken size yol göstermeyeceğiz, ancak iç işleri bilmek istiyorsanız (tamamen yorumlanmış) kaynak kodunu size sağlayacağız.

Shadernesnesinde olduğu gibi, kamera sınıfını da tamamen tek bir başlık dosyasında tanımlıyoruz. Kamera sınıfını burada bulabilirsiniz; bu bölümden sonra kodu anlayabilmeniz gerekir. Kendi kamera sisteminizi nasıl oluşturabileceğinize örnek olarak en azından sınıfı bir kez kontrol etmeniz önerilir.

Tanıttığımız kamera sistemi, çoğu amaca uygun ve Euler açılarıyla iyi çalışan bir kameradır, ancak FPS kamera veya uçuş simülasyon kamerası gibi farklı kamera sistemleri oluştururken dikkatli olun. Her kamera sisteminin kendi hileleri ve tuhaflıkları vardır, bu yüzden onları bildiğinizden emin olun. Örneğin, bu sinek kamera 90dereceden daha yüksek veya buna eşit eğim değerlerine izin verilmez ve yuvarlanan değerleri hesaba kattığımızda (0,1,0) statik bir yukarı yön vektörü çalışmaz.

Yeni kamera nesnesini kullanan kaynak kodun güncellenmiş sürümünü burada bulabilirsiniz.

Alıştırmalar

  • Kamera sınıfını, uçamayacağınız gerçek bir fps kamera olacak şekilde dönüştürüp dönüştüremeyeceğinize bakın; sadece xzüzleminde kalırken etrafınıza bakabilirsiniz: çözüm.

  • Bu bölümün başında tartışıldığı gibi el ile bir görünüm matrisi elde ettiğiniz kendi LookAt işlevinizi oluşturmaya çalışın. GLM' in LookAt işlevini kendi uyarlamanızla değiştirin ve aynı olup olmadığını görün: çözüm.

Orijinal Kaynak: Camera

Çeviri: Nezihe Sözen

Last updated