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


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

http://i-saint.hatenablog.com/entry/2015/02/08/225227

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では、もう少しいけるのかもですが、用途は、要検討でね。。。