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

OpenGL学习笔记(七):Camera 摄像机(视图变换、LookAt矩阵、Camera类的实现)

文章目录

  • 摄像机/观察空间/视图变换
  • LookAt矩阵
  • 移动相机(处理键盘输入)
  • 移动速度
  • 欧拉角
  • 移动视角(处理鼠标输入)
  • 缩放场景(处理滚轮输入)
  • Camera类


摄像机/观察空间/视图变换

在上一节变换中,我们讨论过观察空间中如何使用View矩阵移动场景。

从世界空间(World Space)到观察空间(View Space)的变换,就是视图变换(View Transformation),实际上是在对摄像机做变换,也即View / Camera Transformation

回顾GAMES101学习笔记(二):Transformation 变换 中的视图变换部分:
如何定义相机:

  • 相机的位置
  • 相机的朝向 LookAt
  • 相机的向上方向(固定相机,避免相机绕LookAt方向旋转)

视图变换要做的就是把相机设定在标准位置:

  • 相机放在原点
  • 相机LookAt方向为-Z方向
  • 将其他物体做同样的变换

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

在观察空间中,是以摄像机的视角作为场景原点时设定场景中所有的顶点坐标:观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右侧的向量以及一个指向它上方的向量。细心的读者可能已经注意到我们实际上创建了一个三个单位轴相互垂直的、以摄像机的位置为原点的坐标系。
在这里插入图片描述

1. 摄像机位置P

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

不要忘记正z轴是从屏幕指向你的,如果我们希望摄像机向后移动,我们就沿着z轴的正方向移动。

2. 摄像机方向D

这里指的是摄像机指向哪个方向。现在我们让摄像机指向场景原点:(0, 0, 0)。还记得如果将两个矢量相减,我们就能得到这两个矢量的差吗?用场景原点向量减去摄像机位置向量的结果就是摄像机的指向向量

方向向量(Direction Vector)并不是最好的名字,因为它实际上指向从它到目标向量的相反方向(如上图,蓝色的方向向量大概指向z轴的正方向,与摄像机实际指向的方向是正好相反的)。

由于我们知道摄像机指向z轴负方向,但我们希望方向向量(Direction Vector)指向摄像机的z轴正方向。如果我们交换相减的顺序,我们就会获得一个指向摄像机正z轴方向的向量

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

3. 右轴和上轴

我们需要的另一个向量是一个右向量(Right Vector),它代表摄像机空间的x轴的正方向

如何获取右向量:先定义一个上向量(Up Vector)。接下来把上向量和第二步得到的方向向量进行叉乘。两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量(如果我们交换两个向量叉乘的顺序就会得到相反的指向x轴负方向的向量):

glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); 
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));

现在我们已经有了x轴向量和z轴向量,获取一个指向摄像机的正y轴向量就相对简单了:我们把右向量和方向向量进行叉乘:

glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);

LookAt矩阵

我们创建了所有构成观察/摄像机空间的向量,使用这些向量我们就可以创建一个LookAt矩阵了。

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

这正是LookAt矩阵所做的,现在我们有了3个相互垂直的轴和一个定义摄像机空间的位置坐标,我们可以创建我们自己的LookAt矩阵了:
在这里插入图片描述
其中R是右向量,U是上向量,D是方向向量,P是摄像机位置向量。

注意,位置向量是相反的,因为我们最终希望把世界平移到与我们自身移动的相反方向。

把这个LookAt矩阵作为观察矩阵可以很高效地把所有世界坐标变换到刚刚定义的观察空间。LookAt矩阵就像它的名字表达的那样:它会创建一个看着(Look at)给定目标的观察矩阵。

使用glm库可以很快创建一个新的View矩阵:

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));
  • 第一个参数eye,位置向量,摄像机的位置
  • 第二个参数center,目标向量,注视点
  • 第三个参数up,上向量

接下来我们上手做一个小练习:将摄像机的注视点保持在(0, 0, 0),让摄像机在场景中旋转。

我们需要用到一点三角函数知识来在每一帧创建一个x和z坐标,它会代表圆上的一点,我们将会使用它作为摄像机的位置。通过重新计算x和y坐标,我们会遍历圆上的所有点,这样摄像机就会绕着场景旋转了。

我们预先定义这个圆的半径radius,在每次渲染循环中使用GLFW的glfwGetTime函数重新创建观察矩阵,来扩大这个圆。 参考源码

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

移动相机(处理键盘输入)

接下来,我们处理键盘输入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);

通过这些变量来更新view矩阵:

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

我们将摄像机位置设置为之前定义的cameraPos。方向是当前的位置加上我们刚刚定义的方向向量。这样能保证无论我们怎么移动,摄像机都会注视着目标方向。

