LearnOpenGL——SSAO学习笔记

news2024/11/26 7:35:49

LearnOpenGL——SSAO学习笔记

  • SSAO
    • 一、基本概念
    • 二、样本缓冲
    • 三、法向半球
    • 四、随机核心转动
    • 五、SSAO着色器
    • 六、环境遮蔽模糊
    • 七、应用SSAO遮蔽因子

SSAO

一、基本概念

环境光照是我们加入场景总体光照中的一个固定光照常量,它被用来模拟光的散射(Scattering)。散射应该是有强度的,所以被间接光照的部分也应该有变化的强度。环境光遮蔽(Ambient Occlusion)是一种模拟间接光照的办法。原理是将褶皱、孔洞和靠近墙面的地方变得更暗来模拟间接光照。
在这里插入图片描述
环境光遮蔽这一技术会带来很大的性能开销,因为它还需要考虑周围的几何体。随后Crytek公司发布了一个叫做屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)的技术。SSAO使用了屏幕空间的场景深度而不是真实的几何体数据,速度快效果好。

SSAO原理:对于屏幕四边形上的每一个像素,我们基于像素周围深度值计算一个遮蔽因子(Occlusion Factor),这个遮蔽因子是用来减少或者抵消片元的环境分量。遮蔽因子是通过用球型采样核采集片段周围多个深度样本,并将每个采样点与片元的深度值进行比较得到的。高于片元深度值的采样点的个数,就是遮蔽因子。
在这里插入图片描述

上图中,灰色的深度样本都是高于片元深度值的。采样点数量越多,片元最终的环境分量就越小。样本数量太低会影响渲染精度,太多又会影响性能,所以我们可以通过引入随机性到采样核心(Sample Kernel)的采样中从而减少样本的数目,但是也会有噪声,所以我们再次基础上继续引入模糊来修复。
因为我们的采样核心是球体,对于墙这种平面来说,会有一部分采样到墙后面去,会导致画面看起来灰蒙蒙的
在这里插入图片描述
所以我们不再使用球体,而是使用一个沿着表面法向量的半球体来采样核。法向半球体(Normal-oriented Hemisphere) 周围采样,我们将不会考虑到片段底部的几何体.它消除了环境光遮蔽灰蒙蒙的感觉,从而产生更真实的结果
在这里插入图片描述

二、样本缓冲

因为我们要确定每个片元的遮蔽因子,所以需要得到几何体信息。对于每个片元,我们需要知道:

  • 每个片元的位置向量
  • 每个片元的法线向量
  • 每个片元的漫反射颜色
  • 一个采样核
  • 每个片元的随机旋转向量,用于旋转采样核

基本步骤:

  • 通过在每个片元的观察空间位置,我们可以定义一个半球采样核,这个半球是围绕该片元在视图空间中的表面法线(surface normal)定向的。
  • 并用这个核在各个偏移量(偏移量加在每个样本点位置上,使得样本点不会完全集中在半球的中心,而是稍微散布开来)去采样位置缓冲纹理(位置缓冲纹理包含了场景中每个片元的世界空间位置信息)。
  • 对于每个片元的核样本,我们会比较它的深度值与位置缓冲区中的深度值来决定遮蔽因子
  • 然后通过遮蔽因子来限制最后的环境光照项。通过每个片元的旋转向量,我们也可以显著减少需要采集的样本数量。

在这里插入图片描述

SSAO是一项屏幕采样技术,由于我们没有物体的几何信息,我们就需要将每个片元的几何数据渲染成屏幕空间的纹理,然后将这些纹理发给SSAO着色器,这样就可以访问每个片元的几何数据。这就类似于延迟渲染的G-Buffer了

延续延迟渲染章节的G-Buffer 延迟渲染学习笔记,我们只需要更新一下几何着色器,让它包含片段的线性深度就行了,我们可以从gl_FragCoord.z中提取线性深度:

#version 330 core
layout (location = 0) out vec4 gPositionDepth;
layout (location = 1) out vec3 gNormal;
layout (location = 2) out vec4 gAlbedoSpec;

in vec2 TexCoords;
in vec3 FragPos;
in vec3 Normal;

const float NEAR = 0.1; // 投影矩阵的近平面
const float FAR = 50.0f; // 投影矩阵的远平面
float LinearizeDepth(float depth)
{
    float z = depth * 2.0 - 1.0; // 回到NDC
    return (2.0 * NEAR * FAR) / (FAR + NEAR - z * (FAR - NEAR));    
}

void main()
{    
    // 储存片段的位置矢量到第一个G缓冲纹理
    gPositionDepth.xyz = FragPos;
    // 储存线性深度到gPositionDepth的alpha分量
    gPositionDepth.a = LinearizeDepth(gl_FragCoord.z); 
    // 储存法线信息到G缓冲
    gNormal = normalize(Normal);
    // 和漫反射颜色
    gAlbedoSpec.rgb = vec3(0.95);
}

提取出来的线性深度是在观察空间中的,之后的运算也是在观察空间中,几何阶段顶点着色器提供的FragPos和Normal被转换到视图空间(同时乘以视图矩阵)。

gPositionDepth颜色缓冲如下:

glGenTextures(1, &gPositionDepth);
glBindTexture(GL_TEXTURE_2D, gPositionDepth);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

三、法向半球

我们需要沿着表面法线方向生成大量的样本。我们将在 切线空间(法线方向朝向z轴正向) 生成采样核心。

假设我们有一个单位半球,我们可以获得最大64个样本值的采样值

// 随机浮点数,范围0.0 - 1.0
std::uniform_real_distribution<GLfloat> randomFloats(0.0, 1.0); 

std::default_random_engine generator;
std::vector<glm::vec3> ssaoKernel;
for (GLuint i = 0; i < 64; ++i)
{
    glm::vec3 sample(
        randomFloats(generator) * 2.0 - 1.0, 
        randomFloats(generator) * 2.0 - 1.0, 
        randomFloats(generator)
    );
    sample = glm::normalize(sample);
    sample *= randomFloats(generator);
    GLfloat scale = GLfloat(i) / 64.0; 
    scale = lerp(0.1f, 1.0f, scale * scale);
    sample *= scale;
    ssaoKernel.push_back(sample);  
}
  • uniform_real_distribution<GLfloat>default_random_engine generator 是创建一个均匀分布的随机浮点数生成器范围在[0.0-1.0];创建一个默认的随机数引擎,用于生成随机数。
  • for循环是生成64个随机向量作为采样核的一部分
  • sample是一个三维向量(半球范围),初始化时x和y分量时在[-1.0,1.0]范围的随机数(乘以2.0然后减去1.0是将[0.0, 1.0]范围内的随机数转换为[-1.0, 1.0]范围内的随机数),z分量是在[0.0, 1.0]范围内的随机数
  • sample *= randomFloats(generator); 缩放向量有助于在采样核中引入更多的变化,在SSAO中产生更多的噪点
  • 应用缩放因子 scale scale = lerp(0.1f, 1.0f, scale * scale):在生成采样向量时,可以通过scale来调整每个向量的权重,使得靠近中心的向量具有更高的权重,而靠近边缘的向量具有较低的权重。
GLfloat lerp(GLfloat a, GLfloat b, GLfloat f)
{
    return a + f * (b - a);
}

在这里插入图片描述
如果半球是完全严格按照法线方向生成,那么采样向量可能会过于规律,导致在某些方向上无法很好地捕捉到遮挡情况,或者在某些情况下产生明显的模式(如条纹或斑点)。所以我们为每个半球核引入一个随机转动

四、随机核心转动

通过引入一些随机性到采样核心上,我们可以大大减少获得不错结果所需的样本数量。我们可以创建一个小的随机旋转向量纹理平铺在屏幕上。

