基于可变分辨率的半透明特效渲染优化方案

2024-03-05 19:12

本文主要是介绍基于可变分辨率的半透明特效渲染优化方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

粒子效果在游戏中无处不在。大颗粒系统常见于烟雾、火灾、爆炸、灰尘和雾。如果这些效果填满屏幕,overdraw会非常严重,帧率会掉得很快,即使是在技术成熟的 3A 游戏中也是如此。以至于来半透明渲染的优化问题一直都是难题。

这里的解决方案是将昂贵的粒子渲染到屏幕外渲染目标,其大小是帧缓冲区大小的一小部分。但是这里的解决方案做了一个假设,认为许多用粒子建模的效果都是模糊且柔和的。更专业地说,烟和雾的图像只有低频,没有锐利的边缘。它们可以用少量样本来表示,而不会损失视觉质量。因此,低分辨率帧缓冲区就足够了。如果是具有高图像频率的粒子,例如飞行的岩石碎片。这里的方案可能就不适用了。

离屏渲染

粒子需要被渲染到离屏渲染目标,大小一般要设置主分辨率的一半以上。起码尺度要是二的幂。

离屏深度测试

要正确遮挡场景中其他的粒子,需要深度测试和深度缓冲区,其大小与我们的离屏渲染目标相匹配。我们通过对主 z 缓冲区进行下采样来获得这一点。基本算法是这样的:

  1. 正常渲染场景中的所有实体对象,并进行深度写入。
  2. 对生成的深度缓冲区进行downsample。
  3. 将粒子渲染到屏幕外渲染目标(也就是额外的rendertarget),并做深度测试。
  4. 将粒子渲染目标合成回主帧缓冲区,进行upsample。
获取深度
获取 DirectX 9 中的深度

在 DirectX 9 下,无法直接访问主 z 缓冲区。必须使用三种方法之一显式创建深度。

可以使用多个渲染目标(MRT):颜色信息写入第一个目标;深度写入第二个。这种方法有一些缺点。所有目标必须具有相同的位深度。后台缓冲区将为 32 位(或更多),但更少的缓冲区可能足以满足深度。更严重的是,MRT 与 DirectX 9 中的多重采样抗锯齿 (MSAA) 不兼容。

或者,可以通过在单独的通道中写入单个渲染目标来创建深度。这有一个明显的缺点,即需要对场景的大部分几何体或至少与粒子效果相交的部分进行额外的传递。从好的方面来说,深度可以直接以所需的离屏分辨率写入,无需downsample操作。

如果尚未使用 Alpha,则还可以将深度写入 RGBA 目标的 Alpha 通道。A8R8G8B8可能没有足够的精度。

获取 DirectX 10 中的深度

DirectX 10 允许直接访问深度缓冲区中的值。具体来说,您可以为深度表面绑定着色器资源视图(SRV)。毫不奇怪,您必须首先将其解除绑定为渲染目标。否则,它是相当简单的。

还有一个问题。无法为多重采样深度缓冲区绑定 SRV。原因还是比较深的。MSAA 解析操作应如何应用于深度值?与解析颜色值不同,平均值是不正确的;点采样也是错误的。其实没有正确答案。对于我们对粒子的应用,点采样可能是可以接受的。

MRT 可与 MSAA 配合使用,并且 MRT 目标可以具有不同的位深度。对于浮点表面格式的 MSAA 的使用也没有限制。

downsample深度

无论我们如何获取深度值,深度表面都需要被downsample到离屏渲染目标的较小尺寸。但是没有普遍正确的深度downsample方法。

点采样深度

简单的点采样深度会产生明显的伪影。

绿色三角形是前景对象,靠近视点,具有较低的深度值。白色背景区域具有最大深度。虚线表示的 16 个像素被下采样为 1 个离屏像素。A 点是我们进行深度采样的地方。它不能代表 16 个输入像素中的大部分;只有像素 B 具有相似的深度。

如果绿色三角形是前景对象,后面有一个粒子,则深度测试将遮挡低分辨率像素和所有 16 个高分辨率像素的粒子。结果可能是前景物体周围出现光晕。如下图b所示

最大深度样本

可以通过进行多个单点采样来改善光晕。上图中的c显示了downsample时应用最大函数的结果。从全分辨率深度数据中对四个深度值进行采样,并取最大的一个。这具有缩小粒子屏幕外图像中的对象轮廓的效果。但这绝对没有物理或理论依据。它只是看起来比光环更好。这是一个权宜之计。这就是深度downsample函数必须由应用程序定义的原因。这些瑕疵足够小,当低分辨率粒子图像添加回主帧缓冲区时,它们很大程度上被线性混合隐藏。

深度测试和软粒子

