Skip to main content

シェーダー

シェーダーはGPU(グラフィックスカード)で実行する特別なプログラムである。シェーダーを使うことにより、3Dモデルの「見せ方」(描画)などを自由に変えることができる。ただし、GPUで実行するプログラムは普通のCPUのプログラムと異なり、使い方を学ばないといけない。

GPUとCPUの違い

CPUは「多目的のプロセッサー」で、タスクを切り替えるのは得意な要素である。例えば、Chromeでネットを調べ、見つけたソースコードをVisual Studioに貼り付けたい。プログラムを切り替えるときに、CPUは一旦Chromeを休ませて、Visual Studioを起こして、作業をし続ける。

 一方、GPUは「専門的な部品」である。GPUは同時に1つのタスクしかできず、実行中に切り替えることができない。代わりにそのタスクを大変高速に実行ができる。


CPU GPU
目的 多目的。同時に様々なプログラムを実行できる(マルチタスク) 専門的。同時に1つのタスクしかできない(描画のみ)※1
タスク切り替え 実行中にタスクを切り替えできる タスクが終わってから切り替えることができる
切り替えるコスト 低い 高い
条件分岐(if) 条件により、実行の流れを切り替えることができる。 できない※2
並列化

コアが空いていれば、ある程度並列化できる

非常に強い(千~万単位のコアがある)

※1:計算専用の「Compute Shader」というものが存在する。計算シェーダーは描画のためではなく、並列化し、速くなる処理に使える。
※2:「if」命令があるが、実行コストが高くて、できるだけ使わないようにしましょう。

GPUとCPUの協力:シェーダーの流れ

 OS(Windows)は、ゲームをハードディスクからメモリに転送し、CPUはメモリから1行ずつの命令を読み、実行する。今まで作ってきたスクリプトのすべてはこの流れで実行する。

 ただ、ゲームを実行中に「これからGPUのプログラム(シェーダー)が始まる」という特別な命令が現れる可能性がある。この時、CPUは一旦ゲームを休んで、そのプログラムをGPUに転送する。転送し終わったら、ゲームをもう一度起こして、普通の動作に戻る。

TODO:切り替えの流れの画像をついか!

この「一旦止まり→転送→続く」の処理効率が悪いため、GPUのタスク(プログラム)を切り替えるコストが高いといわれる。従って、できるだけゲーム開始の時に必要なシェーダーをGPUに転送し、その後にあまり変わらないようにすれば良いでしょう。

 Unityはある程度にシェーダーの転送をしてくれるため、実装しなくても大丈夫だが、ゲームエンジンにより、手動でシェーダーの転送をおこなわないといけない。

 シェーダー(プログラム)の他に、必要なデータも転送しなければならない。シェーダーの場合は、主なデータは3Dモデルの頂点や三角形、または位置や変換などである。Unityの「標準シェーダー」の場合はすべてのデータを自動的に転送されるが、「カスタムシェーダー」の場合は、一部のデータを手動で送信する必要な時もある。

TODO:転送画像をついか!

 これからUnityのシェーダーについて学び、そして特殊な効果を再現するため、カスタムシェーダーの作り方を学ぼう。

シェーダーとマテリアルの関係性

Unityでは「シェーダー」と「マテリアル」2つの概念がある。シェーダーはGPUで実行するプログラム。マテリアルは「一部のパラメータを設定したシェーダー」のこと。例えば、シェーダーに「Color」というパラメータがあるとしたら、マテリアルでは「Color=Red」にする。

 従って、1つのシェーダーからたくさんのマテリアルを作成することができる。例えば、以下の「赤いプラ」「輝く金属」と「草と土」が全く同じシェーダー(Universal Render Pipeline / Lit)を使用しているが、パラメター(色、金属性、滑らか性、など)を変えることによって、様々な表面を作ることが可能。

image.png

URP(Universal Rendering Pipeline)のシェーダー

UnityのURPモードは、すぐ使えるたくさんのシェーダーがあるので、最も使うシェーダーについてを細かく見てみましょう。

