終於到了OpenGl入門教學的最後一章Camera,說真的,這個Camera是真的挺難的。
話不多說,上代碼吧!
首先是先創建一個Camera類
Camera.h
#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
class Camera
{
public:
Camera(glm::vec3 position,glm::vec3 target,glm::vec3 worldUp);//通過目標點來控制視角
Camera(glm::vec3 position, float pitch, float yaw, glm::vec3 worldUp);//通過歐拉角控制視角
glm::vec3 Position;//相機位置
glm::vec3 WorldUp;//世界座標的豎直方向
glm::vec3 ForWard;//模型前方
glm::vec3 Right;//模型右方
glm::vec3 Up;//模型上方
float Pitch;//俯仰角
float Yaw;//偏航角
float Sensitivity=0.01f;
void CameraViewMove(float offsetx,float offsety);//相機視野的移動
void CameraPosMoveZ(float offsetz);//相機位置Z軸上的移動
void CameraPosMoveX(float offsetx);//相機位置X軸上的移動
glm::mat4 GetViewMatrix();//獲取視角矩陣
private:
void UpdateViewPosition();//刷新相機位置
};
Camera.cpp
#include "Camera.h"
Camera::Camera(glm::vec3 position, glm::vec3 target, glm::vec3 worldUp)//通過glm::lookAt使用相機功能
{
Position = position;
WorldUp = worldUp;
ForWard = glm::normalize(target - position);//用目標點位置減去相機位置,再做一個歸一化
Right = glm::normalize(glm::cross(ForWard, WorldUp));//向量之間的叉乘得到的是垂直於該平面的向量,如x與z,得到的是y
Up = glm::normalize(glm::cross(Right,ForWard));
//GetViewMatrix();
}
Camera::Camera(glm::vec3 position, float pitch, float yaw, glm::vec3 worldUp) {
Position = position;
WorldUp = worldUp;
Pitch = pitch;
Yaw = yaw;
//下面是通過俯仰角和偏航角來計算前方位置,記住在這個上,官方教程有誤,按本教程寫,想知道原理的看傅老師的camera(2)
ForWard.x = sin(Yaw) * cos(Pitch);
ForWard.z = cos(Yaw)*cos(Pitch);
ForWard.y = sin(Pitch);
ForWard = glm::normalize(ForWard);
Right = glm::normalize(glm::cross(ForWard, WorldUp));
Up = glm::normalize(glm::cross(Right, ForWard));
}
void Camera::CameraViewMove(float offsetx, float offsety)//相機視角的移動,通過鼠標上一座標點與下一座標點差值做比較所得
{
Pitch -= offsety*Sensitivity;
Yaw -= offsetx* Sensitivity;
UpdateViewPosition();
}
void Camera::CameraPosMoveZ( float offsetz)//在模型z軸上進行移動
{
Position += ForWard * offsetz*0.1f;
}
void Camera::CameraPosMoveX(float offsetx)//在模型x軸上進行移動
{
Position += Right * offsetx * 0.1f;
}
glm::mat4 Camera::GetViewMatrix()//獲取視角矩陣
{
return glm::lookAt(Position, Position+ForWard,Up);
}
void Camera::UpdateViewPosition()//刷新視角位置
{
ForWard.x = sin(Yaw) * cos(Pitch);
ForWard.z = cos(Yaw) * cos(Pitch);
ForWard.y = sin(Pitch);
ForWard = glm::normalize(ForWard);
Right = glm::normalize(glm::cross(ForWard, WorldUp));
Up = glm::normalize(glm::cross(Right, ForWard));
}
然後是main.cpp,這次我會將所有的代碼都貼出來,相當於對入門階段的學習,做一個完結吧!
#include<iostream>
#define GLEW_STATIC
#include <GL/glew.h>
#include<GLFW/glfw3.h>
#include "Shader.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "Camera.h"
using namespace std;
void ProcessInput(GLFWwindow* window);
//float vertices[] = {
// // ---- 位置 ---- ---- 顏色 ---- - 紋理座標 -
// 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上
// 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下
// -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下
// -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上
//};
float vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
glm::vec3 cubePositions[] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3(2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f),
glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
GLuint indices[] = { 3,2,1,3,1,0 };
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
// Window dimensions
const GLuint WIDTH = 800, HEIGHT = 600;
Camera* myCamera = new Camera(glm::vec3(0, 0, 5.0f), glm::radians(-15.0f), glm::radians(180.0f), glm::vec3(0, 1.0f, 0));
// The MAIN function, from here we start the application and run the game loop
int main(int argc, char* argv[])
{
// Init GLFW
glfwInit();
// Set all the required options for GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
// Create a GLFWwindow object that we can use for GLFW's functions
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
glfwMakeContextCurrent(window);
//隱藏鼠標
glfwSetInputMode(window,GLFW_CURSOR,GLFW_CURSOR_DISABLED);//新增
//鼠標移動時呼叫mouse_callback函數
glfwSetCursorPosCallback(window, mouse_callback);//新增
// Set the required callback functions
//glfwSetKeyCallback(window, key_callback);
// Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
glewExperimental = GL_TRUE;
//// Initialize GLEW to setup the OpenGL Function pointers
glewInit();
// Define the viewport dimensions
glViewport(0, 0, WIDTH, HEIGHT);
GLuint VBO, VAO;//聲明頂點緩衝,聲明頂點數組用於管理頂點數據
glGenVertexArrays(1, &VAO);//創建頂點數組,返回一個獨一無二的整數,標識數組
glGenBuffers(1, &VBO);//創建頂點緩衝,返回一個獨一無二的整數,標識緩衝區
glBindVertexArray(VAO);//綁定頂點數組
glBindBuffer(GL_ARRAY_BUFFER, VBO);//綁定頂點緩衝
//指定頂點數組的數據源爲vertices,第四個參數代表顯卡如何管理給定的數據,GL_STATIC_DRWA代表幾乎不會改變
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//先存在vbo裏面,然後在給vao
// 指定頂點屬性的解析方式。即,如何從頂點緩衝獲取相應的頂點屬性和相應的顏色屬性。或者說,頂點着色器中如何知道去哪個頂點屬性分量重着色呢
//對每一個頂點而言,屬性有2種,一是位置屬性,而是顏色屬性,因此每六個浮點數決定了一個頂點的位置和顏色
//頂點着色器中使用layout(location = 0)定義了position頂點屬性的位置值(Location),因此第一個參數,代表屬性分量的索引
//參數二:頂點位置屬性的維度,參數三:屬性向量的數據類型,參數四:是否標準化;參數五,頂點位置屬性的總字節長度,參數六:在緩衝數組中的偏移量,即起始位置
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);//從vao裏面在0號索引位上拿取三個值
//glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 8* sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));//從vao裏面在0號索引位上拿取三個值
glVertexAttribPointer(5, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));//從vao裏面在0號索引位上拿取三個值
glEnableVertexAttribArray(3);//啓用屬性0,因爲默認是禁用的
//glEnableVertexAttribArray(4);//啓用屬性0,因爲默認是禁用的
glEnableVertexAttribArray(5);//啓用屬性0,因爲默認是禁用的
GLuint EBO;
glGenBuffers(1, &EBO);//創建一個緩衝區
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//綁定一個元素緩衝區
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//定義緩衝區中的數據,既對頂點數組的索引
//頂點數組對象(Vertex Array Object, VAO)的好處就是,當配置頂點屬性指針時,你只需要將上面的代碼調用執行一次,之後再繪製物體的時候只需要綁定相應的VAO就行了。如下文循環中的綁定再解綁
glBindVertexArray(0); // 解綁 VAO
unsigned int texbufferA;//貼圖緩衝區ID
glGenTextures(1, &texbufferA);//創建
glActiveTexture(GL_TEXTURE0);//申請textbuffer中的緩衝號,這裏申請的是一號
glBindTexture(GL_TEXTURE_2D, texbufferA);//綁定
int width, height, nrChannels;
unsigned char* data = stbi_load("container.jpg",&width,&height,&nrChannels,0);//載入圖像,寬度、高度和顏色通道的個數
if (data)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);//生成貼圖
glGenerateMipmap(GL_TEXTURE_2D);//生成多級紋理
}
else
{
printf("stbi load fail!");
}
stbi_image_free(data);//釋放空間
unsigned int texbufferB;//貼圖緩衝區ID
glGenTextures(1, &texbufferB);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, texbufferB);//綁定
stbi_set_flip_vertically_on_load(true);
unsigned char* data2 = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);//載入圖像,寬度、高度和顏色通道的個數
if (data2)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data2);//生成貼圖
glGenerateMipmap(GL_TEXTURE_2D);//生成多級紋理
}
else
{
printf("stbi load fail!");
}
stbi_image_free(data2);//釋放空間
glm::mat4 viewMat;
glm::mat4 projMat;
projMat = glm::perspective(glm::radians(45.0f), (float)WIDTH / (float)HEIGHT, 0.01f, 100.0f);
glEnable(GL_DEPTH_TEST);
Shader* myShader = new Shader("vertexSource.txt", "fragmentSource.txt");
// Game loop
while (!glfwWindowShouldClose(window))
{ // 檢查事件,調用相應的回調函數,如下文的glfwInput函數
ProcessInput(window);
//trans = glm::translate(trans, glm::vec3(0.01f, 0, 0));
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//渲染顏色到後臺緩衝
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除前臺緩衝
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texbufferA);//綁定
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, texbufferB);//綁定
glBindVertexArray(VAO);//每次循環都調用,綁定函數綁定VAO
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
viewMat = myCamera->GetViewMatrix();//刷新視角//修改
for (size_t i = 1; i <= 10; i++)
{
glm::mat4 modelMat;//模型矩陣
modelMat = glm::translate(modelMat, cubePositions[i]);
if(i==1||i%3==0)
modelMat = glm::rotate(modelMat, (float)glfwGetTime(), glm::vec3(0, 1.0f, 0));
else
modelMat = glm::rotate(modelMat, glm::radians(i*10.0f), glm::vec3(0, 1.0f, 0));
myShader->Use();
glUniform1i(glGetUniformLocation(myShader->ID, "ourTexture"), 0);
glUniform1i(glGetUniformLocation(myShader->ID, "ourFace"), 2);
//glUniformMatrix4fv(glGetUniformLocation(myShader->ID,"transform"),1,GL_FALSE,glm::value_ptr(trans));
glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));
glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));
glDrawArrays(GL_TRIANGLES, 0, 36);
}
//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//繪製三角形,根據索引數組繪製6個頂點,索引數組類型爲GL_UNSIGNED_INT,偏移值爲0
//glDrawArrays(GL_TRIANGLES, 0, 3);//開始繪製三角形從0起始,畫三組數值
//glBindVertexArray(0);//解綁
// Swap the screen buffers
glfwSwapBuffers(window);
glfwPollEvents();
}
// Properly de-allocate all resources once they've outlived their purpose
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
// Terminate GLFW, clearing any resources allocated by GLFW.
glfwTerminate();
return 0;
}
// Is called whenever a key is pressed/released via GLFW
void ProcessInput(GLFWwindow* window)//通過監控鼠標來控制相機的移動//新增
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
{
myCamera->CameraPosMoveZ(1);
}
else if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
{
myCamera->CameraPosMoveZ( -1);
}
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
{
myCamera->CameraPosMoveX(1);
}
else if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
{
myCamera->CameraPosMoveX(-1);
}
}
bool firstMouse = true;
float lastx = 300, lasty = 400;
void mouse_callback(GLFWwindow* window, double xpos, double ypos)//檢查鼠標輸入//新增
{
if (firstMouse==true)//第一次調用該函數的時候直接將當前座標給過去座標值,已此來防止一開始的時候出現視角的巨大晃動
{
lastx = xpos;
lasty = ypos;
firstMouse = false;
}
float offsetx = xpos - lastx;
float offsety = ypos - lasty;
//std::cout << offsetx << std::endl;
lastx = xpos;
lasty = ypos;
myCamera->CameraViewMove(offsetx,offsety);//調用CameraViewMove函數
}
最後的效果是一個類似於FPS第一人稱視角的相機控制。不會發視頻,還有就是難得去找,截幾張圖吧!