シェーダコードも怖くない?UEのCustomノードで学ぶHLSL入門

125.3K Views

June 14, 24

スライド概要

本スライドは2024年5月25日(土)に開催したゲーム開発者向けのリアルイベント『ゲームメーカーズ スクランブル2024』で行われた講演のスライドとなります。

タイトル:
シェーダコードも怖くない?UEのCustomノードで学ぶHLSL入門

内容:
本講演はシェーダコードに対する苦手意識を克服するための第一歩として、UEマテリアルのCustomノードを利用して高級シェーダ言語であるHLSLの使い方、読み方、面白い使い方などを解説します。

登壇者:
グラフィクスエンジニア
もんしょ 氏

講演動画も公開中!
https://youtu.be/PAiU21nG_N4

【アーカイブ記事】https://gamemakers.jp/article/2024_06_14_69370/
【イベントページ】https://gamemakers.jp/scramble2024/
【イベントレポート記事】https://gamemakers.jp/article/2024_06_04_69158/

profile-image

ゲームづくりに役立つ情報をお届けする「ゲームメーカーズ」の資料公開用アカウント。 WEBメディア「ゲームメーカーズ」では、ゲーム開発TIPSや”作り手目線”のインタビュー、お得なセール情報などを毎日更新! http://gamemakers.jp

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

シェーダコードも怖くない? UEのCustomノードで学ぶHLSL入門

2.

自己紹介 もんしょ (X:@monsho1977) 「もんしょの巣穴」管理人 フリーランスのグラフィクスエンジニア いくつかの書籍を共著で執筆

3.

お品書き  本講演の目的  シェーダとは?  シェーダの学習資料  HLSLコード解説  Unreal Engineでのシェーダの扱い  Customノードの使い方  Customノードでポストプロセスを実装する  Customノードのちょっといい話

4.

本講演の目的

5.

本講演の目的  シェーダコードへの苦手意識を克服する第一歩  ノードベースシェーダのおかげでシェーダは身近になった  これまでシェーダはグラフィクスエンジニアの領域  一部ではあるが非グラフィクスエンジニア、アーティストが作れる  シェーダコードは相変わらず遠い存在  苦手意識のあるエンジニア、TAも多いのでは?  しかし、不具合調査や最適化で読まなければならない場面も

6.

本講演の目的  そもそもなぜ苦手意識があるのか?  グラフィクス技術がそもそも難しい  前後関係がわかりにくい  なんとなくシェーダ言語は難しいと思っている  実はそうでもない  書いていれば自然と覚える  書く機会を増やしてみよう!

7.

シェーダとは?

8.

シェーダとは? Wikipediaより  シェーダという言葉の登場はDirectX 8からと思われる  プログラマブルシェーダから  それまでの固定機能は固定シェーダと呼ばれるようになる  初期のシェーダはアセンブリ言語  高級言語HLSLの登場はDirectX 9から

9.

シェーダとは? 出力 頂点入力 頂点変換 ラスタライズ ピクセル計算

10.

シェーダとは? 出力 頂点入力 DirectX 8/9 頂点変換 頂点シェーダ ラスタライズ ピクセル計算 ピクセルシェーダ

11.

シェーダとは? 出力 頂点入力 頂点変換 DirectX 8/9 頂点シェーダ DirectX 10 ジオメトリシェーダ ラスタライズ ピクセル計算 ピクセルシェーダ

12.

シェーダとは? 出力 頂点入力 頂点変換 DirectX 8/9 頂点シェーダ DirectX 10 ジオメトリシェーダ DirectX 11 テッセレータ ラスタライズ ピクセル計算 ピクセルシェーダ コンピュートユニット コンピュートシェーダ

13.

シェーダとは? 出力 頂点入力 頂点変換 ラスタライズ ピクセル計算 DirectX 8/9 頂点シェーダ ピクセルシェーダ DirectX 10 ジオメトリシェーダ DirectX 11 テッセレータ コンピュートシェーダ DirectX 12 メッシュシェーダ レイトレーシング コンピュートユニット ワークグラフ

14.

シェーダの学習資料

15.

シェーダの学習資料  書籍  『Direct3D 12 ゲームグラフィックス実践ガイド』  『DirectX 12の魔導書』  『HLSL シェーダーの魔導書』  DirectX 12のAPIを含む解説書なので、C++側も知りたい人向け  Shadertoy  https://www.shadertoy.com  GLSLコードをブラウザ上で実行可能  ピクセルシェーダのみ  ユーザー制作のコードが多数  移植する場合、コードのライセンスには注意

