references/api-reference.md
# PixelPerfect Skill — Full API Reference
## URP `PixelPerfectCamera` (`UnityEngine.Rendering.Universal`)
API is identical since Unity 2023.2 through latest Unity 6 versions.
### Properties
| Property | Type | Access | Description |
|---|---|---|---|
| `assetsPPU` | `int` | get/set | Must match Pixels Per Unit on all sprites in scene |
| `refResolutionX` | `int` | get/set | Base resolution width |
| `refResolutionY` | `int` | get/set | Base resolution height |
| `cropFrame` | `CropFrame` | get/set | How to handle aspect ratio differences |
| `gridSnapping` | `GridSnapping` | get/set | Snapping mode — replaces old `pixelSnapping`/`upscaleRT` bools |
| `orthographicSize` | `float` | get only | PP Camera's calculated ortho size (different from `Camera.orthographicSize`) |
| `pixelRatio` | `int` | get only | Actual scale factor applied (e.g. 6 on 1080p with 320×180 ref res) |
| `requiresUpscalePass` | `bool` | get only | True when `gridSnapping = UpscaleRenderTexture` |
### Methods
| Method | Returns | Description |
|---|---|---|
| `RoundToPixel(Vector3)` | `Vector3` | Snaps world-space position to pixel grid. Adapts to current `gridSnapping`. |
| `CorrectCinemachineOrthoSize(float)` | `float` | Returns nearest pixel-perfect ortho size. Used by the Cinemachine extension. |
### Static
**`UnityEngine.U2D.PixelPerfectRendering.pixelSnapSpacing`** (static float) — world-space size of one screen pixel. Formula: `Camera.orthographicSize * 2f / Camera.pixelHeight`. Set manually in custom setups that bypass the PP Camera component (e.g. HDRP fallback, custom SRP).
---
## `GridSnapping` Enum
| Value | Old standalone equivalent | Behaviour |
|---|---|---|
| `None` | `pixelSnapping = false`, `upscaleRT = false` | No snapping. |
| `PixelSnapping` | `pixelSnapping = true` | Snaps SpriteRenderers at render-time. Transforms stay at float precision. |
| `UpscaleRenderTexture` | `upscaleRT = true` | Renders at ref res then upscales. Incompatible with post-processing and UI text. |
---
## `CropFrame` Enum
| Value | Old standalone equivalent | Behaviour |
|---|---|---|
| `None` | both false | No cropping. Scene stretches to fill screen. |
| `Pillarbox` | `cropFrameX = true, cropFrameY = false` | Black bars left and right. |
| `Letterbox` | `cropFrameX = false, cropFrameY = true` | Black bars top and bottom. |
| `Windowbox` | both true, `stretchFill = false` | Black bars all sides. Safest default. |
| `StretchFill` | both true, `stretchFill = true` | Fills screen maintaining aspect ratio. Enables component-level Filter Mode. |
**Filter Mode** (only active when `StretchFill` — separate from per-sprite texture Filter Mode):
- `Retro AA` — default. Integer upscale to nearest multiple, then bilinear to screen. Recommended.
- `Point` — nearest-neighbour all the way to screen. Can lose pixel-perfectness at non-integer scale factors.
---
## Recommended Configurations
| Use case | `gridSnapping` | `cropFrame` | Notes |
|---|---|---|---|
| Standard pixel art | `PixelSnapping` | `Windowbox` | Safe default. Compatible with post-processing and UI. |
| Authentic low-res look | `UpscaleRenderTexture` | `Windowbox` | No post-processing. Separate UI camera required. |
| Performance on 4K/HiDPI | `UpscaleRenderTexture` | `Windowbox` | Renders fewer pixels. Same UI caveat. |
| Full-screen stretch | `PixelSnapping` | `StretchFill` | Use `Retro AA` filter, not Point. |
---
## Standalone Built-in (`UnityEngine.U2D.PixelPerfectCamera`)
All booleans, no enums. No `RoundToPixel`, `CorrectCinemachineOrthoSize`, `requiresUpscalePass`, or `orthographicSize`.
| Property | Type | Constraint |
|---|---|---|
| `assetsPPU` | `int` | — |
| `refResolutionX` / `refResolutionY` | `int` | — |
| `pixelSnapping` | `bool` | Silently ignored when `upscaleRT = true` |
| `upscaleRT` | `bool` | When true, `pixelSnapping` has no effect |
| `cropFrameX` | `bool` | Black bars left/right |
| `cropFrameY` | `bool` | Black bars top/bottom |
| `stretchFill` | `bool` | Only functional when **both** `cropFrameX` and `cropFrameY` are `true` |
| `pixelRatio` | `int` readonly | Same semantics as URP `pixelRatio` |
references/camera-setup-builtin.cs
// PixelPerfect Skill — Built-in Standalone Camera Setup
// -------------------------------------------------------
// Assembly: Unity.2D.PixelPerfect
using UnityEngine;
using UnityEngine.U2D;
namespace PixelPerfect
{
public static class CameraSetupBuiltIn
{
public static PixelPerfectCamera ConfigureCamera(Camera camera, int refResX, int refResY, int targetPPU)
{
var pp = camera.gameObject.GetComponent<PixelPerfectCamera>()
?? camera.gameObject.AddComponent<PixelPerfectCamera>();
// Same base settings as URP
camera.orthographic = true;
camera.orthographicSize = (refResY / 2f) / targetPPU;
camera.allowHDR = camera.allowMSAA = camera.allowDynamicResolution = false;
QualitySettings.antiAliasing = 0;
QualitySettings.anisotropicFiltering = AnisotropicFiltering.Disable;
pp.assetsPPU = targetPPU;
pp.refResolutionX = refResX;
pp.refResolutionY = refResY;
pp.pixelSnapping = true; // silently ignored when upscaleRT = true
pp.upscaleRT = false;
pp.cropFrameX = true; // black bars left/right
pp.cropFrameY = true; // black bars top/bottom — both true = Windowbox equivalent
pp.stretchFill = false; // only functional when BOTH cropFrameX and cropFrameY are true
// Read-only: pp.pixelRatio (int) — same semantics as URP pixelRatio
//
// Note: No RoundToPixel, CorrectCinemachineOrthoSize, requiresUpscalePass,
// or orthographicSize on the standalone component.
return pp;
}
}
}
references/camera-setup-urp.cs
// PixelPerfect Skill — URP Camera Setup
// ---------------------------------------
using UnityEngine;
using UnityEngine.Rendering.Universal;
using UnityEngine.U2D;
namespace PixelPerfect
{
public static class CameraSetupURP
{
// orthographicSize formula: (refResY / 2) / PPU
// e.g. 320x180 at 16 PPU → 90 / 16 = 5.625
public static PixelPerfectCamera ConfigureCamera(Camera camera, int refResX, int refResY, int targetPPU)
{
var pp = camera.gameObject.GetComponent<PixelPerfectCamera>()
?? camera.gameObject.AddComponent<PixelPerfectCamera>();
camera.orthographic = true;
camera.orthographicSize = (refResY / 2f) / targetPPU;
camera.allowHDR = camera.allowMSAA = camera.allowDynamicResolution = false;
QualitySettings.antiAliasing = 0;
QualitySettings.anisotropicFiltering = AnisotropicFiltering.Disable;
pp.assetsPPU = targetPPU;
pp.refResolutionX = refResX;
pp.refResolutionY = refResY;
pp.gridSnapping = PixelPerfectCamera.GridSnapping.PixelSnapping; // or UpscaleRenderTexture
pp.cropFrame = PixelPerfectCamera.CropFrame.Windowbox; // or None/Pillarbox/Letterbox/StretchFill
// Read-only properties (for reference):
// pp.pixelRatio (int) — actual scale factor applied (e.g. 6 on 1080p with 320x180 ref res)
// pp.requiresUpscalePass (bool) — true when gridSnapping = UpscaleRenderTexture
// pp.orthographicSize (float) — PP-calculated ortho size (different from Camera.orthographicSize)
// Utility methods (for reference):
// pp.RoundToPixel(Vector3) — pixel-grid snapping for custom camera controllers
// pp.CorrectCinemachineOrthoSize(float) — used by Cinemachine extension
return pp;
}
// For custom setups that bypass the PP Camera component (e.g. HDRP fallback, custom SRP):
// set pixelSnapSpacing manually to enable snapping at the renderer level.
public static void SetManualPixelSnapSpacing(Camera camera)
{
PixelPerfectRendering.pixelSnapSpacing = camera.orthographicSize * 2f / camera.pixelHeight;
}
}
}
references/pipeline-detection.cs
// PixelPerfect Skill — Pipeline Detection
// ----------------------------------------
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.U2D;
namespace PixelPerfect
{
public enum PipelineType { BuiltIn, URP, HDRP, Custom }
public static class PipelineDetection
{
// GraphicsSettings.currentRenderPipeline is always up to date — safe to call on startup.
// Do NOT use RenderPipelineManager.currentPipeline: it is null until at least one frame renders.
public static PipelineType DetectPipeline()
{
var rpa = GraphicsSettings.currentRenderPipeline;
if (rpa == null) return PipelineType.BuiltIn;
// Use FullName (includes namespace) — more stable than Name, which strips the namespace.
// URP: UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset
// HDRP: UnityEngine.Rendering.HighDefinition.HDRenderPipelineAsset
var fullName = rpa.GetType().FullName ?? string.Empty;
if (fullName.Contains("Universal")) return PipelineType.URP;
if (fullName.Contains("HighDefinition")) return PipelineType.HDRP;
return PipelineType.Custom;
}
public static System.Type GetPixelPerfectCameraType(PipelineType pipeline)
{
if (pipeline == PipelineType.URP)
{
var t = System.Type.GetType(
"UnityEngine.Rendering.Universal.PixelPerfectCamera, " +
"Unity.RenderPipelines.Universal.2D.Runtime");
if (t != null) return t;
Debug.LogWarning("URP detected but PixelPerfectCamera not found. Is the 2D Renderer installed?");
}
// Assembly name confirmed from asmdef — no "Runtime" suffix
return System.Type.GetType("UnityEngine.U2D.PixelPerfectCamera, Unity.2D.PixelPerfect");
}
// Migration diagnostic: check for the wrong component type on a URP camera
public static bool HasPipelineMismatch(Camera camera)
{
return DetectPipeline() == PipelineType.URP
&& camera.GetComponent<PixelPerfectCamera>() != null;
}
}
}
references/sprite-settings.cs
// PixelPerfect Skill — Sprite Import Settings (Editor-only)
// ----------------------------------------------------------
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace PixelPerfect.Editor
{
public static class SpriteImportUtility
{
public static TextureImporter GetImporter(Sprite sprite)
{
if (sprite == null) return null;
var path = AssetDatabase.GetAssetPath(sprite);
if (string.IsNullOrEmpty(path)) return null;
return AssetImporter.GetAtPath(path) as TextureImporter;
}
public static bool FixSpriteImportSettings(Sprite sprite, int targetPPU)
{
var importer = GetImporter(sprite);
if (importer == null) { Debug.LogWarning($"No importer for: {sprite?.name}"); return false; }
importer.filterMode = FilterMode.Point;
importer.mipmapEnabled = false;
importer.textureCompression = TextureImporterCompression.Uncompressed;
importer.spritePixelsPerUnit = targetPPU;
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(sprite), ImportAssetOptions.ForceUpdate);
return true;
}
// After batch-fixing multiple sprites, always flush with:
// AssetDatabase.SaveAssets();
// AssetDatabase.Refresh();
}
}
#endif
SKILL.md
---
name: 2d-pixel-perfect
description: Sets up, diagnoses, and fixes pixel perfect 2D rendering with PixelPerfectCamera in URP or Built-in. Use when a retro-style or pixel art 2D game looks blurry, jittery, or misaligned. Not for HD 2D or high-resolution art.
---
Set up, diagnose, and fix pixel perfect 2D rendering in Unity projects.
---
## ⚠ There Are Two Completely Separate Implementations
Pixel perfect rendering in Unity is **not one system** — it is two separate, incompatible implementations, one per render pipeline. **Always detect the pipeline before writing or diagnosing any code.**
| | URP | Built-in |
|---|---|---|
| **Component** | `UnityEngine.Rendering.Universal.PixelPerfectCamera` | `UnityEngine.U2D.PixelPerfectCamera` |
| **Package** | Built into URP — no extra install | `com.unity.2d.pixel-perfect` v6.0.0+ |
| **API style** | Enums (`gridSnapping`, `cropFrame`) | Booleans (`pixelSnapping`, `upscaleRT`, `cropFrameX/Y`) |
**Do not install `com.unity.2d.pixel-perfect` in a URP project.**
**→ Always call `DetectPipeline()` first** (`references/pipeline-detection.cs`), then branch your setup, diagnostics, and fixes based on the result.
---
## When NOT to use this skill
- **HD 2D or high-resolution 2D games** — pixel snapping and point filtering will make smooth art look wrong
- **UI-only scenes** — use Canvas Scaler instead
- **HDRP projects** — Pixel Perfect Camera is not supported
---
## Critical Reminders
⚠ **Detect the render pipeline first** — URP and Built-in use different Pixel Perfect Camera components that are not interchangeable.
⚠ **Filter Mode = Point is the #1 fix** — bilinear filtering is Unity's default and is almost always the cause of blurry sprites.
⚠ **Anti-Aliasing must be disabled** — in Quality Settings and on the camera. AA actively blurs pixel edges.
## Key Principles
### 1. Pipeline Detection & Camera Component Selection
See the two-path comparison table at the top of this file. Assembly name note: the standalone Built-in package installs into a `Runtime/` folder, but the asmdef `"name"` field is `Unity.2D.PixelPerfect` — no `Runtime` suffix. HDRP is unsupported — see the HDRP fallback section under Common Issues.
**→ Code: `references/pipeline-detection.cs`** — `DetectPipeline()`, `GetPixelPerfectCameraType()`, and migration mismatch check.
### 2. Diagnostic-First Approach
Always diagnose before making changes. Report findings, then fix only what is broken.
### 3. Work in the correct scope
Default to scene scope. Scan project-wide only when the user explicitly requests it.
---
## Diagnostic Checklist
**Sprite import settings:**
- [ ] Filter Mode = `Point (no filter)` on all in-scope sprites
- [ ] Mip Maps = disabled
- [ ] Compression = `None` / Uncompressed
- [ ] PPU consistent across all sprites in scene
- [ ] Sprite pivots set to Custom / Pixels mode — a center pivot on an odd-dimension sprite (e.g. 15×15) lands at 7.5px, causing 0.5px misalignment
**→ Code: `references/sprite-settings.cs`** — `GetImporter()` and `FixSpriteImportSettings()`.
**Editor snap settings:**
- [ ] Grid Size = `1 / assetsPPU` on all axes (e.g. PPU 16 → 0.0625, PPU 100 → 0.01)
- [ ] Grid Snapping enabled in the Grid and Snap overlay
- [ ] To snap existing GameObjects: select them → Align Selected → All Axes
**Camera setup:**
- [ ] Camera projection = Orthographic
- [ ] Pixel Perfect Camera component present and correct type for pipeline
- [ ] `allowHDR`, `allowMSAA`, `allowDynamicResolution` all `false`
- [ ] Scene view shows two green bounding boxes on the camera gizmo — solid = visible area, dotted = reference resolution
**→ Code: `references/camera-setup-urp.cs`** — full URP camera + PP Camera configuration.
**→ Code: `references/camera-setup-builtin.cs`** — Built-in standalone configuration.
**Project quality settings:**
- [ ] Anti-Aliasing = 0 in Quality Settings
- [ ] Anisotropic Filtering = Disabled
---
## API Reference
Full property/method tables, `GridSnapping` and `CropFrame` enum values, and recommended configurations:
**→ `references/api-reference.md`** — read this when writing or reviewing camera setup code.
Quick enum summary:
**`GridSnapping`**: `None` · `PixelSnapping` (standard) · `UpscaleRenderTexture` (authentic low-res; incompatible with post-processing and UI text)
**`CropFrame`**: `None` · `Pillarbox` · `Letterbox` · `Windowbox` (safest default) · `StretchFill`
---
## Reference Resolution
Choose before building any assets. Never change after asset production starts.
| Reference resolution | 1080p | 1440p | 4K |
|---|---|---|---|
| 320 × 180 | 6× | 8× | 12× |
| 480 × 270 | 4× | ~5.3× | 8× |
| 640 × 360 | 3× | 4× | 6× |
320×180 is the safest general choice. For screens with no integer fit (e.g. 1366×768), use `cropFrame = Windowbox` to add black bars rather than stretching to a fractional scale.
---
## Migration & Compatibility
### URP project with the Built-in standalone component
**Symptom**: `DetectPipeline()` returns URP but camera has `UnityEngine.U2D.PixelPerfectCamera`. Symptoms are subtle because the standalone component has `ENABLE_URP` conditional code.
**Fix**:
1. Remove `com.unity.2d.pixel-perfect` from Package Manager
2. Remove `UnityEngine.U2D.PixelPerfectCamera` from each camera
3. Add `UnityEngine.Rendering.Universal.PixelPerfectCamera`
4. Reconfigure — booleans (`pixelSnapping`, `upscaleRT`, `cropFrameX/Y`) become enums (`gridSnapping`, `cropFrame`)
**→ Detection code: `references/pipeline-detection.cs`** (bottom of file).
### Old URP namespace (pre-Unity 2022 / URP pre-13.x)
**Symptom**: Compiler errors referencing `UnityEngine.Experimental.Rendering.Universal`.
**Fix**: Replace `using UnityEngine.Experimental.Rendering.Universal;` with `using UnityEngine.Rendering.Universal;`. Update any assembly-qualified type strings. The `[MovedFrom]` attribute handles serialization automatically — components on GameObjects survive the upgrade.
---
## Common Issues & Solutions
### Blurry sprites
**Fix**: Set Filter Mode to Point on all in-scope sprites, disable Mip Maps, disable AA in Quality Settings.
**→ Code: `references/sprite-settings.cs`**
### Tilemap gaps between tiles
Work through in order — workarounds like negative cell gap or PPU = 31.99 break when the camera moves.
| # | Check | Fix |
|---|---|---|
| 1 | Sprite Atlas with Tight Packing off, Padding ≥ 4, Sprite Packer Mode enabled? | Enable Sprite Packer Mode in Editor settings; on the atlas set Padding ≥ 4 and turn Tight Packing off |
| 2 | Mipmaps disabled on tileset textures and atlas? | Disable Generate Mip Maps |
| 3 | AA = 0, MSAA off on camera? | Disable AA globally |
| 4 | Compression = None? | RGBA 32-bit uncompressed |
| 5 | All tile sprites have even pixel dimensions? | Odd dimensions cause 0.5px grid offset |
| 6 | PPU = tile pixel width? (16×16 → PPU 16) | PPU mismatch leaves physical gaps |
| 7 | Gaps only during camera movement after all above pass? | Use PP Camera pixel snapping; do not use `cellGap = -0.01f` |
### Cinemachine conflict
**Cause**: Both Cinemachine and the Pixel Perfect Camera write to orthographic size every frame.
**Fix**: Add `CinemachinePixelPerfect` extension via the Add Extension dropdown on each Virtual Camera. Do not add it via `AddComponent` in code.
**Known limitations**:
- Camera blends between virtual cameras are not pixel-perfect during transitions
- `UpscaleRenderTexture` reduces valid pixel-perfect ortho sizes, which may cause framing to deviate
- Target Group + Framing Transposer causes visible choppiness (no fix available)
### Post-processing blur with `upscaleRT`
**Cause**: Post-processing runs after the PP Camera upscales the render texture.
**Simple fix**: Disable `upscaleRT`. Post-processing then runs at native screen resolution.
**Advanced fix (Unity 6 URP)**: Inject a `ScriptableRendererFeature2D` at `RenderPassEvent2D.AfterRenderingPostProcessing`. Use 2D-specific base classes — `ScriptableRendererFeature` (3D base class) is silently ignored in a URP 2D renderer.
### UI text blurry with `upscaleRT`
**Status**: Known Unity bug, declined to fix (still present Unity 6, 2025).
**Root causes**: (A) Canvas renders into the low-res buffer and is upscaled with the scene. (B) TMP's SDF gradient threshold is miscalibrated at low reference resolutions.
**Fixes in order of reliability**:
1. `Screen Space - Overlay` on Canvas — bypasses the camera, renders at native resolution
2. Dedicated UI camera with no PP Camera component, `Screen Space - Camera` mode
3. Unity 6 only: `Font Material → Debug Settings → Sharpness = 1` (mitigates B, not A)
### Physics / render desync (micro-stutter)
**Cause**: Physics runs at a fixed timestep; interpolated positions produce fractional values that snap to different pixels each frame.
**Fix**:
- Enable `Rigidbody2D.interpolation = RigidbodyInterpolation2D.Interpolate` on physics-driven sprites
- Set `Time.fixedDeltaTime = 1f / 60f` to match target frame rate
- Keep camera tracking in `LateUpdate`, not `FixedUpdate`
### Non-integer scaling / pixel decimation
**Cause**: Screen resolution is not a clean integer multiple of the reference resolution.
**Fix**: Choose a reference resolution from the table above. Use `cropFrame = Windowbox` when no integer fit exists.
### Missing URP 2D Renderer
**Fix**:
1. `Assets > Create > Rendering > URP 2D Renderer Data`
2. Assign it to your URP Asset under Renderer List
3. Requires `com.unity.render-pipelines.universal` 12.0+
### HDRP fallback
Pixel Perfect Camera is unsupported in HDRP.
**Built-in / Unity 5.x**: Use `RenderTexture` + `Graphics.Blit` with `FilterMode.Point`.
**Unity 6 URP**: Use `ScriptableRendererFeature2D` + `ScriptableRenderPass2D` injected at `RenderPassEvent2D.AfterRendering` with `AddRasterRenderPass`. Note: `OnRenderImage` and `Graphics.Blit` are incompatible with Unity 6's render graph.