VolumetricLineStripBehavior.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. using UnityEngine;
  2. namespace VolumetricLines
  3. {
  4. /// <summary>
  5. /// Render a line strip of volumetric lines
  6. ///
  7. /// Based on the Volumetric lines algorithm by Sebastien Hillaire
  8. /// http://sebastien.hillaire.free.fr/index.php?option=com_content&view=article&id=57&Itemid=74
  9. ///
  10. /// Thread in the Unity3D Forum:
  11. /// http://forum.unity3d.com/threads/181618-Volumetric-lines
  12. ///
  13. /// Unity3D port by Johannes Unterguggenberger
  14. /// johannes.unterguggenberger@gmail.com
  15. ///
  16. /// Thanks to Michael Probst for support during development.
  17. ///
  18. /// Thanks for bugfixes and improvements to Unity Forum User "Mistale"
  19. /// http://forum.unity3d.com/members/102350-Mistale
  20. ///
  21. /// /// Shader code optimization and cleanup by Lex Darlog (aka DRL)
  22. /// http://forum.unity3d.com/members/lex-drl.67487/
  23. ///
  24. /// </summary>
  25. [RequireComponent(typeof(MeshFilter))]
  26. [RequireComponent(typeof(MeshRenderer))]
  27. [ExecuteInEditMode]
  28. public class VolumetricLineStripBehavior : MonoBehaviour
  29. {
  30. // Used to compute the average value of all the Vector3's components:
  31. static readonly Vector3 Average = new Vector3(1f/3f, 1f/3f, 1f/3f);
  32. #region private variables
  33. /// <summary>
  34. /// Template material to be used
  35. /// </summary>
  36. [SerializeField]
  37. public Material m_templateMaterial;
  38. /// <summary>
  39. /// Set to false in order to change the material's properties as specified in this script.
  40. /// Set to true in order to *initially* leave the material's properties as they are in the template material.
  41. /// </summary>
  42. [SerializeField]
  43. private bool m_doNotOverwriteTemplateMaterialProperties;
  44. /// <summary>
  45. /// Line Color
  46. /// </summary>
  47. [SerializeField]
  48. private Color m_lineColor;
  49. /// <summary>
  50. /// The width of the line
  51. /// </summary>
  52. [SerializeField]
  53. private float m_lineWidth;
  54. /// <summary>
  55. /// Light saber factor
  56. /// </summary>
  57. [SerializeField]
  58. [Range(0.0f, 1.0f)]
  59. private float m_lightSaberFactor;
  60. /// <summary>
  61. /// This GameObject's specific material
  62. /// </summary>
  63. private Material m_material;
  64. /// <summary>
  65. /// This GameObject's mesh filter
  66. /// </summary>
  67. private MeshFilter m_meshFilter;
  68. /// <summary>
  69. /// The vertices of the line
  70. /// </summary>
  71. [SerializeField]
  72. private Vector3[] m_lineVertices;
  73. #endregion
  74. #region properties
  75. /// <summary>
  76. /// Gets or sets the tmplate material.
  77. /// Setting this will only have an impact once.
  78. /// Subsequent changes will be ignored.
  79. /// </summary>
  80. public Material TemplateMaterial
  81. {
  82. get { return m_templateMaterial; }
  83. set { m_templateMaterial = value; }
  84. }
  85. /// <summary>
  86. /// Gets or sets whether or not the template material properties
  87. /// should be used (false) or if the properties of this MonoBehavior
  88. /// instance should be used (true, default).
  89. /// Setting this will only have an impact once, and then only if it
  90. /// is set before TemplateMaterial has been assigned.
  91. /// </summary>
  92. public bool DoNotOverwriteTemplateMaterialProperties
  93. {
  94. get { return m_doNotOverwriteTemplateMaterialProperties; }
  95. set { m_doNotOverwriteTemplateMaterialProperties = value; }
  96. }
  97. /// <summary>
  98. /// Get or set the line color of this volumetric line's material
  99. /// </summary>
  100. public Color LineColor
  101. {
  102. get { return m_lineColor; }
  103. set
  104. {
  105. CreateMaterial();
  106. if (null != m_material)
  107. {
  108. m_lineColor = value;
  109. m_material.color = m_lineColor;
  110. }
  111. }
  112. }
  113. /// <summary>
  114. /// Get or set the line width of this volumetric line's material
  115. /// </summary>
  116. public float LineWidth
  117. {
  118. get { return m_lineWidth; }
  119. set
  120. {
  121. CreateMaterial();
  122. if (null != m_material)
  123. {
  124. m_lineWidth = value;
  125. m_material.SetFloat("_LineWidth", m_lineWidth);
  126. }
  127. UpdateBounds();
  128. }
  129. }
  130. /// <summary>
  131. /// Get or set the light saber factor of this volumetric line's material
  132. /// </summary>
  133. public float LightSaberFactor
  134. {
  135. get { return m_lightSaberFactor; }
  136. set
  137. {
  138. CreateMaterial();
  139. if (null != m_material)
  140. {
  141. m_lightSaberFactor = value;
  142. m_material.SetFloat("_LightSaberFactor", m_lightSaberFactor);
  143. }
  144. }
  145. }
  146. /// <summary>
  147. /// Gets the vertices of this line strip
  148. /// </summary>
  149. public Vector3[] LineVertices
  150. {
  151. get { return m_lineVertices; }
  152. }
  153. #endregion
  154. #region methods
  155. /// <summary>
  156. /// Creates a copy of the template material for this instance
  157. /// </summary>
  158. private void CreateMaterial()
  159. {
  160. if (null == m_material || null == GetComponent<MeshRenderer>().sharedMaterial)
  161. {
  162. if (null != m_templateMaterial)
  163. {
  164. m_material = Material.Instantiate(m_templateMaterial);
  165. GetComponent<MeshRenderer>().sharedMaterial = m_material;
  166. SetAllMaterialProperties();
  167. }
  168. else
  169. {
  170. m_material = GetComponent<MeshRenderer>().sharedMaterial;
  171. }
  172. }
  173. }
  174. /// <summary>
  175. /// Destroys the copy of the template material which was used for this instance
  176. /// </summary>
  177. private void DestroyMaterial()
  178. {
  179. if (null != m_material)
  180. {
  181. DestroyImmediate(m_material);
  182. m_material = null;
  183. }
  184. }
  185. /// <summary>
  186. /// Calculates the (approximated) _LineScale factor based on the object's scale.
  187. /// </summary>
  188. private float CalculateLineScale()
  189. {
  190. return Vector3.Dot(transform.lossyScale, Average);
  191. }
  192. /// <summary>
  193. /// Updates the line scaling of this volumetric line based on the current object scaling.
  194. /// </summary>
  195. public void UpdateLineScale()
  196. {
  197. if (null != m_material)
  198. {
  199. m_material.SetFloat("_LineScale", CalculateLineScale());
  200. }
  201. }
  202. /// <summary>
  203. /// Sets all material properties (color, width, start-, endpos)
  204. /// </summary>
  205. private void SetAllMaterialProperties()
  206. {
  207. UpdateLineVertices(m_lineVertices);
  208. if (null != m_material)
  209. {
  210. if (!m_doNotOverwriteTemplateMaterialProperties)
  211. {
  212. m_material.color = m_lineColor;
  213. m_material.SetFloat("_LineWidth", m_lineWidth);
  214. m_material.SetFloat("_LightSaberFactor", m_lightSaberFactor);
  215. }
  216. UpdateLineScale();
  217. }
  218. }
  219. /// <summary>
  220. /// Calculate the bounds of this line based on the coordinates of the line vertices,
  221. /// the line width, and the scaling of the object.
  222. /// </summary>
  223. private Bounds CalculateBounds()
  224. {
  225. var maxWidth = Mathf.Max(transform.lossyScale.x, transform.lossyScale.y, transform.lossyScale.z);
  226. var scaledLineWidth = maxWidth * LineWidth * 0.5f;
  227. var scaledLineWidthVec = new Vector3(scaledLineWidth, scaledLineWidth, scaledLineWidth);
  228. Debug.Assert(m_lineVertices.Length > 0);
  229. if (m_lineVertices.Length == 0)
  230. {
  231. return new Bounds();
  232. }
  233. var min = m_lineVertices[0];
  234. var max = m_lineVertices[0];
  235. for (int i = 1; i < m_lineVertices.Length; ++i)
  236. {
  237. min = new Vector3(
  238. Mathf.Min(min.x, m_lineVertices[i].x),
  239. Mathf.Min(min.y, m_lineVertices[i].y),
  240. Mathf.Min(min.z, m_lineVertices[i].z)
  241. );
  242. max = new Vector3(
  243. Mathf.Max(max.x, m_lineVertices[i].x),
  244. Mathf.Max(max.y, m_lineVertices[i].y),
  245. Mathf.Max(max.z, m_lineVertices[i].z)
  246. );
  247. }
  248. return new Bounds
  249. {
  250. min = min - scaledLineWidthVec,
  251. max = max + scaledLineWidthVec
  252. };
  253. }
  254. /// <summary>
  255. /// Updates the bounds of this line according to the current properties,
  256. /// which there are: coordinates of the line vertices, line width, scaling of the object.
  257. /// </summary>
  258. public void UpdateBounds()
  259. {
  260. if (null != m_meshFilter)
  261. {
  262. var mesh = m_meshFilter.sharedMesh;
  263. Debug.Assert(null != mesh);
  264. if (null != mesh)
  265. {
  266. mesh.bounds = CalculateBounds();
  267. }
  268. }
  269. }
  270. /// <summary>
  271. /// Updates the vertices of this VolumetricLineStrip.
  272. /// This is an expensive operation.
  273. /// </summary>
  274. /// <param name="newSetOfVertices">new set of vertices for the line strip.</param>
  275. public void UpdateLineVertices(Vector3[] newSetOfVertices)
  276. {
  277. if (null == newSetOfVertices)
  278. {
  279. return;
  280. }
  281. if (newSetOfVertices.Length < 3)
  282. {
  283. Debug.LogError("Add at least 3 vertices to the VolumetricLineStrip");
  284. return;
  285. }
  286. m_lineVertices = newSetOfVertices;
  287. // fill vertex positions, and indices
  288. // 2 for each position, + 2 for the start, + 2 for the end
  289. Vector3[] vertexPositions = new Vector3[m_lineVertices.Length * 2 + 4];
  290. // there are #vertices - 2 faces, and 3 indices each
  291. int[] indices = new int[(m_lineVertices.Length * 2 + 2) * 3];
  292. int v = 0;
  293. int x = 0;
  294. vertexPositions[v++] = m_lineVertices[0];
  295. vertexPositions[v++] = m_lineVertices[0];
  296. for (int i = 0; i < m_lineVertices.Length; ++i)
  297. {
  298. vertexPositions[v++] = m_lineVertices[i];
  299. vertexPositions[v++] = m_lineVertices[i];
  300. indices[x++] = v - 2;
  301. indices[x++] = v - 3;
  302. indices[x++] = v - 4;
  303. indices[x++] = v - 1;
  304. indices[x++] = v - 2;
  305. indices[x++] = v - 3;
  306. }
  307. vertexPositions[v++] = m_lineVertices[m_lineVertices.Length - 1];
  308. vertexPositions[v++] = m_lineVertices[m_lineVertices.Length - 1];
  309. indices[x++] = v - 2;
  310. indices[x++] = v - 3;
  311. indices[x++] = v - 4;
  312. indices[x++] = v - 1;
  313. indices[x++] = v - 2;
  314. indices[x++] = v - 3;
  315. // fill texture coordinates and vertex offsets
  316. Vector2[] texCoords = new Vector2[vertexPositions.Length];
  317. Vector2[] vertexOffsets = new Vector2[vertexPositions.Length];
  318. int t = 0;
  319. int o = 0;
  320. texCoords[t++] = new Vector2(1.0f, 0.0f);
  321. texCoords[t++] = new Vector2(1.0f, 1.0f);
  322. texCoords[t++] = new Vector2(0.5f, 0.0f);
  323. texCoords[t++] = new Vector2(0.5f, 1.0f);
  324. vertexOffsets[o++] = new Vector2(1.0f, -1.0f);
  325. vertexOffsets[o++] = new Vector2(1.0f, 1.0f);
  326. vertexOffsets[o++] = new Vector2(0.0f, -1.0f);
  327. vertexOffsets[o++] = new Vector2(0.0f, 1.0f);
  328. for (int i = 1; i < m_lineVertices.Length - 1; ++i)
  329. {
  330. if ((i & 0x1) == 0x1)
  331. {
  332. texCoords[t++] = new Vector2(0.5f, 0.0f);
  333. texCoords[t++] = new Vector2(0.5f, 1.0f);
  334. }
  335. else
  336. {
  337. texCoords[t++] = new Vector2(0.5f, 0.0f);
  338. texCoords[t++] = new Vector2(0.5f, 1.0f);
  339. }
  340. vertexOffsets[o++] = new Vector2(0.0f, 1.0f);
  341. vertexOffsets[o++] = new Vector2(0.0f, -1.0f);
  342. }
  343. texCoords[t++] = new Vector2(0.5f, 0.0f);
  344. texCoords[t++] = new Vector2(0.5f, 1.0f);
  345. texCoords[t++] = new Vector2(0.0f, 0.0f);
  346. texCoords[t++] = new Vector2(0.0f, 1.0f);
  347. vertexOffsets[o++] = new Vector2(0.0f, 1.0f);
  348. vertexOffsets[o++] = new Vector2(0.0f, -1.0f);
  349. vertexOffsets[o++] = new Vector2(1.0f, 1.0f);
  350. vertexOffsets[o++] = new Vector2(1.0f, -1.0f);
  351. // fill previous and next positions
  352. Vector3[] prevPositions = new Vector3[vertexPositions.Length];
  353. Vector4[] nextPositions = new Vector4[vertexPositions.Length];
  354. int p = 0;
  355. int n = 0;
  356. prevPositions[p++] = m_lineVertices[1];
  357. prevPositions[p++] = m_lineVertices[1];
  358. prevPositions[p++] = m_lineVertices[1];
  359. prevPositions[p++] = m_lineVertices[1];
  360. nextPositions[n++] = m_lineVertices[1];
  361. nextPositions[n++] = m_lineVertices[1];
  362. nextPositions[n++] = m_lineVertices[1];
  363. nextPositions[n++] = m_lineVertices[1];
  364. for (int i = 1; i < m_lineVertices.Length - 1; ++i)
  365. {
  366. prevPositions[p++] = m_lineVertices[i - 1];
  367. prevPositions[p++] = m_lineVertices[i - 1];
  368. nextPositions[n++] = m_lineVertices[i + 1];
  369. nextPositions[n++] = m_lineVertices[i + 1];
  370. }
  371. prevPositions[p++] = m_lineVertices[m_lineVertices.Length - 2];
  372. prevPositions[p++] = m_lineVertices[m_lineVertices.Length - 2];
  373. prevPositions[p++] = m_lineVertices[m_lineVertices.Length - 2];
  374. prevPositions[p++] = m_lineVertices[m_lineVertices.Length - 2];
  375. nextPositions[n++] = m_lineVertices[m_lineVertices.Length - 2];
  376. nextPositions[n++] = m_lineVertices[m_lineVertices.Length - 2];
  377. nextPositions[n++] = m_lineVertices[m_lineVertices.Length - 2];
  378. nextPositions[n++] = m_lineVertices[m_lineVertices.Length - 2];
  379. if (null != m_meshFilter)
  380. {
  381. var mesh = m_meshFilter.sharedMesh;
  382. Debug.Assert(null != mesh);
  383. if (null != mesh)
  384. {
  385. mesh.SetIndices(null, MeshTopology.Triangles, 0); // Reset before setting again to prevent a unity error message.
  386. mesh.vertices = vertexPositions;
  387. mesh.normals = prevPositions;
  388. mesh.tangents = nextPositions;
  389. mesh.uv = texCoords;
  390. mesh.uv2 = vertexOffsets;
  391. mesh.SetIndices(indices, MeshTopology.Triangles, 0);
  392. UpdateBounds();
  393. }
  394. }
  395. }
  396. #endregion
  397. #region event functions
  398. void Start ()
  399. {
  400. Mesh mesh = new Mesh();
  401. m_meshFilter = GetComponent<MeshFilter>();
  402. m_meshFilter.mesh = mesh;
  403. UpdateLineVertices(m_lineVertices);
  404. CreateMaterial();
  405. }
  406. void OnDestroy()
  407. {
  408. if (null != m_meshFilter)
  409. {
  410. if (Application.isPlaying)
  411. {
  412. Mesh.Destroy(m_meshFilter.sharedMesh);
  413. }
  414. else // avoid "may not be called from edit mode" error
  415. {
  416. Mesh.DestroyImmediate(m_meshFilter.sharedMesh);
  417. }
  418. m_meshFilter.sharedMesh = null;
  419. }
  420. DestroyMaterial();
  421. }
  422. void Update()
  423. {
  424. if (transform.hasChanged)
  425. {
  426. UpdateLineScale();
  427. UpdateBounds();
  428. }
  429. }
  430. void OnValidate()
  431. {
  432. // This function is called when the script is loaded or a value is changed in the inspector (Called in the editor only).
  433. // => make sure, everything stays up-to-date
  434. if(string.IsNullOrEmpty(gameObject.scene.name) || string.IsNullOrEmpty(gameObject.scene.path)) {
  435. return; // ...but not if a Prefab is selected! (Only if we're using it within a scene.)
  436. }
  437. CreateMaterial();
  438. SetAllMaterialProperties();
  439. UpdateBounds();
  440. }
  441. void OnDrawGizmos()
  442. {
  443. Gizmos.color = Color.green;
  444. if (null == m_lineVertices)
  445. {
  446. return;
  447. }
  448. for (int i=0; i < m_lineVertices.Length - 1; ++i)
  449. {
  450. Gizmos.DrawLine(gameObject.transform.TransformPoint(m_lineVertices[i]), gameObject.transform.TransformPoint(m_lineVertices[i+1]));
  451. }
  452. }
  453. #endregion
  454. }
  455. }