【Unity】Unity GameObject渐变透明效果实现:通过代码动态修改材质透明度,实现自然流畅的淡入淡出效果
引言
渐变透明效果(Fade In/Out)是 Unity 中常见的视觉特效,广泛应用于 UI 过渡、物体出现/消失、粒子效果、场景切换等场景。通过动态修改材质的 Alpha 通道实现自然流畅的淡入淡出,支持多种材质类型(Standard、URP Lit、Unlit)和渲染模式(Opaque、Transparent)。核心技术包括 Material.SetColor
、DOTween
动画库和协程控制。
本教程基于 Unity 2021.3+,支持 Built-in、URP、HDRP 渲染管线。时间复杂度 O(1),性能开销极低。提供完整代码实现、优化技巧和跨平台兼容方案。
核心实现原理
1. 材质透明度控制机制
- Alpha 通道:颜色值的 A 分量(0-1),控制像素透明度
- 渲染队列:Transparent(3000)模式支持 Alpha 混合
- Blend Mode:
SrcAlpha OneMinusSrcAlpha
(标准透明混合) - ZWrite/ZTest:透明物体通常禁用 Z 写入,支持排序
2. 材质类型适配
材质类型 | Alpha 属性 | Shader 关键字 |
---|---|---|
Standard | _Color.a 或 _MainColor.a | ALPHABLEND_ON |
URP Lit | SurfaceInput.BaseColor.a | UNIVERSAL |
Unlit | _BaseColor.a | UNLIT |
UI/Default | _Color.a | Canvas 专用 |
基础代码实现
1. 通用 Fade 控制器组件
using UnityEngine;
using System.Collections;
public class FadeController : MonoBehaviour
{
[Header("Fade 设置")]
public float duration = 1f;
public AnimationCurve fadeCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
public bool autoFadeInOnStart = false;
[Header("材质引用")]
public Renderer targetRenderer;
public Material[] overrideMaterials;
[Header("事件")]
public UnityEngine.Events.UnityEvent onFadeComplete;
public UnityEngine.Events.UnityEvent<float> onFadeProgress;
private Material[] originalMaterials;
private Coroutine fadeCoroutine;
private bool isFading;
void Start()
{
InitializeMaterials();
if (autoFadeInOnStart)
FadeIn();
}
void InitializeMaterials()
{
if (targetRenderer == null)
targetRenderer = GetComponent<Renderer>();
if (targetRenderer != null)
{
originalMaterials = targetRenderer.materials;
// 确保材质支持透明
foreach (Material mat in originalMaterials)
{
EnableTransparency(mat);
}
}
}
/// <summary>
/// 启用材质透明渲染
/// </summary>
private void EnableTransparency(Material material)
{
if (material == null) return;
// 检查是否已支持透明
if (material.HasProperty("_Mode"))
{
material.SetFloat("_Mode", 3); // Transparent 模式
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
material.SetInt("_ZWrite", 0);
material.DisableKeyword("_ALPHATEST_ON");
material.EnableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = 3000;
}
else if (material.HasProperty("_Surface"))
{
// URP/HDRP
material.SetFloat("_Surface", 1); // Transparent
material.renderQueue = 3000;
}
}
/// <summary>
/// 淡入效果(从透明到不透明)
/// </summary>
public void FadeIn()
{
if (isFading) StopFade();
fadeCoroutine = StartCoroutine(FadeRoutine(1f));
}
/// <summary>
/// 淡出效果(从不透明到透明)
/// </summary>
public void FadeOut()
{
if (isFading) StopFade();
fadeCoroutine = StartCoroutine(FadeRoutine(0f));
}
/// <summary>
/// 淡入淡出循环
/// </summary>
public void FadeLoop()
{
StartCoroutine(FadeLoopRoutine());
}
private IEnumerator FadeRoutine(float targetAlpha)
{
isFading = true;
float startAlpha = GetCurrentAlpha();
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float progress = elapsed / duration;
float alpha = Mathf.Lerp(startAlpha, targetAlpha, fadeCurve.Evaluate(progress));
SetAlpha(alpha);
onFadeProgress?.Invoke(alpha);
yield return null;
}
// 确保最终值精确
SetAlpha(targetAlpha);
isFading = false;
onFadeComplete?.Invoke();
}
private IEnumerator FadeLoopRoutine()
{
while (true)
{
yield return StartCoroutine(FadeRoutine(0f));
yield return new WaitForSeconds(0.5f);
yield return StartCoroutine(FadeRoutine(1f));
yield return new WaitForSeconds(0.5f);
}
}
private float GetCurrentAlpha()
{
Material mat = GetPrimaryMaterial();
if (mat != null && mat.HasProperty("_Color"))
return mat.color.a;
return 0f;
}
private void SetAlpha(float alpha)
{
Material[] materials = overrideMaterials.Length > 0 ? overrideMaterials : originalMaterials;
foreach (Material mat in materials)
{
if (mat == null) continue;
// 尝试多种 Alpha 属性名
if (mat.HasProperty("_Color"))
mat.color = new Color(mat.color.r, mat.color.g, mat.color.b, alpha);
else if (mat.HasProperty("_BaseColor"))
mat.SetColor("_BaseColor", new Color(mat.GetColor("_BaseColor").r,
mat.GetColor("_BaseColor").g,
mat.GetColor("_BaseColor").b, alpha));
else if (mat.HasProperty("_MainColor"))
mat.SetColor("_MainColor", new Color(mat.GetColor("_MainColor").r,
mat.GetColor("_MainColor").g,
mat.GetColor("_MainColor").b, alpha));
}
}
private Material GetPrimaryMaterial()
{
if (overrideMaterials.Length > 0)
return overrideMaterials[0];
if (targetRenderer != null && originalMaterials.Length > 0)
return originalMaterials[0];
return null;
}
private void StopFade()
{
if (fadeCoroutine != null)
{
StopCoroutine(fadeCoroutine);
isFading = false;
}
}
void OnDestroy()
{
StopFade();
// 恢复原始材质(可选)
if (targetRenderer != null && originalMaterials != null)
{
targetRenderer.materials = originalMaterials;
}
}
}
2. UI Canvas 透明度控制
using UnityEngine;
using UnityEngine.UI;
public class UIFadeController : MonoBehaviour
{
public CanvasGroup canvasGroup;
public float fadeDuration = 1f;
void Start()
{
if (canvasGroup == null)
canvasGroup = GetComponent<CanvasGroup>();
}
public void FadeInUI()
{
StartCoroutine(FadeCanvas(1f));
}
public void FadeOutUI()
{
StartCoroutine(FadeCanvas(0f));
}
private IEnumerator FadeCanvas(float targetAlpha)
{
float startAlpha = canvasGroup.alpha;
float elapsed = 0f;
while (elapsed < fadeDuration)
{
elapsed += Time.deltaTime;
canvasGroup.alpha = Mathf.Lerp(startAlpha, targetAlpha, elapsed / fadeDuration);
yield return null;
}
canvasGroup.alpha = targetAlpha;
}
}
高级动画实现(DOTween)
3. DOTween 流畅动画
首先导入 DOTween(Asset Store 免费):
using DG.Tweening;
using UnityEngine;
public class DOTweenFadeController : MonoBehaviour
{
[Header("DOTween 设置")]
public Renderer targetRenderer;
public Ease fadeEase = Ease.InOutQuad;
public float duration = 1f;
private Material originalMaterial;
void Start()
{
if (targetRenderer == null)
targetRenderer = GetComponent<Renderer>();
originalMaterial = targetRenderer.material;
EnableTransparency(originalMaterial);
}
public void FadeInDOTween()
{
Color targetColor = originalMaterial.color;
targetColor.a = 1f;
originalMaterial.DOFade(1f, duration)
.SetEase(fadeEase)
.OnComplete(() => Debug.Log("Fade In Complete"));
}
public void FadeOutDOTween()
{
originalMaterial.DOFade(0f, duration)
.SetEase(fadeEase)
.OnComplete(() => {
gameObject.SetActive(false); // 可选:淡出后隐藏
});
}
// 链式动画:淡入 → 移动 → 淡出
public void ComplexFadeSequence()
{
Sequence fadeSequence = DOTween.Sequence();
fadeSequence.Append(originalMaterial.DOFade(1f, 0.5f)) // 淡入
.Append(transform.DOMoveY(2f, 1f)) // 移动
.Append(originalMaterial.DOFade(0f, 0.5f)) // 淡出
.SetEase(fadeEase)
.OnComplete(() => Debug.Log("Sequence Complete"));
}
}
场景应用示例
4. 场景切换淡入淡出
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class SceneFadeManager : MonoBehaviour
{
public static SceneFadeManager Instance;
[Header("场景淡入淡出")]
public CanvasGroup fadePanel;
public float fadeDuration = 1f;
public Color fadeColor = Color.black;
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
fadePanel.alpha = 0f;
}
else
{
Destroy(gameObject);
}
}
void Start()
{
StartCoroutine(FadeInScene());
}
public void LoadSceneWithFade(string sceneName)
{
StartCoroutine(LoadSceneRoutine(sceneName));
}
private IEnumerator FadeInScene()
{
yield return StartCoroutine(FadeCanvas(0f, 1f)); // 淡入
}
private IEnumerator LoadSceneRoutine(string sceneName)
{
// 淡出
yield return StartCoroutine(FadeCanvas(1f, 0f));
// 异步加载场景
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
while (!asyncLoad.isDone)
{
yield return null;
}
// 新场景淡入
yield return StartCoroutine(FadeCanvas(0f, 1f));
}
private IEnumerator FadeCanvas(float startAlpha, float endAlpha)
{
float elapsed = 0f;
fadePanel.alpha = startAlpha;
while (elapsed < fadeDuration)
{
elapsed += Time.deltaTime;
fadePanel.alpha = Mathf.Lerp(startAlpha, endAlpha, elapsed / fadeDuration);
yield return null;
}
fadePanel.alpha = endAlpha;
}
}
5. 粒子系统与特效集成
public class ParticleFadeController : MonoBehaviour
{
public ParticleSystem particleSystem;
public Renderer particleRenderer;
public float fadeDuration = 2f;
private Material particleMaterial;
void Start()
{
particleMaterial = particleRenderer.material;
EnableTransparency(particleMaterial);
// 启动粒子并淡入
var main = particleSystem.main;
main.playOnAwake = true;
particleSystem.Play();
StartCoroutine(FadeParticleEmission());
}
IEnumerator FadeParticleEmission()
{
var emission = particleSystem.emission;
emission.enabled = false;
yield return new WaitForSeconds(0.5f);
emission.enabled = true;
// 淡入粒子材质
particleMaterial.DOFade(1f, fadeDuration);
// 淡出粒子(结束后停止)
yield return new WaitForSeconds(3f);
particleMaterial.DOFade(0f, fadeDuration)
.OnComplete(() => {
particleSystem.Stop();
Destroy(gameObject, 1f);
});
}
}
性能优化与最佳实践
6. 材质实例化优化
public class OptimizedFadeController : MonoBehaviour
{
private static readonly int ColorPropertyID = Shader.PropertyToID("_Color");
private MaterialPropertyBlock propertyBlock;
void SetAlphaOptimized(float alpha)
{
if (propertyBlock == null)
propertyBlock = new MaterialPropertyBlock();
propertyBlock.SetFloat(ColorPropertyID, alpha);
targetRenderer.SetPropertyBlock(propertyBlock);
}
// 优势:不创建材质实例,避免内存浪费
}
7. 批量淡入淡出
public static class BatchFadeUtility
{
public static void FadeMultiple(GameObject[] objects, float targetAlpha, float duration)
{
foreach (GameObject obj in objects)
{
FadeController fadeCtrl = obj.GetComponent<FadeController>();
if (fadeCtrl == null)
fadeCtrl = obj.AddComponent<FadeController>();
if (targetAlpha > 0.5f)
fadeCtrl.FadeIn();
else
fadeCtrl.FadeOut();
}
}
// 使用示例
// BatchFadeUtility.FadeMultiple(selectedObjects, 0f, 0.5f);
}
跨渲染管线兼容
8. URP/HDRP 适配
private void EnableTransparencyURP(Material material)
{
if (material == null) return;
// URP Lit Shader
if (material.HasProperty("_Surface"))
{
material.SetFloat("_Surface", 1f); // Transparent
material.SetFloat("_AlphaClip", 0f);
material.SetFloat("_SrcBlend", 5f); // SrcAlpha
material.SetFloat("_DstBlend", 10f); // OneMinusSrcAlpha
material.SetFloat("_ZWrite", 0f);
material.renderQueue = 3000;
}
// 启用 Alpha 混合关键字
material.EnableKeyword("_SURFACE_TYPE_TRANSPARENT");
material.DisableKeyword("_SURFACE_TYPE_OPAQUE");
}
调试与工具
9. 编辑器扩展(可选)
#if UNITY_EDITOR
[UnityEditor.CustomEditor(typeof(FadeController))]
public class FadeControllerEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
FadeController fadeCtrl = (FadeController)target;
if (GUILayout.Button("Test Fade In"))
fadeCtrl.FadeIn();
if (GUILayout.Button("Test Fade Out"))
fadeCtrl.FadeOut();
}
}
#endif
使用示例与集成
完整工作流
- 附加组件:选择 GameObject → Add Component → FadeController
- 配置参数:设置 Duration、Animation Curve、Target Renderer
- 触发动画:
// 代码触发
GetComponent<FadeController>().FadeIn();
// UI 按钮
button.onClick.AddListener(() => fadeController.FadeOut());
// 事件触发
onTriggerEnter.AddListener(() => fadeController.FadeIn());
- 场景集成:使用 SceneFadeManager 管理全局过渡
Inspector 配置示例
FadeController:
├── Duration: 1.0s
├── Fade Curve: Ease In Out
├── Target Renderer: [Mesh Renderer]
├── Auto Fade In: ✓
└── Events:
├── OnFadeComplete → HideObject()
└── OnFadeProgress → UpdateUI()
常见问题解决
问题 | 原因 | 解决方案 |
---|---|---|
物体不透明 | 材质模式错误 | 设置 _Mode = 3 或 _Surface = 1 |
闪烁/排序问题 | Render Queue 冲突 | 统一设置为 3000,调整 ZTest |
性能下降 | 材质实例化过多 | 使用 MaterialPropertyBlock |
URP 不显示 | Shader 关键字缺失 | 启用 _SURFACE_TYPE_TRANSPARENT |
动画卡顿 | Time.deltaTime 不稳定 | 使用 Time.unscaledDeltaTime 或 DOTween |
此实现提供完整的渐变透明系统,支持多种场景和渲染管线。如需特定特效(如波纹淡出、遮罩动画)或性能优化,请提供更多需求!