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);
}

在这里插入图片描述

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

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

相关文章

QT事件机制理解

事件和信号 从硬件层来看: 事件就是一种中断&#xff0c; 中断的产生形式: 1.用户操控硬件所产生的中断。 2.由系统自身所产生的中断&#xff0c;比如说定时器。 这种中断由系统内核监控&#xff0c;由系统内核接收到中断并向CPU发出的执行请求就叫信号。所以说事件是信号产生…

C++,std::bind 详解

文章目录 1. 概述2. 基本用法2.1 使用占位符2.2 示例 3. 总结 1. 概述 std::bind 是 C11 引入的一个功能&#xff0c;它允许你将函数&#xff08;或成员函数、函数对象&#xff09;与其参数绑定&#xff0c;生成一个新的可调用对象。这个功能在需要将函数及其参数一起传递给其…

[OC]萝卜圈玩行车记录仪

图1-1&#xff0c;你的手动小车 代码是 #机器人驱动主程序 #请在main中编写您自己的机器人驱动代码 import tkinter as tk import turtle v0 # 速度 accFalse;slowFalse;leftFalse;rightFalse # 按键状态 step0.5 # 一次速度变化量 def keyup_press(event):global acc;accTru…

正点原子linux开发板 qt程序交叉编译执行

1.开发板光盘 A-基础资料->5、开发工具->1、交叉编译器->fsl-imx-x11-glibc-x86_64-meta-toolchain-qt5-cortexa7hf-neon-toolchain-4.1.15-2.1.0.sh 拷贝到 Ubuntu 虚拟机 用文件传输系统或者共享文件夹传输到linux虚拟机 用ls -l查看权限&#xff0c;如果是白色的使…

保姆级-C#与Halcon的窗体界面展示阈值分割图像教程(机器视觉保姆级教程)

经历上一篇《零基础小白实现C#调用halcon dll的过程&#xff0c;并测试程序证明C#halcon联合开发成功》的发布已经过去三天啦&#xff0c; 零基础小白实现C#调用halcon dll的过程&#xff0c;并测试程序证明C#halcon联合开发成功-CSDN博客 在友友的催更下&#xff0c;我将用我…

rabbitmq镜像集群搭建

用到的ip地址 ip地址端口192.168.101.65&#xff08;主&#xff09;15672192.168.101.7515672192.168.101.8515672 安装erlang和rabbitmq 安装 安装三个包 yum install esl-erlang_23.0-1_centos_7_amd64.rpm -y yum install esl-erlang-compat-18.1-1.noarch.rpm -y rpm -…

探索CompletableFuture:高效异步编程的利器

目录 一、CompletableFuture基本功能安利 二、CompletableFuture使用介绍 &#xff08;一&#xff09;任务创建使用 1.supplyAsync创建带有返回值的异步任务 2.runAsync创建没有返回值的异步任务 &#xff08;二&#xff09;异步回调使用 1.异步回调&#xff1a;thenApp…

基于Sringboot+Vue个人驾校预约管理系统--论文pf

TOC springboot503基于SringbootVue个人驾校预约管理系统--论文pf 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。…

XSS-games

XSS 1.XSS 漏洞简介2.XSS的原理3.XSS的攻击方式4.XSS-GAMESMa SpaghetJefffUgandan KnucklesRicardo MilosAh Thats HawtLigmaMafiaOk, BoomerWW3svg 1.XSS 漏洞简介 ​ XSS又叫CSS&#xff08;Cross Site Script&#xff09;跨站脚本攻击是指恶意攻击者往Web页面里插入恶意Sc…

Nginx服务器申请及配置免费SSL证书

免费SSL证书申请 背景&#xff1a; 我的情况是这样&#xff0c;域名解析是华为云的&#xff0c;然后免费证书在腾讯云申请。但是大致的配置流程都是一样的 在腾讯云平台申请免费的SSL证明(目前有效期是90天)&#xff0c;申请步骤如下 主要步骤说明 申请免费SSL证书根据申请时说…

