QT With OpenGL(SSAO)(Screen-Space Ambient Occlusion)

news2024/12/23 15:08:41

文章目录

  • 在G_Buffer中加入深度信息
  • 使用深度信息得到环境遮蔽的结果
    • 1. 新建SSAO帧缓存类
    • 2.生成法向半球核心
    • 3. 生成随机核心转动纹理
      • 为什么要生成随机核心转动
      • 创建一个小的随机旋转向量纹理
    • 4.使用G_Buffer渲染SSAO纹理
      • 传入参数
      • 着色器
        • 1. 获取当前像素在纹理中的信息
        • 2.计算TBN矩阵
        • 3. 遍历采样点
    • SSAO输出测试

在G_Buffer中加入深度信息

  1. 将深度转化为线性
float LinearizeDepth(float depth)
{
    float z = depth * 2.0 - 1.0; // 回到NDC
    return (2.0 * NEAR * FAR) / (FAR + NEAR - z * (FAR - NEAR));    
}
  1. 将深度记录在position的alpha值中。
gPositionDepth.a = LinearizeDepth(gl_FragCoord.z); 

注:深度的范围不能局限在【0,1】,所以需要设置纹理数据类型为GL_RGBA16F。纹理封装方法设置为GL_CLAMP_TO_EDGE

使用深度信息得到环境遮蔽的结果

首先我们需要在该片段(像素)的法线半球方向上采样

1. 新建SSAO帧缓存类

#include <QOpenGLFunctions>
#include <QOpenGLFramebufferObject>
#include <QOpenGLShaderProgram>

class SSAO : public QOpenGLFunctions
{
public:
    QOpenGLFramebufferObject* SSAOFBO;
    QOpenGLShaderProgram *SSAOShader;
    QVector<QVector3D> SSAOKernel;

public:
    SSAO(int w,int h);
    void generateKernel();
    void setUniform();
    ~SSAO();
};

2.生成法向半球核心

void SSAO::generateKernel()
{
    for(int i=0;i<64;++i){
        QVector3D sample(random_double(-1,1),random_double(-1,1),random_double(0,1));
        sample.normalize();//生成一个半球面的随机向量
        sample *= random_double(0,1);//将随机向量均匀分布在半球体内
        double scale = double(i)/64.0;
        scale = 0.1 + scale*scale*(0.9);//将随机向量再次缩放,使绝大多数采样点接近原点
        sample *= scale;
        SSAOKernel.push_back(sample);
    }
}

3. 生成随机核心转动纹理

为什么要生成随机核心转动

  • 如果对每一个像素都随机生成64个采样向量,那么性能将会受到很大影响。
  • 如果对于每一个像素都使用同一个64随机采样向量,效果不好???
  • 如果对于每一个像素,都将改64随机采样向量做一次旋转,则会得到更为随机的采样

因此引入随机核心转动
如果我们对场景中每一个片段创建一个随机旋转向量,这会很快将内存耗尽。
所以,更好的方法是创建一个小的随机旋转向量纹理平铺在屏幕上。

创建一个小的随机旋转向量纹理

    for(int i=0;i<16;++i){
        QVector3D noise(random_double(-1,1),random_double(-1,1),0.0);
        SSAONoise.push_back(noise);
    }

使用OpenGL函数绑定到纹理

{
    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);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D,noiseTexture);
}

使用QT::QOpenGLTexture,绑定到QOpenGLTexture类。

{
    texture.setSize(4,4,1);
    texture.setFormat(QOpenGLTexture::RGBFormat);
    texture.allocateStorage(QOpenGLTexture::RGB,QOpenGLTexture::Float32);
    texture.setData(0,QOpenGLTexture::RGB,QOpenGLTexture::Float32,(void*)SSAONoise.data());
    texture.setMinMagFilters(QOpenGLTexture::Nearest,QOpenGLTexture::Nearest);
    texture.setWrapMode(QOpenGLTexture::Repeat);
    glActiveTexture(GL_TEXTURE0);
    texture.bind();
}

效果展示

//test
debugShader = new QOpenGLShaderProgram();
debugShader->addShaderFromSourceFile(QOpenGLShader::Vertex,":/map_depth.vert");
debugShader->addShaderFromSourceFile(QOpenGLShader::Fragment,":/map_depth.frag");
debugShader->link();

