终于到了OpenGl入门教学的最后一章Camera,说真的,这个Camera是真的挺难的。
话不多说,上代码吧!
首先是先创建一个Camera类
Camera.h
#pragma once
#include
#include
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
#define GLEW_STATIC
#include
#include
#include "Shader.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include
#include
#include
#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函数
}