株式会社デジタル・フロンティア-Digital Frontier

Header

Main

  • TOP
  • DF TALK
  • 【Unity】 ~Unityでシェーダーを書いてみる~:その1【初心者向け】

【Unity】 ~Unityでシェーダーを書いてみる~:その1【初心者向け】

2015/12/14

Tag: ,

皆さん始めまして。
今年の四月に入社しました開発部の辻です。
気づけば今年もほとんど終わってしまいました。時間の流れの速さを感じます。

さて今回の記事では、Unityのシェーダーについて紹介していきます。

CGにおけるシェーダーはキャラクターや背景の見た目を決定する重要な要素です。
しかし、シェーダーを学習するとなると、まずシェーダー以外の部分でのプログラミングが必要になることが多いです。
やりたいことはシェーダーなのに、それ以外の部分で躓いてしまう人も居るのではないでしょうか?
Unityは僕自身がシェーダーを勉強するために触っていて、個人的に使いやすかったので今回紹介しようと思いました。

この記事ではUnityのダウンロードからUnityの簡単な説明をした後に、オブジェクトにちょっとしたシェーダーを割り当てて説明するところまでやっていく予定です。
初心者向けですので、既にUnityや他のシェーダー言語を使っている方々にとっては物足りないような内容になってしまいますが、ご了承くださいませm(__)m

全体の構成は以下のようになっています。

・そもそもUnityとは
・Unityでシェーダーを書くメリットは?
・Unityをインストールしよう
・プロジェクトを作る
・シェーダーの前準備
・シェーダーを作る
・頂点シェーダーについて
・フラグメントシェーダーについて

それでは参りましょう。

・そもそもUnityとは

Unityとは、無料でも使用出来るゲームエンジンのことです。
ライセンスの形式は無料のPersonal Editionと有料のProfessional Editionがあります。
有料版のほうがより多くのサポートが受けられるわけですが、無料版で使える機能も非常に充実しています。
今回はPersonal Editionで、バージョン5.2.3のUnityを使用しました。

iPhoneやwebPlayerなどなど、様々な環境に対応した出力が簡単に行えるので、
スマートフォンゲームだけでなく、様々なゲームの制作で使用されています。
また、無料で使用できるため使用者が多くコミュニティが広いのも特徴的です。
そのため、インターネット上で情報がとても集めやすくなっています。

・Unityでシェーダーを書くメリットは?

Unityでシェーダーを作成するメリットは、まず環境構築が楽なことだと思っています。
シェーダーを書く場合、まずシェーダー以前にシェーダーを適用するオブジェクトはもちろんライトの位置等も自分で設定しないといけません。
Unityでは、プリミティブなオブジェクトの作成はもちろん、外部から.fbxや.obj形式のモデル読み込みにも対応していますし、モデルの位置の変更やライトの設定もプログラムで制御しなくて良いので簡単に行えます。

加えてシェーダーの反映がスムーズです。
シェーダーを変更したら、その内容はすぐに反映されますし、エラーもコンソールに表示してくれるのでデバッグもしやすいです。
いちいちビルドして結果を見るという手間が無いので快適に開発できます。

また、Unityのシェーダーはシェーダーファイル一つで動かせるのもポイントだと思います。
例えばOpenGLでは、OpenGLのソースコードをC++等で作成した上でGLSLのファイルも作成しなければなりませんが
Unityでは簡単なものであれば、シェーダーのファイル一つ作るだけで動かすことができます。

Unityでシェーダーを覚えても他の環境で使えないんじゃ……と思う人も居るかもしれません。
シェーダー言語はいくつか種類がありますが、根本的なアルゴリズムの部分で必要な情報が違うわけではありません。
例えばLambert反射を実装する場合、どの言語であってもライトの向きと法線の方向を使用します。
ただシェーダー言語によってその情報を持って来る方法が違うだけなので、Unityで学んだ知識が他で無駄になる、ということは無いはずです。

