おもちゃラボ

Unityで遊びを作ってます

【Unity】四角形のポリゴンを作ってテクスチャを貼る

UnityにはPlaneなど、板ポリ用のポリゴンが用意されています。大概の場合はこれらのポリゴンを利用すれば十分ですが、ゲーム実行時に動的にポリゴンを作りたい場合にはスクリプトから作ることもできます。

ここではスクリプトを使って四角形のポリゴンを作成し、そこにテクスチャを貼る方法を紹介したいと思います。想像よりかは面倒だと思います(笑)ただ、応用すれば(そして、頑張れば・・・)板ポリゴンだけでなく、3Dモデルも作れるようになったりします。

四角形のポリゴンを作るのに必要な事前知識

御存知のとおり、ゲームで使う3Dモデルの最小単位は三角形のポリゴンです。四角形ポリゴンを書く場合も三角形のポリゴンを2つ描くことになります。

f:id:nn_hokuson:20180213185922p:plain:w300

ポリゴンを描くために必要な情報は次の3つです。

  • 頂点座標
  • UV座標
  • インデックス

頂点座標

ひとつ目の頂点座標は名前の通りポリゴンに使われている頂点の座標です。この頂点座標を頂点数ぶん配列で用意します。四角形なら4個ですね。

UV座標

ふたつ目UV座標はテクスチャの座標になります。頂点ごとにテクスチャのどの部分を使うかをテクスチャ座標(UV座標)を使って指定します。UV座標は0〜1の範囲で指定します。こちらも頂点数ぶん必要になります。

f:id:nn_hokuson:20180213194517p:plain:w400

ちなみに・・・モデルはキイロイトリです。北海道の雪の上で撮影しました(笑)

インデックス

最後のインデックスは聞き慣れないかもしれません(OpenGLやWebGL、DirectXなどを使っていた方にはおなじみですが・・・)頂点配列の何番目のデータを使ってポリゴンを描くかを指定します。

四角形を描く場合、三角形ごとに頂点座標を指定した場合、指定しなければいけない頂点座標は6点必要になります。

f:id:nn_hokuson:20180213190042p:plain:w400

2頂点ぶんはデータがかぶってしまいますね。これではメモリの無駄遣いなので、頂点配列は4頂点ぶんだけ用意して、その中の何番目のデータを使用するかだけを指定する方式がよく使われます。この使用する頂点データの番号をインデックスとして指定します。

f:id:nn_hokuson:20180213190104p:plain:w500

四角形のポリゴンを作成する

まずは四角形ポリゴンを描画するためのGameObjectを作りましょう。ヒエラルキーウインドウからCreate→Empty Objectを選択して空のゲームオブジェクトを作ります。名前はQuadに変更しておきましょう。

f:id:nn_hokuson:20180213190122p:plain:w300

Unityでポリゴンを表示するためにはMesh FilterとMesh Rendererコンポーネントが必要になります。インスペクタからAdd Componentでこれらのコンポーネントを追加してください。

f:id:nn_hokuson:20180213190133p:plain:w420

続いて四角形ポリゴンを動的に生成するためのスクリプトを作成しましょう。プロジェクトウィンドウで右クリックしてCreate→Script→C# Scriptを選択し、ファイル名をQuadGen.csに変更しましょう。

スクリプトファイルが作成できたら、次のスクリプトを入力してください。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Quad : MonoBehaviour 
{
    public Material mat;

    void Start () 
    {
    Mesh mesh = new Mesh ();
    mesh.vertices = new Vector3[] {
        new Vector3 (-1.2f, -1.2f, 0),
        new Vector3 (-1.2f,  1.2f, 0),       
        new Vector3 (1.2f , -1.2f, 0),
        new Vector3 (1.2f ,  1.2f, 0),
    };

    mesh.uv = new Vector2[] {
        new Vector2 (0, 0),
        new Vector2 (0, 1),
        new Vector2 (1, 0),              
        new Vector2 (1, 1),
    };

    mesh.triangles = new int[] {
        0, 1, 2, 
        1, 3, 2,
    };
    GetComponent<MeshFilter> ().sharedMesh = mesh;
    GetComponent<MeshRenderer> ().material = mat;
    }
}

このスクリプトでは頂点座標を格納するvertices配列、UV座標を格納するuv配列、インデックスを格納するindex配列を用意しています。

