
マイケル
みなさんこんにちは!
マイケルです!
マイケルです!

エレキベア
こんにちクマ〜〜〜

マイケル
今回はUnityのシェーダーについて触れていきます!
シェーダーについては何度か記事にしていますが、Unity特有の事項もいくつかあるためその辺りを重点的に見ていこうと思います。
シェーダーについては何度か記事にしていますが、Unity特有の事項もいくつかあるためその辺りを重点的に見ていこうと思います。

エレキベア
ついにシェーダークマ〜〜〜
これまでの記事ではC++で開発する時に使っていたクマね
これまでの記事ではC++で開発する時に使っていたクマね

マイケル
Unityのシェーダー基礎について触れた後、他の記事と同様にLambert拡散反射を実装するところまでやっていこうと思います!
シェーダーについての基礎知識については上記の記事もご参照ください!
シェーダーについての基礎知識については上記の記事もご参照ください!

エレキベア
シェーダーは毎度ランバートから始まるクマね

マイケル
なお、今回使用したUnityのバージョンは以下になります!
バージョン違いやURPで記述する際には少し異なる場合もあるため、ご了承ください!
バージョン違いやURPで記述する際には少し異なる場合もあるため、ご了承ください!
Unity | 2021.3.1f1 |

エレキベア
こればっかりは仕方ないクマ〜〜・・・
シェーダーとは

マイケル
まずシェーダーとはそもそも何か?という点についてなのですが、
基本的には 描画する際の陰影処理を行うプログラムのこと になります!
しかし、シェーダーで実装出来る範囲はかなり広くなっていて、陰影だけでなく頂点の増減やポリゴン分割をしたり、見た目のトゥーン調にするといったことも出来るようになってきました、
例としてDirectX11の描画パイプラインは下記のようになります。
基本的には 描画する際の陰影処理を行うプログラムのこと になります!
しかし、シェーダーで実装出来る範囲はかなり広くなっていて、陰影だけでなく頂点の増減やポリゴン分割をしたり、見た目のトゥーン調にするといったことも出来るようになってきました、
例としてDirectX11の描画パイプラインは下記のようになります。
↑DirectX11の描画パイプライン

エレキベア
シェーダーの種類もかなり多いクマね
描画全般で使用するプログラムと言ってもよさそうクマ
描画全般で使用するプログラムと言ってもよさそうクマ

マイケル
今はシェーダー芸と呼ばれるほどの表現もできるくらいだからね・・・
GLSLだけど、ShaderToyというサイトで表現の広さは合間見えるのでこちらを見てみるのもおすすめです!
GLSLだけど、ShaderToyというサイトで表現の広さは合間見えるのでこちらを見てみるのもおすすめです!

エレキベア
これ全部シェーダーで実装されているのクマか・・・

マイケル
しかし、これだけシェーダーの種類があっても、主流は「頂点シェーダー」「フラグメントシェーダー(ピクセルシェーダー)」になります。
受け取った座標情報を頂点シェーダーで編集し、その情報をもとにフラグメントシェーダーで色合いを決めるといった流れで、シェーダーを書くというのは基本的にこの辺りの処理を書くことを示すのが多いです。
受け取った座標情報を頂点シェーダーで編集し、その情報をもとにフラグメントシェーダーで色合いを決めるといった流れで、シェーダーを書くというのは基本的にこの辺りの処理を書くことを示すのが多いです。
↑頂点シェーダー、フラグメントシェーダーの役割

エレキベア
Unityでもこの部分の処理を実装できるクマね

マイケル
この辺りの詳しい話については、Unityの下記動画が分かりやすいので是非ご参照ください!
シェーダを理解しよう | Unity Learning Materials

エレキベア
安原さんの説明は本当分かりやすいクマ〜〜
Unityシェーダーの基礎知識

マイケル
それではここからUnity特有の話に入っていきます!
シェーダーの作成方法

マイケル
まずはシェーダーの作成方法の種類について!
こちらはプログラムを書く方法と、ShaderGraphといったノードベースエディタを使用する方法があります。
こちらはプログラムを書く方法と、ShaderGraphといったノードベースエディタを使用する方法があります。
シェーダープログラムを書く | Cg/HLSLといった言語を使用して実装する |
ノードベースエディタを使用する | ShaderGraphといったツールを使用して実装する |

エレキベア
ShaderGraphは流行りクマね〜〜〜

マイケル
ShaderGraphは、視覚的に確認しながら実装できるのが強みです!
ただ、複雑になるほどノードも煩雑になってしまうのでそこは注意が必要です。。
また、シェーダー言語はGLSLも使用できるようですが、Cg/HLSLの使用が推奨されているようです。
ただ、複雑になるほどノードも煩雑になってしまうのでそこは注意が必要です。。
また、シェーダー言語はGLSLも使用できるようですが、Cg/HLSLの使用が推奨されているようです。

