LearnOpenGL-入门-9.摄像机

news2025/7/30 5:04:57

本人刚学OpenGL不久且自学,文中定有代码、术语等错误,欢迎指正。

此篇有点难理解,但是学完会对FPS第一人称3D摄像机的实现有深刻的理解

我写的项目地址:https://github.com/liujianjie/LearnOpenGLProject

LearnOpenGL中文官网:https://learnopengl-cn.github.io/

文章目录

  • OpenGL摄像机基本概念
  • 摄像机/观察空间
    • 摄像机的位置
    • 摄像机的方向
    • 右轴
    • 上轴
    • LookAt
  • 自由移动-重点
    • 移动速度
  • 视角移动 - 重点
    • 欧拉角
    • 鼠标输入
    • 缩放

OpenGL摄像机基本概念

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

摄像机/观察空间

  • 摄像机/观察空间简介

    讨论的是摄像机作为场景原点场景中所有的顶点坐标

    观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。

  • 定义一个摄像机需要

    • 摄像机世界空间中的位置
    • 摄像机观察的方向
    • 指向它右侧的向量
    • 指向它上方的向量

摄像机的位置

glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);

摄像机向后移动,z轴大于0

摄像机的方向

  • 什么是摄像机的方向

    看第一张图的第二张小图

    原点出发,指向摄像机的方向:即摄像机的位置减去原点位置

    glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
    glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);
    
  • 摄像机指向的方向(不要搞反)

    与摄像机的方向相反

    是从摄像机出发,指向原点的方向:即原点位置减去摄像机位置

    glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
    glm::vec3 cameraDirection = glm::normalize(cameraTarget - cameraPos);
    

右轴

  • 什么是右轴

    看第一张图的第三张小图:指向摄像机侧的向量

  • 如何计算出来

    • 先定义一个上向量

    • 把上向量和摄像机的方向向量进行叉乘

      glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); 
      glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));
      
      • 说明cross函数的参数顺序

        cross第一个参数为up、第二个参数为cameraDirection,会得到指向X轴方向向量

        若第一个参数为cameraDirection、第二个参数为up,会得到指向X轴方向向量

      • 测试为什么cross的参数顺序决定指向方向

        up(0, 1, 0)、cameradirection(0, 0,1),叉乘后,(1,0,0),x>0自然是正方向

        cameradirection(0, 0, 1)、up(0, 1,0),叉乘后,(-1,0,0),x<0自然是方向

上轴

  • 什么是上轴

    看第一张图的第四张小图:指向摄像机方的向量

  • 如何计算出来

    向量和摄像机的方向向量进行叉乘

    glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);
    
    • 再次测试为什么cross的参数顺序决定指向方向

      cDirection(0, 0, 1)、cRight(1, 0,0),叉乘后,(0,1,0),y>0自然是方向

      cRight(1, 0,0)、cDirection(0, 0, 1),叉乘后,(0,-1,0),y<0自然是方向

LookAt

  • 什么是LookAt矩阵

    如果使用3个相互垂直(或非线性)的轴定义了一个坐标空间,你可以用这3个轴外加一个平移向量来创建一个矩阵,并且你可以用这个矩阵乘以任何向量来将其变换到那个坐标空间

  • 摄像机的LookAt矩阵

    • 解读
      • 其中R是右向量,U是上向量,D是方向向量,P是摄像机位置向量
      • 位置向量是相反的,我们最终希望把世界平移到与我们自身移动的相反方向

    LookAt矩阵作为摄像机的观察矩阵

  • 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用来计算摄像机的方向:摄像机的位置减去目标位置

      • 第三个:上向量

    • glm的lookAt函数会像上面讨论的步骤得到LookAt矩阵(观察矩阵)

      • 先计算得到摄像机的方向,D(会normalize,向量长度为1)
      • 再与第三个向量cross得到x正方向,R(会normalize,向量长度为1)
      • 再x正方向与摄像机方向cross得到y正方向,U
  • 摄像机看向原点,绕着圆转例子

    float radius = 10.0f;
    float camX = sin(glfwGetTime()) * radius;
    float camZ = cos(glfwGetTime()) * radius;
    glm::mat4 view;
    view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0)); 
    

自由移动-重点

