I’ve done some tests on how many instanced meshes we can expect to render, using our new “procedural” instancing. (Unity 5.6) This is where you will be able to provide your instancing data from custom sources eg a StructureBuffer, instead of supplying/updating matrix arrays etc via the CPU. It will allow you to generate instance data on the GPU, for example, and removes all per-instance CPU code paths.
So, if I use an instanced version of the Standard Shader, I can achieve around 800,000 cubes at 30fps.
But, the Standard Shader does a lot of stuff you may not need. So, if I use a simple custom shader, I can render over 2 million cubes at over 30fps on a GTX 980. If you want more complex meshes/shaders, this will severely impact how many items you can render, but conversely, there are also faster GPU’s available than a GTX 980 
And here’s a preview of what the script/shader might look like:
using UnityEngine;
using System.Collections;
public class ExampleClass : MonoBehaviour {
public int instanceCount = 500000;
public Mesh instanceMesh;
public Material instanceMaterial;
private int cachedInstanceCount = -1;
private ComputeBuffer positionBuffer;
private ComputeBuffer argsBuffer;
void Update() {
// Update starting position buffer
if (cachedInstanceCount != instanceCount)
OnValidate();
// Render
instanceMaterial.SetBuffer("positionBuffer", positionBuffer);
Graphics.DrawMeshInstancedIndirect(instanceMesh, 0, instanceMaterial, new Bounds(Vector3.zero, new Vector3(100.0f, 100.0f, 100.0f)), argsBuffer);
}
void OnGUI() {
GUI.Label(new Rect(265, 25, 200, 30), "Instance Count: " + instanceCount.ToString());
instanceCount = (int)GUI.HorizontalSlider(new Rect(25, 20, 200, 30), (float)instanceCount, 0.0f, 5000000.0f);
}
void OnValidate() {
// positions
positionBuffer = new ComputeBuffer(instanceCount, 16);
Vector4[] positions = new Vector4[instanceCount];
for (int i=0; i < instanceCount; i++)
{
float angle = Random.RandomRange(0.0f, Mathf.PI * 2.0f);
float distance = Random.RandomRange(20.0f, 100.0f);
float height = Random.RandomRange(-2.0f, 2.0f);
float size = Random.RandomRange(0.05f, 0.25f);
positions[i] = new Vector4(Mathf.Sin(angle) * distance, height, Mathf.Cos(angle) * distance, size);
}
positionBuffer.SetData(positions);
// indirect args
uint numIndices = (instanceMesh != null) ? (uint)instanceMesh.GetIndexCount(0) : 0;
uint[] args = new uint[5] { numIndices, (uint)instanceCount, 0, 0, 0 };
argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
argsBuffer.SetData(args);
cachedInstanceCount = instanceCount;
}
}
Shader "Instanced/InstancedShader" {
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" {}
}
SubShader {
Pass {
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
#pragma target 4.5
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"
#include "AutoLight.cginc"
sampler2D _MainTex;
#ifdef SHADER_API_D3D11
StructuredBuffer<float4> positionBuffer;
#endif
struct v2f
{
float4 pos : SV_POSITION;
float2 uv_MainTex : TEXCOORD0;
float3 ambient : TEXCOORD1;
float3 diffuse : TEXCOORD2;
float3 color : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert (appdata_full v, uint instanceID : SV_InstanceID)
{
float4 data = positionBuffer[instanceID];
float3 localPosition = v.vertex.xyz * data.w;
float3 worldPosition = data.xyz + localPosition;
float3 worldNormal = v.normal;
float3 ndotl = saturate(dot(worldNormal, _WorldSpaceLightPos0.xyz));
float3 ambient = ShadeSH9(float4(worldNormal, 1.0f));
float3 diffuse = (ndotl * _LightColor0.rgb);
float3 color = v.color;
v2f o;
o.pos = mul(UNITY_MATRIX_VP, float4(worldPosition, 1.0f));
o.uv_MainTex = v.texcoord;
o.ambient = ambient;
o.diffuse = diffuse;
o.color = color;
TRANSFER_SHADOW(o)
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed shadow = SHADOW_ATTENUATION(i);
fixed4 albedo = tex2D(_MainTex, i.uv_MainTex);
float3 lighting = i.diffuse * shadow + i.ambient;
fixed4 output = fixed4(albedo.rgb * i.color * lighting, albedo.w);
UNITY_APPLY_FOG(i.fogCoord, output);
return output;
}
ENDCG
}
}
}