マイケル
ただ基本的な考え方はどちらも同じなので、個人的にはどちらから触ってもよいと思っています!
今回は内部が分かりやすいよう、コードベースで解説していこうと思います。
今回は内部が分かりやすいよう、コードベースで解説していこうと思います。

エレキベア
ShaderGraphで遊んでから入るのも有りかもしれないクマね
シェーダープログラムの実装方法

マイケル
次は具体的なシェーダープログラムの実装方法について!
こちらは頂点、フラグメントシェーダーを書く方法の他、サーフェイスシェーダー、固定関数シェーダーというものも用意されています。
こちらは頂点、フラグメントシェーダーを書く方法の他、サーフェイスシェーダー、固定関数シェーダーというものも用意されています。
頂点シェーダーとフラグメントシェーダー | 基本的に全て自分で書く一般的な方法 |
サーフェイスシェーダー | ライティングをある程度Unityに任せることができる(※URPでは非推奨) |
固定関数シェーダー | 用意された機能を組み合わせて描画する・・・ようだが多分使わない |

マイケル
ただ、サーフェイスシェーダーはURPでは非対応になっていてShaderGraphへの移行が推奨されているようなので、
コードで書く場合には頂点シェーダーとフラグメントシェーダーを使用する方法を取るのがよさそうです。
コードで書く場合には頂点シェーダーとフラグメントシェーダーを使用する方法を取るのがよさそうです。

エレキベア
サーフェイスシェーダーでの実装例も多いクマからここは注意クマね
その他のシェーダー

マイケル
また、実際にシェーダーを作成しようとすると上記以外にも選択できるShaderがあると思います。

マイケル
UnlitShader、StandardSurfaceShaderは先ほど解説した標準のシェーダーとサーフェイスシェーダになります。
その他3つのシェーダーは下記のようになっています。
その他3つのシェーダーは下記のようになっています。
Image Effect Shader | ポストプロセス用に用意されたテンプレート |
Compute Shader | GPUを計算目的で使用するためのもの(GPGPU) |
Ray Tracing Shader | レイトレーシングを行うためのもの |

マイケル
今回は特に触れませんが、この辺りも後々触っていこうと思います!

エレキベア
ComputeShaderはよく聞くし触ってみたいクマね
簡単なシェーダープログラムの実装

マイケル
前提知識を学んだところで、早速コードを見ていきましょう!
Unlit Shaderの作成

マイケル
まずは Shader > Unlit Shader からシェーダーコードを作成します。
Unlitは陰影処理を行わずにそのまま出力するシンプルなシェーダーになります。
Unlitは陰影処理を行わずにそのまま出力するシンプルなシェーダーになります。

マイケル
今回は SimpleUnlitShaderという名前で作成しました。
作成後は下記のようにマテリアルでも選択できるようになっているはずです。
作成後は下記のようにマテリアルでも選択できるようになっているはずです。

マイケル
このマテリアルをオブジェクトに設定すると、デフォルトでは下記のように真っ白に表示される状態になっています。
ひとまずこれで準備は完了です。
ひとまずこれで準備は完了です。

エレキベア
ここからコードを見ていくクマ〜〜〜
ShaderLabとTags

マイケル
作成されたコードを見ると、下記のように見慣れない文法が出てきて初めは戸惑うと思います。
これはUnity特有のShaderLabという構文で、ここでプロパティやシェーダーの基本的な設定を行う形となります。
「CGPROGRAM」「ENDCG」で囲まれた部分が実際に実行されるプログラムで、こちらにCg/HLSLで実装していきます。
これはUnity特有のShaderLabという構文で、ここでプロパティやシェーダーの基本的な設定を行う形となります。
「CGPROGRAM」「ENDCG」で囲まれた部分が実際に実行されるプログラムで、こちらにCg/HLSLで実装していきます。
↑UnityのシェーダーはShaderLab構文でラップされている

エレキベア
Unityで使用するために囲む必要があるクマね

マイケル
それぞれの構文の意味は下記のようになっています。
一つ一つ見てみるとそこまで難しくはないですね。
一つ一つ見てみるとそこまで難しくはないですね。
Shader | シェーダー宣言 | シェーダーの種類と名前を定義する。 |
Properties | プロパティ宣言 | シェーダーで設定できるプロパティを定義する。 |
SubShader | シェーダーの実行単位 | 複数のSubShaderを定義でき、LOD等の実行条件から実行するSubShaderが選択、実行される。 |
Pass | 実行されるシェーダープログラム | ※左記そのままの意味 |

マイケル
少し複雑なのはSubShader内のTags指定です。
こちらはQueue(レンダリング順)やForceNoShaderCasting(シャドウの投影可否)といった設定を記述することができます。
公式マニュアルの他、下記の記事が分かりやすかったので引用させていただきます。
こちらはQueue(レンダリング順)やForceNoShaderCasting(シャドウの投影可否)といった設定を記述することができます。
公式マニュアルの他、下記の記事が分かりやすかったので引用させていただきます。
ShaderLab: SubShader 内のタグ | Unityマニュアル
SubShaderとPassの中で使用できるTagリスト | 渋谷ほととぎす通信

エレキベア
ここでもいろいろな設定ができるクマね

マイケル
そしてTagsはSubShader内だけでなく、Pass内にも記述することができます。
こちらはLightMode(ライト設定)、PassFlags(パスに渡すデータのフラグ管理)といった設定があります。
こちらはLightMode(ライト設定)、PassFlags(パスに渡すデータのフラグ管理)といった設定があります。
ShaderLab: Pass 内のタグ | Unityマニュアル

エレキベア
こればっかりは触りながら覚えていくしかなさそうクマね
Cg/HLSLの実装

マイケル
次は「CGPROGRAM」「ENDCG」で囲まれたシェーダープログラム部分を見てみましょう。
作成直後は下記のようになっていると思います。
作成直後は下記のようになっていると思います。
↑作成直後のシェーダープログラム(Cg/HLSL部分)

マイケル
デフォルトだとフォグの設定など不要な処理があって見にくいので、少し修正してコメントを付けてみました。
↑不要な処理を削除してコメントを付与したもの

マイケル
こうしてみるとだいぶ見やすくなったのではないでしょうか?
vert、flag関数がそれぞれ頂点シェーダー、フラグメントシェーダーの処理部分になっています。
vert、flag関数がそれぞれ頂点シェーダー、フラグメントシェーダーの処理部分になっています。

エレキベア
Unity以外のシェーダープログラムも大体こんな感じクマね
セマンティクスの定義

マイケル
structで定義された部分は、それぞれのシェーダーに渡すデータの定義になります。
「POSITION」「TEXCOORD0」といった部分はセマンティクスといって、データの意味を表すものになります。
「POSITION」「TEXCOORD0」といった部分はセマンティクスといって、データの意味を表すものになります。
↑データにはそれぞれセマンティクスが定義されている
Semantics – Win32 apps | Microsoft Learn

エレキベア
頂点位置やテクスチャ座標、法線座標とかいろいろあるのクマね
頂点シェーダーの実装

マイケル
頂点シェーダー部分では、受け取った座標をフラグメントシェーダーに渡すために変換しています。
頂点座標をクリップ座標に変換して、uv座標はScale、Tilingを考慮した変換を行なっています。
ここでUnity固有の関数がそれぞれ使用しているため、UnityCG.cgincのincludeが必要になってきます。
頂点座標をクリップ座標に変換して、uv座標はScale、Tilingを考慮した変換を行なっています。
ここでUnity固有の関数がそれぞれ使用しているため、UnityCG.cgincのincludeが必要になってきます。
↑頂点シェーダーの実装

マイケル
座標変換についてよく分からない方は下記記事をご参照ください!
最終的に描画するための2D座標に変換するためのプロセスで、UnityObjectToClipPosでも似たような変換を行なっていると思います。(おそらく)
最終的に描画するための2D座標に変換するためのプロセスで、UnityObjectToClipPosでも似たような変換を行なっていると思います。(おそらく)

エレキベア
(自信無さげクマ・・・)
フラグメントシェーダーの実装

マイケル
フラグメントシェーダーでは下記のように最終的な色を返しています。
今回は受け取ったUV座標からテクスチャを参照して返しているだけですね
今回は受け取ったUV座標からテクスチャを参照して返しているだけですね
↑フラグメントシェーダー

エレキベア
陰影を付ける時はここでわちゃわちゃやるクマね
設定したカラーをそのまま出力する

マイケル
イメージが分かったところで、「設定したカラーをそのまま出力する」シェーダーに改造してみましょう!
今回はSimpleColorShaderとして新たに作成し、下記のように実装してみました!
今回はSimpleColorShaderとして新たに作成し、下記のように実装してみました!

マイケル
各シェーダーに渡すデータはstructで定義されていましたが、このように引数や戻り値に直接記述することもできます。
極端に少ない場合はこちらの方が見やすいかもしれません。
極端に少ない場合はこちらの方が見やすいかもしれません。

エレキベア
だいぶシンプルで見やすくなったクマね

マイケル
後はこのシェーダーをマテリアルに設定して表示を見てみると・・

マイケル
このように単色で描画されることが確認できました!

エレキベア
第一ステップクリアクマ〜〜〜
Lambert拡散反射シェーダーの実装

マイケル
それでは最後に応用として、Lambert拡散反射シェーダーを実装していきます!

エレキベア
いよいよちゃんとしたシェーダーの実装クマね
Lambert拡散反射とは

マイケル
Lambert拡散反射とは、光のN次的な反射は考慮せずに、頂点に光源からの光が当たる量のみを考慮したモデルになります。
頂点からの法線をN、頂点から光源へ向かうベクトルをLとした場合、下記の計算で表すことができます。
頂点からの法線をN、頂点から光源へ向かうベクトルをLとした場合、下記の計算で表すことができます。

エレキベア
いわゆる擬似的なライティングというやつクマね

マイケル
簡単な計算で表せるから比較的計算負荷が軽いのも特徴だね
詳しくは下記の記事でも紹介していますので、こちらもよければご参照ください!
詳しくは下記の記事でも紹介していますので、こちらもよければご参照ください!

エレキベア
懐かしいクマ〜〜〜〜
シェーダープログラムの実装

マイケル
そんなこんなで実装したシェーダープログラムは下記のようになります!

エレキベア
これまでの話を踏まえるとだいぶ読めるようになってきたクマ〜〜

マイケル
今回はライトの情報が必要になるため、TagsでLightModeをForwardBaseに指定して、UnityLightingCommon.cgincをincludeしています。
こうすることで最も優先度の高いライト情報を_WorldSpaceLightPos0、_LightColor0で受け取れるようになります。
こうすることで最も優先度の高いライト情報を_WorldSpaceLightPos0、_LightColor0で受け取れるようになります。
↑ライト情報を受け取るための設定

エレキベア
Unityのライト情報はこんな感じで受け取るクマね

マイケル
なお、複数ライトを考慮する場合はForwardAddとしてパスを追加する必要があるようです。
こちらは下記の記事が分かりやすかったのでご参照ください!
こちらは下記の記事が分かりやすかったのでご参照ください!
【Unity】【シェーダ】Forward Renderingで複数のライトを取り扱う | LIGHT11

エレキベア
ライティングはまたUnity固有で覚えないといけないことがいろいろありそうクマね・・・

マイケル
あとはこのライト情報と法線情報を使ってLambertの計算を行うのみ!
ディレクショナルライトの他、環境光もShadeSH9関数を使用することで考慮しています。
ディレクショナルライトの他、環境光もShadeSH9関数を使用することで考慮しています。
↑Lambert拡散反射の計算

エレキベア
綺麗な実装クマ〜〜〜

マイケル
この状態で実行すると下記のように陰影も描画されていることが分かります!
ディレクショナルライトの向きを変えたり、環境光の色を変えることで陰影結果も変わることも合わせて確認できると思います。
ディレクショナルライトの向きを変えたり、環境光の色を変えることで陰影結果も変わることも合わせて確認できると思います。

エレキベア
やったクマ〜〜〜
参考:サーフェイスシェーダーでの実装

マイケル
最後におまけですが、サーフェイスシェーダーで同様の処理を実装した場合は下記のようになります。
Lambertのライティングはデフォルトで用意されており、#pragma surface surf Lambertの指定だけで完結してしまいます。。
Lambertのライティングはデフォルトで用意されており、#pragma surface surf Lambertの指定だけで完結してしまいます。。
↑サーフェイスシェーダーでのLambertライティング

エレキベア
Unity側にライティングを任せるとはこういうことクマか・・・

マイケル
Lambertの他、Phong反射やPBRライティングも用意されているので、こちらも触ってみると色々面白いと思います!
書籍等でもサーフェイスシェーダを使って解説されていることもありますし・・・
書籍等でもサーフェイスシェーダを使って解説されていることもありますし・・・
参考:
【Unity】Surface Shaderの基本を総まとめ!難しい計算はUnity任せでサクッとシェーダ作成 | LIGHT11

エレキベア
今後使用する機会は減っても一度は触っておいた方がよさそうクマね
おわりに

マイケル
というわけで今回はUnityシェーダーの基礎についてでした!
どうだったかな??
どうだったかな??

エレキベア
Unity固有で覚えないといけないこともいろいろあったクマが、
まあ何となく使える気がしたクマ〜〜
まあ何となく使える気がしたクマ〜〜

マイケル
いきなりUnityシェーダーを実装しようとすると、Unity固有の知識と一般的なシェーダープログラムの知識がごっちゃになってしまいそうだね・・・
シェーダーを覚えるといろいろ面白いと思うから、その辺りも整理しながら触っていこう!!
シェーダーを覚えるといろいろ面白いと思うから、その辺りも整理しながら触っていこう!!

マイケル
それでは今日はこの辺で!
アデューー!!
アデューー!!

エレキベア
クマ〜〜〜〜
【Unity】Unityシェーダーの基礎とLambert拡散反射の実装【シェーダー】〜完〜