我们创建一个4×4朝向切线空间平面法线的随机旋转向量数组:由于采样核心是沿着正z方向在切线空间内旋转,我们设定z分量为0.0,从而围绕z轴旋转。

std::vector<glm::vec3> ssaoNoise;
for (GLuint i = 0; i < 16; i++)
{
    glm::vec3 noise(
        randomFloats(generator) * 2.0 - 1.0, 
        randomFloats(generator) * 2.0 - 1.0, 
        0.0f); 
    ssaoNoise.push_back(noise);
}

然后创建一个包含随机旋转向量的4×4纹理

GLuint noiseTexture; 
glGenTextures(1, &noiseTexture);
glBindTexture(GL_TEXTURE_2D, noiseTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, 
	4, 4, 0, GL_RGB, GL_FLOAT, &ssaoNoise[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)

五、SSAO着色器

SSAO着色器在2D的铺屏四边形上运行,它对于每一个生成的片段计算遮蔽值(为了在最终的光照着色器中使用)。为了存储SSAO的结果,还需要创建一个帧缓冲对象,然后将SSAO的结果作为颜色附件在帧缓冲上。

由于环境遮蔽的结果是一个灰度值,我们将只需要纹理的红色分量,所以我们将颜色缓冲的内部格式设置为GL_RED。

GLuint ssaoFBO;
glGenFramebuffers(1, &ssaoFBO);  
glBindFramebuffer(GL_FRAMEBUFFER, ssaoFBO);
GLuint ssaoColorBuffer;

glGenTextures(1, &ssaoColorBuffer);
glBindTexture(GL_TEXTURE_2D, ssaoColorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ssaoColorBuffer, 0);

完整的SSAO过程:

// 几何处理阶段: 渲染到G缓冲中
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
    [...]
glBindFramebuffer(GL_FRAMEBUFFER, 0); 

 // 使用G缓冲渲染SSAO纹理
glBindFramebuffer(GL_FRAMEBUFFER, ssaoFBO);
glClear(GL_COLOR_BUFFER_BIT);
shaderSSAO.Use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gPositionDepth);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, gNormal);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, noiseTexture);
SendKernelSamplesToShader();
glUniformMatrix4fv(projLocation, 1, GL_FALSE, 
	glm::value_ptr(projection));
RenderQuad();
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// 光照处理阶段: 渲染场景光照
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shaderLightingPass.Use();
[...]
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, ssaoColorBuffer);
[...]
RenderQuad();

shaderSSAO的片元着色器,会接受G-Buffer中的数据,也接受噪声纹理和法向半球核心样本作为输入参数,最终结果是一个灰度值,表示当前片段的遮蔽程度。

#version 330 core
out float FragColor;
in vec2 TexCoords;

uniform sampler2D gPositionDepth;
uniform sampler2D gNormal;
uniform sampler2D texNoise;

uniform vec3 samples[64];
uniform mat4 projection;

// 屏幕的平铺噪声纹理会根据屏幕分辨率除以噪声大小的值来决定
const vec2 noiseScale = vec2(800.0/4.0, 600.0/4.0); // 屏幕 = 800x600

void main()
{
    vec3 fragPos = texture(gPositionDepth, TexCoords).xyz;
    vec3 normal = texture(gNormal, TexCoords).rgb;
    
    vec3 randomVec = texture(texNoise, TexCoords * noiseScale).xyz;
    vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
    vec3 bitangent = cross(normal, tangent);
    mat3 TBN = mat3(tangent, bitangent, normal);

    float occlusion = 0.0;
    for(int i = 0; i < kernelSize; ++i)
    {
        // 获取样本位置
        vec3 sample = TBN * samples[i]; // 切线->观察空间
        sample = fragPos + sample * radius; 

        vec4 offset = vec4(sample, 1.0);
        offset = projection * offset; // 观察->裁剪空间
        offset.xyz /= offset.w; // 透视划分
        offset.xyz = offset.xyz * 0.5 + 0.5; // 变换到0.0 - 1.0的值域
        
        float sampleDepth = -texture(gPositionDepth, offset.xy).w;
        
        float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth));
        occlusion += (sampleDepth >= sample.z ? 1.0 : 0.0) * rangeCheck;   
    }
    occlusion = 1.0 - (occlusion / kernelSize);
    FragColor = occlusion;  
}

下面会对这段代码做个人向的解释

out float FragColor;
in vec2 TexCoords;
  • FragColor: 片段着色器的输出颜色(或者说输出值)。在这段代码中,它表示遮蔽值,作为浮点数输出。
    TexCoords: 输入的纹理坐标,表示当前片段在屏幕上的位置,用来从不同的纹理中采样数据。
uniform sampler2D gPositionDepth;
uniform sampler2D gNormal;
uniform sampler2D texNoise;

uniform vec3 samples[64];
uniform mat4 projection;
const vec2 noiseScale = vec2(800.0/4.0, 600.0/4.0); // 屏幕 = 800x600
  • gPositionDepth:存储片元的世界坐标和深度的G-buffer纹理
  • gNormal:存储法线的G-buffer纹理
  • texNoise:存储随机噪声的纹理,用于创建随机的切线空间。
  • samples[64]:一个存储预计算采样向量的数组,这些向量用于在片段周围进行遮蔽计算。
    • 在 SSAO 的计算中,预定义的采样向量通常是在切线空间中生成的。这些向量表示了局部坐标系下,围绕当前像素的某些方向的偏移量。
  • projection: 投影矩阵,用于将样本点从观察空间转换到裁剪空间。
  • noiseScale: 噪声纹理的缩放因子。
    • 它决定了屏幕上噪声纹理如何重复,确保 SSAO 计算中的随机噪声效果在屏幕上分布均匀,而不是随着屏幕分辨率的变化而变化。
vec3 randomVec = texture(texNoise, TexCoords * noiseScale).xyz;
vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
vec3 bitangent = cross(normal, tangent);
mat3 TBN = mat3(tangent, bitangent, normal);
  • 选择 randomVec 而不是固定的向量来生成切线,保证了每个片元的切线方向是随机的,防止产生规律性的视觉伪影。去掉与法线平行的分量,剩下的部分自然就与法线垂直,从而形成切线。
float occlusion = 0.0;
for(int i = 0; i < kernelSize; ++i)
{
    vec3 sample = TBN * samples[i]; // 切线->观察空间
    sample = fragPos + sample * radius; 

    vec4 offset = vec4(sample, 1.0);
    offset = projection * offset; // 观察->裁剪空间
    offset.xyz /= offset.w; // 透视划分
    offset.xyz = offset.xyz * 0.5 + 0.5; // 变换到0.0 - 1.0的值域
    float sampleDepth = -texture(gPositionDepth, offset.xy).w;
    float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth));
    occlusion += (sampleDepth >= sample.z ? 1.0 : 0.0) * rangeCheck;   
}
  • 将采样点从切线空间转换到观察空间,并以 fragPos 为中心进行偏移,得到实际的采样点位置。
  • 在 SSAO 中,我们需要比较采样点和当前像素的深度。深度信息通常是在屏幕空间的 G-buffer 中存储的,因此需要将 3D 空间中的采样点转换为屏幕空间的纹理坐标,才能进行比较。
  • 从 gPositionDepth 纹理中获取当前采样点的深度值,并与采样点的深度进行比较,判断该点是否被遮挡。如果被遮挡,则增加遮蔽值。
occlusion = 1.0 - (occlusion / kernelSize);
FragColor = occlusion;  
  • occlusion 中的值可能会累积到一个相对较大的数值,为了使这个值能够映射到 [0, 1] 的范围内,需要将它除以 kernelSize。
  • 计算得到的遮蔽值会被归一化,然后取反,以得到正确的环境光遮蔽值。
    在这里插入图片描述

六、环境遮蔽模糊

