FXAA白皮书的翻译和理解补充

本文详细介绍了快速近似抗锯齿(FXAA)算法的实现步骤,包括亮度转换、局部对比度检查、亚像素锯齿测试、边缘检测以及边缘末端检测等关键环节。FXAA旨在通过一系列运算在保持图像细节的同时减少画面锯齿,提高视觉质量。文章还探讨了多个艺术家可控参数的影响,并提供了复现FXAA算法的基本思路。
摘要由CSDN通过智能技术生成

FXAA

实现

1. 算法前瞻


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

  1. 输入一张非线性RGB图(例如:sRGB),并从采样得到的颜色值中得到亮度luminance
  2. 通过亮度,检查当前像素的局部对比度local contrast),来舍弃非边缘像素。检测到的边缘为红色,向黄色渐变表示检测到的亚像素锯齿的程度。(drawn using FXAA_DEBUG_PASSTHROUGHshader define)。
  3. 通过局部对比度检测的像素被分类为金色的水平,和蓝色的垂直。(FXAA_DEBUG_HORZVERT)
  4. 根据当前像素的方向,选出和此方向垂直的(成 9 0 o 90^o 90o),且对比度最高的像素对,图中标记为蓝/绿。(FXAA_DEBUG_PAIR
  5. 沿边缘的负方向和正方向(红/蓝)搜索边缘的末端。检查沿边缘的高对比度像素对的平均亮度是否有明显变化。(FXAA_DEBUG_NEGPOS
  6. 给出边缘的两端边缘上的像素位置进行一个垂直于边缘 9 0 o 90^o 90o的子像素移动,以减少锯齿,红/蓝-/+水平移动金/天蓝-/+垂直移动。(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将选出和此方向垂直的(成 9 0 o 90^o 90o),且对比度最高的像素对。算法沿着正方向和负方向进行搜索,直到达到搜索极限,或者沿着边缘移动的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: 应付完老师再说吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值