You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中3D物体平滑淡入淡出实现遇阻,求解决方案

解决3D物体平滑显隐的正确姿势

一、修复自定义Shader,补上阴影与不透明状态切换

你写的Shader只实现了最基础的透明混合,缺了阴影投射、接收逻辑,而且固定用Transparent队列会导致物体始终处于半透明渲染层级——哪怕alpha=1,也会出现渲染排序问题。下面是修改后的完整Shader:

Shader "Custom/FadeableObject"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _ShadowCutoff ("Shadow Cutoff", Range(0,1)) = 0.5
    }
    SubShader
    {
        // 初始用Geometry队列,alpha<1时动态切换为Transparent
        Tags {"Queue"="Geometry" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
        LOD 200

        // 主渲染Pass:支持透明混合+基础光影
        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite On // alpha接近1时开启深度写入,解决半透明排序bug

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _Color;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;
                // 计算基础漫反射,让物体有光影层次感
                fixed3 normal = normalize(i.worldNormal);
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed diff = max(0, dot(normal, lightDir));
                col.rgb *= _LightColor0.rgb * diff + UNITY_LIGHTMODEL_AMBIENT.rgb;

                // alpha接近1时强制开启深度写入,避免半透明排序错误
                if (col.a >= 0.99) {
                    ZWrite On;
                } else {
                    ZWrite Off;
                    // 动态切换渲染队列到Transparent
                    SetRenderQueue(3000);
                }
                return col;
            }
            ENDCG
        }

        // 阴影投射Pass:让物体能投射阴影
        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode" = "ShadowCaster" }

            ZWrite On ZTest LEqual

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                V2F_SHADOW_CASTER;
                float2 uv : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _Color;
            float _ShadowCutoff;

            v2f vert(appdata v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                o.uv = v.uv;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;
                // alpha低于阈值时不投射阴影
                clip(col.a - _ShadowCutoff);
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }

        // 阴影接收Pass:让物体能接收其他物体的阴影
        Pass
        {
            Name "ShadowReceiver"
            Tags { "LightMode" = "ShadowCaster" }

            ZWrite On ZTest LEqual

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                V2F_SHADOW_CASTER;
                float2 uv : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _Color;

            v2f vert(appdata v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                o.uv = v.uv;
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;
                // alpha>0.1时允许接收阴影
                clip(col.a - 0.1);
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

Shader核心修改点:

  • 动态控制ZWrite和渲染队列:alpha接近1时回到不透明渲染逻辑,避免半透明排序bug
  • 新增阴影投射/接收Pass:用Unity内置阴影宏实现,让物体有正常阴影表现
  • 添加基础漫反射计算:替代纯颜色输出,还原物体光影层次感
  • 可通过_ShadowCutoff调整阴影显示的alpha阈值

二、优化淡入淡出代码

你的原有逻辑没问题,但可以优化材质实例的创建开销,同时确保完全显示后回到不透明状态:

private IEnumerator FadeIn(Renderer renderer)
{
    // 用MaterialPropertyBlock避免修改原材质,减少内存占用
    var propBlock = new MaterialPropertyBlock();
    renderer.GetPropertyBlock(propBlock);
    Color color = propBlock.GetColor("_Color");
    color.a = 0;
    propBlock.SetColor("_Color", color);
    renderer.SetPropertyBlock(propBlock);

    float duration = 1.0f;
    float elapsedTime = 0;

    while (elapsedTime < duration)
    {
        elapsedTime += Time.deltaTime;
        color.a = Mathf.Clamp01(elapsedTime / duration);
        propBlock.SetColor("_Color", color);
        renderer.SetPropertyBlock(propBlock);
        yield return null;
    }

    // 确保alpha完全设为1,并切回不透明渲染队列
    color.a = 1;
    propBlock.SetColor("_Color", color);
    renderer.SetPropertyBlock(propBlock);
    renderer.material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Geometry;
}

代码优化点:

  • 使用MaterialPropertyBlock:避免每次修改颜色都生成新Material实例,节省性能
  • 淡入结束后强制切换渲染队列:确保物体完全显示时和普通不透明物体一致

额外注意事项

  • 如果用URP/HDRP,需要基于对应管线的Lit Shader模板修改,上面的代码是内置管线版本
  • 复杂模型淡入时可能出现面穿透,可开启Shader的双面渲染选项,或调整模型法线方向

内容的提问来源于stack exchange,提问作者Shekel Sniffer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 16:39:56