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

Unity草地Shader在Windows编辑器流畅,安卓设备性能劣化求助

Unity安卓端草地Shader卡顿问题排查与优化方案

问题背景

  • 100-150㎡的草地对象在Windows编辑器运行流畅,但安卓设备上严重卡顿
  • Shader仅包含单个Pass,已移除冗余计算但问题未解决
  • 本人Shader开发零基础,需要定位问题或优化方向

原Shader代码

SubShader
{
    Tags { "RenderType"="Opaque" }
    LOD 100

    Pass
    {
        Name "GeometryGrass"

        Blend SrcAlpha OneMinusSrcAlpha

        CGPROGRAM          
        #include "UnityCG.cginc"

        sampler2D _GrassTex;
        float4 _GrassTex_ST;

        sampler2D _NoGrassTex;
        float4 _NoGrassTex_ST;
     
        #include "CustomTessellation.cginc"

        #pragma target 4.0
        #pragma vertex vert
        #pragma geometry geom
        #pragma fragment frag
        #pragma multi_compile_instancing
        #pragma hull hull
        #pragma domain domain
     
        float4 _TopColor;
        float4 _BottomColor;
        float _BendRotationRandom;
        float _RotationMultiplier;

        float _BladeHeight;
        float _BladeHeightRandom;  
        float _BladeWidth;
        float _BladeWidthRandom;
     

        vertexOutput makevertex(float3 pos, float2 cuv, float2 uv){
            vertexOutput o;

            o.vertex = UnityObjectToClipPos(pos);
            o.uv = uv;
            o.cuv = cuv;
            o.normal = 0;
            o.tangent = 0;

            return o;
        }
     
        float rand(float3 myVector)  {
            return frac(sin(dot(myVector.xyz, float3(12.9898, 78.233, 53.539))) * 43758.5453);
        }

        float3x3 AngleAxis3x3(float angle, float3 axis)
        {
            float c, s;
            sincos(angle, s, c);

            float t = 1 - c;
            float x = axis.x;
            float y = axis.y;
            float z = axis.z;

            return float3x3(
            t * x * x + c, t * x * y - s * z, t * x * z + s * y,
            t * x * y + s * z, t * y * y + c, t * y * z - s * x,
            t * x * z - s * y, t * y * z + s * x, t * z * z + c
            );
        }

        [maxvertexcount(3)]
        void geom(triangle vertexInput IN[3], inout TriangleStream<vertexOutput> triStream)
        {
            float3 pos = IN[0].vertex;
            float3 vNormal = IN[0].normal;
            float4 vTangent = IN[0].tangent;
            float3 vBinormal = cross(vNormal, vTangent) * vTangent.w;
            float3 alpha = 1 - tex2Dlod(_NoGrassTex, float4(IN[0].uv, 0, 0));
         

            if(alpha.x > 0.5){
                float3x3 tangentToLocal = float3x3(
                vTangent.x, vBinormal.x, vNormal.x,
                vTangent.y, vBinormal.y, vNormal.y,
                vTangent.z, vBinormal.z, vNormal.z
                );

                float3x3 bendRotationMatrix = AngleAxis3x3(0, float3(0, 0, 0));//AngleAxis3x3(rand(pos.zzx) * _BendRotationRandom * sin(_Time[0] * _RotationMultiplier) * UNITY_PI * 0.5,float3(-1, 0, 0));
                float3x3 facingRotationMatrix = AngleAxis3x3(rand(pos) * UNITY_TWO_PI, float3(0, 0, 1));

                float3x3 transformationMatrix = tangentToLocal;
                transformationMatrix = mul(transformationMatrix, facingRotationMatrix);
                transformationMatrix = mul(transformationMatrix, bendRotationMatrix);

                float height = (rand(pos.zyx) * 2 - 1) * _BladeHeightRandom + _BladeHeight;
                float width = (rand(pos.xzy) * 2 - 1) * _BladeWidthRandom + _BladeWidth;
             
                triStream.Append(makevertex(pos + mul(transformationMatrix, float3(width, 0, 0)),float2(1, 0),IN[0].uv));
                triStream.Append(makevertex(pos + mul(transformationMatrix, float3(-width, 0, 0)), float2(0, 0), IN[0].uv));
                triStream.Append(makevertex(pos + mul(transformationMatrix, float3(0, 0, height)), float2(0.5, 1), IN[0].uv));
                triStream.RestartStrip();
            }
        }
     
        fixed4 frag (vertexOutput i) : SV_Target
        {
            fixed4 col = tex2D(_GrassTex, i.uv);
            col *= lerp(_BottomColor, _TopColor, i.cuv.y);
            return col;
        }
        ENDCG
    }
}

核心问题分析

安卓GPU与PCGPU性能差距极大,你的Shader用到了几何着色器(Geometry Shader)和曲面细分(Tessellation)——这两个特性在移动GPU上开销极高,尤其是几何着色器,很多中低端安卓GPU对其支持不完善,甚至会触发软件模拟,直接导致帧率暴跌。

针对性优化方案

1. 优先替换架构:用GPU实例化替代几何着色器

移动平台尽量避免用几何着色器生成草叶,改用GPU实例化渲染预制草片模型:

  • 制作单个草片低面数模型(1-2个三角面即可)
  • 给草片材质开启GPU实例化,用顶点动画实现弯曲效果
  • 在CPU端生成草片的位置、旋转、缩放随机数据,通过实例化属性传递给Shader

2. 紧急适配:简化当前Shader的移动版本

