Hatena::ブログ(Diary)

土屋つかさのテクノロジーは今か無しか

2018-05-30

「ユニティちゃんトゥーンシェーダーの コードをひたすら読む本(PDF版)」内容紹介

 仕事が忙し過ぎて更新できないまま技術書典4を迎えてしまったため、その時の新刊「ユニティちゃんトゥーンシェーダーの コードをひたすら読む本 (Ver2.0.3対応版)」の紹介記事をまだブログにアップしていなかったのでした。にもかかわらず、気づけば5月23日時点で紙版(コピー本)とPDF版の総頒布数が100を越えました。皆様に感謝。

 コピー本の方は完売してまして、PDF版がBOOTHさんの下記ページから購入できます。

ユニティちゃんトゥーンシェーダーの コードをひたすら読む本 (Ver2.0.3対応版)
https://s-games.booth.pm/items/662396

 以下、内容紹介になります(本文序文から抜粋)。

内容紹介

 ユニティちゃんトゥーンシェーダー(以下UTS)は、ユニティテクノロジーズ・ジャパン(以下UTJ)がUCL2.0(ユニティちゃんライセンス2.0)下で提供しているシェーダーコード群です。

 UTSはいわゆるNPR(Non-Photorealistic Rendering)系のシェーダーで、トゥーン風の質感を手軽に描画することができます。手軽に輪郭線の描写が可能な他、非常に多くのパラメーターによる制御が可能で、セルアニメのようなパキっとした絵から、ピクサーのような滑らかな影まで、極めて幅の広い表現力を持っています。

 UTSは、UTJの公式キャラ「ユニティちゃん」と共に配布されている事もあり、非常に知名度が高く、国内でのトゥーン系シェーダーとしてはデファクトスタンダードと言えるでしょう。

 本書の目的は、UTSのコードを読み解き、どのようなロジックでUTSがトゥーン処理を実現しているかを理解することです。UTSはシェーダーコードのノードエディタであるShaderForge でベースが作成されていることもあり、コードが読みにくく、気軽に改修するのが難しいです。本書でUTSの仕組みを理解し、是非貴方の求めるシェーダーに改良してください。

参考リンク1

 本書では、3D リアルタイムレンダリング、及びShaderLab/HLSL 言語の仕様を把握している事を前提としています。これらについてご存知無い方は、下記拙著を先に読む事をお勧めします。

Unity シェーダプログラミングの教科書ShaderLab 言語解説編
https://booth.pm/ja/items/660001

参考リンク2

 ユニティちゃんシェーダーの膨大なプロパティの表示を整理するエディタ拡張を作りました。よければお使い下さい。

ユニティちゃんトゥーンシェーダーのUIを改修するエディタ拡張を作ってみた
http://d.hatena.ne.jp/t_tutiya/20180513/1526177049

次回予告(???)

 「紙で読みたいのでコピー本を再販して欲しい」というリクエストを何度か頂いているのですが、再販する際には2.0.4以降対応にした上で、不足している記述を補完してオフセット印刷にしたいと思っています(ただし、シェーダー本2を書くのが先)。

サンプル

f:id:t_tutiya:20180531002806p:image
f:id:t_tutiya:20180531002801p:image
f:id:t_tutiya:20180531002756p:image

2018-05-13

ユニティちゃんトゥーンシェーダーのUIを改修するエディタ拡張を作ってみた

 カスタムマテリアルエディターの勉強の過程で、ユニティちゃんシェーダーのUIを改修するコードを書いてみました。以下主な特徴です
f:id:t_tutiya:20180513110346p:image

f:id:t_tutiya:20180513110339p:image

f:id:t_tutiya:20180513110335p:image

f:id:t_tutiya:20180513110331p:image

ソースコードの配置場所

 ソースコードは下記にアップしてあるので欲しい方はご自由にどうぞ。あくまで土屋が使いやすいように改修してあり、オリジナルと互換性がありません。利用においてはご自身の責任においておねがいします。

https://gist.github.com/t-tutiya/824ba6ccb05fb1d1d210aa4a4389ae03

使い方


対象のユニティちゃんトゥーンシェーダーは2.0.4です。また、現状ではToon_DoubleShadeWithFeather.shaderのみに対応しています。他のシェーダーでは恐らくちゃんと動かないでしょう。

