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. 参考

  1. 《Unity3D学习笔记6——GPU实例化(1)》
  2. 《Unity3D学习笔记6——GPU实例化(2)》
  3. Creating shaders that support GPU instancing
  4. MaterialPropertyBlock

具体实现代码

Unity3D学习笔记8——GPU实例化(3)的更多相关文章

  1. Unity3D学习笔记7——GPU实例化(2)

    目录 1. 概述 2. 详论 2.1. 实现 2.2. 解析 3. 参考 1. 概述 在上一篇文章<Unity3D学习笔记6--GPU实例化(1)>详细介绍了Unity3d中GPU实例化的 ...

  2. Unity3D学习笔记6——GPU实例化(1)

    目录 1. 概述 2. 详论 3. 参考 1. 概述 在之前的文章中说到,一种材质对应一次绘制调用的指令.即使是这种情况,两个三维物体使用同一种材质,但它们使用的材质参数不一样,那么最终仍然会造成两次 ...

  3. unity3d学习笔记(一) 第一人称视角实现和倒计时实现

    unity3d学习笔记(一) 第一人称视角实现和倒计时实现 1. 第一人称视角 (1)让mainCamera和player(视角对象)同步在一起 因为我们的player是生成的,所以不能把mainCa ...

  4. Unity3D学习笔记2——绘制一个带纹理的面

    目录 1. 概述 2. 详论 2.1. 网格(Mesh) 2.1.1. 顶点 2.1.2. 顶点索引 2.2. 材质(Material) 2.2.1. 创建材质 2.2.2. 使用材质 2.3. 光照 ...

  5. 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. 渲染 ...

  6. Unity3D学习笔记4——创建Mesh高级接口

    目录 1. 概述 2. 详论 3. 其他 4. 参考 1. 概述 在文章Unity3D学习笔记2--绘制一个带纹理的面中使用代码的方式创建了一个Mesh,不过这套接口在Unity中被称为简单接口.与其 ...

  7. Unity3D学习笔记12——渲染纹理

    目录 1. 概述 2. 详论 3. 问题 1. 概述 在文章<Unity3D学习笔记11--后处理>中论述了后处理是帧缓存(Framebuffer)技术实现之一:而另外一个帧缓存技术实现就 ...

  8. 一步一步学习Unity3d学习笔记系1.3 英雄联盟服务器集群架构猜想

    说到了网游那就涉及到服务器了,时下最火的属英雄联盟了,我也是它的粉丝,每周必撸一把,都说小撸怡情,大撸伤身,强撸灰飞烟灭,也告诫一下同仁们,注意身体,那么他的服务器架构是什么呢,给大家分享一下, 具体 ...

  9. Unity3D 学习笔记

    不是什么技术文章,纯粹是我个人学习是遇到一些觉得需要注意的要点,当成笔记. 1.关于调试,在Android下无法断点,Debug也无法查看,查看日志方法可以启动adb的log功能,或者自己写个GUI控 ...

随机推荐

  1. hadoop 运行测试

    hadoop集群运行 需要提前配置配置文件 slave节点用户得是hadoop,/usr/local/src的所有文件得属于hadoop 三台虚拟机关闭setenforce与防火墙,并且配置域名解析 ...

  2. Oracle 数据库表删除重复数据

    删除重复数据并保留一条 方法一 1.建立临时表,记录重复的数据 create table 临时表 as select a.字段1,a.字段2,max(a.rowid) as dataid from 原 ...

  3. Apollo的docker配置详解步骤

    Apollo 的docker配置 基础环境 centOS7 + Docker服务 + mysql服务 1. 下载Apollo的包 git clone https://github.com/ctripc ...

  4. [没接触过kubevirt?]15分钟快速入门kubevirt

    @ 目录 本文介绍 前言 环境准备 详细版 搭建步骤 安装KubeVirt 安装virtctl客户端工具 创建VirtualMachine 启动VirtualMachineInstance 启动和停止 ...

  5. C#/VB.NET 实现Word和ODT文档相互转换

    ODT文档格式一种开放文档格式(OpenDocument Text).通常,ODT格式的文件可以使用LibreOffice Writer.MS Word或其他一些文档编辑器来打开.我们在处理文档时,可 ...

  6. spring4+hibernate4 整合

    1.web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app version=" ...

  7. linux下三种服务开机自启的方式

    方式一.二.三适用于ubuntu,centos推荐使用方式二.方式三 方式一 在ubuntu系统中,如果你使用的apt方式安装的软件,可以使用如下方式直接添加服务的开机自启, 如果你是手动解压缩官网下 ...

  8. 【NodeJS】替换模糊查询字符里包含的正则关键字

    问题:正则匹配时字符串中包含了一些特殊字符,导致查询失败 例如,下面的字符包含了( 和 ),这在正则中属于特殊字符 (-)-magnocurarine 正则中的特殊字符如下图 思路: 1.映射查询字符 ...

  9. 个人冲刺(五)——体温上报app(二阶段)

    冲刺任务:完成用户登录和随机验证码功能 loginActivity.java package com.example.helloworld; /** * 纯粹实现登录注册功能,其它功能都被注释掉了 * ...

  10. uni-app 中实现 onLaunch 异步回调后执行 onLoad 最佳实践

    前言 好久没写博客了,由于公司业务需要,最近接触uiapp比较多,一直想着输出一些相关的文章.正好最近时间富余,有机会来一波输出了. 问题描述 在使用 uni-app 开发项目时,会遇到需要在 onL ...