Unity技术学习:渲染大量物体的解决方案,外加RenderMesh、RenderMeshInstanced、RenderMeshIndirect的简单使用

叠甲:本人比较菜,如果哪里不对或者有认知不到的地方,欢迎锐评(不玻璃心)!

导师留了个任务,渲染大量的、移动的物体。

寻找解决方案:

当时找了几个解决方案:

静态批处理:

要求:字如其意,必须是静态的,不能移动旋转缩放等等。

原理:Unity 会自动将相同材质球的物体合并到一个大的mesh里,提取顶点和索引数据放到共享的顶点缓冲和索引缓冲

并不减少drawcall,减少了渲染状态的设置。

缺点;在处理的时候会把相同模型的顶点信息都保存,并变换到世界空间。会导致包体和运行时体积变大。

哪怕勾上,上万个物体就寄了(虽然灵活度很高),只需要勾上就可以了。

动态批处理:

要求:不超过300个顶点(不超过总计900个属性),不包含镜像的缩放,材质一样,物体lightmap指向的位置一样

原理:运行时将所有共享同一材质的模型顶点信息变换到世界空间,一次drawcall 绘制多个模型。

内置渲染管线:

URP:(HDRP没有)

真正意义上的减少drawcall。

缺点:会有额外的cpu性能消耗,而且新一代的图形api在批次间消耗降低,反而使用动态批处理可能更差。

对于打断动态批处理的情况:

  • 若优先级低,会自动禁用:SRPBatch>静态批处理>GPUinstance>动态批处理
  • 多passShader自动禁用动态批处理
  • shadow casters可以使用不用材质,但shadow casters pass使用的参数是相同的就不会禁用动态批处理。
  • 向前渲染路径,如果一个物体接受了多个光照,会为每个per pixel light 产生提交和绘制,从而附加多个Pass导致无法合批。

SRPBatch:

原理:简化了批处理的渲染循环,可以加速相同着色器变体的多种材质在场景中的CPU渲染速度。

要求:Shader中的变体一致,对象不可以是粒子或蒙皮网格(好像最新版已经支持蒙皮了),位置不相邻且中间夹杂着不同shader或不同变体的其他物体,不会进行同批处理。

这个详细阅读一下官方文档,这个本文不做深入研究,但注意:srpBatch与GPUinstance不兼容!(参考上方的优先级)

Unity - 手册:可编写脚本的渲染管线批处理程序

URP:(HDRP默认开启)

缺点:

降低setpass call的消耗

GPUInstance:

要求:同材质、同mesh,默认材质仅支持transform的改变。支持meshRenderer和Graphics.RenderMesh,不支持SkinMeshRenderer。缩放为负的情况下会失效,代码动态改变材质变量就不算同一材质,就会失效,但是可以通过将颜色变化等变量加入常量缓冲区实现。仅支持一个实时光,需要多个光源只能切换到延迟渲染路径。

原理:仅绘制一个,剩下的“复制”。

只需要勾上材质里的GPUInstancing、当使用该材质时,就会合批。但经实测,仅支持完全相同的物体,方块和球使用相同材质并不会合批。而且:不要对顶点少于 256 个的网格使用 GPU 实例化(官方文档提醒)。

减少了draw call 

灵活度也挺高的,但是对于超过上限(貌似是1023?)的物体数量,依旧拉胯,batch数虽然不是猛增但是帧率感人(后续可能会做性能分析)

RenderMeshIndirect:

原理:手动GPUInstance多个对象。

跑了一下示例,woc,吊的一批,就是每个块可控性比较差(也可能是我比较菜,目前已知的就是:使用动画贴图、时间来控制每个单体的行为;而且没有碰撞、单独操作难度高:需要在shader里写改变顶点位置),但是,一次性绘制10w,600帧没有一点儿压力。1000w个能跑十几帧(视觉效果拉满)。而且支持光照和阴影。

所以接下来我从 RenderMesh到RenderMeshInstanced再到RenderMeshIndirect的API给说一下,并且跑一下示例:

小总结: 

(抄过来的,侵删)

 RenderMesh:

使用给定的渲染参数渲染网格(就单纯绘制网格,没有任何合批)。

public static void RenderMesh(ref RenderParams rparams, Mesh mesh, int submeshIndex, Matrix4x4 objectToWorld, Nullable<Matrix4x4> prevObjectToWorld = null);

rparams:

camera用于渲染的相机。如果设置为 null(默认),则为所有摄像机渲染。
layer用于渲染的图层。要使用的图层。
lightProbeProxyVolume用于渲染的光探针代理体积 (LPPV)。
lightProbeUsage光探头使用的类型。
material用于渲染的材质。
matProps用于渲染的材质属性。
motionVectorMode用于渲染的运动矢量模式。
receiveShadows描述渲染的几何体是否应接收阴影。
reflectionProbeUsage用于渲染的反射探针的类型。
rendererPriority渲染器优先级。
renderingLayerMask用于渲染的渲染器图层蒙版。
shadowCastingMode描述几何体是否应投射阴影。
worldBounds定义几何体的世界空间边界。用于对渲染的几何体进行剔除和排序。

MaterialPropertyBlock-CSDN博客

submeshIndex:

当网格体包含多个材质(子网格)时,子网格体 Unity 的索引会呈现。对于具有单个材质的网格,请使用值 0(这个是啥子,冲浪了好久,但是找不到)

objectToWorld:

Unity 用于将网格从对象转换为世界空间的转换矩阵。

prevObjectToWorld:

Unity 使用前面的帧变换矩阵来计算网格的运动矢量。

using UnityEngine;public class ExampleClass : MonoBehaviour
{public Material material;public Mesh mesh;void Update(){RenderParams rp = new RenderParams(material);rp.camera = Camera.main;rp.layer = 6;rp.matProps = new MaterialPropertyBlock();rp.worldBounds = new Bounds(new Vector3(0,0,0),new Vector3(1,1,1));for (int i = 0; i < 10; ++i)Graphics.RenderMesh(rp, mesh, 0, Matrix4x4.Translate(new Vector3(-6.5f + i, 0.0f, 5.0f)));}
}

RenderMeshInstanced:

使用 GPU 实例渲染网格的多个实例(但是不会传入命令)。

public static void RenderMeshInstanced(RenderParams rparams, Mesh mesh, int submeshIndex, NativeArray<T> instanceData, int instanceCount = -1, int startInstance = 0);

instanceData:

用于呈现实例的实例数据数组。

instanceCount:

要呈现的实例数。当此参数为 -1(默认值)时,Unity 会呈现从数组到末尾的所有实例。

startInstance:

要呈现的第一个实例。

using UnityEngine;public class ExampleClass : MonoBehaviour
{public Material material;public Mesh mesh;const int numInstances = 1000;struct MyInstanceData{public Matrix4x4 objectToWorld;public float myOtherData;public uint renderingLayerMask;};void Update(){RenderParams rp = new RenderParams(material);MyInstanceData[] instData = new MyInstanceData[numInstances];for (int i = 0; i < numInstances; ++i){instData[i].objectToWorld = Matrix4x4.Translate(new Vector3(-4.5f + i*3, 0.0f, 5.0f));instData[i].renderingLayerMask = (i & 1) == 0 ? 1u : 2u;}Graphics.RenderMeshInstanced(rp, mesh, 0, instData);}
}

RenderMeshIndirect:

与RenderMeshInstanced类似,但是可以从command buffer中获取命令参数,并且只需要调用一次即可执行。

public static void RenderMeshIndirect(ref RenderParams rparams, Mesh mesh, GraphicsBuffer commandBuffer, int commandCount = 1, int startCommand = 0); 

commandBuffer:

 把命令打包的buffer

using UnityEngine;public class ExampleClass : MonoBehaviour
{public Material material;public Mesh mesh;GraphicsBuffer commandBuf;GraphicsBuffer.IndirectDrawIndexedArgs[] commandData;const int commandCount = 1;public uint   num=100;void Start(){commandBuf = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, commandCount, GraphicsBuffer.IndirectDrawIndexedArgs.size);commandData = new GraphicsBuffer.IndirectDrawIndexedArgs[commandCount];}void OnDestroy(){commandBuf?.Release();commandBuf = null;}void Update(){RenderParams rp = new RenderParams(material);rp.worldBounds = new Bounds(Vector3.zero, 10000 * Vector3.one); // use tighter bounds for better FOV cullingrp.matProps = new MaterialPropertyBlock();rp.matProps.SetMatrix("_ObjectToWorld", Matrix4x4.Translate(new Vector3(0f, 0, 0)));commandData[0].indexCountPerInstance = mesh.GetIndexCount(0);commandData[0].instanceCount = num;commandBuf.SetData(commandData);Graphics.RenderMeshIndirect(rp, mesh, commandBuf, commandCount);}
}

示例shader: 