16.

シェーダの学習資料  GitHub  DirectX-Graphics-Samples  https://github.com/microsoft/DirectX-Graphics-Samples  マイクロソフト謹製  D3D12Samples  https://github.com/Monsho/D3D12Samples  もんしょの巣穴で記事にしているプログラムサンプル  DirectX 11世代のものは巣穴の記事から  その他いっぱい  Unityカスタムシェーダ  https://docs.unity3d.com/ja/2023.2/Manual/SLVertexFragmentShaderExamples.html  マテリアルやポストプロセスを直接コードで作成することが可能  特殊な記述はあるが、言語仕様はHLSL準拠

17.

シェーダの学習資料  第21回UE5ぷちコン応募作品  https://github.com/Monsho/BarrierPusher  ゲームプレイ部分をCompute Shaderで実装  CPUでの実装はGPUドライブ、入力取得、サウンドくらい

18.

HLSLコード解説

19.

HLSLコード解説  HLSLはDirectXで使用されるシェーダ言語  High Level Shading Language  言語仕様はC言語ライク

20.

HLSLコード解説:ベクトル、行列  組み込み型のベクトル、行列  float / int などのあとに次数を指定  ベクトル:float4 / int2 など  行列:float3x4 など  次数最大は4

21.

HLSLコード解説:ベクトル、行列  組み込み型のベクトル、行列表現  コンポーネントへのアクセスは .xyzw / .rgba を利用する  .xyzw と .rgba を混ぜて利用することはできない

22.

HLSLコード解説:ベクトル、行列  組み込み型のベクトル、行列表現  コンポーネントの入れ替えが簡単 ←ノードで作るとこんな感じ

23.

HLSLコード解説:組み込み関数  数学関係の組み込み関数が多く、ベクトル・スカラー両対応が多い  ベクトルの次元は合わせる必要がある  スカラーはベクトルの代わりに利用できる

24.

HLSLコード解説:修飾子  一部の命令に修飾子を指定できる  詳しくはおまけに

25.

HLSLコード解説:ドキュメント  その他の詳細は公式ドキュメント参照  https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphicshlsl  日本語ドキュメントは問題が多いので英語推奨  特にリファレンスの翻訳がひどすぎる 例) 関数名(min)が日本語翻訳され、 しかも最小値なのに時間の分と間違っている

26.

Unreal Engineでのシェーダの扱い

27.

Unreal Engineでのシェーダの扱い  Unreal Engineではシェーダコードを記述可能なものがいくつかある  .usf, .ushファイル  マテリアルのCustomノード  Niagara Scratch PadモジュールのCustom HLSLノード  注意!  UEFNではこれらを使用できない!

28.

Unreal Engineでのシェーダの扱い  .usf, .ushファイル  直接シェーダコードを記述するファイル  .ushはヘッダファイルとして扱う  エンジン実装のほとんどのシェーダはこのファイルで記述  エンジン調査、改造時に真っ先に調べることになる  サポートされているシェーダならほぼ記述可能  自前のファイルを追加する場合、実装部分も自前で記述する必要がある  一部の描画パスを置き換えることが可能  TSRを置き換えるDLSS / FSR実装など  お手軽ではないため中級者以上向け

29.

Unreal Engineでのシェーダの扱い  マテリアルのCustomノード  マテリアルはノードベースだが、最終的にはシェーダコードに展開される  [Window]->[Shader Code]->[HLSL Code]で 展開後コードを見ることができる  Customノードで直接記述できる  マテリアルコードに直接展開される  注意点はあるが、お手軽に利用可能  本講演で解説

30.

Unreal Engineでのシェーダの扱い  Niagara Scratch PadモジュールのCustom HLSLノード  マテリアルのCustomノードと似ている  Customノードより高性能  マテリアルのCustomノードをこの形式にしてほしい…  エミッターやパーティクルの各種パラメータ計算、シミュレーションなど  Niagara Fluidsの流体シミュレーションなどに利用されている  お手軽に使えるが、Niagara自体が少し難しい  慣れるととても楽しい

31.

Customノードの使い方

32.

