程序应该怎样写比较规范

65 篇文章 2 订阅

1,此中有真意,欲辨已忘言。

Google Cardboard中一个脚本

// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

/// @ingroup Scripts
/// This class is the main Cardboard SDK object.
///
/// The Cardboard object communicates with the head-mounted display in order to:
/// -  Query the device for viewing parameters
/// -  Retrieve the latest head tracking data
/// -  Provide the rendered scene to the device for distortion correction (optional)
///
/// There should only be one of these in a scene.  An instance will be generated automatically
/// by this script at runtime, or you can add one via the Editor if you wish to customize
/// its starting properties.
[AddComponentMenu("Cardboard/Cardboard")]
public class Cardboard : MonoBehaviour {
  // Cardboard SDK Version
  public const string CARDBOARD_SDK_VERSION = "0.7";

  /// The singleton instance of the Cardboard class.
  public static Cardboard SDK {
    get {
#if UNITY_EDITOR
      if (sdk == null && !Application.isPlaying) {
        sdk = UnityEngine.Object.FindObjectOfType<Cardboard>();
      }
#endif
      if (sdk == null) {
        Debug.LogError("No Cardboard instance found.  Ensure one exists in the scene, or call "
            + "Cardboard.Create() at startup to generate one.\n"
            + "If one does exist but hasn't called Awake() yet, "
            + "then this error is due to order-of-initialization.\n"
            + "In that case, consider moving "
            + "your first reference to Cardboard.SDK to a later point in time.\n"
            + "If exiting the scene, this indicates that the Cardboard object has already "
            + "been destroyed.");
      }
      return sdk;
    }
  }
  private static Cardboard sdk = null;

  /// Generate a Cardboard instance.  Takes no action if one already exists.
  public static void Create() {
    if (sdk == null && UnityEngine.Object.FindObjectOfType<Cardboard>() == null) {
      Debug.Log("Creating Cardboard object");
      var go = new GameObject("Cardboard", typeof(Cardboard));
      go.transform.localPosition = Vector3.zero;
      // sdk will be set by Cardboard.Awake().
    }
  }

  /// The StereoController instance attached to the main camera, or null if there is none.
  /// @note Cached for performance.
  public static StereoController Controller {
    get {
      Camera camera = Camera.main;
      // Cache for performance, if possible.
      if (camera != currentMainCamera || currentController == null) {
        currentMainCamera = camera;
        currentController = camera.GetComponent<StereoController>();
      }
      return currentController;
    }
  }
  private static Camera currentMainCamera;
  private static StereoController currentController;

  /// @cond
  public bool UILayerEnabled {
    get {
      return uiLayerEnabled;
    }
    private set {
      if (value != uiLayerEnabled && device != null) {
        device.SetUILayerEnabled(value);
      }
      uiLayerEnabled = value;
    }
  }
  // Not serialized.
  private bool uiLayerEnabled = false;
  /// @endcond

  /// Determine whether the scene renders in stereo or mono.
  /// _True_ means to render in stereo, and _false_ means to render in mono.
  public bool VRModeEnabled {
    get {
      return vrModeEnabled;
    }
    set {
      if (value != vrModeEnabled && device != null) {
        device.SetVRModeEnabled(value);
      }
      vrModeEnabled = value;
    }
  }
  [SerializeField]
  private bool vrModeEnabled = true;

  /// Methods for performing lens distortion correction.
  public enum DistortionCorrectionMethod {
      None,    /// No distortion correction
      Native,  /// Use the native C++ plugin
      Unity,   /// Perform distortion correction in Unity (recommended)
  }

  /// Determines the distortion correction method used by the SDK to render the
  /// #StereoScreen texture on the phone.  If _Native_ is selected but not supported
  /// by the device, the _Unity_ method will be used instead.
  public DistortionCorrectionMethod DistortionCorrection {
    get {
      return distortionCorrection;
    }
    set {
      if (device != null && device.RequiresNativeDistortionCorrection()) {
        value = DistortionCorrectionMethod.Native;
      }
      if (value != distortionCorrection && device != null) {
        device.SetDistortionCorrectionEnabled(value == DistortionCorrectionMethod.Native
            && NativeDistortionCorrectionSupported);
        device.UpdateScreenData();
      }
      distortionCorrection = value;
    }
  }
  [SerializeField]
  private DistortionCorrectionMethod distortionCorrection = DistortionCorrectionMethod.Unity;

  /// Enables or disables the vertical line rendered between the stereo views to
  /// help the user align the Cardboard to the phone's screen.
  public bool EnableAlignmentMarker {
    get {
      return enableAlignmentMarker;
    }
    set {
      if (value != enableAlignmentMarker && device != null) {
        device.SetAlignmentMarkerEnabled(value);
      }
      enableAlignmentMarker = value;
    }
  }
  [SerializeField]
  private bool enableAlignmentMarker = true;

