シェーダー
シェーダーは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)を使用しているが、パラメター(色、金属性、滑らか性、など)を変えることによって、様々な表面を作ることが可能。

URP(Universal Rendering Pipeline)のシェーダー
UnityのURPモードは、すぐ使えるたくさんのシェーダーがあるので、最も使うシェーダーについてを細かく見てみましょう。
光と陰の計算あり(Universal Render Pipeline / Lit)

URP で新しいマテリアルを作成すると、このシェーダーになる。いくつかのパラメータがあるので、よく使うのを確認しましょう。
Workflow Mode
反射の計算方法。2つがある:
- 「Metallic」は膨大な素材を再現できる。主には金属を再現するのは得意
- 「Specular」軽くて速い。金属を再現できず、主にプラスチックの雰囲気を再現するモード
Surface Type
透明設定。2つがある:
- 「Opaque」不透明。計算するは速い
- 「Transparent」半透明。後ろのあるものも計算しないといけないので、比較的に遅い
Render Face
見えない三角形(裏側)も描画するかしないか
- 「Front」表のみ
- 「Back」裏のみ
- 「Both」両面
Alpha Clipping
透明度(Alpha)はある数値以下をし下回ると透明になる。この設定は「不透明(Opaque)」でも効く
|
|
|
|
|
透明(Alpha)を持っている画像 |
Alpha Clippingなし |
Alpha Clippingあり |
Base Map
模様と色。画像(テクスチャ)を使わないなら、一定の色になるが、テクスチャを使うと模様を付けることができる。
|
Base Mapなし(色のみ) |
|
|
|
|
なお、テクスチャを正しく貼り付けるため「UV座標」が必要である。「UV座標」については、また後で説明する。
Metallic Map / Specular Map
金属(またはプラスチック)である部分・ではない部分のテクスチャ。全体的に変わりがなければテクスチャは不要であるが、部分的に変えたいなら、このテクスチャを設定:
|
Metallic Map なし |
テクスチャ |
|
Metallic Map あり |
|
Smoothness
表面のなめらかさ。数値が高いほど表面が輝くパラメータ
Normal Map
法線マップのテクスチャ。光の計算を変えるテクスチャであり、細かい凸凹を再現するための特別な画像。
|
|
|
|
|
|
Emission
光る色とテクスチャ(実際に光らないが、光るようにみえる)
|
Emission なし |
Emmision テクスチャ |
|
Emissionあり(色:緑) |
|
光と陰の計算なし(Universal Render Pipeline / Unlit)

陰は計算されない。UIと2Dゲームなどに便利である。
光と陰の計算あり・複雑(Universal Render Pipeline / Complex Lit)

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

パーティクル専用(光の計算あり・なし)
|
|
|
パーティクルエフェクト専用のシェーダー。軽くて速いので、たくさんのパーティクルを作成してもあまり重たくならない。光を計算するとしないバージョンがある。
シェーダの種類
シェーダはいくつかの種類がある:
- 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)経由でモデルとシェーダーのひも付けが行われる。マテリアルを作成したあと、シェーダーを変えることにより、そのマテリアルを使うモデル(メッシュ)をそのシェーダーで処理する。

ShaderLab / HLSL
ShaderLab および HLSL は全く新しい言語であり、学ぶ時間がないので、興味がある学生は個別でお調べください。以下、参考できる URL をご確認ください:
- UnityのShader(ShaderLab/HLSL)入門その1 : Shaderの書き方
- 【Unity入門】シェーダーを書いてみよう!ShaderLabの簡単な書き方!
- 【Unity Shader入門①】ShaderLabとShader Graphの基礎を解説!【初心者向け】
代わりに、もう少し直感的な「Shader Graph」でシェーダーの基礎を学び、フラグメントシェーダーと頂点シェーダを把握できるようになったら、また ShaderLab と HLSL 言語についてお調べください。
Shader Graph
プロジェクト設定
ShaderLabの自由度が高く、とてもパワフルなシェーダの作り方であるが、やや難しいところもある。複雑で高速なシェーダが必要なら、ShaderLabの方が良いと思われるが、単純なシェーダなら、Shader Graphの方が楽で、作るのも速くなる。
Shader Graphは言語ではなく、「ノード」というブロックを紐でつなげることにより、視覚的にシェーダを作成ができる。
まず、Shader Graphをインストールしましょう。Unityの Package Manger から Shader Graph をインストールしてください。

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

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

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

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

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

グラフ・インスペクター(Graph Inspector)
普通のインスペクターと同様に、現在選択されているものに関する情報や数値を確認し、変えることができるパネル:
| ノード設定(ノードによって変わる) | グラフ設定(全般設定) |
|
|
|
プレビュー(Main Preview)
シェーダの結果のプレビュー

様々なシェーダを作ろう!
Shader Graph で使える「ノード」を紹介しながら、いろんな効果を再現し、3Dモデルの見せ方を変えてみましょう。
無限チェスボード

考え方:「XZ平面の位置により、白いコマ、または黒いコマを表示したい」
ならば、3D平面の各ピクセルは3D空間の中にどこにあるのかを求める必要がある。その場合は、以下のノードを使える:
Positionノード
|
Positionノード
入力
設定
出力
|
Positionノードを Base Color(Fragment)と繋ぐと:


ってどういうこと?実は、「位置」を「色」に書き換えた。元々の位置はXYZとして表現していたものを、そのままRGBに書き換えて、表示しているだけ。つまり:
- X=R→X軸の位置が大きくなると、色が赤くなる
- Y=G→Y軸の位置が大きくなると、色が緑になる
- Z=B→Z軸の位置が大きくなると、色が青くなる
確認:平面の位置を変えて、色がどう変わるのか、確認してみてください。
※ポイント:色はいつも0~1の間で表現するので、位置は「1」を超えると最大値になり、それ以上色が変わらない
Positionノードで位置を取れることが分かったが、なかなかチェスボードにならない。問題を分けて、少しずつ解決してみましょう。まず、X軸だけで白黒の「しましま」のパターンを作成しましょう。YとZは要らないので、新しいノードが必要:
Split ノード
|
|
Splitノード
入力
設定
出力
|
グラフを直して、Position→Split(R)→Base Colorをつなげてみましょう:


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

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


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

つまり、「sin」が「0以上」だったら、「1」にし、「0未満」の場合は、「0」とする(青い線)。これは、「Step」ノードで実現できる!
Step ノード
|
|
Stepノード
入力
設定
出力
|
Sineの後にStepノードを挟んで、確認しましょう。Edge は「0」にしてください。


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


Sineの周期をUnityの単位に合わせたので、1つのストライプはグリッドにぴったり!
これで、チェスボードの半分ができた!Z方向の処理は同じになるので、ノードを重複してもOK!(Splitノードの(R)の代わりに(B)を使ってください)

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

色は「白: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 ノード
|
Subtract ノード
入力
設定
出力
|
Absolute ノード
|
|
Absolute ノード
入力
設定
出力
|
X方向とZ方向の差分+絶対値を計算し、Base Colorと繋ぐと:

レインボーキューブ

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





Base Mapあり


