更新processInput函数处理GLFW的键盘输入:

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;
}
  • 向前/向后:把位置向量加上或减去方向向量。
  • 向左/向右:使用叉乘来创建一个右向量(Right Vector),并沿着它相应移动

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

移动速度

目前我们的移动速度是个常量。理论上没什么问题,但是实际情况下根据处理器的能力不同,有些人可能会比其他人每秒绘制更多帧,也就是以更高的频率调用processInput函数。结果就是,根据配置的不同,有些人可能移动很快,而有些人会移动很慢。当你发布你的程序的时候,你必须确保它在所有硬件上移动速度都一样

图形程序和游戏通常会跟踪一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。

我们把所有速度都去乘以deltaTime值。如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的速度需要变得更高来平衡渲染所花去的时间。使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。

设置两个全局变量来计算出deltaTime值:

float deltaTime = 0.0f; // 当前帧与上一帧的时间差
float lastFrame = 0.0f; // 上一帧的时间

在每一帧中我们计算出新的deltaTime以备后用。

float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;

现在我们有了deltaTime,在计算速度的时候可以将其考虑进去了:

void processInput(GLFWwindow *window)
{float cameraSpeed = 2.5f * deltaTime;...
}

现在我们有了一个在任何系统上移动速度都一样的摄像机。

欧拉角

只用键盘移动我们不能转向,移动很受限制。接下来处理鼠标输入,来让我们的视角跟随鼠标转起来。为了能够改变视角,我们需要根据鼠标的输入改变cameraFront向量。然而,根据鼠标移动改变方向向量是非常复杂,需要一些三角学知识,我们先了解一下欧拉角。

欧拉角(Euler Angle)是可以表示3D空间中任何旋转的3个值,由莱昂哈德·欧拉(Leonhard Euler)在18世纪提出。一共有3种欧拉角:俯仰角(Pitch)偏航角(Yaw)滚转角(Roll),下面的图片展示了它们的含义:
在这里插入图片描述

  • 俯仰角(Pitch) 描述我们如何往上或往下看
  • 偏航角(Yaw) 描述我们往左和往右看的程度
  • 滚转角(Roll) 代表我们如何翻滚摄像机,通常在太空飞船的摄像机中使用

每个欧拉角都有一个值来表示,把三个角结合起来我们就能够计算3D空间中任何的旋转向量了。对于我们的摄像机系统来说,我们只关心俯仰角和偏航角,所以我们不会讨论滚转角。

给定一个俯仰角Pitch和偏航角Yaw,我们可以把它们转换为一个代表新的方向向量的3D向量。 (推理图解)

基于俯仰角和偏航角的方向向量:

direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));
direction.y = sin(glm::radians(pitch));
direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));

移动视角(处理鼠标输入)

偏航角和俯仰角是通过鼠标(或手柄)移动获得的,水平的移动影响偏航角竖直的移动影响俯仰角

它的原理就是,储存上一帧鼠标的位置,在当前帧中我们当前计算鼠标位置与上一帧的位置相差多少。如果水平/竖直差别越大那么俯仰角或偏航角就改变越大,也就是摄像机需要移动更多的距离。

在这个例子中,我们创建一个FPS摄像机系统,需要告诉GLFW隐藏光标,并捕捉(Capture)它。我们可以用一个简单地配置调用来完成:

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

在调用这个函数之后,无论我们怎么去移动鼠标,光标都不会显示了,它也不会离开窗口。对于FPS摄像机系统来说非常完美。

接下来,为了计算俯仰角和偏航角,我们需要让GLFW监听鼠标移动事件。(和键盘输入相似)我们会用一个回调函数来完成,函数的原型如下:

void mouse_callback(GLFWwindow* window, double xpos, double ypos);

这里的xposypos代表当前鼠标的位置。当我们用GLFW注册了回调函数之后,鼠标一移动mouse_callback函数就会被调用:

glfwSetCursorPosCallback(window, mouse_callback);

在处理FPS风格摄像机的鼠标输入的时候,我们必须在最终获取方向向量之前做下面这几步:

1. 计算鼠标距上一帧的偏移量

我们必须先在程序中储存上一帧的鼠标位置,我们把它的初始值设置为屏幕的中心(屏幕的尺寸是800x600):

float lastX = 400, lastY = 300;

然后在鼠标的回调函数中我们计算当前帧和上一帧鼠标位置的偏移量:

float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // 注意这里是相反的,因为y坐标是从底部往顶部依次增大的
lastX = xpos;
lastY = ypos;float sensitivity = 0.05f;
xoffset *= sensitivity;
yoffset *= sensitivity;

