FEATURED · 精选文章

Unity材质系统深度解析:从PBR原理到Shader Graph实战与性能优化

发布时间 / 2026/8/7 13:13:48
来源 / 创域科博编辑部
栏目 / 资讯中心
Unity材质系统深度解析:从PBR原理到Shader Graph实战与性能优化 1. 项目概述从“材质”二字说开去在Unity里摸爬滚打这么多年我越来越觉得“材质”这个看似基础的概念恰恰是区分新手和老鸟的一道分水岭。很多朋友刚接触Unity以为材质就是给模型贴张图调个颜色让模型“有颜色”就行。这没错但只对了一小半。Unity中的材质本质上是一个数据容器和指令集的封装体。它告诉Unity的渲染管线“嘿这个表面应该怎么跟光互动是像镜子一样反射还是像布料一样吸收表面是光滑的还是粗糙的有没有凹凸细节” 你拖到模型上的那个材质球就是这份“说明书”的具象化载体。随着项目复杂度的提升尤其是当你想做写实风格的场景、或者对画面表现有更高要求时材质就从一个“美术资源”变成了一个“技术美术”甚至“图形程序”需要深度介入的领域。PBR基于物理的渲染流程的普及Shader Graph可视化编程工具的成熟让材质的创作门槛降低但深度和可能性却大大增加。一个写实的锈蚀金属、湿润的沥青路面、半透明的玉石背后都是一套复杂的材质参数和Shader逻辑在支撑。理解材质不仅是会用内置的Standard Shader调参数更要理解其背后的渲染原理、数据流向和性能开销。这章我们就抛开那些浮于表面的操作深入聊聊Unity材质的“里子”。2. 材质系统的核心架构与数据流要玩转材质不能只停留在Inspector面板那几个滑杆上得先搞清楚Unity材质系统的底层逻辑。它是一条从数据定义到最终屏幕像素的完整流水线。2.1 材质Material与着色器Shader的关系这是最核心的一对关系但也是最容易被误解的。你可以这样理解着色器Shader是一段程序代码。它严格定义了渲染一个像素需要经过哪些计算步骤顶点变换、光照模型、纹理采样等。它就像一份菜谱详细说明了做一道“红烧肉”需要什么食材输入以及煎、炒、炖、煮的每一步流程算法。材质Material是一个资产文件.mat。它存储了执行特定Shader程序所需要的所有参数值。继续用菜谱比喻材质就是根据“红烧肉”菜谱准备好的一份具体配料表五花肉500克、生抽30毫升、冰糖20克等等。同一个Shader菜谱可以创建出无数个不同的材质不同口味、不同分量的红烧肉。在Unity中你创建一个新材质时必须为其指定一个Shader。这个Shader决定了材质Inspector面板里会出现哪些可调节的属性如_MainTex, _Metallic, _Smoothness。你调节这些属性就是在修改材质这个“参数容器”里的数据而不是在修改Shader代码本身。2.2 渲染管线中的材质角色材质的数据是如何被使用的这涉及到渲染管线。以主流的Universal Render Pipeline (URP) 或 Built-in Render Pipeline为例其简化流程如下准备阶段当Unity要渲染一个使用Material A的模型时它会收集这个模型的所有信息网格数据顶点位置、法线、UV、材质数据引用的Shader、纹理、浮点数参数等、以及当前的光照、摄像机信息。设置渲染状态材质中定义的渲染状态如混合模式Blend Mode、深度写入ZWrite、剔除模式Cull会被提交给图形API如OpenGL, Direct3D告诉GPU如何配置接下来的绘制操作。Shader执行GPU根据材质指定的Shader程序对模型的每个顶点和片元像素进行计算。此时材质中存储的纹理和参数值被作为“常量”或“纹理采样器”传入Shader代码中对应的变量。输出经过Shader计算后得到每个片元的最终颜色输出到帧缓冲区最终显示在屏幕上。注意一个常见的性能陷阱是“材质实例化”。如果场景中有100个相同的石头模型但每个都挂载了独立的材质实例即使参数完全相同Unity可能会将其视为100种不同的材质导致Draw Call增加。正确的做法是让这100个石头共享同一个材质资产。如果需要有细微变化如颜色微调可以考虑使用MaterialPropertyBlock在运行时动态修改部分属性而无需创建新的材质实例。2.3 内置渲染管线与URP/HDRP的材质差异这是Unity近年来最大的变化之一也直接影响着材质的工作流。内置渲染管线 (Built-in)提供经典的“Standard”和“Standard (Specular setup)”着色器。功能全面但庞杂一个Shader通过开关组合支持多种效果导致Shader变体众多编译慢且不易定制。通用渲染管线 (URP)为移动端和高端PC提供良好的可扩展性和性能。它提供了“Lit”着色器相当于PBR标准着色器和“Unlit”等简化着色器。URP的材质系统更模块化鼓励使用Shader Graph创建自定义效果并与管线的后期处理、光照系统深度集成。高清渲染管线 (HDRP)为追求极致视觉效果的PC/主机平台设计。其材质系统更加复杂和强大支持更高级的材质模型如Lit、Layered Lit、次表面散射、光线追踪等。HDRP的材质参数也更多对美术和技术要求更高。选择建议对于新项目除非有特殊遗留原因强烈建议从URP开始。它平衡了效果、性能和易用性社区资源丰富是未来的主流方向。内置管线已不再推荐用于新项目。3. PBR材质理论深度解析与实践参数设置现在几乎所有的3A游戏和高质量渲染都采用PBR流程。理解PBR是创作真实感材质的基石。3.1 PBR的核心思想能量守恒与微表面理论PBR不是某一种具体的Shader而是一套基于物理原理的渲染理论框架。其两大支柱是能量守恒一个表面反射的光能漫反射镜面反射加上它吸收的光能总和不能超过入射光能。这意味着一个非常明亮的镜面反射区域其周围的漫反射部分必然会暗一些。在Unity的Standard/URP Lit Shader中你通过调节Metallic金属度滑块就是在控制能量在漫反射和镜面反射之间的分配。金属度越高漫反射越少颜色来自反射环境镜面反射越强。微表面理论它认为物体的表面是由无数个微小的镜面组成的。这些微表面的法线方向分布决定了宏观表面的粗糙度。光滑表面微表面法线方向基本一致导致入射光被集中反射到同一个方向形成清晰的高光和反射。粗糙表面微表面法线方向杂乱无章导致入射光被散射到各个方向形成模糊的高光和漫反射般的反射。在Shader中这个“法线分布函数”通常用Roughness粗糙度或Smoothness光滑度参数来控制。Unity常用Smoothness值越高表面越光滑。3.2 标准PBR材质贴图通道详解一个完整的PBR材质通常由多张纹理贴图共同定义贴图通道作用常见内容与制作要点Albedo (Base Color)定义表面的基础颜色不含光照信息即不能有阴影或高光。对于非金属它是漫反射颜色对于纯金属它决定了镜面反射的颜色如金为黄色铜为红色。使用sRGB颜色空间。确保在均匀光照下观看时是平整的颜色。可以使用Substance Painter、Quixel Mixer等工具生成。Normal Map通过RGB通道存储每个像素的“假”法线方向在不增加模型面数的情况下模拟表面凹凸细节。通常是切线空间法线贴图呈蓝紫色。这是提升细节性价比最高的手段。注意模型需要有合理的UV和切线信息。Metallic Map灰度图定义每个像素的金属度。白色(1)表示纯金属黑色(0)表示非金属电介质。非黑即白中间灰度慎用。例如一把生锈的刀刀刃部分画白色金属锈蚀部分画黑色非金属。Roughness/Smoothness Map灰度图定义每个像素的粗糙/光滑程度。在Unity中常与环境光遮蔽(AO)共用一张图的RGBA通道。黑色表示光滑如漆面白色表示粗糙如水泥。注意粗糙度不是简单的黑白噪点需要根据材质特性绘制如磨损边缘更光滑更黑。Ambient Occlusion (AO)灰度图模拟环境光在缝隙、凹陷处被遮挡的效果增加体积感和真实度。通常由烘焙或软件生成。在Unity Standard Shader中它常作为Smoothness图的Alpha通道。Height Map (Parallax)灰度图表示高度信息可用于视差映射产生更强的深度错觉。性能开销比法线贴图大用于需要强烈深度感的表面如砖墙缝隙、石板路。实操心得不要过度依赖贴图分辨率。对于中小型物体或移动平台一套2048x2048的贴图可能就足够了关键是贴图内容的精度和合理性。使用纹理阵列Texture Array或虚拟纹理Virtual Texturing来管理大量材质变体是大型场景优化的高级手段。3.3 在Unity中设置与调试PBR材质以URP Lit Shader为例在材质Inspector面板中Surface Options设置渲染模式Opaque不透明, Transparent透明, Cutout镂空等。选择透明模式时需要正确处理渲染顺序和混合。Base Map拖入Albedo贴图。下方可以调节基础颜色进行整体染色。Metallic/Smoothness如果提供了Metallic贴图将模式设为“Metallic Alpha Smoothness”并将贴图拖入。此时Metallic滑杆失效由贴图控制。Smoothness源可以选择“Base Map Alpha”或“Metallic Map Alpha”取决于你的贴图打包方式。可以通过“Smoothness”滑杆整体乘性调节贴图的效果。Normal Map拖入法线贴图并可以调节强度Scale。Occlusion Map如果需要独立的AO贴图在此处拖入。调试技巧在Scene视图中使用Shading Mode下拉菜单切换到不同的渲染模式如“Albedo”、“Normal”、“Metallic”、“Smoothness”、“Ambient Occlusion”可以单独查看每个通道的效果快速定位问题。例如发现某个区域高光异常可以切换到“Metallic”视图检查该区域的金属度贴图是否正确。4. Shader与Shader Graph从代码到可视化创作当内置Shader无法满足需求时我们就需要定制Shader。这有两条主要路径手写Shader代码和使用Shader Graph。4.1 Unity ShaderLab基础与片元着色器思维Unity的Shader使用一种名为ShaderLab的声明式语言包裹着实际的HLSL/Cg代码。一个最简单的Unlit Shader结构如下Shader Custom/MySimpleShader { Properties { _MainTex (Texture, 2D) white {} _Color (Color, Color) (1,1,1,1) } SubShader { Tags { RenderTypeOpaque } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _Color; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv) * _Color; return col; } ENDCG } } }Properties块定义了在材质面板中暴露的参数。SubShader和Pass定义了渲染队列、标签和具体的渲染通道。CGPROGRAM...ENDCG包裹着实际的着色器代码。vert函数 (顶点着色器)负责将模型顶点从本地空间变换到裁剪空间并传递数据如UV给片元着色器。frag函数 (片元/像素着色器)这是材质的核心逻辑所在。它接收从顶点着色器插值过来的数据如UV进行纹理采样、光照计算等最终输出一个颜色值SV_Target。对于美术或技术美术来说不必深究所有矩阵变换关键是建立片元着色器思维即思考“对于屏幕上这个像素点我如何根据它的UV、法线、视角方向等信息计算出一个最终颜色”。Shader Graph正是将这种思维可视化。4.2 Shader Graph全流程实战创建一个积雪材质我们用一个经典案例——在物体表面叠加积雪效果来演示Shader Graph的工作流。目标根据模型顶点法线与世界空间上方向的夹角在朝上的表面如屋顶、地面生成积雪并实现积雪的边缘融合。创建与设置在Project窗口右键 Create Shader Graph URP Lit Shader Graph命名为“SnowAccumulation”。双击打开Shader Graph编辑器。获取表面朝向信息添加一个Normal Vector节点将其Space设置为“World”。这得到世界空间下的顶点法线。添加一个Vector3常量节点设置为 (0, 1, 0)代表世界空间的上方向。添加一个Dot Product节点将法线向量和上方向向量连接起来。点积结果表示两个向量的夹角余弦值。当法线朝上时点积接近1朝下时接近-1水平时接近0。定义积雪区域添加一个Remap节点。我们将点积结果范围[-1, 1]重新映射到[0, 1]并控制积雪出现的阈值。例如将输入最小值设为0.5意味着法线与向上方向夹角小于60度左右的表面才开始积雪输出最小值设为0输出最大值设为1。添加一个Saturate节点连接Remap的输出将值钳制在[0,1]区间。这个结果我们称为“Snow Mask”积雪遮罩白色区域表示完全积雪黑色表示无雪灰色是过渡区。混合积雪与底层材质我们需要两个Sample Texture 2D节点分别采样地面的Albedo贴图和积雪的Albedo贴图。添加一个Lerp(线性插值) 节点。将地面颜色连接到A积雪颜色连接到B将Snow Mask连接到T。这样T为0时输出地面色T为1时输出积雪色中间值平滑过渡。将Lerp的输出连接到主节点的Base Color输入口。增加积雪法线细节为了更真实积雪表面应该有不同于地面的微凹凸。我们可以类似地混合法线贴图。使用两个Sample Texture 2D节点采样地面和积雪的法线贴图。使用Lerp节点以同样的Snow Mask为系数混合两个法线向量。将混合后的法线连接到主节点的Normal输入口。调整积雪材质属性积雪通常更粗糙Smoothness低且是非金属的。我们可以创建两个浮点数属性如“_SnowSmoothness”和“_GroundSmoothness”。用另一个Lerp节点以Snow Mask为系数在两个光滑度值之间插值结果连接到主节点的Smoothness。金属度同理积雪金属度为0地面金属度可能由贴图控制这里也可以用Lerp混合或直接使用地面的金属度贴图因为积雪部分会覆盖地面金属度应为0。添加积雪厚度感视差或高度混合更高级的效果可以引入高度图。使用Parallax Mapping节点根据Snow Mask和一张积雪高度图对UV进行偏移模拟积雪的厚度感。这一步计算稍复杂会引入一定的性能开销可根据目标平台选择使用。暴露参数与保存将阈值、积雪颜色/贴图、光滑度等需要调节的参数右键转换为Property。它们会自动出现在生成的材质球面板上。保存Shader Graph然后在Project中创建一个材质球使用这个Shader。将其赋给一个地形或房屋模型调节参数即可看到动态的积雪效果。避坑指南性能Shader Graph中每个节点都对应着GPU指令。避免在片元着色器中滥用复杂的数学运算如sin,pow和循环。对于全屏效果更应考虑使用后处理。精度在移动平台上注意数据精度。对于颜色和范围在[0,1]的值使用float或half通常足够避免使用fixed在URP中已不推荐。变体Shader Graph会自动处理很多变体如不同渲染管线、不同关键字。但如果你使用了自定义分支Branch节点或基于关键字Keyword的切换要留意变体数量爆炸的问题这会导致构建时间变长和内存占用增加。5. 材质性能优化与常见问题排查材质做得好看很重要但让它在目标平台上跑得流畅更重要。以下是关键的优化点和问题排查思路。5.1 性能优化关键策略减少纹理采样与带宽纹理压缩对所有纹理使用合适的压缩格式如ASTC for mobile, DXT5 for PC。在Import Settings中设置。纹理合图Atlas将多个小纹理合并到一张大图上可以减少Draw Call和纹理切换开销。常用于UI和场景小物件。纹理通道打包将Metallic、Roughness、AO等灰度图打包到一张纹理的R、G、B、A通道中。这是PBR工作流的标配能大幅减少纹理采样次数。Mipmap确保为3D纹理启用Mipmap避免远处像素的摩尔纹和性能浪费。简化Shader复杂度使用合适的Shader能用Mobile/Unlit Shader就不用复杂的Lit Shader。URP中的Simple Lit比Lit更轻量。减少透明物体透明Transparent渲染需要从后向前排序并混合开销远大于不透明Opaque。尽量用Cutout镂空替代半透明或使用Alpha Test。警惕实时反射反射探头Reflection Probe尤其是实时更新的开销巨大。尽量使用烘焙的立方体贴图Cubemap或降低更新频率和分辨率。利用GPU Instancing对于大量使用相同材质和网格的物体如草地、树木、石子在材质上勾选Enable GPU Instancing。这允许GPU一次性绘制多个相同物体极大降低CPU提交Draw Call的负担。注意实例化对材质属性动态修改有限制。管理材质属性块MaterialPropertyBlock如果需要批量修改渲染器的某些属性如颜色、纹理偏移而又不想破坏材质实例共享可以使用MaterialPropertyBlock。它可以在运行时将属性传递给渲染器而无需创建新的材质实例是优化动态批处理的利器。5.2 常见渲染问题与排查清单在开发中你肯定会遇到各种材质显示异常的问题。下面是一个快速排查清单问题现象可能原因排查步骤与解决方案模型显示粉红色Missing Shader材质引用的Shader丢失或当前渲染管线不支持。1. 检查材质球Shader属性是否为“Missing”。2. 如果是URP/HDRP项目确认Shader来自对应的Package或使用了兼容的第三方Shader。3. 重新指定正确的Shader。纹理显示模糊或像素化纹理导入设置不当或Mipmap问题。1. 检查纹理的Max Size和Compression格式是否合适。2. 确认Filter Mode不是“Point”除非需要像素风。3. 对于UI纹理或需要清晰显示的2D精灵关闭Generate Mip Maps。法线贴图效果弱或反向法线贴图类型或空间错误或模型切线信息有问题。1. 在纹理导入设置中将Texture Type设为“Normal map”。2. 检查法线贴图是否是切线空间Tangent Space类型。3. 尝试勾选或取消勾选“Bumpiness”下的“Flip Green Channel”不同软件生成的法线Y轴方向可能相反。4. 检查模型导入设置中的“Normals”选项确保是“Calculate”或“Import”。透明物体渲染顺序错乱透明渲染依赖正确的排序Unity默认按物体中心到摄像机的距离排序。1. 确保透明材质的Render Queue高于不透明物体如设为3000。2. 拆分复杂的透明模型或使用Alpha Test代替Alpha Blend。3. 手动控制渲染顺序使用Renderer.sortingOrder2D或编写Shader控制ZWrite/ZTest。金属/高光区域出现锯齿或闪烁可能是高光计算中的精度问题或纹理压缩导致的。1. 尝试在Shader中使用更高精度的变量如float代替half。2. 检查金属度/光滑度贴图的压缩格式避免使用破坏性强的压缩如ETC1 for RGB。3. 开启抗锯齿MSAA或后处理抗锯齿。在特定角度表面变黑通常是法线信息错误或光照计算问题。1. 在Scene视图用“Normal”模式检查法线贴图是否正确应用。2. 检查模型本身是否包含非法线如双面模型未正确设置。3. 如果是自定义Shader检查顶点到片元的法线变换是否正确通常需要从物体空间变换到世界空间。Draw Call异常高材质实例过多或静态合批/动态合批未生效。1. 在Frame Debugger中查看每个Draw Call的成因。2. 合并使用相同材质的静态物体勾选Static启用Static Batching。3. 减少材质变体使用MaterialPropertyBlock替代创建新材质实例。4. 检查是否因Scale不同导致合批失败尝试统一Scale。5.3 高级调试工具Frame Debugger与RenderDoc当问题比较复杂时需要借助更强大的工具。Unity Frame Debugger (Window Analysis Frame Debugger)这是Unity内置的神器。它可以暂停游戏并逐条查看每一帧的每一个Draw Call。你可以看到每个Draw Call使用的Shader、材质、纹理、渲染状态是什么。对于诊断Draw Call过高、材质切换频繁、渲染状态错误等问题极其有效。RenderDoc一个独立的图形调试器。它可以捕获一帧完整的GPU调用序列让你看到每个渲染Pass的输入输出、纹理内容、缓冲区状态。当遇到Shader逻辑错误、渲染目标错误等底层图形问题时RenderDoc是终极解决方案。你可以看到你的片元着色器实际计算出的每一个中间值精准定位计算错误发生在哪一步。掌握材质是一个从“会用”到“懂原理”再到“能优化”的渐进过程。它连接着美术资产与渲染引擎是视觉表现力的直接承载者。我个人的体会是不要害怕去拆解一个复杂的Shader或打开Shader Graph去连接那些节点每一次尝试去理解“这个效果是怎么来的”都会让你对渲染的认识更深一层。从模仿开始比如去Asset Store下载一些优秀的免费材质资源导入后看看它的贴图组成和Shader参数再用Frame Debugger看看它是怎么被渲染出来的这是最快的学习路径。最后永远记住在追求效果的同时心里要有一根性能的弦尤其是在移动平台一个经过精心优化的材质远比一个华丽但卡顿的材质更有价值。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