凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

Unity 向けに HLSL Tools for Visual Studio を便利にするアセットを作ってみた

はじめに

本記事は Unity Advent Calendar 2020 の 20 日目の記事です。

qiita.com

Windows 上の Unity でシェーダを書く(読む)際は Visual Studio を使うことが多いかもしれません(VSCode の人も多いと思いますが...)。しかしながらそのままでは補完も効かずシンタックスハイライトもほとんどありません。

これを良い感じにくれるのが、HLSL Tools for Visual Studio です。

marketplace.visualstudio.com

まず、この拡張機能をインストールします。

すると変数を認識してくれて補完もいくつか効くようになります。

ただ、Unity 本体がインストールされているディレクトリからインクルードしている UnityCG.cginc から来ているメンバは解決できません。また、URP / HDRP ではパッケージからインクルードしているものも有り、これらも解決できない形です。しかしながら、HLSL Tools for Visual Studio には shadertoolsconfig.json というコンフィグファイルを親ディレクトリを辿って見つけ、そこに記述されているディレクトリを #include の対象として含んでくれる機能があります。次の JSON は公式に載っている例です:

{
  "hlsl.preprocessorDefinitions": {
    "MY_PREPROCESSOR_DEFINE_1": "Foo",
    "MY_PREPROCESSOR_DEFINE_2": 1
  },
  "hlsl.additionalIncludeDirectories": [
    "C:\\Code\\MyDirectoryA",
    "C:\\Code\\MyDirectoryB",
    ".",
    "..\\RelativeDirectory"
  ]
}

ただプロジェクトごとにバージョンの異なる Unity(に含まれる UnityCG.cginc など)や各種インストールされているパッケージ(URP や HDRP など)を考慮しながらこの JSON を用意するのは面倒です。そこで、現在動作している Unity のディレクトリやインストールされているパッケージのディレクトリを含む shadertoolsconfig.json を自動生成するアセットを作ってみました。

ダウンロード

github.com

Releases のページから最新をインストールしてください。ライセンスは MIT ですが Editor スクリプトなのでゲームやデモの公開時にはライセンス表記は必要ありません。MiniJSON に依存してますが、すでにプロジェクトに含んでいる場合はインポートの際にチェックを外してください。 執筆時の最新バージョンは v0.0.2 です。

使い方・できること

Window > HLSL Tools for Visual Studio Config Generator を開きます。

Create ボタンを押します。

これで自動的にプロジェクトのルートに shadertoolsconfig.json が作成されます。

次の画像は Legacy パイプラインのコードの編集の様子です。UnityCG.cginc および依存する .cginc が読み込まれて補完やコードジャンプ(F12)ができるようになっています。

HDRP のコードでも Packages/com.unity.render-pipelines.high-definition/... が読み込まれています。ただ HDRP では ShaderLab(.shader)の方で複数の #include をする方式をとっており、単体の .hlsl ファイルからはその #include が見えず、補完が効きづらい問題は依然として残っています。

仕組み

追加するべきものは大きく分けて 2 種類あります。

  1. 現在実行中の Unity の exe のディレクトリ下にある Data/CGIncludes
    • UnityCG.cgincUnityStandardBRDF.cginc といったレガシーパイプラインのインクルードファイルを含む場所
  2. 現在のプロジェクトが依存しているパッケージ

これらを詳しく見ていきます。

CGIncludes

このディレクトリを見つけるのは簡単で、次のようにします。

var appPath = Environment.GetCommandLineArgs()[0];
var appDirPath = Path.GetDirectoryName(appPath);
var cgIncludesPath = Path.Combine(appDirPath, "Data\\CGIncludes");

まずこのディレクトリを hlsl.additionalIncludeDirectories に含めます。

パッケージ

パッケージの方は少し複雑です。依存しているパッケージは Packages/package-lock.json に記述されています。

{
  "dependencies": {
    ...
    "com.unity.render-pipelines.core": {
      "version": "8.3.1",
      "depth": 1,
      "source": "registry",
      "dependencies": {
        "com.unity.ugui": "1.0.0"
      },
      "url": "https://packages.unity.com"
    },
    "com.unity.render-pipelines.high-definition": {
      "version": "8.3.1",
      "depth": 0,
      "source": "registry",
      "dependencies": {
        "com.unity.render-pipelines.core": "8.3.1",
        "com.unity.shadergraph": "8.3.1",
        "com.unity.visualeffectgraph": "8.3.1",
        "com.unity.render-pipelines.high-definition-config": "8.3.1"
      },
      "url": "https://packages.unity.com"
    },
    ...
  }
}

一方、ダウンロードされたパッケージは Library/PackageCache にバージョン付きで保存されています。

一方でシェーダからのインクルードパスは次のようになっています。

#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Material.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/Lit.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/ShaderPass/LitSharePass.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/LitData.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/VertMesh.hlsl"

バージョン番号もなく Package というディレクトリ下にあることも期待されていて、そのままだとパスとして解決できないですね...。そこで、シンボリックリンクを作ってパスを解決するようにします。

f:id:hecomi:20201219101341p:plain

設定画面で指定したディレクトリ(デフォルトでは ShaderInclude)を作成し、その下に Packages ディレクトリを作成、その中にバージョンを抜きにしたディレクトリが見えるようにします。こうして作成した親のディレクトリ(ここでは ShaderInclude)を hlsl.additionalIncludeDirectories に書き込むようにします。

最終的な JSON

{
    "hlsl.preprocessorDefinitions": {},
    "hlsl.additionalIncludeDirectories": [
        "D:\\Applications\\2020.1.17f1\\Editor\\Data\\CGIncludes",
        "ShaderIncludes"
    ]
}

こんなものがルートディレクトリに出来上がります。これで冒頭の GIF のように補完が効くようになります。

追記(2021/01/12)

プロジェクトのディレクトリも含むように修正しました。Include Project にチェックを入れてください。Assets/ からのパス指定でインクルードしたヘッダも補完対象になります。

github.com

f:id:hecomi:20210112225425p:plain

その他

拡張

現状はまだユースケースが見定められていないので、hlsl.preprocessorDefinitions の方は手つかずです。マクロなどの切り替えが出来たりすれば良かったりしますかね。アイディア募集中です。

インクルードディレクトリの追加

シンプルな実装なので上記 2 種類以外のディレクトリは現状含める口が有りません。生成後、直接 JSON をいじる必要があります。もしこういうケースで利用したいなど需要がありましたら教えて下さい。

MiniJSON への依存

JsonUtility では、キーが任意の値(パッケージ名など)を取るような package-local.json の構造がパースできなかったので MiniJSON へ依存しました。

VSCode について

marketplace.visualstudio.com

設定の json 自体は同じなのでこちらでも動くと思っています(未検証)。

おわりに

ちょっと面倒だったシェーダの読み書きが便利になりました。これで HDRP 周りの調査をもう少し進めていきたいと思います。