【OpenGL实践10】关于几何着色器

目录

一、说明

二、几何着色器

2.1 设置

2.2 基本几何着色器

2.2.1 输入类型

2.2.2 输出类型

2.2.3 顶点输入

2.2.4 顶点输出

2.3 创建几何着色器

2.4 几何着色器和顶点属性

三、动态生成几何体

四、结论

练习


一、说明

        几何着色器的应用比较高级,关于几何着色器使用究竟需要什么样的步骤,几何着色器能解决什么样的问题。需要示范案例,本文将演示这个过程。

二、几何着色器

        到目前为止,我们已经使用顶点和片段着色器将输入顶点转换为屏幕上的像素。从 OpenGL 3.2 开始,顶点和片段着色器之间还有第三种可选着色器,称为几何着色 。此着色器具有独特的能力,可以使用顶点着色器的输出作为输入,动态创建新的几何体。

        由于我们忽视前几章中的小猫太久了,它跑到了新家。这给了我们一个重新开始的好机会。在本章的最后,我们将有以下演示:

        这看起来并不那么令人兴奋......直到您认为上面的结果是通过一次绘制调用生成的:

glDrawArrays(GL_POINTS, 0, 4);

        请注意,几何着色器可以做的所有事情都可以通过其他方式完成,但它们从少量输入数据生成几何体的能力允许您减少 CPU -> GPU 带宽使用。

2.1 设置

        让我们首先编写一些简单的代码,仅在屏幕上绘制 4 个红点。

// Vertex shader
const char* vertexShaderSrc = R"glsl(
    #version 150 core
    in vec2 pos;

    void main()
    {
        gl_Position = vec4(pos, 0.0, 1.0);
    }
)glsl";

// Fragment shader
const char* fragmentShaderSrc = R"glsl(
    #version 150 core
    out vec4 outColor;

    void main()
    {
        outColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
)glsl";

        我们首先在文件顶部声明两个非常简单的顶点和片段着色器。顶点着色器只是转发每个点的位置属性,而片段着色器始终输出红色。那里没什么特别的。

        我们还添加一个辅助函数来创建和编译着色器:

GLuint createShader(GLenum type, const GLchar* src) {
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &src, nullptr);
    glCompileShader(shader);
    return shader;
}

        在该main函数中,使用选择的库创建一个窗口和 OpenGL 上下文并初始化 GLEW。着色器并编译和激活:

GLuint vertexShader = createShader(GL_VERTEX_SHADER, vertexShaderSrc);
GLuint fragmentShader = createShader(GL_FRAGMENT_SHADER, fragmentShaderSrc);

GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glUseProgram(shaderProgram);

        之后,创建一个保存点坐标的缓冲区:

GLuint vbo;
glGenBuffers(1, &vbo);

float points[] = {
    -0.45f,  0.45f,
     0.45f,  0.45f,
     0.45f, -0.45f,
    -0.45f, -0.45f,
};

glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);

        我们这里有 4 个点,每个点都有 x 和 y 设备坐标。请记住,设备坐标从左到右、从下到上的屏幕坐标范围为 -1 到 1,因此每个角都有一个点。

        然后创建一个VAO并设置顶点格式规范:

// Create VAO
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

// Specify layout of point data
GLint posAttrib = glGetAttribLocation(shaderProgram, "pos");
glEnableVertexAttribArray(posAttrib);
glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0);

        最后是渲染循环:

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

glDrawArrays(GL_POINTS, 0, 4);

        使用此代码,您现在应该在黑色背景上看到 4 个红点,如下所示:

如果您遇到问题,请查看参考源代码。

2.2 基本几何着色器

        要了解几何着色器的工作原理,让我们看一个示例:

#version 150 core

layout(points) in;
layout(line_strip, max_vertices = 2) out;

void main()
{
    gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);
    EmitVertex();

    gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);
    EmitVertex();

    EndPrimitive();
}

2.2.1 输入类型

        顶点着色器处理顶点,片段着色器处理片段,而几何着色器处理整个图元。第一行描述了我们的着色器应该处理什么样的基元。