  /// Enables or disables the Cardboard settings button.  It appears as a gear icon
  /// in the blank space between the stereo views.  The settings button opens the
  /// Google Cardboard app to allow the user to configure their individual settings
  /// and Cardboard headset parameters.
  public bool EnableSettingsButton {
    get {
      return enableSettingsButton;
    }
    set {
      if (value != enableSettingsButton && device != null) {
        device.SetSettingsButtonEnabled(value);
      }
      enableSettingsButton = value;
    }
  }
  [SerializeField]
  private bool enableSettingsButton = true;

  /// Display modes for the VR "Back Button".
  public enum BackButtonModes {
    Off,       /// Always off
    OnlyInVR,  /// On in VR Mode, otherwise off
    On         /// Always on
  }

  /// Whether to show the onscreen analog of the (Android) Back Button.
  public BackButtonModes BackButtonMode {
    get {
      return backButtonMode;
    }
    set {
      if (value != backButtonMode && device != null) {
        device.SetVRBackButtonEnabled(value != BackButtonModes.Off);
        device.SetShowVrBackButtonOnlyInVR(value == BackButtonModes.OnlyInVR);
      }
      backButtonMode = value;
    }
  }
  [SerializeField]
  private BackButtonModes backButtonMode = BackButtonModes.OnlyInVR;

  /// The native SDK will apply a neck offset to the head tracking, resulting in
  /// a more realistic model of a person's head position.  This control determines
  /// the scale factor of the offset.  To turn off the neck model, set it to 0, and
  /// to turn it all on, set to 1.  Intermediate values can be used to animate from
  /// on to off or vice versa.
  public float NeckModelScale {
    get {
      return neckModelScale;
    }
    set {
      value = Mathf.Clamp01(value);
      if (!Mathf.Approximately(value, neckModelScale) && device != null) {
        device.SetNeckModelScale(value);
      }
      neckModelScale = value;
    }
  }
  [SerializeField]
  private float neckModelScale = 0.0f;

  /// When enabled, drift in the gyro readings is estimated and removed.
  public bool AutoDriftCorrection {
    get {
      return autoDriftCorrection;
    }
    set {
      if (value != autoDriftCorrection && device != null) {
        device.SetAutoDriftCorrectionEnabled(value);
      }
      autoDriftCorrection = value;
    }
  }
  [SerializeField]
  private bool autoDriftCorrection = true;

  /// @cond
  public bool ElectronicDisplayStabilization {
    get {
      return electronicDisplayStabilization;
    }
    set {
      if (value != electronicDisplayStabilization && device != null) {
        device.SetElectronicDisplayStabilizationEnabled(value);
      }
      electronicDisplayStabilization = value;
    }
  }
  [SerializeField]
  private bool electronicDisplayStabilization = false;
  /// @endcond

#if UNITY_EDITOR
  /// Restores level head tilt in when playing in the Unity Editor after you
  /// release the Ctrl key.
  public bool autoUntiltHead = true;

  /// @cond
  /// Use unity remote as the input source.
  [HideInInspector]
  public bool UseUnityRemoteInput = false;
  /// @endcond

  /// The screen size to emulate when testing in the Unity Editor.
  public CardboardProfile.ScreenSizes ScreenSize {
    get {
      return screenSize;
    }
    set {
      if (value != screenSize) {
        screenSize = value;
        if (device != null) {
          device.UpdateScreenData();
        }
      }
    }
  }
  [SerializeField]
  private CardboardProfile.ScreenSizes screenSize = CardboardProfile.ScreenSizes.Nexus5;

  /// The device type to emulate when testing in the Unity Editor.
  public CardboardProfile.DeviceTypes DeviceType {
    get {
      return deviceType;
    }
    set {
      if (value != deviceType) {
        deviceType = value;
        if (device != null) {
          device.UpdateScreenData();
        }
      }
    }
  }
  [SerializeField]
  private CardboardProfile.DeviceTypes deviceType = CardboardProfile.DeviceTypes.CardboardMay2015;
#endif

  // The VR device that will be providing input data.
  private static BaseVRDevice device;

  /// Whether native distortion correction functionality is supported by the VR device.
  public bool NativeDistortionCorrectionSupported { get; private set; }

  /// Whether the VR device supports showing a native UI layer, for example for settings.
  public bool NativeUILayerSupported { get; private set; }

