using AOT; using System; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine.Rendering; using UnityEngine.Serialization; namespace UnityEngine.XR.ARFoundation { /// /// Add this component to a Camera to copy the color camera's texture onto the background. /// If you are using the Lightweight Render Pipeline (version 5.7.2 or later) or the Univerisal Render /// Pipeline (version 7.0.0 or later), you must also add the to the list /// of render features for the scriptable renderer. /// /// /// /// To add the to the list of render features for the scriptable /// renderer: /// /// In Project Settings > Graphics, select the render pipeline asset (either /// LightweightRenderPipelineAsset or UniversalRenderPipelineAsset) that is in the Scriptable Render /// Pipeline Settings field. /// In the render pipeline asset's Inspector window, make sure that the Render Type is set /// to Custom. /// In render pipeline asset's Inspector window, select the Render Type > Data /// asset which would be of type ForwardRendererData. /// In forward renderer data's Inspector window, ensure the Render Features list /// contains a . /// /// /// To customize background rendering with the legacy render pipeline, you can override the /// property and the /// method to modify the given /// CommandBuffer with rendering commands and to inject the given CommandBuffer into the Camera's /// rendering. /// To customize background rendering with a scriptable render pipeline, create a /// ScriptableRendererFeature with the background rendering commands, and insert the /// ScriptableRendererFeature into the list of render features for the scriptable renderer. /// [DisallowMultipleComponent] [RequireComponent(typeof(Camera))] [RequireComponent(typeof(ARCameraManager))] [HelpURL(HelpUrls.ApiWithNamespace + nameof(ARCameraBackground) + ".html")] public class ARCameraBackground : MonoBehaviour { /// /// Name for the custom rendering command buffer. /// const string k_CustomRenderPassName = "AR Background Pass (LegacyRP)"; /// /// Name of the main texture parameter for the material. /// const string k_MainTexName = "_MainTex"; /// /// Name of the shader parameter for the display transform matrix. /// const string k_DisplayTransformName = "_UnityDisplayTransform"; /// /// Property ID for the shader parameter for the main texture. /// static readonly int k_MainTexId = Shader.PropertyToID(k_MainTexName); /// /// Property ID for the shader parameter for the display transform matrix. /// static readonly int k_DisplayTransformId = Shader.PropertyToID(k_DisplayTransformName); /// /// The Property ID for the shader parameter for the forward vector's scaled length. /// static readonly int k_CameraForwardScaleId = Shader.PropertyToID("_UnityCameraForwardScale"); /// /// The camera to which the projection matrix is set on each frame event. /// Camera m_Camera; /// /// The camera manager from which frame information is pulled. /// ARCameraManager m_CameraManager; /// /// The occlusion manager, which might not exist, from which occlusion information is pulled. /// AROcclusionManager m_OcclusionManager; /// /// Command buffer for any custom rendering commands. /// CommandBuffer m_CommandBuffer; /// /// Whether to use the custom material for rendering the background. /// [SerializeField, FormerlySerializedAs("m_OverrideMaterial")] bool m_UseCustomMaterial; /// /// A custom material for rendering the background. /// [SerializeField, FormerlySerializedAs("m_Material")] Material m_CustomMaterial; /// /// The default material for rendering the background. /// Material m_DefaultMaterial; /// /// The previous clear flags for the camera, if any. /// CameraClearFlags? m_PreviousCameraClearFlags; /// /// The original field of view of the camera, before enabling background rendering. /// float? m_PreviousCameraFieldOfView; /// /// True if background rendering is enabled, false otherwise. /// bool m_BackgroundRenderingEnabled; /// /// The camera to which the projection matrix is set on each frame event. /// /// /// The camera to which the projection matrix is set on each frame event. /// #if UNITY_EDITOR protected new Camera camera => m_Camera; #else // UNITY_EDITOR protected Camera camera => m_Camera; #endif // UNITY_EDITOR /// /// The camera manager from which frame information is pulled. /// /// /// The camera manager from which frame information is pulled. /// protected ARCameraManager cameraManager => m_CameraManager; /// /// The occlusion manager, which might not exist, from which occlusion information is pulled. /// protected AROcclusionManager occlusionManager => m_OcclusionManager; /// /// The current Material used for background rendering. /// public Material material => (useCustomMaterial && (customMaterial != null)) ? customMaterial : defaultMaterial; /// /// Whether to use the custom material for rendering the background. /// /// /// true if the custom material should be used for rendering the camera background. Otherwise, /// false. /// public bool useCustomMaterial { get => m_UseCustomMaterial; set => m_UseCustomMaterial = value; } /// /// A custom material for rendering the background. /// /// /// A custom material for rendering the background. /// public Material customMaterial { get => m_CustomMaterial; set => m_CustomMaterial = value; } /// /// Whether background rendering is enabled. /// /// /// true if background rendering is enabled and if at least one camera frame has been received. /// Otherwise, false. /// public bool backgroundRenderingEnabled => m_BackgroundRenderingEnabled; /// /// The default material for rendering the background. /// /// /// The default material for rendering the background. /// Material defaultMaterial => cameraManager.cameraMaterial; /// /// Whether to use the legacy rendering pipeline. /// /// /// true if the legacy render pipeline is in use. Otherwise, false. /// bool useLegacyRenderPipeline => GraphicsSettings.currentRenderPipeline == null; /// /// Stores the previous culling state (XRCameraSubsystem.invertCulling). /// If the requested culling state changes, the command buffer must be rebuilt. /// bool m_CommandBufferCullingState; /// /// A function that can be invoked by /// [CommandBuffer.IssuePluginEvent](https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.IssuePluginEvent.html). /// This function calls the XRCameraSubsystem method that should be called immediately before background rendering. /// /// The id of the event [MonoPInvokeCallback(typeof(Action))] static void BeforeBackgroundRenderHandler(int eventId) { if (s_CameraSubsystem != null) { s_CameraSubsystem.OnBeforeBackgroundRender(eventId); } } /// /// A delegate representation of . This maintains a strong /// reference to the delegate, which is converted to an IntPtr by . /// /// static Action s_BeforeBackgroundRenderHandler = BeforeBackgroundRenderHandler; /// /// A pointer to a method to be called immediately before rendering that is implemented in the XRCameraSubsystem implementation. /// It is called via [CommandBuffer.IssuePluginEvent](https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.IssuePluginEvent.html). /// static readonly IntPtr s_BeforeBackgroundRenderHandlerFuncPtr = Marshal.GetFunctionPointerForDelegate(s_BeforeBackgroundRenderHandler); /// /// Static reference to the active XRCameraSubsystem. Necessary here for access from a static delegate. /// static ARSubsystems.XRCameraSubsystem s_CameraSubsystem; /// /// Whether culling should be inverted. Used during command buffer configuration, /// see [CommandBuffer.SetInvertCulling](https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.SetInvertCulling.html). /// /// protected bool shouldInvertCulling => m_CameraManager?.subsystem?.invertCulling ?? false; void Awake() { m_Camera = GetComponent(); m_CameraManager = GetComponent(); m_OcclusionManager = GetComponent(); } void OnEnable() { // Ensure that background rendering is disabled until the first camera frame is received. m_BackgroundRenderingEnabled = false; cameraManager.frameReceived += OnCameraFrameReceived; if (occlusionManager != null) { occlusionManager.frameReceived += OnOcclusionFrameReceived; } } void OnDisable() { if (occlusionManager != null) { occlusionManager.frameReceived -= OnOcclusionFrameReceived; } cameraManager.frameReceived -= OnCameraFrameReceived; DisableBackgroundRendering(); // We are no longer setting the projection matrix so tell the camera to resume its normal projection matrix // calculations. camera.ResetProjectionMatrix(); } /// /// Enable background rendering by disabling the camera's clear flags, and enabling the legacy RP background /// rendering if your application is in legacy RP mode. /// void EnableBackgroundRendering() { m_BackgroundRenderingEnabled = true; // We must hold a static reference to the camera subsystem so that it is accessible to the // static callback needed for calling OnBeforeBackgroundRender() from the render thread if (m_CameraManager) { s_CameraSubsystem = m_CameraManager.subsystem; } else { s_CameraSubsystem = null; } DisableBackgroundClearFlags(); m_PreviousCameraFieldOfView = m_Camera.fieldOfView; Material material = defaultMaterial; if (useLegacyRenderPipeline && (material != null)) { EnableLegacyRenderPipelineBackgroundRendering(); } } /// /// Disable background rendering by disabling the legacy RP background rendering if your application is in legacy RP mode /// and restoring the camera's clear flags. /// void DisableBackgroundRendering() { m_BackgroundRenderingEnabled = false; DisableLegacyRenderPipelineBackgroundRendering(); RestoreBackgroundClearFlags(); if (m_PreviousCameraFieldOfView.HasValue) { m_Camera.fieldOfView = m_PreviousCameraFieldOfView.Value; m_PreviousCameraFieldOfView = null; } s_CameraSubsystem = null; } /// /// Set the camera's clear flags to do nothing while preserving the previous camera clear flags. /// void DisableBackgroundClearFlags() { m_PreviousCameraClearFlags = m_Camera.clearFlags; m_Camera.clearFlags = CameraClearFlags.Nothing; } /// /// Restore the previous camera's clear flags, if any. /// void RestoreBackgroundClearFlags() { if (m_PreviousCameraClearFlags != null) { m_Camera.clearFlags = m_PreviousCameraClearFlags.Value; } } /// /// The list of [CameraEvent](https://docs.unity3d.com/ScriptReference/Rendering.CameraEvent.html)s /// to add to the [CommandBuffer](https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.html). /// static readonly CameraEvent[] s_DefaultCameraEvents = new[] { CameraEvent.BeforeForwardOpaque, CameraEvent.BeforeGBuffer }; /// /// The list of [CameraEvent](https://docs.unity3d.com/ScriptReference/Rendering.CameraEvent.html)s /// to add to the [CommandBuffer](https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.html). /// By default, returns /// [BeforeForwardOpaque](https://docs.unity3d.com/ScriptReference/Rendering.CameraEvent.BeforeForwardOpaque.html) /// and /// [BeforeGBuffer](https://docs.unity3d.com/ScriptReference/Rendering.CameraEvent.BeforeGBuffer.html)}. /// Override to use different camera events. /// protected virtual IEnumerable legacyCameraEvents => s_DefaultCameraEvents; /// /// Configures the by first clearing it, /// and then adding necessary render commands. /// /// The command buffer to configure. protected virtual void ConfigureLegacyCommandBuffer(CommandBuffer commandBuffer) { Texture texture = !material.HasProperty(k_MainTexName) ? null : material.GetTexture(k_MainTexName); commandBuffer.Clear(); AddBeforeBackgroundRenderHandler(commandBuffer); m_CommandBufferCullingState = shouldInvertCulling; commandBuffer.SetInvertCulling(m_CommandBufferCullingState); commandBuffer.ClearRenderTarget(true, false, Color.clear); commandBuffer.Blit(texture, BuiltinRenderTextureType.CameraTarget, material); } /// /// Enable background rendering getting a command buffer, and configure it for rendering the background. /// void EnableLegacyRenderPipelineBackgroundRendering() { if (m_CommandBuffer == null) { m_CommandBuffer = new CommandBuffer(); m_CommandBuffer.name = k_CustomRenderPassName; ConfigureLegacyCommandBuffer(m_CommandBuffer); foreach (var cameraEvent in legacyCameraEvents) { camera.AddCommandBuffer(cameraEvent, m_CommandBuffer); } } } /// /// Disable background rendering by removing the command buffer from the camera. /// void DisableLegacyRenderPipelineBackgroundRendering() { if (m_CommandBuffer != null) { foreach (var cameraEvent in legacyCameraEvents) { camera.RemoveCommandBuffer(cameraEvent, m_CommandBuffer); } m_CommandBuffer = null; } } /// /// This adds a command to the to make call from the render thread /// to a callback on the `XRCameraSubsystem` implementation. The callback handles any implementation-specific /// functionality needed immediately before the background is rendered. /// /// The [CommandBuffer](https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.html) /// to add the command to. internal static void AddBeforeBackgroundRenderHandler(CommandBuffer commandBuffer) { commandBuffer.IssuePluginEvent(s_BeforeBackgroundRenderHandlerFuncPtr, 0); } /// /// Callback for the camera frame event. /// /// The camera event arguments. void OnCameraFrameReceived(ARCameraFrameEventArgs eventArgs) { // Enable background rendering when first frame is received. if (m_BackgroundRenderingEnabled) { if (eventArgs.textures.Count == 0) { DisableBackgroundRendering(); } else if (m_CommandBuffer != null && m_CommandBufferCullingState != shouldInvertCulling) { ConfigureLegacyCommandBuffer(m_CommandBuffer); } } else if (eventArgs.textures.Count > 0) { EnableBackgroundRendering(); } Material material = this.material; if (material != null) { var count = eventArgs.textures.Count; for (int i = 0; i < count; ++i) { material.SetTexture(eventArgs.propertyNameIds[i], eventArgs.textures[i]); } if (eventArgs.displayMatrix.HasValue) { material.SetMatrix(k_DisplayTransformId, eventArgs.displayMatrix.Value); } SetMaterialKeywords(material, eventArgs.enabledMaterialKeywords, eventArgs.disabledMaterialKeywords); } if (eventArgs.projectionMatrix.HasValue) { camera.projectionMatrix = eventArgs.projectionMatrix.Value; const float twiceRad2Deg = 2 * Mathf.Rad2Deg; var halfHeightOverNear = 1 / camera.projectionMatrix[1, 1]; camera.fieldOfView = Mathf.Atan(halfHeightOverNear) * twiceRad2Deg; } } /// /// Callback for the occlusion frame event. /// /// The occlusion frame event arguments. void OnOcclusionFrameReceived(AROcclusionFrameEventArgs eventArgs) { Material material = this.material; if (material != null) { var count = eventArgs.textures.Count; for (int i = 0; i < count; ++i) { material.SetTexture(eventArgs.propertyNameIds[i], eventArgs.textures[i]); } SetMaterialKeywords(material, eventArgs.enabledMaterialKeywords, eventArgs.disabledMaterialKeywords); // Set scale: this computes the affect the camera's localToWorld has on the the length of the // forward vector, i.e., how much farther from the camera are things than with unit scale. var forward = transform.localToWorldMatrix.GetColumn(2); var scale = forward.magnitude; material.SetFloat(k_CameraForwardScaleId, scale); } } void SetMaterialKeywords(Material material, List enabledMaterialKeywords, List disabledMaterialKeywords) { if (enabledMaterialKeywords != null) { foreach (var materialKeyword in enabledMaterialKeywords) { if (!material.IsKeywordEnabled(materialKeyword)) { material.EnableKeyword(materialKeyword); } } } if (disabledMaterialKeywords != null) { foreach (var materialKeyword in disabledMaterialKeywords) { if (material.IsKeywordEnabled(materialKeyword)) { material.DisableKeyword(materialKeyword); } } } } } }