ここでは次のように(-1.2f,-1.2f)から(1.2f, 1.2f)まで、2.4m四方の正方形のポリゴンになるように座標を指定しています。ここでは、頂点座標は「左下」→「左上」→「右下」→「右上」の順番(ピンク色のインデックス)で宣言しています。

f:id:nn_hokuson:20180213190330p:plain:w300

uv配列には頂点座標と対応したUV座標を指定します。ここでは次のような対応になるようにUV座標を指定しました。UV座標の順番は各頂点の宣言の順番と対応するように「左下」→「左上」→「右下」→「右上」の順番で宣言していることに注意してください。

f:id:nn_hokuson:20180213191626j:plain:w300

またまた登場、キイロイトリ(笑)
[asin:B01LL52EI8:detail]


最後にインデックス配列です。今回は次のように2つの三角形を作ります。ひとつ目の三角形のインデックスは「0, 1, 2」、2つめの三角形のインデックスは「1, 3, 2」になります。

f:id:nn_hokuson:20180213195308p:plain:w300

ここでふたつ目の三角形のインデックスを「1,2,3」ではなく「1,3,2」としていることに注意してください。

インデックスは必ず「時計回り」になるように指定する必要があります。反時計回りで指定すると法線が逆向きのポリゴンが生成されて、表示されていないように見えてしまうので注意してください。

f:id:nn_hokuson:20180213190655p:plain:w500

最後に頂点座標・UV座標・インデックスの3つの情報をMeshオブジェクトにセットし、MeshFilterコンポーネントのshaderedMesh変数に登録しています。同時に表示するテクスチャのマテリアルも登録しています。

四角形のポリゴンを表示してみる

ここまでで四角形のポリゴンを作成するスクリプトができました。一旦動作を確認するために、QuadGen.csスクリプトをヒエラルキーウインドウのQuadにドラッグ&ドロップしてください。

f:id:nn_hokuson:20180213192614j:plain

実行すると次のようにピンク色の四角形が表示されると思います。これはまだテクスチャを指定していないことが原因です。

f:id:nn_hokuson:20180213192639p:plain:w350

テクスチャをセットして表示する

そこで、テクスチャを指定したマテリアルを作りましょう。プロジェクトウィンドウでCreate→Materialを選択し、インスペクタのShaderを「Unit/Texture」に変更して、使用するTextureをドラッグ&ドロップしてください。

f:id:nn_hokuson:20180213193013j:plain

最後に作成したテクスチャのマテリアルをQuadGen.csに指定します。ヒエラルキーウインドウでQuadを選択して、インスペクタからQuadGenスクリプトのmat欄を探します。そこに作成したテクスチャのマテリアルをドラッグ&ドロップしてください。

f:id:nn_hokuson:20180213193221j:plain

実行するとちゃんとテクスチャが表示されているのが確認できるはずです。

f:id:nn_hokuson:20180213193245p:plain:w350

キイロイトリ、めっちゃカワイイですね(もうええっちゅーねん)

[asin:B077WH3HBY:detail]

対策とまとめ

もしもうまく表示されていないときは・・・

  • UV座標の順番は頂点座標と対応しているか
  • インデックスの指定順序は正しいか
  • インデックスは時計回りに指定したか

などを確認してみてください。

今回はスクリプトを使って四角形の板ポリゴンを作成してみました。使う機会は多くないかもしれませんが、動的にポリゴンを作りたい場合に役に立つので覚えておきたいですね!

【Unityシェーダ入門】デプスバッファの内容を表示する

デプスバッファ(深度バッファ、Zバッファ)とはカメラからオブジェクトまでの距離(深度値、Z値)を格納したバッファになります。

f:id:nn_hokuson:20180208191631j:plain:w600

このデプスバッファは被写界深度のエフェクトや影の計算など、さまざまな場面で使われています。Unityを使っていると影やエフェクトなどは自動的に計算してくれるので、あまり馴染みがないかもしれません・・・

ただ、エフェクトを作るときに必要になることが時々あります。そこで、ここではデプスバッファ(Zバッファ)の内容を表示する方法を書いていきます。

デプスバッファの値を表示する流れ

上にも書いたとおり、デプスバッファとはカメラからオブジェクトまでの距離を格納したバッファです。この距離をZ値といいUnityの場合0〜1で取得できます。カメラからの距離が近ければ0、遠くなるに従って値が大きくなっていきます。つまり、近くにあるものほど黒く表示されるのですね。

