【openGL教程08】基于C++的着色器(02)

一、说明

        着色器是openGL渲染的重要内容,客户如果想自我实现渲染灵活性,可以用着色器进行编程,这种程序小脚本被传送到GPU的显卡内部,起到动态灵活的着色作用。

二、着色器简述

        正如“Hello Triangle”一章中提到的,着色器是驻留在 GPU 上的小程序。这些程序针对图形管道的每个特定部分运行。着色器本质:

  • 从基本意义上讲,着色器只不过是将输入转换为输出的程序。
  • 着色器也是非常孤立的程序,因为它们不允许相互通信;他们唯一的沟通方式是通过输入和输出。

        在上一章中,我们简要介绍了着色器的表面以及如何正确使用它们。现在我们将以更通用的方式解释着色器,特别是 OpenGL 着色语言。

三、着色器语言GLSL

        着色器是用类 C 语言 GLSL 编写的。 GLSL 专为图形使用而定制,包含专门针对矢量和矩阵操作的有用功能。

        着色器总是以版本声明开始,后面是输入和输出变量、制服及其相关的列表。主要的功能。每个着色器的入口点位于其主要的函数,我们处理任何输入变量并将结果输出到其输出变量中。如果您不知道什么是uniform,请不要担心,我们很快就会介绍。

3.1 GLSL简述

        着色器通常具有以下结构:


#version version_number
in type in_variable_name;
in type in_variable_name;

out type out_variable_name;
  
uniform type uniform_name;
  
void main()
{
  // process input(s) and do some weird graphics stuff
  ...
  // output processed stuff to output variable
  out_variable_name = weird_stuff_we_processed;
}

        当我们具体讨论顶点着色器时,每个输入变量也称为顶点属性。我们可以声明的顶点属性的最大数量受到硬件的限制。 OpenGL 保证始终有至少 16 个 4 分量顶点属性可用,意味着可以最多同时实现16个场景对象。但某些硬件可能允许更多,您可以通过查询GL_MAX_VERTEX_ATTRIBS来检索:

1)C++语言
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;

  2)python语言

  maxVertexAttribs= glGetIntegerv(GL_MAX_VERTEX_ATTRIBS)
  print( maxVertexAttribs )

-> 16
这通常会返回最小值16,这对于大多数目的来说应该足够了。

3.2 数据类型- 向量

        与任何其他编程语言一样,GLSL 具有用于指定我们要使用的变量类型的数据类型。 GLSL 拥有我们从 C: intfloatdoubleuint等语言中了解到的大多数默认基本类型 bool。 GLSL 还具有我们将经常使用的两种容器类型,即vectorsmatrices。我们将在后面的章节中讨论矩阵。

        GLSL 中的向量是用于刚才提到的任何基本类型的 2,3 或 4 个分量容器。它们可以采用以下形式(n代表组件的数量):

  • vecn:默认的浮点数向量n
  • bvecn:布尔向量n
  • ivecn:整数向量n
  • uvecn:无符号整数向量n
  • dvecn:双分量向量n

        大多数时候我们会使用基本的,vecn因为浮动足以满足我们的大多数目的。

# 示例
layout (location = 0) in vec3 Position;

  vecn.x可以通过向量的x第一个分量 来访问向量的分量。您可以使用.x.y.z.w分别访问它们的第一个、第二个、第三个和第四个组件。 GLSL 还允许您使用rgba颜色或stpq纹理坐标,访问相同的组件。

3.3 向量的相互搅拌生成新向量

        矢量数据类型允许进行一些有趣且灵活的组件选择,称为搅动。 Swizzling 允许我们使用如下语法:

vec2 someVec;
vec4 differentVec = someVec.xyxx;     // 通过someVec​​​​​​​的分量组合,构成differentVec
vec3 anotherVec = differentVec.zyw;   // 缩减differentVec成新向量anotherVec
vec4 otherVec = someVec.xxxx + anotherVec.yxzy; //拼合someVec和anotherVec构建otherVec

总之。搅拌是很灵活的。
     您可以使用最多 4 个字母的任意组合来创建新向量(相同类型),只要原始向量具有这些组件即可;例如,在a 的组件vec2中,不允许访问.z
      我们还可以将向量作为参数传递给不同的向量构造函数调用,从而减少所需参数的数量:vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);

        因此,向量是一种灵活的数据类型,我们可以将其用于各种输入和输出。在整本书中,您将看到大量关于如何创造性地管理向量的示例。