为了创建符合FPS的摄像机移动方式

  • 变量定义

    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);
    
    view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
    

    lookAt第二个参数为什么要加cameraPos,可以直接看第3点小结

    • 若lookAt第二个参数不加cameraPos

      cameraFront(0, 0, -1)

      • 当cameraPos(0, 0, 3)

        摄像机的方向:(0, 0, 3)-(0,0,-1)=(0,0,4)

      • 当camearPos(0,0,-3)

        摄像机的方向:(0,0,-3)-(0,0,-1)=(0,0,-2)

        摄像机的方向会随着自身位置而改变,从而永远会绕着cameraFront(0,0,-1)移动,而不是永远注视前方,不符合FPS游戏的风格

    • 若第二个参数加cameraPos

      cameraFront(0, 0, -1)

      • 当cameraPos(0, 0, 3)

        lookAt第二个参数:cameraPos + cameraFront =(0,0,2)

        摄像机的方向:(0, 0, 3)-(0,0,2)=(0,0,1)

      • 当camearPos(0,0,-0.5)

        lookAt第二个参数:cameraPos + cameraFront =(0,0,-1.5)

        摄像机的方向:(0, 0, -0.5)-(0,0,-1.5)=(0,0,1)

      • 当camearPos(0,0,-3)

        lookAt第二个参数:cameraPos + cameraFront =(0,0,-4)

        摄像机的方向:(0, 0, -3)-(0,0,-4)=(0,0,1)

    • 小结:为什么要加cameraPos

      由于lookAt函数第二个参数(摄像机看向的目标点)lookpoint = cameraPos + cameraFront(0,0,-1)

      摄像机的方向D = cameraPos - lookpoint = cameraPos - cameraPos - camerFront(0, 0, -1) = (0,0,1)

      所以摄像机的方向永远是(0,0,1);

      对应第一张图的第2张小图,摄像机的镜头对着原点,而自己的方向是(0,0,1)
      请添加图片描述

      代表摄像机永远注视自己前方(符合FPS摄像机风格),而不是绕着一个点旋转移动。

      原文是:说这样能保证无论我们怎么移动,摄像机都会注视着目标方向

  • WASD控制

    void processInput(GLFWwindow *window){
        ...
        float cameraSpeed = 0.05f; // adjust accordingly
        if (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;
    }
    

    cameraPos = (0, 0, 0);cameraFront = (0, 0, -1);cameraSpeed = (0,0,1);cameraUp = (0,1, 0)

    • WS中拿W代值举例子,S省略

      cameraPos += cameraSpeed * cameraFront;
      cameraPos = (0, 0, 0) + (0, 0, 1) * (00-1) = (0, 0, -1) 
      

      可见cameraPos的z轴,从0到-1,由于OpenGL的右手坐标系,确实是向前移动

    • AD与WS差不多,都可以参考上面W代值举例子,但这里说明

      glm::cross(cameraFront, cameraUp)得出是右向量(Right Vector)

      cameraFront(0, 0, -1)cameraUp(0, 10),叉乘后,(1,0,0),x>0是正方向
      

    注意,我们对右向量进行了标准化normalize。如果我们没对这个向量进行标准化,最后的叉乘结果会根据cameraFront变量返回大小不同的向量。
    如果我们不对向量进行标准化,我们就得根据摄像机的朝向不同加速或减速移动了,但如果进行了标准化移动就是 匀速 的。

移动速度

  • 目前的问题

    目前我们的移动速度是个常量,据配置的不同,有些人可能会比其他人每秒绘制更多帧,也就是以更高的频率调用processInput函数

    就会造成有些人可能移动很快,而有些人会移动很慢。

  • 如何解决

    图形程序和游戏通常会跟踪一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。我们把所有速度都去乘以deltaTime值。

  • 为什么能解决

    如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的速度需要变得更高来平衡渲染所花去的时间。

    使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。

  • 代码

    float deltaTime = 0.0f; // 当前帧与上一帧的时间差
    float lastFrame = 0.0f; // 上一帧的时间
    
    float currentFrame = glfwGetTime();
    deltaTime = currentFrame - lastFrame;
    lastFrame = currentFrame;
    
    void processInput(GLFWwindow *window)
    {
      float cameraSpeed = 2.5f * deltaTime;
      ...
    }
    
  • 个人举例

    • 问题描述

      若规定每帧移动速度为speed = 60m/s

      若屏幕的HZ为60帧,那么它1秒会移动60次,60 * speed = 360m

      若屏幕的HZ为100帧,那么它1秒会移动100次,100*speed = 6000m

    • deltatime计算

      HZ为60的,1/60 = 0.01666666

      HZ为100的,1/100 = 0.01

    • 按照理论代入值乘以deltatime解决

      若规定每帧移动速度为speed = 60m/s

      若屏幕的HZ为60帧,那么它1秒会移动60次,60 * speed * deltatime(1/60) = 60m

      若屏幕的HZ为100帧,那么它1秒会移动100次,100*speed * deltatime(1/100)= 60m

      S = HZ * 1 / HZ * speed = speed,即解决

视角移动 - 重点

为了能够改变视角,我们需要根据鼠标的输入改变cameraFront向量,从而实现能改变摄像机看向的方向=转向

欧拉角

  • 图示

    3种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll)

  • 前置知识

    这里不知道原文讲得啥,索性改了。。。

    如果我们把斜边边长h定义为1

    • 邻边x的长度是cosθ= x/h= x/1=x
    • 对边y的长度是sinθ = y/h= y/1=y
  • 根据前置知识能推导计算direction的xyz

    注意区分:前置知识是2D坐标系,这里的图是3D坐标系

    这张推导图来源于原网址的评论区

  • 由图示推理的glm代码

    对应推理图的3个波浪线所画的

    direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 译注:direction代表摄像机的前轴(Front),这个前轴是和本文第一幅图片的第二个摄像机的方向向量是相反的
    direction.y = sin(glm::radians(pitch));
    direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
    

    这样我们就有了一个可以把俯仰角度和偏航角度转化为用来自由旋转视角的摄像机的3维方向向量了

  • 疑问点

    疑问:direction是摄像机的方向,还是摄像机的指向方向(原文没说清楚,只说摄像机的3维方向向量)

    解答:是摄像机的指向方向,解答过程在下面

