Unity SDK Docs 1.5.0-beta.6
Loading...
Searching...
No Matches
SplitStereoCamera.cs
1/*
2 * Copyright (C) 2020-2023 Tilt Five, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17using System;
18using System.Collections;
19using System.Collections.Generic;
20using UnityEngine;
22
23#if TILT_FIVE_SRP
24using UnityEngine.Rendering;
25#endif
26
27using AREyes = TiltFive.Glasses.AREyes;
28
29namespace TiltFive
30{
31
32 [System.Serializable]
33 [System.Obsolete("DisplaySettings is deprecated because different glasses may have different framebuffer dimensions. " +
34 "Please use Glasses.TryGetDisplayInfo() instead.")]
35 public class DisplaySettings
36 {
37 private static DisplaySettings instance;
38 private static DisplaySettings Instance
39 {
40 get
41 {
42 if(instance == null)
43 {
44 instance = new DisplaySettings();
45 }
46 return instance;
47 }
48 set => instance = value;
49 }
50
51 private DisplaySettings()
52 {
53 if(!Display.GetDisplayDimensions(ref defaultDimensions))
54 {
55 Log.Warn("Could not retrieve display settings from the plugin.");
56 }
57 }
58
60 public static int monoWidth => Instance.defaultDimensions.x;
62 public static int stereoWidth => monoWidth * 2;
64 public static int height => Instance.defaultDimensions.y;
66 public static float monoWidthToHeightRatio => (float) monoWidth / height;
68 public static float stereoWidthToHeightRatio => (float) stereoWidth / height;
70 public const int depthBuffer = 24;
71
72 // Provide a texture format compatible with the glasses.
73 public const RenderTextureFormat nativeTextureFormat = RenderTextureFormat.ARGB32;
74
75 // Provide a hardcoded default resolution if the plugin is somehow unavailable.
76 private readonly Vector2Int defaultDimensions = new Vector2Int(1216, 768);
77 }
78
79 [RequireComponent(typeof(Camera))]
80 public partial class SplitStereoCamera : MonoBehaviour
81 {
82 [HideInInspector]
83 internal GlassesHandle glassesHandle;
84
85 internal SpectatorSettings spectatorSettings = null;
86 private Camera spectatorCamera => spectatorSettings?.spectatorCamera;
87
88 internal GlassesSettings glassesSettings = null;
89
90 private Glasses.DisplayInfo displayInfo;
91
93 public Camera cameraTemplate => glassesSettings?.cameraTemplate;
95 public GameObject headPose = null;
96 private bool useSpectatorCamera;
97 internal bool UseSpectatorCamera
98 {
99 get => useSpectatorCamera;
100 set
101 {
102 if (useSpectatorCamera != value)
103 {
104 // If we set a new value here, force the letterboxing/pillarboxing to be redrawn,
105 // since this is an implicit mirror mode transition.
106 clearsForMirroring = 0;
107 }
108 useSpectatorCamera = value;
109 }
110 }
111
112 // When the onscreen preview format changes, we need to clear the screen backbuffer in case
113 // the aspect ratio means the whole screen won't be used, and the number of times we need
114 // to do this is dependent on the graphics API and its configuration.
115 private uint requiredClearsForMirroring = 1;
116 private uint clearsForMirroring = 0;
117
118 private IEnumerator presentStereoImagesCoroutine;
119
121 private const string LEFT_EYE_CAMERA_NAME = "Left Eye Camera";
123 private GameObject leftEye;
125 public Camera leftEyeCamera { get { return eyeCameras[AREyes.EYE_LEFT]; } }
126
128 private const string RIGHT_EYE_CAMERA_NAME = "Right Eye Camera";
130 private GameObject rightEye;
132 public Camera rightEyeCamera { get { return eyeCameras[AREyes.EYE_RIGHT]; } }
133
135 public bool showCameras = true;
137 private Dictionary<AREyes, Camera> eyeCameras = new Dictionary<AREyes, Camera>()
138 {
139 { AREyes.EYE_LEFT, null },
140 { AREyes.EYE_RIGHT, null }
141 };
142
147 [Obsolete("SplitStereoCamera.posUGBD_UWRLD is deprecated.")]
148 public Vector3 posUGBD_UWRLD { get => posUSTAGE_UWRLD; set => posUSTAGE_UWRLD = value; }
149
154 [Obsolete("SplitStereoCamera.rotToUGBD_UWRLD is deprecated.")]
155 public Quaternion rotToUGBD_UWRLD { get => rotToUSTAGE_UWRLD; set => rotToUSTAGE_UWRLD = value;}
156
161 [Obsolete("SplitStereoCamera.scaleToUGBD_UWRLD is deprecated.")]
162 public float scaleToUGBD_UWRLD { get => scaleToUSTAGE_UWRLD; set => scaleToUSTAGE_UWRLD = value; }
163
164 private Vector3 posUSTAGE_UWRLD = Vector3.zero;
165 private Quaternion rotToUSTAGE_UWRLD = Quaternion.identity;
166 private float scaleToUSTAGE_UWRLD = 1.0f;
167
168 internal void UpdateStageSpaceData(Pose gameboardPose_UWRLD, float scaleToUSTAGE_UWRLD)
169 {
170 this.posUSTAGE_UWRLD = gameboardPose_UWRLD.position;
171 this.rotToUSTAGE_UWRLD = gameboardPose_UWRLD.rotation;
172 this.scaleToUSTAGE_UWRLD = scaleToUSTAGE_UWRLD;
173 }
174
176 private const string SHADER_DISPLAY_BLIT = "Tilt Five/Simple Blend Shader";
178 private Material displayBlitShader;
179
180 private GlassesMirrorMode glassesMirrorMode => spectatorSettings.glassesMirrorMode;
181 private GlassesMirrorMode previousMirrorMode = GlassesMirrorMode.None;
182 private SplitStereoTextures splitStereoTextures = new SplitStereoTextures();
183
184#if TILT_FIVE_SRP
185 private CommandBuffer commandBuffer;
186#endif
187
189 [System.Obsolete("fieldOfView is deprecated, please use GlassesSettings' fieldOfView instead.")]
190 public float fieldOfView
191 {
192 get { return spectatorCamera.fieldOfView; }
193 set { rightEyeCamera.fieldOfView = leftEyeCamera.fieldOfView = spectatorCamera.fieldOfView = value; }
194 }
195
197 [System.Obsolete("nearClipPlane is deprecated, please use GlassesSettings' nearClipPlane instead.")]
198 public float nearClipPlane
199 {
200 get { return spectatorCamera.nearClipPlane; }
201 set { rightEyeCamera.nearClipPlane = leftEyeCamera.nearClipPlane = spectatorCamera.nearClipPlane = value; }
202 }
203
205 [System.Obsolete("farClipPlane is deprecated, please use GlassesSettings' farClipPlane instead.")]
206 public float farClipPlane
207 {
208 get { return spectatorCamera.farClipPlane; }
209 set { rightEyeCamera.farClipPlane = leftEyeCamera.farClipPlane = spectatorCamera.farClipPlane = value; }
210 }
211
213 [System.Obsolete("aspectRatio is deprecated, please use Glasses.TryGetDisplayInfo().monoWidthToHeightRatio instead.")]
214 public float aspectRatio
215 {
216 get { return spectatorCamera.aspect; }
217 set { spectatorCamera.aspect = value; }
218 }
219
223 private void Awake()
224 {
225 enabled = false;
226 useSpectatorCamera = false;
227 clearsForMirroring = 0;
228
229 if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Vulkan) {
230 // Unlike other graphics APIs, Vulkan does not have a "default framebuffer" concept.
231 // Unfortunately, Unity only sort of hides that it is doing this, so when calling
232 // GL.Clear, it only affects the current buffer, making it necessary to do multiple
233 // calls in a row to ensure that all of the swapchains have received a clear. It is
234 // typical to do triple buffering with Vulkan to provide the best framerate,
235 // especially on more resource-constrained mobile devices. For this reason, Unity
236 // defaults to setting the number of swapchain buffers to 3. This can be changed to
237 // 2 under Project Settings -> Player -> Vulkan Settings -> Number of swapchain
238 // buffers, but this is not recommended because it will generally be worse for
239 // performance.
240 //
241 // Having 3 here will still work even if you reduce the number of buffers, making
242 // this a pretty safe default. If you increase the number of swapchain buffers, this
243 // value would need to be increased, but you really don't want do do that anyway.
244 requiredClearsForMirroring = 3;
245 }
246 }
247
248 public void Initialize(
249 GameObject headPoseRoot,
250 GlassesSettings glassesSettings,
251 SpectatorSettings spectatorSettings,
252 Glasses.DisplayInfo displayInfo)
253 {
254 if(headPoseRoot == null || spectatorSettings == null)
255 {
256 Log.Error("Arguments cannot be null");
257 return;
258 }
259
260 headPose = headPoseRoot;
261 this.glassesSettings = glassesSettings;
262 this.spectatorSettings = spectatorSettings;
263 this.displayInfo = displayInfo;
264
265#if TILT_FIVE_SRP
266 commandBuffer = new CommandBuffer() { name = "Onscreen Preview" };
267#endif
268
269 // For this mode, we need the headPose Camera to be enabled, as it is the
270 // primary Camera for blitting to the backbuffer.
271 if(spectatorCamera != null)
272 {
273 spectatorCamera.enabled = true;
274 }
275
276 if(cameraTemplate != null)
277 {
278 InstantiateEyeCameras(out leftEye, out rightEye);
279 }
280 else
281 {
282 GenerateEyeCameras(out leftEye, out rightEye);
283 }
284 ConfigureEyeCameras();
285
286 // Load the blitting shader to copy the the left & right render textures
287 // into the backbuffer
288 displayBlitShader = new Material(Shader.Find(SHADER_DISPLAY_BLIT));
289 // Did we find it?
290 if (null == displayBlitShader)
291 {
292 Log.Error("Failed to load Shader '{0}'", SHADER_DISPLAY_BLIT);
293 }
294
295 SyncFields();
296 SyncTransform();
297 ShowHideCameras();
298
299 enabled = true;
300 }
301
302 private void InstantiateEyeCameras(out GameObject leftEye, out GameObject rightEye)
303 {
304 var cloneCameraTemplateChildren = glassesSettings.cloneCameraTemplateChildren;
305
306 // When we clone the head pose camera using Instantiate, we may not want to clone its children.
307 // If this is the case, detach the children and reparent them under a placeholder/babysitter GameObject
308 GameObject placeholder = cloneCameraTemplateChildren ? null : new GameObject("Placeholder");
309 if (!cloneCameraTemplateChildren)
310 {
311 placeholder.transform.parent = headPose.transform.parent;
312 while (cameraTemplate.transform.childCount > 0)
313 {
314 cameraTemplate.transform.GetChild(0).parent = placeholder.transform;
315 }
316 }
317
318 // Instantiate left and right eye cameras from the camera template.
319 leftEye = Instantiate(cameraTemplate.gameObject, headPose.transform.position, headPose.transform.rotation, headPose.transform);
320 leftEye.name = LEFT_EYE_CAMERA_NAME;
321 eyeCameras[AREyes.EYE_LEFT] = leftEye.GetComponent<Camera>();
322
323 rightEye = Instantiate(cameraTemplate.gameObject, headPose.transform.position, headPose.transform.rotation, headPose.transform);
324 rightEye.name = RIGHT_EYE_CAMERA_NAME;
325 eyeCameras[AREyes.EYE_RIGHT] = rightEye.GetComponent<Camera>();
326
327 var splitStereoCamerasLeft = leftEye.GetComponents<SplitStereoCamera>();
328 for (int i = 0; i < splitStereoCamerasLeft.Length; i++)
329 {
330 Destroy(splitStereoCamerasLeft[i]);
331 }
332
333 var splitStereoCamerasRight = rightEye.GetComponents<SplitStereoCamera>();
334 for (int i = 0; i < splitStereoCamerasRight.Length; i++)
335 {
336 Destroy(splitStereoCamerasRight[i]);
337 }
338
339
340 if (!cloneCameraTemplateChildren)
341 {
342 // Reclaim the head pose camera's children from the placeholder/babysitter GameObject
343 while (placeholder.transform.childCount > 0)
344 {
345 placeholder.transform.GetChild(0).parent = cameraTemplate.transform;
346 }
347 Destroy(placeholder);
348 }
349 }
350
351 private void GenerateEyeCameras(out GameObject leftEye, out GameObject rightEye)
352 {
353 leftEye = new GameObject(LEFT_EYE_CAMERA_NAME, typeof(Camera));
354 rightEye = new GameObject(RIGHT_EYE_CAMERA_NAME, typeof(Camera));
355
356 eyeCameras[AREyes.EYE_LEFT] = leftEye.GetComponent<Camera>();
357 eyeCameras[AREyes.EYE_RIGHT] = rightEye.GetComponent<Camera>();
358
359 leftEye.transform.parent = headPose.transform;
360 rightEye.transform.parent = headPose.transform;
361
362 leftEye.transform.SetPositionAndRotation(headPose.transform.position, headPose.transform.rotation);
363 rightEye.transform.SetPositionAndRotation(headPose.transform.position, headPose.transform.rotation);
364 }
365
366 private void ConfigureEyeCameras()
367 {
368 // Use the head pose camera's preferred texture format, rather than forcing it to render in LDR
369 splitStereoTextures.Initialize(displayInfo);
370
371 // Configure the left eye camera's render target
372 RenderTexture leftTex = splitStereoTextures.LeftTexture_GLS;
373 if (leftEyeCamera.allowMSAA && QualitySettings.antiAliasing > 1)
374 {
375 leftTex.antiAliasing = QualitySettings.antiAliasing;
376
377 // Ensure that the preview textures' antiAliasing settings match.
378 // Otherwise, Unity 2020.3 complains during Graphics.CopyTexture about the mismatch,
379 // resulting in a broken onscreen preview (manifesting as a black screen).
380 splitStereoTextures.MonoPreviewTex.antiAliasing = QualitySettings.antiAliasing;
381 splitStereoTextures.StereoPreviewTex.antiAliasing = QualitySettings.antiAliasing;
382 }
383
384 leftEyeCamera.targetTexture = leftTex;
385 leftEyeCamera.depth = spectatorCamera.depth - 1;
386
387 // Configure the right eye camera's render target
388 RenderTexture rightTex = splitStereoTextures.RightTexture_GLS;
389 if (rightEyeCamera.allowMSAA && QualitySettings.antiAliasing > 1)
390 {
391 rightTex.antiAliasing = QualitySettings.antiAliasing;
392 }
393
394 rightEyeCamera.targetTexture = rightTex;
395 rightEyeCamera.depth = spectatorCamera.depth - 1;
396 }
397
402 void SyncTransform()
403 {
404
405#if UNITY_EDITOR
406 // We move the eye Cameras in the Editor to emulate head pose and eye movement.
407 // In builds, we only set the camera transforms with Glasses tracking data.
408
409 if (null == cameraTemplate || !Player.TryGetPlayerIndex(glassesHandle, out PlayerIndex playerIndex))
410 return;
411
412 if (!Glasses.IsTracked(playerIndex))
413 {
414 GameObject pose = headPose;
415 // left eye copy and adjust
416 leftEye.transform.position = pose.transform.position;
417 leftEye.transform.localPosition = pose.transform.localPosition;
418 leftEye.transform.rotation = pose.transform.rotation;
419 leftEye.transform.localRotation = pose.transform.localRotation;
420 leftEye.transform.localScale = pose.transform.localScale;
421 leftEye.transform.Translate(-leftEye.transform.right.normalized * (cameraTemplate.stereoSeparation * 0.5f));
422
423 //right eye copy and adjust
424 rightEye.transform.position = pose.transform.position;
425 rightEye.transform.localPosition = pose.transform.localPosition;
426 rightEye.transform.rotation = pose.transform.rotation;
427 rightEye.transform.localRotation = pose.transform.localRotation;
428 rightEye.transform.localScale = headPose.transform.localScale;
429 rightEye.transform.Translate(rightEye.transform.right.normalized * (cameraTemplate.stereoSeparation * 0.5f));
430 }
431#endif
432 }
433
434 void OnEnable()
435 {
436 leftEye.SetActive(true);
437 rightEye.SetActive(true);
438 leftEyeCamera.enabled = true;
439 rightEyeCamera.enabled = true;
440 presentStereoImagesCoroutine = PresentStereoImagesCoroutine();
441 StartCoroutine(presentStereoImagesCoroutine);
442
443#if TILT_FIVE_SRP
444 if(Application.isPlaying)
445 {
446 RenderPipelineManager.beginFrameRendering += OnBeginFrameRendering;
447 RenderPipelineManager.endFrameRendering += OnEndFrameRendering;
448 }
449#endif
450 }
451
452 private void OnDisable()
453 {
454 if (clearsForMirroring > 0)
455 {
456 clearsForMirroring = 0;
457 // If OnDisable() is being called from Destroy(), then the children may no longer
458 // exist. Check before acting on them.
459 if (leftEye)
460 {
461 leftEye.SetActive(false);
462 leftEyeCamera.enabled = false;
463 }
464 if (rightEye)
465 {
466 rightEye.SetActive(false);
467 rightEyeCamera.enabled = false;
468 }
469 }
470
471 if(presentStereoImagesCoroutine != null)
472 {
473 StopCoroutine(presentStereoImagesCoroutine);
474 }
475#if TILT_FIVE_SRP
476 RenderPipelineManager.beginFrameRendering -= OnBeginFrameRendering;
477 RenderPipelineManager.endFrameRendering -= OnEndFrameRendering;
478#endif
479 }
480
481#if TILT_FIVE_SRP
486 private void OnBeginFrameRendering(ScriptableRenderContext context, Camera[] cameras)
487 {
488 // TODO: Determine whether this is necessary, or even permitted; the docs on RenderTexture.IsCreated() are lacking.
489 // We want to check for invalidated render textures before rendering,
490 // and this event should occur at the beginning of RenderPipeline.Render
491 // before any actual render passes occur, so in principle this should work as a substitute for OnPreRender.
492
493 // Check whether the left/right render textures' states have been invalidated,
494 // and reset the cached texture handles if so. See the longer explanation below in Update()
495 splitStereoTextures.ValidateNativeTexturePointers();
496 }
497#endif
498
502 void OnPreRender()
503 {
504 splitStereoTextures.ValidateNativeTexturePointers();
505
506 if(!UseSpectatorCamera)
507 {
508 return;
509 }
510
511 spectatorCamera.targetTexture = null;
512
513 // If the screen mirror mode changes, junk data will be displayed
514 // in the black bars unless we clear the screen buffer.
515 //
516 // This has to be done before we adjust the headpose camera rect,
517 // since GL.Clear's effect is limited by the active viewport.
518 if (glassesMirrorMode != previousMirrorMode)
519 {
520 clearsForMirroring = 0;
521 previousMirrorMode = glassesMirrorMode;
522 }
523
524 if (clearsForMirroring < requiredClearsForMirroring)
525 {
526 // Before calling GL.Clear(), we need to reset the viewport.
527 // Otherwise, we may not clear the entire screen in some cases.
528 GL.Viewport(spectatorCamera.pixelRect);
529 GL.Clear(true, true, Color.black);
530 clearsForMirroring += 1;
531 }
532
533 if(glassesMirrorMode == GlassesMirrorMode.None)
534 {
535 return;
536 }
537
538 spectatorCamera.cullingMask = 0; // Cull all layers, render nothing.
539 spectatorCamera.fieldOfView = glassesSettings.fieldOfView;
540 spectatorCamera.nearClipPlane = glassesSettings.nearClipPlane / scaleToUSTAGE_UWRLD;
541 spectatorCamera.farClipPlane = glassesSettings.farClipPlane / scaleToUSTAGE_UWRLD;
542
543 // Lock the aspect ratio and add pillarboxing/letterboxing as needed.
544 float screenRatio = Screen.width / (float)Screen.height;
545 float targetRatio = glassesMirrorMode == GlassesMirrorMode.Stereoscopic
546 ? displayInfo.stereoWidthToHeightRatio
547 : displayInfo.monoWidthToHeightRatio;
548
549 if(screenRatio > targetRatio) {
550 // Screen or window is wider than the target: pillarbox.
551 float normalizedWidth = targetRatio / screenRatio;
552 float barThickness = (1f - normalizedWidth) / 2f;
553 spectatorCamera.rect = new Rect(barThickness, 0, normalizedWidth, 1);
554 }
555 else {
556 // Screen or window is narrower than the target: letterbox.
557 float normalizedHeight = screenRatio / targetRatio;
558 float barThickness = (1f - normalizedHeight) / 2f;
559 spectatorCamera.rect = new Rect(0, barThickness, 1, normalizedHeight);
560 }
561 }
562
563
564#if TILT_FIVE_SRP
571 void OnEndFrameRendering(ScriptableRenderContext context, Camera[] cameras)
572 {
573 if (this == null || !enabled || glassesMirrorMode == GlassesMirrorMode.None || !UseSpectatorCamera)
574 {
575 spectatorCamera.rect = spectatorSettings.rect;
576 return;
577 }
578
579 // OnEndFrameRendering isn't picky about the camera(s) that finished rendering.
580 // This includes the scene view and/or material preview cameras.
581 // We need to make sure we only run the code in this function when we're performing stereoscopic rendering.
582 bool currentlyRenderingEyeCameras = false;
583
584 for (int i = 0; i < cameras.Length; i++)
585 {
586#if UNITY_EDITOR
587 if (cameras[i].Equals(UnityEditor.SceneView.lastActiveSceneView.camera))
588 {
589 return;
590 }
591#endif
592 if (cameras[i].Equals(leftEyeCamera) || cameras[i].Equals(rightEyeCamera) || cameras[i].Equals(spectatorCamera))
593 {
594 currentlyRenderingEyeCameras = true;
595 }
596 }
597 if (!currentlyRenderingEyeCameras)
598 {
599 return;
600 }
601
602 // Determine the aspect ratio to enable pillarboxing/letterboxing.
603 float screenRatio = Screen.width / (float)Screen.height;
604 float targetRatio = glassesMirrorMode == GlassesMirrorMode.Stereoscopic
605 ? displayInfo.stereoWidthToHeightRatio
606 : displayInfo.monoWidthToHeightRatio;
607 Vector2 frameScale = Vector2.one;
608
609 if (screenRatio != targetRatio)
610 {
611 frameScale = screenRatio > targetRatio
612 ? new Vector2(screenRatio / targetRatio, 1f)
613 : new Vector2(1f, targetRatio / screenRatio);
614 }
615
616 splitStereoTextures.SubmitPreviewTexturesSRP(glassesMirrorMode, spectatorCamera, commandBuffer, frameScale);
617
618 context.ExecuteCommandBuffer(commandBuffer);
619 context.Submit();
620 commandBuffer.Clear();
621
622 spectatorSettings.ResetSpectatorCamera();
623 }
624#endif
625
632 void OnRenderImage(RenderTexture src, RenderTexture dst)
633 {
634 // If we're not supposed to spectate due to the spectated player being set to None
635 // or to a player that isn't connected, have the first SplitStereoCamera attached to
636 // the SpectatorCamera perform a blit.
637 // TODO: Consider adding a spectator setting to define fallback behavior when the specified player isn't connected.
638 // The current behavior is indistinguishable from the PlayerIndex.None case, and perhaps it'd be desirable
639 // to spectate the next available player instead, if there is one?
640 if (spectatorSettings.spectatedPlayer == PlayerIndex.None || !Player.IsConnected(spectatorSettings.spectatedPlayer))
641 {
642 var splitStereoCameras = spectatorCamera.GetComponents<SplitStereoCamera>();
643 if(splitStereoCameras != null && splitStereoCameras.Length > 0 // These two checks should be redundant
644 && splitStereoCameras[0].Equals(this)) // This is the important one
645 {
646 Graphics.Blit(src, null as RenderTexture);
647 return;
648 }
649 // Any SplitStereoCameras that call OnRenderImage after the first one should return, otherwise they'll clear the screen
650 return;
651 }
652
653 if(!UseSpectatorCamera)
654 {
655 return;
656 }
657
658 if (glassesMirrorMode != GlassesMirrorMode.None)
659 {
660 splitStereoTextures.SubmitPreviewTextures(glassesMirrorMode);
661
662 var previewTex = glassesMirrorMode == GlassesMirrorMode.Stereoscopic
663 ? splitStereoTextures.StereoPreviewTex
664 : splitStereoTextures.MonoPreviewTex;
665
666 // Blitting is required when overriding OnRenderImage().
667 // Setting the blit destination to null is the same as blitting to the screen backbuffer.
668 // This will effectively render previewTex to the screen.
669 Graphics.Blit(previewTex,
670 null as RenderTexture,
671 Vector2.one,
672 Vector2.zero);
673 }
674 else Graphics.Blit(src, null as RenderTexture);
675
676 // We're done with our letterboxing/pillarboxing now that we've blitted to the screen.
677 // If the SplitStereoCamera gets disabled next frame, ensure that the original behavior returns.
678 spectatorSettings.ResetSpectatorCamera();
679 }
680
681 IEnumerator PresentStereoImagesCoroutine()
682 {
683 // WaitForEndOfFrame() will let us wait until the last possible moment to send frames to the glasses.
684 // This allows the results of rendering, postprocessing, and even GUI to be displayed.
685 var cachedWaitForEndOfFrame = new WaitForEndOfFrame();
686
687 while (enabled)
688 {
689 yield return cachedWaitForEndOfFrame;
690
691 PresentStereoImages();
692 }
693 }
694
695 private void PresentStereoImages()
696 {
697 splitStereoTextures.GetNativeTexturePointers(out var leftTexHandle, out var rightTexHandle);
698
699 var leftTargetTex = splitStereoTextures.LeftTexture_GLS;
700 var rightTargetTex = splitStereoTextures.RightTexture_GLS;
701
702 bool isSrgb = leftTargetTex.sRGB;
703
704 Vector3 posOfULVC_UWRLD = leftEyeCamera.transform.position;
705 Quaternion rotToUWRLD_ULVC = leftEyeCamera.transform.rotation;
706 Vector3 posOfURVC_UWRLD = rightEyeCamera.transform.position;
707 Quaternion rotToUWRLD_URVC = rightEyeCamera.transform.rotation;
708
709 Vector3 posOfULVC_USTAGE = rotToUSTAGE_UWRLD * (scaleToUSTAGE_UWRLD * (posOfULVC_UWRLD - posUSTAGE_UWRLD));
710 Quaternion rotToUSTAGE_ULVC = rotToUSTAGE_UWRLD * rotToUWRLD_ULVC;
711
712 Vector3 posOfURVC_USTAGE = rotToUSTAGE_UWRLD * (scaleToUSTAGE_UWRLD * (posOfURVC_UWRLD - posUSTAGE_UWRLD));
713 Quaternion rotToUSTAGE_URVC = rotToUSTAGE_UWRLD * rotToUWRLD_URVC;
714
715
716 Display.PresentStereoImages(glassesHandle,
717 leftTexHandle, rightTexHandle,
718 leftTargetTex.width, rightTargetTex.height,
719 isSrgb,
720 glassesSettings.fieldOfView,
721 displayInfo.monoWidthToHeightRatio,
722 rotToUSTAGE_ULVC,
723 posOfULVC_USTAGE,
724 rotToUSTAGE_URVC,
725 posOfURVC_USTAGE);
726 }
727
731 private void SyncFields()
732 {
733 if (glassesSettings == null)
734 {
735 return;
736 }
737 if (leftEyeCamera != null)
738 {
739 leftEyeCamera.fieldOfView = glassesSettings.fieldOfView;
740 leftEyeCamera.nearClipPlane = glassesSettings.nearClipPlane;
741 leftEyeCamera.farClipPlane = glassesSettings.farClipPlane;
742 leftEyeCamera.aspect = displayInfo.monoWidthToHeightRatio;
743 }
744 if (rightEyeCamera != null)
745 {
746 rightEyeCamera.fieldOfView = glassesSettings.fieldOfView;
747 rightEyeCamera.nearClipPlane = glassesSettings.nearClipPlane;
748 rightEyeCamera.farClipPlane = glassesSettings.farClipPlane;
749 rightEyeCamera.aspect = displayInfo.monoWidthToHeightRatio;
750 }
751 }
752
756 void OnValidate()
757 {
758
759#if UNITY_EDITOR
760 if (false == UnityEditor.EditorApplication.isPlaying)
761 return;
762#endif
763 if (null == spectatorCamera)
764 return;
765
766 if (null != leftEye && null != rightEye)
767 ShowHideCameras();
768
769 SyncFields();
770 SyncTransform();
771 }
772
776 void ShowHideCameras()
777 {
778 if (showCameras)
779 {
780 leftEye.hideFlags = HideFlags.None;
781 rightEye.hideFlags = HideFlags.None;
782 }
783 else
784 {
785 leftEye.hideFlags = HideFlags.HideInInspector | HideFlags.HideInHierarchy;
786 rightEye.hideFlags = HideFlags.HideInInspector | HideFlags.HideInHierarchy;
787 }
788 }
789 }
790}
static float monoWidthToHeightRatio
The display aspect ratio.
static int monoWidth
The display width for a single eye.
static float stereoWidthToHeightRatio
The double-width display aspect ratio.
static int height
The display height.
const int depthBuffer
The depth buffer's precision.
static int stereoWidth
The display width for two eyes.
The Glasses API and runtime.
Definition Glasses.cs:35
GlassesSettings encapsulates all configuration data used by the Glasses' tracking runtime to compute ...
The Logger.
Definition Log.cs:42
static void Warn(string m, params object[] list)
WARN logging function call.
Definition Log.cs:166
GlassesMirrorMode glassesMirrorMode
The spectator camera will display content on screen depending on the mirroring mode....
Camera spectatorCamera
The camera used for rendering the onscreen preview.
float nearClipPlane
The Cameras' near clip plane property.
bool showCameras
In-editor toggle for displaying the eye cameras in the runtime Hierarchy.
float aspectRatio
The Cameras' aspect ratio property.
float farClipPlane
The Cameras' far clip plane property.
Quaternion rotToUGBD_UWRLD
The rotation taking points from the Unity world-space reference frame to the game board reference fra...
Camera leftEyeCamera
The left eye Camera property.
Camera cameraTemplate
The Camera used as a template when creating the eye cameras.
Vector3 posUGBD_UWRLD
The position of the game board reference frame w.r.t. the Unity world-space reference frame.
Camera rightEyeCamera
The right eye Camera property.
float scaleToUGBD_UWRLD
The uniform scale factor that takes points from the Unity world-space to the game board reference fra...
float fieldOfView
The Cameras' field of view property.
GameObject headPose
The head pose GameObject property.
PlayerIndex
The Player index (e.g. Player One, Player Two, etc)