光と陰の計算あり(Universal Render Pipeline / Lit)

image.png

URP で新しいマテリアルを作成すると、このシェーダーになる。いくつかのパラメータがあるので、よく使うのを確認しましょう。

Workflow Mode

反射の計算方法。2つがある:

  • 「Metallic」は膨大な素材を再現できる。主には金属を再現するのは得意
  • 「Specular」軽くて速い。金属を再現できず、主にプラスチックの雰囲気を再現するモード
Surface Type

透明設定。2つがある:

  • 「Opaque」不透明。計算するは速い
  • 「Transparent」半透明。後ろのあるものも計算しないといけないので、比較的に遅い
Render Face

見えない三角形(裏側)も描画するかしないか

  • 「Front」表のみ
  • 「Back」裏のみ
  • 「Both」両面
Alpha Clipping

透明度(Alpha)はある数値以下をし下回ると透明になる。この設定は「不透明(Opaque)」でも効く

image.png

 

image.png

image.png

透明(Alpha)を持っている画像

Alpha Clippingなし

Alpha Clippingあり

Base Map

模様と色。画像(テクスチャ)を使わないなら、一定の色になるが、テクスチャを使うと模様を付けることができる。

image.png

Base Mapなし(色のみ)

image.png
テクスチャ

image.pngBase Mapあり

なお、テクスチャを正しく貼り付けるため「UV座標」が必要である。「UV座標」については、また後で説明する。

Metallic Map / Specular Map

金属(またはプラスチック)である部分・ではない部分のテクスチャ。全体的に変わりがなければテクスチャは不要であるが、部分的に変えたいなら、このテクスチャを設定:

image.png

Metallic Map なし

image.png

テクスチャ

image.png

Metallic Map あり

Smoothness

表面のなめらかさ。数値が高いほど表面が輝くパラメータ

Normal Map

法線マップのテクスチャ。光の計算を変えるテクスチャであり、細かい凸凹を再現するための特別な画像。

image.png
Normal Map なし

image.png
法線マップ

image.pngNormal Map あり

Emission

光る色とテクスチャ(実際に光らないが、光るようにみえる)

image.png

Emission なし

image.png

Emmision テクスチャ

image.png

Emissionあり(色:緑)

光と陰の計算なし(Universal Render Pipeline / Unlit)

image.png

陰は計算されない。UIと2Dゲームなどに便利である。

光と陰の計算あり・複雑(Universal Render Pipeline / Complex Lit)

image.png

「Lit」(光計算あり)と同じで、さらに「Clear Coat」というパラメータがある。「Clear Coat」は、車のペンキのように、透明な輝くレイヤ(コーティング)を付けることができる。

image.png

パーティクル専用(光の計算あり・なし)

image.png

image.png

パーティクルエフェクト専用のシェーダー。軽くて速いので、たくさんのパーティクルを作成してもあまり重たくならない。光を計算するとしないバージョンがある。

シェーダの種類

シェーダはいくつかの種類がある:

  • Fragment Shader(フラグメントシェーダー)三角形のピクセル(フラグメント)ごとに実行するプログラム(シェーダー)である。モデルの陰、色、模様などをこのシェーダで計算する。
  • Vertex Shader(バーテックス・頂点シェーダー)三角形の頂点ごとに実行するシェーダーである。頂点の回転、移動、スケールなどを調整できるシェーダー
  • Geometry Shader(三角形シェーダー)三角形ごとに実行するシェーダー。LOD(Level Of Detail)やTesselation(三角形再分割)に使ったりする
  • Compute Shader(計算用シェーダー)汎用的な計算をするためのシェーダー。並列化ができる処理があったら、GPUに転送し、速く処理できる。

ゲーム開発での使う頻度

  • フラグメントシェーダー:よく使う!モデルの変色、模様のアニメーションやエフェクトなどにカスタムシェーダーを作ることが多い
  • 頂点シェーダー:まぁまぁ使う。柔らかいものの変形(海の波、風による草の動き)などの時に便利
  • 三角形シェーダー:ほとんど使わない。ゲームエンジン(Unity)は自動的に操作する
  • 計算用シェーダー:あまり使わないが、何かの特別な計算を並列に実行したいなら使える。

