Ingen beskrivning
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 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. using UnityEngine.Scripting.APIUpdating;
  2. namespace UnityEngine.Rendering.Universal
  3. {
  4. /// <summary>
  5. /// The Pixel Perfect Camera component ensures your pixel art remains crisp and clear at different resolutions, and stable in motion.
  6. /// </summary>
  7. [ExecuteInEditMode]
  8. [DisallowMultipleComponent]
  9. [AddComponentMenu("Rendering/2D/Pixel Perfect Camera")]
  10. [RequireComponent(typeof(Camera))]
  11. [MovedFrom(true, "UnityEngine.Experimental.Rendering.Universal")]
  12. [HelpURL("https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest/index.html?subfolder=/manual/2d-pixelperfect.html%23properties")]
  13. public class PixelPerfectCamera : MonoBehaviour, IPixelPerfectCamera, ISerializationCallbackReceiver
  14. {
  15. /// <summary>
  16. /// An enumeration of the types of display cropping.
  17. /// </summary>
  18. public enum CropFrame
  19. {
  20. /// <summary>
  21. /// No cropping.
  22. /// </summary>
  23. None,
  24. /// <summary>
  25. /// Black borders added to the left and right of viewport to match Reference Resolution.
  26. /// </summary>
  27. Pillarbox,
  28. /// <summary>
  29. /// Black borders added to the top and bottom of viewport to match Reference Resolution.
  30. /// </summary>
  31. Letterbox,
  32. /// <summary>
  33. /// Black borders added to all sides of viewport to match Reference Resolution.
  34. /// </summary>
  35. Windowbox,
  36. /// <summary>
  37. /// Expands the viewport to fit the screen resolution while maintaining the viewport's aspect ratio.
  38. /// </summary>
  39. StretchFill
  40. }
  41. /// <summary>
  42. /// Determines how pixels are snapped to the grid.
  43. /// </summary>
  44. public enum GridSnapping
  45. {
  46. /// <summary>
  47. /// No snapping.
  48. /// </summary>
  49. None,
  50. /// <summary>
  51. /// Prevent subpixel movement and make Sprites appear to move in pixel-by-pixel increments.
  52. /// </summary>
  53. PixelSnapping,
  54. /// <summary>
  55. /// 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.
  56. /// </summary>
  57. UpscaleRenderTexture
  58. }
  59. /// <summary>
  60. /// Defines the filter mode use to render the final render target.
  61. /// </summary>
  62. public enum PixelPerfectFilterMode
  63. {
  64. /// <summary>
  65. /// Uses point filter to upscale to closest multiple of Reference Resolution, followed by bilinear filter to the target resolution.
  66. /// </summary>
  67. RetroAA,
  68. /// <summary>
  69. /// Uses point filter to upscale to target resolution.
  70. /// </summary>
  71. Point,
  72. }
  73. private enum ComponentVersions
  74. {
  75. Version_Unserialized = 0,
  76. Version_1 = 1
  77. }
  78. #if UNITY_EDITOR
  79. const ComponentVersions k_CurrentComponentVersion = ComponentVersions.Version_1;
  80. [SerializeField] ComponentVersions m_ComponentVersion = ComponentVersions.Version_Unserialized;
  81. #endif
  82. /// <summary>
  83. /// Defines how the output display will be cropped.
  84. /// </summary>
  85. public CropFrame cropFrame { get { return m_CropFrame; } set { m_CropFrame = value; } }
  86. /// <summary>
  87. /// Defines if pixels will be locked to a grid determined by assetsPPU.
  88. /// </summary>
  89. public GridSnapping gridSnapping { get { return m_GridSnapping; } set { m_GridSnapping = value; } }
  90. /// <summary>
  91. /// The target orthographic size of the camera.
  92. /// </summary>
  93. public float orthographicSize { get { return m_Internal.orthoSize; } }
  94. /// <summary>
  95. /// Match this value to to the Pixels Per Unit values of all Sprites within the Scene.
  96. /// </summary>
  97. public int assetsPPU { get { return m_AssetsPPU; } set { m_AssetsPPU = value > 0 ? value : 1; } }
  98. /// <summary>
  99. /// The original horizontal resolution your Assets are designed for.
  100. /// </summary>
  101. public int refResolutionX { get { return m_RefResolutionX; } set { m_RefResolutionX = value > 0 ? value : 1; } }
  102. /// <summary>
  103. /// Original vertical resolution your Assets are designed for.
  104. /// </summary>
  105. public int refResolutionY { get { return m_RefResolutionY; } set { m_RefResolutionY = value > 0 ? value : 1; } }
  106. /// <summary>
  107. /// Set to true to have the Scene rendered to a temporary texture set as close as possible to the Reference Resolution,
  108. /// while maintaining the full screen aspect ratio. This temporary texture is then upscaled to fit the full screen.
  109. /// </summary>
  110. [System.Obsolete("Use gridSnapping instead", false)]
  111. public bool upscaleRT
  112. {
  113. get
  114. {
  115. return m_GridSnapping == GridSnapping.UpscaleRenderTexture;
  116. }
  117. set
  118. {
  119. m_GridSnapping = value ? GridSnapping.UpscaleRenderTexture : GridSnapping.None;
  120. }
  121. }
  122. /// <summary>
  123. /// Set to true to prevent subpixel movement and make Sprites appear to move in pixel-by-pixel increments.
  124. /// Only applicable when upscaleRT is false.
  125. /// </summary>
  126. [System.Obsolete("Use gridSnapping instead", false)]
  127. public bool pixelSnapping
  128. {
  129. get
  130. {
  131. return m_GridSnapping == GridSnapping.PixelSnapping;
  132. }
  133. set
  134. {
  135. m_GridSnapping = value ? GridSnapping.PixelSnapping : GridSnapping.None;
  136. }
  137. }
  138. /// <summary>
  139. /// Set to true to crop the viewport with black bars to match refResolutionX in the horizontal direction.
  140. /// </summary>
  141. [System.Obsolete("Use cropFrame instead", false)]
  142. public bool cropFrameX
  143. {
  144. get
  145. {
  146. return m_CropFrame == CropFrame.StretchFill || m_CropFrame == CropFrame.Windowbox || m_CropFrame == CropFrame.Pillarbox;
  147. }
  148. set
  149. {
  150. if (value)
  151. {
  152. if (m_CropFrame == CropFrame.None)
  153. m_CropFrame = CropFrame.Pillarbox;
  154. else if (m_CropFrame == CropFrame.Letterbox)
  155. m_CropFrame = CropFrame.Windowbox;
  156. }
  157. else
  158. {
  159. if (m_CropFrame == CropFrame.Pillarbox)
  160. m_CropFrame = CropFrame.None;
  161. else if (m_CropFrame == CropFrame.Windowbox || m_CropFrame == CropFrame.StretchFill)
  162. m_CropFrame = CropFrame.Letterbox;
  163. }
  164. }
  165. }
  166. /// <summary>
  167. /// Set to true to crop the viewport with black bars to match refResolutionY in the vertical direction.
  168. /// </summary>
  169. [System.Obsolete("Use cropFrame instead", false)]
  170. public bool cropFrameY
  171. {
  172. get
  173. {
  174. return m_CropFrame == CropFrame.StretchFill || m_CropFrame == CropFrame.Windowbox || m_CropFrame == CropFrame.Letterbox;
  175. }
  176. set
  177. {
  178. if (value)
  179. {
  180. if (m_CropFrame == CropFrame.None)
  181. m_CropFrame = CropFrame.Letterbox;
  182. else if (m_CropFrame == CropFrame.Pillarbox)
  183. m_CropFrame = CropFrame.Windowbox;
  184. }
  185. else
  186. {
  187. if (m_CropFrame == CropFrame.Letterbox)
  188. m_CropFrame = CropFrame.None;
  189. else if (m_CropFrame == CropFrame.Windowbox || m_CropFrame == CropFrame.StretchFill)
  190. m_CropFrame = CropFrame.Pillarbox;
  191. }
  192. }
  193. }
  194. /// <summary>
  195. /// Set to true to expand the viewport to fit the screen resolution while maintaining the viewport's aspect ratio.
  196. /// Only applicable when both cropFrameX and cropFrameY are true.
  197. /// </summary>
  198. [System.Obsolete("Use cropFrame instead", false)]
  199. public bool stretchFill
  200. {
  201. get
  202. {
  203. return m_CropFrame == CropFrame.StretchFill;
  204. }
  205. set
  206. {
  207. if (value)
  208. m_CropFrame = CropFrame.StretchFill;
  209. else
  210. m_CropFrame = CropFrame.Windowbox;
  211. }
  212. }
  213. /// <summary>
  214. /// Ratio of the rendered Sprites compared to their original size (readonly).
  215. /// </summary>
  216. public int pixelRatio
  217. {
  218. get
  219. {
  220. if (m_CinemachineCompatibilityMode)
  221. {
  222. if (m_GridSnapping == GridSnapping.UpscaleRenderTexture)
  223. return m_Internal.zoom * m_Internal.cinemachineVCamZoom;
  224. else
  225. return m_Internal.cinemachineVCamZoom;
  226. }
  227. else
  228. {
  229. return m_Internal.zoom;
  230. }
  231. }
  232. }
  233. /// <summary>
  234. /// Returns if an upscale pass is required.
  235. /// </summary>
  236. public bool requiresUpscalePass
  237. {
  238. get
  239. {
  240. return m_Internal.requiresUpscaling;
  241. }
  242. }
  243. /// <summary>
  244. /// Round a arbitrary position to an integer pixel position. Works in world space.
  245. /// </summary>
  246. /// <param name="position"> The position you want to round.</param>
  247. /// <returns>
  248. /// The rounded pixel position.
  249. /// Depending on the values of upscaleRT and pixelSnapping, it could be a screen pixel position or an art pixel position.
  250. /// </returns>
  251. public Vector3 RoundToPixel(Vector3 position)
  252. {
  253. float unitsPerPixel = m_Internal.unitsPerPixel;
  254. if (unitsPerPixel == 0.0f)
  255. return position;
  256. Vector3 result;
  257. result.x = Mathf.Round(position.x / unitsPerPixel) * unitsPerPixel;
  258. result.y = Mathf.Round(position.y / unitsPerPixel) * unitsPerPixel;
  259. result.z = Mathf.Round(position.z / unitsPerPixel) * unitsPerPixel;
  260. return result;
  261. }
  262. /// <summary>
  263. /// Find a pixel-perfect orthographic size as close to targetOrthoSize as possible. Used by Cinemachine to solve compatibility issues with Pixel Perfect Camera.
  264. /// </summary>
  265. /// <param name="targetOrthoSize">Orthographic size from the live Cinemachine Virtual Camera.</param>
  266. /// <returns>The corrected orthographic size.</returns>
  267. public float CorrectCinemachineOrthoSize(float targetOrthoSize)
  268. {
  269. m_CinemachineCompatibilityMode = true;
  270. if (m_Internal == null)
  271. return targetOrthoSize;
  272. else
  273. return m_Internal.CorrectCinemachineOrthoSize(targetOrthoSize);
  274. }
  275. [SerializeField] int m_AssetsPPU = 100;
  276. [SerializeField] int m_RefResolutionX = 320;
  277. [SerializeField] int m_RefResolutionY = 180;
  278. [SerializeField] CropFrame m_CropFrame;
  279. [SerializeField] GridSnapping m_GridSnapping;
  280. [SerializeField] PixelPerfectFilterMode m_FilterMode = PixelPerfectFilterMode.RetroAA;
  281. // These are obsolete. They are here only for migration.
  282. #if UNITY_EDITOR
  283. [SerializeField] bool m_UpscaleRT;
  284. [SerializeField] bool m_PixelSnapping;
  285. [SerializeField] bool m_CropFrameX;
  286. [SerializeField] bool m_CropFrameY;
  287. [SerializeField] bool m_StretchFill;
  288. #endif
  289. Camera m_Camera;
  290. PixelPerfectCameraInternal m_Internal;
  291. bool m_CinemachineCompatibilityMode;
  292. internal FilterMode finalBlitFilterMode
  293. {
  294. get
  295. {
  296. return m_FilterMode == PixelPerfectFilterMode.RetroAA ? FilterMode.Bilinear : FilterMode.Point;
  297. }
  298. }
  299. internal Vector2Int offscreenRTSize
  300. {
  301. get
  302. {
  303. return new Vector2Int(m_Internal.offscreenRTWidth, m_Internal.offscreenRTHeight);
  304. }
  305. }
  306. Vector2Int cameraRTSize
  307. {
  308. get
  309. {
  310. var targetTexture = m_Camera.targetTexture;
  311. return targetTexture == null ? new Vector2Int(Screen.width, Screen.height) : new Vector2Int(targetTexture.width, targetTexture.height);
  312. }
  313. }
  314. // Snap camera position to pixels using Camera.worldToCameraMatrix.
  315. void PixelSnap()
  316. {
  317. Vector3 cameraPosition = m_Camera.transform.position;
  318. Vector3 roundedCameraPosition = RoundToPixel(cameraPosition);
  319. Vector3 offset = roundedCameraPosition - cameraPosition;
  320. offset.z = -offset.z;
  321. // Get world to local camera matrix without scale
  322. var invPos = Matrix4x4.TRS(cameraPosition + offset, Quaternion.identity, Vector3.one).inverse;
  323. var invRot = Matrix4x4.Rotate(m_Camera.transform.rotation).inverse;
  324. var scaleMatrix = Matrix4x4.Scale(new Vector3(1.0f, 1.0f, -1.0f));
  325. // Calculate inverse TRS
  326. m_Camera.worldToCameraMatrix = scaleMatrix * invRot * invPos;
  327. }
  328. void Awake()
  329. {
  330. m_Camera = GetComponent<Camera>();
  331. m_Internal = new PixelPerfectCameraInternal(this);
  332. // Case 1249076: Initialize internals immediately after the scene is loaded,
  333. // as the Cinemachine extension may need them before OnBeginContextRendering is called.
  334. UpdateCameraProperties();
  335. }
  336. void UpdateCameraProperties()
  337. {
  338. var rtSize = cameraRTSize;
  339. m_Internal.CalculateCameraProperties(rtSize.x, rtSize.y);
  340. if (m_Internal.useOffscreenRT)
  341. m_Camera.pixelRect = m_Internal.CalculateFinalBlitPixelRect(rtSize.x, rtSize.y);
  342. else
  343. m_Camera.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
  344. }
  345. void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
  346. {
  347. if (camera == m_Camera)
  348. {
  349. UpdateCameraProperties();
  350. PixelSnap();
  351. if (!m_CinemachineCompatibilityMode)
  352. {
  353. m_Camera.orthographicSize = m_Internal.orthoSize;
  354. }
  355. UnityEngine.U2D.PixelPerfectRendering.pixelSnapSpacing = m_Internal.unitsPerPixel;
  356. }
  357. }
  358. void OnEndCameraRendering(ScriptableRenderContext context, Camera camera)
  359. {
  360. if (camera == m_Camera)
  361. UnityEngine.U2D.PixelPerfectRendering.pixelSnapSpacing = 0.0f;
  362. }
  363. void OnEnable()
  364. {
  365. m_CinemachineCompatibilityMode = false;
  366. RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
  367. RenderPipelineManager.endCameraRendering += OnEndCameraRendering;
  368. }
  369. internal void OnDisable()
  370. {
  371. RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
  372. RenderPipelineManager.endCameraRendering -= OnEndCameraRendering;
  373. m_Camera.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
  374. m_Camera.ResetWorldToCameraMatrix();
  375. }
  376. #if DEVELOPMENT_BUILD || UNITY_EDITOR
  377. // Show on-screen warning about invalid render resolutions.
  378. void OnGUI()
  379. {
  380. Color oldColor = GUI.color;
  381. GUI.color = Color.red;
  382. Vector2Int renderResolution = Vector2Int.zero;
  383. renderResolution.x = m_Internal.useOffscreenRT ? m_Internal.offscreenRTWidth : m_Camera.pixelWidth;
  384. renderResolution.y = m_Internal.useOffscreenRT ? m_Internal.offscreenRTHeight : m_Camera.pixelHeight;
  385. if (renderResolution.x % 2 != 0 || renderResolution.y % 2 != 0)
  386. {
  387. 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);
  388. GUILayout.Box(warning);
  389. }
  390. var targetTexture = m_Camera.targetTexture;
  391. Vector2Int rtSize = targetTexture == null ? new Vector2Int(Screen.width, Screen.height) : new Vector2Int(targetTexture.width, targetTexture.height);
  392. if (rtSize.x < refResolutionX || rtSize.y < refResolutionY)
  393. {
  394. GUILayout.Box("Target resolution is smaller than the reference resolution. Image may appear stretched or cropped.");
  395. }
  396. GUI.color = oldColor;
  397. }
  398. #endif
  399. /// <summary>
  400. /// OnBeforeSerialize implementation.
  401. /// </summary>
  402. public void OnBeforeSerialize()
  403. {
  404. #if UNITY_EDITOR
  405. m_ComponentVersion = k_CurrentComponentVersion;
  406. #endif
  407. }
  408. /// <summary>
  409. /// OnAfterSerialize implementation.
  410. /// </summary>
  411. public void OnAfterDeserialize()
  412. {
  413. #if UNITY_EDITOR
  414. // Upgrade from no serialized version
  415. if (m_ComponentVersion == ComponentVersions.Version_Unserialized)
  416. {
  417. if (m_UpscaleRT)
  418. m_GridSnapping = GridSnapping.UpscaleRenderTexture;
  419. else if (m_PixelSnapping)
  420. m_GridSnapping = GridSnapping.PixelSnapping;
  421. if (m_CropFrameX && m_CropFrameY)
  422. {
  423. if (m_StretchFill)
  424. m_CropFrame = CropFrame.StretchFill;
  425. else
  426. m_CropFrame = CropFrame.Windowbox;
  427. }
  428. else if (m_CropFrameX)
  429. {
  430. m_CropFrame = CropFrame.Pillarbox;
  431. }
  432. else if (m_CropFrameY)
  433. {
  434. m_CropFrame = CropFrame.Letterbox;
  435. }
  436. else
  437. {
  438. m_CropFrame = CropFrame.None;
  439. }
  440. m_ComponentVersion = ComponentVersions.Version_1;
  441. }
  442. #endif
  443. }
  444. }
  445. }