我们将ssao的结果模糊会得到更好的效果。所以我们再创建一个帧缓冲,来存储模糊结果。

GLuint ssaoBlurFBO, ssaoColorBufferBlur;
glGenFramebuffers(1, &ssaoBlurFBO);
glBindFramebuffer(GL_FRAMEBUFFER, ssaoBlurFBO);
glGenTextures(1, &ssaoColorBufferBlur);
glBindTexture(GL_TEXTURE_2D, ssaoColorBufferBlur);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ssaoColorBufferBlur, 0);

由于平铺的随机向量纹理保持了一致的随机性,我们可以使用这一性质来创建一个简单的模糊着色器:对当前像素和其邻近像素进行采样,并计算加权平均值来代替当前像素的值。

#version 330 core
in vec2 TexCoords;
out float fragColor;

uniform sampler2D ssaoInput;

void main() {
    vec2 texelSize = 1.0 / vec2(textureSize(ssaoInput, 0));
    float result = 0.0;
    for (int x = -2; x < 2; ++x) 
    {
        for (int y = -2; y < 2; ++y) 
        {
            vec2 offset = vec2(float(x), float(y)) * texelSize;
            result += texture(ssaoInput, TexCoords + offset).r;
        }
    }
    fragColor = result / (4.0 * 4.0);
}
vec2 texelSize = 1.0 / vec2(textureSize(ssaoInput, 0));
  • 计算单个纹理像素(纹素)的大小。
for (int x = -2; x < 2; ++x) 
    {
        for (int y = -2; y < 2; ++y) 
        {
            vec2 offset = vec2(float(x), float(y)) * texelSize;
            result += texture(ssaoInput, TexCoords + offset).r;
        }
    }
  • 这两个循环遍历了当前像素周围的一个4x4区域共16个采样点
  • offset为基于中心点像素的位移
  • 对一个像素附近的区域进行采样,并将这些采样的结果进行累加
fragColor = result / (4.0 * 4.0);
  • 取平均,输出像素颜色
    在这里插入图片描述

七、应用SSAO遮蔽因子

我们要做的就是逐片元地将环境遮蔽因子×环境分量上。

#version 330 core
out vec4 FragColor;
in vec2 TexCoords;

uniform sampler2D gPositionDepth;
uniform sampler2D gNormal;
uniform sampler2D gAlbedo;
uniform sampler2D ssao;

struct Light {
    vec3 Position;
    vec3 Color;

    float Linear;
    float Quadratic;
    float Radius;
};
uniform Light light;

void main()
{             
    // 从G缓冲中提取数据
    vec3 FragPos = texture(gPositionDepth, TexCoords).rgb;
    vec3 Normal = texture(gNormal, TexCoords).rgb;
    vec3 Diffuse = texture(gAlbedo, TexCoords).rgb;
    float AmbientOcclusion = texture(ssao, TexCoords).r;

    // Blinn-Phong (观察空间中)
    vec3 ambient = vec3(0.3 * AmbientOcclusion); // 这里我们加上遮蔽因子
    vec3 lighting  = ambient; 
    vec3 viewDir  = normalize(-FragPos); // Viewpos 为 (0.0.0),在观察空间中
    // 漫反射
    vec3 lightDir = normalize(light.Position - FragPos);
    vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Diffuse * light.Color;
    // 镜面
    vec3 halfwayDir = normalize(lightDir + viewDir);  
    float spec = pow(max(dot(Normal, halfwayDir), 0.0), 8.0);
    vec3 specular = light.Color * spec;
    // 衰减
    float dist = length(light.Position - FragPos);
    float attenuation = 1.0 / (1.0 + light.Linear * dist + light.Quadratic * dist * dist);
    diffuse  *= attenuation;
    specular *= attenuation;
    lighting += diffuse + specular;

    FragColor = vec4(lighting, 1.0);
}

在这里插入图片描述

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

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

相关文章

数字图像处理【14】特征检测——Harris角点检测

