//======= Copyright (c) Stereolabs Corporation, All rights reserved. ===============

using UnityEngine;
using UnityEngine.Rendering;


/// <summary>
/// Responsible for actually mixing the real and virtual images, and displaying them in a
/// Frame object within the camera rig.
/// First, it displays the image of the ZED into a quad.
/// Then, it inserts the depth and normals inside the pipeline
/// Third, it computes the data for the light and send it to the shaders.
/// Finally, it post-processes the image, if post-processing is enabled.
/// </summary>
[RequireComponent(typeof(Camera))]
public class ZEDRenderingPlane : MonoBehaviour
{
    /// <summary>
    /// The rendering mode accepted.
    /// </summary>
    enum ZED_RENDERING_MODE
    {
        FORWARD,
        DEFERRED,
        LAST
    };

    /// <summary>
    /// Which side of the camera (left/right) and whether or not this can be overridden by the camera's stereoTargetEye.
    /// </summary>
    public enum ZED_CAMERA_SIDE
    {
        /// <summary>
        /// Feed from the ZED's left camera. Can be overridden by the camera's stereoTargetEye value.
        /// </summary>
        LEFT,
        /// <summary>
        /// Feed from the ZED's right camera. Can be overridden by the camera's stereoTargetEye value.
        /// </summary>
        RIGHT,
        /// <summary>
        /// Feed from the ZED's left camera. Won't be overridden.
        /// </summary>
        LEFT_FORCE,
        /// <summary>
        /// Feed from the ZED's right camera. Won't be overridden.
        /// </summary>
        RIGHT_FORCE
    }

    /// <summary>
    /// The GameGbject that displays the final textures.
    /// In the ZED_Rig_Mono and ZED_Rig_Stereo prefabs, this is the Frame object that's a child of each camera.
    /// </summary>
    [Tooltip("The GameGbject that displays the final textures. " +
        "In the ZED_Rig_Mono and ZED_Rig_Stereo prefabs, this is the Frame object that's a child of each camera.")]
    public GameObject canvas;

    /// <summary>
    /// Which camera on the ZED the image/depth/etc. comes from.
    /// If set to LEFT or RIGHT, this may be overridden by the camera's stereoTargetEye.
    /// If set to LEFT_FORCE or RIGHT_FORCE, it will not be changed.
    /// </summary>
    [Tooltip("Which camera on the ZED the image/depth/etc. comes from.\r\n" +
        "If set to LEFT or RIGHT, this may be overridden by the camera's stereoTargetEye.\r\n" +
        "If set to LEFT_FORCE or RIGHT_FORCE, it will not be changed.")]
    public ZED_CAMERA_SIDE viewSide = ZED_CAMERA_SIDE.LEFT;

    /// <summary>
    /// The main material, set to the one on the canvas's MeshRenderer. Used to set the color and depth.
    /// </summary>
    public Material matRGB { get; private set; }

    /// <summary>
    /// Aspect ratio of the textures. All the textures displayed should be in 16:9.
    /// </summary>
    private float aspect = 16.0f / 9.0f;

    /// <summary>
    /// The Camera component representing an 'eye' of the ZED. Must be on the same GameObject as this component.
    /// </summary>
    private Camera cam;
    /// <summary>
    /// The Camera component representing an 'eye' of the ZED. Must be on the same GameObject as this component.
    /// </summary>
    public Camera renderingCam
    {
        get
        {
            return cam;
        }
    }

    /// <summary>
	/// zedCamera controlled by ZEDManager (one manager controls one camera)
    /// </summary>
    private sl.ZEDCamera zedCamera = null;

    /// <summary>
    /// ZED Manager that controls the zed Camera
    /// </summary>
    private ZEDManager zedManager = null;


    /// <summary>
    /// Texture of the real world generated from the ZED. It may be the from the left or right 'eye.'
    /// Once created, the ZED SDK automatically updates it with each frame/image the ZED captures.
    /// </summary>
    private Texture2D textureEye;
    public Texture2D TextureEye { get { return textureEye; } }

    /// <summary>
    /// Depth generated by the ZEDCamera.
    /// Once created, the ZED SDK automatically updates it whenever the ZED captures new frames/images.
    /// </summary>
    Texture2D depth;

    /// <summary>
    /// Normals generated by the ZEDCamera.
    /// Once created, the ZED SDK automatically updates it whenever the ZED captures new frames/images.
    /// </summary>
    Texture2D normals;

#if !ZED_HDRP && !ZED_URP
    /// <summary>
    /// CommandBuffer to integrate the depth into Unity's forward or deferred  pipeline.
    /// </summary>
    CommandBuffer[] buffer = new CommandBuffer[(int)ZED_RENDERING_MODE.LAST];

    /// <summary>
    /// CommandBuffer to create a mask for the virtual objects in forward and deferred rendering.
    /// </summary>
    CommandBuffer[] postProcessBuffer = new CommandBuffer[(int)ZED_RENDERING_MODE.LAST];
#endif

    /// <summary>
    /// The material used to integrate the depth in forward mode after the depth texture is created. Mainly used to get the shadows. Not needed for lighting otherwise.
    /// </summary>
    public Material forwardMat { get; private set; }

    /// <summary>
    /// The material used to integrate the depth in deferred mode. Always used in deferred regardless of lighting/shadows.
    /// </summary>
    public Material deferredMat { get; private set; }

    /// <summary>
    /// The actual rendering path used.
    /// To change this, change the settings in Project Settings -> Graphics within the Unity editor.
    /// </summary>
    private RenderingPath actualRenderingPath = RenderingPath.VertexLit;
    public RenderingPath ActualRenderingPath
    {
        get { return actualRenderingPath; }
    }

    /// <summary>
    /// The MeshFilter component of the canvas object. Used to draw the depth buffer.
    /// </summary>
    MeshFilter meshCanvas;

    /// <summary>
    /// A lower resolution of the depth and normals textures from the ZED.
    /// </summary>
    private sl.Resolution resolution = new sl.Resolution(1280, 720);

    /***LIGHTS definitions***/
    /// <summary>
    /// Point light structure for virtual lights on the real world.
    /// Gets sent to the shader via a compute buffer.
    /// </summary>
    [SerializeField]
    public struct PointLight
    {
        /// <summary>
        /// The color, times the intensity.
        /// </summary>
        public Vector4 color;
        /// <summary>
        /// The range of the light.
        /// </summary>
        public float range;
        /// <summary>
        /// The position of the light.
        /// </summary>
        public Vector3 position;
    }
    /// <summary>
    /// Maximum number of point lights accepted.
    /// </summary>
    const int NUMBER_POINT_LIGHT_MAX = 8;

    /// <summary>
    /// Holds a slot for all point lights that should be cast on the real world.
    /// </summary>
    [SerializeField]
    public PointLight[] pointLights = new PointLight[NUMBER_POINT_LIGHT_MAX];
    /// <summary>
    /// The size, or 'stride', of each PointLight in bytes. Needed to construct computeBufferPointLight.
    /// </summary>
    const int SIZE_POINT_LIGHT_BYTES = 32;

    /// <summary>
    /// Used to pass the pointLights array into matRGB's shader as a buffer.
    /// </summary>
    ComputeBuffer computeBufferPointLight;