組み込みの手順は以下になります。

Editorフォルダ配下にUnityChanShaderGUI_T2.csを配置。
(Editorフォルダが無ければ新規作成)
Toon_DoubleShadeWithFeather.shaderの下部に以下の1行を追加。

    }
    FallBack "Legacy Shaders/VertexLit"
    //↓この行を追加
    CustomEditor "UnityChanShaderGUI_T2"
}

 上記の1行以外は、元のシェーダーコードは無改造でして、カスタムマテリアルエディタ疎結合性が発揮されたように思います(コーディングはやたらと面倒でしたが!)

UI改修点

主な改修箇所は以下の通りです。

々猝椶魍層化(大項目は開閉可能)し、個々のプロパティ名を短くした。
テクスチャと対応するカラーパレットをシングルライン化
Enable系/Tweak系は、トグルをオンにしたら中身が表示されるようにした。
offset/scaleは削除(どうせシェーダー内では使ってない)
ニ\マップがアタッチされた時、その法線マップを使用するかを選択するトグル(_Is_NormalMapToBase/_Is_NormalMapToHighColor/_Is_NormalMapToRimLight)が自動で有効になるようにして、トグルはUIから削除した(法線マップをアタッチした際は、ジオメトリの法線ベクトル情報は使用しないとした)。

,鉢イ砲茲辰謄リジナルとの互換性が維持されれていません。また、大してテストしてないのでバグがあるかもしれませんが、報告頂いてもすぐには直せないかもです。

今後

 今回はカスタムマテリアルエディタの最小限の機能で実装しましたが、将来的にはもっと色々修正したいと思っています。

 また、シェーダーバリアントの機能を使って、現在のユニティちゃんシェーダーのファイルを1つに統合し、UI上で使用する機能を取捨選択するように出来ないかと考えています。シェーダーバリアントはプリコンパイル処理なので、パフォーマンスは下がらない筈です。むしろ今より上がる可能性もあります(いやどうかな……)。

 これは当然シェーダーコード自体の改修になるので、オリジナルとは互換性が完全に失われます。まあ、自分で使う用だからそれでもいいかな……(やるにしてもシェーダー本2が終わってからですが!)。

補足

ユニティちゃんトゥーンシェーダーのコードロジックについて知りたい方はこちらをどうぞ。2.0.3対応版ですが、ロジック自体は同じです。

ユニティちゃんトゥーンシェーダーの コードをひたすら読む本 (Ver2.0.3対応版)
https://s-games.booth.pm/items/662396

Unityシェーダー言語(ShaderLab/HLSL)自体について学びたい方はこちらをどうぞ。

Unityシェーダプログラミングの教科書 ShaderLab言語解説編
https://s-games.booth.pm/items/660001

2018-05-08

カスタムマテリアルエディターまわりのドキュメント誤訳メモ

ShaderGUI.FindProperty

https://docs.unity3d.com/jp/current/ScriptReference/ShaderGUI.FindProperty.html

原文:If true then this method will throw an exception if a property with propertyName was not found.
公式訳:True の場合、このメソッドは例外をスローし、そうでなければ propertyName を持つプロパティーが見つからなかったことを意味します
私訳:Trueの場合、propertyNameに対応するプロパティが発見できなかった場合に、このメソッドは例外を送出します。

 Mandatoryは"強制の"を意味する形容詞で、「そのプロパティが存在することを必須とするか」を設定する真偽値になります。通常、ShaderGUI.FindPropertyでは目的のプロパティが見つからない場合nullが返るのですが、propertyIsMandatoryにtrueが設定されている場合は例外が送出されます。

MaterialEditor.RegisterPropertyChangeUndo

https://docs.unity3d.com/ja/current/ScriptReference/MaterialEditor.RegisterPropertyChangeUndo.html

原文:Call this when you change a material property. It will add an undo for the action.
公式訳:マテリアルプロパティーを変更したときに呼び出し、アクションの取り消しを追加します
私訳:マテリアルプロパティを変更した際にこのメソッドを実行して下さい。このメソッドはその処理のundoを追加します。

 Undo Labelの説明がありませんが、恐らくメニューのEdit>Undoの箇所に表示される文字列のことでしょう。

