Unity URP 绘制参考网格 Shader 教程(抗锯齿 + 渐变淡出)

在 Unity 开发中,尤其是场景编辑、AR/VR、地形预览、3D 可视化场景,一个清晰、美观的透视网格能极大提升开发体验和视觉效果。

本篇教程带你从零实现一个URP 通用管线兼容、无限平铺、抗锯齿、距离淡出、主次网格分层的透视网格 Shader,支持自定义颜色、线宽、间隔、淡出距离,直接可用于项目!

最终效果预览

  • ✅ 无限平铺透视网格,无面片拉伸变形
  • ✅ 主次分层网格(普通线 + 每 N 格高亮线)
  • ✅ 硬件抗锯齿,线条平滑无锯齿
  • ✅ 距离渐变淡出,不突兀
  • ✅ URP 全版本兼容,透明混合,无 Z 冲突
  • ✅ 全参数可调节,开箱即用

一、Shader 核心功能说明

本 Shader 基于URP HLSL编写,核心特性:

  1. 世界空间计算网格:不受模型缩放 / 面片大小影响,真正无限延伸
  2. fwidth 抗锯齿:解决线条锯齿、闪烁问题
  3. 距离淡出:远处网格自动透明,优化视觉
  4. 主次双网格:普通细网格 + 间隔 N 格高亮粗网格
  5. 透明混合:不遮挡场景物体,支持半透明

二、完整 Shader 代码

直接新建Unlit Shader,替换为以下代码即可使用:

复制代码
Shader "Custom/URP_PerspectiveGrid"
{
    Properties
    {
        _GridColor ("普通网格颜色", Color) = (0.7,0.7,0.7,0.5)
        _LineWidth ("普通线宽", Float) = 0.02

        _MajorGridColor ("每N格线颜色", Color) = (0.9,0.9,0.9,0.7)
        _MajorLineWidth ("每N格线宽", Float) = 0.035
        _MajorInterval ("每N格间隔", Float) = 5.0

        _GridSize ("网格尺寸", Float) = 1.0
        _FadeDist ("淡出距离", Float) = 100.0
    }

    SubShader
    {
        Tags
        {
            "RenderType" = "Transparent"
            "Queue" = "Transparent"
            "RenderPipeline" = "UniversalPipeline"
        }

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            Cull Off

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float3 positionWS  : TEXCOORD0;
            };

            CBUFFER_START(UnityPerMaterial)
                float4 _GridColor;
                float  _LineWidth;

                float4 _MajorGridColor;
                float  _MajorLineWidth;
                float  _MajorInterval;

                float  _GridSize;
                float  _FadeDist;
            CBUFFER_END

            // 顶点着色器:传递世界坐标
            Varyings vert(Attributes input)
            {
                Varyings output;
                output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
                output.positionWS  = TransformObjectToWorld(input.positionOS.xyz);
                return output;
            }

            // 片元着色器:计算网格、抗锯齿、淡出
            half4 frag(Varyings input) : SV_Target
            {
                // 世界空间XZ平面计算网格UV(无限平铺)
                float2 gridUV = input.positionWS.xz / _GridSize;

                // 距离相机淡出
                float dist = distance(input.positionWS, _WorldSpaceCameraPos);
                float fade = 1.0 - saturate(dist / _FadeDist);

                // 抗锯齿:像素导数
                float2 fw = max(fwidth(gridUV), 1e-5);

                // 1. 普通网格线计算
                float2 distToGrid = abs(gridUV - round(gridUV));
                float2 normalAA = fw * 1.5;
                float2 normalMask2 = 1.0 - smoothstep(_LineWidth - normalAA, _LineWidth + normalAA, distToGrid);
                float normalGrid = max(normalMask2.x, normalMask2.y);

                // 2. 主次高亮网格线计算
                float interval = max(_MajorInterval, 1.0);
                float majorWidth = max(_MajorLineWidth, 1e-5);

                float2 majorUV = gridUV / interval;
                float2 majorFW = max(fwidth(majorUV), 1e-5);
                float2 distToMajor = abs(majorUV - round(majorUV));
                float2 majorAA = majorFW * 1.5;
                float2 majorMask2 = 1.0 - smoothstep(majorWidth - majorAA, majorWidth + majorAA, distToMajor);
                float majorGrid = max(majorMask2.x, majorMask2.y);

                // 3. 颜色混合:高亮线覆盖普通线
                float useMajor = saturate(majorGrid);
                half4 gridColor = lerp(_GridColor, _MajorGridColor, useMajor);
                float gridAlpha = max(normalGrid, majorGrid) * fade;

                // 最终颜色
                half4 finalColor = gridColor;
                finalColor.a *= gridAlpha;
                return finalColor;
            }
            ENDHLSL
        }
    }
}