Customノードの使い方  Customノードの詳細パネル情報  Code:シェーダコードの入力欄  Output Type:Customノードの出力の型  Description:Customノードの詳細  ノード名になるので適切な名前を入力しておくことを推奨  Inputs:入力引数  引数名のみ入力  Additional Outputs:2つ目以降の出力パラメータ

33.

Customノードの使い方  使用例  以下のノードをCustomノードで再現してみる

34.

Customノードの使い方  使用例  Customノードを配置  詳細パネルで以下のように変更  Output Typeを “CMOT Float 2” に変更  Inputsを1つ追加(初期のものと含めて2つ)  1つ目の名前を “A”、2つ目を “B” とする  入力コネクタにテクスチャ座標とスカラーパラメータを接続  詳細パネルのコードを以下のように変更  出力をDebugFloat2Valuesに接続

35.

Customノードの使い方  使用例  結果

36.

Customノードの使い方  展開されたコードを見てみる  Customノードのコード  呼び出し側  CustomExpressionXという名前の関数が自動で作成される  関数名の数字はCustomノードの処理順序によって変更される

37.

Customノードの使い方  テクスチャサンプル  例)

38.

Customノードの使い方  Customノードでの実装  Customノードを追加し、以下の設定を行う  Output Typeを”CMOT Float 3”に設定  Inputsに”TexA”と”TexCoord”を追加  TexAにテクスチャオブジェクト、TexCoordにテクスチャ座標を接続  コードは以下のように設定

39.

Customノードの使い方  結果

40.

Customノードの使い方  注意  テクスチャサンプラーは”パラメータ名”+”Sampler”  この例ではパラメータ名が”TexA”なので”TexASampler”となります  テクスチャサンプル命令”Texture2DSample”関数について  HLSLではテクスチャサンプルは以下のように行う  マテリアルの利用先がHLSLでコンパイルされるとは限らない  HLSLに依存しすぎるとプラットフォームによっては動作しないかもしれない  テクスチャ配列、ボリュームテクスチャはまた別の関数を用いる  Virtual Textureには現在非対応

41.

Customノードの使い方  For Loop  Customノードを使わなければ難しい処理の代表格  例) テクスチャのボックスブラー

42.

Customノードの使い方  Customノードを利用しての実装

43.

Customノードでポストプロセスを実装する

44.

Customノードでポストプロセスを実装する  Kuwaharaフィルタ  絵画調フィルタとして有名 適用前 適用後

45.

Customノードでポストプロセスを実装する

46.

Customノードでポストプロセスを実装する

47.

Customノードでポストプロセスを実装する  簡単な解説 B A • 処理するピクセルPに対して4方向の正方形エリアを選択 • それぞれABCDエリアとする • 各エリアの色の平均、及び分散を計算 • 分散 = 二乗平均 – 平均の二乗 • 分散が最も小さなエリアの平均をピクセルPに適用 P C D

48.

Customノードでポストプロセスを実装する  ポストプロセス入力のテクスチャサンプル  ポストプロセス入力はテクスチャとしてCustomノードに入力できない  どうすればいい?  ポストプロセスをサンプルしてHLSLコードを読む  SceneTextureLookupという命令でサンプリングしている

49.

Customノードでポストプロセスを実装する  各エリアの平均と分散を求める 入力パラメータ • Count:エリアの縦横ピクセル数 • LeftTop:エリアの左上UV座標 • TexelSize:テクセルサイズ 出力パラメータ • Average:色の平均 • Variance:分散

50.

Customノードでポストプロセスを実装する  最小分散のエリアを選択する  前述の処理を4エリアで行う  分散が小さい場合は平均を更新する

51.

Customノードでポストプロセスを実装する  CenterColorってなんだ?

52.

Customノードでポストプロセスを実装する  CenterColorを削除してみた

53.

Customノードでポストプロセスを実装する  使用するノードによって暗黙的に追加される関数や変数がある  SceneTextureノードを利用するとSceneTextureLookupが追加される  最終結果につながるどこかで接続されている必要がある  置いてあるだけではダメ

54.

Customノードのちょっといい話

55.

Customノードのちょっといい話  Customノードの結果はCustomExpressionという関数名になる  CustomExpressionの後に連番の数字(CustomExpression0、 CustomExpression1…)  この関数名で呼び出せばそのまま利用できる  Parametersという引数が自動的に追加されるので渡すのを忘れずに  頂点シェーダとピクセルシェーダで型が違うことに注意 上のCustomノードが繋がっていないと関数が追加されないので 無理やり接続するために0を乗算してから加算 結果

