2015年4月3日金曜日

【UE4】セルっぽいルックを頑張ってみる その4(エッジ)


エッジについてです
【最終ルック】

 上の画像は左のキャラとそれ以外の部分で、2通りのエッジの付け方を使っています。

 キャラの方は、モデリングの段階でポリゴンを法線方向に拡大し、面を反転してエッジとしています(バックフェース法というらしいです)。
この方法の特徴としては、

  • モデリング時にリアルタイムで調整ができる
  • ポストプロセスが不要なのでモバイル/HTML5環境でも使える
  • 実装が簡単
  • 輪郭の表現はできるが、凹凸部分の表現ができない
  • カメラとの距離によって画面上の線の太さが変わる
  • メッシュの交差時に表示が不自然になる場合がある
  • 頂点数が2倍近くになってしまう


 それ以外の部分はポストプロセスを使い、オブジェクトの深度を表すデプス値と面の向きを表すノーマル(法線)の値を参考にエッジの検出を行っています。
特徴は

  • 輪郭以外の凹凸に対しても線の描画が可能
  • ポリゴンが平面でもノーマルマップがあればエッジをつけることができる
  • カメラの位置にかかわらず画面上の線の太さが一定
  • 機械的に検出するため、意図しない位置に線がでる場合がある
  • ポストプロセスがない環境(モバイルなど)では使用できない

といったところでしょうか。

 既存のタイトルを見ると、ハイエンド向けだからポストプロセスとかというわけではなく、それぞれの特徴を考えて選択しているみたいで、両方を同時に使っているタイトルもあるようです。

 バックフェース法は単にモデリングして表示するだけなので、ここではポストプロセスでの実装方法を中心に説明したいと思います。

●ポストプロセスによる実装


【エッジ検出BP】
エッジを検出するBPの全体像です。

 デプスと法線からラインとなる場所を検出し、距離により表示の有無を判断したあと最後にカスタムデプス以外の部分を表示するようにマスク処理を行っています。
 パラメータで線の太さ、密度、色、表示距離の調整ができるようになっています。


●デプスでの輪郭検出


それぞれの処理を個別に見ていきます。

【デプスの値を色として描画】
 デプスは3D空間上でのカメラからの距離がピクセルごとに数値として入ってるので隣接するピクセルの差が大きいと、空間として前後に離れていることになるので輪郭となります。

【デプスによる輪郭検出】
左キャラはカスタムデプスで対象外にしているので表示されません

【ラプラシアンフィルタっぽいもの(デプス)】
 デプスでの輪郭検出には上下左右のピクセルの平均をとって、中央のピクセルとの差分を算出しています。本来は3x3の周囲8ピクセルでやるみたいですが上下左右の4ピクセルでもさほど問題なさそうだったので4ピクセルのみでの処理にしています。
 ここで算出した値に閾値を設定してラインの検出をすることになります。

上のBPで使用している指定ピクセルのデプス値を取得する関数は以下のようにしました。

【ピクセルデプス取得関数】
InvSizeには1/Sizeの値が入ってくるのでそこから1ピクセルあたりのUV値が求まります。



●ノーマルでの凹凸検出


デプスの値だけではオブジェクトの輪郭しか検出できないのでディティールが足りません。
なのでディティールを加えるためにノーマルを利用して凹凸の検出を行います。

【ノーマルベクトルを色として描画】
RGBがそれぞれXYZに対応しています
ノーマルは面の向きを表しているので、周囲のピクセルのノーマル値つかって尖っている部分や凹んでる部分を判断します。

【ノーマルによる検出】

【ラプラシアンフィルタっぽいもの(ノーマル)】
 基本的にはデプスでやっていることと同じです。ただ、ノーマルはXYZの3つの値が入っているので一度色情報に対応させてから輝度に変換することで1つの値にして差分を計算しています。
 

【ピクセルノーマル取得関数】
使用している指定ピクセルの取得関数もデプスからノーマルに変わっただけです。


【輝度の算出】
輝度の算出です。定数はPhotoshopなどで使われている、人間の感覚に近づけるための値……らしいです。


●エッジ消失距離


 上記2つの処理でエッジの検出と描画は可能となったのですが、このままだと天球に同心円状のラインが入ったり、カメラを引いた時に線の密度が高くなって潰れてしまいます。
 その対策として、デプス、ノーマル、それぞれ個別に線が消える距離を設定できるようにしました。

【エッジ消失距離】
ピクセルデプスの値を元に、設定された距離以上の値は0となるように計算しています。
この計算結果をそれぞれのエッジ検出処理の結果に乗算することでマスクしています。

【カメラを引いた時の比較(拡大画像)】
左:全部の線を描画すると線同士が詰まりすぎてしまっている
右:ノーマルでのラインを消し、デプスによる輪郭だけ描画することでスッキリさせた

●画像への合成


 最後にシーンの画像と乗算合成し、カスタムデプスによるマスクをかけます。

【シーン画像との合成】
線の色は一律で加算し、Clamp関数を使ってつけています。

【カスタムデプスによるマスク関数】
カスタムデプスマスク関数はカスタムデプス内の色とカスタムデプス外の色を指定して対応した色を返します。
 今回はカスタムデプス外にのみ、エッジラインを足した色が出るようにしています。



以上です。



 ここまででセルルックに必要なことはひと通り説明したつもりなので、セルルックに関しては今回までにするつもりです。またなにかネタができたら追記していきます。
 実際のアニメっぽくするにはもっと画面にフィルタをかけたりしてなじませる必要があると思いますが、ココらへんはちょっと勉強不足なもので……。

【とりあえずディフュージョンっぽく……?】


3 件のコメント:

  1. 法線は定数をかけて一つに値にして比較するよりも、
    比較対象との内積を取った方がよさそうですね

    あなたがかけている定数は、色の明るさを求める定数なので
    rgb値ではない法線に掛けて使うのはナンセンスです
    (人間の目はrgbに均等に反応しないため、青よりも赤が、赤よりも緑が
    明るく見える傾向があり、それらを考慮して画像をグレイスケールにするウエイトが0.299,0.587,0.144です)

    返信削除
    返信
    1. ご指摘ありがとうございます!
      投稿した後にも色々試した結果、仰るとおり比較対象との内積の方が精度が高く感じたので現在はその方法でやってます。(情報が古くなっていてすみません……)

      たしかに記事にあるのは数値がほしいだけのモノですし、この定数を掛けるのはおかしいですね。

      削除