注意,我们把偏移量乘以了sensitivity(灵敏度)值。如果我们忽略这个值,鼠标移动就会太大了;可以自己实验一下,找到适合自己的灵敏度值。

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

接下来我们把偏移量加到全局变量pitchyaw上:

yaw   += xoffset;
pitch += yoffset;

3. 对偏航角和俯仰角进行最大和最小值的限制

还需要给摄像机添加一些限制,这样摄像机就不会发生奇怪的移动了(这样也会避免一些奇怪的问题)。对于俯仰角,要让用户不能看向高于89度的地方(在90度时视角会发生逆转,所以我们把89度作为极限),同样也不允许小于-89度。这样能够保证用户只能看到天空或脚下,但是不能超越这个限制。我们可以在值超过限制的时候将其改为极限值来实现:

if(pitch > 89.0f)pitch =  89.0f;
if(pitch < -89.0f)pitch = -89.0f;

注意我们没有给偏航角设置限制,这是因为我们不希望限制用户的水平旋转。要设置的话也很简单,同Pitch

4. 计算方向向量

通过俯仰角和偏航角来计算以得到真正的方向向量:

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

计算出来的方向向量就会包含根据鼠标移动计算出来的所有旋转了。


如果现在运行代码,你会发现在窗口第一次获取焦点的时候摄像机会突然跳一下。这个问题产生的原因是,在你的鼠标移动进窗口的那一刻,鼠标回调函数就会被调用,这时候的xposypos会等于鼠标刚刚进入屏幕的那个位置。这通常是一个距离屏幕中心很远的地方,因而产生一个很大的偏移量,所以就会跳了。我们可以简单的使用一个bool变量检验我们是否是第一次获取鼠标输入,如果是,那么我们先把鼠标的初始位置更新为xposypos值,这样就能解决这个问题;接下来的鼠标移动就会使用刚进入的鼠标位置坐标来计算偏移量了:

if(firstMouse) // 这个bool变量初始时是设定为true的
{lastX = xpos;lastY = ypos;firstMouse = false;
}

最后完整的回调函数:

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{if(firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;}float xoffset = xpos - lastX;float yoffset = lastY - ypos; lastX = xpos;lastY = ypos;float sensitivity = 0.05;xoffset *= sensitivity;yoffset *= sensitivity;yaw   += xoffset;pitch += yoffset;if(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);
}

缩放场景(处理滚轮输入)

再来实现一个缩放(Zoom)功能。在之前的文章中提到过视野(Field of View)fov定义了我们可以看到场景中多大的范围。当视野变小时,场景投影出来的空间就会减小,产生放大(Zoom In)了的感觉。

我们会使用鼠标的滚轮来放大。与鼠标移动、键盘输入一样,我们需要一个鼠标滚轮的回调函数:

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

当滚动鼠标滚轮的时候,yoffset值代表我们竖直滚动的大小。当scroll_callback函数被调用后,我们改变全局变量fov变量的内容。

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

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

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

最后不要忘记注册GLFW的鼠标滚轮的回调函数:

glfwSetScrollCallback(window, scroll_callback);

Camera类

至此,我们已经基本实现了一个使用欧拉角的FPS摄像机系统(当然,真正的FPS摄像机是不能飞行的,只能呆在xz平面上。参考)

它并不完美,根据你的视角限制或者是配置,你仍然可能引入万向节死锁问题。最好的摄像机系统是使用四元数(Quaternions)的。(这里可以查看四元数摄像机的实现)


接下来的学习中,我们将总会使用一个摄像机来浏览场景,从各个角度观察结果。我们需要将他抽象出来,创建一个Camera对象,来完成大量重复的工作。

Shader对象一样,我们把Camera类写在一个单独的头文件中:

#ifndef CAMERA_H
#define CAMERA_H#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>// Defines several possible options for camera movement. Used as abstraction to stay away from window-system specific input methods
enum Camera_Movement {FORWARD,BACKWARD,LEFT,RIGHT
};// Default camera values
const float YAW         = -90.0f;
const float PITCH       =  0.0f;
const float SPEED       =  2.5f;
const float SENSITIVITY =  0.1f;
const float ZOOM        =  45.0f;// An abstract camera class that processes input and calculates the corresponding Euler Angles, Vectors and Matrices for use in OpenGL
class Camera
{
public:// camera Attributesglm::vec3 Position;glm::vec3 Front;glm::vec3 Up;glm::vec3 Right;glm::vec3 WorldUp;// euler Anglesfloat Yaw;float Pitch;// camera optionsfloat MovementSpeed;float MouseSensitivity;float Zoom;// constructor with vectorsCamera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, float pitch = PITCH) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM){Position = position;WorldUp = up;Yaw = yaw;Pitch = pitch;updateCameraVectors();}// constructor with scalar valuesCamera(float posX, float posY, float posZ, float upX, float upY, float upZ, float yaw, float pitch) : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), MouseSensitivity(SENSITIVITY), Zoom(ZOOM){Position = glm::vec3(posX, posY, posZ);WorldUp = glm::vec3(upX, upY, upZ);Yaw = yaw;Pitch = pitch;updateCameraVectors();}// returns the view matrix calculated using Euler Angles and the LookAt Matrixglm::mat4 GetViewMatrix(){return glm::lookAt(Position, Position + Front, Up);}// processes input received from any keyboard-like input system. Accepts input parameter in the form of camera defined ENUM (to abstract it from windowing systems)void ProcessKeyboard(Camera_Movement direction, float deltaTime){float velocity = MovementSpeed * deltaTime;if (direction == FORWARD)Position += Front * velocity;if (direction == BACKWARD)Position -= Front * velocity;if (direction == LEFT)Position -= Right * velocity;if (direction == RIGHT)Position += Right * velocity;}// processes input received from a mouse input system. Expects the offset value in both the x and y direction.void ProcessMouseMovement(float xoffset, float yoffset, GLboolean constrainPitch = true){xoffset *= MouseSensitivity;yoffset *= MouseSensitivity;Yaw   += xoffset;Pitch += yoffset;// make sure that when pitch is out of bounds, screen doesn't get flippedif (constrainPitch){if (Pitch > 89.0f)Pitch = 89.0f;if (Pitch < -89.0f)Pitch = -89.0f;}// update Front, Right and Up Vectors using the updated Euler anglesupdateCameraVectors();}// processes input received from a mouse scroll-wheel event. Only requires input on the vertical wheel-axisvoid ProcessMouseScroll(float yoffset){Zoom -= (float)yoffset;if (Zoom < 1.0f)Zoom = 1.0f;if (Zoom > 45.0f)Zoom = 45.0f;}private:// calculates the front vector from the Camera's (updated) Euler Anglesvoid updateCameraVectors(){// calculate the new Front vectorglm::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));Front = glm::normalize(front);// also re-calculate the Right and Up vectorRight = glm::normalize(glm::cross(Front, WorldUp));  // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.Up    = glm::normalize(glm::cross(Right, Front));}
};
#endif

相关文章:

OpenGL学习笔记(七):Camera 摄像机(视图变换、LookAt矩阵、Camera类的实现)

文章目录 摄像机/观察空间/视图变换LookAt矩阵移动相机&#xff08;处理键盘输入&#xff09;移动速度欧拉角移动视角&#xff08;处理鼠标输入&#xff09;缩放场景&#xff08;处理滚轮输入&#xff09;Camera类 摄像机/观察空间/视图变换 在上一节变换中&#xff0c;我们讨…...

冲刺一区!挑战7天完成一篇趋势性分析GBD DAY1-7

Day1. 公开数据库的挖掘太火热了,其中GBD数据库的挖掘又十分的火爆.那我就来挑战一篇GBD、一篇关于趋势性分析的GBD&#xff01; GBD数据库挖掘是目前的四大刊常客&#xff0c;经常出现在顶级期刊上面。这个数据库亮点就是&#xff1a;可视化&#xff0c;统计学简单、而数据可…...

解锁数据结构密码:层次树与自引用树的设计艺术与API实践

1. 引言&#xff1a;为什么选择层次树和自引用树&#xff1f; 数据结构是编程中的基石之一&#xff0c;尤其是在处理复杂关系和层次化数据时&#xff0c;树形结构常常是最佳选择。层次树&#xff08;Hierarchical Tree&#xff09;和自引用树&#xff08;Self-referencing Tree…...

【AudioClassificationModelZoo-Pytorch】基于Pytorch的声音事件检测分类系统

源码&#xff1a;https://github.com/Shybert-AI/AudioClassificationModelZoo-Pytorch 模型测试表 模型网络结构batch_sizeFLOPs(G)Params(M)特征提取方式数据集类别数量模型验证集性能EcapaTdnn1280.486.1melUrbanSound8K10accuracy0.974, precision0.972 recall0.967, F1-s…...

ARM嵌入式学习--第十二天(WDOG,RTC)

--WDOG -介绍 WatchDog是为了能够防止程序跑飞而使用的一种硬件模块&#xff0c;如果你的程序没有跑飞&#xff0c;那么你的程序会定时的去喂看门狗&#xff1b;如果你的程序跑飞了&#xff0c;那么就不会再去喂狗了&#xff0c;如果超过了喂狗时间&#xff0c;那么狗就会自己…...

自动化构建-make/Makefile 【Linux基础开发工具】

