Unity 网格模型及优化
一个模型中可以包含很多网格,一个模型可以由多个网格组成。在Unity3D中一个网格可以由多个子网格(Sub-Mesh)组成。
在渲染引擎的时候,每个子网格都要匹配一个材质球来做渲染,实际上一个子网格本身就是一个个普通的模型,它由很多个三角形构成,也需要材质球支持达成渲染。
为什么要有子网格?
美术人员在制作3D模型时,为什么不打网格编成一个,而要制作成多个子网格?
- 模型中的一部分网格用一种材质球来表现效果,另一部分用另一种。因为一个网格只能对应一个材质球所以要拆开。
- 模型中的莫部分贴图在众多模型中共同时使用的频率比较高,为了不重复制作以减少重复劳动,原本可以作为一个整体的模型会将公共材质部分单独拆分出来让一部分模型使用同一个材质球。
- 制作动画时,由于动画过于复杂,如果使用同一个模型去表现,则骨骼的数量就会成倍的增加,为了更好的表现动画,也为了能降低骨骼的使用数量,要拆分出来一部分模型,让它们单独成为模型动画的一部分。
从以上看多个子网格比单个网格要灵活的多,但它也有缺点,由于每个子网格都有材质球,导致子网格越多,drawcall 也越多。
子网格功能虽然强大,但也需要注意性能开销。
动态合并3D模型
我们知道每个网格模型都需要有一个材质球支持,导致每个模型都会产生一个drawcall,过多的drawcall,CPU会忙于发送状态数据给GPU,GPU大多数时间处于等待状态,这将导致画面帧率下降及强烈的画面卡顿。
实际项目中通常会遇到这样的问题:场景中会有很多物体,人物、建筑、路标、景观、树木、石头、碎块、花草等等,有的物体可能不只有一个材质球,相同物体共用一个,当然不同的物体也可能使用相同的材质球,这些物体如果不做任何优化处理,就会产生很多drawcall。
合并3D模型的主要目的就是减少drawcall,通过减少材质球的提交次数来完成优化,简单的说就是大拥有相同材质球的模型合并成一个模型和一个材质球,从而减少向GPU提交的drawcall数量,让GPU并行处理数据时更快更顺畅。
在Unity中分为动态合批和静态合批两种,使用它们的前提条件是模型物体必须具有相同的材质球,除了这个必要条件外还有其他条件。
动态批处理
开启动态批处理时,Unity会将场景中的某些物体自动批处理成为同一个drawcall,需要满足:
- 顶点数目要少于900个顶点网格。如果你的着色器使用顶点坐标、法线和单独的UV,那么顶点个数就要在300以内,如果着色器使用顶点坐标、法线、UV0、UV1和切线,则顶点要在180个内的网格。
- 两个物体的缩放单位要相同,比如A(1,1,1) B(1,1,2)就没法合批了
- 使用相同材质球
- 多管线(Pipeline)着色器会中断动态合批,支持多个灯光的前置渲染,它们增加了多个渲染通道,因此无法批处理。旧系统中的延迟渲染路径(灯光前置通道)关闭了动态批处理,因为它需要绘制物体两次。多有多个Pass的着色器增加了渲染管道,不会被动态批处理。
从上面看动态批处理的条件非常苛刻,真实的项目里很多模型时不符合动态批处理要求的。另外动态批处理要消耗CPU将所有物体的顶点转换到世界空间,合并网格也会带来开销。一味的减小drawcall并不是万能的,开销取决于很多因素,drawcall的开销主要是由图形API的调用造成的,如果节省的开销小于准备工作的开销,就得不偿失。例如,一个主机设备或流行的API(Apple Metal这样的),drawcall的开销通常很低,那么对于它来说动态批处理就没有什么优势了。
静态批处理
静态批处理允许引擎离线的情况下进行模型合并的批处理,减少drawcall。无论模型有多大,只要使用同一个材质球都会被静态批处理优化。它通常比动态批处理更有用(因为不需要实时转换顶点来消耗CPU),缺点是消耗了更多的内存。
Unity中需要对物体设置静态标记,也就是不能移动,缩放,旋转的物体。
如果一些物体之前共用一个模型,那么unity会复制这些物体的模型用来合并,所以使内存增大。个人觉得使用内存换CPU是值得的,然而如果用100GB的内存换千分之一的CPU效率那就不值得了。
静态批处理的具体流程是,将所有静态标记的物体放入世界空间->通过材质球分类标准将它们分别合并->构建成一个大的顶点集合和索引缓存。从而减少drawcall。
从技术上来说,静态批处理并没有节省3D API drawcall的数量,但它能减少因3D API之间的状态改变导致的消耗。大多数平台上,批处理限制在64000个顶点和64000个索引内(OpenGLES上为48000个,MacOS上为32000个),所以如果批处理过多,需要取消一些静态标记。
自己编写合并3D模型程序
Unity3D中:
- Mesh类有一个CombineMeshes的接口,提供了合并入口。
- MeshFilter类,是承载数据类。
- MeshRenderer类,是绘制网格的类
在使用之前我们还需要了解
MeshFilter和MeshRenderer中的mesh与shareMesh、material及shareMaterial的区别
mesh和material都是实例型的变量,对mesh和material执行任何操作都是额外复制一份后重新赋值,即使只是get操作,同样也会执行复制操作。而就是说,对mesh和material进行操作后,就会变成另一个实例。sharedMesh和shareMaterial与前面的两个变量不同,它们是共享型的。多个模型可以共同指定一个sharedMesh和sharedMaterial,当年修改了shareMesh或sharedMaterial里面的参数,指向同一个sharedMaterial和sharedMesh的多个模型就会同时改变效果。
materials和sharedMaterialsd的区别
materials是实例型,sharedMaterials是共享型,两个都是数组。material和sharedMaterial只针对主网格。而就是说material等于materials[0],sharedMaterial等于sharedMaterial[0]。
网格、MeshFilter、MeshRenderer的关系
网格时数据资源,它可以有自己的资源文件,通常时.FBX文件。网格里存储了顶点、UV、顶点颜色、三角形、切线、法线、骨骼、骨骼权重等提供渲染所必要的数据。
MeshFilter是一个承载网格数据的类,网格被实例化后存储在MeshFilter类中。MeshFilter包含两种类型,即实例型(mesh)和共享型变量(sharedMesh)。
MeshRenderer具有渲染功能,它会提取MeshFilter中的网格数据,结合自身的materials或sharedMaterials进行渲染。
Mesh.CombineInstance合并数据实例类
合并时需要为每个将要合并的网格创建一个CombineInstance实例,并往里放入mesh、subMesh的索引,lightmap的缩放和偏移,realtimeLightmap的缩放和偏移,以及世界坐标矩阵。它承载了所有需要需要合并的数据。
现在我们掌握了关于合并网格的理论知识我们来实现以下,这里我从Unity 商店下载了一个模型,我将角色手中的 剑和盾牌进行了合并,并生成了一个新的GameObject 效果如下:

using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;public class CombineTest : MonoBehaviour
{public List<MeshFilter> combineMeshFilers = new List<MeshFilter>();// Start is called before the first frame updatevoid Start(){CreateCombineModel();}private void CreateCombineModel(){// 提取网格所需的资源List<Material> materials = new List<Material>();CombineInstance[] combines = new CombineInstance[combineMeshFilers.Count];for (int i = 0; i < combineMeshFilers.Count; i++){CombineInstance tmpInstance = new CombineInstance();MeshFilter tmpFilter = combineMeshFilers[i];tmpInstance.mesh = tmpFilter.sharedMesh;tmpInstance.transform = tmpFilter.transform.localToWorldMatrix;combines[i] = tmpInstance;MeshRenderer renderer = tmpFilter.transform.GetComponent<MeshRenderer>();for (int j = 0; j < renderer.sharedMaterials.Length; j++){if (!materials.Contains(renderer.sharedMaterials[j]))materials.Add(renderer.sharedMaterials[j]);}}// 将所有网格合并成一个新的网格Mesh mesh = new Mesh();MeshFilter combineFilter = transform.AddComponent<MeshFilter>();MeshRenderer combineRenderer = transform.AddComponent<MeshRenderer>();mesh.CombineMeshes(combines);combineFilter.mesh = mesh;combineRenderer.materials = materials.ToArray();}
}
相关文章:
Unity 网格模型及优化
一个模型中可以包含很多网格,一个模型可以由多个网格组成。在Unity3D中一个网格可以由多个子网格(Sub-Mesh)组成。 在渲染引擎的时候,每个子网格都要匹配一个材质球来做渲染,实际上一个子网格本身就是一个个普通的模型࿰…...
离线 快速搭建 docker docker-compose k8s 环境
所需资源 sealos_5.0.1_linux_arm64.tar.gzkubernetes.tar等docker-compose-linux-aarch64 离线安装sealos,用于安装k8sdocker 首先安装sealos工具 tar zxvf sealos_5.0.1_linux_arm64.tar.gz sealos && chmod x sealos && mv sealos /usr/bin*…...
Excel根据条件动态索引单元格范围
假如我是一个老板,下面有数不胜数的员工,我要检查他们每周的工作产出,列一个排行榜,提高员工积极性,毕竟多劳多得嘛。 每天去手动统计,未免显得不太聪明,我们可以利用公式来解决这个问题。 我们…...
【计算机网络五】HTTP协议!网站运行的奥秘!
目录 HTTP协议 1.HTTP是什么? 2.Fiddler抓包查看HTTP协议格式 3.HTTP请求 4.HTTP响应 HTTP协议 1.HTTP是什么? HTTP ( 全称为 " 超文本传输协议 ") 诞生与 1991 年 . 目前已经发展为最主流使用的一种应用层协议 . HTTP 的前几个版本…...
开源模型应用落地-qwen模型小试-Qwen2.5-7B-Instruct-tool usage入门-Qwen-Agent深入学习(四)
一、前言 Qwen-Agent 是一个利用开源语言模型Qwen的工具使用、规划和记忆功能的框架。其模块化设计允许开发人员创建具有特定功能的定制代理,为各种应用程序提供了坚实的基础。同时,开发者可以利用 Qwen-Agent 的原子组件构建智能代理,以理解和响应用户查询。 本篇将换一种方…...
stream学习
Stream流 定义 Steam流,用于操作集合或者数组中的数据,大量结合了Lamda表达式的语法风格,代码简洁。 重点: 流只能收集一次 获取Stream流 Stream流要与数据源建立连接。 1.list 直接调用steam()即可 // list List<Stri…...
【数据结构】实验二 单链表的基本操作
一、实验目的 掌握线性表的链式存储结构的表示和实现方法。 掌握链表基本操作的算法实现,以及对相应算法的性能分析。 二、实验内容 1)根据输入的一系列整数,以0标志结束,用头插法建立单链表,并输出单链表中各元素…...
SQL 分组查询中的非聚合列要求及实例解析
在 SQL 查询中,当我们对数据进行分组时,通常会用到 GROUP BY 子句。SQL 标准要求:所有非聚合列(即没有使用聚合函数的列)都必须出现在 GROUP BY 子句中,或者是聚合函数的结果。这篇文章通过一个实例来说明这…...
Unity中实现战斗帧同步的高级技术
一、帧同步的基本原理 帧同步(Frame Synchronization)在网络游戏中指的是在每一帧上保证所有玩家所看到的游戏状态一致,而不是每个玩家单独计算自己的状态。实现帧同步通常需要每个客户端仅发送用户输入到服务器,并由服务器进行全…...
Qt 正则表达式提取文件中的 USB 设备 ID
Qt 正则表达式提取文件中的 USB 设备 ID flyfish 文档内容 Bus: 001 Device: 001 Description: 1d6b:0002 Linux Foundation 2.0 root hub Bus: 002 Device: 003 Description: 0e0f:0002 , Inc. USB Hub Bus: 002 Device: 002 Description: 0e0f:0003 , Inc. Mouse Bus: 002…...
使用 Python 和 OpenCV 实现摄像头人脸检测并截图
概述 在现代应用中,人脸检测是一项非常重要的技术,广泛应用于安全监控、身份验证等领域。本文将详细介绍如何使用 Python 和 OpenCV 库实现摄像头人脸检测并截图,并通过具体的代码示例来展示整个过程。 环境准备 在开始编写代码之前&#…...
【二叉搜素树】——LeetCode二叉树问题集锦:6个实用题目和解题思路
文章目录 计算布尔二叉树的值求根节点到叶节点的数字之和二叉树剪枝验证二叉搜索树二叉搜索树中第K小的元素二叉树的所有路径 计算布尔二叉树的值 解题思路: 这是一个二叉树的布尔评估问题。树的每个节点包含一个值,其中叶子节点值为 0 或 1࿰…...
【计算机视觉】FusionGAN
1. FusionGAN论文阅读 abreheret/FusionGAN: Pytorch implementation of "Generating a Fusion Image: One’s Identity and Another’s Shape" 1.1. WHY 在现实世界中,将对象或人物转换为期望的形状是一种常用技术,但现有的图像翻译方法在处理身份和形状时存在…...
问:SQL优化,七条实践总结?
SQL语句优化是数据库性能调优的重要部分,通过合理的优化可以显著提升查询速度和系统性能。文章总结几种常见SQL语句优化方法。 1. 优化Where子句的顺序 原则:表之间的连接条件应写在其他Where条件之前,能够过滤掉最大数量记录的条件应优先写…...
unity单例模式的不同声明(待完善
总结: 这段代码实现了一个泛型单例模式(Singleton Pattern),用于确保某个类(由泛型参数 T 指定)在整个应用程序中只有一个实例,并且在第一次访问时才创建该实例。该模式保证了该实例的全局唯一…...
大模型在蓝鲸运维体系应用——蓝鲸运维开发智能助手
本文来自腾讯蓝鲸智云社区用户: CanWay 背景 1、运维转型背景 蓝鲸平台从诞生之初,就一直在不遗余力地推动运维转型,让运维团队可以通过一体化PaaS平台,快速编写脚本,编排流程,开发运维工具,从被动地提供…...
vue2,vue3响应式的理解
vue2的话主要使用的是defineProperty对已有属性添加get,set,从而完成对数据的响应式控制,但每次需要for循环对属性进行遍历 function DefineReactive(target, key, value) {//存在多层嵌套的objectObserver(value);Object.defineReactive(target, key, {get() {retu…...
群控系统服务端开发模式-应用开发-前端退出功能
我们从未登录一直到退出,现在已经登录到操作,现在完成退出。退出有两种情况下会退出:第一种情况下是手动点击退出按钮,第二种情况下是登录过期时间到了自动退出的。 一、手动退出 因退出及个人信息页面都在公有页面,所…...
Web入门
Spring 官网:Spring | Home Spring是一个开源的Java企业级应用开发框架。Spring的主要目的是使Java EE(Java Platform, Enterprise Edition)开发更容易,并且通过提供一系列丰富的库和接口来促进良好编程实践,是…...
基于SpringBoot网上超市的设计与实现录像
基于SpringBoot网上超市的设计与实现录像 SpringBoot网上超市的设计与实现录像...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...
