用键盘鼠标控制摄像机移动
主要点有欧拉角控制俯仰,偏航角,键盘控制摄像机距离物体的远近。核心是控制viewMatrix的数值,也就是控制摄像机的位置,观察的点和头部方向
#include
#include
#include "glad/glad.h"
#include "GLFW/glfw3.h"
#include "utils/Shader.h"
//图片工具库
#include "utils/stb_image.h"
//矩阵工具库
#include "utils/glm/glm.hpp"
#include "utils/glm/gtc/matrix_transform.hpp"
#include "utils/glm/gtc/type_ptr.hpp"
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
void processInput(GLFWwindow *window);
void loadTexture(unsigned int *texture, const char *path);
void mouse_callback(GLFWwindow *window, double xpos, double ypos);
void scroll_callback(GLFWwindow *window, double xoffset, double yoffset);
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);//头顶的方向
float deltaTime = 0.0f; //当前帧与上一帧的时间差
float lastFrameTime = 0.0f;//上一帧的时间
float lastX = 300;
float lastY = 300;
float yaw;
float pitch;
float fov = 45.0f;//视野比例
/**
* 顶点数组对象:Vertex Array Object,VAO
* 顶点缓冲对象:Vertex Buffer Object,VBO
* 索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO
* @return
*/
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_OPENGL_CORE_PROFILE);
//第三个参数是标题
GLFWwindow *window = glfwCreateWindow(600, 600, "LearnOpengl", NULL, NULL);
if (window == NULL) {
std::cout << "Fail to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
//奇特的写法,本应是个函数,写出来却像个变量,连参数都不需要传了
if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
std::cout << "File to initialize GLAD" << std::endl;
return -1;
}
//使窗口隐藏光标并且捕捉它
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
//使用回调
glfwSetCursorPosCallback(window, mouse_callback);
unsigned int texture1, texture2;
const char *path1 = "D:\\cl_workspace\\TestOpengl\\resource\\girl.jpg";
const char *path2 = "D:\\cl_workspace\\TestOpengl\\resource\\awesomeface.jpg";
//两个都要传递引用,这样函数更改的值才是上面的值
loadTexture(&texture1, path1);
loadTexture(&texture2, path2);
glViewport(0, 0, 600, 600);//指定视口大小,跟java一样
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetScrollCallback(window, scroll_callback);
// Shader shader("D:\\cl_workspace\\TestOpengl\\shader\\vertex_shader_two.glsl",
// "D:\\cl_workspace\\TestOpengl\\shader\\fragment_shader_two.glsl");
Shader shader("D:\\cl_workspace\\TestOpengl\\shader\\vertex_shader_three.glsl",
"D:\\cl_workspace\\TestOpengl\\shader\\fragment_shader_three.glsl");
/*//矩形
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
};
unsigned int indices[] = { // 注意索引从0开始!
0, 1, 3, // 第一个三角形
1, 2, 3 // 第二个三角形
};
unsigned int VBO;//顶点缓冲对象
glGenBuffers(1, &VBO);//&符号应该是传引用对象的意思,不然会传值进去
//设置索引数据
unsigned int EBO;//索引缓冲对象
glGenBuffers(1, &EBO);
//创建一个VAO,看起来像是对上面的固定过程的封装,毕竟链接着色器程序,指定数据,都是一些固定操作
//且VAO是必须的
unsigned int VAO;
glGenVertexArrays(1, &VAO);
//初始化代码,一般只运行一次
//1.绑定VAP
glBindVertexArray(VAO);
//2.把顶点数组复制到缓冲中供OpenGl使用,GL_ARRAY_BUFFER这个是个特殊的东西
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//2.5 把索引数组复制到一个索引缓冲中,供opengl使用
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0);
glEnableVertexAttribArray(0);
//设置颜色属性指针
// glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) (3 * sizeof(float)));
// glEnableVertexAttribArray(1);
//设置纹理属性指针,第一个参数2,其实是Location,第三个参数是offset
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) (3 * sizeof(float)));
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
//平移的例子
// glm::vec4 vec(1.0f,0.0f,0.0f,1.0f);
// glm::mat4 trans;
// trans = glm::translate(trans,glm::vec3(1.0f,1.0f,0.0f));
// vec = trans * vec;
//生成一个旋转并且缩放的矩阵
// glm::mat4 trans;
// //radians是把90度转化为弧度
// trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));//沿某个向量转90度
// trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
//设置矩阵
/*glm::mat4 model;
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
glm::mat4 view;
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
glm::mat4 projection;//创建投影矩阵,第二个值应该为宽高比
projection = glm::perspective(glm::radians(45.0f), 1.0f, 0.1f, 100.0f);
shader.use();//必须先激活
//再次确认一下,设置值,必须在链接编译程序以后才能进行
// unsigned int transformLoc = glGetUniformLocation(shader.ID, "transform");
// glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
//分别设置model,view,projection四个矩阵的值
int modelLoc = glGetUniformLocation(shader.ID, "model");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
int viewLoc = glGetUniformLocation(shader.ID, "view");
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
int projectionLoc = glGetUniformLocation(shader.ID, "projection");
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));*/
//弄多个立方体
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)
};
glUniform1i(glGetUniformLocation(shader.ID, "texture1"), 0);
shader.setInt("texture1", 0);//我记得这两个是等价的嘛,0对应GL_TEXTURE0
shader.setInt("texture2", 1);
//循环绘制
while (!glfwWindowShouldClose(window)) {//判断界面是否关闭,没关闭就循环绘制
processInput(window);
//记录deltaTime
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrameTime;
lastFrameTime = currentFrame;
//同java
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//如果开启了深度测试,这里就要把GL_DEPTH_BUFFER_BIT也清空
//让纹理转起来
//生成一个不断旋转并且平移的矩阵
// glm::mat4 trans;
// float val = float(glfwGetTime());
// trans = glm::translate(trans, glm::vec3(val / (20.0f), val / (20.0f), 0.0));
// trans = glm::rotate(trans, val, glm::vec3(0.0, 0.0, 1.0));//沿某个向量转90度
// //将值设置下去
// unsigned int transformLoc = glGetUniformLocation(shader.ID, "transform");
// glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));
//开启深度测试
glEnable(GL_DEPTH_TEST);
float radius = 5.0f;
float camX = sin(glfwGetTime()) * radius;
float camZ = cos(glfwGetTime()) * radius;
glm::mat4 view;
// view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
//所以说,这个lookat也就是创建一个视角矩阵,只是它更加形象
//第一个参数为摄像机位置,第二个为看的点的位置,目前设置的原点,第三个为头顶方向
//这样我们就是在旋转摄像机的位置,以自身旋转的方式来
// 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));
//相机始终看着自己面前一个单位距离的位置
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
glm::mat4 projection;//创建投影矩阵,第二个值应该为宽高比
projection = glm::perspective(glm::radians(fov), 1.0f, 0.1f, 100.0f);
shader.use();
int modelLoc = glGetUniformLocation(shader.ID, "model");
// glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
int viewLoc = glGetUniformLocation(shader.ID, "view");
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
int projectionLoc = glGetUniformLocation(shader.ID, "projection");
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glBindVertexArray(VAO);
// glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
for (int i = 0; i < 10; i++) {//平移到不同的位置 ,绘制多个立方体
//让立方体不断旋转
//这里我让十个立方体都先自转再平移,就需要用两个矩阵,一个自转,一个平移
//然后用平移的后乘旋转的矩阵,印证了之前书里的一个说法,不能用同一个矩阵先自转,再平移达到这个效果
//因为前后影响,生成的结果必然是先平移,再沿某个向量旋转
glm::mat4 model1;
model1 = glm::rotate(model1, (float) glfwGetTime(), glm::vec3(0.5f, 1.0f, 0.0f));
glm::mat4 model;
model = glm::translate(model, cubePositions[i]);
model = model * model1;
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glDrawArrays(GL_TRIANGLES, 0, 36);
}
glBindVertexArray(0);
//双缓冲机制,前缓冲保存着最终输出的图像,后缓冲则进行绘制,绘制完成以后与前缓冲交换,就会立即显示
//单缓冲会存在闪烁问题
glfwSwapBuffers(window);//交换颜色缓冲
glfwPollEvents();//检查有没有什么触发事件,鼠标键盘等,并调用相关的回调
}
//回收数据
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glfwTerminate();//结束绘制
return 0;
}
//窗口尺寸改变的回调
void framebuffer_size_callback(GLFWwindow *window, int width, int height) {
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow *window) {
//帧间隔长,就让它移动的多一些,这样能间接保证速度
float cameraSpeed = 2.5f * deltaTime;
//按下esc键的意思
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);//关闭窗户
} else if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
cameraPos += cameraSpeed * cameraFront;
} else if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
cameraPos -= cameraSpeed * cameraFront;
} else if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
//glm::normalize(glm::cross(cameraFront, cameraUp))这个求的是标准化的右向量
cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
} else if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}
}
//*代表引用传递,传参数时要用&符号,而取*里的值则需要*p
void loadTexture(unsigned int *texture, const char *path) {
glGenTextures(1, texture);
// 对*p赋值,从而改变p所指的地址上说保存的值
//*textrue就能表示这个内存地址上表示的值
glBindTexture(GL_TEXTURE_2D, *texture);//GL_TEXTURE_2D同样,它也是一个目标
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);//设置环绕和过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//加载纹理,第三个参数是颜色通道的个数
int width, height, nrChannels;
stbi_set_flip_vertically_on_load(true);
//图片
unsigned char *data = stbi_load(path, &width,
&height, &nrChannels, 0);
//第二个参数为多级渐远纹理的级别,0为基本级别,第三个参数为指定纹理存储为何种格式
//第六个总是设置为0,第七第八定义源图的格式和数据类型
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
//释放图像内存
stbi_image_free(data);
}
bool firstMouse = true;
void mouse_callback(GLFWwindow *window, double xpos, double ypos) {
//
std::cout << "xpos:" << xpos << " ypos:" << ypos << std::endl;
if (firstMouse) // 这个bool变量初始时是设定为true的
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
// return;
}
float xoffset = xpos - lastX;
float yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
float sensitivity = 0.05f;//灵敏度
xoffset *= sensitivity;
yoffset *= sensitivity;
yaw += xoffset;
pitch += yoffset;
pitch = pitch > 89.0f ? 89.0f : pitch;
pitch = pitch < -89.0f ? -89.0f : pitch;
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))-1;
cameraFront = glm::normalize(front);
}
//滚轮的回调
void scroll_callback(GLFWwindow *window, double xoffset, double yoffset) {
if (fov >= 1.0f && fov <= 45.0f) {
fov -= yoffset;
}
fov = fov <= 1.0f ? 1.0f : fov;
fov = fov >= 45.0f ? 45.0f : fov;
}