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 一、基本概念 环境光照是我们加入场景总体光照中的一个固定光照常量,它被用来模拟光的散射(Scattering)。散射应…...
[C语言]-基础知识点梳理-文件管理
前言 各位师傅们好,我是qmx_07,今天给大家讲解文件管理的相关知识,也就是常见的 读取,删除一类的操作 文件 为什么要使用文件? 程序的数据是存储在电脑的内存中,如果程序退出,内存回收&…...
pcdn闲置带宽被动收入必看教程。第五讲:光猫更换和基础设置
PCDN闲置带宽被动收入必看教程 —— 第五讲:光猫更换和基础设置 为了从闲置带宽中获得被动收入,高效的网络设备至关重要。运营商提供的光猫通常能满足日常家用需求,但对于PCDN应用来说,它们可能不足以提供所需的高性能和稳定性。…...
工业数据采集网关简介-天拓四方
随着工业4.0和物联网(IoT)技术的深入发展,工业数据采集网关作为连接现场设备与上层管理系统的关键节点,其在智能工厂中的作用愈发凸显。本文将深入探讨工业数据采集网关的功能、特点、应用场景及其实操性,以期为读者提…...
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分享(附原数据表)
原文链接:https://tecdat.cn/?p37366 近年来,随着人工智能、物联网和自动化技术的不断进步,商用服务机器人行业迅速崛起,展现出广阔的发展前景。从最初的实验室研发到如今的规模化应用,商用服务机器人已逐渐成为各行…...
SQL UA注入 (injection 第十八关)
简介 SQL注入(SQL Injection)是一种常见的网络攻击方式,通过向SQL查询中插入恶意的SQL代码,攻击者可以操控数据库,SQL注入是一种代码注入攻击,其中攻击者将恶意的SQL代码插入到应用程序的输入字段中&a…...
初阶数据结构之计数排序
非比较排序 计数排序 计数排序⼜称为鸽巢原理,是对哈希直接定址法的变形应⽤。 操作步骤: 1)统计相同元素出现次数 2)根据统计的结果将序列回收到原来的序列中 #include "CountSort.h" void Count(int* arr, int n)…...
【开端】记一次诡异的接口排查过程
一、绪论 最近碰到这么一个情况,接口请求超时。前提是两台服务器间的网络是畅通的,端口也是通,应用代码也是通。意思是在应用上,接口没有任何报错,能正常返回数据。客户端到服务端接口也能通,但是接收不到服…...
jenkins最佳实践(二):Pipeline流水线部署springCloud微服务项目
各位小伙伴们大家好呀,我是小金,本篇文章我们将介绍如何使用Pipeline流水线部署我们自己的微服务项目,之前没怎么搞过部署相关的,以至于构建流水线的过程中中也遇到了很多自己以前没有考虑过的问题,特写此篇࿰…...
第2章 C语言基础知识
第2章 C语言基础知识 1.printf()函数 在控制台输出数据,需要使用输出函数,C语言常用的输出函数为printf()。 printf()函数为格式化输出函数,其功能是按照用户指定的格式将数据输出到屏幕上。 printf(“格式控制字符串”,[输出列表]); 格式控…...
鹭鹰优化算法SBOA优化RBF神经网络的扩散速度实现多数入多输出数据预测,可以更改数据集(MATLAB代码)
一、鹭鹰优化算法介绍 鹭鹰优化算法(Secretary Bird Optimization Algorithm, SBOA)是一种新型的元启发式算法,它于2024年4月由Youfa Fu等人提出,并发表在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. 如果要打开摄像头则点击 “打开摄像头”按钮,以触发onOpenCamera事件的调用 4. 当触发onOpenCamera调用时 a. 设置约束条件,…...
Simple RPC - 06 从零开始设计一个服务端(上)_注册中心的实现
文章目录 Pre核心内容服务端结构概述注册中心的实现1. 注册中心的架构2. 面向接口编程的设计3. 注册中心的接口设计4. SPI机制的应用 小结 Pre Simple RPC - 01 框架原理及总体架构初探 Simple RPC - 02 通用高性能序列化和反序列化设计与实现 Simple RPC - 03 借助Netty实现…...
【深度学习】基于Transformers的大模型推理框架
本文旨在介绍基于transformers的decoder-only语言模型的推理框架。与开源推理框架不同的是: 本框架没有利用额外的开源推理仓库,仅基于huggingface,transformers,pytorch等原生工具进行推理,适合新手学习大模型推理流…...
电脑监控怎样看回放视频?一键解锁电脑监控回放,守护安全不留死角!高效员工电脑监控,回放视频随时查!
你是否曾好奇那些键盘敲击背后的秘密?电脑监控不仅是守护企业安全的隐形盾牌,更是揭秘高效与合规的魔法镜!一键解锁安企神监控回放,就像打开时间宝盒,让过去的工作瞬间跃然眼前。无论是精彩瞬间还是潜在风险࿰…...
【一起学Rust | 框架篇 | Tauri2.0框架】tauri中rust和前端的相互调用(rust调用前端)
文章目录 前言1. rust中调用前端2. 如何向前端发送事件3. 前端监听事件4. 执行js代码 前言 近期Tauri 2.0 rc版本发布,2.0版本迎来第一个稳定版本,同时官方文档也进行了更新。Tauri是一个使用Rust构建的框架,可以让你使用前端技术来构建桌面…...
deque容器
deque容器的基本概念 deque 是 C 标准库中的双端队列(double-ended queue)容器,提供了在两端进行插入和删除操作的功能。 deque与vector区别: vector对于头部的插入删除效率低,数据量越大效率越低。deque相对而言&am…...
Redis远程字典服务器(9)—— 类型补充
类型查询传送门:Understand Redis data types | Docs 一,stream类型 官方文档对于这个类型的解释是:streams是一个数据结构,它表现得像一个 “append-only log”,就是只能往后面添加,底层是字符串&#x…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...
