[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)较少,因此这些地方会更暗。而在车辆的边缘处(绿色),几乎不会受到周围模型面片的遮挡,接收到的环境光较多,因此这些地方会更亮。
AO示意图

(2). 什么是 Screen-Space Ambient Occlusion, SSAO

为了计算准确的 AO,可以使用光线跟踪算法。但是光线跟踪消耗计算资源太大,而 屏幕空间环境光遮蔽 (Screen-Space Ambient Occlusion, SSAO) 是一种仅仅基于屏幕信息(例如,屏幕上各像素对应 片元 的空间位置信息)快速估计 AO 的算法。

SSAO 算法的基本思想为:
对于目标着色点,在其周围的一个球(或者面向相机方向的半球)内采样多个采样点,如果采样点大多被模型的其他面片遮挡,那么说明该目标着色点的 Ambien Occlusion 比较大,因此该着色点理应较暗些。而反之,目标着色点周围得到的采样点大部分并不会被模型的其他面片遮挡,那么说明该着色点的 Ambient Occlusion 更小,因此会更亮。

以下图为例:
在图中的 红色着色点 附近的一个球内采样,得到 8 个采样点,其中只有两个采样点(白色采样点)相比模型中的其他面片更靠近相机,不会被模型面片遮挡。而其他 6 个采样点(灰色采样点)都会被模型中的其他面片遮挡,则红色目标着色点的 Ambient Occlusion 更大,渲染结果中此着色点会更暗。
而图中的 绿色着色点 附近的大多数采样点(白色采样点)都不会被模型面片遮挡,只有两个采样点(灰色采样点)会被遮挡,则绿色目标着色点的 Ambient Occlusion 更小,渲染结果中此着色点会更亮。

SSAO 示意图
在实现时也可以在朝向相机的半球内采样,理论上这样的结果会更加准确,而不是上图中所示的整个球内采样。在计算得到 各点的 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=IablurredAO+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)

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

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

相关文章

MATLAB绘图时线段颜色、数据点形状与颜色等设置,介绍

MATLAB在绘图时&#xff0c;设置线段颜色和数据点的形状与颜色是提高图形可读性与美观性的重要手段。本文将详细介绍如何在 MATLAB 中设置这些属性。 文章目录 线段颜色设置单字母颜色表示法RGB 值表示法 数据点的形状与颜色设置设置数据点颜色和形状示例代码 运行结果小结 线段…

AIGC视频生成国产之光:ByteDance的PixelDance模型

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍ByteDance的视频生成模型PixelDance&#xff0c;论文于2023年11月发布&#xff0c;模型上线于2024年9月&#xff0c;同时期上线的模型还有Seaweed&…

mac 电脑上安装adb命令

在Mac下配置android adb命令环境&#xff0c;配置方式如下&#xff1a; 1、下载并安装IDE &#xff08;android studio&#xff09; Android Studio官网下载链接 详细的安装连接请参考 Mac 安装Android studio 2、配置环境 在安装完成之后&#xff0c;将android的adb工具所在…

# [0114] Task01 《数学建模导论》P1 解析几何与方程模型

链接&#xff1a;https://www.datawhale.cn/activity/124 整理的相关代码库 GitHub 页面链接 绪论 姜启源&#xff1a;“数学建模就是建立数学模型解决实际问题” 本质还是解应用题&#xff0c;只是曾经的“小明买糖”变成了如今的“嫦娥探月”。 SEIR 模型&#xff0c;也…

NewStar CTF week1 web wp

谢谢皮蛋 做这题之前需要先去学习一些数据库的知识 1 order by 2 1可以理解为输入的id&#xff0c;是一个占位符&#xff0c;按第二列排序用来测试列数&#xff0c;如果没有两列则会报错-1 union select 1,2 -1同样是占位符&#xff0c;union的作用是将注入语句合并到原始语句…

备赛蓝桥杯之第十五届职业院校组省赛第二题:分享点滴

提示&#xff1a;本篇文章仅仅是作者自己目前在备赛蓝桥杯中&#xff0c;自己学习与刷题的学习笔记&#xff0c;写的不好&#xff0c;欢迎大家批评与建议 由于个别题目代码量与题目量偏大&#xff0c;请大家自己去蓝桥杯官网【连接高校和企业 - 蓝桥云课】去寻找原题&#xff0…

C语言初阶牛客网刷题——JZ17 打印从1到最大的n位数【难度:入门】

1.题目描述 牛客网OJ题链接 题目描述&#xff1a; 输入数字 n&#xff0c;按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3&#xff0c;则打印出 1、2、3 一直到最大的 3 位数 999。 用返回一个整数列表来代替打印n 为正整数&#xff0c;0 < n < 5 示例1 输入&…

PAT甲级-1014 Waiting in Line

