SMAA
スクリーンスペースのアンチエイリアスとして,FXAAを組み込みました。
今回は,もう少し綺麗になるらしい,スクリーンスペースのアンチエイリアスであるSMAAを組み込んでみます。
http://www.iryoku.com/smaa/
https://github.com/iryoku/smaa
UnityのPostProcessingStack v2にも組み込まれているようです。
SMAAの組み込み
SMAAは3つのパス(エッジ検出パス,ブレンド重み決定パス,ブレンドパス)と,テーブルとして利用されるテクスチャ2つ(AreaTexture,SerchTexture)が必要となります。
上記githubから,次のファイルを持ってきます。
- smaa/SMAA.hlsl
- smaa/Textures/SearchTex.h
- smaa/Textures/AreaTex.h
SMAA.hlslはシェーダー本体で,SearchTex.h,AreaTex.hはテーブルとして利用されるテクスチャのバイナリ定義です。
SearchTextureはRG8フォーマット,AreaTextureはR8フォーマットとして,先に読み込んでおきます。
SMAA.hlslをインクルードして,3つのパスを定義しますが,この時,次のマクロを定義します。
- SMAA_RT_METRICS :
zwにスクリーンサイズ,xyにスクリーンサイズの逆数の入ったfloat4
- SMAA_THRESHOLD :
エッジ検出の閾値
- SMAA_MAX_SEARCH_STEPS :
ブレンド重み計算時のstep数
- SMAA_MAX_SEARCH_STEPS_DIAG :
ブレンド重み計算時のstep数。SMAA_DISABLE_DIAG_DETECTION定義で無効
- SMAA_CORNER_ROUNDING :
ブレンド重み計算時の角の丸め率? SMAA_DISABLE_CORNER_DETECTION定義で無効
実際の組み込み例は次のような感じです。
#define SMAA_HLSL_4
#define SMAA_RT_METRICS (g_aTemp[0].xyzw)
#define SMAA_THRESHOLD (g_aTemp[1].x)
#define SMAA_MAX_SEARCH_STEPS (g_aTemp[1].y)
#define SMAA_MAX_SEARCH_STEPS_DIAG (g_aTemp[1].z)
#define SMAA_CORNER_ROUNDING (g_aTemp[1].w)
#define SCALEOFFSET (g_aTemp[2])
SamplerState g_LinearSampler : register(s0);
SamplerState g_PointSampler : register(s1);
#define SMAA_LINEAR_SAMPLER g_LinearSampler
#define SMAA_POINT_SAMPLER g_PointSampler
#include "SMAA.fx"
void GetPositionUV(out float4 position, out float2 uv, in uint index, in float4 scaleOffset)
{
const float2 vId = float2(index / 2u, index % 2u);
float2 screenPosition = float2(2.0f, 2.0f) * vId.xy * scaleOffset.xy + scaleOffset.zw;
screenPosition = float2(2.0f, -2.0f) * screenPosition + float2(-1.0f, 1.0f);
position = float4(screenPosition.xy, 0.1f, 1.0f);
uv = float2(2.0f, 2.0f) * vId.xy * scaleOffset.xy + scaleOffset.zw;
}
#ifdef PASS_EDGE_DETECTION
Texture2D g_SrcTexture : register(t0);
struct EDGE_DETECTION_OUT
{
float4 Position : SV_POSITION;
float2 UV : TEXCOORD0;
float4 aOffset[3] : TEXCOORD1;
};
EDGE_DETECTION_OUT VS_EdgeDetection(uint index : SV_VertexID)
{
EDGE_DETECTION_OUT Out = (EDGE_DETECTION_OUT)0;
GetPositionUV(Out.Position, Out.UV, index, SCALEOFFSET);
SMAAEdgeDetectionVS(Out.UV, Out.aOffset);
return Out;
}
float2 PS_EdgeDetection(EDGE_DETECTION_OUT In) : SV_TARGET0
{
return SMAAColorEdgeDetectionPS(In.UV.xy, In.aOffset, g_SrcTexture);
}
#endif
#ifdef PASS_BLENDING_WEIGHT_CALC
Texture2D g_EdgeTexture : register(t0);
Texture2D g_AreaTexture : register(t1);
Texture2D g_SearchTexture : register(t2);
struct BLENDING_WEIGHT_OUT
{
float4 Position : SV_POSITION;
float2 UV : TEXCOORD0;
float2 PixCoord : TEXCOORD1;
float4 aOffset[3] : TEXCOORD2;
};
BLENDING_WEIGHT_OUT VS_BlendingWeightCalculation(uint index : SV_VertexID)
{
BLENDING_WEIGHT_OUT Out = (BLENDING_WEIGHT_OUT)0;
GetPositionUV(Out.Position, Out.UV, index, SCALEOFFSET);
SMAABlendingWeightCalculationVS(Out.UV, Out.PixCoord, Out.aOffset);
return Out;
}
float4 PS_BlendingWeightCalculation(BLENDING_WEIGHT_OUT In) : SV_TARGET0
{
return SMAABlendingWeightCalculationPS(
In.UV,
In.PixCoord,
In.aOffset,
g_EdgeTexture,
g_AreaTexture,
g_SearchTexture,
0.0f
);
}
#endif
#ifdef PASS_NEIGHBORHOOD_BLENDING
Texture2D g_ColorTexture : register(t0);
Texture2D g_BlendTexture : register(t1);
Texture2D g_BeyerMatrixTexture : register(t2);
Texture2D g_NoiseTexture : register(t3);
SamplerState g_BeyerMatrixSampler : register(s2);
struct NEIGHBORHOOD_BLENDING_OUT
{
float4 Position : SV_POSITION;
float2 UV : TEXCOORD0;
float4 Offset : TEXCOORD1;
};
NEIGHBORHOOD_BLENDING_OUT VS_NeighborhoodBlending(uint index : SV_VertexID)
{
NEIGHBORHOOD_BLENDING_OUT Out = (NEIGHBORHOOD_BLENDING_OUT)0;
GetPositionUV(Out.Position, Out.UV, index, SCALEOFFSET);
SMAANeighborhoodBlendingVS(Out.UV, Out.Offset);
return Out;
}
float4 PS_NeighborhoodBlending(NEIGHBORHOOD_BLENDING_OUT In) : SV_TARGET0
{
const float2 uv = In.UV;
float4 outputColor = SMAANeighborhoodBlendingPS(
uv,
In.Offset,
g_ColorTexture,
g_BlendTexture
);
return outputColor;
}
#endif
エッジ検出パスでは,輝度ベースでエッジ検出を行うSMAALumaEdgeDetectionPSパスか,Color値のそれぞれの要素の差分から検出を行うSMAAColorEdgeDetectionPSパスを選択できます。
また,Depthテクスチャを渡してエッジの誤検出を防ぐような仕組みもあるようです。
今回は利用していませんが,TAA的に履歴バッファを利用してさらにサンプル数を稼ぐようなパスも用意されています。
結果
SMAA.hlslのクォリティ定義SMAA_PRESET_HIGHにあたる設定で適用してみました。
SMAA OFF |
SMAA ON |
以前実装したFXAAは,エッジ部分がボケた形になってしあう弱点があるのですが,
SMAAだとそのような事はおきず,綺麗にエッジのギザギザが無くなっています。
FXAAと比べてもかなり高品質な印象です。
ただし,FXAAだと0.14ms程度掛かっていた処理が,SMAAだと0.442ms程度まで増加しています。
やはり処理が複雑であることと,3つのパスを利用していることが効いているのでしょうか。