debugShader->bind();//shader
debugShader->setUniformValue("depthMap",0);
renderQuad();

在这里插入图片描述
纹理成功生成。

4.使用G_Buffer渲染SSAO纹理

传入参数

  • gPositionDepth (观察空间(View Space))位置深度信息
  • gNormal 法线信息
  • noiseTexture 随机法向半球
  • samples[64]; 64个采样向量

注意: SSAO需要的位置信息是视口(View) 角度下看到的物体的视口坐标信息和深度,这与之前做延迟渲染(Deferred Shader) 不同,延迟渲染需要的信息为视口角度下物体的世界坐标信息。

因此若要统一G_Buffer,需要将G_Buffer提供的位置深度信息统一为视口坐标下的位置深度信息。在延迟渲染中,将 视口矩阵 ∗ 光源信息 视口矩阵*光源信息 视口矩阵光源信息 ,使整个光照渲染都在视口坐标系下进行。

gPosition不再是世界坐标,而是视口坐标(第一张图)
在这里插入图片描述

着色器

1. 获取当前像素在纹理中的信息

width,height为当前屏幕的大小

const vec2 noiseScale = vec2(width/4.0f, height/4.0f);
    // Get input for SSAO algorithm
    vec3 fragPos = texture(gPositionDepth, TexCoords).xyz;
    vec3 normal = texture(gNormal, TexCoords).rgb;
    vec3 randomVec = texture(texNoise, TexCoords * noiseScale).xyz;

若渲染(0,0)像素点,随机转动核心纹理为(0,0)点
若渲染( 1 / w i d t h 1/width 1/width 1 / h e i g h t 1/height 1/height)像素点,随机转动核心纹理为( 1 / 4 1/4 1/4 1 / 4 1/4 1/4)点
即每4X4个像素使用一组随机转动核心纹理。

2.计算TBN矩阵

已知Normal向量,获得了一个随机向量randomVec ( x , y , 0 ) (x,y,0) (x,y,0),求TBN。
在这里插入图片描述

    // Create TBN change-of-basis matrix: from tangent-space to view-space
    vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
    vec3 bitangent = cross(normal, tangent);
    mat3 TBN = mat3(tangent, bitangent, normal);

关于切线空间的内容可参考

  • 【D3D11游戏编程】学习笔记二十四:切线空间(Tangent Space)
  • LearnOpenGL

3. 遍历采样点

将样本从切线空间变换到观察空间

float occlusion = 0.0;
for(int i = 0; i < kernelSize; ++i)
{
    // 获取样本位置
    vec3 sample = TBN * samples[i]; // 切线->观察空间
    sample = fragPos + sample * radius; 

    [...]
}

变换sample从观察空间到屏幕空间

    vec4 offset = vec4(Tsample, 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; 

引入范围测试从而保证我们只当被测深度值在取样半径内时影响遮蔽因子
fragPos.z是渲染点深度
sampleDepth是采样点的像素深度

smoothstep的意义可参照Shader实验室: smoothstep函数

float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth ));
occlusion += (sampleDepth >= Tsample.z ? 1.0 : 0.0) * rangeCheck;

如果 ∣ f r a g P o s . z − s a m p l e D e p t h ∣ < r a d i u s |fragPos.z - sampleDepth|<radius fragPos.zsampleDepth<radius,则rangeCheck=1
如果 ∣ f r a g P o s . z − s a m p l e D e p t h ∣ > r a d i u s |fragPos.z - sampleDepth|>radius fragPos.zsampleDepth>radius,则rangeCheck ∈ ( 0 , 1 ) \in(0,1) (0,1)
∣ f r a g P o s . z − s a m p l e D e p t h ∣ > r a d i u s |fragPos.z - sampleDepth|>radius fragPos.zsampleDepth>radius并不直接移除遮蔽贡献是因为,这样会在范围检测边缘看到很明显的边界。因此使用距离远近改变贡献度。
在这里插入图片描述

当采样点像素深度(物体边缘)大于等于检测点深度(空心圈),说明该点未遮蔽了渲染点的环境光照,Occlusion遮蔽值增加。