题目 题目大意 一个银行有n个窗口&#xff0c;每个窗口最多站m个人&#xff0c;其余人在黄线外等候。假设k个人同时进入银行按先后次序排队&#xff0c;每个人都有相应的服务时间。每个顾客都选择最短队列站&#xff0c;如果有多个相同长度的队列&#xff0c;按序号小的站。给…

LangChain + llamaFactory + Qwen2-7b-VL 构建本地RAG问答系统

单纯仅靠LLM会产生误导性的 “幻觉”&#xff0c;训练数据会过时&#xff0c;处理特定知识时效率不高&#xff0c;缺乏专业领域的深度洞察&#xff0c;同时在推理能力上也有所欠缺。 正是在这样的背景下&#xff0c;检索增强生成技术&#xff08;Retrieval-Augmented Generati…

11 文件与IO

1 File类 1.1 基本介绍 File类代表系统中的文件对象(文件或目录)&#xff0c;位于java.io包下。 存储介质上的文件或目录在Java程序中都是用File类的实例来表示。 通过File类&#xff0c;可以实现对系统中文件或目录的操作&#xff0c;类似我们在操作系统中借助鼠标、快捷键…

Windows第一次上手鸿蒙周边

端云一体所需装备 很重要&#xff1a;C/D/E/F盘要有二三十G的可用空间&#xff01; 硬件&#xff1a;华为鸿蒙实验箱&#xff08;基础版&#xff09;》飞机板核心板环境监测板 软件&#xff1a;Visual Studio Code写代码 终端编译 Hiburn烧录到开发板 MobaXterm &#xff08…

Node.js——express中间件(全局中间件、路由中间件、静态资源中间件)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

财税资金数据管理一体化大屏 | 智慧金融合集

随着科技的快速进步和数字化转型的加速&#xff0c;金融、税务等机构和企业面临的数据量呈现出爆炸式增长。传统的数据分析方法早已无法胜任现代业务的需求。为此&#xff0c;许多机构开始尝试创新的软件工具来更好的管理繁琐的数据。 通过图扑软件的数据可视化大屏&#xff0c…

5. 推荐算法的最基础和最直观的认识

1.性别年龄转换为统一的计量单位 所谓推荐&#xff0c;就是替别人推荐&#xff0c;比如工厂A需要招男员工&#xff0c;希望大家推荐认识的人。那么在这里&#xff0c;就有了推荐的概念&#xff0c;限定条件是男。我们知道&#xff0c;人的性别一般分为男或者女。在这里假设把男…

【Postgres_Python】使用python脚本将多个PG数据库合并为一个PG数据库

需要合并的多个PG数据库表个数和结构一致&#xff0c;这里提供一种思路&#xff0c;选择sql语句insert插入的方式进行&#xff0c;即将其他PG数据库的每个表内容插入到一个PG数据库中完成数据库合并 示例代码说明&#xff1a; 选择一个数据库导出表结构为.sql文件&#xff08…

MyBatis和JPA区别详解

文章目录 MyBatis和JPA区别详解一、引言二、设计理念与使用方式1、MyBatis&#xff1a;半自动化的ORM框架1.1、代码示例 2、JPA&#xff1a;全自动的ORM框架2.1、代码示例 三、性能优化与适用场景1、MyBatis&#xff1a;灵活的SQL控制1.1、适用场景 2、JPA&#xff1a;开发效率…

通信工程行业现状与前景2024:引领未来增长新浪潮

在当今数字化时代&#xff0c;通信工程犹如现代社会的神经系统&#xff0c;其重要性不言而喻。从日常的语音通话、信息传递&#xff0c;到复杂的工业自动化、智能交通系统&#xff0c;通信技术无处不在&#xff0c;深刻改变着人类的生活方式与社会运转模式。随着科技的持续创新…

学习ASP.NET Core的身份认证(基于JwtBearer的身份认证7)

本文验证基于请求头中传递token信息的认证方式&#xff0c;webapi项目的控制器类中新建如下函数&#xff0c;仅通过验证的客户端能调用&#xff0c;需要客户端请求在Header中添加’Authorization’: Bearer token’的键值对且通过token验证后才能调用。 [Authorize] [HttpGet]…

电子应用设计方案101:智能家庭AI喝水杯系统设计

智能家庭 AI 喝水杯系统设计 一、引言 智能家庭 AI 喝水杯系统旨在为用户提供个性化的饮水提醒和健康管理服务&#xff0c;帮助用户养成良好的饮水习惯。 二、系统概述 1. 系统目标 - 精确监测饮水量和饮水频率。 - 根据用户的身体状况和活动量&#xff0c;智能制定饮水计划。…

Navicat 导出表结构后运行查询失败ERROR 1064 (42000): You have an error in your SQL syntax;

本文主要介绍了在使用 Navicat 导出 MySQL 表后新建查询时出现报错的问题及解决方案。 一、问题描述 Navicat导出MySql中的表&#xff0c;在新建数据库新建查询时通常会报错You have an error in your SQL syntax; check the manual that corresponds to your MySQL server …