暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PixelPerfectCamera.cs 11KB

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