    /// <summary>
    /// Structure of the spotlight send to the shader
    /// </summary>
    [SerializeField]
    public struct SpotLight
    {
        /// <summary>
        /// The color, times the intensity.
        /// </summary>
        public Vector4 color;
        /// <summary>
        /// The position of the light.
        /// </summary>
        public Vector3 position;
        /// <summary>
        /// The light's normalized direction and angle.
        /// </summary>
        public Vector4 direction;
        /// <summary>
        /// The parameters for the light's falloff.
        /// </summary>
        public Vector4 parameters;
    }
    /// <summary>
    /// Maximum number of spotlights accepted.
    /// </summary>
    const int NUMBER_SPOT_LIGHT_MAX = 8;

    /// <summary>
    /// Holds a slot for all spotlights that should be cast on the real world.
    /// </summary>
    [SerializeField]
    public SpotLight[] spotLights = new SpotLight[NUMBER_SPOT_LIGHT_MAX];

    /// <summary>
    /// The size, or 'stride', of each SpotLight in bytes. Needed to construct computeBufferSpotLight.
    /// </summary>
    const int SIZE_SPOT_LIGHT_BYTES = 60;

    /// <summary>
    /// Maximum number of total lights rendered (point and spot combined).
    /// </summary>
    const int NUMBER_LIGHTS_MAX = NUMBER_POINT_LIGHT_MAX / 2 + NUMBER_SPOT_LIGHT_MAX / 2;

    /// <summary>
    /// Data from a directional light. [0] is its direction and [1] is its color.
    /// Only one directional light is allowed at once.
    /// </summary>
    private Vector4[] directionalLightData = new Vector4[2];

    /// <summary>
    /// Used to pass the spotLights array into matRGB's shader as a buffer.
    /// </summary>
    ComputeBuffer computeBufferSpotLight;

    //Forward ID shader caches.
    /// <summary>
    /// Property ID of the number of point lights in the ZED_Lighting shader include file.
    /// </summary>
    private int numberPointLightsID;

    /// <summary>
    /// Property ID of the number of spotlights in the ZED_Lighting shader include file.
    /// </summary>
    private int numberSpotLightsID;

    /// <summary>
    /// Cached property id for _IsTextured. use isTexturedID property instead.
    /// </summary>
    private int? _istexturedid;
    /// <summary>
    /// Property id for _IsTextured, which is whether the rendered image has a texture overlay.
    /// </summary>
    private int isTexturedID
    {
        get
        {
            if (_istexturedid == null) _istexturedid = Shader.PropertyToID("_IsTextured");
            return (int)_istexturedid;
        }
    }

    /// <summary>
    /// Cached property id for _Mask. use maskPropID property instead.
    /// </summary>
    private int? _maskpropid;
    /// <summary>
    /// Property id for _Mask, which is the RenderTexture property for an overlay texture.
    /// </summary>
    private int maskPropID
    {
        get
        {
            if (_maskpropid == null) _maskpropid = Shader.PropertyToID("_Mask");
            return (int)_maskpropid;
        }
    }

    /*** Post-process definitions***/

    /// <summary>
    /// The mask used for post-processing. Filled at runtime and updated each frame.
    /// </summary>
    private RenderTexture mask;

    /// <summary>
    /// The post process material, used to add noise and change the color.
    /// </summary>
    private Material postprocessMaterial;

    /// <summary>
    /// Activate/deactivate post-processing. If false, the mask will not be generated.
    /// Set by ZEDManager.setRenderingSettings() based on a checkbox in ZEDManager's custom editor.
    /// </summary>
    private bool ARpostProcessing = true;

    /// <summary>
    /// Used to blur the mask.
    /// </summary>
    private Material blurMaterial;

    /// <summary>
    /// Used to load a source image's alpha channel into all channels of a destination image.
    /// Used during post-processing.
    /// </summary>
    private Material blitMaterial;

    /// <summary>
    /// Used to convert the stencil buffer of a rendertexture into a regular texture.
    /// </summary>
    private Material matStencilToMask;

    /// <summary>
    /// Used to compose the virtual mask from different textures.
    /// </summary>
    private Material matComposeMask;

    /// <summary>
    /// Used to blend the textures from ZEDMeshRenderer, when present.
    /// This adds the wireframe effect seen from 3D scanning or plane detection.
    /// </summary>
    private Material blender;

    /// <summary>
    /// What kind of image the final result will display. Usually set to View.
    /// Set this to VIEW_DEPTH or VIEW_NORMAL to see the live depth or normal maps.
    /// </summary>
    public sl.VIEW_MODE viewMode = sl.VIEW_MODE.VIEW_IMAGE;

    /// <summary>
    /// Which side of the camera we render. Left = 0, Right ==1.
    /// </summary>
    private int side
    {
        get
        {
            if (viewSide == ZED_CAMERA_SIDE.LEFT || viewSide == ZED_CAMERA_SIDE.LEFT_FORCE) return 0;
            else return 1;
        }
    }

    /// <summary>
    /// Default near plane value. Overrides camera's settings on start but will update if camera values change at runtime.
    /// </summary>
    private float nearplane = 0.1f;
    /// <summary>
    /// Default far plane value. Overrides camera's settings on start but will update if camera values change at runtime.
    /// </summary>
    private float farplane = 500.0f;

    /// <summary>
    /// The target RenderTexture we'll render to if in AR mode.
    /// </summary>
    [HideInInspector]
    private RenderTexture renderTextureTarget;
    public RenderTexture target
    {
        get { return renderTextureTarget; }
    }

    void Awake()
    {
        //Get the current camera and set the aspect ratio.
        zedManager = gameObject.transform.parent.GetComponent<ZEDManager>();
        cam = GetComponent<Camera>();
        cam.aspect = aspect;
        cam.renderingPath = RenderingPath.UsePlayerSettings; //Assigns the camera's rendering path to be consistent with the project's settings.

        //Make the canvas allow rendering this camera.
        HideFromWrongCameras.RegisterZEDCam(cam); //Makes all objects with a HideFromWrongCamera hide from this, unless set to this specifc one.
        HideFromWrongCameras hider = canvas.GetComponent<HideFromWrongCameras>();
        if (!hider)
        {
            hider = canvas.AddComponent<HideFromWrongCameras>();
        }

        hider.SetRenderCamera(cam); //This canvas will allow this camera to render it.
    }

    /// <summary>
    /// Whether or not post-processing effects are applied.
    /// Usually set by ZEDManager based on the selection in its Inspector.
    /// </summary>
    /// <param name="c"></param>
	public void SetPostProcess(bool c)
    {
        ARpostProcessing = c;
    }

    /// <summary>
    /// The object that forces the ZED image to be shown at 16:9 aspect ratio, regardless of the target window's resolution.
    /// </summary>
    private WindowAspectRatio aspectRatio;

    /// <summary>
    /// Changes the scene's global lighting settings to prevent global illumnation from causing
    /// lighting that doesn't match the real world.
    /// </summary>
    private void SetUpGI()
    {
        //Only do this if "Hide Skybox" is enabled in ZEDManager, which is is by default.
        if (zedManager)
        {
            if (zedManager.greySkybox)
            {
                RenderSettings.skybox = null;
                Color c;
                ColorUtility.TryParseHtmlString("#999999", out c);
                RenderSettings.ambientLight = c;
                DynamicGI.UpdateEnvironment();
            }
        }
    }