3.4 来龙去脉

        着色器本身就是很好的小程序,但它们是整体的一部分,因此我们希望在各个着色器上有输入和输出,以便我们可以移动东西。 GLSL 专门为此目的定义了in和关键字。out每个着色器都可以使用这些关键字指定输入和输出,并且只要输出变量与它们传递的下一个着色器阶段的输入变量匹配即可。不过,顶点着色器和片段着色器略有不同。

  • 顶点着色器应该接收某种形式的输入,否则它将非常低效。
  • 顶点着色器的输入有所不同,因为它直接从顶点数据接收输入。
  • 为了定义顶点数据的组织方式,我们使用位置元数据指定输入变量,以便我们可以在 CPU 上配置顶点​​属性。我们在前一章中已经看到了这一点layout (location = 0)。因此,顶点着色器需要为其输入提供额外的布局规范,以便我们可以将其与顶点数据链接起来。
        也可以省略layout (location = 0)说明符并通过以下方式查询 OpenGL 代码中的属性位置:glGetAttribLocation,但我更愿意将它们设置在顶点着色器中。它更容易理解并为您(和 OpenGL)节省一些工作。

        另一个例外是片段着色器需要vec4颜色输出变量,因为片段着色器需要生成最终的输出颜色。如果您未能在片段着色器中指定输出颜色,这些片段的颜色缓冲区输出将是未定义的(这通常意味着 OpenGL 会将它们渲染为黑色或白色)。

        因此,如果我们想要将数据从一个着色器发送到另一个着色器,我们必须在发送着色器中声明一个输出,并在接收着色器中声明一个类似的输入。当两侧的类型和名称相同时,OpenGL 会将这些变量链接在一起,然后就可以在着色器之间发送数据(这是在链接程序对象时完成的)。为了向您展示这在实践中是如何工作的,我们将更改上一章中的着色器,让顶点着色器决定片段着色器的颜色。

1)示范顶点着色器

#version 330 core
layout (location = 0) in vec3 aPos; // the position variable has attribute position 0
  
out vec4 vertexColor; // specify a color output to the fragment shader

void main()
{
    gl_Position = vec4(aPos, 1.0); // see how we directly give a vec3 to vec4's constructor
    vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // set the output variable to a dark-red color
}

2)示范片段着色器

#version 330 core
out vec4 FragColor;
  
in vec4 vertexColor; // the input variable from the vertex shader (same name and same type)  

void main()
{
    FragColor = vertexColor;
} 

        您可以看到我们声明了一个vertexColor变量作为vec4我们在顶点着色器中设置的输出,并且我们在片段着色器中声明了一个类似的vertexColor输入。由于它们具有相同的类型和名称,因此片段着色器中的vertexColor链接到顶点着色器中的vertexColor。因为我们在顶点着色器中将颜色设置为深红色,所以生成的片段也应该是深红色。下图显示了输出:

        我们开始吧!我们刚刚设法将一个值从顶点着色器发送到片段着色器。让我们稍微调味一下,看看我们是否可以将颜色从我们的应用程序发送到片段着色器!

3.5  uniform(全局统一变量)

        制服是将数据从 CPU 上的应用程序传递到 GPU 上的着色器的另一种方法。然而,制服与顶点属性相比略有不同。首先,制服是全局的。全局,意味着统一变量对于每个着色器程序对象都是唯一的,并且可以在着色器程序中的任何阶段从任何着色器访问。其次,无论您将统一值设置为多少,统一都会保留其值,直到重置或更新为止。

        要在 GLSL 中声明统一,我们只需将uniform关键字添加到具有类型和名称的着色器即可。从那时起,我们可以在着色器中使用新声明的制服。让我们看看这次我们是否可以通过统一设置三角形的颜色:


#version 330 core
out vec4 FragColor;
  
uniform vec4 ourColor; // we set this variable in the OpenGL code.

