Skip to content

Commit c52dc63

Browse files
committed
Imported Alexander Ocias' solution for pixel-perfect 2D
Created a new scene to test it
1 parent 40182eb commit c52dc63

28 files changed

+19071
-8
lines changed

Assets/Ocias.meta

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Ocias/PixelArtCamera.meta

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Ocias/PixelArtCamera/Editor.meta

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using UnityEngine;
2+
using UnityEditor;
3+
using System.Collections;
4+
5+
[CustomEditor(typeof(PixelArtCamera))]
6+
public class PixelArtCameraEditor : Editor {
7+
SerializedProperty pixels;
8+
SerializedProperty pixelsPerUnit;
9+
SerializedProperty smooth;
10+
SerializedProperty forceSquarePixels;
11+
12+
SerializedProperty screenResolution;
13+
SerializedProperty upscaledResolution;
14+
SerializedProperty internalResolution;
15+
SerializedProperty finalBlitStretch;
16+
17+
SerializedProperty mainCamera;
18+
SerializedProperty mainCanvas;
19+
20+
void OnEnable () {
21+
pixels = serializedObject.FindProperty("pixels");
22+
pixelsPerUnit = serializedObject.FindProperty("pixelsPerUnit");
23+
smooth = serializedObject.FindProperty("smooth");
24+
forceSquarePixels = serializedObject.FindProperty("forceSquarePixels");
25+
screenResolution = serializedObject.FindProperty("screenResolution");
26+
upscaledResolution = serializedObject.FindProperty("upscaledResolution");
27+
internalResolution = serializedObject.FindProperty("internalResolution");
28+
finalBlitStretch = serializedObject.FindProperty("finalBlitStretch");
29+
mainCamera = serializedObject.FindProperty("mainCamera");
30+
mainCanvas = serializedObject.FindProperty("mainCanvas");
31+
}
32+
33+
public override void OnInspectorGUI() {
34+
serializedObject.Update();
35+
36+
//GUILayout.Label ("Smooth");
37+
DrawDefaultInspector ();
38+
pixels.vector2IntValue = EditorGUILayout.Vector2IntField("Target Pixel Dimensions", pixels.vector2IntValue);
39+
pixelsPerUnit.floatValue = EditorGUILayout.FloatField("Pixels Per Unit", pixelsPerUnit.floatValue);
40+
smooth.boolValue = EditorGUILayout.Toggle("Smooth", smooth.boolValue);
41+
forceSquarePixels.boolValue = EditorGUILayout.Toggle("Force Square Pixels", forceSquarePixels.boolValue);
42+
EditorGUILayout.LabelField("Screen: " + screenResolution.vector2IntValue.x + "×" + screenResolution.vector2IntValue.y);
43+
EditorGUILayout.LabelField("Pixel Resolution: " + internalResolution.vector2IntValue.x + "×" + internalResolution.vector2IntValue.y);
44+
EditorGUILayout.LabelField("Upscaled Resolution: " + upscaledResolution.vector2IntValue.x + "×" + upscaledResolution.vector2IntValue.y);
45+
Vector2 pixelSize = Vector2.zero;
46+
pixelSize.x = (float)screenResolution.vector2IntValue.x / (float)internalResolution.vector2IntValue.x / finalBlitStretch.vector2Value.x;
47+
pixelSize.y = (float)screenResolution.vector2IntValue.y / (float)internalResolution.vector2IntValue.y / finalBlitStretch.vector2Value.y;
48+
EditorGUILayout.LabelField("Pixel Scale: " + pixelSize.x + "×" + pixelSize.y);
49+
50+
serializedObject.ApplyModifiedProperties ();
51+
}
52+
}

