/// OSVR-Unity Connection /// /// http://sensics.com/osvr /// /// /// Copyright 2015 Sensics, Inc. /// /// 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. /// /// /// Author: Greg Aring /// Email: greg@sensics.com /// using UnityEngine; using UnityEngine.Rendering; using System.Collections; using System; namespace OSVR { namespace Unity { //*This class is responsible for creating stereo rendering in a scene, and updating viewing parameters // throughout a scene's lifecycle. // The number of viewers, eyes, and surfaces, as well as viewports, projection matrices,and distortion // paramerters are obtained from OSVR via ClientKit. // // DisplayController creates VRViewers and VREyes as children. Although VRViewers and VREyes are siblings // in the scene hierarchy, conceptually VREyes are indeed children of VRViewers. The reason they are siblings // in the Unity scene is because GetViewerEyePose(...) returns a pose relative to world space, not head space. // // In this implementation, we are assuming that there is exactly one viewer and one surface per eye. //*/ public class DisplayController : MonoBehaviour { public const uint NUM_VIEWERS = 1; private const int TARGET_FRAME_RATE = 60; //@todo get from OSVR private ClientKit _clientKit; private OSVR.ClientKit.DisplayConfig _displayConfig; private VRViewer[] _viewers; private uint _viewerCount; private bool _displayConfigInitialized = false; private uint _totalDisplayWidth; private uint _totalSurfaceHeight; private bool _osvrClientKitError = false; //variables for controlling use of osvrUnityRenderingPlugin.dll which enables DirectMode private OsvrRenderManager _renderManager; private bool _useRenderManager = false; //requires Unity 5.2+ and RenderManager configured osvr server public bool UseRenderManager { get { return _useRenderManager; } set { _useRenderManager = value; } } public OSVR.ClientKit.DisplayConfig DisplayConfig { get { return _displayConfig; } set { _displayConfig = value; } } public VRViewer[] Viewers { get { return _viewers; } } public uint ViewerCount { get { return _viewerCount; } } public OsvrRenderManager RenderManager { get { return _renderManager; } } [Tooltip("Renders an extra camera to show what the HMD user sees while in Direct Mode. Comes at a framerate cost until this feature becomes part of RenderManager.")] public bool showDirectModePreview = false; //should the monitor show what the user sees in the HMD? public uint TotalDisplayWidth { get { return _totalDisplayWidth; } set { _totalDisplayWidth = value; } } public uint TotalDisplayHeight { get { return _totalSurfaceHeight; } set { _totalSurfaceHeight = value; } } void Start() { _clientKit = ClientKit.instance; if (_clientKit == null) { Debug.LogError("[OSVR-Unity] DisplayController requires a ClientKit object in the scene."); } SetupApplicationSettings(); } void SetupApplicationSettings() { //VR should never timeout the screen: Screen.sleepTimeout = SleepTimeout.NeverSleep; //Set the framerate //@todo get this value from OSVR, not a const value //Performance note: Developers should try setting Time.fixedTimestep to 1/Application.targetFrameRate //Application.targetFrameRate = TARGET_FRAME_RATE; } // Setup RenderManager for DirectMode or non-DirectMode rendering. // Checks to make sure Unity version and Graphics API are supported, // and that a RenderManager config file is being used. void SetupRenderManager() { //check if we are configured to use RenderManager or not string renderManagerPath = _clientKit.context.getStringParameter("/renderManagerConfig"); _useRenderManager = !(renderManagerPath == null || renderManagerPath.Equals("")); if (_useRenderManager) { //found a RenderManager config _renderManager = GameObject.FindObjectOfType(); if (_renderManager == null) { GameObject renderManagerGameObject = new GameObject("RenderManager"); //add a RenderManager component _renderManager = renderManagerGameObject.AddComponent(); } //check to make sure Unity version and Graphics API are supported bool supportsRenderManager = _renderManager.IsRenderManagerSupported(); _useRenderManager = supportsRenderManager; if (!_useRenderManager) { Debug.LogError("[OSVR-Unity] RenderManager config found but RenderManager is not supported."); Destroy(_renderManager); } else { // attempt to create a RenderManager in the plugin int result = _renderManager.InitRenderManager(); if (result != 0) { Debug.LogError("[OSVR-Unity] Failed to create RenderManager."); _useRenderManager = false; } } } else { Debug.Log("[OSVR-Unity] RenderManager config not detected. Using normal Unity rendering path."); } } // Get a DisplayConfig object from the server via ClientKit. // Setup stereo rendering with DisplayConfig data. void SetupDisplay() { //get the DisplayConfig object from ClientKit if (_clientKit == null || _clientKit.context == null) { if (!_osvrClientKitError) { Debug.LogError("[OSVR-Unity] ClientContext is null. Can't setup display."); _osvrClientKitError = true; } return; } _displayConfig = _clientKit.context.GetDisplayConfig(); if (_displayConfig == null) { return; } _displayConfigInitialized = true; SetupRenderManager(); //get the number of viewers, bail if there isn't exactly one viewer for now _viewerCount = _displayConfig.GetNumViewers(); if (_viewerCount != 1) { Debug.LogError("[OSVR-Unity] " + _viewerCount + " viewers found, but this implementation requires exactly one viewer."); return; } //Set Unity player resolution SetResolution(); //create scene objects CreateHeadAndEyes(); //create RenderBuffers in RenderManager if(UseRenderManager && RenderManager != null) { RenderManager.ConstructBuffers(); } SetRenderParams(); } //Set RenderManager rendering parameters: near and far clip plane distance and IPD private void SetRenderParams() { if (UseRenderManager && RenderManager != null) { RenderManager.SetNearClippingPlaneDistance(Camera.main.nearClipPlane); RenderManager.SetFarClippingPlaneDistance(Camera.main.farClipPlane); //could set IPD as well } } //Set Resolution of the Unity game window based on total surface width private void SetResolution() { TotalDisplayWidth = 0; //add up the width of each eye TotalDisplayHeight = 0; //don't add up heights int numDisplayInputs = DisplayConfig.GetNumDisplayInputs(); //for each display for (int i = 0; i < numDisplayInputs; i++) { OSVR.ClientKit.DisplayDimensions surfaceDisplayDimensions = DisplayConfig.GetDisplayDimensions((byte)i); TotalDisplayWidth += (uint)surfaceDisplayDimensions.Width; //add up the width of each eye TotalDisplayHeight = (uint)surfaceDisplayDimensions.Height; //store the height -- this shouldn't change } //Set the resolution. Don't force fullscreen if we have multiple display inputs //We only need to do this if we aren't using RenderManager, because it adjusts the window size for us //@todo figure out why this causes problems with direct mode, perhaps overfill factor? if (numDisplayInputs > 1 && !UseRenderManager) { Screen.SetResolution((int)TotalDisplayWidth, (int)TotalDisplayHeight, false); } } // Creates a head and eyes as configured in clientKit // Viewers and Eyes are siblings, children of DisplayController // Each eye has one child Surface which has a camera private void CreateHeadAndEyes() { /* ASSUME ONE VIEWER */ // Create VRViewers, only one in this implementation _viewerCount = (uint)_displayConfig.GetNumViewers(); if (_viewerCount != NUM_VIEWERS) { Debug.LogError("[OSVR-Unity] " + _viewerCount + " viewers detected. This implementation supports exactly one viewer."); return; } _viewers = new VRViewer[_viewerCount]; uint viewerIndex = 0; //Check if there are already VRViewers in the scene. //If so, create eyes for them. VRViewer[] viewersInScene = FindObjectsOfType(); if (viewersInScene != null && viewersInScene.Length > 0) { for (viewerIndex = 0; viewerIndex < viewersInScene.Length; viewerIndex++) { VRViewer viewer = viewersInScene[viewerIndex]; // get the VRViewer gameobject GameObject vrViewer = viewer.gameObject; vrViewer.name = "VRViewer" + viewerIndex; //change its name to VRViewer0 //@todo optionally add components if (vrViewer.GetComponent() == null) { vrViewer.AddComponent(); //add an audio listener } viewer.DisplayController = this; //pass DisplayController to Viewers viewer.ViewerIndex = viewerIndex; //set the viewer's index vrViewer.transform.parent = this.transform; //child of DisplayController vrViewer.transform.localPosition = Vector3.zero; _viewers[viewerIndex] = viewer; if (viewerIndex == 0) { vrViewer.tag = "MainCamera"; //set the MainCamera tag for the first Viewer } // create Viewer's VREyes uint eyeCount = (uint)_displayConfig.GetNumEyesForViewer(viewerIndex); //get the number of eyes for this viewer viewer.CreateEyes(eyeCount); } } // loop through viewers because at some point we could support multiple viewers // but this implementation currently supports exactly one for (; viewerIndex < _viewerCount; viewerIndex++) { // create a VRViewer GameObject vrViewer = new GameObject("VRViewer" + viewerIndex); if (vrViewer.GetComponent() == null) { vrViewer.AddComponent(); //add an audio listener } VRViewer vrViewerComponent = vrViewer.AddComponent(); vrViewerComponent.DisplayController = this; //pass DisplayController to Viewers vrViewerComponent.ViewerIndex = viewerIndex; //set the viewer's index vrViewer.transform.parent = this.transform; //child of DisplayController vrViewer.transform.localPosition = Vector3.zero; _viewers[viewerIndex] = vrViewerComponent; vrViewer.tag = "MainCamera"; // create Viewer's VREyes uint eyeCount = (uint)_displayConfig.GetNumEyesForViewer(viewerIndex); //get the number of eyes for this viewer vrViewerComponent.CreateEyes(eyeCount); } } void Update() { // sometimes it takes a few frames to get a DisplayConfig from ClientKit // keep trying until we have initialized if (!_displayConfigInitialized) { SetupDisplay(); } } //helper method for updating the client context public void UpdateClient() { _clientKit.context.update(); } public bool CheckDisplayStartup() { return DisplayConfig != null && _displayConfigInitialized && DisplayConfig.CheckDisplayStartup(); } public void ExitRenderManager() { if (UseRenderManager && RenderManager != null) { RenderManager.ExitRenderManager(); } } } } }