    /// <summary>
    /// Configures materials/values/settings needed for post-processing and displaying in proper aspect ratio.
    /// </summary>
    private void Start()
    {
        //No environmental lighting per default
        Shader.SetGlobalFloat("_ZEDExposure", -1);
        //Load the materials.
        matStencilToMask = new Material(Resources.Load("Materials/PostProcessing/Mat_ZED_Stencil2Mask") as Material);
        matComposeMask = new Material(Resources.Load("Materials/PostProcessing/Mat_ZED_MaskCompositor") as Material);

        //Load and configure the post-process material.
        postprocessMaterial = new Material(Resources.Load("Materials/PostProcessing/Mat_ZED_PostProcessing") as Material);
        postprocessMaterial.SetFloat("_gamma", 1.0f / (0.87f * 0.9f));
        postprocessMaterial.SetFloat("_MinBlack", 15.0f / 255.0f);
        postprocessMaterial.SetInt("_NoiseSize", 2);

        //Configure the weights for the blur effect.
        float[] weights;
        float[] offsets;
        ZEDPostProcessingTools.ComputeWeights(0.3f, out weights, out offsets);

        //Set the blur config to the shader, should be constant
        blurMaterial = new Material(Resources.Load("Materials/PostProcessing/Mat_ZED_Blur") as Material);
        blurMaterial.SetFloatArray("weights", weights);
        blurMaterial.SetFloatArray("offset", offsets);
        //blurMaterial.SetTexture("_Mask", mask);
        blurMaterial.SetTexture(maskPropID, mask);


        //Force Unity into 16:9 mode to match the ZED's output.
#if UNITY_EDITOR
        UnityEditor.PlayerSettings.SetAspectRatio(UnityEditor.AspectRatio.Aspect16by9, true);
        UnityEditor.PlayerSettings.SetAspectRatio(UnityEditor.AspectRatio.Aspect16by10, false);
        UnityEditor.PlayerSettings.SetAspectRatio(UnityEditor.AspectRatio.Aspect4by3, false);
        UnityEditor.PlayerSettings.SetAspectRatio(UnityEditor.AspectRatio.Aspect5by4, false);
#endif
        CreateRenderTexture();

        //Load the blender for the zedmesher
        blender = new Material(Resources.Load("Materials/SpatialMapping/Mat_ZED_PostProcess_Blend") as Material);
        SetMeshRenderAvailable(false);

        //Set the bounds around the camera, used to detect if a point/spotlight is close enough to be applied.
        bounds = new Bounds(transform.position, new Vector3(20, 20, 20));

        //IF AR REMOVE
        //aspectRatio = new WindowAspectRatio(cam);

#if ZED_LWRP || ZED_HDRP || ZED_URP
        RenderPipelineManager.beginFrameRendering += SRPStartFrame;
#endif
    }


    /// <summary>
    /// Configures numerous settings that can't be set until the ZED is fully initialized.
    /// Subscribed to ZEDManager.OnZEDReady in OnEnable().
    /// </summary>
    void ZEDReady()
    {
        //Add the fade-in effect for when the camera first becomes visible.
        if (zedManager.fadeInOnStart)
            gameObject.AddComponent<LoadingFade>();

        //This cannot happen but just in case...
        if (zedManager == null)
            return;

        zedCamera = zedManager.zedCamera;
        SetTextures(zedCamera, viewMode);
        canvas.SetActive(true);
        canvas.transform.SetParent(cam.transform);
        ConfigureLightAndShadow(cam.actualRenderingPath);

        //Move the plane with the optical centers.
        float plane_distance = 0.15f;
        Vector4 opticalCenters = zedCamera.ComputeOpticalCenterOffsets(plane_distance);

        if (side == 0)
            canvas.transform.localPosition = new Vector3(opticalCenters.x, -1.0f * opticalCenters.y, plane_distance);
        else if (side == 1)
            canvas.transform.localPosition = new Vector3(opticalCenters.z, -1.0f * opticalCenters.w, plane_distance);

        //Set the camera's parameters based on the ZED's, and scale the screen based on its distance.
        if (zedCamera.IsCameraReady)
        {
            //cam.projectionMatrix = zedCamera.Projection;
            SetProjection(nearplane, farplane);
            cam.nearClipPlane = nearplane;
            cam.farClipPlane = farplane;
            //mainCamera.nearClipPlane = 0.1f;
            //mainCamera.farClipPlane = 500.0f;
            scale(canvas.gameObject, GetFOVYFromProjectionMatrix(cam.projectionMatrix));
            cam.fieldOfView = zedCamera.VerticalFieldOfView * Mathf.Rad2Deg;
        }
        else //Just scale the screen.
        {
            scale(canvas.gameObject, cam.fieldOfView);
        }
    }

    /// <summary>
    /// Hides the canvas. Called when the ZED is disconnected via the ZEDManager.OnZEDDisconnected event.
    /// </summary>
	void ZEDDisconnected()
    {
        canvas.SetActive(false);
    }

    /// <summary>
    /// Fixes GI, enables the canvas and subscribes to events from the ZED.
    /// </summary>
    private void OnEnable()
    {
        SetUpGI();

        meshCanvas = gameObject.transform.GetChild(0).GetComponent<MeshFilter>();
        canvas.SetActive(false);

        //iterate until we found the ZED Manager parent...
        Transform ObjParent = gameObject.transform;
        int tries = 0;
        while (zedManager == null && tries < 5)
        {
            if (ObjParent != null)
                zedManager = ObjParent.GetComponent<ZEDManager>();
            if (zedManager == null && ObjParent != null)
                ObjParent = ObjParent.parent;
            tries++;
        }

        if (zedManager != null)
        {
            zedManager.OnZEDReady += ZEDReady;
            zedManager.OnZEDDisconnected += ZEDDisconnected;
        }

#if ZED_HDRP
        //Check which material the ZEDManager's SRP Lighting Type wants us to use, then apply it.
        Renderer rend = canvas.GetComponent<Renderer>();
        Material basemattoapply;
        bool changemat = zedManager.GetChosenSRPMaterial(out basemattoapply);

        if (changemat) //A specific material has been specified. Apply that one.
        {
            matRGB = new Material(basemattoapply); //We make a new instance so we can apply different textures to the left/right eye, and different cameras.
        }
        else //We'll leave whatever material is already on the canvas.
        {
            matRGB = new Material(rend.material);
        }

        rend.material = matRGB;
#endif
    }

    /// <summary>
    /// Disables the canvas and unsubscribes from events from the ZED.
    /// </summary>
    private void OnDisable()
    {
        if (zedManager != null)
        {
            zedManager.OnZEDReady -= ZEDReady;
            zedManager.OnZEDDisconnected -= ZEDDisconnected;
        }
        canvas.SetActive(false);
    }


    /// <summary>
    /// Invisible object used to force Unity to render a shadow map.
    /// </summary>
    GameObject forceShadowObject = null;

    /// <summary>
    /// Configure the canvas to get and light and shadow.
    /// </summary>
    /// <param name="renderingPath">The current rendering path used</param>
    private void ConfigureLightAndShadow(RenderingPath renderingPath)
    {
        RenderingPath current = actualRenderingPath;
        actualRenderingPath = renderingPath;

        if (renderingPath == RenderingPath.Forward)
        {
            canvas.SetActive(true);
            SetForward();

        }
        else if (renderingPath == RenderingPath.DeferredShading)
        {
            SetDeferred();
        }
        else //We're using an unknown rendering path. Log an error.
        {
            actualRenderingPath = current;
            Debug.LogError(" [ ZED Plugin ] : The rendering path " + cam.actualRenderingPath.ToString() + " is not compatible with the ZED");
        }
    }

#if !ZED_HDRP && !ZED_URP
    /// <summary>
    /// Clear the depth buffer used.
    /// Called when configuring this script for the given rendering path (forward or deferred).
    /// </summary>
    private void ClearDepthBuffers()
    {
        if (buffer[(int)ZED_RENDERING_MODE.FORWARD] != null)
        {
            buffer[(int)ZED_RENDERING_MODE.FORWARD].Dispose();
            buffer[(int)ZED_RENDERING_MODE.FORWARD] = null;
        }

        if (buffer[(int)ZED_RENDERING_MODE.DEFERRED] != null)
        {
            buffer[(int)ZED_RENDERING_MODE.DEFERRED].Dispose();
            buffer[(int)ZED_RENDERING_MODE.DEFERRED] = null;

        }
    }
#endif

