1. 程式人生 > ><五>初探opengl,編寫我們的鏡頭

<五>初探opengl,編寫我們的鏡頭

back elements 過程 swa 透視投影 height 級別 決定 poi

  現在我們嘗試編寫一個鏡頭類,有了這個類,我們能上下左右前後移動,感覺在玩fps遊戲,很不錯,下面開始看看怎麽寫。

 

  初次接觸鏡頭類是我在魔獸地圖編輯中,當時創建一個鏡頭的步驟就是放到某個位置,調節角度,分別有3個角度可以調節,一個是類似高度一樣的東西,一個是環繞著某個點的旋轉角度,還有就是鏡頭的旋轉。opengl鏡頭其實跟這個是差不多的。

  1.首先我們需要定一個攝像機位置 ,也就是把攝像機放到什麽位置去

  2.然後我們要定一個目標位置,這個決定我們攝像機觀察的方向

  3.然後就是一個上向量,這個一般為(0,1,0)

  從這幾個變量,我們可以獲得攝像機的右軸,我們用攝像機方向叉乘上向量,就能得到一條垂直於攝像機方向與上向量構成的一個平面的法向量。有了這條軸,我們可以左右平移攝像機。還能得到計算機的上軸 ,

就方向和右軸叉乘下就可以得到了,用來上下移動。

  接下來我們可以使用函數lookAt來建構這個觀察模型

glm::mat4 view;
view = glm::lookAt(vec3Pos, vec3Target, vec3Up); //提供攝像機位置,攝像機目標,上向量,他會根據上面的方法得出其他東西,構建矩陣

這樣作為視圖轉換中的觀察矩陣穿進去就好。貌似忘了記錄這個:

技術分享圖片

轉換過程如上,模型變換->視圖變換->投射變換->裁剪。

首先我們寫一段代碼,創建一個立方體

float vertices[] = {
        0.5f, 0.5f, 0.5f
, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上 0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下 -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下 -0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, // 左上 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下 -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上 }; unsigned int indices[] = { 3, 2, 1, 0, 1, 3, 7, 6, 5, 4, 5, 7, 0, 1, 4, 1, 4, 5, 0, 3, 4, 3, 4, 7, 2, 3, 6, 3, 6, 7, 1, 2, 6, 1, 5, 6, }; glGenVertexArrays(1, &vao); glBindVertexArray(vao); glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glGenBuffers(1, &ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); //鏈接頂點屬性 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); //顏色鏈接 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); glEnable(GL_DEPTH_TEST);

我們定義了立方體的8個頂點和具體面的索引,其他按照之前做法來就可以了,最後是開啟深度測試,不然面都畫到前面來,沒有了層次就不像立方體了。

繪圖代碼:

glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

現在應該立方體畫出來了,技術分享圖片這樣還是看不出他是立方體,我們需要轉換一下觀察的位置。

     glm::mat4 model = glm::mat4(1.0f);
        model = glm::rotate(model, glm::radians(0.0f), glm::vec3(0.5f, 1.0f, 0.0f)); //模型矩陣,可以實現模型基本的操作,例如縮放,位移等
        model = glm::translate(model, cubePositions[i]);

        glm::mat4 view = glm::mat4(1.0f);
        view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f)); //觀察矩陣,用來調整觀察的視野

        glm::mat4 projection = glm::mat4(1.0f);  //投影矩陣,分為正射投影和透視投影。2d一般正射,3d一般透視投影,這裏使用了透視投影,營造3d效果
        projection = glm::perspective(glm::radians(fov), (float)800 / 600, 0.1f, 100.0f); //透視投影矩陣生成,fov為鏡頭的開口角度,
     //創建位置,目標,上向量
        glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); 
        glm::vec3 target = glm::vec3(0, 0, 0);
        glm::vec3 cameraDir = glm::normalize(cameraPos - target);

        glm::vec3 up = glm::vec3(0, 1, 0);
        glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDir));

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

        float radius = 10.0f;
        float camX = sin(glfwGetTime())*radius;
        float camZ = cos(glfwGetTime())*radius;
        //view = glm::lookAt(glm::vec3(camX, 5, camZ), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
        //view = glm::lookAt(cameraPos, target, up);
        view = glm::lookAt(pos, pos + front, up); //創建觀察矩陣,傳入了位置,目標,和上向量,這裏的目標恒定為攝像機前面的一點,由front控制
       //給著色器傳入幾個矩陣,供給他們計算
        unsigned int transformLoc = glGetUniformLocation(shader->ID, "model");
        glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(model));
        unsigned int transformLoc2 = glGetUniformLocation(shader->ID, "view");
        glUniformMatrix4fv(transformLoc2, 1, GL_FALSE, glm::value_ptr(view));
        unsigned int transformLoc3 = glGetUniformLocation(shader->ID, "projection");
        glUniformMatrix4fv(transformLoc3, 1, GL_FALSE, glm::value_ptr(projection));

     glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

