如何为每个GameObject独立调整同一Shader的Outline width参数?
解决Unity中Outline Shader参数同步问题
问题原因
你遇到的核心问题是多个GameObject共享了同一个材质球实例。在Unity中,材质球是共享资源——当多个物体使用同一个材质球时,修改材质球的任何参数都会同步到所有引用它的物体上,这就是为什么改Cube的Outline Width会影响Sphere和Cylinder。
解决方案
下面提供两种不同场景下的解决方案,你可以根据自己的需求选择:
方案1:为每个物体创建独立材质球(简单直接)
这是最容易实现的方法,适合物体数量不多的场景:
- 选中Cube,找到Inspector面板中Renderer组件的材质球,点击材质球右侧的小箭头,选择Duplicate。这会生成一个原材质球的副本,仅属于当前Cube。
- 对Sphere和Cylinder重复上述操作,让每个物体拥有自己独立的材质球副本。
- 现在修改每个物体材质球的
_Outline参数,只会影响对应的物体,不会再同步到其他物体。
方案2:使用MaterialPropertyBlock实现单材质多实例独立参数(性能友好)
如果你的场景中有大量需要独立Outline参数的物体,创建多个材质球会增加Draw Call,影响性能。这种情况下,推荐使用MaterialPropertyBlock来实现单材质球下每个物体拥有独立参数:
步骤1:修改Shader(可选但推荐)
在原Shader的Properties部分,给_Outline参数加上[PerRendererData]标记,告诉Unity这个参数可以被每个Renderer独立覆盖:
Properties { _Color ("Main Color", Color) = (.5,.5,.5,1) _OutlineColor ("Outline Color", Color) = (0,0,0,1) [PerRendererData] _Outline ("Outline width", Range (0.0, 0.1)) = .005 _MainTex ("Base (RGB)", 2D) = "white" { } }
步骤2:编写C#控制脚本
创建一个名为PerObjectOutline.cs的脚本,挂载到每个需要独立Outline的物体上:
using UnityEngine; [RequireComponent(typeof(Renderer))] public class PerObjectOutline : MonoBehaviour { [Range(0.0f, 0.1f)] public float outlineWidth = 0.005f; private Renderer _renderer; private MaterialPropertyBlock _propertyBlock; void Awake() { _renderer = GetComponent<Renderer>(); _propertyBlock = new MaterialPropertyBlock(); // 初始化时就设置一次参数 UpdateOutlineWidth(); } // 当Inspector参数修改时自动更新 void OnValidate() { if (_renderer != null) UpdateOutlineWidth(); } void UpdateOutlineWidth() { _renderer.GetPropertyBlock(_propertyBlock); _propertyBlock.SetFloat("_Outline", outlineWidth); _renderer.SetPropertyBlock(_propertyBlock); } }
使用方法
- 将脚本分别挂载到Cube、Sphere、Cylinder上。
- 现在你可以在每个物体的Inspector面板中直接修改
Outline Width参数,只会影响当前物体,同时所有物体仍然共享同一个材质球,不会增加额外的Draw Call。
额外提示
原Shader的Outline Pass中使用了顶点颜色来计算偏移(v.vertex.xyz += v.color * _Outline),如果你的物体顶点颜色不是纯白色,可能会导致Outline偏移方向异常。如果需要基于法线的标准Outline,可以把vert函数修改为:
v2f vert(appdata v) { v2f o; // 基于法线方向偏移顶点,实现标准轮廓线 float3 norm = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal)); float2 offset = TransformViewToProjection(norm.xy); o.pos = UnityObjectToClipPos(v.vertex); o.pos.xy += offset * o.pos.z * _Outline; o.color = _OutlineColor; return o; }
内容的提问来源于stack exchange,提问作者Daniel Lip