将遮蔽贡献根据核心的大小标准化
occlusion / kernelSize 得到未遮蔽系数,再用1减去,得到遮蔽系数。

occlusion = 1.0 - (occlusion / kernelSize);
FragColor = occlusion;

SSAO输出测试

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

SpringBoot -- AOP

一直只听过AOP&#xff0c;但是并不知道AOP的原理使用&#xff0c;参考深入浅出SpringBoot2.x学习一下SpringBoot AOP编程 AOP是基于动态代理实现的 静态代理就是代理类中有一个实现类&#xff0c;和实现类相同名称的方法&#xff0c;调用代理的request方法&#xff0c;执行顺…

RF检测器/控制器MS2351可pin对pin兼容AD8314、MAX4003

MS2351M/MS2351D 是一款对数放大器芯片&#xff0c;主要用于接收信号强度指示 (RSSI) 与控制功率放大器&#xff0c;工作频率范围是50MHz&#xff5e;3000MHz&#xff0c;动态范围可达 35dB 到 45dB。可pin对pin兼容AD8314、MAX4003。 MS2351M/MS2351D 是电压响应器件&#xff…

【c语言】结构体详解 | 结构体数组/指针

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c语言系列专栏&#xff1a;c语言之路重点知识整合 &#x…

【ChirpStack 】如何获取 JWT TOKEN

LoRa App Server 提供了两类 API 接口&#xff0c;其中 RESTful JSON API 提供了一个 API console&#xff0c;在AS地址的基础上使用 /api 即可访问&#xff0c;罗列了 API 端点和文档介绍&#xff0c;测试起来非常方便。 本文主要介绍 如何使用 chirpstack 的API 进行测试以及…

商户查询的缓存——基于逻辑过期方式解决缓存击穿问题

//基于逻辑过期方式解决缓存基穿问题 理论上讲都是可以命中的 public Shop queryWithLogincalExpire(Long id){ //1.从redis中查商铺缓存 String jsonShop stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY id); //2.未命中 if(StringUtils.isBlank(j…

Python程序运行中报Memoryerror的解决方案

在求解某高维时间依赖的PDE中&#xff0c;空间计算域在每一个空间方向均为M256&#xff0c;且快速算法被执行以解决储存与计算速度问题&#xff1b;而时间方向取T2000&#xff0c;时间步长0.01。 在Spyder提前运行过&#xff0c;第一次假如迭代了1468次&#xff0c;即会报Memo…

本地部署 Dolly V2

本地部署 Dolly V2 1. 什么是 Dolly V22. Github 地址3. 安装 Miniconda34. 创建虚拟环境5. 部署 Dolly V26. 编写测试程序7. 运行测试程序 1. 什么是 Dolly V2 Databricks的dolly-v2-12b&#xff0c;是一个在 Databricks 机器学习平台上训练的指令跟随型大型语言模型&#xf…

计算机图形学 | 实验七:完成摄像机类的创建

计算机图形学 | 实验七&#xff1a;完成摄像机类的创建 计算机图形学 | 实验七&#xff1a;完成摄像机类的创建摄像机/观察空间Look At 矩阵自由移动视角移动鼠标输入缩放 华中科技大学《计算机图形学》课程 MOOC地址&#xff1a;计算机图形学&#xff08;HUST&#xff09; 计…

【自然语言处理】自然语言处理 --- NLP入门指南

文章目录 一、什么是NLP二、NLP任务类型三、NLP的预处理英文 NLP 语料预处理的 6 个步骤中文 NLP 语料预处理的 4 个步骤第1步&#xff1a;收集您的数据---语料库第2步&#xff1a;清理数据 --- 文本清洗第3步&#xff1a;分词第4步&#xff1a;标准化第5步&#xff1a;特征提取…

花式玩转二叉树层序遍历——实现二叉树Z字输出

文章目录 题目介绍二叉树层序遍历——队列实现Java完整代码 分析Java完整代码实现总结 题目介绍 这个题目是在做一个测试里面遇到的&#xff0c;大致描述如下&#xff1a; 现在有一棵二叉树&#xff0c;需要实现如图所示的交叉来回遍历&#xff1a; 即相较于普通的层序遍历&a…

