OpenGL 4.0的Tessellation Shader(细分曲面着色器)

news2024/9/28 1:15:58

细分曲面着色器(Tessellation Shader)处于顶点着色器阶段的下一个阶段,我们可以看以下链接的OpenGL渲染流水线的图:Rendering Pipeline Overview。它是由ATI在2001年率先设计出来的。


目录

  • 细分曲面着色器
  • 细分曲面Patch
  • 细分曲面控制着色器
    • 生成输出patch顶点
    • 细分曲面控制着色器变量
  • 控制细分曲面
    • 四边形细分曲面
    • 等值线细分曲面
  • 旁通细分曲面控制着色器
  • 细分曲面图元生成
    • 抽象patch
  • 细分曲面计算着色器
    • 指定图元生成域
    • 指定生成图元的面部朝向
    • 指定细分曲面坐标的空间
    • 额外的细分曲面计算着色器layout选项
    • 指定一个顶点的位置
    • 细分曲面计算着色器变量
  • 参考资料
  • 完整工程项目代码

细分曲面着色器

直到这个阶段,对于操作几何图元而言,只有顶点着色器对我们可用。尽管使用顶点着色器可以使用不少图形技术,不过顶点着色器也确实存在一些限制。一个就是它们在执行过程中无法创建额外的几何图形。它们仅仅更新与它们当前所处理的顶点相关的数据。而且,它们甚至无法访问在当前图元中其它顶点的数据。

为了解决那些问题,OpenGL流水线含有几个其它着色器阶段来打破这些限制。我们这里所要介绍的细分曲面着色器可以使用一个新的几何图元类型,称为 patch(斑点、碎片),来生成一个三角形网格。

细分曲面着色器在OpenGL流水线中增添了两个着色器阶段来生成一个几何图元的网格。比起在使用顶点着色器时不得不指定所有线与三角形来形成自己的模型,在使用细分曲面时,一开始指定一个 patch,它是一列排好序的顶点。当一个 patch 被渲染时,细分曲面控制着色器Tessellation Control Shader) 先执行,对 patch 顶点进行操作,并指定从 patch 中应该生成多少几何图形。细分曲面控制着色器是可选的,我们后面会看到,如果不用它的话需要使用哪些条件。在细分曲面控制着色器完成之后,第二个着色器——细分曲面计算着色器Tessellation Evaluation Shader)使用细分曲面坐标来放置所生成的顶点,并且将它们发送到光栅化器,或发送到几何着色器做进一步处理。


细分曲面Patch

细分曲面过程并不对OpenGL典型的几何图元(点、线和三角形)进行操作,而是使用一个新的图元(在OpenGL 4.0版本中新增的),称为 patchpatch 由流水线中所有活动的着色阶段处理。相比起来,其它图元类型仅仅被顶点、片段和几何着色器处理,而旁通细分曲面阶段。实际上,如果有任一细分曲面着色器是活跃的,那么传递任何其它几何类型会产生一个 GL_INVALID_OPERATION 错误。相反地,如果企图渲染一个 patch 而没有任何细分曲面着色器(明确地说,是一个细分曲面计算着色器;我们会看到细分曲面控制着色器是可选的),那么将也会得到一个 GL_INVALID_OPERATION 错误。

patch 仅仅是传入到OpenGL的一列顶点列表,该列表在处理期间保存它们的次序。当用细分曲面与 patch 进行渲染时,使用像 glDrawArrays() 这样的渲染命令,并指定从绑定的顶点缓存对象(VBO)将被读出的顶点的总数,然后为该绘制调用进行处理。当用其它的OpenGL图元进行渲染时,OpenGL基于在绘制调用中所指定的图元类型而隐式地知道要使用多少顶点,比如使用三个顶点来绘制一个三角形。然后,当使用一个 patch 时,需要告诉OpenGL顶点数组中要使用多少个顶点来组成一个 patch,而这可以通过使用 glPatchParameteri() 进行指定。由同一个绘制调用(drawcall)所处理的所有指定的 patch,它们的尺寸(即每个patch的顶点个数)将是相同的。

