当前位置: 首页 > news >正文

LearnOpenGL——SSAO学习笔记

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; // 回到NDCreturn (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); // 屏幕 = 800x600void 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);
}

在这里插入图片描述

相关文章:

LearnOpenGL——SSAO学习笔记

LearnOpenGL——SSAO学习笔记 SSAO一、基本概念二、样本缓冲三、法向半球四、随机核心转动五、SSAO着色器六、环境遮蔽模糊七、应用SSAO遮蔽因子 SSAO 一、基本概念 环境光照是我们加入场景总体光照中的一个固定光照常量&#xff0c;它被用来模拟光的散射(Scattering)。散射应…...

[C语言]-基础知识点梳理-文件管理

前言 各位师傅们好&#xff0c;我是qmx_07&#xff0c;今天给大家讲解文件管理的相关知识&#xff0c;也就是常见的 读取&#xff0c;删除一类的操作 文件 为什么要使用文件&#xff1f; 程序的数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&…...

pcdn闲置带宽被动收入必看教程。第五讲:光猫更换和基础设置

PCDN闲置带宽被动收入必看教程 —— 第五讲&#xff1a;光猫更换和基础设置 为了从闲置带宽中获得被动收入&#xff0c;高效的网络设备至关重要。运营商提供的光猫通常能满足日常家用需求&#xff0c;但对于PCDN应用来说&#xff0c;它们可能不足以提供所需的高性能和稳定性。…...

工业数据采集网关简介-天拓四方

随着工业4.0和物联网&#xff08;IoT&#xff09;技术的深入发展&#xff0c;工业数据采集网关作为连接现场设备与上层管理系统的关键节点&#xff0c;其在智能工厂中的作用愈发凸显。本文将深入探讨工业数据采集网关的功能、特点、应用场景及其实操性&#xff0c;以期为读者提…...

Java 调整字符串,验证码生成

