在 Unity 开发中,尤其是场景编辑、AR/VR、地形预览、3D 可视化场景,一个清晰、美观的透视网格能极大提升开发体验和视觉效果。
本篇教程带你从零实现一个URP 通用管线兼容、无限平铺、抗锯齿、距离淡出、主次网格分层的透视网格 Shader,支持自定义颜色、线宽、间隔、淡出距离,直接可用于项目!
最终效果预览
- ✅ 无限平铺透视网格,无面片拉伸变形
- ✅ 主次分层网格(普通线 + 每 N 格高亮线)
- ✅ 硬件抗锯齿,线条平滑无锯齿
- ✅ 距离渐变淡出,不突兀
- ✅ URP 全版本兼容,透明混合,无 Z 冲突
- ✅ 全参数可调节,开箱即用
一、Shader 核心功能说明
本 Shader 基于URP HLSL编写,核心特性:
- 世界空间计算网格:不受模型缩放 / 面片大小影响,真正无限延伸
- fwidth 抗锯齿:解决线条锯齿、闪烁问题
- 距离淡出:远处网格自动透明,优化视觉
- 主次双网格:普通细网格 + 间隔 N 格高亮粗网格
- 透明混合:不遮挡场景物体,支持半透明
二、完整 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. 材质创建
- 在 Project 窗口右键 →
Create → Material,命名为Grid_Mat - 材质的Shader 选择:
Custom/URP_PerspectiveGrid
2. 网格对象设置
- 新建一个Plane / Quad / 自定义大平面(推荐 Plane,默认大小 10x10)
- 将材质拖给平面
- 调整平面位置(一般 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 基于世界坐标渲染,完全贴合地面。
七、扩展优化方向
- 添加 Y 轴网格:支持垂直墙面网格
- 相机距离动态线宽:近粗远细
- 颜色渐变:根据高度 / 距离改色
- 接收阴影:添加 URP 阴影支持
- 分轴颜色:X 轴 / Z 轴使用不同颜色
八、总结
这个 URP 透视网格 Shader 是3D 项目必备工具 Shader,代码轻量、效率高、效果专业,完全满足开发预览、可视化、独立游戏、工业仿真等场景使用。
你可以直接复制代码到项目中使用,也可以根据需求扩展功能!
