374 lines
14 KiB
C#
374 lines
14 KiB
C#
|
using UnityEngine;
|
||
|
using UnityEngine.Rendering;
|
||
|
|
||
|
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
|
||
|
|
||
|
public class OVRSandwichComposition : OVRCameraComposition
|
||
|
{
|
||
|
public float frameRealtime;
|
||
|
|
||
|
public Camera fgCamera;
|
||
|
public Camera bgCamera;
|
||
|
|
||
|
public class HistoryRecord
|
||
|
{
|
||
|
public float timestamp = float.MinValue;
|
||
|
public RenderTexture fgRenderTexture;
|
||
|
public RenderTexture bgRenderTexture;
|
||
|
public RenderTexture boundaryMeshMaskTexture;
|
||
|
}
|
||
|
|
||
|
public readonly int historyRecordCount = 8; // enough to compensate 88ms latency @ 90 fps
|
||
|
public readonly HistoryRecord[] historyRecordArray;
|
||
|
public int historyRecordCursorIndex = 0;
|
||
|
|
||
|
public GameObject cameraProxyPlane;
|
||
|
|
||
|
public Camera compositionCamera;
|
||
|
public OVRSandwichCompositionManager compositionManager;
|
||
|
|
||
|
private int _cameraFramePlaneLayer = -1;
|
||
|
|
||
|
// find an unnamed layer between 24..29
|
||
|
public int cameraFramePlaneLayer
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (_cameraFramePlaneLayer < 0)
|
||
|
{
|
||
|
for (int i=24; i<=29; ++i)
|
||
|
{
|
||
|
string layerName = LayerMask.LayerToName(i);
|
||
|
if (layerName == null || layerName.Length == 0)
|
||
|
{
|
||
|
_cameraFramePlaneLayer = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (_cameraFramePlaneLayer == -1)
|
||
|
{
|
||
|
Debug.LogWarning("Unable to find an unnamed layer between 24 and 29.");
|
||
|
_cameraFramePlaneLayer = 25;
|
||
|
}
|
||
|
Debug.LogFormat("Set the CameraFramePlaneLayer in SandwichComposition to {0}. Please do NOT put any other gameobject in this layer.", _cameraFramePlaneLayer);
|
||
|
}
|
||
|
return _cameraFramePlaneLayer;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override OVRManager.CompositionMethod CompositionMethod() { return OVRManager.CompositionMethod.Sandwich; }
|
||
|
|
||
|
public OVRSandwichComposition(GameObject parentObject, Camera mainCamera, OVRManager.CameraDevice cameraDevice, bool useDynamicLighting, OVRManager.DepthQuality depthQuality)
|
||
|
: base(cameraDevice, useDynamicLighting, depthQuality)
|
||
|
{
|
||
|
frameRealtime = Time.realtimeSinceStartup;
|
||
|
|
||
|
historyRecordCount = OVRManager.instance.sandwichCompositionBufferedFrames;
|
||
|
if (historyRecordCount < 1)
|
||
|
{
|
||
|
Debug.LogWarning("Invalid sandwichCompositionBufferedFrames in OVRManager. It should be at least 1");
|
||
|
historyRecordCount = 1;
|
||
|
}
|
||
|
if (historyRecordCount > 16)
|
||
|
{
|
||
|
Debug.LogWarning("The value of sandwichCompositionBufferedFrames in OVRManager is too big. It would consume a lot of memory. It has been override to 16");
|
||
|
historyRecordCount = 16;
|
||
|
}
|
||
|
historyRecordArray = new HistoryRecord[historyRecordCount];
|
||
|
for (int i=0; i<historyRecordCount; ++i)
|
||
|
{
|
||
|
historyRecordArray[i] = new HistoryRecord();
|
||
|
}
|
||
|
historyRecordCursorIndex = 0;
|
||
|
|
||
|
GameObject fgObject = new GameObject("MRSandwichForegroundCamera");
|
||
|
fgObject.transform.parent = parentObject.transform;
|
||
|
fgCamera = fgObject.AddComponent<Camera>();
|
||
|
fgCamera.depth = 200;
|
||
|
fgCamera.clearFlags = CameraClearFlags.SolidColor;
|
||
|
fgCamera.backgroundColor = Color.clear;
|
||
|
fgCamera.cullingMask = mainCamera.cullingMask & (~OVRManager.instance.extraHiddenLayers);
|
||
|
fgCamera.nearClipPlane = mainCamera.nearClipPlane;
|
||
|
fgCamera.farClipPlane = mainCamera.farClipPlane;
|
||
|
|
||
|
GameObject bgObject = new GameObject("MRSandwichBackgroundCamera");
|
||
|
bgObject.transform.parent = parentObject.transform;
|
||
|
bgCamera = bgObject.AddComponent<Camera>();
|
||
|
bgCamera.depth = 100;
|
||
|
bgCamera.clearFlags = mainCamera.clearFlags;
|
||
|
bgCamera.backgroundColor = mainCamera.backgroundColor;
|
||
|
bgCamera.cullingMask = mainCamera.cullingMask & (~OVRManager.instance.extraHiddenLayers);
|
||
|
bgCamera.nearClipPlane = mainCamera.nearClipPlane;
|
||
|
bgCamera.farClipPlane = mainCamera.farClipPlane;
|
||
|
|
||
|
// Create cameraProxyPlane for clipping
|
||
|
Debug.Assert(cameraProxyPlane == null);
|
||
|
cameraProxyPlane = GameObject.CreatePrimitive(PrimitiveType.Quad);
|
||
|
cameraProxyPlane.name = "MRProxyClipPlane";
|
||
|
cameraProxyPlane.transform.parent = parentObject.transform;
|
||
|
cameraProxyPlane.GetComponent<Collider>().enabled = false;
|
||
|
cameraProxyPlane.GetComponent<MeshRenderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
||
|
Material clipMaterial = new Material(Shader.Find("Oculus/OVRMRClipPlane"));
|
||
|
cameraProxyPlane.GetComponent<MeshRenderer>().material = clipMaterial;
|
||
|
clipMaterial.SetColor("_Color", Color.clear);
|
||
|
clipMaterial.SetFloat("_Visible", 0.0f);
|
||
|
cameraProxyPlane.transform.localScale = new Vector3(1000, 1000, 1000);
|
||
|
cameraProxyPlane.SetActive(true);
|
||
|
OVRMRForegroundCameraManager foregroundCameraManager = fgCamera.gameObject.AddComponent<OVRMRForegroundCameraManager>();
|
||
|
foregroundCameraManager.clipPlaneGameObj = cameraProxyPlane;
|
||
|
|
||
|
GameObject compositionCameraObject = new GameObject("MRSandwichCaptureCamera");
|
||
|
compositionCameraObject.transform.parent = parentObject.transform;
|
||
|
compositionCamera = compositionCameraObject.AddComponent<Camera>();
|
||
|
compositionCamera.stereoTargetEye = StereoTargetEyeMask.None;
|
||
|
compositionCamera.depth = float.MaxValue;
|
||
|
compositionCamera.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
|
||
|
compositionCamera.clearFlags = CameraClearFlags.Depth;
|
||
|
compositionCamera.backgroundColor = mainCamera.backgroundColor;
|
||
|
compositionCamera.cullingMask = 1 << cameraFramePlaneLayer;
|
||
|
compositionCamera.nearClipPlane = mainCamera.nearClipPlane;
|
||
|
compositionCamera.farClipPlane = mainCamera.farClipPlane;
|
||
|
|
||
|
if (!hasCameraDeviceOpened)
|
||
|
{
|
||
|
Debug.LogError("Unable to open camera device " + cameraDevice);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Debug.Log("SandwichComposition activated : useDynamicLighting " + (useDynamicLighting ? "ON" : "OFF"));
|
||
|
CreateCameraFramePlaneObject(parentObject, compositionCamera, useDynamicLighting);
|
||
|
cameraFramePlaneObject.layer = cameraFramePlaneLayer;
|
||
|
RefreshRenderTextures(mainCamera);
|
||
|
compositionManager = compositionCamera.gameObject.AddComponent<OVRSandwichCompositionManager>();
|
||
|
compositionManager.fgTexture = historyRecordArray[historyRecordCursorIndex].fgRenderTexture;
|
||
|
compositionManager.bgTexture = historyRecordArray[historyRecordCursorIndex].bgRenderTexture;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void Update(Camera mainCamera)
|
||
|
{
|
||
|
if (!hasCameraDeviceOpened)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
frameRealtime = Time.realtimeSinceStartup;
|
||
|
|
||
|
++historyRecordCursorIndex;
|
||
|
if (historyRecordCursorIndex >= historyRecordCount)
|
||
|
{
|
||
|
historyRecordCursorIndex = 0;
|
||
|
}
|
||
|
|
||
|
if (!OVRPlugin.SetHandNodePoseStateLatency(OVRManager.instance.handPoseStateLatency))
|
||
|
{
|
||
|
Debug.LogWarning("HandPoseStateLatency is invalid. Expect a value between 0.0 to 0.5, get " + OVRManager.instance.handPoseStateLatency);
|
||
|
}
|
||
|
|
||
|
RefreshRenderTextures(mainCamera);
|
||
|
|
||
|
bgCamera.clearFlags = mainCamera.clearFlags;
|
||
|
bgCamera.backgroundColor = mainCamera.backgroundColor;
|
||
|
bgCamera.cullingMask = mainCamera.cullingMask & (~OVRManager.instance.extraHiddenLayers);
|
||
|
|
||
|
fgCamera.cullingMask = mainCamera.cullingMask & (~OVRManager.instance.extraHiddenLayers);
|
||
|
|
||
|
if (OVRMixedReality.useFakeExternalCamera || OVRPlugin.GetExternalCameraCount() == 0)
|
||
|
{
|
||
|
OVRPose worldSpacePose = new OVRPose();
|
||
|
OVRPose trackingSpacePose = new OVRPose();
|
||
|
trackingSpacePose.position = OVRMixedReality.fakeCameraPositon;
|
||
|
trackingSpacePose.orientation = OVRMixedReality.fakeCameraRotation;
|
||
|
worldSpacePose = OVRExtensions.ToWorldSpacePose(trackingSpacePose);
|
||
|
|
||
|
RefreshCameraPoses(OVRMixedReality.fakeCameraFov, OVRMixedReality.fakeCameraAspect, worldSpacePose);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
OVRPlugin.CameraExtrinsics extrinsics;
|
||
|
OVRPlugin.CameraIntrinsics intrinsics;
|
||
|
|
||
|
// So far, only support 1 camera for MR and always use camera index 0
|
||
|
if (OVRPlugin.GetMixedRealityCameraInfo(0, out extrinsics, out intrinsics))
|
||
|
{
|
||
|
OVRPose worldSpacePose = ComputeCameraWorldSpacePose(extrinsics);
|
||
|
|
||
|
float fovY = Mathf.Atan(intrinsics.FOVPort.UpTan) * Mathf.Rad2Deg * 2;
|
||
|
float aspect = intrinsics.FOVPort.LeftTan / intrinsics.FOVPort.UpTan;
|
||
|
|
||
|
RefreshCameraPoses(fovY, aspect, worldSpacePose);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Debug.LogWarning("Failed to get external camera information");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
compositionCamera.GetComponent<OVRCameraFrameCompositionManager>().boundaryMeshMaskTexture = historyRecordArray[historyRecordCursorIndex].boundaryMeshMaskTexture;
|
||
|
HistoryRecord record = GetHistoryRecordForComposition();
|
||
|
UpdateCameraFramePlaneObject(mainCamera, compositionCamera, record.boundaryMeshMaskTexture);
|
||
|
OVRSandwichCompositionManager compositionManager = compositionCamera.gameObject.GetComponent<OVRSandwichCompositionManager>();
|
||
|
compositionManager.fgTexture = record.fgRenderTexture;
|
||
|
compositionManager.bgTexture = record.bgRenderTexture;
|
||
|
|
||
|
cameraProxyPlane.transform.position = fgCamera.transform.position + fgCamera.transform.forward * cameraFramePlaneDistance;
|
||
|
cameraProxyPlane.transform.LookAt(cameraProxyPlane.transform.position + fgCamera.transform.forward);
|
||
|
}
|
||
|
|
||
|
public override void Cleanup()
|
||
|
{
|
||
|
base.Cleanup();
|
||
|
|
||
|
Camera[] cameras = { fgCamera, bgCamera, compositionCamera };
|
||
|
|
||
|
foreach (Camera c in cameras)
|
||
|
{
|
||
|
OVRCompositionUtil.SafeDestroy(c.gameObject);
|
||
|
}
|
||
|
|
||
|
fgCamera = null;
|
||
|
bgCamera = null;
|
||
|
compositionCamera = null;
|
||
|
|
||
|
Debug.Log("SandwichComposition deactivated");
|
||
|
}
|
||
|
|
||
|
private RenderTextureFormat DesiredRenderTextureFormat(RenderTextureFormat originalFormat)
|
||
|
{
|
||
|
if (originalFormat == RenderTextureFormat.RGB565)
|
||
|
{
|
||
|
return RenderTextureFormat.ARGB1555;
|
||
|
}
|
||
|
else if (originalFormat == RenderTextureFormat.RGB111110Float)
|
||
|
{
|
||
|
return RenderTextureFormat.ARGBHalf;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return originalFormat;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected void RefreshRenderTextures(Camera mainCamera)
|
||
|
{
|
||
|
int width = Screen.width;
|
||
|
int height = Screen.height;
|
||
|
RenderTextureFormat format = mainCamera.targetTexture ? DesiredRenderTextureFormat(mainCamera.targetTexture.format) : RenderTextureFormat.ARGB32;
|
||
|
int depth = mainCamera.targetTexture ? mainCamera.targetTexture.depth : 24;
|
||
|
|
||
|
Debug.Assert(fgCamera != null && bgCamera != null);
|
||
|
|
||
|
HistoryRecord record = historyRecordArray[historyRecordCursorIndex];
|
||
|
|
||
|
record.timestamp = frameRealtime;
|
||
|
|
||
|
if (record.fgRenderTexture == null || record.fgRenderTexture.width != width || record.fgRenderTexture.height != height || record.fgRenderTexture.format != format || record.fgRenderTexture.depth != depth)
|
||
|
{
|
||
|
record.fgRenderTexture = new RenderTexture(width, height, depth, format);
|
||
|
record.fgRenderTexture.name = "Sandwich FG " + historyRecordCursorIndex.ToString();
|
||
|
}
|
||
|
fgCamera.targetTexture = record.fgRenderTexture;
|
||
|
|
||
|
if (record.bgRenderTexture == null || record.bgRenderTexture.width != width || record.bgRenderTexture.height != height || record.bgRenderTexture.format != format || record.bgRenderTexture.depth != depth)
|
||
|
{
|
||
|
record.bgRenderTexture = new RenderTexture(width, height, depth, format);
|
||
|
record.bgRenderTexture.name = "Sandwich BG " + historyRecordCursorIndex.ToString();
|
||
|
}
|
||
|
bgCamera.targetTexture = record.bgRenderTexture;
|
||
|
|
||
|
if (OVRManager.instance.virtualGreenScreenType != OVRManager.VirtualGreenScreenType.Off)
|
||
|
{
|
||
|
if (record.boundaryMeshMaskTexture == null || record.boundaryMeshMaskTexture.width != width || record.boundaryMeshMaskTexture.height != height)
|
||
|
{
|
||
|
record.boundaryMeshMaskTexture = new RenderTexture(width, height, 0, RenderTextureFormat.R8);
|
||
|
record.boundaryMeshMaskTexture.name = "Boundary Mask " + historyRecordCursorIndex.ToString();
|
||
|
record.boundaryMeshMaskTexture.Create();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
record.boundaryMeshMaskTexture = null;
|
||
|
}
|
||
|
|
||
|
Debug.Assert(fgCamera.targetTexture != null && bgCamera.targetTexture != null && (OVRManager.instance.virtualGreenScreenType == OVRManager.VirtualGreenScreenType.Off || record.boundaryMeshMaskTexture != null));
|
||
|
}
|
||
|
|
||
|
protected HistoryRecord GetHistoryRecordForComposition()
|
||
|
{
|
||
|
float expectedTime = frameRealtime - OVRManager.instance.sandwichCompositionRenderLatency;
|
||
|
int currIndex = historyRecordCursorIndex;
|
||
|
int prevIndex = currIndex - 1;
|
||
|
if (prevIndex < 0)
|
||
|
{
|
||
|
prevIndex = historyRecordCount - 1;
|
||
|
}
|
||
|
while (prevIndex != historyRecordCursorIndex)
|
||
|
{
|
||
|
if (historyRecordArray[prevIndex].timestamp <= expectedTime)
|
||
|
{
|
||
|
float timeToCurrIndex = historyRecordArray[currIndex].timestamp - expectedTime;
|
||
|
float timeToPrevIndex = expectedTime - historyRecordArray[prevIndex].timestamp;
|
||
|
return timeToCurrIndex <= timeToPrevIndex ? historyRecordArray[currIndex] : historyRecordArray[prevIndex];
|
||
|
}
|
||
|
currIndex = prevIndex;
|
||
|
prevIndex = currIndex - 1;
|
||
|
if (prevIndex < 0) prevIndex = historyRecordCount - 1;
|
||
|
}
|
||
|
// return the earliest frame
|
||
|
return historyRecordArray[currIndex];
|
||
|
}
|
||
|
|
||
|
protected void RefreshCameraPoses(float fovY, float aspect, OVRPose pose)
|
||
|
{
|
||
|
Camera[] cameras = { fgCamera, bgCamera, compositionCamera };
|
||
|
foreach (Camera c in cameras)
|
||
|
{
|
||
|
c.fieldOfView = fovY;
|
||
|
c.aspect = aspect;
|
||
|
c.transform.FromOVRPose(pose);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class OVRSandwichCompositionManager : MonoBehaviour
|
||
|
{
|
||
|
public RenderTexture fgTexture;
|
||
|
public RenderTexture bgTexture;
|
||
|
public Material alphaBlendMaterial;
|
||
|
|
||
|
void Start()
|
||
|
{
|
||
|
Shader alphaBlendShader = Shader.Find("Oculus/UnlitTransparent");
|
||
|
if (alphaBlendShader == null)
|
||
|
{
|
||
|
Debug.LogError("Unable to create transparent shader");
|
||
|
return;
|
||
|
}
|
||
|
alphaBlendMaterial = new Material(alphaBlendShader);
|
||
|
}
|
||
|
|
||
|
private void OnPreRender()
|
||
|
{
|
||
|
if (fgTexture == null || bgTexture == null || alphaBlendMaterial == null)
|
||
|
{
|
||
|
Debug.LogError("OVRSandwichCompositionManager has not setup properly");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Graphics.Blit(bgTexture, RenderTexture.active);
|
||
|
}
|
||
|
|
||
|
void OnPostRender()
|
||
|
{
|
||
|
if (fgTexture == null || bgTexture == null || alphaBlendMaterial == null)
|
||
|
{
|
||
|
Debug.LogError("OVRSandwichCompositionManager has not setup properly");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Graphics.Blit(fgTexture, RenderTexture.active, alphaBlendMaterial);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|