Unity中Temporal AA

Author Avatar
Kanglai Qian 2月 25, 2015

这个东西是trace在群里提到的,然后我看了一些相关资源Filtering Approaches for
Real-Time Anti-Aliasing
(很多sig course好棒好棒)、High Quality Temporal SupersamplingCryENGINE3 Graphics Gems。在这么多资料(其实是现成代码…)的帮助下,我主要参考CryEngine里的SMAA 1TX山寨了下,UE4的那个有点过于麻烦了。

备忘

  • Unity中矩阵是左乘的,和UE4里相反,所以在对projectionMatrix做jitter的时候要反下
  • UNITY_MATRIX_MVP在之前的post里已提过,这个是卡我最久的地方(╯‵□′)╯︵┻━┻
  • Depth Buffer也在同一个post里提了,这是卡我第二久的地方……
  • OnRenderImage里如果不手动设置RenderTexture.active的话,最好要保证最后对dest调用下Graphics.Blit,不然之后画的就乱七八糟了

其实都是一些API上的东西,搞的我连蒙带猜的…

效果

自我感觉效果还行吧…一开始边缘一直有闪动,慢慢改对代码之后降低了不少,最后就调参数了只能…

Temporal AA:

W/O AA:

Nexus5真机

Temporal AA W/O AA

放大出来还是能看出来的

性能

在Nexus 5上跑了下Shadow Gun Sample Level这个场景,每帧消耗时间大概增加了7ms;从profiler上来看主要是因为用到了Depth Texture,而且看起来不是直接用的ZBuffer导致的(见Unity中矩阵变换、深度纹理及杂项),话说还是Defer大法好-_,-

NVidia在Far Cry 4 Graphics, Performance & Tweaking Guide中有不同AA效果对比;TweakGuides的Crysis 3 Tweak Guide中有一节专门讲Antialiasing,里面有性能图标。后来问了下老大,她意思就是AA还是比较费的,等有性能余地的话才加;而且比较尴尬的是手机屏幕上其实看不太出区别,看来还是要配合动态分辨率+upscale~

TemporalAA1Tx.shaderview raw
Shader "Hidden/TemporalAA1Tx" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
ZTest Off Cull Off ZWrite Off Blend Off
Fog { Mode off }

Pass {
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest

struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float2 projPos : TEXCOORD1;
};

v2f vert( appdata_img v )
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.projPos = o.pos.xy / o.pos.w;
o.uv = v.texcoord.xy;
return o;
}

sampler2D _MainTex, _MainTex1;
sampler2D _CameraDepthTexture;
float4x4 combinedVP;
float2 texel;
float4 vParams;

float4 frag(v2f i) : SV_Target
{
float3 PosN = float3(i.projPos.xy, 0.0f);
PosN.z = tex2D(_CameraDepthTexture, i.uv.xy).r;
float4 temp = mul(combinedVP, float4(PosN, 1));

float2 BackN = temp.xy / temp.w;

float2 v = BackN.xy - PosN.xy;
// Kanglai: Proj Space -> Screen Space
v *= 0.5f;
v.y *= _ProjectionParams.x;

float fMaxFramesL = vParams.z; // Frames to keep in history (low freq). Higher = less aliasing, but blurier result. Lower = sharper result, but more aliasing.
float fMaxFramesH = vParams.w; // Frames to keep in history (high freq). Higher = less aliasing, but blurier result. Lower = sharper result, but more aliasing.

// Curr frame and neighboor texels
half3 cM = tex2D(_MainTex, i.uv.xy);
half3 cTL = tex2D(_MainTex, i.uv.xy + texel * float2(-0.5f, -0.5f));
half3 cTR = tex2D(_MainTex, i.uv.xy + texel * float2( 0.5f, -0.5f));
half3 cBL = tex2D(_MainTex, i.uv.xy + texel * float2(-0.5f, 0.5f));
half3 cBR = tex2D(_MainTex, i.uv.xy + texel * float2( 0.5f, 0.5f));

half3 cBlur = (cTL + cTR + cBL + cBR) * 0.25f;
cM.rgb = lerp(cBlur, cM, vParams.x);

half3 cMin = min(min(min(min(cTL, cTR), cBL), cBR), cM);
half3 cMax = max(max(max(max(cTL, cTR), cBL), cBR), cM);

float3 cAcc = tex2D(_MainTex1, i.uv.xy + v);
cAcc.rgb = clamp(cAcc, cMin, cMax); // Limit acc buffer color range to current frame

half3 cHiFreq = sqrt(abs( cBlur.rgb - cM.rgb));

float4 c = 0;
c.rgb = lerp(cAcc, cM, saturate(lerp(fMaxFramesL, fMaxFramesH, cHiFreq)) );
return c;
}