鼠标输入

  • 要这个干嘛

    用来计算俯仰角度或偏航角度

  • 如何用鼠标移动来算角度

    储存上一帧鼠标的位置,在当前帧中我们当前计算鼠标位置与上一帧的位置相差多少。

    如果水平/竖直差别越大那么俯仰角或偏航角就改变越大,也就是摄像机需要移动更多的距离

  • 最终获取方向向量步骤与代码

    1. 设置鼠标输入回调函数

      glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);// 屏幕不显示鼠标
      void mouse_callback(GLFWwindow* window, double xpos, double ypos);
      glfwSetCursorPosCallback(window, mouse_callback);
      
    2. 计算鼠标距上一帧的偏移量。

    3. 把偏移量添加到摄像机的俯仰角和偏航角中。

      float xoffset = xpos - lastX;
      // 注意下面计算y的偏移量是相反的
      /*
      	我们希望y坐标是从底部往顶部依次增大的
      	但是glfwSetCursorPosCallback得到的是屏幕左上角的坐标为(0,0),所以越往上的y值越小,不符合坐标系中y轴朝上规	则,所以得改变
      */
      float yoffset = lastY - ypos; 
      lastX = xpos;
      lastY = ypos;
      
      float sensitivity = 0.05f;
      xoffset *= sensitivity;
      yoffset *= sensitivity;
      yaw += xoffset;
      pitch += yoffset;
      
    4. 对偏航角和俯仰角进行最大和最小值的限制。

      if(pitch > 89.0f)
        pitch =  89.0f;
      if(pitch < -89.0f)
        pitch = -89.0f;
      
    5. 计算方向向量。(通过俯仰角和偏航角)

      glm::vec3 front;
      front.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
      front.y = sin(glm::radians(pitch));
      front.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
      cameraFront = glm::normalize(front);
      
  • 疑问:direction=cameraFront是摄像机的方向,还是摄像机的指向方向?

    • 自由移动那节推导小结

      glm::mat4 view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
      

      由于Center = cameraPos + cameraFront(0,0,-1)

      摄像机的方向D = cameraPos - Center = cameraPos - cameraPos - camerFront(0,0,-1)=(0,0,1)

      所以摄像机的方向永远是(0,0,1);

      对应第一张图的第2张小图,摄像机的镜头对着原点,而自己的方向是(0,0,1)

      代表摄像机永远注视自己前方(摄像机的指向方向(0,0,-1))。

    • 代值计算

      根据yaw和pitch角度对cameraFront的x y z重新计算

      若cameraFront(0, 1, 0)

      由于Center = cameraPos + cameraFront(0,1,0)

      摄像机的方向D = cameraPos - Center = cameraPos - cameraPos - camerFront(0, 1, 0)=(0,-1,0)

      摄像机的方向是(0, -1, 0);代表摄像机指向方向是(0, 1, 0) = cameraFront,看向右方

    • 所以

      direction = cameraFront是摄像机的指向方向

  • 此节重要流程与目的

    由鼠标偏移值计算yaw,pitch角度,而这两个角度关乎摄像机的指向方向

    1.于是yaw和pitch重新计算摄像机的指向方向从而实现移动鼠标位置改变视角的功能

    2.当改变摄像机位置,根据摄像机的指向方向而正确的移动

    // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
    if (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;
    
    • 保证WS永远朝着摄像机指向方向(前方)移动
    • 保证AD永远朝着垂直于摄像机指向方向的右向量移动

缩放

  • 如何实现缩放

    视野(Field of View)或fov定义了我们可以看到场景中多大的范围

    当视野fov变小时,场景投影出来的空间就会减小,产生放大(Zoom In)了的感觉

  • 关键代码

    • 控制fov改变

      glfwSetScrollCallback(window, scroll_callback);//注册鼠标滚轮的回调函数
      void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)// 鼠标滚轮的回调函数
      {
        if(fov >= 1.0f && fov <= 45.0f)
          fov -= yoffset;
        if(fov <= 1.0f)
          fov = 1.0f;
        if(fov >= 45.0f)
          fov = 45.0f;
      }
      

      因为45.0f是默认的视野值,我们将会把缩放级别(Zoom Level)限制在1.0f45.0f

    • 每一帧都必须把透视投影矩阵上传到GPU,并且现在使用fov变量作为它的视野

      projection = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);
      
  • 效果

    请添加图片描述

  • 原文说要注意的

    使用欧拉角的摄像机系统并不完美。根据你的视角限制或者是配置,你仍然可能引入万向节死锁问题。最好的摄像机系统是使用四元数(Quaternions)的,但我们将会把这个留到后面讨论。(译注:这里可以查看四元数摄像机的实现)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/394562.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一、Java类加载机制

