unity Gpu优化

不一样的视角,深度解读unity性能优化。unity性能优化,unity内存优化,cpu优化,gpu优化,资源优化,资源包、资源去重优化,ugui优化。

  • gpu优化
    • 静态批处理
      • 静态批处理原理规则
        • 静态合批的原理
        • 静态合批的必须条件
        • 结论与总结
          • 静态合批原则与优化核心
    • 动态批处理
    • srpBach
    • Gpu实例化
    • 蒙皮实例化

gpu优化

静态批处理

静态批处理原理规则

静态批处理官方文档

静态合批的原理

静态批处理是场景中将同一个材质(属性完全相同的同一个材质,可以是不同网格)的不同变换(位移,旋转,缩放)网格合并成一个大的网格。然后在调用drawcall绑定shader设置shader状态和绑定网格到Gpu,进而一次完成渲染一个大网格实现渲染场景多个相同网格的优化

基础条件

  • GameObject 是活动的。
  • GameObject 具有启用的 Mesh Filter 组件。
  • Mesh Filter 组件引用一个 Mesh。
  • Mesh 启用了读/写。
  • Mesh 的顶点数大于 0。
  • Mesh 没有与其他 Mesh 组合过。
  • GameObject 具有启用的 Mesh Renderer 组件。
  • Mesh Renderer 组件未使用具有 DisableBatching 标签设置为 true 的材质。
  • Camera.main.opaqueSortMode这个对静态合批的影响很大
静态合批的必须条件
  • 网格使用同一个材质,如果通过Instance或者其它方法获取的材质即使属性完全相同,也不是同一个材质。除非所有相同网格都使用这个实例化后的材质,才是同一材质
  • 然后就是shader没有设置DisableBatching 标签设置为 true
  • Mesh 启用了读/写,值得一提的是当你把GameObject的Static Editor Flags 中启用 Batching Static时,运行unity后,unity会自动将网格设置为可读状态

注意:静态批处理对顶点数量有上限。每个静态批次最多可以包含 64000 个顶点。如果超过此数量,Unity 会创建另一个批次。
为了测试静态批处理首先编写一个测试脚本

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;

public class GreenCtrl : MonoBehaviour
{
    [Serializable]
    public class MeshInfo
    {
        public MeshInfo(Mesh mesh)
        {
            CurMesh = mesh;
            // Vertex = mesh.vertices.Length;
            Name = mesh.name;
        }

        public string Name;
        public Mesh CurMesh;
        public int Vertex;

        public override int GetHashCode()
        {
            return CurMesh.name.GetHashCode();
        }
    }


    [SerializeField] private Transform[] greens;
    [SerializeField] private Transform greensParent;

    public int count = 2000;
    public int size = 100;

    public float localScale = 10;

    public GameObject[] instances;
    public bool isGpuInstance = false;

    public Mesh[] meshes;


    // Start is called before the first frame update
    void Start()
    {
        if (isGpuInstance)
        {
            Gen();
            InitData();
        }
        Camera.main.opaqueSortMode = UnityEngine.Rendering.OpaqueSortMode.Default;
        MargeMesh();
    }

    void MargeMesh()
    {
        StaticBatchingUtility.Combine(instances, greensParent.gameObject);
      
    }

    public void CloseSort()
    {
        Camera.main.opaqueSortMode = UnityEngine.Rendering.OpaqueSortMode.NoDistanceSort;
    }

    public void SetMesh()
    {
        HashSet<Mesh> meshHash = new HashSet<Mesh>();
        meshes = Array.ConvertAll(instances, obj => obj.GetComponentInChildren<MeshFilter>().sharedMesh);
        HashSet<string> names = new HashSet<string>();
        for (int i = 0; i < instances.Length; i++)
        {
            Mesh mesh = meshes[i];
            List<Vector3> v = new List<Vector3>();

            meshHash.Add(mesh);
            names.Add(mesh.name);
        }

        meshes = meshHash.ToArray();
        Array.Sort(meshes, (m1, m2) => GetIndex(m1.name).CompareTo(GetIndex(m2.name)));
        Debug.LogError($"所有网格总数:{meshHash.Count}====={names.Count}");
    }

    int GetIndex(string name)
    {
        int index = 0;
        int.TryParse(name.Substring("Combined Mesh (root:scene) ".Length), out index);
        return index;
    }

    // Update is called once per frame
    void Update()
    {
        if (isGpuInstance)
        {
            ResetDraw();
        }
    }