56.

Customノードのちょっといい話  別のCustomノードを追加したらどうなる?  結果が変わってしまった! 結果

57.

Customノードのちょっといい話  コード比較 EmissiveColorなし EmissiveColorあり

58.

Customノードのちょっといい話  ”Include File Paths”を利用する  ただし、シェーダパスとして登録しておく必要がある  シェーダパスを追加するためのモジュールをプロジェクトに追加  プラグインでもOK  StartupModuleメンバ関数でシェーダパスを追加

59.

Customノードのちょっといい話  登録したパスに.ushファイルを追加  例) Kuwaharaフィルタの平均と分散を求める関数 MyShader.ush Customノードのコード

60.

Customノードのちょっといい話  Customノードのコード展開を利用する手法  Customノードのコード展開は2つに分解できる  エンジンが自動的に付与する部分  Customノードのコードで記述する部分 エンジンは、 ・ Customノードのコードの上に関数名と中括弧初めを追加 ・ Customノードのコードの下に中括弧閉じを追加

61.

Customノードのちょっといい話  Customノードのコードをこのように書いたらどうなる?

62.

Customノードのちょっといい話  正解はこちら  MyShaderFunction関数が追加されている!  展開後のコードがシェーダコード的に正しいなら問題ない

63.

Customノードのちょっといい話  これを利用して複雑な関数を追加

64.

Customノードのちょっといい話  Kuwahara Functionsの中身

65.

Customノードのちょっといい話  展開後のコード

66.

Customノードのちょっといい話  独自関数利用についてのまとめと注意点  CustomExpressionを直接呼び出すのは避ける  モジュールを追加してインクルードファイルを利用しましょう  モジュール追加は面倒ですが、プラグインにしておくと使いまわしやすい  Customノードだけで関数を追加する手段はハック的手法  いつ使えなくなるかわからない  UE3から使えた手法らしいので、今後もそのまま使えそう  Customノードの接続順が重要になる  関数名はユニークなものに  プロジェクト名やモジュール名を利用しよう

67.

Customノードのちょっといい話  クラッシュしやすいCustomノード  コードを編集してたら…  入力パラメータを追加してたら…  入力パラメータにノードを接続したら…  回避方法はない  コードは別のエディタで書いてからコピーする  Visual Studio Codeがオススメ  細かく保存  入力パラメータを追加したら接続前にとりあえず保存

68.

最後に シェーダコード、怖くないよ 楽しいよ こっちにおいで

69.

おまけ

70.

Divergent Branch  シェーダの分岐は遅い?  昔から言われている有名な話  これ本当?  理解するために、GPUの振る舞いをおさらいしてみる  GPUが高速と言われる理由を理解しよう

71.

Divergent Branch  CPUの場合  複数コアが存在  コア1つにつき1~2のスレッド(処理の流れ)を実行  スレッドはそれぞれ別の処理を実行可能 CPUコア0 ゲーム メイン 物理 演算 CPUコア1 音楽 再生 ゲーム AI CPUコア2 描画 OS 図は一例 実際にはスレッドはより多くのものがあり、各コアは優先度に応じてスレッドを実行する

72.

Divergent Branch  GPUの場合  複数コアが存在  コア1つにつき32~のスレッドを実行  スレッドはすべて同じ処理を、同じタイミングで実行  命令の実行タイミングも終了タイミングもすべて一緒  この実行単位をWaveと呼ぶ(DirectXの場合) GPUコア0 すべて同じ処理

73.

Divergent Branch  おじいさんは山へ芝刈り、おばあさんは川に洗濯  川の上流からおおきな桃が流れてきた!  おばあさんは桃を取得し、帰宅  おじいさんは芝刈り終了後に帰宅  このような処理に対して、CPUとGPUの動作の違いは?

74.

Divergent Branch  CPUの場合 おじいさんとおばあさんは別々の仕事を行う おじいさんの仕事とおばあさんの仕事は無関係 山で芝刈り 川で洗濯 桃ゲットだぜ! 帰宅 帰宅

75.

Divergent Branch  GPUの場合 おじいさんとおばあさんは切り離せない なので一緒の仕事を行う 山で芝刈り 本来はおじいさんの仕事 おばあさんはやらなくてもいい 川で洗濯 本来はおばあさんの仕事 おじいさんはやらなくてもいい 帰宅