    /// <summary>
    /// Configure the materials and buffer for the forward rendering path.
    /// </summary>
    private void SetForward()
    {
        ghasShadows = false;

        blitMaterial = new Material(Resources.Load("Materials/PostProcessing/Mat_ZED_Blit") as Material);

        Shader.SetGlobalInt("_HasShadows", 0);

        gameObject.transform.GetChild(0).GetComponent<MeshRenderer>().enabled = true;

        //Set the canvas's material to the material for forward rendering.
        matRGB = canvas.GetComponent<Renderer>().material;
        matRGB.SetInt("_isLinear", System.Convert.ToInt32(QualitySettings.activeColorSpace));

        forwardMat = new Material(Resources.Load("Materials/Lighting/Mat_ZED_Forward") as Material);


        // Configure the invisible object that forces Unity to calculate shadows.
        if (forceShadowObject == null)
        {
            ConfigureForceShadowObject();
        }

        //Set the textures in the materials to the proper ones.
        matRGB.SetTexture("_MainTex", textureEye);
        matRGB.SetTexture("_CameraTex", textureEye);
        matRGB.SetTexture("_DepthXYZTex", depth);
        matRGB.SetTexture("_NormalsTex", normals);

#if ZED_HDRP && !ZED_URP//Need FoV to calculate world space positions accurately.
        matRGB.SetFloat("_ZEDHFoVRad", zedCamera.GetCalibrationParameters().leftCam.hFOV * Mathf.Deg2Rad);
        matRGB.SetFloat("_ZEDVFoVRad", zedCamera.GetCalibrationParameters().leftCam.vFOV * Mathf.Deg2Rad);
#endif

        forwardMat.SetTexture("_MainTex", textureEye);
        forwardMat.SetTexture("_DepthXYZTex", depth);

#if !ZED_HDRP  && !ZED_URP
        //Clear the buffers.
        if (buffer[(int)ZED_RENDERING_MODE.FORWARD] != null)
            cam.RemoveCommandBuffer(CameraEvent.BeforeDepthTexture, buffer[(int)ZED_RENDERING_MODE.FORWARD]);

        if (buffer[(int)ZED_RENDERING_MODE.DEFERRED] != null)
            cam.RemoveCommandBuffer(CameraEvent.AfterGBuffer, buffer[(int)ZED_RENDERING_MODE.DEFERRED]);

        ClearDepthBuffers();

        if (postProcessBuffer[(int)ZED_RENDERING_MODE.DEFERRED] != null)
            cam.RemoveCommandBuffer(CameraEvent.AfterFinalPass, postProcessBuffer[(int)ZED_RENDERING_MODE.DEFERRED]);

        if (postProcessBuffer[(int)ZED_RENDERING_MODE.DEFERRED] != null)
        {
            postProcessBuffer[(int)ZED_RENDERING_MODE.DEFERRED].Dispose();
            postProcessBuffer[(int)ZED_RENDERING_MODE.DEFERRED] = null;
        }

        //Set the depth buffer.
        buffer[(int)ZED_RENDERING_MODE.FORWARD] = new CommandBuffer();
        buffer[(int)ZED_RENDERING_MODE.FORWARD].name = "ZED_DEPTH";
        buffer[(int)ZED_RENDERING_MODE.FORWARD].SetRenderTarget(BuiltinRenderTextureType.CurrentActive);
        buffer[(int)ZED_RENDERING_MODE.FORWARD].DrawMesh(meshCanvas.mesh, gameObject.transform.GetChild(0).transform.localToWorldMatrix, forwardMat);
#endif

        if (mask == null || !mask.IsCreated())
        {
            mask = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.R8);
        }

        //Set up the post-processing material.
        postprocessMaterial.SetTexture("ZEDMaskPostProcess", mask);
        postprocessMaterial.SetTexture("ZEDTex", textureEye);

#if !ZED_HDRP && !ZED_URP

        postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD] = new CommandBuffer();
        postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD].name = "ZED_FORWARD_POSTPROCESS";
        postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD].Blit(BuiltinRenderTextureType.CameraTarget, mask, blitMaterial, 0);
        postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD].SetGlobalTexture("_ZEDMaskVirtual", mask);


        cam.RemoveCommandBuffer(CameraEvent.AfterForwardAlpha, postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD]);
        cam.AddCommandBuffer(CameraEvent.AfterForwardAlpha, postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD]);
#endif

        //Configure the light containers.
        if (computeBufferPointLight == null)
        {
            computeBufferPointLight = new ComputeBuffer(NUMBER_POINT_LIGHT_MAX, SIZE_POINT_LIGHT_BYTES);
            computeBufferPointLight.SetData(pointLights);

            matRGB.SetBuffer("pointLights", computeBufferPointLight);
        }

        if (computeBufferSpotLight == null)
        {
            computeBufferSpotLight = new ComputeBuffer(NUMBER_SPOT_LIGHT_MAX, SIZE_SPOT_LIGHT_BYTES);
            computeBufferSpotLight.SetData(spotLights);
            matRGB.SetBuffer("spotLights", computeBufferSpotLight);
        }

        //Register the property IDs to improve performance. (Setting properties by string is slower)
        numberPointLightsID = Shader.PropertyToID("numberPointLights");
        numberSpotLightsID = Shader.PropertyToID("numberSpotLights");

    }

    /// <summary>
    /// Configure the materials and buffer for the deferred rendering path.
    /// </summary>
    private void SetDeferred()
    {
        //Disable MSSA as it's not supported with deferred.
#if UNITY_5_6_OR_NEWER
        cam.allowMSAA = false;
#endif

        ghasShadows = false;
        deferredMat = new Material(Resources.Load("Materials/Lighting/Mat_ZED_Deferred") as Material);
        blitMaterial = new Material(Resources.Load("Materials/PostProcessing/Mat_ZED_Blit") as Material);

        //Sets the custom shader for the deferred pipeline.
        GraphicsSettings.SetCustomShader(BuiltinShaderType.DeferredShading, (Resources.Load("Materials/Lighting/Mat_ZED_Deferred_Lighting") as Material).shader);

        deferredMat.SetMatrix("_Model", canvas.transform.localToWorldMatrix.transpose);
        deferredMat.SetMatrix("_Projection", cam.projectionMatrix);

        deferredMat.SetTexture("_MainTex", textureEye);
        deferredMat.SetTexture("_DepthXYZTex", depth);
        deferredMat.SetTexture("_NormalsTex", normals);


        //Clear the buffers.
#if !ZED_HDRP && !ZED_URP
        if (buffer[(int)ZED_RENDERING_MODE.FORWARD] != null)
            cam.RemoveCommandBuffer(CameraEvent.BeforeDepthTexture, buffer[(int)ZED_RENDERING_MODE.FORWARD]);

        if (buffer[(int)ZED_RENDERING_MODE.DEFERRED] != null)
            cam.RemoveCommandBuffer(CameraEvent.AfterGBuffer, buffer[(int)ZED_RENDERING_MODE.DEFERRED]);

        if (postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD] != null)
            cam.RemoveCommandBuffer(CameraEvent.AfterForwardAlpha, postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD]);

        if (postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD] != null)
        {
            postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD].Dispose();
            postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD] = null;
        }

        ClearDepthBuffers();

        //Set the depths buffer. This buffer will be changed if the camera is set to allow HDR.
        buffer[(int)ZED_RENDERING_MODE.DEFERRED] = new CommandBuffer();
        buffer[(int)ZED_RENDERING_MODE.DEFERRED].name = "ZED_DEPTH";


        if (cam.allowHDR)
        {
            RenderTargetIdentifier[] mrt = { BuiltinRenderTextureType.GBuffer0, BuiltinRenderTextureType.GBuffer1, BuiltinRenderTextureType.GBuffer2, BuiltinRenderTextureType.CameraTarget };
            buffer[(int)ZED_RENDERING_MODE.DEFERRED].SetRenderTarget(mrt, BuiltinRenderTextureType.CameraTarget);
        }
        else
        {
            RenderTargetIdentifier[] mrt = { BuiltinRenderTextureType.GBuffer0, BuiltinRenderTextureType.GBuffer1, BuiltinRenderTextureType.GBuffer2, BuiltinRenderTextureType.GBuffer3 };
            buffer[(int)ZED_RENDERING_MODE.DEFERRED].SetRenderTarget(mrt, BuiltinRenderTextureType.CameraTarget);
        }
        buffer[(int)ZED_RENDERING_MODE.DEFERRED].DrawMesh(meshCanvas.mesh, gameObject.transform.GetChild(0).transform.localToWorldMatrix, deferredMat);

        if (mask == null || !mask.IsCreated())
        {
            mask = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.R8);
        }
        //Set the post process buffer
        postProcessBuffer[(int)ZED_RENDERING_MODE.DEFERRED] = new CommandBuffer();
        postProcessBuffer[(int)ZED_RENDERING_MODE.DEFERRED].name = "ZED_FORWARD_POSTPROCESS";
        postProcessBuffer[(int)ZED_RENDERING_MODE.DEFERRED].Blit(BuiltinRenderTextureType.GBuffer0, mask, blitMaterial, 0);
        postProcessBuffer[(int)ZED_RENDERING_MODE.DEFERRED].SetGlobalTexture("_ZEDMaskVirtual", mask);
        postprocessMaterial.SetTexture("ZEDMaskPostProcess", mask);
        postprocessMaterial.SetTexture("ZEDTex", textureEye);



        cam.AddCommandBuffer(CameraEvent.AfterGBuffer, buffer[(int)ZED_RENDERING_MODE.DEFERRED]);
        cam.AddCommandBuffer(CameraEvent.AfterFinalPass, postProcessBuffer[(int)ZED_RENDERING_MODE.DEFERRED]);