    public void Gen()
    {
        while (greensParent.childCount > 0)
        {
            DestroyImmediate(greensParent.GetChild(0).gameObject);
        }

        instances = new GameObject[count];
        for (int i = 0; i < count; i++)
        {
            Vector2 pos = Random.insideUnitCircle; //随机一个圆内的位置出来
            pos *= size;
            // float x = Mathf.Abs(pos.x);//把随机出来的位置固定到第一象限
            // float y = Mathf.Abs(pos.y);
            int index = (int)Mathf.Lerp(0, greens.Length, Random.value);
            Transform trans = greens[index];
            trans = Instantiate(trans, greensParent);
            trans.localPosition = new Vector3(pos.x, Mathf.Lerp(0, 2, Random.value), pos.y);
            trans.localScale = Vector3.one * localScale;
            instances[i] = trans.gameObject;
        }

        InitObj();
    }


    private MaterialPropertyBlock materialPropertyBlock;

    void InitObj()
    {
        materialPropertyBlock = new MaterialPropertyBlock();
        MeshRenderer renderer;

        foreach (GameObject obj in instances)
        {
            // 为每个实例设置一个随机颜色
            float r = Random.Range(0f, 1f);
            float g = Random.Range(0f, 1f);
            float b = Random.Range(0f, 1f);

            Color randomColor = new Color(r, g, b);

            // 设置属性块中的颜色
            // materialPropertyBlock.SetColor("_Color", randomColor);

            // 获取渲染器并应用属性块
            renderer = obj.GetComponent<MeshRenderer>();
            // renderer.SetPropertyBlock(materialPropertyBlock);
        }
    }

    public void GenGpuInstance()
    {
        for (int i = 0; i < count; i++)
        {
            Vector2 pos = Random.insideUnitCircle; //随机一个圆内的位置出来
            pos *= size;
            int index = (int)Mathf.Lerp(0, greens.Length, Random.value);
            Transform trans = greens[index];
            trans = Instantiate(trans, greensParent);
            trans.localPosition = new Vector3(pos.x, Mathf.Lerp(0, 2, Random.value), pos.y);
            trans.localScale = Vector3.one * localScale * Random.value;
            instances[i] = trans.gameObject;
        }

        InitObj();
    }

    private GraphicsDraw CurGraphicsDraw = new GraphicsDraw();

    public void InitData()
    {
        CurGraphicsDraw.InitDrawData(instances);
    }

    public void ResetDraw()
    {
        //CurGraphicsDraw.DrawGrass();
        CurGraphicsDraw.DrawGrassGpuInstance();
    }
}

编写此类的编辑器脚本,放在Editor文件夹下

using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(GreenCtrl))]
public class GreenCtrlEditor : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        if (GUILayout.Button("生成植被"))
        {
            ((GreenCtrl)target).Gen();
        }
        if (GUILayout.Button("动态合批"))
        {
            ((GreenCtrl)target).SetMesh();
        }
        if (GUILayout.Button("设置网格"))
        {
            ((GreenCtrl)target).SetMesh();
        }
        if (GUILayout.Button("关闭渲染顺序"))
        {
            ((GreenCtrl)target).CloseSort();
        }
    }
}

挂载设置脚本,将对象设置成静态合批Batching Static,以确保生成的对象也是静态批处理,设置父节点,草模型,生成数量Count为100
在这里插入图片描述

注意的是Greens是草模型
在这里插入图片描述
点击生成植被,我设置的生成100个草模型,没有超过64000个顶点
打开FrameDebugger
在这里插入图片描述
可看到植被生成217-2=215个批处理。
点击运行,启动静态批处理
在这里插入图片描述
这是你会发现所有相同材质的网格都合并成一个批次,就是说所有相同材质草模型合并成一个大网格使用同一个材质调用一个drawcall完成了多个模型的渲染。并且静态合批的名称为Static Batch
继续增加草的数量为6000,生成草区域Size调到250,调整一下摄像机的位置
在这里插入图片描述
现在草的静态合批为192-2=190个批次。理论上应该生成1个批次,因为同一个材质会合并为同一批次。为什么会生成这么多个批次呢?
因为静态合批的合并定点数最多64000个顶点,所以超过这个顶点数就会把剩下的网格合并到另一个批次。
那么如何查看合并后的网格信息呢
选中一个草模型对象,在Mesh参数中,当启动游戏后,Mesh就会自动刷新成合批后的网格
未运行前的网格
在这里插入图片描述
运行后的静态合批网格
在这里插入图片描述
你会发现合批后网格都会刷到每一个草模型的Mesh,并且网格名字都是以Combined Mesh (root:Scene)为前缀的网格。
双击网格查看网格信息
在这里插入图片描述
你会发现红色框模型顶点总数接近64000个顶点,但不会超过这个数。
同时绿框为合并网格后的一个个子网格信息
但是我发现所有草合并后的网格只有46个.理论上应该只有46个合批,为什么变成190个合批呢。。。
这一步测试如下
点击“设置网格”
在这里插入图片描述
此功能会把所有不同网格保存起来。会发现确实只有46个合成的大网格。那么为什么多生成这么多合批呢?
这是因为合批还会根据相机的Camera.main.opaqueSortMode渲染顺序模式有关
点击关闭渲染顺序
在这里插入图片描述
点击Disable两次,刷新数据
会发现现在合批会非常接近46了。至于为什么还会多出7个。应该是渲染的其它因素影响。但是到这也差不多把影响合批的原理逻辑基本说明了。
Camera.main.opaqueSortMode这个对静态合批的影响很大。即使正常合并网格,Camera.main.opaqueSortMode也会对静态合批影响很大。
现在将草的模型添加多个,并且这些草使用同一材质,关闭渲染顺序
在这里插入图片描述
你会发下增减了7中同一材质的不同模型,静态合批数量也只增了1倍。