文章目录什么是类的加载&#xff1f;类的生命周期加载连接[验证、准备、解析]初始化结束生命周期类加载器类加载方式双亲委派机制自定义类加载器总结--类加载什么是类的加载&#xff1f; 类的加载指的是将类的.class文件中的二进制数据读入到内存中&#xff0c;将其放在运行时…

Git的SSH密钥配置

Git的SSH密钥配置简记Githttps和ssh的区别基本需求SSH密钥类型ED25519 SSH 密钥RSA SSH 密钥查看您是否有现有的 SSH 密钥对设置流程设置user name和emailssh密钥配置检查是否存在ssh Key创建新的ssh key将ssh密钥添加到您的Git帐户验证您是否可以连接使用Git有一段时间了&…

代码随想录-51-110.平衡二叉树

目录前言题目1.求高度和深度的区别节点的高度节点的深度2. 本题思路分析&#xff1a;3. 算法实现4. pop函数的算法复杂度5. 算法坑点前言 在本科毕设结束后&#xff0c;我开始刷卡哥的“代码随想录”&#xff0c;每天一节。自己的总结笔记均会放在“算法刷题-代码随想录”该专…

OpenCV入门(五)快速学会OpenCV4文字绘制边界填充

OpenCV入门&#xff08;五&#xff09;快速学会OpenCV4文字绘制&边界填充 1.文字绘制 OpenCV中除了提供绘制各种图形的函数外&#xff0c;还提供了一个特殊的绘制函数&#xff0c;即在图像上绘制文字。 这个函数是putText()&#xff0c;它是命名空间cv2中的函数&#xff…

(二十三)操作系统-多生产者·多消费者问题

文章目录一、问题描述二、问题分析1. 关系分析2. 整理思路三、实现1. 代码2.如果不要互斥信号量3. 将盘子&#xff08;缓冲区&#xff09;容量设为2四、总结一、问题描述 桌子上有一只盘子&#xff0c;每次只能向其中放入一个水果。爸爸专向盘子中放苹果&#xff0c;妈妈专向盘…

用强化学习神包trl轻松实现GPT2可控文本生成

来源&#xff1a;投稿 作者&#xff1a;Sally can wait 编辑&#xff1a;学姐 模型github: lvwerra/trl: Train transformer language models with reinforcement learning. (github.com)https://github.com/lvwerra/trl 这个项目是复现 ”Fine-Tuning Language Models from H…

C++vector 简单实现

一。概述 vector是我们经常用的一个容器&#xff0c;其本质是一个线性数组。通过对动态内存的管理&#xff0c;增删改查数据&#xff0c;达到方便使用的目的。 作为一个线性表&#xff0c;控制元素个数&#xff0c;容量&#xff0c;开始位置的指针分别是&#xff1a; start …

Hive---拉链表

拉链表 文章目录拉链表定义用途案例全量流程增量流程合并过程第一步第二步第三步案例二&#xff08;含分区&#xff09;创建外部表orders增量分区表历史记录表定义 拉链表是一种数据模型&#xff0c;主要是针对数据仓库设计中表存储数据的方式而定义的&#xff0c;顾名思义&am…

