こんにちは!!!クライアントエンジニアの小林です。
今回はSMAAというアンチエイリアスをUnrealEngineに組み込みながら色々と勉強していきます。
プラグインで動くようにするのは面倒なのでエンジン改造です。
- 作業環境
- 概要
- SMAAとは
- 実装方針
- Edge Detection Pass
- Blend Weighting Pass
- Neighborhood Blending
- PS/CS負荷計測
- 小ネタ
- おわり!!!
- 参考サイト
- 実装
- 改造ルール
- Engine\Config\BaseEngine.ini
- Engine\Source\Editor\HardwareTargeting\Private\HardwareTargetingModule.cpp
- Engine\Source\Runtime\Engine\Classes\Engine\Engine.h
- Engine\Source\Runtime\Engine\Classes\Engine\Scene.h
- Engine\Source\Runtime\Engine\Private\Scene.cpp
- Engine\Source\Runtime\Engine\Private\SceneUtils.cpp
- Engine\Source\Runtime\Engine\Private\SceneView.cpp
- Engine\Source\Runtime\Engine\Private\UnrealEngine.cpp
- Engine\Source\Runtime\Engine\Public\SceneUtils.h
- Engine\Source\Runtime\Engine\Public\SceneViewExtension.h
- Engine\Source\Runtime\Renderer\Private\PostProcess\PostProcessing.cpp
- Engine\Source\Runtime\Renderer\Private\PostProcess\PostProcessSMAA.cpp
- Engine\Source\Runtime\Renderer\Private\PostProcess\PostProcessSMAA.h
- Engine\Shaders\Private\SMAA\SMAACommon.ush
- Engine\Shaders\Private\SMAA\SMAAEdgeDetection.usf
- Engine\Shaders\Private\SMAA\SMAABlendingWeightCalculation.usf
- Engine\Shaders\Private\SMAA\SMAANeighborhoodBlending.usf
作業環境
・windows 10
・visual studio 2022
・visual studio code
・unreal engine 5.2
概要
最近、趣味と業務の両方で話題に上がったので少しだけSMAAに興味が湧きました。
筆者は趣味でTTS開発を目標に深層学習の勉強をしています。
その勉強の一環としてアドベンチャーゲームに特化した日本語OCRを作っていました。
OCR開発も終盤の事です。精度テストの結果を纏めていると、ある傾向が出てきました。
それは最新の作品は精度が良好なのに対して、昔の作品(2000年代頃)はイマイチなことです。
調べてみると理由は単純で、昔の作品はブランドによっては文字にほぼほぼアンチエイリアス処理が適用されていませんでした。
筆者が作成したデータセットは「アンチエイリアスあり」のデータしか含んでいなかったので、真っ向から精度比較をしたら、そりゃ差分は出るよね、という感じでした。
趣味だとこのあたりでアンチエイリアスさんがひょっこりしてきた感じです。
業務に関してはアニメ調な見た目と動きが激しい作品とで、悲劇的な程、テンポラリィなアンチエイリアスと相性が悪いということになり、他に何か無いものかと調べていたら、原神でFSRとSMAAについての投稿を見つけて、「ほへぇ、あのmiHoYoさんでもテンポラリィなものは扱いが難しいのか、ならしゃーないやん」という自己解決をしたと同時に、なんでUnrealEngineにはSMAAないやねんと思ったのです。だってUnityにはあるの。
こんな感じで興味を持つ理由が十二分に揃ったので、組み込みながら勉強という名目でエンジン改造を楽しんでいこうという魂胆です。
SMAAとは
Unityのドキュメントで分かりやすく一文で説明されています。
冒頭にも書いたとおり、勉強目的で組み込みを進めるので、全貌を理解していません。
筆者が現状把握していることの一覧。
・スクリーンスペースベースなアンチエイリアス
・FXAAの強化版の立ち位置
・FXAAよりエッジが背景と混ざりすぎない
・3パス構成
・クオリティ設定ではテンポラル系同様にVelocityを見るような改修もできる
※今回実装するかは考え中
実装方針
SMAAの実装はgithubに公開されているため、そちらを参考にUnrealEngine仕様に改良しながら組み込んでいきます。
色々と調べていたらUE版のSMAA実装例があるっぽいのですが、それを覗いてしまうと筆者の楽しみが半減してしまうので見なかったことにします。
また、レンダリング設定はディファードでシェーディングモデル6のPC版でのみ動作確認をしていきます。
フォワードを外している理由は、筆者のUnrealEngineはディファード前提で色々とエンジン改造をしているので、シンプルにフォワードに切り替えると二度と起動できなくなるためです。
あとは公式のコーディング規約を無視してautoを多用しています。
気になる方は明示的な宣言に変えてください。
イテレータ以外はコンパイラが優秀っぽいのでautoでぶん投げてます。
こんなところでしょうか。
Edge Detection Pass
SMAAはエッジ検出情報を元に色々と処理をしているみたいです。
Edge Detection
エッジ検出方法はアウトライン同様に近傍ピクセルとの差分算出です。
差分の取り方は3つほど用意されています。
・SceneColorを輝度に変換、輝度差分から求めるLuma
・SceneColorの差分の絶対値を出して、各成分の最大値から求めるColor
・アウトラインでお馴染みの深度から求めるDepth
Depthは一般的なアウトライン計算と異なり、シーン深度に変換しないでデバイス深度で計算しているようです。
UnrealEngineのようにマルチプラットフォームに対応している環境ではシーン深度で扱った方が互換性取りやすいので、Depthで組む人はこの辺りを改修したほうが後々面倒ごとを避けられるかもですね。




DepthEdgeは閾値を緩くすると法線が垂直なベクトルですぐ検出事故が起こりますね。
これなら深度使わずに輝度か色差分が安牌な気がしますね。

Local Contrast Adaptation Factor
This allows to eliminate spurious crossing edges, and is based on the fact that, if there is too much contrast in a direction, that will hide perceptually contrast in the other neighbors.
DeepLとGoogleに聞いても「知覚的なコンストラクタ」という意味不明なことを返されました。
計算的には近傍との乖離具合でstepで影響を切り捨ててる感じでした。
なんとなくやりたいことは分かりましたが翻訳が意味わからない。



Predicate Texture
PredicateTextureを用いることでエッジの検出の閾値を細かく調整できるらしいです。
説明には「オブジェクトIDや深度などを使ったりするよ」と記載がありましたので、適当に深度と法線で試してみます。
the depth buffer usage may be limited to indoor or short range scenes.
ちなみに「深度は室内で使ってね」と記載がありましたが、一旦無視します。



説明にも記載があったとおり、Predication Threshold, Scale, Strength
パラメータをPredicate Textureに適した値に調整しないと使い物にならなさそうですね。
なんとなく用途は理解できましたし、今回は調整が面倒という理由で使わないことにします。
輝度計算の違い
SMAAとFXAAで使用されているLuma係数が違います。
SMAAは(0.2126, 0.7152, 0.0722)
に対して、FXAAは(0.299, 0.587, 0.114)
です。


目に見える差分は出ますが、なんか納得のいく解釈が筆者の中で出来ていません。
allows the tonemapper to not output Luma in alpha channel to use a R10G10B10A2
FXAAの実装コメントには「R10G10B10A2だからうんたらかんたら」と記載がありました。
なのでビット数による違いだと思うんですが、この辺りを理解するには筆者のレベルが足りないようです。
Blend Weighting Pass
EdgeDetectionPassで作成したEdgesTextureから偽エッジを取り除くパスのようです。
こちらのパスで行われている処理は、コードからだけでは汲み取れなかったので、SIGGRAPHさんで掲載されていた資料を頼りに進めました。
イベントの存在は知っていますが、筆者の苦手な自然言語でお話しされているので割と無理なんですよねぇ。日本語も英語もよく分からないですわ。
パターン検索
パターン検索はSIGGRAPHさんの資料が理解しやすかったので持ってきました。
偽エッジか否かを愚直に計算するとご想像のとおり計算量がエグいので、用意したパターンに合致するかで見ているそうです。
赤と緑がエッジで、赤色が現在のピクセル、緑色が周辺ピクセルです。
現在のエッジ(赤色)の先端と終端がどこかしらに伸びていれば真のエッジとして判定され、逆に先端と終端のどちらかが伸びていなければ、偽のエッジとして切り捨てられるようです。
切り捨て方法は真のエッジの重みと偽のエッジの重み、どちらが重いか, 強いかで判定しているそうです。
このような判定なのでwhileが多用されていたのですね。


AreaTextureとSearchTexture
パターン検索に必要な事前計算テクスチャですね。
圧縮方法はTipに「BC5とBC4を使用するとパフォーマンスが向上するよ」と記載がありましたので、そちらに従っています。
品質劣化についても触れられていましたが、確認した限りでは分からないレベルでした。


Max Search Steps, Max Search Steps Diag, Corner Rounding
とても親切なパラメータ名で助かります。




Neighborhood Blending
アンチエイリアスを適用している最終パスですね。
サンプリングするお隣さんの位置はBlendTextureに格納された情報を元に算出しているようです。
エッジがない部分は現在位置のSceneColorをバイリニアで取得して適当ぼかしをしているようです。
FXAAよりエッジの消失が少ないみたいなことを見かけましたが、諸々こういうことをしているからだったのですね。
None vs FXAA vs SMAA
ざっくりと見た目の比較でもしましょうか。
調子に乗ってスクショを撮り過ぎてGIFを量産してしましました。
こういう最後の動作確認は楽しいから仕方なしです。
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
改めて比較するとFXAAは言い方悪いと適当サンプリングなので、1ピクセル程度のアウトラインだと結構ボケちゃうのですね。
その点SMAAはアウトラインが輝度や色差分で確実にヒットするため、エッジがくっきりとした感じになりますね。
この辺りは求める絵作りによりけりだと思うので、好きなのを使ってねという感じでしょうか。
やっぱりSMAAも標準実装してほしいな。
PS/CS負荷計測




画面サイズによっても結果は変わると思いますが、予想していたよりコンピュートシェーダーによる恩恵が少なかったです。
Custom Qualityで非現実的なクオリティ設定をしてやっとCSが少し優位になる程度です。
CS版の実装はおまけ程度ですごく適当な実装なため、シンプルに実装ミスしている可能性も否めません。SRV指定しないとダメなのかなぁ。
小ネタ
既知や新しく知ったことのメモ。
usfとush
UnrealEngineのシェーダーファイル拡張子です。
usfがエントリーポイントで、ushがインクルードされている点に気が付けば、勘のいい人はなんとなく分かるかもですね。
拡張子 | 正式名 |
usf | Unreal Shader File |
ush | Unreal Shader Header |
mad
こんな便利な命令があったのですか、知りませんでした。
UnrealEngineではprecise
とセットで使用されている命令なので精度は劣っているのかな。
おわり!!!
お疲れさまでした!!!
新しいものと戯れる時間は楽しいものですね。
おそらく皆さんからしたら昔話からあるような(そこまで昔じゃない?)本技術ですけど、筆者さんはまだまだ若々で知らないこと多いので新しいのです。
アンチエイリアスまわりについて調べていたらニューラルネットワークを利用したDLAAなるものを見つけてしまいました。
データセットを用意するのが面倒ですがちょっと興味が湧きました。
コスト度返しで考えればTransformerを組み込んで大量のサンプルデータを突っ込めば、汎用性ゴリゴリなエッジ検出器が出来るはずので、確かにという気はしますね。
実際には畳み込み程度で収めているっぽいですが、いつか試してみたいものです。
参考サイト
- GitHub - iryoku/smaa: SMAA is a very efficient GPU-based MLAA implementation (DX9, DX10, DX11 and OpenGL), capable of handling subpixel features seamlessly, and featuring an improved and advanced pattern detection & handling mechanism.
- Advances in Real-Time Rendering- SIGGRAPH 2016 (Filmic SMAA: Sharp Morphological and Temporal Antialiasing)
- SMAA vs FSR2 immediately after switching characters: The oversharpening during rapid image changes was why people wanted SMAA back. : Genshin_Impact
- Anti-aliasing | Post Processing | 3.3.0
- アンチエイリアスのFXAAとSMAAってなに - tokei-note
- Enhanced Sub-pixel Morphological Anti-Aliasing, SMAAとは何? わかりやすく解説 Weblio辞書
- アンチエイリアス - Wikipedia
実装
リポジトリ用意するのを面倒くさがってコピペしたら割とな量になってしまったので、実装項目を最後にしています。
SMAA 1xのみの実装です、当初はS2xとT2xの実装も進めていましたが、途中で飽きてしまったので一旦オミットしました。
筆者のような気分屋さんは瞬間火力は高いのですが、冷めたときが致命的なんですよねぇ。
気が向いたら追記でもしておきます。
改造ルール
エンジン改造箇所のコード規約です。
記述フォーマットが公式で定められていないので、筆者はこんな感じで書いています。
//// rein.carnation @kobayashi-arata 2023/06/24 //// #if 0 // 改造前のコード // エンジンのバージョンアップが楽になるからこんな感じで残しています。 auto Flags = 0; #else // 改造後のコード auto Flags = 1; #endif //// rein.carnation @kobayashi-arata 2023/06/24 //// //// rein.carnation @kobayashi-arata 2023/06/24 //// // 新しいコードを挿入するだけの場合は #if ~ #else ~ #endifで囲わないです。 auto NewCode = ...; //// rein.carnation @kobayashi-arata 2023/06/24 ////
Engine\Config\BaseEngine.ini
AreaTexture
とSearchTexture
のアセットパスを指定しています。
パスを間違えたりアセットを消したりすると、それを解決しない限りUnrealEngineが起動できなくなります。
地味に注意してください。
LightMapDensityTextureName=/Engine/EngineMaterials/DefaultWhiteGrid.DefaultWhiteGrid ;//// rein.carnation @kobayashi-arata 2023/06/24 //// AreaTextureName=/Engine/EngineResources/AreaTexture.AreaTexture SearchTextureName=/Engine/EngineResources/SearchTexture.SearchTexture ;//// rein.carnation @kobayashi-arata 2023/06/24 //// NaniteHiddenSectionMaterialName=/Engine/EngineMaterials/NaniteHiddenSectionMaterial.NaniteHiddenSectionMaterial
Engine\Source\Editor\HardwareTargeting\Private\HardwareTargetingModule.cpp


