当前位置: 首页 > news >正文

OpengGL教程(七)---摄像机

本章参考官方教程:摄像机

本系列历史文
OpengGL教程(一)—OpenGL环境的配置(GLFW3,GLAD)
OpengGL教程(二)—渲染一个简单的窗体
OpengGL教程(三)—使用VAO和VBO方式绘制三角形
OpengGL教程(四)—使用EBO方式绘制矩形
OpengGL教程(五)—纹理的应用
OpengGL教程(六)—坐标的变换和坐标系的变换

本章主要讲述了MVP矩阵中的V矩阵。
需要了解:格拉姆—施密特正交化

OpenGL本身没有摄像机(Camera)的概念,但我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机,产生一种我们在移动的感觉,而不是场景在移动。

官网中有上面这样一段话,不要去纠结有无摄像机,就默认为有,对于此章节我们一切矩阵操作的对象都是摄像机

在这里插入图片描述
摄像机看一个物体有哪些需要注意点地方呢?

1、摄像机的位置:摄像机不同的位置,拍摄的角度不同,则看到的画面就不同。
2、摄像机的注视点:摄像机位置不动,但是我可以控制对着哪拍阿,看的地方不动,画面肯定也不一样。
3、摄像机的姿态: 摄像机位置不动,摄像机上下反过来,最终拍出的照片不就颠倒了莫。

那么对于此章我们要注意的不就是上面这三点嘛,下面我们对于这三点分别分析。

摄像机的位置

在这里插入图片描述

摄像机的注视点

在这里插入图片描述

摄像机的姿态

在这里插入图片描述
图中绿色的箭头为摄像头的上向量(0,1,0),上向量的不同,摄像头的姿态是不是也是不同。如果给图中绿色箭头朝向改为向下(0,-1,0).那么拍摄的画面就倒过来咯。

上诉这三个要点,每个要点都可以通过矩阵的方式来表示,具体推导见官网教案,这里不做详细表述。下面是一些简单的推导。

下面举个例子:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

根据上诉例子在结合下面内容就好理解。

在这里插入图片描述

GLM已经提供了这些支持。我们要做的只是定义一个摄像机位置,一个目标位置和一个表示世界空间中的上向量的向量(我们计算右向量使用的那个上向量)。接着GLM就会创建一个LookAt矩阵,我们可以把它当作我们的观察矩阵:

glm::mat4 view;
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));

glm::LookAt函数需要一个位置、目标上向量。它会创建一个和在上一节使用的一样的观察矩阵。

演示demo

main.cpp

