Skip to content

Instantly share code, notes, and snippets.

@karlgluck
Created September 13, 2018 22:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karlgluck/3ecf3ed00ae5fde47171c130221b0dc4 to your computer and use it in GitHub Desktop.
Save karlgluck/3ecf3ed00ae5fde47171c130221b0dc4 to your computer and use it in GitHub Desktop.
Main file from the Perfect Pixel Camera asset on the Unity asset store, licensed with MIT https://assetstore.unity.com/packages/tools/camera/perfect-pixel-camera-by-gg-ez-100000
// Copyright 2018 Karl Gluck
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using UnityEngine;
using UnityEngine.Rendering;
namespace GGEZ
{
[
ExecuteInEditMode, // Run this script in edit mode so the preview window looks good
RequireComponent (typeof(Camera)), // Only add this component if there is a camera
HelpURL ("http://ggez.org/posts/perfect-pixel-camera/"), // Website opened by clicking the book icon on the component
DisallowMultipleComponent, // Only one of these per GameObject
AddComponentMenu ("GGEZ/Camera/Perfect Pixel Camera") // Insert into the "Add Component..." menu
]
public class PerfectPixelCamera : MonoBehaviour
{
// Set this value to the same value as Pixels Per Unit when importing sprites
[
Tooltip ("The number of texture pixels that fit in 1.0 world units. Common values are 8, 16, 32 and 64. If you're making a tile-based game, this is your tile size."),
Range (1, 64)
]
public int TexturePixelsPerWorldUnit = 16;
// Reference to the camera on this same GameObject. Found
// by the OnEnable function.
private Camera cameraComponent;
// Set to a value that compensates for the half-pixel offset when rendering
// with Direct3D. This is automatically handled by Unity 5.5 and later.
// If that's the case, it is declared as a constant 0 which the compiler
// can use to optimize calculations in LateUpdate.
// See: https://docs.unity3d.com/Manual/UpgradeGuide55.html
#if UNITY_5_5_OR_NEWER
private const float halfPixelOffsetIfNeededForD3D = 0f;
#else
private float halfPixelOffsetIfNeededForD3D;
#endif
// Objects that you want to be perfectly aligned should have X and Y
// coordinates that are integer multiples of this value. It is always
// safe to align to 1.0 / TexturePixelsPerWorldUnit, but this value can
// be smaller if the camera is zoomed and will make movement more smooth.
public float SnapSizeWorldUnits { get; private set; }
//---------------------------------------------------------------------------
// OnEnable - Called by Unity when the component is created or enabled
//---------------------------------------------------------------------------
void OnEnable ()
{
// Grab a reference to the camera
this.cameraComponent = (Camera)this.GetComponent (typeof(Camera));
#if !UNITY_5_5_OR_NEWER
// Detect whether we are using Direct3D, because D3D rendering has a
// half-pixel offset from OpenGL rendering.
bool isD3D =
SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D9
|| SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11
|| SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D12;
// 0.4975f and not 0.5f is used because 0.5f is able to be represented
// as a perfect IEEE float. This means that when added to other
// floats that are imperfect, the results can sometimes be rounded
// the wrong way. It can be tricky to reproduce so this isn't part
// of the main demo.
this.halfPixelOffsetIfNeededForD3D = isD3D ? 0.4975f : 0f;
#endif
// Run the LateUpdate immediately so that the projection gets set up
this.LateUpdate ();
}
//---------------------------------------------------------------------------
// OnDisable - Called by Unity when the component is disabled or destroyed
// This function cleans up after the PerfectPixelCamera so that the
// projection matrix isn't left in an altered state by this component.
//---------------------------------------------------------------------------
void OnDisable ()
{
if (this.cameraComponent == null)
{
return;
}
this.cameraComponent.ResetProjectionMatrix ();
this.cameraComponent = null;
}
//---------------------------------------------------------------------------
// LateUpdate - Called by Unity after all other functions have run Update.
// If you have other scripts that use LateUpdate, you might want to use
// the Script Execution Order project setting to make this script run last.
//---------------------------------------------------------------------------
void LateUpdate ()
{
// Get a local reference
Camera camera = this.cameraComponent;
// Make sure the camera is in 2D mode
camera.transparencySortMode = TransparencySortMode.Orthographic;
camera.orthographic = true;
camera.transform.rotation = Quaternion.identity;
camera.orthographicSize = Mathf.Max (camera.orthographicSize, 0.00001f);
// This is the code that computes the parameters needed to perfectly map
// world-space pixels to screen-space pixels.
var pixelRect = camera.pixelRect;
float texturePixelsPerWorldUnit = this.TexturePixelsPerWorldUnit;
float zoomFactor = Mathf.Max (1f, Mathf.Ceil ((1f * pixelRect.height) / (camera.orthographicSize * 2f * texturePixelsPerWorldUnit)));
float halfWidth = (1f * pixelRect.width) / (zoomFactor * 2f * texturePixelsPerWorldUnit);
float halfHeight = (1f * pixelRect.height) / (zoomFactor * 2f * texturePixelsPerWorldUnit);
float snapSizeWorldUnits = 1f / (zoomFactor * texturePixelsPerWorldUnit);
float halfPixelOffsetInWorldUnits = halfPixelOffsetIfNeededForD3D * snapSizeWorldUnits;
float pixelPerfectXOffset = halfPixelOffsetInWorldUnits - Mathf.Repeat (snapSizeWorldUnits + Mathf.Repeat (camera.transform.position.x, snapSizeWorldUnits), snapSizeWorldUnits);
float pixelPerfectYOffset = halfPixelOffsetInWorldUnits - Mathf.Repeat (snapSizeWorldUnits + Mathf.Repeat (camera.transform.position.y, snapSizeWorldUnits), snapSizeWorldUnits);
// Save the snap size so other scripts can use it
this.SnapSizeWorldUnits = snapSizeWorldUnits;
// Build a manual projection matrix that fixes the camera!
camera.projectionMatrix = Matrix4x4.Ortho (
-halfWidth + pixelPerfectXOffset,
halfWidth + pixelPerfectXOffset,
-halfHeight + pixelPerfectYOffset,
halfHeight + pixelPerfectYOffset,
camera.nearClipPlane,
camera.farClipPlane
);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment