////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2007-2020 , Inc. All Rights Reserved. // ////////////////////////////////////////////////////////////////////////// using System; using UnityEngine; using GCSeries.Core; namespace GCSeries.zView { public class VirtualCameraAR : VirtualCamera { ////////////////////////////////////////////////////////////////// // Unity MonoBehaviour Callbacks ////////////////////////////////////////////////////////////////// void Awake() { this.CreateCameras(); this.CreateBoxMask(); this.LoadResources(); } void OnRenderImage(RenderTexture src, RenderTexture dest) { Material compositorMaterial = (_isTransparencyEnabled) ? _compositorMaterialRGBA : _compositorMaterialRGB; if (compositorMaterial == null) { Graphics.Blit(src, dest); return; } if (_isTransparencyEnabled) { compositorMaterial.SetTexture("_MaskDepthTexture", _maskDepthRenderTexture); compositorMaterial.SetTexture("_NonEnvironmentTexture", _nonEnvironmentRenderTexture); } else { compositorMaterial.SetTexture("_MaskDepthTexture", _maskDepthRenderTexture); compositorMaterial.SetTexture("_NonEnvironmentDepthTexture", _nonEnvironmentRenderTexture); compositorMaterial.SetColor("_MaskColor", MASK_COLOR); } Graphics.Blit(src, dest, compositorMaterial, 0); } ////////////////////////////////////////////////////////////////// // Virtual Camera Overrides ////////////////////////////////////////////////////////////////// public override void SetUp(ZView zView, IntPtr connection, ZView.ModeSetupPhase phase) { switch (phase) { case ZView.ModeSetupPhase.Initialization: // Do nothing. break; case ZView.ModeSetupPhase.Completion: // Grab the image dimensions from the connection settings. _imageWidth = zView.GetSettingUInt16(connection, ZView.SettingKey.ImageWidth); _imageHeight = zView.GetSettingUInt16(connection, ZView.SettingKey.ImageHeight); // Create the mask depth render texture (mask only). // NOTE: This is used for both RGB or RGBA overlay. _maskDepthRenderTexture = new RenderTexture((int)_imageWidth, (int)_imageHeight, 24, RenderTextureFormat.ARGB32); _maskDepthRenderTexture.filterMode = FilterMode.Point; _maskDepthRenderTexture.name = "MaskDepthRenderTexture"; _maskDepthRenderTexture.Create(); // Create the non-environment render texture (non-environment objects + mask). // NOTE: For the RGB overlay, this is used to perform a depth render // of the non-environment objects (excluding the mask). For the RGBA overlay, // this is used to render non-environment objects (including the mask depth). _nonEnvironmentRenderTexture = new RenderTexture((int)_imageWidth, (int)_imageHeight, 24, RenderTextureFormat.ARGB32); _nonEnvironmentRenderTexture.filterMode = FilterMode.Point; _nonEnvironmentRenderTexture.name = "NonEnvironmentRenderTexture"; _nonEnvironmentRenderTexture.Create(); // Create the final composite render texture. // NOTE: This is used for both RGB or RGBA overlay. _finalRenderTexture = new RenderTexture((int)_imageWidth, (int)_imageHeight, 24, RenderTextureFormat.ARGB32); _finalRenderTexture.filterMode = FilterMode.Point; _finalRenderTexture.name = "CompositeRenderTexture"; _finalRenderTexture.Create(); // Cache the composite render texture's native texture pointer. Per Unity documentation, // calling GetNativeTexturePtr() when using multi-threaded rendering will // synchronize with the rendering thread (which is a slow operation). So, only // call and cache once upon initialization. _nativeTexturePtr = _finalRenderTexture.GetNativeTexturePtr(); break; default: break; } } public override void TearDown() { // Reset the camera's target texture. _compositorCamera.targetTexture = null; // Reset the render texture's native texture pointer. _nativeTexturePtr = IntPtr.Zero; // Reset the image width and height; _imageWidth = 0; _imageHeight = 0; // Clean up the existing render textures. if (_maskDepthRenderTexture != null) { UnityEngine.Object.Destroy(_maskDepthRenderTexture); _maskDepthRenderTexture = null; } if (_nonEnvironmentRenderTexture != null) { UnityEngine.Object.Destroy(_nonEnvironmentRenderTexture); _nonEnvironmentRenderTexture = null; } if (_finalRenderTexture != null) { UnityEngine.Object.Destroy(_finalRenderTexture); _finalRenderTexture = null; } } public override void Render(ZView zView, IntPtr connection, IntPtr receivedFrame) { // The camera's parent transform represents viewport center and // is used here to align the AR camera against. Transform cameraParentTransform = zView.ActiveZCamera?.transform.parent; // Cache whether transparency is enabled. _isTransparencyEnabled = zView.ARModeEnableTransparency; // Grab the viewer scale. float viewerScale = cameraParentTransform?.lossyScale.x ?? 1.0f; /////////////////////////////// // Camera Properties Update /////////////////////////////// // Cache the camera's culling mask and near/far clip planes so that they // can be restored after it renders the frame. int originalCullingMask = _compositorCamera.cullingMask; float originalNearClipPlane = _compositorCamera.nearClipPlane; float originalFarClipPlane = _compositorCamera.farClipPlane; // Grab the web cam's display space pose matrix and intrinsic values // from the frame data. Matrix4x4 cameraPoseMatrixInDisplaySpace = zView.GetFrameDataMatrix4x4(receivedFrame, ZView.FrameDataKey.CameraPose); float focalLength = zView.GetFrameDataFloat(receivedFrame, ZView.FrameDataKey.CameraFocalLength); float principalPointOffsetX = zView.GetFrameDataFloat(receivedFrame, ZView.FrameDataKey.CameraPrincipalPointOffsetX); float principalPointOffsetY = zView.GetFrameDataFloat(receivedFrame, ZView.FrameDataKey.CameraPrincipalPointOffsetY); float pixelAspectRatio = zView.GetFrameDataFloat(receivedFrame, ZView.FrameDataKey.CameraPixelAspectRatio); float axisSkew = zView.GetFrameDataFloat(receivedFrame, ZView.FrameDataKey.CameraAxisSkew); // Update the near and far clip values to account for viewer scale. float nearClipPlane = originalNearClipPlane * viewerScale; float farClipPlane = originalFarClipPlane * viewerScale; // Calculate the camera's transform by transforming its corresponding // display space pose matrix to world space. Matrix4x4 displayToWorld = cameraParentTransform?.localToWorldMatrix ?? Matrix4x4.identity; Matrix4x4 worldPoseMatrix = displayToWorld * cameraPoseMatrixInDisplaySpace; // Calculate the camera's projection matrix based on the camera intrinsic // and near/far clip values. Matrix4x4 projectionMatrix = this.ComputeProjectionMatrix( focalLength, principalPointOffsetX, principalPointOffsetY, pixelAspectRatio, axisSkew, (float)_imageWidth, (float)_imageHeight, nearClipPlane, farClipPlane); // Update the primary camera's properties (i.e. transform, projection, etc.). _compositorCamera.transform.position = worldPoseMatrix.GetColumn(3); _compositorCamera.transform.rotation = Quaternion.LookRotation(worldPoseMatrix.GetColumn(2), worldPoseMatrix.GetColumn(1)); _compositorCamera.projectionMatrix = projectionMatrix; _compositorCamera.cullingMask = _compositorCamera.cullingMask & ~(zView.ARModeIgnoreLayers); _compositorCamera.nearClipPlane = nearClipPlane; _compositorCamera.farClipPlane = farClipPlane; // Copy the compositor camera's properties to the secondary camera. _secondaryCamera.CopyFrom(_compositorCamera); #if UNITY_5_6_OR_NEWER //make sure this is not HDR, it's not supported _secondaryCamera.allowHDR = false; #endif // UNITY_5_6_OR_NEWER /////////////////////////////// // Box Mask Update /////////////////////////////// // Enable the box mask to be rendered by the depth camera. // Note: The box mask will be disabled immediately after it is rendered // by the AR depth camera so that it isn't inadvertently rendered by // other cameras in the scene. _boxMaskObject.SetActive(true); // Update the box mask's transform and layer. _boxMaskObject.transform.SetPositionAndRotation( cameraParentTransform?.position ?? Vector3.zero, cameraParentTransform?.rotation ?? Quaternion.identity); _boxMaskObject.transform.localScale = cameraParentTransform?.lossyScale ?? Vector3.one; _boxMaskObject.layer = zView.ARModeMaskLayer; // Update the box mask's size. _boxMask.SetSize(zView.ARModeMaskSize); // Set the box mask's cutout size to be the size of the viewport // in viewport space (meters) since its associated transform's // local scale accounts for viewer scale. _boxMask.SetCutoutSize(ZProvider.WindowSize); // Update the box mask's render queue priority. _boxMask.SetRenderQueue(zView.ARModeMaskRenderQueue); /////////////////////////////// // Scene Render /////////////////////////////// if (zView.ARModeEnableTransparency) { this.RenderRGBA(zView); } else { this.RenderRGB(zView); } // Disable the box mask so that it isn't inadvertently rendered by // any other cameras in the scene. _boxMaskObject.SetActive(false); // Restore the camera's culling mask and near/far clip planes. _compositorCamera.cullingMask = originalCullingMask; _compositorCamera.nearClipPlane = originalNearClipPlane; _compositorCamera.farClipPlane = originalFarClipPlane; } public override IntPtr GetNativeTexturePtr() { return _nativeTexturePtr; } ////////////////////////////////////////////////////////////////// // Private Methods ////////////////////////////////////////////////////////////////// private void CreateCameras() { // Create a new Unity camera and disable it to allow for manual // rendering via Camera.Render(). // NOTE: The camera's rendering path must be set to the forward // rendering path since the current technique for rendering // the augmented reality overlay does not work for deferred // rendering. _compositorCamera = this.gameObject.AddComponent(); _compositorCamera.enabled = false; _compositorCamera.nearClipPlane = 0.03f; _compositorCamera.renderingPath = RenderingPath.Forward; // Create the secondary camera. GameObject secondaryCameraObject = new GameObject("SecondaryCamera"); secondaryCameraObject.transform.parent = this.transform; secondaryCameraObject.hideFlags = HideFlags.HideAndDontSave; _secondaryCamera = secondaryCameraObject.AddComponent(); _secondaryCamera.enabled = false; _secondaryCamera.renderingPath = RenderingPath.Forward; } private void CreateBoxMask() { // Create the box mask. _boxMaskObject = new GameObject("BoxMask"); _boxMaskObject.transform.parent = this.transform; _boxMaskObject.hideFlags = HideFlags.HideAndDontSave; _boxMask = _boxMaskObject.AddComponent(); _boxMask.SetSize(Vector3.one); _boxMaskObject.SetActive(false); } private void LoadResources() { // Find and cache the depth render shader. _depthRenderShader = Shader.Find("/zView/DepthRender"); if (_depthRenderShader == null) { Debug.LogError("Failed to find the /zView/DepthRender shader."); } // Create the RGB compositor material from its associated shader. _compositorShaderRGB = Shader.Find("/zView/CompositorRGB"); if (_compositorShaderRGB != null) { _compositorMaterialRGB = new Material(_compositorShaderRGB); _compositorMaterialRGB.name = "CompositorRGB"; } else { Debug.LogError("Failed to find the /zView/CompositorRGB shader."); } // Create the RGBA compositor material from its associated shader. _compositorShaderRGBA = Shader.Find("/zView/CompositorRGBA"); if (_compositorShaderRGBA != null) { _compositorMaterialRGBA = new Material(_compositorShaderRGBA); _compositorMaterialRGBA.name = "CompositorRGBA"; } else { Debug.LogError("Failed to find the /zView/CompositorRGBA shader."); } } private void RenderRGB(ZView zView) { // Update globals for the depth render shader. Shader.SetGlobalFloat("_Log2FarPlusOne", (float)Math.Log(_secondaryCamera.farClipPlane + 1, 2)); // Perform a depth render of the mask. _secondaryCamera.clearFlags = CameraClearFlags.Color; _secondaryCamera.backgroundColor = Color.white; _secondaryCamera.cullingMask = (1 << zView.ARModeMaskLayer); _secondaryCamera.targetTexture = _maskDepthRenderTexture; _secondaryCamera.RenderWithShader(_depthRenderShader, string.Empty); // Perform a depth render of the scene excluding the mask // layer and any environment layers. _secondaryCamera.cullingMask = _compositorCamera.cullingMask & ~(1 << zView.ARModeMaskLayer) & ~(zView.ARModeEnvironmentLayers); _secondaryCamera.targetTexture = _nonEnvironmentRenderTexture; _secondaryCamera.RenderWithShader(_depthRenderShader, string.Empty); // Perform the composite render of the entire scene excluding // the mask. _compositorCamera.cullingMask = _compositorCamera.cullingMask & ~(1 << zView.ARModeMaskLayer); _compositorCamera.targetTexture = _finalRenderTexture; _compositorCamera.Render(); } private void RenderRGBA(ZView zView) { if (zView.ARModeEnvironmentLayers != 0) { // Update globals for the depth render shader. Shader.SetGlobalFloat("_Log2FarPlusOne", (float)Math.Log(_secondaryCamera.farClipPlane + 1, 2)); // Perform a depth render of the mask. _secondaryCamera.clearFlags = CameraClearFlags.Color; _secondaryCamera.backgroundColor = Color.white; _secondaryCamera.cullingMask = (1 << zView.ARModeMaskLayer); _secondaryCamera.targetTexture = _maskDepthRenderTexture; _secondaryCamera.RenderWithShader(_depthRenderShader, string.Empty); // Render all non-environment objects including the box mask. _secondaryCamera.clearFlags = CameraClearFlags.Skybox; _secondaryCamera.backgroundColor = MASK_COLOR; _secondaryCamera.cullingMask = _compositorCamera.cullingMask & ~(zView.ARModeEnvironmentLayers); _secondaryCamera.targetTexture = _nonEnvironmentRenderTexture; _secondaryCamera.Render(); // Perform the composite render of the entire scene excluding // the mask. _compositorCamera.cullingMask = _compositorCamera.cullingMask & ~(1 << zView.ARModeMaskLayer); _compositorCamera.targetTexture = _finalRenderTexture; _compositorCamera.Render(); } else { // Perform a render of the entire scene including the box mask. // NOTE: If no environment layers are set, we can optimize this // to a single pass. _secondaryCamera.backgroundColor = MASK_COLOR; _secondaryCamera.targetTexture = _finalRenderTexture; _secondaryCamera.Render(); } } private Matrix4x4 ComputeProjectionMatrix( float focalLength, float principalPointOffsetX, float principalPointOffsetY, float pixelAspectRatio, float axisSkew, float imageWidth, float imageHeight, float nearClip, float farClip) { // Calculate the perspective projection matrix: Matrix4x4 perspectiveProjectionMatrix = new Matrix4x4(); perspectiveProjectionMatrix[0, 0] = focalLength; perspectiveProjectionMatrix[1, 0] = 0.0f; perspectiveProjectionMatrix[2, 0] = 0.0f; perspectiveProjectionMatrix[3, 0] = 0.0f; // Negate this column to take into account image Y axis pointing down, // opposite of OpenGL camera Y axis. perspectiveProjectionMatrix[0, 1] = -axisSkew; perspectiveProjectionMatrix[1, 1] = -(focalLength * pixelAspectRatio); perspectiveProjectionMatrix[2, 1] = 0.0f; perspectiveProjectionMatrix[3, 1] = 0.0f; // Negate this column to take into account OpenGL camera looking down // negative Z axis, opposite of convention used in typical camera // intrinsics matrix (where camera looks down positive Z axis). perspectiveProjectionMatrix[0, 2] = -principalPointOffsetX; perspectiveProjectionMatrix[1, 2] = -principalPointOffsetY; perspectiveProjectionMatrix[2, 2] = nearClip + farClip; perspectiveProjectionMatrix[3, 2] = -1.0f; perspectiveProjectionMatrix[0, 3] = 0.0f; perspectiveProjectionMatrix[1, 3] = 0.0f; perspectiveProjectionMatrix[2, 3] = nearClip * farClip; perspectiveProjectionMatrix[3, 3] = 0.0f; Matrix4x4 ndcConversion = Matrix4x4.Ortho(0.0f, imageWidth, imageHeight, 0.0f, nearClip, farClip); return ndcConversion * perspectiveProjectionMatrix; } ////////////////////////////////////////////////////////////////// // Private Members ////////////////////////////////////////////////////////////////// private static readonly Color MASK_COLOR = new Color(0, 0, 0, 0); private Camera _compositorCamera = null; private Camera _secondaryCamera = null; private Shader _depthRenderShader = null; private Shader _compositorShaderRGB = null; private Shader _compositorShaderRGBA = null; private Material _compositorMaterialRGB = null; private Material _compositorMaterialRGBA = null; private GameObject _boxMaskObject = null; private BoxMask _boxMask = null; private UInt16 _imageWidth = 0; private UInt16 _imageHeight = 0; private RenderTexture _maskDepthRenderTexture = null; private RenderTexture _nonEnvironmentRenderTexture = null; private RenderTexture _finalRenderTexture = null; private bool _isTransparencyEnabled = false; private IntPtr _nativeTexturePtr = IntPtr.Zero; } }