#include <iostream>
#include <string>
#include <vector>
#include "glew.h"
#include "glfw3.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"#include "log.h"
#include "GlslDealConfig.h"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
};std::vector<glm::vec3> cubePositions = {glm::vec3( 0.0f,  0.0f,   0.0f), glm::vec3( 0.0f,  1.2f,   0.0f),glm::vec3( 1.2f,  1.2f,   0.0f),glm::vec3( 0.0f,  -1.2f,  0.0f),glm::vec3( 1.2f,  -1.2f,  0.0f),glm::vec3( 1.2f,  0.0f,   0.0f), glm::vec3( -1.2f,  0.0f,   0.0f), glm::vec3( -1.2f,  1.2f,   0.0f), glm::vec3( -1.2f,  -1.2f,   0.0f), glm::vec3(-1.5f, -2.2f, -2.5f),  glm::vec3( 2.4f, -0.4f, -3.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)  
};// 主函数
int main() {GlslDealConfig glslConfig;// 初始化 GLFWif (!glfwInit()) {LOGE("Failed to initialize GLFW");return -1;}glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 创建窗口GLFWwindow* window = glfwCreateWindow(1000, 1000, "3D Demo", nullptr, nullptr);if (!window) {LOGE("Failed to create GLFW window");glfwTerminate();return -1;}glfwMakeContextCurrent(window);// 初始化 GLEWGLenum err = glewInit();if (err != GLEW_OK) {LOGE("Failed to initialize GLEW: %s", reinterpret_cast<const char*>(glewGetErrorString(err)));return -1;}glfwSwapInterval(1);// 加载着色器std::string vertexShaderCode   = glslConfig.ReadGlslFile("/home/ryan/zxp/Rendering/demo/glsl/Trans/VertexShader.glsl");std::string fragmentShaderCode = glslConfig.ReadGlslFile("/home/ryan/zxp/Rendering/demo/glsl/Trans/FragmentShader.glsl");GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);const GLchar* vShaderSource = vertexShaderCode.c_str();const GLchar* fShaderSource = fragmentShaderCode.c_str();glShaderSource(vertexShader, 1, &vShaderSource, nullptr);glShaderSource(fragmentShader, 1, &fShaderSource, nullptr);glCompileShader(vertexShader);glCompileShader(fragmentShader);glslConfig.CheckShaderCompileV(vertexShader);glslConfig.CheckShaderCompileF(fragmentShader);GLuint shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);glslConfig.CheckProgmaLinkStatus(shaderProgram);glDeleteShader(vertexShader);glDeleteShader(fragmentShader);// 配置 VAO, VBO, EBOGLuint VAO, VBO[2];glGenVertexArrays(1, &VAO);glGenBuffers(2, VBO);glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)0);glEnableVertexAttribArray(0);glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)(3*sizeof(GLfloat)));glEnableVertexAttribArray(1);// 加载纹理GLuint texture[2];texture[0] = glslConfig.loadTexture("/home/ryan/zxp/Rendering/demo/resource/brickwall.jpg",   GL_RGB);texture[1] = glslConfig.loadTexture("/home/ryan/zxp/Rendering/demo/resource/awesomeface.png", GL_RGBA);// 设置投影矩阵glm::mat4 projection = glm::mat4(1.0f);glUseProgram(shaderProgram);GLuint projLoc = glGetUniformLocation(shaderProgram, "projection");glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));// 激活纹理单元glActiveTexture(GL_TEXTURE3);GLuint textureLocation = glGetUniformLocation(shaderProgram, "textureClass");glUniform1i(textureLocation, 3);glActiveTexture(GL_TEXTURE6);GLuint textureLocation2 = glGetUniformLocation(shaderProgram, "textureClass2");glUniform1i(textureLocation2, 6);glm::mat4 Mmatrix    = glm::mat4(1.0f);glm::mat4 Vmatrix    = glm::mat4(1.0f);glm::mat4 Pmatrix    = glm::mat4(1.0f);Pmatrix = glm::perspective(glm::radians(45.0f), 1000.0f/1000.0f, 0.1f, 100.0f);GLuint TransM = glGetUniformLocation(shaderProgram, "Mtrans");GLuint TransV = glGetUniformLocation(shaderProgram, "Vtrans");GLuint TransP = glGetUniformLocation(shaderProgram, "Ptrans");glUniformMatrix4fv(TransM, 1, GL_FALSE, glm::value_ptr(Mmatrix));glUniformMatrix4fv(TransV, 1, GL_FALSE, glm::value_ptr(Vmatrix));glUniformMatrix4fv(TransP, 1, GL_FALSE, glm::value_ptr(Pmatrix));glEnable(GL_DEPTH_TEST);// 渲染循环while (!glfwWindowShouldClose(window)){glViewport(0, 0, 1000, 1000);glClearColor(0.0f, 0.0f, 0.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glUseProgram(shaderProgram);glBindVertexArray(VAO);glActiveTexture(GL_TEXTURE3);glBindTexture(GL_TEXTURE_2D, texture[0]);glActiveTexture(GL_TEXTURE6);glBindTexture(GL_TEXTURE_2D, texture[1]);glm::mat4 view = glm::mat4(1.0f); float radius = 10.0f;float camX = static_cast<float>(sin(glfwGetTime()) * radius);float camZ = static_cast<float>(cos(glfwGetTime()) * radius);//一个位置、目标和上向量view = glm::lookAt(glm::vec3(camX, 0.0f, camZ), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));glUniformMatrix4fv(TransV, 1, GL_FALSE, glm::value_ptr(view));for(unsigned int i = 0; i < cubePositions.size(); i++){glm::mat4 model = glm::mat4(1.0f);model = glm::translate(model, cubePositions[i]);glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "Mtrans"), 1, GL_FALSE, glm::value_ptr(model));glDrawArrays(GL_TRIANGLES, 0, 36);}glBindVertexArray(0);glBindTexture(GL_TEXTURE_2D, 0);glfwSwapBuffers(window);glfwPollEvents();}// 清理资源glDeleteVertexArrays(1, &VAO);glDeleteBuffers(2, VBO);glDeleteProgram(shaderProgram);glfwTerminate();return 0;
}

核心代码

        glm::mat4 view = glm::mat4(1.0f); float radius = 10.0f;float camX = static_cast<float>(sin(glfwGetTime()) * radius);float camZ = static_cast<float>(cos(glfwGetTime()) * radius);//一个位置、目标和上向量view = glm::lookAt(glm::vec3(camX, 0.0f, camZ), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));

相机位置 :glm::vec3(camX, 0.0f, camZ)
相机注视点 :glm::vec3(0.0f, 0.0f, 0.0f)
相机姿态 :glm::vec3(0.0f, 1.0f, 0.0f)

需要注意,是通过相机的位置和相机的注视点来确定相机看向的位置的。注视点向量 - 相机位置向量 = 相机看向的方向
ps:看向的方向,并不是看向某一个点,是方向!

