Unity中如何正确绘制像素完美的Aliased 2D线条?
在Unity中绘制像素完美的Aliased 2D线条解决方案
问题背景
使用Unity的GL类绘制2D线条时遇到以下问题:
- 关闭相机MSAA时,线条出现大量重叠像素;
- 开启相机MSAA时,线条接近预期但带有额外抗锯齿像素;
- 目标是实现类似Paint.NET手绘的像素完美、无抗锯齿的Aliased线条效果。
原实现代码:
using UnityEngine; public class GLTest : MonoBehaviour { [SerializeField] [HideInInspector] private Material Material; public Vector3Int Point1 = new(0, 0, 0); public Vector3Int Point2 = new(50, 10, 0); private readonly Vector3 Half = Vector3.one * 0.5f; private void OnEnable() { if (Material == null) { Material = new Material(Shader.Find("Hidden/Internal-Colored")) { hideFlags = HideFlags.HideAndDontSave }; } } private void OnRenderObject() { Material.SetPass(0); GL.PushMatrix(); GL.LoadPixelMatrix(); GL.Begin(GL.LINES); GL.Color(Color.red); GL.Vertex(Point1 + Half); GL.Vertex(Point2 + Half); GL.End(); GL.PopMatrix(); } }
问题原因
- 硬件光栅化的
GL.LINES渲染逻辑:即使加载像素矩阵,GPU的线条绘制会自动处理边缘,导致要么像素重叠(无MSAA)要么产生抗锯齿像素(有MSAA); - 默认的
Hidden/Internal-ColoredShader包含混合和采样逻辑,无法保证像素级的精确填充。
解决方案
要实现完全像素完美的Aliased线条,需要放弃硬件线条绘制,改用手动像素点绘制,配合自定义Shader确保像素无模糊、无重叠。
步骤1:创建自定义Pixel Perfect Shader
创建一个无抗锯齿、不透明的Shader,确保每个像素要么完全填充,要么不填充:
Shader "Custom/PixelPerfectLine" { Properties { _Color ("Line Color", Color) = (1,1,1,1) } SubShader { Tags { "Queue"="Geometry" "RenderType"="Opaque" } LOD 100 Cull Off ZWrite On ZTest Always Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 2.0 #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; fixed4 _Color; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { return _Color; } ENDCG } } }
步骤2:使用Bresenham算法手动绘制像素点
用Bresenham直线算法生成线条上的所有像素点,通过GL.POINTS逐个绘制,完全控制每个像素的位置:
using UnityEngine; public class PixelPerfectLine : MonoBehaviour { [SerializeField] private Material lineMaterial; public Vector3Int Point1 = new(0, 0, 0); public Vector3Int Point2 = new(50, 10, 0); public Color lineColor = Color.red; private void OnEnable() { if (lineMaterial == null) { lineMaterial = new Material(Shader.Find("Custom/PixelPerfectLine")) { hideFlags = HideFlags.HideAndDontSave }; } // 强制关闭混合和抗锯齿相关设置 lineMaterial.SetInt("_ZWrite", 1); lineMaterial.DisableKeyword("_ALPHATEST_ON"); lineMaterial.DisableKeyword("_ALPHABLEND_ON"); lineMaterial.DisableKeyword("_ALPHAPREMULTIPLY_ON"); } private void OnRenderObject() { lineMaterial.SetColor("_Color", lineColor); lineMaterial.SetPass(0); GL.PushMatrix(); GL.LoadPixelMatrix(); GL.Begin(GL.POINTS); // 用Bresenham算法生成所有像素点 DrawBresenhamLine(Point1, Point2); GL.End(); GL.PopMatrix(); } private void DrawBresenhamLine(Vector3Int start, Vector3Int end) { int x0 = start.x; int y0 = start.y; int x1 = end.x; int y1 = end.y; int dx = Mathf.Abs(x1 - x0); int dy = Mathf.Abs(y1 - y0); int stepX = x0 < x1 ? 1 : -1; int stepY = y0 < y1 ? 1 : -1; int error = dx - dy; while (true) { // 偏移0.5是因为GL_POINTS的原点在像素中心,确保像素完全填充 GL.Vertex(new Vector3(x0 + 0.5f, y0 + 0.5f, 0)); if (x0 == x1 && y0 == y1) break; int errorDouble = error * 2; if (errorDouble > -dy) { error -= dy; x0 += stepX; } if (errorDouble < dx) { error += dx; y0 += stepY; } } } }
步骤3:相机和项目设置
- 关闭MSAA:在
Edit > Project Settings > Quality中,将Anti Aliasing设为Disabled; - 正交相机设置:确保相机为Orthographic,调整
Size使屏幕像素与世界单位一一对应(例如,屏幕分辨率为1920x1080时,Size设为540); - 关闭纹理过滤:如果使用纹理,将纹理的
Filter Mode设为Point (no filter),避免模糊。
效果验证
通过以上设置,绘制的线条将完全匹配Paint.NET手绘的像素完美效果:无重叠像素、无抗锯齿边缘,每个线条像素都精确对应屏幕像素。
内容的提问来源于stack exchange,提问作者aybe
相关产品推荐
相关产品推荐

