FXAA

实现

1. 算法前瞻


算法流程参考上图,可以分为以下几步:

  1. 输入一张非线性RGB图(例如:sRGB),并从采样得到的颜色值中得到亮度luminance
  2. 通过亮度,检查当前像素的局部对比度local contrast),来舍弃非边缘像素。检测到的边缘为红色,向黄色渐变表示检测到的亚像素锯齿的程度。(drawn using FXAA_DEBUG_PASSTHROUGHshader define)。
  3. 通过局部对比度检测的像素被分类为金色的水平,和蓝色的垂直。(FXAA_DEBUG_HORZVERT)
  4. 根据当前像素的方向,选出和此方向垂直的(成90o90^o90o),且对比度最高的像素对,图中标记为蓝/绿。(FXAA_DEBUG_PAIR
  5. 沿边缘的负方向和正方向(红/蓝)搜索边缘的末端。检查沿边缘的高对比度像素对的平均亮度是否有明显变化。(FXAA_DEBUG_NEGPOS
  6. 给出边缘的两端边缘上的像素位置进行一个垂直于边缘90o90^o90o的子像素移动,以减少锯齿,红/蓝-/+水平移动金/天蓝-/+垂直移动。(FXAA_DEBUG_OFFSET
  7. 考虑这个子像素偏移量,对输入的纹理进行重新采样。
  8. 最后,根据检测到的子像素锯齿的程度,加入一个低通滤波器

2. 亮度转换(过程1

直接使用RG通道进行mad操作——经验上,B通道锯齿很少出现在游戏中:

float FxaaLuma(float3 rgb)
{return rgb.y * (0.587/0.299) + rgb.x;
}

3. 局部对比度检查(过程2

局部对比度检查使用东西南北四个邻域像素。如果局部最大和最小对比度之差小于阈值(正比于最大局部对比度),则直接退出(不是边缘像素)。这个阈值应该进行clamp,避免太小,而错误的处理黑色区域

float3 rgbN  = FxaaTextureOffset(tex, pos.xy, FxaaInt2( 0,-1)).xyz;
float3 rgbW  = FxaaTextureOffset(tex, pos.xy, FxaaInt2(-1, 0)).xyz;
float3 rgbM  = FxaaTextureOffset(tex, pos.xy, FxaaInt2( 0, 0)).xyz;
float3 rgbE  = FxaaTextureOffset(tex, pos.xy, FxaaInt2( 1, 0)).xyz;
float3 rgbS  = FxaaTextureOffset(tex, pos.xy, FxaaInt2( 0, 1)).xyz;
float lumaN  = FxaaLuma(rgbN);
float lumaW  = FxaaLuma(rgbW);
float lumaM  = FxaaLuma(rgbM);
float lumaE  = FxaaLuma(rgbE);
float lumaS  = FxaaLuma(rgbS);
float rangeMin = min(lumaM, min(min(lumaN, lumaW), min(lumaS, lumaE)));
float rangeMax = max(lumaM, max(max(lumaN, lumaW), max(lumaS, lumaE)));
float range = rangeMax - rangeMin;
if(range <  max(FXAA_EDGE_THRESHOLD_MIN, rangeMax * XAA_EDGE_THRESHOLD))
{ return FxaaFilterReturn(rgbM);
}

由此产生的、由艺术家控制的参数:

  • FXAA_EDGE_THRESHOLD:对比度检测阈值,参考值——1/3(too little),1/4(low quality),1/8(high quality),1/16(overkill)
  • FXAA_EDGE_THRESHOLD_MIN:最小阈值,参考值——1/32(visible limit),1/16(high quality),1/12(upper limit,start of visible unfiltered edges)

4. 亚像素锯齿测试(过程2

首先,像素对比度lumaL的计算方法是:通过一个低通滤波器得到平均值,然后减去中间亮度,意义上就是绝对差异。像素对比度与局部对比度的比率被用来检测子像素锯齿。这个比率在单像素点存在的情况下接近1.0,而当更多的像素贡献于一个边缘时,开始下降趋于0.0。这个比率后续会拿来(最后一步)作为低通滤波器的强度

float lumaL = (lumaN + lumaW + lumaE + lumaS) * 0.25;
float rangeL = abs(lumaL - lumaM);
float blendL = max(0.0, (rangeL / range) - FXAA_SUBPIX_TRIM) * FXAA_SUBPIX_TRIM_SCALE;
blendL = min(FXAA_SUBPIX_CAP, blendL);

在算法的最后,用于过滤子像素锯齿的低通滤波器是一个完整的**3x3BOX滤波器**。

// 已经采样好的,就直接进行黎曼和
float3 rgbL = rgbN + rgbW + rgbM + rgbE + rgbS;
// ...
// 经历了诸多过程,再来采样四个角落,凑出完整的3x3领域
float3 rgbNW = FxaaTextureOffset(tex, pos.xy, FxaaInt2(-1,-1)).xyz;
float3 rgbNE = FxaaTextureOffset(tex, pos.xy, FxaaInt2( 1,-1)).xyz;
float3 rgbSW = FxaaTextureOffset(tex, pos.xy, FxaaInt2(-1, 1)).xyz;
float3 rgbSE = FxaaTextureOffset(tex, pos.xy, FxaaInt2( 1, 1)).xyz;
rgbL += (rgbNW + rgbNE + rgbSW + rgbSE);
rgbL *= FxaaToFloat3(1.0/9.0);

由此产生的、由艺术家控制的参数:(除了关闭完全开启特性外,这些参数不会影响性能。默认情况下,亚像素锯齿的去除程度是有限的。这使得细微的特征得以保留,但对比度足够低,这样它们就不会分散眼睛的注意力。完全开启会使图像模糊。)

  • FXAA_SUBPIX:亚像素过滤的开关,参考值——0(关闭)、1(开启)、2(完全开启,忽略FXAA_SUBPIX_TRICAP
  • FXAA_SUBPIX_TRIM:控制亚像素锯齿的移除,参考值——1/2(low removal),1/3(medium removal),1/4(default removal),1/8(high removal),0(complete removal)
  • FXAA_SUBPIX_CAP:确保精细的细节不被完全删除。这部分覆盖了FXAA_SUBPIX_TRIM。参考值——3/4(default amount of filtering),7/8(high amount of filtering),1( no capping of filtering)

5. 水平/垂直边缘检测(过程3

Sobel这样的边缘检测滤波器在通过像素中心的单像素线上效果很差。FXAA局部3x3邻域行和列的高通值的加权平均幅度,作为局部边缘程度的指示:

float edgeVert =  abs((0.25 * lumaNW) + (-0.5 * lumaN) + (0.25 * lumaNE)) + abs((0.50 * lumaW ) + (-1.0 * lumaM) + (0.50 * lumaE )) + abs((0.25 * lumaSW) + (-0.5 * lumaS) + (0.25 * lumaSE));
float edgeHorz =  abs((0.25 * lumaNW) + (-0.5 * lumaW) + (0.25 * lumaSW)) + abs((0.50 * lumaN ) + (-1.0 * lumaM) + (0.50 * lumaS )) + abs((0.25 * lumaNE) + (-0.5 * lumaE) + (0.25 * lumaSE));
bool horzSpan = edgeHorz >= edgeVert;

那个话看似复杂,实际上很简单,这个还是得看示意图:

Todo,灵魂画师用平板画。

6. 边缘末端检测(过程5

给定局部边缘方向FXAA将选出和此方向垂直的(成90o90^o90o),且对比度最高的像素对。算法沿着正方向和负方向进行搜索,直到达到搜索极限,或者沿着边缘移动的pair平均亮度变化足以表示边缘结束。

在水平或垂直方向上平行搜索负方向和正方向(单个循环)。这样做是为了避免在着色器中的分支。

搜索加速的原理(预设012):使用各向异性过滤作为盒式过滤器来检查不止一个像素对。

for(uint i = 0; i < FXAA_SEARCH_STEPS; i++) { #if FXAA_SEARCH_ACCELERATION == 1 if(!doneN) lumaEndN = FxaaLuma(FxaaTexture(tex, posN.xy).xyz); if(!doneP) lumaEndP = FxaaLuma(FxaaTexture(tex, posP.xy).xyz); #else if(!doneN) lumaEndN = FxaaLuma(FxaaTextureGrad(tex, posN.xy, offNP).xyz); if(!doneP) lumaEndP = FxaaLuma(FxaaTextureGrad(tex, posP.xy, offNP).xyz); #endif doneN = doneN || (abs(lumaEndN - lumaN) >= gradientN); /*负方向*/doneP = doneP || (abs(lumaEndP - lumaN) >= gradientN); /*正方向*/if(doneN && doneP) break; if(!doneN) posN -= offNP; if(!doneP) posP += offNP;
}

由此产生的、由艺术家控制的参数:(这些定义对性能有最大的影响。注意,使用搜索加速可能会在边缘过滤中引起一些抖动。)

  • FXAA_SEARCH_STEPS:控制搜索步数的最大值。乘以FXAA_SEARCH_ACCELERATION过滤半径
  • FXAA_SEARCH_ACCELERATION:各向异性过滤加速搜索的程度,参考值——1(无加速)、2(跳过2个像素)、3(跳过3个)、4
  • FXAA_SEARCH_THRESHOLD:控制什么时候停止搜索,参考值——1/4(似乎是最佳选择)。此外,个人觉得,代码中的gradientN就和这个参数有关。

7. 关于后续的过程456

首先,这三个过程其实主要是为了一个目的,那就是获得当前像素的偏移量。这个偏移量又和边缘的末端有什么关系呢?

在我看来,首先这个边缘越长,说明这个边越水平/垂直,因为我们采样就是按照标准方向来的,所以哪怕这个边实际上很长,但是如果它很斜的话,那么我们的循环也走不长。所以,我们可以得出第一个结论:两个端点距离越长,则偏移量越小。

但仅仅这样想,会有一个问题,那就是这个边真的很短,而且有真的很标准(完全垂直或水平),那么,我们此时就不能按照上诉想法,给它分配一个较大的偏移量。这个时候,就引出了第二个评判标准,那就是,这个像素如果离某一个端点很近,那么哪怕这个边不长,也应该给它分配一个小的偏移量

最后,给出每一步的意义:

  • 过程4:仅仅是为了获得最大对比度对,来作为第五步的截止条件。
  • 过程5:搜索得到两个端点。
  • 过程6:利用两个端点和上诉分析得到的两个规则,给当前像素一个偏移量。
  • 过程78:使用偏移量,重新确定当前像素的采样中心,然后根据亚像素锯齿程度来决定box过滤核的大小,进行最后的过滤。

8. 技术总结

就我个人感觉,FXAA的核心就是两个:

  • 重新确定采样核心位置。
  • 确定过滤核的半径。

9. 复现

Todo
Ps: 应付完老师再说吧。

FXAA白皮书的翻译和理解补充相关推荐

  1. 编译原理四种文法的理解补充

    如果你是学习,给定一个文法,判断是哪一个文法的话,那这篇不是讲这个,只是一点小小的理解补充. 四种文法如下: 补充: 1.0型文法,即递归可枚举文法相当于图灵机是指: 给定一个文法G,一个句子g,如果 ...

  2. go之树型结构深度理解补充

    go之树型结构深度理解补充 在上一篇中借用了 Ilija Eftimov 文章来讲解了tree的定义和一些方法.这篇文章主要是讲解在树型结构中如何判断节点与节点之间的关系. A节点是否是B节点的直接上 ...

  3. View Invariant Gait Recognition Using Only One Uniform Model论文翻译以及理解

    View Invariant Gait Recognition Using Only One Uniform Model论文翻译以及理解 一行英文,一行翻译 论文中所述的优点:The unique a ...

  4. GaitGAN: Invariant Gait Feature Extraction Using Generative Adversarial Networks论文翻译以及理解

    GaitGAN: Invariant Gait Feature Extraction Using Generative Adversarial Networks论文翻译以及理解 格式:一段英文,一段中 ...

  5. 云计算 | 中国信通院《2022 云计算白皮书》阅读、理解与总结

    云计算 | 中国信通院<2022 云计算白皮书>阅读.理解与总结 前言 1. 云计算市场 1.1 国内外云计算市场现状对比 1.2 2021年国内公有云与私有云市场对比 1.3 国内公有云 ...

  6. Multi-Task GANs for View-Specific Feature Learning in Gait Recognition论文翻译以及理解

    Multi-Task GANs for View-Specific Feature Learning in Gait Recognition论文翻译以及理解 今天想尝试一下翻译一篇自己读的论文.写的不 ...

  7. 区块链广告平台:AdRealm白皮书简要翻译

    区块链是否能和在线广告营销结合,迸发出更多新力量? 18年03月刚上线的平台,AdRealm,研究了一下,感觉挺有意思.把自己简单翻译的流程放出来.里面也有一些点我还不能彻底理解,欢迎各方大佬交流. ...

  8. OPC通讯开发——客户端开发工具WTopcclient说明手册部分翻译及个人补充

    前言    经过多个月的开发.现场测试和反馈修改,自己开发的OPC客户端终于真正能在现场使用了.由于工业现场的服务器很古老,只支持OPCDA架构,我们也只能按照DA的标准设计客户端.由于有着WTopc ...

  9. 论文篇 | 2020-Facebook-DETR :利用Transformers端到端的目标检测=>翻译及理解(持续更新中)

    论文题目:End-to-End Object Detection with Transformers 2020 论文复现可参考:项目复现 | DETR:利用transformers端到端的目标检测_夏 ...

最新文章

  1. 【PAT乙级】1086 就不告诉你 (15 分)
  2. cross-domain policy file
  3. 【萌味】小夕说,不了解动态空间增长的程序喵都是假喵(中)
  4. Win7电脑,无法把文件保存到桌面上?
  5. ewsa 字典_湖南字典头条胖U
  6. 使用TryParse()来执行数值转换
  7. Ubuntu源码安装Tomcat7
  8. 不是区块链的特征_区块链的四大特征
  9. AAC规格(LC,HE,HEv2)及性能对比
  10. c++ stl下的sort()函数介绍及基本用法
  11. 新浪微博批量删除微博的方法
  12. 论文被引上千次,GitHub 开源6000星,他们是首届字节跳动奖学金获奖者
  13. gRPC源码阅读及实践之 Resolver
  14. 三星砸钱买公司以提升Bixby性能 奋力追赶亚马逊谷歌
  15. 侯捷C++视频(百度云盘)
  16. Windows Server2008上安装VS2008出错及解决办法
  17. html5游戏占内存和cpu,IIS解决CPU和内存占用率太高的问题
  18. Vivado 2019.1安装包下载
  19. (项目部署)day60javaEE基础查漏补缺
  20. Arduino与Proteus仿真实例-NTC热敏电阻驱动仿真

热门文章

  1. TED | 别让任何人打乱你人生的节奏
  2. 一个33岁老程序员的感悟
  3. 【作为测试这些MySQL知识必备】。书写万字手把手教你MySQL,从建库开始步步教学,也可直接复制粘贴使用
  4. oracle 11g的下载、安装、使用。完整版。亲自使用
  5. 【Git】如何修改gitlab的项目描述
  6. 鼠标事件案例--带有hover样式的导航栏、div跟着鼠标移动而移动
  7. 我,30岁,机械工程,转行互联网成为我这辈子做过最好的决定
  8. ORACLE 错误解释
  9. js手机格式校验+隐藏手机号中间四位,变成*星号 || 身份证生日四位变*,邮箱*号显示
  10. 目标检测mmdetection-demo