跳到主要内容
腾讯题库 · Q14

原题 Q14 · 当前收录 13 / 49

学习状态 未开始

视距 LOD 材质切换

Shader LOD · shader 对应课程:Shader 与材质

面试官问

PDF 第 32 页

如何在 Shader 中实现“基于视距的 LOD 材质切换”?请说明其与模型 LOD 的协同机制。(常问题,理清逻辑)

学习进度

0/9 已勾

只保存在这台浏览器;勾满 9 项即视为本题完成。

先把题目说成人话

一棵树离相机很近时,叶片的凹凸、透光和风摆都看得见;退到远处以后,它在屏幕里只剩几十个像素,这些细节已经分辨不出来,却仍可能继续花费纹理采样和光照计算。要做的不是让着色程序(Shader)随便找一个距离少算几行,而是让树的轮廓、材质和阴影按照同一把尺一起换档。

这种按可见大小更换细节的办法叫细节层级(Level of Detail,简称 LOD)。你熟悉的游戏引擎(Unity)通常用细节层级组(LODGroup)把每一档的网格和渲染器放在一起。材质也应跟着同一档切换:近处保留能被看见的功能,远处换成真正更便宜的程序,而不是只把最终颜色做得像变简单了。

这是虚构教学故事,不是个人项目经历。

场景:先让问题变得看得见

测试场景只有一棵树和一台来回移动的相机。树有三套已经配好的档位:近景完整树、中景简化树、远景剪影。先不谈代码,只看换景是否连贯。

(右脑)画面:把树想成舞台中央的一件布景。它在画框里很小时,观众只能认出树冠剪影,所以后台只挂一张远景板;它再大一些,换成有主要枝叶的简化树;靠近以后,才搬出叶片凹凸、透光和完整阴影。舞台监督只拿一张表判断树在画框里占多高,并让网格和表面效果同时换。若相机停在两档门口,前后台可以短暂交接来遮住跳变;若相机反复跨线,还要给进门和出门留出不同门槛,免得布景不停搬上搬下。

(左脑)对表:舞台监督对应 Unity 的 LODGroup,共同刻度对应 screenRelativeTransitionHeight,每档布景对应一组 Renderer、Mesh 和共享 Material。这个刻度是物体高度占屏幕高度的比例,没有长度单位;世界距离则有米或项目单位。相同距离下,大树比小树占屏更多,改变相机视野也会改变占屏,所以原题虽写“视距”,通用实现更适合让材质跟随 LODGroup 的占屏阈值。这里先只确认三件事:哪一档正在显示、网格和材质是否同时切换、相机往返时是否跳动。

小明:先形成一个完整猜测

小明在片元 Shader 里直接比较相机距离:近处算全部效果,远处跳过一部分。单棵同尺寸树、固定视野下,它看起来能够工作。

相机视野一改,问题马上出现:模型已经因为占屏变小换到中档,材质却仍按原来的世界距离留在高档。更远处虽然分支没有执行某些采样,那些功能仍可能留在同一个编译程序里;额外 Pass、关键字组合和构建体积也不会因为一次条件为假就自动消失。小明只改变了“这次走哪条分支”,没有建立一套模型与材质共用的换档系统。

小红:把猜测放回现实检查

小红把三档都放进同一个 LODGroup,并给每档的 Renderer 绑定对应网格与共享材质:

  • LOD0:完整网格与完整材质。
  • LOD1:减面网格与中档材质,去掉远处辨认不出的微法线和次要透光。
  • LOD2:低模或远景替身,使用只保留轮廓、主色和必要风摆的材质。

她在 Frame Debugger 中确认,进入 LOD1 后实际使用的是中档材质和对应 Pass,而不只是画面变暗。随后用不同大小的树、不同相机视野和往返镜头复测:模型与材质仍在同一占屏节点换档。

相邻档位可用 Cross Fade 和抖动遮住视觉跳变,但过渡期间两档会短暂同时绘制,因此不是免费。交叉淡出负责“怎样交接”,并不自动负责“怎样防止来回跨线”;若项目确实出现边界抖动,自定义选择器还要让走近和走远使用略有差别的阈值,这才是滞回(Hysteresis)。