void glPatchParameteri(GLenum pname, GLint value);
/**
 * 使用value来指定一个patch中的顶点个数。pname必须设置为GL_PATCH_VERTICES。
 * 如果value小于零或大于GL_MAX_PATCH_VERTICES,将会产一个GL_INVALID_ENUM的错误。
 * 一个patch的默认顶点个数是三。如果一个patch的顶点个数小于参数value值,那么该patch将被忽略,从而不会有几何图形产生。
*/

要指定一个 patch,使用类型 GL_PATCHES 输入到任一OpenGL绘制命令。以下代码描述了发射两个 patch,每个 patch 含有四个顶点,然后通过 glDrawArrays 绘制命令进行渲染。

GLfloat vertices[][2] = {
    {-0.75f, -0.25f}, {-0.25f, -0.25f}, {-0.25f, 0.25f}, {-0.75f, 0.25f},
    {0.25f, -0.25f}, {0.75f, -0.25f}, {0.75f, -0.25f}, {0.75f, 0.25f}, {0.25f, 0.25f}
};

glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(vPos, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
glPatchParameteri(GL_PATCH_VERTICES, 4);
glDrawArrays(GL_PATCHES, 0, 8);

每个 patch 的顶点首先由当前绑定的顶点着色器处理,然后用于初始化数组 gl_in,这个变量在细分曲面控制着色器中被隐式地声明。gl_in 中的元素个数与由 glPatchParameteri() 所指定的 patch 大小相同。在一个细分曲面着色器内部,变量 gl_PatchVerticesIn 提供了 gl_in 中的元素个数(就好比使用 sizeof(gl_in) / sizeof(gl_in[0]) 进行查询)。


细分曲面控制着色器

一旦应用发射了一个 patch,细分曲面控制着色器就会被调用(如果有所绑定的话)并且负责完成以下行动:

  • 生成细分曲面输出 patch 顶点,传递到细分曲面计算着色器,以及更新任一每个顶点的,或每个 patch 的属性值,若有必要的话。
  • 指定细分曲面程度因子,控制图元生成器的操作。这些是特殊的细分曲面控制着色器变量,称为 gl_TessLevelInnergl_TessLevelOuter,并在细分曲面控制着色器中隐式声明。

我们将依次讨论这些行动的每一个。


生成输出patch顶点

细分曲面控制器使用由应用所指定的顶点——这些顶点我们称为输入 patch 顶点(作为顶点着色器的输出)——来生成一组新的顶点,这些新的顶点为输出 patch 顶点。它们存放在细分曲面控制着色器的 gl_out 数组中。细分曲面控制着色器在产出输出** patch** 顶点时,可以修改传递自应用的值(比如顶点属性),也可以创建或移除来自输入 patch 顶点中的顶点。

使用一个 layout 构造在细分曲面控制着色器中指定输出 patch 顶点的个数。下面语句描述了设置输出 patch 顶点的个数为16。

layout (vertices = 16) out;

layout 指示符中的 vertices 参数所设置的值做了两件事情:它设置了输出 patch 顶点 gl_out 的大小;并且指定了细分曲面控制着色器将被执行多少次:对每个输出 patch 顶点执行一次。

为了确定正在处理哪个输出顶点,细分曲面控制着色器可以使用 gl_InvocationID 变量。该变量最经常被用作为 gl_out 数组的一个索引。当一个细分曲面控制着色器在执行时,它具有对所有 patch 顶点数据的访问,包括输入顶点和输出顶点。这可能会导致发射一次着色器调用,该调用需要使用来自另一个着色器调用的数据值,但是那个着色器调用尚未发生。细分曲面控制着色器可以使用GLSL的 barrier() 函数,该函数使得对一个输入 patch 的所有控制着色器的执行,并等待所有这些着色器的执行到达那个函数的调用点,从而确保了可能要设置的所有数据值将被计算。

细分曲面控制着色器的一个普遍的习惯用法仅仅是将输入 patch 顶点输出到此着色器的外面。下面的例子描述了带有四个顶点的一个输出 patch

#version 410 core

layout (vertices = 4) out;

void main(void)
{
    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;

    // 下面设置细分曲面程度
}

细分曲面控制着色器变量

gl_in 数组实际上是一个结构体的数组,每个元素被定义为:

in gl_PerVertex {
    vec4 gl_Position;
    float gl_PointSize;
    float gl_ClipDistance[];

}gl_in[gl_PatchVerticesIn];

并且对于每个需要向下一个阶段传递的值(比如,向下传递到细分曲面计算着色器),需要进行相应地赋值。如上述代码片段所述,传递了 gl_Position 变量。

gl_out 数组具有相同的结构体成员,不过数组大小与 gl_in 不同,它是由 gl_PatchVerticesOut 来指定的。而这个值则是在细分曲面控制器中的 out 这一 layout 限定符中设置。此外,以下标量值用于确定正在被着色的图元和输出顶点:

  • gl_InvocationID:当前细分曲面着色器的输出顶点的调用索引
  • gl_PrimitiveID:当前输入 patch 的图元索引
  • gl_PatchVerticesIn:输入patch中的顶点个数,它作为 gl_in 数组变量中的元素个数
  • gl_PatchVerticesOut:输出 patch 中的顶点个数,它作为 gl_out 数组变量中的元素个数

如果我们需要额外的基于每个顶点的属性值,或为输入或为输出,那么这需要在我们的细分曲面控制着色器中将它们声明为 inout 数组。一个输入数组的大小需要与输入 patch 大小相同,或者可以被声明为缺省大小的,这样OpenGL将会为其所有值适当地分配空间。类似地,每个顶点的输出属性需要与输出 patch 中的顶点个数相一致,也可以为输出属性声明为缺省大小的。输出属性值将会被传递到细分曲面计算着色器,作为其输入属性值。

比如:

#version 410 core

layout (vertices = 4) out;

in vec4 vertexCoeffs1[4];    // 我们假定指定一个输入patch含有4个顶点
in vec4 vertexCoeffs2[];     // 这里使用缺省大小的数组变量,OpenGL将会自动为其分配大小

out vec2 vertexTexCoord1[4];    // 这里需要用上面out的layout (vertices)值一致
out vec2 vertexTexCoord2[];     // 这里使用缺省大小的数组变量,OpenGL将会自动为其分配大小

void main(void)
{
    // Do something here
}

上面给出的是基于逐个顶点的属性值,它们可以用 gl_InvocationID 作为索引,不过要注意的是,gl_InvocationID 标识的是当前细分曲面着色器的输出顶点的调用索引。我们可以使用 patch 限定符来声明每个 patch 的输出变量。每个 patch 的变量不是以数组方式定义而是以普通单实例变量的方式来定义。当然,我们也可以将它们定义为数组。所有细分曲面控制着色器的调用看到的都是同一个 patch 变量。比如:

#version 410 core

patch out vec4 data;

layout (vertices = 4) out;

void main(void)
{
    // Do something here

    data = vec4(1.0, 0.0, 0.0, 1.0);
}

这里,我们定义了一个标识符为 data 的 patch 输出变量。对于每次细分曲面控制着色器的调用,data 的值都被写为 vec4(1.0, 0.0, 0.0, 1.0)。因此,任一细分曲面控制着色器可以写基于每个 patch 的输出变量;实际上,所有细分曲面控制着色器的调用一般都将写到一个基于每个 patch 的变量。只要它们都写相同的值,那么一切都是良好的。


控制细分曲面

一个细分曲面控制着色器的另一个功能是指定对输出 patch 细分多少。然而我们还没详细地讨论细分曲面计算着色器,它们控制用于渲染的输出 patch 的类型,结果也就是细分曲面所发生的域。OpenGL支持三种细分曲面域:四边形,三角形,和等值线集合。这些通过细分曲面计算着色器中的 inlayout 进行指定。

细分曲面的数量通过指定两组值:内部和外部细分曲面程度来控制的。外部细分曲面的值控制域的周边是如何划分的,然后存放在一个隐式声明的名为 gl_TessLevelOuter 的含有四个元素的数组中。而内部细分曲面程度指定了域的内部如何进行划分,然后存放在一个名为 gl_TessLevelInner 的含有两个元素的数组中。所有细分曲面程度因子是浮点值,并且我们将会看到浮点值在细分曲面上以一个比特的效果。最后一点是,尽管隐式声明的细分曲面程度因子数组的维度是固定的,不过从那些数组所使用的值的个数依赖于细分曲面域的类型。下面来看看这两个OpenGL内建的细分曲面输出 patch 变量的声明:

patch out float gl_TessLevelOuter[4];
patch out float gl_TessLevelInner[2];

理解外部与内部细分曲面程度如何操作是让细分曲面做我们想要做的事情的关键。每个细分曲面程度因子指定了对一个区域划分多少条“线段”,以及生成多少细分曲面坐标与几何图元。这种划分如何完成根据不同域类型而有所不同。我们将依次讨论域的每种类型。


四边形细分曲面

使用四边形域可能是最直观的,因此我们先介绍这种类型。当输入 patch 形状为矩形时,这很有用,当我们可能使用二维的样条曲面时,比如 Bézier 曲面。四边形域使用所有内部和外部细分曲面程度来划分单位正方形。比如,如果我们用以下代码来设置细分曲面程度因子,那么OpenGL将会把四边形域细分为如下图所示的样子。

#version 410 core

layout (vertices = 4) out;

void main(void)
{
    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;

    // 下面设置细分曲面程度
    gl_TessLevelInner[0] = 3.0;     // 内部划分3条垂直区域,即内部新增2列顶点
    gl_TessLevelInner[1] = 4.0;     // 内部划分4条水平区域,即内部新增3行顶点
    
    gl_TessLevelOuter[0] = 2.0;     // 左边2条线段
    gl_TessLevelOuter[1] = 3.0;     // 下边3条线段
    gl_TessLevelOuter[2] = 4.0;     // 右边4条线段
    gl_TessLevelOuter[3] = 5.0;     // 上边5条线段
}

下图为上述细分曲面控制着色器可能会得到的几何图形样子。

黄线垂直划分绿线水平划分最终完整
quad_yellowquad_greenquad_dst

上图中,三条黄色线条表示三条垂直区域;四条绿色线条表示四条水平区域。

注意,外部细分曲面程度值对应于围绕周边的每条边的线段个数,而内部细分曲面程度指定了域的内部空间中水平与垂直方向上有多少个“区域”。使用虚线则是将整个域用三角形进行划分。域的三角形划分是依赖于实现的。实心圆点表示细分曲面坐标,每个坐标都会提供给细分曲面计算着色器,作为其输入。在四边形域的情况下,细分曲面坐标有两个坐标值 (u, v),两者值的范围都在 [0, 1] 范围内,并且每个细分曲面坐标将传递到细分曲面计算着色器中,作为它的一次调用。


等值线细分曲面

类似于四边形域,等值线域也生成 (u, v) 对作为给细分曲面计算着色器的细分曲面坐标。然而,等值线仅仅使用外部细分曲面程度的两个元素值来判定划分量(这里没有用到内部细分曲面程度)。

下面是设置等值线域的细分曲面程度:

gl_TessLevelOuter[0] = 6;      // 6条等值线
gl_TessLevelOuter[1] = 8;      // 每条等值线被划分为8条线段

下图为可能的结果图形:

patch_lines


旁通细分曲面控制着色器

正如我们所提到过的,细分曲面着色器往往只是一个直通着色器,将数据从输入直接到输出。在这种情况下,我们实际上可以使一个细分曲面着色器进行旁通,仅仅使用主机端OpenGL API来设置细分曲面程度因子。使用 glPatchParameterfv() 函数来设置内部与外部细分曲面程度。

void glPatchParameterfv(GLenum pname, const GLfloat *values);

/**
 * 当没有细分曲面控制着色器时,设置内部与外部细分曲面程度。
 * 参数pname要么是GL_PATCH_DEFAULT_OUTER_LEVEL,要么是GL_PATCH_DEFAULT_INNER_LEVEL。
 * 当pname是GL_PATCH_DEFAULT_OUTER_LEVEL时,参数values必须是含有四个单精度浮点值的数组,指定四个外部细分曲面程度。
 * 类似地,当pname是GL_PATCH_DEFAULT_INNER_LEVEL时,values必须是含有两个单精度浮点值的数组,指定两个内部细分曲面程度。
*/

细分曲面图元生成

图元生成是一个固定功能阶段,负责从输入 patch 来创建一组新的图元。此阶段仅当一个细分曲面计算着色器在当前程序或程序流水线中活动时才会执行。图元生成受以下因素影响:

  • 细分曲面程度
  • 被细分的顶点的空间划分,这个由细分曲面计算着色器阶段进行定义。它可以是 equal_spacingfractional_even_spacingfractional_odd_spacing
  • 由后续细分曲面着色器所定义的图元类型,即 trianglesquadsisolines。细分曲面计算着色器也可以迫使细分曲面的生成作为一系列的点,而不是三角形或线。这个可以通过 point_mode 来指定。
  • 由后续细分曲面计算着色器所定义的图元生成次序,比如 cw(顺时针)或 ccw(逆时针)。

抽象patch

注意,图元生成不受细分曲面控制着色器中用户定义的输出影响(或当没有细分曲面控制着色器时,也不受顶点着色器的影响),不受细分曲面控制着色器的输出patch大小影响,也不受任一每个 patch 的细分曲面控制着色器输出的影响,而只受细分曲面程度的影响。细分曲面阶段的图元生成部分完全对实际的顶点坐标与其它 patch 数据是不可见的。

图元生成系统的目的是确定要生成多少个顶点,用哪个次序来生成它们,以及用哪种图元来构造它们。实际为这些顶点的每个顶点的数据,诸如位置、颜色等等,是通过细分曲面计算着色器来生成的,基于图元生成器所提供的信息。

因为这种对分,图元生成器对可以被认为是一个“抽象patch”的东东进行操作。它并不从细分曲面控制器的输出来看patch;它仅考虑一个抽象四边形、三角形或等值线块的细分曲面。

依赖于抽象 patch 类型,图元生成器计算不同数量的细分曲面程度并应用不同的细分曲面算法。每个所生成的顶点在一个抽象 patch 内具有一个规格化的位置(即,坐标范围在 [0, 1] 之内)。这个位置具有两个或三个分量,依赖于patch的类型。这些坐标通过内建的

in vec3 gl_TessCoord;

输入提供给细分曲面计算着色器。


细分曲面计算着色器

OpenGL细分曲面流水线的最后一个阶段就是细分曲面计算着色器执行。绑定的细分曲面计算着色器对图元生成器发射的每个细分曲面坐标逐个执行,并负责确定从细分曲面坐标所得到的顶点的位置。我们将看到,细分曲面计算着色器看上去与顶点着色器类似,将顶点变换到屏幕位置(除非细分曲面着色器的数据将被进一步由几何着色器来处理)。

配置一个细分曲面计算着色器的第一步是配置图元生成器,这通过使用一个 layout 指示符来完成。其参数指定了细分曲面域与后续所生成的图元的类型;实体图元的面部朝向(用于做面剔除);以及在图元生成期间如何应用细分曲面程度。


指定图元生成域

我们现在将描述细分曲面计算着色器的 in 这个 layout 的参数。首先,我们先谈论指定细分曲面域。我们之前已经提及过,一共有三种类型的域来生成细分曲面坐标:

  • quads——单位正方形中的一个矩形域;域坐标:带有范围在 [0, 1] 内的 u, v 值的一个个坐标对 (u, v)。
  • triangles——使用重心坐标的一个三角形域;域坐标:带有范围在 [0, 1] 内的a、b、c三个值的坐标 (a, b, c),这里 a + b + c = 1
  • isolines——跨单位正方形的一组线;域坐标:u 值范围在 [0, 1],v 值范围在 [0, 1) 范围的 (u, v) 坐标对。

指定生成图元的面部朝向

与OpenGL中任何填充的图元一样,所发射的顶点的次序决定了图元的脸部朝向。由于我们在这种情况下不直接发射顶点,而只是让图元生成器为我们来做,不过我们需要告诉图元生成器我们图元的右手旋转方向。在 layout 指示符中,指定 cw 为顺时针旋转,ccw 为逆时针。


指定细分曲面坐标的空间

此外,我们可以控制外部细分曲面程度的浮点值如何用在确定周边的细分曲面坐标生成上。(内部细分曲面程度受这些选项影响。)

  • equal_spacing——细分曲面程度被裁减到 [1, max] 范围内,然后取整到下一个最大整数值。
  • fractional_even_spacing——值被裁减到 [2, max] 范围内,然后取整到下一个最大偶整数值n。边然后被划分为 n-2 条等长部分,以及两个剩余部分,每个在一端,剩余部分长度可能比其它长度要短。
  • fractional_odd_spacing——值被裁减到 [1, max-1] 范围内,然后取整到下一个最大奇整数值n。边然后被划分为 n-2 条等长部分,以及两个剩余部分,每个在一端,剩余部分长度可能比其它长度要短。

额外的细分曲面计算着色器layout选项

最后,如果我们想输出点,而不是等值线或填充区域的话,我们可以应用 point_mode 选项。该选项将为每个由细分曲面计算着色器所处理的顶点渲染一单个点。

layout 指示符内的选项的次序不重要。下面例子描述的是一个生成三角形域的图元,使用相等空间,逆时针方向的三角形,但只渲染点,而不是互联的图元。

layout (triangles, equal_spacing, ccw, point_mode) in;

指定一个顶点的位置

从细分曲面控制着色器输出的顶点(即,在 gl_out 数组中的 gl_Position 的值)在计算着色器中的 gl_in 变量中可用。当它们与细分曲面坐标相结合时,可以用于生成输出顶点的位置。

细分曲面坐标在变量 gl_TessCoord 中提供给计算着色器。在下面例子中我们使用相等空间划分的四边形来渲染一个简单的 patch。在这个例子中,细分曲面坐标用于对表面进行上色,然后此例子也描述了如何计算顶点坐标。我们这里要注意的是,gl_in 中的 gl_Position 相当于原始的 patch 每个顶点的坐标,而 gl_TessCoord 则是经过细分曲面之后的新增细分曲面顶点,这些顶点的坐标值是被规格化在 [0, 1] 范围内的。我们通过以原 patch 的顶点坐标与当前处理的细分曲面顶点坐标做相应的插值计算来获得此细分曲面顶点最后输出的坐标值。我们在细分曲面计算着色器中可以访问所有 gl_in 数组的元素,即每次调用可以访问当前 patch 所有输入的顶点坐标。

#version 410 core

layout (quads, equal_spacing, ccw) in;

out vec4 color;

void main(void)
{
    float u = gl_TessCoord.x;
    float omu = 1 - u;    // omu为1减去"u"
    float v = gl_TessCoord.y;
    float omv = 1 - v;    // omv为1减去"v"

    color = gl_TessCoord; // color最后给到片段着色器时,值为(gl_TessCoord.x, gl_TessCoord.y, 0.0, 1.0)

    gl_Position = omu * omv * gl_in[0].gl_Position +
                  u * omv * gl_in[1].gl_Position +
                  u * v * gl_in[2].gl_Position +
                  omu * v * gl_in[3].gl_Position;
}

细分曲面计算着色器变量

与细分曲面控制着色器类似,细分曲面计算着色器也有 gl_in 数组,它是一个结构体数组,每个元素如下定义:

in gl_PerVertex {
    vec4 gl_Position;
    float gl_PointSize;
    float gl_ClipDistance[];
} gl_in[gl_PatchVerticesIn];

输出顶点的数据被存放在一个接口块中,如下定义:

out gl_PerVertex {
    vec4 gl_Position;
    float gl_PointSize;
    float gl_ClipDistance[];
};

参考资料

  • 《OpenGL Programming Guide Eighth Edition – The Official Guide tyo Learning OpenGL, Version 4.3》
  • Tessellation

完整工程项目代码

OpenGL4.1 Tessellation Shader使用demo(基于macOS)

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

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

相关文章

手把手实现项目中自定义动态数据源?

手把手实现项目中自定义动态数据源? 第一步:创建项目,添加POM依赖第二步:添加application.yml配置文件第三步:自定义注解指定使用哪个数据源第四步:创建DynamicDataSourceContextHolder工具类,存…

LeetCode:347. 前 K 个高频元素

🍎道阻且长,行则将至。🍓 🌻算法,不如说它是一种思考方式🍀 算法专栏: 👉🏻123 题解目录 一、🌱[347. 前 K 个高频元素](https://leetcode.cn/problems/bina…

安全防线再升级 | 中睿天下全流量安全分析系统重磅回归

随着信息化的加速,企业网络日趋完善。企业数字化的加速,让越来越多的关键业务运行在计算机网络基础之上,越来越多的重要信息通过网络传送,企业网络面临日益严重的安全威胁,这些安全威胁以窃取信息和收集情报为主&#…

基于docker安装MySQL

为了更好的管理,打算把MySQL、redis等服务放在虚拟机中统一部署,这样不会因为这些服务的问题影响到系统本身。前段时间正好在看docker相关的内容,打算在虚拟机中通过docker来使用MySQL等服务。 这次先记录安装MySQL的过程。 docker安装 首先…

超越 Nginx!号称下一代 Web 服务器,用起来够优雅!

Nginx是一款非常流行的Web服务器,在Github上已有16KStar,我们经常用它来做静态资源托管或反向代理。最近发现了一款全新的Web服务器Caddy,Star数超越Nginx,标星38KStar。试用了一下Caddy,发现它使用起来比Nginx优雅多了…

掌握这五个核心步骤,让你的方案完美无缺

写策划方案怎么来写,可能会是刚入行策划人的难点,策划方案其实就是一份营销计划,一份完整的策划能让策划人看清自己、认清竞争对手,形成对市场的整体认知。 一般大的营销策划方案里面会用到STP、SWOT和4P等模型,模型本…

【Java多线程编程】创建线程的基本方式

大家好,我是一只爱打拳的程序猿。今天给大家带来的内容是 Java 创建线程的基本方式。 多线程的基本创建方式: 继承Thread类实现Runnable接口匿名内部类使用lambda表达式 目录 1. 继承Thread类 1.1 Thread类启动线程 2. 实现Runnable接口 2.1 创建Thread类实例…

while和until的使用方法(还有一些小实验:计算器、猜价格游戏、购物)

while和until的使用方法 一、while用法二、Until循环语句三、猜价格小实验四、计算器实验六、购物实验 一、while用法 for循环语句非常适用于列表对象无规律,且列表来源以固定(如某个列表文件)的场合。而对于要求控制循环次数,操…

C++学习day--06 向计算机输入数据

1、输入 当缓冲区为空时&#xff0c;程序才会暂停&#xff0c;让用户输入数据。 输入回车后&#xff0c;数据全部送到输入缓冲区。 #include <iostream> #include <Windows.h> int main( void ){ char girlType; int salary; float height; std::cout &l…

记录--极致舒适的Vue页面保活方案

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 为了让页面保活更加稳定&#xff0c;你们是怎么做的&#xff1f; 我用一行配置实现了 Vue页面保活是指在用户离开当前页面后&#xff0c;可以在返回时恢复上一次浏览页面的状态。这种技术可以让用户享…

shell脚本4函数

文章目录 shell脚本函数1 函数概述2 定义2.1 形式2.2 使用原则2.3 函数传参2.4 函数变量的作用范围 3 递归3.1 阶乘 4 函数库5 实验5.1 阶乘5.2 递归目录5.3 调用函数库 shell脚本函数 1 函数概述 1、将命令序列按格式写在一起 2、可方便重复使用命令序列 使用函数可以避免代码…

FS4067升压充电8.4V锂电池充电IC电流3A

FS4067升压型5V升压充电8.4V两串锂电池充电IC&#xff0c;工作电压范围于 2.7V 到 6.5V 的 PFM 升压型两节锂电池充电控制集成电路。 FS4067采用恒流和恒压模式对电池进行充电管理&#xff0c;内部集成有基准电压源&#xff0c; 电感电流检测单元&#xff0c;电池电压检测电路和…

【原创】强烈推荐三个可视化模块,绘制的图表真的很酷炫!!

Matplotlib是Python编程语言中最受欢迎的绘图库之一。它提供了一套面向对象的API&#xff0c;可将图表嵌入到使用通用GUI工具包&#xff08;如Tkinter、wxPython、Qt或GTK&#xff09;的应用程序中。Matplotlib还常用于创建静态、动画和交互式的Python数据可视化。它能够绘制各…

【Unity】在Unity下使用websocket的一些经验

首先&#xff0c;先上大家都知道的简介&#xff0c;这一版是我认为比较清晰的。。。虽然在度娘的教导和知乎的教导下&#xff0c;总算认识了websocket&#xff0c;但这个过程比较艰辛&#xff0c;给大家发出来看一下&#xff1a; --------------------------------------------…

精准测试之过程与实践 | 京东云技术团队

作者&#xff1a;京东工业 宛煜昕 一、怎样的技术 •百度百科&#xff1a; 精准测试是一套计算机测试辅助分析系统。 精准测试的核心组件包含的软件测试示波器、用例和代码的双向追溯、智能回归测试用例选取、覆盖率分析、缺陷定位、测试用例聚类分析、测试用例自动生成系统…

苹果(ios)家庭APP广告推送,照片,相册,日历消息推送,【iMessage苹果推】,【苹果家庭推群发】,【imessage相册推送】

解决方案 若是你完全担任苹果的这个默许功效&#xff0c;那就不必要去编削任何代码。 如果&#xff0c;你原本就比较细心&#xff0c;曾经配置了modalPresentationStyle的值&#xff0c;那你也不会有这个影响。 对于想要找回本来默认交互的同学&#xff0c;直接设置以下便可&am…

携手共赢 HashData亮相华为合作伙伴大会

5月8日-9日&#xff0c;以“因聚而生 众志有为”为主题的“华为中国合作伙伴大会2023”在深圳国际会展中心举办。 HashData作为国内云原生数据仓库的代表企业&#xff0c;也是华为重要的生态合作伙伴。在本次大会上&#xff0c;HashData展示了云数仓领域最新前沿技术以及联合…

Java经典笔试题—day04

Java经典笔试题—day04 &#x1f50e;选择题&#x1f50e;编程题&#x1f95d;计算糖果&#x1f95d;进制转换 &#x1f50e;结尾 &#x1f50e;选择题 (1)下列与队列结构有关联的是&#xff08;&#xff09; A. 函数的递归调用 B. 数组元素的引用 C. 多重循环的执行 D. 先到…

Windows系统运行速度优化(系统内存扩充)!

之前有几篇文章&#xff0c;讲述了一些关于提升Windows系统运行速度的方法。链接如下&#xff1a; 如何让Windows系统10秒开机&#xff1f; 电脑运行卡顿怎么办&#xff1f;一招让Windows系统运行流畅 Windows系统重新安装后必须要做的优化 这里还有一个Windows系统的优化方法…

技术领先、“忠”于业务,用友走出多维数据库的价值之路

本文转自科技商业 作者 于洪涛 对于当今的企业而言&#xff0c;精细化管理&#xff0c;已经成为发展之源&#xff0c;甚至是生存之本。 尤其是随着数字化和智能化转型的推进&#xff0c;在企业经营管理过程中&#xff0c;数据正在日益发挥更为关键的要素作用。 相比过去&…