接上一章:OpenGL基础14:摄像机
现在来尝试通过输入控制摄像机
一、键盘输入
其实从第一章开始,代码里面一直都有实现键盘输入:
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}
实现的是:按下Esc键,关闭窗口
这样的话,如果我们想通过WASD来控制摄像机往对应方向移动,就只需要多加几行判定:
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
GLfloat cameraSpeed = 0.05f;
if (key == GLFW_KEY_W)
cameraPos += cameraSpeed * cameraFront;
if (key == GLFW_KEY_S)
cameraPos -= cameraSpeed * cameraFront;
if (key == GLFW_KEY_A)
cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
if (key == GLFW_KEY_D)
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}
还记得上一章的LookAt方法嘛,里面传入的是摄像机位置、目标位置和世界空间的上向量,其中摄像机位置对应的正是cameraPos,而世界空间的上向量对应的是cameraUp,我们设定cameraFront为摄像机的方向向量,那么目标位置就可以是cameraPos + cameraFront
初始化全局变量:
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);
LookAt方法传参:
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
经过测试你会发现两个问题:
- 不能同时按下两个键(例如W和A)来控制摄像机
- 刚开始长按某个方向的按键的时候明显会感觉到有一段0.5秒左右的停顿
原因是keyCallback每次只能同时响应一个key,因此正确的方法是在 while (!glfwWindowShouldClose(window)) 循环内判断哪些按键是按下状态,并进行值的更新,这样的话,keyCallback回调里面我们只需要记录和改变按键的状态就可以了,改动如下:
void cameraMove()
{
GLfloat cameraSpeed = 0.05f;
if (keys[GLFW_KEY_W])
cameraPos += cameraSpeed * cameraFront;
if (keys[GLFW_KEY_S])
cameraPos -= cameraSpeed * cameraFront;
if (keys[GLFW_KEY_A])
cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
if (keys[GLFW_KEY_D])
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (action == GLFW_PRESS) //如果当前是按下操作
keys[key] = true;
else if (action == GLFW_RELEASE) //松开键盘
keys[key] = false;
}
while (!glfwWindowShouldClose(window))
{
//……
cameraMove();
//……
}
测试一下,非常舒服!
二、deltaTime
变量
一般电脑游戏流畅运行,大概是1秒60帧左右,也就是FPS = 60,如果配置强悍,FPS能到达144以上,每台电脑每秒绘制的次数不同就会导致在同样的时间内,上面的cameraMove()方法调用的次数不同
当我们发布我们的游戏/应用时,我们需要确保无论哪台设备,输入控制的移动速度都一样,可是上面的代码是做不到这一点的,这个时候我们就需要deltaTime这个变量,如果之前学过Unity3D等其它图形应用的话,应该都会对这个变量非常清楚,它储存着渲染上一帧所用的时间
这样的话,我们只需要每一帧进行cameraMove()计算的时候,将deltaTime
的值乘以速度参数,这样就可以做到速度与刷新率的平衡,无论你的机器快还是慢,摄像机的速度都会保持一致,每个用户的体验就都一样了
openGL的话,自己算吧:
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
void cameraMove()
{
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
GLfloat cameraSpeed = 1.0f * deltaTime;
if (keys[GLFW_KEY_W])
cameraPos += cameraSpeed * cameraFront;
if (keys[GLFW_KEY_S])
cameraPos -= cameraSpeed * cameraFront;
if (keys[GLFW_KEY_A])
cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
if (keys[GLFW_KEY_D])
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}
三、滚轮缩放
有了键盘输入后,我们来看看鼠标的输入
一般游戏都有滑动滚轮控制视角缩放的功能,这里也尝试一下:
和键盘输入一样:注册回调函数并实现
glfwSetScrollCallback(window, scroll_callback);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
if (aspect >= 1.0f && aspect <= 45.0f)
aspect -= yoffset;
if (aspect <= 1.0f)
aspect = 1.0f;
if (aspect >= 45.0f)
aspect = 45.0f;
}
修改下投影矩阵,搞定!
projection = glm::perspective(glm::radians(aspect), (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.0f);
四、其它鼠标输入
上面实现了摄像机的移动和缩放,当然还有一个非常重要的东西:摄像机视角控制
一般游戏的视角都是通过鼠标的移动来控制的,这相对于移动和缩放,要更难和复杂,所以这一章就先不讲了
现在暂时只了解如何获取鼠标的移动事件以及按键点击事件吧
鼠标点击事件:
glfwSetMouseButtonCallback(window, mouse_button_callback);
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
if (action == GLFW_PRESS)
{
switch (button)
{
case GLFW_MOUSE_BUTTON_LEFT:
printf("鼠标左键按下!!");
break;
case GLFW_MOUSE_BUTTON_MIDDLE:
printf("鼠标中间按下!!");
break;
case GLFW_MOUSE_BUTTON_RIGHT:
printf("鼠标右键按下!!");
break;
default:
return;
}
}
return;
}
鼠标移动事件:
glfwSetCursorPosCallback(window, mouse_callback);
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
printf("鼠标移动差:x = %.1f, y = %.1f", xoffset, yoffset);
}
别忘了窗口y轴坐标是从下往上的!所以从代码中可以看到:在计算y轴的移动差的时候,是反过来的
隐藏光标:
- glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED):该函数为输入模式设置,第二个参数为要设置的模式,第三个参数为对应的值,这里设置的功能是隐藏光标,并捕捉(Capture)它。捕捉鼠标意味着当应用集中焦点到鼠标上的时候光标就应该留在窗口中,除非应用拾取焦点或退出
完整代码:
#include<iostream>
#include<opengl/glew.h>
#define GLEW_STATIC
#include<GLFW/glfw3.h>
#include<glm/glm.hpp>
#include<glm/gtc/matrix_transform.hpp>
#include<glm/gtc/type_ptr.hpp>
#include"Shader.h"
#include<opengl/freeglut.h>
#include<SOIL.h>
bool keys[1024];
bool firstMouse = true;
GLfloat lastX, lastY;
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void cameraMove();
GLfloat aspect = 45.0f;
const GLuint WIDTH = 800, HEIGHT = 600;
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);
int main()
{
glfwInit();
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);
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
glfwSetMouseButtonCallback(window, mouse_button_callback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glewExperimental = GL_TRUE;
glewInit();
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
Shader shaderYellow("VShader.txt", "FShaderY.txt");
GLfloat 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
};
GLuint VBO, VAO, texture;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenTextures(1, &texture);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindTexture(GL_TEXTURE_2D, texture);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
int picWidth, picHeight;
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);
unsigned char* image = SOIL_load_image("Texture/wood.jpg", &picWidth, &picHeight, 0, SOIL_LOAD_RGB);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, picWidth, picHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glEnable(GL_DEPTH_TEST);
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_DEPTH_BUFFER_BIT);
cameraMove();
glBindTexture(GL_TEXTURE_2D, texture);
shaderYellow.Use();
float radius = 5.0f;
float camX = sin(glfwGetTime()) * radius;
float camZ = cos(glfwGetTime()) * radius;
glm::mat4 model = glm::mat4(1.0f);
glm::mat4 view = glm::mat4(1.0f);
glm::mat4 projection = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians(57.0f), glm::vec3(-0.5f, 1.0f, 0.0f));
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
projection = glm::perspective(glm::radians(aspect), (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.0f);
GLint modelLoc = glGetUniformLocation(shaderYellow.Program, "model");
GLint viewLoc = glGetUniformLocation(shaderYellow.Program, "view");
GLint projLoc = glGetUniformLocation(shaderYellow.Program, "projection");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glfwSwapBuffers(window);
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glfwTerminate();
return 0;
}
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
void cameraMove()
{
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
GLfloat cameraSpeed = 1.0f * deltaTime;
if (keys[GLFW_KEY_W])
cameraPos += cameraSpeed * cameraFront;
if (keys[GLFW_KEY_S])
cameraPos -= cameraSpeed * cameraFront;
if (keys[GLFW_KEY_A])
cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
if (keys[GLFW_KEY_D])
cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (action == GLFW_PRESS) //如果当前是按下操作
keys[key] = true;
else if (action == GLFW_RELEASE) //松开键盘
keys[key] = false;
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
if (aspect >= 1.0f && aspect <= 45.0f)
aspect -= yoffset;
if (aspect <= 1.0f)
aspect = 1.0f;
if (aspect >= 45.0f)
aspect = 45.0f;
}
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
if (action == GLFW_PRESS)
{
switch (button)
{
case GLFW_MOUSE_BUTTON_LEFT:
printf("鼠标左键按下!!");
break;
case GLFW_MOUSE_BUTTON_MIDDLE:
printf("鼠标中间按下!!");
break;
case GLFW_MOUSE_BUTTON_RIGHT:
printf("鼠标右键按下!!");
break;
default:
return;
}
}
return;
}
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
printf("鼠标移动差:x = %.1f, y = %.1f", xoffset, yoffset);
}