从零开始学GeoServer源码十一(如何处理多个文件解析器Multipart Resolver引起的冲突问题)

目录前言1.现象2.排查问题3.找到问题4.解决问题5.总结前言 本文起源于我们遇到的一个问题&#xff0c;本来 GeoServer 使用的好好的&#xff0c;但是有天突然发现&#xff0c;无法在 GeoServer 中上传样式的 sld 文件了&#xff0c;报错 “No Multipart-config for Servlet” …

java.lang.IllegalArgumentException: itemView may not be null

报错截图&#xff1a;场景介绍&#xff1a;在使用recycleView 自动递增数据&#xff0c;且自动滚动到最新行&#xff1b; 当数据达到273条 时出现ANR&#xff1b;项目中 全部的列表适配器使用的三方库&#xff1a;BaseRecyclerViewAdapterHelper &#xff08;很早之前的项目&am…

《SQL基础》16. 锁

锁锁全局锁表级锁表锁元数据锁意向锁行级锁行锁间隙锁临键锁锁 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并…

uniapp在线升级关联云空间

升级中心 uni-upgrade-center - App&#xff1a; https://ext.dcloud.net.cn/plugin?id4542 App升级中心 uni-upgrade-center文档&#xff1a; https://uniapp.dcloud.net.cn/uniCloud/upgrade-center.html#uni-upgrade-center-app 升级中心 uni-upgrade-center - Admin&#…

Ka频段需要更多带宽?

随着全球连接需求的增长&#xff0c;许多卫星通信(satcom)系统日益采用Ka频段&#xff0c;对数据速率的要求也水涨船高。目前&#xff0c;高性能信号链已经能支持数千兆瞬时带宽&#xff0c;一个系统中可能有成百上千个收发器&#xff0c;超高吞吐量数据速率已经成为现实。 另…

JavaWeb—HTML

目录 1、B/S 软件的结构 2、前端的开发流程 3、网页的组成部分 4、HTML 简介 5、创建 HTML 文件 6、HTML 文件的书写规范 7、HTML 标签介绍 8、常用标签介绍 8.1、font 字体标签 8.2、特殊字符 8.3、标题标签 8.4、超链接 &#xff08; **** 重 点 &#xff0c;必 …

如何实现jwt鉴权机制之详解

jwt鉴权一是什么headerpayloadSignature二、如何实现生成 token校验token三、优缺点优点&#xff1a;缺点&#xff1a;一是什么 JWT&#xff08;JSON Web Token&#xff09;&#xff0c;本质就是一个字符串书写规范&#xff0c;如下图&#xff0c;作用是用来在用户和服务器之间…

Wannacrypt蠕虫老树开花?又见Wannacrypt

Wannacrypt蠕虫是一个在2017年就出现的远古毒株&#xff0c;其利用永恒之蓝漏洞降维打击用户服务器&#xff0c;而后进行扩散勒索&#xff0c;曾经一度风靡全球&#xff0c;可谓是闻者伤心&#xff0c;听着落泪&#xff0c;因为这玩意解密是不可能 解密的。 而2023年的今天&am…

MCM 箱模型建模方法及大气 O3 来源解析实用干货

OBM 箱模型可用于模拟光化学污染的发生、演变过程&#xff0c;研究臭氧的生成机制和进行敏感性分析&#xff0c;探讨前体物的排放对光化学污染的影响。箱模型通常由化学机理、物理过程、初始条件、输入和输出模块构成&#xff0c;化学机理是其核心部分。MCM (Master Chemical M…

【每天学习一点新知识】JNDI注入

什么是JNDIJNDI是Java的一种API&#xff0c;为我们提供了查找和访问各种命名和目录服务的通用统一的接口。通过JNDI统一接口我们可以来访问各种不同类型的服务&#xff0c;例如远程方法调用&#xff08;RMI&#xff09;&#xff0c;通用对象请求代理体系结构&#xff08;CORBA&…

Qt QTreeView简单使用

QT-QTreeView使用方法 QTreeView: 用于显示树状结构数据&#xff0c;适用于树状结构数据的操作。 一、初始化 ​ 利用QStandardlternModel来初始化数据&#xff0c;标准的基于项数据的数据模型类&#xff0c; 每个项数据可以是任何数据类型。 // 初始化model QStandardItem…

工作实战之拦截器模式

目录 前言 一、结构中包含的角色 二、拦截器使用 1.拦截器角色 a.自定义拦截器UserValidateInterceptor&#xff0c;UserUpdateInterceptor&#xff0c;UserEditNameInterceptor b.拦截器配置者UserInterceptorChainConfigure&#xff0c;任意组装拦截器顺序 c.拦截器管理者…