You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
    }
}

问题原因

  1. 硬件光栅化的GL.LINES渲染逻辑:即使加载像素矩阵,GPU的线条绘制会自动处理边缘,导致要么像素重叠(无MSAA)要么产生抗锯齿像素(有MSAA);
  2. 默认的Hidden/Internal-Colored Shader包含混合和采样逻辑,无法保证像素级的精确填充。

解决方案

要实现完全像素完美的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:相机和项目设置

  1. 关闭MSAA:在Edit > Project Settings > Quality中,将Anti Aliasing设为Disabled;
  2. 正交相机设置:确保相机为Orthographic,调整Size使屏幕像素与世界单位一一对应(例如,屏幕分辨率为1920x1080时,Size设为540);
  3. 关闭纹理过滤:如果使用纹理,将纹理的Filter Mode设为Point (no filter),避免模糊。

效果验证

通过以上设置,绘制的线条将完全匹配Paint.NET手绘的像素完美效果:无重叠像素、无抗锯齿边缘,每个线条像素都精确对应屏幕像素。

内容的提问来源于stack exchange,提问作者aybe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 13:00:17