/************************************************************************************
|
|
|
|
Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
|
|
|
See SampleFramework license.txt for license terms. Unless required by applicable law
|
|
or agreed to in writing, the sample code is provided “AS IS” WITHOUT WARRANTIES OR
|
|
CONDITIONS OF ANY KIND, either express or implied. See the license for specific
|
|
language governing permissions and limitations under the license.
|
|
|
|
************************************************************************************/
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using System;
|
|
|
|
namespace OculusSampleFramework
|
|
{
|
|
|
|
/// <summary>
|
|
/// The rendering methods swappable via radio buttons
|
|
/// </summary>
|
|
public enum EUiDisplayType
|
|
{
|
|
EUDT_WorldGeoQuad,
|
|
EUDT_OverlayQuad,
|
|
EUDT_None,
|
|
EUDT_MaxDislayTypes
|
|
}
|
|
|
|
/// <summary>
|
|
/// Usage: demonstrate how to use overlay layers for a paneled UI system
|
|
/// On Mobile, we support both Cylinder layer and Quad layer
|
|
/// Press any button: it will cycle [world geometry Quad]->[overlay layer Quad]->[world geometry cylinder]->[overlay layer cylinder]
|
|
/// On PC, only Quad layer is supported
|
|
/// Press any button: it will cycle [world geometry Quad]->[overlay layer Quad]
|
|
///
|
|
/// You should be able to observe sharper and less aliased image when switch from world geometry to overlay layer.
|
|
///
|
|
/// </summary>
|
|
public class OVROverlaySample : MonoBehaviour
|
|
{
|
|
|
|
bool inMenu;
|
|
|
|
/// <summary>
|
|
/// The string identifiers for DebugUI radio buttons
|
|
/// </summary>
|
|
const string ovrOverlayID = "OVROverlayID";
|
|
const string applicationID = "ApplicationID";
|
|
const string noneID = "NoneID";
|
|
|
|
/// <summary>
|
|
/// Toggle references
|
|
/// </summary>
|
|
Toggle applicationRadioButton;
|
|
Toggle noneRadioButton;
|
|
|
|
[Header("App vs Compositor Comparison Settings")]
|
|
/// <summary>
|
|
/// The main camera used to calculate reprojected OVROverlay quad
|
|
/// </summary>
|
|
public GameObject mainCamera;
|
|
|
|
/// <summary>
|
|
/// The camera used to render UI panels
|
|
/// </summary>
|
|
public GameObject uiCamera;
|
|
|
|
/// <summary>
|
|
/// The parents of grouped UI panels
|
|
/// </summary>
|
|
public GameObject uiGeoParent;
|
|
public GameObject worldspaceGeoParent;
|
|
|
|
/// <summary>
|
|
/// The OVROverlay component to pass the uiCamera rendered RT to
|
|
/// </summary>
|
|
public OVROverlay cameraRenderOverlay;
|
|
|
|
/// <summary>
|
|
/// The OVROverlay component displaying which rendering mode is active
|
|
/// </summary>
|
|
public OVROverlay renderingLabelOverlay;
|
|
|
|
/// <summary>
|
|
/// The quad textures to indicate the active rendering method
|
|
/// </summary>
|
|
public Texture applicationLabelTexture;
|
|
public Texture compositorLabelTexture;
|
|
|
|
/// <summary>
|
|
/// The resources & settings needed for the level loading simulation demo
|
|
/// </summary>
|
|
[Header("Level Loading Sim Settings")]
|
|
public GameObject prefabForLevelLoadSim;
|
|
public OVROverlay cubemapOverlay;
|
|
public OVROverlay loadingTextQuadOverlay;
|
|
public float distanceFromCamToLoadText;
|
|
public float cubeSpawnRadius;
|
|
public float heightBetweenItems;
|
|
public int numObjectsPerLevel;
|
|
public int numLevels;
|
|
public int numLoopsTrigger = 500000000;
|
|
List<GameObject> spawnedCubes = new List<GameObject>();
|
|
|
|
#region MonoBehaviour handler
|
|
|
|
void Start()
|
|
{
|
|
DebugUIBuilder.instance.AddLabel("OVROverlay Sample");
|
|
DebugUIBuilder.instance.AddDivider();
|
|
DebugUIBuilder.instance.AddLabel("Level Loading Example");
|
|
DebugUIBuilder.instance.AddButton("Simulate Level Load", TriggerLoad);
|
|
DebugUIBuilder.instance.AddButton("Destroy Cubes", TriggerUnload);
|
|
DebugUIBuilder.instance.AddDivider();
|
|
DebugUIBuilder.instance.AddLabel("OVROverlay vs. Application Render Comparison");
|
|
DebugUIBuilder.instance.AddRadio("OVROverlay", "group", delegate (Toggle t) { RadioPressed(ovrOverlayID, "group", t); }).GetComponentInChildren<Toggle>();
|
|
applicationRadioButton = DebugUIBuilder.instance.AddRadio("Application", "group", delegate (Toggle t) { RadioPressed(applicationID, "group", t); }).GetComponentInChildren<Toggle>();
|
|
noneRadioButton = DebugUIBuilder.instance.AddRadio("None", "group", delegate (Toggle t) { RadioPressed(noneID, "group", t); }).GetComponentInChildren<Toggle>();
|
|
|
|
DebugUIBuilder.instance.Show();
|
|
|
|
// Start with Overlay Quad
|
|
CameraAndRenderTargetSetup();
|
|
cameraRenderOverlay.enabled = true;
|
|
cameraRenderOverlay.currentOverlayShape = OVROverlay.OverlayShape.Quad;
|
|
spawnedCubes.Capacity = numObjectsPerLevel * numLevels;
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
// Switch ui display types
|
|
if (OVRInput.GetDown(OVRInput.Button.Two) || OVRInput.GetDown(OVRInput.Button.Start))
|
|
{
|
|
if (inMenu) DebugUIBuilder.instance.Hide();
|
|
else DebugUIBuilder.instance.Show();
|
|
inMenu = !inMenu;
|
|
}
|
|
|
|
// Trigger loading simulator via keyboard
|
|
if (Input.GetKeyDown(KeyCode.A))
|
|
{
|
|
TriggerLoad();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Private Functions
|
|
|
|
/// <summary>
|
|
/// Usage: Activate the world geometry and deactivate OVROverlay display
|
|
/// </summary>
|
|
void ActivateWorldGeo()
|
|
{
|
|
worldspaceGeoParent.SetActive(true);
|
|
uiGeoParent.SetActive(false);
|
|
uiCamera.SetActive(false);
|
|
cameraRenderOverlay.enabled = false;
|
|
renderingLabelOverlay.enabled = true;
|
|
renderingLabelOverlay.textures[0] = applicationLabelTexture;
|
|
Debug.Log("Switched to ActivateWorldGeo");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Usage: Activate OVROverlay display and deactivate the world geometry
|
|
/// </summary>
|
|
void ActivateOVROverlay()
|
|
{
|
|
worldspaceGeoParent.SetActive(false);
|
|
uiCamera.SetActive(true);
|
|
cameraRenderOverlay.enabled = true;
|
|
uiGeoParent.SetActive(true);
|
|
renderingLabelOverlay.enabled = true;
|
|
renderingLabelOverlay.textures[0] = compositorLabelTexture;
|
|
Debug.Log("Switched to ActivateOVROVerlay");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Usage: Deactivate both world geometry and OVROverlay display
|
|
/// </summary>
|
|
void ActivateNone()
|
|
{
|
|
worldspaceGeoParent.SetActive(false);
|
|
uiCamera.SetActive(false);
|
|
cameraRenderOverlay.enabled = false;
|
|
uiGeoParent.SetActive(false);
|
|
renderingLabelOverlay.enabled = false;
|
|
Debug.Log("Switched to ActivateNone");
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// This function is to simulate a level load event in Unity
|
|
/// The idea is to enable a cubemap overlay right before any action that will stall the main thread
|
|
/// This cubemap overlay can be combined with other OVROverlay objects, such as animated textures to indicate "Loading..."
|
|
/// </summary>
|
|
void TriggerLoad()
|
|
{
|
|
StartCoroutine(WaitforOVROverlay());
|
|
}
|
|
|
|
IEnumerator WaitforOVROverlay()
|
|
{
|
|
Transform camTransform = mainCamera.transform;
|
|
Transform uiTextOverlayTrasnform = loadingTextQuadOverlay.transform;
|
|
Vector3 newPos = camTransform.position + camTransform.forward * distanceFromCamToLoadText;
|
|
newPos.y = camTransform.position.y;
|
|
uiTextOverlayTrasnform.position = newPos;
|
|
cubemapOverlay.enabled = true;
|
|
loadingTextQuadOverlay.enabled = true;
|
|
noneRadioButton.isOn = true;
|
|
yield return new WaitForSeconds(0.1f);
|
|
ClearObjects();
|
|
SimulateLevelLoad();
|
|
cubemapOverlay.enabled = false;
|
|
loadingTextQuadOverlay.enabled = false;
|
|
yield return null;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Usage: Destroy all loaded resources and switch back to world geometry rendering mode.
|
|
/// </summary>
|
|
void TriggerUnload()
|
|
{
|
|
ClearObjects();
|
|
applicationRadioButton.isOn = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Usage: Recreate UI render target according overlay type and overlay size
|
|
/// </summary>
|
|
void CameraAndRenderTargetSetup()
|
|
{
|
|
float overlayWidth = cameraRenderOverlay.transform.localScale.x;
|
|
float overlayHeight = cameraRenderOverlay.transform.localScale.y;
|
|
float overlayRadius = cameraRenderOverlay.transform.localScale.z;
|
|
|
|
#if UNITY_ANDROID
|
|
// Gear VR display panel resolution
|
|
float hmdPanelResWidth = 2560;
|
|
float hmdPanelResHeight = 1440;
|
|
#else
|
|
// Rift display panel resolution
|
|
float hmdPanelResWidth = 2160;
|
|
float hmdPanelResHeight = 1200;
|
|
#endif
|
|
|
|
float singleEyeScreenPhysicalResX = hmdPanelResWidth * 0.5f;
|
|
float singleEyeScreenPhysicalResY = hmdPanelResHeight;
|
|
|
|
// Calculate RT Height
|
|
// screenSizeYInWorld : how much world unity the full screen can cover at overlayQuad's location vertically
|
|
// pixelDensityY: pixels / world unit ( meter )
|
|
|
|
float halfFovY = mainCamera.GetComponent<Camera>().fieldOfView / 2;
|
|
float screenSizeYInWorld = 2 * overlayRadius * Mathf.Tan(Mathf.Deg2Rad * halfFovY);
|
|
float pixelDensityYPerWorldUnit = singleEyeScreenPhysicalResY / screenSizeYInWorld;
|
|
float renderTargetHeight = pixelDensityYPerWorldUnit * overlayWidth;
|
|
|
|
// Calculate RT width
|
|
float renderTargetWidth = 0.0f;
|
|
|
|
// screenSizeXInWorld : how much world unity the full screen can cover at overlayQuad's location horizontally
|
|
// pixelDensityY: pixels / world unit ( meter )
|
|
|
|
float screenSizeXInWorld = screenSizeYInWorld * mainCamera.GetComponent<Camera>().aspect;
|
|
float pixelDensityXPerWorldUnit = singleEyeScreenPhysicalResX / screenSizeXInWorld;
|
|
renderTargetWidth = pixelDensityXPerWorldUnit * overlayWidth;
|
|
|
|
// Compute the orthographic size for the camera
|
|
float orthographicSize = overlayHeight / 2.0f;
|
|
float orthoCameraAspect = overlayWidth / overlayHeight;
|
|
uiCamera.GetComponent<Camera>().orthographicSize = orthographicSize;
|
|
uiCamera.GetComponent<Camera>().aspect = orthoCameraAspect;
|
|
|
|
if (uiCamera.GetComponent<Camera>().targetTexture != null)
|
|
uiCamera.GetComponent<Camera>().targetTexture.Release();
|
|
|
|
RenderTexture overlayRT = new RenderTexture(
|
|
(int)renderTargetWidth * 2,
|
|
(int)renderTargetHeight * 2,
|
|
0,
|
|
RenderTextureFormat.ARGB32,
|
|
RenderTextureReadWrite.sRGB);
|
|
Debug.Log("Created RT of resolution w: " + renderTargetWidth + " and h: " + renderTargetHeight);
|
|
|
|
overlayRT.hideFlags = HideFlags.DontSave;
|
|
overlayRT.useMipMap = true;
|
|
overlayRT.filterMode = FilterMode.Trilinear;
|
|
overlayRT.anisoLevel = 4;
|
|
#if UNITY_5_5_OR_NEWER
|
|
overlayRT.autoGenerateMips = true;
|
|
#else
|
|
overlayRT.generateMips = true;
|
|
#endif
|
|
uiCamera.GetComponent<Camera>().targetTexture = overlayRT;
|
|
|
|
cameraRenderOverlay.textures[0] = overlayRT;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Usage: block main thread with an empty for loop and generate a bunch of cubes around the player.
|
|
/// </summary>
|
|
void SimulateLevelLoad()
|
|
{
|
|
int numToPrint = 0;
|
|
for (int p = 0; p < numLoopsTrigger; p++)
|
|
{
|
|
numToPrint++;
|
|
}
|
|
Debug.Log("Finished " + numToPrint + " Loops");
|
|
Vector3 playerPos = mainCamera.transform.position;
|
|
playerPos.y = 0.5f;
|
|
// Generate a bunch of blocks, "blocking" the mainthread ;)
|
|
for (int j = 0; j < numLevels; j++)
|
|
{
|
|
for (var i = 0; i < numObjectsPerLevel; i++)
|
|
{
|
|
var angle = i * Mathf.PI * 2 / numObjectsPerLevel;
|
|
float stagger = (i % 2 == 0) ? 1.5f : 1.0f;
|
|
var pos = new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * cubeSpawnRadius * stagger;
|
|
pos.y = j * heightBetweenItems;
|
|
var newInst = Instantiate(prefabForLevelLoadSim, pos + playerPos, Quaternion.identity);
|
|
var newObjTransform = newInst.transform;
|
|
newObjTransform.LookAt(playerPos);
|
|
Vector3 newAngle = newObjTransform.rotation.eulerAngles;
|
|
newAngle.x = 0.0f;
|
|
newObjTransform.rotation = Quaternion.Euler(newAngle);
|
|
spawnedCubes.Add(newInst);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Usage: destroy all created cubes and garbage collect.
|
|
/// </summary>
|
|
void ClearObjects()
|
|
{
|
|
for (int i = 0; i < spawnedCubes.Count; i++)
|
|
{
|
|
DestroyImmediate(spawnedCubes[i]);
|
|
}
|
|
spawnedCubes.Clear();
|
|
GC.Collect();
|
|
}
|
|
#endregion
|
|
|
|
#region Debug UI Handlers
|
|
|
|
/// <summary>
|
|
/// Usage: radio button handler.
|
|
/// </summary>
|
|
public void RadioPressed(string radioLabel, string group, Toggle t)
|
|
{
|
|
if (string.Compare(radioLabel, ovrOverlayID) == 0)
|
|
{
|
|
ActivateOVROverlay();
|
|
}
|
|
else if (string.Compare(radioLabel, applicationID) == 0)
|
|
{
|
|
ActivateWorldGeo();
|
|
}
|
|
else if (string.Compare(radioLabel, noneID) == 0)
|
|
{
|
|
ActivateNone();
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|