#endif

        //Congigure the invisible object
        if (forceShadowObject == null)
        {
            ConfigureForceShadowObject();
        }
        transform.GetChild(0).GetComponent<MeshRenderer>().enabled = false;

    }

    /// <summary>
    /// Sets up the invisible shadow GameObject that forces Unity to draw shadows.
    /// </summary>
    private void ConfigureForceShadowObject()
    {
        forceShadowObject = GameObject.CreatePrimitive(PrimitiveType.Quad);
        forceShadowObject.name = "ZED_FORCE_SHADOW";
        forceShadowObject.transform.parent = transform;

        forceShadowObject.transform.localPosition = new Vector3(0, 0, cam.nearClipPlane);
        forceShadowObject.GetComponent<MeshRenderer>().sharedMaterial = Resources.Load("Materials/Lighting/Mat_ZED_Hide") as Material;
        Destroy(forceShadowObject.GetComponent<MeshCollider>());
        forceShadowObject.hideFlags = HideFlags.HideInHierarchy;
    }

    /// <summary>
    /// The bounds around the camera that filter out point/spotlights that are too far away to be rendered.
    /// </summary>
    private Bounds bounds;

    /// <summary>
    /// Sets the camera's local pos/rot to origin/identity and sets up the RenderTexture if in stereo mode.
    /// This RenderTexture is then displayed in hidden planes handled by ZEDMixedRealityPlugin where the final
    /// output to the HMD is rendered.
    /// </summary>
    private void CreateRenderTexture()
    {
        transform.localRotation = Quaternion.identity;
        transform.localPosition = new Vector3(0, 0, 0);
        if (cam.stereoTargetEye != StereoTargetEyeMask.None && zedManager.IsStereoRig == true)
        {
            if (zedCamera != null && zedCamera.IsCameraReady)
            {
                renderTextureTarget = new RenderTexture(zedCamera.ImageWidth, zedCamera.ImageHeight, 24, RenderTextureFormat.ARGB32);
                cam.targetTexture = renderTextureTarget;
            }
            else if (sl.ZEDCamera.CheckPlugin())
            {
                renderTextureTarget = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.ARGB32);
                cam.targetTexture = renderTextureTarget;
            }
        }
    }

    /// <summary>
    /// Creates and sets the textures from the ZED, including image, depth and normals as needed.
    /// Once created, the ZED SDK updates the textures automatically when the ZED sends new frames, so they don't need to be updated here.
    /// </summary>
    /// <param name="zedCamera"></param>
	private void SetTextures(sl.ZEDCamera zedCamera, sl.VIEW_MODE view_mode)
    {
        float baseline = zedCamera.Baseline;
        canvas.transform.localRotation = Quaternion.identity;
        canvas.transform.localPosition = new Vector3(0, 0, 0);

        if (zedManager.IsStereoRig == true && cam.stereoTargetEye != StereoTargetEyeMask.None)
        {
            if (zedCamera != null && zedCamera.IsCameraReady)
            {
                renderTextureTarget = new RenderTexture(zedCamera.ImageWidth, zedCamera.ImageHeight, 24, RenderTextureFormat.ARGB32);
                cam.targetTexture = renderTextureTarget;
            }

            //Set the camera to match the target stereo eye, unless force otherwise.
            switch (cam.stereoTargetEye)
            {
                case StereoTargetEyeMask.Left:
                    if (viewSide == ZED_CAMERA_SIDE.RIGHT) viewSide = ZED_CAMERA_SIDE.LEFT;
                    break;
                case StereoTargetEyeMask.Right:
                    if (viewSide == ZED_CAMERA_SIDE.LEFT) viewSide = ZED_CAMERA_SIDE.RIGHT;
                    break;
                default:
                    break;
            }
        }

        switch (viewSide) //Set up textures from the left camera or right camera, depending.
        {
            case ZED_CAMERA_SIDE.LEFT:
            case ZED_CAMERA_SIDE.LEFT_FORCE:
            default:
                switch (view_mode) //Which kind of texture we view.
                {
                    case sl.VIEW_MODE.VIEW_IMAGE:
                        textureEye = zedCamera.CreateTextureImageType(sl.VIEW.LEFT);
                        break;
                    case sl.VIEW_MODE.VIEW_DEPTH:
                        textureEye = zedCamera.CreateTextureImageType(sl.VIEW.DEPTH);
                        break;
                    case sl.VIEW_MODE.VIEW_NORMALS:
                        textureEye = zedCamera.CreateTextureImageType(sl.VIEW.NORMALS);
                        break;
                }
                normals = zedCamera.CreateTextureMeasureType(sl.MEASURE.NORMALS, resolution);
                depth = zedCamera.CreateTextureMeasureType(sl.MEASURE.DEPTH, resolution);
                break;
            case ZED_CAMERA_SIDE.RIGHT:
            case ZED_CAMERA_SIDE.RIGHT_FORCE:
                switch (view_mode)//Which kind of texture we view.
                {
                    case sl.VIEW_MODE.VIEW_IMAGE:
                        textureEye = zedCamera.CreateTextureImageType(sl.VIEW.RIGHT);
                        break;
                    case sl.VIEW_MODE.VIEW_DEPTH:
                        textureEye = zedCamera.CreateTextureImageType(sl.VIEW.DEPTH_RIGHT);
                        break;
                    case sl.VIEW_MODE.VIEW_NORMALS:
                        textureEye = zedCamera.CreateTextureImageType(sl.VIEW.NORMALS_RIGHT);
                        break;
                }
                normals = zedCamera.CreateTextureMeasureType(sl.MEASURE.NORMALS_RIGHT, resolution);
                depth = zedCamera.CreateTextureMeasureType(sl.MEASURE.DEPTH_RIGHT, resolution);
                break;
        }
    }

    /// <summary>
    /// Enables/disables keywords for the material used in the first pass, when in forward rendering.
    /// </summary>
    /// <param name="enable">New state of the keyword.</param>
    /// <param name="name">Keyword's name.</param>
    /// <returns></returns>
    public bool ManageKeywordForwardMat(bool enable, string name)
    {
        if (forwardMat)
        {
            if (enable)
            {
                forwardMat.EnableKeyword(name);
            }
            else
            {
                forwardMat.DisableKeyword(name);
            }
            return true;

        }

        return false;
    }

    /// <summary>
    /// Enables/disables keywords for the material used in the first pass, when in deferred rendering.
    /// </summary>
    /// <param name="enable">New state of the keyword.</param>
    /// <param name="name">Keyword's name.</param>
    /// <returns></returns>
	public bool ManageKeywordDeferredMat(bool enable, string name)
    {
        if (deferredMat)
        {
            if (enable)
            {
                deferredMat.EnableKeyword(name);
            }
            else
            {
                deferredMat.DisableKeyword(name);
            }
            return true;

        }

        return false;
    }

    /// <summary>
    /// Enables/disables keywords for the final display material.
    /// </summary>
    /// <param name="enable">New state of the keyword.</param>
    /// <param name="name">Keyword's name.</param>
    /// <returns></returns>
	public bool ManageKeywordPipe(bool enable, string name)
    {
        if (matRGB)
        {
            if (enable)
            {
                matRGB.EnableKeyword(name);
            }
            else
            {
                matRGB.DisableKeyword(name);
            }
            return true;

        }

        return false;
    }


    //Variables to get information about the lights.

    /// <summary>
    /// How many point lights are currently being rendered on the real world by this camera. Excludes ones filtered out by distance.
    /// </summary>
    [HideInInspector]
    public int numberPointLights;

    /// <summary>
    /// How many spotlights are currently being rendered on the real world by this camera. Excludes ones filtered out by distance.
    /// </summary>
    [HideInInspector]
    public int numberSpotLights;
    bool ghasShadows = false;

    /// <summary>
    /// Updates lighting information, packages them into ComputeBuffers and sends them to the shader.
    /// </summary>
    void UpdateLights()
    {
        bool hasShadows = false;

        int pointLightIndex = 0;
        int spotLightIndex = 0;
        bounds.center = transform.position;

        foreach (ZEDLight zed_light in ZEDLight.s_lights)
        {
            Light light = zed_light.cachedLight;

            if (light.type == LightType.Directional || Vector3.Distance(bounds.center, light.transform.position) < (light.range + bounds.extents.x))
            {

                //Deactivate all shadows from point light and spotlights as they are not currently supported.
                if (light.type != LightType.Directional)
                {
                    light.shadows = LightShadows.None;
                }
                if (zed_light.IsEnabled() && ((pointLightIndex + spotLightIndex) < NUMBER_LIGHTS_MAX || light.type == LightType.Directional))
                {
                    if (light.type == LightType.Point)
                    {

                        if (pointLightIndex < NUMBER_POINT_LIGHT_MAX)
                        {
                            pointLights[pointLightIndex].color = light.color * light.intensity;
                            pointLights[pointLightIndex].position = light.gameObject.transform.position;
                            pointLights[pointLightIndex].range = light.range;

                            pointLightIndex++;
                        }

                    }

                    else if (light.type == LightType.Spot)
                    {

                        if (spotLightIndex < NUMBER_SPOT_LIGHT_MAX)
                        {
                            spotLights[spotLightIndex].color = light.color * light.intensity;
                            spotLights[spotLightIndex].position = light.gameObject.transform.position;
                            spotLights[spotLightIndex].direction = new Vector4(light.gameObject.transform.forward.normalized.x, light.gameObject.transform.forward.normalized.y, light.gameObject.transform.forward.normalized.z, Mathf.Cos((light.spotAngle / 2.0f) * Mathf.Deg2Rad));
                            spotLights[spotLightIndex].parameters = new Vector4(light.spotAngle, light.intensity, 1.0f / light.range, zed_light.interiorCone);
                            spotLightIndex++;
                        }
                    }
                    else if (light.type == LightType.Directional)
                    {
                        hasShadows = light.shadows != LightShadows.None && QualitySettings.shadows != ShadowQuality.Disable;
                        directionalLightData[0] = new Vector4(light.gameObject.transform.forward.normalized.x, light.gameObject.transform.forward.normalized.y, light.gameObject.transform.forward.normalized.z, 0);
                        directionalLightData[1] = light.color * light.intensity;
                        // Copy the shadows from the directional light. If not, no shadows in transparent mode.
                        if (light.commandBufferCount == 0)
                        {
                            forwardMat.SetInt("_HasShadows", System.Convert.ToInt32(light.shadows != LightShadows.None));

                            // Copy the shadows from the directional light. If not, no shadows in transparent mode.
                            if (light.commandBufferCount == 0)
                            {
                                CommandBuffer lightBuffer = new CommandBuffer();
                                lightBuffer.name = "ZED_Copy_ShadowMap";
                                lightBuffer.SetGlobalTexture("_DirectionalShadowMap", BuiltinRenderTextureType.CurrentActive);

                                light.AddCommandBuffer(LightEvent.AfterScreenspaceMask, lightBuffer);
                            }

                        }
                    }
                }
            }
        }
        //Send the new light data to the final display material.
        if (computeBufferPointLight != null)
        {
            computeBufferPointLight.SetData(pointLights);
        }
        if (computeBufferSpotLight != null)
        {
            computeBufferSpotLight.SetData(spotLights);
        }

        numberPointLights = pointLightIndex;
        numberSpotLights = spotLightIndex;

        if (matRGB != null)
        {
            //Send the number of point lights/spotlights to the shader.
            matRGB.SetInt(numberPointLightsID, pointLightIndex);
            matRGB.SetInt(numberSpotLightsID, spotLightIndex);

#if !ZED_HDRP && !ZED_URP
            //Add the command buffer to get shadows only if a directional light creates shadows.
            if (hasShadows != ghasShadows)
            {
                ghasShadows = hasShadows;
                Shader.SetGlobalInt("_HasShadows", System.Convert.ToInt32(ghasShadows));
                cam.RemoveCommandBuffer(CameraEvent.BeforeDepthTexture, buffer[(int)ZED_RENDERING_MODE.FORWARD]);
                if (hasShadows)
                {
                    cam.AddCommandBuffer(CameraEvent.BeforeDepthTexture, buffer[(int)ZED_RENDERING_MODE.FORWARD]);
                }

            }
            matRGB.SetVectorArray("ZED_directionalLight", directionalLightData);
#endif
        }
    }


    /// <summary>
    /// Gets the vertical field of view from the given projection matrix, to bypass a round number.
    /// </summary>
    /// <param name="projection">Projection matrix from a camera.</param>
    /// <returns></returns>
    float GetFOVYFromProjectionMatrix(Matrix4x4 projection)
    {
        return Mathf.Atan(1 / projection[1, 1]) * 2.0f;
    }

    /// <summary>
    /// Gets the horizontal field of view from the given projection matrix.
    /// </summary>
    /// <param name="projection">Projection matrix from a camera.</param>
    /// <returns></returns>
	float GetFOVXFromProjectionMatrix(Matrix4x4 projection)
    {
        return Mathf.Atan(1 / projection[0, 0]) * 2.0f;
    }

    /// <summary>
    /// Scales the canvas in front of the camera so that it fills the whole screen exactly.
    /// </summary>
    /// <param name="screen">Canvas object.</param>
    /// <param name="fov">Camera's vertical field of view. </param>
	private void scale(GameObject screen, float fov)
    {
        float height = Mathf.Tan(0.5f * fov) * Vector3.Distance(screen.transform.localPosition, Vector3.zero) * 2;
        screen.transform.localScale = new Vector3((height * aspect), height, 1);
    }

    private void scaleXY(GameObject screen, float fovH, float fovV)
    {
        float height = Mathf.Tan(0.5f * fovV) * Vector3.Distance(screen.transform.localPosition, Vector3.zero) * 2;
        float width = Mathf.Tan(0.5f * fovH) * Vector3.Distance(screen.transform.localPosition, Vector3.zero) * 2;
        screen.transform.localScale = new Vector3(width, height, 1);
    }

