//======= Copyright (c) Valve Corporation, All rights reserved. =============== // // Purpose: Used to render an external camera of vr player (split front/back). // //============================================================================= using UnityEngine; using UnityEngine.Rendering; using Valve.VR; namespace Valve.VR { public class SteamVR_ExternalCamera : MonoBehaviour { private SteamVR_Action_Pose cameraPose = null; private SteamVR_Input_Sources cameraInputSource = SteamVR_Input_Sources.Camera; [System.Serializable] public struct Config { public float x, y, z; public float rx, ry, rz; public float fov; public float near, far; public float sceneResolutionScale; public float frameSkip; public float nearOffset, farOffset; public float hmdOffset; public float r, g, b, a; // chroma key override public bool disableStandardAssets; } [Space()] public Config config; public string configPath; [Tooltip("This will automatically activate the action set the specified pose belongs to. And deactivate it when this component is disabled.")] public bool autoEnableDisableActionSet = true; public void ReadConfig() { try { var mCam = new HmdMatrix34_t(); var readCamMatrix = false; object c = config; // box var lines = System.IO.File.ReadAllLines(configPath); foreach (var line in lines) { var split = line.Split('='); if (split.Length == 2) { var key = split[0]; if (key == "m") { var values = split[1].Split(','); if (values.Length == 12) { mCam.m0 = float.Parse(values[0]); mCam.m1 = float.Parse(values[1]); mCam.m2 = float.Parse(values[2]); mCam.m3 = float.Parse(values[3]); mCam.m4 = float.Parse(values[4]); mCam.m5 = float.Parse(values[5]); mCam.m6 = float.Parse(values[6]); mCam.m7 = float.Parse(values[7]); mCam.m8 = float.Parse(values[8]); mCam.m9 = float.Parse(values[9]); mCam.m10 = float.Parse(values[10]); mCam.m11 = float.Parse(values[11]); readCamMatrix = true; } } #if !UNITY_METRO else if (key == "disableStandardAssets") { var field = c.GetType().GetField(key); if (field != null) field.SetValue(c, bool.Parse(split[1])); } else { var field = c.GetType().GetField(key); if (field != null) field.SetValue(c, float.Parse(split[1])); } #endif } } config = (Config)c; //unbox // Convert calibrated camera matrix settings. if (readCamMatrix) { var t = new SteamVR_Utils.RigidTransform(mCam); config.x = t.pos.x; config.y = t.pos.y; config.z = t.pos.z; var angles = t.rot.eulerAngles; config.rx = angles.x; config.ry = angles.y; config.rz = angles.z; } } catch { } // Clear target so AttachToCamera gets called to pick up any changes. target = null; #if !UNITY_METRO // Listen for changes. if (watcher == null) { var fi = new System.IO.FileInfo(configPath); watcher = new System.IO.FileSystemWatcher(fi.DirectoryName, fi.Name); watcher.NotifyFilter = System.IO.NotifyFilters.LastWrite; watcher.Changed += new System.IO.FileSystemEventHandler(OnChanged); watcher.EnableRaisingEvents = true; } } System.IO.FileSystemWatcher watcher; #else } #endif public void SetupPose(SteamVR_Action_Pose newCameraPose, SteamVR_Input_Sources newCameraSource) { cameraPose = newCameraPose; cameraInputSource = newCameraSource; AutoEnableActionSet(); SteamVR_Behaviour_Pose poseBehaviour = this.gameObject.AddComponent(); poseBehaviour.poseAction = newCameraPose; poseBehaviour.inputSource = newCameraSource; } public void SetupDeviceIndex(int deviceIndex) { SteamVR_TrackedObject trackedObject = this.gameObject.AddComponent(); trackedObject.SetDeviceIndex(deviceIndex); } void OnChanged(object source, System.IO.FileSystemEventArgs e) { ReadConfig(); } Camera cam; Transform target; GameObject clipQuad; Material clipMaterial; protected SteamVR_ActionSet activatedActionSet; protected SteamVR_Input_Sources activatedInputSource; public void AttachToCamera(SteamVR_Camera steamVR_Camera) { Camera vrcam; if (steamVR_Camera == null) { vrcam = Camera.main; if (target == vrcam.transform) return; target = vrcam.transform; } else { vrcam = steamVR_Camera.camera; if (target == steamVR_Camera.head) return; target = steamVR_Camera.head; } var root = transform.parent; var origin = target.parent; root.parent = origin; root.localPosition = Vector3.zero; root.localRotation = Quaternion.identity; root.localScale = Vector3.one; // Make a copy of the eye camera to pick up any camera fx. vrcam.enabled = false; var go = Instantiate(vrcam.gameObject); vrcam.enabled = true; go.name = "camera"; DestroyImmediate(go.GetComponent()); DestroyImmediate(go.GetComponent()); cam = go.GetComponent(); cam.stereoTargetEye = StereoTargetEyeMask.None; cam.fieldOfView = config.fov; cam.useOcclusionCulling = false; cam.enabled = false; // manually rendered cam.rect = new Rect(0, 0, 1, 1); //fix order of operations issue colorMat = new Material(Shader.Find("Custom/SteamVR_ColorOut")); alphaMat = new Material(Shader.Find("Custom/SteamVR_AlphaOut")); clipMaterial = new Material(Shader.Find("Custom/SteamVR_ClearAll")); var offset = go.transform; offset.parent = transform; offset.localPosition = new Vector3(config.x, config.y, config.z); offset.localRotation = Quaternion.Euler(config.rx, config.ry, config.rz); offset.localScale = Vector3.one; // Strip children of cloned object (AudioListener in particular). while (offset.childCount > 0) DestroyImmediate(offset.GetChild(0).gameObject); // Setup clipping quad (using camera clip causes problems with shadows). clipQuad = GameObject.CreatePrimitive(PrimitiveType.Quad); clipQuad.name = "ClipQuad"; DestroyImmediate(clipQuad.GetComponent()); var clipRenderer = clipQuad.GetComponent(); clipRenderer.material = clipMaterial; clipRenderer.shadowCastingMode = ShadowCastingMode.Off; clipRenderer.receiveShadows = false; clipRenderer.lightProbeUsage = LightProbeUsage.Off; clipRenderer.reflectionProbeUsage = ReflectionProbeUsage.Off; var clipTransform = clipQuad.transform; clipTransform.parent = offset; clipTransform.localScale = new Vector3(1000.0f, 1000.0f, 1.0f); clipTransform.localRotation = Quaternion.identity; clipQuad.SetActive(false); } public float GetTargetDistance() { if (target == null) return config.near + 0.01f; var offset = cam.transform; var forward = new Vector3(offset.forward.x, 0.0f, offset.forward.z).normalized; var targetPos = target.position + new Vector3(target.forward.x, 0.0f, target.forward.z).normalized * config.hmdOffset; var distance = -(new Plane(forward, targetPos)).GetDistanceToPoint(offset.position); return Mathf.Clamp(distance, config.near + 0.01f, config.far - 0.01f); } Material colorMat, alphaMat; public void RenderNear() { var w = Screen.width / 2; var h = Screen.height / 2; if (cam.targetTexture == null || cam.targetTexture.width != w || cam.targetTexture.height != h) { var tex = new RenderTexture(w, h, 24, RenderTextureFormat.ARGB32); tex.antiAliasing = QualitySettings.antiAliasing == 0 ? 1 : QualitySettings.antiAliasing; cam.targetTexture = tex; } cam.nearClipPlane = config.near; cam.farClipPlane = config.far; var clearFlags = cam.clearFlags; var backgroundColor = cam.backgroundColor; cam.clearFlags = CameraClearFlags.Color; cam.backgroundColor = Color.clear; clipMaterial.color = new Color(config.r, config.g, config.b, config.a); float dist = Mathf.Clamp(GetTargetDistance() + config.nearOffset, config.near, config.far); var clipParent = clipQuad.transform.parent; clipQuad.transform.position = clipParent.position + clipParent.forward * dist; MonoBehaviour[] behaviours = null; bool[] wasEnabled = null; if (config.disableStandardAssets) { behaviours = cam.gameObject.GetComponents(); wasEnabled = new bool[behaviours.Length]; for (int i = 0; i < behaviours.Length; i++) { var behaviour = behaviours[i]; if (behaviour.enabled && behaviour.GetType().ToString().StartsWith("UnityStandardAssets.")) { behaviour.enabled = false; wasEnabled[i] = true; } } } clipQuad.SetActive(true); cam.Render(); Graphics.DrawTexture(new Rect(0, 0, w, h), cam.targetTexture, colorMat); // Re-render scene with post-processing fx disabled (if necessary) since they override alpha. var pp = cam.gameObject.GetComponent("PostProcessingBehaviour") as MonoBehaviour; if ((pp != null) && pp.enabled) { pp.enabled = false; cam.Render(); pp.enabled = true; } Graphics.DrawTexture(new Rect(w, 0, w, h), cam.targetTexture, alphaMat); // Restore settings. clipQuad.SetActive(false); if (behaviours != null) { for (int i = 0; i < behaviours.Length; i++) { if (wasEnabled[i]) { behaviours[i].enabled = true; } } } cam.clearFlags = clearFlags; cam.backgroundColor = backgroundColor; } public void RenderFar() { cam.nearClipPlane = config.near; cam.farClipPlane = config.far; cam.Render(); var w = Screen.width / 2; var h = Screen.height / 2; Graphics.DrawTexture(new Rect(0, h, w, h), cam.targetTexture, colorMat); } void OnGUI() { // Necessary for Graphics.DrawTexture to work even though we don't do anything here. } Camera[] cameras; Rect[] cameraRects; float sceneResolutionScale; void OnEnable() { // Move game view cameras to lower-right quadrant. cameras = FindObjectsOfType(); if (cameras != null) { var numCameras = cameras.Length; cameraRects = new Rect[numCameras]; for (int i = 0; i < numCameras; i++) { var cam = cameras[i]; cameraRects[i] = cam.rect; if (cam == this.cam) continue; if (cam.targetTexture != null) continue; if (cam.GetComponent() != null) continue; cam.rect = new Rect(0.5f, 0.0f, 0.5f, 0.5f); } } if (config.sceneResolutionScale > 0.0f) { sceneResolutionScale = SteamVR_Camera.sceneResolutionScale; SteamVR_Camera.sceneResolutionScale = config.sceneResolutionScale; } AutoEnableActionSet(); } private void AutoEnableActionSet() { if (autoEnableDisableActionSet) { if (cameraPose != null) { if (cameraPose.actionSet.IsActive(cameraInputSource) == false) { activatedActionSet = cameraPose.actionSet; //automatically activate the actionset if it isn't active already. (will deactivate on component disable) activatedInputSource = cameraInputSource; cameraPose.actionSet.Activate(cameraInputSource); } } } } void OnDisable() { if (autoEnableDisableActionSet) { if (activatedActionSet != null) //deactivate the action set we activated for this camera { activatedActionSet.Deactivate(activatedInputSource); activatedActionSet = null; } } // Restore game view cameras. if (cameras != null) { var numCameras = cameras.Length; for (int i = 0; i < numCameras; i++) { var cam = cameras[i]; if (cam != null) cam.rect = cameraRects[i]; } cameras = null; cameraRects = null; } if (config.sceneResolutionScale > 0.0f) { SteamVR_Camera.sceneResolutionScale = sceneResolutionScale; } } } }