Tech Blog By 小林開人

輪郭線をPostProcessで描画する

  • 新人ブログ
  • unity
  • RobertsCross
  • SobelFilter
  • エッジ検出
  • ポストプロセス
  • 輪郭線

はじめに

備忘録と復習を兼ねて、PostProcessで輪郭線を描画してみました。
本記事では、PostProcessで輪郭線を描画した際に使用した技法について、簡潔に解説をしていきます。

開発環境

Unity 2020.2.3f1を用いて開発を行っています。

エッジの検出手法

今回、画像からエッジを検出する手法として、RobertsCrossとSobelFilterを使用しました。下に二つの手法の簡単な説明を載せておきます。

SobelFilter

SobelFilterはノイズを減らすための平滑化処理とエッジ抽出処理をひとまとめにして行う手法で、下図のような縦線検出と横線検出の2つのオペレータを用います。

縦線検出オペレータ
横線検出オペレータ

RobertsCross

SobelFilterとは違い、下図のように斜め方向に差分を計算してエッジを抽出する手法です。

RobertsCrossオペレータ

深度情報から輪郭線を抽出

手始めにRobaertsCrossを使用して深度バッファからエッジを検出して輪郭線を描画してみます。下にシェーダコードを載せておきます。

各行で何をやっているかについて説明します。
1~13行にかけて、_SampleScaleに基づいてサンプリングするUVのオフセット値を求めて、深度バッファから深度値をサンプリングしています。
15~17行では、RobertsCross演算を行っており、注目画素におけるエッジの強さを算出しています。
19行では、17行目で算出した値に対して閾値で制限をかけて、ノイズが誤ってエッジ検出されることを防いでいます。ここで閾値に注目画素の深度値を乗算しているのは、遠方でのエッジ検出ではより小さな差分が必要になるためです。閾値を変調して遠方でエッジ検出がうまくいくようにしています。

SobelFilterでも同じように深度バッファからエッジを抽出してみます。

SobelFilterもやっていることはRobertsCrossとあまり大差ありませんが、サンプルする回数が多いです。

描画結果を見比べてみます。

深度を用いたエッジ検出の結果

あまり見た目に大差はありませんが、RobertsCrossに比べてSobelFilterがのほうが太く出ていることがわかります。

ビュー法線情報からエッジ検出

基本的に深度バッファからエッジを検出するのとやることは変わらないので、コードは割愛します。
結果だけ見比べてみます。

法線バッファを用いたエッジ検出の結果

深度バッファを用いてエッジ検出したものと見比べてほしいのですが、法線を使ってエッジ検出をすることで、深度を使ったものより詳細なエッジを検出できていることがこの結果からわかります。立方体の角がわかりやすいかと思います。

色情報からエッジを検出してみる

これもコードは割愛します。まず下図のようなシーンのレンダリング結果があるとして、これを入力とし、RGBA情報のR成分に注目してエッジ検出を行ってみます。本来であれば、線の厚さ等をコントロールするバッファを用意することになると思いますが、今回は色があれば何でもいいので一旦レンダリング結果をそのまま使うことにしました。

シーンのレンダリング結果

下図のような結果となりました。

レンダリング結果を用いたエッジ検出の結果 (a)

上記結果を見てわかる通り、愚直にシーンのレンダリング結果からエッジを検出しようとすると、エッジを検出してほしくないところまで検出処理が走ってしまうので工夫が必要のようです。今回は「別のバッファにエッジコントロール専用のデータを書き出して、ポストプロセス処理で入力として使用する」といった凝ったことはせずに、レンダリング結果をそのまま使えるように、ジオメトリを描画するシェーダーをデフォルトのものから、下記のようなライティング処理を行わずマテリアルカラーをそのまま出力するシェーダーに変えるようにしてみます。

上記のシェーダーコードでレンダリングした結果が下記になります。

UnlitShaderでレンダリングした結果

そして、上記を入力として再度エッジ検出をかけた結果が下記になります。

レンダリング結果を用いたエッジ検出の結果 (b)

それぞれのバッファを合成してみる

最後に、それぞれのバッファからエッジを検出した結果をレンダリング結果と合成して出力します。

合成結果

エッジの色はマゼンタで描画しています。

終わりに

RobertsCrossとSobleFilterで結果を比較しながら進めてみたわけですが、あまり差異がないように見えました。RobertsCrossの方がサンプル数が少ない分、負荷が軽そうです。ComputeShaderを使わずPixelShaderでポストプロセスを行いましたが、ComputeShaderを使えばPixelShaderより負荷が軽いものを作れそうです。
もし次に記事を書く機会があるとすれば、NPR系Shaderについて何か書こうと思っています。

参考

Sobel Filter
Roberts Cross
エッジ検出
画像処理 (4)エッジ検出