97.5K 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/
ゲームづくりに役立つ情報をお届けする「ゲームメーカーズ」の資料公開用アカウント。 WEBメディア「ゲームメーカーズ」では、ゲーム開発TIPSや”作り手目線”のインタビュー、お得なセール情報などを毎日更新! http://gamemakers.jp
シェーダコードも怖くない? UEのCustomノードで学ぶHLSL入門
自己紹介 もんしょ (X:@monsho1977) 「もんしょの巣穴」管理人 フリーランスのグラフィクスエンジニア いくつかの書籍を共著で執筆
お品書き 本講演の目的 シェーダとは? シェーダの学習資料 HLSLコード解説 Unreal Engineでのシェーダの扱い Customノードの使い方 Customノードでポストプロセスを実装する Customノードのちょっといい話
本講演の目的
本講演の目的 シェーダコードへの苦手意識を克服する第一歩 ノードベースシェーダのおかげでシェーダは身近になった これまでシェーダはグラフィクスエンジニアの領域 一部ではあるが非グラフィクスエンジニア、アーティストが作れる シェーダコードは相変わらず遠い存在 苦手意識のあるエンジニア、TAも多いのでは? しかし、不具合調査や最適化で読まなければならない場面も
本講演の目的 そもそもなぜ苦手意識があるのか? グラフィクス技術がそもそも難しい 前後関係がわかりにくい なんとなくシェーダ言語は難しいと思っている 実はそうでもない 書いていれば自然と覚える 書く機会を増やしてみよう!
シェーダとは?
シェーダとは? Wikipediaより シェーダという言葉の登場はDirectX 8からと思われる プログラマブルシェーダから それまでの固定機能は固定シェーダと呼ばれるようになる 初期のシェーダはアセンブリ言語 高級言語HLSLの登場はDirectX 9から
シェーダとは? 出力 頂点入力 頂点変換 ラスタライズ ピクセル計算
シェーダとは? 出力 頂点入力 DirectX 8/9 頂点変換 頂点シェーダ ラスタライズ ピクセル計算 ピクセルシェーダ
シェーダとは? 出力 頂点入力 頂点変換 DirectX 8/9 頂点シェーダ DirectX 10 ジオメトリシェーダ ラスタライズ ピクセル計算 ピクセルシェーダ
シェーダとは? 出力 頂点入力 頂点変換 DirectX 8/9 頂点シェーダ DirectX 10 ジオメトリシェーダ DirectX 11 テッセレータ ラスタライズ ピクセル計算 ピクセルシェーダ コンピュートユニット コンピュートシェーダ
シェーダとは? 出力 頂点入力 頂点変換 ラスタライズ ピクセル計算 DirectX 8/9 頂点シェーダ ピクセルシェーダ DirectX 10 ジオメトリシェーダ DirectX 11 テッセレータ コンピュートシェーダ DirectX 12 メッシュシェーダ レイトレーシング コンピュートユニット ワークグラフ
シェーダの学習資料
シェーダの学習資料 書籍 『Direct3D 12 ゲームグラフィックス実践ガイド』 『DirectX 12の魔導書』 『HLSL シェーダーの魔導書』 DirectX 12のAPIを含む解説書なので、C++側も知りたい人向け Shadertoy https://www.shadertoy.com GLSLコードをブラウザ上で実行可能 ピクセルシェーダのみ ユーザー制作のコードが多数 移植する場合、コードのライセンスには注意
シェーダの学習資料 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準拠
シェーダの学習資料 第21回UE5ぷちコン応募作品 https://github.com/Monsho/BarrierPusher ゲームプレイ部分をCompute Shaderで実装 CPUでの実装はGPUドライブ、入力取得、サウンドくらい
HLSLコード解説
HLSLコード解説 HLSLはDirectXで使用されるシェーダ言語 High Level Shading Language 言語仕様はC言語ライク
HLSLコード解説:ベクトル、行列 組み込み型のベクトル、行列 float / int などのあとに次数を指定 ベクトル:float4 / int2 など 行列:float3x4 など 次数最大は4
HLSLコード解説:ベクトル、行列 組み込み型のベクトル、行列表現 コンポーネントへのアクセスは .xyzw / .rgba を利用する .xyzw と .rgba を混ぜて利用することはできない
HLSLコード解説:ベクトル、行列 組み込み型のベクトル、行列表現 コンポーネントの入れ替えが簡単 ←ノードで作るとこんな感じ
HLSLコード解説:組み込み関数 数学関係の組み込み関数が多く、ベクトル・スカラー両対応が多い ベクトルの次元は合わせる必要がある スカラーはベクトルの代わりに利用できる
HLSLコード解説:修飾子 一部の命令に修飾子を指定できる 詳しくはおまけに
HLSLコード解説:ドキュメント その他の詳細は公式ドキュメント参照 https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphicshlsl 日本語ドキュメントは問題が多いので英語推奨 特にリファレンスの翻訳がひどすぎる 例) 関数名(min)が日本語翻訳され、 しかも最小値なのに時間の分と間違っている
Unreal Engineでのシェーダの扱い
Unreal Engineでのシェーダの扱い Unreal Engineではシェーダコードを記述可能なものがいくつかある .usf, .ushファイル マテリアルのCustomノード Niagara Scratch PadモジュールのCustom HLSLノード 注意! UEFNではこれらを使用できない!
Unreal Engineでのシェーダの扱い .usf, .ushファイル 直接シェーダコードを記述するファイル .ushはヘッダファイルとして扱う エンジン実装のほとんどのシェーダはこのファイルで記述 エンジン調査、改造時に真っ先に調べることになる サポートされているシェーダならほぼ記述可能 自前のファイルを追加する場合、実装部分も自前で記述する必要がある 一部の描画パスを置き換えることが可能 TSRを置き換えるDLSS / FSR実装など お手軽ではないため中級者以上向け
Unreal Engineでのシェーダの扱い マテリアルのCustomノード マテリアルはノードベースだが、最終的にはシェーダコードに展開される [Window]->[Shader Code]->[HLSL Code]で 展開後コードを見ることができる Customノードで直接記述できる マテリアルコードに直接展開される 注意点はあるが、お手軽に利用可能 本講演で解説
Unreal Engineでのシェーダの扱い Niagara Scratch PadモジュールのCustom HLSLノード マテリアルのCustomノードと似ている Customノードより高性能 マテリアルのCustomノードをこの形式にしてほしい… エミッターやパーティクルの各種パラメータ計算、シミュレーションなど Niagara Fluidsの流体シミュレーションなどに利用されている お手軽に使えるが、Niagara自体が少し難しい 慣れるととても楽しい
Customノードの使い方
Customノードの使い方 Customノードの詳細パネル情報 Code:シェーダコードの入力欄 Output Type:Customノードの出力の型 Description:Customノードの詳細 ノード名になるので適切な名前を入力しておくことを推奨 Inputs:入力引数 引数名のみ入力 Additional Outputs:2つ目以降の出力パラメータ
Customノードの使い方 使用例 以下のノードをCustomノードで再現してみる
Customノードの使い方 使用例 Customノードを配置 詳細パネルで以下のように変更 Output Typeを “CMOT Float 2” に変更 Inputsを1つ追加(初期のものと含めて2つ) 1つ目の名前を “A”、2つ目を “B” とする 入力コネクタにテクスチャ座標とスカラーパラメータを接続 詳細パネルのコードを以下のように変更 出力をDebugFloat2Valuesに接続
Customノードの使い方 使用例 結果
Customノードの使い方 展開されたコードを見てみる Customノードのコード 呼び出し側 CustomExpressionXという名前の関数が自動で作成される 関数名の数字はCustomノードの処理順序によって変更される
Customノードの使い方 テクスチャサンプル 例)
Customノードの使い方 Customノードでの実装 Customノードを追加し、以下の設定を行う Output Typeを”CMOT Float 3”に設定 Inputsに”TexA”と”TexCoord”を追加 TexAにテクスチャオブジェクト、TexCoordにテクスチャ座標を接続 コードは以下のように設定
Customノードの使い方 結果
Customノードの使い方 注意 テクスチャサンプラーは”パラメータ名”+”Sampler” この例ではパラメータ名が”TexA”なので”TexASampler”となります テクスチャサンプル命令”Texture2DSample”関数について HLSLではテクスチャサンプルは以下のように行う マテリアルの利用先がHLSLでコンパイルされるとは限らない HLSLに依存しすぎるとプラットフォームによっては動作しないかもしれない テクスチャ配列、ボリュームテクスチャはまた別の関数を用いる Virtual Textureには現在非対応
Customノードの使い方 For Loop Customノードを使わなければ難しい処理の代表格 例) テクスチャのボックスブラー
Customノードの使い方 Customノードを利用しての実装
Customノードでポストプロセスを実装する
Customノードでポストプロセスを実装する Kuwaharaフィルタ 絵画調フィルタとして有名 適用前 適用後
Customノードでポストプロセスを実装する
Customノードでポストプロセスを実装する
Customノードでポストプロセスを実装する 簡単な解説 B A • 処理するピクセルPに対して4方向の正方形エリアを選択 • それぞれABCDエリアとする • 各エリアの色の平均、及び分散を計算 • 分散 = 二乗平均 – 平均の二乗 • 分散が最も小さなエリアの平均をピクセルPに適用 P C D
Customノードでポストプロセスを実装する ポストプロセス入力のテクスチャサンプル ポストプロセス入力はテクスチャとしてCustomノードに入力できない どうすればいい? ポストプロセスをサンプルしてHLSLコードを読む SceneTextureLookupという命令でサンプリングしている
Customノードでポストプロセスを実装する 各エリアの平均と分散を求める 入力パラメータ • Count:エリアの縦横ピクセル数 • LeftTop:エリアの左上UV座標 • TexelSize:テクセルサイズ 出力パラメータ • Average:色の平均 • Variance:分散
Customノードでポストプロセスを実装する 最小分散のエリアを選択する 前述の処理を4エリアで行う 分散が小さい場合は平均を更新する
Customノードでポストプロセスを実装する CenterColorってなんだ?
Customノードでポストプロセスを実装する CenterColorを削除してみた
Customノードでポストプロセスを実装する 使用するノードによって暗黙的に追加される関数や変数がある SceneTextureノードを利用するとSceneTextureLookupが追加される 最終結果につながるどこかで接続されている必要がある 置いてあるだけではダメ
Customノードのちょっといい話
Customノードのちょっといい話 Customノードの結果はCustomExpressionという関数名になる CustomExpressionの後に連番の数字(CustomExpression0、 CustomExpression1…) この関数名で呼び出せばそのまま利用できる Parametersという引数が自動的に追加されるので渡すのを忘れずに 頂点シェーダとピクセルシェーダで型が違うことに注意 上のCustomノードが繋がっていないと関数が追加されないので 無理やり接続するために0を乗算してから加算 結果
Customノードのちょっといい話 別のCustomノードを追加したらどうなる? 結果が変わってしまった! 結果
Customノードのちょっといい話 コード比較 EmissiveColorなし EmissiveColorあり
Customノードのちょっといい話 ”Include File Paths”を利用する ただし、シェーダパスとして登録しておく必要がある シェーダパスを追加するためのモジュールをプロジェクトに追加 プラグインでもOK StartupModuleメンバ関数でシェーダパスを追加
Customノードのちょっといい話 登録したパスに.ushファイルを追加 例) Kuwaharaフィルタの平均と分散を求める関数 MyShader.ush Customノードのコード
Customノードのちょっといい話 Customノードのコード展開を利用する手法 Customノードのコード展開は2つに分解できる エンジンが自動的に付与する部分 Customノードのコードで記述する部分 エンジンは、 ・ Customノードのコードの上に関数名と中括弧初めを追加 ・ Customノードのコードの下に中括弧閉じを追加
Customノードのちょっといい話 Customノードのコードをこのように書いたらどうなる?
Customノードのちょっといい話 正解はこちら MyShaderFunction関数が追加されている! 展開後のコードがシェーダコード的に正しいなら問題ない
Customノードのちょっといい話 これを利用して複雑な関数を追加
Customノードのちょっといい話 Kuwahara Functionsの中身
Customノードのちょっといい話 展開後のコード
Customノードのちょっといい話 独自関数利用についてのまとめと注意点 CustomExpressionを直接呼び出すのは避ける モジュールを追加してインクルードファイルを利用しましょう モジュール追加は面倒ですが、プラグインにしておくと使いまわしやすい Customノードだけで関数を追加する手段はハック的手法 いつ使えなくなるかわからない UE3から使えた手法らしいので、今後もそのまま使えそう Customノードの接続順が重要になる 関数名はユニークなものに プロジェクト名やモジュール名を利用しよう
Customノードのちょっといい話 クラッシュしやすいCustomノード コードを編集してたら… 入力パラメータを追加してたら… 入力パラメータにノードを接続したら… 回避方法はない コードは別のエディタで書いてからコピーする Visual Studio Codeがオススメ 細かく保存 入力パラメータを追加したら接続前にとりあえず保存
最後に シェーダコード、怖くないよ 楽しいよ こっちにおいで
おまけ
Divergent Branch シェーダの分岐は遅い? 昔から言われている有名な話 これ本当? 理解するために、GPUの振る舞いをおさらいしてみる GPUが高速と言われる理由を理解しよう
Divergent Branch CPUの場合 複数コアが存在 コア1つにつき1~2のスレッド(処理の流れ)を実行 スレッドはそれぞれ別の処理を実行可能 CPUコア0 ゲーム メイン 物理 演算 CPUコア1 音楽 再生 ゲーム AI CPUコア2 描画 OS 図は一例 実際にはスレッドはより多くのものがあり、各コアは優先度に応じてスレッドを実行する
Divergent Branch GPUの場合 複数コアが存在 コア1つにつき32~のスレッドを実行 スレッドはすべて同じ処理を、同じタイミングで実行 命令の実行タイミングも終了タイミングもすべて一緒 この実行単位をWaveと呼ぶ(DirectXの場合) GPUコア0 すべて同じ処理
Divergent Branch おじいさんは山へ芝刈り、おばあさんは川に洗濯 川の上流からおおきな桃が流れてきた! おばあさんは桃を取得し、帰宅 おじいさんは芝刈り終了後に帰宅 このような処理に対して、CPUとGPUの動作の違いは?
Divergent Branch CPUの場合 おじいさんとおばあさんは別々の仕事を行う おじいさんの仕事とおばあさんの仕事は無関係 山で芝刈り 川で洗濯 桃ゲットだぜ! 帰宅 帰宅
Divergent Branch GPUの場合 おじいさんとおばあさんは切り離せない なので一緒の仕事を行う 山で芝刈り 本来はおじいさんの仕事 おばあさんはやらなくてもいい 川で洗濯 本来はおばあさんの仕事 おじいさんはやらなくてもいい 帰宅
Divergent Branch おじいさんとおばあさんは別スレッドと考える CPUではスレッドごとに仕事を変更できる 芝刈りと洗濯は無関係なので並列に動作できる おじいさんが芝刈りをしている最中におばあさんが洗濯可能 GPUではスレッドごとに仕事を変更できない おじいさんとおばあさんは切り離せない おじいさんとおばあさんで手分けして芝刈り、その後手分けして洗濯 桃をゲットしたら二人で帰宅 仲睦まじいね!
Divergent Branch 手分けしての仕事がうまくいくとは限らない 芝刈りを手分けしてたら頻繁にぶつかってしまってむしろ非効率! 洗濯してたら上流での洗濯汚れが下流の洗濯に影響を与えてしまった! やっぱり一人ずつ作業したほうが効率いいね! しかしGPUではおじいさんとおばあさんは分けられない どうする?
Divergent Branch Divergent Branch 山で芝刈り おやすみ 川で洗濯 おやすみ 帰宅 帰宅 片方が仕事している間、 もう片方はおやすみ
Divergent Branch Divergent Branch GPU独特の分岐処理 分岐を実行しないスレッドはマスクされて休止状態となる 分岐が終了したら同じ命令を実行する 分岐を実行したくないスレッドが存在する場合とは? テクスチャサンプルなどメモリアクセスが発生する場合 メモリアクセスはデータのやり取りが必要なため時間がかかる 不要なデータのやり取りは渋滞の原因となる 現代GPUにおいてメモリアクセスによる渋滞は大きな問題 単純な計算処理では分岐しないほうが速い場合が多い この場合、分岐の両方の処理を行い、どちらかを選択する メモリアクセスが発生しなければGPUの計算処理内部だけで完結する 複雑な計算の場合、その限りではない
フロー制御命令の修飾子 if文の[branch]、[flatten] [branch]はDivergent Branchを行う [flatten]は両方の処理を実行し、どちらかを選択する for文の[loop]、[unroll] [loop]はループ処理をそのままにする [unroll]はループ処理を展開して、最大回数まで実行する これらはあくまでもコンパイラに対するヒント コンパイラは独自の基準で処理を選択する 例えば[unroll]と指定していても、ループ回数が不定の場合は展開されない 場合もある
UEマテリアルグラフのif命令 これってどうなの?
UEマテリアルグラフのif命令 正解 両方処理して選択する if文は使わない 選択される2つの値の一方、もしくは両方の処理負荷が高い場合は要注意 テクスチャサンプル ノイズ命令 ループ処理の結果 などなど
Static Switch ParameterのDynamic Branch Static Switch Parameterの詳細設定に存在 通常、Bool値を切り替えると別のシェーダが生成される Dynamic Branch有効でシェーダを生成せずに切替可能 内部実装はどうなっている?
Static Switch ParameterのDynamic Branch 正解 [branch]付きSwitch文に置き換えられる 動的分岐になるからDivergent Branchになってくれる? この書き方では問題がある
Static Switch ParameterのDynamic Branch Compiler Explorerで確認してみる https://godbolt.org いろいろなプログラミング言語のコンパイル結果を表示してくれるサイト 以下のコードをコンパイルしてみる
Static Switch ParameterのDynamic Branch 結果 テクスチャサンプルが2回 分岐が使われず、select命令で選択されている Divergent Branchは使われていない
Static Switch ParameterのDynamic Branch 一度変数に格納しているのが悪い 分岐命令内でテクスチャサンプルするとどうなるか? 分岐命令 テクスチャサンプルは 分岐ごと Divergent Branchが使われている
Static Switch ParameterのDynamic Branch エンジン改造できちんとしたDynamic Branchを実装することが可能 EGJ鈴木さんによる記事(ちょっと古いがまだ使えると思われる) https://qiita.com/suzuki_takashi/items/88f033d07669da81f6eb パフォーマンスには注意する Divergent Branchは常に高速な訳ではない 負荷の高い処理に対しては有効な場合が多いが、常にそうでもない 基本方針として、負荷の高いシェーダで分岐が適切か調査 変更前後のプロファイルをきちんと行うこと プラットフォームやハードによっても違いが出るかも