ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Unity场景道路制作全攻略:Terrain笔刷与样条网格生成实战

Unity场景道路制作全攻略:Terrain笔刷与样条网格生成实战 在 Unity 场景里做一条道路看起来是最简单的任务放一个 Plane贴一张沥青纹理就算完事。但实际项目里第一版路面往往不是“能用”而是“能看不能走”——UV 拉伸、车辆颠簸、道路悬空、转弯处拧成麻花。这些问题单独出现任何一个玩家或演示客户一眼就能看出来。这篇文章想直接说明我的核心判断Unity 场景道路制作表面上是美术贴图问题本质上是一个工程问题——道路必须先解决路径贴合、碰撞可走、纹理连续这三件事才算真正完成。至于在 Terrain 上刷笔刷、用样条生成 Mesh还是让美术在 DCC 软件里建模都只是实现路径的差异。读完这篇文章你会得到几条真正可落地的经验第一开局先判断用哪种道路方案而不是直接动手第二一份可以复制到项目的 C# 道路网格生成器脚本第三碰撞体、UV、材质和性能优化中容易踩的坑我会直接用表格列出来。下面的内容按“概念 → 环境 → 三种制作方案 → 碰撞与材质 → 性能与排错”的顺序展开。你可以从头读也可以直接跳到 5.1 节复制脚本。1. Unity 场景道路制作的三种实现路线先给结论在 Unity 中做道路常用的不是一种方案而是三条路线。很多人做坏道路不是因为手艺不好而是因为从第一步就选错了路线。1.1 路线一Terrain 地形笔刷在 Terrain 上通过 Raise/Lower、Set Height 等笔刷先雕刻出路面轮廓再使用 Paint Texture 刷上一层路面贴图。这种方式与地形融为一体过渡自然特别适合开放世界、野外环境、山间土路这类场景。优势是没有网格接缝问题路面和草地、泥土之间过渡柔和。劣势是编辑精度差难以做出笔直的城市道路、干净的马路牙子和精确的交叉口。如果你要做一个工业园区仿真或城市街道Terrain 笔刷并不是最优解。1.2 路线二样条生成道路网格Mesh这是本文重点。定义一个路径点数组按样条插值生成道路中心线再向左右两侧拉伸出一个连续 Mesh。很多商业插件如 EasyRoads3D、Road Architect 的核心思路与此相同只是把编辑过程封装成了 Inspector 工具。这条路线的优势是可控性最强路面宽度、长度、贴图平铺、弯道平滑度都可以用参数控制适合城市道路、高架桥、高速匝道、车辆仿真场地。劣势是需要写代码或买插件对刚接触 Unity 的新手有一定门槛。1.3 路线三DCC 建模 / 贴花在 Blender、3ds Max、Maya 中直接建好路面模型导入 Unity 使用或者用 Decal 贴花在平面上投影一条道路。建模路线适合高质量展示项目但迭代慢改一次路网要重新导一次。贴花路线适合轻量展示、不做物理碰撞的场景比如 UI 底图、小地图示意不适合车辆开上去的交互场景。方案视觉质量编辑成本支持物理适合场景Terrain 笔刷自然过渡好较低自带 TerrainCollider野外、开放世界样条网格可控性强中高需手动添加碰撞体城市、仿真、交通系统DCC 建模最高高需手动添加碰撞体影视级展示、特定关卡贴花一般最低不支持轻量展示、非交互选型的判断标准很简单如果这条路要被车辆反复压过、要被角色行走、要和交通系统交互就不要用贴花如果这条路是笔直的城市道路就不要只依赖 Terrain 笔刷。2. 道路制作涉及的核心概念继续深入之前先解决三个概念问题样条、UV 连续性和碰撞体。它们决定了道路是否能用。2.1 样条为什么道路需要插值而不是直线道路有弯道。如果你只用两个点连线弯道就是折线。样条Spline的作用是在路点之间生成平滑曲线让道路转弯自然。常用的有 Catmull-Rom、Bezier、Hermite 等。在 Unity 里手工做道路不一定非要实现完整样条最简单的插值方式是在每两个路点之间用若干个插值点组成路径再根据插值点位置生成路面顶点。后面 5.1 节示例会采用“按路点分段取 forward 生成网格”的方式它处理直线和简单弯道够用如果要更圆滑的弯道可以改成 Catmull-Rom 采样本质也是在点之间多算几个中间点。2.2 UV 连续性纹理不拉伸的秘密很多新手做长道路时直接把 Plane 拉长结果沥青纹理被拉伸成一条条的。问题不在贴图而在 UV——UV 的纵向分量应该是“沿道路累计长度 / 贴图单位长度”而不是使用平面原本的 0 到 1。正确的做法是当你在代码里生成道路 Mesh 时每经过一个顶点都要用累计距离除以贴图重复间隔来写入 UV 的纵向值。这样无论道路多长纹理都会按实际距离重复平铺而不是被单张拉伸。2.3 碰撞体让道路参与物理Terrain 自带 TerrainCollider所以用 Terrain 刷出来的路可以直接走。但用 Mesh 生成的路面默认只有 MeshRenderer没有碰撞体。车辆开上去会直接穿透角色也会掉下去。给网格道路添加碰撞体有两种常用方式网格数量不大时用 MeshCollider 直接匹配视觉 Mesh长距离道路则建议分段使用 BoxCollider 或 TerrainCollider减少物理计算。6.1 节会给出可直接使用的 C# 脚本。3. 环境准备与前置条件在开始做道路之前先确认项目环境和资源。这里不写死具体版本重点说明通用判断。Unity 版本建议使用 LTS 版本例如 2021.3 或 2022.3。旧版本编辑器布局会有差异但核心概念一致。如果你用 Unity 6编辑器布局变化会更大但下文脚本的核心逻辑仍然可用。渲染管线项目如果是内置渲染管线Built-in道路材质使用 Standard如果是 URP则需要使用 URP/Lit 材质。代码中 Shader 查找逻辑后面会给出兼容写法。单位设置Unity 默认 1 Unit 1 米。道路宽度按真实尺度设置有助于车辆、角色、贴图比例统一。城市单向车道约 3.5 米双向标准车道 7 米左右实际数值按项目需求定。场景准备新建一个 3D Core 模板场景保存到项目 Assets/Scenes 目录。创建一个空物体 RoadContainer用于归拢道路相关子物体。素材准备准备沥青 Base Map、Normal Map可选、路牙/边缘纹理。没有素材时先用纯色材质把路网跑通后再贴细节。环境检查完成后下面从最直观的 Terrain 方案开始。4. 方案一Terrain 地形道路制作步骤4.1 创建并雕刻地形在 Hierarchy 中右键 → 3D Object → Terrain创建一个默认地形。Unity 会自动生成一个 1000×1000 尺寸的地形文件。这个文件默认较大建议在 Terrain Settings 中把分辨率调整到符合需求的档位不要无脑用 4096。创建完成后选中 Terrain使用 Raise/Lower 工具沿路线把地形抬高或降低。对于道路推荐的做法是先让路面区域比周边地形低 5~10 厘米。这样后面刷道路纹理时路面不会因为高度差而“浮空”也不会因为和草地重叠产生 Z-fighting 闪烁。4.2 处理弯道和坡度弯道处不要只在一个点刷。地形笔刷的边缘是软边刷出来的道路边缘会比较模糊。可以在弯道内侧多刷几层再用 Smooth Height 工具把坡度过渡处理一下。如果做的是车辆仿真场景还要注意坡度不能太陡转弯处要有一定超高的意识。Unity 的 Terrain 笔刷可以实时改高度先跑通路线再做细节。4.3 给道路刷材质纹理在地形 Inspector 面板中选择 Paint Texture在 Terrain Layers 中新建一个道路纹理层。如果你已经有沥青贴图直接添加到 Layer 的 Diffuse 和 Normal Map 上。接下来用笔刷沿着雕刻好的路面区域刷即可。这里容易犯错的地方是道路纹理层的 Smoothness 一般要调低沥青路面不应该有高反光。如果项目是雨天场景可以再单独控制全局积水效果而不是在道路材质上硬加高光。4.4 Terrain 道路的适用边界Terrain 方案的优势是过渡自然适合山坡、土路、园区道路。劣势是难以生成“边缘清晰”的城市道路比如路缘石、人行道和车道的分离。我的建议是如果做开放世界或自然景观先用 Terrain 刷道路如果做城市道路、车辆仿真、数字孪生场景直接跳到下面第 5 节的网格生成方案。5. 方案二基于样条点的自动道路网格生成这个方案是文章的核心干货。它的目标是用代码从一个路点数组生成一段连续道路 Mesh并且把 UV 按实际距离处理好。你不需要第三方插件也能跑通。5.1 完整脚本RoadGenerator.cs在 Assets/Scripts 目录下新建 RoadGenerator.cs代码如下。脚本挂在 RoadContainer 下的一个空物体上Inspector 中通过拖拽把路径点顺序放入 Waypoints 列表点击 Generate Road 即可生成路面。// 文件路径Assets/Scripts/RoadGenerator.cs using System.Collections.Generic; using UnityEngine; public class RoadGenerator : MonoBehaviour { [Header(路径点按顺序排列)] public ListTransform waypoints new ListTransform(); [Header(路面参数)] public float roadWidth 6f; public float textureLength 4f; // 每多少米重复一次纹理 public Material roadMaterial; [ContextMenu(Generate Road)] public void GenerateRoad() { if (waypoints null || waypoints.Count 2) { Debug.LogWarning(至少需要两个路点才能生成道路); return; } ListVector3 vertices new ListVector3(); Listint triangles new Listint(); ListVector2 uvs new ListVector2(); float totalLength 0f; Vector3 lastPos waypoints[0].position; for (int i 0; i waypoints.Count; i) { Vector3 p waypoints[i].position; // 计算当前点的前进方向 Vector3 forward; if (i waypoints.Count - 1) forward waypoints[i 1].position - p; else forward p - waypoints[i - 1].position; forward.y 0f; forward.Normalize(); Vector3 right Vector3.Cross(Vector3.up, forward) * roadWidth * 0.5f; if (i 0) totalLength Vector3.Distance(p, lastPos); lastPos p; // 左侧和右侧顶点 vertices.Add(p - right); vertices.Add(p right); // UV横向按实际宽度重复纵向按累计长度重复 float uRight roadWidth / textureLength; float vAt totalLength / textureLength; uvs.Add(new Vector2(0f, vAt)); uvs.Add(new Vector2(uRight, vAt)); // 生成两段三角形形成当前路段的面 if (i waypoints.Count - 1) { int baseIndex i * 2; triangles.Add(baseIndex); triangles.Add(baseIndex 1); triangles.Add(baseIndex 2); triangles.Add(baseIndex 1); triangles.Add(baseIndex 3); triangles.Add(baseIndex 2); } } Mesh mesh new Mesh(); mesh.name RoadMesh; mesh.SetVertices(vertices); mesh.SetTriangles(triangles, 0); mesh.SetUVs(0, uvs); mesh.RecalculateNormals(); mesh.RecalculateBounds(); MeshFilter filter GetComponentMeshFilter(); if (filter null) filter gameObject.AddComponentMeshFilter(); filter.sharedMesh mesh; MeshRenderer renderer GetComponentMeshRenderer(); if (renderer null) renderer gameObject.AddComponentMeshRenderer(); renderer.sharedMaterial roadMaterial; } }脚本中你不必在 Unity 里输入中文字符注释只是解释逻辑。5.2 脚本关键逻辑讲解这段代码看起来不长但几个点要注意forward 计算中间点的 forward 取“下一点减当前点”最后一个点取“当前点减上一点”。如果路点高度不一致你需要把 forward.y 归零否则道路会随着路点上下倾斜网格会变得扭曲。这是新手最容易踩的第一个坑。right 向量用Vector3.Cross(Vector3.up, forward)得到垂直于前进方向的地面横向向量再乘半宽得到左右顶点。这就是道路宽度的来源。UV 计算这里的技巧在于totalLength。由于是逐点累加纵向 UV 会随实际道路长度增长纹理不会拉伸。横向 UV 用roadWidth / textureLength让路面宽度也按实际米数参与平铺。法线生成 Mesh 后调用 RecalculateNormalsUnity 会根据三角面自动计算法线。如果不调用路面会呈现诡异的明暗变化看起来像被压扁的棱镜。5.3 如何在 Unity 中运行操作步骤如下在场景中创建一个空物体命名 Road_I。给 Road_I 挂载 RoadGenerator 脚本。在场景中创建几个空物体作为路点例如 RoadPoint_A、RoadPoint_B、RoadPoint_C把它们放在预期坐标上。把路点按顺序拖入 RoadGenerator 的 Waypoints 列表。在项目里创建一个材质并拖入 Road Material 槽位。在 Inspector 上点击 RoadGenerator 脚本右上角的 ContextMenu 菜单Generate Road。执行后场景中会生成一个带 MeshRenderer 但暂时没有碰撞体的道路网格。此时如果直接运行游戏角色会从路面穿下去下一步就解决这个问题。5.4 让道路贴合起伏地形上面的代码默认路点在同一高度适合平地和缓坡。如果想在起伏地形上生成道路不能在脚本里直接使用路点坐标而应该先对地形做一次射线检测取实际碰撞点的 y 坐标。简单的思路是在每个路点上方发一条射线向下打向地形用返回的 hit.point 作为真正的路面采样点。然后把 RoadGenerator 中的 waypoint 坐标替换成采样后的坐标再进行生成。这一步是道路不与地形相交的关键。核心代码片段如下你需要放在正式生成前执行// 示意用射线把路点贴合到地形表面 // 需在生成道路前对每个 waypoint 执行 Vector3 p waypoint.position; if (Physics.Raycast(p Vector3.up * 20f, Vector3.down, out RaycastHit hit, 50f, terrainLayerMask)) { p.y hit.point.y; }注意 terrainLayerMask 需要是你地形所在的 Layer。如果只用 Terrain也可以直接用 Terrain.activeTerrain.SampleHeight(p) 来采样地形高度这样更快但要保证路点 xz 在 Terrain 范围内。5.5 扩展方向现在这个生成器是“直线连接”版本弯道是折线。要获得平滑弯道有两种低成本方案在每两个路点之间再插值 3~5 个点插值算法用 Catmull-Rom 或 Bezier再走同样的 Mesh 生成逻辑。用 AnimationCurve 控制弯道宽度渐变形成入口宽、出口窄的渠化效果。对于交叉路口可以在路点数量上做文章十字路口让四条道路各自生成到路口中心再手动在中心补一个路面 Patch。如果项目对交叉口要求高可以考虑基于 Polygon 拼口的算法不过那是一个更深的话题下面单独说明。6. 道路碰撞体配置让角色和车辆真正压在路上6.1 为什么 Terrain 道路不需要额外处理用 Terrain 刷出来的道路天生自带 TerrainCollider。角色走在地形上不会掉下去所以不需要额外处理。用网格生成的道路则需要手动添加 Collider。6.2 网格道路的行车碰撞体最简单的方法是在生成道路之后给道路物体添加一个 MeshCollider并指定为刚生成的 Mesh。下面是可直接挂载的辅助脚本// 文件路径Assets/Scripts/AddRoadCollider.cs using UnityEngine; [RequireComponent(typeof(MeshCollider))] public class AddRoadCollider : MonoBehaviour { public float staticFriction 0.8f; public float dynamicFriction 0.6f; [ContextMenu(Build Road Collider)] public void BuildRoadCollider() { MeshFilter filter GetComponentMeshFilter(); if (filter null || filter.sharedMesh null) { Debug.LogWarning(当前物体没有 MeshFilter 或 Mesh); return; } MeshCollider mc GetComponentMeshCollider(); if (mc null) mc gameObject.AddComponentMeshCollider(); mc.sharedMesh filter.sharedMesh; mc.convex false; mc.isTrigger false; PhysicMaterial roadPhysic new PhysicMaterial(RoadPhysic); roadPhysic.dynamicFriction dynamicFriction; roadPhysic.staticFriction staticFriction; roadPhysic.bounciness 0f; roadPhysic.frictionCombine PhysicMaterialCombine.Average; mc.material roadPhysic; } }同样在 Inspector 中点击 Build Road Collider 即可完成配置。这段代码创建的 MeshCollider 会与渲染 Mesh 完全贴合所以车辆开上去不会穿模也不会碰到“看不见的碰撞体”。6.3 何时不要用 MeshColliderMeshCollider 的缺点是当道路 Mesh 很大、很长时物理引擎的碰撞检测开销会明显增加。对于绵延几公里的道路我更推荐以下做法将大 Mesh 切分成若干块每块独立生成 MeshCollider。用 BoxCollider 进行近似模拟。视觉上车辆压在一个看不见的台阶或平面上只要分段足够细玩家感知不到。如果道路只是地形的一部分用 TerrainCollider 统一接收所有碰撞道路 Mesh 只做渲染。这个选择背后的原理是物理计算的最小单位是 Collider 的原始成本MeshCollider 每帧要处理三角形数量BoxCollider 只有简单的几何形状。把一条 1 公里长的道路拆成每 10 米一个 Box 的成本远低于一份巨大 MeshCollider。6.4 车辆和角色的配置提醒如果道路给车辆用车辆需要有 Rigidbody 和合适的 Collider。道路的 PhysicMaterial 的摩擦系数会影响车辆抓地力不要让道路材质摩擦系数设成 0否则车辆会像在冰面上打滑。如果角色用 CharacterController 或 NavMeshAgent道路本身提供碰撞足够角色可以正常行走。但如果你让角色直接走到一个 Mesh 路面与 Terrain 的接缝处两者高度差必须控制在极小的范围比如 0.01~0.05 单位否则会有明显台阶感和抖动。7. 道路材质、UV 与渲染细节7.1 创建道路材质在项目窗口右键 → Create → Material命名为 Road_Material。把 Shader 选为 Standard内置管线或 URP/Lit。主贴图选择沥青纹理Normal Map 选择法线贴图Smoothness 一般设 0.1 到 0.3避免路面过亮。如果你希望用代码在运行时动态创建材质可以参考下面的脚本。这个脚本会优先查找 URP/Lit找不到再退回 Standard// 文件路径Assets/Scripts/RoadMaterialSetter.cs using UnityEngine; public class RoadMaterialSetter : MonoBehaviour { public Texture roadDiffuse; public Texture roadNormal; public float smoothness 0.2f; [ContextMenu(Apply Road Material)] public void ApplyRoadMaterial() { Shader shader Shader.Find(Universal Render Pipeline/Lit); if (shader null) shader Shader.Find(Standard); if (shader null) { Debug.LogWarning(当前渲染管线找不到可用 Shader); return; } Material mat new Material(shader); if (mat.HasProperty(_BaseMap)) mat.SetTexture(_BaseMap, roadDiffuse); if (mat.HasProperty(_MainTex)) mat.SetTexture(_MainTex, roadDiffuse); if (roadNormal ! null) { if (mat.HasProperty(_BumpMap)) mat.SetTexture(_BumpMap, roadNormal); if (mat.HasProperty(_NormalMap)) mat.SetTexture(_NormalMap, roadNormal); } if (mat.HasProperty(_Smoothness)) mat.SetFloat(_Smoothness, smoothness); MeshRenderer renderer GetComponentMeshRenderer(); if (renderer ! null) renderer.sharedMaterial mat; } }这段代码演示的是运行时创建材质实际项目建议在材质资产上直接调整。7.2 纹理拉伸与平铺的误区这里的核心问题还是 UV。如果道路生成脚本已经按 distance 写入 UV那么你在材质检查器中把 Base Map 的 Tiling 设为 1 即可因为 UV 已经负责平铺。如果美术直接在材质里调整 Tiling会和脚本的 UV 产生叠加导致纹理过密或过疏这个要提前和团队沟通清楚。另外普通沥青贴图是一张方形图。当 roadWidth 和 textureLength 比值接近 1 时路面颗粒密度最自然。如果你的沥青贴图是 2:1 的长条图需要调整 textureLength 参数而不是去改 Tiling Y。7.3 道路边缘与地形融合城市道路边缘可以接受明显路牙野外土路则希望道路材质和草地材质自然过渡。常用做法有顶点色渐变在道路边缘的顶点上写入 Alpha 或颜色再在材质 Shader 中利用顶点色混合草地图。这是比较标准的做法。贴花使用 Decal Projector 在道路边缘投射一层土色或草色贴花只做视觉不做物理。半透明边缘面片在道路两侧放一个带渐变 Alpha 的宽面片适合远景不适合车辆反复压过。上述方法根据项目精度选择没有绝对优劣。8. 道路与地形的融合、交叉口处理8.1 路面下沉法前面 4.1 节已经提过Terrain 道路要先把路面区域下沉 5~10 厘米再刷纹理这样道路不会浮起来。对于网格道路应当确认路面 Mesh 的 Y 坐标与 Terrain 表面高度对齐或者略低于周边地形。如果道路高于地形车辆在路边会撞上台阶如果低于太多角色走到边界会看到断崖。这里真正容易踩坑的地方是你调整了路点的 Y 坐标但忘记在生成器中重新生成 Mesh。很多“道路看起来没问题运行起来却穿模”的案例都是因为 Mesh 和地形高度没有同步更新。8.2 交叉口处理道路交叉口是 Unity 场景道路制作中最容易露馅的地方。两条直线道路直接交会交接处会出现三角形空洞或重叠面。基础解决办法两条道路都在路口处提前结束不互相穿过。在路口中心单独创建一个矩形或正方形平面用同样的沥青材质覆盖。在车辆行驶实验时重点观察转弯处是否有模型穿插。如果有手动调整路口的顶点。更进阶的方案是使用道路插件例如 Road Architect它在交叉口处理上内置了拼口算法。但插件也有体积和学习成本开发阶段建议先用基础办法跑通。8.3 路网批量生成当道路数量很多时不要每一条路手动整理。可以写一个 RoadNetworkBuilder 脚本读取一个路点配置文件循环调用 RoadGenerator 生成每条道路。路点数据可以来自 ScriptableObject、JSON 或 Excel 导出的 CSV。这样策划和美术调整路网时不需要在场景里手动拖动几十个点也可以更方便地对道路数据进行版本管理。如果你的项目需要频繁调整道路线型数据驱动是唯一能保持团队协作效率的做法。在开发初期就建立路点数据格式比后期重构成本低得多。9. 性能优化与工程化建议9.1 合并道路网格一个 RoadGenerator 生成一条道路 Mesh多个道路物体就会产生多个 Draw Call。做城市级场景时可以把若干相邻道路合并成一个 Mesh或者使用 Mesh Combine 工具在编辑器里批量合并。合并前注意不同材质需要分配到 SubMesh否则会出现材质丢失。对于场景中的静态路面可以勾选 Static让 Unity 的静态合批与遮挡剔除发挥作用。9.2 碰撞体性能预算物理性能不是单纯看 Collider 数量还要看计算复杂度。优先保证行车路线的主要路面使用轻量碰撞。不参与交互的远景路面不要加碰撞体。车辆和角色物理更新频率不要因为大量 BoxCollider 而无谓升高。如果你的场景里同时跑几十辆车道路碰撞对物理引擎的影响会被放大。建议重新审视哪些道路需要碰撞、哪些只是背景。这一点在数字孪生、仿真项目里尤其重要。9.3 场景组织与命名道路物体建议按区块组织场景中建立 RoadContainer → CityBlock_01 → Road_A每个生成的道路物体命名为 Road_Block_Lane_01 这类可读名称。不要出现“Road (12)”这种开发到后期自己都分不清的命名。如果版本控制后出现大量 prefab 冲突道路生成代码化反而能降低合并成本。9.4 Navigation Mesh 与寻路道路制作完成后如果需要 NPC 沿路行走需要烘焙 NavMesh。选择 Window → AI → Navigation在 Navigation 窗口中设置 Agent 半径与高度选择道路所在区域点击 Bake。烘焙完成后用 NavMeshAgent 挂到角色上就可以沿道路行走。注意一个细节如果你的道路是 MeshColliderNavMesh 烘焙会读取可走路面如果道路用 TerrainTerrain 也会被识别。但如果在道路上放了大量装饰物它们会阻挡寻路烘焙结果可能与视觉不符。9.5 阴影、LOD 与抗锯齿长道路上最容易出现的是阴影闪烁Shadow Acne。避免方式有三种抬升路面与地形的间距、使用烘焙 Lightmap 而不是实时阴影、调高 Shadow Near Plane。在实时灯光下做长距离道路更推荐使用 Lightmap 烘焙或混合实时烘焙。LOD 方面道路主体不太需要 LOD因为它是长条形且贴地。但是路面上的细节物体、路牙、红绿灯需要 LOD。道路网格的顶点数要注意纹理连续很重要但 Mesh 顶点密度并不需要特别高平滑弯道时插值点控制在 3~5 个即可过多会白白增加三角形数量。10. 常见问题与排查参考下面是道路制作中最常见的现象和排查顺序建议收藏备用。问题现象可能原因排查方式解决方案道路悬空路点没有贴合地形检查 Mesh 顶点 Y 坐标与地形高度用射线或 SampleHeight 贴合地形路面纹理严重拉伸UV 未按路径距离写入查看 Mesh 的 UV 数据在生成脚本中用累计距离计算 UV弯道处路面扭曲forward 向量未归一化或含 Y 分量检查 forward.y 是否归零生成前将 forward.y 设为 0 并 Normalize车辆穿透路面只有渲染没有 Collider查看 GameObject 上是否有 MeshCollider运行 AddRoadCollider 脚本路面与地形闪烁两个面完全重叠检查 Z-fighting路面下沉 0.01~0.05 或设置 Lightmap道路接缝处有台阶相邻道路高度不一致检查相邻路点 Y 坐标统一道路节点高度留极小重叠运行卡顿道路 Mesh 过细、Collider 过多查看 Profiler 中 Physics 耗时合并道路、减少复杂 Collider夜晚路面反光刺眼Smoothness 过高查看材质参数路面 Smoothness 调低到 0.1~0.3这里的很多问题根源都在生成器脚本的前 50 行。把 UV 和 forward 处理好后面会少走很多弯路。11. 总结回到开头那个判断Unity 场景道路制作表面上是铺贴图本质上要先解决路径贴合、碰撞可走、纹理连续。这也是为什么很多人手动拉了一个平面却总觉得不对——因为那只是“路面”不是“道路”。如果你准备开始做第一个 Unity 场景建议先按第 4 节用 Terrain 刷一条测试路跑通基础流程然后按第 5 节把 RoadGenerator 脚本复制进项目通过路点生成一小段网格道路再按第 6 节把碰撞体和材质接上。前后花不了多少时间但对道路的认知会完全不一样。后续可以继续深入的方向包括Catmull-Rom 平滑样条、交叉路口自动拼接、道路与交通流系统联动、基于高程数据的真实道路导入。每一步都能增强“道路制作”的工程化能力而不只是停留在视觉效果。如果你在实践过程中卡在某个具体问题上回到第 10 节排查表先看你的顶点、UV、Collider 这三个维度。问题通常就在其中之一。
返回列表