Search Unity

Pixel perfect sprites?

Discussion in '2D' started by voltage, Dec 4, 2016.

  1. voltage

    voltage

    Joined:
    Nov 11, 2011
    Posts:
    515
    I know this is a loaded topic, as I've looked into it before. It seems as though it's more effort than what it's worth. Between all the different supported resolutions, how does one make pixel perfect art? I want to make pixel art. I've found assets that supposedly resolve this issue, but they don't always work?
     
  2. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    What game are you planning, I use sprites 64x64 on all resolutions and fiddle with the cameras orthographic projection size, mine is a game from 1985 mind you so I wanted that pixel feel.

    But you can do pixel perfect, it just might be small on a higher resolution screen, that's why I fiddled with the camera size.
     
  3. voltage

    voltage

    Joined:
    Nov 11, 2011
    Posts:
    515
    Alright, so what you're saying is I should increase the size of the Ortho camera for a 1080p res via script? Is there a resolution you base your game on and build up from there?

    Additionally, say I use 64x64 for all my sprites as well. It's a reasonable size for doodling pixel art. If all my sprite images are the same size and match the same PPU, how do I make trees/the landscape etc. significantly bigger than the player character? Do I just draw a really small man inside a 64x64 image? Or, do I have the liberty to draw flexibly in the 64x64 for the player and utilize for example 128x128 for a tree?
     
    Last edited: Dec 5, 2016
  4. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    Here is my Camera code for you, I've left in a couple of other attempts (see commented out code) but this will give you an idea.

    the other code in the class allows you to return the camera bounds for lets say making your sprites out the the view not render their self, this helps to speed up the game :)


    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class CameraComponent : MonoBehaviour
    6. {
    7. //public Transform Target;
    8. public float Tilesize;
    9.  
    10. private Camera mCamera;
    11.  
    12. private int mLeftTile;
    13. private int mTopTile;
    14. private int mRightTile;
    15. private int mBottomTile;
    16.  
    17. private GameObject mHero;
    18.  
    19. void Awake()
    20. {
    21.   Tilesize = 64.0F;
    22.   mCamera = GetComponent<Camera>();
    23.   mHero = GameObject.Find("Hero" + GameObject.Find("World").GetComponent<WorldComponent>().HeroType.ToString().ToLower());
    24. }
    25.  
    26.     void Start ()
    27. {
    28.  
    29.     }
    30.  
    31.     void Update ()
    32. {
    33.   //mCamera.orthographicSize = ((64F / 64F) * 16) / 2f;
    34.  
    35.   //Debug.Log("orthographicSize: " + mCamera.orthographicSize);
    36.  
    37.   float mScale = Screen.height / 600F;
    38.   mCamera.orthographicSize = (Screen.height / (mScale * Tilesize)) * 0.5F;
    39.  
    40.    //mCamera.orthographicSize = ((768)/(1 * 12)) * 0.5F;
    41.   //mCamera.orthographicSize =  Screen.height / (2 * Tilesize);
    42.   //mCamera.orthographicSize = (Screen.height / Tilesize) / 2F;
    43.  
    44. // Debug.Log("orthographic Size: " + mCamera.orthographicSize.ToString());
    45.  
    46.   if (mHero != null)
    47.     {
    48.      transform.position = Vector3.Lerp(transform.position, mHero.transform.position, 0.1F) + new Vector3(0, 0, -10);
    49.  
    50.      //Debug.Log(string.Format("X{0}, Y{1}, Z{2}", mHero.transform.position.x, mHero.transform.position.y, mHero.transform.position.z));
    51.  
    52.  
    53.      //Debug.Log(string.Format("X{0}, Y{1}, Z{2}", transform.position.x, transform.position.y,transform.position.z));
    54.  
    55.  
    56.      float mHeroTileX = (float)System.Math.Floor(mHero.transform.position.x);
    57.      float mHeroTileY = (float)System.Math.Floor(mHero.transform.position.y);
    58.  
    59. //     var vertExtent = mCamera.orthographicSize;
    60.   //   var horzExtent = vertExtent * Screen.width / Screen.height;
    61.  
    62.     // Debug.Log(string.Format("vertExtent: {0}, horzExtent: {1}", vertExtent, horzExtent));
    63.      //Debug.Log(string.Format("mCamera.pixelRect.width: {0}, mCamera.pixelRect.height: {1}", mCamera.pixelRect.width, mCamera.pixelRect.height));
    64.  
    65.      //Debug.Log(string.Format("Hero X: {0}, Hero Y: {1}", mHeroTileX, mHeroTileY));
    66.  
    67.      //double mHeroTileY = System.Math.Abs(System.Math.Floor(mHero.transform.position.y));
    68.  
    69.      // 64.0 = Tile Size
    70.      float mTilesInHalfWidth = ((mCamera.pixelRect.width * 0.5F) / Tilesize);
    71.      float mTilesInHalfHeight = ((mCamera.pixelRect.height * 0.5F) / Tilesize);
    72.  
    73.      //Debug.Log(string.Format("mTilesInHalfWidth: {0}, mTilesInHalfHeight: {1}", mTilesInHalfWidth, mTilesInHalfHeight));
    74.  
    75.      mLeftTile = System.Convert.ToInt32(System.Math.Truncate(mHeroTileX - mTilesInHalfWidth));
    76.      mTopTile = System.Convert.ToInt32(System.Math.Truncate(mHeroTileY + mTilesInHalfHeight));
    77.      mRightTile = System.Convert.ToInt32(System.Math.Truncate(mHeroTileX + mTilesInHalfWidth));
    78.      mBottomTile = System.Convert.ToInt32(System.Math.Truncate(mHeroTileY - mTilesInHalfHeight));
    79.  
    80.  
    81.      //Debug.Log(string.Format("mRightTile: {0}", mRightTile));
    82.  
    83.      //Debug.Log(string.Format("Old Left: {0} Top: {1} Right: {2} Bottom: {3}", mLeftTile, mTopTile, mRightTile, mBottomTile));
    84.  
    85.      //mLeftTile -= mLeftTile;
    86.      //mTopTile += mTopTile;
    87.      //mRightTile += mRightTile;
    88.      //mBottomTile -= mBottomTile;
    89.      //Debug.Log(string.Format("New Left: {0} Top: {1} Right: {2} Bottom: {3}", mLeftTile, mTopTile, mRightTile, mBottomTile));
    90.     }
    91.     }
    92.  
    93. public Rect CameraScaledViewPort(float xscale, float yscale)
    94. {
    95.   Rect mRect = CameraViewPort();
    96.   float mTilesInHalfWidth = ((mCamera.pixelRect.width * xscale) / Tilesize);
    97.   float mTilesInHalfHeight = ((mCamera.pixelRect.height * yscale) / Tilesize);
    98.    float mLeft = (mRect.xMin - mTilesInHalfWidth);
    99.    float mTop = (mRect.yMin + mTilesInHalfHeight);
    100.    float mRight = (mRect.xMax + mTilesInHalfWidth);
    101.    float mBottom = (mRect.yMax - mTilesInHalfHeight);
    102.   return new Rect(mLeft, mTop, mRight - mLeft, mBottom - mTop);
    103. }
    104.  
    105. public Rect CameraViewPort()
    106. {
    107.   return new Rect(mLeftTile, mTopTile, mRightTile - mLeftTile, mBottomTile - mTopTile);
    108. }
    109.  
    110. public void DrawViewPort(Rect rect, Color colour, float duration)
    111. {
    112.   // Top
    113.   Debug.DrawLine(new Vector3(rect.xMin, rect.yMin, 0), new Vector3(rect.xMax, rect.yMin, 0), colour, duration);
    114.  
    115.   // Bottom
    116.   Debug.DrawLine(new Vector3(rect.xMin, rect.yMax, 0), new Vector3(rect.xMax, rect.yMax, 0), colour, duration);
    117.  
    118.   // Left
    119.   Debug.DrawLine(new Vector3(rect.xMin, rect.yMin, 0), new Vector3(rect.xMin, rect.yMax, 0), colour, duration);
    120.  
    121.   // Right
    122.   Debug.DrawLine(new Vector3(rect.xMax, rect.yMin, 0), new Vector3(rect.xMax, rect.yMax, 0), colour, duration);
    123. }
    124.  
    125. public bool ObjectCoordinatesInCameraView(int x, int y)
    126. {
    127.   if (x >= mLeftTile && x <= mRightTile)
    128.    if (y >= System.Math.Min(mTopTile, mBottomTile) && y <= System.Math.Max(mTopTile, mBottomTile))
    129.     return true;
    130.  
    131.   return false;
    132. }
    133.  
    134. public bool ObjectCoordinatesInScaledCameraView(float xscale, float yscale, int x, int y)
    135. {
    136.   Rect mRect = CameraScaledViewPort(xscale, yscale);
    137.   if (x >= mRect.xMin && x <= mRect.xMax)
    138.    if (y >= System.Math.Min(mRect.yMin, mRect.yMax) && y <= System.Math.Max(mRect.yMin, mRect.yMax))
    139.     return true;
    140.  
    141.   return false;
    142. }
    143. }
    144.  
    I hope this helps.
     
  5. GooseNinja

    GooseNinja

    Joined:
    Sep 14, 2012
    Posts:
    45
  6. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    What exactly is your question?

    You want to draw pixel art and ask what a good base resolution is? Let us know what "logical" screen resolutions you would like to support (I.e. Higher resolutions would be scaled up)

    You have pixel art assets and want to know how to achieve pixel perfect rendering at different resolutions?
     
  7. imaginaryhuman

    imaginaryhuman

    Joined:
    Mar 21, 2010
    Posts:
    5,834
    Using a single resolution/size of sprite textures, you CANNOT and never will be able to get pixel perfect sprites in multiple resolutions. You will only be able to get pixel-perfect in SOME resolutions, typically those which exactly have a 1:1 or 1:2 or 1:4 pixel to texel ratio with no scaling. e.g. you can make sprites for 1920x1080 which are pixel perfect at that resolution, and they would also look technically crisp at 960x540 which is half the res, or at double UHD res, but any other resolution - forget it. There is no way it will be perfect. The grid of pixels in the sprite data simply does not exactly divide into the grid of pixels on the screen, meaning some columns have to be added or removed, destroying the 'pixel perfection'. If the resolution isn't an exact multiple of the original resolution, simple math tells you there must be some 'remainder' or sub-pixel squishing. The only way to get pixel perfect on all resolutions is to make separate sets of sprite textures for each individual resolution ratio.
     
  8. voltage

    voltage

    Joined:
    Nov 11, 2011
    Posts:
    515
    Thank you for that explanation, I needed that. So say I wanted my pixels to look like this:
    I can see the individual pixels on the screen and it looks lovely. I'd like to support resolutions from 720p - 4k. What orthographic size should I base my game's textures on? Sure it'll scale as you've said, but ideally it'll remain pixel perfect at 1080p. I've read that this equation - "target height in pixels/pixels per unit/2.0f" will give you the correct ortho size. So half of 1080p is 540, divided by 64 PPU, divided by 2 is 4.21875. Am I understanding this correctly?

    Ideally I'd like to use 64x64 sprites. I hear you're supposed to match your sprite size with PPU evenly. Is this true? Here's the link to the info I gathered:
    https://www.reddit.com/r/Unity2D/comments/2bsoj1/recommended_best_practices_for_pixel_art_games/

    And here's the result:
    I know I'm missing something obvious, but I don't understand how this process works and need guidance.
     

    Attached Files:

  9. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    I think there are different schools of thought with regards to ppu. In case you use physic, it can make a difference (I never tried as I use my own collision/physics).

    I have so far used 1:1 for pixel perfect and 1:100 otherwise. In the end, it does not matter so much, but 1:1 allows easier rounding.

    For different aspect ratios you have to decide on letter/pillar boxing or filling those areas with cover / ui / non critical game play. Good to plan this in the beginning.

    Other than that you have to pick a base resolution that scales in integers to your target resolution. It really depends on the "pixel" size you desire as an output.

    Example:
    Base 320x180
    Ppu 1:1
    Ortho 180/2 = 90 (if ppu were 1:2 ortho = 45 and so forth)

    If displayed at 1280 x 720, unity will automatically upscale and 1 pixel will scale by factor 4 to 4x4 pixel. Since the aspect ratio of base and target is the same, no black borders.

    You would set the ortho in code to support different aspect ratios, depending on device resolution and you have to deal with / accept letter/pillar boxes.
     
  10. voltage

    voltage

    Joined:
    Nov 11, 2011
    Posts:
    515
    So I should set my pixels to be 1:1 PPU for best practices in physics, and then divide my base res by 2 and that's it?

    I've done so just now and my sprite is still very small on screen, (although it does look slightly better.) How would I go about making my sprites large on screen like the example? Do I just eyeball it? I noticed if I set the ortho to 32, the 64x64 sprite matches perfectly from top to bottom - at a PPU of 1:1.

    Maybe I'm conflating pixel perfection with something else?
     
  11. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    For physics a ppu of 1:100 is best practise, but it's something you can worry about later. Possibly a ppu of 1:1 is the easiest to start with as it sets one "unit" in Unity to one pixel.

    Correct, in that 1:1 ppu, ortho size is base resolution height divided by two.

    If you use 320x180 as the base and set ortho to 90, and import the 64x64 pixel sprites with ppu of 1, you can put 320 / 64 = 5 horizontal and 180/64 =2.8 vertical to fill the entire screen. If this is not what you are getting, something is wrong.

    Edit:
    Well at ortho 32 you are basically zooming in by 90/32. This times 64 = 180, so one Sprite filling the height of the screen would be expected, not the width of the screen...
     
    Last edited: Dec 5, 2016
  12. voltage

    voltage

    Joined:
    Nov 11, 2011
    Posts:
    515
    But you can't set the width of the camera independently from the height, unless you're referring to the pixel perfect camera asset?

    I decided to try 1:100 PPU with the 540 ortho and it's almost invisible.
     
  13. piggybank1974

    piggybank1974

    Joined:
    Dec 15, 2015
    Posts:
    621
    imaginaryhuman is right on this, but could you imagine how big the assets would be for all formats of android, just forget it, I wonder how candy crush developers do it, when I had a poke into their Apk "shush don't tell anybody" a few years ago, I couldn't see individual assets if I remember, I know they don't use unity or anything like that c++ I would of thought but they do it somehow??
     
  14. voltage

    voltage

    Joined:
    Nov 11, 2011
    Posts:
    515
    I've seen this blog before and decided to have a closer look. They suggest a ortho screen of 5 for a 1080p screen. Set the PPU to 108 and apparently that means I have nice/clean pixels. However, my sprite is far too small to even be usable. So if I give up on this pixel perfection jargon, can I still use pixel art on any sizes I want? It's becoming a pain just to get started.

    Additionally, they confirm that 108 pixels per unit space. But how many unit spaces are on a 1080p screen? Is it the same for all screens? If my Ortho is 5, does that mean I have a total of 10 vertical Unit spaces?
     
  15. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    @voltage

    Pixel perfect is not trivial, but you have not yet reached the parts that are less obvious.

    Understanding screen aspect ratio, logical (design resolution), target resolution and scaling, ortho camera size, etc. are fundamental and I strongly recommend that you experiment with this until you fully understand it.

    I can see from your responses that your are having some difficulties. My feeling is that you are looking at too many different explanations and that you are mixing things up somehow.

    Simplified description + I am sure this is explained better in some of the learn unity pages:
    1. Unity works in "world space" with an imaginary unit called "unit"
    2. Artwork is designed in "pixel space" with the unit "pixel"
    3. PPU simply is a scale factor between those two, like the scale on a geographic map
    4. PPU scale, besides from the obvious scale effect, as far as I know only matters for physics engines as their calculations work best at a certain scale (i.e. if the space is too small / too big, the calculations become less realistic) --- around 1:100 seems to be fine, but it does not matter if it is exactly 1:100 or not
    5. The camera converts the "world space with units" back to "device screen space in pixels". Each device has a native pixel resolution and aspect ratio.
    6. The position and size setting of the orthographic camera defines, which part of the "world space" shows up on the "device screen space".
    7. By definition, the camera renders 1 unit to 1 pixel, if its size is set to (target screen height in pixels) / (2 * PPU)
    8. You can effectively zoom, by adjusting the ortho size to (target screen height in pixels) / (2 * PPU * zoom factor)
    9. Different from point 8, Unity automatically scales the rendered screen to fit the devices display height. In case you set the target screen height in pixels to say 1/4 of the actual native device display height, Unity will automatically scale up the screen to fit the device display height. Effectively 1 pixel now occupies 4x4 native pixles (i.e. it still looks pixelated on the high resolution devices of today)
    10. By definition, this all happens in relation to the height, and only indirectly to the width (the relation is the aspect ratio)...so yes, you only use the height for defining ortho size

    I typically have a simple image in my projects for visual debugging purposes with the key design resolutions, the attached on is just taken from a project that was for mobile, hence the portrait orientation.
    • Basically the red area is the area visible in all aspect ratios and used for core game play.
    • The yellow and green areas are overlay areas that you would fill with less relevant items or UI elements, unless you want the letter / pillar boxes in black
    • You can ignore the blue area, it is irrelevant for display (just marks a spawn area) and the grid (it is just an orientation for a tile map and where those are drawn)
     

    Attached Files:

    ErykT, luigi7 and voltage like this.
  16. ColossalPaul

    ColossalPaul

    Unity Technologies

    Joined:
    May 1, 2013
    Posts:
    174
    https://blogs.unity3d.com/2015/06/19/pixel-perfect-2d/

    Hope this helps...
     
  17. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    @ColossalPaul

    Are you the author of this article? While it is very useful, for some reason, I personally did not find it intuitive and easy to follow, in particular if you are new to Unity. (It the first thing I read when I started using unity but it took a lot of extra effort to get it right)

    Many people (including myself) struggle with pixel perfect and I think it would be helpful for the community to expand on that article. I am not native English and not a writer, but would be happy to work with you on said 'expansion'

    Let me know.
     
  18. ColossalPaul

    ColossalPaul

    Unity Technologies

    Joined:
    May 1, 2013
    Posts:
    174
    Pixel perfect is not something we tell new users to do. Like you said:
    I would love to improve my writing... do give me some pointers... However, an article that spans new users to experts could be boring for both. Tell me your secret on how to solve it!
     
    GooseNinja likes this.
  19. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    @ColossalPaul

    I will gather my thoughts and PM them to you - likely in the next 1-2 days.
     
    voltage likes this.
  20. Redden44

    Redden44

    Joined:
    Nov 15, 2014
    Posts:
    159
    I use these on my Camera, hope it helps.

    This function is called from my OptionManager when it sets/changes the screen resolution; if you change the screen resolution, you must update the camera orthogrphic size.
    The scale factor basically allows me to change the zoom of the camera by using a multiple of the correct ratio, which means the pixels will still look good.
    I don't know if it's a good solution or not, it just works for me..when you change resolution, if you don't scale the orthographicSize, sometimes you will see a giant portion of the game, sometimes a small one..the scale factor helps with that.
    So basically, you can't have the same ratio and zoom with different resolutions by using the same spritesheet..you need different sprite sheets for each resolution. If you want to use 1 sprite sheet, you need to calculate the correct OrthographicSize, which will mess up your zoom..to fix that, you can use a scale factor and find a compromise.
    Code (CSharp):
    1.  
    2. public void UpdateOrthographicSize(int scaleIndex)
    3.     {
    4.         // OrthographicSize = Screen resolution height / PPU * Scale PPU*8 o PPU*4);
    5.         float screenHeight = Screen.height;
    6.         int scale;
    7.         if (scaleIndex == 0)
    8.             scale = 4;
    9.         else
    10.             scale = 8;
    11.         _mainCamera.orthographicSize = screenHeight / (32 * scale);
    12.     }
    This code prevents Unity from stretching the sprites, there is a problem if you turn off the VSync, but you can fix it by limiting the number of FPS.
    Code (CSharp):
    1. public static float RoundToNearestPixel(float unityUnits, Camera viewingCamera)
    2.     {
    3.         float valueInPixels = (Screen.height / (viewingCamera.orthographicSize * 2)) * unityUnits;
    4.         valueInPixels = Mathf.Round(valueInPixels);
    5.         float adjustedUnityUnits = valueInPixels / (Screen.height / (viewingCamera.orthographicSize * 2));
    6.         return adjustedUnityUnits;
    7.     }
    8.  
    9.    void LateUpdate()
    10.     {
    11.         Vector3 newPos = transform.position;
    12.         Vector3 roundPos = new Vector3(RoundToNearestPixel(newPos.x, _mainCamera), RoundToNearestPixel(newPos.y, _mainCamera), newPos.z);
    13.         transform.position = roundPos;
    14.      
    15.     }
    To limit the FPS you can do something like this within your Options:
    Code (CSharp):
    1.  
    2. if (_vSync == false)
    3.         {
    4.             QualitySettings.vSyncCount = 0;
    5.             Application.targetFrameRate = 60;
    6.         }
    7.         else
    8.             QualitySettings.vSyncCount = 1;
    Last thing I read you should turn PixelSnap ON in your materials which use shaders like SpriteDefault and SpriteDiffuse; I didn't research that yet though.
     
  21. voltage

    voltage

    Joined:
    Nov 11, 2011
    Posts:
    515
    That's exactly what I want. I would love to have a thorough explanation from beginning to end on how this process works - with examples and images. I want to know why I'm modifying variables, not just because I'm told to.
     
  22. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    @voltage
    I am with you on this one. In the end, I do not believe that this is a real "expert" topic. There are far harder nuts to crack. What is missing is a good end to end description of the process / settings and a good explanation with examples.

    I shared a rough outline with @ColossalPaul yesterday. My problem is that (a) I am not very good at explaining in written word + (b) I am not an expert of Unity at all, so I would really benefit from working together with a Unity expert to make sure the explanation uses the right terminology, etc.

    PS: what really helps is the picture I shared earlier (mainly for scaling, aspect ratios) and to have a 1x1 pixel, 2x2 pixel and a 3x3 pixel sprite in the project file and place them exactly in the screen corners (they can overlap each other, use different colors). Try this first at a relatively low resolution (i.e. 100x100 or 160x90, etc.) this way you can see better what happens as they become very big.
     
  23. ErykT

    ErykT

    Joined:
    Oct 30, 2017
    Posts:
    1
    I'm brand-spankin' new to ALL of this and I find this reply very helpful. While I know I'm going to get everything wrong at first, lol, this answer will at least be pointing me in the right direction.