CaptureFromCamera360ODS.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. //-----------------------------------------------------------------------------
  5. // Copyright 2012-2022 RenderHeads Ltd. All rights reserved.
  6. //-----------------------------------------------------------------------------
  7. namespace RenderHeads.Media.AVProMovieCapture
  8. {
  9. /// <summary>
  10. /// Capture to a stereo 360 view from camera in equi-rectangular format.
  11. /// Based on Google's Omni-direction Stereo paper: https://developers.google.com/vr/jump/rendering-ods-content.pdf
  12. /// </summary>
  13. [AddComponentMenu("AVPro Movie Capture/Capture From Camera 360 Stereo ODS (VR)", 101)]
  14. public class CaptureFromCamera360ODS : CaptureBase
  15. {
  16. [System.Serializable]
  17. public class Settings
  18. {
  19. [SerializeField]
  20. public Camera camera = null;
  21. [SerializeField]
  22. public CameraSelector cameraSelector = null;
  23. [SerializeField]
  24. [Tooltip("Render 180 degree equirectangular instead of 360 degrees. Also faster rendering")]
  25. public bool render180Degrees = false;
  26. [SerializeField]
  27. [Tooltip("Makes assumption that 1 Unity unit is 1m")]
  28. public float ipd = 0.064f;
  29. [SerializeField]
  30. [Tooltip("Higher value meant less slices to render, but can affect quality.")]
  31. public int pixelSliceSize = 1;
  32. [SerializeField]
  33. [Range(1, 31)]
  34. [Tooltip("May need to be increased to work with some post image effects. Value is in pixels.")]
  35. public int paddingSize = 1;
  36. [SerializeField]
  37. public CameraClearFlags cameraClearMode = CameraClearFlags.Color;
  38. [SerializeField]
  39. public Color cameraClearColor = Color.black;
  40. [SerializeField]
  41. public Behaviour[] cameraImageEffects = null;
  42. }
  43. [SerializeField] Settings _settings = new Settings();
  44. public Settings Setup
  45. {
  46. get { return _settings; }
  47. }
  48. // State
  49. private int _eyeWidth = 1920;
  50. private int _eyeHeight = 1080;
  51. private Transform _cameraGroup;
  52. private Camera _leftCameraTop;
  53. private Camera _leftCameraBot;
  54. private Camera _rightCameraTop;
  55. private Camera _rightCameraBot;
  56. private RenderTexture _final;
  57. private System.IntPtr _targetNativePointer = System.IntPtr.Zero;
  58. private Material _finalMaterial;
  59. private int _propSliceCenter;
  60. public CaptureFromCamera360ODS()
  61. {
  62. // Override the default values to match more common use cases for this capture component
  63. this.IsRealTime = false;
  64. _renderResolution = Resolution.POW2_4096x4096;
  65. /*_settings.camera = this.GetComponent<Camera>();
  66. if (_settings.camera != null)
  67. {
  68. _settings.cameraClearMode = _settings.camera.clearFlags;
  69. _settings.cameraClearColor = _settings.camera.backgroundColor;
  70. }*/
  71. }
  72. public void SetCamera(Camera camera)
  73. {
  74. _settings.camera = camera;
  75. // TODO: add support for camera chains
  76. }
  77. public override void Start()
  78. {
  79. Shader mergeShader = Shader.Find("Hidden/AVProMovieCapture/ODSMerge");
  80. if (mergeShader != null)
  81. {
  82. _finalMaterial = new Material(mergeShader);
  83. }
  84. else
  85. {
  86. Debug.LogError("[AVProMovieCapture] Can't find Hidden/AVProMovieCapture/ODSMerge shader");
  87. }
  88. _propSliceCenter = Shader.PropertyToID("_sliceCenter");
  89. base.Start();
  90. }
  91. private Camera CreateEye(Camera camera, string gameObjectName, float yRot, float xOffset, int cameraTargetHeight, int cullingMask, float fov, float aspect, int aalevel)
  92. {
  93. bool isCreated = false;
  94. if (camera == null)
  95. {
  96. GameObject eye = new GameObject(gameObjectName);
  97. eye.transform.parent = _cameraGroup;
  98. eye.transform.rotation = Quaternion.AngleAxis(-yRot, Vector3.right);
  99. eye.transform.localPosition = new Vector3(xOffset, 0f, 0f);
  100. // NOTE: We copy the hideFlags otherwise when instantiated by the Movie Capture window which has the DontSave flag
  101. // Unity throws an error about destroying transforms when coming out of play mode.
  102. eye.hideFlags = this.gameObject.hideFlags;
  103. camera = eye.AddComponent<Camera>();
  104. isCreated = true;
  105. }
  106. camera.fieldOfView = fov;
  107. camera.aspect = aspect;
  108. camera.clearFlags = _settings.cameraClearMode;
  109. camera.backgroundColor = _settings.cameraClearColor;
  110. camera.cullingMask = cullingMask;
  111. camera.useOcclusionCulling = false;
  112. camera.renderingPath = _settings.camera.renderingPath;
  113. camera.nearClipPlane = _settings.camera.nearClipPlane;
  114. camera.farClipPlane = _settings.camera.farClipPlane;
  115. #if UNITY_5_6_OR_NEWER
  116. camera.allowHDR = _settings.camera.allowHDR;
  117. camera.allowMSAA = _settings.camera.allowMSAA;
  118. if (camera.renderingPath == RenderingPath.DeferredShading
  119. #if AVPRO_MOVIECAPTURE_DEFERREDSHADING
  120. || camera.renderingPath == RenderingPath.DeferredLighting
  121. #endif
  122. )
  123. {
  124. camera.allowMSAA = false;
  125. }
  126. #endif
  127. {
  128. int textureWidth = _settings.pixelSliceSize + 2 * _settings.paddingSize;
  129. int textureHeight = cameraTargetHeight;
  130. if (camera.targetTexture != null)
  131. {
  132. camera.targetTexture.DiscardContents();
  133. if (camera.targetTexture.width != textureWidth || camera.targetTexture.height != textureHeight || camera.targetTexture.antiAliasing != aalevel)
  134. {
  135. RenderTexture.ReleaseTemporary(camera.targetTexture);
  136. camera.targetTexture = null;
  137. }
  138. }
  139. if (camera.targetTexture == null)
  140. {
  141. camera.targetTexture = RenderTexture.GetTemporary(textureWidth, textureHeight, 24, RenderTextureFormat.Default, RenderTextureReadWrite.Default, aalevel);
  142. }
  143. }
  144. // Disable the camera as we render it manually
  145. camera.enabled = false;
  146. // Copy any image effects to the new cameras
  147. if (isCreated)
  148. {
  149. // TODO: make it so the components can be added/updated each time if they have changed
  150. if (_settings.cameraImageEffects != null)
  151. {
  152. for (int i = 0; i < _settings.cameraImageEffects.Length; i++)
  153. {
  154. Behaviour origComponent = _settings.cameraImageEffects[i];
  155. if (origComponent != null)
  156. {
  157. if (origComponent.enabled)
  158. {
  159. #if UNITY_EDITOR
  160. Behaviour newComponent = (Behaviour)camera.gameObject.AddComponent(origComponent.GetType());
  161. // TODO: we need to copy any post image effect component fields here via reflection? or perhaps use prefabs?
  162. UnityEditor.EditorUtility.CopySerialized(origComponent, newComponent);
  163. // Some effects (such as MICHAËL JIMENEZ HBAO) need to be toggled to apply the settings
  164. newComponent.enabled = false;
  165. newComponent.enabled = true;
  166. #endif
  167. }
  168. }
  169. else
  170. {
  171. Debug.LogWarning("[AVProMovieCapture] Image effect is null");
  172. }
  173. }
  174. }
  175. }
  176. return camera;
  177. }
  178. public override void UpdateFrame()
  179. {
  180. if (_settings.cameraSelector != null)
  181. {
  182. if (_settings.camera != _settings.cameraSelector.Camera)
  183. {
  184. SetCamera(_settings.cameraSelector.Camera);
  185. }
  186. }
  187. if (_useWaitForEndOfFrame)
  188. {
  189. if (_capturing && !_paused)
  190. {
  191. StartCoroutine(FinalRenderCapture());
  192. }
  193. }
  194. else
  195. {
  196. Capture();
  197. }
  198. base.UpdateFrame();
  199. }
  200. private IEnumerator FinalRenderCapture()
  201. {
  202. yield return _waitForEndOfFrame;
  203. Capture();
  204. }
  205. private void Capture()
  206. {
  207. TickFrameTimer();
  208. AccumulateMotionBlur();
  209. if (_capturing && !_paused)
  210. {
  211. if (_settings.camera != null && _handle >= 0)
  212. {
  213. bool canGrab = true;
  214. if (IsUsingMotionBlur())
  215. {
  216. // If the motion blur is still accumulating, don't grab this frame
  217. canGrab = _motionBlur.IsFrameAccumulated;
  218. }
  219. if (canGrab && CanOutputFrame())
  220. {
  221. // Frame to encode either comes from rendering, or motion blur accumulation
  222. RenderTexture finalTexture = null;
  223. if (!IsUsingMotionBlur())
  224. {
  225. RenderFrame();
  226. finalTexture = _final;
  227. }
  228. else
  229. {
  230. finalTexture = _motionBlur.FinalTexture;
  231. }
  232. // Side-by-side transparency
  233. RenderTexture newSourceTexture = UpdateForSideBySideTransparency( finalTexture );
  234. if (newSourceTexture)
  235. {
  236. finalTexture = newSourceTexture;
  237. }
  238. if (_targetNativePointer == System.IntPtr.Zero || _supportTextureRecreate)
  239. {
  240. // NOTE: If support for captures to survive through alt-tab events, or window resizes where the GPU resources are recreated
  241. // is required, then this line is needed. It is very expensive though as it does a sync with the rendering thread.
  242. _targetNativePointer = finalTexture.GetNativeTexturePtr();
  243. }
  244. NativePlugin.SetTexturePointer(_handle, _targetNativePointer);
  245. RenderThreadEvent(NativePlugin.PluginEvent.CaptureFrameBuffer);
  246. GL.InvalidateState();
  247. UpdateFPS();
  248. }
  249. }
  250. }
  251. RenormTimer();
  252. }
  253. private void AccumulateMotionBlur()
  254. {
  255. if (_motionBlur != null)
  256. {
  257. if (_capturing && !_paused)
  258. {
  259. if (_settings.camera != null && _handle >= 0)
  260. {
  261. RenderFrame();
  262. _motionBlur.Accumulate(_final);
  263. }
  264. }
  265. }
  266. }
  267. private void RenderFrame()
  268. {
  269. _cameraGroup.position = _settings.camera.transform.position;
  270. Quaternion originalRot = _settings.camera.transform.rotation * Quaternion.AngleAxis(180f, Vector3.up); // Apply 180 degree correction align the front camera vector to the center of the output image
  271. float rotationStep = 360f / _eyeWidth;
  272. int startPixelX = 0;
  273. int maxPixelX = _eyeWidth / _settings.pixelSliceSize;
  274. int endPixelX = maxPixelX;
  275. if (_settings.render180Degrees)
  276. {
  277. // Remove the first and last quarter of the rendering
  278. startPixelX = endPixelX / 4;
  279. endPixelX -= startPixelX;
  280. // Add a few pixels back in case of bilinear filtering issues
  281. startPixelX = Mathf.Max(0, startPixelX - 2);
  282. endPixelX = Mathf.Min(maxPixelX, endPixelX + 2);
  283. }
  284. _final.DiscardContents();
  285. for (int i = startPixelX; i < endPixelX; ++i)
  286. {
  287. int step = i * _settings.pixelSliceSize;
  288. float v = step * rotationStep;
  289. _cameraGroup.rotation = originalRot * Quaternion.AngleAxis(v, Vector3.up);
  290. _leftCameraTop.targetTexture.DiscardContents();
  291. _leftCameraBot.targetTexture.DiscardContents();
  292. _rightCameraTop.targetTexture.DiscardContents();
  293. _rightCameraBot.targetTexture.DiscardContents();
  294. _leftCameraTop.Render();
  295. _leftCameraBot.Render();
  296. _rightCameraTop.Render();
  297. _rightCameraBot.Render();
  298. _finalMaterial.SetFloat(_propSliceCenter, step + _settings.pixelSliceSize / 2f);
  299. Graphics.Blit(null, _final, _finalMaterial);
  300. //System.GC.Collect();
  301. }
  302. }
  303. public override Texture GetPreviewTexture()
  304. {
  305. if (IsUsingMotionBlur())
  306. {
  307. return _motionBlur.FinalTexture;
  308. }
  309. return _final;
  310. }
  311. public override bool PrepareCapture()
  312. {
  313. if (_capturing)
  314. {
  315. return false;
  316. }
  317. #if UNITY_EDITOR_WIN || (!UNITY_EDITOR && UNITY_STANDALONE_WIN)
  318. if (SystemInfo.graphicsDeviceVersion.StartsWith("Direct3D 9"))
  319. {
  320. Debug.LogError("[AVProMovieCapture] Direct3D9 not yet supported, please use Direct3D11 instead.");
  321. return false;
  322. }
  323. else if (SystemInfo.graphicsDeviceVersion.StartsWith("OpenGL") && !SystemInfo.graphicsDeviceVersion.Contains("emulated"))
  324. {
  325. Debug.LogError("[AVProMovieCapture] OpenGL not yet supported for CaptureFromCamera360ODS component, please use Direct3D11 instead. You may need to switch your build platform to Windows.");
  326. return false;
  327. }
  328. #endif
  329. // This component makes the profiler use a TON of memory, so warn the user to disable it
  330. if (UnityEngine.Profiling.Profiler.enabled)
  331. {
  332. Debug.LogWarning("[AVProMovieCapture] Having the Unity profiler enabled while using the CaptureFromCamera360ODS component is not recommended. Too many samples are generated which can make the system run out of memory. Disable the profiler, close the window and remove the tab. A Unity restart may be required after disabling the profiler recording");
  333. }
  334. // Setup material
  335. _pixelFormat = NativePlugin.PixelFormat.RGBA32;
  336. _isTopDown = true;
  337. if (_settings.cameraSelector != null)
  338. {
  339. if (_settings.camera != _settings.cameraSelector.Camera)
  340. {
  341. SetCamera(_settings.cameraSelector.Camera);
  342. }
  343. }
  344. if (_settings.camera == null)
  345. {
  346. SetCamera(this.GetComponent<Camera>());
  347. }
  348. if (_settings.camera == null)
  349. {
  350. Debug.LogError("[AVProMovieCapture] No camera assigned to CaptureFromCamera360ODS");
  351. return false;
  352. }
  353. // Resolution
  354. int finalWidth = Mathf.FloorToInt(_settings.camera.pixelRect.width);
  355. int finalHeight = Mathf.FloorToInt(_settings.camera.pixelRect.height);
  356. if (_renderResolution == Resolution.Custom)
  357. {
  358. finalWidth = (int)_renderSize.x;
  359. finalHeight = (int)_renderSize.y;
  360. }
  361. else if (_renderResolution != Resolution.Original)
  362. {
  363. GetResolution(_renderResolution, ref finalWidth, ref finalHeight);
  364. }
  365. _eyeWidth = Mathf.Clamp(finalWidth, 1, 8192);
  366. // NOTE: Height must be even
  367. _eyeHeight = Mathf.Clamp(finalHeight / 2, 1, 4096);
  368. _eyeHeight -= _eyeHeight & 1;
  369. finalWidth = _eyeWidth;
  370. finalHeight = _eyeHeight * 2;
  371. int aaLevel = GetCameraAntiAliasingLevel(_settings.camera);
  372. // NOTE: Pixel slice size must be divisible by the total width
  373. _settings.pixelSliceSize = Mathf.Clamp(_settings.pixelSliceSize, 1, _eyeWidth);
  374. _settings.pixelSliceSize = _settings.pixelSliceSize - (_eyeWidth % _settings.pixelSliceSize);
  375. _settings.paddingSize = Mathf.Clamp(_settings.paddingSize, 0, 31);
  376. float offset = _settings.ipd / 2f;
  377. float aspect = (_settings.pixelSliceSize * 2f) / _eyeHeight;
  378. if (_cameraGroup == null)
  379. {
  380. GameObject go = new GameObject("OdsCameraGroup");
  381. go.transform.parent = this.gameObject.transform;
  382. // NOTE: We copy the hideFlags otherwise when instantiated by the Movie Capture window which has the DontSave flag
  383. // Unity throws an error about destroying transforms when coming out of play mode.
  384. go.hideFlags = this.gameObject.hideFlags;
  385. _cameraGroup = go.transform;
  386. }
  387. // TODO: only recreate textures if they don't already exist or size has changed
  388. _leftCameraTop = CreateEye(_leftCameraTop, "LeftEyeTop", 45f, -offset, _eyeHeight / 2, _settings.camera.cullingMask, 90f, aspect, aaLevel);
  389. _leftCameraBot = CreateEye(_leftCameraBot, "LeftEyeBot", -45f, -offset, _eyeHeight / 2, _settings.camera.cullingMask, 90f, aspect, aaLevel);
  390. _rightCameraTop = CreateEye(_rightCameraTop, "RightEyeTop", 45f, offset, _eyeHeight / 2, _settings.camera.cullingMask, 90f, aspect, aaLevel);
  391. _rightCameraBot = CreateEye(_rightCameraBot, "RightEyeBot", -45f, offset, _eyeHeight / 2, _settings.camera.cullingMask, 90f, aspect, aaLevel);
  392. // Create final texture (if not already created)
  393. _targetNativePointer = System.IntPtr.Zero;
  394. if (_final != null)
  395. {
  396. _final.DiscardContents();
  397. if (_final.width != finalWidth || _final.height != finalHeight || _final.antiAliasing != aaLevel)
  398. {
  399. RenderTexture.ReleaseTemporary(_final);
  400. _final = null;
  401. }
  402. _final = null;
  403. }
  404. if (_final == null)
  405. {
  406. _final = RenderTexture.GetTemporary(finalWidth, finalHeight, 0);
  407. }
  408. // Setup material
  409. _finalMaterial.SetTexture("_leftTopTex", _leftCameraTop.targetTexture);
  410. _finalMaterial.SetTexture("_leftBotTex", _leftCameraBot.targetTexture);
  411. _finalMaterial.SetTexture("_rightTopTex", _rightCameraTop.targetTexture);
  412. _finalMaterial.SetTexture("_rightBotTex", _rightCameraBot.targetTexture);
  413. _finalMaterial.SetFloat("_pixelSliceSize", _settings.pixelSliceSize);
  414. _finalMaterial.SetInt("_paddingSize", _settings.paddingSize);
  415. _finalMaterial.SetFloat("_targetXTexelSize", 1.0f / finalWidth);
  416. if (_settings.render180Degrees)
  417. {
  418. _finalMaterial.DisableKeyword("LAYOUT_EQUIRECT360");
  419. _finalMaterial.EnableKeyword("LAYOUT_EQUIRECT180");
  420. }
  421. else
  422. {
  423. _finalMaterial.DisableKeyword("LAYOUT_EQUIRECT180");
  424. _finalMaterial.EnableKeyword("LAYOUT_EQUIRECT360");
  425. }
  426. _Transparency = Transparency.None;
  427. if ( !NativePlugin.IsBasicEdition() )
  428. {
  429. if( (_outputTarget == OutputTarget.VideoFile || _outputTarget == OutputTarget.NamedPipe) && GetEncoderHints().videoHints.transparency != Transparency.None )
  430. {
  431. _Transparency = GetEncoderHints().videoHints.transparency;
  432. }
  433. else if( _outputTarget == OutputTarget.ImageSequence && GetEncoderHints().imageHints.transparency != Transparency.None )
  434. {
  435. _Transparency = GetEncoderHints().imageHints.transparency;
  436. }
  437. //
  438. if( _Transparency == Transparency.TopBottom || _Transparency == Transparency.LeftRight )
  439. {
  440. InitialiseSideBySideTransparency( finalWidth, finalHeight );
  441. }
  442. // Cameras might need transparency
  443. if( _Transparency != Transparency.None )
  444. {
  445. _leftCameraTop.backgroundColor = new Color(0f, 0f, 0f, 0f);
  446. _leftCameraBot.backgroundColor = new Color(0f, 0f, 0f, 0f);
  447. _rightCameraTop.backgroundColor = new Color(0f, 0f, 0f, 0f);
  448. _rightCameraBot.backgroundColor = new Color(0f, 0f, 0f, 0f);
  449. }
  450. switch ( _Transparency )
  451. {
  452. case Transparency.TopBottom: finalHeight *= 2; break;
  453. case Transparency.LeftRight: finalWidth *= 2; break;
  454. }
  455. }
  456. // Setup capture
  457. SelectRecordingResolution(finalWidth, finalHeight);
  458. GenerateFilename();
  459. if (base.PrepareCapture())
  460. {
  461. UpdateInjectionOptions(StereoPacking.TopBottom, _settings.render180Degrees?SphericalVideoLayout.Equirectangular180:SphericalVideoLayout.Equirectangular360);
  462. return true;
  463. }
  464. return false;
  465. }
  466. private static void DestroyEye(Camera camera)
  467. {
  468. if (camera != null)
  469. {
  470. RenderTexture.ReleaseTemporary(camera.targetTexture);
  471. if (Application.isPlaying)
  472. {
  473. GameObject.Destroy(camera.gameObject);
  474. }
  475. #if UNITY_EDITOR
  476. else
  477. {
  478. GameObject.DestroyImmediate(camera.gameObject);
  479. }
  480. #endif
  481. }
  482. }
  483. public override void OnDestroy()
  484. {
  485. _targetNativePointer = System.IntPtr.Zero;
  486. if (_final != null)
  487. {
  488. RenderTexture.ReleaseTemporary(_final);
  489. _final = null;
  490. }
  491. DestroyEye(_leftCameraTop);
  492. DestroyEye(_leftCameraBot);
  493. DestroyEye(_rightCameraTop);
  494. DestroyEye(_rightCameraBot);
  495. _leftCameraTop = null;
  496. _leftCameraBot = null;
  497. _rightCameraTop = null;
  498. _rightCameraBot = null;
  499. if (_cameraGroup != null)
  500. {
  501. if (Application.isPlaying)
  502. {
  503. GameObject.Destroy(_cameraGroup.gameObject);
  504. }
  505. #if UNITY_EDITOR
  506. else
  507. {
  508. GameObject.DestroyImmediate(_cameraGroup.gameObject);
  509. }
  510. #endif
  511. _cameraGroup = null;
  512. }
  513. if (_finalMaterial)
  514. {
  515. Destroy(_finalMaterial);
  516. _finalMaterial = null;
  517. }
  518. base.OnDestroy();
  519. }
  520. }
  521. }