Assets/Ocias/PixelArtCamera/Editor/PixelArtCameraEditor.cs.meta

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Ppppperfect pixel art rendering!!
2+
// By Alexander Ocias
3+
// https://ocias.com
4+
5+
using UnityEngine;
6+
using UnityEngine.UI;
7+
8+
[ExecuteInEditMode]
9+
public class PixelArtCamera : MonoBehaviour {
10+
[HideInInspector] public Vector2Int screenResolution;
11+
[HideInInspector] public Vector2Int internalResolution;
12+
[HideInInspector] public Vector2Int upscaledResolution;
13+
//[SerializeField] bool windowboxing = false; //Maybe implement this later?
14+
[HideInInspector] public bool smooth = false;
15+
[HideInInspector] public bool forceSquarePixels = false;
16+
[HideInInspector] public Vector2Int pixels = new Vector2Int(1080/12, 1920/12);
17+
[HideInInspector] public float pixelsPerUnit = 100f;
18+
RenderTexture rt;
19+
20+
float targetAspectRatio;
21+
float currentAspectRatio;
22+
23+
[HideInInspector] public Vector2 finalBlitStretch = Vector2.one;
24+
25+
public Camera mainCamera;
26+
public Canvas mainCanvas;
27+
28+
// Use this for initialization
29+
void Start () {
30+
SetupRenderTexture();
31+
}
32+
33+
void Reset () {
34+
// Try to connect everything automatically on first attach
35+
mainCamera = Camera.main;
36+
GameObject canvasObj = GameObject.Find("Canvas");
37+
if (canvasObj != null) {
38+
mainCanvas = canvasObj.GetComponent<Canvas>();
39+
}
40+
}
41+
42+
public void SetupRenderTexture () {
43+
// Try to connect missing pieces
44+
if (mainCamera == null) {
45+
mainCamera = Camera.main;
46+
}
47+
if (mainCanvas != null) {
48+
GameObject canvasObj = GameObject.Find("Canvas");
49+
if (canvasObj != null) {
50+
mainCanvas = canvasObj.GetComponent<Canvas>();
51+
}
52+
}
53+
// prevent 0-size rendertextures, just in case
54+
if (pixels.x == 0 || pixels.y == 0) {
55+
return;
56+
}
57+
// Configure canvas properly to match camera
58+
if (mainCanvas != null) {
59+
mainCanvas.renderMode = RenderMode.ScreenSpaceCamera;
60+
mainCanvas.worldCamera = mainCamera;
61+
CanvasScaler scaler = mainCanvas.GetComponent<CanvasScaler>();
62+
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
63+
scaler.referenceResolution = new Vector2(pixels.x, pixels.y);
64+
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
65+
scaler.referencePixelsPerUnit = pixelsPerUnit;
66+
}
67+
68+
if (rt != null) {
69+
rt.Release();
70+
}
71+
72+
screenResolution.x = Screen.width;
73+
screenResolution.y = Screen.height;
74+
75+
targetAspectRatio = (float)pixels.x / (float)pixels.y;
76+
currentAspectRatio = (float)Screen.width / (float)Screen.height;
77+
78+
internalResolution.x = pixels.x;
79+
internalResolution.y = pixels.y;
80+
81+
// Figure out best pixel resolution for aspect ratio we're on
82+
if (currentAspectRatio != targetAspectRatio) {
83+
if (currentAspectRatio > targetAspectRatio) {
84+
// Wider screen
85+
internalResolution.x = (int)Mathf.Round((float)pixels.y * currentAspectRatio);
86+
} else {
87+
// Taller screen
88+
internalResolution.y = (int)Mathf.Round((float)pixels.x / currentAspectRatio);
89+
}
90+
}
91+
92+
// Determine scale to keep pixels square
93+
finalBlitStretch = Vector2.one;
94+
if (forceSquarePixels) {
95+
float internalResAspect = (float)internalResolution.x / (float)internalResolution.y;
96+
if (currentAspectRatio != targetAspectRatio) {
97+
if (currentAspectRatio > targetAspectRatio) {
98+
// Wider screen
99+
finalBlitStretch.x = (currentAspectRatio / internalResAspect);
100+
} else {
101+
// Taller screen
102+
finalBlitStretch.y = (internalResAspect / currentAspectRatio);
103+
}
104+
}
105+
}
106+
107+
// Make sure our camera projection fits our resolution
108+
mainCamera.orthographicSize = internalResolution.y / 2f / pixelsPerUnit;
109+
110+
rt = new RenderTexture(internalResolution.x, internalResolution.y, 16, RenderTextureFormat.ARGB32);
111+
112+
if (smooth) {
113+
rt.filterMode = FilterMode.Bilinear;
114+
} else {
115+
rt.filterMode = FilterMode.Point;
116+
}
117+
rt.Create();
118+
}
119+
120+
void OnPreRender() {
121+
if ((float)Screen.width / (float)Screen.height != currentAspectRatio) {
122+
SetupRenderTexture();
123+
}
124+
if (mainCamera != null) {
125+
// Render to our small internal texture
126+
mainCamera.targetTexture = rt;
127+
}
128+
}
129+
void OnPostRender() {
130+
if (mainCamera == null) {
131+
return;
132+
}
133+
// null the targettexture so we can blit to the screen
134+
mainCamera.targetTexture = null;
135+
136+
if (smooth) {
137+
Graphics.Blit(rt, null, finalBlitStretch, (finalBlitStretch - Vector2.one) / -2f);
138+
} else {
139+
// draw to buffer at least as big as the screen
140+
int scaleMultiple = Mathf.CeilToInt((float)Screen.width / (float)internalResolution.x);
141+
upscaledResolution.x = internalResolution.x * scaleMultiple;
142+
upscaledResolution.y = internalResolution.y * scaleMultiple;
143+
RenderTexture largeRt = RenderTexture.GetTemporary(upscaledResolution.x, upscaledResolution.y);
144+
Graphics.Blit(rt, largeRt);
145+
146+
// Scale down to screen
147+
Graphics.Blit(largeRt, null, finalBlitStretch, (finalBlitStretch - Vector2.one) / -2f);
148+
RenderTexture.ReleaseTemporary(largeRt);
149+
}
150+
151+
}
152+
}
153+
154+