・Unityをインストールしよう

さて、Unityでシェーダーを書こうにも、Unityをインストールしないことには始まりません。
というわけでこのページからインストーラをダウンロードします。

・補足
現在の最新バージョンはUnity5.3.0ですが、この記事は5.2.3の環境で作成しています。ですので、この記事内の説明と多少食い違う部分がある可能性があります。
気になる方は以前のバージョンをこちらから入手することができます。

インストーラをダウンロードした後に起動すると、このような画面が出てくると思います。(このページの画像はクリックすると大きくなります)

ダウンロードするフォルダ等を設定しつつ、指示に従ってインストールを行ってください。

インストールが終わったら起動します。
恐らく初回ではPersonal EditionとProfessional Editionのどちらを使うか聞かれると思うので
Personal Editionを選択します。
次にサインインの画面になると思います。
Unityのアカウントを作成していない人は作成してから入りましょう。

こんな画面が出ればおっけーです。

とりあえずこれでプロジェクトを作る準備が出来ました。

・プロジェクトを作る

次にプロジェクトを作ってオブジェクトを生成するまでやって見ます。
New Projectを押すとプロジェクト作成画面になります。プロジェクトの名前はお好みで、今回は下のようにしました。

3Dか2Dか選択できますが、今回は3Dを選択します。

開くと画像のような画面になっていると思います。
初めて見ると情報量が多くてびっくりしますが、一つ一つ簡単に説明を。

……と、その前にちょっとだけレイアウト変更をします。
画面の一番右上の“Layout”というプルダウンをクリックして、“2by3”を選択してください。

“2by3″にするとこんな感じです。画面は大きく五つに分類できます。

個人的にこの画面が一番見やすいのですが、他のレイアウトがいいという方は
同じく“Layout”からお好みのレイアウトを選びましょう。

それでは各画面の見方に参ります。
Unityの画面には、
1・Scene
2・Game
3・Hieralchy
4・Project
5・Inspector
の五つがあります。

1・Scene

ここでは、オブジェクトの位置の変更や、回転、拡大縮小などを行います。
後述するマテリアルの登録等も、この画面に対して行います。

2・Game

ここでは、実際カメラに映る結果が表示されています。
ゲームを作成する場合には、ここがプレイヤーの見る画面になるわけですね。

3・Hieralchy

Sceneに配置されているオブジェクト達がここに一覧として表示されています。
オブジェクトの選択はSceneのオブジェクトを直接クリックする以外にここから選択することも可能です。

4・Project

ここではこのプロジェクト内に存在するオブジェクトからscriptファイルまでの全てが表示されます。
Hieralchyよりもより多くのものがここで管理できます。

5・Inspector

ここは選択されたオブジェクトの詳細が表示される場所です。
アトリビュートの変更などもここから行います。

次に、簡単にオブジェクトを生成してみます。
画像のように、一番上の“GameObject”から、“3D Object”>”Sphere”を選びます。

画面の中央に白い球体ができていれば成功です。
出てきたSphereをクリックすると移動や拡大縮小が行えます。
どの動作をするかは、画面左上のこれ

をクリックする、

もしくはキーボードで”q,w,e,r”を押すと切り替えることが出来ます。

それぞれ、
q>画面そのもののスクロール
w>移動
e>回転
r>拡大縮小
です。

視点を回転させたい場合は”Altキー”を押しながら、”左クリック+マウス移動”で。
視点を前後させたい場合は”Altキー”を押しながら、”右クリック+マウス移動”で出来ます。

この辺の操作はMayaとほぼほぼ同じなので、使ったことがある人はなじみやすいかもしれません。
これで簡単にUnityが触れるようになったはずです。

・シェーダーの前準備

シェーダーの話に入る前に、Unityのシェーダーを使用するにはまずマテリアルが無いといけません。
Unityではマテリアルにシェーダーを登録し、そのマテリアルをオブジェクトに登録する形になります。
というわけで作成して行きましょう。