文章目录 一、背景二、Makefile编译过程三、变量四、变量赋值1、""是最普通的等号2、“:” 表示直接赋值3、“?” 表示如果该变量没有被赋值&#xff0c;4、""和写代码是一样的&#xff0c; 五、预定义变量六、函数**通配符** 七、伪目标 .PHONY八、其他常…...

三天急速通关JavaWeb基础知识:Day 2 前端基础知识(计划有变,前端工程化部分暂时搁置)

三天急速通关JavaWeb基础知识&#xff1a;Day 2 前端基础知识 0 文章说明1 HTML1.1 介绍1.2 基本结构及语法1.3 常见标签2 CSS2.1 介绍2.2 引入方式2.3 选择器2.4 浮动 定位 盒子模型 3 JavaScript3.1 介绍3.2 组成3.3 基础语法 4 Ajax4.1 介绍4.2 示例 未完待续&#xff0c;前…...

Chapter2 Amplifiers, Source followers Cascodes

Chapter2 Amplifiers, Source followers & Cascodes MOS单管根据输入输出, 可分为CS放大器, source follower和cascode 三种结构. Single-transistor amplifiers 这一章学习模拟电路基本单元-单管放大器 单管运放由Common-Source加上DC电流源组成. Avgm*Rds, gm和rds和…...

14-9-3C++STL的set容器

set容器的pair对组——set容器set.equal range(elem) 1.返回容器中与elem相等的上下限的两个迭代器&#xff1b;上限是闭区间&#xff0c;下限是开区间&#xff0c;如[beg, end) 2.函数返回两个迭代器&#xff0c;而这两个迭代器被封装在pair中 pair< set<int>::ite…...

用Impala对存储在HDFS中的大规模数据集进行快速、实时的交互式SQL查询的具体步骤和关键代码

AWS EMR&#xff08;Elastic MapReduce&#xff09;中应用Impala的典型案例&#xff0c;主要体现在大型企业和数据密集型组织如何利用Impala对存储在Hadoop分布式文件系统&#xff08;HDFS&#xff09;中的大规模数据集进行快速、实时的交互式SQL查询。以下是一个具体的案例说明…...

如何确认Linux嵌入式系统的触摸屏对应的是哪个设备文件(/dev/input/event1)?如何查看系统中所有的输入设备?输入设备的设备文件有什么特点?

Linux嵌入式系统的输入设备的设备文件有什么特点&#xff1f; 在 Linux 中&#xff0c;所有的输入设备&#xff08;如键盘、鼠标、触摸屏等&#xff09;都会被内核识别为 输入事件设备&#xff0c;并在 /dev/input/ 目录下创建相应的 设备文件&#xff0c;通常是&#xff1a; …...

C++泛型编程指南08 auto decltype

文章目录 [TOC]第3章&#xff1a;auto占位符&#xff08;C11&#xff5e;C17&#xff09;3.1 auto关键字的重新定义3.2 类型推导规则 3.3 何时使用auto3.4 返回类型推导3.5 在Lambda表达式中使用auto3.6 非类型模板参数占位符 总结第4章 decltype说明符&#xff08;C11&#xf…...

php的使用及 phpstorm环境部署

php语法 环境搭建&#xff1a;在小皮中新建网站&#xff0c;注意先填写域名再点击选择根目录。 成功创建网站后&#xff0c;打开发现forbidden&#xff0c;因为新建的网站里是空的&#xff0c;需要新建index.php文件----> 在Phpstorm中左上角打开文件&#xff0c;打开那个文…...

人工智能学习(五)之机器学习逻辑回归算法

深入剖析机器学习逻辑回归算法 一、引言 在机器学习领域&#xff0c;逻辑回归是一种极为经典且应用广泛的算法。虽说名字里带有 “回归”&#xff0c;但它主要用于解决分类问题&#xff0c;在医学、金融、互联网等多个领域都发挥着关键作用。例如&#xff0c;在医学上辅助判断…...

Kubernetes学习之包管理工具(Helm)

一、基础知识 1.如果我们需要开发微服务架构的应用&#xff0c;组成应用的服务可能很多&#xff0c;使用原始的组织和管理方式就会非常臃肿和繁琐以及较难管理&#xff0c;此时我们需要一个更高层次的工具将这些配置组织起来。 2.helm架构&#xff1a; chart:一个应用的信息集合…...

数据结构课程设计(四)校园导航

4 校园导航 4.1 需求规格说明 【问题描述】 一个学校平面图&#xff0c;至少包括10个以上的场所&#xff0c;每个场所带有编号、坐标、名称、类别等信息&#xff0c;两个场所间可以有路径相通&#xff0c;路长&#xff08;耗时&#xff09;各有不同。要求读取该校园平面图&a…...