layout(points) in;

        下面列出了可用的类型及其等效的绘图命令类型:

  • - GL_POINTS(1 个顶点)
  • 线- GL_LINES、GL_LINE_STRIP、GL_LINE_LIST(2 个顶点)
  • lines_adjacency - GL_LINES_ADJACENCY、GL_LINE_STRIP_ADJACENCY(4 个顶点)
  • 三角形- GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN(3 个顶点)
  • triangles_adjacency - GL_TRIANGLES_ADJACENCY、GL_TRIANGLE_STRIP_ADJACENCY(6 个顶点)

        由于我们正在绘图GL_POINTS,因此points类型是合适的。

2.2.2 输出类型

        下一行描述着色器的输出。几何着色器的有趣之处在于它们可以输出完全不同类型的几何形状,并且生成的图元数量甚至可以变化!

layout(line_strip, max_vertices = 2) out;

        第二行指定输出类型及其可以传递的最大顶点数。这是着色器调用的最大数量,而不是单个图元(line_strip在本例中)的最大数量。

可以使用以下输出类型:

  • 线带
  • 三角形带

        这些类型看起来有些受限,但如果你仔细想想,这些类型足以涵盖所有可能的图元类型。例如,只有 3 个顶点的三角形条带相当于一个正三角形。

2.2.3 顶点输入

  gl_Position可以使用几何着色器中的数组来访问在顶点着色器中设置 的gl_in。它是一个结构数组,如下所示:

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

        请注意,诸如pos和 之类的顶点属性color不包括在内,我们稍后将考虑访问这些属性。

2.2.4 顶点输出

        几何着色器程序可以调用两个特殊函数来生成图元,EmitVertexEndPrimitive。每次程序调用 时 EmitVertex,都会将一个顶点添加到当前图元中。添加完所有顶点后,程序将调用EndPrimitive生成图元。

void main()
{
    gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);
    EmitVertex();

    gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);
    EmitVertex();

    EndPrimitive();
}

        在调用之前EmitVertex,应将顶点的属性分配给诸如 之类的变量gl_Position,就像在顶点着色器中一样。稍后我们将讨论如何设置color片段着色器等属性。

        现在您知道了每条线的含义,您能解释一下这个几何着色器的作用吗?

它为传递给它的每个点坐标创建一条水平线。

2.3 创建几何着色器

        没有太多需要解释的,几何着色器的创建和激活方式与其他类型的着色器完全相同。让我们向 4 点示例添加一个尚未执行任何操作的几何着色器。

const char* geometryShaderSrc = R"glsl(
    #version 150 core

    layout(points) in;
    layout(points, max_vertices = 1) out;

    void main()
    {
        gl_Position = gl_in[0].gl_Position;
        EmitVertex();
        EndPrimitive();
    }
)glsl";

        这个几何着色器应该相当简单。对于每个输入点,它生成一个等效的输出点。这是在屏幕上显示点所需的最少代码量。

        使用辅助函数,创建几何着色器很容易:

GLuint geometryShader = createShader(GL_GEOMETRY_SHADER, geometryShaderSrc);

        将其附加到着色器程序也没有什么特别的:

glAttachShader(shaderProgram, geometryShader);

        当您现在运行该程序时,它应该仍然像以前一样显示点。您可以通过从其函数中删除代码来验证几何着色器现在是否正在执行其工作main。您将看到不再绘制任何点,因为没有生成任何点!

        现在,尝试用上一节中的线带生成代码替换几何着色器代码:

#version 150 core

layout(points) in;
layout(line_strip, max_vertices = 2) out;

void main()
{
    gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);
    EmitVertex();

    gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);
    EmitVertex();

    EndPrimitive();
}

        即使我们没有对绘制调用进行任何更改,GPU 也会突然绘制细线而不是点!

        尝试做一些实验来感受一下。例如,尝试使用输出矩形triangle_strip