运行效果
在这里插入图片描述
基于上面内容,我们修改以下代码实现键盘WASD控制摄像头移动的效果。

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);void processInput(GLFWwindow *window)
{float cameraSpeed = 0.05f; // adjust accordinglyif (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)cameraPos += cameraSpeed * cameraFront;if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)cameraPos -= cameraSpeed * cameraFront;if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}

processInput添加到循环渲染中去。

    // 渲染循环while (!glfwWindowShouldClose(window)){............processInput(window);............}

补充叉积数学定义

在这里插入图片描述

视角移动

为了能够改变视角,我们需要根据鼠标的输入改变cameraFront向量,这就引入了一个新的知识点欧拉角。欧拉角(Euler angles)是一种用来描述三维空间中物体方向(姿态)的方式,通过绕固定顺序的三个轴的旋转来表示一个方向或旋转。
即由俯仰角(上下看)、偏转角(左右看)、旋转角(打滚看)。

在这里插入图片描述

如何运用欧拉角实现改变视角:这部分建议看官网有一点点掌握即可

官网中给出的改变视角的方法就下面这三行代码

    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));

下面解释这段代码是怎么来的。这三行代码的实质就是将球面坐标系转换成笛卡尔坐标系。如下图的点A。设OA的距离为R,
则在球面坐标系中A点的坐标可以表示为 A(R, pitch, yaw)。pitch是俯仰角,yaw是偏转角。

在这里插入图片描述

我们将A(R, pitch, yaw)转换为笛卡尔坐标系下的坐标:

注意:我的坐标系是按照OpenGL建造的,即Z轴正半轴指向屏幕前的你。

y = R * sin(pitch)

x = OK = OB * cos(yaw) = R * cos(pitch) * cos(yaw)

z = KB = OB * sin(yaw) = R * cos(pitch) * sin(yaw)

视角移动完整代码