(done) MIT6.S081 2023 学习笔记 (Day7: LAB6 Multithreading)

网页&#xff1a;https://pdos.csail.mit.edu/6.S081/2023/labs/thread.html (任务1教会了你如何用 C 语言调用汇编&#xff0c;编译后链接即可) 任务1&#xff1a;Uthread: switching between threads (完成) 在这个练习中&#xff0c;你将设计一个用户级线程系统中的上下文切…...

大年初六,风很大

北京的风在立春附近的几天突然大了&#xff0c;正在盘算着这个冬天可能就这样平庸的去了&#xff0c;没成想风来了。走在风中&#xff0c;穿着本应该是三九天穿的冬装&#xff0c;紧闭着嘴&#xff0c;缩着身子&#xff0c;感受着这冬天该有的低温。这是冬天该有的样子&#xf…...

【算法】回溯算法专题③ ——排列型回溯 python

目录 前置小试牛刀回归经典举一反三总结 前置 【算法】回溯算法专题① ——子集型回溯 python 【算法】回溯算法专题② ——组合型回溯 剪枝 python 小试牛刀 全排列 https://leetcode.cn/problems/permutations/description/ 给定一个不含重复数字的数组 nums &#xff0c;返…...

利用deepseek参与软件测试 基本架构如何 又该在什么环节接入deepseek

利用DeepSeek参与软件测试&#xff0c;可以考虑以下基本架构和接入环节&#xff1a; ### 基本架构 - **数据层** - **测试数据存储**&#xff1a;用于存放各种测试数据&#xff0c;包括正常输入数据、边界值数据、异常数据等&#xff0c;这些数据可以作为DeepSeek的输入&…...

99.20 金融难点通俗解释:中药配方比喻马科维茨资产组合模型(MPT)

目录 0. 承前1. 核心知识点拆解2. 中药搭配比喻方案分析2.1 比喻的合理性 3. 通俗易懂的解释3.1 以中药房为例3.2 配方原理 4. 实际应用举例4.1 基础配方示例4.2 效果说明 5. 注意事项5.1 个性化配置5.2 定期调整 6. 总结7. 代码实现 0. 承前 本文主旨&#xff1a; 本文通过中…...

为AI聊天工具添加一个知识系统 之79 详细设计之20 正则表达式 之7

本文要点 要点 “正则表达式” 本来是计算机科学计算机科学的一个概念。本项目将它推广&#xff08;扩张&#xff09;到认知科学的“认知范畴”概念&#xff0c; 聚合&#xff08;收敛&#xff09;到 神经科学 的“神经元”概念。 做法是&#xff1a;用reg 来系统化定义认知…...

[ Spring ] Spring Boot Mybatis++ 2025

文章目录 StructureMyBatis Controller AbilitiesConfigure Plugins and RepositoriesApply Plugins and Add DependenciesMyBatis Spring PropertiesMyBatis ApplicationMyBatis BeansMyBatis MapperMyBatis Query Builder Structure this blog introduce 3 ways using mybat…...

虚幻基础17:动画层接口

能帮到你的话&#xff0c;就给个赞吧 &#x1f618; 文章目录 animation layer interface animation layer interface 动画层接口&#xff1a;动画图表的集。仅有名字。 添加到动画蓝图中&#xff0c;由动画蓝图实现动画图表。...

前缀和算法

文章目录 算法总览题目1371.每个元音包含偶数次的最长子字符串 算法总览 题目 1371.每个元音包含偶数次的最长子字符串 1371.每个元音包含偶数次的最长子字符串 参考博主的讲解 思路分析&#xff1a;就是得使用前缀和记录情况&#xff0c;dp[i][j]表示s[0] 到s[i] 中&…...

稀疏混合专家架构语言模型(MoE)

注&#xff1a;本文为 “稀疏混合专家架构语言模型&#xff08;MoE&#xff09;” 相关文章合辑。 手把手教你&#xff0c;从零开始实现一个稀疏混合专家架构语言模型&#xff08;MoE&#xff09; 机器之心 2024年02月11日 12:21 河南 选自huggingface 机器之心编译 机器之心…...

深入理解 `box-sizing: border-box;`:CSS 布局的利器

深入理解 box-sizing: border-box;&#xff1a;CSS 布局的利器 默认行为示例代码 使用 box-sizing: border-box;示例代码 全局应用 box-sizing: border-box;示例代码 实际应用场景1. 表单布局2. 网格布局 总结 在 CSS 中&#xff0c;box-sizing 属性决定了元素的总宽度和高度是…...

MySQL不适合创建索引的11种情况

