opengl 学习纹理

news2024/11/19 3:43:06

一.纹理是什么?

        纹理是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节;类似于图像一样,纹理也可以被用来储存大量的数据,这些数据可以发送到着色器上。

        采样是指用纹理坐标来获取纹理颜色的过程。

        纹理坐标在x和y轴上,范围为0到1之间(注意我们使用的是2D纹理图像)。纹理坐标起始于(0, 0),也就是纹理图片的左上角,终始于(1, 1),即纹理图片的右下角。这里,与原作者的教案对不上,我通过自己的论证得到的坐标。原作者说的(0,0)坐标是在左下角,(1,1)坐标是右上角。大家有兴趣可以验证下,与我沟通,也可能是中文翻译出了问题。正确的是这样

float texCoords[] = {
    0.0f, 0.0f, // 左上角
    1.0f, 0.0f, // 右上角
    1.0f, 1.0f, // 右下角
    0.0f, 1.0f  // 左下角
};

        纹理坐标的范围通常是从(0, 0)到(1, 1),那如果我们把纹理坐标设置在范围之外会发生什么?OpenGL默认的行为是重复这个纹理图像(我们基本上忽略浮点纹理坐标的整数部分),但OpenGL提供了更多的选择,纹理环绕方式:

环绕方式描述
GL_REPEAT对纹理的默认行为。重复纹理图像。
GL_MIRRORED_REPEAT和GL_REPEAT一样,但每次重复图片是镜像放置的。
GL_CLAMP_TO_EDGE纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
GL_CLAMP_TO_BORDER超出的坐标为用户指定的边缘颜色。

设置纹理环绕的方式用的函数是glTexParameter,第一个参数指定了纹理目标,我们使用的是2D纹理,因此纹理目标是GL_TEXTURE_2D;第二个参数需要我们指定设置的选项与应用的纹理轴。我们打算配置的是WRAP选项,并且指定ST轴(如何是3D纹理的话,还有一个参数是r);最后一个参数需要我们传递一个环绕方式。如下:

        设置x轴:glTexParamteri(GL_TEXTURE_2D,GL_TEXT_WRAP_S,GL_MIRRORED_REPEAT)

        设置y轴:glTexParamteri (GL_TEXTURE_2D,GL_TEXT_WRAP_T,GL_MIRRORED_REPEAT)

        设置边框:

        float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f }; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

        纹理过滤是在物体很大而相对于纹理的分辨率又很低时,你需要使用它。opengl需要知道怎样将纹理的像素映射到纹理的坐标。纹理坐标是我们上面提供传入的顶点数组。纹理像素是把图像放大到像素级别时,单个像素的颜色。源作者给你的解释:

        你可以想象你打开一张.jpg格式图片,不断放大你会发现它是由无数像素点组成的,这个点就是纹理像素;注意不要和纹理坐标搞混,纹理坐标是你给模型顶点设置的那个数组,OpenGL以这个顶点的纹理坐标数据去查找纹理图像上的像素,然后进行采样提取纹理像素的颜色。

        好了,回归到纹理过滤上来,主要分为两种:邻近过滤(GL_NEAREST:默认的方式)和线性过滤(GL_LINEAR)。

        邻近过滤:OpenGL会选择中心点最接近纹理坐标的那个像素。如图,+号表示纹理坐标,所以蓝色的中心点离它最近,返回蓝色。

        线性过滤:OpenGL会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。说明白点的话,就是离+号越近的颜色点与最终样本的颜色越接近。

                                      

                                        邻近过滤                                                 线性过滤

        两种纹理过滤方式的视觉效果:

                        

        GL_NEAREST产生了颗粒状的图案,我们能够清晰看到组成纹理的像素,而GL_LINEAR能够产生更平滑的图案,很难看出单个的纹理像素。GL_LINEAR可以产生更真实的输出,但有些开发者更喜欢8-bit风格,所以他们会用GL_NEAREST选项。

        你可以在纹理被缩小的时候使用邻近过滤,被放大时使用线性过滤。设置放大和缩小时的纹理过滤方式:

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        现在我们针对的是一个物体,假如有多个物体时,为了让纹理更真实。OpenGL使用一种叫做多级渐远纹理(Mipmap)的概念来解决这个问题,它简单来说就是一系列的纹理图像,后一个纹理图像是前一个的二分之一。多级渐远纹理背后的理念很简单:距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个。由于距离远,解析度不高也不会被用户注意到。同时,多级渐远纹理另一加分之处是它的性能非常好

        手工为每个纹理图像创建一系列多级渐远纹理很麻烦,幸好OpenGL有一个glGenerateMipmaps函数,在创建完一个纹理后调用它OpenGL就会承担接下来的所有工作了。

        在渲染中切换多级渐远纹理级别(Level)时,OpenGL在两个不同级别的多级渐远纹理层之间会产生不真实的生硬边界。就像普通的纹理过滤一样,切换多级渐远纹理级别时你也可以在两个不同多级渐远纹理级别之间使用NEAREST和LINEAR过滤。为了指定不同多级渐远纹理级别之间的过滤方式,你可以使用下面四个选项中的一个代替原有的过滤方式: 