深度测试是通过像素着色器中的比较来实现的。downsample深度缓冲区绑定为纹理(DirectX 10 中的 SRV)。正在渲染的粒子的深度在顶点着色器中计算并向下传递到像素着色器。

在像素着色器中实现的深度测试
float4 particlePS(VS_OUT vIn): COLOR {   float myDepth = vIn.depth;   float sceneDepth = tex2D(depthSampler, vIn.screenUV).x;   if (myDepth > sceneDepth)     discard;   // Compute color, etc.   . . . } 

软粒子效果会在粒子接近与实体场景对象相交处时淡化粒子 alpha。从而避免了恶劣、尖锐的交叉路口。视觉效果非常好。

软粒子比二元深度测试更好
float4 particlePS(VS_OUT vIn): COLOR {   float myDepth = vIn.depth;   float sceneDepth = tex2D(depthSampler, vIn.screenUV).x;   float zFade = saturate(scale * (myDepth - sceneDepth));   // Compute (r,g,b,a), etc.   . . .   return float4(r,g,b, a * zFade); } 

Alpha 混合

部分透明的粒子效果通常混合到帧缓冲区中。设置 Alpha 混合渲染状态以创建混合方程:

1 = d ( 1 - 1 ) + 1 1 ,

其中1是最终颜色,d是帧缓冲区中已有的目标颜色,1和 1是像素着色器输出的传入源颜色和 Alpha。如果随后在p 1上混合更多粒子,结果将变为

2 = ( 1 )(1 - 2 ) + 2 2 = ( d (1 - 1 ) + 1 1 )(1 - 2 ) + 2 2 ,

3 = ( 2 )(1 - 3 ) + 3 3 = (( d (1 - 1 ) + 1 1 )(1 - 2 ) + 2 2 (1 - 3 ) + 3 3 ,

将p 3 的方程重新排列为组ds项给出

3 = d (1 - 1 )(1 - 2 )(1 - 3 ) + 1 1 (1 - 2 )(1 - 3 ) + 2 2 (1 - 3 ) + 3 3。

将除帧缓冲区之外的所有内容存储离屏渲染目标中。最终的组合操作需要生成3,或更一般地说n

先取d项——这样更简单。它乘以每个混合的 alpha 值的倒数。因此,我们将这些乘法累加到渲染目标的 Alpha 通道中。

接下来,检查对3起作用的s项。请注意,如果目标初始化为零,则它们采用与2、3等的传统 alpha 混合方程相同的形式:

0(1 - 1 ) + 1 1

0(1 - 1 )(1 - 2 ) + 1 1 (1 - 2 ) + 2 2

0(1 - 1 )(1 - 2 )(1 - 3 ) + 1 1 (1 - 2 )(1 - 3 ) + 2 2 (1 - 3 )+ 3 3

用于渲染屏幕外粒子的 Alpha 混合状态
AlphaBlendEnable = true; SrcBlend = SrcAlpha; DestBlend = InvSrcAlpha; SeparateAlphaBlendEnable = true; SrcBlendAlpha  = Zero; DestBlendAlpha = InvSrcAlpha; 

将粒子添加到帧缓冲区中以实现火焰等发射效果也很常见。处理这种情况比前面的 alpha 混合情况简单得多。值会累加到离屏目标中,然后添加到帧缓冲区中。

混合分辨率渲染

使用最大深度值对深度进行下采样可减少光晕。然而,屏幕外的粒子图像仍然可能是不可接受的效果。

当图像动画化时,这些伪影会闪烁并弹出。这在快速移动的动画中可能并不明显。然而,缓慢且连续地移动就会有这种效果。

我们可以通过边缘检测后然后对边缘做全分辨率处理来解决这个问题。

边缘检测

检查渲染目标中的离屏颜色和 Alpha 通道

使用标准 Sobel 滤波器可以轻松实现边缘检测。结果存储在另一个离屏渲染目标中。

使用模板进行排版

边缘检测滤波器选择一小部分块状像素。可以通过仅在出现边缘的地方以全帧缓冲区分辨率渲染粒子来修复像素化区域。

模板缓冲区用于有效屏蔽帧缓冲区的区域。当低分辨率、离屏粒子被组合回主帧缓冲区时,我们同时写入模板缓冲区。模板值无法由像素着色器显式设置。所以需要为所有像素启用模板写入,然后像素着色器可以调用丢弃关键字来禁止输出。这可以防止该像素的模板写入,并且像素着色器可以有效地控制模板值,从而创建蒙版。

然后对粒子进行第二次渲染。模板状态的设置使得像素仅写入先前合成通道未触及的区域 。这里显示下结果。

结果

上图将全分辨率粒子与混合分辨率结果进行了比较,其中屏幕外表面按 4x4 缩放。混合分辨率结果非常接近高分辨率版本。但是要注意低分辨率表面无法充分表示此细节,也要注意高频的图像是无法这样处理的,并且可能会导致画面模糊。

结论

结果表明,低分辨率离屏渲染可以带来显着的性能提升。它有助于使粒子的成本更具可扩展性,以便在过度绘制的最坏情况下(粒子系统填满屏幕)帧速率不会急剧下降。在真实的游戏中,这将转化为更一致的帧速率体验。

从视觉上看,结果永远不可能完全没有伪影,因为没有完美的方法来对深度值进行downsample。所以总会存在一些采样不足的问题。然而,在游戏中,诸如爆炸之类的粒子通常会快速移动,因此小的伪影可能是可以接受的。

这篇关于基于可变分辨率的半透明特效渲染优化方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/777531

相关文章

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

小白也能轻松上手! 路由器设置优化指南

《小白也能轻松上手!路由器设置优化指南》在日常生活中,我们常常会遇到WiFi网速慢的问题,这主要受到三个方面的影响,首要原因是WiFi产品的配置优化不合理,其次是硬件性能的不足,以及宽带线路本身的质... 在数字化时代,网络已成为生活必需品,追剧、游戏、办公、学习都离不开稳定高速的网络。但很多人面对新路由器

MySQL深分页进行性能优化的常见方法

《MySQL深分页进行性能优化的常见方法》在Web应用中,分页查询是数据库操作中的常见需求,然而,在面对大型数据集时,深分页(deeppagination)却成为了性能优化的一个挑战,在本文中,我们将... 目录引言:深分页,真的只是“翻页慢”那么简单吗?一、背景介绍二、深分页的性能问题三、业务场景分析四、

MySQL 迁移至 Doris 最佳实践方案(最新整理)

《MySQL迁移至Doris最佳实践方案(最新整理)》本文将深入剖析三种经过实践验证的MySQL迁移至Doris的最佳方案,涵盖全量迁移、增量同步、混合迁移以及基于CDC(ChangeData... 目录一、China编程JDBC Catalog 联邦查询方案(适合跨库实时查询)1. 方案概述2. 环境要求3.

Linux进程CPU绑定优化与实践过程

《Linux进程CPU绑定优化与实践过程》Linux支持进程绑定至特定CPU核心,通过sched_setaffinity系统调用和taskset工具实现,优化缓存效率与上下文切换,提升多核计算性能,适... 目录1. 多核处理器及并行计算概念1.1 多核处理器架构概述1.2 并行计算的含义及重要性1.3 并

SpringBoot3.X 整合 MinIO 存储原生方案

《SpringBoot3.X整合MinIO存储原生方案》本文详细介绍了SpringBoot3.X整合MinIO的原生方案,从环境搭建到核心功能实现,涵盖了文件上传、下载、删除等常用操作,并补充了... 目录SpringBoot3.X整合MinIO存储原生方案:从环境搭建到实战开发一、前言:为什么选择MinI

Knife4j+Axios+Redis前后端分离架构下的 API 管理与会话方案(最新推荐)

《Knife4j+Axios+Redis前后端分离架构下的API管理与会话方案(最新推荐)》本文主要介绍了Swagger与Knife4j的配置要点、前后端对接方法以及分布式Session实现原理,... 目录一、Swagger 与 Knife4j 的深度理解及配置要点Knife4j 配置关键要点1.Spri

MyBatisPlus如何优化千万级数据的CRUD

《MyBatisPlus如何优化千万级数据的CRUD》最近负责的一个项目,数据库表量级破千万,每次执行CRUD都像走钢丝,稍有不慎就引起数据库报警,本文就结合这个项目的实战经验,聊聊MyBatisPl... 目录背景一、MyBATis Plus 简介二、千万级数据的挑战三、优化 CRUD 的关键策略1. 查

SQLite3 在嵌入式C环境中存储音频/视频文件的最优方案

《SQLite3在嵌入式C环境中存储音频/视频文件的最优方案》本文探讨了SQLite3在嵌入式C环境中存储音视频文件的优化方案,推荐采用文件路径存储结合元数据管理,兼顾效率与资源限制,小文件可使用B... 目录SQLite3 在嵌入式C环境中存储音频/视频文件的专业方案一、存储策略选择1. 直接存储 vs

SpringBoot服务获取Pod当前IP的两种方案

《SpringBoot服务获取Pod当前IP的两种方案》在Kubernetes集群中,SpringBoot服务获取Pod当前IP的方案主要有两种,通过环境变量注入或通过Java代码动态获取网络接口IP... 目录方案一:通过 Kubernetes Downward API 注入环境变量原理步骤方案二:通过