Unity3D学习笔记8——GPU实例化(3)
1. 概述
在前两篇文章《Unity3D学习笔记6——GPU实例化(1)》《Unity3D学习笔记6——GPU实例化(2)》分别介绍了通过简单的顶点着色器+片元着色器,以及通过表面着色器实现GPU实例化的过程。而在Unity的官方文档Creating shaders that support GPU instancing里,也提供了一个GPU实例化的案例,这里就详细论述一下。
2. 详论
2.1. 自动实例化
一个有意思的地方在于,Unity提供的标准材质支持自动实例化,而不用像《Unity3D学习笔记6——GPU实例化(1)》《Unity3D学习笔记6——GPU实例化(2)》那样额外编写脚本和Shader。并且,会自动将transform,也就是模型矩阵作为每个实例的属性。
照例,还是编写一个脚本挂到一个空的GameObject对象上:
using UnityEngine;
public class Note8Main : MonoBehaviour
{
public Mesh mesh;
public Material material;
public int instanceCount = 5000;
// Start is called before the first frame update
void Start()
{
MaterialPropertyBlock props = new MaterialPropertyBlock();
for (int i = 0; i < instanceCount; i++)
{
GameObject go = new GameObject();
go.name = i.ToString();
MeshFilter mf = go.AddComponent<MeshFilter>();
mf.mesh = mesh;
MeshRenderer mr = go.AddComponent<MeshRenderer>();
mr.material = material;
go.transform.position = Random.insideUnitSphere * 5;
go.transform.eulerAngles = new Vector3(Random.Range(0.0f, 90.0f), Random.Range(0.0f, 90.0f), Random.Range(0.0f, 90.0f));
float s = Random.value;
go.transform.localScale = new Vector3(s, s, s);
go.transform.parent = gameObject.transform;
}
}
// Update is called once per frame
void Update()
{
}
}
这个脚本的意思是,给挂接的GameObject下新建很多GameObject,它们使用我们传入的Mesh和Material,但是位置、姿态和大小是随机的。传入的Mesh使用Unity自带的胶囊体,Material使用Unity的标准材质。运行结果如下:

这个时候Unity还没有自动实例化,打开Frame Debug就可以看到:

这个时候我们可以在使用的材质上勾选打开实例化的选项:

再次运行,就会在Frame Debug看到Unity实现了自动实例化,绘制的批次明显减少,并且性能会有所提升:

可以看到确实是自动进行实例化绘制了,但是这种方式却似乎存在实例化个数的上限,所有的实例化数据还是分成了好几个批次进行绘制。与《Unity3D学习笔记6——GPU实例化(1)》《Unity3D学习笔记6——GPU实例化(2)》提到的通过底层接口Graphic进行实例化绘制相比,效率还是要低一些。
2.2. MaterialPropertyBlock
自动实例化只能将transform,也就是模型矩阵作为每个实例的属性。如果需要增加自己的实例属性,就需要使用MaterialPropertyBlock,也就是材质属性块。
修改上面的脚本:
using UnityEngine;
public class Note8Main : MonoBehaviour
{
public Mesh mesh;
public Material material;
public int instanceCount = 5000;
// Start is called before the first frame update
void Start()
{
MaterialPropertyBlock props = new MaterialPropertyBlock();
for (int i = 0; i < instanceCount; i++)
{
GameObject go = new GameObject();
go.name = i.ToString();
MeshFilter mf = go.AddComponent<MeshFilter>();
mf.mesh = mesh;
MeshRenderer mr = go.AddComponent<MeshRenderer>();
mr.material = material;
float r = Random.Range(0.0f, 1.0f);
float g = Random.Range(0.0f, 1.0f);
float b = Random.Range(0.0f, 1.0f);
props.SetColor("_Color", new Color(r, g, b));
mr.SetPropertyBlock(props);
go.transform.position = Random.insideUnitSphere * 5;
go.transform.eulerAngles = new Vector3(Random.Range(0.0f, 90.0f), Random.Range(0.0f, 90.0f), Random.Range(0.0f, 90.0f));
float s = Random.value;
go.transform.localScale = new Vector3(s, s, s);
go.transform.parent = gameObject.transform;
}
}
// Update is called once per frame
void Update()
{
}
}
脚本使用的材质,其使用的Shader如下,可以直接在Standard Surface Shader的基础上改:
Shader "Custom/HiddenSurfaceIntanceShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
//fixed4 _Color;
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Albedo comes from a texture tinted by color
//fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
关键的代码在于Unity内置宏UNITY_INSTANCING_BUFFER_START和UNITY_INSTANCING_BUFFER_END、UNITY_DEFINE_INSTANCED_PROP定义了实例化属性,在着色器中,通过内置宏UNITY_ACCESS_INSTANCED_PROP来获取这个属性值。这个实例化属性也就是脚本代码中MaterialPropertyBlock传入的颜色值。
查看Unity Shader源代码,这四个用于实例化的宏封装的是一个cbuffer数组,cbuffer就是hlsl的常量缓冲区:
#define UNITY_INSTANCING_CBUFFER_SCOPE_BEGIN(name) cbuffer name {
#define UNITY_INSTANCING_CBUFFER_SCOPE_END }
#define UNITY_INSTANCING_BUFFER_START(buf) UNITY_INSTANCING_CBUFFER_SCOPE_BEGIN(UnityInstancing_##buf) struct {
#define UNITY_INSTANCING_BUFFER_END(arr) } arr##Array[UNITY_INSTANCED_ARRAY_SIZE]; UNITY_INSTANCING_CBUFFER_SCOPE_END
#define UNITY_DEFINE_INSTANCED_PROP(type, var) type var;
#define UNITY_ACCESS_INSTANCED_PROP(arr, var) arr##Array[unity_InstanceID].var
运行的结果如下:

可以看到除了纹理,每一个胶囊体还获取了随机赋予给材质的颜色,也就是我们设置的颜色成为了实例化属性数据。MaterialPropertyBlock主要由Graphics.DrawMesh和Renderer.SetPropertyBlock使用,在希望绘制具有相同材质,但属性略有不同的多个对象时可使用它。
个人认为使用MaterialPropertyBlock自动实例化性能比不上使用Graphics.DrawMeshInstancedIndirect(),但是它有个优点是实例化的要求没那么高,Graphics.DrawMeshInstancedIndirect()要求使用同一mesh,同一贴图;但是MaterialPropertyBlock没这个要求,只要是同一材质,任何属性不一样都可以用,在减少绘制批次的同时还能减少材质的个数。
3. 参考
- 《Unity3D学习笔记6——GPU实例化(1)》
- 《Unity3D学习笔记6——GPU实例化(2)》
- Creating shaders that support GPU instancing
- MaterialPropertyBlock
Unity3D学习笔记8——GPU实例化(3)的更多相关文章
- Unity3D学习笔记7——GPU实例化(2)
目录 1. 概述 2. 详论 2.1. 实现 2.2. 解析 3. 参考 1. 概述 在上一篇文章<Unity3D学习笔记6--GPU实例化(1)>详细介绍了Unity3d中GPU实例化的 ...
- Unity3D学习笔记6——GPU实例化(1)
目录 1. 概述 2. 详论 3. 参考 1. 概述 在之前的文章中说到,一种材质对应一次绘制调用的指令.即使是这种情况,两个三维物体使用同一种材质,但它们使用的材质参数不一样,那么最终仍然会造成两次 ...
- unity3d学习笔记(一) 第一人称视角实现和倒计时实现
unity3d学习笔记(一) 第一人称视角实现和倒计时实现 1. 第一人称视角 (1)让mainCamera和player(视角对象)同步在一起 因为我们的player是生成的,所以不能把mainCa ...
- Unity3D学习笔记2——绘制一个带纹理的面
目录 1. 概述 2. 详论 2.1. 网格(Mesh) 2.1.1. 顶点 2.1.2. 顶点索引 2.2. 材质(Material) 2.2.1. 创建材质 2.2.2. 使用材质 2.3. 光照 ...
- Unity3D学习笔记3——Unity Shader的初步使用
目录 1. 概述 2. 详论 2.1. 创建材质 2.2. 着色器 2.2.1. 名称 2.2.2. 属性 2.2.3. SubShader 2.2.3.1. 标签(Tags) 2.2.3.2. 渲染 ...
- Unity3D学习笔记4——创建Mesh高级接口
目录 1. 概述 2. 详论 3. 其他 4. 参考 1. 概述 在文章Unity3D学习笔记2--绘制一个带纹理的面中使用代码的方式创建了一个Mesh,不过这套接口在Unity中被称为简单接口.与其 ...
- Unity3D学习笔记12——渲染纹理
目录 1. 概述 2. 详论 3. 问题 1. 概述 在文章<Unity3D学习笔记11--后处理>中论述了后处理是帧缓存(Framebuffer)技术实现之一:而另外一个帧缓存技术实现就 ...
- 一步一步学习Unity3d学习笔记系1.3 英雄联盟服务器集群架构猜想
说到了网游那就涉及到服务器了,时下最火的属英雄联盟了,我也是它的粉丝,每周必撸一把,都说小撸怡情,大撸伤身,强撸灰飞烟灭,也告诫一下同仁们,注意身体,那么他的服务器架构是什么呢,给大家分享一下, 具体 ...
- Unity3D 学习笔记
不是什么技术文章,纯粹是我个人学习是遇到一些觉得需要注意的要点,当成笔记. 1.关于调试,在Android下无法断点,Debug也无法查看,查看日志方法可以启动adb的log功能,或者自己写个GUI控 ...
随机推荐
- rocketmq消息及流程
1.为什么用mq 优势 主要有3个: 应用解耦(降低微服务之间的关联). 异步提速(微服务拿到mq消息后同时工作). 削峰填谷(可以消息堆积) 劣势 系统可用性降低(MQ一旦宕机整个系统不可用) 复杂 ...
- ThinkPhP $map用法
ThinkPHP内置了非常灵活的查询方法,可以快速的进行数据查询操作,查询条件可以用于CURD等任何操作,作为where方法的参数传入即可,下面来一一讲解查询语言的内涵.查询方式ThinkPHP可以支 ...
- 攻防世界-MISC:SimpleRAR
这是攻防世界新手练习区的第十题,题目如下: 点击下载附件1,是一个RAR文件,解压一下,得到flag.txt文件,激动的点进去看一下 好吧,就知道没有这么简单,用010editor打开这个RAR文件, ...
- 自学java的困难
在自学的一些基础阶段,倒是没什么太大的问题,但是在想搞一个项目的时候,就显得手足无措了.因为,很多博主讲的的那些,都行需要一定的条件,比如前端框架,数据库的数据等等. 简单一点的SSM框架整合相对简单 ...
- MySQL性能优化 - 别再只会说加索引了
MySQL性能优化 MySQL性能优化我们可以从以下四个维度考虑:硬件升级.系统配置.表结构设计.SQL语句和索引. 从成本上来说:硬件升级>系统配置>表结构设计>SQL语句及索引, ...
- 一文读懂 Kubernetes 容器网络
点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 在Kubernetes中要保证容器之间网络互通,网络至关 ...
- CRM项目的整理---第一篇
CRM:cunstomer relationship management 客户管理系统 1.项目的使用者:销售 班主任 讲师 助教 2.项目的需求分析 2.1.注册 2.2.登录 2.3 ...
- numpy学习笔记Ⅰ
一直被numpy和matplotlib困扰,打算好好学习一下,也是从自己的观点,学对自己帮助最大的部分 主要参考<https: www.runoob.com="" numpy ...
- kali linux安装后乱码的解决方法
操作系统是5.3 解决方法是在终端执行命令: sudo apt-get install ttf-wqy-zenhei
- intelij idea 好用的插件
简介 记录一下平时使用的插件 Foldable ProjectView 隐藏目录或文件 One Dark theme 主题比较好用 Gitmoji Plus: Commit Button 在 comm ...