485 lines
21 KiB
C#
Raw Normal View History

2025-01-03 13:42:53 +08:00
//////////////////////////////////////////////////////////////////////////
//
// 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<Camera>();
_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<Camera>();
_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>();
_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;
}
}