Kosaku Kurino

Kosaku Kurino

【Unity】 スクリプトで始めるUnity Shaderの基礎2019 (1)

はじめに

この記事では、Shaderはどのようなものなのか、そしてどのようにShaderを作っていくのかという超入門的なところを説明しています。

Shaderを勉強し始めたばかりのため、説明が間違ってる箇所、説明があいまいな個所があると思いますがご了承下さい。間違っている箇所あればコメントにて指摘してください。

シリーズ

【Unity】スクリプトで始めるUnity Shaderの基礎2019 (2)

Shaderとは

Shaderは3Dオブジェクトの見え方をカスタマイズできるものです。 Shaderを使えば、3Dオブジェクトをアニメ調(Toon Shader)にしたり、内部から光が漏れているような表現をさせたりできます。

toon-shader.jpg

Unityのシェーダーは大きく2つに書き方が分かれます。

Surface ShaderVertex/Fragment Shader

Surface ShaderはVertex/Fragment Shaderを簡単に扱えるように包んだものみたいなイメージです。なので基礎を学ぶのであれば、Vertex/Fragment Shaderを学ぶのがいいと思います。この記事ではVertex/Fragment Shaderでスクリプトを書いていきます。

Shader言語について

Unityでは、Cg言語とShaderLabを駆使してShaderを書きます。

Cg言語

Cg言語は、GPUを利用してCG描写させるためのShader言語です。Unityでは使われていませんが、Cg言語以外にも有名なShader言語(HLSL、GLSL、DirectX)があります。

ShaderLab

ShaderLabはUnity独自の言語で、UnityでShaderを書くのに便利な関数や仕組みが容易されています。

Shaderスクリプトの例

CGPROGRAM ~ ENDCGで囲われている箇所は、Cg言語で書かれています。Propertiesや、TagsはShaderLabです。

Shader "Custom/SampleShader"
{
  Properties
  {
    _Color ("Color", Color) = (1,1,1,1)
  }
  SubShader
  {
    Tags { "RenderType"="Opaque" }
    LOD 200

    Pass
    {
      CGPROGRAM

      #pragma vertex vert
      #pragma fragment frag

      #include "UnityCG.cginc"

      struct appdata
      {
        float4 vertex : POSITION;
      };

      struct v2f
      {
        float4 vertex : SV_POSITION;
      };

      fixed4 _Color;

      v2f vert(appdata v)
      {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        return o;
      }

      fixed4 frag(v2f i) : SV_TARGET
      {
        return _Color;
      }

      ENDCG
    }
  }
}

CG描写の流れについて(レンダリングパイプライン)

CG描写の流れのことをレンダリングパイプラインといいます。ざっくりイメージとしては、3D空間に配置したオブジェクトの頂点情報から3D空間にモデルを作り出し、カメラを通してモデルが2D画面のどの位置に映るかを算出し、映る場所に色を塗るというような流れになっています。

Vertex Shaderでは、基本的に3D空間の頂点を操作することができます。 Fragment Shaderでは、2D画面の画素(面)一つひとつに色を塗ることができます。

CGPROGRAM内で#pragma vertex vertを宣言し頂点の操作を、#pragma fragment fragを宣言し画素に色を塗れるようになります。

ハンズオン

準備

まず、ShaderとMaterialを作成しましょう。

Poject内で右クリック→Create→Shaderから適当に選んでShaderを作成しましょう。作成できたらShaderを右クリック→Create→Materialでマテリアルを作成しましょう。

BoxオブジェクトにMaterialを適用しましょう。

適当にBoxオブジェクトを作成し、Materialをアタッチしてください。

Shader

それでは実際に、Shaderを書いていきます。

Shaderはすでに何か書かれていますが消してしまってください。

Shader "Custom/SampleShader" // Shader "グループ名/Shader名"
{
  SubShader
  {
    Tags { "RenderType"="Opaque" } // Shaderの分類を定義します
    LOD 200

    Pass 
    {
      CGPROGRAM

      // Cg言語をここに書きます。

      ENDCG
    }
  }
}

Cg言語を書いていきます。 まずは、Vertex/Fragment Shaderを使用するための宣言、Unityの便利な関数が詰まったUnityCG.cgincを読み込みます。