过滤方式描述
GL_NEAREST_MIPMAP_NEAREST使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样
GL_LINEAR_MIPMAP_NEAREST使用最邻近的多级渐远纹理级别,并使用线性插值进行采样
GL_NEAREST_MIPMAP_LINEAR在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样
GL_LINEAR_MIPMAP_LINEAR在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样

        一个常见的错误是,将放大过滤的选项设置为多级渐远纹理过滤选项之一。这样没有任何效果,因为多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理,为放大过滤设置多级渐远纹理的选项会产生一个GL_INVALID_ENUM错误代码。 

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

 二.加载和创建纹理

        要使纹理加载到应用程序中,我们使用图像加载库stb_image.h。下面是实操,我就不粘贴原作者的教案和博客了,当然,重要的东西还是会用原作者原始文案,方便后续复查。

        1.第一步先用stb_image.h将图片加载到内存。如下:

         int width, height, nrChannels;

         unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);

        这个函数首先接受一个图像文件的位置作为输入。接下来它需要三个int作为它的第二、第三和第四个参数,stb_image.h将会用图像的宽度高度颜色通道的个数填充这三个变量。我们之后生成纹理的时候会用到的图像的宽度和高度的。

        2.第二步生成纹理,创建纹理ID,绑定纹理,生成纹理,释放内存编写。生成纹理函数:

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

  • 第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。
  • 第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
  • 第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有RGB值,因此我们也把纹理储存为RGB值。
  • 第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
  • 下个参数应该总是被设为0(历史遗留的问题)。
  • 第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为char(byte)数组,我们将会传入对应值。
  • 最后一个参数是真正的图像数据。

        当调用glTexImage2D时,当前绑定的纹理对象就会被附加上纹理图像。然而,目前只有基本级别(Base-level)的纹理图像被加载了,如果要使用多级渐远纹理,我们必须手动设置所有不同的图像(不断递增第二个参数)。或者,直接在生成纹理之后调用glGenerateMipmap。这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。 这里将创建绑定生成释放四个过程用单独的类来进行管理,包括第一步的stb_image.h库的包含。如下:

        texture.h

#ifndef TEXTURE_H
#define TEXTURE_H
#define STB_IMAGE_IMPLEMENTATION

class Texture
{
public:
    // 纹理ID
    unsigned int ID;

    Texture(const char* imagePath);

    unsigned int getTextID() const { return ID; }
};

#endif // TEXTURE_H

        texture.cpp

#include "texture.h"
#include "stb_image.h"
#include <glad/glad.h>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

Texture::Texture(const char* imagePath)
{
    // 创建纹理
    glGenTextures(1, &ID);
    // 绑定纹理
    glBindTexture(GL_TEXTURE_2D, ID);
    // 为当前绑定的纹理对象设置环绕、过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // 加载并生成纹理
    int width, height, nrChannels;
    unsigned char *data = stbi_load(imagePath, &width, &height, &nrChannels, 0);
    if (data)
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    stbi_image_free(data);
}

         3.第三步就是应用纹理。首先,前面我们知道VAO(顶点数组对象)在内存中的存放规则,我们需要将纹理也添加到顶点的属性当中,这里respect一下原作者,写教案真是用心了,一环扣一环,环环相应。先看下面的结构:

                                                告诉opengl我们的顶点格式  

        原作者是正方形,我们是六边形,下面看数据格式排版代码(如果你是一个上下对称的图像,原作者的代码没有问题,如果不是,原作者的坐标是有问题的。可参照六边形的设定,上面讲过纹理的坐标计算):