文章目录 前言1. **数据量小的表**2. **频繁更新的列**3. **低选择性的列**4. **频繁插入和删除的表**5. **查询中很少使用的列**6. **大文本或BLOB列**7. **复合索引中未使用的前导列**8. **频繁进行批量插入的表**9. **查询返回大部分数据的表**10. **临时表**11. **列值频繁…...

shell呈现数据——在脚本中重定向

重定向输出 只需简单地重定向相应的文件描述符&#xff0c;就可以在脚本中用文件描述符STDOUT和STDERR在多个位置生成输出。在脚本中重定向输出的方法有两种。 临时重定向每一行。永久重定向脚本中的所有命令。 下面将具体展示这两种方法的工作原理。 1.临时重定向 如果你…...

vector容器(详解)

本文最后是模拟实现全部讲解&#xff0c;文章穿插有彩色字体&#xff0c;是我总结的技巧和关键 1.vector的介绍及使用 1.1 vector的介绍 https://cplusplus.com/reference/vector/vector/&#xff08;vector的介绍&#xff09; 了解 1. vector是表示可变大小数组的序列容器。…...

【初/高中生讲机器学习】0. 本专栏 “食用” 指南——写在一周年之际⭐

创建时间&#xff1a;2025-01-27 首发时间&#xff1a;2025-01-29 最后编辑时间&#xff1a;2025-01-29 作者&#xff1a;Geeker_LStar 你好呀~这里是 Geeker_LStar 的人工智能学习专栏&#xff0c;很高兴遇见你~ 我是 Geeker_LStar&#xff0c;一名高一学生&#xff0c;热爱计…...

SAP SD学习笔记28 - 请求计划(开票计划)之2 - Milestone请求(里程碑开票)

上一章讲了请求计划&#xff08;开票计划&#xff09;中的 定期请求。 SAP SD学习笔记27 - 请求计划(开票计划)之1 - 定期请求-CSDN博客 本章继续来讲请求计划&#xff08;开票计划&#xff09;的其他内容&#xff1a; Milestone请求(里程碑请求)。 目录 1&#xff0c;Miles…...

【PyTorch介绍】

PyTorch 是什么&#xff1f; PyTorch 是一个开源的深度学习框架&#xff0c;由 Facebook 的人工智能研究实验室&#xff08;FAIR&#xff09;开发和维护。它是一个基于 Python 的库&#xff0c;专为深度学习和人工智能研究设计&#xff0c;支持动态计算图&#xff08;dynamic …...

语言月赛 202412【正在联系教练退赛】题解(AC)

》》》点我查看「视频」详解》》》 [语言月赛 202412] 正在联系教练退赛 题目背景 在本题中&#xff0c;我们称一个字符串 y y y 是一个字符串 x x x 的子串&#xff0c;当且仅当从 x x x 的开头和结尾删去若干个&#xff08;可以为 0 0 0 个&#xff09;字符后剩余的字…...

【C++】B2122 单词翻转

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 &#x1f4af;一、我的做法代码实现&#xff1a;代码解析思路分析 &#x1f4af;二、老师的第一种做法代码实现&a…...

redis基本数据结构

基本数据结构 String String是Redis中最常见的数据存储类型&#xff1a; 其基本编码方式是RAW&#xff0c;基于简单动态字符串&#xff08;SDS&#xff09;实现&#xff0c;存储上限为512mb。 如果存储的SDS长度小于44字节&#xff0c;则会采用EMBSTR编码&#xff0c;此时ob…...

基于STM32景区环境监测系统的设计与实现(论文+源码)

1系统方案设计 根据系统功能的设计要求&#xff0c;展开基于STM32景区环境监测系统设计。如图2.1所示为系统总体设计框图。系统以STM32单片机作为系统主控模块&#xff0c;通过DHT11传感器、MQ传感器、声音传感器实时监测景区环境中的温湿度、空气质量以及噪音数据。系统监测环…...

使用冒泡排序模拟实现qsort函数

