UnityでShaderを作ってみましょう♯01

shader7
LINEで送る
Pocket

初めまして〜クリエイティブ事業部のテクニカルアーティスト、iwonchuと申します。

みなさん、Unityで色んなゲームを作ってますよね。
今回の内容はUnityのShader初心者向けの内容となります。 
あまり期待はしないでください〜、 本当に”基本的”な内容なので・・・

Shaderで一番先に知っておくことは「ShaderLab」です。
ShaderLabは一言で言うとUnityのShader言語です。
普通、Shader言語と言うと、Cg, HLSL, GLSLだと思われますが、
Unityでは、基本的にShaderLabを使用しております。
では、どうしてUnityはShaderLabを使用しているのでしょうか?
もう、勉強すべき言語がたくさんでかなり大変ですよね・・・ (ー_ー;;;;)

でもこれはUnityが元々マルチプラットフォームエンジンなので、多様なプラットフォーム、多様なデバイスに対応しなければならないためです。 どんな環境や状況でも対応できる共通のインターフェースが要求されるので、このようなShader言語を使うのです。

ShaderLab文法は初めにはっきりと知っておくのが良いです。 UnityでShaderを作成するにはどうであれShaderLabの領域は抜け出すことはできないためです。
それではUnityShaderの基本構造とともに、代表的なShaderLab構文を説明します。

Shader “PokelaboCustomShader" { 
     SubShader { 
          // ...  body  ... // 
     } 
} 

上のコードはUnityShaderファイルの最も簡単な構造です。
一つのShaderブロックがあり、その中にSubShaderが入る構成されます。
“PokelaboCustomShader”はShaderの名前です。
そしてSubShader内のbodyと表記された部分がありますが、ここには実際の実行部分を記述することになります。
つまりどんなShaderを作成したい時も、body部分にコードを記述することになります。
ここで一つ疑問を感じることができます。単純にbody部分を作成すればいいのにどうしてわざわざSubShader{}を作っているのでしょうか?
Shaderは一つのSubShaderだけではなく複数のSubShaderで構成することができます。

Shader "PokelaboCustomShader" { 
     SubShader { 
          // ...  body  ... // 
     } 
     SubShader { 
          // ...  body  ... // 
     }       
     SubShader { 
          // ...  body  ... // 
     }

// ...other subshaders//
}

このようにですね!
つまり一つのShaderに複数のパターンを構成することができる訳です。
このようにいくつかのパターンを用意できるのはグラフィックハードウェアの性能の多様性のためです
グラフィックハードウェアは非常に多様で、その性能も様々です。
その為、特定のハードウェアでShaderが動作しないことがあります。
このような状況を防ぐため、どんなハードウェア環境でも動作するように複数のSubShaderを用意しておきます。
UnityはSubShaderの一覧を調べた後に使用可能なSubShaderを一番上から選んで使用します。

コードで確認してみると、

Shader "PokelaboCustomShader" {  
      SubShader{  
          // A級デバイス用 (最上級の品質で表現) 
     }  
      SubShader{  
          // B級デバイス用
     }  
      SubShader{  
          // C級デバイス用 
     }  
      SubShader{  
          // その他。古いデバイス用 (品質は・・・期待しない方がいいでしょう)  
     }  

     Fallback "Diffuse"  
}

このような意図を持ってShaderを作成することになります

真上のコードの下の方にFallbackという新しいコマンドが追加されています。
このFallbackは何か役割なんでしょうか?

全てのSubShaderがグラフィックハードウェアに対応しなかった場合は、Fallbackに設定されたShaderのSubShaderを検索することになります。

別の見方をすれば野球の代打のようなものです。

Shaderが使えない時の為に、代わりになるのShaderを指定しておきましょう。
FallbackShaderを指定しておけばクオリティが低くてもレンダリングに失敗することがありません。
上の例題では”Diffuse”という名前のShaderをFallbackShaderで指定しました。
それでUnityが”PokelaboCustomShader”で適当なSubShaderを探せない場合、”Diffuse”ShaderでSubShaderを探す作業を繰り返すことになるのです。

最後に「Pass」に対して説明します。
このPassはオブジェクトのレンダリングの単位を意味して、SubShaderの中のPassの数のオブジェクトがレンダリングできます。 したがってUnityではPass構文を通じて場合によってはShaderをマルチパスで実現することができます。

再び整理してみると一般的なShader構成は

Shader "PokelaboCustomShader" { 
     SubShader{ 
          Pass{ //  ...  // } 
          Pass{ //  ...  // } 
     } 

     SubShader{ 
          Pass{ //  ...  // } 
     } 

     //  ...other subshaders  // 

     Fallback "OtherShader" 
}

この程度の簡単な形式になります。

それでは、実際に画面に何かを表示する超簡単なShaderを一つ作成してみます。

Shader “Pokelabo_White" { 
    SubShader { 
        Pass {  
            Color (1,1,1,1)  
        } 
    } 
} 

とてもシンプル!”Pokelabo_White”という名前を持つShaderです!その出来栄えは〜

shader1

コードは特別なものではなく、新しくColorコマンドが登場しました。
Colorコマンドはオブジェクトに単一RGBA色を指定するコマンドです。
したがって上のShaderは条件なしにそのままオブジェクト全体をそのまま真っ白でレンダリングするものになります。
単純すぎて使い道はなさそうですね (^_^;) 立体感もなくて面白くない・・・

実際にやりたいことは物体の表面が周りの影響を受けることです。
それでは今から表面が光の影響を受けているShaderを作成してみましょう!

シーン(scene)ではすでにDirectional lightが1個存在していると仮定します。
Shaderコードで追加するべきコードは二種類です。
材質(material)とライティング(lighting)を設定します。

まずは物体表面の材質(material)を定義してみます。
材質は次の通りShaderLabのMaterial構文を利用して定義できます。

Material {
Diffuse (1,1,1,1)
Ambient (1,1,1,1)
}

非常に簡単ですね〜 、材質のDiffuse色とAmbient色全部白色に指定しろとの構文です。
物体の材質は定義したので、次はワールドに存在する光(ここではDirectional light)と、たった今定義した材質が’どのように反応するのかを設定しなければなりません。
すなわち、照明に対しどのように演算するのかを設定しなければならないという話です。
結論的としては、私たちはライティング演算のために「固定機能パイプライン」を利用するでしょう。

これは開発者がGPUで既に決まっている機能を呼び出して簡単にレンダリングを行うことができます。

なので照明に対して難しい処理を考えず、GPUレベルで用意されている機能を使うだけでよいのです。
それなら私たちがしなければならないことが何でしょうか?
そのまま固定機能パイプラインを通じてライティング演算をするとShaderに記述すればいいだけです。
ただShaderLabのLighting (On|Off)コマンドを利用してライティングをつければ(On)終了です。

Lighting On

このコード1行だけ固定機能パイプラインの標準ライティングを適用することができます。
最後に整理するとShaderのコードは、

Shader “Pokelabo_White" { 
    SubShader { 
        Pass {  
            Material {
                 Diffuse (1,1,1,1)
                 Ambient (1,1,1,1)
            }
            Lighting On
        } 
    } 
} 

上のように完成できます。
実際適用された画面を見れば下記のとおりです。

shader2

単一色で表示した時とは違い、立体感が感じられます。
ワールドにあるdirectional lightの方向を変化させた時も、

shader3

表面が変わるので、問題なく動作できたと思います!

ShaderLabだけでライティングを利用して表示するShaderを作ることができました。

でも・・・コードを見ると本当にプログラミングをした気がしませんね。( ー_ー)a

そのまま提供されているパラメータを’設定’しただけです。
材質の値を設定して、ライティング機能をONにしただけ・・・

元々固定機能パイプラインではPer-vertex lightingを使います。すなわち、ライティング演算がVertexレベルで成り立つということです。
したがってピクセルレベルでのライティング演算に比べると処理は軽いけど、当然それだけ品質がとても落ちます。
困ったことにこれを改善したり修正する方法はありません。
‘固定機能’だからですね!…結局出来ることが限られているのですね・・・・
それならば固定機能パイプラインは使い道が無いのでしょうか? (それでは今まで何をしたことでしょう…. ;)

結論から言ってみればそうではありません。 幸い役に立ちます!

固定機能パイプラインは多様なハードウェアの対応に必ず必要です。上記で記載しましたが多様なフラットフォームと多様なデバイス環境に対して対応しなければなりません。

そうするために固定機能パイプラインのような低レベルShaderの作成が必要になります。
特に古くなったグラフィックハードウェアに対するfallbackShaderで使われるのにとても役立ちます。
最終的なfallbackは固定機能パイプラインを使って、どんなデバイスにも対応できるように構成して置いた方がいいしょう。
そして品質は良くなくても、簡単な機能のシェイダーは固定機能パイプラインで製作することでさらに簡単になります。
固定機能パイプラインはShaderLabだけで構成されるので容易で簡潔に作成できます。
その為知っておいて損は無いということです。

今まで固定機能パイプラインについて少し語りました〜、それでは戻って最後に作成したShaderを再び説明します。

Shader “Pokelabo_White" { 
    SubShader { 
        Pass {  
            Material {
                 Diffuse (1,1,1,1)
                 Ambient (1,1,1,1)
            }
            Lighting On
        } 
    } 
} 