float vertices[] = {
//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
     0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
     0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
    -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
    -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
};

                                        四边形的四个顶点 

 float vertices[] = {
        -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
        0.5f,  0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
        1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.5f,
        0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
        -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
        -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f
    };

    unsigned int indices[] = {0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5};

                                        六边形的六个顶点  

        设置属性:

    // 位置属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // 颜色属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    // 纹理属性
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

        4.第四步就是编辑我们的两个着色器:                    

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;
out vec3 vertexColor;
out vec2 TexCoord;
void main()
{
    gl_Position = vec4(aPos, 1.0);
    vertexColor = aColor;
    TexCoord = aTexCoord;
}

                                                顶点着色器

#version 330 core
out vec4 FragColor;
in vec3 vertexColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;

void main()
{
    FragColor = texture(ourTexture, TexCoord);
}

                                                片段着色器

        片段着色器能访问纹理对象,但是我们怎样能把纹理对象传给片段着色器呢?GLSL有一个供纹理对象使用的内建数据类型,叫做采样器(Sampler),它以纹理类型作为后缀,比如sampler1Dsampler3D,或在我们的例子中的sampler2D。我们可以简单声明一个uniform sampler2D把一个纹理添加到片段着色器中,上面我们把纹理赋值给这个uniform。

        我们使用GLSL内建的texture函数来采样纹理的颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标。texture函数会使用之前设置的纹理参数对相应的颜色值进行采样。这个片段着色器的输出就是纹理的(插值)纹理坐标上的(过滤后的)颜色。

        5.第五步就是如何绘制了,在绘制之前绑定我们的纹理id就可以了。text是上面我们建立的纹理类创建的对象,传入一张jpeg的图像参数得到这个对象。通过调用纹理ID绑定。

// bind Texture
glBindTexture(GL_TEXTURE_2D, text.getTextID());

// 3. 绘制物体
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);

        6.操作完后,你会得到这样的一个效果:

         原图是这样的:

        7.上面我们还未使用颜色值,我们还可以把得到的纹理颜色与顶点颜色混合,来获得更有趣的效果。我们只需把纹理颜色与顶点颜色在片段着色器中相乘来混合二者的颜色:

        FragColor = texture(ourTexture, TexCoord) * vec4(vertexColor, 1.0);

        得到下面的效果:

         8.纹理单元

        你可能会奇怪为什么sampler2D变量是个uniform,我们却不用glUniform给它赋值。使用glUniform1i,我们可以给纹理采样器分配一个位置值,这样的话我们能够在一个片段着色器中设置多个纹理。一个纹理的位置值通常称为一个纹理单元(Texture Unit)。一个纹理的默认纹理单元是0,它是默认的激活纹理单元,所以教程前面部分我们没有分配一个位置值。

        纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。就像glBindTexture一样,我们可以使用glActiveTexture激活纹理单元,传入我们需要使用的纹理单元:

        glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元         glBindTexture(GL_TEXTURE_2D, texture);

        激活纹理单元之后,接下来的glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元,纹理单元GL_TEXTURE0默认总是被激活,所以我们在前面的例子里当我们使用glBindTexture的时候,无需激活任何纹理单元。

        OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。

#version 330 core
out vec4 FragColor;
in vec3 vertexColor;
in vec2 TexCoord;
uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2) * vec4(vertexColor, 1.0);
}

        GLSL内建的mix函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。

        激活纹理:

        glActiveTexture(GL_TEXTURE0);

        glBindTexture(GL_TEXTURE_2D, texture1);

        glActiveTexture(GL_TEXTURE1);

        glBindTexture(GL_TEXTURE_2D, texture2);

        设置纹理采集:

        我们还要通过使用glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元。我们只需要设置一次即可,所以这个会放在渲染循环的前面:

ourShader.use(); // 不要忘记在设置uniform变量之前激活着色器程序!
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // 手动设置
ourShader.setInt("texture2", 1); // 或者使用着色器类设置

        得到的效果如下:

        

        最后说下坐标的问题, 原来原作者没有错误,作者最后说了句,std_image.h库默认会上下翻转图像数据。下面是原话:

        你可能注意到纹理上下颠倒了!这是因为OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部。很幸运,stb_image.h能够在图像加载时帮助我们翻转y轴,只需要在加载任何图像前加入以下语句即可:

        stbi_set_flip_vertically_on_load(true);

        那么我们纹理的坐标理解应该是左下角(0,0),右上角(1,1)。这里不管是咋样,也不想求证了,可以用一个第三方库加载图像来验证纹理的坐标。到这里基本学习了纹理。

三.学习地址:(感谢原作者的讲解)

        纹理 - LearnOpenGL CN (learnopengl-cn.github.io)

四.demo地址:

        learningOpengl: 一起学习opengl

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

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

相关文章

npm install 失败,需要node 切换到 对应版本号

npm install 失败 原本node 的版本号是16.9&#xff0c;就会报以上错误 node版本问题了&#xff0c;我切到这个版本&#xff0c;报同样的错。降一下node&#xff08;14.18&#xff09;版本就好了 具体的方法&#xff1a;&#xff08;需要在项目根目录下切换&#xff09; 1. …

微服务学习

一、服务注册发现 服务注册就是维护一个登记簿&#xff0c;它管理系统内所有的服务地址。当新的服务启动后&#xff0c;它会向登记簿交待自己的地址信息。服务的依赖方直接向登记簿要Service Provider地址就行了。当下用于服务注册的工具非常多ZooKeeper&#xff0c;Consul&am…

JavaScript从零写网站《一瞬》开发日志20240223

产品介绍 一个无需注册能随时发布图片并配一段文字介绍的app&#xff0c;有时间线&#xff0c;用户在主页面向下滑动&#xff0c;可以看到被发布的若干图片&#xff0c;并且能够在每一个发布处做基本互动——评论&#xff0c;点赞 编程语言 本产品使用htmlcssJavaScript开发…

【Docker】构建pytest-playwright镜像并验证

Dockerfile FROM ubuntu LABEL maintainer "langhuang521l63.com" ENV TZAsia/Shanghai #设置时区 #安装python3依赖与下载安装包 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \&& apt update \&&…

Linux——进程概念

目录 冯诺依曼体系结构 操作系统 管理 系统调用和库函数 进程的概念 进程控制块——PCB 查看进程 通过系统调用获取进程标示符 通过系统调用创建进程 进程状态 运行状态-R ​编辑 浅度睡眠状态-S 深度睡眠状态-D 暂停状态-T 死亡状态-X 僵尸状态-Z 僵尸进程…

Open CASCADE学习|绘制砂轮

今天绘制一个砂轮&#xff0c;其轮廓由两条直线段和两段圆弧构成&#xff0c;圆弧分别与直线相切&#xff0c;两条圆弧之间相交而非相切。建模思路是&#xff1a;先给定两条直线段的起始点及长度&#xff0c;画出直线段&#xff0c;然后给定其中一圆弧的半径及圆心角&#xff0…

Linux之ACL访问控制列表

一、ACL权限的介绍 1.1 什么是ACL 访问控制列表&#xff08;ACL&#xff09;是一种网络安全技术&#xff0c;它通过在网络设备&#xff08;如路由器、交换机和防火墙&#xff09;上定义一系列规则&#xff0c;对进出接口的数据包进行控制。这些规则可以包含“允许”&…

解决IDEA中Maven下载依赖包过慢或报错的问题

由于公司项目迭代&#xff0c;越来越多的项目开始转型新版本&#xff0c;由于我对Java一直不感冒&#xff0c;但要顺应公司项目要求&#xff0c;遂自己要逐步开始完善Java相关的知识层面&#xff0c;此篇是我在学习SpringBoot时对一些不懂地方及遇到问题时的记录。 学习视频链…

Day 1.进程的基本概念、相关命令、函数结口

