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);
}
}
}
}
}
}