#include <iostream>
#include <string>
#include <vector>
#include "glew.h"
#include "glfw3.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"#include "log.h"
#include "GlslDealConfig.h"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
};std::vector<glm::vec3> cubePositions = {glm::vec3( 0.0f,  0.0f,   0.0f), glm::vec3( 0.0f,  1.2f,   0.0f),glm::vec3( 1.2f,  1.2f,   0.0f),glm::vec3( 0.0f,  -1.2f,  0.0f),glm::vec3( 1.2f,  -1.2f,  0.0f),glm::vec3( 1.2f,  0.0f,   0.0f), glm::vec3( -1.2f,  0.0f,   0.0f), glm::vec3( -1.2f,  1.2f,   0.0f), glm::vec3( -1.2f,  -1.2f,   0.0f), glm::vec3(-1.5f, -2.2f, -2.5f),  glm::vec3( 2.4f, -0.4f, -3.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)  
};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);bool  firstMouse = true;
float yaw   = -90.0f;
float pitch =  0.0f;
float lastX =  800.0f / 2.0;
float lastY =  600.0 / 2.0;
float fov   =  45.0f;void processInput(GLFWwindow *window)
{float cameraSpeed = 0.05f; // adjust accordinglyif (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)cameraPos += cameraSpeed * cameraFront;if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)cameraPos -= cameraSpeed * cameraFront;if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{float xpos = static_cast<float>(xposIn);float ypos = static_cast<float>(yposIn);if (firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}float xoffset = xpos - lastX;float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to toplastX = xpos;lastY = ypos;float sensitivity = 0.1f; // change this value to your likingxoffset *= sensitivity;yoffset *= sensitivity;yaw += xoffset;pitch += yoffset;printf("pitch : %f\n", pitch);printf("yaw   : %f\n", yaw);// make sure that when pitch is out of bounds, screen doesn't get flippedif (pitch > 89.0f)pitch = 89.0f;if (pitch < -89.0f)pitch = -89.0f;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));cameraFront = glm::normalize(front);
}// glfw: whenever the mouse scroll wheel scrolls, this callback is called
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{fov -= (float)yoffset;if (fov < 1.0f)fov = 1.0f;if (fov > 45.0f)fov = 45.0f;
}// 主函数
int main() {GlslDealConfig glslConfig;// 初始化 GLFWif (!glfwInit()) {LOGE("Failed to initialize GLFW");return -1;}glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// 创建窗口GLFWwindow* window = glfwCreateWindow(1000, 1000, "3D Demo", nullptr, nullptr);if (!window) {LOGE("Failed to create GLFW window");glfwTerminate();return -1;}glfwMakeContextCurrent(window);glfwSetCursorPosCallback(window, mouse_callback);glfwSetScrollCallback(window,    scroll_callback);glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);// 初始化 GLEWGLenum err = glewInit();if (err != GLEW_OK) {LOGE("Failed to initialize GLEW: %s", reinterpret_cast<const char*>(glewGetErrorString(err)));return -1;}glfwSwapInterval(1);// 加载着色器std::string vertexShaderCode   = glslConfig.ReadGlslFile("/home/ryan/zxp/Rendering/demo/glsl/Trans/VertexShader.glsl");std::string fragmentShaderCode = glslConfig.ReadGlslFile("/home/ryan/zxp/Rendering/demo/glsl/Trans/FragmentShader.glsl");GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);const GLchar* vShaderSource = vertexShaderCode.c_str();const GLchar* fShaderSource = fragmentShaderCode.c_str();glShaderSource(vertexShader, 1, &vShaderSource, nullptr);glShaderSource(fragmentShader, 1, &fShaderSource, nullptr);glCompileShader(vertexShader);glCompileShader(fragmentShader);glslConfig.CheckShaderCompileV(vertexShader);glslConfig.CheckShaderCompileF(fragmentShader);GLuint shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);glslConfig.CheckProgmaLinkStatus(shaderProgram);glDeleteShader(vertexShader);glDeleteShader(fragmentShader);// 配置 VAO, VBO, EBOGLuint VAO, VBO[2];glGenVertexArrays(1, &VAO);glGenBuffers(2, VBO);glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)0);glEnableVertexAttribArray(0);glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)(3*sizeof(GLfloat)));glEnableVertexAttribArray(1);// 加载纹理GLuint texture[2];texture[0] = glslConfig.loadTexture("/home/ryan/zxp/Rendering/demo/resource/brickwall.jpg",   GL_RGB);texture[1] = glslConfig.loadTexture("/home/ryan/zxp/Rendering/demo/resource/awesomeface.png", GL_RGBA);// 设置投影矩阵glm::mat4 projection = glm::mat4(1.0f);glUseProgram(shaderProgram);GLuint projLoc = glGetUniformLocation(shaderProgram, "projection");glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));// 激活纹理单元glActiveTexture(GL_TEXTURE3);GLuint textureLocation = glGetUniformLocation(shaderProgram, "textureClass");glUniform1i(textureLocation, 3);glActiveTexture(GL_TEXTURE6);GLuint textureLocation2 = glGetUniformLocation(shaderProgram, "textureClass2");glUniform1i(textureLocation2, 6);glm::mat4 Mmatrix    = glm::mat4(1.0f);glm::mat4 Vmatrix    = glm::mat4(1.0f);glm::mat4 Pmatrix    = glm::mat4(1.0f);Pmatrix = glm::perspective(glm::radians(45.0f), 1000.0f/1000.0f, 0.1f, 100.0f);GLuint TransM = glGetUniformLocation(shaderProgram, "Mtrans");GLuint TransV = glGetUniformLocation(shaderProgram, "Vtrans");GLuint TransP = glGetUniformLocation(shaderProgram, "Ptrans");glUniformMatrix4fv(TransM, 1, GL_FALSE, glm::value_ptr(Mmatrix));glUniformMatrix4fv(TransV, 1, GL_FALSE, glm::value_ptr(Vmatrix));glUniformMatrix4fv(TransP, 1, GL_FALSE, glm::value_ptr(Pmatrix));glEnable(GL_DEPTH_TEST);// 渲染循环while (!glfwWindowShouldClose(window)){glViewport(0, 0, 1000, 1000);glClearColor(0.0f, 0.0f, 0.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glUseProgram(shaderProgram);glBindVertexArray(VAO);glActiveTexture(GL_TEXTURE3);glBindTexture(GL_TEXTURE_2D, texture[0]);glActiveTexture(GL_TEXTURE6);glBindTexture(GL_TEXTURE_2D, texture[1]);processInput(window);Pmatrix = glm::perspective(glm::radians(fov), 1000.0f/1000.0f, 0.1f, 100.0f);glUniformMatrix4fv(TransP, 1, GL_FALSE, glm::value_ptr(Pmatrix));glm::mat4 view = glm::mat4(1.0f); //始终注释相机正前方view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);// view = glm::lookAt(cameraPos, glm::vec3(0.0f, 0.0f, 0.0f), cameraUp);glUniformMatrix4fv(TransV, 1, GL_FALSE, glm::value_ptr(view));for(unsigned int i = 0; i < cubePositions.size(); i++){glm::mat4 model = glm::mat4(1.0f);model = glm::translate(model, cubePositions[i]);glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "Mtrans"), 1, GL_FALSE, glm::value_ptr(model));glDrawArrays(GL_TRIANGLES, 0, 36);}glBindVertexArray(0);glBindTexture(GL_TEXTURE_2D, 0);glfwSwapBuffers(window);glfwPollEvents();}// 清理资源glDeleteVertexArrays(1, &VAO);glDeleteBuffers(2, VBO);glDeleteProgram(shaderProgram);glfwTerminate();return 0;
}

相关文章:

OpengGL教程(七)---摄像机

本章参考官方教程&#xff1a;摄像机 本系列历史文 OpengGL教程(一)—OpenGL环境的配置(GLFW3,GLAD) OpengGL教程(二)—渲染一个简单的窗体 OpengGL教程(三)—使用VAO和VBO方式绘制三角形 OpengGL教程(四)—使用EBO方式绘制矩形 OpengGL教程(五)—纹理的应用 OpengGL教程(六)—…...

springboot禁用静态资源