76.

Divergent Branch  おじいさんとおばあさんは別スレッドと考える  CPUではスレッドごとに仕事を変更できる  芝刈りと洗濯は無関係なので並列に動作できる  おじいさんが芝刈りをしている最中におばあさんが洗濯可能  GPUではスレッドごとに仕事を変更できない  おじいさんとおばあさんは切り離せない  おじいさんとおばあさんで手分けして芝刈り、その後手分けして洗濯  桃をゲットしたら二人で帰宅  仲睦まじいね!

77.

Divergent Branch  手分けしての仕事がうまくいくとは限らない  芝刈りを手分けしてたら頻繁にぶつかってしまってむしろ非効率!  洗濯してたら上流での洗濯汚れが下流の洗濯に影響を与えてしまった!  やっぱり一人ずつ作業したほうが効率いいね!  しかしGPUではおじいさんとおばあさんは分けられない  どうする?

78.

Divergent Branch  Divergent Branch 山で芝刈り おやすみ 川で洗濯 おやすみ 帰宅 帰宅 片方が仕事している間、 もう片方はおやすみ

79.

Divergent Branch  Divergent Branch  GPU独特の分岐処理  分岐を実行しないスレッドはマスクされて休止状態となる  分岐が終了したら同じ命令を実行する  分岐を実行したくないスレッドが存在する場合とは?  テクスチャサンプルなどメモリアクセスが発生する場合  メモリアクセスはデータのやり取りが必要なため時間がかかる  不要なデータのやり取りは渋滞の原因となる  現代GPUにおいてメモリアクセスによる渋滞は大きな問題  単純な計算処理では分岐しないほうが速い場合が多い  この場合、分岐の両方の処理を行い、どちらかを選択する  メモリアクセスが発生しなければGPUの計算処理内部だけで完結する  複雑な計算の場合、その限りではない

80.

フロー制御命令の修飾子  if文の[branch]、[flatten]  [branch]はDivergent Branchを行う  [flatten]は両方の処理を実行し、どちらかを選択する  for文の[loop]、[unroll]  [loop]はループ処理をそのままにする  [unroll]はループ処理を展開して、最大回数まで実行する  これらはあくまでもコンパイラに対するヒント  コンパイラは独自の基準で処理を選択する  例えば[unroll]と指定していても、ループ回数が不定の場合は展開されない 場合もある

81.

UEマテリアルグラフのif命令  これってどうなの?

82.

UEマテリアルグラフのif命令  正解  両方処理して選択する  if文は使わない  選択される2つの値の一方、もしくは両方の処理負荷が高い場合は要注意  テクスチャサンプル  ノイズ命令  ループ処理の結果  などなど

83.

Static Switch ParameterのDynamic Branch  Static Switch Parameterの詳細設定に存在  通常、Bool値を切り替えると別のシェーダが生成される  Dynamic Branch有効でシェーダを生成せずに切替可能  内部実装はどうなっている?

84.

Static Switch ParameterのDynamic Branch  正解  [branch]付きSwitch文に置き換えられる  動的分岐になるからDivergent Branchになってくれる?  この書き方では問題がある

85.

Static Switch ParameterのDynamic Branch  Compiler Explorerで確認してみる  https://godbolt.org  いろいろなプログラミング言語のコンパイル結果を表示してくれるサイト  以下のコードをコンパイルしてみる

86.

Static Switch ParameterのDynamic Branch  結果 テクスチャサンプルが2回 分岐が使われず、select命令で選択されている Divergent Branchは使われていない

87.

Static Switch ParameterのDynamic Branch  一度変数に格納しているのが悪い  分岐命令内でテクスチャサンプルするとどうなるか? 分岐命令 テクスチャサンプルは 分岐ごと Divergent Branchが使われている

88.

Static Switch ParameterのDynamic Branch  エンジン改造できちんとしたDynamic Branchを実装することが可能  EGJ鈴木さんによる記事(ちょっと古いがまだ使えると思われる)  https://qiita.com/suzuki_takashi/items/88f033d07669da81f6eb  パフォーマンスには注意する  Divergent Branchは常に高速な訳ではない  負荷の高い処理に対しては有効な場合が多いが、常にそうでもない  基本方針として、負荷の高いシェーダで分岐が適切か調査  変更前後のプロファイルをきちんと行うこと  プラットフォームやハードによっても違いが出るかも