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
相关产品推荐
相关产品推荐