使用 application.properties 在 application.properties 中添加如下配置&#xff1a; spring.web.resources.add-mappingsfalse使用 application.yml 在 application.yml 中添加如下配置&#xff1a; spring:web:resources:add-mappings: false配置解释 spring.web.resou…...

猫咪如厕检测与分类识别系统系列【二】多图上传及猫咪分类特征提取更新

前情提要 家里养了三只猫咪&#xff0c;其中一只布偶猫经常出入厕所。但因为平时忙于学业&#xff0c;没法时刻关注牠的行为。我知道猫咪的如厕频率和时长与健康状况密切相关&#xff0c;频繁如厕可能是泌尿问题&#xff0c;停留过久也可能是便秘或不适。为了更科学地了解牠的…...

‌ViewModel和AndroidViewModel的主要区别

‌ViewModel和AndroidViewModel的主要区别在于它们的继承关系、构造函数以及使用场景。‌ ‌继承关系与构造函数‌&#xff1a; ‌ViewModel‌&#xff1a;ViewModel是一个抽象类&#xff0c;位于androidx.lifecycle包中。它是所有ViewModel类的基类&#xff0c;构造函数较为…...

myeclise导入项目并运行

1、把项目复制到myeclise工作目录 2、导入项目&#xff1a;将项目导入到myeclise工作目录 3、配置jre 4、把项目放到tomcat 5、运行项目 配置数据库后运行...

XSS 防御转义规则笔记

一、转义规则概述 核心目标&#xff1a;防止用户输入被浏览器解析为可执行代码&#xff0c;确保输入始终被视为数据而非代码。 关键策略&#xff1a;根据数据嵌入的上下文环境&#xff08;HTML、JavaScript、CSS 等&#xff09;&#xff0c;对特殊字符进行转义或编码。 二、不…...

【2025年认证杯数学中国数学建模网络挑战赛】C题 完整论文 全三问模型+求解+代码

目录 【2025年认证杯数学建模挑战赛】C题数据预处理与全三问求解一、问题重述二、模型假设与符号说明2.1 模型基本假设2.2 符号说明 三、数据预处理及分析四、问题一五、问题二5.1 基于互相关函数的反应延时识别5.2 反应过程延时结果分析5.3 基于BP神经网络的不合格产物预测5.4…...

iOS应用开发指南

开发一款iOS应用是一个系统化的过程&#xff0c;涵盖从环境搭建、界面设计、编码实现到测试发布的各个环节。以下是一份面向初学者的iOS移动应用开发指南&#xff0c;帮助你从零开始构建自己的App。 一、准备工作&#xff1a;开发环境与工具 必备设备 Mac电脑&#xff1a;iO…...

小刚说C语言刷题——第21讲 一维数组

在日常生活中&#xff0c;我们经常输入一组数据。例如输入一个班30名学生的语文成绩&#xff0c;或者输入一组商品的价格。这个时候&#xff0c;我们如何输入一组类型相同的数据呢&#xff1f;这里我们就要用到数组。 1.数组的概念 所谓数组就是一组相同类型数据的集合。数组中…...

苍穹外卖2

根据id查询员工 调用顺序&#xff1a;Controller—>Service—>ServiceImpl—>Mapper—>xml 1.controller层一个tab秒了&#xff08;ai生成&#xff09; 由于result泛型中希望返回一个employee对象&#xff0c;所以定义一个employee来接受Service的getbyid方法,在…...

C语言之双层for循环

一、第一小题引入 循环次数用 外层循环内层循环 这一题即是: 3412(次) 外循环一次 内循环一趟(可以形象理解为 时针和 分针) 运行结果如下: 一、第二小题引入 请阅读下列代码: 执行程序.程序执行的循环次数为多少? 答案:20次 这一题 外层循环 从5开始到i结束 中间的i是…...

第8课:多智能体系统评估与迭代

多智能体系统评估与迭代:从指标设计到持续优化的全流程指南 一、引言:当智能体协作出现“磨合期”:评估与迭代为何是必经之路? 在多智能体系统(MAS)的实际运行中,即使架构设计合理,也可能面临“协作效率下降”“资源浪费”等问题: 任务完成率突然从95%降至70%,却找…...

HTTP:三.HTTP报文

报文流 http报文是以一种类似的流的方式来发送数据的,所以报文流讲述了http报文的一些客观状态,相关术语:流入、流出形容事务处理。http报文任何时候是从上游向下游流入的!其中进过的节点既可能是上游,有可能是下游,如果从某个节点流出,那么相对于此节点流入的那个节点…...

.NET MAUI教程1-入门并发布apk包安装到真机

由于本人水平有限&#xff0c;如有写得不对的地方往指出&#xff0c;由于是使用公司的电脑&#xff0c;电脑的操作系统是英文的&#xff0c;没有权限修改&#xff0c;所以本文截图中的vs是英文版的 以发布android为例进行讲解 测试环境&#xff1a; window 11 vs2022 步骤如…...

