PXR_Audio_Spatializer_SceneGeometry.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. // Copyright © 2015-2022 Pico Technology Co., Ltd. All Rights Reserved.
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using PXR_Audio.Spatializer;
  6. using UnityEditor;
  7. using UnityEngine;
  8. [RequireComponent(typeof(PXR_Audio_Spatializer_SceneMaterial))]
  9. public class PXR_Audio_Spatializer_SceneGeometry : MonoBehaviour
  10. {
  11. [SerializeField] private bool includeChildren = false;
  12. [SerializeField] private bool visualizeMeshInEditor = false;
  13. [SerializeField] private Mesh bakedStaticMesh;
  14. #region EDITOR-ONLY SerializedFields
  15. #if UNITY_EDITOR
  16. [SerializeField] private LayerMask meshBakingLayerMask = ~0;
  17. [SerializeField, HideInInspector] private string currentBakedStaticMeshAssetPath = null;
  18. #endif
  19. #endregion
  20. public bool isStaticMeshBaked => bakedStaticMesh != null;
  21. private int geometryId = -1;
  22. public int GeometryId
  23. {
  24. get => geometryId;
  25. }
  26. private int staticGeometryID = -1;
  27. public int StaticGeometryId => staticGeometryID;
  28. private PXR_Audio_Spatializer_SceneMaterial material;
  29. public PXR_Audio_Spatializer_SceneMaterial Material
  30. {
  31. get
  32. {
  33. if (material == null)
  34. {
  35. material = GetComponent<PXR_Audio_Spatializer_SceneMaterial>();
  36. }
  37. return material;
  38. }
  39. }
  40. private MeshConfig meshConfig;
  41. private uint propertyMask = 0;
  42. private int currentContextUuid = -2;
  43. private void OnEnable()
  44. {
  45. if (PXR_Audio_Spatializer_Context.Instance == null) return;
  46. // If geometries are added after context is initialized
  47. if (PXR_Audio_Spatializer_Context.Instance.UUID != currentContextUuid)
  48. {
  49. var ret = SubmitMeshToContext();
  50. var staticRet = SubmitStaticMeshToContext();
  51. }
  52. else
  53. {
  54. meshConfig = new MeshConfig(true, Material, transform.localToWorldMatrix);
  55. if (geometryId >= 0)
  56. PXR_Audio_Spatializer_Context.Instance.SetMeshConfig(geometryId, ref meshConfig,
  57. (uint)MeshProperty.All);
  58. if (staticGeometryID >= 0)
  59. PXR_Audio_Spatializer_Context.Instance.SetMeshConfig(staticGeometryID, ref meshConfig,
  60. (uint)MeshProperty.All);
  61. }
  62. }
  63. private void OnDisable()
  64. {
  65. if (PXR_Audio_Spatializer_Context.Instance == null) return;
  66. if (PXR_Audio_Spatializer_Context.Instance.UUID != currentContextUuid) return;
  67. meshConfig.enabled = false;
  68. if (geometryId >= 0)
  69. PXR_Audio_Spatializer_Context.Instance.SetMeshConfig(geometryId, ref meshConfig,
  70. (uint)MeshProperty.Enabled);
  71. if (staticGeometryID >= 0)
  72. PXR_Audio_Spatializer_Context.Instance.SetMeshConfig(staticGeometryID, ref meshConfig,
  73. (uint)MeshProperty.Enabled);
  74. }
  75. private void OnDestroy()
  76. {
  77. if (PXR_Audio_Spatializer_Context.Instance == null) return;
  78. if (PXR_Audio_Spatializer_Context.Instance.UUID != currentContextUuid) return;
  79. if (geometryId >= 0)
  80. PXR_Audio_Spatializer_Context.Instance.RemoveMesh(geometryId);
  81. if (staticGeometryID >= 0)
  82. PXR_Audio_Spatializer_Context.Instance.RemoveMesh(staticGeometryID);
  83. }
  84. private void Update()
  85. {
  86. if (PXR_Audio_Spatializer_Context.Instance == null) return;
  87. // // If geometries are added after context is initialized
  88. // if (PXR_Audio_Spatializer_Context.Instance.UUID != currentContextUuid)
  89. // {
  90. // var ret = SubmitMeshToContext();
  91. // var staticRet = SubmitStaticMeshToContext();
  92. // }
  93. if (transform.hasChanged)
  94. {
  95. meshConfig.SetTransformMatrix4x4(transform.localToWorldMatrix);
  96. propertyMask |= (uint)MeshProperty.ToWorldTransform;
  97. transform.hasChanged = false;
  98. }
  99. if (propertyMask > 0)
  100. {
  101. if (geometryId >= 0)
  102. PXR_Audio_Spatializer_Context.Instance.SetMeshConfig(geometryId, ref meshConfig,
  103. propertyMask);
  104. if (staticGeometryID >= 0)
  105. PXR_Audio_Spatializer_Context.Instance.SetMeshConfig(staticGeometryID, ref meshConfig,
  106. propertyMask);
  107. propertyMask = 0;
  108. }
  109. }
  110. public void UpdateAbsorptionMultiband(float[] absorptions)
  111. {
  112. meshConfig.materialType = AcousticsMaterial.Custom;
  113. meshConfig.absorption.v0 = Material.absorption[0] = absorptions[0];
  114. meshConfig.absorption.v1 = Material.absorption[1] = absorptions[1];
  115. meshConfig.absorption.v2 = Material.absorption[2] = absorptions[2];
  116. meshConfig.absorption.v3 = Material.absorption[3] = absorptions[3];
  117. propertyMask |= (uint)MeshProperty.Material | (uint)MeshProperty.Absorption;
  118. }
  119. public void UpdateScattering(float scattering)
  120. {
  121. meshConfig.materialType = AcousticsMaterial.Custom;
  122. meshConfig.scattering = Material.scattering = scattering;
  123. propertyMask |= (uint)MeshProperty.Material | (uint)MeshProperty.Scattering;
  124. }
  125. public void UpdateTransmission(float transmission)
  126. {
  127. meshConfig.materialType = AcousticsMaterial.Custom;
  128. meshConfig.transmission = Material.transmission = transmission;
  129. propertyMask |= (uint)MeshProperty.Material | (uint)MeshProperty.Transmission;
  130. }
  131. public void UpdateMaterialType(PXR_Audio.Spatializer.AcousticsMaterial materialType)
  132. {
  133. meshConfig.materialType = materialType;
  134. propertyMask |= (uint)MeshProperty.Material;
  135. }
  136. private void GetAllMeshFilter(Transform transform, bool includeChildren, List<MeshFilter> meshFilterList,
  137. bool isStatic, LayerMask layerMask)
  138. {
  139. if (includeChildren)
  140. {
  141. int childCount = transform.childCount;
  142. for (int i = 0; i < childCount; i++)
  143. {
  144. var childTransform = transform.GetChild(i);
  145. if (childTransform.GetComponent<PXR_Audio_Spatializer_SceneGeometry>() == null)
  146. {
  147. GetAllMeshFilter(childTransform.transform, includeChildren, meshFilterList, isStatic, layerMask);
  148. }
  149. }
  150. }
  151. // Gather this mesh only when
  152. // 1. Its isStatic flag is equal to our requirement
  153. // 2. Its layer belongs to layerMask set
  154. if (((1 << transform.gameObject.layer) & layerMask) != 0)
  155. {
  156. var meshFilterArray = transform.GetComponents<MeshFilter>();
  157. // cases we don't add to mesh filter list
  158. // 1. meshFilter.sharedmesh == null
  159. // 2. meshFilter.sharedmesh.isReadable == false
  160. if (meshFilterArray != null)
  161. {
  162. foreach (var meshFilter in meshFilterArray)
  163. {
  164. if (meshFilter != null && meshFilter.sharedMesh != null &&
  165. (
  166. (isStatic && (transform.gameObject.isStatic || !meshFilter.sharedMesh.isReadable)) ||
  167. (!isStatic && (!transform.gameObject.isStatic && meshFilter.sharedMesh.isReadable))
  168. ))
  169. {
  170. meshFilterList.Add(meshFilter);
  171. }
  172. }
  173. }
  174. }
  175. }
  176. private static Mesh CombineMeshes(List<MeshFilter> meshFilterList, Transform rootTransform)
  177. {
  178. Mesh combinedMesh = new Mesh
  179. {
  180. name = "combined meshes",
  181. indexFormat = UnityEngine.Rendering.IndexFormat.UInt32
  182. };
  183. var combinedVertices = Array.Empty<Vector3>();
  184. var combinedIndices = Array.Empty<int>();
  185. // Accumulate combined vertices buffer size
  186. foreach (var meshFilter in meshFilterList)
  187. {
  188. int vertexOffset = combinedVertices.Length;
  189. combinedVertices = combinedVertices.Concat(meshFilter.sharedMesh.vertices).ToArray();
  190. int vertexSegmentEnd = combinedVertices.Length;
  191. var toWorld = rootTransform.worldToLocalMatrix *
  192. meshFilter.transform.localToWorldMatrix;
  193. for (int i = vertexOffset; i < vertexSegmentEnd; ++i)
  194. {
  195. combinedVertices[i] = toWorld.MultiplyPoint3x4(combinedVertices[i]);
  196. }
  197. var trianglesStartIdx = combinedIndices.Length;
  198. combinedIndices = combinedIndices.Concat(meshFilter.sharedMesh.triangles).ToArray();
  199. var trianglesEndIdx = combinedIndices.Length;
  200. for (var i = trianglesStartIdx; i < trianglesEndIdx; ++i)
  201. {
  202. combinedIndices[i] += vertexOffset;
  203. }
  204. }
  205. combinedMesh.vertices = combinedVertices;
  206. combinedMesh.triangles = combinedIndices;
  207. combinedMesh.RecalculateNormals();
  208. return combinedMesh;
  209. }
  210. private static float[] FlattenVerticesBuffer(Vector3[] verticesBuffer)
  211. {
  212. float[] vertices = new float[verticesBuffer.Length * 3];
  213. int index = 0;
  214. foreach (Vector3 vertex in verticesBuffer)
  215. {
  216. vertices[index++] = vertex.x;
  217. vertices[index++] = vertex.y;
  218. vertices[index++] = vertex.z;
  219. }
  220. return vertices;
  221. }
  222. /// <summary>
  223. /// Submit non-static mesh of this geometry and its material into spatializer engine context
  224. /// </summary>
  225. /// <returns>Result of static mesh submission</returns>
  226. public PXR_Audio.Spatializer.Result SubmitMeshToContext()
  227. {
  228. // find all meshes
  229. var meshFilterList = new List<MeshFilter>();
  230. GetAllMeshFilter(transform, includeChildren, meshFilterList, false, ~0);
  231. // Combine all meshes
  232. Mesh combinedMesh = CombineMeshes(meshFilterList, transform);
  233. // flatten vertices buffer into a float array
  234. float[] vertices = FlattenVerticesBuffer(combinedMesh.vertices);
  235. meshConfig = new MeshConfig(enabled, Material, transform.localToWorldMatrix);
  236. // Submit all meshes
  237. PXR_Audio.Spatializer.Result result = PXR_Audio_Spatializer_Context.Instance.SubmitMeshWithConfig(
  238. vertices, vertices.Length / 3,
  239. combinedMesh.triangles, combinedMesh.triangles.Length / 3,
  240. ref meshConfig, ref geometryId);
  241. if (result != Result.Success)
  242. Debug.LogError("Failed to submit audio mesh: " + gameObject.name + ", Error code is: " + result);
  243. else
  244. Debug.LogFormat("Submitted geometry #{0}, gameObject name is {1}", geometryId.ToString(),
  245. name);
  246. if (result == Result.Success)
  247. currentContextUuid = PXR_Audio_Spatializer_Context.Instance.UUID;
  248. return result;
  249. }
  250. /// <summary>
  251. /// Submit static mesh of this geometry and its material into spatializer engine context
  252. /// </summary>
  253. /// <returns>Result of static mesh submission</returns>
  254. public PXR_Audio.Spatializer.Result SubmitStaticMeshToContext()
  255. {
  256. PXR_Audio.Spatializer.Result result = Result.Success;
  257. if (bakedStaticMesh != null)
  258. {
  259. float[] tempVertices = FlattenVerticesBuffer(bakedStaticMesh.vertices);
  260. meshConfig = new MeshConfig(enabled, Material, transform.localToWorldMatrix);
  261. result = PXR_Audio_Spatializer_Context.Instance.SubmitMeshWithConfig(tempVertices,
  262. bakedStaticMesh.vertices.Length, bakedStaticMesh.triangles,
  263. bakedStaticMesh.triangles.Length / 3, ref meshConfig,
  264. ref staticGeometryID);
  265. if (result != Result.Success)
  266. Debug.LogError("Failed to submit static audio mesh: " + gameObject.name + ", Error code is: " + result);
  267. else
  268. Debug.LogFormat("Submitted static geometry #{0}, gameObject name is {1}", staticGeometryID.ToString(),
  269. name);
  270. }
  271. if (result == Result.Success)
  272. currentContextUuid = PXR_Audio_Spatializer_Context.Instance.UUID;
  273. return result;
  274. }
  275. #if UNITY_EDITOR
  276. public int BakeStaticMesh(LayerMask layerMask)
  277. {
  278. List<MeshFilter> meshList = new List<MeshFilter>();
  279. GetAllMeshFilter(transform, includeChildren, meshList, true, meshBakingLayerMask);
  280. SerializedObject serializedObject = new SerializedObject(this);
  281. if (meshList.Count == 0)
  282. {
  283. bakedStaticMesh = null;
  284. }
  285. else
  286. {
  287. bakedStaticMesh = CombineMeshes(meshList, transform);
  288. bakedStaticMesh.name = "baked mesh for ygg";
  289. }
  290. serializedObject.FindProperty("bakedStaticMesh").objectReferenceValue = bakedStaticMesh;
  291. if (bakedStaticMesh != null)
  292. {
  293. System.IO.Directory.CreateDirectory("Assets/Resources/PxrAudioSpatializerBakedSceneMeshes/");
  294. if (!string.IsNullOrEmpty(currentBakedStaticMeshAssetPath))
  295. {
  296. AssetDatabase.DeleteAsset(currentBakedStaticMeshAssetPath);
  297. }
  298. currentBakedStaticMeshAssetPath = "Assets/Resources/PxrAudioSpatializerBakedSceneMeshes/" + name + "_" +
  299. GetInstanceID() + "_" +
  300. System.DateTime.UtcNow.ToBinary() + ".yggmesh";
  301. serializedObject.FindProperty("currentBakedStaticMeshAssetPath").stringValue =
  302. currentBakedStaticMeshAssetPath;
  303. AssetDatabase.CreateAsset(bakedStaticMesh, currentBakedStaticMeshAssetPath);
  304. AssetDatabase.SaveAssets();
  305. }
  306. serializedObject.ApplyModifiedProperties();
  307. return meshList.Count;
  308. }
  309. public void ClearBakeStaticMesh()
  310. {
  311. SerializedObject serializedObject = new SerializedObject(this);
  312. bakedStaticMesh = null;
  313. serializedObject.FindProperty("bakedStaticMesh").objectReferenceValue = null;
  314. if (!string.IsNullOrEmpty(currentBakedStaticMeshAssetPath))
  315. {
  316. AssetDatabase.DeleteAsset(currentBakedStaticMeshAssetPath);
  317. currentBakedStaticMeshAssetPath = null;
  318. serializedObject.FindProperty("currentBakedStaticMeshAssetPath").stringValue =
  319. currentBakedStaticMeshAssetPath;
  320. }
  321. serializedObject.ApplyModifiedProperties();
  322. }
  323. #endif
  324. public void OnDrawGizmos()
  325. {
  326. if (visualizeMeshInEditor)
  327. {
  328. // Visualize non-static meshes
  329. // find all MeshFilter
  330. var meshFilterList = new List<MeshFilter>();
  331. GetAllMeshFilter(transform, includeChildren, meshFilterList, false, ~0);
  332. for (int i = 0; i < meshFilterList.Count; i++)
  333. {
  334. var mesh = meshFilterList[i].sharedMesh;
  335. var transform = meshFilterList[i].transform;
  336. Gizmos.DrawWireMesh(mesh,
  337. transform.position, transform.rotation, transform.localScale);
  338. }
  339. // Visualize baked static meshes
  340. if (isStaticMeshBaked)
  341. {
  342. Color colorBackUp = Gizmos.color;
  343. Color c;
  344. c.r = 0.0f;
  345. c.g = 0.7f;
  346. c.b = 0.0f;
  347. c.a = 1.0f;
  348. Gizmos.color = c;
  349. var gizmosMatrixBackup = Gizmos.matrix;
  350. Gizmos.matrix = transform.localToWorldMatrix;
  351. Gizmos.DrawWireMesh(bakedStaticMesh);
  352. Gizmos.color = colorBackUp;
  353. Gizmos.matrix = gizmosMatrixBackup;
  354. }
  355. }
  356. }
  357. }
粤ICP备19079148号