在上一篇文章已经介绍了opencv特征检测中的一些必要的概念&#xff0c;介绍了什么是特征&#xff0c;什么是角点&#xff0c;这些角点特征可以做什么。今天来看看对于我们人来说很容易就识别到角点特征&#xff0c;对于计算机来说是如何识别的&#xff0c;具体的步嘴原理是怎样…

78.内存对齐

目录 一.什么是内存对齐 二.为什么要内存对齐 三.视频教程 一.什么是内存对齐 有下面例子 #include <stdio.h>struct TEST {char a;int b; };int main(void) {struct TEST test;test.a A;test.b 1;printf("sizeof %ld\n",sizeof(test));return 0;} …

JVM指令重排序

文章目录 什么是指令重排序编译器优化JIT 编译优化处理器优化重排序数据依赖性 硬件层的内存屏障指令重排的代码验证好处减少管道阻塞提高缓存利用率利用并行执行单元性能提升更好地利用硬件资源 问题内存可见性问题编程复杂性增加调试困难 解决方案&#xff1a;Java内存模型&a…

AICon 全球人工智能与机器学习技术大会参会有感

目录 引言 大会背景 大会议程 参会体验 会后感想 结束语 引言 在数字化浪潮席卷全球的今天&#xff0c;人工智能和机器学习技术已成为推动社会进步和产业升级的关键力量。作为一名对AI技术非常感兴趣的开发者&#xff0c;在 8 月 18 日至 19 日这两天&#xff0c;我有幸参…

代码随想录算法训练营43期 | Day 15——110.平衡二叉树、 257. 二叉树的所有路径、404. 左叶子之和、222. 完全二叉树的节点个数

