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