f:id:nn_hokuson:20180208193944j:plain

デプスバッファの値はシェーダの_CameraDepthTexture変数で取得できます。そこで、この値を表示するまでの流れは次のようになります。

  1. Z値を取得して表示するシェーダを作る
  2. ポストエフェクト用のC#スクリプトを作る
  3. C#スクリプトにdepthシェーダを指定する

それでは1ステップずつ見ていきましょう。

Z値を取得して表示するシェーダを作る

まずはデプスバッファから値(Z値)を取得し表示するシェーダを作成しましょう。プロジェクトウィンドウで右クリックし、Create→Shader→Unlit Shaderを選択して、depth.shaderという名前で保存してください。また、作成したdepth.shaderを右クリックしてCreate→MaterialでUnlit_depth.matというマテリアルを作成しておきます。

f:id:nn_hokuson:20180208192022p:plain:w200

続いて今作成したdepth.shaderを開いて次のプログラムを入力してください。

Shader "depth"
{
        SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _CameraDepthTexture;

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

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

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture,i.uv));
                return fixed4(depth,depth,depth,1);
            }
            ENDCG
        }
    }
}

通常の頂点・フラグメントシェーダを見慣れている方にとっては非常にシンプルな内容になっていると思います。

特徴としては、sampler2D _CameraDepthTexture;としてデプスバッファのテクスチャを宣言し、フラグメントシェーダでtex2D関数を使ってデプスバッファからZ値を取り出しています。最後にデプス値をRGBAの色に変換して出力しているだけです。

ポストエフェクト用のC#スクリプトを作る

デプスバッファの内容はポストエフェクトとして表示するのでした。そこで、ポストエフェクト用のスクリプトを作りましょう。

プロジェクトウィンドウで右クリックし、Create→Script→C# Scriptを選択して、DispDepth.csというファイルを作成してください。

作成したC#スクリプトに次のプログラムを入力します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

public class DispDepth : MonoBehaviour {
    public Material mat;
    void Start () 
    {
        GetComponent<Camera>().depthTextureMode |= DepthTextureMode.Depth;
    }

    public void OnRenderImage(RenderTexture source, RenderTexture dest)
    {                
        Graphics.Blit(source, dest, mat);
    }
}

OnRenderImageの中でGraphics.Blitメソッドを使ってポストエフェクトをかける方法は定石です。ポストエフェクトの基本的な方法は次の記事を参考にしてください。

nn-hokuson.hatenablog.com

簡単に説明すると、OnRenderImage の第一引数であるsourceにはレンダリング後の画像が入っていいます。このsource画像に対してmatで指定したシェーダの処理をして最終出力結果をdestに出力するイメージです。

f:id:nn_hokuson:20180208194757j:plain:w650

またStartメソッドの中で、カメラがデプステクスチャを生成するように次の一行を追加しています。これを書かないと_CameraDepthTextureでデプス値を取得することが出来ません。

_camera.depthTextureMode |= DepthTextureMode.Depth;

C#スクリプトにdepthシェーダを指定する

DispDepth.csのスクリプトが入力できたら、これをヒエラルキービューのMain Cameraにドラッグ&ドロップしてアタッチしましょう。

f:id:nn_hokuson:20180208192639j:plain

最後にスクリプト中のMat変数にdepth.matを指定します。Main CameraにアタッチしたDepthスクリプトのMat欄にdepth.matをドラッグ&ドロップしてください。

f:id:nn_hokuson:20180208192923j:plain

実行すると次のようにゲーム画面にデプスバッファの値が表示されます。

f:id:nn_hokuson:20180208192958p:plain:w400

デプスバッファを使った応用例は西川善司さんの「ゲーム制作者になるための3Dグラフィックス技術」にいくつか例が載っています。合わせて参考にしてみてください。

[asin:4844333542:detail]
[asin:4844329510:detail]

【Unityシェーダ入門】アウトラインシェーダを作る

トゥーンシェーダなどを使ってアニメ調の塗りをする場合、モデルにアウトラインを付けたいことがあります。ここでは、モデルと背景の境界線上にアウトラインを表示するシェーダを作る方法を紹介します。

f:id:nn_hokuson:20180205200341j:plain

今回の記事の内容は次のとおりです。

アウトラインの概要

モデルにアウトラインをつけるには次のように幾つかの方法があります。

  • ステンシルバッファを使う
  • 法線情報から境界を計算する
  • 少し膨らませたモデルを使う
  • ポストエフェクトで輪郭抽出する

