PixelPerfectCamera.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. using UnityEngine.Rendering;
  2. using UnityEngine.Scripting.APIUpdating;
  3. namespace UnityEngine.Experimental.Rendering.Universal
  4. {
  5. /// <summary>
  6. /// The Pixel Perfect Camera component ensures your pixel art remains crisp and clear at different resolutions, and stable in motion.
  7. /// </summary>
  8. [DisallowMultipleComponent]
  9. [AddComponentMenu("Rendering/2D/Pixel Perfect Camera (Experimental)")]
  10. [RequireComponent(typeof(Camera))]
  11. [MovedFrom("UnityEngine.Experimental.Rendering.LWRP")] public class PixelPerfectCamera : MonoBehaviour, IPixelPerfectCamera
  12. {
  13. /// <summary>
  14. /// Match this value to to the Pixels Per Unit values of all Sprites within the Scene.
  15. /// </summary>
  16. public int assetsPPU { get { return m_AssetsPPU; } set { m_AssetsPPU = value > 0 ? value : 1; } }
  17. /// <summary>
  18. /// The original horizontal resolution your Assets are designed for.
  19. /// </summary>
  20. public int refResolutionX { get { return m_RefResolutionX; } set { m_RefResolutionX = value > 0 ? value : 1; } }
  21. /// <summary>
  22. /// Original vertical resolution your Assets are designed for.
  23. /// </summary>
  24. public int refResolutionY { get { return m_RefResolutionY; } set { m_RefResolutionY = value > 0 ? value : 1; } }
  25. /// <summary>
  26. /// Set to true to have the Scene rendered to a temporary texture set as close as possible to the Reference Resolution,
  27. /// while maintaining the full screen aspect ratio. This temporary texture is then upscaled to fit the full screen.
  28. /// </summary>
  29. public bool upscaleRT { get { return m_UpscaleRT; } set { m_UpscaleRT = value; } }
  30. /// <summary>
  31. /// Set to true to prevent subpixel movement and make Sprites appear to move in pixel-by-pixel increments.
  32. /// Only applicable when upscaleRT is false.
  33. /// </summary>
  34. public bool pixelSnapping { get { return m_PixelSnapping; } set { m_PixelSnapping = value; } }
  35. /// <summary>
  36. /// Set to true to crop the viewport with black bars to match refResolutionX in the horizontal direction.
  37. /// </summary>
  38. public bool cropFrameX { get { return m_CropFrameX; } set { m_CropFrameX = value; } }
  39. /// <summary>
  40. /// Set to true to crop the viewport with black bars to match refResolutionY in the vertical direction.
  41. /// </summary>
  42. public bool cropFrameY { get { return m_CropFrameY; } set { m_CropFrameY = value; } }
  43. /// <summary>
  44. /// Set to true to expand the viewport to fit the screen resolution while maintaining the viewport's aspect ratio.
  45. /// Only applicable when both cropFrameX and cropFrameY are true.
  46. /// </summary>
  47. public bool stretchFill { get { return m_StretchFill; } set { m_StretchFill = value; } }
  48. /// <summary>
  49. /// Ratio of the rendered Sprites compared to their original size (readonly).
  50. /// </summary>
  51. public int pixelRatio
  52. {
  53. get
  54. {
  55. if (m_CinemachineCompatibilityMode)
  56. {
  57. if (m_UpscaleRT)
  58. return m_Internal.zoom * m_Internal.cinemachineVCamZoom;
  59. else
  60. return m_Internal.cinemachineVCamZoom;
  61. }
  62. else
  63. {
  64. return m_Internal.zoom;
  65. }
  66. }
  67. }
  68. /// <summary>
  69. /// Round a arbitrary position to an integer pixel position. Works in world space.
  70. /// </summary>
  71. /// <param name="position"> The position you want to round.</param>
  72. /// <returns>
  73. /// The rounded pixel position.
  74. /// Depending on the values of upscaleRT and pixelSnapping, it could be a screen pixel position or an art pixel position.
  75. /// </returns>
  76. public Vector3 RoundToPixel(Vector3 position)
  77. {
  78. float unitsPerPixel = m_Internal.unitsPerPixel;
  79. if (unitsPerPixel == 0.0f)
  80. return position;
  81. Vector3 result;
  82. result.x = Mathf.Round(position.x / unitsPerPixel) * unitsPerPixel;
  83. result.y = Mathf.Round(position.y / unitsPerPixel) * unitsPerPixel;
  84. result.z = Mathf.Round(position.z / unitsPerPixel) * unitsPerPixel;
  85. return result;
  86. }
  87. /// <summary>
  88. /// Find a pixel-perfect orthographic size as close to targetOrthoSize as possible. Used by Cinemachine to solve compatibility issues with Pixel Perfect Camera.
  89. /// </summary>
  90. /// <param name="targetOrthoSize">Orthographic size from the live Cinemachine Virtual Camera.</param>
  91. /// <returns>The corrected orthographic size.</returns>
  92. public float CorrectCinemachineOrthoSize(float targetOrthoSize)
  93. {
  94. m_CinemachineCompatibilityMode = true;
  95. if (m_Internal == null)
  96. return targetOrthoSize;
  97. else
  98. return m_Internal.CorrectCinemachineOrthoSize(targetOrthoSize);
  99. }
  100. [SerializeField] int m_AssetsPPU = 100;
  101. [SerializeField] int m_RefResolutionX = 320;
  102. [SerializeField] int m_RefResolutionY = 180;
  103. [SerializeField] bool m_UpscaleRT;
  104. [SerializeField] bool m_PixelSnapping;
  105. [SerializeField] bool m_CropFrameX;
  106. [SerializeField] bool m_CropFrameY;
  107. [SerializeField] bool m_StretchFill;
  108. Camera m_Camera;
  109. PixelPerfectCameraInternal m_Internal;
  110. bool m_CinemachineCompatibilityMode;
  111. internal bool isRunning
  112. {
  113. get
  114. {
  115. #if UNITY_EDITOR
  116. return (Application.isPlaying || runInEditMode) && enabled;
  117. #else
  118. return enabled;
  119. #endif
  120. }
  121. }
  122. internal FilterMode finalBlitFilterMode
  123. {
  124. get
  125. {
  126. if (!isRunning)
  127. return FilterMode.Bilinear;
  128. else
  129. return m_Internal.useStretchFill ? FilterMode.Bilinear : FilterMode.Point;
  130. }
  131. }
  132. internal Vector2Int offscreenRTSize
  133. {
  134. get
  135. {
  136. if (!isRunning)
  137. return Vector2Int.zero;
  138. else
  139. return new Vector2Int(m_Internal.offscreenRTWidth, m_Internal.offscreenRTHeight);
  140. }
  141. }
  142. // Snap camera position to pixels using Camera.worldToCameraMatrix.
  143. void PixelSnap()
  144. {
  145. Vector3 cameraPosition = m_Camera.transform.position;
  146. Vector3 roundedCameraPosition = RoundToPixel(cameraPosition);
  147. Vector3 offset = roundedCameraPosition - cameraPosition;
  148. offset.z = -offset.z;
  149. Matrix4x4 offsetMatrix = Matrix4x4.TRS(-offset, Quaternion.identity, new Vector3(1.0f, 1.0f, -1.0f));
  150. m_Camera.worldToCameraMatrix = offsetMatrix * m_Camera.transform.worldToLocalMatrix;
  151. }
  152. void Awake()
  153. {
  154. m_Camera = GetComponent<Camera>();
  155. m_Internal = new PixelPerfectCameraInternal(this);
  156. m_Internal.originalOrthoSize = m_Camera.orthographicSize;
  157. }
  158. void LateUpdate()
  159. {
  160. #if UNITY_EDITOR
  161. if (!UnityEditor.EditorApplication.isPaused)
  162. #endif
  163. {
  164. // Reset the Cinemachine compatibility mode every frame.
  165. // If any CinemachinePixelPerfect extension is present, they will turn this on
  166. // at a later time (during CinemachineBrain's LateUpdate(), which is
  167. // guaranteed to be after PixelPerfectCamera's LateUpdate()).
  168. m_CinemachineCompatibilityMode = false;
  169. }
  170. }
  171. void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
  172. {
  173. if (camera != m_Camera)
  174. return;
  175. var targetTexture = m_Camera.targetTexture;
  176. Vector2Int rtSize = targetTexture == null ? new Vector2Int(Screen.width, Screen.height) : new Vector2Int(targetTexture.width, targetTexture.height);
  177. m_Internal.CalculateCameraProperties(rtSize.x, rtSize.y);
  178. PixelSnap();
  179. if (m_Internal.useOffscreenRT)
  180. m_Camera.pixelRect = m_Internal.CalculateFinalBlitPixelRect(rtSize.x, rtSize.y);
  181. else
  182. m_Camera.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
  183. // In Cinemachine compatibility mode the control over orthographic size should
  184. // be given to the virtual cameras, whose orthographic sizes will be corrected to
  185. // be pixel-perfect. This way when there's blending between virtual cameras, we
  186. // can have temporary not-pixel-perfect but smooth transitions.
  187. if (!m_CinemachineCompatibilityMode)
  188. {
  189. m_Camera.orthographicSize = m_Internal.orthoSize;
  190. }
  191. UnityEngine.U2D.PixelPerfectRendering.pixelSnapSpacing = m_Internal.unitsPerPixel;
  192. }
  193. void OnEndCameraRendering(ScriptableRenderContext context, Camera camera)
  194. {
  195. if (camera == m_Camera)
  196. UnityEngine.U2D.PixelPerfectRendering.pixelSnapSpacing = 0.0f;
  197. }
  198. void OnEnable()
  199. {
  200. RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
  201. RenderPipelineManager.endCameraRendering += OnEndCameraRendering;
  202. #if UNITY_EDITOR
  203. if (!UnityEditor.EditorApplication.isPlaying)
  204. UnityEditor.EditorApplication.playModeStateChanged += OnPlayModeChanged;
  205. #endif
  206. }
  207. internal void OnDisable()
  208. {
  209. RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
  210. RenderPipelineManager.endCameraRendering -= OnEndCameraRendering;
  211. m_Camera.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
  212. m_Camera.orthographicSize = m_Internal.originalOrthoSize;
  213. m_Camera.ResetWorldToCameraMatrix();
  214. #if UNITY_EDITOR
  215. if (!UnityEditor.EditorApplication.isPlaying)
  216. UnityEditor.EditorApplication.playModeStateChanged -= OnPlayModeChanged;
  217. #endif
  218. }
  219. #if DEVELOPMENT_BUILD || UNITY_EDITOR
  220. // Show on-screen warning about invalid render resolutions.
  221. void OnGUI()
  222. {
  223. #if UNITY_EDITOR
  224. if (!UnityEditor.EditorApplication.isPlaying && !runInEditMode)
  225. return;
  226. #endif
  227. Color oldColor = GUI.color;
  228. GUI.color = Color.red;
  229. Vector2Int renderResolution = Vector2Int.zero;
  230. renderResolution.x = m_Internal.useOffscreenRT ? m_Internal.offscreenRTWidth : m_Camera.pixelWidth;
  231. renderResolution.y = m_Internal.useOffscreenRT ? m_Internal.offscreenRTHeight : m_Camera.pixelHeight;
  232. if (renderResolution.x % 2 != 0 || renderResolution.y % 2 != 0)
  233. {
  234. 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);
  235. GUILayout.Box(warning);
  236. }
  237. var targetTexture = m_Camera.targetTexture;
  238. Vector2Int rtSize = targetTexture == null ? new Vector2Int(Screen.width, Screen.height) : new Vector2Int(targetTexture.width, targetTexture.height);
  239. if (rtSize.x < refResolutionX || rtSize.y < refResolutionY)
  240. {
  241. GUILayout.Box("Target resolution is smaller than the reference resolution. Image may appear stretched or cropped.");
  242. }
  243. GUI.color = oldColor;
  244. }
  245. #endif
  246. #if UNITY_EDITOR
  247. void OnPlayModeChanged(UnityEditor.PlayModeStateChange state)
  248. {
  249. // Stop running in edit mode when entering play mode.
  250. if (state == UnityEditor.PlayModeStateChange.ExitingEditMode)
  251. {
  252. runInEditMode = false;
  253. OnDisable();
  254. }
  255. }
  256. #endif
  257. }
  258. }