你所拨打的电话是空号?手机状态查询API

一、引言 在当今数字化营销时代&#xff0c;电话销售仍然是许多企业获取客户的重要手段之一。然而&#xff0c;电销过程中常常遇到空号、风险号和沉默号等问题&#xff0c;这不仅降低了营销效率&#xff0c;还增加了企业的运营成本。例如&#xff0c;频繁拨打空号浪费了大量时…...

C++顺序栈的实现

顺序栈详细介绍 定义与特点 顺序栈&#xff08;Sequential Stack&#xff09;是一种基于数组实现的栈结构&#xff0c;利用数组的连续内存空间存储元素&#xff0c;遵循后进先出&#xff08;LIFO&#xff09;原则。其核心特点包括&#xff1a; 固定或动态容量&#xff1a;初始…...

element-ui 中的 select 组件如何 remote-method 函数中传参

在 select 组件中我们使用其 change 事件可以传参&#xff0c;请查看&#xff1a;el-select 中change 事件传参问题。 在我们使用 select 组件的远程搜索时&#xff0c;我们如何给 remote-method 这个方法添加自定义参数呢&#xff1f; 代码实现如下&#xff1a; <el-sel…...

浅谈解释型语言的运用

不得不说&#xff0c;程序不需要编译&#xff0c;程序在运行时才翻译成机器语言&#xff0c;每执行一次&#xff0c;都要翻译一次&#xff0c;因此效率比较低。在运行程序时才翻译&#xff0c;专门有一个解释器去进行翻译&#xff0c;每个语句都是执行的时候才翻译&#xff0c;…...

云手机哪个平台最好用?云手机性能、服务、技术等多维度测评分析

在云手机市场日渐繁荣的当下&#xff0c;面对琳琅满目的云手机平台&#xff0c;用户往往难以抉择云手机哪个平台最好。下面我们就从从性能、价格等多维度分析&#xff0c;云手机平台哪家强&#xff0c;队国内好用的云手机平台进行排行盘点。 好用的云手机平台排行盘点 第一&…...

idea版的cursor:Windsurf Wave 7

在企业环境中&#xff0c;Visual Studio Code和JetBrains系列是最常用的开发工具&#xff0c;覆盖了全球绝大多数开发者。这两类IDE各有优势&#xff0c;但JetBrains系列凭借其针对特定语言和企业场景的深度优化&#xff0c;使得用户很难轻易更换工具。 虽然Windsurf编辑器是基…...

【IDEA】创建 SpringBoot 项目连接 MySQL

前言 IntelliJ IDEA 作为一款专业的 Java 开发工具&#xff0c;在创建和管理 Spring Boot 项目方面具有显著优势。它深度集成了 Spring Boot 的开发流程&#xff0c;从项目初始化到最终部署都提供了高效的支持。IDEA 内置的 Spring Initializr 工具让项目创建变得极其简单&…...

使用 IntelliJ IDEA 进行远程调试

1. 前言 今天线上出现了个 Bug &#xff0c;而且比较坑的是涉及到k8s环境相关的东西不能线下调试。传统方式是在代码中各种的日志 log 埋点然后重新部署进行调试&#xff0c;再根据 log 中的信息进行分析。如果你的 log 埋点不合理&#xff0c;就要不停的修改代码、不停的打包…...

Linux操作系统--进程状态

