unity Gpu优化

news2025/1/8 22:22:33

不一样的视角,深度解读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实例化

蒙皮实例化

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Boot视频网站:安全与可扩展性设计

4 系统设计 4.1系统概要设计 视频网站系统并没有使用C/S结构&#xff0c;而是基于网络浏览器的方式去访问服务器&#xff0c;进而获取需要的数据信息&#xff0c;这种依靠浏览器进行数据访问的模式就是现在用得比较广泛的适用于广域网并且没有网速限制要求的B/S结构&#xff0c…

Appium环境搭建、Appium连接真机

文章目录 一、安装Android SDK二、安装Appium-desktop三、安装Appium Inspector 一、安装Android SDK 首先需要安装jdk&#xff0c;这里就不演示安装jdk的过程了 SDK下载地址&#xff1a;Android SDK 下载 1、点击 Android SDK 下载 -> SKD Tools 2、选择对应的版本进行下…

mysql 慢查询日志slowlog

慢查询参数 slow log 输出示例 # Time: 2024-08-08T22:39:12.80425308:00 #查询结束时间戳 # UserHost: root[root] localhost [] Id: 83 # Query_time: 2.331306 Lock_time: 0.000003 Rows_sent: 9762500 Rows_examined: 6250 SET timestamp1723127950; select *…

云栖实录 | 智能运维年度重磅发布及大模型实践解读

本文根据2024云栖大会实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a; 钟炯恩 | 阿里云智能集团运维专家 张颖莹 | 阿里云智能集团算法专家 活动&#xff1a; 2024 云栖大会 AI 可观测专场 -智能运维&#xff1a;云原生大规模集群GitOps实践 2024 …

【c++】c++11多线程开发

2 C多线程 本文是参考爱编程的大丙c多线程部分内容&#xff0c;按照自己的理解对其进行整理的一篇学习笔记&#xff0c;具体一些APi的详细说明请参考大丙老师教程。 代码性能的问题主要包括两部分的内容&#xff0c;一个是前面提到资源的获取和释放&#xff0c;另外一个就是多…

使用rabbitmq-operator在k8s集群上部署rabbitmq实例

文章目录 前言一、rabbitmq-operator二、进行部署1.部署cluster-operator2.创建自己需要的特定命名空间3.创建rabbitmq的instance4.创建nodeport访问 结果验证 前言 使用rabbitmq-operator在k8s集群上部署rabbitmq实例。时区设置为上海 一、rabbitmq-operator 官网地址&#…

数学建模算法与应用 第16章 优化与模拟方法

目录 16.1 线性规划 Matlab代码示例&#xff1a;线性规划求解 16.2 整数规划 Matlab代码示例&#xff1a;整数规划求解 16.3 非线性规划 Matlab代码示例&#xff1a;非线性规划求解 16.4 蒙特卡洛模拟 Matlab代码示例&#xff1a;蒙特卡洛模拟计算圆周率 习题 16 总结…

java代码生成器集成dubbo,springcloud详解以及微服务遐想

摘要 今天终于有了点空闲时间&#xff0c;所以更新了一下代码生成器&#xff0c;修复了用户反馈的bug&#xff0c;本次更新主要增加了dubbo和springcloud脚手架的下载功能&#xff0c;架子是本人亲自搭建&#xff0c;方便自由扩展或者小白学习使用&#xff0c;你也许会问为什么…

红日安全vulnstack (二)

目录 环境搭建 网卡设置 修改Kali网段 IP 分布 WEB渗透 Weblogin服务开启 漏洞扫描 CVE工具利用 MSF上线 内网渗透 域内信息收集 凭证横向移动 权限维持 黄金票据 参考文章 https://www.cnblogs.com/bktown/p/16904232.htmlhttps://blog.csdn.net/m0_75178803/ar…

leetcode54:螺旋矩阵

给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5]示例 2&#xff1a; 输入&#xff1a;matrix [[1,2,3,…

hackmyvm-Hundred靶机

主机发现 sudo arp-scan -l 以sudo权限执行arp-scan -l 扫描并列出本地存在的机器&#xff0c;发现靶机ip为192.168.91.153 nmap扫描 端口发现 21/tcp open ftp 22/tcp open ssh 80/tcp open http web信息收集 我们先尝试一下ftp端口的匿名登录 FTP:是文件传输协议的端…

个人博客系统_测试报告

1.项目背景 基于SSM框架实现的个人博客系统&#xff0c;由五个页面构成&#xff1a;用户登录页、博客发表页、博客编辑页、博客列表页以及博客详情页。登录即可查看自己与其他用户已发布的博客&#xff0c;也可以使用自己的账号发布博客&#xff0c;通过使用Selenium定位web元…

《人工智能:CSDN 平台上的璀璨之星》

一、CSDN 上的 AI 热门话题 GPT-3 作为 CSDN 上的热门话题&#xff0c;其应用极为广泛。GPT-3 是 OpenAI 开发的一种基于 Transformer 架构的大规模预训练语言模型&#xff0c;拥有惊人的 1750 亿个参数。它具有多任务处理能力&#xff0c;能够执行多种自然语言处理任务&#x…

保护企业终端安全,天锐DLP帮助企业智能管控终端资产

为有效预防员工非法调包公司的软硬件终端资产&#xff0c;企业管理员必须建立高效的企业终端安全管控机制&#xff0c;确保能够即时洞察并确认公司所有软硬件资产的状态变化。这要求企业要有一套能够全面管理终端资产的管理系统&#xff0c;确保任何未经授权的资产变动都能被迅…

Ajax处理错误信息(处理响应报文)

<!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title></head><body><form action""><div>用户名<input type"text" class"username"></div>…

【2024-10-16】某小破站w_rid参数分析

声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、参数分析三、代码一、前言 看一下小破站的参数加密 网址: aHR0cHM6Ly9zcGFjZS5iaWxpYmlsaS5jb20vNDA1Nz…

c++就业1.1.3海量数据去重的Hash与BloomFilter

找到具体的位置 通过映射 当前需要插入的指向 上一层最后一个 方便头插 布隆过滤器 - 查找是否有这个值 但是不能够返回value 服务器和过滤器进行网络交互 我们要知道这个key在不在mysql中 去查询并且在mysql中进行查询 所以在服务器部署布隆过滤器 为了节约内存 用位图 对str…

Linux权限理解及环境基础开发工具使用

前言 Linux中有两种用户&#xff0c;一种是root用户&#xff0c;另一种是普通用户&#xff0c;二者的权限不一样&#xff0c;即能做的事情不一样。下面我们来细讲一下这些不同。 root用户能在Linux系统下做任何事情&#xff0c;而普通用户只能做有限的事情。 root用户的命令…

FPGA采集adc,IP核用法,AD驱动(上半部分)

未完结&#xff0c;明天补全 IP核&#xff1a;集成的一个现有的模块 串口写好后基本不会再修改串口模块内部的一些逻辑&#xff0c;将串口.v文件添加进来&#xff0c;之后通过他的上层的接口去对他进行使用&#xff0c;所以我们打包IP&#xff0c;之后就不用去添加源文件了&a…

Pollard‘s p-1算法

概述 光滑数 (Smooth number)&#xff1a;指可以分解为多个小素数乘积的正整数 当p是N 的因数&#xff0c;并且p−1是光滑数&#xff0c;可以考虑使用Pollards p-1算法来分解N 当p是N的因数&#xff0c;并且p1是光滑数&#xff0c;可以考虑使用Williamss p1算法来分解N 这里…