ここでは直感的に分かりやすい3つめの「少し膨らませたモデルを使う」方法を紹介したいと思います。この方法は2パス(モデルを2回描画する)でアウトラインを描画します。

まず1パス目では、法線方向にモデルを少し膨らませてから、モデルの裏面を塗りつぶしで描画します。これが輪郭線になります。

f:id:nn_hokuson:20180206194757p:plain:w500

続いて2パス目では通常通りモデルを描画します。このときモデルの表面のみを描画するようにします。1パス目では少し大きめのモデルを描画しているため、この部分がはみ出てアウトラインになります

f:id:nn_hokuson:20180206194804p:plain:w500

このように、1パス目で法線方向に拡大したモデルを塗りつぶして描画し、2パス目で通常通り描画することで、アウトラインのついたモデルを作ることが出来ます。直感的に理解しやすいですね!

また、ステンシルバッファを使ってアウトラインを表示する方法は(Unityについての記事ではありませんが)こちらの記事が参考になるかと思います。

wgld.org

ポストエフェクトとして輪郭抽出する方法は「DirectX 9 シェーダプログラミングブック」で詳しく解説されています(こちらもUnityではありませが)

[asin:4839912475:detail]

それでは実際にシェーダを書いていきましょう。

シェーダファイルの準備

続いてアウトライン用のシェーダを作ります。プロジェクトビューで右クリック→Create→Shader→Unlit Shaderを選択し、作成したファイル名をoutline.shaderに変更します。

続いてこのシェーダファイルをアタッチするマテリアルも作成しましょう。outline.shaderを選択した状態で右クリック→Create→Materialを選択すると、Unlit_outlineというマテリアルが作成されます。

f:id:nn_hokuson:20180205200457p:plain:w200

outline.shaderが作成できたら、次のシェーダプログラムを入力してください。ここでは頂点・フラグメントシェーダを使って記述しています。

Shader "outline"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            Cull Front

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                v.vertex += float4(v.normal * 0.04f, 0);   
                o.vertex = UnityObjectToClipPos(v.vertex); 
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = fixed4(0.1,0.1,0.1,1);                
                return col;
            }
            ENDCG
        }

        Pass
        {
            Cull Back

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 normal : NORMAL;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {                
                half nl = max(0, dot(i.normal, _WorldSpaceLightPos0.xyz));
                if( nl <= 0.01f ) nl = 0.1f;
                else if( nl <= 0.3f ) nl = 0.3f;
                else nl = 1.0f;
                fixed4 col = fixed4(nl, nl, nl, 1);
                return col;
            }
            ENDCG
        }
    }

1パス目の頂点シェーダではモデルを頂点方向にすこし膨らませています。ここでは裏面のみを描画するため「Cull Front」を指定していることに注意してください。

頂点シェーダに入力される頂点座標と法線方向はローカル座標系です。そこでローカル座標系でモデルを膨張させてから、UnityObjectToClipPos関数を使って頂点座標をワールド座標系に変換しています。

1パス目のフラグメントシェーダは、特にシェーディングの計算などはせず、黒色でベタ塗りしているだけです。このフラグメントシェーダで指定した色がアウトラインの色になります。

続いて2パス目ではモデルを通常通り描画します。ここではトゥーン調の表示にするためフラグメントシェーダでランバートの計算をした後、3段階に階調化して表示しています。トゥーンシェーダについては次の記事で解説しているので合わせて参考にしてください。

nn-hokuson.hatenablog.com

モデルにマテリアルをアタッチする

アウトラインシェーダが作れたら、マテリアルをモデルにアタッチしてみてください。ここでは球とトーラスをシーンに配置して、そこにoutlineマテリアルをドラッグ&ドロップしました。
f:id:nn_hokuson:20180205201651j:plain:w570

アウトラインシェーダの描画結果はこのようになりました。アウトラインがあるだけで、通常のトゥーンシェーダよりも引き締まって見えますね!

f:id:nn_hokuson:20180205200341j:plain:w550

まとめ

今回はトゥーンシェーダと合わせて使えるアウトラインシェーダを紹介しました。アウトラインを描画するには2パス必要にはなるものの、シェーダプログラムは分かりやすかったのではないでしょうか。次はステンシルバッファを使ったアウトラインシェーダも紹介したいと思います。

[asin:B01ET5I2FQ:detail]