ENDCG
}
}
FallBack Off
}
TemporalAA.csview raw
using UnityEngine;
using System.Collections;

[RequireComponent (typeof (Camera))]
public class TemporalAA : MonoBehaviour {
public Shader shader = null;
private Material mat = null;

private bool initialized = false, usingRT1 = true;
private RenderTexture rt1 = null, rt2 = null;

private Matrix4x4 prevViewProj;
private int JitterIdx = 0;

private Matrix4x4 getViewProjMatrix() {
return GL.GetGPUProjectionMatrix(camera.projectionMatrix, false) * camera.worldToCameraMatrix;
}

void Start () {
mat = new Material(shader);
mat.hideFlags = HideFlags.HideAndDontSave;
}

void OnEnable() {
prevViewProj = getViewProjMatrix();

camera.depthTextureMode |= DepthTextureMode.Depth;

initialized = false;
usingRT1 = true;
}
void OnDisable() {
RenderTexture.Destroy(rt1);
rt1 = null;
RenderTexture.Destroy(rt2);
rt2 = null;
}

private float []JitterOffsetX = { -8.0f/16.0f, 0.0f/16.0f };
private float []JitterOffsetY = { 0.0f/16.0f, 8.0f/16.0f };
float Halton( int Index, int Base )
{
float Result = 0.0f;
float InvBase = 1.0f / Base;
float Fraction = InvBase;
while( Index > 0 )
{
Result += ( Index % Base ) * Fraction;
Index /= Base;
Fraction *= InvBase;
}
return Result;
}

void Update () {
JitterIdx = (JitterIdx + 1) % 8;

Matrix4x4 matrix = camera.projectionMatrix;
matrix[0,2] += (Halton(JitterIdx+1, 2)-0.5f)/Screen.width;//JitterOffsetX[JitterIdx]/Screen.width;
matrix[1,2] += (Halton(JitterIdx+1, 3)-0.5f)/Screen.height;//JitterOffsetY[JitterIdx]/Screen.height;
camera.projectionMatrix = matrix;
}

public Vector4 vParams = new Vector4(0.8f, 0, 24.0f, 32.0f);
void OnRenderImage(RenderTexture src, RenderTexture dest) {
if(rt1 == null) {
rt1 = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.ARGBHalf);
rt1.Create();
}
if(rt2 == null) {
rt2 = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.ARGBHalf);
rt2.Create();
}

camera.ResetProjectionMatrix();
Matrix4x4 ViewProj = getViewProjMatrix();
mat.SetMatrix("combinedVP", prevViewProj * Matrix4x4.Inverse(ViewProj));
prevViewProj = ViewProj;

RenderTexture prev = null, current = null;
if(usingRT1) {
current = rt1;
prev = rt2;
usingRT1 = false;
}
else {
current = rt2;
prev = rt1;
usingRT1 = true;
}

if(!initialized){
prev = src;
initialized = true;
}

mat.SetVector("texel", new Vector4(1.0f/src.width, 1.0f/src.height, 0, 0));
mat.SetVector("vParams", new Vector4(vParams.x , 0, 1.0f / vParams.z, 1.0f / vParams.w));
mat.SetTexture("_MainTex1", prev);
current.DiscardContents();
Graphics.Blit(src, current, mat);

Graphics.Blit(current, dest);
}
}