情報学的バナナの皮

だらだらと自作プログラムについての備忘録

Unityで3Dモデルをドット絵風に描画したい<1>

作るといってもとりあえず現状作りかけのものを紹介する

基本的な方針は下の方を参考にした

mizuooon.hatenablog.jp

とりあえず当初欲しいなとおもった機能

シェーダを書いて解決した要素

・リアルになりすぎないイラスト的な影とハイライトの付き方

・モデル全体の色数の制限

・輪郭線の描画

スプリクトを書いて解決した要素
・動きのコマ落ち(昔のゲームのドット絵は1コマ1コマ手作業で描いていたはずなので、その雰囲気を再現したい)

・一枚絵らしく見せるために動く際のチラつきを減らす(上記サイトの”その2”を見てくれるとわかる)

その他

・ドット絵らしいピクセル感のある描画

・モデルはドット絵化、背景はそのまま、みたいに適応するものしないものを選べるようにしたい

現状報告その1

順を追いながら説明していく

・ドット絵らしいピクセル感のある描画

→unityの公式AssetのPixelPerfectCameraを使う

これはカメラに映る画面全体の解像度と、1m当たりに詰め込むピクセル数を設定してあげると勝手に映っているものの描画をピクセルパーフェクトにしてくれるasset。

ポストエフェクトで自作できる気もするけどとにかく手軽なので導入。

公式の例だと2Dのスプライトとかに使っている例しか出てこないけど普通に画面全体の描写に適応されるので3Dモデルでも関係なくピクセル化できる。

他に考えたのは

シェーダ内でピクセル

→輪郭線がドット化できないから没

描画結果をレンダーテクスチャに書き出して、板ポリゴンに張り付けた上でピクセル

→めんどくさい、位置の調節がむずかしい、重いの三重苦で没

 

下はただのStandardshaderを適応した球のモデルをPixelPerfectCameraで描画してみた図。

まぁドットではあるけどこれじゃないよね。

f:id:tkg_lag:20210326143201p:plain

 

・リアルになりすぎないイラスト的な影とハイライトの付き方

影は単純に濃さを可変できるようにした。

頂点シェーダ内の式はこんな感じ。

_NomalColorはモデルのベースとなる色、_GradationColorは影の強さを指すfloatの変数。

 float l = dot(normalize(o.lightDir), v.normal) * 0.5 + 0.5;

  o.shadecolor = lerp(_NomalColor* _GradationColor, _NomalColor, l * l);

法線と光の方向の内積から影の付く度合いlを計算し、lerpで影が付く部分は_NomalColor* _GradationColorで濃いベース色に、つきにくい部分は_NomalColorで着色するようにしている。

lを二乗している理由は単純に影の付き方を強めるため。ただのlでは影が弱く感じたため。

 

ハイライトは先述のサイトを参考に環境マップで実装した。

f:id:tkg_lag:20210326145538p:plain

上の画像の明るいところのみ加算してやると下のようになる。

f:id:tkg_lag:20210326145528p:plain

これは下のコードのようになる。

fixed4 col = (rgb2hsv(tex2D(_MatCap, i.uv)).z < _CapPower) ?(i.shadecolor* _BlendPower + tex2D(_MainTex, i.uvtex)*(1- _BlendPower)): (tex2D(_MatCap, i.uv) * _BlendPower + tex2D(_MainTex, i.uvtex) * (1 - _BlendPower)));

筆者が三項演算子好きが故に見づらくなってしまっているのでif文に描き直すと、

if(rgb2hsv(tex2D(_MatCap, i.uv)).z < _CapPower){

i.shadecolor* _BlendPower

+

tex2D(_MainTex, i.uvtex)*(1- _BlendPower);

}

else{

tex2D(_MatCap, i.uv) * _BlendPower

+

tex2D(_MainTex, i.uvtex) * (1 - _BlendPower);

}

_MainTexはモデルのテクスチャ、_MatCapが先ほどのモノクロの画像を指すテクスチャ。

rgb2hsvはrgb(赤緑青)情報からhsv(色相、彩度、明度)に色味を変換する関数。それのzはつまり明度であるので、環境テクスチャの明度が_CapPowerより小さい(=暗い部分)場合は前者に、大きい(=明るい部分)は後者に分岐する。

テクスチャを_BlendPowerで弱めたものと、前者では先ほど説明した式で算出した影を、後者では環境テクスチャをハイライトとしてそれぞれ加算している。

ここまでやった結果をピクセル化すると以下のようになる。

f:id:tkg_lag:20210326150752p:plain

ハイライト様々といった感じ、でもまだレトロゲーっぽくはないかな。

・モデル全体の色数の制限

色数の制限というか、減色処理といった感じ。

ホントは使える色をパレットで指定して~とかやったほうがそれっぽくなるんだろうけどそこまでの技術はないのでただ色を階調化してあげる。

fixed4 posterize(fixed4 color) {
float4 tmp = (_GradationNum == 0 ?
color
: (floor(color/ _GradationNum) * (_GradationNum))
);
return tmp;
}

与えられた色を階調化する関数、これは簡単なので三項演算子のまま説明しちゃう。

_GradationNumは減色後の色数。これが0の時は与えられた色をそのまま返す。

他の場合はよく見る式を通して階調化。シンプル。

f:id:tkg_lag:20210326151416p:plainf:id:tkg_lag:20210326151402p:plain

_GradationNumの値を変えれば減色の数も変わるので扱いやすい。

これに先ほどの影とハイライト、及びピクセル化をすると下のように。

f:id:tkg_lag:20210326151642p:plain

だいぶそれらしくなってきたのではないだろうか。この調子で行こう。

・輪郭線の描画

これがだいぶ悩んだ、ただ線を膨らませて二度描画だとエッジが欠けちゃって綺麗にならない。

結果既存の手法を丸パクリさせてもらうことに。

qiita.com

こちらの記事を参考にしたので内容は省略。これまでの結果はこうなった。

f:id:tkg_lag:20210326152323p:plain

完全な円になっていないのはさておきいい感じではなかろうか。

他の例も一応挙げとく。

f:id:tkg_lag:20210326153150p:plain

f:id:tkg_lag:20210326152947p:plain

自作シェーダでの表示なのでUnityChanシェーダーとは色味がかなり違うけど、もうちょっとパラメータいじれば寄せれはすると思うがドット化するのであんま関係ないかな。目とかはつぶれちゃうので非表示にしてる。

とりあえず今回はここまで、続きは後で気が向いたら書きます。