#if !ZED_HDRP && !ZED_URP
    /// <summary>
    /// Clears all command buffers on the camera.
    /// </summary>
    public void Clear()
    {

        if (buffer[(int)ZED_RENDERING_MODE.FORWARD] != null)
            cam.RemoveCommandBuffer(CameraEvent.BeforeDepthTexture, buffer[(int)ZED_RENDERING_MODE.FORWARD]);

        if (buffer[(int)ZED_RENDERING_MODE.DEFERRED] != null)
            cam.RemoveCommandBuffer(CameraEvent.AfterGBuffer, buffer[(int)ZED_RENDERING_MODE.DEFERRED]);

        if (postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD] != null)
            cam.RemoveCommandBuffer(CameraEvent.AfterForwardAlpha, postProcessBuffer[(int)ZED_RENDERING_MODE.FORWARD]);

        if (postProcessBuffer[(int)ZED_RENDERING_MODE.DEFERRED] != null)
            cam.RemoveCommandBuffer(CameraEvent.AfterFinalPass, postProcessBuffer[(int)ZED_RENDERING_MODE.DEFERRED]);


        ClearDepthBuffers();

    }
#endif

    void OnApplicationQuit()
    {
        if (computeBufferPointLight != null)
        {
            computeBufferPointLight.Release();
        }

        if (computeBufferSpotLight != null)
        {
            computeBufferSpotLight.Release();
        }

        if (mask != null)
        {
            mask.Release();
        }

#if ZED_HDRP || ZED_URP
        RenderPipelineManager.beginFrameRendering -= SRPStartFrame;
#endif
    }

    /// <summary>
    /// Updates the output size to fit the window at 16:9 and the bounds for light filtering, and calculates the lighting.
    /// </summary>
    void Update()
    {
        if (zedManager == null)
            return;

        if (aspectRatio != null)
        {
            aspectRatio.Update();
        }

        if (actualRenderingPath == RenderingPath.Forward)
        {
            bounds.center = transform.position;
            UpdateLights();
        }

        if (zedManager.IsZEDReady && (cam.nearClipPlane != nearplane || cam.farClipPlane != farplane))
        {
            SetProjection(nearplane, farplane); //If the camera's near/far planes changed, update the matrix.
        }

#if UNITY_EDITOR
        if (actualRenderingPath != RenderingPath.VertexLit && cam.actualRenderingPath != actualRenderingPath)
        {
            ConfigureLightAndShadow(cam.actualRenderingPath);
        }
#endif
    }

    /// <summary>
    /// Used by the ZEDMeshRenderer/ZEDPlaneRenderer to draw chunks/planes onto the final images.
    /// </summary>
	private RenderTexture textureMapping;

    /// <summary>
    /// Sets the RenderTexture that gets blended into the final result, if using Plane Detection or Spatial Mapping.
    /// ZEDPlaneRenderer and ZEDMeshRenderer call this with the RenderTextures to which they render each frame.
    /// </summary>
    /// <param name="texture"></param>
    public void SetTextureOverlayMapping(RenderTexture texture)
    {
        textureMapping = texture;
        blender.SetTexture("_ZEDMeshTex", textureMapping);
    }

    public void SetMeshRenderAvailable(bool r)
    {
        int d = -1;
        if (r) d = 0;
        blender.SetInt("_IsTextured", d);
        blender.SetInt(isTexturedID, d);
    }

#if ZED_HDRP || ZED_URP
    /// <summary>
    /// Blend the wireframe into the image. Used in SRP because there is no OnRenderImage automatic function.
    /// </summary>
    private void SRPStartFrame(ScriptableRenderContext context, Camera[] rendcam)
    {
        foreach(Camera camera in rendcam)
        {
            if (camera == renderingCam && zedManager.GetSpatialMapping.display)
            {
                DrawSpatialMappingMeshes(camera);
            }
        }

    }
    /// <summary>
    /// Draw every chunk of the wiremesh
    /// </summary>
    /// <param name="drawcam"></param>
    private void DrawSpatialMappingMeshes(Camera drawcam)
    {

        ZEDSpatialMapping spatialMapping = zedManager.GetSpatialMapping;

        if (spatialMapping == null) return;
        if(spatialMapping.IsRunning()) // Draw all chunks/submeshes used while spatial mapping is running, before merging.
        {
            foreach (ZEDSpatialMapping.Chunk chunk in spatialMapping.Chunks.Values)
            {
                Matrix4x4 canvastrs = Matrix4x4.TRS(chunk.o.transform.position, chunk.o.transform.rotation, chunk.o.transform.localScale);
                Graphics.DrawMesh(chunk.mesh, canvastrs, chunk.o.GetComponent<MeshRenderer>().material, gameObject.layer, drawcam, 0, null, false, false);
            }
        }
        else  if (!spatialMapping.IsRunning()) // Draw final chunks, after merging.
            foreach (ZEDSpatialMapping.Chunk chunk in spatialMapping.ChunkList)
            {
                Matrix4x4 canvastrs = Matrix4x4.TRS(chunk.o.transform.position, chunk.o.transform.rotation, chunk.o.transform.localScale);
                Graphics.DrawMesh(chunk.mesh, canvastrs, chunk.o.GetComponent<MeshRenderer>().material, gameObject.layer, drawcam, 0, null, false, false);
            }
    }