对商品评论进行文本分析(NLP)的实战项目

文本分析技术是指使用计算机程序或算法处理、分析和理解文本数据的一系列方法。这种技术在自然语言处理&#xff08;NLP&#xff09;领域中非常重要&#xff0c;它可以应用于多种场景&#xff0c;包括但不限于情感分析、主题识别、信息提取、文本分类等。以下是一些常见的文本分…

如何在本地和远程删除 Git 分支?

如何在本地和远程删除 Git 分支&#xff1f; 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市开发者社区主理人 擅长.n…

江西学术会议:第五届计算机、大数据与人工智能国际会议

第五届计算机、大数据与人工智能国际会议(ICCBDAI 2024)将于2024年11月1日-3日在江西景德镇召开。本届会议由景德镇陶瓷大学主办&#xff0c;西安交通大学、暨南大学、南京邮电大学、景德镇学院、ELSP&#xff08;爱迩思出版社&#xff09;、ESBK国际学术交流中心、AC学术平台协…

Transformer模型中的Position Embedding实现

引言 在自然语言处理&#xff08;NLP&#xff09;中&#xff0c;Transformer模型自2017年提出以来&#xff0c;已成为许多任务的基础架构&#xff0c;包括机器翻译、文本摘要和问答系统等。Transformer模型的核心之一是其处理序列数据的能力&#xff0c;而Position Embedding在…

你是如何克服编程学习中的挫折感的?(-@-^-0-)

在编程学习中遇到挫折感是极为常见且正常的现象&#xff0c;因为编程往往涉及解决复杂问题、理解抽象概念以及不断试错的过程。 以下是一些建议&#xff0c;帮助你在面对挫折时调整心态&#xff0c;继续前行&#xff1a; 接受失败是成长的一部分&#xff1a;首先要认识到&#…

专题---自底向上的计算机网络(计算机网络相关概述)

目录 计算机网络相关概述 物理层 数据链路层 网络层 运输层 应用层 网络安全 1.计算机网络相关概述&#xff08;具体细节http://t.csdnimg.cn/NITAW&#xff09; 什么是计算机网络&#xff1f; 计算机网络是将一个分散的&#xff0c;具有独立功能的计算机系统&#x…

测绘程序设计|初识C#编程语言|C#源码结构|面向对象|MFC、WinFrom与WPF

由于微信公众号改变了推送规则&#xff0c;为了每次新的推送可以在第一时间出现在您的订阅列表中&#xff0c;记得将本公众号设为星标或置顶喔~ 根据笔者经验&#xff0c;分享了C#编程语言、面向对象以及MFC、WinForm与WPF界面框架相关知识~ &#x1f33f;前言 c#作为测绘程序…

海外媒体投稿:怎样在法国媒体发稿宣传中获得成功

法国是一个充满机遇的销售市场&#xff0c;而媒体发稿营销推广是企业在法国市场里扩张曝光度和提升知名度的有效途径。下面我们就共享如何运用低投资得到高收益的办法&#xff0c;帮助企业在法国媒体发稿推广过程中获得成功。 第一步&#xff1a;掌握目标群体在进行法国媒体发稿…

AI时代来临:数字人主播,虚拟代言人正颠覆行业!

数字人主播、虚拟代言人……你的身边有“数字同事”了吗&#xff1f;近年来&#xff0c;越来越多的数字人开始活跃在各个行业&#xff0c;承担起直播带货、知识讲解、新闻播报和品牌代言等任务。就在刚刚过去的五一假期&#xff0c;当大多数人还在休息时&#xff0c;不少数字人…

RongCallKit iOS 端本地私有 pod 方案

RongCallKit iOS 端本地私有 pod 方案 需求背景 适用于源码集成 CallKit 时&#xff0c;使用 pod 管理 RTC framework 以及源码。集成 CallKit 时&#xff0c;需要定制化修改 CallKit 的样式以及部分 UI 功能。适用于 CallKit 源码 Debug 调试便于定位相关问题。 解决方案 从…