[前沿技术] AMD FSR 1.0源码分析(二)

FSR技术分析

前文:[前沿技术] AMD FSR 1.0源码分析(一)

2. EASU源码分析

2.3 FsrEasuF分析

1️⃣首先,就参数而言,主要是:

void FsrEasuF(
 out AF3 pix,
 AU2 ip, // 整数像素位置
 AU4 con0, // FsrEasuCon中设置的四个const vector
 AU4 con1,
 AU4 con2,
 AU4 con3)

然后,参考下图的12 tap窗口的编号:
在这里插入图片描述

首先,我们获得f的位置,得到fp

// Get position of 'f'.
AF2 pp = AF2(ip) * AF2_AU2(con0.xy) + AF2_AU2(con0.zw);
AF2 fp = floor(pp);
pp -= fp;

然后,根据输入的const向量,我们可以得到四个tap的中心:

在这里插入图片描述

// Allowing dead-code removal to remove the 'z's.
// 以F为原点,定位到(0)
AF2 p0 = fp * AF2_AU2(con1.xy) + AF2_AU2(con1.zw);
// These are from p0 to avoid pulling two constants on pre-Navi hardware.
// 以(0)为原点。定位(1) (2) (3)
AF2 p1 = p0 + AF2_AU2(con2.xy);
AF2 p2 = p0 + AF2_AU2(con2.zw);
AF2 p3 = p0 + AF2_AU2(con3.xy);

2️⃣然后是以下的一大串调用:

AF4 bczzR = FsrEasuRF(p0);
AF4 bczzG = FsrEasuGF(p0);
AF4 bczzB = FsrEasuBF(p0);
AF4 ijfeR = FsrEasuRF(p1);
AF4 ijfeG = FsrEasuGF(p1);
AF4 ijfeB = FsrEasuBF(p1);
AF4 klhgR = FsrEasuRF(p2);
AF4 klhgG = FsrEasuGF(p2);
AF4 klhgB = FsrEasuBF(p2);
AF4 zzonR = FsrEasuRF(p3);
AF4 zzonG = FsrEasuGF(p3);
AF4 zzonB = FsrEasuBF(p3);

这些FsrEasuXF函数是我们用户自定义的,在PostProcessFFX_FSR.usf中可以找到其定义:

AF4 FsrEasuRF(AF2 p) { AF4 res = InputTexture.GatherRed  (samLinearClamp, p, ASU2(0, 0)); return res; }
AF4 FsrEasuGF(AF2 p) { AF4 res = InputTexture.GatherGreen(samLinearClamp, p, ASU2(0, 0)); return res; }
AF4 FsrEasuBF(AF2 p) { AF4 res = InputTexture.GatherBlue (samLinearClamp, p, ASU2(0, 0)); return res; }

对于GatherRed类函数,可以参考https://docs.microsoft.com/zh-cn/windows/win32/direct3dhlsl/sm5-object-texture2d-gatherred。

所以,上面就是读取四个tap的值,不过和一般的读取不同,我们是读取一个 2 × 2 2\times2 2×2区域的指定通道。所以我们可以很快的转换成luma

// Simplest multi-channel approximate luma possible (luma times 2, in 2 FMA/MAD).
AF4 bczzL = bczzB * AF4_(0.5) + (bczzR * AF4_(0.5) + bczzG);
AF4 ijfeL = ijfeB * AF4_(0.5) + (ijfeR * AF4_(0.5) + ijfeG);
AF4 klhgL = klhgB * AF4_(0.5) + (klhgR * AF4_(0.5) + klhgG);
AF4 zzonL = zzonB * AF4_(0.5) + (zzonR * AF4_(0.5) + zzonG);

虽然我们读取了16个vec3数据,但是实际上正如之前的理论探讨所说,我们只需要12vec3数据,接下来我们直接把所需的数据单独提取出来:

// Rename.
AF1 bL = bczzL.x;
AF1 cL = bczzL.y;
AF1 iL = ijfeL.x;
AF1 jL = ijfeL.y;
AF1 fL = ijfeL.z;
AF1 eL = ijfeL.w;
AF1 kL = klhgL.x;
AF1 lL = klhgL.y;
AF1 hL = klhgL.z;
AF1 gL = klhgL.w;
AF1 oL = zzonL.z;
AF1 nL = zzonL.w;

3️⃣接下里,就是对4taps窗口进行分析,来计算得到directionlength

// Accumulate for bilinear interpolation.
AF2 dir = AF2_(0.0);
AF1 len = AF1_(0.0);
FsrEasuSetF(dir,len,pp,true, false,false,false,bL,eL,fL,gL,jL);
FsrEasuSetF(dir,len,pp,false,true ,false,false,cL,fL,gL,hL,kL);
FsrEasuSetF(dir,len,pp,false,false,true ,false,fL,iL,jL,kL,nL);
FsrEasuSetF(dir,len,pp,false,false,false,true ,gL,jL,kL,lL,oL);
FsrEasuSetF分析

1️⃣首先,我们很快可以知道:这个函数的4bool参数是用来区分tap的位置,从而计算双线性插值的权重

AF1 w = AF1_(0.0);
if(biS) w = (AF1_(1.0) - pp.x) * (AF1_(1.0) - pp.y);
if(biT) w =              pp.x  * (AF1_(1.0) - pp.y);
if(biU) w = (AF1_(1.0) - pp.x) *              pp.y ;
if(biV) w =              pp.x  *              pp.y ;

上面的权重计算怎么来的?首先,我们需要知道,12 tap窗口默认是套在f编号上的(下图Q点),也就是我们应该按照下图计算双线性插值:
在这里插入图片描述
在这里插入图片描述

然后,我们给tap窗口进行编号,如下所示:
在这里插入图片描述

先以x轴方向为例,计算direction很简单,就是梯度,然后乘上双线性插值权重

AF1 dirX = lD - lB;
dir.x += dirX * w;

2️⃣length边缘特征)的计算则要复杂很多:

// ASatF1 : saturate
AF1 dc = lD - lC;
AF1 cb = lC - lB;
AF1 lenX = max(abs(dc),abs(cb));
// 快速倒数 1/lenX
lenX = APrxLoRcpF1(lenX);
// 分子结合分母,然后clamp
lenX = ASatF1(abs(dirX) * lenX);
lenX *= lenX;
// 乘上双线性插值权重
len += lenX * w;

根据大佬博客和直接推导,可以很容易得到:EASU定义的边缘特征的计算公式为:
在这里插入图片描述

以上代码就是 F X 2 FX^2 FX2的计算。

3️⃣之后就是在Y轴重复一遍代码:

AF1 ec = lE-lC;
AF1 ca = lC-lA;
AF1 lenY = max(abs(ec),abs(ca));
lenY = APrxLoRcpF1(lenY);
AF1 dirY = lE-lA;
dir.y += dirY*w;
lenY = ASatF1(abs(dirY)*lenY);
lenY *= lenY;
len += lenY*w;

注意:len越大,说明这个特征越是Large Feature

正如前文所说,算法会忽略thin Feature——所以结果上,thin Feature和平坦区域的计算结果都会一致的: l e n = 0 len=0 len=0

回到主函数1

1️⃣回到主函数,我们已经得到了双线性插值完毕的directionlength(边缘特征)。首先,我们对direction进行归一化,由于要考虑除零问题,所以代码稍显复杂,但其实就是归一化

AF2 dir2 = dir * dir;
AF1 dirR = dir2.x + dir2.y;
AP1 zro = dirR < AF1_(1.0 / 32768.0);
dirR = APrxLoRsqF1(dirR);
dirR = zro ? AF1_(1.0) : dirR;
dir.x = zro ? AF1_(1.0) : dir.x;
dir *= AF2_(dirR);

然后,根据以下公式,由length计算出实际的Feature F / 2 F/2 F/2 是了从 [ 0 , 2 ] [0,2] [0,2]映射回 [ 0 , 1 ] [0,1] [0,1]):

在这里插入图片描述

// Transform from {0 to 2} to {0 to 1} range, and shape with square.
len = len * AF1_(0.5);
len *= len;

2️⃣我们然后计算stretch变量,用于拉伸过滤核:对其轴线时,这个值为1,对其对角线时,这个值为 2 \sqrt{2} 2 。具体代码如下:

// Stretch kernel {1.0 vert|horz, to sqrt(2.0) on diagonal}.
// 因为归一化了,分子不就是1吗
AF1 stretch = (dir.x * dir.x + dir.y * dir.y) * APrxLoRcpF1(max(abs(dir.x), abs(dir.y)));

3️⃣然后计算len2

// Anisotropic length after rotation,
//  x := 1.0 lerp to 'stretch' on edges
//  y := 1.0 lerp to 2x on edges
AF2 len2 = AF2(AF1_(1.0) + (stretch - AF1_(1.0)) * len,AF1_(1.0) + AF1_(-0.5) * len);

数学上的形式如下:

在这里插入图片描述

大佬指出:为了减少锯齿,EASU还提出可以根据梯度和边缘信息进行缩放,EASU定义的缩放比例如上。

回顾第一大节,我们也说了:“长度用于在XY轴上对旋转后的内核进行缩放”。所以,我们得到的len2是为了缩放过滤核的size。这里就是我们要计算Feature的两大目的之一了。

这里,回顾下缩放的解释。对于缩放,更加准确的算法解释是,此外,针对上诉公式,我对缩放区间进行了修改(为什么不一致呢?):

  • X轴:缩放区间是 [ 2 2 , 1 ] [\frac{\sqrt{2}}{2},1] [22 ,1],对应的是从轴对齐对角线。这意味着:对角线情况下使用更大的内核,来避免带状伪影band)。
  • Y轴:缩放区间是 [ 0.5 , 1 ] [0.5,1] [0.5,1]​,对应的是从small featurelarge feature。这意味着:对小的特征使用无比例,这样就得到了一个小的对称核,它不会在特征本身之外采样。而当特征变大时,使用一个较长的核,这样我们可以更好地还原边缘

在这里插入图片描述
为什么中写的是上诉呢?考虑一下,我们把输入值进行缩小,不就相当于放大过滤核嘛。所以实际上我们做的,确实是放大操作。

然后来思考下缩放输入值(放大内核)的作用:我后续观察lanczos核,它的特点很明显: [ 0 , 1 ] [0,1] [0,1]区间是正权重, [ 1 , 1 w ] [1,\frac{1}{\sqrt{w}}] [1,w 1]是负权重。而我们传入该核的输入值 x x x,实际上是该像素到目标像素的距离,而且这个距离还是在屏幕空间算的!——距离都会大于1!感觉讲不清楚,直接上图:

在这里插入图片描述

在这里插入图片描述

4️⃣然后计算:

// Based on the amount of 'edge',
// the window shifts from +/-{sqrt(2.0) to slightly beyond 2.0}.
AF1 lob = AF1_(0.5) + AF1_((1.0 / 4.0 - 0.04) - 0.5) * len;
// Set distance^2 clipping point to the end of the adjustable window.
AF1 clp = APrxLoRcpF1(lob);

这里又是干什么的,别忘了,我们使用的过滤核是lanczos核,所以我们还一个值没有确定,那就是用于控制其形状的基数w,而这个就是通过Feature(也就是length)获得的,这里直接给出映射公式
w = 1 2 − 1 4 F e a t u r e w=\frac{1}{2}-\frac{1}{4}Feature w=2141Feature
那么上诉lob的计算其实就是这个公式,也就是 l o b = w lob=w lob=w。那第二行呢,这个其实是为了裁剪——至于为什么,以及怎么得出裁剪值是 1 w \frac{1}{\sqrt{w}} w 1,具体见:https://zhuanlan.zhihu.com/p/401030221。但作用实际就是:超过这个区间的像素值的权重都是0

5️⃣计算得到12 tap窗口的最大值最小值

AF3 min4 = min(AMin3F3(AF3(ijfeR.z,ijfeG.z,ijfeB.z),AF3(klhgR.w,klhgG.w,klhgB.w),AF3(ijfeR.y,ijfeG.y,ijfeB.y)),
               AF3(klhgR.x,klhgG.x,klhgB.x));
AF3 max4 = max(AMax3F3(AF3(ijfeR.z,ijfeG.z,ijfeB.z),AF3(klhgR.w,klhgG.w,klhgB.w),AF3(ijfeR.y,ijfeG.y,ijfeB.y)),
               AF3(klhgR.x,klhgG.x,klhgB.x));

6️⃣然后,就是调用FsrEasuTapF 12次,累加aC(颜色)和aW(权重):

