3
1

More than 5 years have passed since last update.

【Unity】SV_VertexIDを、Meshオブジェクトの、uv2プロパティで代用するやり方

Last updated at Posted at 2017-12-23

SV_VertexIDを、Meshオブジェクトの、uv2プロパティで、代用するやり方です。
頂点テクスチャフェッチが苦手と、なんだか、操作がめんどくさいなという感じがあったので、この方法を試しました。
SV_VertexIDは、openGLES3以上、DX11以上という、条件もあったので、それ以下も、対応したく、uv2を、SV_VertexIDを、以下のブログで知り、方法を記載します。

Meshオブジェクトには、このブログにも記載がありますが、tangentプロパティも用意されているのですが、Androidだと、正規化されてしまうようなので、uv2で、試しました。

これがあると、1つのMeshで、大量に、モデルを描画したあと、たくさんのモデルの中の、特定のモデルを、特定の位置に動かしたいということを、CPU側で、頂点情報の一部を更新するだけで、移動/回転などが、できる感じです。同時に、特定の動きを実現したい場合は、頂点シェーダー側で対応すれば良いですし、割と、操作しやすいかなと、全てを、GPUでというと、頑張って頂点テクスチャフェッチを使うしかないんですけどね。

サンプルソースコード

https://github.com/yasuohasegawa/UnityVertexIDAndUV2Sample
SV_VertexIDのサンプルもあります。

テスト環境

Unity5.6以上

コード例

C#側:

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

public class UV2Main : MonoBehaviour {
    private int m_faceCount = 0;
    private List<Vector3> m_vertices = new List<Vector3>();
    private List<Color> m_faceColors = new List<Color>();
    private List<int> m_indices = new List<int>();
    private List<Vector2> m_uvs = new List<Vector2>();
    private List<Vector4> m_posList = new List<Vector4> ();
    private List<Vector2> m_uv2s = new List<Vector2> ();

    private MeshRenderer m_renderer;

    // Use this for initialization
    void Start () {
        m_renderer = gameObject.GetComponent<MeshRenderer> ();

        // 1つ目のプレーンの位置情報
        m_posList.Add (new Vector4(0f,2f,0f,1f));
        m_posList.Add (new Vector4(0f,2f,0f,1f));
        m_posList.Add (new Vector4(0f,2f,0f,1f));
        m_posList.Add (new Vector4(0f,2f,0f,1f));

        // 2つ目のプレーンの位置情報
        m_posList.Add (new Vector4(0f,0f,2f,1f));
        m_posList.Add (new Vector4(0f,0f,2f,1f));
        m_posList.Add (new Vector4(0f,0f,2f,1f));
        m_posList.Add (new Vector4(0f,0f,2f,1f));

        // 頂点indexの作成
        for(int i = 0; i<m_posList.Count; i++) {
            m_uv2s.Add(new Vector2 ((float)i, 0f));
        }

        m_renderer.materials [0].SetVectorArray ("_posList", m_posList);
        CreatePlane (new Vector3(0f,0f,0f),new Vector3(1f,0f,0f));
        CreatePlane (new Vector3(3f,0f,0f),new Vector3(1f,0f,0f));

        UpdateMesh ();
    }

    // Update is called once per frame
    void Update () {

    }

    public void CreatePlane(Vector3 pos, Vector3 col) {
        float x = pos.x * 0.5f;
        float y = pos.y * 0.5f;
        float z = pos.z * 0.5f;

        m_vertices.Add (new Vector3 (x - 0.5f, y - 0.5f, z));
        m_vertices.Add (new Vector3 (x - 0.5f, y + 0.5f, z));
        m_vertices.Add (new Vector3 (x + 0.5f, y + 0.5f, z));
        m_vertices.Add (new Vector3 (x + 0.5f, y - 0.5f, z));

        m_faceColors.Add (new Color (col.x, col.y, col.z));
        m_faceColors.Add (new Color (col.x, col.y, col.z));
        m_faceColors.Add (new Color (col.x, col.y, col.z));
        m_faceColors.Add (new Color (col.x, col.y, col.z));

        m_indices.Add (m_faceCount * 4); //1
        m_indices.Add (m_faceCount * 4 + 1); //2
        m_indices.Add (m_faceCount * 4 + 2); //3
        m_indices.Add (m_faceCount * 4); //1
        m_indices.Add (m_faceCount * 4 + 2); //3
        m_indices.Add (m_faceCount * 4 + 3); //4

        m_uvs.Add (new Vector2 (0f, 0f));
        m_uvs.Add (new Vector2 (1f, 0f));
        m_uvs.Add (new Vector2 (1f, 1f));
        m_uvs.Add (new Vector2 (0f, 1f));

        m_faceCount++;
    }

    private void UpdateMesh() {
        Mesh mesh = gameObject.GetComponent<MeshFilter> ().mesh;
        mesh.Clear ();
        mesh.vertices = m_vertices.ToArray();
        mesh.triangles = m_indices.ToArray();
        mesh.uv = m_uvs.ToArray ();
        mesh.colors = m_faceColors.ToArray();
        mesh.uv2 = m_uv2s.ToArray ();

        mesh.RecalculateNormals ();
        mesh.RecalculateBounds ();
    }

    public void OnUpdateVertices() {
        Vector4 pos = m_posList [4];
        pos.x = 0f;
        pos.y = 0f;
        pos.z = 0f;
        m_posList [4] = pos;
        m_posList [5] = pos;
        m_posList [6] = pos;
        m_posList [7] = pos;
        m_renderer.materials [0].SetVectorArray ("_posList", m_posList);
    }
}

gameObjectには、あらかじめ、MeshFilterと、MeshRendererを、addComponentしておいてください。
このC#側のソースコードは、1つの、Meshで、2つのPlaneを「CreatePlane」メソッドで作成しています。2つPlaneの頂点情報と、この、頂点に対する、頂点indexを、Vector2の、x座標に、

for(int i = 0; i<m_posList.Count; i++) {
    m_uv2s.Add(new Vector2 ((float)i, 0f));
}

で、格納し、

mesh.uv2 = m_uv2s.ToArray ();

で、Meshオブジェクトに頂点indexを渡します。

shader側:

Shader "Unlit/UV2"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Cull off

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            #define POS_SIZE 8

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                fixed4 color : TEXCOORD1;
            };


            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _posList[POS_SIZE];

            v2f vert (appdata v) // uint vid : SV_VertexID you can do this way as well but it needs openGLES3 above
            {
                v2f o;

                int vid = v.uv2.x; // replace for SV_VertexID

                v.vertex.x += _posList[vid].x;
                v.vertex.y += _posList[vid].y;
                v.vertex.z += _posList[vid].z;

                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                float f = (float)vid;
                o.color = half4(sin(f/10),sin(f/100),sin(f/1000),0) * 0.5 + 0.5;

                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target{
                return i.color;
            }
            ENDCG
        }
    }
}

頂点シェーダーの入力に、

struct appdata
{
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
    float2 uv2 : TEXCOORD1; // 追加
};
float4 _posList[POS_SIZE];
:
:

uv2を追加。

v2f vert (appdata v)
{
    v2f o;

    int vid = v.uv2.x;

    v.vertex.x += _posList[vid].x;
    v.vertex.y += _posList[vid].y;
    v.vertex.z += _posList[vid].z;
:
:

頂点シェーダー側で、Meshオブジェクトで、付与した、頂点indexを、

int vid = v.uv2.x

で、受け取ります。
SetVectorArrayで、流し込んだ、頂点情報を、「_posList」に紐づけて、
個々の頂点情報を、更新することができるようになります。
コード例では、2つ目に作成した、Planeの頂点情報を、以下のように、CPU側で、更新しています。

public void OnUpdateVertices() {
    Vector4 pos = m_posList [4];
    pos.x = 0f;
    pos.y = 0f;
    pos.z = 0f;
    m_posList [4] = pos;
    m_posList [5] = pos;
    m_posList [6] = pos;
    m_posList [7] = pos;
    m_renderer.materials [0].SetVectorArray ("_posList", m_posList);
}

今後は、移動/回転/拡大の処理は、頂点シェーダー側で計算して、頂点情報を更新できるようにし、新しいサンプルをgitにPUSHしようかと思います。ご参考になればとです。

追記

今回の環境でですが、SetVectorArrayなどで設定した、Shader側で、扱える配列の要素数の最大が、253でした。。。最新のUnityでは、もう少しいけるのかもですが、用途は、要検討でね。。。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1