渋谷ほととぎす通信

「Unityをわかりやすく」初心者のためのゲーム作りブログ

【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法

【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法

こんにちは、エンジニアのオオバです。

お悩みさん
お悩みさん
  • 3Dプログラミングで画像を貼り付ける方法を知りたい
  • UV座標が何なのかを知りたい
  • 画像がメッシュに貼り付けられる仕組みを理解したい
  • オオバ
    オオバ
    本記事ではこれらの悩みを解決します。

    このブログでは初心者向けに3Dプログラミングの基礎を解説しています。今回は画像(テクスチャ)です。画像をメッシュに貼り付ける仕組みを理解しておくことは大事です。

    いざ、画像を貼り付けると思ったのと違うことがあります。調整したくても仕組みを理解していなければ難しいですよね。本記事では3Dプログラミング初心者向けに画像をメッシュに貼り付ける方法を解説します。

    今回も前回同様シェーダーを書きますので、シェーダー初めての方は↓こちらの前回記事を読んでみてください。

    また、最初に用語を整理しておきます。画像は「テクスチャ」と呼ばれます。本記事でも画像のことは「テクスチャ」と呼称しています。

    C#でUV座標を書き込み、シェーダーで画像をメッシュに描画する

    頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法の結論を簡単にまとめます。

    C#でUV座標を書き込み、シェーダーで画像をメッシュに描画する
    • C#で各頂点にUV座標を書き込む
    • C#でテクスチャをシェーダーに渡す
    • シェーダーでUV座標とテクスチャを受け取る
    • UV座標とはテクスチャを貼り付ける座標(横軸U、縦軸Vの2つの値で表す)
    • 各頂点のUV座標からテクスチャの色を参照してピクセルに描画する

    以上のような手順でメッシュにテクスチャを描画します。本記事の肝は「UV座標」です。テクスチャをメッシュに貼り付ける際に必須の知識。 UV座標を理解していないとテクスチャがなぜその場所に貼られるのかわかりません。

    この記事ではイメージしづらい「メッシュをテクスチャに貼り付ける」処理を分かりやすく解説しています。なんとなくメッシュにテクスチャを貼り付けていた方はぜひ最後まで読んでみてください。これからの3Dプログラミングに役立つと思います。

    👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!

    テクスチャがメッシュに貼られる仕組み

    まずはテクスチャがメッシュに貼られる仕組みを理解しましょう。

    UV座標とは?

    そもそもUV座標とは何か解説します。UV座標とは「テクスチャの座標」です。横軸をU座標、縦軸をV座標になります。

    今回使用する幅高さ256pxのテクスチャをUV座標で表すと以下のようになります。

    【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法_2

    UV座標は左下が原点(0, 0) で、右上に位ほど値は大きくなります(最大値は1)。シェーダーはこの座標を使ってテクスチャの色を取得してピクセルを描画します。

    UV座標は「頂点」に書き込まれます。 つまり 事前に各頂点にUV座標を書き込む必要がある のです。
    頂点には座標以外にさまざまな値を保持することができ、UV座標もまた頂点に含むことができます。

    次の表は頂点によく書き込まれるデータの一部です。

    頂点データ内容
    頂点座標頂点の座標(必須)
    法線ベクトル頂点の方向
    UV座標画像を貼り付けるときに利用する座標
    色情報頂点カラー

    頂点が持つUV座標を使って、テクスチャの場所を指定し描画するという流れになります。

    頂点データについてはこちらの記事で詳しく解説しているのであわせて確認してみてください。

    UV座標とテクスチャ、頂点の関係性

    ここから具体的に、UV座標とテクスチャ、頂点の関係性を具体例を示しながら解説していきます。
    三角形メッシュの各頂点に以下のUV座標を書き込んでみます。

    【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法_3

    この状態の時、三角形メッシュはどのようにメッシュに描画されるか確認します。

    【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法_4

    三角形メッシュは↑このように描画されます。

    【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法_5

    つまり、ちょうどテクスチャの三角形の模様部分が描画されたというわけです。

    では、別のUV座標を指定してみましょう。

    【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法_6

    先の例とは違ってキリの悪い数値を書き込んでみました。

    【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法_7

    すると三角形はこのように描画されます。

    なぜテクスチャが歪んで貼られてしまったのか分かりやすく図解で解説します。

    【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法_8

    まずは「頂点A」に注目してみます。頂点AのUV座標は(0.12, 0.91)です。この値がテクスチャのどの部分にあたるのかは、「UV座標×テクスチャのサイズ」で取得できます。今回使用したテクスチャは幅高さともに256pxです。

    U座標(横)→ 256px × U:0.12 = 約30px
    V座標(縦)→ 256px × V:0.91 = 約232px

    (30、232)という値が算出されました。これは、画像の左から30px、下から232pxを指します。つまり以下の場所で青色を参照することになります。

    【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法_9

    Aの他、B、C頂点も図に落とし込んでみます。

    【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法_10

    UV座標によってテクスチャが貼られる位置が変わるイメージが付いたのではないでしょうか。テクスチャと各頂点に書き込まれたUV座標の関係性が分かればテクスチャを貼ること自体は怖くありません。

    UV座標を簡単にまとめるとこちらです。

    UV座標まとめ
    • UV座標とはテクスチャの座標 UV座標は左下が原点(0.0
    • 0.0)右上が(1.0
    • 1.0)
    • 横軸は「U座標」、縦軸は「V座標」
    • UV座標は頂点に書き込まれる UV座標によってテクスチャの貼られる位置が変わる

    UV座標とはテクスチャの座標です。頂点に書き込まれたUV座標を使って描画します。以降の章では具体的なロジックの解説に入ります。ぜひ一緒に手を動かしながら学んでいきましょう。

    Unityで頂点にUV座標を書き込みテクスチャを貼り付ける4つの手順

    では具体的にUV座標を書き込んで、テクスチャを貼り付ける手順を解説していきます。

    Unityで頂点にUV座標を書き込みテクスチャを貼り付ける4つの手順

    1.C#でUV座標を頂点に書き込む

    2.テクスチャを貼り付けるシェーダーの作成

    3.C#でテクスチャをシェーダーに渡す

    手順1.C#でUV座標を頂点に書き込む

    UV座標は頂点に書き込まれます。以前の記事で三角形メッシュをプログラミングで作ったときと同じように頂点にデータをセットしていきます。Meshクラスの SetUVsメソッド を使ってUV座標を頂点に書き込むのです。

    👉 Unityで3Dプログラミング基礎!三角形メッシュ(Mesh)の作り方

    mesh.SetUVs(0, new Vector2[]  
    {
        new Vector2(0.5f, 1f),  
        new Vector2(1f, 0f),  
        new Vector2(0f, 0f)  
    });  
    

    SetUVsメソッドの第一引数はチャンネルです。複数のテクスチャをシェーダーで使用する際は、1以上の数値を指定します。今回は1枚しか使わないためチャンネル0を指定しておきます。

    ソースコード全文は記事後半で公開しますのでご安心ください。

    手順2.テクスチャを貼り付けるシェーダーの作成方法4ステップ

    テクスチャを貼り付けるシェーダーを作成します。4つのステップで解説します。

    テクスチャを貼り付けるシェーダーの作成方法4ステップ

    ①シェーダー内で受け取り合うデータ型の定義

    ②C#から受け取るテクスチャの宣言

    ③頂点シェーダーでUV座標を受け取る

    ④フラグメントシェーダーでテクスチャの色を取得して描画

    シェーダー側で頂点に書き込んだUV座標を受け取り、ディスプレイに映し出すまでの流れを解説します。

    ①シェーダー内で受け取り合うデータ型の定義

    Unityのシェーダーはまず、頂点シェーダー、フラグメントシェーダーにそれぞれ入力するデータを構造体で定義します。

    次のコードは頂点シェーダー、フラグメントシェーダーそれぞれに入力するデータの型です。

    // 頂点シェーダーに渡すデータ型「appdata」  
    struct appdata {  
        float4 vertex : POSITION;  
        float2 texcoord : TEXCOORD0;  
    };  
    
    // フラグメントシェーダーに渡すデータ型「v2f」  
    struct v2f {  
        float4 vertex : SV_POSITION;  
        float2 texcoord : TEXCOORD0;  
    };  
    

    appdataは頂点シェーダーへ、v2fはフラグメントシェーダーに渡ります。

    float4 vertex : POSITION; とは頂点の座標で、 float2 texcoord : TEXCOORD0; はUV座標のことです。

    ②C#から受け取るテクスチャの宣言

    今回作るシェーダーは、頂点データ以外にテクスチャのデータも受け取る必要があります。分かりやすく図にするとこんな感じです。

    【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法_11

    ピンクの枠がシェーダーです。今回新たにテクスチャデータをシェーダーにセットします。

    シェーダー側のコードはシンプルです。

    sampler2D _MainTex;  
    

    上記のようにテクスチャは sampler2D型 を使用します。 後述しますが、テクスチャデータをシェーダーに渡す処理をC#で記述します。

    ③頂点シェーダーでUV座標を受け取る

    頂点シェーダーでUV座標を受け取ります。

    v2f vert (appdata v)  
    {
        v2f o;  
        // UV座標を受け渡す  
        o.texcoord = v.texcoord;  
        o.vertex = UnityObjectToClipPos(v.vertex);  
        return o;  
    }
    

    重要なのは o.texcoord = v.texcoord; 。各頂点をフラグメントシェーダーに渡しているところです。
    そして、フラグメントシェーダーでUV座標を使います。

    ④フラグメントシェーダーでテクスチャの色を取得して描画

    最後にフラグメントシェーダーの処理を解説します。頂点シェーダーで受け取ったUV座標を使ってテクスチャの色を取得します。

    フラグメントシェーダーはたったの1行。UV座標に従ってテクスチャの色をピクセルに描画しています。

    fixed4 frag (v2f i) : SV_Target  
    {
        return tex2D(_MainTex, i.texcoord);  
    }
    

    tex2D メソッドの第1引数にテクスチャ、第2引数にUV座標をセットします。すると UV座標からテクスチャの色を取得 できるのです。

    その色をそのままピクセルの色として出力しています。

    【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法_12

    今の状態で画面に表示してもテクスチャは描画されません。なぜなら、シェーダーにテクスチャが渡されていないからです。

    最後の手順でC#側からシェーダーにテクスチャを渡します。

    手順3.C#でテクスチャをシェーダーに渡す

    最後はテクスチャをシェーダーに渡します。C#からシェーダーに値を渡すときは「Material」にアクセスします。 Materialを通して値はシェーダーに渡されるのです。

    Texture2D _texture;  
    
    var material = new Material(Shader.Find("Unlit/SimpleTexture"));  
    material.SetTexture("_MainTex", _texture);  
    

    シェーダーをアタッチしたMaterialに対して、 SetTextureメソッド を使います。第1引数にシェーダー内の変数名、第2引数にテクスチャです。

    「第1引数にシェーダー内の変数名」を忘れてしまった方は「②C#から受け取るテクスチャの宣言」を思い出してみてください。シェーダー内に sampler2D型 で宣言しています。

    【Unity】頂点にUV座標を書き込んでメッシュに画像(テクスチャ)を貼り付ける方法_13

    以上の手順を踏むことで、テクスチャをメッシュに貼り付けることができました↑。ソースコード全文は「ソースコード全文を共有」で確認してみてください。

    今回はテクスチャをC#から渡しました。その他にも 数値や色もC#からMaterialを通してシェーダーに渡すことが可能 です。「C#からシェーダーに値を渡す」テクニックはとても良く使うのでぜひ覚えておきましょう。

    まとめ

    記事の内容を簡単にまとめます。

    UV座標を理解して三角形に画像(テクスチャ)を貼る方法
    • テクスチャを貼るときにUV座標の理解は必須である
    • UV座標は各頂点に書き込む
    • UV座標は頂点シェーダーで受け取りフラグメントシェーダーにわたす テクスチャもC#からシェーダーに渡して、フラグメントシェーダーで処理する
    こんな感じです。

    三角形にテクスチャを貼り付けることができました。 テクスチャをメッシュに貼り付けるためにUV座標の理解が必須 だという意味がわかったと思います。また、 C#からMaterialを通してシェーダーにデータ(テクスチャ)を送信する方法 も学びました。このテクニックはよく使います。

    テクスチャをメッシュに貼り付けるテクニックは3Dプログラミングの基礎です。今回はシンプルな三角形1つでしたが、複雑な図形に慣ればなるほど難しくなります。しかし 基本さえ固めておけば大丈夫 です。複雑な図形はただ単に 頂点が増えただけ なのです。仕組みを正しく理解しておけば迷うことはありません。

    ぜひ、この記事を通してテクスチャがメッシュに貼られる仕組みを正しく理解しましょう。一度読んだだけでは理解できないかもしれません。繰り返し読み直し、実際に手を動かしてみてください。「ソースコード全文を共有」で今回使用したソースコードは 全文 共有しています。

    自分で三角形を作り、色を塗り、画像を貼る。この一連の処理を自分で書くとより理解が深まります。こちらの記事たちもあわせて読んでみてください。

    プログラミングで三角形メッシュを作る方法を学ぶ記事です。まずはここから読むことをおすすめします。

    超シンプルなシェーダーが登場します。三角形メッシュにシェーダーを適用する方法を学べます。

    頂点について学びます。頂点にはさまざまなデータを格納可能。この記事では頂点に色情報(頂点カラー)を格納して三角形を描画する方法を学びます。

    引き続き3Dプログラミングの基礎を学習していきましょう。

    最後に今回使用したソースコードを共有します。

    ソースコード全文を共有

    今回使用したソースコードは2種類です。C#ファイル1つ、シェーダーファイル1つです。ソースコード内に適宜コメントを記載しているので確認してみてください。

    💻ソースコード : DynamicCreateMesh.cs
    using UnityEngine;  
    
    [RequireComponent(typeof(MeshRenderer))]  
    [RequireComponent(typeof(MeshFilter))]  
    public class DynamicCreateMesh : MonoBehaviour  
    {
        // シェーダーに渡すテクスチャを事前にセットしてください  
        [SerializeField] private Texture2D _texture;  
    
        private void Start()  
        {
            // メッシュの作成  
            var mesh = new Mesh();  
    
            // 頂点座標配列をメッシュにセット  
            mesh.SetVertices(new Vector3[] {  
                new Vector3 (0, 1f),  
                new Vector3 (1f, -1f),  
                new Vector3 (-1f, -1f),  
            });  
    
            // インデックス配列をメッシュにセット  
            mesh.SetTriangles(new int[] {  
                0, 1, 2  
            }, 0);  
    
            // UV座標を頂点にセット  
            mesh.SetUVs(0, new Vector2[]  
            {
                new Vector2(0.5f, 1f),  
                new Vector2(1f, 0f),  
                new Vector2(0f, 0f)  
            });  
    
            // MeshFilterを通してメッシュをMeshRendererにセット  
            var filter = GetComponent<MeshFilter>();  
            filter.sharedMesh = mesh;  
    
            // シェーダーをロードしてMaterialを生成  
            var material = new Material(Shader.Find("Unlit/SimpleTexture"));  
            material.SetTexture("_MainTex", _texture);  
    
            // MeshRendererにMaterialをセット  
            var renderer = GetComponent<MeshRenderer>();  
            renderer.material = material;  
        }
    }
    
    💻ソースコード : SimpleTexture.shader
    Shader "Unlit/SimpleTexture" {  
        SubShader {  
            Pass {  
                CGPROGRAM  
                #pragma vertex vert  
                #pragma fragment frag  
                #include "UnityCG.cginc"  
    
                struct appdata {  
                    float4 vertex : POSITION;  
                    float2 texcoord : TEXCOORD0;  
                };  
    
                struct v2f {  
                    float4 vertex : SV_POSITION;  
                    float2 texcoord : TEXCOORD0;  
                };  
    
                sampler2D _MainTex;  
    
                v2f vert (appdata v)  
                {
                    v2f o;  
                    // UV座標をフラグメントシェーダーに渡す  
                    o.texcoord = v.texcoord;  
                    o.vertex = UnityObjectToClipPos(v.vertex);  
                    return o;  
                }
    
                fixed4 frag (v2f i) : SV_Target  
                {
                    // テクスチャの色をUV座標から取得してピクセルを描画  
                    return tex2D(_MainTex, i.texcoord);  
                }
                ENDCG  
            }
        }
    }
    

    今回の記事は以上です。引き続き3Dプログラミングをやっていきましょう。

    「Unity初心者大学」というUnity初心者向けのYouTube始めました!!
    ぜひチャンネル登録をお願いします!

    最後まで読んでいただきありがとうございました!
    すばらしい3Dプログラミングライフをお過ごしください。

    オススメ記事
    検証環境
    • Unity2020.3.31f1