因為懶惰,我們沒有寫這些進階過來的過程,具體可以看一下opengl教程 坐標系統

幾個矩陣分別控制的東西我們已經知道了,接下來就是頂點著色器的編寫。

#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;

out vec4 vertexColor;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view*model* vec4(aPos, 1.0);
    vertexColor = vec4(aColor, 1);
}

可以看到,我們從代碼裏已經傳遞進來的3個矩陣,通過一定的順序來相乘來進行轉換

技術分享圖片我們能看到一個類似的效果,立方體的觀察,隨著你的調整會從不同角度的觀察。

  此時,我們要加入點更加遊戲的元素,就是使用鍵盤鼠標來操作鏡頭移動。

  我們在主循環裏加入一個鍵盤操作處理函數的調用,然後編寫我們的方法

while (!glfwWindowShouldClose(window))
{
    handleInput(window);
    draw();
    glfwSwapBuffers(window);
    glfwPollEvents();
}
void Camera::handleInput(GLFWwindow* window)
{
    float speed = 0.005f;
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        pos += speed * front;
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        pos -= speed * front;
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        pos -= glm::normalize(glm::cross(front, up)) * speed;
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        pos += glm::normalize(glm::cross(front, up)) * speed;
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
}

pos是我們攝像機的位置,front是表示一個方向,讓我們鏡頭保持看向某一個方向,當我們前後移動,我們和front處理,當我們需要左右移動,我們求出右軸後左右變換即可。

然後我們需要修改我們lookAt參數,使其適配移動

view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp); //cameraPos+cameraFront讓鏡頭目標一致處理front方向上

現在,應該可以進行前後左右的移動了。此刻,我們還要加上鼠標的操作,就完美了。

  首先我們得了解一個叫歐拉角的東西,這個是可以表示3d空間中任何旋轉的3個值,分別是俯視角(pitch),偏航角(yaw)和滾轉角(roll)

技術分享圖片

用這三個角,我們就能控制我們的鏡頭各種移動了,我們先看看著3個角怎麽實現操作

技術分享圖片俯視角pitch是鏡頭方向和xz平面構成的夾角,假設我們距離原點為1,那y的高度就是sin pitch,x和z是cos pitch。

技術分享圖片偏航角yaw是方向偏離x軸的角度,可見z為sin yaw, x為是cos yaw

所以根據上面所看,當我們知道pitch和yaw角度後,即可計算出一個方向向量

direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 譯註:direction代表攝像機的前軸(Front),這個前軸是和本文第一幅圖片的第二個攝像機的方向向量是相反的
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));

這幾個角度,我們可以通過鼠標的操作來獲取。

首先我們做一些設置

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwSetCursorPosCallback(window, mouse_callback);

設置glfwSetInputMode,鼠標就會鎖定在窗口裏,鼠標也看不到了,這時候關閉窗口就很麻煩,我在鍵盤操作裏加個了關閉熱鍵。然後添加鼠標操作的回調

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if(firstMouse) //為上次鼠標點設置默認值
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX; //x方向的偏移
    float yoffset = lastY - ypos; //y方向偏移,因為是鼠標向下移動,鼠標位置越大
    lastX = xpos;
    lastY = ypos;

    float sensitivity = 0.05; //靈敏度
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    yaw   += xoffset;
    pitch += yoffset;

    if(pitch > 89.0f) //這裏俯視角不能高於89度,否則視角會發生逆轉
        pitch = 89.0f;
    if(pitch < -89.0f)
        pitch = -89.0f;

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

這裏就能計算出攝像機新的朝向角度,配合上wads的移動,我們就能實現看起來像3d遊戲裏的自由移動了。

那麽入門級別的教程筆記就寫到這裏了。

<五>初探opengl,編寫我們的鏡頭