1、为什么批处理会影响顶点动画
Unity中默认有静态批处理和动态批处理,批处理的主要作用是合并多个对象,将他们作为一个DrawCall进行处理,之所以批处理会对顶点动画带来影响,是因为不同的对象会拥有不同的变换矩阵(位置、旋转、缩放),而批处理后他们的变换矩阵会进行统一处理
举例:
物体A: 位于世界空间位置(0, 0, 0),无旋转。
物体B: 位于世界空间位置(5, 0, 0),无旋转。
他们是两个独立的对象,拥有不同的变换矩阵
- 不进行批处理时:每个对象的变换矩阵会单独传递给Shader,顶点的模型空间位置会根据各自的变换进行正确计算
- 进行批处理时:启用批处理后,Unity会将对象A和对象B合并为一个Draw Call,并使用一个统一的变换矩阵,比如在静态批处理中,Unity会将对象A和对象B的顶点合并为一个网格,并使用统一的变换进行渲染
批处理后顶点位置是混合的,Shader中无法区分不同对象的模型空间位置,可能带来的问题有:
- 顶点动画失效:假设你希望顶点在模型空间的x方向上进行sin波动动画。如果对象A和对象B的模型空间位置被混合,波动动画会变得不可预测
- 变换混淆:对象A和对象B有不同的变换矩阵。如果批处理后使用统一的变换矩阵,Shader无法区分每个顶点属于哪个对象,导致所有顶点的动画效果混淆。
总结
批处理会让对象失去独立性,相当于将多个对象之间独立的模型空间坐标系合并为一个坐标系
从而影响顶点的相对位置和变换矩阵等信息,导致顶点动画结果异常,因此我们通过渲染标签来关闭批处理
关闭批处理带来的问题
关闭批处理带来的最直接问题就是导致DrawCall的提升,DrawCall的提升可能会带来性能问题。
如果DrawCall的增加并没有带来性能问题,那我们可以通过关闭批处理来解决顶点动画问题
如果带来了性能问题,并且必须优化带有顶点动画的Shader,应该如何解决呢?
开启批处理后怎么获取正确的顶点数据
开启批处理后,顶点颜色来存储每个顶点的位置信息或相对位置信息
我们在C#代码中获取模型网格顶点数据,将数据存储到网格的颜色属性中,然后在Shader中通过颜色属性获取顶点信息
MeshFilter meshFilter = GetComponent<MeshFilter>();
if (meshFilter != null)
{
Mesh mesh = meshFilter.mesh;
Vector3[] vertices = mesh.vertices;
Color[] colors = new Color[vertices.Length];
for (int i = 0; i < vertices.Length; i++)
{
// 将模型空间位置存储在顶点颜色中
colors[i] = new Color(vertices[i].x, vertices[i].y, vertices[i].z, 1);
}
mesh.colors = colors;
}
在Shader中直接在appdata_full结构体中点出颜色成员既可以利用它获取到顶点信息
和顶点颜色方案类似,只是把相关信息存储到uv通道中而已,但是一般在存储两个值时使用
2、顶点动画物体投射阴影
我们可以为有顶点动画的物体 使用 LightMode(灯光模式) 为 ShadowCaster(阴影投射) 的 Pass(渲染通道),这样它便能投射阴影
但是如果我们直接使用内置的这种Pass(默认Shader中的,通过FallBack寻找到的)投射的阴影会是不正确的,因为默认Pass当中并不会使用新的顶点位置来投射,而是按照模型原来的顶点位置来计算阴影
为了让顶点动画物体投射正确的阴影, 我们需要自定义一个LightMode(灯光模式) 为 ShadowCaster(阴影投射) 的 Pass(渲染通道), 在顶点着色器函数中进行顶点相关的计算
Shader "ShaderProj/8/2DWater_Shadow"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", Color) = (1,1,1,1)
// 波动的幅度、频率、波长的倒数
_WaveAmplitude("WaveAmplitude", Float) = 1
_WaveFrequency("WaveFrequency", Float) = 1
_InvWaveLength("InvWaveLength", Float) = 1
// 纹理变化速度
_Speed("Speed", Float) = 1
}
SubShader
{
//透明Shader相关渲染标签 + 关闭批处理标签
Tags { "RenderType"="Tranparent" "Queue"="Transparent" "IgnoreProjector" = "True" "DisableBatching" ="True"}
Pass
{
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Color;
float _WaveAmplitude;
float _WaveFrequency;
float _InvWaveLength;
float _Speed;
v2f vert (appdata_base v)
{
v2f o;
// 模型空间下的偏移位置
float4 offset;
// 在模型空间的 x 轴进行偏移
offset.x = sin(_Time.y * _WaveFrequency + v.vertex.z * _InvWaveLength) * _WaveAmplitude;
offset.yzw = float3(0, 0, 0);
o.vertex = UnityObjectToClipPos(v.vertex + offset);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv += float2(0, _Time.y * _Speed);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 color = tex2D(_MainTex, i.uv);
color.rgb *= _Color.rgb;
return color;
}
ENDCG
}
Pass{
Tags{"LightMode" = "ShadowCaster"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct v2f{
V2F_SHADOW_CASTER;
};
float _WaveAmplitude;
float _WaveFrequency;
float _InvWaveLength;
v2f vert(appdata_base v)
{
v2f data;
float4 offset;
offset.x = sin(_Time.y * _WaveFrequency + v.vertex.z * _InvWaveLength) * _WaveAmplitude;
offset.yzw = float3(0,0,0);
v.vertex = v.vertex + offset;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(data);
return data;
}
float4 frag(v2f i):SV_Target
{
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}
}
}