画像のように、右クリック>Create>Folderでマテリアルを入れるフォルダをCreateします。
同じように右クリック>Create>Materialでマテリアルが作成出来ます。

白い玉の映ったマテリアルができたと思います。名前は自由に付けて構いません。
フォルダの外に作ってしまった場合はドラッグしてフォルダにドロップすれば移動が可能です。
このあたりの操作は普段のファイル操作と変わりませんね。

作ったマテリアルは、左クリックでドラッグした後、Scene内のマテリアルを設定したいオブジェクトの上で
ドロップすると登録することが出来ます。
マテリアルを選択するとInspectorが画像のようになっていると思います。

この内部を弄ることで表示される結果を変えることができます。
試しに色を変えました。Albedoのところにある、スポイトみたいなマークの隣の四角いUIをダブルクリックすることで、色を変更できるパレットが開きます。

変更されると、四角いUIの色も対応して変わります。良く見ると下と左上に表示されてる球体の色も変わっていますね。

白い球体が真っ赤になりました。

このInspector、マテリアルを選んだ状態でよく見るとにShaderというプルダウンがあります。

実はUnityのシェーダーはここに表示され、どのシェーダーを使用するかはここから選択できます。
試しに別の好きなシェーダーに変えてみると、Inspectorにあるアトリビュートが変わるのが確認できると思います。
シェーダーの登録はこれだけ簡単に行うことができるのです。
これから、ここに自分で作成したシェーダーを実際にオブジェクトに登録して行きます。

・シェーダーを作る

FolderやMaterialと同じように、シェーダーもCreateのShaderから作成することが可能です。
四種類候補が出てくると思いますが、今回はImageEffectShaderをCreateします。

補足:Unityのバージョンによっては、Create>Shaderで作れるシェーダーが1種類の場合があります。その場合は後述する内容とソースコードの中身が一致しません。

Createした後は、好きな名前をつけましょう。今回は“myShader”にしてみました。

作成したシェーダーファイルを開くと以下のようなソースコードになっていると思います。
もし上手く作れない場合は、メモ帳などに以下のソースコードをコピペして、
“好きな名前.shader”で保存した後にUnityのProjectにドロップするのでも大丈夫です。

Shader "Hidden/myShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                // just invert the colors
                col = 1 - col;
                return col;
            }
            ENDCG
        }
    }
}

これがシェーダーの中身になります。
恐らく初見ではわけがわからないと思います。僕もそうでした。
まずはこのシェーダーを使って見るために、
一番上のこの部分

Shader "Hidden/myShader"

Shader "Custom/myShader"

こんな感じに変更します。
これによって、先ほど作成したマテリアルのShaderのところにCustomが追加され、
自分のつけた名前のシェーダーが選べるようになっているはずです。
ちなみにCustomの部分は好きな名前にするとそれが追加されます。せっかくなのでDF_TALKに変えてみました

シェーダーを作ったら、マテリアルのShaderのプルダウンから、自分のつけた名前のシェーダーを選びましょう。
このシェーダーはテクスチャに登録した画像のカラーを反転させる内容になっています。
せっかくなので下のようなテクスチャを登録してみました。

テクスチャ画像は、その画像のあるフォルダから、Projectの中にドラッグするだけで
登録できます。
登録したテクスチャ画像は、マテリアルのInspectorにあるNone(Texture)となっている場所に
ドラッグ&ドロップしましょう。

オブジェクトもちょっと増やしてそれぞれにマテリアルを適用してみました。
なんかちょっと不思議な見た目ですね。

複数オブジェクトを出して、カメラを回転して見ると、なにやら重なって見えます。

これはシェーダーのカリングと深度値の設定によるものです。
今回は詳しく説明しませんが、気になる人は先ほどのソースコードの

Cull Off ZWrite Off ZTest Always

この部分を

Cull Back ZWrite On ZTest LEqual

に変更して見てください。見慣れた感じ(?)に描画されると思います。