MaterialEditor.GetTexturePropertyCustomArea

https://docs.unity3d.com/ScriptReference/MaterialEditor.GetTexturePropertyCustomArea.html
原文:Returns the free rect below the label and before the large thumb object field. Is used for e.g. tiling and offset properties.
公式訳:ラベルの下で大きないくつかのオブジェクトフィールドの前にフリーな Rect を返します。タイルやオフセットプロパティーなどに使用されます
土屋訳:ラベルより下、かつ、サムネイル(大)フィールドより手前の利用可能なRectを返します。tiling&offsetプロパティなどに利用します。

MaterialEditor.SetDefaultGUIWidths

https://docs.unity3d.com/2018.1/Documentation/ScriptReference/MaterialEditor.SetDefaultGUIWidths.html
原文:Set EditorGUIUtility.fieldWidth and labelWidth to the default values that PropertiesGUI uses.
公式訳:PropertiesGUI を使用して EditorGUIUtility.fieldWidth と labelWidth をデフォルト値に設定します
土屋訳:PropertiesGUIが参照するEditorGUIUtility.fieldWidthとlabelWidthを初期値に設定します。

2018-05-07

ミップマップレベルを可視化する

ミップマップテクスチャの簡単な説明

 Unityではテクスチャを登録すると、そのテクスチャのミップマップテクスチャ群が自動生成され、リソースに追加されます。ミップマップテクスチャというのは、元の画像を25%ずつ縮小した画像群(通常9枚作成され、元の画像と合わせて10枚になります)です。
 視点から離れた所に配置されているプリミティブのサンプリング対象として高精細なテクスチャを使うと、期待する結果になりません。そこで、画面に表示される大きさに応じたミップマップテクスチャサンプリング対象にします。テクスチャをミップマップ対応にすると、全体では容量が33%増えるのですが、テクスチャの品質が大きく向上するので、広く使われています。

ミップマップの確認

 ミップマップの適用はGPUハードウェア側で透過的に行われる為、ユーザーは意識する必要はありません。が、シェーダー本を書いている最中に実際にミップマップが機能していることを確認したくなりました。

 そこでミップマップレベルごとに異なる画像が設定された画像データを作る方法を探していたのですが、なんか面倒だったのでやめて(!)、作成済みのデータを見つけました。下記の記事からリンクを飛んでダウンロードできます。

Mip Mapping(via polycount)
http://wiki.polycount.com/wiki/Mip_Mapping

 適用してみたイメージ。ジオメトリはカプセルを縦に伸ばした物です。
f:id:t_tutiya:20180507220551p:image
 遠くに行くとフェードでミップマップレベルが変わっているのが分かります。赤が元画像の1024×1024、そこから水色(512×512)→紫(256×256)となっています。

おしまい

 うーし、ミップマップレベルについてはこれでなんとかなるぞ。いずれは権利的に問題ないチェック用の画像データを自作したいと思うけど、優先度は低めで……。

2018-05-01

Unityで投影テクスチャマッピングを実装する際に抑えておくべき基礎情報

 ブログで告知する余裕がなかったのですが、10月22日に開催された技術書典4では既刊のUnityシェーダー本を過去最多部数の再販と、新刊コピー本の「ユニティちゃんトゥーンシェーダーのコードをひたすら読む本」を持ち込んだ所、技術書典始まって以来の晴天に恵まれたこともあって無事完売しました。

 ユニティちゃんシェーダー本はPDF版がBOOTHにて頒布中です。近いうちにちゃんと記事起こします。ちなみに紙版を再販する予定はありません。する時は2.0.4対応にして、オフセットで作る予定です。

https://s-games.booth.pm/items/662396

投影テクスチャマッピング

 突然ですが現在シェーダー本2の執筆に入っています。その過程で投影テクスチャマッピングについて調べてまして、その際に得た知見を共有しておきます。

