SteamVR_SkyboxEditor.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. //======= Copyright (c) Valve Corporation, All rights reserved. ===============
  2. //
  3. // Purpose: Custom inspector display for SteamVR_Skybox
  4. //
  5. //=============================================================================
  6. using UnityEngine;
  7. using UnityEditor;
  8. using System.Text;
  9. using System.Collections.Generic;
  10. using Valve.VR;
  11. using System.IO;
  12. namespace Valve.VR
  13. {
  14. [CustomEditor(typeof(SteamVR_Skybox)), CanEditMultipleObjects]
  15. public class SteamVR_SkyboxEditor : Editor
  16. {
  17. private const string nameFormat = "{0}/{1}-{2}.png";
  18. private const string helpText = "Take snapshot will use the current " +
  19. "position and rotation to capture six directional screenshots to use as this " +
  20. "skybox's textures. Note: This skybox is only used to override what shows up " +
  21. "in the compositor (e.g. when loading levels). Add a Camera component to this " +
  22. "object to override default settings like which layers to render. Additionally, " +
  23. "by specifying your own targetTexture, you can control the size of the textures " +
  24. "and other properties like antialiasing. Don't forget to disable the camera.\n\n" +
  25. "For stereo screenshots, a panorama is render for each eye using the specified " +
  26. "ipd (in millimeters) broken up into segments cellSize pixels square to optimize " +
  27. "generation.\n(32x32 takes about 10 seconds depending on scene complexity, 16x16 " +
  28. "takes around a minute, while will 8x8 take several minutes.)\n\nTo test, hit " +
  29. "play then pause - this will activate the skybox settings, and then drop you to " +
  30. "the compositor where the skybox is rendered.";
  31. public override void OnInspectorGUI()
  32. {
  33. DrawDefaultInspector();
  34. EditorGUILayout.HelpBox(helpText, MessageType.Info);
  35. if (GUILayout.Button("Take snapshot"))
  36. {
  37. var directions = new Quaternion[] {
  38. Quaternion.LookRotation(Vector3.forward),
  39. Quaternion.LookRotation(Vector3.back),
  40. Quaternion.LookRotation(Vector3.left),
  41. Quaternion.LookRotation(Vector3.right),
  42. Quaternion.LookRotation(Vector3.up, Vector3.back),
  43. Quaternion.LookRotation(Vector3.down, Vector3.forward)
  44. };
  45. Camera tempCamera = null;
  46. foreach (SteamVR_Skybox target in targets)
  47. {
  48. var targetScene = target.gameObject.scene;
  49. var sceneName = Path.GetFileNameWithoutExtension(targetScene.path);
  50. var scenePath = Path.GetDirectoryName(targetScene.path);
  51. var assetPath = scenePath + "/" + sceneName;
  52. if (!AssetDatabase.IsValidFolder(assetPath))
  53. {
  54. var guid = AssetDatabase.CreateFolder(scenePath, sceneName);
  55. assetPath = AssetDatabase.GUIDToAssetPath(guid);
  56. }
  57. var camera = target.GetComponent<Camera>();
  58. if (camera == null)
  59. {
  60. if (tempCamera == null)
  61. tempCamera = new GameObject().AddComponent<Camera>();
  62. camera = tempCamera;
  63. }
  64. var targetTexture = camera.targetTexture;
  65. if (camera.targetTexture == null)
  66. {
  67. targetTexture = new RenderTexture(1024, 1024, 24);
  68. targetTexture.antiAliasing = 8;
  69. camera.targetTexture = targetTexture;
  70. }
  71. var oldPosition = target.transform.localPosition;
  72. var oldRotation = target.transform.localRotation;
  73. var baseRotation = target.transform.rotation;
  74. var t = camera.transform;
  75. t.position = target.transform.position;
  76. camera.orthographic = false;
  77. camera.fieldOfView = 90;
  78. for (int i = 0; i < directions.Length; i++)
  79. {
  80. t.rotation = baseRotation * directions[i];
  81. camera.Render();
  82. // Copy to texture and save to disk.
  83. RenderTexture.active = targetTexture;
  84. var texture = new Texture2D(targetTexture.width, targetTexture.height, TextureFormat.ARGB32, false);
  85. texture.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0);
  86. texture.Apply();
  87. RenderTexture.active = null;
  88. var assetName = string.Format(nameFormat, assetPath, target.name, i);
  89. System.IO.File.WriteAllBytes(assetName, texture.EncodeToPNG());
  90. }
  91. if (camera != tempCamera)
  92. {
  93. target.transform.localPosition = oldPosition;
  94. target.transform.localRotation = oldRotation;
  95. }
  96. }
  97. if (tempCamera != null)
  98. {
  99. Object.DestroyImmediate(tempCamera.gameObject);
  100. }
  101. // Now that everything has be written out, reload the associated assets and assign them.
  102. AssetDatabase.Refresh();
  103. foreach (SteamVR_Skybox target in targets)
  104. {
  105. var targetScene = target.gameObject.scene;
  106. var sceneName = Path.GetFileNameWithoutExtension(targetScene.path);
  107. var scenePath = Path.GetDirectoryName(targetScene.path);
  108. var assetPath = scenePath + "/" + sceneName;
  109. for (int i = 0; i < directions.Length; i++)
  110. {
  111. var assetName = string.Format(nameFormat, assetPath, target.name, i);
  112. var importer = AssetImporter.GetAtPath(assetName) as TextureImporter;
  113. #if (UNITY_5_4 || UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0)
  114. importer.textureFormat = TextureImporterFormat.RGB24;
  115. #else
  116. importer.textureCompression = TextureImporterCompression.Uncompressed;
  117. #endif
  118. importer.wrapMode = TextureWrapMode.Clamp;
  119. importer.mipmapEnabled = false;
  120. importer.SaveAndReimport();
  121. var texture = AssetDatabase.LoadAssetAtPath<Texture>(assetName);
  122. target.SetTextureByIndex(i, texture);
  123. }
  124. }
  125. }
  126. else if (GUILayout.Button("Take stereo snapshot"))
  127. {
  128. const int width = 4096;
  129. const int height = width / 2;
  130. const int halfHeight = height / 2;
  131. var textures = new Texture2D[] {
  132. new Texture2D(width, height, TextureFormat.ARGB32, false),
  133. new Texture2D(width, height, TextureFormat.ARGB32, false) };
  134. var timer = new System.Diagnostics.Stopwatch();
  135. Camera tempCamera = null;
  136. foreach (SteamVR_Skybox target in targets)
  137. {
  138. timer.Start();
  139. var targetScene = target.gameObject.scene;
  140. var sceneName = Path.GetFileNameWithoutExtension(targetScene.path);
  141. var scenePath = Path.GetDirectoryName(targetScene.path);
  142. var assetPath = scenePath + "/" + sceneName;
  143. if (!AssetDatabase.IsValidFolder(assetPath))
  144. {
  145. var guid = AssetDatabase.CreateFolder(scenePath, sceneName);
  146. assetPath = AssetDatabase.GUIDToAssetPath(guid);
  147. }
  148. var camera = target.GetComponent<Camera>();
  149. if (camera == null)
  150. {
  151. if (tempCamera == null)
  152. tempCamera = new GameObject().AddComponent<Camera>();
  153. camera = tempCamera;
  154. }
  155. var fx = camera.gameObject.AddComponent<SteamVR_SphericalProjection>();
  156. var oldTargetTexture = camera.targetTexture;
  157. var oldOrthographic = camera.orthographic;
  158. var oldFieldOfView = camera.fieldOfView;
  159. var oldAspect = camera.aspect;
  160. var oldPosition = target.transform.localPosition;
  161. var oldRotation = target.transform.localRotation;
  162. var basePosition = target.transform.position;
  163. var baseRotation = target.transform.rotation;
  164. var transform = camera.transform;
  165. int cellSize = int.Parse(target.StereoCellSize.ToString().Substring(1));
  166. float ipd = target.StereoIpdMm / 1000.0f;
  167. int vTotal = halfHeight / cellSize;
  168. float dv = 90.0f / vTotal; // vertical degrees per segment
  169. float dvHalf = dv / 2.0f;
  170. var targetTexture = new RenderTexture(cellSize, cellSize, 24);
  171. targetTexture.wrapMode = TextureWrapMode.Clamp;
  172. targetTexture.antiAliasing = 8;
  173. camera.fieldOfView = dv;
  174. camera.orthographic = false;
  175. camera.targetTexture = targetTexture;
  176. // Render sections of a sphere using a rectilinear projection
  177. // and resample using a sphereical projection into a single panorama
  178. // texture per eye. We break into sections in order to keep the eye
  179. // separation similar around the sphere. Rendering alternates between
  180. // top and bottom sections, sweeping horizontally around the sphere,
  181. // alternating left and right eyes.
  182. for (int v = 0; v < vTotal; v++)
  183. {
  184. var pitch = 90.0f - (v * dv) - dvHalf;
  185. var uTotal = width / targetTexture.width;
  186. var du = 360.0f / uTotal; // horizontal degrees per segment
  187. var duHalf = du / 2.0f;
  188. var vTarget = v * halfHeight / vTotal;
  189. for (int i = 0; i < 2; i++) // top, bottom
  190. {
  191. if (i == 1)
  192. {
  193. pitch = -pitch;
  194. vTarget = height - vTarget - cellSize;
  195. }
  196. for (int u = 0; u < uTotal; u++)
  197. {
  198. var yaw = -180.0f + (u * du) + duHalf;
  199. var uTarget = u * width / uTotal;
  200. var xOffset = -ipd / 2 * Mathf.Cos(pitch * Mathf.Deg2Rad);
  201. for (int j = 0; j < 2; j++) // left, right
  202. {
  203. var texture = textures[j];
  204. if (j == 1)
  205. {
  206. xOffset = -xOffset;
  207. }
  208. var offset = baseRotation * Quaternion.Euler(0, yaw, 0) * new Vector3(xOffset, 0, 0);
  209. transform.position = basePosition + offset;
  210. var direction = Quaternion.Euler(pitch, yaw, 0.0f);
  211. transform.rotation = baseRotation * direction;
  212. // vector pointing to center of this section
  213. var N = direction * Vector3.forward;
  214. // horizontal span of this section in degrees
  215. var phi0 = yaw - (du / 2);
  216. var phi1 = phi0 + du;
  217. // vertical span of this section in degrees
  218. var theta0 = pitch + (dv / 2);
  219. var theta1 = theta0 - dv;
  220. var midPhi = (phi0 + phi1) / 2;
  221. var baseTheta = Mathf.Abs(theta0) < Mathf.Abs(theta1) ? theta0 : theta1;
  222. // vectors pointing to corners of image closes to the equator
  223. var V00 = Quaternion.Euler(baseTheta, phi0, 0.0f) * Vector3.forward;
  224. var V01 = Quaternion.Euler(baseTheta, phi1, 0.0f) * Vector3.forward;
  225. // vectors pointing to top and bottom midsection of image
  226. var V0M = Quaternion.Euler(theta0, midPhi, 0.0f) * Vector3.forward;
  227. var V1M = Quaternion.Euler(theta1, midPhi, 0.0f) * Vector3.forward;
  228. // intersection points for each of the above
  229. var P00 = V00 / Vector3.Dot(V00, N);
  230. var P01 = V01 / Vector3.Dot(V01, N);
  231. var P0M = V0M / Vector3.Dot(V0M, N);
  232. var P1M = V1M / Vector3.Dot(V1M, N);
  233. // calculate basis vectors for plane
  234. var P00_P01 = P01 - P00;
  235. var P0M_P1M = P1M - P0M;
  236. var uMag = P00_P01.magnitude;
  237. var vMag = P0M_P1M.magnitude;
  238. var uScale = 1.0f / uMag;
  239. var vScale = 1.0f / vMag;
  240. var uAxis = P00_P01 * uScale;
  241. var vAxis = P0M_P1M * vScale;
  242. // update material constant buffer
  243. fx.Set(N, phi0, phi1, theta0, theta1,
  244. uAxis, P00, uScale,
  245. vAxis, P0M, vScale);
  246. camera.aspect = uMag / vMag;
  247. camera.Render();
  248. RenderTexture.active = targetTexture;
  249. texture.ReadPixels(new Rect(0, 0, targetTexture.width, targetTexture.height), uTarget, vTarget);
  250. RenderTexture.active = null;
  251. }
  252. }
  253. }
  254. }
  255. // Save textures to disk.
  256. for (int i = 0; i < 2; i++)
  257. {
  258. var texture = textures[i];
  259. texture.Apply();
  260. var assetName = string.Format(nameFormat, assetPath, target.name, i);
  261. File.WriteAllBytes(assetName, texture.EncodeToPNG());
  262. }
  263. // Cleanup.
  264. if (camera != tempCamera)
  265. {
  266. camera.targetTexture = oldTargetTexture;
  267. camera.orthographic = oldOrthographic;
  268. camera.fieldOfView = oldFieldOfView;
  269. camera.aspect = oldAspect;
  270. target.transform.localPosition = oldPosition;
  271. target.transform.localRotation = oldRotation;
  272. }
  273. else
  274. {
  275. tempCamera.targetTexture = null;
  276. }
  277. DestroyImmediate(targetTexture);
  278. DestroyImmediate(fx);
  279. timer.Stop();
  280. Debug.Log(string.Format("<b>[SteamVR]</b> Screenshot took {0} seconds.", timer.Elapsed));
  281. }
  282. if (tempCamera != null)
  283. {
  284. DestroyImmediate(tempCamera.gameObject);
  285. }
  286. DestroyImmediate(textures[0]);
  287. DestroyImmediate(textures[1]);
  288. // Now that everything has be written out, reload the associated assets and assign them.
  289. AssetDatabase.Refresh();
  290. foreach (SteamVR_Skybox target in targets)
  291. {
  292. var targetScene = target.gameObject.scene;
  293. var sceneName = Path.GetFileNameWithoutExtension(targetScene.path);
  294. var scenePath = Path.GetDirectoryName(targetScene.path);
  295. var assetPath = scenePath + "/" + sceneName;
  296. for (int i = 0; i < 2; i++)
  297. {
  298. var assetName = string.Format(nameFormat, assetPath, target.name, i);
  299. var importer = AssetImporter.GetAtPath(assetName) as TextureImporter;
  300. importer.mipmapEnabled = false;
  301. importer.wrapMode = TextureWrapMode.Repeat;
  302. #if (UNITY_5_4 || UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0)
  303. importer.SetPlatformTextureSettings("Standalone", width, TextureImporterFormat.RGB24);
  304. #else
  305. var settings = importer.GetPlatformTextureSettings("Standalone");
  306. settings.textureCompression = TextureImporterCompression.Uncompressed;
  307. settings.maxTextureSize = width;
  308. importer.SetPlatformTextureSettings(settings);
  309. #endif
  310. importer.SaveAndReimport();
  311. var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(assetName);
  312. target.SetTextureByIndex(i, texture);
  313. }
  314. }
  315. }
  316. }
  317. }
  318. }