ちょっと脱線してしまいましたが、このファイルを書き換えていくことで、自分の書いたシェーダーを使うことができるわけです。
中身について理解するために、簡素なシェーダーを以下に書きます。

公式のサンプルを少し弄ったものになっています。
公式サンプルはこちら
Unityのシェーダーは3種類ありますが、今回は頂点・フラグメントシェーダーを使用します。
以下のソースを書き込んで動かしてみてください。書き込む際は先ほどCreateしたシェーダーを上書きするか、新しくCreateしましょう。

Shader "DF_TALK/myShader"//ここは自分の設定した名前でどうぞ
{
    Properties
    {
        //色を設定するアトリビュートを作る
        _myColor ("myColor", Color) = (1.0, 1.0, 1.0, 1.0)
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM//ここからCgでプログラムを記述するという宣言をする
            #pragma vertex vert
            #pragma fragment frag

            float4 _myColor;

            //頂点に対しての処理
            float4 vert(float4 vertexPos : POSITION) : SV_POSITION
            {
                // 座標変換後の位置を返す
                return mul(UNITY_MATRIX_MVP, vertexPos);
            }
            //ピクセル単位の処理
            float4 frag() : COLOR
            {
                return _myColor;
            }
            ENDCG//CGPROGRAMの終わりがここという宣言をしている
        }
    }
}

アトリビュートが“myColor”だけのシェーダーが作れたはずです。
myColorを変更するとオブジェクトの色が変わるのでちょろっと弄ってみてください。

それでは、このシェーダーでそれぞれ何をやっているのか、順を追って説明していきます。

Properties
{
    //色を設定するアトリビュートを作る
    _myColor ("myColor", Color) = (1.0, 1.0, 1.0, 1.0)
}

ここでは、UnityのInspectorに表示するアトリビュートを設定します。
_myColor>このプログラムの中で扱う時の名前。
“myColor”>Inspectorに表示される名前。
Color>アトリビュートの種類。
となっています。後ろにある=(1.0, 1.0, 1.0, 1.0)の部分では、初期値を設定しており、四つ数値があるのは、ColorというアトリビュートがRGBA(赤、緑、青、透明度)の四つのステータスを持っているからです。
初期値の部分は、アトリビュートの種類によって書き方が変わります。

SubShader
{
    //省略
}

SubShaderは、シェーダーの中身を記述する部分です。
SubShaderは複数宣言することができ、その時動いている環境に対応したシェーダーが選ばれるようになっているそうです。
ゲームエンジンなので、様々な環境に対応出来るようになっているわけですね。誰かに使ってもらったりするわけでなければ特に気にしなくて大丈夫だと思います。

Pass
{
    //省略
}

このPassはSubShaderの中に最低一つ無ければならない処理になっています。
複数のPassを使うことでより複雑な表現を行うこともできますが、今回は扱いませんので、
SubShaderの中にPassを記述するという部分を覚えておけばよいでしょう。

CGPROGRAM
ENDCG

この二つの宣言で囲まれた部分に、どの種類の言語でシェーダーの記述を行うかを決定しており、今回はCgというシェーディング言語で書くことを設定しています。
他にも種類がありますが、今は知らなくても大丈夫です。

#pragma vertex vert
#pragma fragment frag

頂点シェーダーとフラグメントシェーダーの処理を行う関数が、どのような名前になるかを設定しています。
今回の頂点シェーダー関数はvert、フラグメントシェーダー関数はfragという名前になっています。
ここでは名前を決めているということがわかってもらえたら大丈夫です。

float4 _myColor;

ここでは変数を設定しています。
_myColorは、Propertiesで設定したものと同じ名前を設定する必要があり、こうすることでUnity側のInspectorで設定した値をシェーダー内で扱うことが出来ます。
“float4”で宣言しているのは、先ほども説明したとおり_myColorが4つの要素を持っているためです。
例えばこれが二次元の座標を示す変数であれば、“float2”となります。