void main()
{
    FragColor = ourColor;
}   

        我们在片段着色器中声明了一个统一的vec4 ourColor,并将片段的输出颜色设置为该统一值的内容。由于制服是全局变量,我们可以在任何我们想要的着色器阶段定义它们,因此不需要再次通过顶点着色器来获取片段着色器的内容。我们不在顶点着色器中使用此统一,因此无需在那里定义它。

        如果您声明的统一变量未在 GLSL 代码中的任何地方使用,编译器会默默地从编译版本中删除该变量,这会导致一些令人沮丧的错误;记住这一点!

        制服目前是空的;我们还没有向制服添加任何数据,所以让我们尝试一下。我们首先需要在着色器中找到统一属性的索引/位置。一旦我们有了制服的索引/位置,我们就可以更新它的值。我们不要将单一颜色传递给片段着色器,而是通过随着时间的推移逐渐改变颜色来增加趣味性:


float timeValue = glfwGetTime();
float greenValue = (sin(timeValue) / 2.0f) + 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

        首先,我们通过以下方式检索运行时间(以秒为单位):glfw获取时间()。然后我们在0.0-范围内改变1.0颜色罪函数并将结果存储在greenValue中。

        然后我们使用以下命令查询ourColor制服 的位置glGetUniformLocation。我们向查询函数提供着色器程序和制服名称(我们要从中检索位置)。如果glGetUniformLocation返回后-1,找不到位置。最后我们可以使用以下命令设置统一值统一4f功能。请注意,查找统一位置并不要求您首先使用着色器程序,但更新统一确实需要您首先使用该程序(通过调用glUse程序),因为它在当前活动的着色器程序上设置统一。

        因为 OpenGL 的核心是一个 C 库,所以它没有对函数重载的本机支持,因此只要可以使用不同类型调用函数,OpenGL 就会为所需的每种类型定义新函数;统一就是一个完美的例子。该函数需要您要设置的制服类型的特定后缀。一些可能的后缀是:

  • f:该函数期望 afloat作为其值。
  • i:该函数需要 anint作为其值。
  • ui:该函数需要 anunsigned int作为其值。
  • 3f:该函数期望 3 floats 作为其值。
  • fv:该函数需要一个float向量/数组作为其值。
        每当您想要配置 OpenGL 的选项时,只需选择与您的类型相对应的重载函数即可。在我们的例子中,我们想要单独设置 4 个制服浮点数,因此我们通过以下方式传递数据统一4f(请注意,我们也可以使用该fv版本)。

        现在我们知道如何设置统一变量的值,我们可以使用它们进行渲染。如果我们希望颜色逐渐变化,我们希望每一帧都更新这个制服,否则如果我们只设置一次,三角形将保持单一的纯色。因此,我们计算greenValue并更新每次渲染迭代的统一值:


while(!glfwWindowShouldClose(window))
{
    // input
    processInput(window);

    // render
    // clear the colorbuffer
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // be sure to activate the shader
    glUseProgram(shaderProgram);
  
    // update the uniform color
    float timeValue = glfwGetTime();
    float greenValue = sin(timeValue) / 2.0f + 0.5f;
    int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
    glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

    // now render the triangle
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);
  
    // swap buffers and poll IO events
    glfwSwapBuffers(window);
    glfwPollEvents();
}

        该代码是对先前代码的相对简单的改编。这次,我们在绘制三角形之前每帧更新一个统一值。如果正确更新制服,您应该会看到三角形的颜色逐渐从绿色变为黑色,然后又变回绿色。

        如果您遇到困难, 请查看此处的 源代码。

        正如您所看到的,制服是一个有用的工具,用于设置可能更改每帧的属性,或者用于在应用程序和着色器之间交换数据,但是如果我们想为每个顶点设置颜色怎么办?在这种情况下,我们必须声明与顶点一样多的制服。更好的解决方案是在顶点属性中包含更多数据,这就是我们现在要做的。

3.6 更多属性!

        我们在上一章中看到了如何填充 VBO、配置顶点属性指针并将其全部存储在 VAO 中。这次,我们还想将颜色数据添加到顶点数据中。我们将把颜色数据作为 3float秒添加到顶点数组中。我们分别为三角形的每个角指定红色、绿色和蓝色:


float vertices[] = {
    // positions         // colors
     0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,   // bottom right
    -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,   // bottom left
     0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f    // top 
};    

        由于我们现在有更多数据要发送到顶点着色器,因此有必要调整顶点着色器以也接收我们的颜色值作为顶点属性输入。请注意,我们使用布局说明符 将aColor属性的位置设置为 1:


#version 330 core
layout (location = 0) in vec3 aPos;   // the position variable has attribute position 0
layout (location = 1) in vec3 aColor; // the color variable has attribute position 1
  
out vec3 ourColor; // output a color to the fragment shader

void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor; // set ourColor to the input color we got from the vertex data
}       

        由于我们不再使用统一的片段颜色,但现在使用ourColor输出变量,我们还必须更改片段着色器:


#version 330 core
out vec4 FragColor;  
in vec3 ourColor;
  
void main()
{
    FragColor = vec4(ourColor, 1.0);
}

        因为我们添加了另一个顶点属性并更新了 VBO 的内存,所以我们必须重新配置顶点属性指针。 VBO 内存中的更新数据现在看起来有点像这样:

VBO 内位置和颜色的交错数据可通过 <function id='30'>glVertexAttribPointer</function> 配置

        知道当前布局我们可以更新顶点格式glVertexAttribPointer:


// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));
glEnableVertexAttribArray(1);

        的前几个论点glVertexAttribPointer都比较简单。这次我们在属性 location 上配置顶点​​属性1。颜色值的大小为3 floats,我们不对这些值进行标准化。

        由于我们现在有两个顶点属性,我们必须重新计算步幅值。为了获得x数据数组中的下一个属性值(例如位置向量的下一个分量),我们必须将6 floats 向右移动,三个用于位置值,三个用于颜色值。这给我们提供了 a 大小的 6 倍float(以字节为单位)的步幅值 (= 24bytes)。
另外,这次我们必须指定一个偏移量。对于每个顶点,位置顶点属性是第一个,因此我们声明偏移量0。颜色属性在位置数据之后开始,因此偏移量以3 * sizeof(float)字节 (= 12bytes) 为单位。

        运行应用程序应该会产生以下图像:

        如果您遇到困难, 请查看此处的 源代码。

        该图像可能并不完全符合您的预期,因为我们只提供了 3 种颜色,而不是我们现在看到的巨大调色板。这都是某种叫做的东西的结果片段插值在片段着色器中。渲染三角形时,光栅化阶段通常会产生比最初指定的顶点多得多的片段。然后,光栅化器根据这些片段在三角形上的位置来确定每个片段的位置。
        基于这些立场,插值所有片段着色器的输入变量。举例来说,我们有一条线,其上部点为绿色,下部点为蓝色。如果片段着色器在位于该线位置周围的片段上运行70%,则其生成的颜色输入属性将是绿色和蓝色的线性组合;更准确地说:30%蓝色和70%绿色。

        这正是三角区发生的情况。我们有 3 个顶点,因此有 3 种颜色,从三角形的像素来看,它可能包含大约 50000 个片段,片段着色器在这些像素之间插入颜色。如果你仔细观察颜色,你会发现这一切都是有道理的:红色到蓝色首先变成紫色,然后变成蓝色。片段插值应用于片段着色器的所有输入属性。

四、建立我们自己的着色器类

        编写、编译和管理着色器可能相当麻烦。作为着色器主题的最后一步,我们将通过构建一个着色器类来让我们的生活变得更轻松一些,该类从磁盘读取着色器、编译和链接它们、检查错误并且易于使用。这也让您了解如何将迄今为止学到的一些知识封装到有用的抽象对象中。

        我们将完全在头文件中创建着色器类,主要是出于学习目的和可移植性。让我们首先添加所需的包含并定义类结构:


#ifndef SHADER_H
#define SHADER_H

#include <glad/glad.h> // include glad to get all the required OpenGL headers
  
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
  

class Shader
{
public:
    // the program ID
    unsigned int ID;
  
    // constructor reads and builds the shader
    Shader(const char* vertexPath, const char* fragmentPath);
    // use/activate the shader
    void use();
    // utility uniform functions
    void setBool(const std::string &name, bool value) const;  
    void setInt(const std::string &name, int value) const;   
    void setFloat(const std::string &name, float value) const;
};
  
