[OpenGL]实现屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)
一、简介
本文介绍了 屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 的基本概念,实现流程和简单的代码实现。实现 SSAO 时使用到了 OpenGL 中的延迟着色 (Deferred shading)技术。
按照本文代码实现后,可以实现以下效果:

二、SSAO介绍以及实现流程
1. SSAO 介绍
(1). 什么是 Ambient Occlusion, AO
简单来说 Ambient Occlusion(AO) 是一种全局照明(Global Illumination,GI)中的根据环境光(Ambient Light)参数和环境几何信息来计算场景中任何一点的光照强度系数的算法。
AO 描述了表面上的任何一点所接受到的环境光被周围几何体所遮蔽的百分比, 因此使得渲染的结果更加富有层次感,对比度更高。
例如:对于下图中的车辆缝隙处(红色),会受到周边模型面片的遮挡,导致接收到的环境光 (Ambient light)较少,因此这些地方会更暗。而在车辆的边缘处(绿色),几乎不会受到周围模型面片的遮挡,接收到的环境光较多,因此这些地方会更亮。

(2). 什么是 Screen-Space Ambient Occlusion, SSAO
为了计算准确的 AO,可以使用光线跟踪算法。但是光线跟踪消耗计算资源太大,而 屏幕空间环境光遮蔽 (Screen-Space Ambient Occlusion, SSAO) 是一种仅仅基于屏幕信息(例如,屏幕上各像素对应 片元 的空间位置信息)快速估计 AO 的算法。
SSAO 算法的基本思想为:
对于目标着色点,在其周围的一个球(或者面向相机方向的半球)内采样多个采样点,如果采样点大多被模型的其他面片遮挡,那么说明该目标着色点的 Ambien Occlusion 比较大,因此该着色点理应较暗些。而反之,目标着色点周围得到的采样点大部分并不会被模型的其他面片遮挡,那么说明该着色点的 Ambient Occlusion 更小,因此会更亮。
以下图为例:
在图中的 红色着色点 附近的一个球内采样,得到 8 个采样点,其中只有两个采样点(白色采样点)相比模型中的其他面片更靠近相机,不会被模型面片遮挡。而其他 6 个采样点(灰色采样点)都会被模型中的其他面片遮挡,则红色目标着色点的 Ambient Occlusion 更大,渲染结果中此着色点会更暗。
而图中的 绿色着色点 附近的大多数采样点(白色采样点)都不会被模型面片遮挡,只有两个采样点(灰色采样点)会被遮挡,则绿色目标着色点的 Ambient Occlusion 更小,渲染结果中此着色点会更亮。

在实现时也可以在朝向相机的半球内采样,理论上这样的结果会更加准确,而不是上图中所示的整个球内采样。在计算得到 各点的 AO 值后,也可以使用 滤波 操作对屏幕上各点的 AO 值进行滤波操作,平滑遮蔽效果,消除噪点。
2. SSAO 实现流程
实现 SSAO 主要分为 4 趟 pass。
(1). Geometry Pass
该 pass 对输入场景模型进行处理,将屏幕各像素对应片元的 texture_color, positon (in world space), normal 和 position (in view space) 输出到 GBuffer 中;
(2). Cal SSAO Pass
该 pass 根据 屏幕各像素对应片元(目标着色点)的 position (in view space) 信息,在各 片元 周围采样,得到采样点,根据采样点 是否会被模型遮挡计算目标着色点的 AO 值;
(3.) Blur SSAO Pass
该 pass 对 上趟流程中计算得到的 AO 进行滤波操作,的到滤波后的 blurredAO;
(4). Lighting (Shading) Pass
该 pass 根据 pass (1) 中得到的 texture_color, positon (in world space), normal 和 pass (3) 中得到的 blurredAO 计算各着色点的颜色值。使用 Phong 着色模型,公式如下:
I = I a ∗ b l u r r e d A O + I d + I s I=Ia * blurredAO + Id + Is I=Ia∗blurredAO+Id+Is
3. 主要代码讲解
(1). Geometry Pass Shader
geometryPassShader.vert:
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;
layout(location = 2) in vec2 aTexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;out vec3 vertexPos;
out vec3 vertexNor;
out vec2 textureCoord;
out vec4 vertexViewPos;void main() {textureCoord = aTexCoord;// 裁剪空间坐标系 (clip space) 中 点的位置gl_Position = projection * view * model * vec4(aPos, 1.0f);// 世界坐标系 (world space) 中 点的位置vertexPos = (model * vec4(aPos, 1.0f)).xyz;// 世界坐标系 (world space) 中 点的法向vertexNor = mat3(transpose(inverse(model))) * aNor;// 视图坐标系 (view space) 中 点的位置vertexViewPos = view * model * vec4(aPos, 1.0f);
}
geometryPassShader.frag:
#version 330 core
layout(location = 0) out vec4 FragColor; // diffuse color
layout(location = 1) out vec3 FragPos; // position in world space
layout(location = 2) out vec3 FragNor; // normal in world space
layout(location = 3) out vec4 FragViewPos; // position in view spacein vec3 vertexPos;
in vec3 vertexNor;
in vec2 textureCoord;
in vec4 vertexViewPos;uniform sampler2D texture0;void main() {FragPos = vertexPos;FragNor = vertexNor;FragColor = texture(texture0, textureCoord);FragViewPos = vertexViewPos;
}
(2). Cal SSAO Pass Shader
calSSAOPassShader.vert
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;
layout(location = 2) in vec2 aTexCoord;
out vec2 textureCoord;
void main() {gl_Position = vec4(aPos, 1.0f);textureCoord = aTexCoord;
}
calSSAOPassShader.frag:
#version 330 core
out float AO;
in vec2 textureCoord;uniform mat4 projection;uniform sampler2D textureViewPos; // position (in view space)uniform vec3 gKernel[64]; // random position offset// 计算目标片段的 Ambient Occlusion (AO) 值
// AO in [0,1]
// 为了便于后续计算 代码中的 AO 规定为:
// AO 越接近 0,说明该片段被遮挡的越多(越暗)
// AO 越接近 1,说明该片段被遮挡的越多(越亮)
void main() {vec3 shadeViewPos = texture(textureViewPos, textureCoord).xyz; // 目标片段在 view space 中的坐标AO = 0.0;float gSampleRad = 1.5f;for (int i = 0; i < 64; i++) {vec3 sampleViewPos = shadeViewPos + gKernel[i]; // 在目标片段周围随机采样vec4 sampleProPos =vec4(sampleViewPos, 1.0); // 采样点 在 view space 中的坐标sampleProPos = projection * sampleProPos;sampleProPos.xy /= sampleProPos.w;// 采样点 投影到屏幕,再归一化到[0,1]的 xy 坐标 (即采样点对应的 uv 坐标)sampleProPos.xy = sampleProPos.xy * 0.5 + vec2(0.5, 0.5);// 相机-采样点 射线与场景相交点(场景表面点)对应的 z 值(在 view space 中)float surfaceDepth = texture(textureViewPos, sampleProPos.xy).z;if (abs(shadeViewPos.z - surfaceDepth) < gSampleRad) {// step(a,b) = if (a<b) return 1.0 else return 0.0;// 在 view sapce 中, camera position 为 (0,0,0)// 假如 abs(surfaceDepth) < abs(sampleViewPos.z) 说明 场景表面点 比// 采样点距离相机更近,那么 AO += 1// 假如 abs(surfaceDepth) >= abs(sampleViewPos.z) 说明 采样点 比// 场景表面点 距离相机更近,那么 AO += 0AO += step(abs(surfaceDepth), abs(sampleViewPos.z));}}// 前面 AO 记录的是 '采样点 被 场景表面 遮挡的次数'// 因此需要 令 AO = 1.0 - AO / (采样次数)// 最后得到的 AO 才是目标片段的 AO 值AO = 1.0 - AO / 64;
}
(3). Blur SSAO Pass Shader
blurSSAOPassShader.vert:
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;
layout(location = 2) in vec2 aTexCoord;
out vec2 textureCoord;
void main() {gl_Position = vec4(aPos, 1.0f);textureCoord = aTexCoord;
}
blurSSAOPassShader.frag:
#version 330 core
// out vec4 FragColor;
out float blurredAO;in vec2 textureCoord;uniform sampler2D textureAO;void main() {blurredAO = 0.0;float Offsets[4] = float[](-1.5, -0.5, 0.5, 1.5);float originAO = texture(textureAO, textureCoord).x;for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {float AO = texture(textureAO, textureCoord).r;vec2 tc = textureCoord;tc.x = textureCoord.x + Offsets[j] / textureSize(textureAO, 0).x;tc.y = textureCoord.y + Offsets[i] / textureSize(textureAO, 0).y;blurredAO += texture(textureAO, tc).x;}}blurredAO /= 16.0;
}
(4). Lighting (Shading) Pass Shader
lightingSSAOPassShader.vert:
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNor;
layout(location = 2) in vec2 aTexCoord;
out vec2 textureCoord;
void main() {gl_Position = vec4(aPos, 1.0f);textureCoord = aTexCoord;
}
lightingSSAOPassShader.frag:
#version 330 core
out vec4 FragColor;in vec2 textureCoord;uniform int state;uniform vec3 lightPos;uniform vec3 cameraPos;
uniform vec3 k;uniform sampler2D textureColor; // color
uniform sampler2D texturePos; // position (in world space)
uniform sampler2D textureNor; // normal (in world space)
uniform sampler2D textureBlurredAO; // blurredAOvoid main() {vec3 vertexPos = texture(texturePos, textureCoord).xyz;vec3 vertexNor = texture(textureNor, textureCoord).xyz;vec3 lightColor = vec3(1.0f, 1.0f, 1.0f);// Ambient// Ia = ka * Lafloat ambientStrenth = k[0];vec3 ambient = ambientStrenth * lightColor;float blurredAO = texture(textureBlurredAO, textureCoord).x;if (state == 0) {// Rendering scene with SSAO on.ambient = ambient * vec3(blurredAO);} else if (state == 1) {// Rendering scene with SSAO off.} else {// Rendering AO.FragColor = vec4(blurredAO);return;}vec3 diffuse = vec3(0, 0, 0);vec3 specular = vec3(0, 0, 0);// Diffuse// Id = kd * max(0, normal dot light) * Ldfloat diffuseStrenth = k[1];vec3 normalDir = normalize(vertexNor);vec3 lightDir = normalize(lightPos - vertexPos);diffuse = diffuseStrenth * max(dot(normalDir, lightDir), 0.0) * lightColor;// Specular (Phong)// Is = ks * (view dot reflect)^s * Lsfloat specularStrenth = k[2];vec3 viewDir = normalize(cameraPos - vertexPos);vec3 reflectDir = reflect(-lightDir, normalDir);specular = specularStrenth * pow(max(dot(viewDir, reflectDir), 0.0f), 2) *lightColor;// Specular (Blinn-Phong)// Is = ks * (normal dot halfway)^s Ls// float specularStrenth = k[2];// vec3 viewDir = normalize(cameraPos - vertexPos);// vec3 halfwayDir = normalize(lightDir + viewDir);// vec3 temp_specular = specularStrenth *// pow(max(dot(normalDir, halfwayDir), 0.0f), 2) *// lightColor;diffuse = clamp(diffuse, 0.0, 1.0);specular = clamp(specular, 0.0, 1.0);// Obejct colorvec3 objectColor = texture(textureColor, textureCoord).xyz;// Color = Ambient + Diffuse + Specular// I = Ia + Id + IsFragColor = vec4((ambient + diffuse + specular) * objectColor, 1.0f);
}
4. 全部代码及模型文件
使用OpenGL实现 屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 的全部代码以及模型文件可以在 OpenGL实现屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 中下载。程序运行后按下 空格 键在使用SSAO渲染场景、直接渲染场景和渲染AO值三种模式中切换。
渲染结果如下:

三、参考
[1].ogl-tutorial45 Screen Space Ambient Occlusion
[2].游戏后期特效第四发 – 屏幕空间环境光遮蔽(SSAO)
相关文章:
[OpenGL]实现屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO)
一、简介 本文介绍了 屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO) 的基本概念,实现流程和简单的代码实现。实现 SSAO 时使用到了 OpenGL 中的延迟着色 (Deferred shading)技术。 按照本文代码实现后,可以实现以下…...
linux-NFS网络共享存储服务配置
1.NFS服务原理 NFS会经常用到,用于在网络上共享存储,这样讲,你对NFS可能不太了解,举一个例子, 加入有三台机器A,B,C,它们需要访问同一个目录,目录中都是图片,传统的做法是把这些 图…...
w-form-select.vue(自定义下拉框组件)
文章目录 1、w-form-select.vue 组件中每个属性的含义2、实例3、源代码 1、w-form-select.vue 组件中每个属性的含义 好的,我们来详细解释 w-form-select.vue 组件中每个属性的含义,并用表格列出它们是否与后端字段直接相关: 属性解释表格&…...
ovs实现lb负载均衡
负载均衡定义 负载均衡器的实现原理是通过硬件或软件设备将客户端访问流量根据转发策略分发到多个服务器或设备上,以确保系统的负载均衡。常见的实现方式包括: 二层负载均衡:使用虚拟MAC地址方式,根据OSI模型的二层进行负载均…...
机器学习-核函数(Kernel Function)
核函数(Kernel Function)是一种数学函数,主要用于将数据映射到一个更高维的特征空间,以便于在这个新特征空间中更容易找到数据的结构或模式。核函数的主要作用是在不需要显式计算高维特征空间的情况下,通过内积操作来实…...
计算最接近的数
计算最接近的数 真题目录: 点击去查看 E B卷 100分题型 题目描述 给定一个数组X和正整数K,请找出使表达式: X[i] - X[i 1] - … - X[i K - 1] 结果最接近于数组中位数的下标 i ,如果有多个 i 满足条件,请返回最大的 i. 其中&…...
【QNX】QNX侧查看内存信息的方法
在QNX实时操作系统中,🉑查看内存信息的方法有showmem、pidin、top以及hogs等👇🏻。 ① showmem 🦋🦋🦋showmem可用于显示进程的内存使用情况。 🦋🦋🦋通过…...
逐笔成交逐笔委托Level2高频数据下载和分析:20250121
逐笔成交逐笔委托下载 链接: https://pan.baidu.com/s/15NI2zLXYiczrUMQtwHgUrg?pwdbeiu 提取码: beiu Level2逐笔成交逐笔委托数据分享下载 通过Level2的逐笔成交与委托记录,这种高精度的毫秒级数据能够洞察诸多重要信息,包括庄家目的、误导性行为&am…...
AutoSar架构学习笔记
1.AUTOSAR(Automotive Open System Architecture,汽车开放系统架构)是一个针对汽车行业的软件架构标准,旨在提升汽车电子系统的模块化、可扩展性、可重用性和互操作性。AUTOSAR的目标是为汽车电子控制单元(ECU…...
2024年智慧消防一体化安全管控年度回顾与2025年预测
随着科技的飞速发展,智慧营区一体化安全管控在2024年取得了显著进展,同时也为2025年的发展奠定了坚实基础。 2024年年度回顾 政策支持力度持续加大:国家对消防安全的重视程度不断提高,出台了一系列涵盖技术创新、市场应用、人才培…...
基于单片机的智能台灯设计
摘要: 方向和亮度,采用的是手动调节。而对于儿童来说,他们通常不知道如何调整以及调整到何种程度。本文设计了一款智能台灯,当有人的 台灯是用于阅读学习而设计使用的灯,一般台灯用的灯泡是白炽灯、节能灯泡以及市面上流行的护眼台灯,可以调节高度、光照的时候,可以根据…...
HJ108 求最小公倍数(Java版本)
一、试题地址 求最小公倍数_牛客题霸_牛客网 二、试题描述 描述 对于给定的两个正整数 a,b,它们的最小公倍数 lcm(a,b) 是指能同时被 a 和 b 整除的最小正整数。 求解 lcm(a,b)。 输入描述: 在一行上输入两个整数 a,b(1≦a,b≦105)。 输出描述…...
使用tritonserver完成clip-vit-large-patch14图像特征提取模型的工程化。
1、关于clip-vit-large-patch14模型 关于openapi开源的clip-vit-large-patch14模型的特征提取,可以参考之前的文章:Elasticsearch向量检索需要的数据集以及768维向量生成这篇文章详细介绍了模型的下载地址、使用方式、测试脚本,可以让你一步…...
实操演练第003讲-数据通途:客户端连接SQL Server的完美攻略
SQL Server简介 基本概念 SQL Server是由微软公司开发的关系型数据库管理系统。它基于SQL(Structured Query Language,结构化查询语言)来管理和操作数据。SQL Server可以存储大量结构化数据,如客户信息、订单记录、库存数据等&a…...
golang接口
1.概念 golang接口是一个动态类型和动态值的集合,定义了对象的行为,不指定实现。只要一个类型定义了接口全部的方法,就可被认为是实现接口 **动态类型:**实现接口的具体数据类型 **动态值:**实现接口的数据的值或者引…...
LeetCode:37. 解数独
跟着carl学算法,本系列博客仅做个人记录,建议大家都去看carl本人的博客,写的真的很好的! 代码随想录 LeetCode:37. 解数独 编写一个程序,通过填充空格来解决数独问题。 数独的解法需 遵循如下规则ÿ…...
数据结构与算法之递归: LeetCode 37. 解数独 (Ts版)
解数独 https://leetcode.cn/problems/sudoku-solver/description/ 描述 编写一个程序,通过填充空格来解决数独问题数独的解法需 遵循如下规则: 数字 1-9 在每一行只能出现一次数字 1-9 在每一列只能出现一次数字 1-9 在每一个以粗实线分隔的 3x3 宫内…...
【氮化镓】香港科技大学陈Kevin-单片集成GaN比较器
一、引言(Introduction) GaN HEMT的重要性 文章开篇便强调了氮化镓(GaN)高电子迁移率晶体管(HEMT)在下一代功率转换系统中的巨大潜力。GaN HEMT具备高开关频率、低导通电阻、高击穿电压以及宽工作温度范围等优势,使其成为功率电子领域的热门研究对象。这些特性使得GaN…...
axios的使用总结
一、Axios 简介 Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js。在 Vue 项目中,它主要用于发送 HTTP 请求来获取数据(如从 API 获取数据)或者提交数据(如用户登录、注册等表单数据)。 二…...
革新未来:高效智能数字人技术引领多元化应用
随着科技的不断进步,数字人技术已逐渐成为企业数字化转型中的重要工具。数字人不仅能够优化客户体验,还可以显著提升企业运营效率。本文将详细介绍一种高性能、高质量、低延迟、快速响应以及安全稳定的数字人技术方案,帮助企业在多元化场景中…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