我:用检查结果修正模型

我把“材质换档”落在可以证明真的省下工作的地方。近、中、远三份共享材质可以使用同一个 Shader 的不同关键字组合,也可以使用不同 Shader;关键是远档对应的 Shader Variant 里确实不再包含被删除的采样和分支。若要连整个 Pass 一起省掉,最清楚的做法通常是让远档 Shader 根本没有该 Pass。Material.SetShaderPassEnabled 能让某材质在运行时不提交具名 Pass,但它不会把已经编译的程序从构建包里删除。

还要区分两个名字很像的对象:Material Variant 是材质资产之间的父子继承,解决编辑管理;Shader Variant 才是关键字和 Pass 组合编译出的程序。前者不能证明后者更便宜。构建里最终保留哪些程序,则由 shader_feature、项目引用和变体裁剪规则决定,要用构建日志或变体统计核对。

同一档许多树要共享 Material,不能用 renderer.material 给每棵树制造副本。颜色和风相位等实例差异应放进实例数据;改变关键字、纹理或渲染状态会形成合理的批次边界。最后同时检查画面跳变、重叠绘制、顶点、Pass、批次和目标设备 GPU 时间,不能只看到 LOD 名字变化就宣布优化成功。

(右脑)画面修正:影片重放时,后台不再有两张互相矛盾的换景表。树在画框里达到某个大小,完整网格和昂贵表面一起上台;变小到下一档,简化网格与中档表面一起替换;远处只留下稳定的轮廓和主色。交接带只负责遮住搬景,进出门槛只负责防止反复搬景,仓库清单则另行决定哪些昂贵布景根本不随游戏出货。

(左脑)严格对表:先以目标相机和物体 Bounds 设置无量纲的 LODGroup 屏幕高度阈值;再为每档绑定对应 Mesh、Renderer 和共享 Material;用 Shader Variant 或独立 Shader 删除远档不可见功能,并从 Frame Debugger 与编译结果确认 Pass、采样和变体确实改变;需要交叉淡出时用 unity_LODFade.x 等管线提供的数据驱动抖动;需要滞回时由自定义选择逻辑提供不同进入、退出阈值。验收必须覆盖不同物体尺寸、相机视野、分辨率和移动方向,并记录 pop、过渡期额外 Draw、顶点、批次和 GPU 时间。

老师解惑

  • 为什么不只用世界距离:距离没有考虑物体大小和相机视野;占屏比例更接近“观众还能看见多少”。若项目对象尺寸固定,距离带仍可作为较简单的特例。
  • 动态分支与编译变体:动态分支决定这次执行哪段代码;Shader Variant 决定编译出哪套程序。两者都要用目标平台编译与抓帧验证,不能仅凭源码猜成本。
  • 淡出与滞回:淡出解决切换看起来是否平滑;滞回解决边界附近是否反复切换,是两个不同问题。
  • ShaderLab LOD:它帮助引擎按允许的 Shader 级别选择 SubShader,不会自动替每个物体完成基于视距的模型与材质协同。

工具分工

  • Unity LODGroup Inspector:配置每档占屏阈值、Renderer、网格和交叉淡出。
  • C# / CullingGroup:实现设备质量档、自定义距离带或明确的进入、退出阈值。
  • Frame Debugger:确认当前档实际使用的材质、Pass、批次和过渡重叠。
  • Profiler / 平台 GPU 工具:比较目标设备上的顶点、片元、提交和总 GPU 时间。

记忆钩子

先看树在画面里有多大,再让模型和材质一起换;淡出管交接,滞回管防抖,抓帧证明远档真的少做了事。

这是教学场景,不是目标项目的真实捕获或个人经历。

下一步:验证这条因果

同一棵树怎样按占屏或项目距离策略切换 LOD0/1/2,并把滞回、Cross Fade、剔除、三角面和提交分开。

教学边界:它是确定性的网页教学模型,不替代 Unity LODGroup、CullingGroup、Profiler、Frame Debugger 与目标设备固定镜头捕获。

进入教学 Lab:「Unity LODGroup:同一棵树,何时换成哪一档」