  /// Scales the resolution of the #StereoScreen.  Set to less than 1.0 to increase
  /// rendering speed while decreasing sharpness, or greater than 1.0 to do the
  /// opposite.
  public float StereoScreenScale {
    get {
      return stereoScreenScale;
    }
    set {
      value = Mathf.Clamp(value, 0.1f, 10.0f);  // Sanity.
      if (stereoScreenScale != value) {
        stereoScreenScale = value;
        StereoScreen = null;
      }
    }
  }
  [SerializeField]
  private float stereoScreenScale = 1;

  /// The texture that Unity renders the scene to.  After the frame has been rendered,
  /// this texture is drawn to the screen with a lens distortion correction effect.
  /// The texture size is based on the size of the screen, the lens distortion
  /// parameters, and the #StereoScreenScale factor.
  public RenderTexture StereoScreen {
    get {
      // Don't need it except for distortion correction.
      if (distortionCorrection == DistortionCorrectionMethod.None || !vrModeEnabled) {
        return null;
      }
      if (stereoScreen == null) {
        // Create on demand.
        StereoScreen = device.CreateStereoScreen();  // Note: uses set{}
      }
      return stereoScreen;
    }
    set {
      if (value == stereoScreen) {
        return;
      }
      if (stereoScreen != null) {
        stereoScreen.Release();
      }
      stereoScreen = value;
      if (OnStereoScreenChanged != null) {
        OnStereoScreenChanged(stereoScreen);
      }
    }
  }
  private static RenderTexture stereoScreen = null;

  /// A callback for notifications that the StereoScreen property has changed.
  public delegate void StereoScreenChangeDelegate(RenderTexture newStereoScreen);

  /// Emitted when the StereoScreen property has changed.
  public event StereoScreenChangeDelegate OnStereoScreenChanged;

  /// Describes the current device, including phone screen.
  public CardboardProfile Profile {
    get {
      return device.Profile;
    }
  }

  /// Distinguish the stereo eyes.
  public enum Eye {
    Left,   /// The left eye
    Right,  /// The right eye
    Center  /// The "center" eye (unused)
  }

  /// When retrieving the #Projection and #Viewport properties, specifies
  /// whether you want the values as seen through the Cardboard lenses (`Distorted`) or
  /// as if no lenses were present (`Undistorted`).
  public enum Distortion {
    Distorted,   /// Viewing through the lenses
    Undistorted  /// No lenses
  }

  /// The transformation of head from origin in the tracking system.
  public Pose3D HeadPose {
    get {
      return device.GetHeadPose();
    }
  }

  /// The transformation from head to eye.
  public Pose3D EyePose(Eye eye) {
    return device.GetEyePose(eye);
  }

  /// The projection matrix for a given eye.
  /// This matrix is an off-axis perspective projection with near and far
  /// clipping planes of 1m and 1000m, respectively.  The CardboardEye script
  /// takes care of adjusting the matrix for its particular camera.
  public Matrix4x4 Projection(Eye eye, Distortion distortion = Distortion.Distorted) {
    return device.GetProjection(eye, distortion);
  }

  /// The screen space viewport that the camera for the specified eye should render into.
  /// In the _Distorted_ case, this will be either the left or right half of the `StereoScreen`
  /// render texture.  In the _Undistorted_ case, it refers to the actual rectangle on the
  /// screen that the eye can see.
  public Rect Viewport(Eye eye, Distortion distortion = Distortion.Distorted) {
    return device.GetViewport(eye, distortion);
  }

  /// The distance range from the viewer in user-space meters where objects may be viewed
  /// comfortably in stereo.  If the center of interest falls outside this range, the stereo
  /// eye separation should be adjusted to keep the onscreen disparity within the limits set
  /// by this range.  StereoController will handle this if the _checkStereoComfort_ is
  /// enabled.
  public Vector2 ComfortableViewingRange {
    get {
      return defaultComfortableViewingRange;
    }
  }
  private readonly Vector2 defaultComfortableViewingRange = new Vector2(0.4f, 100000.0f);

  /// @cond
  // Optional.  Set to a URI obtained from the Google Cardboard profile generator at
  //   https://www.google.com/get/cardboard/viewerprofilegenerator/
  // Example: Cardboard I/O 2015 viewer profile
  //public Uri DefaultDeviceProfile = new Uri("http://google.com/cardboard/cfg?p=CgZHb29nbGUSEkNhcmRib2FyZCBJL08gMjAxNR0J-SA9JQHegj0qEAAAcEIAAHBCAABwQgAAcEJYADUpXA89OghX8as-YrENP1AAYAM");
  public Uri DefaultDeviceProfile = null;
  /// @endcond