package text7;public class ZiFanz {public static void main(String[] args) {//1.定义两个字符串String strA "abcde";String strB "deabc";//2.abcde->bcdea->cdeab->deabc旋转字符串//旋转并比较boolean result cheak(strA, strB);System…...

【专题】全球商用服务机器人市场研究(2023)报告合集PDF分享(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p37366 近年来&#xff0c;随着人工智能、物联网和自动化技术的不断进步&#xff0c;商用服务机器人行业迅速崛起&#xff0c;展现出广阔的发展前景。从最初的实验室研发到如今的规模化应用&#xff0c;商用服务机器人已逐渐成为各行…...

SQL UA注入 (injection 第十八关)

简介 SQL注入&#xff08;SQL Injection&#xff09;是一种常见的网络攻击方式&#xff0c;通过向SQL查询中插入恶意的SQL代码&#xff0c;攻击者可以操控数据库&#xff0c;SQL注入是一种代码注入攻击&#xff0c;其中攻击者将恶意的SQL代码插入到应用程序的输入字段中&a…...

初阶数据结构之计数排序

非比较排序 计数排序 计数排序⼜称为鸽巢原理&#xff0c;是对哈希直接定址法的变形应⽤。 操作步骤&#xff1a; 1&#xff09;统计相同元素出现次数 2&#xff09;根据统计的结果将序列回收到原来的序列中 #include "CountSort.h" void Count(int* arr, int n)…...

【开端】记一次诡异的接口排查过程

一、绪论 最近碰到这么一个情况&#xff0c;接口请求超时。前提是两台服务器间的网络是畅通的&#xff0c;端口也是通&#xff0c;应用代码也是通。意思是在应用上&#xff0c;接口没有任何报错&#xff0c;能正常返回数据。客户端到服务端接口也能通&#xff0c;但是接收不到服…...

jenkins最佳实践(二):Pipeline流水线部署springCloud微服务项目

各位小伙伴们大家好呀&#xff0c;我是小金&#xff0c;本篇文章我们将介绍如何使用Pipeline流水线部署我们自己的微服务项目&#xff0c;之前没怎么搞过部署相关的&#xff0c;以至于构建流水线的过程中中也遇到了很多自己以前没有考虑过的问题&#xff0c;特写此篇&#xff0…...

第2章 C语言基础知识

第2章 C语言基础知识 1.printf()函数 在控制台输出数据&#xff0c;需要使用输出函数&#xff0c;C语言常用的输出函数为printf()。 printf()函数为格式化输出函数&#xff0c;其功能是按照用户指定的格式将数据输出到屏幕上。 printf(“格式控制字符串”,[输出列表]); 格式控…...

鹭鹰优化算法SBOA优化RBF神经网络的扩散速度实现多数入多输出数据预测,可以更改数据集(MATLAB代码)

一、鹭鹰优化算法介绍 鹭鹰优化算法&#xff08;Secretary Bird Optimization Algorithm, SBOA&#xff09;是一种新型的元启发式算法&#xff0c;它于2024年4月由Youfa Fu等人提出&#xff0c;并发表在SCI人工智能二区顶刊《Artificial Intelligence Review》上。该算法的灵感…...

MySQL基础练习题48-连续出现的数字

目录 题目 准备数据 分析数据 题目 找出所有至少连续出现三次的数字。 准备数据 ## 创建库 create database db; use db;## 创建表 Create table If Not Exists Logs (id int, num int)## 向表中插入数据 Truncate table Logs insert into Logs (id, num) values (1, 1) i…...

webrtc学习笔记2

音视频采集和播放 打开摄像头并将画面显示到页面 1. 初始化button、video控件 2. 绑定“打开摄像头”响应事件onOpenCamera 3. 如果要打开摄像头则点击 “打开摄像头”按钮&#xff0c;以触发onOpenCamera事件的调用 4. 当触发onOpenCamera调用时 a. 设置约束条件&#xff0c…...

Simple RPC - 06 从零开始设计一个服务端(上)_注册中心的实现

文章目录 Pre核心内容服务端结构概述注册中心的实现1. 注册中心的架构2. 面向接口编程的设计3. 注册中心的接口设计4. SPI机制的应用 小结 Pre Simple RPC - 01 框架原理及总体架构初探 Simple RPC - 02 通用高性能序列化和反序列化设计与实现 Simple RPC - 03 借助Netty实现…...

【深度学习】基于Transformers的大模型推理框架

本文旨在介绍基于transformers的decoder-only语言模型的推理框架。与开源推理框架不同的是&#xff1a; 本框架没有利用额外的开源推理仓库&#xff0c;仅基于huggingface&#xff0c;transformers&#xff0c;pytorch等原生工具进行推理&#xff0c;适合新手学习大模型推理流…...

电脑监控怎样看回放视频?一键解锁电脑监控回放,守护安全不留死角!高效员工电脑监控,回放视频随时查!

你是否曾好奇那些键盘敲击背后的秘密&#xff1f;电脑监控不仅是守护企业安全的隐形盾牌&#xff0c;更是揭秘高效与合规的魔法镜&#xff01;一键解锁安企神监控回放&#xff0c;就像打开时间宝盒&#xff0c;让过去的工作瞬间跃然眼前。无论是精彩瞬间还是潜在风险&#xff0…...

【一起学Rust | 框架篇 | Tauri2.0框架】tauri中rust和前端的相互调用(rust调用前端)

文章目录 前言1. rust中调用前端2. 如何向前端发送事件3. 前端监听事件4. 执行js代码 前言 近期Tauri 2.0 rc版本发布&#xff0c;2.0版本迎来第一个稳定版本&#xff0c;同时官方文档也进行了更新。Tauri是一个使用Rust构建的框架&#xff0c;可以让你使用前端技术来构建桌面…...

deque容器

deque容器的基本概念 deque 是 C 标准库中的双端队列&#xff08;double-ended queue&#xff09;容器&#xff0c;提供了在两端进行插入和删除操作的功能。 deque与vector区别&#xff1a; vector对于头部的插入删除效率低&#xff0c;数据量越大效率越低。deque相对而言&am…...

Redis远程字典服务器(9)—— 类型补充

类型查询传送门&#xff1a;Understand Redis data types | Docs 一&#xff0c;stream类型 官方文档对于这个类型的解释是&#xff1a;streams是一个数据结构&#xff0c;它表现得像一个 “append-only log”&#xff0c;就是只能往后面添加&#xff0c;底层是字符串&#x…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

1.3 VSCode安装与环境配置

进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件&#xff0c;然后打开终端&#xff0c;进入下载文件夹&#xff0c;键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

Rust 异步编程

Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

嵌入式常见 CPU 架构

架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集&#xff0c;单周期执行&#xff1b;低功耗、CIP 独立外设&#xff1b;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel&#xff08;原始…...

Docker拉取MySQL后数据库连接失败的解决方案

在使用Docker部署MySQL时&#xff0c;拉取并启动容器后&#xff0c;有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致&#xff0c;包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因&#xff0c;并提供解决方案。 一、确认MySQL容器的运行状态 …...