2025-01-03 13:42:53 +08:00

771 lines
29 KiB
C#

using System.Threading;
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2007-2020 , Inc. All Rights Reserved.
//
////////////////////////////////////////////////////////////////////////////////
using System.Collections;
using UnityEngine;
using GCSeries.Core.Extensions;
using GCSeries.Core.Sdk;
namespace GCSeries.Core
{
[ExecuteInEditMode]
[DefaultExecutionOrder(ScriptPriority)]
[DisallowMultipleComponent]
[RequireComponent(typeof(Camera))]
public sealed partial class ZCamera : MonoBehaviour
{
public const int ScriptPriority = ZProvider.ScriptPriority + 20;
public enum RenderMode
{
SingleCamera = 0,
MultiCamera = 1,
}
////////////////////////////////////////////////////////////////////////
// Inspector Fields
////////////////////////////////////////////////////////////////////////
/// <summary>
/// Flag to control whether stereoscopic 3D rendering is enabled.
/// </summary>
[Tooltip(
"Flag to control whether stereoscopic 3D rendering is enabled.")]
public bool EnableStereo = true;
/// <summary>
/// The time in seconds to wait while the head target is not visible
/// before initiating the automatic transition from stereoscopic 3D
/// to monoscopic 3D rendering.
/// </summary>
[Tooltip(
"The time in seconds to wait while the head target is not " +
"visible before initiating the automatic transition from " +
"stereoscopic 3D to monoscopic 3D rendering.")]
public float StereoToMonoDelay = 5.0f;
/// <summary>
/// The duration in seconds of the transition from stereoscopic 3D
/// to monoscopic 3D rendering (and vice versa).
/// </summary>
[Tooltip(
"The duration in seconds of the transition from stereoscopic 3D " +
"to monoscopic 3D rendering (and vice versa).")]
public float StereoToMonoDuration = 1.0f;
/// <summary>
/// The camera's stereoscopic 3D render mode.
/// </summary>
///
/// <remarks>
/// SingleCamera (default) and MultiCamera are the two currently
/// supported stereoscopic 3D render modes.
///
/// The SingleCamera render mode is more optimal due to it being able
/// to share culling and shadow passes for both the left and right
/// eyes. As a result, there are noticable visual artifacts when
/// rendering features such as shadows.
///
/// If your application requires shadows, please use the MultiCamera
/// render mode to avoid these visual artifacts. Note, that if the
/// MultiCamera render mode is enabled, any post-process rendering
/// related camera scripts must be added to the secondary left and right
/// child camera GameObjects.
/// </remarks>
// [Tooltip("The camera's stereoscopic 3D render mode.")]
// public RenderMode StereoRenderMode = RenderMode.MultiCamera;
/// <summary>
/// The left eye camera to be used when StereoRenderMode is set to
/// RenderMode.MultiCamera.
/// </summary>
[Tooltip(
"The left eye camera to be used when StereoRenderMode is set to " +
"RenderMode.MultiCamera.")]
[SerializeField]
private Camera _leftCamera = null;
/// <summary>
/// The right eye camera to be used when StereoRenderMode is set to
/// RenderMode.MultiCamera.
/// </summary>
[Tooltip(
"The right eye camera to be used when StereoRenderMode is set to " +
"RenderMode.MultiCamera.")]
[SerializeField]
private Camera _rightCamera = null;
////////////////////////////////////////////////////////////////////////
// MonoBehaviour Callbacks
////////////////////////////////////////////////////////////////////////
private void Reset()
{
Camera camera = this.Camera;
camera.stereoSeparation = ZFrustum.DefaultIpd;
camera.nearClipPlane = ZFrustum.DefaultNearClip;
camera.farClipPlane = ZFrustum.DefaultFarClip;
}
private void OnEnable()
{
if (Application.isPlaying)
{
this.StopAllCoroutines();
this.StartCoroutine(this.EndOfFrameUpdate());
}
}
private void Awake()
{
this._camera = this.GetComponent<Camera>();
#if UNITY_EDITOR && UNITY_2019_1_OR_NEWER
if (UnityEditor.PlayerSettings.useFlipModelSwapchain)
UnityEditor.PlayerSettings.useFlipModelSwapchain = false;
#endif
if (Application.isPlaying)
{
#if UNITY_EDITOR
if (!this.CompareTag("MainCamera"))
{
Debug.LogWarningFormat(
this,
"<color=cyan>{0}</color> will not render to the XR " +
"Overlay. To enable XR Overlay rendering, please set " +
"{0}'s associated tag to \"MainCamera\".",
this.name);
}
#endif
if (ZProvider.IsInitialized)
{
this._headTarget = ZProvider.HeadTarget;
this._frustum = ZProvider.Viewport.Frustum;
}
// Initialize members related to transitioning from stereo
// to mono (and vice versa).
bool isHeadVisible = this._headTarget?.IsVisible ?? false;
this._stereoWeight = isHeadVisible ? 1 : 0;
this._stereoTimeRemaining = this.StereoToMonoDelay;
FARDll.CurrentColorSpace = (FARDll.U3DColorSpace)QualitySettings.activeColorSpace;
#if !UNITY_EDITOR
if (this.IsStereoAvailable &&
this._contextStereoDisplayMode == ZStereoDisplayMode.NativePlugin)
{
this.InitializeNativePluginStereoDisplay();
}
#endif
// Initialize the internal early updater.
this._earlyUpdater =
this.gameObject.AddComponent<EarlyUpdater>();
this._earlyUpdater.Camera = this;
this._earlyUpdater.hideFlags = HideFlags.HideInInspector;
// Initialize the internal updater.
this._layterUpdater = this.gameObject.AddComponent<LaterUpdater>();
this._layterUpdater.Camera = this;
this._layterUpdater.hideFlags = HideFlags.HideInInspector;
}
}
private void Update()
{
this.UpdateTransform();
this.UpdateStereoWeight();
this.UpdatePerspective();
this.UpdateTargetTexture();
}
private void OnApplicationPause(bool isPaused)
{
// Disable stereoscopic 3D rendering if the application is paused.
if (isPaused)
{
this._stereoWeight = 0;
}
}
private void OnDestroy()
{
if (this._earlyUpdater != null)
{
Destroy(this._earlyUpdater);
}
if (this._layterUpdater != null)
{
Destroy(this._layterUpdater);
}
#if UNITY_EDITOR
this.DestroyOverlayResources();
#endif
}
////////////////////////////////////////////////////////////////////////
// Public Properties
////////////////////////////////////////////////////////////////////////
/// <summary>
/// Gets the associated Unity Camera.
/// </summary>
public Camera Camera => this._camera;
/// <summary>
/// The current scale of the world.
/// </summary>
///
/// <remarks>
/// The world scale is computed as the product of the parent camera
/// rig's viewer scale multiplied by the current display scale factor
/// accessible via ZProvider.DisplayScaleFactor.
/// </remarks>
public Vector3 WorldScale => this._worldScale;
/// <summary>
/// Gets the camera's offset in meters.
/// </summary>
public Vector3 CameraOffset => this._cameraOffset;
/// <summary>
/// The transformation matrix from camera to world space.
/// </summary>
///
/// <remarks>
/// This is useful in scenarios such as transforming a 6-DOF
/// trackable target's pose from camera space to world space.
/// </remarks>
public Matrix4x4 CameraToWorldMatrix => this._monoLocalToWorldMatrix;
/// <summary>
/// The world space transformation matrix of the zero parallax
/// (screen) plane.
/// </summary>
public Matrix4x4 ZeroParallaxLocalToWorldMatrix =>
this.transform.parent?.localToWorldMatrix ?? Matrix4x4.identity;
/// <summary>
/// The world space pose of the zero parallax (screen) plane.
/// </summary>
public Pose ZeroParallaxPose =>
this.transform.parent?.ToPose() ??
new Pose(Vector3.zero, Quaternion.identity);
/// <summary>
/// The Unity Plane in world space representing the zero parallax
/// (screen) plane.
/// </summary>
public Plane ZeroParallaxPlane => new Plane(
-this.transform.parent?.forward ?? Vector3.back,
this.transform.parent?.position ?? Vector3.zero);
/// <summary>
/// Gets whether stereoscopic 3D rendering capabilities are available.
/// </summary>
public bool IsStereoAvailable => (this._frustum != null && EnableStereo);
/// <summary>
/// The current weight value between 0 and 1 (inclusive) that
/// represents whether the camera's perspective is monoscopic
/// or stereoscopic 3D.
/// </summary>
///
/// <remarks>
/// The only time this value will be in between 0 and 1 is when the
/// camera is performing a transition from stereoscopic to monoscopic
/// 3D (or vice versa).
///
/// Additionally, a value of 0 means the camera is rendering a
/// monoscopic 3D perspective. A value of 1 means the camera is
/// rendering a stereoscopic 3D perspective.
/// </remarks>
public float StereoWeight => this._stereoWeight;
////////////////////////////////////////////////////////////////////////
// Private Methods
////////////////////////////////////////////////////////////////////////
private void InitializeNativePluginStereoDisplay()
{
// Issue an event to the native plugin to tell it to enable the
// native plugin context's graphics API binding on the Unity render
// thread.
GL.IssuePluginEvent(FARDll.GetRenderEventFunc(), 10001);
// Create the render textures that will contain the per-eye images
// to be displayed by the native plugin.
var perEyeImageResolution = ZProvider.DisplayReferenceResolution;
// if (this._nativePluginLeftEyeRenderTexture == null)
// _nativePluginLeftEyeRenderTexture = Resources.Load<RenderTexture>("gViewRT");
// if (this._nativePluginRightEyeRenderTexture == null)
// this._nativePluginRightEyeRenderTexture = Resources.Load<RenderTexture>("gViewRT_2");
this._nativePluginLeftEyeRenderTexture = new RenderTexture(
width: perEyeImageResolution.x,
height: perEyeImageResolution.y,
depth: 24,
format: RenderTextureFormat.ARGB32);
this._nativePluginLeftEyeRenderTexture.dimension = UnityEngine.Rendering.TextureDimension.Tex2D;
this._nativePluginLeftEyeRenderTexture.useMipMap = false;
this._nativePluginLeftEyeRenderTexture.depth = 0;
this._nativePluginLeftEyeRenderTexture.antiAliasing = 1;
this._nativePluginLeftEyeRenderTexture.anisoLevel = 0;
this._nativePluginLeftEyeRenderTexture.Create();
this._nativePluginRightEyeRenderTexture = new RenderTexture(
width: perEyeImageResolution.x,
height: perEyeImageResolution.y,
depth: 24,
format: RenderTextureFormat.ARGB32);
this._nativePluginRightEyeRenderTexture.dimension = UnityEngine.Rendering.TextureDimension.Tex2D;
this._nativePluginRightEyeRenderTexture.useMipMap = false;
this._nativePluginRightEyeRenderTexture.depth = 0;
this._nativePluginRightEyeRenderTexture.antiAliasing = 1;
this._nativePluginRightEyeRenderTexture.anisoLevel = 0;
this._nativePluginRightEyeRenderTexture.Create();
// Give the per-eye render textures to the native plugin to use
// later for stereo display.
int result = FARDll.SetStereoDisplayTextures(
this._nativePluginLeftEyeRenderTexture.GetNativeTexturePtr(),
this._nativePluginRightEyeRenderTexture.GetNativeTexturePtr(),
0);
if (result < 0)
{
// this.EnableStereo = false;
Destroy(_nativePluginLeftEyeRenderTexture);
_nativePluginLeftEyeRenderTexture = null;
Destroy(_nativePluginRightEyeRenderTexture);
_nativePluginRightEyeRenderTexture = null;
Debug.LogError($"Init Stereo Display output failed with error {result}");
}
}
private void BeginFrame()
{
FARDll.BeginFrame();
}
private void EndFrame()
{
FARDll.EndFrame();
}
private void UpdateTransform()
{
this._cameraOffset = (Vector3.back * ZProvider.WindowSize.magnitude);
this.transform.localPosition = this._cameraOffset;
this.transform.localRotation = Quaternion.identity;
this.transform.localScale = Vector3.one;
this._worldScale = this.transform.lossyScale;
this._monoLocalToWorldMatrix = this.transform.localToWorldMatrix;
this._monoWorldToCameraMatrix = this.Camera.worldToCameraMatrix;
this._monoLocalPoseMatrix = Matrix4x4.TRS(
this.transform.localPosition,
this.transform.localRotation,
Vector3.one);
}
private void UpdateStereoWeight()
{
if (this._headTarget == null)
{
return;
}
float maxDelta = (this.StereoToMonoDuration != 0) ?
Time.unscaledDeltaTime / this.StereoToMonoDuration :
float.MaxValue;
if (this.EnableStereo && this._headTarget.IsVisible)
{
// Start transitioning from mono to stereo immediately
// after the head becomes visible.
this._stereoTimeRemaining = this.StereoToMonoDelay;
this._stereoWeight = Mathf.MoveTowards(
this._stereoWeight, 1, maxDelta);
}
else
{
// Start transitioning from stereo to mono after the
// specified stereo to mono delay.
if (this.EnableStereo && this._stereoTimeRemaining > 0)
{
this._stereoTimeRemaining -= Time.unscaledDeltaTime;
}
else
{
this._stereoWeight = Mathf.MoveTowards(
this._stereoWeight, 0, maxDelta);
}
}
}
private void UpdatePerspective()
{
// Update the main camera's perspective.
if (!Application.isPlaying || !this.IsStereoAvailable)
{
this.UpdateMonoPerspective();
}
else
{
this.UpdateStereoPerspective();
}
// Update the left and right camera perspectives.
this.UpdateSecondaryCameraPerspectives();
}
private void UpdateMonoPerspective()
{
Camera camera = this.Camera;
// Compute the half extents of the corresponding to the positions
// of the left, right, top, and bottom frustum bounds.
float nearScale = camera.nearClipPlane / this._cameraOffset.magnitude;
Vector2 halfExtents = ZProvider.WindowSize * 0.5f * nearScale;
// Compute and set the monoscopic projection matrix.
Matrix4x4 projectionMatrix = Matrix4x4.Frustum(
-halfExtents.x, halfExtents.x, -halfExtents.y, halfExtents.y,
camera.nearClipPlane, camera.farClipPlane);
camera.projectionMatrix = projectionMatrix;
// Set the stereo view and projection matrices to be equal
// to the monoscopic view and projection matrices.
camera.SetStereoViewMatrix(
Camera.StereoscopicEye.Left, this._monoWorldToCameraMatrix);
camera.SetStereoViewMatrix(
Camera.StereoscopicEye.Right, this._monoWorldToCameraMatrix);
camera.SetStereoProjectionMatrix(
Camera.StereoscopicEye.Left, projectionMatrix);
camera.SetStereoProjectionMatrix(
Camera.StereoscopicEye.Right, projectionMatrix);
}
private void UpdateStereoPerspective()
{
Camera camera = this.Camera;
// Apply camera settings to the frustum.
this._frustum.NearClip = camera.nearClipPlane;
this._frustum.FarClip = camera.farClipPlane;
this._frustum.CameraOffset = this._cameraOffset;
this._frustum.Ipd = Mathf.Lerp(
0, camera.stereoSeparation, this._stereoWeight);
this._frustum.HeadPose = PoseExtensions.Lerp(
this._frustum.DefaultHeadPose,
this._headTarget.Pose,
this._stereoWeight);
// Update the camera's view matrices for the
// center, left, and right eyes.
camera.transform.SetLocalPose(this.GetLocalPose(ZEye.Center));
camera.SetStereoViewMatrix(
Camera.StereoscopicEye.Left,
this._frustum.GetViewMatrix(ZEye.Left, this.WorldScale) *
this._monoWorldToCameraMatrix);
camera.SetStereoViewMatrix(
Camera.StereoscopicEye.Right,
this._frustum.GetViewMatrix(ZEye.Right, this.WorldScale) *
this._monoWorldToCameraMatrix);
// Update the camera's projection matrices for the
// center, left, and right eyes.
camera.projectionMatrix =
this._frustum.GetProjectionMatrix(ZEye.Center);
camera.SetStereoProjectionMatrix(
Camera.StereoscopicEye.Left,
this._frustum.GetProjectionMatrix(ZEye.Left));
camera.SetStereoProjectionMatrix(
Camera.StereoscopicEye.Right,
this._frustum.GetProjectionMatrix(ZEye.Right));
}
private void UpdateSecondaryCameraPerspectives()
{
Camera camera = this.Camera;
if (this._leftCamera != null)
{
this._leftCamera.CopyFrom(
camera, Camera.StereoscopicEye.Left);
this._leftCamera.transform.SetPose(
this.GetPose(ZEye.Left), true);
}
if (this._rightCamera != null)
{
this._rightCamera.CopyFrom(
camera, Camera.StereoscopicEye.Right);
this._rightCamera.transform.SetPose(
this.GetPose(ZEye.Right), true);
}
}
private void UpdateCameraActiveState()
{
bool isPrimaryCameraEnabled = false;
// Update whether the main camera is enabled.
this.Camera.enabled = isPrimaryCameraEnabled;
// Update whether the secondary left and right cameras
// are enabled.
if (this._leftCamera != null)
{
this._leftCamera.gameObject.SetActive(!isPrimaryCameraEnabled);
this._leftCamera.enabled = !isPrimaryCameraEnabled;
}
if (this._rightCamera != null)
{
this._rightCamera.gameObject.SetActive(!isPrimaryCameraEnabled);
this._rightCamera.enabled = !isPrimaryCameraEnabled;
}
}
private void UpdateTargetTexture()
{
// Only include code related to the native plugin stereo display
// mode when not running in the Unity editor because displaying
// stereo via the native plugin does not currently work in the
// Unity editor.
#if !UNITY_EDITOR
// If native plugin stereo display is requested, then set the left
// and right camera target textures to the render textures that
// will be used by the native plugin for stereo display.
//
// The main camera's target texture is not set here because it must
// be set before rendering each eye since there is a different
// target texture for each eye and the main camera only has one
// target texture. If the main camera is being used (i.e. if
// single-camera mode is requested), its target texture will be set
// immediately before it is manually rendered for each eye at the
// end of the frame.
if (this.IsStereoAvailable &&
this._contextStereoDisplayMode ==
ZStereoDisplayMode.NativePlugin)
{
if (this._leftCamera != null && _nativePluginLeftEyeRenderTexture != null)
{
this._leftCamera.targetTexture = this._nativePluginLeftEyeRenderTexture;
}
if (this._rightCamera != null&& _nativePluginRightEyeRenderTexture != null)
{
this._rightCamera.targetTexture = this._nativePluginRightEyeRenderTexture;
}
}
#endif
}
private Pose GetPose(ZEye eye)
{
if (this._frustum != null)
{
Matrix4x4 viewMatrix =
this._frustum.GetViewMatrix(eye).FlipHandedness();
Matrix4x4 localToWorldMatrix =
this._monoLocalToWorldMatrix * viewMatrix.inverse;
return localToWorldMatrix.ToPose();
}
else
{
return this._monoLocalToWorldMatrix.ToPose();
}
}
private Pose GetLocalPose(ZEye eye)
{
if (this._frustum != null)
{
Matrix4x4 viewMatrix =
this._frustum.GetViewMatrix(eye).FlipHandedness();
Matrix4x4 localPoseMatrix =
this._monoLocalPoseMatrix * viewMatrix.inverse;
return localPoseMatrix.ToPose();
}
return this._monoLocalPoseMatrix.ToPose();
}
private IEnumerator EndOfFrameUpdate()
{
while (true)
{
yield return new WaitForEndOfFrame();
// Only include code related to the native plugin stereo display
// mode when not running in the Unity editor because displaying
// stereo via the native plugin does not currently work in the
// Unity editor.
#if !UNITY_EDITOR
// If native plugin stereo display is requested, perform the
// rendering according to the requested
// single-camera/multi-camera mode.
if (this.IsStereoAvailable &&
this._contextStereoDisplayMode ==
ZStereoDisplayMode.NativePlugin)
{
// If single-camera mode is requested, perform the rendering
// for each eye by manually rendering the main camera.
// if (this.StereoRenderMode == RenderMode.SingleCamera)
// {
// this.PerformSingleCameraNativePluginStereoDisplayEyeRendering();
// }
// If multi-camera mode is requested, the rendering for
// each eye will have already been performed by the left
// and right cameras.
// Regardless of the requested single-camera/multi-camera
// mode, issue an event to the native plugin to tell it to
// display the latest stereo images on the Unity render
// thread.
GL.IssuePluginEvent(FARDll.GetRenderEventFunc(), 10002);
}
#endif
if (this.Camera != null)
{
this.Camera.enabled = true;
}
this.EndFrame();
}
}
private void PerformSingleCameraNativePluginStereoDisplayEyeRendering()
{
this.Camera.Render(
this._nativePluginLeftEyeRenderTexture,
Camera.StereoscopicEye.Left,
this.GetPose(ZEye.Left));
this.Camera.Render(
this._nativePluginRightEyeRenderTexture,
Camera.StereoscopicEye.Right,
this.GetPose(ZEye.Right));
}
////////////////////////////////////////////////////////////////////////
// Private Types
////////////////////////////////////////////////////////////////////////
// Make the default script execution order high enough to hopefully
// ensure that the camera active state and XR Overlay will be updated
// after all MonoBehaviour Update() and LateUpdate() callbacks have had
// a chance to run.
[DefaultExecutionOrder(10000)]
private class LaterUpdater : MonoBehaviour
{
public ZCamera Camera { get; set; } = null;
private void LateUpdate()
{
if (this.Camera != null)
{
// Ensure the appropriate cameras are enabled prior to
// rendering.
this.Camera.UpdateCameraActiveState();
#if UNITY_EDITOR_WIN
// NOTE: Updating and rendering to the XR Overlay performs
// best when executed from MonoBehaviour.LateUpdate().
if (this.Camera.enabled)
{
this.Camera.UpdateOverlay();
}
#endif
}
}
}
////////////////////////////////////////////////////////////////////////
// Private Types
////////////////////////////////////////////////////////////////////////
// Make the default script execution order low enough to hopefully
// ensure that the native plugin context has been notified that a new
// frame is beginning before all MonoBehaviour Update()callbacks have
// had a chance to run.
[DefaultExecutionOrder(-10000)]
private class EarlyUpdater : MonoBehaviour
{
public ZCamera Camera { get; set; }
private void Update()
{
this.Camera.BeginFrame();
}
}
////////////////////////////////////////////////////////////////////////
// Private Members
////////////////////////////////////////////////////////////////////////
private Camera _camera = null;
private ZTarget _headTarget = null;
private ZFrustum _frustum = null;
private Vector3 _cameraOffset = ZFrustum.DefaultCameraOffset;
private Vector3 _worldScale = Vector3.one;
private Matrix4x4 _monoLocalToWorldMatrix;
private Matrix4x4 _monoWorldToCameraMatrix;
private Matrix4x4 _monoLocalPoseMatrix;
private float _stereoWeight = 1;
private float _stereoTimeRemaining = 0;
#pragma warning disable 0414
private ZStereoDisplayMode _contextStereoDisplayMode = ZStereoDisplayMode.NativePlugin;
private RenderTexture _nativePluginLeftEyeRenderTexture;
private RenderTexture _nativePluginRightEyeRenderTexture;
private EarlyUpdater _earlyUpdater = null;
private LaterUpdater _layterUpdater = null;
}
}