/// <summary>
 /// Render a reflection camera.
 /// </summary>
 /// <param name="info">Camera info</param>
 /// <param name="reflectionTransform">Reflection transform (parent of reflection camera)</param>
 /// <param name="reflectionNormal">Reflection normal vector (up for planes, forward for quads)</param>
 /// <param name="clipPlaneOffset">Clip plane offset for near clipping</param>
 /// <param name="stereoSeparationMultiplier">Stereo separation multiplier</param>
 public static void RenderReflection
 (
     ReflectionCameraInfo info,
     Transform reflectionTransform,
     Vector3 reflectionNormal,
     float clipPlaneOffset,
     float stereoSeparationMultiplier
 )
 {
     if (info.SourceCamera.stereoEnabled)
     {
         if (info.SourceCamera.stereoTargetEye == StereoTargetEyeMask.Both || info.SourceCamera.stereoTargetEye == StereoTargetEyeMask.Left)
         {
             RenderReflectionInternal(info, reflectionTransform, reflectionNormal, clipPlaneOffset, StereoTargetEyeMask.Left, info.TargetTexture, stereoSeparationMultiplier);
         }
         if (info.SourceCamera.stereoTargetEye == StereoTargetEyeMask.Both || info.SourceCamera.stereoTargetEye == StereoTargetEyeMask.Right)
         {
             RenderReflectionInternal(info, reflectionTransform, reflectionNormal, clipPlaneOffset, StereoTargetEyeMask.Right, info.TargetTexture2, stereoSeparationMultiplier);
         }
     }
     else
     {
         RenderReflectionInternal(info, reflectionTransform, reflectionNormal, clipPlaneOffset, StereoTargetEyeMask.Both, info.TargetTexture, stereoSeparationMultiplier);
     }
 }
        /// <summary>
        /// Queue a reflection camera
        /// </summary>
        /// <param name="sourceCamera">Viewing camera</param>
        /// <returns>Reflection camera info</returns>
        public ReflectionCameraInfo QueueReflection(Camera sourceCamera)
        {
            bool isReflection;

            if (ReflectMaterial == null || sourceCamera == null || WeatherMakerLightManagerScript.Instance == null || ShouldIgnoreCamera(sourceCamera, out isReflection))
            {
                return(null);
            }

            // HACK: Reflections need to ensure the frustum planes and corners are up to date. This is normally done in a pre-render
            //  event, but we cannot do that as rendering a reflection in pre-render mucks up state and such
            WeatherMakerLightManagerScript.Instance.CalculateFrustumPlanes(sourceCamera);

            // this frustum cull test is expensive, but well worth it if it prevents a reflection from needing to be rendered
            // get up to date frustum info
            if (ReflectRenderer != null && !WeatherMakerGeometryUtility.BoxIntersectsFrustum(sourceCamera, WeatherMakerLightManagerScript.Instance.CurrentCameraFrustumPlanes,
                                                                                             WeatherMakerLightManagerScript.Instance.CurrentCameraFrustumCorners, ReflectRenderer.bounds))
            {
                return(null);
            }

            // Debug.LogFormat("WeatherMakerReflectionScript rendering in {0}, bounds: {1}", sourceCamera.name, ReflectMaterial.bounds);
            // start off some render textures for the reflection
            KeyValuePair <RenderTexture, RenderTexture> kv = new KeyValuePair <RenderTexture, RenderTexture>
                                                             (
                ReflectMaterial.GetTexture(ReflectionSamplerName) as RenderTexture,
                ReflectMaterial.GetTexture(ReflectionSamplerName2) as RenderTexture
                                                             );

            currentRenderTextures.Add(kv);
            ReflectionCameraInfo cam = CreateReflectionCamera(sourceCamera, isReflection);

            RenderReflectionCamera(cam);
            return(cam);
        }
        private void RenderReflectionCamera(ReflectionCameraInfo info)
        {
            // bail if we don't have a camera or renderer
            if (info == null || info.ReflectionCamera == null || info.SourceCamera == null || ReflectRenderer == null || ReflectRenderer.sharedMaterial == null || !ReflectRenderer.enabled)
            {
                return;
            }

            CurrentRecursionLevel = currentCameras.Count + (info.SourceCameraIsReflection ? 1 : 0);
            renderCount++;
            Camera        sourceCamera       = info.SourceCamera;
            Camera        reflectionCamera   = info.ReflectionCamera;
            int           oldPixelLightCount = QualitySettings.pixelLightCount;
            int           oldCullingMask     = reflectionCamera.cullingMask;
            bool          oldSoftParticles   = QualitySettings.softParticles;
            int           oldAntiAliasing    = QualitySettings.antiAliasing;
            ShadowQuality oldShadows         = QualitySettings.shadows;

            SyncCameraSettings(reflectionCamera, sourceCamera);

            // MAGIC MIRROR RECURSION OPTIMIZATION
            if (currentCameras.Count > 1)
            {
                if (currentCameras.Count > 3)
                {
                    QualitySettings.shadows = ShadowQuality.Disable;
                }
                QualitySettings.shadows         = ShadowQuality.HardOnly;
                QualitySettings.antiAliasing    = 0;
                QualitySettings.softParticles   = false;
                QualitySettings.pixelLightCount = 0;
                reflectionCamera.cullingMask   &= waterLayerInverse;
            }
            else
            {
                QualitySettings.pixelLightCount = MaximumPerPixelLightsToReflect;
            }
            Transform reflectionTransform = transform;
            Vector3   reflectionNormal    = (NormalIsForward ? -reflectionTransform.forward : reflectionTransform.up);

            // use shared reflection render function
            RenderReflection(info, reflectionTransform, reflectionNormal, ClipPlaneOffset);

            // restore render state
            reflectionCamera.cullingMask    = oldCullingMask;
            QualitySettings.pixelLightCount = oldPixelLightCount;
            QualitySettings.softParticles   = oldSoftParticles;
            QualitySettings.antiAliasing    = oldAntiAliasing;
            QualitySettings.shadows         = oldShadows;
            ReflectRenderer.sharedMaterial.SetTexture(ReflectionSamplerName, info.TargetTexture);
            ReflectRenderer.sharedMaterial.SetTexture(ReflectionSamplerName2, info.TargetTexture2);
            ReflectRenderer.sharedMaterial.DisableKeyword(mirrorRecursionLimitKeyword);

            currentCameras.Remove(info);
            info.SourceCamera   = null;
            info.TargetTexture  = null;
            info.TargetTexture2 = null;
            cameraCache.Add(info);
        }
        public ReflectionCameraInfo QueueReflection(Camera sourceCamera)
        {
            bool isReflection;

            if (ShouldIgnoreCamera(sourceCamera, out isReflection))
            {
                return(null);
            }
            ReflectionCameraInfo cam = CreateReflectionCamera(sourceCamera, isReflection);

            RenderReflectionCamera(cam);
            return(cam);
        }
        private void CleanupCamera(ReflectionCameraInfo info, bool destroyCamera)
        {
            if (info.ReflectionCamera == null)
            {
                return;
            }
            //info.ReflectCamera.targetTexture = null;
            if (destroyCamera

#if UNITY_EDITOR
                && Application.isPlaying
#endif

                )
            {
                DestroyImmediate(info.ReflectionCamera.gameObject);
            }
        }
        private ReflectionCameraInfo CreateReflectionCamera(Camera sourceCamera, bool sourceCameraIsReflection)
        {
            // don't render if we are not enabled
            if (ReflectRenderer == null || !ReflectRenderer.enabled || ReflectRenderer.sharedMaterial == null || sourceCamera == null)
            {
                return(null);
            }

            // only render reflection cameras with this script
            MagicMirrorScript reflScript = (sourceCameraIsReflection && sourceCamera.transform.parent != null ? sourceCamera.transform.parent.GetComponent <MagicMirrorScript>() : null);

            if (sourceCameraIsReflection && (reflScript == null || reflScript == this))
            {
                // don't render ourselves in our camera
                ReflectRenderer.sharedMaterial.EnableKeyword(mirrorRecursionLimitKeyword);
                return(null);
            }

            // recursion limit hit, bail...
            if (reflScript != null && currentCameras.Count >=

#if UNITY_EDITOR
                (Application.isPlaying ? RecursionLimit : 0)
#else
                RecursionLimit
#endif

                )
            {
                ReflectRenderer.sharedMaterial.EnableKeyword(mirrorRecursionLimitKeyword);
                return(null);
            }

            ReflectionCameraInfo info;
            if (cameraCache.Count == 0)
            {
                GameObject obj = new GameObject("MirrorReflectionCamera");
                obj.hideFlags = HideFlags.HideAndDontSave;
                obj.SetActive(false);
                obj.transform.parent = transform;
                Camera newReflectionCamera = obj.AddComponent <Camera>();
                newReflectionCamera.enabled = false;
                info = new ReflectionCameraInfo
                {
                    SourceCamera     = sourceCamera,
                    ReflectionCamera = newReflectionCamera
                };
            }
            else
            {
                int idx = cameraCache.Count - 1;
                info = cameraCache[idx];
                cameraCache.RemoveAt(idx);
                CleanupCamera(info, false);
            }
            info.SourceCamera             = sourceCamera;
            info.SourceCameraIsReflection = sourceCameraIsReflection;
            int size = Math.Max(32, (int)(Mathf.Pow(RecursionRenderTextureSizeReducerPower, (float)CurrentRecursionLevel) * (float)RenderTextureSize));
            info.TargetTexture            = RenderTexture.GetTemporary(size, size, 16, RenderTextureFormat.DefaultHDR);
            info.TargetTexture.wrapMode   = TextureWrapMode.Clamp;
            info.TargetTexture.filterMode = FilterMode.Bilinear;
            if (AntiAliasingSamples > AntiAliasingSampleCount.None)
            {
                info.TargetTexture.antiAliasing = (int)AntiAliasingSamples;
            }
            AddRenderTextureForSourceCamera(sourceCamera, info.TargetTexture, StereoTargetEyeMask.Left);
            if (sourceCamera.stereoEnabled)
            {
                info.TargetTexture2            = RenderTexture.GetTemporary(size, size, 16, RenderTextureFormat.DefaultHDR);
                info.TargetTexture2.wrapMode   = TextureWrapMode.Clamp;
                info.TargetTexture2.filterMode = FilterMode.Bilinear;
                if (AntiAliasingSamples > AntiAliasingSampleCount.None)
                {
                    info.TargetTexture2.antiAliasing = (int)AntiAliasingSamples;
                }
                AddRenderTextureForSourceCamera(sourceCamera, info.TargetTexture2, StereoTargetEyeMask.Right);
            }
            else
            {
                info.TargetTexture2 = info.TargetTexture;
            }
            currentCameras.Add(info);
            return(info);
        }
        /// <summary>
        /// Render a reflection camera. Reflection camera should already be setup with a render texture.
        /// </summary>
        /// <param name="info">Camera info</param>
        /// <param name="reflectionTransform">Reflection transform</param>
        /// <param name="reflectionNormal">Reflection normal vector</param>
        /// <param name="clipPlaneOffset">Clip plane offset for near clipping</param>
        /// <param name="eye">Stereo eye mask</param>
        /// <param name="targetTexture">Target texture</param>
        private static void RenderReflectionInternal
        (
            ReflectionCameraInfo info,
            Transform reflectionTransform,
            Vector3 reflectionNormal,
            float clipPlaneOffset,
            StereoTargetEyeMask eye,
            RenderTexture targetTexture
        )
        {
            bool oldInvertCulling = GL.invertCulling;

            // find out the reflection plane: position and normal in world space
            Vector3 pos = reflectionTransform.position;

            // Render reflection
            // Reflect camera around reflection plane
            if (info.SourceCameraIsReflection && GL.invertCulling)
            {
                reflectionNormal = -reflectionNormal;
            }

            float   d = -Vector3.Dot(reflectionNormal, pos) - clipPlaneOffset;
            Vector4 reflectionPlane = new Vector4(reflectionNormal.x, reflectionNormal.y, reflectionNormal.z, d);

            Matrix4x4 reflection;

            CalculateReflectionMatrix(out reflection, reflectionPlane);
            Vector3   savedPos            = info.SourceCamera.transform.position;
            Vector3   reflectPos          = reflection.MultiplyPoint(savedPos);
            Matrix4x4 worldToCameraMatrix = info.SourceCamera.worldToCameraMatrix;

            if (eye == StereoTargetEyeMask.Left)
            {
                worldToCameraMatrix[12] += (info.SourceCamera.stereoSeparation * 0.5f);
                info.ReflectionCamera.projectionMatrix = info.SourceCamera.GetStereoProjectionMatrix(Camera.StereoscopicEye.Left);
            }
            else if (eye == StereoTargetEyeMask.Right)
            {
                worldToCameraMatrix[12] -= (info.SourceCamera.stereoSeparation * 0.5f);
                info.ReflectionCamera.projectionMatrix = info.SourceCamera.GetStereoProjectionMatrix(Camera.StereoscopicEye.Right);
            }
            else
            {
                info.ReflectionCamera.projectionMatrix = info.SourceCamera.projectionMatrix;
            }
            info.ReflectionCamera.worldToCameraMatrix = worldToCameraMatrix * reflection;
            if (info.ReflectionCamera.actualRenderingPath != RenderingPath.DeferredShading)
            {
                // Optimization: Setup oblique projection matrix so that near plane is our reflection plane.
                // This way we clip everything below/above it for free.
                Vector4 clipPlane = CameraSpacePlane(info.ReflectionCamera, pos, reflectionNormal, clipPlaneOffset, GL.invertCulling ? -1.0f : 1.0f);
                info.ReflectionCamera.projectionMatrix = info.ReflectionCamera.CalculateObliqueMatrix(clipPlane);
            }

            GL.invertCulling = !GL.invertCulling;
            info.ReflectionCamera.transform.position = reflectPos;
            if (++renderCount < maxRenderCount)
            {
                info.ReflectionCamera.targetTexture = targetTexture;
                info.ReflectionCamera.Render();
                info.ReflectionCamera.targetTexture = null;
            }
            info.ReflectionCamera.transform.position = savedPos;
            GL.invertCulling = oldInvertCulling;
        }
        private void RenderReflectionCamera(ReflectionCameraInfo info)
        {
            // bail if we don't have a camera or renderer
            if (info == null || info.ReflectionCamera == null || info.SourceCamera == null || ReflectMaterial == null)
            {
                return;
            }

            CurrentRecursionLevel = currentCameras.Count + (info.SourceCameraIsReflection ? 1 : 0);
            renderCount++;
            Camera        sourceCamera       = info.SourceCamera;
            Camera        reflectionCamera   = info.ReflectionCamera;
            int           oldPixelLightCount = QualitySettings.pixelLightCount;
            int           oldCullingMask     = reflectionCamera.cullingMask;
            bool          oldSoftParticles   = QualitySettings.softParticles;
            int           oldAntiAliasing    = QualitySettings.antiAliasing;
            ShadowQuality oldShadows         = QualitySettings.shadows;

            SyncCameraSettings(reflectionCamera, sourceCamera);
            if (WeatherMakerScript.Instance != null && QualitySettings.shadows != ShadowQuality.Disable &&
                WeatherMakerLightManagerScript.ScreenSpaceShadowMode != UnityEngine.Rendering.BuiltinShaderMode.Disabled)
            {
                QualitySettings.shadows = WeatherMakerScript.Instance.PerformanceProfile.ReflectionShadows;
            }

            // MAGIC MIRROR RECURSION OPTIMIZATION
            if (currentCameras.Count > 1)
            {
                if (currentCameras.Count > 3)
                {
                    QualitySettings.shadows = ShadowQuality.Disable;
                }
                else if (WeatherMakerScript.Instance == null && QualitySettings.shadows != ShadowQuality.Disable &&
                         WeatherMakerLightManagerScript.ScreenSpaceShadowMode != UnityEngine.Rendering.BuiltinShaderMode.Disabled)
                {
                    QualitySettings.shadows = ShadowQuality.HardOnly;
                }
                QualitySettings.antiAliasing    = 0;
                QualitySettings.softParticles   = false;
                QualitySettings.pixelLightCount = 0;
                reflectionCamera.cullingMask   &= waterLayerInverse;
            }
            else
            {
                QualitySettings.pixelLightCount = MaximumPerPixelLightsToReflect;
            }

            // get reflection normal vector
            Transform reflectionTransform = transform;
            Vector3   reflectionNormal    = (NormalIsForward ? -reflectionTransform.forward : reflectionTransform.up);

            if (TransformNormalNegate)
            {
                reflectionNormal = -reflectionNormal;
            }

            // use shared reflection render function
            RenderReflection(info, reflectionTransform, reflectionNormal, ClipPlaneOffset, ReflectionOffsetFunc);

            // restore render state
            reflectionCamera.cullingMask    = oldCullingMask;
            QualitySettings.pixelLightCount = oldPixelLightCount;
            QualitySettings.softParticles   = oldSoftParticles;
            QualitySettings.antiAliasing    = oldAntiAliasing;
            QualitySettings.shadows         = oldShadows;
            ReflectMaterial.SetTexture(ReflectionSamplerName, info.TargetTexture);
            ReflectMaterial.SetTexture(ReflectionSamplerName2, info.TargetTexture2);
            ReflectMaterial.DisableKeyword(mirrorRecursionLimitKeyword);

            currentCameras.Remove(info);
            info.SourceCamera   = null;
            info.TargetTexture  = null;
            info.TargetTexture2 = null;
            cameraCache.Add(info);
        }