次はシェーダーの肝である頂点・フラグメントシェーダーの頂点シェーダー関数についてです。

・頂点シェーダーについて

//頂点に対しての処理
float4 vert(float4 vertexPos : POSITION) : SV_POSITION
{
    // 座標変換後の位置
    return mul(UNITY_MATRIX_MVP, vertexPos);
}

ここが頂点についての処理を行っている頂点シェーダーです。先ほど決めた名前の関数を宣言しています。
頂点シェーダーの処理は“オブジェクトの各頂点が、画面のどこに表示されるのか”を決定します。

(float4 vertexPos : POSITION)の部分で、受け取る頂点情報を設定しています。
変数の後ろに : POSITIONをつけることで、Unity側から、オブジェクトのローカル座標の頂点情報が入ってきます。
この : POSITIONのように、コロンの後ろに大文字で宣言されている部分をセマンティクスと言い、セマンティクスによって決められた役割が変数に与えられます。
一番後ろの : SV_POSITIONは、もろもろの座標変換を行った後の頂点座標であることを宣言しています。
戻り値はfloat4もしくはfloat4の変数を含んでいる構造体でなければならないようです。

return mul(UNITY_MATRIX_MVP, vertexPos);

mulという関数は、行列演算を行ってくれる関数です。
入力されてきた頂点情報vertexPosに、UNITY_MATRIX_MVP行列をかけているわけですね。
UNITY_MATRIX_MVPには、いわゆる座標変換行列が入っていて、
Model View Projectionの三つの変換がまとまっています。これを行うことで、
頂点の情報がしっかりとカメラに映る位置に移動すると思ってもらえれば大丈夫です。
試しに、

return vertexPos;

として座標変換を止めてみると、画像のように画面に凄い主張してくるオブジェクトが表示できるはずです。
Scene上でカメラを動かしてみても微動だにしないと思います。

・フラグメントシェーダーについて

頂点シェーダーで出力した情報によって、オブジェクトの位置や向きが決まりました。
次はそのオブジェクトが描画される範囲をピクセル化します。これをラスタライズと呼びます。

この“ラスタライズされた各ピクセルに対して処理を行う”のがフラグメントシェーダーです。
ちなみにフラグメントシェーダーはピクセルシェーダーと呼ばれることもあります。

//ピクセル単位の処理
float4 frag() : COLOR
{
    return _myColor;
}

今回はUnityのInspectorで設定できるColor情報をそのまま使用しています。
: COLORは、フラグメントシェーダーでカラー情報を返すことを設定しています。

このフラグメントシェーダーでは、頂点シェーダーの処理を経てラスタライズされたピクセルを
_myColorで塗りつぶす処理を行っているわけです。
大体のシェーダーは、ここで更に法線情報やライトの向きなども使用し、どのような色をつけるか計算を行っています。

これで説明がひと段落しました。

個人的に大事なところは、頂点シェーダーとフラグメントシェーダーの役割を知ることがまず一つ。
頂点・フラグメントシェーダーの流れは他のシェーダー言語でもあるので、これを覚えるだけでもシェーダーのイメージが変わるのではないでしょうか。

二つ目はセマンティクスが何の情報を持ってくるのか知ることです。
これはCg特有の書き方ですが、何を持ってこれるのかわかりやすいと思います。
もし、別のサンプルやアルゴリズムを見て欲しい情報があったら、
まずセマンティクスで持ってくることはできないかどうかを探して見るのが良いと思います。

次に、ここまでのことを踏まえて、初めにCreateしたシェーダーの中身を見直して見ます。
初めて見た時と見え方が変わっていると良いのですが……どうでしょうか?
少しカリングとデプスの設定を書き換えてあるのと、説明のコメントアウトを追記してあります。
また、後で他のアトリビュートも扱いたくなった時用に、全種類では無いですがPropertiesにアトリビュートを追加してます。
公式のPropertiesの説明はこちら
良ければいろいろ変更してみてください。