代码随想录算法训练营 代码随想录算法训练营43期 | Day 15110.平衡二叉树257. 二叉树的所有路径404. 左叶子之和222. 完全二叉树的节点个数 代码随想录算法训练营43期 | Day 15 110.平衡二叉树 /*** Definition for a binary tree node.* struct TreeNode {* int val;* …

【读点论文】Adaptive degraded document image binarization,真难复现,流程有点复杂

Adaptive degraded document image binarization Abstract 本文介绍了一种新的自适应方法&#xff0c;用于对退化文档进行二值化和增强。所提出的方法不需要用户进行任何参数调整&#xff0c;并且可以处理由于阴影、不均匀照明、低对比度、大信号相关噪声、污点和应变而发生的…

Spring 的 Aop 支持

Spring 的 Aop 支持 一、AOP基本概念1_AOP基本术语2_通知类型回顾3_AOP基于代理4_SpringBoot整合AOP 二、整体流程剖析1_AspectJAutoProxyRegistrar2_AnnotationAwareAspectJAutoProxyCreator1_继承结构2_初始化时机3_AnnotationAwareAspectJAutoProxyCreator的作用时机 3_收集…

【C++】_string类字符串详细解析(1)

假如没有给你生命&#xff0c;你连失败的机会都没有。你已经得到了最珍贵的&#xff0c;还需要抱怨什么!&#x1f493;&#x1f493;&#x1f493; 目录 ✨说在前面 &#x1f34b;知识点一&#xff1a;什么是string&#xff1f; •&#x1f330;1.string类的概念 •&#x1…

嘉立创PCB4层板

视频&#xff1a; 四层板PCB设计保姆级教程&#xff08;1&#xff09;&#xff1a;3.0HUB设计概述_哔哩哔哩_bilibili&#xff08;虽然是四层板实际这个还是两层板&#xff01;&#xff09; 不太建议看这个。 四层PCB 最简单终教学 高校培训课程 深入浅出 不会电路也能学会 设…

Nginx的核心!!! 负载均衡、反向代理

目录 负载均衡 1.轮询 2.最少连接数 3.IP哈希 4.加权轮询 5.最少时间 6.一致性哈希 反向代理 测试 之前讲过Nginx 的简介和正则表达式&#xff0c;那些都是Nginx较为基础的操作&#xff0c;Nginx 最重要的最核心的功能&#xff0c;当属反向代理和负载均衡了。 负载均…

YOLOv8实例分割+双目相机实现物体尺寸测量

1&#xff0c;YOLOv8实例分割原理介绍 YOLOv8是YOLO系列的最新版本&#xff0c;它在目标检测和实例分割方面都进行了显著的改进和创新。以下是YOLOv8实例分割原理的一些关键点&#xff1a; 先进的骨干和颈部架构&#xff1a;YOLOv8采用了先进的骨干和颈部架构来提高特征提取和物…

分治,1875C - Jellyfish and Green Apple

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1875C - Jellyfish and Green Apple 二、解题报告 1、思路分析 n 个苹果…

小阿轩yx-Kubernetes Pod入门

小阿轩yx-Kubernetes Pod入门 前言 Kubernetes 中 一个重要的概念就是 Pod&#xff08;豆荚&#xff09;并不直接管理容器&#xff0c;它的最小管理单元叫做 Pod。 Docker 应用中 把一个应用程序封装在一个镜像中&#xff0c;之后启动这个镜像并映射个宿主机端口号&#x…

03、Redis实战:商户查询缓存、缓存更新策略、缓存穿透、缓存雪崩、缓存击穿

2、商户查询缓存 2.1 什么是缓存? 什么是缓存? 就像自行车,越野车的避震器 举个例子:越野车,山地自行车,都拥有"避震器",防止车体加速后因惯性,在酷似"U"字母的地形上飞跃,硬着陆导致的损害,像个弹簧一样; 同样,实际开发中,系统也需要"避震器&qu…

2、Unity【基础】Mono中的重要内容

Unity基础 MonoBehavior中的重要内容 文章目录 Mono中的重要内容1、延迟函数1、延迟函数概念2、延迟函数使用3、延迟函数受对象失活销毁影响思考1 利用延时函数实现计时器思考2 延时销毁 2、协同程序1、Unity是否支持多线程2、协同程序概念3、协同程序和线程的区别4、协程的使用…

APP架构设计_1.官方应用架构指南

1.官方应用架构指南 1.1架构的原则 应用架构定义了应用的各个部分之间的界限以及每个部分应承担的职责。谷歌建议按照以下原则设计应用架构。 分离关注点通过数据模型驱动界面单一数据源单向数据流 1.2谷歌推荐的应用架构 每个应用应至少有两个层&#xff1a; 界面层 - 在屏…

近视防控明星:蔡司小乐圆中期临床数据详解

近视防控明星&#xff1a;蔡司小乐圆中期临床数据详解 小乐圆镜片作为近视防控镜片里的明星产品&#xff0c;从22年5月上市以来防控效果就一直备受大家的关注。而最近中期临床试验的结果&#xff0c;给家长孩子吃了一颗定心丸。 本次实验中&#xff0c;240位受试者被随机分成三…

基于springboot框架的电影订票系统_wqc3k

TOC springboot611基于springboot框架的电影订票系统_wqc3k--论文 绪 论 1.1研究背景和意义 随着科学技术的不断发展&#xff0c;计算机现在已经成为了社会的必需品&#xff0c;人们通过网络可以获得海量的信息&#xff0c;这些信息可以和各行各业进行关联&#xff0c;电影…

你应该停止使用的 7 个已弃用的 Python 库

欢迎来到雲闪世界。升级您的 Python 工具包&#xff1a;发现 7 个应停止使用的过时库以及替代它们的功能。最近&#xff0c;我回顾了 Python 的新特性&#xff0c;发现每个版本都引入了创新&#xff0c;使我们的日常开发工作变得更加轻松。 这让我意识到科技是一门永无止境的艺…

8.21 QT

1.思维导图 2. 服务器端 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer>//服务器类 #include <QMessageBox> #include <QDebug> #include <QList> #include <QTcpSocket>QT_BEGIN_NAMESPACE names…