顶点动画的注意事项

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
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值