结论与总结
静态合批原则与优化核心
  • 将场景模型使用同一个材质,并使用上面同一个图集。保证所有模型使用相同材质
  • 将场景所有不同模型通过工具或者脚本把相对应的纹理合并为一个个对应图集,如所有模型的主纹理合并一个图集,法线纹理合并为一个纹理。也可以让美术去合并。脚本的话可以使用unity 自带的Texture2D.PackTextures()方法区合并图集。但是还要对网格的UV做处理。

动态批处理

srpBach

Gpu实例化

蒙皮实例化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/892991.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023年华为杯数学建模竞赛B题论文和代码

DFT类矩阵的整数分解逼近 离散傅里叶变换&#xff08;Discrete Fourier Transform&#xff0c;DFT&#xff09;傅里叶分析方法是信号分析的最基本方法&#xff0c;傅里叶变换是傅里叶分析的核心&#xff0c;通过它把信号从时间域变换到频率域&#xff0c;进而研究信号的频谱结构…

SSM四川工商学院学生宿舍管理系统---附源码54633

摘 要 从20年代开始&#xff0c;计算机疯狂的出现在人们的生活以及工作当中&#xff0c;成为人们生活、工作的好帮手&#xff0c;计算机深入到每家每户当中&#xff0c;网络办公&#xff0c;网络教学更是替换了传统手工记录管理的方式&#xff0c;使用计算机办公可以不必局限于…

MySQL-12.DQL-聚合函数