  private void InitDevice() {
    if (device != null) {
      device.Destroy();
    }
    device = BaseVRDevice.GetDevice();
    device.Init();

    List<string> diagnostics = new List<string>();
    NativeDistortionCorrectionSupported = device.SupportsNativeDistortionCorrection(diagnostics);
    if (diagnostics.Count > 0) {
      Debug.LogWarning("Built-in distortion correction disabled. Causes: ["
                       + String.Join("; ", diagnostics.ToArray()) + "]");
    }
    diagnostics.Clear();
    NativeUILayerSupported = device.SupportsNativeUILayer(diagnostics);
    if (diagnostics.Count > 0) {
      Debug.LogWarning("Built-in UI layer disabled. Causes: ["
                       + String.Join("; ", diagnostics.ToArray()) + "]");
    }

    if (DefaultDeviceProfile != null) {
      device.SetDefaultDeviceProfile(DefaultDeviceProfile);
    }

    device.SetAlignmentMarkerEnabled(enableAlignmentMarker);
    device.SetSettingsButtonEnabled(enableSettingsButton);
    device.SetVRBackButtonEnabled(backButtonMode != BackButtonModes.Off);
    device.SetShowVrBackButtonOnlyInVR(backButtonMode == BackButtonModes.OnlyInVR);
    device.SetDistortionCorrectionEnabled(distortionCorrection == DistortionCorrectionMethod.Native
        && NativeDistortionCorrectionSupported);
    device.SetNeckModelScale(neckModelScale);
    device.SetAutoDriftCorrectionEnabled(autoDriftCorrection);
    device.SetElectronicDisplayStabilizationEnabled(electronicDisplayStabilization);

    device.SetVRModeEnabled(vrModeEnabled);

    device.UpdateScreenData();
  }

  /// @note Each scene load causes an OnDestroy of the current SDK, followed
  /// by and Awake of a new one.  That should not cause the underlying native
  /// code to hiccup.  Exception: developer may call Application.DontDestroyOnLoad
  /// on the SDK if they want it to survive across scene loads.
  void Awake() {
    if (sdk == null) {
      sdk = this;
    }
    if (sdk != this) {
      Debug.LogError("There must be only one Cardboard object in a scene.");
      UnityEngine.Object.DestroyImmediate(this);
      return;
    }
#if UNITY_IOS
    Application.targetFrameRate = 60;
#endif
    // Prevent the screen from dimming / sleeping
    Screen.sleepTimeout = SleepTimeout.NeverSleep;
    InitDevice();
    StereoScreen = null;
    AddCardboardCamera();
  }

  void Start() {
    UILayerEnabled = true;
  }

  void AddCardboardCamera() {
    var preRender = UnityEngine.Object.FindObjectOfType<CardboardPreRender>();
    if (preRender == null) {
      var go = new GameObject("PreRender", typeof(CardboardPreRender));
      go.SendMessage("Reset");
      go.transform.parent = transform;
    }
    var postRender = UnityEngine.Object.FindObjectOfType<CardboardPostRender>();
    if (postRender == null) {
      var go = new GameObject("PostRender", typeof(CardboardPostRender));
      go.SendMessage("Reset");
      go.transform.parent = transform;
    }
  }

  /// Emitted whenever a trigger occurs.
  public event Action OnTrigger;

  /// Emitted whenever the viewer is tilted on its side.
  public event Action OnTilt;

  /// Emitted whenever the app should respond to a possible change in the device viewer
  /// profile, that is, the QR code scanned by the user.
  public event Action OnProfileChange;

  /// Emitted whenever the user presses the "VR Back Button".
  public event Action OnBackButton;

  /// Whether the Cardboard trigger was pulled. True for exactly one complete frame
  /// after each pull.
  public bool Triggered { get; private set; }

  /// Whether the Cardboard viewer was tilted on its side. True for exactly one complete frame
  /// after each tilt.  Whether and how to respond to this event is up to the app.
  public bool Tilted { get; private set; }

  /// Whether the Cardboard device profile has possibly changed.  This is meant to indicate
  /// that a new QR code has been scanned, although currently it is actually set any time the
  /// application is unpaused, whether it was due to a profile change or not.  True for one
  /// frame.
  public bool ProfileChanged { get; private set; }

  /// Whether the user has pressed the "VR Back Button", which on Android should be treated the
  /// same as the normal system Back Button, although you can respond to either however you want
  /// in your app.
  public bool BackButtonPressed { get; private set; }

  // Only call device.UpdateState() once per frame.
  private int updatedToFrame = 0;