添加了接收阴影效果:

复制代码
Shader "Custom/URP_PerspectiveGrid"
{
    Properties
    {
        _GridColor ("普通网格颜色", Color) = (0.7,0.7,0.7,0.7)
        _LineWidth ("普通线宽", Float) = 0.02

        _MajorGridColor ("每N格线颜色", Color) = (0.9,0.9,0.9,0.9)
        _MajorLineWidth ("每N格线宽", Float) = 0.035
        _MajorInterval ("每N格间隔", Float) = 5.0

        _GridSize ("网格尺寸", Float) = 1.0
        _FadeDist ("淡出距离", Float) = 100.0

        _ShadowStrength ("阴影强度", Range(0,1)) = 0.65
        _ShadowOpacity ("阴影不透明度", Range(0,1)) = 0.75
    }

    SubShader
    {
        Tags
        {
            "RenderType" = "Transparent"
            "Queue" = "Transparent"
            "RenderPipeline" = "UniversalPipeline"
        }

        Pass
        {
            Name "Forward"
            Tags { "LightMode" = "UniversalForward" }

            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            Cull Off

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_SCREEN
            #pragma multi_compile _ _SHADOWS_SOFT

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float3 positionWS  : TEXCOORD0;
            };

            CBUFFER_START(UnityPerMaterial)
                float4 _GridColor;
                float  _LineWidth;

                float4 _MajorGridColor;
                float  _MajorLineWidth;
                float  _MajorInterval;

                float  _GridSize;
                float  _FadeDist;
                float  _ShadowStrength;
                float  _ShadowOpacity;
            CBUFFER_END

            Varyings vert(Attributes input)
            {
                Varyings output;
                output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
                output.positionWS  = TransformObjectToWorld(input.positionOS.xyz);
                return output;
            }

            half4 frag(Varyings input) : SV_Target
            {
                float2 gridUV = input.positionWS.xz / _GridSize;

                float dist = distance(input.positionWS, _WorldSpaceCameraPos);
                float fade = 1.0 - saturate(dist / _FadeDist);

                float2 fw = max(fwidth(gridUV), 1e-5);

                // 普通网格(AA)
                float2 distToGrid = abs(gridUV - round(gridUV));
                float2 normalAA = fw * 1.5;
                float2 normalMask2 = 1.0 - smoothstep(_LineWidth - normalAA, _LineWidth + normalAA, distToGrid);
                float normalGrid = max(normalMask2.x, normalMask2.y);

                // 每N格网格(AA)
                float interval = max(_MajorInterval, 1.0);
                float majorWidth = max(_MajorLineWidth, 1e-5);

                float2 majorUV = gridUV / interval;
                float2 majorFW = max(fwidth(majorUV), 1e-5);
                float2 distToMajor = abs(majorUV - round(majorUV));
                float2 majorAA = majorFW * 1.5;
                float2 majorMask2 = 1.0 - smoothstep(majorWidth - majorAA, majorWidth + majorAA, distToMajor);
                float majorGrid = max(majorMask2.x, majorMask2.y);

                float useMajor = saturate(majorGrid);
                float lineMask = saturate(max(normalGrid, majorGrid));

                // 线条 alpha
                float lineBaseAlpha = lerp(_GridColor.a, _MajorGridColor.a, useMajor);
                float lineAlpha = lineMask * lineBaseAlpha * fade;

                // 阴影 alpha(仅阴影,不带背景)
                float4 shadowCoord = TransformWorldToShadowCoord(input.positionWS);
                Light mainLight = GetMainLight(shadowCoord);
                float shadowDark = (1.0 - mainLight.shadowAttenuation) * saturate(_ShadowStrength);
                float shadowAlpha = shadowDark * _ShadowOpacity * fade;

                // 合成:线条 + 阴影(其余透明)
                float outAlpha = saturate(lineAlpha + shadowAlpha * (1.0 - lineAlpha));
                half3 lineColor = lerp(_GridColor.rgb, _MajorGridColor.rgb, useMajor);

                // 直通 alpha 输出,避免线条被阴影覆盖变灰
                half3 outRgb = (outAlpha > 1e-5) ? (lineColor * lineAlpha / outAlpha) : half3(0,0,0);

                return half4(outRgb, outAlpha);
            }
            ENDHLSL
        }
    }
}