マルチプラットフォーム対応な故にモバイルは別実装というのが描画まわりはアリアリなのですごく最初は面倒というかヤヤコシイデス。
case AAM_TSR: return LOCTEXT("AAM_TSR", "Temporal Super-Resolution (TSR)"); //// rein.carnation @kobayashi-arata 2023/06/24 //// case AAM_SMAA: return LOCTEXT("AA_SMAA", "Subpixel Morphological Anti-Aliasing (SMAA)"); //// rein.carnation @kobayashi-arata 2023/06/24 //// default: return FText::AsNumber((int32)Value);
Engine\Source\Runtime\Engine\Classes\Engine\Engine.h
AreaTexture
とSearchTexture
はメモリに常駐させておきたいテクスチャなので、class UEngine
という低レイヤー部分に配置しています。
UnrealEngineさんは素人目で見る限りかなりGCが優秀なので、使っていないものないし、正しいGC設定が行えていないものは随時ポイポイ解放しちゃうので、常駐させたいテクスチャなどは配置を気を付けないとすぐにNULLります。
厳密にはUEngineに配置したものがGCで解放されなくなるのではなく、後述するAddToRoot
などを叩けばいいだけなのですが、UEngineにおいてある=常駐感ということ直感的に伝えられるので、ここに配置しています。
/** Path of the texture used to display LightMapDensity */ UPROPERTY(globalconfig) FSoftObjectPath LightMapDensityTextureName; //// rein.carnation @kobayashi-arata 2023/06/24 //// UPROPERTY() TObjectPtr<class UTexture2D> AreaTexture; UPROPERTY(globalconfig) FSoftObjectPath AreaTextureName; UPROPERTY() TObjectPtr<class UTexture2D> SearchTexture; UPROPERTY(globalconfig) FSoftObjectPath SearchTextureName; //// rein.carnation @kobayashi-arata 2023/06/24 //// // Variables.
Engine\Source\Runtime\Engine\Classes\Engine\Scene.h
ポストプロセス設定にSMAAの項目を追加している部分です。
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Overrides, meta=(PinHiddenByDefault, InlineEditConditionToggle)) uint8 bOverride_MotionBlurPerObjectSize:1; //// rein.carnation @kobayashi-arata 2023/06/24 //// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle)) uint8 bOverride_SMAAThreshld : 1; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle)) uint8 bOverride_SMAAMaxSearchSteps : 1; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle)) uint8 bOverride_SMAAMaxSearchStepsDiag : 1; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (PinHiddenByDefault, InlineEditConditionToggle)) uint8 bOverride_SMAACornerRounding : 1; //// rein.carnation @kobayashi-arata 2023/06/24 //// UPROPERTY() uint8 bOverride_ScreenPercentage_DEPRECATED:1;
/** The minimum projected screen radius for a primitive to be drawn in the velocity pass, percentage of screen width. smaller numbers cause more draw calls, default: 4% */ UPROPERTY(interp, BlueprintReadWrite, Category="Rendering Features|Motion Blur", meta=(ClampMin = "0.0", UIMax = "100.0", editcondition = "bOverride_MotionBlurPerObjectSize", DisplayName = "Per Object Size")) float MotionBlurPerObjectSize; //// rein.carnation @kobayashi-arata 2023/06/24 //// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rendering Features|SMAA", meta = (ClampMin = "0.0", UIMax = "0.5", editcondition = "bOverride_SMAAThreshld", DisplayName = "Threshld")) float SMAAThreshld; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rendering Features|SMAA", meta = (ClampMin = "0", UIMax = "112", editcondition = "bOverride_SMAAMaxSearchSteps", DisplayName = "Max Search Steps")) int32 SMAAMaxSearchSteps; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rendering Features|SMAA", meta = (ClampMin = "0", UIMax = "20", editcondition = "bOverride_SMAAMaxSearchStepsDiag", DisplayName = "Max Search Steps Diag")) int32 SMAAMaxSearchStepsDiag; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rendering Features|SMAA", meta = (ClampMin = "0.0", UIMax = "100.0", editcondition = "bOverride_SMAACornerRounding", DisplayName = "Corner Rounding")) float SMAACornerRounding; //// rein.carnation @kobayashi-arata 2023/06/24 //// UPROPERTY() float LPVIntensity_DEPRECATED;
Engine\Source\Runtime\Engine\Private\Scene.cpp
SMAAパラメータの初期値やポストプロセス設定をオーバーライドする際の設定など。
MotionBlurPerObjectSize = 0.f; //// rein.carnation @kobayashi-arata 2023/06/24 //// SMAAThreshld = 0.1f; SMAAMaxSearchSteps = 16; SMAAMaxSearchStepsDiag = 8; SMAACornerRounding = 0.25f; //// rein.carnation @kobayashi-arata 2023/06/24 //// ScreenPercentage_DEPRECATED = 100.0f;
, bOverride_MotionBlurPerObjectSize(Settings.bOverride_MotionBlurPerObjectSize) //// rein.carnation @kobayashi-arata 2023/06/24 //// , bOverride_SMAAThreshld(Settings.bOverride_SMAAThreshld) , bOverride_SMAAMaxSearchSteps(Settings.bOverride_SMAAMaxSearchSteps) , bOverride_SMAAMaxSearchStepsDiag(Settings.bOverride_SMAAMaxSearchStepsDiag) , bOverride_SMAACornerRounding(Settings.bOverride_SMAACornerRounding) //// rein.carnation @kobayashi-arata 2023/06/24 //// , bOverride_ScreenPercentage_DEPRECATED(Settings.bOverride_ScreenPercentage_DEPRECATED)
, MotionBlurPerObjectSize(Settings.MotionBlurPerObjectSize) //// rein.carnation @kobayashi-arata 2023/06/24 //// , SMAAThreshld(Settings.SMAAThreshld) , SMAAMaxSearchSteps(Settings.SMAAMaxSearchSteps) , SMAAMaxSearchStepsDiag(Settings.SMAAMaxSearchStepsDiag) , SMAACornerRounding(Settings.SMAACornerRounding) //// rein.carnation @kobayashi-arata 2023/06/24 //// , TranslucencyType(Settings.TranslucencyType)
Engine\Source\Runtime\Engine\Private\SceneUtils.cpp
FXAAやTAAがNOPなのでたぶんNOPで大丈夫です。
else if (AntiAliasingMethod == EAntiAliasingMethod::AAM_TSR) { if (!SupportsTSR(ShaderPlatform)) { // Fallback to UE4's TAA if TSR isn't supported on that platform AntiAliasingMethod = AAM_TemporalAA; } } //// rein.carnation @kobayashi-arata 2023/06/24 //// else if (AntiAliasingMethod == EAntiAliasingMethod::AAM_SMAA) { // NOP } //// rein.carnation @kobayashi-arata 2023/06/24 //// else { unimplemented(); }
Engine\Source\Runtime\Engine\Private\SceneView.cpp
Weightに応じてSMAAパラメータを線形補間しています。
別にしなくてもいいです。割と適当。
というかWeightの実装自体が結構曖昧なので、2値で操作しないと見た目まわりのバグの温床になります。
LERP_PP(MotionBlurPerObjectSize); //// rein.carnation @kobayashi-arata 2023/06/24 //// LERP_PP(SMAAThreshld); LERP_PP(SMAAMaxSearchSteps); LERP_PP(SMAAMaxSearchStepsDiag); LERP_PP(SMAACornerRounding); //// rein.carnation @kobayashi-arata 2023/06/24 //// LERP_PP(ScreenSpaceReflectionQuality);
Engine\Source\Runtime\Engine\Private\UnrealEngine.cpp
LoadEngineTexture関数内で指定されたリソースに対してAddToRoot
を叩いてGCで勝手に解放されないようにしています。
XxxTextureNameが不正な場合には先ほど触れたようにこの関数でエラーが吐かれてUEが起動できなくなる感じです。
LoadEngineTexture(WeightMapPlaceholderTexture, *WeightMapPlaceholderTextureName.ToString()); LoadEngineTexture(LightMapDensityTexture, *LightMapDensityTextureName.ToString()); //// rein.carnation @kobayashi-arata 2023/06/24 //// // NOTE: 参照が死んでいるとLoadObjectでエラー吐いて抜け出せないのでテクスチャを置き換える際は要注意 LoadEngineTexture(AreaTexture, *AreaTextureName.ToString()); LoadEngineTexture(SearchTexture, *SearchTextureName.ToString()); //// rein.carnation @kobayashi-arata 2023/06/24 //// #if WITH_EDITOR // Avoid breaking some engine textures that might be cached very early (i.e. BlueNoise) FTextureCompilingManager::Get().FinishAllCompilation(); #endif
Engine\Source\Runtime\Engine\Public\SceneUtils.h
アンチエイリアスにSMAAの項目を追加しています。
AAM_TSR UMETA(DisplayName = "Temporal Super-Resolution (TSR)"), //// rein.carnation @kobayashi-arata 2023/06/24 //// AAM_SMAA UMETA(DisplayName = "Subpixel Morphological Anti-Aliasing (SMAA)"), //// rein.carnation @kobayashi-arata 2023/06/24 //// AAM_MAX,
Engine\Source\Runtime\Engine\Public\SceneViewExtension.h
プラグイン等で描画機能を拡張する際に触れることになる部分ですが、今回は使用しないのでとりあえずで実装しているだけです。
enum class EPostProcessingPass : uint32 { SSRInput, MotionBlur, Tonemap, FXAA, //// rein.carnation @kobayashi-arata 2023/06/24 //// SMAA, //// rein.carnation @kobayashi-arata 2023/06/24 //// VisualizeDepthOfField,
Engine\Source\Runtime\Renderer\Private\PostProcess\PostProcessing.cpp
ポストプロセスパスにSMAAを追加しています。
基本的にはFXAAと同位置にしています。
ポストプロセスパス関数もモバイルとそれ以外で実装が別れているため、実装箇所を間違えないように少し注意が必要です。
#include "PostProcess/PostProcessing.h" #include "PostProcess/PostProcessAA.h" //// rein.carnation @kobayashi-arata 2023/06/24 //// #include "PostProcess/PostProcessSMAA.h" //// rein.carnation @kobayashi-arata 2023/06/24 //// #if WITH_EDITOR
enum class EPass : uint32 { MotionBlur, Tonemap, FXAA, //// rein.carnation @kobayashi-arata 2023/06/24 //// SMAA, //// rein.carnation @kobayashi-arata 2023/06/24 //// PostProcessMaterialAfterTonemapping,
const auto TranslatePass = [](ISceneViewExtension::EPostProcessingPass Pass) -> EPass { switch (Pass) { case ISceneViewExtension::EPostProcessingPass::MotionBlur : return EPass::MotionBlur; case ISceneViewExtension::EPostProcessingPass::Tonemap : return EPass::Tonemap; case ISceneViewExtension::EPostProcessingPass::FXAA : return EPass::FXAA; //// rein.carnation @kobayashi-arata 2023/06/24 //// case ISceneViewExtension::EPostProcessingPass::SMAA : return EPass::SMAA; //// rein.carnation @kobayashi-arata 2023/06/24 //// case ISceneViewExtension::EPostProcessingPass::VisualizeDepthOfField : return EPass::VisualizeDepthOfField;
const TCHAR* PassNames[] = { TEXT("MotionBlur"), TEXT("Tonemap"), TEXT("FXAA"), //// rein.carnation @kobayashi-arata 2023/06/24 //// TEXT("SMAA"), //// rein.carnation @kobayashi-arata 2023/06/24 //// TEXT("PostProcessMaterial (AfterTonemapping)"),
PassSequence.SetEnabled(EPass::Tonemap, bTonemapEnabled); PassSequence.SetEnabled(EPass::FXAA, AntiAliasingMethod == AAM_FXAA); //// rein.carnation @kobayashi-arata 2023/06/24 //// PassSequence.SetEnabled(EPass::SMAA, AntiAliasingMethod == AAM_SMAA); //// rein.carnation @kobayashi-arata 2023/06/24 ////
PassInputs.ColorGradingTexture = ColorGradingTexture; //// rein.carnation @kobayashi-arata 2023/06/24 //// #if 0 PassInputs.bWriteAlphaChannel = AntiAliasingMethod == AAM_FXAA || bProcessSceneColorAlpha; #else PassInputs.bWriteAlphaChannel = (AntiAliasingMethod == AAM_FXAA || AntiAliasingMethod == AAM_SMAA ) || bProcessSceneColorAlpha; #endif //// rein.carnation @kobayashi-arata 2023/06/24 //// PassInputs.bOutputInHDR = bTonemapOutputInHDR;
SceneColor = AddAfterPass(EPass::FXAA, SceneColor); //// rein.carnation @kobayashi-arata 2023/06/24 //// if (PassSequence.IsEnabled(EPass::SMAA)) { FSMAAInputs PassInputs; PassSequence.AcceptOverrideIfLastPass(EPass::SMAA, PassInputs.OverrideOutput); PassInputs.SceneColor = SceneColor; PassInputs.SceneDepth = SceneDepth; PassInputs.WorldNormal = FScreenPassTexture(SceneTextureParameters.GBufferATexture); PassInputs.Quality = GetSMAAQuality(); PassInputs.Input = GetSMAAInput(); PassInputs.Predication = GetSMAAPredication(); SceneColor = AddSMAAPass(GraphBuilder, View, PassInputs); } SceneColor = AddAfterPass(EPass::SMAA, SceneColor); //// rein.carnation @kobayashi-arata 2023/06/24 //// // Post Process Material Chain - After Tonemapping
// Minimal PostProcessing - Separate translucency composition and gamma-correction only. else { PassSequence.SetEnabled(EPass::MotionBlur, false); PassSequence.SetEnabled(EPass::Tonemap, true); PassSequence.SetEnabled(EPass::FXAA, false); //// rein.carnation @kobayashi-arata 2023/06/24 //// PassSequence.SetEnabled(EPass::SMAA, false); //// rein.carnation @kobayashi-arata 2023/06/24 //// PassSequence.SetEnabled(EPass::PostProcessMaterialAfterTonemapping, false);
Engine\Source\Runtime\Renderer\Private\PostProcess\PostProcessSMAA.cpp
新規追加ファイルのPostProcessSMAA.cppです。
近くにあるファイルをコピーしてリネームするだけです。
リネーム後はGenerateProjectFiles.bat
を叩いてソリューションを更新しないとIntelliSenseが効かなかったり、ビルドがとおらなかったりするので注意が必要です。
このあたりも初見だと地味に引っかかるポイントだったりします。少なくとも筆者は引っかかりました。
SMAAシェーダーの読込やパスまわりの構築云々をしています。
やっていることはシンプルでリソースの確保、コンスタントバッファのセット、GraphBuilderに描画パスを追加、それを3回繰り返して、SMAAの結果をSceneColorに突っ込んで終わり。
当初はT2Xまで対応する予定だったので、リソースをDescsに突っ込んだり、前フレームのテクスチャを保存するフローがあったりしましたが、途中で燃え尽き症候群が発症したため、無かったことになりました。
// Copyright Epic Games, Inc. All Rights Reserved. //// rein.carnation @kobayashi-arata 2023/06/24 //// #include "PostProcess/PostProcessSMAA.h" #include "DataDrivenShaderPlatformInfo.h" #include "PostProcess/PostProcessing.h" #include "ScreenPass.h" #include "PixelShaderUtils.h" namespace { const int32 GSMAATileSizeX = 8; const int32 GSMAATileSizeY = 8; TAutoConsoleVariable<int32> CVarSMAAQuality( TEXT("r.SMAA.Quality"), 2, TEXT("Selects the quality permutation of SMAA.\n") TEXT(" 0: Low\n") TEXT(" 1: Medium\n") TEXT(" 2: High (Default)\n") TEXT(" 3: Ultra\n") TEXT(" 4: Custom"), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable<int32> CVarSMAAInput( TEXT("r.SMAA.Input"), 0, TEXT("Selects the input for edge detection.\n") TEXT(" 0: Luma (Default)\n") TEXT(" 1: Color\n") TEXT(" 2: Depth"), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable<float> CVarSMAALocalContrastAdaptationFactor( TEXT("r.SMAA.LocalContrastAdaptationFactor"), 2.0, TEXT("If there is an neighbor edge that has SMAA_LOCAL_CONTRAST_FACTOR times\n") TEXT("bigger contrast than current edge, current edge will be discarded.\n") TEXT("\n") TEXT("This allows to eliminate spurious crossing edges, and is based on the fact\n") TEXT("that, if there is too much contrast in a direction, that will hide\n") TEXT("perceptually contrast in the other neighbors.\n"), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable<int32> CVarSMAAPredication( TEXT("r.SMAA.Predication"), 0, TEXT("'PredicationTex'の種類\n") TEXT(" 0: None (Default)\n") TEXT(" 1: SceneDepth\n") TEXT(" 2: WorldNormal"), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable<float> CVarSMAAPredicationThreshold( TEXT("r.SMAA.Predication.Threshold"), 0.01, TEXT("'PredicationTex'で使用される閾値"), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable<float> CVarSMAAPredicationScale( TEXT("r.SMAA.Predication.Scale"), 2.0, TEXT("'PredicationTex'使用時、輝度orカラーエッジ検出に使用される閾値に対するスケール"), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable<float> CVarSMAAPredicationStrength( TEXT("r.SMAA.Predication.Strength"), 0.4, TEXT("'PredicationTex'使用時、閾値を局所的にどれだけ下げるか"), ECVF_Scalability | ECVF_RenderThreadSafe); TAutoConsoleVariable<int32> CVarSMAAUseCompute( TEXT("r.SMAA.UseCompute"), 0, TEXT(" 0: Uses pixel shader to save bandwidth with FBC on tiled gpu (default);\n") TEXT(" 1: Uses compute shader;\n"), ECVF_RenderThreadSafe); DECLARE_GPU_STAT(SMAA) } // namespace namespace { struct FSMAADescs { FScreenPassRenderTarget OverrideOutput; ESMAAQuality Quality; ESMAAInput Input; FScreenPassTexture SceneColor; FScreenPassTexture SceneDepth; FScreenPassTexture WorldNormal; ESMAAPredication Predication; FScreenPassTexture Edges; FScreenPassTexture Blend; FScreenPassTexture Output; bool bIsComputePass; FVector4f SubsampleIndices; explicit FSMAADescs(const FSMAAInputs& Inputs) : OverrideOutput(Inputs.OverrideOutput), Quality(Inputs.Quality), Input(Inputs.Input), SceneColor(Inputs.SceneColor), SceneDepth(Inputs.SceneDepth), WorldNormal(Inputs.WorldNormal), Predication(Inputs.Predication), Edges(), Blend(), Output(), bIsComputePass(false), SubsampleIndices(FVector4f(0.0f, 0.0f, 0.0f, 0.0f)) {} FRDGTextureDesc CreateEdgesTextureDesc() const { if (bIsComputePass) { return FRDGTextureDesc::Create2D( SceneColor.Texture->Desc.Extent, PF_FloatRGBA, FClearValueBinding::None, TexCreate_ShaderResource | TexCreate_UAV); } else { return FRDGTextureDesc::Create2D( SceneColor.Texture->Desc.Extent, // In NVIDIA cards R8G8 is slower, avoid it: PF_R8G8B8A8, FClearValueBinding(FLinearColor(0.0f, 0.0f, 0.0f, 0.0f)), TexCreate_RenderTargetable | TexCreate_ShaderResource); } } FRDGTextureDesc CreateBlendTextureDesc() const { if (bIsComputePass) { return FRDGTextureDesc::Create2D( SceneColor.Texture->Desc.Extent, PF_FloatRGBA, FClearValueBinding::None, TexCreate_ShaderResource | TexCreate_UAV); } else { return FRDGTextureDesc::Create2D( SceneColor.Texture->Desc.Extent, PF_R8G8B8A8, FClearValueBinding(FLinearColor(0.0f, 0.0f, 0.0f, 0.0f)), TexCreate_RenderTargetable | TexCreate_ShaderResource); } } FRDGTextureDesc CreateOutputTextureDesc() const { if (bIsComputePass) { return FRDGTextureDesc::Create2D( SceneColor.Texture->Desc.Extent, PF_FloatRGBA, FClearValueBinding::None, TexCreate_ShaderResource | TexCreate_UAV); } else { FRDGTextureDesc TextureDesc = SceneColor.Texture->Desc; TextureDesc.Reset(); return TextureDesc; } } }; BEGIN_SHADER_PARAMETER_STRUCT(FSMAAParameters, ) // xy: 1.0 / ViewportSize.xy, zw: ViewportSize.xy SHADER_PARAMETER(FVector4f, ViewportSize) // x: Threshld, y: MaxSearchSteps, z: MaxSearchStepsDiag, w: CornerRounding SHADER_PARAMETER(FVector4f, SMAAParams) // x: LocalContrastAdaptationFactor, y: PredicationThreshold, z: PredicationScale, w: PredicationStrength SHADER_PARAMETER(FVector4f, SMAAParams2) SHADER_PARAMETER_SAMPLER(SamplerState, PointSampler) SHADER_PARAMETER_SAMPLER(SamplerState, LinearSampler) END_SHADER_PARAMETER_STRUCT() class FQualityDim : SHADER_PERMUTATION_ENUM_CLASS("SMAA_PRESET", ESMAAQuality); class FPredicationDim : SHADER_PERMUTATION_BOOL("SMAA_PREDICATION"); using FSMAAPermutationDomain = TShaderPermutationDomain<FQualityDim, FPredicationDim>; } // namespace namespace { class FSMAAShader : public FGlobalShader { public: static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsPCPlatform(Parameters.Platform); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); if (IsPCPlatform(Parameters.Platform)) { OutEnvironment.SetDefine(TEXT("SMAA_HLSL_4_1"), 1); } } using FPermutationDomain = FSMAAPermutationDomain; FSMAAShader() {} FSMAAShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer) : FGlobalShader(Initializer) {} }; class FSMAAVertexShader : public FSMAAShader { public: static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FSMAAShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("SMAA_INCLUDE_VS"), 1); } FSMAAVertexShader() {} FSMAAVertexShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer) : FSMAAShader(Initializer) {} }; class FSMAAPixelShader : public FSMAAShader { public: static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FSMAAShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("SMAA_INCLUDE_PS"), 1); } FSMAAPixelShader() {} FSMAAPixelShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer) : FSMAAShader(Initializer) {} }; class FSMAAComputeShader : public FSMAAShader { public: static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FSMAAShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("SMAA_INCLUDE_CS"), 1); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), GSMAATileSizeX); OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), GSMAATileSizeY); } FSMAAComputeShader() {} FSMAAComputeShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer) : FSMAAShader(Initializer) {} }; class FSMAAEdgeDetectionVS : public FSMAAVertexShader { public: DECLARE_GLOBAL_SHADER(FSMAAEdgeDetectionVS); SHADER_USE_PARAMETER_STRUCT_WITH_LEGACY_BASE(FSMAAEdgeDetectionVS, FSMAAVertexShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FSMAAParameters, SMAAParameters) END_SHADER_PARAMETER_STRUCT() }; template<ESMAAInput SMAAInput> class FSMAAEdgeDetectionPS : public FSMAAPixelShader { public: DECLARE_GLOBAL_SHADER(FSMAAEdgeDetectionPS); SHADER_USE_PARAMETER_STRUCT(FSMAAEdgeDetectionPS, FSMAAPixelShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FSMAAParameters, SMAAParameters) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ColorTex) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, DepthTex) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, PredicationTex) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() }; template<ESMAAInput SMAAInput> class FSMAAEdgeDetectionCS : public FSMAAComputeShader { public: DECLARE_GLOBAL_SHADER(FSMAAEdgeDetectionCS); SHADER_USE_PARAMETER_STRUCT(FSMAAEdgeDetectionCS, FSMAAComputeShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FSMAAParameters, SMAAParameters) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ColorTex) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, DepthTex) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, PredicationTex) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, EdgesTexture) END_SHADER_PARAMETER_STRUCT() }; class FSMAABlendingWeightCalculationVS : public FSMAAVertexShader { public: DECLARE_GLOBAL_SHADER(FSMAABlendingWeightCalculationVS); SHADER_USE_PARAMETER_STRUCT_WITH_LEGACY_BASE(FSMAABlendingWeightCalculationVS, FSMAAVertexShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FSMAAParameters, SMAAParameters) END_SHADER_PARAMETER_STRUCT() }; class FSMAABlendingWeightCalculationPS : public FSMAAPixelShader { public: DECLARE_GLOBAL_SHADER(FSMAABlendingWeightCalculationPS); SHADER_USE_PARAMETER_STRUCT(FSMAABlendingWeightCalculationPS, FSMAAPixelShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FSMAAParameters, SMAAParameters) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, EdgesTex) SHADER_PARAMETER_TEXTURE(Texture2D, AreaTex) SHADER_PARAMETER_TEXTURE(Texture2D, SearchTex) SHADER_PARAMETER(FVector4f, SubsampleIndices) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() }; class FSMAABlendingWeightCalculationCS : public FSMAAComputeShader { public: DECLARE_GLOBAL_SHADER(FSMAABlendingWeightCalculationCS); SHADER_USE_PARAMETER_STRUCT(FSMAABlendingWeightCalculationCS, FSMAAComputeShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FSMAAParameters, SMAAParameters) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, EdgesTex) SHADER_PARAMETER_TEXTURE(Texture2D, AreaTex) SHADER_PARAMETER_TEXTURE(Texture2D, SearchTex) SHADER_PARAMETER(FVector4f, SubsampleIndices) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, BlendTexture) END_SHADER_PARAMETER_STRUCT() }; class FSMAANeighborhoodBlendingVS : public FSMAAVertexShader { public: DECLARE_GLOBAL_SHADER(FSMAANeighborhoodBlendingVS); SHADER_USE_PARAMETER_STRUCT_WITH_LEGACY_BASE(FSMAANeighborhoodBlendingVS, FSMAAVertexShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FSMAAParameters, SMAAParameters) END_SHADER_PARAMETER_STRUCT() }; class FSMAANeighborhoodBlendingPS : public FSMAAPixelShader { public: DECLARE_GLOBAL_SHADER(FSMAANeighborhoodBlendingPS); SHADER_USE_PARAMETER_STRUCT(FSMAANeighborhoodBlendingPS, FSMAAPixelShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FSMAAParameters, SMAAParameters) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ColorTex) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, BlendTex) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() }; class FSMAANeighborhoodBlendingCS : public FSMAAComputeShader { public: DECLARE_GLOBAL_SHADER(FSMAANeighborhoodBlendingCS); SHADER_USE_PARAMETER_STRUCT(FSMAANeighborhoodBlendingCS, FSMAAComputeShader); BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FSMAAParameters, SMAAParameters) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ColorTex) SHADER_PARAMETER_RDG_TEXTURE(Texture2D, BlendTex) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, OutputTexture) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FSMAAEdgeDetectionVS, "/Engine/Private/SMAA/SMAAEdgeDetection.usf", "SMAAEdgeDetectionVertexShader", SF_Vertex); IMPLEMENT_SHADER_TYPE(template<>, FSMAAEdgeDetectionPS<ESMAAInput::Luma>, TEXT("/Engine/Private/SMAA/SMAAEdgeDetection.usf"), TEXT("SMAALumaEdgeDetectionPixelShader"), SF_Pixel); IMPLEMENT_SHADER_TYPE(template<>, FSMAAEdgeDetectionPS<ESMAAInput::Color>, TEXT("/Engine/Private/SMAA/SMAAEdgeDetection.usf"), TEXT("SMAAColorEdgeDetectionPixelShader"), SF_Pixel); IMPLEMENT_SHADER_TYPE(template<>, FSMAAEdgeDetectionPS<ESMAAInput::Depth>, TEXT("/Engine/Private/SMAA/SMAAEdgeDetection.usf"), TEXT("SMAADepthEdgeDetectionPixelShader"), SF_Pixel); IMPLEMENT_SHADER_TYPE(template<>, FSMAAEdgeDetectionCS<ESMAAInput::Luma>, TEXT("/Engine/Private/SMAA/SMAAEdgeDetection.usf"), TEXT("SMAALumaEdgeDetectionComputeShader"), SF_Compute); IMPLEMENT_SHADER_TYPE(template<>, FSMAAEdgeDetectionCS<ESMAAInput::Color>, TEXT("/Engine/Private/SMAA/SMAAEdgeDetection.usf"), TEXT("SMAAColorEdgeDetectionComputeShader"), SF_Compute); IMPLEMENT_SHADER_TYPE(template<>, FSMAAEdgeDetectionCS<ESMAAInput::Depth>, TEXT("/Engine/Private/SMAA/SMAAEdgeDetection.usf"), TEXT("SMAADepthEdgeDetectionComputeShader"), SF_Compute); IMPLEMENT_GLOBAL_SHADER(FSMAABlendingWeightCalculationVS, "/Engine/Private/SMAA/SMAABlendingWeightCalculation.usf", "SMAABlendingWeightCalculationVertexShader", SF_Vertex); IMPLEMENT_GLOBAL_SHADER(FSMAABlendingWeightCalculationPS, "/Engine/Private/SMAA/SMAABlendingWeightCalculation.usf", "SMAABlendingWeightCalculationPixelShader", SF_Pixel); IMPLEMENT_GLOBAL_SHADER(FSMAABlendingWeightCalculationCS, "/Engine/Private/SMAA/SMAABlendingWeightCalculation.usf", "SMAABlendingWeightCalculationComputeShader", SF_Compute); IMPLEMENT_GLOBAL_SHADER(FSMAANeighborhoodBlendingVS, "/Engine/Private/SMAA/SMAANeighborhoodBlending.usf", "SMAANeighborhoodBlendingVertexShader", SF_Vertex); IMPLEMENT_GLOBAL_SHADER(FSMAANeighborhoodBlendingPS, "/Engine/Private/SMAA/SMAANeighborhoodBlending.usf", "SMAANeighborhoodBlendingPixelShader", SF_Pixel); IMPLEMENT_GLOBAL_SHADER(FSMAANeighborhoodBlendingCS, "/Engine/Private/SMAA/SMAANeighborhoodBlending.usf", "SMAANeighborhoodBlendingComputeShader", SF_Compute); } // namespace namespace { const TCHAR* const kSMAAEdgeDetectionNames[] = { TEXT("Luma"), TEXT("Color"), TEXT("Depth"), }; FSMAAParameters CreateSMAAParameters(const FSMAADescs& SMAADescs, const FViewInfo& View) { FSMAAParameters Parameters; const auto Viewport = GetScreenPassTextureViewportParameters(FScreenPassTextureViewport(SMAADescs.SceneColor)); Parameters.ViewportSize = FVector4f( Viewport.ExtentInverse, Viewport.Extent); Parameters.SMAAParams = FVector4f( View.FinalPostProcessSettings.SMAAThreshld, View.FinalPostProcessSettings.SMAAMaxSearchSteps, View.FinalPostProcessSettings.SMAAMaxSearchStepsDiag, View.FinalPostProcessSettings.SMAACornerRounding); Parameters.SMAAParams2 = FVector4f( CVarSMAALocalContrastAdaptationFactor.GetValueOnRenderThread(), CVarSMAAPredicationThreshold.GetValueOnRenderThread(), CVarSMAAPredicationScale.GetValueOnRenderThread(), CVarSMAAPredicationStrength.GetValueOnRenderThread()); Parameters.PointSampler = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(); Parameters.LinearSampler = TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(); return Parameters; } FRDGTextureRef GetPredicationTexture(FRDGBuilder& GraphBuilder, const FSMAADescs& SMAADescs) { switch (SMAADescs.Predication) { case ESMAAPredication::SceneDepth: return SMAADescs.SceneDepth.Texture; case ESMAAPredication::WorldNormal: return SMAADescs.WorldNormal.Texture; default: return GSystemTextures.GetBlackDummy(GraphBuilder); } } bool DoesSMAAUseComputeShader(const EShaderPlatform& Platform) { return IsPCPlatform(Platform) && CVarSMAAUseCompute.GetValueOnAnyThread() > 0; } FSMAAPermutationDomain BuildSMAAPermutationVector(const FSMAADescs& SMAADescs) { FSMAAPermutationDomain SMAAPermutationVector; SMAAPermutationVector.Set<FQualityDim>(SMAADescs.Quality); SMAAPermutationVector.Set<FPredicationDim>(SMAADescs.Predication != ESMAAPredication::None); return SMAAPermutationVector; } } // namespace namespace { template<ESMAAInput SMAAInput> FScreenPassTexture AddEdgesDetectionPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, FSMAADescs& SMAADescs) { auto EdgesTexture = FScreenPassRenderTarget( GraphBuilder.CreateTexture(SMAADescs.CreateEdgesTextureDesc(), TEXT("SMAA.EdgesTexture")), SMAADescs.SceneColor.ViewRect, ERenderTargetLoadAction::EClear); const FScreenPassTextureViewport Viewport(SMAADescs.SceneColor); auto SMAAPermutationVector = BuildSMAAPermutationVector(SMAADescs); if (SMAADescs.bIsComputePass) { auto PassParameters = GraphBuilder.AllocParameters<FSMAAEdgeDetectionCS<SMAAInput>::FParameters>(); PassParameters->SMAAParameters = CreateSMAAParameters(SMAADescs, View); PassParameters->ColorTex = SMAADescs.SceneColor.Texture; PassParameters->DepthTex = SMAADescs.SceneDepth.Texture; PassParameters->PredicationTex = GetPredicationTexture(GraphBuilder, SMAADescs); PassParameters->EdgesTexture = GraphBuilder.CreateUAV(EdgesTexture.Texture); TShaderMapRef<FSMAAEdgeDetectionCS<SMAAInput>> ComputeShader(View.ShaderMap, SMAAPermutationVector); ClearUnusedGraphResources(ComputeShader, PassParameters); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("%sEdgeDetection %dx%d (CS)", kSMAAEdgeDetectionNames[static_cast<int32_t>(SMAAInput)], Viewport.Rect.Width(), Viewport.Rect.Height()), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(Viewport.Rect.Size(), FIntPoint(GSMAATileSizeX, GSMAATileSizeY))); } else { auto PassParameters = GraphBuilder.AllocParameters<FSMAAEdgeDetectionPS<SMAAInput>::FParameters>(); PassParameters->SMAAParameters = CreateSMAAParameters(SMAADescs, View); PassParameters->ColorTex = SMAADescs.SceneColor.Texture; PassParameters->DepthTex = SMAADescs.SceneDepth.Texture; PassParameters->PredicationTex = GetPredicationTexture(GraphBuilder, SMAADescs); PassParameters->RenderTargets[0] = EdgesTexture.GetRenderTargetBinding(); TShaderMapRef<FSMAAEdgeDetectionVS> VertexShader(View.ShaderMap, SMAAPermutationVector); TShaderMapRef<FSMAAEdgeDetectionPS<SMAAInput>> PixelShader(View.ShaderMap, SMAAPermutationVector); auto BlendState = TStaticBlendState<CW_RGBA>::GetRHI(); auto DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI(); AddDrawScreenPass( GraphBuilder, RDG_EVENT_NAME("%sEdgeDetection %dx%d (PS)", kSMAAEdgeDetectionNames[static_cast<uint32_t>(SMAAInput)], Viewport.Rect.Width(), Viewport.Rect.Height()), View, Viewport, Viewport, FScreenPassPipelineState(VertexShader, PixelShader, BlendState, DepthStencilState), PassParameters, EScreenPassDrawFlags::None, [VertexShader, PixelShader, PassParameters](FRHICommandList& RHICmdList) { FSMAAEdgeDetectionVS::FParameters VertexParameters; VertexParameters.SMAAParameters = PassParameters->SMAAParameters; SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), VertexParameters); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters); }); } return MoveTemp(EdgesTexture); } FScreenPassTexture AddEdgesDetectionPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, FSMAADescs& SMAADescs) { switch (SMAADescs.Input) { case ESMAAInput::Luma: return AddEdgesDetectionPass<ESMAAInput::Luma>(GraphBuilder, View, SMAADescs); case ESMAAInput::Color: return AddEdgesDetectionPass<ESMAAInput::Color>(GraphBuilder, View, SMAADescs); case ESMAAInput::Depth: return AddEdgesDetectionPass<ESMAAInput::Depth>(GraphBuilder, View, SMAADescs); default: check(false); break; } return FScreenPassTexture(); } FScreenPassTexture AddBlendingWeightsCalculationPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, FSMAADescs& SMAADescs) { auto BlendTexture = FScreenPassRenderTarget( GraphBuilder.CreateTexture(SMAADescs.CreateBlendTextureDesc(), TEXT("SMAA.BlendTexture")), SMAADescs.SceneColor.ViewRect, ERenderTargetLoadAction::EClear); const FScreenPassTextureViewport Viewport(SMAADescs.SceneColor); auto SMAAPermutationVector = BuildSMAAPermutationVector(SMAADescs); if (SMAADescs.bIsComputePass) { auto PassParameters = GraphBuilder.AllocParameters<FSMAABlendingWeightCalculationCS::FParameters>(); PassParameters->SMAAParameters = CreateSMAAParameters(SMAADescs, View); PassParameters->EdgesTex = SMAADescs.Edges.Texture; PassParameters->AreaTex = GEngine->AreaTexture->GetResource()->TextureRHI; PassParameters->SearchTex = GEngine->SearchTexture->GetResource()->TextureRHI; PassParameters->SubsampleIndices = SMAADescs.SubsampleIndices; PassParameters->BlendTexture = GraphBuilder.CreateUAV(BlendTexture.Texture); TShaderMapRef<FSMAABlendingWeightCalculationCS> ComputeShader(View.ShaderMap, SMAAPermutationVector); ClearUnusedGraphResources(ComputeShader, PassParameters); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("BlendingWeightCalculation %dx%d (CS)", Viewport.Rect.Width(), Viewport.Rect.Height()), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(Viewport.Rect.Size(), FIntPoint(GSMAATileSizeX, GSMAATileSizeY))); } else { auto PassParameters = GraphBuilder.AllocParameters<FSMAABlendingWeightCalculationPS::FParameters>(); PassParameters->SMAAParameters = CreateSMAAParameters(SMAADescs, View); PassParameters->EdgesTex = SMAADescs.Edges.Texture; PassParameters->AreaTex = GEngine->AreaTexture->GetResource()->TextureRHI; PassParameters->SearchTex = GEngine->SearchTexture->GetResource()->TextureRHI; PassParameters->SubsampleIndices = SMAADescs.SubsampleIndices; PassParameters->RenderTargets[0] = BlendTexture.GetRenderTargetBinding(); TShaderMapRef<FSMAABlendingWeightCalculationVS> VertexShader(View.ShaderMap, SMAAPermutationVector); TShaderMapRef<FSMAABlendingWeightCalculationPS> PixelShader(View.ShaderMap, SMAAPermutationVector); auto BlendState = TStaticBlendState<CW_RGBA>::GetRHI(); auto DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI(); AddDrawScreenPass( GraphBuilder, RDG_EVENT_NAME("BlendingWeightCalculation %dx%d (PS)", Viewport.Rect.Width(), Viewport.Rect.Height()), View, Viewport, Viewport, FScreenPassPipelineState(VertexShader, PixelShader, BlendState, DepthStencilState), PassParameters, EScreenPassDrawFlags::None, [VertexShader, PixelShader, PassParameters](FRHICommandList& RHICmdList) { FSMAABlendingWeightCalculationVS::FParameters VertexParameters; VertexParameters.SMAAParameters = PassParameters->SMAAParameters; SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), VertexParameters); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters); }); } return MoveTemp(BlendTexture); } FScreenPassTexture AddNeighborhoodBlendingPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FSMAADescs& SMAADescs) { FScreenPassRenderTarget Output = SMAADescs.OverrideOutput; if (!Output.IsValid()) { Output = FScreenPassRenderTarget( GraphBuilder.CreateTexture(SMAADescs.CreateOutputTextureDesc(), TEXT("SMAA")), SMAADescs.SceneColor.ViewRect, View.GetOverwriteLoadAction()); } const FScreenPassTextureViewport Viewport(SMAADescs.SceneColor); auto SMAAPermutationVector = BuildSMAAPermutationVector(SMAADescs); if (SMAADescs.bIsComputePass) { auto PassParameters = GraphBuilder.AllocParameters<FSMAANeighborhoodBlendingCS::FParameters>(); PassParameters->SMAAParameters = CreateSMAAParameters(SMAADescs, View); PassParameters->ColorTex = SMAADescs.SceneColor.Texture; PassParameters->BlendTex = SMAADescs.Blend.Texture; PassParameters->OutputTexture = GraphBuilder.CreateUAV(Output.Texture); TShaderMapRef<FSMAANeighborhoodBlendingCS> ComputeShader(View.ShaderMap, SMAAPermutationVector); ClearUnusedGraphResources(ComputeShader, PassParameters); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("NeighborhoodBlending %dx%d (CS)", Viewport.Rect.Width(), Viewport.Rect.Height()), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(Viewport.Rect.Size(), FIntPoint(GSMAATileSizeX, GSMAATileSizeY))); } else { auto PassParameters = GraphBuilder.AllocParameters<FSMAANeighborhoodBlendingPS::FParameters>(); PassParameters->SMAAParameters = CreateSMAAParameters(SMAADescs, View); PassParameters->ColorTex = SMAADescs.SceneColor.Texture; PassParameters->BlendTex = SMAADescs.Blend.Texture; PassParameters->RenderTargets[0] = Output.GetRenderTargetBinding(); TShaderMapRef<FSMAANeighborhoodBlendingVS> VertexShader(View.ShaderMap, SMAAPermutationVector); TShaderMapRef<FSMAANeighborhoodBlendingPS> PixelShader(View.ShaderMap, SMAAPermutationVector); auto BlendState = TStaticBlendState<CW_RGBA>::GetRHI(); auto DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI(); AddDrawScreenPass( GraphBuilder, RDG_EVENT_NAME("NeighborhoodBlending %dx%d (PS)", Viewport.Rect.Width(), Viewport.Rect.Height()), View, Viewport, Viewport, FScreenPassPipelineState(VertexShader, PixelShader, BlendState, DepthStencilState), PassParameters, EScreenPassDrawFlags::None, [VertexShader, PixelShader, PassParameters](FRHICommandList& RHICmdList) { FSMAANeighborhoodBlendingVS::FParameters VertexParameters; VertexParameters.SMAAParameters = PassParameters->SMAAParameters; SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), VertexParameters); SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), *PassParameters); }); } return MoveTemp(Output); } } // namespace ESMAAQuality GetSMAAQuality() { return ESMAAQuality(FMath::Clamp( CVarSMAAQuality.GetValueOnRenderThread(), 0, static_cast<int32_t>(ESMAAQuality::MAX) - 1)); } ESMAAInput GetSMAAInput() { return ESMAAInput(FMath::Clamp( CVarSMAAInput.GetValueOnRenderThread(), 0, static_cast<int32_t>(ESMAAInput::MAX) - 1)); } ESMAAPredication GetSMAAPredication() { return ESMAAPredication(FMath::Clamp( CVarSMAAPredication.GetValueOnRenderThread(), 0, static_cast<int32_t>(ESMAAPredication::MAX) - 1)); } FScreenPassTexture AddSMAAPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FSMAAInputs& Inputs) { check(Inputs.IsValid()); RDG_GPU_STAT_SCOPE(GraphBuilder, SMAA); RDG_EVENT_SCOPE(GraphBuilder, "SMAA(Quality=%d)", Inputs.Quality); FSMAADescs SMAADescs(Inputs); SMAADescs.bIsComputePass = DoesSMAAUseComputeShader(View.GetShaderPlatform()); SMAADescs.Edges = AddEdgesDetectionPass(GraphBuilder, View, SMAADescs); SMAADescs.Blend = AddBlendingWeightsCalculationPass(GraphBuilder, View, SMAADescs); SMAADescs.Output = AddNeighborhoodBlendingPass(GraphBuilder, View, SMAADescs); return MoveTemp(SMAADescs.Output); } //// rein.carnation @kobayashi-arata 2023/06/24 ////
Engine\Source\Runtime\Renderer\Private\PostProcess\PostProcessSMAA.h
// Copyright Epic Games, Inc. All Rights Reserved. #pragma once //// rein.carnation @kobayashi-arata 2023/06/24 //// #include "ScreenPass.h" #include "OverridePassSequence.h" enum class ESMAAQuality : uint8 { Low, Medium, High, Ultra, Custom, MAX, }; enum class ESMAAMode : uint8 { SMAA_1X, SMAA_T2X, SMAA_S2X, SMAA_4X, MAX, }; enum class ESMAAInput : uint8 { Luma, Color, Depth, MAX, }; enum class ESMAAPredication : uint8 { None, SceneDepth, WorldNormal, MAX, }; ESMAAQuality GetSMAAQuality(); ESMAAInput GetSMAAInput(); ESMAAPredication GetSMAAPredication(); struct FSMAAInputs { FScreenPassRenderTarget OverrideOutput; FScreenPassTexture SceneColor; FScreenPassTexture SceneDepth; FScreenPassTexture WorldNormal; // SMAA filter quality. ESMAAQuality Quality = ESMAAQuality::MAX; ESMAAPredication Predication = ESMAAPredication::None; ESMAAInput Input = ESMAAInput::Luma; inline bool IsValid() const { return SceneColor.IsValid(); } }; FScreenPassTexture RENDERER_API AddSMAAPass(FRDGBuilder& GraphBuilder, const FViewInfo& View, const FSMAAInputs& Inputs); //// rein.carnation @kobayashi-arata 2023/06/24 ////
Engine\Shaders\Private\SMAA\SMAACommon.ush
シェーダーコードに関しては参考元のgithubからコピーしただけです。
/** * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) * * Permission is hereby granted, free of charge, to any person obtaining a copy * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to * do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. As clarification, there * is no requirement that the copyright notice and permission be included in * binary distributions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * _______ ___ ___ ___ ___ * / || \/ | / \ / \ * | (---- | \ / | / ^ \ / ^ \ * \ \ | |\/| | / /_\ \ / /_\ \ * ----) | | | | | / _____ \ / _____ \ * |_______/ |__| |__| /__/ \__\ /__/ \__\ * * E N H A N C E D * S U B P I X E L M O R P H O L O G I C A L A N T I A L I A S I N G * * http://www.iryoku.com/smaa/ * * Hi, welcome aboard! * * Here you'll find instructions to get the shader up and running as fast as * possible. * * IMPORTANTE NOTICE: when updating, remember to update both this file and the * precomputed textures! They may change from version to version. * * The shader has three passes, chained together as follows: * * |input|------------------� * v | * [ SMAA*EdgeDetection ] | * v | * |edgesTex| | * v | * [ SMAABlendingWeightCalculation ] | * v | * |blendTex| | * v | * [ SMAANeighborhoodBlending ] <------� * v * |output| * * Note that each [pass] has its own vertex and pixel shader. Remember to use * oversized triangles instead of quads to avoid overshading along the * diagonal. * * You've three edge detection methods to choose from: luma, color or depth. * They represent different quality/performance and anti-aliasing/sharpness * tradeoffs, so our recommendation is for you to choose the one that best * suits your particular scenario: * * - Depth edge detection is usually the fastest but it may miss some edges. * * - Luma edge detection is usually more expensive than depth edge detection, * but catches visible edges that depth edge detection can miss. * * - Color edge detection is usually the most expensive one but catches * chroma-only edges. * * For quickstarters: just use luma edge detection. * * The general advice is to not rush the integration process and ensure each * step is done correctly (don't try to integrate SMAA T2x with predicated edge * detection from the start!). Ok then, let's go! * * 1. The first step is to create two RGBA temporal render targets for holding * |edgesTex| and |blendTex|. * * In DX10 or DX11, you can use a RG render target for the edges texture. * In the case of NVIDIA GPUs, using RG render targets seems to actually be * slower. * * On the Xbox 360, you can use the same render target for resolving both * |edgesTex| and |blendTex|, as they aren't needed simultaneously. * * 2. Both temporal render targets |edgesTex| and |blendTex| must be cleared * each frame. Do not forget to clear the alpha channel! * * 3. The next step is loading the two supporting precalculated textures, * 'areaTex' and 'searchTex'. You'll find them in the 'Textures' folder as * C++ headers, and also as regular DDS files. They'll be needed for the * 'SMAABlendingWeightCalculation' pass. * * If you use the C++ headers, be sure to load them in the format specified * inside of them. * * You can also compress 'areaTex' and 'searchTex' using BC5 and BC4 * respectively, if you have that option in your content processor pipeline. * When compressing then, you get a non-perceptible quality decrease, and a * marginal performance increase. * * 4. All samplers must be set to linear filtering and clamp. * * After you get the technique working, remember that 64-bit inputs have * half-rate linear filtering on GCN. * * If SMAA is applied to 64-bit color buffers, switching to point filtering * when accesing them will increase the performance. Search for * 'SMAASamplePoint' to see which textures may benefit from point * filtering, and where (which is basically the color input in the edge * detection and resolve passes). * * 5. All texture reads and buffer writes must be non-sRGB, with the exception * of the input read and the output write in * 'SMAANeighborhoodBlending' (and only in this pass!). If sRGB reads in * this last pass are not possible, the technique will work anyway, but * will perform antialiasing in gamma space. * * IMPORTANT: for best results the input read for the color/luma edge * detection should *NOT* be sRGB. * * 6. Before including SMAA.h you'll have to setup the render target metrics, * the target and any optional configuration defines. Optionally you can * use a preset. * * You have the following targets available: * SMAA_HLSL_3 * SMAA_HLSL_4 * SMAA_HLSL_4_1 * SMAA_GLSL_3 * * SMAA_GLSL_4 * * * * (See SMAA_INCLUDE_VS and SMAA_INCLUDE_PS below). * * And four presets: * SMAA_PRESET_LOW (%60 of the quality) * SMAA_PRESET_MEDIUM (%80 of the quality) * SMAA_PRESET_HIGH (%95 of the quality) * SMAA_PRESET_ULTRA (%99 of the quality) * * For example: * #define SMAA_RT_METRICS float4(1.0 / 1280.0, 1.0 / 720.0, 1280.0, 720.0) * #define SMAA_HLSL_4 * #define SMAA_PRESET_HIGH * #include "SMAA.h" * * Note that SMAA_RT_METRICS doesn't need to be a macro, it can be a * uniform variable. The code is designed to minimize the impact of not * using a constant value, but it is still better to hardcode it. * * Depending on how you encoded 'areaTex' and 'searchTex', you may have to * add (and customize) the following defines before including SMAA.h: * #define SMAA_AREATEX_SELECT(sample) sample.rg * #define SMAA_SEARCHTEX_SELECT(sample) sample.r * * If your engine is already using porting macros, you can define * SMAA_CUSTOM_SL, and define the porting functions by yourself. * * 7. Then, you'll have to setup the passes as indicated in the scheme above. * You can take a look into SMAA.fx, to see how we did it for our demo. * Checkout the function wrappers, you may want to copy-paste them! * * 8. It's recommended to validate the produced |edgesTex| and |blendTex|. * You can use a screenshot from your engine to compare the |edgesTex| * and |blendTex| produced inside of the engine with the results obtained * with the reference demo. * * 9. After you get the last pass to work, it's time to optimize. You'll have * to initialize a stencil buffer in the first pass (discard is already in * the code), then mask execution by using it the second pass. The last * pass should be executed in all pixels. * * * After this point you can choose to enable predicated thresholding, * temporal supersampling and motion blur integration: * * a) If you want to use predicated thresholding, take a look into * SMAA_PREDICATION; you'll need to pass an extra texture in the edge * detection pass. * * b) If you want to enable temporal supersampling (SMAA T2x): * * 1. The first step is to render using subpixel jitters. I won't go into * detail, but it's as simple as moving each vertex position in the * vertex shader, you can check how we do it in our DX10 demo. * * 2. Then, you must setup the temporal resolve. You may want to take a look * into SMAAResolve for resolving 2x modes. After you get it working, you'll * probably see ghosting everywhere. But fear not, you can enable the * CryENGINE temporal reprojection by setting the SMAA_REPROJECTION macro. * Check out SMAA_DECODE_VELOCITY if your velocity buffer is encoded. * * 3. The next step is to apply SMAA to each subpixel jittered frame, just as * done for 1x. * * 4. At this point you should already have something usable, but for best * results the proper area textures must be set depending on current jitter. * For this, the parameter 'subsampleIndices' of * 'SMAABlendingWeightCalculationPS' must be set as follows, for our T2x * mode: * * @SUBSAMPLE_INDICES * * | S# | Camera Jitter | subsampleIndices | * +----+------------------+---------------------+ * | 0 | ( 0.25, -0.25) | float4(1, 1, 1, 0) | * | 1 | (-0.25, 0.25) | float4(2, 2, 2, 0) | * * These jitter positions assume a bottom-to-top y axis. S# stands for the * sample number. * * More information about temporal supersampling here: * http://iryoku.com/aacourse/downloads/13-Anti-Aliasing-Methods-in-CryENGINE-3.pdf * * c) If you want to enable spatial multisampling (SMAA S2x): * * 1. The scene must be rendered using MSAA 2x. The MSAA 2x buffer must be * created with: * - DX10: see below (*) * - DX10.1: D3D10_STANDARD_MULTISAMPLE_PATTERN or * - DX11: D3D11_STANDARD_MULTISAMPLE_PATTERN * * This allows to ensure that the subsample order matches the table in * @SUBSAMPLE_INDICES. * * (*) In the case of DX10, we refer the reader to: * - SMAA::detectMSAAOrder and * - SMAA::msaaReorder * * These functions allow to match the standard multisample patterns by * detecting the subsample order for a specific GPU, and reordering * them appropriately. * * 2. A shader must be run to output each subsample into a separate buffer * (DX10 is required). You can use SMAASeparate for this purpose, or just do * it in an existing pass (for example, in the tone mapping pass, which has * the advantage of feeding tone mapped subsamples to SMAA, which will yield * better results). * * 3. The full SMAA 1x pipeline must be run for each separated buffer, storing * the results in the final buffer. The second run should alpha blend with * the existing final buffer using a blending factor of 0.5. * 'subsampleIndices' must be adjusted as in the SMAA T2x case (see point * b). * * d) If you want to enable temporal supersampling on top of SMAA S2x * (which actually is SMAA 4x): * * 1. SMAA 4x consists on temporally jittering SMAA S2x, so the first step is * to calculate SMAA S2x for current frame. In this case, 'subsampleIndices' * must be set as follows: * * | F# | S# | Camera Jitter | Net Jitter | subsampleIndices | * +----+----+--------------------+-------------------+----------------------+ * | 0 | 0 | ( 0.125, 0.125) | ( 0.375, -0.125) | float4(5, 3, 1, 3) | * | 0 | 1 | ( 0.125, 0.125) | (-0.125, 0.375) | float4(4, 6, 2, 3) | * +----+----+--------------------+-------------------+----------------------+ * | 1 | 2 | (-0.125, -0.125) | ( 0.125, -0.375) | float4(3, 5, 1, 4) | * | 1 | 3 | (-0.125, -0.125) | (-0.375, 0.125) | float4(6, 4, 2, 4) | * * These jitter positions assume a bottom-to-top y axis. F# stands for the * frame number. S# stands for the sample number. * * 2. After calculating SMAA S2x for current frame (with the new subsample * indices), previous frame must be reprojected as in SMAA T2x mode (see * point b). * * e) If motion blur is used, you may want to do the edge detection pass * together with motion blur. This has two advantages: * * 1. Pixels under heavy motion can be omitted from the edge detection process. * For these pixels we can just store "no edge", as motion blur will take * care of them. * 2. The center pixel tap is reused. * * Note that in this case depth testing should be used instead of stenciling, * as we have to write all the pixels in the motion blur pass. * * That's it! */ #pragma once #include "../Common.ush" //----------------------------------------------------------------------------- // SMAA Parameters // xy: 1.0 / ViewportSize.xy, zw: ViewportSize.xy float4 ViewportSize; // x: Threshld, y: MaxSearchSteps, z: MaxSearchStepsDiag, w: CornerRounding float4 SMAAParams; // x: LocalContrastAdaptationFactor, y: PredicationThreshold, z: PredicationScale, w: PredicationStrength float4 SMAAParams2; // Filter = MIN_MAG_MIP_POINT; // AddressU = Clamp; // AddressV = Clamp; SamplerState PointSampler; // Filter = MIN_MAG_LINEAR_MIP_POINT; // AddressU = Clamp; // AddressV = Clamp; SamplerState LinearSampler; //----------------------------------------------------------------------------- // SMAA Presets /** * Note that if you use one of these presets, the following configuration * macros will be ignored if set in the "Configurable Defines" section. */ #if SMAA_PRESET == 0 #define SMAA_THRESHOLD 0.15 #define SMAA_MAX_SEARCH_STEPS 4 #define SMAA_DISABLE_DIAG_DETECTION #define SMAA_DISABLE_CORNER_DETECTION #elif SMAA_PRESET == 1 #define SMAA_THRESHOLD 0.1 #define SMAA_MAX_SEARCH_STEPS 8 #define SMAA_DISABLE_DIAG_DETECTION #define SMAA_DISABLE_CORNER_DETECTION #elif SMAA_PRESET == 2 #define SMAA_THRESHOLD 0.1 #define SMAA_MAX_SEARCH_STEPS 16 #define SMAA_MAX_SEARCH_STEPS_DIAG 8 #define SMAA_CORNER_ROUNDING 25 #elif SMAA_PRESET == 3 #define SMAA_THRESHOLD 0.05 #define SMAA_MAX_SEARCH_STEPS 32 #define SMAA_MAX_SEARCH_STEPS_DIAG 16 #define SMAA_CORNER_ROUNDING 25 #else #define SMAA_THRESHOLD SMAAParams.x #define SMAA_MAX_SEARCH_STEPS SMAAParams.y #define SMAA_MAX_SEARCH_STEPS_DIAG SMAAParams.z #define SMAA_CORNER_ROUNDING SMAAParams.w #endif //----------------------------------------------------------------------------- // Configurable Defines #define SMAA_RT_METRICS ViewportSize /** * SMAA_THRESHOLD specifies the threshold or sensitivity to edges. * Lowering this value you will be able to detect more edges at the expense of * performance. * SMAA_THRESHOLDは、エッジに対する閾値または感度を指定します。 * この値を下げると、パフォーマンスを犠牲にしてより多くのエッジを検出できるようになります。 * * Range: [0, 0.5] * 0.1 is a reasonable value, and allows to catch most visible edges. * 0.05 is a rather overkill value, that allows to catch 'em all. * 0.1は妥当な値で、目に見えるほとんどのエッジを捉えることができます。 * 0.05 はかなり過剰な値で、すべてを検出することができます。 * * If temporal supersampling is used, 0.2 could be a reasonable value, as low * contrast edges are properly filtered by just 2x. * テンポラル・スーパーサンプリングが使用される場合、0.2が妥当な値となり、低コントラストのエッジはわずか2倍で適切にフィルタリングされます。 */ #ifndef SMAA_THRESHOLD #define SMAA_THRESHOLD 0.1 #endif /** * SMAA_DEPTH_THRESHOLD specifies the threshold for depth edge detection. * SMAA_DEPTH_THRESHOLD は、深度エッジ検出のしきい値を指定します。 * * Range: depends on the depth range of the scene. * Range: シーンの深度範囲に依存します。 */ #ifndef SMAA_DEPTH_THRESHOLD #define SMAA_DEPTH_THRESHOLD (0.1 * SMAA_THRESHOLD) #endif /** * SMAA_MAX_SEARCH_STEPS specifies the maximum steps performed in the * horizontal/vertical pattern searches, at each side of the pixel. * パターン検索の最大ステップ数。 * * In number of pixels, it's actually the double. So the maximum line length * perfectly handled by, for example 16, is 64 (by perfectly, we meant that * longer lines won't look as good, but still antialiased). * * Range: [0, 112] */ #ifndef SMAA_MAX_SEARCH_STEPS #define SMAA_MAX_SEARCH_STEPS 16 #endif /** * SMAA_MAX_SEARCH_STEPS_DIAG specifies the maximum steps performed in the * diagonal pattern searches, at each side of the pixel. In this case we jump * one pixel at time, instead of two. * * Range: [0, 20] * * On high-end machines it is cheap (between a 0.8x and 0.9x slower for 16 * steps), but it can have a significant impact on older machines. * * Define SMAA_DISABLE_DIAG_DETECTION to disable diagonal processing. */ #ifndef SMAA_MAX_SEARCH_STEPS_DIAG #define SMAA_MAX_SEARCH_STEPS_DIAG 8 #endif /** * SMAA_CORNER_ROUNDING specifies how much sharp corners will be rounded. * * Range: [0, 100] * * Define SMAA_DISABLE_CORNER_DETECTION to disable corner processing. */ #ifndef SMAA_CORNER_ROUNDING #define SMAA_CORNER_ROUNDING 25 #endif /** * If there is an neighbor edge that has SMAA_LOCAL_CONTRAST_FACTOR times * bigger contrast than current edge, current edge will be discarded. * 現在のエッジよりも「SMAA_LOCAL_CONTRAST_FACTOR」倍大きいコントラストを持つ隣接エッジが存在する場合、 * 現在のエッジは破棄されます。 * * This allows to eliminate spurious crossing edges, and is based on the fact * that, if there is too much contrast in a direction, that will hide * perceptually contrast in the other neighbors. * これは、ある方向にコントラストが強すぎると、他の隣接方向のコントラストが知覚的に隠されてしまうという事実に基づいています。 */ #ifndef SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR #define SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR SMAAParams2.x #endif /** * Predicated thresholding allows to better preserve texture details and to * improve performance, by decreasing the number of detected edges using an * additional buffer like the light accumulation buffer, object ids or even the * depth buffer (the depth buffer usage may be limited to indoor or short range * scenes). * 'PredicationTex'から'SMAA_THRESHOLD'を決めるアレ。 * * It locally decreases the luma or color threshold if an edge is found in an * additional buffer (so the global threshold can be higher). * * This method was developed by Playstation EDGE MLAA team, and used in * Killzone 3, by using the light accumulation buffer. More information here: * http://iryoku.com/aacourse/downloads/06-MLAA-on-PS3.pptx */ #ifndef SMAA_PREDICATION #define SMAA_PREDICATION 0 #endif /** * Threshold to be used in the additional predication buffer. * 'PredicationTex'で使用される閾値。 * * Range: depends on the input, so you'll have to find the magic number that * works for you. * 'PredicationTex'に依存するので適当に決めてね。 */ #ifndef SMAA_PREDICATION_THRESHOLD #define SMAA_PREDICATION_THRESHOLD SMAAParams2.y #endif /** * How much to scale the global threshold used for luma or color edge * detection when using predication. * 'PredicationTex'使用時、輝度orカラーエッジ検出に使用される閾値に対するスケール。 * * Range: [1, 5] */ #ifndef SMAA_PREDICATION_SCALE #define SMAA_PREDICATION_SCALE SMAAParams2.z #endif /** * How much to locally decrease the threshold. * 閾値を局所的にどれだけ下げるか。 * * Range: [0, 1] */ #ifndef SMAA_PREDICATION_STRENGTH #define SMAA_PREDICATION_STRENGTH SMAAParams2.w #endif /** * Temporal reprojection allows to remove ghosting artifacts when using * temporal supersampling. We use the CryEngine 3 method which also introduces * velocity weighting. This feature is of extreme importance for totally * removing ghosting. More information here: * http://iryoku.com/aacourse/downloads/13-Anti-Aliasing-Methods-in-CryENGINE-3.pdf * * Note that you'll need to setup a velocity buffer for enabling reprojection. * For static geometry, saving the previous depth buffer is a viable * alternative. * UEでいうところのTSRを使用する際にこれを1に変更するとアーティファクトを軽減できるらしい。 */ #ifndef SMAA_REPROJECTION #define SMAA_REPROJECTION 0 #endif /** * SMAA_REPROJECTION_WEIGHT_SCALE controls the velocity weighting. It allows to * remove ghosting trails behind the moving object, which are not removed by * just using reprojection. Using low values will exhibit ghosting, while using * high values will disable temporal supersampling under motion. * * Behind the scenes, velocity weighting removes temporal supersampling when * the velocity of the subsamples differs (meaning they are different objects). * * Range: [0, 80] */ #ifndef SMAA_REPROJECTION_WEIGHT_SCALE #define SMAA_REPROJECTION_WEIGHT_SCALE 30.0 #endif /** * On some compilers, discard cannot be used in vertex shaders. Thus, they need * to be compiled separately. */ #ifndef SMAA_INCLUDE_VS //#define SMAA_INCLUDE_VS 1 #endif #ifndef SMAA_INCLUDE_PS //#define SMAA_INCLUDE_PS 1 #endif #ifndef SMAA_INCLUDE_CS //#define SMAA_INCLUDE_CS 1 #endif //----------------------------------------------------------------------------- // Texture Access Defines #ifndef SMAA_AREATEX_SELECT #if defined(SMAA_HLSL_3) #define SMAA_AREATEX_SELECT(sample) sample.ra #else #define SMAA_AREATEX_SELECT(sample) sample.rg #endif #endif #ifndef SMAA_SEARCHTEX_SELECT #define SMAA_SEARCHTEX_SELECT(sample) sample.r #endif #ifndef SMAA_DECODE_VELOCITY #define SMAA_DECODE_VELOCITY(sample) sample.rg #endif //----------------------------------------------------------------------------- // Non-Configurable Defines #define SMAA_AREATEX_MAX_DISTANCE 16 #define SMAA_AREATEX_MAX_DISTANCE_DIAG 20 #define SMAA_AREATEX_PIXEL_SIZE (1.0 / float2(160.0, 560.0)) #define SMAA_AREATEX_SUBTEX_SIZE (1.0 / 7.0) #define SMAA_SEARCHTEX_SIZE float2(66.0, 33.0) #define SMAA_SEARCHTEX_PACKED_SIZE float2(64.0, 16.0) #define SMAA_CORNER_ROUNDING_NORM (float(SMAA_CORNER_ROUNDING) / 100.0) //----------------------------------------------------------------------------- // Porting Functions #if defined(SMAA_HLSL_3) #define SMAATexture2D(tex) sampler2D tex #define SMAATexturePass2D(tex) tex #define SMAASampleLevelZero(tex, coord) tex2Dlod(tex, float4(coord, 0.0, 0.0)) #define SMAASampleLevelZeroPoint(tex, coord) tex2Dlod(tex, float4(coord, 0.0, 0.0)) #define SMAASampleLevelZeroOffset(tex, coord, offset) tex2Dlod(tex, float4(coord + offset * SMAA_RT_METRICS.xy, 0.0, 0.0)) #define SMAASample(tex, coord) tex2D(tex, coord) #define SMAASamplePoint(tex, coord) tex2D(tex, coord) #define SMAASampleOffset(tex, coord, offset) tex2D(tex, coord + offset * SMAA_RT_METRICS.xy) #define SMAA_FLATTEN [flatten] #define SMAA_BRANCH [branch] #endif // SMAA_HLSL_3 #if defined(SMAA_HLSL_4) || defined(SMAA_HLSL_4_1) #define SMAATexture2D(tex) Texture2D tex #define SMAATexturePass2D(tex) tex #define SMAASampleLevelZero(tex, coord) tex.SampleLevel(LinearSampler, coord, 0) #define SMAASampleLevelZeroPoint(tex, coord) tex.SampleLevel(PointSampler, coord, 0) #define SMAASampleLevelZeroOffset(tex, coord, offset) tex.SampleLevel(LinearSampler, coord, 0, offset) #if !defined(SMAA_INCLUDE_CS) #define SMAASample(tex, coord) tex.Sample(LinearSampler, coord) #define SMAASamplePoint(tex, coord) tex.Sample(PointSampler, coord) #else // !SMAA_INCLUDE_CS // NOTE: cannot map expression to cs_5_0 instruction set #define SMAASample(tex, coord) Texture2DSample(tex, LinearSampler, coord) #define SMAASamplePoint(tex, coord) Texture2DSample(tex, PointSampler, coord) #endif // SMAA_INCLUDE_CS #define SMAASampleOffset(tex, coord, offset) tex.Sample(LinearSampler, coord, offset) // NOTE: see 'Platform.ush' #define SMAA_FLATTEN FLATTEN #define SMAA_BRANCH BRANCH #define SMAATexture2DMS2(tex) Texture2DMS<float4, 2> tex #define SMAALoad(tex, pos, sample) tex.Load(pos, sample) #if defined(SMAA_HLSL_4_1) // NOTE: // UE5.0あたりからGatherが使える前提で組まれている // see 'ShadowFilteringCommon.ush' #define SMAAGather(tex, coord) tex.Gather(LinearSampler, coord, 0) #endif #endif // SMAA_HLSL_4 || SMAA_HLSL_4_1 #if defined(SMAA_GLSL_3) || defined(SMAA_GLSL_4) #define SMAATexture2D(tex) sampler2D tex #define SMAATexturePass2D(tex) tex #define SMAASampleLevelZero(tex, coord) textureLod(tex, coord, 0.0) #define SMAASampleLevelZeroPoint(tex, coord) textureLod(tex, coord, 0.0) #define SMAASampleLevelZeroOffset(tex, coord, offset) textureLodOffset(tex, coord, 0.0, offset) #define SMAASample(tex, coord) texture(tex, coord) #define SMAASamplePoint(tex, coord) texture(tex, coord) #define SMAASampleOffset(tex, coord, offset) texture(tex, coord, offset) #define SMAA_FLATTEN #define SMAA_BRANCH #define lerp(a, b, t) mix(a, b, t) #define saturate(a) clamp(a, 0.0, 1.0) #if defined(SMAA_GLSL_4) #define mad(a, b, c) fma(a, b, c) #define SMAAGather(tex, coord) textureGather(tex, coord) #else #define mad(a, b, c) (a * b + c) #endif #define float2 vec2 #define float3 vec3 #define float4 vec4 #define int2 ivec2 #define int3 ivec3 #define int4 ivec4 #define bool2 bvec2 #define bool3 bvec3 #define bool4 bvec4 #endif // SMAA_GLSL_3 || SMAA_GLSL_4 #if !defined(SMAA_HLSL_3) && !defined(SMAA_HLSL_4) && !defined(SMAA_HLSL_4_1) && !defined(SMAA_GLSL_3) && !defined(SMAA_GLSL_4) && !defined(SMAA_CUSTOM_SL) #error you must define the shading language: SMAA_HLSL_*, SMAA_GLSL_* or SMAA_CUSTOM_SL #endif //----------------------------------------------------------------------------- // Misc functions /** * Gathers current pixel, and the top-left neighbors. */ float3 SMAAGatherNeighbours(float2 texcoord, float4 offset[3], SMAATexture2D(tex)) { #ifdef SMAAGather return SMAAGather(tex, texcoord + SMAA_RT_METRICS.xy * float2(-0.5, -0.5)).grb; #else float P = SMAASamplePoint(tex, texcoord).r; float Pleft = SMAASamplePoint(tex, offset[0].xy).r; float Ptop = SMAASamplePoint(tex, offset[0].zw).r; return float3(P, Pleft, Ptop); #endif } /** * Adjusts the threshold by means of predication. * 'PredicationTex'を利用して閾値を調整します。 */ float2 SMAACalculatePredicatedThreshold(float2 texcoord, float4 offset[3], SMAATexture2D(predicationTex)) { float3 neighbours = SMAAGatherNeighbours(texcoord, offset, SMAATexturePass2D(predicationTex)); float2 delta = abs(neighbours.xx - neighbours.yz); float2 edges = step(SMAA_PREDICATION_THRESHOLD, delta); return SMAA_PREDICATION_SCALE * SMAA_THRESHOLD * (1.0 - SMAA_PREDICATION_STRENGTH * edges); } /** * Conditional move: */ void SMAAMovc(bool2 cond, inout float2 variable, float2 value) { SMAA_FLATTEN if (cond.x) variable.x = value.x; SMAA_FLATTEN if (cond.y) variable.y = value.y; } void SMAAMovc(bool4 cond, inout float4 variable, float4 value) { SMAAMovc(cond.xy, variable.xy, value.xy); SMAAMovc(cond.zw, variable.zw, value.zw); } #if defined(SMAA_INCLUDE_VS) || defined(SMAA_INCLUDE_CS) //----------------------------------------------------------------------------- // Vertex Shaders /** * Edge Detection Vertex Shader */ void SMAAEdgeDetectionVS(float2 texcoord, out float4 offset[3]) { offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-1.0, 0.0, 0.0, -1.0), texcoord.xyxy); offset[1] = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); offset[2] = mad(SMAA_RT_METRICS.xyxy, float4(-2.0, 0.0, 0.0, -2.0), texcoord.xyxy); } /** * Blend Weight Calculation Vertex Shader */ void SMAABlendingWeightCalculationVS(float2 texcoord, out float2 pixcoord, out float4 offset[3]) { pixcoord = texcoord * SMAA_RT_METRICS.zw; // We will use these offsets for the searches later on (see @PSEUDO_GATHER4): offset[0] = mad(SMAA_RT_METRICS.xyxy, float4(-0.25, -0.125, 1.25, -0.125), texcoord.xyxy); offset[1] = mad(SMAA_RT_METRICS.xyxy, float4(-0.125, -0.25, -0.125, 1.25), texcoord.xyxy); // And these for the searches, they indicate the ends of the loops: offset[2] = mad(SMAA_RT_METRICS.xxyy, float4(-2.0, 2.0, -2.0, 2.0) * float(SMAA_MAX_SEARCH_STEPS), float4(offset[0].xz, offset[1].yw)); } /** * Neighborhood Blending Vertex Shader */ void SMAANeighborhoodBlendingVS(float2 texcoord, out float4 offset) { offset = mad(SMAA_RT_METRICS.xyxy, float4( 1.0, 0.0, 0.0, 1.0), texcoord.xyxy); } #endif // SMAA_INCLUDE_VS || SMAA_INCLUDE_CS #if defined(SMAA_INCLUDE_PS) || defined(SMAA_INCLUDE_CS) //----------------------------------------------------------------------------- // Edge Detection Pixel Shaders (First Pass) /** * Luma Edge Detection * * IMPORTANT NOTICE: luma edge detection requires gamma-corrected colors, and * thus 'colorTex' should be a non-sRGB texture. */ float2 SMAALumaEdgeDetectionPS(float2 texcoord, float4 offset[3], SMAATexture2D(colorTex) #if SMAA_PREDICATION , SMAATexture2D(predicationTex) #endif ) { // Calculate the threshold: #if SMAA_PREDICATION float2 threshold = SMAACalculatePredicatedThreshold(texcoord, offset, SMAATexturePass2D(predicationTex)); #else float2 threshold = float2(SMAA_THRESHOLD, SMAA_THRESHOLD); #endif // Calculate lumas: #if 0 float3 weights = float3(0.2126, 0.7152, 0.0722); #else // NOTE: FXAAで使用されているLuma係数 float3 weights = float3(0.299f, 0.587f, 0.114f); #endif float L = dot(SMAASamplePoint(colorTex, texcoord).rgb, weights); float Lleft = dot(SMAASamplePoint(colorTex, offset[0].xy).rgb, weights); float Ltop = dot(SMAASamplePoint(colorTex, offset[0].zw).rgb, weights); // We do the usual threshold: float4 delta; delta.xy = abs(L - float2(Lleft, Ltop)); float2 edges = step(threshold, delta.xy); // Then discard if there is no edge: // エッジがない場合は破棄します。 if (dot(edges, float2(1.0, 1.0)) == 0.0) { #ifndef SMAA_INCLUDE_CS discard; #else return float2(0.0, 0.0); #endif } // Calculate right and bottom deltas: float Lright = dot(SMAASamplePoint(colorTex, offset[1].xy).rgb, weights); float Lbottom = dot(SMAASamplePoint(colorTex, offset[1].zw).rgb, weights); delta.zw = abs(L - float2(Lright, Lbottom)); // Calculate the maximum delta in the direct neighborhood: float2 maxDelta = max(delta.xy, delta.zw); // Calculate left-left and top-top deltas: float Lleftleft = dot(SMAASamplePoint(colorTex, offset[2].xy).rgb, weights); float Ltoptop = dot(SMAASamplePoint(colorTex, offset[2].zw).rgb, weights); delta.zw = abs(float2(Lleft, Ltop) - float2(Lleftleft, Ltoptop)); // Calculate the final maximum delta: maxDelta = max(maxDelta.xy, delta.zw); float finalDelta = max(maxDelta.x, maxDelta.y); // Local contrast adaptation: edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); return edges; } /** * Color Edge Detection * * IMPORTANT NOTICE: color edge detection requires gamma-corrected colors, and * thus 'colorTex' should be a non-sRGB texture. */ float2 SMAAColorEdgeDetectionPS(float2 texcoord, float4 offset[3], SMAATexture2D(colorTex) #if SMAA_PREDICATION , SMAATexture2D(predicationTex) #endif ) { // Calculate the threshold: #if SMAA_PREDICATION float2 threshold = SMAACalculatePredicatedThreshold(texcoord, offset, predicationTex); #else float2 threshold = float2(SMAA_THRESHOLD, SMAA_THRESHOLD); #endif // Calculate color deltas: float4 delta; float3 C = SMAASamplePoint(colorTex, texcoord).rgb; float3 Cleft = SMAASamplePoint(colorTex, offset[0].xy).rgb; float3 t = abs(C - Cleft); delta.x = max(max(t.r, t.g), t.b); float3 Ctop = SMAASamplePoint(colorTex, offset[0].zw).rgb; t = abs(C - Ctop); delta.y = max(max(t.r, t.g), t.b); // We do the usual threshold: float2 edges = step(threshold, delta.xy); // Then discard if there is no edge: if (dot(edges, float2(1.0, 1.0)) == 0.0) { #ifndef SMAA_INCLUDE_CS discard; #else return float2(0.0, 0.0); #endif } // Calculate right and bottom deltas: float3 Cright = SMAASamplePoint(colorTex, offset[1].xy).rgb; t = abs(C - Cright); delta.z = max(max(t.r, t.g), t.b); float3 Cbottom = SMAASamplePoint(colorTex, offset[1].zw).rgb; t = abs(C - Cbottom); delta.w = max(max(t.r, t.g), t.b); // Calculate the maximum delta in the direct neighborhood: float2 maxDelta = max(delta.xy, delta.zw); // Calculate left-left and top-top deltas: float3 Cleftleft = SMAASamplePoint(colorTex, offset[2].xy).rgb; t = abs(C - Cleftleft); delta.z = max(max(t.r, t.g), t.b); float3 Ctoptop = SMAASamplePoint(colorTex, offset[2].zw).rgb; t = abs(C - Ctoptop); delta.w = max(max(t.r, t.g), t.b); // Calculate the final maximum delta: maxDelta = max(maxDelta.xy, delta.zw); float finalDelta = max(maxDelta.x, maxDelta.y); // Local contrast adaptation: edges.xy *= step(finalDelta, SMAA_LOCAL_CONTRAST_ADAPTATION_FACTOR * delta.xy); return edges; } /** * Depth Edge Detection */ float2 SMAADepthEdgeDetectionPS(float2 texcoord, float4 offset[3], SMAATexture2D(depthTex)) { float3 neighbours = SMAAGatherNeighbours(texcoord, offset, SMAATexturePass2D(depthTex)); float2 delta = abs(neighbours.xx - float2(neighbours.y, neighbours.z)); float2 edges = step(SMAA_DEPTH_THRESHOLD, delta); if (dot(edges, float2(1.0, 1.0)) == 0.0) { #ifndef SMAA_INCLUDE_CS discard; #else return float2(0.0, 0.0); #endif } return edges; } //----------------------------------------------------------------------------- // Diagonal Search Functions #if !defined(SMAA_DISABLE_DIAG_DETECTION) /** * Allows to decode two binary values from a bilinear-filtered access. */ float2 SMAADecodeDiagBilinearAccess(float2 e) { // Bilinear access for fetching 'e' have a 0.25 offset, and we are // interested in the R and G edges: // // +---G---+-------+ // | x o R x | // +-------+-------+ // // Then, if one of these edge is enabled: // Red: (0.75 * X + 0.25 * 1) => 0.25 or 1.0 // Green: (0.75 * 1 + 0.25 * X) => 0.75 or 1.0 // // This function will unpack the values (mad + mul + round): // wolframalpha.com: round(x * abs(5 * x - 5 * 0.75)) plot 0 to 1 e.r = e.r * abs(5.0 * e.r - 5.0 * 0.75); return round(e); } float4 SMAADecodeDiagBilinearAccess(float4 e) { e.rb = e.rb * abs(5.0 * e.rb - 5.0 * 0.75); return round(e); } /** * These functions allows to perform diagonal pattern searches. */ float2 SMAASearchDiag1(SMAATexture2D(edgesTex), float2 texcoord, float2 dir, out float2 e) { float4 coord = float4(texcoord, -1.0, 1.0); float3 t = float3(SMAA_RT_METRICS.xy, 1.0); while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && coord.w > 0.9) { coord.xyz = mad(t, float3(dir, 1.0), coord.xyz); e = SMAASampleLevelZero(edgesTex, coord.xy).rg; coord.w = dot(e, float2(0.5, 0.5)); } return coord.zw; } float2 SMAASearchDiag2(SMAATexture2D(edgesTex), float2 texcoord, float2 dir, out float2 e) { float4 coord = float4(texcoord, -1.0, 1.0); coord.x += 0.25 * SMAA_RT_METRICS.x; // See @SearchDiag2Optimization float3 t = float3(SMAA_RT_METRICS.xy, 1.0); while (coord.z < float(SMAA_MAX_SEARCH_STEPS_DIAG - 1) && coord.w > 0.9) { coord.xyz = mad(t, float3(dir, 1.0), coord.xyz); // @SearchDiag2Optimization // Fetch both edges at once using bilinear filtering: e = SMAASampleLevelZero(edgesTex, coord.xy).rg; e = SMAADecodeDiagBilinearAccess(e); // Non-optimized version: // e.g = SMAASampleLevelZero(edgesTex, coord.xy).g; // e.r = SMAASampleLevelZeroOffset(edgesTex, coord.xy, int2(1, 0)).r; coord.w = dot(e, float2(0.5, 0.5)); } return coord.zw; } /** * Similar to SMAAArea, this calculates the area corresponding to a certain * diagonal distance and crossing edges 'e'. */ float2 SMAAAreaDiag(SMAATexture2D(areaTex), float2 dist, float2 e, float offset) { float2 texcoord = mad(float2(SMAA_AREATEX_MAX_DISTANCE_DIAG, SMAA_AREATEX_MAX_DISTANCE_DIAG), e, dist); // We do a scale and bias for mapping to texel space: texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); // Diagonal areas are on the second half of the texture: texcoord.x += 0.5; // Move to proper place, according to the subpixel offset: texcoord.y += SMAA_AREATEX_SUBTEX_SIZE * offset; // Do it! return SMAA_AREATEX_SELECT(SMAASampleLevelZero(areaTex, texcoord)); } /** * This searches for diagonal patterns and returns the corresponding weights. */ float2 SMAACalculateDiagWeights(SMAATexture2D(edgesTex), SMAATexture2D(areaTex), float2 texcoord, float2 e, float4 subsampleIndices) { float2 weights = float2(0.0, 0.0); // Search for the line ends: float4 d; float2 end; if (e.r > 0.0) { d.xz = SMAASearchDiag1(SMAATexturePass2D(edgesTex), texcoord, float2(-1.0, 1.0), end); d.x += float(end.y > 0.9); } else d.xz = float2(0.0, 0.0); d.yw = SMAASearchDiag1(SMAATexturePass2D(edgesTex), texcoord, float2(1.0, -1.0), end); SMAA_BRANCH if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 // Fetch the crossing edges: float4 coords = mad(float4(-d.x + 0.25, d.x, d.y, -d.y - 0.25), SMAA_RT_METRICS.xyxy, texcoord.xyxy); float4 c; c.xy = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).rg; c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).rg; c.yxwz = SMAADecodeDiagBilinearAccess(c.xyzw); // Non-optimized version: // float4 coords = mad(float4(-d.x, d.x, d.y, -d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); // float4 c; // c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).g; // c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2( 0, 0)).r; // c.z = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).g; // c.w = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, -1)).r; // Merge crossing edges at each side into a single value: float2 cc = mad(float2(2.0, 2.0), c.xz, c.yw); // Remove the crossing edge if we didn't found the end of the line: SMAAMovc(bool2(step(float2(0.9, 0.9), d.zw)), cc, float2(0.0, 0.0)); // Fetch the areas for this line: weights += SMAAAreaDiag(SMAATexturePass2D(areaTex), d.xy, cc, subsampleIndices.z); } // Search for the line ends: d.xz = SMAASearchDiag2(SMAATexturePass2D(edgesTex), texcoord, float2(-1.0, -1.0), end); if (SMAASampleLevelZeroOffset(edgesTex, texcoord, int2(1, 0)).r > 0.0) { d.yw = SMAASearchDiag2(SMAATexturePass2D(edgesTex), texcoord, float2(1.0, 1.0), end); d.y += float(end.y > 0.9); } else d.yw = float2(0.0, 0.0); SMAA_BRANCH if (d.x + d.y > 2.0) { // d.x + d.y + 1 > 3 // Fetch the crossing edges: float4 coords = mad(float4(-d.x, -d.x, d.y, d.y), SMAA_RT_METRICS.xyxy, texcoord.xyxy); float4 c; c.x = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2(-1, 0)).g; c.y = SMAASampleLevelZeroOffset(edgesTex, coords.xy, int2( 0, -1)).r; c.zw = SMAASampleLevelZeroOffset(edgesTex, coords.zw, int2( 1, 0)).gr; float2 cc = mad(float2(2.0, 2.0), c.xz, c.yw); // Remove the crossing edge if we didn't found the end of the line: SMAAMovc(bool2(step(float2(0.9, 0.9), d.zw)), cc, float2(0.0, 0.0)); // Fetch the areas for this line: weights += SMAAAreaDiag(SMAATexturePass2D(areaTex), d.xy, cc, subsampleIndices.w).gr; } return weights; } #endif // !defined(SMAA_DISABLE_DIAG_DETECTION) //----------------------------------------------------------------------------- // Horizontal/Vertical Search Functions /** * This allows to determine how much length should we add in the last step * of the searches. It takes the bilinearly interpolated edge (see * @PSEUDO_GATHER4), and adds 0, 1 or 2, depending on which edges and * crossing edges are active. */ float SMAASearchLength(SMAATexture2D(searchTex), float2 e, float offset) { // The texture is flipped vertically, with left and right cases taking half // of the space horizontally: float2 scale = SMAA_SEARCHTEX_SIZE * float2(0.5, -1.0); float2 bias = SMAA_SEARCHTEX_SIZE * float2(offset, 1.0); // Scale and bias to access texel centers: scale += float2(-1.0, 1.0); bias += float2( 0.5, -0.5); // Convert from pixel coordinates to texcoords: // (We use SMAA_SEARCHTEX_PACKED_SIZE because the texture is cropped) scale *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; bias *= 1.0 / SMAA_SEARCHTEX_PACKED_SIZE; // Lookup the search texture: return SMAA_SEARCHTEX_SELECT(SMAASampleLevelZero(searchTex, mad(scale, e, bias))); } /** * Horizontal/vertical search functions for the 2nd pass. */ float SMAASearchXLeft(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { /** * @PSEUDO_GATHER4 * This texcoord has been offset by (-0.25, -0.125) in the vertex shader to * sample between edge, thus fetching four edges in a row. * Sampling with different offsets in each direction allows to disambiguate * which edges are active from the four fetched ones. */ float2 e = float2(0.0, 1.0); while (texcoord.x > end && e.g > 0.8281 && // Is there some edge not activated? e.r == 0.0) { // Or is there a crossing edge that breaks the line? e = SMAASampleLevelZero(edgesTex, texcoord).rg; texcoord = mad(-float2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); } float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0), 3.25); return mad(SMAA_RT_METRICS.x, offset, texcoord.x); // Non-optimized version: // We correct the previous (-0.25, -0.125) offset we applied: // texcoord.x += 0.25 * SMAA_RT_METRICS.x; // The searches are bias by 1, so adjust the coords accordingly: // texcoord.x += SMAA_RT_METRICS.x; // Disambiguate the length added by the last step: // texcoord.x += 2.0 * SMAA_RT_METRICS.x; // Undo last step // texcoord.x -= SMAA_RT_METRICS.x * (255.0 / 127.0) * SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.0); // return mad(SMAA_RT_METRICS.x, offset, texcoord.x); } float SMAASearchXRight(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { float2 e = float2(0.0, 1.0); while (texcoord.x < end && e.g > 0.8281 && // Is there some edge not activated? e.r == 0.0) { // Or is there a crossing edge that breaks the line? e = SMAASampleLevelZero(edgesTex, texcoord).rg; texcoord = mad(float2(2.0, 0.0), SMAA_RT_METRICS.xy, texcoord); } float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e, 0.5), 3.25); return mad(-SMAA_RT_METRICS.x, offset, texcoord.x); } float SMAASearchYUp(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { float2 e = float2(1.0, 0.0); while (texcoord.y > end && e.r > 0.8281 && // Is there some edge not activated? e.g == 0.0) { // Or is there a crossing edge that breaks the line? e = SMAASampleLevelZero(edgesTex, texcoord).rg; texcoord = mad(-float2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); } float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e.gr, 0.0), 3.25); return mad(SMAA_RT_METRICS.y, offset, texcoord.y); } float SMAASearchYDown(SMAATexture2D(edgesTex), SMAATexture2D(searchTex), float2 texcoord, float end) { float2 e = float2(1.0, 0.0); while (texcoord.y < end && e.r > 0.8281 && // Is there some edge not activated? e.g == 0.0) { // Or is there a crossing edge that breaks the line? e = SMAASampleLevelZero(edgesTex, texcoord).rg; texcoord = mad(float2(0.0, 2.0), SMAA_RT_METRICS.xy, texcoord); } float offset = mad(-(255.0 / 127.0), SMAASearchLength(SMAATexturePass2D(searchTex), e.gr, 0.5), 3.25); return mad(-SMAA_RT_METRICS.y, offset, texcoord.y); } /** * Ok, we have the distance and both crossing edges. So, what are the areas * at each side of current edge? */ float2 SMAAArea(SMAATexture2D(areaTex), float2 dist, float e1, float e2, float offset) { // Rounding prevents precision errors of bilinear filtering: float2 texcoord = mad(float2(SMAA_AREATEX_MAX_DISTANCE, SMAA_AREATEX_MAX_DISTANCE), round(4.0 * float2(e1, e2)), dist); // We do a scale and bias for mapping to texel space: texcoord = mad(SMAA_AREATEX_PIXEL_SIZE, texcoord, 0.5 * SMAA_AREATEX_PIXEL_SIZE); // Move to proper place, according to the subpixel offset: texcoord.y = mad(SMAA_AREATEX_SUBTEX_SIZE, offset, texcoord.y); // Do it! return SMAA_AREATEX_SELECT(SMAASampleLevelZero(areaTex, texcoord)); } //----------------------------------------------------------------------------- // Corner Detection Functions void SMAADetectHorizontalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { #if !defined(SMAA_DISABLE_CORNER_DETECTION) float2 leftRight = step(d.xy, d.yx); float2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; rounding /= leftRight.x + leftRight.y; // Reduce blending for pixels in the center of a line. float2 factor = float2(1.0, 1.0); factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, 1)).r; factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, 1)).r; factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(0, -2)).r; factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(1, -2)).r; weights *= saturate(factor); #endif } void SMAADetectVerticalCornerPattern(SMAATexture2D(edgesTex), inout float2 weights, float4 texcoord, float2 d) { #if !defined(SMAA_DISABLE_CORNER_DETECTION) float2 leftRight = step(d.xy, d.yx); float2 rounding = (1.0 - SMAA_CORNER_ROUNDING_NORM) * leftRight; rounding /= leftRight.x + leftRight.y; float2 factor = float2(1.0, 1.0); factor.x -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2( 1, 0)).g; factor.x -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2( 1, 1)).g; factor.y -= rounding.x * SMAASampleLevelZeroOffset(edgesTex, texcoord.xy, int2(-2, 0)).g; factor.y -= rounding.y * SMAASampleLevelZeroOffset(edgesTex, texcoord.zw, int2(-2, 1)).g; weights *= saturate(factor); #endif } //----------------------------------------------------------------------------- // Blending Weight Calculation Pixel Shader (Second Pass) float4 SMAABlendingWeightCalculationPS(float2 texcoord, float2 pixcoord, float4 offset[3], SMAATexture2D(edgesTex), SMAATexture2D(areaTex), SMAATexture2D(searchTex), float4 subsampleIndices) { // Just pass zero for SMAA 1x, see @SUBSAMPLE_INDICES. float4 weights = float4(0.0, 0.0, 0.0, 0.0); float2 e = SMAASample(edgesTex, texcoord).rg; SMAA_BRANCH if (e.g > 0.0) { // Edge at north #if !defined(SMAA_DISABLE_DIAG_DETECTION) // Diagonals have both north and west edges, so searching for them in // one of the boundaries is enough. weights.rg = SMAACalculateDiagWeights(SMAATexturePass2D(edgesTex), SMAATexturePass2D(areaTex), texcoord, e, subsampleIndices); // We give priority to diagonals, so if we find a diagonal we skip // horizontal/vertical processing. SMAA_BRANCH if (weights.r == -weights.g) { // weights.r + weights.g == 0.0 #endif float2 d; // Find the distance to the left: float3 coords; coords.x = SMAASearchXLeft(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[0].xy, offset[2].x); coords.y = offset[1].y; // offset[1].y = texcoord.y - 0.25 * SMAA_RT_METRICS.y (@CROSSING_OFFSET) d.x = coords.x; // Now fetch the left crossing edges, two at a time using bilinear // filtering. Sampling at -0.25 (see @CROSSING_OFFSET) enables to // discern what value each edge has: float e1 = SMAASampleLevelZero(edgesTex, coords.xy).r; // Find the distance to the right: coords.z = SMAASearchXRight(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[0].zw, offset[2].y); d.y = coords.z; // We want the distances to be in pixel units (doing this here allow to // better interleave arithmetic and memory accesses): d = abs(round(mad(SMAA_RT_METRICS.zz, d, -pixcoord.xx))); // SMAAArea below needs a sqrt, as the areas texture is compressed // quadratically: float2 sqrt_d = sqrt(d); // Fetch the right crossing edges: float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.zy, int2(1, 0)).r; // Ok, we know how this pattern looks like, now it is time for getting // the actual area: weights.rg = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.y); // Fix corners: coords.y = texcoord.y; SMAADetectHorizontalCornerPattern(SMAATexturePass2D(edgesTex), weights.rg, coords.xyzy, d); #if !defined(SMAA_DISABLE_DIAG_DETECTION) } else e.r = 0.0; // Skip vertical processing. #endif } SMAA_BRANCH if (e.r > 0.0) { // Edge at west float2 d; // Find the distance to the top: float3 coords; coords.y = SMAASearchYUp(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[1].xy, offset[2].z); coords.x = offset[0].x; // offset[1].x = texcoord.x - 0.25 * SMAA_RT_METRICS.x; d.x = coords.y; // Fetch the top crossing edges: float e1 = SMAASampleLevelZero(edgesTex, coords.xy).g; // Find the distance to the bottom: coords.z = SMAASearchYDown(SMAATexturePass2D(edgesTex), SMAATexturePass2D(searchTex), offset[1].zw, offset[2].w); d.y = coords.z; // We want the distances to be in pixel units: d = abs(round(mad(SMAA_RT_METRICS.ww, d, -pixcoord.yy))); // SMAAArea below needs a sqrt, as the areas texture is compressed // quadratically: float2 sqrt_d = sqrt(d); // Fetch the bottom crossing edges: float e2 = SMAASampleLevelZeroOffset(edgesTex, coords.xz, int2(0, 1)).g; // Get the area for this direction: weights.ba = SMAAArea(SMAATexturePass2D(areaTex), sqrt_d, e1, e2, subsampleIndices.x); // Fix corners: coords.x = texcoord.x; SMAADetectVerticalCornerPattern(SMAATexturePass2D(edgesTex), weights.ba, coords.xyxz, d); } return weights; } //----------------------------------------------------------------------------- // Neighborhood Blending Pixel Shader (Third Pass) float4 SMAANeighborhoodBlendingPS(float2 texcoord, float4 offset, SMAATexture2D(colorTex), SMAATexture2D(blendTex) #if SMAA_REPROJECTION , SMAATexture2D(velocityTex) #endif ) { // Fetch the blending weights for current pixel: float4 a; a.x = SMAASample(blendTex, offset.xy).a; // Right a.y = SMAASample(blendTex, offset.zw).g; // Top a.wz = SMAASample(blendTex, texcoord).xz; // Bottom / Left // Is there any blending weight with a value greater than 0.0? SMAA_BRANCH if (dot(a, float4(1.0, 1.0, 1.0, 1.0)) < 1e-5) { float4 color = SMAASampleLevelZero(colorTex, texcoord); #if SMAA_REPROJECTION float2 velocity = SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, texcoord)); // Pack velocity into the alpha channel: color.a = sqrt(5.0 * length(velocity)); #endif return color; } else { bool h = max(a.x, a.z) > max(a.y, a.w); // max(horizontal) > max(vertical) // Calculate the blending offsets: float4 blendingOffset = float4(0.0, a.y, 0.0, a.w); float2 blendingWeight = a.yw; SMAAMovc(bool4(h, h, h, h), blendingOffset, float4(a.x, 0.0, a.z, 0.0)); SMAAMovc(bool2(h, h), blendingWeight, a.xz); blendingWeight /= dot(blendingWeight, float2(1.0, 1.0)); // Calculate the texture coordinates: float4 blendingCoord = mad(blendingOffset, float4(SMAA_RT_METRICS.xy, -SMAA_RT_METRICS.xy), texcoord.xyxy); // We exploit bilinear filtering to mix current pixel with the chosen // neighbor: float4 color = blendingWeight.x * SMAASampleLevelZero(colorTex, blendingCoord.xy); color += blendingWeight.y * SMAASampleLevelZero(colorTex, blendingCoord.zw); #if SMAA_REPROJECTION // Antialias velocity for proper reprojection in a later stage: float2 velocity = blendingWeight.x * SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, blendingCoord.xy)); velocity += blendingWeight.y * SMAA_DECODE_VELOCITY(SMAASampleLevelZero(velocityTex, blendingCoord.zw)); // Pack velocity into the alpha channel: color.a = sqrt(5.0 * length(velocity)); #endif return color; } } //----------------------------------------------------------------------------- // Temporal Resolve Pixel Shader (Optional Pass) float4 SMAAResolvePS(float2 texcoord, SMAATexture2D(currentColorTex), SMAATexture2D(previousColorTex) #if SMAA_REPROJECTION , SMAATexture2D(velocityTex) #endif ) { #if SMAA_REPROJECTION // Velocity is assumed to be calculated for motion blur, so we need to // inverse it for reprojection: float2 velocity = -SMAA_DECODE_VELOCITY(SMAASamplePoint(velocityTex, texcoord).rg); // Fetch current pixel: float4 current = SMAASamplePoint(currentColorTex, texcoord); // Reproject current coordinates and fetch previous pixel: float4 previous = SMAASamplePoint(previousColorTex, texcoord + velocity); // Attenuate the previous pixel if the velocity is different: float delta = abs(current.a * current.a - previous.a * previous.a) / 5.0; float weight = 0.5 * saturate(1.0 - sqrt(delta) * SMAA_REPROJECTION_WEIGHT_SCALE); // Blend the pixels according to the calculated weight: return lerp(current, previous, weight); #else // Just blend the pixels: float4 current = SMAASamplePoint(currentColorTex, texcoord); float4 previous = SMAASamplePoint(previousColorTex, texcoord); return lerp(current, previous, 0.5); #endif } //----------------------------------------------------------------------------- // Separate Multisamples Pixel Shader (Optional Pass) #ifdef SMAALoad void SMAASeparatePS(float4 position, float2 texcoord, out float4 target0, out float4 target1, SMAATexture2DMS2(colorTexMS)) { int2 pos = int2(position.xy); target0 = SMAALoad(colorTexMS, pos, 0); target1 = SMAALoad(colorTexMS, pos, 0); } #endif //----------------------------------------------------------------------------- #endif // SMAA_INCLUDE_PS || SMAA_INCLUDE_CS
Engine\Shaders\Private\SMAA\SMAAEdgeDetection.usf
1パス。
/** * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) * * Permission is hereby granted, free of charge, to any person obtaining a copy * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to * do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. As clarification, there * is no requirement that the copyright notice and permission be included in * binary distributions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "SMAACommon.ush" //----------------------------------------------------------------------------- // Vertex Shaders #ifdef SMAA_INCLUDE_VS /** * Edge Detection Vertex Shader */ void SMAAEdgeDetectionVertexShader( in float4 InPosition : ATTRIBUTE0, in float2 InTexCoord : ATTRIBUTE1, out noperspective float2 OutTexCoord : TEXCOORD0, out noperspective float4 OutOffsets[3] : TEXCOORD1, out float4 OutPosition : SV_POSITION) { DrawRectangle(InPosition, InTexCoord, OutPosition, OutTexCoord); SMAAEdgeDetectionVS(OutTexCoord, OutOffsets); } #endif // SMAA_INCLUDE_VS //----------------------------------------------------------------------------- // Pixel Shaders #ifdef SMAA_INCLUDE_PS /** * Input textures */ SMAATexture2D(ColorTex); SMAATexture2D(DepthTex); SMAATexture2D(PredicationTex); /** * Luma Edge Detection * * IMPORTANT NOTICE: luma edge detection requires gamma-corrected colors, and * thus 'colorTex' should be a non-sRGB texture. */ void SMAALumaEdgeDetectionPixelShader( in noperspective float2 TexCoord : TEXCOORD0, in noperspective float4 Offsets[3] : TEXCOORD1, out float4 OutColor : SV_Target0) { OutColor = float4(SMAALumaEdgeDetectionPS( TexCoord , Offsets , ColorTex #if SMAA_PREDICATION , PredicationTex #endif ), 0.0, 0.0); } /** * Color Edge Detection * * IMPORTANT NOTICE: color edge detection requires gamma-corrected colors, and * thus 'colorTex' should be a non-sRGB texture. */ void SMAAColorEdgeDetectionPixelShader( in noperspective float2 TexCoord : TEXCOORD0, in noperspective float4 Offsets[3] : TEXCOORD1, out float4 OutColor : SV_Target0) { OutColor = float4(SMAAColorEdgeDetectionPS( TexCoord , Offsets , ColorTex #if SMAA_PREDICATION , PredicationTex #endif ), 0.0, 0.0); } /** * Depth Edge Detection */ void SMAADepthEdgeDetectionPixelShader( in noperspective float2 TexCoord : TEXCOORD0, in noperspective float4 Offsets[3] : TEXCOORD1, out float4 OutColor : SV_Target0) { OutColor = float4(SMAADepthEdgeDetectionPS(TexCoord, Offsets, DepthTex), 0.0, 0.0); } #endif // SMAA_INCLUDE_PS //----------------------------------------------------------------------------- // Compute Shaders #ifdef SMAA_INCLUDE_CS /** * Input textures */ SMAATexture2D(ColorTex); SMAATexture2D(DepthTex); SMAATexture2D(PredicationTex); /** * Output textures */ RWTexture2D<float4> EdgesTexture; /** * Luma Edge Detection * * IMPORTANT NOTICE: luma edge detection requires gamma-corrected colors, and * thus 'colorTex' should be a non-sRGB texture. */ [numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)] void SMAALumaEdgeDetectionComputeShader(uint2 DispatchThreadId : SV_DispatchThreadID) { float2 TexCoord = (float2(DispatchThreadId) + 0.5) * SMAA_RT_METRICS.xy; float4 Offsets[3]; SMAAEdgeDetectionVS(TexCoord, Offsets); float2 Edges = SMAALumaEdgeDetectionPS( TexCoord , Offsets , ColorTex #if SMAA_PREDICATION , PredicationTex #endif ); EdgesTexture[DispatchThreadId.xy] = float4(Edges, 0.0, 0.0); } /** * Color Edge Detection * * IMPORTANT NOTICE: color edge detection requires gamma-corrected colors, and * thus 'colorTex' should be a non-sRGB texture. */ [numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)] void SMAAColorEdgeDetectionComputeShader(uint2 DispatchThreadId : SV_DispatchThreadID) { float2 TexCoord = (float2(DispatchThreadId) + 0.5) * SMAA_RT_METRICS.xy; float4 Offsets[3]; SMAAEdgeDetectionVS(TexCoord, Offsets); float2 Edges = SMAAColorEdgeDetectionPS( TexCoord , Offsets , ColorTex #if SMAA_PREDICATION , PredicationTex #endif ); EdgesTexture[DispatchThreadId.xy] = float4(Edges, 0.0, 0.0); } /** * Depth Edge Detection */ [numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)] void SMAADepthEdgeDetectionComputeShader(uint2 DispatchThreadId : SV_DispatchThreadID) { float2 TexCoord = (float2(DispatchThreadId) + 0.5) * SMAA_RT_METRICS.xy; float4 Offsets[3]; SMAAEdgeDetectionVS(TexCoord, Offsets); float2 Edges = SMAADepthEdgeDetectionPS(TexCoord, Offsets, DepthTex); EdgesTexture[DispatchThreadId.xy] = float4(Edges, 0.0, 0.0); } #endif // SMAA_INCLUDE_CS
Engine\Shaders\Private\SMAA\SMAABlendingWeightCalculation.usf
2パス。
/** * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) * * Permission is hereby granted, free of charge, to any person obtaining a copy * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to * do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. As clarification, there * is no requirement that the copyright notice and permission be included in * binary distributions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "SMAACommon.ush" //----------------------------------------------------------------------------- // Vertex Shaders #ifdef SMAA_INCLUDE_VS /** * Blend Weight Calculation Vertex Shader */ void SMAABlendingWeightCalculationVertexShader( in float4 InPosition : ATTRIBUTE0, in float2 InTexCoord : ATTRIBUTE1, out noperspective float2 OutTexCoord : TEXCOORD0, out noperspective float2 OutPixCoord : TEXCOORD1, out noperspective float4 OutOffsets[3] : TEXCOORD2, out float4 OutPosition : SV_POSITION) { DrawRectangle(InPosition, InTexCoord, OutPosition, OutTexCoord); SMAABlendingWeightCalculationVS(OutTexCoord, OutPixCoord, OutOffsets); } #endif // SMAA_INCLUDE_VS //----------------------------------------------------------------------------- // Pixel Shaders #ifdef SMAA_INCLUDE_PS /** * This is only required for temporal modes (SMAA T2x). * * Just pass zero for SMAA 1x, see @SUBSAMPLE_INDICES. * SMAA 1x は0.0を指定します。 */ float4 SubsampleIndices; /** * Temporal textures (texture in first pass) */ SMAATexture2D(EdgesTex); /** * Pre-computed area and search textures */ SMAATexture2D(AreaTex); SMAATexture2D(SearchTex); /** * Blending Weight Calculation Pixel Shader (Second Pass) */ void SMAABlendingWeightCalculationPixelShader( in noperspective float2 TexCoord : TEXCOORD0, in noperspective float2 PixCoord : TEXCOORD1, in noperspective float4 Offsets[3] : TEXCOORD2, out float4 OutColor : SV_Target0) { OutColor = SMAABlendingWeightCalculationPS(TexCoord, PixCoord, Offsets, EdgesTex, AreaTex, SearchTex, SubsampleIndices); } #endif // SMAA_INCLUDE_PS //----------------------------------------------------------------------------- // Compute Shaders #ifdef SMAA_INCLUDE_CS /** * This is only required for temporal modes (SMAA T2x). * * Just pass zero for SMAA 1x, see @SUBSAMPLE_INDICES. * SMAA 1x は0.0を指定します。 */ float4 SubsampleIndices; /** * Temporal textures (texture in first pass) */ SMAATexture2D(EdgesTex); /** * Pre-computed area and search textures */ SMAATexture2D(AreaTex); SMAATexture2D(SearchTex); /** * Output texture */ RWTexture2D<float4> BlendTexture; [numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)] void SMAABlendingWeightCalculationComputeShader(uint2 DispatchThreadId : SV_DispatchThreadID) { float2 TexCoord = (float2(DispatchThreadId) + 0.5) * SMAA_RT_METRICS.xy; float2 PixCoord; float4 Offsets[3]; SMAABlendingWeightCalculationVS(TexCoord, PixCoord, Offsets); float4 OutColor = SMAABlendingWeightCalculationPS( TexCoord, PixCoord, Offsets, EdgesTex, AreaTex, SearchTex, SubsampleIndices); BlendTexture[DispatchThreadId.xy] = OutColor; } #endif // SMAA_INCLUDE_CS
Engine\Shaders\Private\SMAA\SMAANeighborhoodBlending.usf
3パス。
/** * Copyright (C) 2013 Jorge Jimenez (jorge@iryoku.com) * Copyright (C) 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) * Copyright (C) 2013 Belen Masia (bmasia@unizar.es) * Copyright (C) 2013 Fernando Navarro (fernandn@microsoft.com) * Copyright (C) 2013 Diego Gutierrez (diegog@unizar.es) * * Permission is hereby granted, free of charge, to any person obtaining a copy * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to * do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. As clarification, there * is no requirement that the copyright notice and permission be included in * binary distributions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "SMAACommon.ush" //----------------------------------------------------------------------------- // Vertex Shaders #ifdef SMAA_INCLUDE_VS /** * Neighborhood Blending Vertex Shader */ void SMAANeighborhoodBlendingVertexShader( in float4 InPosition : ATTRIBUTE0, in float2 InTexCoord : ATTRIBUTE1, out noperspective float2 OutTexCoord : TEXCOORD0, out noperspective float4 OutOffset : TEXCOORD1, out float4 OutPosition : SV_POSITION) { DrawRectangle(InPosition, InTexCoord, OutPosition, OutTexCoord); SMAANeighborhoodBlendingVS(OutTexCoord, OutOffset); } #endif // SMAA_INCLUDE_VS //----------------------------------------------------------------------------- // Pixel Shaders #ifdef SMAA_INCLUDE_PS SMAATexture2D(ColorTex); SMAATexture2D(BlendTex); /** * Neighborhood Blending Pixel Shader (Third Pass) */ void SMAANeighborhoodBlendingPixelShader( in noperspective float2 TexCoord : TEXCOORD0, in noperspective float4 Offset : TEXCOORD1, out float4 OutColor : SV_Target0) { OutColor = SMAANeighborhoodBlendingPS(TexCoord, Offset, ColorTex, BlendTex); } #endif // SMAA_INCLUDE_PS //----------------------------------------------------------------------------- // Compute Shaders #ifdef SMAA_INCLUDE_CS SMAATexture2D(ColorTex); SMAATexture2D(BlendTex); /** * Output texture */ RWTexture2D<float4> OutputTexture; [numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)] void SMAANeighborhoodBlendingComputeShader(uint2 DispatchThreadId : SV_DispatchThreadID) { float2 TexCoord = (float2(DispatchThreadId) + 0.5) * SMAA_RT_METRICS.xy; float4 Offset; SMAANeighborhoodBlendingVS(TexCoord, Offset); float4 OutColor = SMAANeighborhoodBlendingPS(TexCoord, Offset, ColorTex, BlendTex); OutputTexture[DispatchThreadId.xy] = OutColor; } #endif // SMAA_INCLUDE_CS