  /// Reads the latest tracking data from the phone.  This must be
  /// called before accessing any of the poses and matrices above.
  ///
  /// Multiple invocations per frame are OK:  Subsequent calls merely yield the
  /// cached results of the first call.  To minimize latency, it should be first
  /// called later in the frame (for example, in `LateUpdate`) if possible.
  public void UpdateState() {
    if (updatedToFrame != Time.frameCount) {
      updatedToFrame = Time.frameCount;
      device.UpdateState();

      if (device.profileChanged) {
        if (distortionCorrection != DistortionCorrectionMethod.Native
            && device.RequiresNativeDistortionCorrection()) {
          DistortionCorrection = DistortionCorrectionMethod.Native;
        }
        if (stereoScreen != null
            && device.ShouldRecreateStereoScreen(stereoScreen.width, stereoScreen.height)) {
          StereoScreen = null;
        }
      }

      DispatchEvents();
    }
  }

  private void DispatchEvents() {
    // Update flags first by copying from device and other inputs.
    Triggered = device.triggered || Input.GetMouseButtonDown(0);
    Tilted = device.tilted;
    ProfileChanged = device.profileChanged;
    BackButtonPressed = device.backButtonPressed || Input.GetKeyDown(KeyCode.Escape);
    // Reset device flags.
    device.triggered = false;
    device.tilted = false;
    device.profileChanged = false;
    device.backButtonPressed = false;
    // All flags updated.  Now emit events.
    if (Tilted && OnTilt != null) {
      OnTilt();
    }
    if (Triggered && OnTrigger != null) {
      OnTrigger();
    }
    if (ProfileChanged && OnProfileChange != null) {
      OnProfileChange();
    }
    if (BackButtonPressed && OnBackButton != null) {
      OnBackButton();
    }
  }

  /// Presents the #StereoScreen to the device for distortion correction and display.
  /// @note This function is only used if #DistortionCorrection is set to _Native_,
  /// and it only has an effect if the device supports it.
  public void PostRender(RenderTexture stereoScreen) {
    if (NativeDistortionCorrectionSupported && stereoScreen != null && stereoScreen.IsCreated()) {
      device.PostRender(stereoScreen);
    }
  }

  /// Resets the tracker so that the user's current direction becomes forward.
  public void Recenter() {
    device.Recenter();
  }

  /// Launch the device pairing and setup dialog.
  public void ShowSettingsDialog() {
    device.ShowSettingsDialog();
  }

  void OnEnable() {
#if UNITY_EDITOR
    // This can happen if you edit code while the editor is in Play mode.
    if (device == null) {
      InitDevice();
    }
#endif
    device.OnPause(false);
  }

  void OnDisable() {
    device.OnPause(true);
  }

  void OnApplicationPause(bool pause) {
    device.OnPause(pause);
  }

  void OnApplicationFocus(bool focus) {
    device.OnFocus(focus);
  }

  void OnLevelWasLoaded(int level) {
    device.OnLevelLoaded(level);
  }

  void OnApplicationQuit() {
    device.OnApplicationQuit();
  }

  void OnDestroy() {
    VRModeEnabled = false;
    UILayerEnabled = false;
    if (device != null) {
      device.Destroy();
    }
    if (sdk == this) {
      sdk = null;
    }
  }
}

// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using UnityEngine;
using System.Collections;
using System.Linq;

/// @ingroup Scripts
/// Controls a pair of CardboardEye objects that will render the stereo view
/// of the camera this script is attached to.
///
/// This script must be added to any camera that should render stereo when the app
/// is in VR Mode.  This includes picture-in-picture windows, whether their contents
/// are in stereo or not: the window itself must be twinned for stereo, regardless.
///
/// For each frame, StereoController decides whether to render via the camera it
/// is attached to (the _mono_ camera) or the stereo eyes that it controls (see
/// CardboardEye). You control this  decision for all cameras at once by setting
/// the value of Cardboard#VRModeEnabled.
///
/// For technical reasons, the mono camera remains enabled for the initial portion of
/// the frame.  It is disabled only when rendering begins in `OnPreCull()`, and is
/// reenabled again at the end of the frame.  This allows 3rd party scripts that use
/// `Camera.main`, for example, to refer the the mono camera even when VR Mode is
/// enabled.
///
/// At startup the script ensures it has a full stereo rig, which consists of two
/// child cameras with CardboardEye scripts attached, and a CardboardHead script
/// somewhere in the hierarchy of parents and children for head tracking.  The rig
/// is created if necessary, the CardboardHead being attached to the controller
/// itself.  The child camera settings are then cloned or updated from the mono
/// camera.
///
/// It is permissible for a StereoController to contain another StereoController
/// as a child.  In this case, a CardboardEye is controlled by its closest
/// StereoController parent.
///
/// The Inspector panel for this script includes a button _Update Stereo Cameras_.
/// This performs the same action as described above for startup, but in the Editor.
/// Use this to generate the rig if you intend to customize it.  This action is also
/// available via _Component -> Cardboard -> Update Stereo Cameras_ in the Editor’s
/// main menu, and in the context menu for the `Camera` component.
[RequireComponent(typeof(Camera))]
[AddComponentMenu("Cardboard/StereoController")]
public class StereoController : MonoBehaviour {
  /// Whether to draw directly to the output window (_true_), or to an offscreen buffer
  /// first and then blit (_false_). If you wish to use Deferred Rendering or any
  /// Image Effects in stereo, turn this option off.  A common symptom that indicates
  /// you should do so is when one of the eyes is spread across the entire screen.
  [Tooltip("Whether to draw directly to the output window (true), or " +
           "to an offscreen buffer first and then blit (false).  Image " +
           " Effects and Deferred Lighting may only work if set to false.")]
  public bool directRender = true;

