[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 获取数据)或者提交数据(如用户登录、注册等表单数据)。 二…...

革新未来:高效智能数字人技术引领多元化应用
随着科技的不断进步,数字人技术已逐渐成为企业数字化转型中的重要工具。数字人不仅能够优化客户体验,还可以显著提升企业运营效率。本文将详细介绍一种高性能、高质量、低延迟、快速响应以及安全稳定的数字人技术方案,帮助企业在多元化场景中…...
鸿蒙OS基于UniApp的区块链钱包开发实践:打造支持鸿蒙生态的Web3应用#三方框架 #Uniapp
基于UniApp的区块链钱包开发实践:打造支持鸿蒙生态的Web3应用 前言 最近在带领团队开发一个支持多链的区块链钱包项目时,我们选择了UniApp作为开发框架。这个选择让我们不仅实现了传统移动平台的覆盖,还成功将应用引入了快速发展的鸿蒙生态…...

MySQL高可用集群
https://dev.mysql.com/doc/mysql-shell/8.4/en/mysql-innodb-cluster.html 1 什么是MySQL高可用集群 MySQL高可用集群:MySQL InnoDB ClusterInnoDB Cluster是MySQL官方实现高可用读写分离的架构方案,包含以下组件 MySQL Group Replication:简…...

Android Native 之 adbd进程分析
目录 1、adbd守护进程 2、adbd权限降级 3、adbd命令解析 1)adb shell 2)adb root 3)adb reboot 4、案例 1)案例之实现不需要执行adb root命令自动具有root权限 2)案例之实现不需要RSA认证直接能够使用adb she…...

树欲静而风不止,子欲养而亲不待
2025年6月2日,13~26℃,一般 待办: 物理2 、物理 学生重修 职称材料的最后检查 教学技能大赛PPT 遇见:使用通义创作了一副照片,很好看!都有想用来创作自己的头像了! 提示词如下: A b…...

【HarmonyOS Next之旅】DevEco Studio使用指南(二十九) -> 开发云数据库
目录 1 -> 开发流程 2 -> 创建对象类型 3 -> 添加数据条目 3.1 -> 手动创建数据条目文件 3.2 -> 自动生成数据条目文件 4 -> 部署云数据库 1 -> 开发流程 云数据库是一款端云协同的数据库产品,提供端云数据的协同管理、统一的数据模型和…...
【前端】性能优化篇
长期更新,建议关注收藏点赞。 目录 性能优化具体指标监控工具/系统解决方案htmlcssjsvuereact包体积静态资源图片优化白屏首屏加载速度缓存优化网络优化web worker动画 面试题汇总怎么实现无限加载,如果有一亿条数据怎么优化 性能优化 本文仅是列出常见…...

【Java学习笔记】异常
异常(Exception) 一、基本介绍 在 Java 程序中,将运行中发生的不正常情况称为 “异常”,开发过程中的语法错误和运行时发生的异常情况是不一样的。 二、异常的分类 1. Error(错误):Java 虚拟…...
Spring boot 策略模式
public abstract class Node {/*** 执行** param a* param b* return*/public abstract Integer execute(int a, int b); }package my.node;import org.springframework.stereotype.Component;Component("exec") public class ExecNode extends Node {Overridepublic…...
NodeJS全栈开发面试题讲解——P6安全与鉴权
✅ 6.1 如何防止 SQL 注入 / XSS / CSRF? 面试官您好,Web 安全三大经典问题分别从不同层面入手: 🔸 SQL 注入(Server端) 原理:恶意用户将 SQL 注入查询语句拼接,导致数据泄露或破坏…...
TCP三次握手/四次握手-TCP/IP四层模型-SSL/TLS-HTTP-HTTPS
重要概念 seq ( Squence Number ) 序列号,用于数据排序、去重,防止数据包乱序 ack ( Acknowledgement Number ) 确认好,表示期望接受的下一个字节序号,用于确认数据包被对方接受 TCP三次握手是建立可靠连接的过程,确…...