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 Inspector with the render pipeline asset selected, ensure that the Render Type is set /// to Custom. /// In the Inspector with the render pipeline asset selected, select the Render Type -> Data /// asset which would be of type ForwardRendererData. /// In the Inspector with the forward renderer data selected, ensure the Render Features list /// contains a . /// /// /// To customize background rendering with the legacy render pipeline, you may 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("https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@3.0/api/UnityEngine.XR.ARFoundation.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 /// internal 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 display transform matrix. /// static readonly int k_DisplayTransformId = Shader.PropertyToID(k_DisplayTransformName); /// /// 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; /// /// 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; /// /// Whether background rendering is enabled. /// 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 current Material used for background rendering. /// public Material material { get { return (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 fi the legacy render pipeline is in use. Otherwise, false. /// bool useLegacyRenderPipeline => GraphicsSettings.renderPipelineAsset == 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 does nothing, but Unity requires a valid function pointer in order to add IssuePluginEvent to a command /// buffer. Doing so has the side effect of resetting the OpenGL state. /// /// The id of the event /// [MonoPInvokeCallback(typeof(Action))] static void ResetGlState(int eventId) {} /// /// A delegate representation of . This maintains a strong /// reference to the delegate, which is converted to an IntPtr by . /// /// static Action s_ResetGlStateDelegate = ResetGlState; /// /// A pointer to that can be passed to /// [CommandBuffer.IssuePluginEvent](https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.IssuePluginEvent.html). /// /// static readonly IntPtr s_ResetGlStateFuncPtr = Marshal.GetFunctionPointerForDelegate(s_ResetGlStateDelegate); /// /// 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(); } void OnEnable() { // Ensure that background rendering is disabled until the first camera frame is received. m_BackgroundRenderingEnabled = false; cameraManager.frameReceived += OnCameraFrameReceived; } void OnDisable() { cameraManager.frameReceived -= OnCameraFrameReceived; DisableBackgroundRendering(); } /// /// Enable background rendering by disabling the camera's clear flags, and enabling the legacy RP background /// rendering if we are in legacy RP mode. /// void EnableBackgroundRendering() { m_BackgroundRenderingEnabled = true; DisableBackgroundClearFlags(); Material material = defaultMaterial; if (useLegacyRenderPipeline && (material != null)) { EnableLegacyRenderPipelineBackgroundRendering(); } } /// /// Disable background rendering by disabling the legacy RP background rendering if we are in legacy RP mode /// and restoring the camera's clear flags. /// void DisableBackgroundRendering() { m_BackgroundRenderingEnabled = false; DisableLegacyRenderPipelineBackgroundRendering(); RestoreBackgroundClearFlags(); // We are no longer setting the projection matrix so tell the camera to resume its normal projection matrix // calculations. camera.ResetProjectionMatrix(); } /// /// 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(); AddOpenGLES3ResetStateCommand(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; } } /// /// When using OpenGLES3, this adds a command to the /// which will force Unity to reset the OpenGL state. This is necessary on devices using OpenGLES3. /// If OpenGLES3 is not the current graphics device type, this method does nothing. This should be /// the first command in the command buffer. /// /// The [CommandBuffer](https://docs.unity3d.com/ScriptReference/Rendering.CommandBuffer.html) /// to add the command to. internal protected static void AddOpenGLES3ResetStateCommand(CommandBuffer commandBuffer) { if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3) { commandBuffer.IssuePluginEvent(s_ResetGlStateFuncPtr, 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 (m_CommandBuffer != null && m_CommandBufferCullingState != shouldInvertCulling) { ConfigureLegacyCommandBuffer(m_CommandBuffer); } } else { 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; } } 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); } } } } } }