投影テクスチャマッピングとはなにか

 投影テクスチャマッピング(projection texture mapping. 射影テクスチャマッピングとも言いますが、どちらもprojectionの訳語です)とは、いわゆるプロジェクションマッピングのように、テクスチャマップを任意のモデル形状の上に貼り付けるように描画する手法のことです。

 投影テクスチャマッピングは、AAAタイトルではポピュラーに使われている手法です。よく見るのは壁に弾痕を刻んだり、雪原に足跡の軌跡を描くなどのシチュエーションです。背景BGモデルが複雑な凹凸を描いている時、単に弾痕のテクスチャを新規に作成して配置しただけでは、凹凸に沿わせることが出来ません。ヒットした場所の凹凸を解析して弾痕プリミティブを変形させるのも現実的ではないです。

 逆に、背景BGモデルのテクスチャに弾痕なり足跡なりを重ねてしまうという事も考えられますが、これもうまくいきません。というのも、テクスチャはプリミティブに対してuv座標を介してバインドされていますが、この時テクスチャ自由にスケールできるので、弾痕をどの大きさで貼り付ければいいのか判断できません。また、背景モデルは一般に広大な空間を描くことになるので、相対的テクスチャ解像度が低くなります。弾痕や足跡はカメラに寄りがちなので、解像度が荒いとユーザー体験に結びつかない可能性があります。

 さらに言えば、プリミティブが隣り合っている場所がテクスチャ上でも隣り合っているかは、uvマッピングに依存していて確実ではありません。仮に隣り合っていたとしても、プリミティブ同士が角度を持っていれば、単に弾痕を上書きしただけでは求める画像にはなりません。

 このように、広大かつ複雑にUV展開されている背景BGモデルには、高精度な画像を動的に合成することができません。対処する方法は幾つかあるのですが、その一つが今回解説する「投影テクスチャマッピング」という手法です。

注意

 以下投影テクスチャマッピングの仕組みの説明が続きますが、注意して欲しいのは、シェーダーを実行するのは「投影する側のテクスチャマップ」ではなく「投影される側のプリミティブ」だという点です(そもそも投影する側のテクスチャマップ」はパラメータに過ぎず、オブジェクトですらありません)。土屋が調べている際、なぜかこれをごっちゃにしてしまい、かなり時間を浪費してしまったので、ここでエクスキューズを出しておきます。

投影テクスチャマッピングメカニズム

 プリミティブ(ポリゴンの最小単位。3Dプログラミングではほぼ3角形のことです)にテクスチャを描画する方法は、それが通常のテクスチャだろうが投影テクスチャだろうが違いはありません。即ち「uv座標を元にテクスチャルックアップ関数を実行し、テクスチャから対応するカラー値をサンプリングする」です。

 uv座標というのは画像の左上を原点とした2次元座標です。通常のテクスチャの場合、頂点ごとに対応するuv座標がモデルデータに同梱されているので、それをそのまま使えます。しかし、投影テクスチャマッピングの場合、プリミティブに投影される位置は動的に決まるため、uv座標を計算して求めなければなりません。

 投影テクスチャuv座標を取得するには、専用の射影変換が必要になります。ここでの「射影変換」というのは「プロジェクション変換行列を使って、3次元座標を2次元座標に変換すること」だと思ってください。

 以下の図を元にして、この射影変換について説明します。
f:id:t_tutiya:20180501160301p:image
 説明の都合上、まず通常の射影変換について解説してから、投影テクスチャの射影変換について説明します。

 通常、ローカル空間に存在するプリミティブは、ワールド変換行列によってワールド空間に配置され、続いてビュー変換行列でビュー空間、プロジェクション変換行列でプロジェクション空間(=射影空間)に配置されます。このプロジェクション空間の時のxy座標が、スクリーン上(即ち2次元)での位置になります。

 この処理は言い換えると「ある視点(ここではカメラ)から見た時、矩形(ここではスクリーン)上のどの位置にプリミティブの頂点が存在するか」を計算していることになります。ということは、同じ方法を使って、仮想の視点と矩形を用意して射影変換を行い、算出された2次元座標を投影テクスチャuv座標としてサンプリングすれば、あたかもそのテクスチャがプリミティブに投影されているかのように描画することができます。

 これが、投影テクスチャマッピングの仕組みです。「プリミティブの頂点座標スクリーン上の対応する座標に変換する」という通常処理を、uv座標を計算する為にテクスチャマップに対して行う所がミソです。それほど計算コストも必要とせずにインパクトのある描画が可能になる、とても巧妙な手法だと感じます(ただ、直感的に理解するのがなかなか大変だと思います。土屋は相当手こずりました……)。

