52.顶点动画的批处理

52.动态效果-顶点动画-顶点动画的注意事项-批处理


52.1 知识点

知识回顾

在之前的顶点动画相关课程中,我们提到过需要在渲染标签中添加 "DisableBatching"="True" 来让该 Shader 渲染的对象不进行批处理。这样做的目的是确保基于模型空间的计算能够正确进行,且不会影响最终的渲染结果。

为什么批处理会影响顶点动画

Unity 中默认有静态批处理和动态批处理。批处理的主要作用是将多个对象合并,作为一个 DrawCall 进行处理。批处理会对顶点动画产生影响,原因在于不同的对象拥有不同的变换矩阵(位置、旋转、缩放),而批处理会将这些对象的变换矩阵统一处理。

举个例子:

  • 物体 A:位于世界空间位置 (0, 0, 0),无旋转。
  • 物体 B:位于世界空间位置 (5, 0, 0),无旋转。

它们是两个独立的对象,拥有不同的变换矩阵。

不进行批处理时:
每个对象的变换矩阵会单独传递给 Shader,顶点的模型空间位置会根据各自的变换进行正确计算。

进行批处理时:
启用批处理后,Unity 会将对象 A 和对象 B 合并为一个 DrawCall,并使用统一的变换矩阵。例如,在静态批处理中,Unity 会将对象 A 和对象 B 的顶点合并为一个网格,并使用统一的变换进行渲染。此时,批处理后顶点位置是混合的,Shader 中无法区分不同对象的模型空间位置,这可能会导致以下问题:

  • 顶点动画失效: 假设你希望顶点在模型空间的 x 方向上进行sin波动动画。如果对象 A 和对象 B 的模型空间位置被混合,波动动画会变得不可预测。

  • 变换混淆: 对象 A 和对象 B 拥有不同的变换矩阵。如果批处理后使用统一的变换矩阵,Shader 无法区分每个顶点属于哪个对象,导致所有顶点的动画效果混淆。

总结: 批处理会让对象失去独立性,相当于将多个对象之间独立的模型空间坐标系合并为一个坐标系,从而影响顶点的相对位置和变换矩阵等信息,导致顶点动画结果异常。因此,我们通过渲染标签来关闭批处理。

关闭批处理带来的问题

关闭批处理带来的最直接问题是导致 DrawCall 的提升。DrawCall 的增加可能会带来性能问题。如果 DrawCall 的增加并没有带来性能问题,那么可以通过关闭批处理来解决顶点动画问题。如果性能问题很严重,并且必须优化带有顶点动画的 Shader,那么应该如何解决呢?

如何解决问题

开启批处理的同时,可以通过以下方法来解决问题:

1. 顶点颜色

可以利用顶点颜色来存储每个顶点的位置信息或相对位置信息。具体步骤如下:

  • 获取当前游戏对象上的网格过滤器组件(MeshFilter)。
  • MeshFilter 用于存储和管理游戏对象的网格(Mesh)数据。
  • 获取网格的顶点数组,每个顶点是一个三维向量(Vector3),表示模型空间中的一个点的位置。
  • 创建一个颜色数组,长度与顶点数组相同,用于存储每个顶点的颜色信息。
  • 遍历顶点数组,将顶点的模型空间位置存储到顶点颜色中。
  • 在 Shader 中通过颜色属性获取顶点信息。

示例代码:

// MeshFilter 用于存储和管理游戏对象的网格(Mesh)数据。
MeshFilter meshFilter = GetComponent<MeshFilter>();
if (meshFilter != null)
{
    // 获取 MeshFilter 组件所关联的网格(Mesh)对象。
    Mesh mesh = meshFilter.mesh;

    // 获取网格的顶点数组,每个顶点是一个三维向量(Vector3),
    // 表示模型空间中的一个点的位置。
    Vector3[] vertices = mesh.vertices;

    // 创建一个颜色数组,长度与顶点数组相同。
    // 这个数组将用于存储每个顶点的颜色信息。
    Color[] colors = new Color[vertices.Length];

    // 遍历顶点数组中的每个顶点。
    for (int i = 0; i < vertices.Length; i++)
    {
        // 将模型空间位置存储在顶点颜色中
        // 创建一个新的颜色对象,将顶点的 x、y、z 坐标作为颜色的 RGB 分量,
        // 第四个参数 1 通常表示不透明度为完全不透明。
        colors[i] = new Color(vertices[i].x, vertices[i].y, vertices[i].z, 1);
    }
    // 将计算得到的颜色数组赋值给网格的颜色属性,
    // 这样可以在渲染时根据顶点颜色进行着色。
    mesh.colors = colors;

}