目录 1.运行、阻塞、挂起 1.1运行 1.2阻塞 1.3挂起(了解一下即可&#xff0c;基本不会出现这种情况) 2.进程状态 2.1进程状态查看 2.2 Z-僵尸进程 2.3孤儿进程 1.运行、阻塞、挂起 1.1运行 运行状态&#xff1a; 进程正在占用CPU执行指令。此时进程对系统资源&#xff…...

【端到端】端到端自动驾驶依赖Occupancy进行运动规划?还是可以具有生成局部地图来规划?

端到端自动驾驶系统的架构设计&#xff0c;目前主流做法实际上已经出现两种路径&#xff0c;我们来拆解一下&#xff1a; &#x1f698; 一、Occupancy 是否用于运动规划&#xff1f; 一种趋势是使用 Occupancy 表示作为 中间表征&#xff0c;用于&#xff1a; 运动规划&…...

HarmonyOS-ArkUIV2装饰器-@Param:组件外部输入

上文我们了解了@Local装饰器 ,讲明了Local装饰器不允许外部传入值对其进行初始化。详见: HarmonyOS-ArkUI V2装饰器@Local装饰器:组件内部状态-CSDN博客。 但总有场景是需要外部组件传值过来,然后本组件接收这个值这种场景的。而且很多情况下,一个状态变量的作用范围会是…...

报错 ImportError: cannot import name ‘packaging‘ from ‘pkg_resources‘

解决方法一 &#xff08;推荐&#xff09; 先检查setuptools是否为70.0.0版本 pip list | grep setuptools再降低版本 python -m pip install setuptools69.5.1或者&#xff1a; 解决方法二 上述的报错信息表明&#xff1a;在安装 mmcv 时出现了 pkg_resources 模块的问题…...

Ollama调用多GPU实现负载均衡

文章目录 &#x1f4ca; 背景说明&#x1f6e0;️ 修改 systemd 服务配置1. 配置文件路径2. 编辑服务文件2. 重新加载配置并重启服务3. 验证配置是否成功 &#x1f4c8; 应用效果示例1. 调用单个70b模型2. 调用多个模型&#xff08;70b和32b模型&#xff09; 总结&#x1f4cc;…...

Next.js 简介

Next.js 是一个由 Vercel 开发的基于 React 的 Web 开发框架&#xff0c;旨在简化 React 应用的开发流程&#xff0c;提供更好的性能和开发体验。 &#x1f31f; Next.js 的核心特点 1. 文件系统路由&#xff08;File-system Routing&#xff09; 在 pages/ 目录中创建文件就…...

使用Apache POI(Java)创建docx文档和表格

1、引入poi 依赖组件 <dependency><groupId>org.apache.poi</groupId><artifactId>poi-scratchpad</artifactId><version>4.0.0</version> </dependency> <dependency><groupId>org.apache.poi</groupId>&…...

Dynamics365 ExportPdfTemplateExportWordTemplate两个Action调用的body构造

这两天在用ExportPdfTemplate做pdf导出功能时&#xff0c;遇到了如下问题InnerException : Microsoft.OData.ODataException: An unexpected StartArray node was found when reading from the JSON reader. A PrimitiveValue node was expected. 我的场景是使用power automate…...

《算法笔记》3.3小节——入门模拟->图形输出

1036 跟奥巴马一起编程 #include <iostream> #include <cmath> using namespace std;int main() {int n,m;char c;cin>>n>>c;for (int i 0; i < n; i) {cout<<c;}cout<<endl;m round(1.0*n/2)-2;//round里面不能直接写n/2&#xff0c;…...

iOS 上的内存管理是如何处理的?

iOS主要通过自动引用计数&#xff08;ARC&#xff09;和内存管理模型来处理内存。以下是对这两者的详细介绍以及在实际工作中的应用场景&#xff1a; 1. 自动引用计数&#xff08;ARC&#xff09; ARC是iOS和macOS中的内存管理机制。它能自动跟踪和管理应用程序的内存使用情况…...

河北工程大学e2e平台,python

题目&#xff0c;选择题包100分&#xff01; 题目&#xff0c;选择题包100分&#xff01; 题目&#xff0c;选择题包100分&#xff01; 联系&#x1f6f0;&#xff1a;18039589633...

【Qt】spdlog日志模块的使用

版本 spdlog版本&#xff1a;1.5.0 采用1.5.0版本主要基于以下考虑&#xff1a;兼容Qt5.9.X版本和兼容C11。 spdlog 1.5.0下载地址&#xff1a;https://github.com/gabime/spdlog/releases/tag/v1.5.0 摘要 在Qt应用程序开发中&#xff0c;良好的日志系统至关重要。本文将介…...

python相关面试题

python相关面试题 1.上下文管理器需要实现哪两种方法以及相关应用 2.对比一下进程&#xff0c;线程和协程 3.魔法函数有哪些 4.什么是闭包和装饰器&#xff0c;装饰器缺点是什么 5.什么是浅拷贝和深拷贝 6.什么是GIL锁和互斥锁 7.init和new有什么区别&#xff0c;new方法的返回…...

swift菜鸟教程11-12(数组与字典)

一个朴实无华的目录 今日学习内容&#xff1a;1.Swift 数组1.1创建数组1.2访问数组1.3修改数组使用 append() 方法或者赋值运算符 在数组末尾添加元素通过索引修改数组元素的值&#xff1a; 1.4遍历数组 使用for-in循环同时需要每个数据项的值和索引值 1.5合并数组1.6count 属…...

.NET WPF 控件类分层结构

.NET WPF 控件类分层结构 在 WPF 中&#xff0c;类的层级结构设计是为了实现线程安全、依赖属性、可视化渲染、布局和交互等功能。以下是 WPF 核心基类的逐级说明&#xff0c;从最底层到最顶层&#xff1a; 1 DispatcherObject 作用&#xff1a; 提供与 WPF 线程模型&#xf…...

基于ImGui+FFmpeg实现播放器

基于ImGuiFFmpeg实现播放器 演示&#xff1a; ImGui播放器 继续研究FFmpeg&#xff0c;之前做了一个SDL的播放器&#xff0c;发现SDL的可视化UI界面的功能稍微差了点&#xff0c;所以今天我们换了一个新的工具&#xff0c;也就是ImGui。 ImGui官方文档&#xff1a;https://g…...

【Docker】快速部署 Certbot 并为 Nginx 服务器配置 SSL/TLS 证书

【Docker】快速部署 Certbot 并为 Nginx 服务器配置 SSL/TLS 证书 引言 Certbot 是一个免费的开源工具&#xff0c;用于自动化管理和获取 SSL/TLS 证书&#xff0c;主要用于与 Let’s Encrypt 证书颁发机构交互。 步骤 Nginx 挂载 certbot 文件夹。 docker run -d \--name…...

MATLAB编写的机械臂控制仿真程序,它主要实现了对一个二连杆机械臂的运动控制仿真,比较了PID控制和非线性模型预测控制两种方法在机械臂轨迹跟踪任务中的性能

clc; clear; close all;%% 机械臂参数 l1 0.5; l2 0.4; Ts 0.02; sim_time 60; t 0:Ts:sim_time;%% 物理参数 m1 1.0; m2 0.8; g 9.81;%% 直线轨迹参数 start_point [0.3; 0.1]; end_point [0.7; 0.3]; progress t/sim_time; xd start_point(1) (end_point(1) - s…...

python办公自动化------word文件的操作

一、 word文件的创建 需要安装包&#xff1a;python-docx 例1&#xff1a;创建word文件 from docx import Document# 创建doc对象 doc Document()# 指定路径下创建一个docx文件 doc.save(./dataFile/test1_doc.docx) 运行结果&#xff1a; 例2&#xff1a;word中添加内容 …...

Python小程序 - 文件处理3:正则表达式

正则表达式&#xff1a;文本年鉴表。遗留的问题很多。。。用AI再想想 需求&#xff1a;读入txt文件&#xff0c;过滤文件有关年记录 0&#xff09;读入txt文件 1&#xff09;以“。”&#xff0c;中文句号&#xff0c;为界区分一句&#xff0c;最小统计单位 2&#xff09;年格…...

JAVA中正则表达式的入门与使用

JAVA中正则表达式的入门与使用 一&#xff0c;基础概念 正则表达式&#xff08;Regex&#xff09; 用于匹配字符串中的特定模式&#xff0c;Java 中通过 java.util.regex 包实现&#xff0c;核心类为&#xff1a; Pattern&#xff1a;编译后的正则表达式对象。 Matcher&#…...

智慧能源管理平台:驱动电网数字化转型,引领绿色能源新时代

安科瑞 华楠18706163979 一、引言 在全球能源转型和"双碳"目标的推动下&#xff0c;微电网作为分布式能源系统的重要组成部分&#xff0c;正迎来快速发展期。安科瑞电气股份有限公司推出的微电网智慧能源管理平台&#xff08;EMS 3.0&#xff09;&#xff0c;通过整…...

OpenCV 图形API(30)图像滤波-----腐蚀操作函数erode()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 使用特定的结构元素腐蚀图像。 cv::gapi::erode 是 OpenCV 的 G-API 模块中用于执行图像腐蚀操作的函数。腐蚀是一种基本的形态学操作&#xff0…...

02-MySQL 面试题-mk

1.如何定位慢查询? 什么是慢查询? 页面加载过慢、接口压测响应时间过长(超过1s) 慢查询出现的情况有哪些? 聚合查询多表查询表数据量过大查询深度分页查询如何定位慢查询? 方案一:开源工具 调试工具:Arthas运维工具:Prometheus、Skywalking**Arthas:**可以使用命令的…...

利用安固软件实现电脑屏幕录像:四种实用方法分享

在日常工作中&#xff0c;有时我们需要录制电脑屏幕以进行教学演示、软件操作记录或重要会议的存档。安固软件终端安全管理系统提供了强大的屏幕录像功能&#xff0c;可以满足这些需求。 接下来&#xff0c;本文将介绍如何使用安固软件设置电脑实时画面录像&#xff0c;并分享…...

Gitee DevSecOps 以 CBB 驱动军工研发范式革新:平台化管理构件实践

随着军工软件向智能化、标准化与集约化发展&#xff0c;传统研发模式在效率、质量及协同方面面临显著瓶颈。项目规模扩大与系统复杂度的提升&#xff0c;亟需一种创新研发范式。Gitee DevSecOps 平台基于 CBB&#xff08;通用构件库&#xff09;理念&#xff0c;通过模块化、标…...

spring:xml方式调用构造方法创建Bean,调用set方法配置字段

如题&#xff1a; 要创建的Bean类UserServiceImpl02 &#xff1a; package com.itheima.service.impl;import com.itheima.dao.interfaces.InterfaceUserDao; import com.itheima.service.interfaces.InterfaceUserService;/*** copyright 2003-2024* author qiao wei* da…...

PM2 完全指南:Node.js 应用后台启动、关闭与重启详解

文章目录 **PM2 完全指南&#xff1a;Node.js 应用后台启动、关闭与重启详解****1. 什么是 PM2&#xff1f;****2. 安装 PM2****全局安装****验证安装** **3. 使用 PM2 启动 Node.js 应用****基本启动****指定应用名称****集群模式&#xff08;多进程负载均衡&#xff09;****监…...