投影テクスチャマッピングに必要な変換行列について

投影テクスチャマッピングを行うには、以下の情報が必要になります。今回は、これらの値の算出方法については解説しません(ごめんなさい><)

オブジェクトのローカル空間座標
▲錙璽襯俵間への変換行列
2樵曚了訶世鮓凝世箸靴織咼紂雫間への変換行列
げ樵曚離メラによるプロジェクション空間への変換行列(射影変換)
uv座標系変換行列

 ´△歪名錣汎韻検↓い眥名錣了訶澄織メラではなく仮想の視点/仮想のカメラを使っている以外は同じです。イ世影端譴覆里撚鮴發靴泙后

 射影変換を終えて計算された座標は、スクリーンの中心を原点としたプロジェクション空間座標系の値です。一方、サンプリング先であるテクスチャマップの座標は、左上を原点としたuv座標系の値です。これを一致させないと、正しいカラー値がサンプリング出来ません。

 この変換処理は複数の計算を伴うのですが、以下のような変換行列で乗算すると一発で変換できます。

float4x4 matrix = {
0.5f, 0.0f, 0.0f, 0.0f,
0.0f, -0.5f, 0.0f, 0.0f
0.0f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f };

 複数の変換行列は事前に合成が可能です。シェーダー上での演算量を軽減するために、CPU側で△らイ泙任魄豸弔諒儡更堽鵑砲泙箸瓩討くのが一般的です。

サンプルコード(ないですすみません)

 本当ならここでサンプルコードを提示すべきなのですが、ちょっと今時間がないので、ひとまず他サイトのサンプルコードを示しておきます。

Cg Programming/Unity/Projectors
https://en.wikibooks.org/wiki/Cg_Programming/Unity/Projectors

 このサンプルは、Projectorコンポーネント内での使用を想定しています。Projectorコンポーネントは、先述の画像で言う「仮想の視点」を用意する物で、シェーダーをセットすることで簡単に投影テクスチャマッピングを実現できます。

 Projectorコンポーネントは簡易カメラのような仕組みを持っていて、視錐台で投影範囲を指定します。投影範囲に含まれるオブジェクト(恐らく、レイを飛ばして判定してると思われる)は描画処理がフックされProjectorに設定されたシェーダーが追加実行されます(これも挙動を想像してるだけで、実際にどうしてるかはわかりません)。

 Projectorコンポーネントシェーダーをバインドすると、"unity_Projector"変数に射影変換行列が設定されます。uv座標系への変換まで済んでいる物なので(恐らく。想像が多くてすみません。ProjectorコンポーネントってC++で書いてあるみたいで内部実装が読めないんですよね……)、これをmulすれば必要なuv座標を取得できます。

 ちなみに、サイトのサンプルコードでは"_Projector"となっていますが、5.6から名称が"unity_Projector"に変わっています。旧名を使ったシェーダーをコンパイルすると自動的に名前が書き換わるので注意してください(この挙動もイマイチな気もするんですが。オプションで抑制できたりしないのかな)

tex2Dproj関数の役割

 サンプルコードを見ると、投影テクスチャからのサンプリングするロジックが以下のようになっています。

