PixelPerfectCamera.cs 12 KB

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