シェーダの開発言語

シェーダーはGPUで実行するプログラムで、GPUを理解できる言語を使わないといけない。主に2つある:GLSL(GL Shader Language)とHLSL(High-Level Shader Language)。GLSLはOpenGL / Vulkan系のライブラリを使うシェーダー言語である。HLSL は Microsoft社が開発した「DirectX」をベースにしたライブラリで使用する言語である。

 Unity は HLSLを基づいた「ShaderLab」という言語を使う。HLSLで使える命令はほとんどそのままUnityで使えるようになり、その上に様々な便利な機能がすでに実装されている。例えば、モデルの変換(移動、回転、スケール)や光の計算は実装済みであり、それ以外に機能だけ実装すれば、カスタムシェーダーを作成ができる。

 また、よりも楽にシェーダーを作るには、Unityは「Shader Graph」も対応し、ノードをつなげるだけでシェーダーを作成ができる。

 GLSL / HLSL / ShaderLabのどれでも、だいたいC言語と似ている。ただし:

  • if文を使わないでほしい(一応できるが、計算のコストが高い)
  • 使える関数ははGPU専用のものを使用(テクスチャ・画像の読み方が異なる)
  • デバッグしにくい:「Debug.Log」がないし、実行中に止めることができない(ブレーキポイントを使えない)
  • すべてのプログラムは「フラグメントごと」または「頂点ごと」に実行することを忘れないでほしい。

Unityでシェーダー実行する方法

シェーダーは「フラグメントごと」、または「頂点ごと」に実行するため、当然、シェーダーとモデルを連携しないといけない。Unityでは、マテリアル(Material)経由でモデルとシェーダーのひも付けが行われる。マテリアルを作成したあと、シェーダーを変えることにより、そのマテリアルを使うモデル(メッシュ)をそのシェーダーで処理する。

image.png

ShaderLab / HLSL

ShaderLab および HLSL は全く新しい言語であり、学ぶ時間がないので、興味がある学生は個別でお調べください。以下、参考できる URL をご確認ください:

代わりに、もう少し直感的な「Shader Graph」でシェーダーの基礎を学び、フラグメントシェーダーと頂点シェーダを把握できるようになったら、また ShaderLab と HLSL 言語についてお調べください。

Shader Graph

プロジェクト設定

ShaderLabの自由度が高く、とてもパワフルなシェーダの作り方であるが、やや難しいところもある。複雑で高速なシェーダが必要なら、ShaderLabの方が良いと思われるが、単純なシェーダなら、Shader Graphの方が楽で、作るのも速くなる。

 Shader Graphは言語ではなく、「ノード」というブロックを紐でつなげることにより、視覚的にシェーダを作成ができる。

 まず、Shader Graphをインストールしましょう。Unityの Package Manger から Shader Graph をインストールしてください。

image.png

初めてのシェーダーグラフ

まず、空のシェーダを作成しましょう。「Project」の中に、専用の「Shader」フォルダーを作成し、その中に「Lit Shader Graph」を作成してください。このシェーダーは後ほど使うので名前を「Chessboard」にしてください。

image.png

新しく作ったシェーダーグラフをダブルクリックすると、シェーダーエディタが開く:

image.png

各部品を理解しましょう:

頂点シェーダー(Vertex)

頂点シェーダーは、メッシュの各頂点の位置、または法線ベクトルを変えることができるシェーダーである。マテリアルを設定するだけで、3Dモデルの変形ができるシェーダー:

image.png

フラグメントシェーダー(Fragment)

フラグメントシェーダーは、メッシュの各ピクセルの色、法線ベクトル、金属性などを変えることができるシェーダーである。マテリアルを設定するだけで、3Dモデルの見え方を変えれるシェーダー:

image.png

ブラックボード(Blackboard)