1.冒泡排序 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h>int main() {int arr[] { 0,2,5,3,4,8,9,7,6,1 };int sz sizeof(arr) / sizeof(arr[0]);//冒泡排序一共排序 sz-1 趟for (int i 0; i < sz - 1; i){//标志位&#xff0c;如果有序&#xff0c;直接…...

探秘Linux IO虚拟化:virtio的奇幻之旅

在当今数字化时代&#xff0c;虚拟化技术早已成为推动计算机领域发展的重要力量。想象一下&#xff0c;一台物理主机上能同时运行多个相互隔离的虚拟机&#xff0c;每个虚拟机都仿佛拥有自己独立的硬件资源&#xff0c;这一切是如何实现的呢&#xff1f;今天&#xff0c;就让我…...

在React中使用redux

一、首先安装两个插件 1.Redux Toolkit 2.react-redux 第一步&#xff1a;创建模块counterStore 第二步&#xff1a;在store的入口文件进行子模块的导入组合 第三步&#xff1a;在index.js中进行store的全局注入 第四步&#xff1a;在组件中进行使用 第五步&#xff1a;在组件中…...

从 C 到 C++:理解结构体中字符串的存储与操作

对于刚入门 C/C 的程序员来说&#xff0c;字符串的存储和操作可能是个容易混淆的知识点。在 C 中&#xff0c;std::string 提供了非常友好的接口&#xff0c;我们可以轻松地在结构体中使用字符串类型&#xff0c;无需关注底层细节。然而&#xff0c;在 C 语言中&#xff0c;字符…...

2.3学习总结

图&#xff1a; 1.图的基本概念 2.图的存储和遍历 3.最小生成树 4.最短路径 5.拓扑排序和关键路径 一、图的基本概念 图的定义&#xff1a;不允许没有顶点&#xff0c;但边集可以为空 {无向图 {有向图&#xff1a;边弧&#xff0c;弧头&#xff08;有箭头&#xff09;…...

wordpress代码结构解析

WordPress 是一个基于 PHP 和 MySQL 的开源内容管理系统&#xff08;CMS&#xff09;&#xff0c;广泛用于构建网站和博客。要解析 WordPress 代码&#xff0c;首先需要了解其核心结构、主要文件和常用的函数。以下是 WordPress 代码解析的基本指南&#xff1a; --- ### 1. *…...

使用 Numpy 自定义数据集,使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测,对预测结果计算精确度和召回率及F1分数

1. 导入必要的库 首先&#xff0c;导入我们需要的库&#xff1a;Numpy、Pytorch 和相关工具包。 import numpy as np import torch import torch.nn as nn import torch.optim as optim from sklearn.metrics import accuracy_score, recall_score, f1_score2. 自定义数据集 …...

FPGA|例化生成的PLL功能IP核

1、例化上一篇文章中调用的IP核&#xff0c;新建文件PLL_test.v 2、代码如图 timescale 1ns / 1ps module PLL_test(input clk,input rst_n,output clkout0,output clkout1,output clkout2,output clkout3,output clkout4);wire locked;PLL pll_inst(.inclk0(clk),.c0(clkout0)…...

K个不同子数组的数目--滑动窗口--字节--亚马逊

Stay hungry, stay foolish 题目描述 给定一个正整数数组 nums和一个整数 k&#xff0c;返回 nums 中 「好子数组」 的数目。 如果 nums 的某个子数组中不同整数的个数恰好为 k&#xff0c;则称 nums 的这个连续、不一定不同的子数组为 「好子数组 」。 例如&#xff0c;[1,2,…...

手机连接WIFI可以上网,笔记本电脑连接WIFI却不能上网? 解决方法?

原因&#xff1a;DNS受污染了 解决办法 step 1&#xff1a;清空域名解析记录&#xff08;清空DNS&#xff09; ipconfig /flushdns (Windows cmd命令行输入) step 2&#xff1a;重新从DHCP 获取IP ipconfig /release&#xff08;释放当前IP地址&#xff09; ipconfig /renew &…...

大模型综述一镜到底(全文八万字) ——《Large Language Models: A Survey》

论文链接&#xff1a;https://arxiv.org/abs/2402.06196 摘要&#xff1a;自2022年11月ChatGPT发布以来&#xff0c;大语言模型&#xff08;LLMs&#xff09;因其在广泛的自然语言任务上的强大性能而备受关注。正如缩放定律所预测的那样&#xff0c;大语言模型通过在大量文本数…...

C语言按位取反【~】详解,含原码反码补码的0基础讲解【原码反码补码严格意义上来说属于计算机组成原理的范畴,不过这也是学好编程初级阶段的必修课】

目录 概述【适合0基础看的简要描述】&#xff1a; 上述加粗下划线的内容提取版&#xff1a; 从上述概述中提取的核心知识点&#xff0c;需背诵&#xff1a; 整数【包含整数&#xff0c;负整数和0】的原码反码补码相互转换的过程图示&#xff1a; 过程详细刨析&#xff1a;…...

本地部署与使用SenseVoice语音大模型简析

前言 SenseVoice 是一种语音基础模型&#xff0c;具有多种语音理解功能&#xff0c;包括自动语音识别 (ASR)、口语识别 (LID)、语音情感识别 (SER) 和音频事件检测 (AED)。本博客将指导您安装和使用 SenseVoice 模型&#xff0c;使其尽可能方便用户使用。 Github 仓库链接: ht…...