Pass 
{
  CGPROGRAM

  #pragma vertex vert // 頂点の操作
  #pragma fragment frag // 画素の色塗り

  #include "UnityCG.cginc"
  
  ENDCG
}

vert関数とfrag関数使用する頂点情報の構造体を作ります。 頂点情報として、座標を使用するのでfloat4型でvertexを定義しています。

POSITIONSV_POSITIONはセマンティクスといい、変数がどのような役割を持っているのかを示しています。セマンティクスのおかげでGPUは何の変数なのか理解できレンダリングパイプラインをうまく流すことができています。

Pass 
{
  CGPROGRAM

  #pragma vertex vert
  #pragma fragment frag

  #include "UnityCG.cginc"
  
  // vert関数で使用する構造体
  struct appdata
  {
    float4 vertex : POSITION;
  };

  // vert関数、frag関数で使用する構造体
  struct v2f
  {
    float4 vertex : SV_POSITION;
  };

  ENDCG
}

頂点情報のつまった構造体ができたので、頂点を操作するvert関数を書いていきます。

ここでのvert関数はappdata型からv2f型に変換しているだけです。

UnityObjectToClipPosUnityCG.cgincが用意してくれている便利な関数です。3D空間の座標情報を2D画面の画像情報に変換してくれます。

Pass 
{
  ...

  struct appdata
  {
    float4 vertex : POSITION;
  };

  struct v2f
  {
    float4 vertex : SV_POSITION;
  };

  // 頂点の操作をする関数
  v2f vert(appdata v)
  {
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    return o;
  }

  ENDCG
}

vert関数ができたので、画素に色を塗るfrag関数を書いていきます。

frag関数ではfixed4型で塗る色のrgbaを返しているだけです。fixed4(0.5, 0.5, 0.5, 1)なのでグレーになります。

Pass 
{
  ...

  struct appdata
  {
    float4 vertex : POSITION;
  };

  struct v2f
  {
    float4 vertex : SV_POSITION;
  };

  v2f vert(appdata v)
  {
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    return o;
  }

  // 画素に色を塗る関数
  fixed4 frag(v2f i) : SV_TARGET
  {
    return fixed4(0.5, 0.5, 0.5, 1);
  }

  ENDCG
}

ただグレーに塗るだけでは寂しいにで、インスペクターから色を変更できるようにします。

これを実現するのはShaderLabです。Propertiesを利用して、インスペクター上で色を選択できるようにします。

変数は変数名("インスペクター上の表記", 変数の型) = 初期値で定義します。

Shader "Custom/SampleShader"
{
  // インスペクターで変数を変えられるようになる
  Properties
  {
    _Color ("Color", Color) = (1,1,1,1)
  }

  ...

}

Colorの項目がインスペクター上で確認できるはずです。

color-pick.jpg

次に、_Colorの変数で画素を塗るようにCGPROGRAMの中を修正します。

これで、インスペクター上で色を変えられるShaderができたはずです。

Pass 
{
  ...

  struct appdata
  {
    float4 vertex : POSITION;
  };

  struct v2f
  {
    float4 vertex : SV_POSITION;
  };

  // Propertiesで定義した変数をCGPROGRAM内で定義する
  fixed _Color;

  v2f vert(appdata v)
  {
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    return o;
  }

  fixed4 frag(v2f i) : SV_TARGET
  {
    return _Color; // 定義した_Colorを返すように書き換える
  }

  ENDCG
}

最後に、時間とともに頂点を動かして拡大・縮小させてみます。

UnityCG.cgincが時間も_Timeとして用意してくれているので、こちらをsinカーブの引数として利用して拡大・縮小させます。

Pass 
{
  ...

  v2f vert(appdata v)
  {
    v2f o;
    float amp = 0.5 * sin(_Time * 100); // 拡大率を決める
    o.vertex = UnityObjectToClipPos(v.vertex * amp); // 拡大率を座標にかける
    return o;
  }

  ...
}

shader_scale_up.gif

最後に

Shaderがどのようなものか、そしてどのように作っていくのか見えてきましたか? 次回は、頂点座標(POSITION)だけでなく、UV座標など他の変数も使いShaderをより深く理解できる記事を書きます。

【Unity】スクリプトで始めるUnity Shaderの基礎2019 (2)