AF3 aC = AF3_(0.0);
AF1 aW = AF1_(0.0);
FsrEasuTapF(aC,aW,AF2( 0.0,-1.0)-pp,dir,len2,lob,clp,AF3(bczzR.x,bczzG.x,bczzB.x)); // b
FsrEasuTapF(aC,aW,AF2( 1.0,-1.0)-pp,dir,len2,lob,clp,AF3(bczzR.y,bczzG.y,bczzB.y)); // c
FsrEasuTapF(aC,aW,AF2(-1.0, 1.0)-pp,dir,len2,lob,clp,AF3(ijfeR.x,ijfeG.x,ijfeB.x)); // i
FsrEasuTapF(aC,aW,AF2( 0.0, 1.0)-pp,dir,len2,lob,clp,AF3(ijfeR.y,ijfeG.y,ijfeB.y)); // j
FsrEasuTapF(aC,aW,AF2( 0.0, 0.0)-pp,dir,len2,lob,clp,AF3(ijfeR.z,ijfeG.z,ijfeB.z)); // f
FsrEasuTapF(aC,aW,AF2(-1.0, 0.0)-pp,dir,len2,lob,clp,AF3(ijfeR.w,ijfeG.w,ijfeB.w)); // e
FsrEasuTapF(aC,aW,AF2( 1.0, 1.0)-pp,dir,len2,lob,clp,AF3(klhgR.x,klhgG.x,klhgB.x)); // k
FsrEasuTapF(aC,aW,AF2( 2.0, 1.0)-pp,dir,len2,lob,clp,AF3(klhgR.y,klhgG.y,klhgB.y)); // l
FsrEasuTapF(aC,aW,AF2( 2.0, 0.0)-pp,dir,len2,lob,clp,AF3(klhgR.z,klhgG.z,klhgB.z)); // h
FsrEasuTapF(aC,aW,AF2( 1.0, 0.0)-pp,dir,len2,lob,clp,AF3(klhgR.w,klhgG.w,klhgB.w)); // g
FsrEasuTapF(aC,aW,AF2( 1.0, 2.0)-pp,dir,len2,lob,clp,AF3(zzonR.z,zzonG.z,zzonB.z)); // o
FsrEasuTapF(aC,aW,AF2( 0.0, 2.0)-pp,dir,len2,lob,clp,AF3(zzonR.w,zzonG.w,zzonB.w)); // n
FsrEasuTapF分析

1️⃣首先,参数分析:

// Filtering for a given tap for the scalar.
 void FsrEasuTapF(
 inout AF3 aC, // 累积的颜色, with negative lobe.
 inout AF1 aW, // 累积的权重.
 AF2 off, // Pixel offset from resolve position to tap. 偏移
 AF2 dir, // Gradient direction. 旋转过滤核
 AF2 len, // Length.  缩放过滤核
 AF1 lob, // Negative lobe strength.  控制基数w
 AF1 clp, // Clipping point.  裁剪点
 AF3 c){ // Tap color.  tap的颜色

2️⃣使用方向offset(本质就是旋转过滤核):

// Rotate offset by direction.
AF2 v;
v.x = (off.x * ( dir.x)) + (off.y * dir.y);
v.y = (off.x * (-dir.y)) + (off.y * dir.x);

关于旋转,和之前讨论的缩放区间问题是一致的,我们原理是要旋转过滤核,但操作上更方便的,还是旋转输入值,但也要注意:旋转输入值,是要反方向旋转,这才对应的正确的过滤核旋转!

然后,继续缩放:

// Anisotropy.
v *= len;

3️⃣计算当前的位置离resolve point的距离,然后进行裁剪:
在这里插入图片描述

// Compute distance^2.
AF1 d2 = v.x * v.x + v.y * v.y;
// Limit to the window as at corner, 2 taps can easily be outside.
d2 = min(d2,clp);

4️⃣以下一大串都是,以wd2(也就是x)作为输入,来应用lanczos核,也就是计算当前tap的权重:
[ 25 16 ( 2 5 x 2 − 1 ) 2 − ( 25 16 − 1 ) ] ( w x 2 − 1 ) 2 [\frac{25}{16}(\frac{2}{5}x^2-1)^2-(\frac{25}{16}-1)](wx^2-1)^2 [1625(52x21)2(16251)](wx21)2

AF1 wB = AF1_(2.0 / 5.0) * d2 + AF1_(-1.0);
AF1 wA = lob * d2 + AF1_(-1.0);
wB *= wB;
wA *= wA;
wB = AF1_(25.0/16.0)*wB+AF1_(-(25.0/16.0-1.0));
AF1 w = wB * wA;

5️⃣最后,累加颜色和权重:

aC += c * w;
aW += w;
回到主函数2

C = 1 ∑ i = 0 n w i ∑ i = 1 n w i C i C=\frac{1}{\sum_{i=0}^{n}w_i}\sum_{i=1}^{n}w_iC_i C=i=0nwi1i=1nwiCi

最后,进行基操:累加的颜色,除以累加权重。然后使用之前计算好的局部最大值最小值,对结果进行clamp

pix = min(max4, max(min4, aC * AF3_(ARcpF1(aW))));

Tip : 之后

最后就是RACS源码分析, 就是最后的 AMD FSR 1.0源码分析(三)

可能还会有FSR 2.0技术的分享,反正文件夹是建好了,嘿嘿。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JMXIN422

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值