2.4 几何着色器和顶点属性

        让我们为正在绘制的线条添加一些变化,让每条线条都具有独特的颜色。通过向顶点着色器添加颜色输入变量,我们可以指定每个顶点的颜色,从而指定每个生成线的颜色。

#version 150 core

in vec2 pos;
in vec3 color;

out vec3 vColor; // Output to geometry (or fragment) shader

void main()
{
    gl_Position = vec4(pos, 0.0, 1.0);
    vColor = color;
}

        更新程序代码中的顶点规范:

GLint posAttrib = glGetAttribLocation(shaderProgram, "pos");
glEnableVertexAttribArray(posAttrib);
glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);

GLint colAttrib = glGetAttribLocation(shaderProgram, "color");
glEnableVertexAttribArray(colAttrib);
glVertexAttribPointer(colAttrib, 3, GL_FLOAT, GL_FALSE,
                       5 * sizeof(float), (void*) (2 * sizeof(float)));

        并更新点数据以包含每个点的 RGB 颜色:

float points[] = {
    -0.45f,  0.45f, 1.0f, 0.0f, 0.0f, // Red point
     0.45f,  0.45f, 0.0f, 1.0f, 0.0f, // Green point
     0.45f, -0.45f, 0.0f, 0.0f, 1.0f, // Blue point
    -0.45f, -0.45f, 1.0f, 1.0f, 0.0f, // Yellow point
};

        因为顶点着色器现在后面不是片段着色器,而是几何着色器,所以我们必须将变量vColor作为输入处理。

#version 150 core

layout(points) in;
layout(line_strip, max_vertices = 2) out;

in vec3 vColor[]; // Output from vertex shader for each vertex

out vec3 fColor; // Output to fragment shader

void main()
{
    ...

        您可以看到它与片段着色器中处理输入的方式非常相似。唯一的区别是输入现在必须是数组,因为几何着色器可以接收具有多个顶点的图元作为输入,每个顶点都有自己的属性值。

        由于颜色需要进一步向下传递到片段着色器,因此我们将其添加为几何着色器的输出。我们现在可以为其赋值,就像我们之前对gl_Position.

void main()
{
    fColor = vColor[0]; // Point has only one vertex

    gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.1, 0.0, 0.0);
    EmitVertex();

    gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.1, 0.0, 0.0);
    EmitVertex();

    EndPrimitive();
}

        每当EmitVertex调用 now 时,都会发出一个fColor带有颜色属性当前值的顶点。我们现在可以在片段着色器中访问该属性:

#version 150 core

in vec3 fColor;

out vec4 outColor;

void main()
{
    outColor = vec4(fColor, 1.0);
}

        因此,当您为顶点指定属性时,它首先会作为输入传递给顶点着色器。然后顶点着色器可以选择将其输出到几何着色器。然后几何着色器可以选择进一步输出到片段着色器。

        然而,这个演示并不是很有趣。我们可以通过使用单行创建顶点缓冲区并发出几个使用统一变量设置的不同颜色和位置的绘制调用来轻松复制此行为。

三、动态生成几何体

        几何着色器的真正威力在于能够生成不同数量的图元,因此让我们创建一个正确滥用此功能的演示。

        假设您正在制作一款世界由圆圈组成的游戏。您可以绘制一个圆形模型并重复绘制它,但这种方法并不理想。如果距离太近,这些“圆圈”看起来会像丑陋的多边形,如果距离太远,显卡就会在渲染复杂性上浪费性能,而您甚至看不到。

        我们可以使用几何着色器做得更好!我们可以编写一个着色器,根据运行时条件生成适当的分辨率圆。我们首先修改几何着色器以在每个点绘制一个 10 边形多边形。如果你还记得你的三角函数,那么它应该是小菜一碟:

#version 150 core

layout(points) in;
layout(line_strip, max_vertices = 11) out;

in vec3 vColor[];
out vec3 fColor;

const float PI = 3.1415926;

void main()
{
    fColor = vColor[0];

    for (int i = 0; i <= 10; i++) {
        // Angle between each side in radians
        float ang = PI * 2.0 / 10.0 * i;

        // Offset from center of point (0.3 to accomodate for aspect ratio)
        vec4 offset = vec4(cos(ang) * 0.3, -sin(ang) * 0.4, 0.0, 0.0);
        gl_Position = gl_in[0].gl_Position + offset;

        EmitVertex();
    }

    EndPrimitive();
}

        重复第一个点以闭合线循环,这就是绘制 11 个顶点的原因。结果如预期:

        现在添加顶点属性来控制边数很简单。将新属性添加到数据和规范中:

float points[] = {
//  Coordinates  Color             Sides
    -0.45f,  0.45f, 1.0f, 0.0f, 0.0f,  4.0f,
     0.45f,  0.45f, 0.0f, 1.0f, 0.0f,  8.0f,
     0.45f, -0.45f, 0.0f, 0.0f, 1.0f, 16.0f,
    -0.45f, -0.45f, 1.0f, 1.0f, 0.0f, 32.0f
};

...

// Specify layout of point data
GLint posAttrib = glGetAttribLocation(shaderProgram, "pos");
glEnableVertexAttribArray(posAttrib);
glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE,
                       6 * sizeof(float), 0);

GLint colAttrib = glGetAttribLocation(shaderProgram, "color");
glEnableVertexAttribArray(colAttrib);
glVertexAttribPointer(colAttrib, 3, GL_FLOAT, GL_FALSE,
                       6 * sizeof(float), (void*) (2 * sizeof(float)));

GLint sidesAttrib = glGetAttribLocation(shaderProgram, "sides");
glEnableVertexAttribArray(sidesAttrib);
glVertexAttribPointer(sidesAttrib, 1, GL_FLOAT, GL_FALSE,
                       6 * sizeof(float), (void*) (5 * sizeof(float)));

        更改顶点着色器以将值传递给几何着色器:

#version 150 core

in vec2 pos;
in vec3 color;
in float sides;

out vec3 vColor;
out float vSides;

void main()
{
    gl_Position = vec4(pos, 0.0, 1.0);
    vColor = color;
    vSides = sides;
}

        并使用几何着色器中的变量而不是幻数边数 10.0。还需要max_vertices为我们的输入设置适当的值,否则具有更多顶点的圆将被切断。

layout(line_strip, max_vertices = 64) out;

...

in float vSides[];

...

// Safe, floats can represent small integers exactly
for (int i = 0; i <= vSides[0]; i++) {
    // Angle between each side in radians
    float ang = PI * 2.0 / vSides[0] * i;

    ...

        现在,您只需添加更多点即可创建具有任意边数的圆!

        如果没有几何着色器,每当这些圆中的任何一个需要更改时,我们就必须重建整个顶点缓冲区,现在我们可以简单地更改顶点属性的值。在游戏设置中,该属性可以根据玩家距离而改变,如上所述。您可以在此处找到完整的代码。

四、结论

        诚然,几何着色器可能没有像帧缓冲区和纹理等那样多的现实世界用例,但它们绝对可以帮助在 GPU 上创建内容,如此处所示。

        如果您需要多次重复单个网格(例如体素游戏中的立方体),您可以创建一个几何着色器,以类似的方式从点生成立方体。然而,对于每个生成的网格完全相同的情况,有更有效的方法,例如实例化代码(实例化代码)。

        最后,关于可移植性,最新的 WebGL 和 OpenGL ES 标准尚不支持几何着色器,因此如果您正在考虑开发移动或 Web 应用程序,请记住这一点。

实例化代码:

// Link statically with GLEW
#define GLEW_STATIC

// Headers
#include <GL/glew.h>
#include <SFML/Window.hpp>

// Vertex shader
const GLchar* vertexShaderSrc = R"glsl(
    #version 150 core

    in vec2 pos;
    in vec3 color;
    in float sides;

    out vec3 vColor;
    out float vSides;

    void main()
    {
        gl_Position = vec4(pos, 0.0, 1.0);
        vColor = color;
        vSides = sides;
    }
)glsl";

// Geometry shader
const GLchar* geometryShaderSrc = R"glsl(
    #version 150 core

    layout(points) in;
    layout(line_strip, max_vertices = 64) out;

    in vec3 vColor[];
    in float vSides[];
    out vec3 fColor;

    const float PI = 3.1415926;

    void main()
    {
        fColor = vColor[0];

        // Safe, GLfloats can represent small integers exactly
        for (int i = 0; i <= vSides[0]; i++) {
            // Angle between each side in radians
            float ang = PI * 2.0 / vSides[0] * i;

            // Offset from center of point (0.3 to accomodate for aspect ratio)
            vec4 offset = vec4(cos(ang) * 0.3, -sin(ang) * 0.4, 0.0, 0.0);
            gl_Position = gl_in[0].gl_Position + offset;

            EmitVertex();
        }

        EndPrimitive();
    }
)glsl";

// Fragment shader
const GLchar* fragmentShaderSrc = R"glsl(
    #version 150 core

    in vec3 fColor;

    out vec4 outColor;

    void main()
    {
        outColor = vec4(fColor, 1.0);
    }
)glsl";

// Shader creation helper
GLuint createShader(GLenum type, const GLchar* src) {
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &src, nullptr);
    glCompileShader(shader);
    return shader;
}

int main()
{
    sf::ContextSettings settings;
    settings.depthBits = 24;
    settings.stencilBits = 8;
    settings.majorVersion = 3;
    settings.minorVersion = 2;

    sf::Window window(sf::VideoMode(800, 600, 32), "Circles", sf::Style::Titlebar | sf::Style::Close, settings);

    // Initialize GLEW
    glewExperimental = GL_TRUE;
    glewInit();

    // Compile and activate shaders
    GLuint vertexShader = createShader(GL_VERTEX_SHADER, vertexShaderSrc);
    GLuint geometryShader = createShader(GL_GEOMETRY_SHADER, geometryShaderSrc);
    GLuint fragmentShader = createShader(GL_FRAGMENT_SHADER, fragmentShaderSrc);

    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, geometryShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    glUseProgram(shaderProgram);

    // Create VBO with point coordinates
    GLuint vbo;
    glGenBuffers(1, &vbo);

    GLfloat points[] = {
    //  Coordinates     Color             Sides
        -0.45f,  0.45f, 1.0f, 0.0f, 0.0f,  4.0f,
         0.45f,  0.45f, 0.0f, 1.0f, 0.0f,  8.0f,
         0.45f, -0.45f, 0.0f, 0.0f, 1.0f, 16.0f,
        -0.45f, -0.45f, 1.0f, 1.0f, 0.0f, 32.0f
    };

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);

    // Create VAO
    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // Specify the layout of the vertex data
    GLint posAttrib = glGetAttribLocation(shaderProgram, "pos");
    glEnableVertexAttribArray(posAttrib);
    glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), 0);

    GLint colAttrib = glGetAttribLocation(shaderProgram, "color");
    glEnableVertexAttribArray(colAttrib);
    glVertexAttribPointer(colAttrib, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void*) (2 * sizeof(GLfloat)));

    GLint sidesAttrib = glGetAttribLocation(shaderProgram, "sides");
    glEnableVertexAttribArray(sidesAttrib);
    glVertexAttribPointer(sidesAttrib, 1, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void*) (5 * sizeof(GLfloat)));

    bool running = true;
    while (running)
    {
        sf::Event windowEvent;
        while (window.pollEvent(windowEvent))
        {
            switch (windowEvent.type)
            {
            case sf::Event::Closed:
                running = false;
                break;
            }
        }

        // Clear the screen to black
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // Render frame
        glDrawArrays(GL_POINTS, 0, 4);

        // Swap buffers
        window.display();
    }

    glDeleteProgram(shaderProgram);
    glDeleteShader(fragmentShader);
    glDeleteShader(geometryShader);
    glDeleteShader(vertexShader);

    glDeleteBuffers(1, &vbo);

    glDeleteVertexArrays(1, &vao);

    window.close();

    return 0;
}