添加刻度:

cs 复制代码
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class GridMeterLabels : MonoBehaviour
{
    [Header("References")]
    public Transform gridRoot;                 // 网格原点(通常是平面中心)
    public TMP_FontAsset fontAsset;            // 可选:指定字体

    [Header("Range")]
    public int maxMeters = 20;                 // 显示 -max ~ +max
    public float meterStep = 1f;               // 每1米一个刻度文本
    public float gridSize = 1f;                // 对应 shader 的 _GridSize(1格=几米)

    [Header("Style")]
    public float textSize = 1.2f;
    public Color xAxisColor = new Color(1f, 0.35f, 0.35f, 1f);
    public Color zAxisColor = new Color(0.35f, 0.65f, 1f, 1f);
    public Vector3 xAxisOffset = new Vector3(0f, 0.01f, -0.18f); // X轴刻度偏移
    public Vector3 zAxisOffset = new Vector3(-0.18f, 0.01f, 0f); // Z轴刻度偏移

    [Header("Horizontal Text Rotation")]
    public Vector3 textEuler = new Vector3(90f, 180f, 0f); // 水平放置;若反向可改Y/Z

    private readonly List<TextMeshPro> labels = new();

    void Start()
    {
        Rebuild();
    }

    [ContextMenu("Rebuild Labels")]
    public void Rebuild()
    {
        if (gridRoot == null) gridRoot = transform;

        ClearAll();

        int count = Mathf.FloorToInt(maxMeters / meterStep);
        for (int i = -count; i <= count; i++)
        {
            float meterValue = i * meterStep;
            if (Mathf.Approximately(meterValue, 0f)) continue; // 原点不显示

            // X轴标签(沿X分布,显示 xm)
            CreateLabel(
                localPos: new Vector3(meterValue / gridSize, 0f, 0f) + xAxisOffset,
                text: $"{meterValue:0.#}m",
                color: xAxisColor,
                name: $"X_{meterValue:0.#}m"
            );

            // Z轴标签(沿Z分布,显示 zm)
            CreateLabel(
                localPos: new Vector3(0f, 0f, meterValue / gridSize) + zAxisOffset,
                text: $"{meterValue:0.#}m",
                color: zAxisColor,
                name: $"Z_{meterValue:0.#}m"
            );
        }
    }

    private void CreateLabel(Vector3 localPos, string text, Color color, string name)
    {
        var go = new GameObject(name);
        go.transform.SetParent(gridRoot, false);
        go.transform.localPosition = localPos;

        // 固定为水平文字(不朝向相机)
        go.transform.localRotation = Quaternion.Euler(textEuler);

        var tmp = go.AddComponent<TextMeshPro>();
        tmp.text = text;
        tmp.fontSize = textSize;
        tmp.color = color;
        tmp.alignment = TextAlignmentOptions.Center;
        tmp.enableWordWrapping = false;
        tmp.raycastTarget = false;
        if (fontAsset != null) tmp.font = fontAsset;

        labels.Add(tmp);
    }

    private void ClearAll()
    {
        if (gridRoot == null) return;

        for (int i = gridRoot.childCount - 1; i >= 0; i--)
        {
            var c = gridRoot.GetChild(i);
            if (c.GetComponent<TextMeshPro>() != null)
            {
#if UNITY_EDITOR
                if (!Application.isPlaying) DestroyImmediate(c.gameObject);
                else Destroy(c.gameObject);
#else
                Destroy(c.gameObject);
#endif
            }
        }

        labels.Clear();
    }
}

添加了灰色背景效果:

