OpenGL基础15:输入控制

 

接上一章: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);

 

经过测试你会发现两个问题:

  1. 不能同时按下两个键(例如W和A)来控制摄像机
  2. 刚开始长按某个方向的按键的时候明显会感觉到有一段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);
}

 

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: C语言OpenGL大作业是一个通过使用C语言结合OpenGL图形库来实现的作业项目。这个项目的目的是让学生们在实践中加深对C语言和计算机图形学的理解,提高编程能力和图形渲染技术。 具体而言,C语言是一种高级编程语言,而OpenGL是一种跨平台的图形库,用于渲染2D和3D图形。在这个大作业中,学生们需要利用C语言中的编程技巧来实现一些图形学的基本功能,如绘制简单的图形、处理用户交互输入、实现基本的光照效果等等。 在完成C语言OpenGL大作业时,学生们可能需要遵循一些基本步骤。首先,他们需要学习和理解C语言和OpenGL库的基本知识,包括语法、图形学概念、图形渲染流程等。接下来,他们需要设计并实现自己的图形方案,包括场景的搭建、图形对象的绘制和变换等。在完成代码之后,他们会编译和运行程序,并通过调试和测试来确保其正常运行和效果达到预期。 对于C语言OpenGL大作业的评估标准则通常基于项目的完成度、代码质量和效率等方面进行。学生的代码应具有良好的结构和可读性,遵循编程规范,并能够正确地展示所学知识和技术的应用。 总之,C语言OpenGL大作业是一个能够帮助学生们深入理解C语言和图形学的重要项目。通过该项目,学生们能够锻炼编程能力、加深对图形学概念的理解,并在实践中体会编写复杂程序的过程。 ### 回答2: C语言OpenGL大作业是一项学术项目,要求学生运用C语言编写OpenGL程序,展示对图形学知识的理解和应用。这个作业通常涉及到图形渲染,光照效果,纹理映射,矩阵变换等技术的运用。 在做这个大作业之前,学生需要系统学习C语言和OpenGL图形库的基础知识。他们需要掌握OpenGL的核心概念,如窗口创建、三维投影、相机控制等。此外,还需要理解OpenGL管线的工作原理,包括顶点着色器、片段着色器、几何着色器等的作用。 大作业的具体要求可能会因教师的要求或课程的不同而有所不同。学生通常会选择一个主题或场景进行展示,比如一个3D游戏的场景、一个建筑模型或一个动画场景。在实现这个主题或场景时,学生需要运用所学的技术来创建和渲染3D模型,添加光照效果和纹理,实现相机控制和用户交互等。 在完成大作业的过程中,学生将会面临一些挑战和困难。首先,他们需要深入理解图形学的原理和算法,并将其转化为C语言代码。其次,他们还需要熟悉OpenGL的各种函数和接口,能够正确地调用它们来实现所需的功能。此外,学生还需要有良好的代码设计和调试能力,以便有效地组织和管理程序的结构和逻辑。 总结起来,C语言OpenGL大作业是一项考验学生图形学和编程能力的重要任务。通过完成这个作业,学生将能够深入理解图形学的概念和原理,并能够熟练运用C语言和OpenGL来实现各种图形效果和交互功能。 ### 回答3: C语言OpenGL大作业是指在使用C语言编写OpenGL程序的一个大型项目。OpenGL是一个强大的图形库,可以实现各种各样的图形效果和交互性。在这个大作业中,学生通常需要编写一个具有一定复杂度的图形应用程序。 首先,我们需要理解OpenGL的基本原理和使用方法。学生需要掌握OpenGL的各种库函数和技术,如OpenGL窗口创建、图形绘制、光照效果、3D变换等。他们还需要理解图形渲染管线、着色器和顶点缓冲对象等关键概念。 对于大作业的具体主题和要求,可能会有很多选择。例如,学生可以选择实现一个简单的3D游戏,设计一个图形化界面的数据可视化工具,或者开发一个基于OpenGL的图像处理程序。 在开始实际编码之前,学生需要进行一些规划和设计工作。他们需要确定大作业的需求和功能,设计程序的整体结构和模块划分。这些步骤通常可以通过编写一个详细的设计文档和创建一个项目计划来完成。 然后,学生可以开始编写代码。使用C语言编写OpenGL程序需要一定的编程技巧和算法知识。学生需要合理地组织代码,优化性能,并解决可能遇到的各种问题和错误。 最后,学生需要进行测试和调试。他们应该确保程序能够正确运行,并满足预期的需求和功能。在测试过程中,学生可能需要使用一些调试工具和技术,如断点调试和性能分析。 总之,C语言OpenGL大作业是一个涉及图形编程和算法设计的具有挑战性的项目。通过完成这个大作业,学生可以提高他们的编程技能和对图形学的理解,同时也锻炼了他们的逻辑思维和问题解决能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值