  /// When enabled, UpdateStereoValues() is called every frame to keep the stereo cameras
  /// completely synchronized with both the mono camera and the device profile.  When
  /// disabled, you must call UpdateStereoValues() whenever you make a change to the mono
  /// camera that should be mirrored to the stereo cameras.  Changes to the device profile
  /// are handled automatically.  It is better for performance to leave this option disabled
  /// whenever possible.  Good use cases for enabling it are when animating values on the
  /// mono camera (like background color), or during development to debug camera synchronization
  /// issues.
  [Tooltip("When enabled, UpdateStereoValues() is called every frame to keep the stereo cameras " +
           "completely synchronized with both the mono camera and the device profile. It is " +
           "better for performance to leave this option disabled whenever possible.")]
  public bool keepStereoUpdated = false;

  /// Adjusts the level of stereopsis for this stereo rig.
  /// @note This parameter is not the virtual size of the head -- use a scale
  /// on the head game object for that.  Instead, it is a control on eye vergence,
  /// or rather, how cross-eyed or not the stereo rig is.  Set to 0 to turn
  /// off stereo in this rig independently of any others.
  [Tooltip("Set the stereo level for this camera.")]
  [Range(0,1)]
  public float stereoMultiplier = 1.0f;


  /// The stereo cameras by default use the actual optical FOV of the Cardboard device,
  /// because otherwise the match between head motion and scene motion is broken, which
  /// impacts the virtual reality effect.  However, in some cases it is desirable to
  /// adjust the FOV anyway, for special effects or artistic reasons.  But in no case
  /// should the FOV be allowed to remain very different from the true optical FOV for
  /// very long, or users will experience discomfort.
  ///
  /// This value determines how much to match the mono camera's field of view.  This is
  /// a fraction: 0 means no matching, 1 means full matching, and values in between are
  /// compromises.  Reasons for not matching 100% would include preserving some VR-ness,
  /// and that due to the lens distortion the edges of the view are not as easily seen as
  /// when the phone is not in VR-mode.
  ///
  /// Another use for this variable is to preserve scene composition against differences
  /// in the optical FOV of various Cardboard models.  In all cases, this value simply
  /// lets the mono camera have some control over the scene in VR mode, like it does in
  /// non-VR mode.
  [Tooltip("How much to adjust the stereo field of view to match this camera.")]
  [Range(0,1)]
  public float matchMonoFOV = 0;

  /// Determines the method by which the stereo cameras' FOVs are matched to the mono
  /// camera's FOV (assuming #matchMonoFOV is not 0).  The default is to move the stereo
  /// cameras (#matchByZoom = 0), with the option to instead do a simple camera zoom
  /// (#matchByZoom = 1).  In-between values yield a mix of the two behaviors.
  ///
  /// It is not recommended to use simple zooming for typical scene composition, as it
  /// conflicts with the VR need to match the user's head motion with the corresponding
  /// scene motion.  This should be reserved for special effects such as when the player
  /// views the scene through a telescope or other magnifier (and thus the player knows
  /// that VR is going to be affected), or similar situations.
  ///
  /// @note Matching by moving the eyes requires that the #centerOfInterest object
  /// be non-null, or there will be no effect.
  [Tooltip("Whether to adjust FOV by moving the eyes (0) or simply zooming (1).")]
  [Range(0,1)]
  public float matchByZoom = 0;

  /// Matching the mono camera's field of view in stereo by moving the eyes requires
  /// a designated "center of interest".  This is either a point in space (an empty
  /// gameobject) you place in the scene as a sort of "3D cursor", or an actual scene
  /// entity which the player is likely to be focussed on.
  ///
  /// The FOV adjustment is done by moving the eyes toward or away from the COI
  /// so that it appears to have the same size on screen as it would in the mono
  /// camera.  This is disabled if the COI is null.
  [Tooltip("Object or point where field of view matching is done.")]
  public Transform centerOfInterest;