如果暂时不想改架构,先做移动端适配:

  • 移除曲面细分相关代码:删掉#include "CustomTessellation.cginc"、#pragma hull、#pragma domain
  • 降低Shader目标版本:把#pragma target 4.0改成#pragma target 3.0(移动GPU普遍支持3.0,4.0支持有限)
  • 几何着色器减负:
    • 把tex2Dlod(_NoGrassTex, ...)纹理采样移到顶点着色器,避免几何阶段采样纹理(移动GPU纹理采样在几何阶段开销高)
    • 简化旋转矩阵计算,比如直接手动构造面向旋转矩阵,避免调用AngleAxis3x3函数
    • 减少随机函数调用,复用随机值

3. 渲染管线级优化

  • 开启遮挡剔除(Occlusion Culling),让相机看不到的草片不渲染
  • 用LOD分组,远处的草片用更低面数、更简单的Shader
  • 替换Alpha混合为Alpha测试:把Blend SrcAlpha OneMinusSrcAlpha改成AlphaTest Greater 0.5,混合会大幅增加移动GPU的渲染开销

修改后的简化Shader示例(移动端适配)

SubShader
{
    Tags { "RenderType"="Opaque" }
    LOD 100

    Pass
    {
        Name "GeometryGrass"
        AlphaTest Greater 0.5 // 替换混合为Alpha测试,降低GPU开销

        CGPROGRAM          
        #include "UnityCG.cginc"

        sampler2D _GrassTex;
        float4 _GrassTex_ST;

        sampler2D _NoGrassTex;
        float4 _NoGrassTex_ST;
         
        #pragma target 3.0 // 适配移动GPU的目标版本
        #pragma vertex vert
        #pragma geometry geom
        #pragma fragment frag
        #pragma multi_compile_instancing
     
        float4 _TopColor;
        float4 _BottomColor;
        float _BladeHeight;
        float _BladeHeightRandom;  
        float _BladeWidth;
        float _BladeWidthRandom;
     
        struct vertexOutput {
            float4 vertex : SV_POSITION;
            float2 uv : TEXCOORD0;
            float2 cuv : TEXCOORD1;
            float noGrassAlpha : TEXCOORD2; // 顶点阶段传递草片剔除信息
        };

        struct appdata_full {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
            float4 tangent : TANGENT;
            float2 texcoord : TEXCOORD0;
        };

        vertexOutput vert(appdata_full v) {
            vertexOutput o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            o.uv = v.texcoord;
            // 顶点阶段采样NoGrassTex,避免几何阶段采样
            o.noGrassAlpha = 1 - tex2Dlod(_NoGrassTex, float4(v.texcoord.xy, 0, 0)).x;
            return o;
        }

        vertexOutput makevertex(float3 pos, float2 cuv, float2 uv){
            vertexOutput o;
            o.vertex = UnityObjectToClipPos(pos);
            o.uv = uv;
            o.cuv = cuv;
            return o;
        }
     
        float rand(float3 myVector)  {
            return frac(sin(dot(myVector.xyz, float3(12.9898, 78.233, 53.539))) * 43758.5453);
        }

        [maxvertexcount(3)]
        void geom(triangle vertexOutput IN[3], inout TriangleStream<vertexOutput> triStream)
        {
            if(IN[0].noGrassAlpha > 0.5){
                float3 pos = mul(unity_ObjectToWorld, float4(IN[0].vertex.xyz, 1)).xyz;
                float3 vNormal = IN[0].normal;
                float4 vTangent = IN[0].tangent;
                float3 vBinormal = cross(vNormal, vTangent) * vTangent.w;

                float3x3 tangentToLocal = float3x3(
                    vTangent.x, vBinormal.x, vNormal.x,
                    vTangent.y, vBinormal.y, vNormal.y,
                    vTangent.z, vBinormal.z, vNormal.z
                );

                // 手动构造面向旋转矩阵,简化计算
                float randVal = rand(pos);
                float facingAngle = randVal * UNITY_TWO_PI;
                float sinAngle, cosAngle;
                sincos(facingAngle, sinAngle, cosAngle);
                float3x3 facingRotationMatrix = float3x3(
                    cosAngle, -sinAngle, 0,
                    sinAngle, cosAngle, 0,
                    0, 0, 1
                );

                float3x3 transformationMatrix = mul(tangentToLocal, facingRotationMatrix);

                float height = (rand(pos.zyx) * 2 - 1) * _BladeHeightRandom + _BladeHeight;
                float width = (rand(pos.xzy) * 2 - 1) * _BladeWidthRandom + _BladeWidth;
             
                triStream.Append(makevertex(pos + mul(transformationMatrix, float3(width, 0, 0)), float2(1, 0), IN[0].uv));
                triStream.Append(makevertex(pos + mul(transformationMatrix, float3(-width, 0, 0)), float2(0, 0), IN[0].uv));
                triStream.Append(makevertex(pos + mul(transformationMatrix, float3(0, 0, height)), float2(0.5, 1), IN[0].uv));
                triStream.RestartStrip();
            }
        }
     
        fixed4 frag (vertexOutput i) : SV_Target
        {
            fixed4 col = tex2D(_GrassTex, i.uv);
            col *= lerp(_BottomColor, _TopColor, i.cuv.y);
            return col;
        }
        ENDCG
    }
}

额外建议

  • 用Unity Profiler的GPU Usage工具分析安卓端瓶颈,明确是Draw Call过多还是单帧计算量过大
  • 减少草片总数:远处用纹理模拟草地,近处才用3D草片
  • 尽量把复杂计算移到CPU端,比如预计算旋转矩阵、随机值,再传递给Shader

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 09:55:27