练习

  • 尝试在 3D 场景中使用几何着色器来创建更复杂的网格,例如基于点的立方体。

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

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

相关文章

Postman使用技巧

Postman是一款广泛使用的API开发和测试工具&#xff0c;专为简化Web服务API的开发、测试、文档编制和协作过程而设计。它支持各种HTTP请求方法&#xff0c;包括GET、POST、PUT、DELETE等&#xff0c;允许用户轻松地构建和发送请求&#xff0c;以及检查响应。 本文介绍几个提升效…

Linux信号:信号的概念及意义

目录 一、什么是信号 kill-l查看信号 二、信号的产生 2.1系统调用 kill raise abort 2.2软件条件 13)SIGPIPE pipe信号 14&#xff09;SIGAKARM alarm信号 2.2硬件中断 2.3异常 8)SIGFPE 除0异常 11)SIGSEGV 野指针 2.4信号处理的常见方式 三、Core Dump和term…

docker如何拉取redis最新镜像并运行

要拉取Docker Hub上最新版本的Redis镜像&#xff0c;您可以使用以下命令&#xff1a; docker pull redis:latest 这里的latest标签会自动获取Redis镜像的最新版本。如果您希望指定一个确切的版本号&#xff0c;可以直接使用该版本号替换latest。例如&#xff0c;要拉取Redis版…

Python | Leetcode Python题解之第108题将有序数组转换为二叉搜索树

题目&#xff1a; 题解&#xff1a; class Solution:def sortedArrayToBST(self, nums: List[int]) -> TreeNode:def helper(left, right):if left > right:return None# 选择任意一个中间位置数字作为根节点mid (left right randint(0, 1)) // 2root TreeNode(nums…

计算机网络数据链路层知识点总结

3.1 数据链路层功能概述 &#xff08;1&#xff09;知识总览 &#xff08;2&#xff09;数据链路层的研究思想 &#xff08;3&#xff09;数据链路层基本概念 &#xff08;4&#xff09;数据链路层基本功能 3.1 封装成帧和透明传输 &#xff08;1&#xff09;数据链路层功能…

上海亚商投顾:三大指数均跌超1.3%,全市场下跌个股超4600只

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 市场全天震荡调整&#xff0c;三大股指均跌超1.3%。PEEK概念股逆势大涨&#xff0c;肯特股份、沃特股份涨停&a…

[STM32-HAL库]AS608-指纹识别模块-STM32CUBEMX开发-HAL库开发系列-主控STM32F103C8T6

目录 一、前言 二、详细步骤 1.光学指纹模块 2.配置STM32CUBEMX 3.程序设计 3.1 输出重定向 3.2 导入AS608库 3.3 更改端口宏定义 3.4 添加中断处理部分 3.5 初始化AS608 3.6 函数总览 3.7 录入指纹 3.8 验证指纹 3.9 删除指纹 3.10 清空指纹库 三、总结及资源 一、前言 …

AI赋能 企业智能化应用实践

前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff1a;https://www.captainbed.cn/z ChatGPT体验地址 文章目录 前言云定价优化语音助手与聊天机器人预测性维护客服运营自动化低功耗微处理器神…

无线网络安全技术基础

无线网络安全技术基础 无线网络安全风险和隐患 随着无线网络技术广泛应用,其安全性越来越引起关注.无线网络的安全主要有访问控制和数据加密,访问控制保证机密数据只能由授权用户访问,而数据加密则要求发送的数据只能被授权用户所接受和使用。 无线网络在数据传输时以微波进…

微软MSBuild大会发布Copilot+PC:技术革新还是隐私噩梦?

微软在最近的MSBuild 2024大会上发布了全新的CopilotPC概念&#xff0c;这一技术结合了高通骁龙X Elite芯片&#xff0c;将人工智能与PC紧密结合。此次发布引起了广泛关注&#xff0c;不仅是因为其技术创新&#xff0c;还因为潜在的隐私问题。甚至连Elon Musk也对此表示担忧&am…

