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 をご確認ください:

          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 をインストールしてください。

          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

                    ってどういうこと?実は、「位置」を「色」に書き換えた。元々の位置は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)