4.2 材质、着色器与光照:画面调色台 本节摘要:材质决定物体"穿什么",光照决定它"在什么光线下"。本节拆解材质面板的核心参数与渲染模式,讲清烘焙光照与实时光照的分工与选型,并用脚本演示运行时改材质的两种正确姿势与一个性能陷阱。 上色的两层结构 上一节末尾说材质是"着色器的实例加参数包",本节把这个说法展开。材质面板上最重要的几组参数:基础色贴图(Albedo,物体的底色与图案)、法线贴图(Normal,用假凹凸骗过光照,不增加几何量)、金属度与光滑度(Metallic 与 Smoothness,决定反射的强度与锐利度)、渲染模式(Surface Type,不透明或透明)。调材质的入门练习很朴素:同一张贴图配不同金属度与光滑度,从"塑料"调到"金属",体会参数与观感的对应关系。
本节摘要:材质决定物体"穿什么",光照决定它"在什么光线下"。本节拆解材质面板的核心参数与渲染模式,讲清烘焙光照与实时光照的分工与选型,并用脚本演示运行时改材质的两种正确姿势与一个性能陷阱。
上一节末尾说材质是"着色器的实例加参数包",本节把这个说法展开。材质面板上最重要的几组参数:基础色贴图(Albedo,物体的底色与图案)、法线贴图(Normal,用假凹凸骗过光照,不增加几何量)、金属度与光滑度(Metallic 与 Smoothness,决定反射的强度与锐利度)、渲染模式(Surface Type,不透明或透明)。调材质的入门练习很朴素:同一张贴图配不同金属度与光滑度,从"塑料"调到"金属",体会参数与观感的对应关系。
光照则是画面的另一半。同一个材质在正午日光下与昏暗走廊里完全是两种观感。Unity 的光照体系按"算一次还是每帧算"分成两大阵营,这是本节的选型重心。
烘焙(Bake)是在编辑阶段把光照结果算好、存进贴图(光照贴图),运行时物体直接取用,几乎不花运行时成本;代价是只适用于静止物体——光源动、物体动,烘焙结果就作废。实时光照每帧计算,动态光源、动态物体都支持,但每盏实时灯都是真金白银的 GPU 开销,尤其带阴影的灯。
工程上的主流做法是混合:场景主体用烘焙光照打底(太阳光、天光、室内固定灯全烘进去),只给少数关键动态物体(角色、可搬运物)保留一盏实时方向光或一组探针。判断某盏灯该不该实时,问一句"它每帧都在变吗"——不变就烘,变才实时。新手最常见的账单事故是往场景里丢七八盏实时点光源还全开阴影,帧率当场腰斩;正确姿势多半是"一盏主光带阴影,其余烘掉或干脆用自发光材质模拟灯泡"。
材质与光源之外,还有一类常被忽略的照明来源:环境光。它不是一盏灯,而是场景整体反射出来的"底色"——晴天白日下阴影面泛蓝、室内傍晚泛暖,这些氛围都靠环境光提供。Unity 里环境光的来源由 Lighting 面板指定,最常用的做法是引用一张天空盒:引擎从天空盒采样,把天空的颜色当作全向的环境光与反射源。这意味着换一张天空盒,全场景的光照氛围跟着换,成本几乎为零——关卡从清晨改成黄昏,常常只需要换天空盒加调烘焙。
反射探针(Reflection Probe)是环境光的局部修正手段:烘焙场景里,金属与光滑表面反射的是全局天空,可能与局部环境不符(室内金属反射出蓝天就很穿帮)。在关键位置放一枚反射探针,让它记录周围环境的六面快照,附近的反射材质就会取用这份局部数据。探针也分烘焙与实时,策略与灯光一致:不动就烘,动才实时。
背景:角色受击时要闪红光,护盾激活时材质要变半透明。两个需求都是"运行时改材质",但写法差异巨大,一个安全一个埋雷。
操作:对照写法如下:
using UnityEngine; public class MaterialFlash : MonoBehaviour { Renderer rend; Material inst; // 实例化出来的私有材质副本 Color baseColor; void Awake() { rend = GetComponent<Renderer>(); // 正确姿势:实例化材质副本,改副本不动原型 inst = rend.material; baseColor = inst.color; } void Update() { // 受击闪红:颜色回弹 inst.color = Color.Lerp(inst.color, baseColor, Time.deltaTime * 8f); if (Input.GetKeyDown(KeyCode.F)) { inst.color = Color.red; } // 护盾:切渲染模式为透明并调透明度 if (Input.GetKeyDown(KeyCode.G)) { inst.SetFloat("_Smoothness", 0.9f); Debug.Log("护盾开启(透明度调整需着色器支持透明模式)"); } } void OnDestroy() { // 实例化的材质要手动释放,否则每次重生泄漏一份 if (inst != null) Destroy(inst); } }
结果:按 F 角色闪红并自动回弹;红色只影响这一个角色,场景里同款材质的其他物体颜色不变。
解读:关键在 rend.material 与 rend.sharedMaterial 的一字之差。访问 material 会自动实例化一份独立副本,改动不外溢——这是运行时改材质的正确入口,但副本是运行时资源,销毁时要手动 Destroy,否则每次角色重生都泄漏一份材质。访问 sharedMaterial 则直接改到磁盘上的材质资产:运行中一改,全场同款材质立刻变色,停止运行后改动还留在资产里(资产改动不受彩排回滚保护)。想要"全场一起变"的场合(比如昼夜变红警戒)用 sharedMaterial 反而正确——关键是分清意图。
变式:把闪红改成发光闪红——把颜色赋给自发光属性(emission)并让颜色乘一个强度系数,再确认材质勾选了自发光开关。自发光是"不依赖光照自己亮"的通道,警报灯、熔岩、能量武器都用它,也是烘焙场景里最廉价的"假灯"。
阴影是光照里最贵的部分,两个纪律能省一半账单。其一,光源的阴影距离要按场景尺度设置,默认值常把远处本来模糊的阴影也算得清清楚楚;其二,动态小物体用阴影探针(Light Probe)接光,不投完整阴影——一排探针记录周围光照信息,动态物体经过时取插值,成本几乎为零,视觉上"光打得对"就够了。烘焙场景里的动态角色,靠"探针接光加一盏不投影的补光"就能融进环境,不必为它开实时阴影。
最后集中列几张贴在调色台边的速查条。画面整体发灰:十有八九是色彩空间没切到线性(项目设置里的 Color Space),伽马空间下光照计算发闷,新项目一律线性。阴影处一片死黑没有细节:环境光缺失,补天空盒或环境光来源,而不是把灯加亮。材质远处闪烁锯齿:法线贴图的 Mipmap 与各向异性过滤设置问题,贴图导入设置里勾上各向异性通常立愈。透明物体排序错乱:透明队列按距离排序无法完全正确,交叠的半透明面片拆分或改用不透明加镂空(Alpha Cutout)是标准解法。这些病单看都不致命,叠加起来就是"说不上哪里不对但就是糙"的画面观感——调色台的功夫,一大半是识别并清掉这类小病。
画面有色有光了,还缺动作。下一节让角色动起来:动画状态机的值班逻辑。