zoukankan      html  css  js  c++  java
  • Linux 下的 OpenGL 之路(六):使用 GLFW 在 OpenGL 的场景中漫游

    前言

    前面已经建立了 OpenGL 框架,加载了 3D 模型,但是还没有在场景中漫游的功能。为了展示 3D 模型,我只是简单地利用变换视图矩阵的方式使模型在视野中旋转。同时,之前的程序连最简单的改变窗口大小的功能都没有,不能放大窗口而观察模型的更多细节。从这一节开始,我要实现在场景中漫游的功能。

    功能的设计很简单,就像所有的 FPS 游戏一样,按A W S D进行前进后退和左右移动,使用鼠标控制方向,为了简单起见,暂时只考虑左右转动,不实现上下转动的功能。

    改变窗口大小

    改变窗口大小的功能很简单,添加一个 static 的 onWindowSize() 函数就可以了,然后调用 glfwSetWindowSizeCallback() 注册这个回调函数。添加这个功能后,我们就可以把窗口放大到全屏了,如下图:

    切换线框模式和填充模式

    前面一直使用的是线框模型,这里可以设置按M键来切换线框模式和填充模式。这里可以先编写一个 onKey() 方法,然后使用 glfwSetSetKeyCallback() 来设置回调。

    前后左右移动摄像机

    这时不能使用 glfwSetSetKeyCallback() 来设置回调,因为 onKey() 方法只在每次按键的时候调用一次,即使按着键不动,它也不会连续调用,不符合我们的要求。这时,需要在每一帧的绘图函数里面调用 processInput() 方法,并在 processInput() 方法里面调用 glfwGetKey() 来实现这个效果。

    另外,我们的视图矩阵要改了。我们可以在 App 类里面设置三个变量,cameraPosition、cameraFront、cameraUp,分别代表摄像机的位置、前方、上方,然后使用 GLM 的 lookAt() 函数来设置视图矩阵。

    根据 3D 场景的复杂程度不同,其渲染速度也会不同,为了保证我们移动速度的一致性,我这里顺便搞一个计算帧率的功能。

    左右转动视角

    使用 GLFW 的鼠标回调函数,可以很方便地得到鼠标的 X 坐标和 Y 坐标,因此实现左右转动视角的功能非常方便。我没有使用很复杂的三角函数计算,只是利用 GLM 的 rotate() 函数对 cameraFront 向量进行旋转就可以了。我们还可以充分利用 GLFW 捕获鼠标功能,设计为在窗口中点击鼠标后捕获鼠标指针,按ESC键后释放鼠标捕获,只有在捕获鼠标指针的状态下才能够左右旋转视角。这时主要用到的 API 是 glfwSetCursorPosCallback() 和 glfwSetMouseButtonCallback()。

    经过修改后的 app.hpp 完整代码如下:

    #ifndef __APP_HPP__
    #define __APP_HPP__
    
    #include <GL/glew.h>
    #include <GLFW/glfw3.h>
    #include <iostream>
    #include <glm/glm.hpp>
    #include <glm/gtc/matrix_transform.hpp>
    
    class App
    {
    private:
        const int SCR_WIDTH = 1920;
        const int SCR_HEIGHT = 1080;
    
    public:
        static App *the_app;
        float aspect;
        glm::vec3 cameraPosition;
        glm::vec3 cameraFront;
        glm::vec3 cameraUp;
        float cameraSpeed;
        double timeLastFrame;
        double timeThisFrame;
        double timeAccumulate;
        int countFrames;
        bool showFps;
        bool firstMouse;
        double lastX;
        bool captureCursor;
    
        App()
        {
            aspect = (float)SCR_WIDTH / (float)SCR_HEIGHT;
            cameraPosition = glm::vec3(0.0f, 0.0f, 0.0f);
            cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
            cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
            firstMouse = true;
        }
    
        static void onWindowSize(GLFWwindow *window, int width, int height)
        {
            glViewport(0, 0, width, height);
            the_app->aspect = (float)width / (float)height;
        }
    
        static void onKey(GLFWwindow *window, int key, int scancode, int action, int mods)
        {
            if (action == GLFW_PRESS)
            {
                switch (key)
                {
                case GLFW_KEY_M: //切换线框模式和填充模式
                {
                    static GLenum mode = GL_FILL;
                    mode = (mode == GL_FILL ? GL_LINE : GL_FILL);
                    glPolygonMode(GL_FRONT_AND_BACK, mode);
                    return;
                }
                case GLFW_KEY_ESCAPE: //停止鼠标捕获,主要是应付鼠标被捕获的情况
                {
                    if (the_app->captureCursor)
                    {
                        glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
                        the_app->captureCursor = false;
                    }
    
                    return;
                }
                case GLFW_KEY_F: //打开和关闭输出fps的功能,输出到控制台
                {
                    the_app->showFps = (the_app->showFps == false ? true : false);
                    return;
                }
                }
            }
        }
    
        virtual void processInput(GLFWwindow *window)
        {
            if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
            {
                cameraPosition += cameraSpeed * cameraFront;
            }
            if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
            {
                cameraPosition -= cameraSpeed * cameraFront;
            }
            if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
            {
                cameraPosition += cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp));
            }
            if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
            {
                cameraPosition -= cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp));
            }
        }
    
        static void onMouseMove(GLFWwindow *window, double xpos, double ypos)
        {
            //std::cout << "xpos:" << xpos << "   ypos:" << ypos << std::endl;
            if (!the_app->captureCursor)
            {
                return;
            }
            if (the_app->firstMouse)
            {
                the_app->lastX = xpos;
                the_app->firstMouse = false;
                return;
            }
            double xoffset = xpos - the_app->lastX;
            the_app->lastX = xpos;
    
            double sensitivity = 0.005f; //灵敏度
            xoffset *= sensitivity;
    
            glm::mat4 I(1.0f);
            glm::vec3 Y(0.0f, 1.0f, 0.0f);
    
            the_app->cameraFront = glm::vec3(glm::vec4(the_app->cameraFront, 1.0f) * glm::rotate(I, (float)xoffset, Y));
        }
    
        static void onMouseButton(GLFWwindow *window, int button, int action, int mods)
        {
            if (action == GLFW_PRESS)
            {
                switch (button)
                {
                case GLFW_MOUSE_BUTTON_LEFT:
                    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
                    the_app->captureCursor = true;
                    return;
                }
            }
        }
    
        virtual void init()
        {
        }
    
        virtual void display()
        {
        }
    
        virtual void run(App *app)
        {
            if (the_app != NULL)
            { //同一时刻,只能有一个App运行
                std::cerr << "The the_app is already run." << std::endl;
                return;
            }
            the_app = app;
    
            glfwInit();
            GLFWwindow *window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "StudyOpenGL", NULL, NULL);
            if (window == NULL)
            {
                std::cerr << "Failed to create GLFW window" << std::endl;
                glfwTerminate();
                return;
            }
            glfwMakeContextCurrent(window);
            glfwSetWindowSizeCallback(window, onWindowSize);
            glfwSetKeyCallback(window, onKey);
            glfwSetCursorPosCallback(window, onMouseMove);
            glfwSetMouseButtonCallback(window, onMouseButton);
            if (glewInit() != GLEW_OK)
            {
                std::cerr << "Failed to initalize GLEW" << std::endl;
                return;
            }
    
            init(); //Init主要是用来创建VAO、VBO等,并准备要各种数据
    
            while (!glfwWindowShouldClose(window))
            {
                display(); //这里才是渲染图形的主战场
    
                timeThisFrame = glfwGetTime();
    
                //记录帧渲染之后的时间,并计算帧率,如果输出帧率,则每一秒输出一次(主要是标准输出太慢),同时计算cameraSpeed;
                double timeInterval = timeThisFrame - timeLastFrame;
                timeLastFrame = timeThisFrame;
                if (showFps)
                {
                    if (timeAccumulate < 1.0)
                    {
                        countFrames++;
                        timeAccumulate += timeInterval;
                    }
                    else
                    {
                        std::cout << "FPS: " << countFrames << std::endl;
                        countFrames = 0;
                        timeAccumulate = 0;
                    }
                }
                cameraSpeed = 2.5f * (float)timeInterval;
    
                glfwSwapBuffers(window);
                processInput(window);
                glfwPollEvents();
            }
            glfwDestroyWindow(window);
    
            glfwTerminate();
            return;
        }
    };
    
    App *App::the_app = NULL;
    
    #define DECLARE_MAIN(a)                   
        int main(int argc, const char **argv) 
        {                                     
            a *app = new a;                   
            app->run(app);                    
            delete app;                       
            return 0;                         
        }
    
    #endif
    

    然后,我们的 WanderInScene.cpp 的完整内容如下:

    #include "../include/app.hpp"
    #include "../include/shader.hpp"
    #include "../include/model.hpp"
    #include <glm/glm.hpp>
    #include <glm/gtc/matrix_transform.hpp>
    
    class MyApp : public App {
        private:
            const GLfloat clearColor[4] = {0.2f, 0.3f, 0.3f, 1.0f};
            Model lita;
            Shader* simpleShader;
    
        public:
            void init(){
                
                ShaderInfo shaders[] = {
                    {GL_VERTEX_SHADER, "simpleShader.vert"},
                    {GL_FRAGMENT_SHADER, "simpleShader.frag"},
                    {GL_NONE, ""}
                };
                simpleShader = new Shader(shaders);
                lita.loadModel("lita.obj");
              
                glEnable(GL_DEPTH_TEST);
                glDepthFunc(GL_LEQUAL);
    
                glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
            }
    
            void display(){
                glClearBufferfv(GL_COLOR, 0, clearColor);
                glClear(GL_DEPTH_BUFFER_BIT);
    
                glm::mat4 I(1.0f);
                glm::vec3 X(1.0f, 0.0f, 0.0f);
                glm::vec3 Y(0.0f, 1.0f, 0.0f);
                glm::vec3 Z(0.0f, 0.0f, 1.0f);
    
                glm::mat4 view_matrix = glm::lookAt(cameraPosition, cameraPosition + cameraFront, cameraUp);
    
                glm::mat4 projection_matrix = glm::perspective(glm::radians(45.0f), aspect, 1.0f, 100.0f);
    
                glm::mat4 allis_model_matrix = glm::translate(I, glm::vec3(0.0f, 2.0f, 0.0f)) 
                                                * glm::scale(I, glm::vec3(0.8f, 0.8f, 0.8f)) * glm::rotate(I, glm::radians(0.0f), X);
                
                simpleShader->setModelMatrix(allis_model_matrix);
                simpleShader->setViewMatrix(view_matrix);
                simpleShader->setProjectionMatrix(projection_matrix);
                simpleShader->setCurrent();
                lita.render();
    
            }
    
            ~MyApp(){
                if(simpleShader != NULL){
                    delete simpleShader;
                }
            }
    
    };
    
    
    DECLARE_MAIN(MyApp)
    

    编译运行的命令如下:

    g++ -o WanderInScene WanderInScene.cpp -lGL -lglfw -lGLEW -lassimp
    ./WanderInScene
    

    就可以看到程序运行的效果了,我们可以很方便地从不同角度、不同距离观察 3D 模型,如下图:

    版权申明

    该随笔由京山游侠在2021年08月09日发布于博客园,引用请注明出处,转载或出版请联系博主。QQ邮箱:1841079@qq.com

  • 相关阅读:
    武功秘籍 蓝桥杯
    切面条 蓝桥杯
    啤酒和饮料 蓝桥杯
    蚂蚁感冒 蓝桥杯
    hdu N!
    hdu 神、上帝以及老天爷
    ListView滑动删除 ,仿腾讯QQ
    C++ 习题 输出日期时间--友元函数
    C++习题 商品销售
    渠道运营一点事
  • 原文地址:https://www.cnblogs.com/youxia/p/cg006.html
Copyright © 2011-2022 走看看