Présentation du système d’instancing dans endless legend reskin lors du 10e meetup Unity3D le 17 mars 2015
http://www.meetup.com/Paris-Unity/events/220950443/
5. Situation
Nombre d’objets à l’écran
Nombre d’objets dans le
monde
Temps de « fabrication » du
contenu d’une zone
Mémoire nécessaire pour
stocker le contenu d’une zone
Objectifs
Performance CPU/GPU
Mémoire CPU/GPU
Temps réel
PROBLÈME
6. En moyenne 10 000 draw call dès qu’on dezoom
Batching dynamique …
Mauvais frame rate (CPU bound) mais régulier
Éditeur friendly
UNITY. DE BASE
7. Lent
Mémoire gigantesque
Très lent en fait!
Bon frame rate mais grosse saccade + out of memory
Pas éditeur friendly
UNITY.COMBINEMESH
11. int valueIn0_1_8bit = System.Math.Max(0, System.Math.Min(255, (int)(valueIn01 * 255.0f)));
UnityEngine.Color32 color = new UnityEngine.Color32((byte)valueIn0_1_8bit, …, … ,… );
int valueIn0_1_16bit = (int)System.Math.Max(0, System.Math.Min(65535, (int)(valueIn01 * 65535.0f)));
int lowBits = 255;
int highBits = 255 << 8;
UnityEngine.Color32 color = new UnityEngine.Color32((byte)(valueIn0_1_16bit & lowBits), ((byte)((valueIn0_1_16bit &
highBits) >> 8), …, …)
float4 texContent = tex2Dlod(_EncodedContentTexture, texCoord);
float valueIn0_1 = texContent.x;
float valueIn0_255 = 255 * texContent.x; //on récupère exactement la valeur valueIn0_1_8bit
float4 texContent = tex2Dlod(_EncodedContentTexture, texCoord);
float valueIn0_65535 = texContent.x * 255 + texContent.y * 255 * 256; //on récupère exactement la valeur
valueIn0_1_16bit
float valueIn0_1 = valueIn0_65535 / 65535;
FORMULES
Encoder en 8 bits - un float entre 0 et 1
Encoder en 16 bits
Décoder en 8 bits
Décoder en 16 bits
12. Strip de mesh : Pour chaque mesh de chaque type de décors, on
prépare un mesh qui est une répétition de copie de ce mesh. Dans
le strip de mesh, on ajoute la composante couleur où l’on encode
l’index de la copie dans la série
On encode l’affichage d’un groupe d’objet de même type en une
bande de pixel
On transmet au vertex-shader l’endroit où il faut commencer à lire
et le nombre d’objet qu’il faut décoder
tree tree4tree0 tree1 tree2 tree3 tree4 tree5 tree6
DÉCODAGE AU VERTEX-SHADER
13. On utilise la version de UnityEngine.Graphics.DrawMesh avec 9
paramètres dont un MaterialPropertyBlock
MaterialPropertyBlock : contiendra ce qui change par
DrawMesh : le _StartUV, le MeshCount, la bounding box, etc…
UnityEngine.Shader.SetGlobalXXX : pour ce qui ne change pas
par draw, en particulier la texture de contrôle
IMPLÉMENTATION 1/2
14. X lo X hi Y lo Y hi Z lo Z hi Fx lo Fx hi Fz lo Fz hi Up lo Up hi X lo X hiUp lo Up hi
Instance 0 Instance 1 Instance 2 Instance 3 Instance 4
Shader parameters
_ControlTex
_StartUV
_ObjectCount
float instanceIndex = 255 * vertex.color.x;
int pixelCountPerInstance = 3;
float2 baseTexCoord = _StartUV.xy + float2(_ControlTex_TexelSize.x * (pixelCountPerInstance * instanceIndex), 0) +
_ControlTex_TexelSize.xy * 0.5f;
float4 controlValue0 = tex2Dlod (_ControlTex, float4(baseTexCoord, 0, 0));
float4 controlValue1 = tex2Dlod (_ControlTex, float4(baseTexCoord + float2(_ControlTex_TexelSize.x, 0), 0, 0));
float4 controlValue2 = tex2Dlod (_ControlTex, float4(baseTexCoord + float2(2 * _ControlTex_TexelSize.x, 0), 0, 0));
float valueX01 = (controlValue0.x * 255 + controlValue0.y * 255 * 256) / 65535;
(…)
float3 pos = lerp(_MinBBox.xyz, _MaxBBox.xyz, float3(valueX01, valueY01, valueZ01));
(…)
float visible = step(colorIndex, _ObjectCount – 0.5);
controlValue0 controlValue1 controlValue2
_ControlTex
…
…
…
Zoom +
15. A partir d’un ensemble de groupe d’objets disparates, on
fabrique un ensemble de groupe d’objets de même type
On les encode dans la texture de contrôle
On envoie les commandes de rendu avec DrawMesh
IMPLÉMENTATION 2/2
16. La consultation de la texture au vertex-shader est gratuite!
• Il faut être GPU bound pour le voir
• Il faut vraiment beaucoup de vertex alors que généralement le
coût principal vient des pixels
• Même dans ce cas là, on a seulement quelques % de différence
Remplir une texture sans mip-map à partir d’un tableau de
color32, n’est pas très cher
PERFORMANCES
17. Ca pique!! Chaque type d’objet est répliqué, parfois plusieurs
centaines de fois
En cause certaines limitations de Unity
Ajuster la taille des strips de mesh
Optimiser le layout mémoire
• Encoder la normale dans la couleur
• Encoder la tangeante dans la normale
MÉMOIRE
18. Pour le debug et la mise au point, pouvoir switcher entre
« Unity pur » et « instancing », le premier définissant la
référence
Nécessite une autre manière de regrouper les meshs pour
certains éléments
Animation : manipuler quelques pixels pour bouger un objet
DIVERS
21. Nombre de draw call réduit
Temps de régénération et coût de stockage d’une zone réduit
Zéro alloc ou presque en réutilisant les buffers de stockage
Empreinte mémoire importante à t=0 mais quasi constante
par la suite
Animation de certains éléments facilitée
Nécessite une certaine infrastructure et un certain
investissement pour la mise au point
RÉSULTATS
23. Travailler avec des valeurs entières permet d’avoir des indices
et donc des tableaux d’objets consultables
Il n’est pas prohibitif de faire au pixel shader ce genre
d’opération
On peut produire le contenu de texture d’encodage à partir
d’un rendu et donc animer au GPU
PLUS LOIN