Nginx企业级负载均衡:技术详解系列(11)—— 实战一机多站部署技巧

你好&#xff0c;我是赵兴晨&#xff0c;97年文科程序员。 工作中你是否遇到过这种情况&#xff1a;公司业务拓展&#xff0c;新增一个域名&#xff0c;但服务器资源有限&#xff0c;只能跟原有的网站共用同一台Nginx服务器。 也就是说两个网站的域名都指向同一台Nginx服务器…

WPF中DataGrid实现多选框功能

1. 效果图 2. Model建立 public class RstModelCheck : ObservableObject {//为了显示Head1和Head2.而且View中绑定属性而非字段&#xff0c;否则不能显示。public string? Name { get; set; } public bool PlatenAll {get > _platenAll;set{SetProperty(ref _platenAl…

MoonDream2微调指南【最小VLM】

在本指南中&#xff0c;我们将探讨如何使用计算机视觉数据集对完全开源的小型视觉语言模型 Moondream2 进行微调&#xff0c;以计数项目&#xff08;这是 GPT-4V 一直表现不一致的任务&#xff09;&#xff0c;并以一种可以依赖输出用于生产应用程序的方式进行微调。 视觉语言…

电力巡检穿戴式智能手环:让巡检不孤立无援

电力巡检穿戴式智能手环:让巡检不孤立无援 在电力巡检的广袤天地里中&#xff0c;电力工作人员他们身着工装&#xff0c;头戴安全帽&#xff0c;手持仪器&#xff0c;穿梭在高压线路与铁塔之间。他们的健康状态&#xff0c;直接关系到电力作业的安全与效率。如今&#xff0c;电…

先进电气技术 —— 控制理论中的“观测器”概述

一、背景 观测器在现代控制理论中的地位十分重要&#xff0c;它是实现系统状态估计的关键工具。观测器的发展历程可以从以下几个方面概述&#xff1a; 1. 起源与发展背景&#xff1a; 观测器的概念源于对系统状态信息的需求&#xff0c;特别是在只能获取部分或间接输出信息…

免费分享一套微信小程序旅游推荐(智慧旅游)系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】,帅呆了~~

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序旅游推荐(智慧旅游)系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序旅游推荐(智慧旅游)系统(SpringBoot后端Vue管理端) Java毕业设计…

VSCode注释模板配置(koroFileheader插件)

近期公司转远程开发&#xff0c;原本idea本地开发用得很丝滑&#xff0c;但使用idea client切换到远程开发&#xff0c;发现各种难受&#xff0c;于是只好探索新工具&#xff0c;py和cpp一直用vscode&#xff0c;于是也试试java&#xff0c;果然比idea client强不只一点点&…

小程序-滚动触底-页面列表数据无限加载

// index/index.vue <template> <!-- 自定义导航栏 --> <CustomNavbar /> <scroll-view scrolltolower"onScrolltolower" scroll-y class"scroll-view"> <!-- 猜你喜欢 --> <Guess ref"guessRef" /> </s…

【RFID打破时尚行业货品管理困境】

时尚行业数字化转型 越来越多的快时尚品牌意识到&#xff0c;要应对市场的快速变化和消费者的多样化需求&#xff0c;构建数字化转型是服饰企业发展的必然趋势。 数智化技术成为了企业“降本增效”的关键因素&#xff0c;时尚行业供应链管理开始走向数字化、智能化。根据第一…

在kaggle中的notebook 如何自定义 cuda 版本以及如何使用自定义的conda或python版本运行项目(一)

问题 第一部分 当前kaggle中带有gpu的notebook 默认的cuda 是12.1版本&#xff0c;如果我要跑一个项目是11.3的&#xff0c;如何将默认的cuda 改为自己需要的cuda 11.3 方法 step1 从官网下载需要的版本cuda run 文件&#xff08;如cuda 11.3&#xff09; 在nvidia cuda 下…