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

  • 相关阅读:
    Unable To Open Database After ASM Upgrade From Release 11.1 To Release 11.2
    11g Understanding Automatic Diagnostic Repository.
    How to perform Rolling UpgradeDowngrade in 11g ASM
    Oracle 11.2.0.2 Patch 说明
    Pattern Matching Metacharacters For asm_diskstring
    Steps To MigrateMove a Database From NonASM to ASM And ViceVersa
    Upgrading ASM instance from Oracle 10.1 to Oracle 10.2. (Single Instance)
    OCSSD.BIN Process is Running in a NonRAC Environment
    Steps To MigrateMove a Database From NonASM to ASM And ViceVersa
    On RAC, expdp Removes the Service Name [ID 1269319.1]
  • 原文地址:https://www.cnblogs.com/youxia/p/cg006.html
Copyright © 2011-2022 走看看