Unityのシェーダーグラフでベイヤーディザをやってみる
blenderのコンポジットノードだと、スクリーン座標を取得するようなノードが無い(方法があれば是非教えていただきたく……)んですが、Unityのシェーダーグラフを触ってみたところ、そのままズバリ 〈Screen Position〉というノードがある事を知りました。
これなら 「ベイヤーディザ法による減色表示が実現できるのでは?」と頑張ってみたところ、なんとか形にはなったみたいなのでご紹介します。と言ってもあまり賢くなさそうな感じになっているので、もっとスマートな方法があれば是非教えて下さい🙇
キーキャッピーちゃんと喘息に欠かせないブロンの写真。右がオリジナル、左が今回作ったシェーダーグラフをマテリアルに指定したものです。シーンビューでピクセル等倍じゃないのでモアレが出てしまっていますが、概ね綺麗に減色できている気がします。
ベイヤーディザについて
先にベイヤーディザについて説明します。もう知ってるよという方は次の項まで飛ばして下さい。
ざっくり言うと、画像を減色する時のディザリング手法の一つです。Photoshopで言うところの“パターンディザ”。例えば白黒2値に減色する場合、ベイヤーフィルタと呼ばれるパターン(行列のようなもの)を画像に当てはめながら、ベイヤーフィルタの値を閾《しきい》値とし、画素の輝度がそれ以上なら白、以下なら黒にしていくと、不思議な網掛け模様を残しつつ、白黒2値だけでもそれっぽく見える画像に減色できます。これ、本当に不思議ですよね……
閾値と画素値の差は捨てちゃうので、差を周囲の画素に分散していく誤差拡散法ほどキレイにはなりませんが、独特のパターンから醸《かも》し出されるレトロな雰囲気がたまりません。みんな大好きゲームボーイのポケットカメラで撮影される画像にも使われています(断言しておいて申し訳ないのですが、本当のところは知りません。見えるパターンが同じなのでおそらくそうだと僕が思ってるだけです)。
電池を入れたらバッチリ動く21年前のデジタルカメラ。数年ほっぽっただけで電池がすっからかんになって起動できなくなるリチウムイオン電池デバイスとは格が違うんだよー。
シェーダーグラフ全体図
さてこちらがグラフ全体図です。やっている事はシンプルなのですが、ベイヤーフィルタの値を取得する部分が大きくなってしまっています。最後にちょこっと画素値と比較して白か黒をアウトプットしています。ちなみにこれは2Dスプライト用のシェーダーです。
スクリーン座標の取得とベイヤーフィルタ上の位置の算出
先頭のこの部分ではスプライトのスクリーン座標を取得しています。Screen PositionノードでXY座標がそのまま出てくるのかと思ったんですが、どうも0~1.0に正規化された値のようでした。そのため、Screenノードでスクリーンサイズを取得してMultiplyすることでピクセル単位に変換しています。
ピクセル座標が取得できたら、X, Yそれぞれを4とで余りを取って4x4のベイヤーフィルタにおいて参照すべき位置に変換します。これはX要素、Y要素に分けたあと、Floorノードで整数に変換しているだけです。これで全ての値が0..3の整数に収まりました。あとはベイヤーフィルタ上の対応する位置を取得して閾値とし、画素値と比較するだけです。
ベイヤーフィルタから任意の位置の値を取得する
ベイヤーフィルタは単純な4x4の行列で表せるのでMatrix4x4ノードを使おうと思ったのですが、シェーダーグラフ上でそのマトリックスから任意の位置にある値を取り出す方法がわかりませんでした。
そこで、頭の悪そうな方法ですが、 〈Comparisonノード〉と 〈Andノード〉、それから 〈Branchノード〉を組み合わせて、“X値が0かつY値が0なら1行目1列目の値”、“X値が0かつY値が1なら2行目1列目の値”、“X値が0かつY値が2なら3行目1列目の値”……、“X値が1かつY値が0なら1行目2列目の値”…… これを馬鹿正直に16回繰り返しています。それぞれ条件に当てはまらない場合は0を出力します。このため、16箇所で算出された値をすべて足し合わせれば、結果としてベイヤーフィルタから欲しかった位置にある値が取り出せたことになります。
なお、0..1.0の画素値と比較するために、取り出した値を最後に16で割ってスケールを合わせています。
画素値の取得とグレイスケール化
一方、こちらはベイヤーフィルタと比較すべき画素値を取得している部分です。元々のスプライトの画素値はReferenceを"_MainTex"
とした
〈Texture2DのInputノード〉を
〈Sample Texture 2Dノード〉に繋げることで取得できるようです。
次にベイヤーフィルタと比較する前の下準備として、RGB値をグレイスケールに変換しました。グレースケール化では一般的なR * 0.299 + G * 0.587 + B * 0.114
の式を使っています。
グレースケール - Wikipedia
https://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%AB
ベイヤーフィルタ値と画素値の比較、白黒の出力
最後に、画素値の値Aから、ベイヤーフィルタの値Bを引くことで両者を比較しています。画素値が閾値より大きければプラス、小さければマイナスになるので、
〈Signノード〉を使って-1または1に変換し、それをそのままRGBの各チャンネルに分配して白または黒の色とし、最終的な
〈Sprite Lit Masterノード〉のColorに繋げています。-1
は黒(0)
にクランプされるみたいです。
結果
というわけで、シェーダーグラフの見た目はともかく、ベイヤーディザそのものはおそらく実現できているのではないかと…… ひょっとしたら間違っているかもしれませんが、少なくとも見た目はそれっぽくなりました。
まだシェーダーグラフはよくわからなくて、これを書いている間にも 〈“サブグラフ”〉という概念を知ったので、もう少しシンプルに纏《まと》められそうな気がします。例えばこのグラフではAddノードで3つや4つの値を足す箇所が多いのですが、複数の値を足すようなサブグラフを作ればもう少しスッキリしそうです。
この記事はここで終わりです。
読んでいただきありがとうございました。
良かったらシェアしてね!
That's all for this article. Thank you for your reading.
Please share this if you like it!