モジュールを追加する

Shader Core のモジュール(.scmodule)は、シェーダー本体が描いた結果の上に 効果を足す仕組みです。本体側が置いた __SC_PHASE_*__ の位置にコードが 差し込まれます。第三者の .scshader にも後から乗せられます。

モジュールのファイルを配置し、Core の数式をテストして配布物へ組み込む流れ
モジュールのファイルを配置し、Core の数式をテストして配布物へ組み込む流れ

できること・できないこと

Shader Core の README が明示している制約です。

  • パスは追加できません。 アウトラインのような別パスが要る表現は、 モジュールではなくシェーダー本体として作ります。
  • 本体や他モジュールが決めた動作は変えられません。 競合するためです。 ベースの絵を消す、ライティングの方向性を変える、といったものは対象外です。

画面全体を加工する表現(合成後のピクセル化や歪み)もできません。 隣接ピクセルを読むには GrabPass が要り、このリポジトリでは使わない方針です。

ファイルの構成

Packages/io.github.sabas0ba.sabashader/Modules/<名前>/
  <名前>.scmodule        モジュール定義(JSON)
  properties.hlsl        プロパティ定義
  includes.hlsl          Core の include
  phase_<フェーズ>.hlsl  差し込まれるコード
  <名前>Core.hlsl        数式(Unity 非依存・テスト対象)
  lang/ja-JP.po, en-US.po

phase_<フェーズ名>.hlsl は JSON に書かなくても自動で拾われます。 順序の指定が要るときだけ JSON に phases を書きます。

順序を指定するときはファイル名を変える

JSON に phases を書くと、そのフェーズのファイル名を phase_*.hlsl から 外す必要があります。Shader Core は JSON のフェーズを読んだあと、 ディレクトリの phase_*.hlsl を無条件に足すためです (SCModule.FromFile の AddRange。重複を除くための exists は 組み立てられるだけで使われていません)。両方に該当すると同じコードが 2 回差し込まれ、効果が二重にかかります。

同梱の CrtGlitch は、ドット絵風の後にかけたいので JSON 側だけに載せています。

{
    "name": "__CrtGlitch",
    "uniqueID": "io.github.sabas0ba.crtglitch",
    "phases": [
        { "phase": "postpixel", "path": "crt_postpixel.hlsl", "afters": ["__PixelArt"] }
    ]
}

afters / befores に書いた名前のモジュールが無い場合は無視されるだけなので、 同梱していないモジュールを指しても壊れません。順序を指定しなければ モジュール名の辞書順になります。

この食い違いはハーネス側では見えません(ハーネスは重複を除きます)。 tests/test_scmodule.py::test_package_module_phase_files_are_declared_once が 両方に該当する状態を落とします。

フェーズ

フェーズいつ何ができるか
morph頂点の最初変形・法線操作
postvertex頂点の最後クリップ空間での操作
baseベースカラーとノーマル取得後色調補正、デカール、UV 操作
lightライトごとスペキュラの加算
customlightライト合成前独自ライトの追加
modifylight全ライト合成後ライト色・方向の変更、独自の影
shade乗算シェーディング
reflection任意の合成多くの場合、順序の指定が要る
add加算シェーディングリムライト、スペキュラ
postpixelピクセルの最後仕上げの色操作

base フェーズの時点では sd.N はまだ接空間です。ワールド空間の法線が 要るときは vertex.N を使い、ワールドのベクトルを接空間へ持ち込むときは mul(vertex.TBN, v) とします。

プロパティ名は書き換わる

モジュールのプロパティ名には uniqueID が前置きされます。 io.github.example.foo の _Amount は _io_github_example_foo_Amount になります。 モジュールの HLSL は素の名前で書き、インポータが書き換えます。

このため、サンプラーを自前で宣言してはいけません。sampler_Texture が _io_github_example_foosampler_Texture になり、Unity のインライン sampler の 命名規約から外れます。Shader Core が用意している SCSampleRepeat / SCSampleClamp、または sampler_linear_repeat を使ってください。

数式はテストできる形に切り出す

Unity 依存のない数式は <名前>Core.hlsl に出します。 docs/testing.md の制約(全成分を書く、行列・テクスチャ・ グローバル変数を使わない、prelude にある組み込みだけ)を守れば、 ヘッドレス描画の回帰テストにそのままかけられます。

時間に依存するものは、_Time を直接読まずに引数で受け取ってください。 テストから固定値を渡せて、描画が決定的になります。

有効化

Shader Core はシェーダーごとに有効なモジュールを持ちます。既定値は 「そのシェーダーと同じディレクトリにあるモジュール」なので、別ディレクトリに 置いた本パッケージのモジュールは明示的に有効化が要ります。

Unity では、シェーダーのインスペクタに出るモジュール一覧で切り替えて Apply を押します。設定は ProjectSettings/jp.lilxyzw.shadercore.asset に 保存されます。

検証用プロジェクトでは tools/setup_unity_project.py がこの設定を書き出し、 パッケージ内のモジュールを Illust2D で有効にします。Thin2D や Debug は同じ phase・変数を公開していないため、一律には追加しません。NonToon では検証済みの Mochi Skin と Transformation Bank を有効にします。

テスト

tools/dev.sh python -m pytest tests -q

tests/test_scmodule.py が次を見ます。

  • uniqueID の欠落、フェーズのファイルの欠落
  • プロパティ名が uniqueID で書き換わっているか
  • 同一フェーズ内の befores / afters の矛盾と循環
  • モジュールが宣言していないプロパティを参照していないか
  • lang/*.po の翻訳漏れ

頂点変位の使いどころ

morph フェーズで頂点を動かせますが、縁を丸めることはできません。 丸めるにはジオメトリを足す必要があり、モジュールはパスを追加できず テッセレーションのフックもありません。

そのため厚みを出す表現(積もりなど)は、次のような前提で使ってください。

  • 真横から縁を見る場面を想定しない。上から見る面や遠景に向く
  • 縁をなだらかにしたい場合は、頂点カラーで厚みを落として境界をぼかす
  • 硬いエッジのメッシュ(立方体など)は、面ごとに頂点が分かれているため 面の向きで厚みが変わると継ぎ目が開く

近景で縁まで作り込む必要がある場合は、モジュールではなくシェーダー本体を 作り、シェル用のパスを持たせる方が向いています。

VRChat での制約

VRChat は Android / Quest のアバターに SDK 付属シェーダーしか許可しません。 このパッケージのシェーダーとモジュールは PC アバターとワールド専用です。