一.DQL-分组查询 二.聚合函数 -- DQL:分组查询 -- 聚合函数 -- 1.统计该企业员工数量 count select count(id) from tb_emp; select count(job) from tb_emp;select count(A) from tb_emp; select count(*) from tb_emp;-- 2.统计该企业最早入职的员工 min select min(entr…

SQL第18课挑战题

1. 创建一个名为customerswithorders的视图&#xff0c;其中包含customers表中的所有列&#xff0c;但仅仅是那些已下订单的列。提示&#xff1a;可以在orders表上使用join来仅仅过滤所需的顾客&#xff0c;然后使用select来确保用有正确的数据。 创建视图&#xff1a;

电影台词摘抄(十一)——Banana!

Scarlet&#xff1a;Do you know who this is? Kevin&#xff1a;Uh. La cucaracha? n.伊丽莎白(女子名) Scarlet&#xff1a;This is Queen Elizabeth, ruler of England.Oh, I love England, Their music, the …

Linux - 环境变量 | 命令行参数 | 进程基础

文章目录 一、了解冯诺依曼体系结构1、概念2、对数据层面3、实例二、操作系统1、概念2、设计OS的目的3、定位4、操作系统怎么管理&#xff1f; 三、进程1、概念2、怎么管理进程3、描述进程-PCB4、描述进程怎么运行&#xff08;粗略&#xff09;5、进程属性6、创建子进程7、创建…

bash之基本运算符

一.算术运算符 vim test.sh #!/bin/basha10 b20valexpr $a $b echo "a b : $val"valexpr $a - $b echo "a - b : $val"valexpr $a \* $b echo "a * b : $val"valexpr $b / $a echo "b / a : $val"valexpr $b % $a echo "b % a …

c++STL——map与set的使用及介绍

目录 前言&#xff1a; 1. 关联式容器 2. 键值对 3. 树形结构的关联式容器 3.1 set 3.1.1 set的介绍 3.1.2 set的使用 1. set的模板参数列表 2. set的构造 3. set的迭代器 4. set的容量 5. set修改操作 6. set的使用举例 3.2 map 3.2.1 map的介绍 3.2.2 map的…

Vue3浮动按钮(FloatButton)

效果如下图&#xff1a;在线预览 APIs FloatButton 参数说明类型默认值left按钮定位的左边距&#xff0c;单位 pxnumber | stringundefinedright按钮定位的右边距&#xff0c;单位 pxnumber | string24top按钮定位的上边距&#xff0c;单位 pxnumber | stringundefinedbottom…

spring 如何将mutipartFile转存到本地磁盘

两者的区别和联系 MutipartFile是spring的一部分&#xff0c;File则是java的标准类MutipartFile用于接收web传递的文件&#xff0c;File操作本地系统的文件 MutipartFile 转换File的三种方式 使用MutipartFile 自带的transferTo方法使用java自带的FileOutPutStream流使用java自…

使用Langchain-chatchat搭建RAG应用,并使用postman进行测试验证

Github地址&#xff1a;https://github.com/chatchat-space/Langchain-Chatchat 一、概述 LangChain-Chatchat (原 Langchain-ChatGLM)&#xff0c;一种利用 langchain 思想实现的基于本地知识库的问答应用&#xff0c;目标期望建立一套对中文场景与开源模型支持友好、可离线运…

React(一) 认识React、熟悉类组件、JSX书写规范、嵌入变量表达式、绑定属性

文章目录 一、初始React1. React的基本认识2. Hello案例2.1 三个依赖2.2 渲染页面2.3 hello案例完整代码 二、类组件1. 封装类组件2. 组件里的数据3. 组件里的函数 (重点)4. 案例练习(1) 展示电影列表 三、JSX语法1. 认识JSX2. JSX书写规范及注释3. JSX嵌入变量作为子元素4. JS…

android app执行shell命令视频课程补充android 10/11适配-千里马android

(https://blog.csdn.net/learnframework/article/details/120103471) https://blog.csdn.net/learnframework/article/details/120103471 hi&#xff0c;有学员在学习跨进程通信专题课程时候&#xff0c;在实战app执行一个shell命令的项目时候&#xff0c;对课程本身的android …

推荐算法的学习

文章目录 前言1、模型1.1 从本领域模型的发展历史中学习1.1.1 在历史中总结发展规律和趋势1.1.2 发现模型之间的共性&#xff0c;方便记忆 1.2 从其他领域的发展中学习1.2.1 注意力机制1.2.2 残差网络 1.3 实践该怎么办&#xff1f; 2、 特征2.1 数据源的选择与建立2.2 特征构造…

Element中el-table组件设置max-height右侧出现空白列的解决方法

之前就出现过这个情况&#xff0c;没理过&#xff0c;因为不影响啥除了不美观...但今天看着实在是难受&#xff0c;怎么都不顺眼(可能是我自己烦躁--) 试了很多网上的方法&#xff0c;都不得行&#xff0c;后面发现了这篇文章&#xff0c;解决了! 感谢&#xff01; Element中t…

PageHelper循环依赖问题

1. 问题 2. 原因 项目中SpringBoot的版本为2.7.18。 SpringBoot2.6.x后不推荐使用循环依赖&#xff0c;也就是说从2.6.x版本开始&#xff0c;如果项目里还存在循环依赖&#xff0c;SpringBoot将拒绝启动&#xff01; 3. 解决 去pageHelper github看&#xff0c;才看到新版本…

Pandas缺失值处理

目录 NaN 加载包含缺失的数据 查看缺失值 通过info函数查看缺失值 通过isnull函数查看缺失值 通过notnull函数查看缺失值 通过isnull().sum()统计空值 缺失值处理 准备数据 dropna删除缺失值 fillna平均值填充缺失值 fillna前后值填充缺失值 interpolate线性插值 …

C++中的vector二维数组(全面详解)

目录 二维数组概念&#xff1a; 二维数组格式&#xff1a; 二维数组的初始化&#xff1a; 在创建的时候就进行初始化&#xff1a; resize初始化&#xff1a; 构造v的时候只给行数&#xff0c;列数用resize开辟 构造v的时候不给行数不给列数&#xff0c;都用resize来开辟…

Java中使用protobuf

一、简介 Protocal Buffers(简称protobuf)是谷歌的一项技术&#xff0c;用于结构化的数据序列化、反序列化。 Protocol Buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法&#xff0c;它可用于&#xff08;数据&#xff09;通信协议、数据存储等。 Protocol B…

第十三章 RabbitMQ之消息幂等性

目录 一、引言 二、消息幂等解决方案 2.1. 方案一 2.2. 方案二 一、引言 幂等是一个数学概念&#xff0c;用函数表达式来描述是这样的&#xff1a;f(x) f(f(x)) 。在程序开发中&#xff0c;则是指同一个业务&#xff0c;执行一次或多次对业务状态的影响是一致的。有些业务…