  /// The #centerOfInterest is generally meant to be just a point in space, like a 3D cursor.
  /// Occasionally, you will want it to be an actual object with size.  Set this
  /// to the approximate radius of the object to help the FOV-matching code
  /// compensate for the object's horizon when it is close to the camera.
  [Tooltip("If COI is an object, its approximate size.")]
  public float radiusOfInterest = 0;

  /// If true, check that the #centerOfInterest is between the min and max comfortable
  /// viewing distances (see Cardboard.cs), or else adjust the stereo multiplier to
  /// compensate.  If the COI has a radius, then the near side is checked.  COI must
  /// be non-null for this setting to have any effect.
  [Tooltip("Adjust stereo level when COI gets too close or too far.")]
  public bool checkStereoComfort = true;

  /// Smoothes the changes to the stereo camera FOV and position based on #centerOfInterest
  /// and #checkStereoComfort.
  [Tooltip("Smoothing factor to use when adjusting stereo for COI and comfort.")]
  [Range(0,1)]
  public float stereoAdjustSmoothing = 0.1f;

  /// For picture-in-picture cameras that don't fill the entire screen,
  /// set the virtual depth of the window itself.  A value of 0 means
  /// zero parallax, which is fairly close.  A value of 1 means "full"
  /// parallax, which is equal to the interpupillary distance and equates
  /// to an infinitely distant window.  This does not affect the actual
  /// screen size of the the window (in pixels), only the stereo separation
  /// of the left and right images.
  [Tooltip("Adjust the virtual depth of this camera's window (picture-in-picture only).")]
  [Range(0,1)]
  public float screenParallax = 0;

  /// For picture-in-picture cameras, move the window away from the edges
  /// in VR Mode to make it easier to see.  The optics of HMDs make the screen
  /// edges hard to see sometimes, so you can use this to keep the PIP visible
  /// whether in VR Mode or not.  The x value is the fraction of the screen along
  /// either side to pad.
  [Tooltip("Move the camera window horizontally towards the center of the screen (PIP only).")]
  [Range(0,1)]
  public float stereoPaddingX = 0;

  /// For picture-in-picture cameras, move the window away from the edges
  /// in VR Mode to make it easier to see.  The optics of HMDs make the screen
  /// edges hard to see sometimes, so you can use this to keep the PIP visible
  /// whether in VR Mode or not.  The y value is for the top and bottom of the screen to pad.
  [Tooltip("Move the camera window vertically towards the center of the screen (PIP only).")]
  [Range(0,1)]
  public float stereoPaddingY = 0;

  // Flags whether we rendered in stereo for this frame.
  private bool renderedStereo = false;

#if !UNITY_EDITOR
  // Cache for speed, except in editor (don't want to get out of sync with the scene).
  private CardboardEye[] eyes;
  private CardboardHead head;
#endif

  /// Returns an array of stereo cameras that are controlled by this instance of
  /// the script.
  /// @note This array is cached for speedier access.  Call
  /// InvalidateEyes if it is ever necessary to reset the cache.
  public CardboardEye[] Eyes {
    get {
#if UNITY_EDITOR
      CardboardEye[] eyes = null;  // Local variable rather than member, so as not to cache.
#endif
      if (eyes == null) {
        eyes = GetComponentsInChildren<CardboardEye>(true)
               .Where(eye => eye.Controller == this)
               .ToArray();
      }
      return eyes;
    }
  }

  /// Returns the nearest CardboardHead that affects our eyes.
  /// @note Cached for speed.  Call InvalidateEyes to clear the cache.
  public CardboardHead Head {
    get {
#if UNITY_EDITOR
      CardboardHead head = null;  // Local variable rather than member, so as not to cache.
#endif
      if (head == null) {
        head = Eyes.Select(eye => eye.Head).FirstOrDefault();
      }
      return head;
    }
  }

  /// Clear the cached array of CardboardEye children, as well as the CardboardHead that controls
  /// their gaze.
  /// @note Be sure to call this if you programmatically change the set of CardboardEye children
  /// managed by this StereoController.
  public void InvalidateEyes() {
#if !UNITY_EDITOR
    eyes = null;
    head = null;
#endif
  }

  /// Updates the stereo cameras from the mono camera every frame.  This includes all Camera
  /// component values such as background color, culling mask, viewport rect, and so on.  Also,
  /// it includes updating the viewport rect and projection matrix for side-by-side stereo, plus
  /// applying any adjustments for center of interest and stereo comfort.
  public void UpdateStereoValues() {
    CardboardEye[] eyes = Eyes;
    for (int i = 0, n = eyes.Length; i < n; i++) {
      eyes[i].UpdateStereoValues();
    }
  }

  public Camera cam { get; private set; }