パラメータを作成ができるところ。Unityのインスペクターで変えることができる数値をここに追加する。

image.png

グラフ・インスペクター(Graph Inspector)

普通のインスペクターと同様に、現在選択されているものに関する情報や数値を確認し、変えることができるパネル:

ノード設定(ノードによって変わる) グラフ設定(全般設定)

image.png

image.png

プレビュー(Main Preview)

シェーダの結果のプレビュー

image.png

様々なシェーダを作ろう!

Shader Graph で使える「ノード」を紹介しながら、いろんな効果を再現し、3Dモデルの見せ方を変えてみましょう。

無限チェスボード

image.png

考え方:「XZ平面の位置により、白いコマ、または黒いコマを表示したい」

ならば、3D平面の各ピクセルは3D空間の中にどこにあるのかを求める必要がある。その場合は、以下のノードを使える:

Positionノード

image.png

Positionノード

    Vertexと繋ぐと:各頂点の位置 Fragmentと繋ぐと:各ピクセルの位置

    入力

      なし

      設定

        Space:どの位置を使う?
          World:3Dシーンの絶対位置 Object:GameObject内の相対位置 View:カメラから見た位置

          出力

            Out(3):XYZの位置

            Positionノードを Base Color(Fragment)と繋ぐと:

            image.png

            image.png

            ってどういうこと?実は、「位置」を「色」に書き換えた。元々の位置はXYZとして表現していたものを、そのままRGBに書き換えて、表示しているだけ。つまり:

              X=R→X軸の位置が大きくなると、色が赤くなる Y=G→Y軸の位置が大きくなると、色が緑になる Z=B→Z軸の位置が大きくなると、色が青くなる

              確認:平面の位置を変えて、色がどう変わるのか、確認してみてください。
              ※ポイント:色はいつも0~1の間で表現するので、位置は「1」を超えると最大値になり、それ以上色が変わらない

               Positionノードで位置を取れることが分かったが、なかなかチェスボードにならない。問題を分けて、少しずつ解決してみましょう。まず、X軸だけで白黒の「しましま」のパターンを作成しましょう。YとZは要らないので、新しいノードが必要:

              Split ノード

              image.png

              Splitノード

                ベクトルを成分に分割する

                入力

                  In(1):2D, 3Dまたは4Dのベクトル

                  設定

                    なし

                    出力

                      R(1):赤色、またはX軸 G(1):緑色、またはY軸 B(1):青色、またはZ軸 A(1):アルファ値、または4Dの4つ目の成分(W)

                      グラフを直して、Position→Split(R)→Base Colorをつなげてみましょう:

                      image.png

                      image.png

                      X軸の数値だけしか残らないので、Xの位置が大きいほど、白くなる!(RGBはすべてXの数値になる)。ちょっと近づいた気がしますが、しましまのパターンになっていない。何か周期的に繰り返すものがないか?

                      image.png

                      この図は「sin(θ)」のグラフであり、2πごとになみのように繰り返す関数である!x軸はどれだけ大きくなっても、出力は必ず-1~1の間になるので、次のノードを追加しましょう:

                      Sine ノード

                      image.png

                      Sineノード

                        ベクトルを成分に分割する

                        入力

                          In(1):角度(ラジアン)

                          設定

                            なし

                            出力

                              Out(1):サイン関数の結果

                              Position→Split(R)→Sine→Base Colorにすると:

                              image.png

                              image.png

                              しましまのパターンが現れた!…が、色がなめらかに変わる。確かに、Sinのグラフを見ると、-1〜1の間にゆっくり変わる。「白→黒→白→黒」の2色だけがほしい、以下のように:

                              image.png

                              つまり、「sin」が「0以上」だったら、「1」にし、「0未満」の場合は、「0」とする(青い線)。これは、「Step」ノードで実現できる!

                              Step ノード

                              image.png

                              Stepノード

                                入力した値を「しきい値」以上だと「1」を返す、以下だと「0」を返す

                                入力

                                  Edge(1):しきい値 In(1):入力値

                                  設定

                                    なし

                                    出力

                                      Out(1):「0」か「1」

                                      Sineの後にStepノードを挟んで、確認しましょう。Edge は「0」にしてください。

                                      image.png

                                      image.png

                                      かなりよくなった!ただし、1つの「ストライプ」はUnityのグリッドと比べると、サイズが合わない。なぜなら、Unityのグリッドの間隔は「1単位」なのだが、sin の周期は π(約3.1416)である。これは簡単に解決できる。sin の周期は Unity の1単位にすれば良いでしょう。つまり、1つの単位をπ倍数にすれば良いということです。掛け算のノードは:

                                      Multiply ノード

                                      image.png

                                      Multiplyノード

                                        2つの数値の掛け算を返す

                                        入力

                                          A(1):1つ目の数値、またはベクトル B(1):2つ目の数値、またはベクトル

                                          設定

                                            なし

                                            出力

                                              Out(1)
                                                数値と数値:A × B 
                                                  10 x 4 = 40 数値とベクトル:数値と各成分でできる新しいベクトル
                                                    3 x [1, 2, 3] = [3, 6, 9] ベクトルの場合は、各成分の掛け算を返す
                                                      例:[1, 2, 3] × [4, 5, 6] = [4, 10, 18]

                                                      Split → Multiply → Sine を繋ぎ、「B」は「3.1416」にしておいてください:

                                                      image.png

                                                      image.png

                                                      Sineの周期をUnityの単位に合わせたので、1つのストライプはグリッドにぴったり!

                                                      これで、チェスボードの半分ができた!Z方向の処理は同じになるので、ノードを重複してもOK!(Splitノードの(R)の代わりに(B)を使ってください)

                                                      image.png

                                                      では、ZとXどう合成すれば良いでしょう?Zは「黒」の場合は、Xのストライプはそのままにして、Zは「白」になったら、Xの色を反転してほしい(白↔黒)

                                                      image.png

                                                      色は「白:1」「黒:0」というのを分かっているので、XとZの差分(X-Z)を計算してみましょう:

                                                      X方向の値(色) Z方向の値(色) X-Z 期待 1(白) 0(黒) 1(白) 1:✓ 0(黒) 0(黒) 0(黒) 0:✓ 1(白) 1(白) 0(黒) 0:✓ 0(黒) 1(白) -1(?) 1:×

                                                      最後だけは「1」ではなく、「-1」になってしまうので、絶対値を取れば直せる。以下の2つのノードで「X」と「Z」を合成しましょう: 

                                                      Subtract ノード

                                                      image.png

                                                      Subtract ノード

                                                        2つの数値の引き算を返す

                                                        入力

                                                          A(1):1つ目の数値、またはベクトル B(1):2つ目の数値、またはベクトル

                                                          設定

                                                            なし

                                                            出力

                                                              Out(1)
                                                                数値と数値:A - B 
                                                                  10 - 4 = 6 数値とベクトル:数値と各成分でできる新しいベクトル
                                                                    3 - [1, 2, 3] = [2, 1, 0] ベクトルの場合は、各成分の引き算を返す
                                                                      例:[1, 2, 3] - [4, 5, 6] = [-3, -3, -3]
                                                                      Absolute ノード

                                                                      image.png

                                                                      Absolute ノード

                                                                        絶対値を返す

                                                                        入力

                                                                          In(1):数値、またはベクトル

                                                                          設定

                                                                            なし

                                                                            出力

                                                                              Out(1):数値、また各成分の絶対値

                                                                              X方向とZ方向の差分+絶対値を計算し、Base Colorと繋ぐと:

                                                                              image.png

                                                                              image.png

                                                                              レインボーキューブ

                                                                              image143.gif

                                                                              このシェーダは、オブジェクトのUVを変えて、テクスチャをアニメーションするシェーダである。まず、「rainbow」のテクスチャをドラッグドロップでシェーダに追加する。自動的に、Sample Texture 2Dのノードを作成される: