Page 1 sur 7

[C#] GLWireframe

Publié : 17 Fév 2013 13:20
par artemisart
-NOM DU SCRIPT : GLWireframe

-AUTEUR : artemisart

-DESCRIPTION : Comme son nom l'indique, ce script sert à afficher le wireframe des meshes en utilisant les fonctions GL, ça marche parfaitement bien sur la free 3.5 (et versions légèrement antérieures) bien que Unity dise le contraire. J'ai déjà vu certains WebPlayer avec le wireframe baké sur une texture et le résultat est souvent bofbof, donc j'ai décidé de coder ça.

-UTILISATION : Le script est à mettre sur une caméra (absolument). Variables utilisées :
  • Transform[] meshRoots :
    les transforms contenant les meshes (directement sur eux + enfants) dont on veut afficher le wireframe.
  • MeshFilter[] meshes:
    les meshes dont on veut afficher le wireframe, les enfants ne sont pas pris en compte.
    Si meshRoots et meshes ne contiennent rien, tous les meshes de la scène sont pris.
  • bool displayWireframe :
    affiche ou non le wireframe.
  • KeyCode toggleDisplayKey:
    touche pour inverser l'affichage du wireframe.
  • Color wireColor :
    la couleur du wire (par défaut en noir avec alpha de 0.2f (51/255)).
  • Rect rect :
    rect utilisé pour afficher les performances du script (on peut commenter cette variable, la fonction OnGUI et les Stopwatch si c'est inutile).
-SCRIPT :

Code : Tout sélectionner

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using UnityEngine;
using Debug = UnityEngine.Debug;
using ThreadPriority = System.Threading.ThreadPriority;

public class GLWireframe : MonoBehaviour
{
	private class MeshWire
	{
		private Vector3 position;
		private Quaternion rotation;
		private Vector3 scale;
		
		private MeshFilter mf;
		private Vector3[] vertices;
		private int[] triangles;
		private List<bool> uniqueEdges;
		
		private bool isVisible;
		private GLWireframe instance;
		
		public MeshWire (MeshFilter meshFilter, GLWireframe inst)
		{
			mf = meshFilter;
			isVisible = true;
			vertices = mf.sharedMesh.vertices;
			triangles = mf.sharedMesh.triangles;
			uniqueEdges = new List<bool> ();
			
			instance = inst;
			ThreadPool.QueueUserWorkItem (CheckEdges);
		}
		
		private bool V3Equals (Vector3 a, Vector3 b)
		{ return a.x == b.x && a.y == b.y && a.z == b.z; }
		private Vector3 Vertex (int i)
		{ return vertices[triangles[i]]; }
		private bool ShouldDrawEdge (int i)
		{ return uniqueEdges.Count > i ? uniqueEdges[i] : true; }
		
		private bool TestAddEdge (List<Vector3> list, Vector3 a, Vector3 b)
		{
			Vector3 l_a, l_b;
			for (int i = 0; i < list.Count && instance.runningThreads != -1; i += 2)
			{
				l_a = list[i];
				l_b = list[i + 1];
				if ((V3Equals (l_a, a) && V3Equals (l_b, b)) ||
					(V3Equals (l_a, b) && V3Equals (l_b, a)))
					return false;
			}
			list.Add (a);
			list.Add (b);
			return true;
		}
		
		private void CheckEdges (object uselessObject)
		{
			instance.runningThreads++;
			uniqueEdges.Clear ();
			List<Vector3> newVerts = new List<Vector3> ((int)(triangles.Length * 1.3f));
			Vector3 a, b, c;
			for (int i = 0; i < triangles.Length && instance.runningThreads != -1; i += 3)
			{
				a = Vertex (i);
				b = Vertex (i + 1);
				c = Vertex (i + 2);
				uniqueEdges.Add (TestAddEdge (newVerts, a, b));
				uniqueEdges.Add (TestAddEdge (newVerts, b, c));
				uniqueEdges.Add (TestAddEdge (newVerts, c, a));
			}
			newVerts.TrimExcess ();
			Debug.Log ("glwireframe: thread finished in " + instance.time);
			instance.runningThreads--;
		}
		
		public void CullAndBake ()
		{
			isVisible = true;
			if (mf.renderer && mf.renderer.enabled && !mf.renderer.isVisible)
				isVisible = false;
			else
			{
				Transform tr = mf.transform;
				if (tr.position != position || tr.rotation != rotation || tr.lossyScale != scale)
				{
					position = tr.position;
					rotation = tr.rotation;
					scale = tr.lossyScale;
					
					Matrix4x4 matrix = tr.localToWorldMatrix;
					vertices = mf.sharedMesh.vertices.Select (v => matrix.MultiplyPoint3x4 (v)).ToArray ();
				}
			}
		}
		
		public void Render ()
		{
			if (!isVisible)
				return;
			Vector3 a, b, c;
			for (int i = 0; i < triangles.Length; i += 3)
			{
				a = Vertex (i);
				b = Vertex (i + 1);
				c = Vertex (i + 2);
				
				if (ShouldDrawEdge (i))
				{
					GL.Vertex (a);
					GL.Vertex (b);
				}
				if (ShouldDrawEdge (i + 1))
				{
					GL.Vertex (b);
					GL.Vertex (c);
				}
				if (ShouldDrawEdge (i + 2))
				{
					GL.Vertex (c);
					GL.Vertex (a);
				}
			}
		}
	}
	
	private static Material mat;
	
	public Transform[] meshRoots;
	public MeshFilter[] meshes;
	public bool displayWireframe = true;
	public KeyCode toggleDisplayKey = KeyCode.Z;
	public Color wireColor = new Color (0, 0, 0, 0.2f);
	public ThreadPriority threadPriority = ThreadPriority.Normal;
	public ScreenRect rect = new ScreenRect (0, 0, 150, 110);
	
	private List<MeshWire> wires;
	
	// stats
	private float startTime;
	private float time;
	private float milliseconds_cullbake	= 0;
	private float milliseconds_render	= 0;
	private int runningThreads = 0;
	
	void Start ()
	{
		startTime = Time.realtimeSinceStartup;
		
		wires = new List<MeshWire> ();
		
		SetMaterial ();
		GetSetMeshes ();
	}
	
	void SetMaterial ()
	{
		mat = new Material (
@"Shader ""GLwire"" {
	SubShader {
		Pass {
			Blend SrcAlpha OneMinusSrcAlpha
			ZWrite On
			Cull Off
			BindChannels {
				Bind ""vertex"", vertex
				Bind ""color"", color
			}
		}
	}
}
");
		mat.hideFlags = HideFlags.HideAndDontSave;
		mat.shader.hideFlags = HideFlags.HideAndDontSave;
	}
	
	void GetSetMeshes ()
	{
		List<MeshFilter> mf;
		if (meshRoots.Length > 0 || meshes.Length > 0)
		{
			mf = new List<MeshFilter> ();
			for (int i = 0; i < meshRoots.Length; i++)
				mf.AddRange (meshRoots[i].GetComponentsInChildren<MeshFilter> ());
			mf.AddRange (meshes);
			mf = mf.Distinct ().ToList ();
			Debug.Log ("glwireframe : " + mf.Count + " wires to render");
		}
		else
		{
			mf = (FindObjectsOfType (typeof (MeshFilter)) as MeshFilter[]).ToList ();
			Debug.Log ("glwireframe : " + mf.Count + " meshfilters (all)");
		}
		
		for (int i = 0; i < mf.Count; i++)
			wires.Add (new MeshWire (mf[i], this));
	}
	
	void Update ()
	{
		if (Input.GetKeyDown (toggleDisplayKey))
			displayWireframe = !displayWireframe;
		time = Time.realtimeSinceStartup - startTime;
	}
	
	void OnGUI ()
	{
		int maxThreads;
		int useless;
		ThreadPool.GetMaxThreads (out maxThreads, out useless);
		
		GUI.Label (rect,
			"GLWireframe :\n" +
			"	max threads : " + maxThreads + "\n" +
			"	running threads : " + runningThreads + "\n" +
			"	cull/bake : " + milliseconds_cullbake.ToString ("f") + " ms\n" +
			"	render : " + milliseconds_render.ToString ("f") + " ms"
			);
	}
	
	void OnPostRender ()
	{
		if (!displayWireframe)
		{
			milliseconds_cullbake = 0;
			milliseconds_render = 0;
			return;
		}
		
		ProcessRender ();
	}
	
	void ProcessRender ()
	{
		Stopwatch timer = Stopwatch.StartNew ();
		
		for (int i = 0; i < wires.Count; i++)
			wires[i].CullAndBake ();
		
		timer.Stop ();
		milliseconds_cullbake = timer.ElapsedTicks / 10000f;
		timer.Reset ();
		timer.Start ();
		
		mat.SetPass (0);
		GL.Color (wireColor);
		GL.Begin (GL.LINES);
		
		for (int i = 0; i < wires.Count; i++)
			wires[i].Render ();
		GL.End ();
		
		timer.Stop ();
		milliseconds_render = timer.ElapsedTicks / 10000f;
	}
	
	void OnApplicationQuit ()
	{
		runningThreads = -1; // stop threads
	}
}
Si vous avez des suggestions, idées d'améliorations ou d'optimisation, n’hésitez pas ;) .

Re: [C#] GLWireframe

Publié : 17 Fév 2013 14:09
par Max
J'ai testé, ça marche bien à priori ;)

Cela semble basé sur le code de Chubbspet non ?

Re: [C#] GLWireframe

Publié : 17 Fév 2013 14:16
par Franck
Merci. 8-)

Re: [C#] GLWireframe

Publié : 17 Fév 2013 14:20
par artemisart
Je connaissais pas du tout ce code en fait :mrgreen: .
Bon là je suis en train d'ajouter le support des mesh dynamiques, mais unity crash à chaque fois que je test le code :/

Code : Tout sélectionner

public class GLWireframe : MonoBehaviour
{
	private static Material mat;
	
	public Transform meshesRoot;
	public bool displayWireframe = true;
	public Color wireColor = Color.black;
	
	private List<Vector3> staticVertices = new List<Vector3> ();
	private List<Mesh> dynamicMeshes = new List<Mesh> ();
	private List<Transform> dynamicTransforms = new List<Transform> ();
	
	void Awake ()
	{
		// create material
		mat = new Material (
"Shader \"GLcurveline\" {" +
"SubShader { Pass { " +
"    Blend SrcAlpha OneMinusSrcAlpha " +
"    ZWrite On Cull Off Fog { Mode Off } " +
"    BindChannels {" +
"      Bind \"vertex\", vertex Bind \"color\", color }" +
"} } }"
);
		mat.hideFlags = HideFlags.HideAndDontSave;
		mat.shader.hideFlags = HideFlags.HideAndDontSave;
		
		MeshFilter[] mf;
		if (meshesRoot != null)
			mf = meshesRoot.GetComponentsInChildren<MeshFilter> ();
		else
			mf = FindObjectsOfType (typeof (MeshFilter)) as MeshFilter[];
		
		Transform tr; // cache transform
		Mesh mesh; // cache mesh
		for (int i = 0; i < mf.Length; i++) // foreach meshfilter
		{
			// if GO is static, it caches vertices in a list
			if (mf[i].gameObject.isStatic)
			{
				tr = mf[i].transform;
				mesh = mf[i].mesh;
				
				// foreach triangle
				for (int tri = 0; tri < mesh.triangles.Length; tri += 3)
				{
					staticVertices.Add ( tr.TransformPoint ( mesh.vertices[ mesh.triangles[tri    ] ] ) );
					staticVertices.Add ( tr.TransformPoint ( mesh.vertices[ mesh.triangles[tri + 1] ] ) );
					staticVertices.Add ( tr.TransformPoint ( mesh.vertices[ mesh.triangles[tri + 2] ] ) );
				}
			}
			else
			{
				dynamicMeshes.Add (mf[i].mesh);
				dynamicTransforms.Add (mf[i].transform);
			}
		}
	}
	
	void OnPostRender ()
	{
		if (!displayWireframe) return;
		
		mat.SetPass (0);
		
		GL.Begin (GL.LINES);
		GL.PushMatrix ();
		GL.Color (wireColor);
		
		// foreach static triangle
		for (int i = 0; i < staticVertices.Count; i += 3)
		{
			GL.Vertex (staticVertices[i    ]); // first line
			GL.Vertex (staticVertices[i + 1]);
			
			GL.Vertex (staticVertices[i + 1]); // second line
			GL.Vertex (staticVertices[i + 2]);
			
			GL.Vertex (staticVertices[i + 2]); // third line
			GL.Vertex (staticVertices[i    ]);
		}
		
		// foreach dynamic meshfilter
		Mesh mesh; // cache mesh
		for (int i = 0; i < dynamicMeshes.Count; i++)
		{
			mesh = dynamicMeshes[i];
			// foreach triangle
			// ça crash là apparemment
			for (int tri = 0; tri < mesh.triangles.Length; i += 3)
			{
				GL.Vertex (mesh.vertices[ mesh.triangles[tri    ] ]); // first line
				GL.Vertex (mesh.vertices[ mesh.triangles[tri + 1] ]);
				
				GL.Vertex (mesh.vertices[ mesh.triangles[tri + 1] ]); // second line
				GL.Vertex (mesh.vertices[ mesh.triangles[tri + 2] ]);
				
				GL.Vertex (mesh.vertices[ mesh.triangles[tri + 2] ]); // third line
				GL.Vertex (mesh.vertices[ mesh.triangles[tri    ] ]);
			}
		}
		
		GL.PopMatrix ();
		GL.End ();
	}
}

Re: [C#] GLWireframe

Publié : 17 Fév 2013 14:37
par Max
artemisart a écrit :J mais unity crash à chaque fois que je test le code :/
Ben, j'ai pas vraiment testé, mais en lisant rapidement ton code, y-a un truc qui ressort :mrgreen:
for (int tri = 0; tri < mesh.triangles.Length; i += 3)
c'est pas plutôt
for (int tri = 0; tri < mesh.triangles.Length; tri += 3)
:roll:

Re: [C#] GLWireframe

Publié : 17 Fév 2013 16:50
par artemisart
Effectivement, grosse boulette :oops:
Du coup j'ai pu le finir et ça marche plutôt bien, j'update le premier poste.

Re: [C#] GLWireframe

Publié : 17 Fév 2013 17:18
par artemisart
Post mis à jour !

EDIT :
Un petit aperçu :
En haut j'ai désactivé les renderers et mis la couleur du wire à (1, 1, 1, 1) (j'aurais peut-être du baisser un peu l'alpha) et le background de la caméra en noir.
En bas tout est par défaut (le wire se "fond" avec les meshes parce que la caméra est loin, normalement ça fait pas ça).
Image

Re: [C#] GLWireframe

Publié : 20 Fév 2013 23:19
par artemisart
Je viens de me rendre compte d'un très (très) gros soucis de perfs lors du build du jeu mais pas dans Unity (ce qui perd tout son intérêt car on a déjà des fonctions équivalentes et natives en editor).
Premièrement car, pour détecter les meshes statiques, j'utilise gameObject.isStatic qui est editor-only et renvoit toujours false dans un jeu buildé... (donc tous les meshes sont considérés comme dynamiques, mais je devrais pouvoir le résoudre dans peu de temps).
Deuxièmement car l'affichage des meshes dynamiques est 3x fois plus long (!) en WebPlayer qu'en editor, et là, je sèche :( , si vous avez une idée...

Pour info, voici les statistiques de la scène de test :
Environ 3500-4000 edges répartis dans 22 meshes, dont 5 dynamiques.
En editor : 0-1ms pour 3286 edges statiques et 3-6ms pour 5 meshes dynamiques.
En webplayer : aucun edge statique, 95-102ms pour 22 meshes dynamiques :lol: (je rigole mais c'est nerveux).

Re: [C#] GLWireframe

Publié : 21 Fév 2013 00:50
par artemisart
WOW !
J'ai tout refait, et le résultat est 17x plus rapide ! (en webplayer) chuis très très content :mrgreen:

Les stats, avec la même scène qu'au dessus :
En editor : 2-5ms de baking et 1 ms de rendu.
En webplayer : 6ms de baking max, 0ms de rendu !

Le script fonctionne maintenant avec un système de baking, c'est à dire qu'il sauvegarde une liste de vertices (en world space) pour chaque mesh, et que cette liste est rebuilée seulement si le transform change.
J'update le premier message dès que possible.

EDIT : premier post mis à jour.

Re: [C#] GLWireframe

Publié : 21 Fév 2013 10:16
par Max
C'est vrai qu'on sentait bien que les perf en prenaient vite un gros cout :mrgreen:
Donc bien vue cette mise à jour ;)