Shader "ExampleShader"
{SubShader{Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"#define UNITY_INDIRECT_DRAW_ARGS IndirectDrawIndexedArgs#include "UnityIndirect.cginc"struct v2f{float4 pos : SV_POSITION;float4 color : COLOR0;};uniform float4x4 _ObjectToWorld;v2f vert(appdata_base v, uint svInstanceID : SV_InstanceID){InitIndirectDrawArgs(0);v2f o;uint cmdID = GetCommandID(0);uint instanceID = GetIndirectInstanceID(svInstanceID);float timeOffset = _Time.y * 0.5; // 调整运动速度float xOffset = sin(timeOffset + instanceID *5) * 10; // x方向偏移float yOffset = cos(timeOffset + instanceID * 7) * 10; // y方向偏移float zOffset = sin(timeOffset + instanceID * 9) *10; // z方向偏移float4 wpos = mul(_ObjectToWorld, v.vertex + float4(instanceID%1000+ xOffset, cmdID+ yOffset, instanceID / 1000 *3+zOffset, 0));o.pos = mul(UNITY_MATRIX_VP, wpos);o.color = float4(cmdID & 1 ? 0.0f : 1.0f, cmdID & 1 ? 1.0f : 0.0f, instanceID / float(GetIndirectInstanceCount()), 0.0f);return o;}float4 frag(v2f i) : SV_Target{return i.color;}ENDCG}}
}

100w个方块能跑150帧,cool!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《编译原理》阅读笔记:p1-p3

《编译原理》学习第 1 天&#xff0c;p1-p3总结&#xff0c;总计 3 页。 一、技术总结 1.compiler(编译器) p1, But, before a program can be run, it first must be translated into a form in which it can be executed by a computer. The software systems that do thi…

c++多线程2小时速成

简介 c多线程基础需要掌握这三个标准库的使用&#xff1a;std::thread,std::mutex, andstd::async。 1. Hello, world #include <iostream> #include <thread>void hello() { std::cout << "Hello Concurrent World!\n"; }int main() {std::th…

C++学习————第十天(string的基本使用)

1、string 对象类的常见构造 (constructor)函数名称 功能说明&#xff1a; string() &#xff08;重点&#xff09; 构造空的string类对象&#xff0c;即空字符串 string(const char* s) &#xff08;重点&#xff09;…

初识C++ · 模板初阶

目录 1 泛型编程 2 函数模板 3 类模板 1 泛型编程 模板是泛型编程的基础&#xff0c;泛型我们碰到过多次了&#xff0c;比如malloc函数返回的就是泛型指针&#xff0c;需要我们强转。 既然是泛型编程&#xff0c;也就是说我们可以通过一个样例来解决类似的问题&#xff0c…

【linux】进程概念|task_struct|getpid|getppid

目录 ​编辑 1.进程的概念 进程的基本概念 进程与程序的主要区别 进程的特征 进程的状态 描述进程—PCB task_struct中的内容 查看进程 1.创建一个进程&#xff0c;运行以下代码 通过系统调用获取进程标示符 getpid()以及getppid() 1.进程的概念 进程的基本概念…

rust容器、迭代器

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff1a;点击跳转 目录 一&#xff0c;std容器 1&#xff0c;Vec&#xff08;向量、栈&#xff09; 2&#xff0c;VecDeque&#xff08;队列、双端队…

C++类和对象(基础篇)

前言&#xff1a; 其实任何东西&#xff0c;只要你想学&#xff0c;没人能挡得住你&#xff0c;而且其实学的也很快。那么本篇开始学习类和对象&#xff08;C的&#xff0c;由于作者有Java基础&#xff0c;可能有些东西过得很快&#xff09;。 struct在C中的含义&#xff1a; …

开机弹窗找不到OpenCL.dll是怎么回事,哪种修复方法更推荐

当用户在操作电脑过程中遇到系统提示“OpenCL.dll丢失”时&#xff0c;这究竟是怎么一回事呢&#xff1f;OpenCL.dll&#xff0c;作为Open Computing Language&#xff08;开放计算语言&#xff09;的重要动态链接库文件&#xff0c;它在图形处理器&#xff08;GPU&#xff09;…

爬虫:爬取豆瓣电影

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 上篇我们将到如何利用xpath的规则&#xff0c;那么这一次&#xff0c;我们将通过案例来告诉读者如何使用Xpath来定位到我们需要的数据&#xff0c;就算你不懂H5代码是怎么个嵌套或者十分复…

my-room-in-3d中的电脑,电视,桌面光带发光原理

1. my-room-in-3d中的电脑&#xff0c;电视&#xff0c;桌面光带发光原理 最近在github中&#xff0c;看到了这样的一个项目&#xff1b; 项目地址 我看到的时候&#xff0c;蛮好奇他这个光带时怎么做的。 最后发现&#xff0c;他是通过&#xff0c;加载一个 lightMap.jpg这个…

C++语法|如何写出高效的C++代码(一)|对象使用过程中背后调用了哪些方法(构造和析构过程)?

文章目录 再探拷贝构造函数和重载复制运算符实例化新对象和赋值操作强转为类类型指针和引用时临时对象的构造和析构过程 考考你问题答案 再探拷贝构造函数和重载复制运算符 实例化新对象和赋值操作 首先我们写一个类&#xff0c;实现它的拷贝构造并重载赋值运算符。 class T…

数值计算方法——大题题型总结

目录 一、绝对误差限、相对误差限 1.1 例题 1.2 解题套路 1.3 题解 二、敛散性、收敛速度 2.1 例题 2.2 解题套路 2.3 题解 三、牛顿迭代法 3.1 例题 3.2 解题套路 3.3 题解 四、割线法 4.1 例题 4.2 解题套路 ​4.3 题解 五、列主元素消去法 5.1 例题 5.…

python爬虫实战

import requests import json yesinput(输入页数&#xff1a;) yesint(yes)headers {"accept": "application/json, text/plain, */*","accept-language": "zh-CN,zh;q0.9","content-type": "application/json",…

JAVA基础之jsp标准标签

jsp动作标签实现实例化一个实体类 <jsp:useBean id"标识符" class"java类名" scope"作用范围"> 传统的java方式实例化一个实体类 Users user new Users(); <%%> id: 对象名 * class:类 创建对象时,完全限定名(包名…

Linux基础之yum和vim

目录 一、软件包管理器yum 1.1 软件包的概念 1.2 软件包的查看 1.3 软件包的安装和删除 二、Linux编辑器之vim 2.1 vim的基本概念 2.2 正常模式&#xff08;命令模式&#xff09; 2.3 底行模式 2.4 输入模式 2.5 替换模式 2.6 视图模式 2.7 总结 一、软件包管理器yu…

嵌入式Linux学习第四天启动方式学习

嵌入式Linux学习第四天 今天学习I.MX6U 启动方式详解。I.MX6U有多种启动方式&#xff0c;可以从 SD/EMMC、NAND Flash、QSPI Flash等启动。 启动方式选择 BOOT 的处理过程是发生在 I.MX6U 芯片上电以后&#xff0c;芯片会根据 BOOT_MODE[1:0]的设置来选择 BOOT 方式。BOOT_M…

Spring - 9 ( 10000 字 Spring 入门级教程 )

一&#xff1a; MyBatis XML 配置文件 Mybatis 的开发有两种方式&#xff1a; 注解XML 我们已经学习了注解的方式, 接下来我们学习 XML 的方式 MyBatis XML 的方式需要以下两步: 配置数据库连接字符串和 MyBatis写持久层代码 1.1 配置连接字符串和 MyBatis 此步骤需要进…

STC8增强型单片机开发 【第一个程序 - 点亮第一盏灯】

目录 一、创建项目 1. 创建一个新的项目 ​编辑 2. 配置开发板信息 ​编辑 3. 取消汇编配置 4. 项目结构 二、编码实现 1. 项目准备 2. 代码实现 点灯&#xff1a; 熄灯&#xff1a; 3. 编译烧录运行 配置编译输出 保存和编译代码 ​编辑 烧录 一、创建项目 1. …

OceanBase 如何实现多层面的资源隔离

OceanBase的资源隔离涵盖了多个方面&#xff0c;如物理机器间的隔离、不同租户之间的隔离、同一租户内的隔离&#xff0c;以及针对大型查询请求的隔离等。在实际应用OceanBase的过程中&#xff0c;我们经常会遇到这些操作场景或产生相关需求。这篇文章针对这些内容进行了简要的…

阿里云SLB监听虚拟服务器组时,既有部署在k8s容器里的应用,又有部署在ecs机器上的应用,k8s应用无法连接部署在ecs机器上的应用

一、背景 阿里云SLB可以添加多个监听端口&#xff0c;包括http和tcp&#xff0c;但是当被添加的后端应用&#xff0c;既有部署在k8s里&#xff0c;也有部署在ecs机器里。同一个slb下&#xff0c;这种混合方式的监听&#xff0c;会导致部署在k8s应用中的应用无法连接后者&#…