【Unity】Unity GameObject渐变透明效果实现:通过代码动态修改材质透明度,实现自然流畅的淡入淡出效果

引言

渐变透明效果(Fade In/Out)是 Unity 中常见的视觉特效,广泛应用于 UI 过渡、物体出现/消失、粒子效果、场景切换等场景。通过动态修改材质的 Alpha 通道实现自然流畅的淡入淡出,支持多种材质类型(Standard、URP Lit、Unlit)和渲染模式(Opaque、Transparent)。核心技术包括 Material.SetColorDOTween 动画库和协程控制。

本教程基于 Unity 2021.3+,支持 Built-in、URP、HDRP 渲染管线。时间复杂度 O(1),性能开销极低。提供完整代码实现、优化技巧和跨平台兼容方案。

核心实现原理

1. 材质透明度控制机制

  • Alpha 通道:颜色值的 A 分量(0-1),控制像素透明度
  • 渲染队列:Transparent(3000)模式支持 Alpha 混合
  • Blend ModeSrcAlpha OneMinusSrcAlpha(标准透明混合)
  • ZWrite/ZTest:透明物体通常禁用 Z 写入,支持排序

2. 材质类型适配

材质类型Alpha 属性Shader 关键字
Standard_Color.a_MainColor.aALPHABLEND_ON
URP LitSurfaceInput.BaseColor.aUNIVERSAL
Unlit_BaseColor.aUNLIT
UI/Default_Color.aCanvas 专用

基础代码实现

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

使用示例与集成

完整工作流

  1. 附加组件:选择 GameObject → Add Component → FadeController
  2. 配置参数:设置 Duration、Animation Curve、Target Renderer
  3. 触发动画
   // 代码触发
   GetComponent<FadeController>().FadeIn();

   // UI 按钮
   button.onClick.AddListener(() => fadeController.FadeOut());

   // 事件触发
   onTriggerEnter.AddListener(() => fadeController.FadeIn());
  1. 场景集成:使用 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

此实现提供完整的渐变透明系统,支持多种场景和渲染管线。如需特定特效(如波纹淡出、遮罩动画)或性能优化,请提供更多需求!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注