IMHOTEP Framework
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Pages
SteamVR_SkyboxEditor.cs
1 //======= Copyright (c) Valve Corporation, All rights reserved. ===============
2 //
3 // Purpose: Custom inspector display for SteamVR_Skybox
4 //
5 //=============================================================================
6 
7 using UnityEngine;
8 using UnityEditor;
9 using System.Text;
10 using System.Collections.Generic;
11 using Valve.VR;
12 using System.IO;
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 
32  public override void OnInspectorGUI()
33  {
34  DrawDefaultInspector();
35 
36  EditorGUILayout.HelpBox(helpText, MessageType.Info);
37 
38  if (GUILayout.Button("Take snapshot"))
39  {
40  var directions = new Quaternion[] {
41  Quaternion.LookRotation(Vector3.forward),
42  Quaternion.LookRotation(Vector3.back),
43  Quaternion.LookRotation(Vector3.left),
44  Quaternion.LookRotation(Vector3.right),
45  Quaternion.LookRotation(Vector3.up, Vector3.back),
46  Quaternion.LookRotation(Vector3.down, Vector3.forward)
47  };
48 
49  Camera tempCamera = null;
50  foreach (SteamVR_Skybox target in targets)
51  {
52  var targetScene = target.gameObject.scene;
53  var sceneName = Path.GetFileNameWithoutExtension(targetScene.path);
54  var scenePath = Path.GetDirectoryName(targetScene.path);
55  var assetPath = scenePath + "/" + sceneName;
56  if (!AssetDatabase.IsValidFolder(assetPath))
57  {
58  var guid = AssetDatabase.CreateFolder(scenePath, sceneName);
59  assetPath = AssetDatabase.GUIDToAssetPath(guid);
60  }
61 
62  var camera = target.GetComponent<Camera>();
63  if (camera == null)
64  {
65  if (tempCamera == null)
66  tempCamera = new GameObject().AddComponent<Camera>();
67  camera = tempCamera;
68  }
69 
70  var targetTexture = camera.targetTexture;
71  if (camera.targetTexture == null)
72  {
73  targetTexture = new RenderTexture(1024, 1024, 24);
74  targetTexture.antiAliasing = 8;
75  camera.targetTexture = targetTexture;
76  }
77 
78  var oldPosition = target.transform.localPosition;
79  var oldRotation = target.transform.localRotation;
80  var baseRotation = target.transform.rotation;
81 
82  var t = camera.transform;
83  t.position = target.transform.position;
84  camera.orthographic = false;
85  camera.fieldOfView = 90;
86 
87  for (int i = 0; i < directions.Length; i++)
88  {
89  t.rotation = baseRotation * directions[i];
90  camera.Render();
91 
92  // Copy to texture and save to disk.
93  RenderTexture.active = targetTexture;
94  var texture = new Texture2D(targetTexture.width, targetTexture.height, TextureFormat.ARGB32, false);
95  texture.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0);
96  texture.Apply();
97  RenderTexture.active = null;
98 
99  var assetName = string.Format(nameFormat, assetPath, target.name, i);
100  System.IO.File.WriteAllBytes(assetName, texture.EncodeToPNG());
101  }
102 
103  if (camera != tempCamera)
104  {
105  target.transform.localPosition = oldPosition;
106  target.transform.localRotation = oldRotation;
107  }
108  }
109 
110  if (tempCamera != null)
111  {
112  Object.DestroyImmediate(tempCamera.gameObject);
113  }
114 
115  // Now that everything has be written out, reload the associated assets and assign them.
116  AssetDatabase.Refresh();
117  foreach (SteamVR_Skybox target in targets)
118  {
119  var targetScene = target.gameObject.scene;
120  var sceneName = Path.GetFileNameWithoutExtension(targetScene.path);
121  var scenePath = Path.GetDirectoryName(targetScene.path);
122  var assetPath = scenePath + "/" + sceneName;
123 
124  for (int i = 0; i < directions.Length; i++)
125  {
126  var assetName = string.Format(nameFormat, assetPath, target.name, i);
127  var importer = AssetImporter.GetAtPath(assetName) as TextureImporter;
128 #if (UNITY_5_4 || UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0)
129  importer.textureFormat = TextureImporterFormat.RGB24;
130 #else
131  importer.textureCompression = TextureImporterCompression.Uncompressed;
132 #endif
133  importer.wrapMode = TextureWrapMode.Clamp;
134  importer.mipmapEnabled = false;
135  importer.SaveAndReimport();
136 
137  var texture = AssetDatabase.LoadAssetAtPath<Texture>(assetName);
138  target.SetTextureByIndex(i, texture);
139  }
140  }
141  }
142  else if (GUILayout.Button("Take stereo snapshot"))
143  {
144  const int width = 4096;
145  const int height = width / 2;
146  const int halfHeight = height / 2;
147 
148  var textures = new Texture2D[] {
149  new Texture2D(width, height, TextureFormat.ARGB32, false),
150  new Texture2D(width, height, TextureFormat.ARGB32, false) };
151 
152  var timer = new System.Diagnostics.Stopwatch();
153 
154  Camera tempCamera = null;
155  foreach (SteamVR_Skybox target in targets)
156  {
157  timer.Start();
158 
159  var targetScene = target.gameObject.scene;
160  var sceneName = Path.GetFileNameWithoutExtension(targetScene.path);
161  var scenePath = Path.GetDirectoryName(targetScene.path);
162  var assetPath = scenePath + "/" + sceneName;
163  if (!AssetDatabase.IsValidFolder(assetPath))
164  {
165  var guid = AssetDatabase.CreateFolder(scenePath, sceneName);
166  assetPath = AssetDatabase.GUIDToAssetPath(guid);
167  }
168 
169  var camera = target.GetComponent<Camera>();
170  if (camera == null)
171  {
172  if (tempCamera == null)
173  tempCamera = new GameObject().AddComponent<Camera>();
174  camera = tempCamera;
175  }
176 
177  var fx = camera.gameObject.AddComponent<SteamVR_SphericalProjection>();
178 
179  var oldTargetTexture = camera.targetTexture;
180  var oldOrthographic = camera.orthographic;
181  var oldFieldOfView = camera.fieldOfView;
182  var oldAspect = camera.aspect;
183 
184  var oldPosition = target.transform.localPosition;
185  var oldRotation = target.transform.localRotation;
186  var basePosition = target.transform.position;
187  var baseRotation = target.transform.rotation;
188 
189  var transform = camera.transform;
190 
191  int cellSize = int.Parse(target.StereoCellSize.ToString().Substring(1));
192  float ipd = target.StereoIpdMm / 1000.0f;
193  int vTotal = halfHeight / cellSize;
194  float dv = 90.0f / vTotal; // vertical degrees per segment
195  float dvHalf = dv / 2.0f;
196 
197  var targetTexture = new RenderTexture(cellSize, cellSize, 24);
198  targetTexture.wrapMode = TextureWrapMode.Clamp;
199  targetTexture.antiAliasing = 8;
200 
201  camera.fieldOfView = dv;
202  camera.orthographic = false;
203  camera.targetTexture = targetTexture;
204 
205  // Render sections of a sphere using a rectilinear projection
206  // and resample using a sphereical projection into a single panorama
207  // texture per eye. We break into sections in order to keep the eye
208  // separation similar around the sphere. Rendering alternates between
209  // top and bottom sections, sweeping horizontally around the sphere,
210  // alternating left and right eyes.
211  for (int v = 0; v < vTotal; v++)
212  {
213  var pitch = 90.0f - (v * dv) - dvHalf;
214  var uTotal = width / targetTexture.width;
215  var du = 360.0f / uTotal; // horizontal degrees per segment
216  var duHalf = du / 2.0f;
217 
218  var vTarget = v * halfHeight / vTotal;
219 
220  for (int i = 0; i < 2; i++) // top, bottom
221  {
222  if (i == 1)
223  {
224  pitch = -pitch;
225  vTarget = height - vTarget - cellSize;
226  }
227 
228  for (int u = 0; u < uTotal; u++)
229  {
230  var yaw = -180.0f + (u * du) + duHalf;
231 
232  var uTarget = u * width / uTotal;
233 
234  var xOffset = -ipd / 2 * Mathf.Cos(pitch * Mathf.Deg2Rad);
235 
236  for (int j = 0; j < 2; j++) // left, right
237  {
238  var texture = textures[j];
239 
240  if (j == 1)
241  {
242  xOffset = -xOffset;
243  }
244 
245  var offset = baseRotation * Quaternion.Euler(0, yaw, 0) * new Vector3(xOffset, 0, 0);
246  transform.position = basePosition + offset;
247 
248  var direction = Quaternion.Euler(pitch, yaw, 0.0f);
249  transform.rotation = baseRotation * direction;
250 
251  // vector pointing to center of this section
252  var N = direction * Vector3.forward;
253 
254  // horizontal span of this section in degrees
255  var phi0 = yaw - (du / 2);
256  var phi1 = phi0 + du;
257 
258  // vertical span of this section in degrees
259  var theta0 = pitch + (dv / 2);
260  var theta1 = theta0 - dv;
261 
262  var midPhi = (phi0 + phi1) / 2;
263  var baseTheta = Mathf.Abs(theta0) < Mathf.Abs(theta1) ? theta0 : theta1;
264 
265  // vectors pointing to corners of image closes to the equator
266  var V00 = Quaternion.Euler(baseTheta, phi0, 0.0f) * Vector3.forward;
267  var V01 = Quaternion.Euler(baseTheta, phi1, 0.0f) * Vector3.forward;
268 
269  // vectors pointing to top and bottom midsection of image
270  var V0M = Quaternion.Euler(theta0, midPhi, 0.0f) * Vector3.forward;
271  var V1M = Quaternion.Euler(theta1, midPhi, 0.0f) * Vector3.forward;
272 
273  // intersection points for each of the above
274  var P00 = V00 / Vector3.Dot(V00, N);
275  var P01 = V01 / Vector3.Dot(V01, N);
276  var P0M = V0M / Vector3.Dot(V0M, N);
277  var P1M = V1M / Vector3.Dot(V1M, N);
278 
279  // calculate basis vectors for plane
280  var P00_P01 = P01 - P00;
281  var P0M_P1M = P1M - P0M;
282 
283  var uMag = P00_P01.magnitude;
284  var vMag = P0M_P1M.magnitude;
285 
286  var uScale = 1.0f / uMag;
287  var vScale = 1.0f / vMag;
288 
289  var uAxis = P00_P01 * uScale;
290  var vAxis = P0M_P1M * vScale;
291 
292  // update material constant buffer
293  fx.Set(N, phi0, phi1, theta0, theta1,
294  uAxis, P00, uScale,
295  vAxis, P0M, vScale);
296 
297  camera.aspect = uMag / vMag;
298  camera.Render();
299 
300  RenderTexture.active = targetTexture;
301  texture.ReadPixels(new Rect(0, 0, targetTexture.width, targetTexture.height), uTarget, vTarget);
302  RenderTexture.active = null;
303  }
304  }
305  }
306  }
307 
308  // Save textures to disk.
309  for (int i = 0; i < 2; i++)
310  {
311  var texture = textures[i];
312 
313  texture.Apply();
314  var assetName = string.Format(nameFormat, assetPath, target.name, i);
315  File.WriteAllBytes(assetName, texture.EncodeToPNG());
316  }
317 
318  // Cleanup.
319  if (camera != tempCamera)
320  {
321  camera.targetTexture = oldTargetTexture;
322  camera.orthographic = oldOrthographic;
323  camera.fieldOfView = oldFieldOfView;
324  camera.aspect = oldAspect;
325 
326  target.transform.localPosition = oldPosition;
327  target.transform.localRotation = oldRotation;
328  }
329  else
330  {
331  tempCamera.targetTexture = null;
332  }
333 
334  DestroyImmediate(targetTexture);
335  DestroyImmediate(fx);
336 
337  timer.Stop();
338  Debug.Log(string.Format("Screenshot took {0} seconds.", timer.Elapsed));
339  }
340 
341  if (tempCamera != null)
342  {
343  DestroyImmediate(tempCamera.gameObject);
344  }
345 
346  DestroyImmediate(textures[0]);
347  DestroyImmediate(textures[1]);
348 
349  // Now that everything has be written out, reload the associated assets and assign them.
350  AssetDatabase.Refresh();
351  foreach (SteamVR_Skybox target in targets)
352  {
353  var targetScene = target.gameObject.scene;
354  var sceneName = Path.GetFileNameWithoutExtension(targetScene.path);
355  var scenePath = Path.GetDirectoryName(targetScene.path);
356  var assetPath = scenePath + "/" + sceneName;
357 
358  for (int i = 0; i < 2; i++)
359  {
360  var assetName = string.Format(nameFormat, assetPath, target.name, i);
361  var importer = AssetImporter.GetAtPath(assetName) as TextureImporter;
362  importer.mipmapEnabled = false;
363  importer.wrapMode = TextureWrapMode.Repeat;
364 #if (UNITY_5_4 || UNITY_5_3 || UNITY_5_2 || UNITY_5_1 || UNITY_5_0)
365  importer.SetPlatformTextureSettings("Standalone", width, TextureImporterFormat.RGB24);
366 #else
367  var settings = importer.GetPlatformTextureSettings("Standalone");
368  settings.textureCompression = TextureImporterCompression.Uncompressed;
369  settings.maxTextureSize = width;
370  importer.SetPlatformTextureSettings(settings);
371 #endif
372  importer.SaveAndReimport();
373 
374  var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(assetName);
375  target.SetTextureByIndex(i, texture);
376  }
377  }
378  }
379  }
380 }
381