复制代码
Shader "Custom/URP_PerspectiveGrid"
{
    Properties
    {
        _BackgroundColor ("背景颜色", Color) = (0.78,0.78,0.78,0.85)

        _GridColor ("普通网格颜色", Color) = (0.7,0.7,0.7,0.55)
        _LineWidth ("普通线宽", Float) = 0.02

        _MajorGridColor ("每N格线颜色", Color) = (0.9,0.9,0.9,0.75)
        _MajorLineWidth ("每N格线宽", Float) = 0.035
        _MajorInterval ("每N格间隔", Float) = 5.0

        _GridSize ("网格尺寸", Float) = 1.0
        _FadeDist ("淡出距离", Float) = 100.0

        _ShadowStrength ("阴影强度", Range(0,1)) = 0.65
    }

    SubShader
    {
        Tags
        {
            "RenderType" = "Transparent"
            "Queue" = "Transparent"
            "RenderPipeline" = "UniversalPipeline"
        }

        Pass
        {
            Name "Forward"
            Tags { "LightMode" = "UniversalForward" }

            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            Cull Off

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_SCREEN
            #pragma multi_compile _ _SHADOWS_SOFT

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float3 positionWS  : TEXCOORD0;
            };

            CBUFFER_START(UnityPerMaterial)
                float4 _BackgroundColor;

                float4 _GridColor;
                float  _LineWidth;

                float4 _MajorGridColor;
                float  _MajorLineWidth;
                float  _MajorInterval;

                float  _GridSize;
                float  _FadeDist;
                float  _ShadowStrength;
            CBUFFER_END

            Varyings vert(Attributes input)
            {
                Varyings output;
                output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
                output.positionWS  = TransformObjectToWorld(input.positionOS.xyz);
                return output;
            }

            half4 frag(Varyings input) : SV_Target
            {
                float2 gridUV = input.positionWS.xz / _GridSize;

                // 距离淡出
                float dist = distance(input.positionWS, _WorldSpaceCameraPos);
                float fade = 1.0 - saturate(dist / _FadeDist);

                // 抗锯齿导数
                float2 fw = max(fwidth(gridUV), 1e-5);

                // 普通网格线(AA)
                float2 distToGrid = abs(gridUV - round(gridUV));
                float2 normalAA = fw * 1.5;
                float2 normalMask2 = 1.0 - smoothstep(_LineWidth - normalAA, _LineWidth + normalAA, distToGrid);
                float normalGrid = max(normalMask2.x, normalMask2.y);

                // 每N格线(AA)
                float interval = max(_MajorInterval, 1.0);
                float majorWidth = max(_MajorLineWidth, 1e-5);

                float2 majorUV = gridUV / interval;
                float2 majorFW = max(fwidth(majorUV), 1e-5);
                float2 distToMajor = abs(majorUV - round(majorUV));
                float2 majorAA = majorFW * 1.5;
                float2 majorMask2 = 1.0 - smoothstep(majorWidth - majorAA, majorWidth + majorAA, distToMajor);
                float majorGrid = max(majorMask2.x, majorMask2.y);

                // 每N格线覆盖普通线
                float useMajor = saturate(majorGrid);
                float lineMask = saturate(max(normalGrid, majorGrid));

                // 主光阴影
                float4 shadowCoord = TransformWorldToShadowCoord(input.positionWS);
                Light mainLight = GetMainLight(shadowCoord);
                float shadowAtten = lerp(1.0, mainLight.shadowAttenuation, saturate(_ShadowStrength));

                // 背景受阴影,线条保持清晰
                half3 bgColor = _BackgroundColor.rgb * shadowAtten;
                half3 lineColor = lerp(_GridColor.rgb, _MajorGridColor.rgb, useMajor);

                half3 finalRgb = lerp(bgColor, lineColor, lineMask);
                float finalAlpha = _BackgroundColor.a * fade;

                return half4(finalRgb, finalAlpha);
            }
            ENDHLSL
        }
    }
}

三、使用方法(超简单)

1. 材质创建

  1. 在 Project 窗口右键 → Create → Material,命名为Grid_Mat
  2. 材质的Shader 选择:Custom/URP_PerspectiveGrid