在 Shader 中,通过 appdata_full.color 获取顶点信息。

2. UV 通道

和上面的顶点颜色方案类似,只是将相关信息存储到 UV 通道中。一般在存储两个值时,使用 mesh.uv2mesh.uv3,并在顶点着色器中通过 appdata_full.texcoord 获取。

总结

若顶点动画 Shader 由于关闭批处理而带来了性能问题,可以通过以下方案避免顶点动画渲染问题:

  1. 利用顶点颜色;
  2. 使用 UV 通道;
  3. 其他方案。

这些方法能够在确保顶点动画正常的同时,解决由批处理带来的性能问题。


52.2 知识点代码

Lesson52_动态效果_顶点动画_顶点动画的注意事项_批处理.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson52_动态效果_顶点动画_顶点动画的注意事项_批处理 : MonoBehaviour
{
    void Start()
    {
        #region 知识回顾

        //我们在之前的顶点动画相关课程中一再强调
        //我们需要在渲染标签中添加
        //"DisableBatching"="True"
        //来让该Shader渲染的对象不进行批处理
        //目的是让基于模型空间的计算能够正确进行
        //不会影响最终的渲染结果

        #endregion

        #region 知识点一 为什么批处理会影响顶点动画

        //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无法区分每个顶点属于哪个对象,导致所有顶点的动画效果混淆。

        //总结:
        //批处理会让对象失去独立性
        //相当于将多个对象之间独立的模型空间坐标系合并为一个坐标系
        //从而影响顶点的相对位置和变换矩阵等信息
        //导致顶点动画结果异常
        //因此我们通过渲染标签来关闭批处理

        #endregion

        #region 知识点二 关闭批处理带来的问题

        //关闭批处理带来的最直接问题就是导致
        //DrawCall的提升
        //DrawCall的提升可能会带来性能问题

        //如果DrawCall的增加并没有带来性能问题
        //那我们可以通过关闭批处理来解决顶点动画问题
        //如果带来了性能问题,并且必须优化带有顶点动画的Shader,我们应该如何解决呢?

        #endregion

        #region 知识点三 如何解决问题

        //开启批处理
        //1.顶点颜色
        //  利用顶点颜色来存储每个顶点的位置信息或相对位置信息
        //  我们在C#代码中获取模型网格顶点数据,将数据存储到网格的颜色属性中
        //  在Shader中通过颜色属性获取顶点信息


        // 获取当前游戏对象上的网格过滤器组件(MeshFilter)。
        // MeshFilter 用于存储和管理游戏对象的网格(Mesh)数据。
        MeshFilter meshFilter = GetComponent<MeshFilter>();
        if (meshFilter != null)
        {
            // 获取 MeshFilter 组件所关联的网格(Mesh)对象。
            Mesh mesh = meshFilter.mesh;

            // 获取网格的顶点数组,每个顶点是一个三维向量(Vector3),
            // 表示模型空间中的一个点的位置。
            Vector3[] vertices = mesh.vertices;

            // 创建一个颜色数组,长度与顶点数组相同。
            // 这个数组将用于存储每个顶点的颜色信息。
            Color[] colors = new Color[vertices.Length];

            // 遍历顶点数组中的每个顶点。
            for (int i = 0; i < vertices.Length; i++)
            {
                // 将模型空间位置存储在顶点颜色中
                // 创建一个新的颜色对象,将顶点的 x、y、z 坐标作为颜色的 RGB 分量,
                // 第四个参数 1 通常表示不透明度为完全不透明。
                colors[i] = new Color(vertices[i].x, vertices[i].y, vertices[i].z, 1);
            }
            // 将计算得到的颜色数组赋值给网格的颜色属性,
            // 这样可以在渲染时根据顶点颜色进行着色。
            mesh.colors = colors;

        }
        //  在Shader中直接在appdata_full结构体中点出颜色成员既可以利用它获取到顶点信息 
        //  在顶点着色器使用appdata_full.color获取

        //2.uv通道
        //  和上面的顶点颜色方案类似,只是把相关信息存储到uv通道中而已,但是一般在存储两个值时使用
        //  只需要将上面的 mesh.colors改成 mesh.uv2  mesh.uv3 这样
        //  在顶点着色器使用appdata_full.texcoord获取

        //等等

        #endregion

        #region 总结

        //若顶点动画Shader由于关闭批出来带来了性能问题
        //我们可以去掉渲染标签"DisableBatching"="True"
        //通过
        //1.顶点颜色
        //2.uv通道
        //等方案去避免顶点动画渲染问题

        #endregion
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