Unityのシェーダースクリプトについて

今回はUnityのシェーダースクリプトについて見ていこうと思います。

シェーダーについて詳しいわけではないので、個人的なメモ書き程度ということにさせて頂きます。(._.)

またシェーダースクリプトを書かずともシェーダーグラフを使うとビジュアル操作でシェーダースクリプトを作成出来ますが、そちらはまだ触っていないので出来次第記事にしたいと思います。

Shader Graphを触る前に自分で手書きでスクリプトを組んでおくとより理解が深まるかな?という思惑もあります。

実際にシェーダーを作成する時はシェーダーグラフで作成し、中身はどうなってんだろ?と言う時にスクリプトの中身を多少理解出来ていると役に立つかもしれません。

スポンサーリンク

シェーダーって何?

シェーダーとはGPU上で動く陰影処理を行うプログラムの事みたいです。

GPUはGraphics Processing Unitの略でリアルタイム画像処理に特化したプロセッサの事です。

GPUは膨大な量の並列処理を行う事が出来るので、CPUを使って描画をコントロールするよりも処理が速くなります。

シェーダーを使うと3Dオブジェクトの頂点や画面に描画するピクセル等を変化させ3Dモデルに様々なエフェクトを加えることが出来ます。

Unityで使うシェーダー

シェーダーなんて使っていません!という人がいるかもしれませんが、実は既に使っています(この文句どこかで書いたような記憶があったけれど、C#の勉強カテゴリの継承の記事で使ったかも)。

Unity内でCubeやSphereを作成すると、そのオブジェクトにはDefault-Materialという名前のマテリアルが設定されています。

そのDefault-MaterialのShaderにはビルトインのStandardシェーダーが設定されています。

Default-Materialのシェーダー

そんなわけで、マテリアルをゲームオブジェクトに設定するとシェーダーはマテリアルに設定されているということになります。

マテリアルはAssetsエリア内で右クリック→Create→Materialで自分で作成出来ますが、シェーダースクリプトも右クリック→Create→Shader→用途に応じて選択で作成出来ます。

Unityで作るシェーダースクリプトの種類

ライティングや影などを考慮したシェーダーを作る場合はStandard Surface Shader、それらを考慮しない場合はUnlit Shaderを選択します。

Image Effect Shaderはイメージエフェクトを作成する時に使用するといいかもしれません。

Compute Shaderはよくわからないので省略します。(^_^;)

Standard Surface Shader、Unlit Shader、Image Effect Shaderは用途に応じて選択しますが、それ用のデフォルトの設定が書かれているだけなので、どれを選択しても中身を理解し書き換えれば同じです。

Unityではサーフェスシェーダー(ライティングを考慮した)と頂点シェーダー&フラグメントシェーダー(頂点やピクセルを操作)、固定関数シェーダーで記述出来るようです。

シェーダースクリプトの構造

サーフェスシェーダースクリプト、頂点シェーダー&フラグメントシェーダーの構造を見ていきましょう。

サーフェスシェーダースクリプトの全体構造

Assetsエリア内で右クリック→Create→Shader→Standard Surface Shaderを選択し、名前をMySurfaceShaderという名前に変更します。

MySurfaceShaderをダブルクリックしてスクリプトを開くと

サーフェスシェーダーは上のような構造になっています(頂点&フラグメントシェーダーは後で見ていきます)。

シェーダー名の部分

の部分でマテリアルのシェーダー選択時の階層とシェーダー名を自分で設定する事が出来ます。

シェーダーのプロパティ

Propertiesの中にはマテリアルのインスペクタで表示するプロパティを宣言します。

_MainTexの部分はスクリプト等から参照する時のプロパティ名で()の中の第1引数のTextureはマテリアルのインスペクタで表示する名前、第2引数が型で=以降が初期値です。

Propertiesのスクリプトの記述の最後に;(セミコロン)は要りません。

サブシェーダー部分

次はSubShaderの部分を見ていきます。

SubShaderは複数記述する事が出来、ユーザーの環境でサポートされる最初のサブシェーダーが選択されるようになります。

どのサブシェーダーもサポートされていない場合はFallBackで指定したシェーダーの使用を試みるようです。

Tags部分

次はTagsの部分を見ていきます。

Tagsはレンダリングエンジンがいつどのようにレンダリングするかを設定する部分です。

“キー” = “値”

という対で設定します。

Queueはレンダリングの順番の設定で、Backgroundは最初にレンダリング、Geometryは不透明な普通のオブジェクト、AlphaTestはGeometryの後、Transparentはこれらの後の透明なオブジェクト等、Overlayは全てのオブジェクトの後にレンダリングするものです。

レンダリング順に関しては

Subshader はタグを使用して、いつどのようにしてレンダリングエンジンでレンダリングするか示します。

を参照してください。

RenderTypeはシェーダーを定義済みのいくつかのグループに分ける為に使用します。

複数のサブシェーダーを定義してそれぞれRenderTypeを分けて作成しておき、スクリプト等からサブシェーダーを切り替える必要がある時等に使うのかも?

いくつかのレンダリングエフェクトは異なるセットのシェーダーを用いてシーンをレンダリングする必要があります。例えば、優れたエッジ検出には、表面の凸凹が異なるエッジを検出するために、シーンに法線のあるテクスチャが必要です。他のエフェクトでも、シーンにデプスのあるテクスチャが必要だったりします。このためには、シーンを、すべて...

シェーダーの処理の記述部分

次は実際のシェーダーの処理を記述する部分を見ていきます。

CGPROGRAMとENDCGの中に操作したい処理を記述します。

サーフェスシェーダーコンパイルディレクティブ部分

次はサーフェスシェーダーコンパイルディレクティブの部分を見ていきます。

#pragma surfaceでサーフェスシェーダーであることを示し、その後に関数名等を記述します。

ライティングモードやオプションパラメータの指定方法はかなりあるので、

ライティングと相互作用するシェーダーの記述は複雑です。各種ライト、各種の影のオプション、各種レンダリングパス (フォワードやデファードレンダリング) があり、シェーダーはその複雑さを処理する必要があります。

を参照してください。

構造体部分

次は構造体の部分を見ていきます。

サーフェスシェーダーのInput構造体を作成します。

テクスチャ座標を使う場合はuvの後にテクスチャ名を記述します。

Input構造体で指定出来る要素は

ライティングと相互作用するシェーダーの記述は複雑です。各種ライト、各種の影のオプション、各種レンダリングパス (フォワードやデファードレンダリング) があり、シェーダーはその複雑さを処理する必要があります。

のページの「サーフェスシェーダーのInput構造体」の項目に載っています。

変数宣言部分

次は変数宣言部分を見ていきます。

2Dのテクスチャプロパティの場合はsampler2D、Colorはfixed4にしています。

データの精度として浮動小数点の高精度float、浮動小数点の中精度half、固定小数点の低精度fixed、整数型のint等があります。

関数部分

次は関数部分を見ていきます。

サーフェス処理関数は上のようになっています。

関数名の所にはサーフェスシェーダーコンパイルディレクティブで指定した名前を書きます。

Inputは先ほど作ったInput構造体から入力がきます。

サーフェスに関する処理はinout(入力出力)でSurfaceOutputStandard構造体に出力されます。

SurfaceOutputStandardはサーフェスシェーダーコンパイルディレクティブのライティングモードでStandardを指定した時の構造体で、そこにStandard以外を指定した時は別の構造体を指定する必要があります。

StandardSpecularをライティングモードに指定したら構造体はSurfaceOutputStandardSpecular、Lambertを指定したらSurfaceOutputとなります。

ここまででサーフェスシェーダーの大まかな構造を見てきました。

サーフェスシェーダースクリプトを作ってみる

大まかにサーフェスシェーダーの構造を見てきたので、実際にスクリプトを作ってみます。

シェーダースクリプトで使う組み込み関数は

に載っているのでそちらを参考にさせて頂きましょう。

それでは非常にシンプルなスクリプトを作ってみます。

まずはシェーダースクリプトをマテリアルのShaderの項目のどこに表示されるか?シェーダーの名前を指定します。

今回はMySurfaceShaderにMyShaderというシェーダースクリプトを配置します。

Propertiesではメインテクスチャの_MainTex、セカンドテクスチャの_SecondTex、色の_TintColorを用意しました。

TagsではQueueをGeometryにしてレンダリング順を不透明な通常のオブジェクトとし、RenderTypeをOpaqueにします。

サーフェスシェーダーコンパイルディレクティブには

とsurf関数、Standardで物理ベースのシェーダーの宣言をし、addshadowで影ありにします。

入力構造体は

と記述し、メインテクスチャとセカンドテクスチャのUVを宣言します。

変数宣言では

メインテクスチャ、セカンドテクスチャ、カラーの3つの変数を宣言します。

最後に実際の処理の部分です。

o.Albedoはアルベド(表面そのものの色)を設定するので、tex2Dを使って_MainTexをサンプラーステートにし、In.uv_MainTexをテクスチャ座標にし、それに_TintColorを乗算してアルベドに設定しています。

正直何のこっちゃ?って感じですが、tex2Dは_MainTexをIn.uv_MainTexをテクスチャ座標にしてピクセル毎の色を計算してくれるようです。

なので_MainTexにテクスチャを指定すると、そのテクスチャを_MainTexに指定したテクスチャ座標値によって表示する色を決定するので、_MainTexのTilingやOffsetを変更するとテクスチャの貼り付けられ方が変わります。

仮に

上のようにIN.uv_MainTexの部分をIN.uv_SecondTexに変えた場合は_SecondTexに設定したテクスチャのTilingやOffsetを操作するとメインテクスチャの貼り付けられ方が変わります。

サンプラーステートに関してはUnityマニュアルの

Most of the time when sampling textures in shaders, the texture sampling state should come form texture settings – essentially, textures and samplers are ...

に詳しい説明があります。

サンプリングしたテクスチャの.rgb(RGB値)に_TintColorを乗算するのでインスペクタで_TintColorの色を変更するとテクスチャの色合いが変わります。

さらにmadを使って今設定したアルベドにセカンドテクスチャを加算して再度アルベドに入れています。

madの第1引数が乗算値で、第2引数が1番目の加算値、第3引数が2番目の加算値となります。

これで簡単なサーフェスシェーダースクリプトが出来ました。

試しにゲームオブジェクトに設定して確認してみましょう。

まずはシェーダーを設定するマテリアルが必要になるので、Assetsエリア内で右クリック→Create→Materialを選択し名前をMySurfaceMaterialとします。

MySurfaceMaterialのShaderでMySurfaceShaderのMyShaderを設定します。

自前のシェーダースクリプトをマテリアルに設定

MyShaderを設定したら、MainTex、SecondTexにテクスチャを設定し、TintColorで赤色を設定します。

MyShaderにテクスチャと色を設定する

次にヒエラルキー上で右クリック→3D Objects→Cubeを選択し、MySurfaceMaterialマテリアルをCubeにドラッグ&ドロップします。

すると、下のようにゲームオブジェクトにマテリアルが設定され、シェーダースクリプトで施した処理が実行されていることがわかります。

MyShaderを設定したゲームオブジェクトのサンプル

テクスチャのTilingやOffsetを変更したりTintColorの色を変えるとゲームオブジェクトの変化がわかります。

自作のサーフェスシェーダーでパラメータを操作したサンプル

上のような感じになりました。

頂点シェーダー&フラグメントシェーダー

次は頂点シェーダー&フラグメントシェーダーを見ていきます。

Assetsフォルダ内で右クリック→Create→Shader→Unlit Shaderを選択し、名前をMyFragmentShaderとします。

頂点シェーダー&フラグメントシェーダーの全体構造

MyFragmentShaderを開くと

上のような構造になっています。

ほとんどサーフェスシェーダーと同じですが、CGPROGRAM~ENDCGはPassの中に記述する事になります。

CGPROGRAMの中身もほとんどサーフェスシェーダーと同じなのでコンパイラーディレクティブ、構造体や関数の部分だけを見ていきます。

コンパイラーディレクティブ部分

まずはコンパイラーディレクティブの部分を見ていきます。

サーフェスシェーダーの時と同じようにvertex(頂点)、fragment(フラグメント)のディレクティブを書いています。

その他のコンパイラーディレクティブは

を参照してください。

構造体宣言

次は構造体宣言部分です。

appdataは頂点の入力データ、v2fはVertext To Fragmentの略で頂点データからフラグメントへと流し込むデータです。

appdataでは頂点データvertexをPOSITIONセマンティック署名を付けて定義し、テクスチャ座標uvをTEXCOORD0を付けて定義しています。

セマンティック署名はこのデータがどのようなデータなのかを示すために設定します。

POSITIONはオブジェクト空間の頂点位置、TEXCOORD0はテクスチャ座標や位置を表していて最後の数字は何番目かどうかみたいです。

SV_POSITIONはピクセル位置(クリップスペース位置)を表しています。

頂点関数

次は頂点関数を見ていきます。

構造体appdataのデータを引数として受け取りそれに処理を加えてv2f構造体を返します。

フラグメント関数

次はフラグメント関数を見ていきます。

引数としてvert関数で処理したデータを受け取りそれに処理を加えてフラグメント処理(ピクセル処理)を行います。

結果はfixed4型で返しますがそれはSV_Targetで色としてのデータであることをセマンティックで指定しています。

頂点シェーダー&フラグメントシェーダースクリプトを作ってみる

実際に頂点シェーダー&フラグメントシェーダーのスクリプトを作ってみます。

MyShaderの中にMyFragmentShaderを作成します。

コンパイラーディレクティブの後に

という記述がありますが、これは定義済みの変数や関数を呼び出すためのファイルを取り込んでいます。

ビルトインのシェーダーincludeファイルに関しては

Unity には、定義済みの変数やヘルパー関数を呼び出すために シェーダプログラム で使用できるいくつかのファイルが含まれています。これは以下のような標準的な #include ディレクティブで行われています。

を参照してください。

今回はUnityCG.cgincを取り込んでいますが、この中で定義されている関数をvert関数内で使用します。

変数の宣言では

メインテクスチャのテクスチャ_MainTex、メインテクスチャのTilingとOffsetを表す_MainTex_STを宣言しています。

_MainTexの後に_STと付いていますが、テクスチャの後に_STと付けるとTilingやOffsetの情報を表すようです。

vert関数ではUnityCG.cgincで定義されているUnityObjectToClipPos関数を使用して頂点データをオブジェクト空間からカメラのクリップ座標(カメラから見た時の縦横の座標)に変換します。

TRANSFORM_TEXで入力のテクスチャを_MainTexをテクスチャ座標としてUVを作成しています。

最後にreturnで処理した頂点データをfrag関数に渡します。

frag関数ではメインテクスチャを受け取った頂点データinputのテクスチャ座標でサンプリングします。

シェーダースクリプトが出来たので、Assetsフォルダ内で右クリック→Create→Materialを選択し、名前をMyFragmentMaterialとしShaderにMyFragmentShaderを設定します。

MyFragmentMaterialのMainTexに何かしらのテクスチャを設定します。

ヒエラルキー上に新しくCube(サーフェスシェーダー用に作ったCubeを使ってもよし)を作りそれにMyFragmentMaterialをドラッグ&ドロップし設定します。

すると、

自前の何もしない頂点シェーダー&フラグメントシェーダーを設定したCube

上のようにCubeが表示されました。

ただ単にCubeにテクスチャが張られているだけのように見えますが・・・・、その通りです!!

vert関数ではオブジェクトの位置を3D空間からカメラのクリップ情報に変換しテクスチャのUVの割り当てをし、frag関数ではそのUVデータを元にメインテクスチャを表示しているだけです。

vert関数やfrag関数はどこから呼ばれてる?

UnityのシェーダースクリプトはHLSLという言語の異形のようです。

基本的な事を勉強していないのでシェーダースクリプトは分からない!というままです。

そもそもvert関数やfrag関数はどうやっていつ呼び出されてるのか?となんだか処理の流れがわかりません。

どうやらUnityさんの方から勝手に呼んでくれているようで、オブジェクトの全ての頂点をvert関数で処理し、そのデータをfrag関数の引数として受け取りピクセル処理をしているようです。

これらの処理を順番に処理するわけでなくGPUを使って並列に大量処理をしています。

さてこれで頂点シェーダー&フラグメントシェーダーのサンプル(実際は何も処理していません)も出来ましたが、本当は頂点を操作したりしてエフェクトを加えたいところですが、手書きで何かエフェクトを加えようと思うと結構難しそうです・・・(^_^;)

Unityマニュアルのシェーダー関係のページでサンプルは載っていますが、なんだか難しそうです。(^_^;)

セマンティック署名、頂点シェーダー&フラグメントシェーダーのサンプルはUnityマニュアルの

Cg/HLSL 頂点プログラム に対し、メッシュ の頂点データは頂点シェーダー関数への入力で渡されます。それぞれの入力には特定の セマンティック が必要です。例えば POSITION の入力は頂点の位置、そして NORMAL の入力は頂点の法線のデータです。

にいくつか載っています。

実際のサンプルはシェーダーグラフを使って面白そうなのが出来たら記事にしようと思います。

でも手書きで何もやらないのもあれなんで、頂点を操作してオブジェクトの形状を変化させるサンプルを作成してみましょう。

幽霊のようにキャラクターを歪ませるサンプル

まずは人型キャラクターの頂点を動かしユラユラと動く幽霊やバーチャル映像の乱れのようなエフェクトを作成していきます。

スタンダードアセットのThirdPersonControllerをヒエラルキー上にドラッグ&ドロップします。

次はAssetsフォルダ内で右クリック→Create→Materialを選択し、名前をDistortMaterialとします。

Assetsフォルダ内で右クリック→Create→Shader→Unlit Shaderを選択し、名前をDistortShaderとします。

DistortMaterialのShaderをDistortShaderにします。

Propertiesで歪ませる量を_Amount、歪みのスピードを_Speedとして定義しました。

TagsではQueueとRenderTypeをTransparentにします。

その後

上のようにZWrite Offでデプスバッファをオフにし、Blend SrcAlpha OneMinusSrcAlphaで昔ながらの透明のブレンディングをします。

ZWriteに関しては

カリングは、視点から見えない反対側を向いたポリゴンをレンダリングしないという最適化手法です。すべてのポリゴンは表と裏側があり、オブジェクトのほとんどが閉じている事実を利用します。もしキューブがあった場合、視点と反対側を見ることはない(必ず視点の側を向いている面がある)ので、反対側の面は描画する必要がありません。このため...

Blendに関しては

Blending は透明なオブジェクトの作成に使用します。

のUnityマニュアルを参照してください。

幽霊感を出すために人型キャラクターを半透明にする為の処理です。

変数宣言と関数の部分を見ていきます。

vert関数でsinを使って-1~1の値を得ますが、引数でビルトインのシェーダー変数_Timeを使って時間経過でsinで得られる値を変化させます。

ビルトインのシェーダー変数に関してはUnityマニュアルを参照してください。

Unity は、シェーダー用のビルトインのグローバル変数をいくつか提供しています。例えば、現在のオブジェクトの変形行列、ライトのパラメーター、現在の時間などです。これらは他の変数と同様、 シェーダープログラム で使用します。ただ 1 つの違いは、宣言する必要がないことです。これらの変数はすべて、自動的に加えられる in...

_Timeでステージのロードからの経過時間を取得出来ます。

_Timeはfloat4型で倍速?を指定出来ます。

_Time[0]は1/20、_Time[1]は1、_Time[2]は2、_Time[3]は3倍のようです。

またfloat4型なので0はx、1はy、2はz、3はwに対応していて、_Time.x、_Time.y、_Time.z、_Time.wでも指定出来ます。

_Timeにカメラのクリップ空間に変換した頂点のY座標と_Speedとかけてsinの引数に指定しスピードを変化させます。

それに_Amountをかけて歪みの量を変化させた位置を頂点のX座標に足してそれを頂点のX座標に入れなおします。

カメラのクリップ位置のX座標をsinを使ってY座標に応じて頂点位置をずらしています。

ビルトインのシェーダー変数には_SinTimeでも時間に応じたサイン値を取得する事が出来ます。

frag関数ではキャラクターを半透明にする為、

とアルファ値に0.5を入れています。

DistortMaterialのTextureでEthanOcclusionを設定します。

ThirdPersonControllerの子要素のEthanBodyとEthanGlassesにDistortMaterialをドラッグ&ドロップします。

EthanGlassesにもDistortMaterialを設定しましたが、これはサングラス用に別のマテリアルを作成するのが面倒なのでそのまま適用しました。

それでは設定が終わったので確認してみましょう。

幽霊のようなエフェクトを加えたサンプル

上のようにキャラクターが歪んで表示されました。

マテリアルのインスペクタでSpeedやAmountを変化させると歪みを変化させることが出来るので試してみてください。

スクリプトからシェーダーのプロパティを変更する

先ほどのシェーダースクリプトでキャラクターに幽霊のようなエフェクトを加えることが出来ますが、SpeedやAmountをインスペクタであらかじめ設定した値から変更できませんでした。

そこでC#スクリプトからシェーダーのプロパティ値を変更し、キャラクターの歪みのスピードや大きさを変更出来るようにしてみます。

インスペクタでキャラクターに使用しているマテリアルを設定します。

頂点をズラすスピードと量の最小値と最大値をインスペクタで設定出来るようにし、その範囲でランダムな値を生成し、シェーダーのプロパティに設定します。

シェーダーのプロパティは

で設定出来ます。

プロパティ名はシェーダースクリプトで設定したプロパティ名を指定します。

作成したChangeOfDistortionParameterスクリプトをThirdPersonControllerに取り付けます。

ChangeOfDistortionParameterスクリプトのインスペクタの設定

上のようにMaterialにDistortMaterialをAssetsフォルダからドラッグ&ドロップしておきます。

これでランダム値がシェーダースクリプトのプロパティ値に入るので動きがランダムになります。

Ethanの体の透過はZWrite Offだとうまく描画されないので、ZWrite Onに書き換えます。

それでは実行してみましょう。

上のような感じでランダムに歪みが変わるようになります。

終わりに

シェーダーを自分で書くのはかなり大変ですね。(^_^;)

最初に勉強を始めた時はこの関数はどうやって呼ばれてるんだ?流れはどうなってんの?と通常のC#のプログラム等と似て非なるものなので混乱しました。

Unityのマニュアルのシェーダーに関するページはかなり多く、専門的な用語が書いてあったりするので理解をするのは難しいです。

というわけでこの記事は自分用のメモという体にさせて頂きました。

さらにシェーダーはレンダリング方式に応じて全方式のシェーダーを全て用意しなければならない為、ここら辺を考えると自分でシェーダースクリプトを作成するよりシェーダーグラフを使った方が良さそうです。

まだシェーダーグラフを使ってないので何とも言えませんが・・・・(^_^;)

参考サイト

Unityマニュアルーシェーダーリファレンスーのページから色々探せます。

スポンサーリンク

記事をシェアして頂ける方はこちら

フォローして頂くとやる気が出ます