基于目标级联法的微网群多主体分布式优化调度(已更新)

目录 一、主要内容 1.1 上层微网群模型 1.2 下层微网模型 二、部分程序 三、实现效果 四、下载链接 一、主要内容 本文复现《基于目标级联法的微网群多主体分布式优化调度》文献的目标级联部分&#xff0c; 建立微网群系统的两级递阶优化调度模型: 上层是微网群能量调度中…

Jvm --java虚拟机(下)

目录 执行引擎 什么是执行引擎&#xff1f; 什么是解释器&#xff1f;什么是 JIT 编译器&#xff1f; 为什么 Java 是半编译半解释型语言&#xff1f; JIT 编译器执行效率高为什么还需要解释器&#xff1f; 垃圾回收 垃圾回收概述 什么是垃圾&#xff1f; 为什么需要GC&a…

Redis持久化--RDB

一. RDB是什么 在指定的时间间隔内将内存中的数据集快照写入磁盘&#xff0c; 也就 Snapshot 快照&#xff0c;恢复时将快照文件读到内存二. RDB持久化的流程 解读&#xff1a; redis 客户端执行 bgsave 命令或者自动触发 bgsave 命令&#xff1b;主进程判断当前是否已经存在…

【开源之夏 2023】欢迎报名 SOFAStack 社区项目!

开源之夏是由“开源软件供应链点亮计划”发起并长期支持的一项暑期开源活动&#xff0c;旨在鼓励在校学生积极参与开源软件的开发维护&#xff0c;促进优秀开源软件社区的蓬勃发展&#xff0c;培养和发掘更多优秀的开发者。 活动联合国内外各大开源社区&#xff0c;针对重要开…

荔枝派Zero(全志V3S)驱动开发之RGB LCD屏幕显示bmp图片

文章目录 前言一、如何在 linux 下驱动 LCD1、什么是 Framebuffer 设备2、如何确保 Framebuffer 设备已存在3、Frame_buffer 设备结构体<1>、fb_info 详解<2>、struct fb_fix_screeninfo 详解<3>、struct fb_var_screeninfo 详解 4、设备树中有关 framebuffe…

使用 Appium 进行 WPF 自动化

文章目录 关于1 环境准备2 集成单元测试3 新增基本测试代码4 测试 WPF 程序5 启动测试 关于 参考链接&#xff1a;Get Your WPF Apps Automated With Appium Appium官网&#xff1a;http://appium.io/docs/en/2.0/quickstart 1 环境准备 一、下载 Windows Application Driv…

JVM 虚拟机栈

虚拟机栈概述 背景: 由于跨平台性的设计&#xff0c;Java 的指令都是根据栈来设计的。不同平台 CPU 架构不同&#xff0c;所以不能设计为基于寄存器的优点是跨平台, 指令集小&#xff0c;编译器容易实现&#xff0c;缺点是性能下降&#xff0c;实现同样的功能需要更多的指令 …

CVE-2023-21839 Weblogic RCE

前言 刷B站的时候给我推的一个WebLogic的比较新的漏洞&#xff0c;可以通过此漏洞直接达到RCE进行getShell的效果&#xff0c;于是就简单复现和分析一下&#xff0c;做个记录。 视频链接 漏洞简单分析 此漏洞是属于WebLogic的JNDI注入漏洞&#xff0c;漏洞造成的原因是Weblo…

《创新者的基因》读书笔记

本书是企业创新管理的研究成果&#xff0c;针对个人和企业如何培养、提升商业创新能力给出了行动指南&#xff0c;每一种能力都提供了很多训练小技巧&#xff0c;在此不一一列举&#xff0c;只写自己的读书笔记、对策和思考。 破坏性创新者的基因 发问&#xff1a;目的是提出…

Java字符串详解:概念、特点与常见的使用场景

Java字符串是开发中经常使用到的一种数据类型&#xff0c;使用它可以处理文本、URL、文件路径等多种类型的数据。本文将对Java字符串的概念、特点以及常见使用场景进行详细解释。本文将分为以下几个部分&#xff1a; String的概念和特点Java字符串常量池字符串的不可变性使用e…