実践ドット絵シェーダー その2 - チラつき抑制編

これの続き。 mizuooon.hatenablog.jp

今回は、静止画だと綺麗に見えるけど動かすとなんか 3DCG っぽさあるよねというのを改善したい。

そもそも問題はなにか

まず、前回の結果が以下である。

フレーム間でなんとなくチラチラしているのがドット絵っぽさを損なっている気がする。 この感じの正体はなんだろうか。

ここで、さらに簡単な場合として、 以下のようなボールを移動アニメーションさせるときを考えてみる。

f:id:mizuooon:20180729235615p:plain

このとき我々が期待するのは、ボールは上図の見た目を保ったまま移動する、という挙動である。 だが、ドット絵シェーダでこのボールをアニメーションさせた場合、以下のようになる。

やたら輪郭がチラついており、想定通りの挙動をしていないと言える。

結論を言うと、このチラつきの原因はラスタライズ時のサンプリング位置の一貫性が保たれていないことである。

球の形状データは、ラスタライズを経て 2 次元の画像へと変換される。 この際、下の画像のように、各ドットの中央の点において球の情報を離散的にサンプリングする。

f:id:mizuooon:20180730002003p:plain:w400

球の座標は 1 ドット未満の単位で変化するため、 毎フレームごとに球上の相対的なサンプリング位置が変化し、 それが見た目のチラつきをもたらすのである。

対処法

対策はシンプルである。 球の座標が 1 ドット未満の単位で変化するためにサンプリング位置が毎回バラつくということは、 逆に言うと、球の座標を 1 ドット単位に離散化してやればこれは解決される。

具体的には、下図のように、毎フレームの描画時に球の中心座標を近傍のドット中心へスナップしてやればよい。

f:id:mizuooon:20180730002008p:plain:w400

これを適用した結果が以下である。

毎フレームの相対的なサンプリング位置が一貫性を持つため、チラつきがなくなっているのがわかる。 (形状が完全な円になってないのはこの際よいとする)

実装時に注意しなければならないのは、 座標のスナップは描画時だけ適用し、描画が終わったら描画対象は元の座標に戻さなければならないことである。 スナップを適用しっぱなしになっていると、当然座標の誤差が毎フレーム蓄積されて訳の分からない挙動になる。

キャラクターモデルへの適用

これを、キャラクターモデルの描画へと適用する。 実装として、 やや大胆にも思えるが描画時にモデルの各ボーンの座標をドット中心へとスナップするようにした。 Unity で実装しているので、LateUpdate 内でボーンをいじる形になっている。

結果は以下である。わかりやすさのために、頂点アニメーションなどは切ってある。

(左が座標スナップ適用、右が未適用)

妙なヌルヌル感が抑えられ、 アニメーションの感じは なんとなく、ドット絵と聞いてイメージされるものに近くなっていると思われる。 特に、輪郭が安定していることで遠目で見たときの印象に違いが出ている。

メモとか

  • 実装時、親ボーンの座標修正が子ボーンのワールド座標を変化させないようにする必要があることに注意。
    • そうしない場合、末端近くのボーンは座標誤差が蓄積してガクガクする。
    • 最初これでバグってた。
  • 手の形状がちゃんと見えるようになってしまったのでもっとちゃんと作らないといけない気がする。
  • アニメーションではボーンの位置情報にもキーを打っておかないとボーンの座標誤差が溜まっていってすごい形状になる。
  • ボーンの回転角離散化というのも試してみたが、大変な見た目になっただけだった。
    • バグってただけの気もする。
    • ねじれ角度だけの離散化ならできる?
  • 他のチラつき抑制アイデアについて。
    • 描画は何フレームかに一回行っているが、表に描画していないサブフレームの情報を使ってアニメーションを補間する。
      • 各フレームの情報は G バッファに書き出しているので、各フレームのモデルの形状の和を取るとかは楽。
      • ブラーの表現も一緒にできそう。
      • 重そう。
    • モデルの情報は一度 G バッファに描画して、それをドット絵シェーダに与えているが、G バッファのサイズを倍にして、ドット絵シェーダでより細かい形状情報も使えるようにする。
      • そこそこ有望そうだが、やり方が自明でない。