  void Awake() {
    Cardboard.Create();
    cam = GetComponent<Camera>();
    AddStereoRig();
  }

  /// Helper routine for creation of a stereo rig.  Used by the
  /// custom editor for this class, or to build the rig at runtime.
  public void AddStereoRig() {
    // Simplistic test if rig already exists.
    // Note: Do not use Eyes property, because it caches the result before we have created the rig.
    var eyes = GetComponentsInChildren<CardboardEye>(true).Where(eye => eye.Controller == this);
    if (eyes.Any()) {
      return;
    }
    CreateEye(Cardboard.Eye.Left);
    CreateEye(Cardboard.Eye.Right);
    if (Head == null) {
      var head = gameObject.AddComponent<CardboardHead>();
      // Don't track position for dynamically added Head components, or else
      // you may unexpectedly find your camera pinned to the origin.
      head.trackPosition = false;
    }
  }

  // Helper routine for creation of a stereo eye.
  private void CreateEye(Cardboard.Eye eye) {
    string nm = name + (eye == Cardboard.Eye.Left ? " Left" : " Right");
    GameObject go = new GameObject(nm);
    go.transform.SetParent(transform, false);
    go.AddComponent<Camera>().enabled = false;
    var cardboardEye = go.AddComponent<CardboardEye>();
    cardboardEye.eye = eye;
    cardboardEye.CopyCameraAndMakeSideBySide(this);
  }

  /// Compute the position of one of the stereo eye cameras.  Accounts for both
  /// FOV matching and stereo comfort, if those features are enabled.  The input is
  /// the [1,1] entry of the eye camera's projection matrix, representing the vertical
  /// field of view, and the overall scale being applied to the Z axis.  Returns the
  /// position of the stereo eye camera in local coordinates.
  public Vector3 ComputeStereoEyePosition(Cardboard.Eye eye, float proj11, float zScale) {
    if (centerOfInterest == null || !centerOfInterest.gameObject.activeInHierarchy) {
      return Cardboard.SDK.EyePose(eye).Position * stereoMultiplier;
    }

    // Distance of COI relative to head.
    float distance = centerOfInterest != null ?
        (centerOfInterest.position - transform.position).magnitude : 0;

    // Size of the COI, clamped to [0..distance] for mathematical sanity in following equations.
    float radius = Mathf.Clamp(radiusOfInterest, 0, distance);

    // Move the eye so that COI has about the same size onscreen as in the mono camera FOV.
    // The radius affects the horizon location, which is where the screen-size matching has to
    // occur.
    float scale = proj11 / cam.projectionMatrix[1, 1];  // vertical FOV
    float offset =
        Mathf.Sqrt(radius * radius + (distance * distance - radius * radius) * scale * scale);
    float eyeOffset = (distance - offset) * Mathf.Clamp01(matchMonoFOV) / zScale;

    float ipdScale = stereoMultiplier;
    if (checkStereoComfort) {
      // Manage IPD scale based on the distance to the COI.
      float minComfort = Cardboard.SDK.ComfortableViewingRange.x;
      float maxComfort = Cardboard.SDK.ComfortableViewingRange.y;
      if (minComfort < maxComfort) {  // Sanity check.
        // If closer than the minimum comfort distance, IPD is scaled down.
        // If farther than the maximum comfort distance, IPD is scaled up.
        // The result is that parallax is clamped within a reasonable range.
        float minDistance = (distance - radius) / zScale - eyeOffset;
        ipdScale *= minDistance / Mathf.Clamp(minDistance, minComfort, maxComfort);
      }
    }

    return ipdScale * Cardboard.SDK.EyePose(eye).Position + eyeOffset * Vector3.forward;
  }

  void OnEnable() {
    StartCoroutine("EndOfFrame");
  }

  void OnDisable() {
    StopCoroutine("EndOfFrame");
  }

  void OnPreCull() {
    if (Cardboard.SDK.VRModeEnabled) {
      // Activate the eyes under our control.
      CardboardEye[] eyes = Eyes;
      for (int i = 0, n = eyes.Length; i < n; i++) {
        eyes[i].cam.enabled = true;
      }
      // Turn off the mono camera so it doesn't waste time rendering.  Remember to reenable.
      // @note The mono camera is left on from beginning of frame till now in order that other game
      // logic (e.g. referring to Camera.main) continues to work as expected.
      cam.enabled = false;
      renderedStereo = true;
    } else {
      Cardboard.SDK.UpdateState();
    }
  }

  IEnumerator EndOfFrame() {
    while (true) {
      // If *we* turned off the mono cam, turn it back on for next frame.
      if (renderedStereo) {
        cam.enabled = true;
        renderedStereo = false;
      }
      yield return new WaitForEndOfFrame();
    }
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值