123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- using UnityEngine.Scripting.APIUpdating;
-
- namespace UnityEngine.Rendering.Universal
- {
- /// <summary>
- /// The Pixel Perfect Camera component ensures your pixel art remains crisp and clear at different resolutions, and stable in motion.
- /// </summary>
- [ExecuteInEditMode]
- [DisallowMultipleComponent]
- [AddComponentMenu("Rendering/2D/Pixel Perfect Camera")]
- [RequireComponent(typeof(Camera))]
- [MovedFrom(true, "UnityEngine.Experimental.Rendering.Universal")]
- [HelpURL("https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest/index.html?subfolder=/manual/2d-pixelperfect.html%23properties")]
- public class PixelPerfectCamera : MonoBehaviour, IPixelPerfectCamera, ISerializationCallbackReceiver
- {
- /// <summary>
- /// An enumeration of the types of display cropping.
- /// </summary>
- public enum CropFrame
- {
- /// <summary>
- /// No cropping.
- /// </summary>
- None,
- /// <summary>
- /// Black borders added to the left and right of viewport to match Reference Resolution.
- /// </summary>
- Pillarbox,
- /// <summary>
- /// Black borders added to the top and bottom of viewport to match Reference Resolution.
- /// </summary>
- Letterbox,
- /// <summary>
- /// Black borders added to all sides of viewport to match Reference Resolution.
- /// </summary>
- Windowbox,
- /// <summary>
- /// Expands the viewport to fit the screen resolution while maintaining the viewport's aspect ratio.
- /// </summary>
- StretchFill
- }
-
- /// <summary>
- /// Determines how pixels are snapped to the grid.
- /// </summary>
- public enum GridSnapping
- {
- /// <summary>
- /// No snapping.
- /// </summary>
- None,
- /// <summary>
- /// Prevent subpixel movement and make Sprites appear to move in pixel-by-pixel increments.
- /// </summary>
- PixelSnapping,
- /// <summary>
- /// The scene is rendered to a temporary texture set as close as possible to the Reference Resolution, while maintaining the full screen aspect ratio. This temporary texture is then upscaled to fit the full screen.
- /// </summary>
- UpscaleRenderTexture
- }
-
- /// <summary>
- /// Defines the filter mode use to render the final render target.
- /// </summary>
- public enum PixelPerfectFilterMode
- {
- /// <summary>
- /// Uses point filter to upscale to closest multiple of Reference Resolution, followed by bilinear filter to the target resolution.
- /// </summary>
- RetroAA,
- /// <summary>
- /// Uses point filter to upscale to target resolution.
- /// </summary>
- Point,
- }
-
- private enum ComponentVersions
- {
- Version_Unserialized = 0,
- Version_1 = 1
- }
-
- #if UNITY_EDITOR
- const ComponentVersions k_CurrentComponentVersion = ComponentVersions.Version_1;
- [SerializeField] ComponentVersions m_ComponentVersion = ComponentVersions.Version_Unserialized;
- #endif
-
- /// <summary>
- /// Defines how the output display will be cropped.
- /// </summary>
- public CropFrame cropFrame { get { return m_CropFrame; } set { m_CropFrame = value; } }
-
- /// <summary>
- /// Defines if pixels will be locked to a grid determined by assetsPPU.
- /// </summary>
- public GridSnapping gridSnapping { get { return m_GridSnapping; } set { m_GridSnapping = value; } }
-
- /// <summary>
- /// The target orthographic size of the camera.
- /// </summary>
- public float orthographicSize { get { return m_Internal.orthoSize; } }
-
- /// <summary>
- /// Match this value to to the Pixels Per Unit values of all Sprites within the Scene.
- /// </summary>
- public int assetsPPU { get { return m_AssetsPPU; } set { m_AssetsPPU = value > 0 ? value : 1; } }
-
- /// <summary>
- /// The original horizontal resolution your Assets are designed for.
- /// </summary>
- public int refResolutionX { get { return m_RefResolutionX; } set { m_RefResolutionX = value > 0 ? value : 1; } }
-
- /// <summary>
- /// Original vertical resolution your Assets are designed for.
- /// </summary>
- public int refResolutionY { get { return m_RefResolutionY; } set { m_RefResolutionY = value > 0 ? value : 1; } }
-
- /// <summary>
- /// Set to true to have the Scene rendered to a temporary texture set as close as possible to the Reference Resolution,
- /// while maintaining the full screen aspect ratio. This temporary texture is then upscaled to fit the full screen.
- /// </summary>
- [System.Obsolete("Use gridSnapping instead", false)]
- public bool upscaleRT
- {
- get
- {
- return m_GridSnapping == GridSnapping.UpscaleRenderTexture;
- }
- set
- {
- m_GridSnapping = value ? GridSnapping.UpscaleRenderTexture : GridSnapping.None;
- }
- }
-
- /// <summary>
- /// Set to true to prevent subpixel movement and make Sprites appear to move in pixel-by-pixel increments.
- /// Only applicable when upscaleRT is false.
- /// </summary>
- [System.Obsolete("Use gridSnapping instead", false)]
- public bool pixelSnapping
- {
- get
- {
- return m_GridSnapping == GridSnapping.PixelSnapping;
- }
- set
- {
- m_GridSnapping = value ? GridSnapping.PixelSnapping : GridSnapping.None;
- }
- }
-
- /// <summary>
- /// Set to true to crop the viewport with black bars to match refResolutionX in the horizontal direction.
- /// </summary>
- [System.Obsolete("Use cropFrame instead", false)]
- public bool cropFrameX
- {
- get
- {
- return m_CropFrame == CropFrame.StretchFill || m_CropFrame == CropFrame.Windowbox || m_CropFrame == CropFrame.Pillarbox;
- }
- set
- {
- if (value)
- {
- if (m_CropFrame == CropFrame.None)
- m_CropFrame = CropFrame.Pillarbox;
- else if (m_CropFrame == CropFrame.Letterbox)
- m_CropFrame = CropFrame.Windowbox;
- }
- else
- {
- if (m_CropFrame == CropFrame.Pillarbox)
- m_CropFrame = CropFrame.None;
- else if (m_CropFrame == CropFrame.Windowbox || m_CropFrame == CropFrame.StretchFill)
- m_CropFrame = CropFrame.Letterbox;
- }
- }
- }
-
- /// <summary>
- /// Set to true to crop the viewport with black bars to match refResolutionY in the vertical direction.
- /// </summary>
- [System.Obsolete("Use cropFrame instead", false)]
- public bool cropFrameY
- {
- get
- {
- return m_CropFrame == CropFrame.StretchFill || m_CropFrame == CropFrame.Windowbox || m_CropFrame == CropFrame.Letterbox;
- }
- set
- {
- if (value)
- {
- if (m_CropFrame == CropFrame.None)
- m_CropFrame = CropFrame.Letterbox;
- else if (m_CropFrame == CropFrame.Pillarbox)
- m_CropFrame = CropFrame.Windowbox;
- }
- else
- {
- if (m_CropFrame == CropFrame.Letterbox)
- m_CropFrame = CropFrame.None;
- else if (m_CropFrame == CropFrame.Windowbox || m_CropFrame == CropFrame.StretchFill)
- m_CropFrame = CropFrame.Pillarbox;
- }
- }
- }
-
- /// <summary>
- /// Set to true to expand the viewport to fit the screen resolution while maintaining the viewport's aspect ratio.
- /// Only applicable when both cropFrameX and cropFrameY are true.
- /// </summary>
- [System.Obsolete("Use cropFrame instead", false)]
- public bool stretchFill
- {
- get
- {
- return m_CropFrame == CropFrame.StretchFill;
- }
- set
- {
- if (value)
- m_CropFrame = CropFrame.StretchFill;
- else
- m_CropFrame = CropFrame.Windowbox;
- }
- }
-
- /// <summary>
- /// Ratio of the rendered Sprites compared to their original size (readonly).
- /// </summary>
- public int pixelRatio
- {
- get
- {
- if (m_CinemachineCompatibilityMode)
- {
- if (m_GridSnapping == GridSnapping.UpscaleRenderTexture)
- return m_Internal.zoom * m_Internal.cinemachineVCamZoom;
- else
- return m_Internal.cinemachineVCamZoom;
- }
- else
- {
- return m_Internal.zoom;
- }
- }
- }
-
- /// <summary>
- /// Returns if an upscale pass is required.
- /// </summary>
- public bool requiresUpscalePass
- {
- get
- {
- return m_Internal.requiresUpscaling;
- }
- }
-
- /// <summary>
- /// Round a arbitrary position to an integer pixel position. Works in world space.
- /// </summary>
- /// <param name="position"> The position you want to round.</param>
- /// <returns>
- /// The rounded pixel position.
- /// Depending on the values of upscaleRT and pixelSnapping, it could be a screen pixel position or an art pixel position.
- /// </returns>
- public Vector3 RoundToPixel(Vector3 position)
- {
- float unitsPerPixel = m_Internal.unitsPerPixel;
- if (unitsPerPixel == 0.0f)
- return position;
-
- Vector3 result;
- result.x = Mathf.Round(position.x / unitsPerPixel) * unitsPerPixel;
- result.y = Mathf.Round(position.y / unitsPerPixel) * unitsPerPixel;
- result.z = Mathf.Round(position.z / unitsPerPixel) * unitsPerPixel;
-
- return result;
- }
-
- /// <summary>
- /// Find a pixel-perfect orthographic size as close to targetOrthoSize as possible. Used by Cinemachine to solve compatibility issues with Pixel Perfect Camera.
- /// </summary>
- /// <param name="targetOrthoSize">Orthographic size from the live Cinemachine Virtual Camera.</param>
- /// <returns>The corrected orthographic size.</returns>
- public float CorrectCinemachineOrthoSize(float targetOrthoSize)
- {
- m_CinemachineCompatibilityMode = true;
-
- if (m_Internal == null)
- return targetOrthoSize;
- else
- return m_Internal.CorrectCinemachineOrthoSize(targetOrthoSize);
- }
-
- [SerializeField] int m_AssetsPPU = 100;
- [SerializeField] int m_RefResolutionX = 320;
- [SerializeField] int m_RefResolutionY = 180;
-
- [SerializeField] CropFrame m_CropFrame;
- [SerializeField] GridSnapping m_GridSnapping;
- [SerializeField] PixelPerfectFilterMode m_FilterMode = PixelPerfectFilterMode.RetroAA;
-
- // These are obsolete. They are here only for migration.
- #if UNITY_EDITOR
- [SerializeField] bool m_UpscaleRT;
- [SerializeField] bool m_PixelSnapping;
- [SerializeField] bool m_CropFrameX;
- [SerializeField] bool m_CropFrameY;
- [SerializeField] bool m_StretchFill;
- #endif
-
- Camera m_Camera;
- PixelPerfectCameraInternal m_Internal;
- bool m_CinemachineCompatibilityMode;
-
- internal FilterMode finalBlitFilterMode
- {
- get
- {
- return m_FilterMode == PixelPerfectFilterMode.RetroAA ? FilterMode.Bilinear : FilterMode.Point;
- }
- }
-
- internal Vector2Int offscreenRTSize
- {
- get
- {
- return new Vector2Int(m_Internal.offscreenRTWidth, m_Internal.offscreenRTHeight);
- }
- }
-
- Vector2Int cameraRTSize
- {
- get
- {
- var targetTexture = m_Camera.targetTexture;
- return targetTexture == null ? new Vector2Int(Screen.width, Screen.height) : new Vector2Int(targetTexture.width, targetTexture.height);
- }
- }
-
- // Snap camera position to pixels using Camera.worldToCameraMatrix.
- void PixelSnap()
- {
- Vector3 cameraPosition = m_Camera.transform.position;
- Vector3 roundedCameraPosition = RoundToPixel(cameraPosition);
- Vector3 offset = roundedCameraPosition - cameraPosition;
- offset.z = -offset.z;
-
- // Get world to local camera matrix without scale
- var invPos = Matrix4x4.TRS(cameraPosition + offset, Quaternion.identity, Vector3.one).inverse;
- var invRot = Matrix4x4.Rotate(m_Camera.transform.rotation).inverse;
- var scaleMatrix = Matrix4x4.Scale(new Vector3(1.0f, 1.0f, -1.0f));
-
- // Calculate inverse TRS
- m_Camera.worldToCameraMatrix = scaleMatrix * invRot * invPos;
- }
-
- void Awake()
- {
- m_Camera = GetComponent<Camera>();
- m_Internal = new PixelPerfectCameraInternal(this);
-
- // Case 1249076: Initialize internals immediately after the scene is loaded,
- // as the Cinemachine extension may need them before OnBeginContextRendering is called.
- UpdateCameraProperties();
- }
-
- void UpdateCameraProperties()
- {
- var rtSize = cameraRTSize;
- m_Internal.CalculateCameraProperties(rtSize.x, rtSize.y);
-
- if (m_Internal.useOffscreenRT)
- m_Camera.pixelRect = m_Internal.CalculateFinalBlitPixelRect(rtSize.x, rtSize.y);
- else
- m_Camera.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
- }
-
- void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
- {
- if (camera == m_Camera)
- {
- UpdateCameraProperties();
- PixelSnap();
-
- if (!m_CinemachineCompatibilityMode)
- {
- m_Camera.orthographicSize = m_Internal.orthoSize;
- }
-
- UnityEngine.U2D.PixelPerfectRendering.pixelSnapSpacing = m_Internal.unitsPerPixel;
- }
- }
-
- void OnEndCameraRendering(ScriptableRenderContext context, Camera camera)
- {
- if (camera == m_Camera)
- UnityEngine.U2D.PixelPerfectRendering.pixelSnapSpacing = 0.0f;
- }
-
- void OnEnable()
- {
- m_CinemachineCompatibilityMode = false;
-
- RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
- RenderPipelineManager.endCameraRendering += OnEndCameraRendering;
- }
-
- internal void OnDisable()
- {
- RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
- RenderPipelineManager.endCameraRendering -= OnEndCameraRendering;
-
- m_Camera.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
- m_Camera.ResetWorldToCameraMatrix();
- }
-
- #if DEVELOPMENT_BUILD || UNITY_EDITOR
- // Show on-screen warning about invalid render resolutions.
- void OnGUI()
- {
- Color oldColor = GUI.color;
- GUI.color = Color.red;
-
- Vector2Int renderResolution = Vector2Int.zero;
- renderResolution.x = m_Internal.useOffscreenRT ? m_Internal.offscreenRTWidth : m_Camera.pixelWidth;
- renderResolution.y = m_Internal.useOffscreenRT ? m_Internal.offscreenRTHeight : m_Camera.pixelHeight;
-
- if (renderResolution.x % 2 != 0 || renderResolution.y % 2 != 0)
- {
- string warning = string.Format("Rendering at an odd-numbered resolution ({0} * {1}). Pixel Perfect Camera may not work properly in this situation.", renderResolution.x, renderResolution.y);
- GUILayout.Box(warning);
- }
-
- var targetTexture = m_Camera.targetTexture;
- Vector2Int rtSize = targetTexture == null ? new Vector2Int(Screen.width, Screen.height) : new Vector2Int(targetTexture.width, targetTexture.height);
-
- if (rtSize.x < refResolutionX || rtSize.y < refResolutionY)
- {
- GUILayout.Box("Target resolution is smaller than the reference resolution. Image may appear stretched or cropped.");
- }
-
- GUI.color = oldColor;
- }
-
- #endif
-
- /// <summary>
- /// OnBeforeSerialize implementation.
- /// </summary>
- public void OnBeforeSerialize()
- {
- #if UNITY_EDITOR
- m_ComponentVersion = k_CurrentComponentVersion;
- #endif
- }
-
- /// <summary>
- /// OnAfterSerialize implementation.
- /// </summary>
- public void OnAfterDeserialize()
- {
- #if UNITY_EDITOR
- // Upgrade from no serialized version
- if (m_ComponentVersion == ComponentVersions.Version_Unserialized)
- {
- if (m_UpscaleRT)
- m_GridSnapping = GridSnapping.UpscaleRenderTexture;
- else if (m_PixelSnapping)
- m_GridSnapping = GridSnapping.PixelSnapping;
-
- if (m_CropFrameX && m_CropFrameY)
- {
- if (m_StretchFill)
- m_CropFrame = CropFrame.StretchFill;
- else
- m_CropFrame = CropFrame.Windowbox;
- }
- else if (m_CropFrameX)
- {
- m_CropFrame = CropFrame.Pillarbox;
- }
- else if (m_CropFrameY)
- {
- m_CropFrame = CropFrame.Letterbox;
- }
- else
- {
- m_CropFrame = CropFrame.None;
- }
-
- m_ComponentVersion = ComponentVersions.Version_1;
- }
- #endif
- }
- }
- }
|