ところで赤色にしてみたくなりませんか?(しませんか?しますよね〜〜w)
簡単に材質のDiffuse,Ambient色値を変更するだけです!

Material {
Diffuse (1,0,0,1)
Ambient (1,0,0,1)
}

とても簡単!
ですがこのようにすると材質の色が固定化されてしまいますね。
これでは他の色を使いたい時は、色数分だけShaderを作って各オブジェクトに適用しなければならなくなってしまいます。 これは非常に非効率的!
色値を固定せずに外部から値を変更できるようにするべきです。
その為にはProperties構文を使用します。 Properties中には外部から値を変更できるパラメータを設定できます。
まずコードにProperties構文を追加してみます。(ついでにShaderの名前も変更しましょう)

Shader "Pokelabo_Sample" { 
    Properties { 
        _SimpleColor ("Main Color", COLOR) = (0,0,1,1) 
    } 
    SubShader { 
        Pass { 
            Material { 
                Diffuse [_SimpleColor] 
                Ambient [_SimpleColor] 
            } 
            Lighting On 
        } 
    } 
} 

上のコードでは新しくPropertiesブロックが追加されています。 そして1個のパラメーター変数が追加されましたね。

_SimpleColor (“Main Color”,COLOR) = (0,0,1,1)

_SimpleColorは属性変数の名前を現わして、”Main Color”はエディター上で見られるラベル名です。
COLORは変数が色のタイプであることを意味して、エディター上でデフォルト価格は青色(0,0,1,1)で指定することを意味します。

このようにPropertiesブロックが追加されるのに伴ってエディターにはどのような変化が起きたのか一度見ます。

shader4

インスペクターウィンドウに “Main Color”という色を設定できるインターフェースができ、デフォルトで青色が設定されました。

Propertiesブロックに追加した内容が反映されたとのことが分かります。 予想した通りエディターの”Main Color”値を変更すれば、セイドの_SimpleColor値にその値が適用されます。

それではこの値を使うには、Shaderではどのようにしたらいいのでしょうか?
Shader内部では「プロパティの名前」だと作成してその値を使用できます。
そのまま上のコードの一部分を確認してみましょう。

Material {
Diffuse [_SimpleColor]
Ambient [_SimpleColor]
}

MaterialのDiffuseとAmbient色を(r,g,b,a)から[_SimpleColor]に修正しましたね。
すなわち、_SimpleColor値を参照して色値を指定するということでしょう。
さあ、いよいよエディターからいつでも望む色でオブジェクトの色を指定することができるようになりました。

もちろんここで使ったCOLORタイププロパティの他にも多様なタイプのプロパティが存在して、Shaderではそれらを使用できます。

 