进程基本概念 一、进程&#xff1a; 程序&#xff1a;存放在外存中的一段数据组成的文件 进程&#xff1a;是一个程序动态执行的过程&#xff0c;包括进程的创建、进程的调度、进程的消亡 二、进程相关的命令 1.top 动态查看当前系统中所有的进程信息&#xff08;根据CPU…

基于PostGIS的慢查询引起的空间索引提升实践

目录 前言 一、问题定位 1、前端接口定位 2、后台应用定位 3、找到问题所在 二、空间索引优化 1、数据库查询 2、创建空间索引 3、geography索引 4、再看前端响应 总结 前言 这是一个真实的案例&#xff0c;也是一个新入门的工程师很容易忽略的点。往往在设计数据库的…

项目管理:如何成功完成一个项目

项目管理是一项重要的技能&#xff0c;它可以帮助你成功地完成一个项目。以下是一些关键的步骤&#xff0c;可以帮助你实现这一目标&#xff1a; 1. 明确项目目标&#xff1a;在开始项目之前&#xff0c;你需要明确项目的目标。这将有助于你制定一个明确的计划&#xff0c;并确…

最长公共前缀【简单】

题目 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 ""。 示例如下&#xff1a; 所给提示如下&#xff1a; 1 < strs.length < 2000 < strs[i].length < 200strs[i] 仅由小写英文字母组成 解题 根据…

iOS面试:4.多线程GCD

一、多线程基础知识 1.1 什么是进程&#xff1f; 进程是指在系统中正在运行的一个应用程序。对于电脑而已&#xff0c;你打开一个软件&#xff0c;就相当于开启了一个进程。对于手机而已&#xff0c;你打开了一个APP&#xff0c;就相当于开启了一个进程。 1.2 什么是线程&am…

反序列化字符串逃逸 [安洵杯 2019]easy_serialize_php1

打开题目 $_SESSION是访客与整个网站交互过程中一直存在的公有变量 然后看extract()函数的功能&#xff1a; extract($_POST)就是将post的内容作为这个函数的参数。 extract() 函数从数组中将变量导入到当前的符号表(本题的作用是将_SESSION的两个函数变为post传参) function…

MySql-DQL-条件查询

目录 条件查询修改数据 查询 姓名 为 Name10 的员工查询 id小于等于5 的员工信息查询 没有分配职位 的员工信息查询 有职位 的员工信息查询 密码不等于 password1 的员工信息查询 入职日期 在 2000-01-01 (包含) 到 2010-01-01(包含) 之间的员工信息查询 入职时间 在 2000-01-0…

linux之权限管理(实施必会!!!!)

文章目录 一、什么是ACL二、操作步骤2.1. 添加测试目录&#xff0c;用户&#xff0c;组&#xff0c;并将用户添加到组2.2.查看组是否正常建立 cat /etc/group2.3设定权限2.4此时需要为临时用户进行权限&#xff1a; r-x 三、ACL中mask修改最大权限四、ACL权限的删除五、ACL权限…

pclpy KD-Tree K近邻搜索

pclpy KD-Tree K近邻搜索 一、算法原理1.KD-Tree 介绍2.原理 二、代码三、结果1.原点云2.k近邻点搜索后的点云 四、相关数据 一、算法原理 1.KD-Tree 介绍 kd 树或 k 维树是计算机科学中使用的一种数据结构&#xff0c;用于在具有 k 维的空间中组织一定数量的点。它是一个二叉…

MybatisPlus--03--IService、ServiceImpl

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1. IService接口1.1 IService、ServiceImpl 接口的使用第一步&#xff1a;实现basemapper接口第二步&#xff1a;编写service类第三步&#xff1a;编写serviceImpl第…

【统计分析数学模型】判别分析(三):Bayes判别法

【统计分析数学模型】判别分析&#xff08;三&#xff09;&#xff1a;Bayes判别法 一、Bayes判别法二、R语言实现Bayes判别法1. 运行NaiveBayes()函数2. 绘制密度曲线3. 计算回判正确率 一、Bayes判别法 Bayes判别法假定对研究对象有一定的认识&#xff0c;这种认识用先验概率…

政安晨:【示例演绎机器学习】(四)—— 神经网络的标量回归问题示例 (价格预测)

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让小伙伴们一起学习、交流进步&#xff0c;不论是学业还是工…