Shader "Custom/myShader"
{
    Properties
    {
        //テクスチャを入力したい時の宣言
        //"white"はデフォルトのテクスチャで、それで初期化を行っています
        //何も書かずに""{}とすることもできます
        _MainTex ("Texture", 2D) = "white" {}

        //Boxに数値を入力する形のアトリビュートです
        _Value ("Value", float) = 1.0

        //こっちはスライダーで数値を選べるアトリビュートです
        _Slider ("Slider", Range(0.0, 1.0)) = 1.0

        //ColorとVectorです。要素の数は同じですが、表示のされ方が違います
        _Vector ("Vector", Vector) = (0.0, 0.0, 0.0, 0.0)
        _myColor ("myColor", Color) = (1.0, 1.0, 1.0, 1.0)
    }
    SubShader
    {
        //カリングと、デプス描画についての設定を行っています
        Cull Back ZWrite On ZTest LEqual
        //Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            //便利な構造体や関数が定義されているinclude
            //今回は使われてないですが、とりあえず書いておくのでも今は問題ないはず
            #include "UnityCG.cginc"

            //入力に使う構造体です
            //頂点入力のPOSITIONに加えて、オブジェクトのuv座標である
            //TEXCOORD0が増えています。
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            //出力に使う構造体です
            //入力と見比べると、POSITIONがSV_POSITIONに変わっていますね
            //uvの座標をフラグメントシェーダーで受け取りたいので、TEXCOORD0のセマンティクスが宣言されています
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            //入力がappdataになっています
            //また、戻り値の型がfloat4ではなく上で宣言したv2fになっています
            //v2fの中にあるvertexがfloat4でSV_POTISIONなので問題ないわけですね
            v2f vert (appdata v)
            {
                //構造体を生成
                v2f o;

                //頂点情報には先ほどと同じく変換した情報を入れます
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);

                //uv座標を引き渡しています
                o.uv = v.uv;
                return o;
            }

            //上で宣言したアトリビュート達。書き換えて見たい場合に使ってみてください
            //float4の個別のアトリビュートにアクセスしたい時は
            //_Vector.xのように後ろにつけることで、その要素を扱うことができます
            float _Value;
            float _Slider;
            float4 _Vector;
            float4 _myColor;

            //Propertiesで宣言したテクスチャを使えるように宣言しています
            //2Dテクスチャはsampler2Dで定義します
            sampler2D _MainTex;

            //戻り値がfixed4になっています。これはfloatより精度の低い小数の型です
            //頂点シェーダーの出力はフラグメントシェーダーで受け取ることができますこれは先ほどのシェーダーには無かった部分ですね
            //(v2f i)の部分で、頂点シェーダーの戻り値と同じ型を宣言しているのがわかります
            // : SV_Targetは新しい表記で、COLORと同じ意味のセマンティクスみたいです(勉強不足で断言できません…)
            fixed4 frag (v2f i) : SV_Target
            {
                //出力するカラーを作っています
                //tex2Dという関数は、Sampler2Dとuv座標を引数にとって、今描画しようとしているピクセルは、テクスチャのどこのピクセルなのかを計算しています
                fixed4 col = tex2D(_MainTex, i.uv);

                //カラーはRGBAそれぞれ0.0~1.0の範囲になるので、1からcolを引くことによって、色が反転します
                col = 1 - col;
                return col;
            }
            ENDCG
        }
    }
}

・終わりに

ここまで読んで頂いた方はお疲れ様でした。
実際にライトや視線のベクトルを使ってシェーディング! みたいな内容は、次回以降やっていけたら良いなと思ってます。
至らぬ点も多々あったかと思いますが、何か一つでも皆様のプラスになれば幸いです。

それでは。

※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。

コメント

コメントはありません

コメントフォーム

コメントは承認制ですので、即時に反映されません。ご了承ください。

CAPTCHA


 

*