ここまできたら、もう少し欲が出ますね。
今度は単純に材質の色を設定するだけではなく、表面にテクスチャーイメージを表示してみます。
これはShaderLabのSetTextureを利用すればできます。
その前に使うテクスチャーをエディターで設定する必要があります。
そうするためにはさっき習ったプロパティを活用すれば良いです。 Propertiesブロック中に次のプロパティ変数を一つ追加します。

_MainTex (“Base Texture”,2D) = “white” {}

テクスチャーのタイプは2Dタイプです。 デフォルト値で”white”が指定されています。
これはテクスチャが設定されなかった場合の色です。 “white”だから白い色値で取り扱うでしょうね。 その他にも”black”,”gray”,”bump”を設定することができ、また何も設定しない””も可能です。
{ }中には別途オプションが入ります。 このオプションに関しては今回は扱わないで、次の回にチャンスがあったら扱うようにします。

エディターは次の通り変化がおきました!

shader5

Textureプロパティが追加されたのを確認できますね。 (テクスチャーは任意にイメージをセッティングしましょう〜)

shader6

それではエディターで設定したテクスチャを反映させる為に、先ほどのSetTextureというコマンドを使ってみます。

一つ注意する点は、テクスチャーは色を決める最終段階で処理をするのでコードの最後に入れなければなりません。

SetTexture[_MainTex]

このように追加するとエディターで設定した_MainTexを適用することになります。

それではコードは、

Shader "Pokelabo_Sample" {
     Properties { 
        _SimpleColor ("Main Color", COLOR) = (0,0,1,1) 
        _MainTex ("Base Texture",2D) = "white" {}
    } 

    SubShader { 
        Pass { 
            Material { 
                Diffuse [_SimpleColor] 
                Ambient [_SimpleColor] 
            } 
            Lighting On 

            SetTexture[_MainTex]
        } 
    } 
}

適用した結果は〜

shader7

ところで何か変に見えます。 ライティング演算が反映されないような気がして…
あ、そういえば最終レンダリングの色がテクスチャーイメージの色だけになっていましたね。

Passの終わりの部分、SetTexture [プロパティ] だけしかありません。以前に材質と光がライティング演算を通じて出た結果色を無視してしまい、現在のテクスチャーで上書きしてしまっています。

以前に計算された結果色値と現在のテクスチャーがどのように結合するのかを決めなければなりません。 うまく結合する事でもっとリアルな結果を出すことが出来ます。
再び次のコードを追加してみます。

SetTexture [_MainTex] { Combine texture* primary }

SetTextureコマンドに新しいブロックを追加されました。
Combineコマンドにどのように色を結合するか設定します。textureは設定したテクスチャーの色を意味し、primaryはライティング演算された色を意味します。この場合は以前のライティングの結果と現在のテクスチャーをかけ合わせています。
ここでもう少しコードを追加してみましょうか!
照明の強度をちょっと高めるために結果値を二倍にする意味のDOUBLEを追加します。DOUBLEは結果値をx2するキーワードです。

SetTexture [_MainTex] { Combine texture* primary DOUBLE }

それでは最終コードは、

Shader "Pokelabo_Sample" {
     Properties { 
        _SimpleColor ("Main Color", COLOR) = (0,0,1,1) 
        _MainTex ("Base Texture",2D) = "white" {}
    } 

    SubShader { 
        Pass { 
            Material { 
                Diffuse [_SimpleColor] 
                Ambient [_SimpleColor] 
            } 
            Lighting On 

            SetTexture[_MainTex]{
                Combine texture * primary DOUBLE 
            }
        } 
    } 
}

実際適用してみた結果は〜〜

shader8

 

 

ベースの色と影、テキスチャーがうまく結合されましたね〜
ちなみに、エディター上でMain Colorを白にすると、

shader9

みたいに、テキスチャーの元の色で表示する事も出きます。

さあ、今回はここまでです。
次回のブログではより一層それらしいShaderを作成してみます。
ありがとうございました~!

別件、ポケラボ クリエイティブTwitterを開始しました
よろしければフォローよろしくお願いします!