Assets/Ocias/PixelArtCamera/PixelArtCamera.cs.meta

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Simplified Diffuse shader. Differences from regular Diffuse one:
2+
// - no Main Color
3+
// - fully supports only 1 directional light. Other lights can affect it, but it will be per-vertex/SH.
4+
5+
Shader "Ocias/Pixel Art Diffuse" {
6+
Properties {
7+
_MainTex ("Base (RGB)", 2D) = "white" {}
8+
}
9+
SubShader {
10+
Tags { "RenderType"="Opaque" }
11+
LOD 150
12+
13+
CGPROGRAM
14+
#pragma surface surf Lambert vertex:vert noforwardadd
15+
16+
sampler2D _MainTex;
17+
18+
struct Input {
19+
float2 uv_MainTex;
20+
};
21+
22+
inline float4 ViewSpacePixelSnap (float4 pos) {
23+
24+
float2 halfScreenRes = _ScreenParams.xy * 0.5f;
25+
26+
// // View space Pixel Snapping
27+
float2 pixelPos = floor(pos * halfScreenRes + 1 / halfScreenRes) / halfScreenRes; // put back in that half pixel offset when you're done
28+
pos.xy = pixelPos;
29+
30+
// Odd resolution handling
31+
// float2 odd = _ScreenParams.xy % 2;
32+
// pos.x += odd.x * 0.5 / halfScreenRes.x;
33+
// pos.y += odd.y * 0.5 / halfScreenRes.y;
34+
35+
return pos;
36+
}
37+
38+
inline float4 WorldSpacePixelSnap (float4 pos) {
39+
40+
//float ppu = _PixelsPerUnit;
41+
float zoom = 1/(unity_CameraProjection[1][1]);
42+
float ppu = _ScreenParams.y / zoom / 2;
43+
44+
pos = mul(unity_ObjectToWorld, pos);
45+
46+
// World space Pixel Snapping
47+
pos = floor(pos * ppu + 1 / ppu) / ppu;
48+
// Adjust to pixel relative to camera position
49+
float3 snappedCameraPosition = floor(_WorldSpaceCameraPos * ppu + 1 / ppu) / ppu;
50+
float3 cameraSubpixelOffset = snappedCameraPosition - _WorldSpaceCameraPos;
51+
pos.x -= cameraSubpixelOffset.x;
52+
pos.y -= cameraSubpixelOffset.y;
53+
// Odd resolution handling
54+
float2 odd = round(_ScreenParams.xy) % 2;
55+
pos.x += odd.x * 0.5 / ppu;
56+
pos.y += odd.y * 0.5 / ppu;
57+
58+
pos = mul(unity_WorldToObject, pos);
59+
60+
return pos;
61+
}
62+
63+
void vert (inout appdata_full v) {
64+
// Offset position based on distance from camera to nearest pixel
65+
// float zoom = 1/(unity_CameraProjection[1][1]);
66+
// float ppu = _ScreenParams.y / zoom / 2;
67+
// float3 snappedCameraPosition = floor(_WorldSpaceCameraPos * ppu + 1 / ppu) / ppu;
68+
// float3 cameraSubpixelOffset = snappedCameraPosition - _WorldSpaceCameraPos;
69+
// v.vertex.x -= cameraSubpixelOffset.x;
70+
// v.vertex.y -= cameraSubpixelOffset.y;
71+
72+
v.vertex = WorldSpacePixelSnap(v.vertex);
73+
}
74+
75+
void surf (Input IN, inout SurfaceOutput o) {
76+
fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
77+
o.Albedo = c.rgb;
78+
o.Alpha = c.a;
79+
}
80+
ENDCG
81+
}
82+
83+
Fallback "Mobile/VertexLit"
84+
}

Assets/Ocias/PixelArtCamera/PixelArtDiffuse.shader.meta

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
%YAML 1.1
2+
%TAG !u! tag:unity3d.com,2011:
3+
--- !u!21 &2100000
4+
Material:
5+
serializedVersion: 6
6+
m_ObjectHideFlags: 0
7+
m_PrefabParentObject: {fileID: 0}
8+
m_PrefabInternal: {fileID: 0}
9+
m_Name: PixelArtFont
10+
m_Shader: {fileID: 4800000, guid: 81779508cd63b403fb890fd1e517d5ab, type: 3}
11+
m_ShaderKeywords:
12+
m_LightmapFlags: 4
13+
m_EnableInstancingVariants: 0
14+
m_DoubleSidedGI: 0
15+
m_CustomRenderQueue: -1
16+
stringTagMap: {}
17+
disabledShaderPasses: []
18+
m_SavedProperties:
19+
serializedVersion: 3
20+
m_TexEnvs:
21+
- _MainTex:
22+
m_Texture: {fileID: 0}
23+
m_Scale: {x: 1, y: 1}
24+
m_Offset: {x: 0, y: 0}
25+
m_Floats:
26+
- _ColorMask: 15
27+
- _Stencil: 0
28+
- _StencilComp: 8
29+
- _StencilOp: 0
30+
- _StencilReadMask: 255
31+
- _StencilWriteMask: 255
32+
m_Colors:
33+
- _Color: {r: 1, g: 1, b: 1, a: 1}

0 commit comments

Comments
 (0)