return tex2D(_ShadowTex, input.posProj.xy / input.posProj.w);
// alternatively: return tex2Dproj(_ShadowTex, input.posProj);

 "alternatively"は「あるいは」の意味で、つまりどちらも同じ処理になります。tex2Dprojは、テクスチャサンプリングする前に、xy要素をw要素で除算する物なので、その計算を自前でやれば、tex2Dで代用できるわけです。

 さて、投影テクスチャマッピングについて解説している記事を見ると、多くがtex2Dprojを使っています。というのも、w除算が必要になるケースが非常に限られていて、それこそ投影テクスチャマッピング処理以外では使用する機会が少ないためです(更に言えば、前述した通り、tex2Dprojはtex2Dに書き換えが可能です)。ではtex2Dprojはどんな役割を持っているのでしょうか? これについて補足解説しておきます。

 座標変換は、元の座標float4.xyzwと、座標変換行列float4x4とのmul乗算によって行います。この時、float4.wは"1"が入っていて、mulの後も"1"が維持されます。なぜこうなっているかというと、移動/回転/拡大縮小を1回の演算で行うには4行4列の変換行列が必要になり、その為には元の座標にも4要素目が必要だからです。変換してもwは変化しないので丁度良いのですね。

 ところが、ビュー空間座標からプロジェクション空間座標に変換する「射影変換」だけは話が別で、この時はwの値が変わります。また、射影変換後のxyzはwの値に依存する結果になっており、そのままでは使用できません。これはxyzw全ての要素をwで除算すると正しい値になります。この時wは1になります。

 今、「正しい値になります」と書いてしまいましたが、wで除算さえすれば必要な値になるのだから、除算するのはその値が本当に必要になった時で構わないことになります。そこで、GPUではw除算する前の4要素と除算した後の4要素は同じ値であると見なし、透過的に処理することにしています。これを「同次座標」と言います。また、w除算した後のxyzwを指して「正規化デバイス座標」と呼びます。

 さて、シェーダーコードを書く時は「同次座標」や「正規化デバイス座標」という概念を意識する時がほとんどありません(土屋もこの記事を書くまでたいして意識していませんでした)。これは、w除算が必要になるシーンが、ユーザー処理側ではほとんど発生しないためです。以下で代表的な座標計算を幾つか確認してみましょう。

…催シェーダ関数から出力されるSV_POSITION

 頂点シェーダーで行った頂点の座標変換には射影変換が含まれるのでwが変化しています。しかし、SV_POSITIONはGPU側でw除算を行う(実際に行っているのかは知らないけど)ので、意識する必要がありません。また、SV_POSITIONの情報はフラグメントシェーダーには渡されません。

通常のテクスチャルックアップ

 tex2D関数そもそも第2要素にfloat2しか受けとりません。TEXCOORDnはfloat4ですが、tex2Dに渡すのはxyのみなわけです。逆に、この特性を利用して、TEXCOORDnのzw要素にカスタムのパラメーターを詰め込む手法もあるようです。

K\ベクトルなど、ワールド空間座標に変換した値

 頂点シェーダーからTEXCOORDnを介してフラグメントシェーダーに送るパラメータの一つに法線ベクトルがあります。これは頂点シェーダーでワールド空間座標に変換してからフラグメントシェーダーに渡す事が多いのですが、この場合は射影変換を行っていないので、wは1のまま。なのでw除算は必要ありません。

投影テクスチャマッピング

  銑で見てきたように、ほとんどの場合において、ユーザー側でのw除算は必要ありません。しかし、投影テクスチャマッピングの場合には、仮想視点での射影変換をしているので、w除算が必要になります。その時に使えるのがtex2Dprojなわけです。内部では透過処理されるので、手動でw除算をしてtex2Dを使うようり、tex2Dprojを直接使った方が、処理速度が速いのではないでしょうか(想像です。検証したわけではありません)。

おまけ

 余談ですが、投影テクスチャマッピングでは、通常のシェーダー処理で使える手法は基本的に全部使えるので、法線マップを使って投影した箇所に対して疑似凹凸を表現することもできます。また、最近では「法線マップの疑似凹凸が現実の凹凸だった場合に、視線の角度からは見えない筈の部分の描写を詰める」という技術が使用できる(これを「視差遮蔽マッピング (Parallax Occulusion Mapping)」と言います)ので、弾痕の表現は更にリアルになっています(弾痕以外に使い道無いのかよと思うけど、あんまりないかなあ……?)。

おわりに

 いやー長かった!w ネットで投影テクスチャマッピングの事を調べると、どうもふんわりした情報が多かったり(ちゃんと書くとこれだけの分量が必要になるし、この記事だって射影変換の作り方は省略してるし!)、「Projectorコンポーネントを使いなさい」以上の情報がなかったりして調べるのが大変でした。

 本来はシェーダー本2に収録する内容になるんですが、うーん、どうしようかな……。これだけでコピー本一冊作れそうな気がしてきたな……w