#else
    /// <summary>
    /// Where the post-processing occurs.
    /// Called by Unity whenever the attached Camera renders an image.
    /// </summary>
    /// <param name="source"></param>
    /// <param name="destination"></param>
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (zedManager.GetSpatialMapping.display) //If displaying a mesh from spatial mapping, blend the wireframe into the image.
        {
            RenderTexture tmpSource = RenderTexture.GetTemporary(source.width, source.height, source.depth, source.format, RenderTextureReadWrite.sRGB);
            Graphics.Blit(source, tmpSource);

            Graphics.Blit(tmpSource, destination, blender);
            RenderTexture.ReleaseTemporary(tmpSource);
        }
        else
        {
            if (ARpostProcessing && mask != null && zedCamera.IsCameraReady) //Apply post-processing, if enabled.
            {

                if (actualRenderingPath == RenderingPath.DeferredShading)
                {

                    RenderTexture bluredMask = RenderTexture.GetTemporary(mask.width, mask.height, mask.depth, mask.format);
                    RenderTexture buffer = RenderTexture.GetTemporary(source.width, source.height, 24);

                    Graphics.SetRenderTarget(buffer);
                    GL.Clear(false, true, new Color(0, 0, 0, 0));   // clear the full RT

                    //To keep the stencil in post-processing, since Graphics.Blit normally clears it.
                    Graphics.SetRenderTarget(buffer.colorBuffer, source.depthBuffer);
                    Graphics.Blit(source, matStencilToMask);

                    //Compose the second mask retrieved in the forward pass. The shader should set the stencil to 148.
                    Graphics.Blit(mask, bluredMask);
                    //matComposeMask.SetTexture("_Mask", bluredMask);
                    matComposeMask.SetTexture(maskPropID, bluredMask);
                    Graphics.Blit(buffer, mask, matComposeMask);

                    ApplyPostProcess(source, destination, bluredMask);

                    RenderTexture.ReleaseTemporary(buffer);
                    RenderTexture.ReleaseTemporary(bluredMask);
                }
                else //Forward path.
                {
                    RenderTexture bluredMask = RenderTexture.GetTemporary(mask.width, mask.height, mask.depth, mask.format);
                    ApplyPostProcess(source, destination, bluredMask);
                    RenderTexture.ReleaseTemporary(bluredMask);
                }
            }
            else //Not using post-processing.
            {
                Graphics.Blit(source, destination);
            }
        }
    }
#endif
    /// <summary>
    /// Apply post-processing effects to the given RenderTexture.
    /// </summary>
    /// <param name="source">Source RenderTexture.</param>
    /// <param name="destination">Destination RenderTexture.</param>
    /// <param name="bluredMask">Mask used to apply blurring effects.</param>
    private void ApplyPostProcess(RenderTexture source, RenderTexture destination, RenderTexture bluredMask)
    {
        RenderTexture tempDestination = RenderTexture.GetTemporary(source.width, source.height, source.depth, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Default);

        Graphics.Blit(source, tempDestination, postprocessMaterial);
        ZEDPostProcessingTools.Blur(mask, bluredMask, blurMaterial, 3, 1, 1);

        //blurMaterial.SetTexture("_Mask", bluredMask);
        blurMaterial.SetTexture(maskPropID, bluredMask);
        ZEDPostProcessingTools.Blur(tempDestination, destination, blurMaterial, 2, 1, 1);
        mask.SetGlobalShaderProperty("_ZEDMaskVirtual");
        RenderTexture.ReleaseTemporary(tempDestination);

    }

    /// <summary>
    /// Assigns the projection matrix from the ZED to this camera with the specified near/far planes.
    /// </summary><remarks>
    /// Adjusts the matrix values from a copy rather than reassigning them in ZEDCamera to avoid getting applied
    /// to all copies of the camera.
    ///</remarks>
    ///<param name="near">Desired near plane distance.</param>
    ///<param name="far">Desired far plane distance.</param>
    private void SetProjection(float near = 0.1f, float far = 500f)
    {
        //float near = mainCamera.nearClipPlane;
        //float far = mainCamera.farClipPlane;

        Matrix4x4 newmat = zedCamera.Projection;
        newmat[2, 2] = -(far + near) / (far - near);
        newmat[2, 3] = -(2.0f * far * near) / (far - near);
        cam.projectionMatrix = newmat;

        nearplane = near;
        farplane = far;
    }

    /// Forces the ZED's image to be displayed at a 16:9 ratio, regardless of the window's aspect ratio.
    /// This is why the image doesn't stretch when the Game window in the editor is set to Free Aspet.
    /// </summary>
    public class WindowAspectRatio
    {
        /// <summary>
        /// Current screen width.
        /// </summary>
        private int ScreenSizeX = 0;

        /// <summary>
        /// Current screen height.
        /// </summary>
        private int ScreenSizeY = 0;

        /// <summary>
        /// Camera to set to 16:9.
        /// </summary>
        private Camera cam;

        /// <summary>
        /// Aspect ratio targeted.
        /// </summary>
        private const float TARGET_ASPECT = 16.0f / 9.0f;


        public WindowAspectRatio(Camera camera)
        {
            cam = camera;
            RescaleCamera();
            CreateCamera();
        }

        /// <summary>
        /// Create a custom hidden camera to render black bars in the background.
        /// </summary>
        /// <returns></returns>
        private GameObject CreateCamera()
        {
            GameObject o = new GameObject("CameraBlackBackground");
            Camera cam = o.AddComponent<Camera>();
            cam.backgroundColor = Color.black;
            cam.cullingMask = 0;
            cam.clearFlags = CameraClearFlags.SolidColor;
            cam.depth = -int.MaxValue;
            cam.useOcclusionCulling = false;
#if UNITY_5_6_OR_NEWER
            cam.allowHDR = false;
            cam.allowMSAA = false;
#endif
            cam.stereoTargetEye = StereoTargetEyeMask.None;
            cam.renderingPath = RenderingPath.Forward;
            o.hideFlags = HideFlags.HideInHierarchy;
            return o;

        }

        /// <summary>
        /// Rescale the view port of the current camera to keep the 16:9 aspect ratio.
        /// This is called on start and updated each frame.
        /// </summary>
        private void RescaleCamera()
        {
            //If no change, then return
            if (Screen.width == ScreenSizeX && Screen.height == ScreenSizeY) return;

            float windowaspect = (float)Screen.width / (float)Screen.height;
            float scaleheight = windowaspect / TARGET_ASPECT;

            if (scaleheight < 1.0f) //Height is too large. Shrink it, adding letterboxes to the top and bottom.
            {
                Rect rect = cam.rect;

                rect.width = 1.0f;
                rect.height = scaleheight;
                rect.x = 0;
                rect.y = (1.0f - scaleheight) / 2.0f;

                //cam.rect = rect;
            }
            else //Height is too small. Reduce width, adding pillarboxes to the sides.
            {
                float scalewidth = 1.0f / scaleheight;

                Rect rect = cam.rect;

                rect.width = scalewidth;
                rect.height = 1.0f;
                rect.x = (1.0f - scalewidth) / 2.0f;
                rect.y = 0;

                cam.rect = rect;
            }

            ScreenSizeX = Screen.width;
            ScreenSizeY = Screen.height;
        }


        /// <summary>
        /// Calls RescaleCamera(). Called in ZEDRenderingPlane's Update() function.
        /// </summary>
        public void Update()
        {
            RescaleCamera();
        }

    }

}