#endif
        我们用了几个预处理器指令在头文件的顶部。使用这些小代码行可以通知编译器仅包含并编译此头文件(如果尚未包含该头文件),即使多个文件包含着色器头也是如此。这可以防止链接冲突。

        着色器类保存着色器程序的ID。它的构造函数分别需要顶点着色器和片段着色器源代码的文件路径,我们可以将其作为简单文本文件存储在磁盘上。为了添加一些额外的内容,我们还添加了一些实用函数来让我们的生活稍微轻松一些:使用激活着色器程序,并且所有放...函数查询统一位置并设置其值。

五、从文件中读取

        我们使用 C++ 文件流将文件内容读取到多个string对象中:


Shader(const char* vertexPath, const char* fragmentPath)
{
    // 1. retrieve the vertex/fragment source code from filePath
    std::string vertexCode;
    std::string fragmentCode;
    std::ifstream vShaderFile;
    std::ifstream fShaderFile;
    // ensure ifstream objects can throw exceptions:
    vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
    fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
    try 
    {
        // open files
        vShaderFile.open(vertexPath);
        fShaderFile.open(fragmentPath);
        std::stringstream vShaderStream, fShaderStream;
        // read file's buffer contents into streams
        vShaderStream << vShaderFile.rdbuf();
        fShaderStream << fShaderFile.rdbuf();		
        // close file handlers
        vShaderFile.close();
        fShaderFile.close();
        // convert stream into string
        vertexCode   = vShaderStream.str();
        fragmentCode = fShaderStream.str();		
    }
    catch(std::ifstream::failure e)
    {
        std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
    }
    const char* vShaderCode = vertexCode.c_str();
    const char* fShaderCode = fragmentCode.c_str();
    [...]

        接下来我们需要编译并链接着色器。请注意,我们还会检查编译/链接是否失败,如果失败,则打印编译时错误。这在调试时非常有用(您最终将需要这些错误日志):


// 2. compile shaders
unsigned int vertex, fragment;
int success;
char infoLog[512];
   
// vertex Shader
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
// print compile errors if any
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if(!success)
{
    glGetShaderInfoLog(vertex, 512, NULL, infoLog);
    std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
};
  
// similiar for Fragment Shader
[...]
  
// shader Program
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
glLinkProgram(ID);
// print linking errors if any
glGetProgramiv(ID, GL_LINK_STATUS, &success);
if(!success)
{
    glGetProgramInfoLog(ID, 512, NULL, infoLog);
    std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
  
// delete the shaders as they're linked into our program now and no longer necessary
glDeleteShader(vertex);
glDeleteShader(fragment);

        这使用函数很简单:


void use() 
{ 
    glUseProgram(ID);
}  

对于任何统一设置函数也类似:


void setBool(const std::string &name, bool value) const
{         
    glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value); 
}
void setInt(const std::string &name, int value) const
{ 
    glUniform1i(glGetUniformLocation(ID, name.c_str()), value); 
}
void setFloat(const std::string &name, float value) const
{ 
    glUniform1f(glGetUniformLocation(ID, name.c_str()), value); 
} 

        这样我们就有了一个完整的着色器类。使用着色器类相当简单;我们创建一个着色器对象一次,从那时起就开始使用它:


Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.fs");
[...]
while(...)
{
    ourShader.use();
    ourShader.setFloat("someUniform", 1.0f);
    DrawStuff();
}

        在这里,我们将顶点和片段着色器源代码存储在两个名为shader.vs和的文件中shader.fs。您可以随意命名着色器文件;我个人认为这些扩展.vs非常.fs直观。

您可以在此处找到使用我们新创建的着色器类的 源代码。请注意,您可以单击着色器文件路径来查找着色器的源代码。

六、练习

  1. 调整顶点着色器,使三角形颠倒:解决方案。
  2. 通过统一指定水平偏移,并使用此偏移值将三角形移动到顶点着色器中的屏幕右侧:解决方案。
  3. 使用关键字将顶点位置输出到片段着色器out,并将片段的颜色设置为等于该顶点位置(查看如何在三角形上插值顶点位置值)。一旦你成功做到了这一点;尝试回答以下问题:为什么三角形的左下角是黑色的?:解决方案。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NXP实战笔记(十):S32K3xx基于RTD-SDK在S32DS上配置CAN通信

目录 1、概述 2、SDK配置 2.1、配置目标 2.2、CAN配置 3、代码实现 4、测试结果 1、概述 S32K3xx的FlexCan与之前的S32K1xx很相似,Can的中断掩码寄存器(IMASK3)与中断标志位寄存器(IFLAG3)依赖于邮箱数。 FlexCan配置实例如下 FlexCan的整体图示如下 Protocol Engine…

AI论文速读 | 【综述】(LLM4TS)大语言模型用于时间序列

题目&#xff1a;Large Language Models for Time Series: A Survey 作者&#xff1a;Xiyuan Zhang , Ranak Roy Chowdhury , Rajesh K. Gupta and Jingbo Shang 机构&#xff1a;加州大学圣地亚哥分校&#xff08;UCSD&#xff09; 网址&#xff1a;https://arxiv.org/abs/…

OPENSSL-PKCS7入门知识介绍

1 PKCS7数据结构说明 p7包括6种数据内容&#xff1a;数据(data),签名数据&#xff08;sign&#xff09;&#xff0c;数字信封数据&#xff08;enveloped&#xff09;&#xff0c;签名数字信封数据&#xff08;signed_and_enveloped&#xff09;&#xff0c;摘要数据&#xff08…

Zookeeper客户端命令、JAVA API、监听原理、写数据原理以及案例

1. Zookeeper节点信息 指定服务端&#xff0c;启动客户端命令&#xff1a; bin/zkCli.sh -server 服务端主机名:端口号 1&#xff09;ls / 查看根节点下面的子节点 ls -s / 查看根节点下面的子节点以及根节点详细信息 其中&#xff0c;cZxid是创建节点的事务id&#xff0c…

RabbitMq:RabbitMq 主从镜像模式②

一、模式思想 所有的技术设计思想&#xff0c;基本都在两点上下功夫&#xff1a;1. 生产力上 2. 稳定性上 二、集群模式 今天又有人问起来rabbitmq的高可用方式&#xff0c;因为和常见的主从模式有点区别&#xff0c;所以就记录一下。 rabbitmq集群的镜像队列提供了更高级的主…

Windows 开机启动 | 启动项管理

开机启动 开机启动对于保障系统的正常运行、提高用户体验、及时响应系统事件以及自动化管理和维护系统都具有重要意义。合理管理开机启动项&#xff0c;系统启动时自动运行必要的程序和服务。 但是&#xff0c;随着操作系统使用时长的增加启动项越来越多&#xff0c;而且还很难…

分布式知识整理

分布式锁 以商场系统超卖现象举例 超卖现象一 现象&#xff1a; 商品卖出数量超出了库存数量。 产生原因&#xff1a; 扣减库存的动作在程序中进行&#xff0c;在程序中计算剩余库存&#xff0c;在并发场景下&#xff0c;导致库存计算错误。 代码复现 es.shutdown(); cycl…

2024年2月16日优雅草蜻蜓API大数据服务中心v1.1.1大更新-UI全新大改版采用最新设计ui·增加心率计算器·退休储蓄计算·贷款还款计算器等数接口

2024年2月16日优雅草蜻蜓API大数据服务中心v1.1.1大更新-UI全新大改版采用最新设计ui增加心率计算器退休储蓄计算贷款还款计算器等数接口 更新日志 前言&#xff1a;本次更新中途跨越了很多个版本&#xff0c;其次本次ui大改版-同步实时发布教程《带9.7k预算的实战项目layuiph…

SpringBoot3整合Swagger3,访问出现404错误问题(未解决)

秉承着能用就用新的的理念&#xff0c;在JDK、SpringBoot、SpringCloud版本的兼容性下&#xff0c;选择了Java17、SpringBoot3.0.2整合Swagger3。 代码编译一切正常&#xff0c;Swagger的Bean也能加载&#xff0c;到了最后访问前端页面swagger-ui的时候出现404。 根据网上资料…

jmeter 压测数据库

当前版本&#xff1a; jmeter 5.6.3mysql 5.7.39 简介 JMeter 是一个开源的 Java 应用程序&#xff0c;主要用于进行性能测试和负载测试。它支持多种协议&#xff0c;包括但不限于 HTTP、HTTPS、FTP、JDBC 以及各种 Web Services。对于数据库的压力测试可以使用 JDBC 协议与数…

字符函数和字符串函数(C语言进阶)(二)

目录 前言 1.4 strcmp 1.5 strncpy strncat strncmp 1.6 strstr 前言 C语言中对字符和字符串的处理是很频繁的&#xff0c;但是c语言本身是没有字符串类型的&#xff0c;字符串通常放在常量字符串中或着字符数组中。 字符串常量适用于那些对它不做修改的字符串函数。 1…

matlab新能源汽车三自由度操纵稳定性分析及优化

1、内容简介 略 可以交流、咨询、答疑 55-新能源汽车三自由度操纵稳定性分析及优化 2、内容说明 略 摘 要 电动化是节能减排、寻求替代能源的最佳途径&#xff0c;已成为行业共识&#xff0c;论文基于江西科技学院桑塔纳轿车油改气项目&#xff0c;在拆除发动机、变速…

低功耗设计——门控时钟

1. 前言 芯片功耗组成中&#xff0c;有高达40%甚至更多是由时钟树消耗掉的。这个结果的原因也很直观&#xff0c;因为这些时钟树在系统中具有最高的切换频率&#xff0c;而且有很多时钟buffer&#xff0c;而且为了最小化时钟延时&#xff0c;它们通常具有很高的驱动强度。此外&…

ChatGPT调教指南 | 咒语指南 | Prompts提示词教程(三)

在人工智能成为我们日常互动中无处不在的一部分的时代&#xff0c;与大型语言模型(llm)有效沟通的能力是无价的。“良好提示的26条原则”为优化与这些复杂系统的交互提供了全面的指导。本指南证明了人类和人工智能之间的微妙关系&#xff0c;强调清晰、专一和结构化的沟通方法。…

matlab绘制雷达图和二维FFT变换图

1、内容简介 略 49-可以交流、咨询、答疑 matlab绘制雷达图和二维FFT变换图 NMO组及NORMAL组 RNFL层、GCL层、IPL层、GCC层、ORL层做雷达图&#xff08;共10张&#xff09; 2、内容说明 略 NMO组及NORMAL组 RNFL层、GCL层、IPL层、GCC层、ORL层请分别做雷达图&#xff08…

YOLOv8改进 | Conv篇 | 全新的SOATA轻量化下采样操作ADown(参数量下降百分之二十,附手撕结构图)

一、本文介绍 本文给大家带来的改进机制是利用2024/02/21号最新发布的YOLOv9其中提出的ADown模块来改进我们的Conv模块,其中YOLOv9针对于这个模块并没有介绍,只是在其项目文件中用到了,我将其整理出来用于我们的YOLOv8的项目,经过实验我发现该卷积模块(作为下采样模块)…

QT DAY4 事件

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);speecher new QTextToSpeech(this); }Widget::~Widget() {delete ui; }//显示系统时间 void Widget::timerEve…

栈和堆什么意思,Rust所有权机制又是什么

栈和堆什么意思 栈&#xff1a;存储基本数据类型和引用数据类型的指针引用(地址)&#xff0c;基本数据类型占据固定大小的内存空间。 堆&#xff1a;存储引用数据类型的值&#xff0c;引用数据类型包括对象&#xff0c;数组和函数&#xff0c;在堆中&#xff0c;引用数据类型…

8k中英双语文本嵌入模型效果初探

一 模型介绍 向量模型用于生成向量表示&#xff0c;被广泛应用于检索、分类、聚类或语义匹配等传统的自然语言处理任务。到了大模型时代&#xff0c;由于上下文长度的限制&#xff0c;需要压缩、存储和查询大量的信息&#xff0c;这就需要用到向量模型对输入的文本进行向量化表…

HTB-Bizness

一、信息收集 访问ip自动跳转域名&#xff0c;host绑定域名后访问 目录爆破 有一个登录目录&#xff0c;访问发现是apahce ofbiz登录页面 发现存在漏洞 二、漏洞利用 在github上找到了图形化利用工具 使用工具反弹shell 得到flag 三、权限提升 从本地利用python开启http服务…