2. 网格对象设置

  1. 新建一个Plane / Quad / 自定义大平面(推荐 Plane,默认大小 10x10)
  2. 将材质拖给平面
  3. 调整平面位置(一般 Y=0,作为地面网格)

3. 场景适配

无限网格 :平面不需要很大,Shader 会自动无限延伸✅ 无 Z 冲突 :Shader 已关闭 ZWrite,透明混合,不会遮挡物体✅ 适配任何 URP 项目:2D/3D、URP 10+、12+、14 + 全兼容


四、参数面板详解(可调效果)

表格

参数 作用 推荐值
普通网格颜色 基础网格线颜色 + 透明度 (0.7,0.7,0.7,0.5)
普通线宽 基础网格线粗细 0.02
每 N 格线颜色 主刻度线高亮颜色 (0.9,0.9,0.9,0.7)
每 N 格线宽 主刻度线更粗 0.035
每 N 格间隔 多少格显示一条主刻度线 5
网格尺寸 单个网格大小(单位:米) 1
淡出距离 超过该距离网格完全透明 100

五、核心技术原理(进阶理解)

1. 为什么用世界空间 XZ 计算?

普通 UV 网格会受模型缩放、UV 拉伸影响,产生变形;直接使用世界坐标 XZ 平面 → 网格无限延伸、等比例、不受模型影响。

2. fwidth 抗锯齿原理

fwidth() 获取片元导数,自动根据屏幕像素密度计算抗锯齿宽度,解决:

  • 线条锯齿
  • 远处闪烁
  • 透视拉伸模糊

3. 平滑线条 smoothstep

使用smoothstep替代硬边缘裁剪,让网格线边缘平滑过渡,视觉更柔和。

4. 距离淡出

根据相机到网格的距离做线性衰减,远处网格自动消失,避免画面杂乱。


六、常见问题解决方案

1. 网格不显示

  • 检查是否使用URP 管线
  • 检查平面是否正面朝向相机
  • 检查材质颜色 A 通道是否为 0(设为 > 0 即可)

2. 线条闪烁 / 锯齿

  • 增大淡出距离
  • 不要把线宽设得太小
  • 确保使用fwidth抗锯齿(本 Shader 已内置)

3. 遮挡场景物体

  • 本 Shader 已设置Transparent队列,正常不会遮挡
  • 若仍遮挡,检查其他物体的 Shader 队列

4. 想让网格只显示在地面 Y=0

直接将网格平面的 Position Y 设为 0 即可,Shader 基于世界坐标渲染,完全贴合地面。


七、扩展优化方向

  1. 添加 Y 轴网格:支持垂直墙面网格
  2. 相机距离动态线宽:近粗远细
  3. 颜色渐变:根据高度 / 距离改色
  4. 接收阴影:添加 URP 阴影支持
  5. 分轴颜色:X 轴 / Z 轴使用不同颜色

八、总结

这个 URP 透视网格 Shader 是3D 项目必备工具 Shader,代码轻量、效率高、效果专业,完全满足开发预览、可视化、独立游戏、工业仿真等场景使用。

你可以直接复制代码到项目中使用,也可以根据需求扩展功能!

相关推荐
空中海2 小时前
第三篇:Unity进阶阶段(商业项目能力)
unity·游戏引擎
Yuk丶6 小时前
Procedural Dialogue Engine - UE4程序化对话系统的技术实现
c++·游戏引擎·ue4·游戏程序·虚幻
RReality8 小时前
【Unity Shader URP】屏幕空间扭曲后处理(Screen Space Distortion)实战教程
ui·unity·游戏引擎·图形渲染·材质
zcc8580797629 小时前
Unity 事件驱动架构
unity
心之所向,自强不息10 小时前
VSCode + EmmyLua 调试 Unity Lua(最简接入 + 不阻塞运行版)
vscode·unity·lua
空中海10 小时前
第六篇:Unity专项方向
unity·游戏引擎
mxwin11 小时前
Unity Shader 屏幕空间反射 (SSR) 原理解析
jvm·unity·游戏引擎·shader
心前阳光11 小时前
Unity之利用特性给ScriptableObject分组
unity·游戏引擎
mxwin11 小时前
Unity Shader 屏幕空间法线重建 从深度缓冲反推世界法线——原理、踩坑与 URP Shader 实战
unity·游戏引擎·shader