1. 概述

在之前的文章中说到,一种材质对应一次绘制调用的指令。即使是这种情况,两个三维物体使用同一种材质,但它们使用的材质参数不一样,那么最终仍然会造成两次绘制指令。原因在于,图形工作都是一种状态机,状态发生了变化,就必须进行一次绘制调用指令。

GPU实例化用于解决这样的问题:对于像草地、树木这样的物体,它们往往是数据量很大,但同时又只存在微小的差别如位置、姿态、颜色等。如果像常规物体那样进行渲染,所使用的绘制指令必然很多,资源占用必然很大。一个合理的策略就是,我们指定一个需要绘制物体对象,以及大量该对象不同的参数,然后根据参数在一个绘制调用中绘制出来——这就是所谓的GPU实例化。

2. 详论

首先,我们创建一个空的GameObject对象,并且挂接如下脚本:

using UnityEngine;

//实例化参数
public struct InstanceParam
{
public Color color;
public Matrix4x4 instanceToObjectMatrix; //实例化到物方矩阵
} [ExecuteInEditMode]
public class Note6Main : MonoBehaviour
{
public Mesh mesh;
public Material material; int instanceCount = 200;
Bounds instanceBounds; ComputeBuffer bufferWithArgs = null;
ComputeBuffer instanceParamBufferData = null; // Start is called before the first frame update
void Start()
{
instanceBounds = new Bounds(new Vector3(0, 0, 0), new Vector3(100, 100, 100)); uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
bufferWithArgs = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
int subMeshIndex = 0;
args[0] = mesh.GetIndexCount(subMeshIndex);
args[1] = (uint)instanceCount;
args[2] = mesh.GetIndexStart(subMeshIndex);
args[3] = mesh.GetBaseVertex(subMeshIndex);
bufferWithArgs.SetData(args); InstanceParam[] instanceParam = new InstanceParam[instanceCount]; for (int i = 0; i < instanceCount; i++)
{
Vector3 position = Random.insideUnitSphere * 5;
Quaternion q = Quaternion.Euler(Random.Range(0.0f, 90.0f), Random.Range(0.0f, 90.0f), Random.Range(0.0f, 90.0f));
float s = Random.value;
Vector3 scale = new Vector3(s, s, s); instanceParam[i].instanceToObjectMatrix = Matrix4x4.TRS(position, q, scale);
instanceParam[i].color = Random.ColorHSV();
} int stride = System.Runtime.InteropServices.Marshal.SizeOf(typeof(InstanceParam));
instanceParamBufferData = new ComputeBuffer(instanceCount, stride);
instanceParamBufferData.SetData(instanceParam);
material.SetBuffer("dataBuffer", instanceParamBufferData);
material.SetMatrix("ObjectToWorld", Matrix4x4.identity);
} // Update is called once per frame
void Update()
{
if(bufferWithArgs != null)
{
Graphics.DrawMeshInstancedIndirect(mesh, 0, material, instanceBounds, bufferWithArgs, 0);
}
} private void OnDestroy()
{
if (bufferWithArgs != null)
{
bufferWithArgs.Release();
} if(instanceParamBufferData != null)
{
instanceParamBufferData.Release();
}
}
}

这个脚本的意思是,设置一个网格和一个材质,通过随机获取的实例化参数,渲染这个网格的多个实例:

GPU实例化的关键接口是Graphics.DrawMeshInstancedIndirect()。Graphics对象的一系列接口是Unity的底层API,它是需要每一帧调用的。Graphics.DrawMeshInstanced()也可以实例绘制,但是最多只能绘制1023个实例。所以还是Graphics.DrawMeshInstancedIndirect()比较好。

实例化参数InstanceParam和GPU缓冲区参数bufferWithArgs都是存储于一个ComputeBuffer对象中。ComputeBuffe定义了一个GPU数据缓冲区对象,能够映射到Unity Shader中的 StructuredBuffer中。实例化参数InstanceParam存储了每个实例化对象的位置,姿态、缩放以及颜色信息,通过Material.SetBuffer(),传递到着色器中:

Shader "Custom/SimpleInstanceShader"
{
Properties
{
}
SubShader
{
Tags{"Queue" = "Geometry"} Pass
{
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma target 4.5 sampler2D _MainTex; float4x4 ObjectToWorld; struct InstanceParam
{
float4 color;
float4x4 instanceToObjectMatrix;
}; #if SHADER_TARGET >= 45
StructuredBuffer<InstanceParam> dataBuffer;
#endif //顶点着色器输入
struct a2v
{
float4 position : POSITION;
float3 normal: NORMAL;
float2 texcoord : TEXCOORD0;
}; //顶点着色器输出
struct v2f
{
float4 position: SV_POSITION;
float2 texcoord: TEXCOORD0;
float4 color: COLOR;
}; v2f vert(a2v v, uint instanceID : SV_InstanceID)
{
#if SHADER_TARGET >= 45
float4x4 instanceToObjectMatrix = dataBuffer[instanceID].instanceToObjectMatrix;
float4 color = dataBuffer[instanceID].color;
#else
float4x4 instanceToObjectMatrix = float4x4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
float4 color = float4(1.0f, 1.0f, 1.0f, 1.0f);
#endif float4 localPosition = mul(instanceToObjectMatrix, v.position);
//float4 localPosition = v.position;
float4 worldPosition = mul(ObjectToWorld, localPosition); v2f o;
//o.position = UnityObjectToClipPos(v.position);
o.position = mul(UNITY_MATRIX_VP, worldPosition);
o.texcoord = v.texcoord;
o.color = color; return o;
} fixed4 frag(v2f i) : SV_Target
{
return i.color;
} ENDCG
}
} Fallback "Diffuse"
}

这是一个改进自《Unity3D学习笔记3——Unity Shader的初步使用》的简单实例化着色器。实例化绘制往往位置并不是固定的,这意味着Shader中获取的模型矩阵UNITY_MATRIX_M一般是不正确的。因而实例化绘制的关键就在于对模型矩阵的重新计算,否则绘制的位置是不正确的。实例化的数据往往位置比较接近,所以可以先传入一个基准位置(矩阵ObjectToWorld),然后实例化数据就可以只传入于这个位置的相对矩阵(instanceToObjectMatrix)。

最终的运行结果如下,绘制了大量不同位置、不同姿态、不同大小以及不同颜色的胶囊体,并且性能基本上不受影响。

3. 参考

  1. 《Unity3D学习笔记3——Unity Shader的初步使用》
  2. Graphics.DrawMeshInstanced

具体实现代码

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

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

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

  2. Unity3D学习笔记8——GPU实例化(3)

    目录 1. 概述 2. 详论 2.1. 自动实例化 2.2. MaterialPropertyBlock 3. 参考 1. 概述 在前两篇文章<Unity3D学习笔记6--GPU实例化(1)&g ...

  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. PostgreSQL 数据库备份脚本

    PostgreSQL 数据库备份脚本 #!/bin/bash # PG家目录(/opt/postgresql/pg96/) PG_HOME=${PGHOME} # pg数据库连接信息 PG_HOST= ...

  2. GAIA-IR: GraphScope 上的并行化图查询引擎

    在本文中,我们将介绍 GraphScope 图交互式查询引擎 GAIA-IR,它支持高效的 Gremlin 语言表达的交互图查询,同时高度抽象了图上的查询计算,具有高可扩展性. 背景介绍 在海量数据的 ...

  3. [Vue]写一个简单的文件上传控件

    ​这篇将介绍如何写一个简单的基于Vue+Element的文件上传控件. 控件将具有 1. 上传队列的列表,显示文件名称,大小等信息,可以显示上传进度实时刷新 2. 取消上传 ​ 使用Element的u ...

  4. Python学习笔记: pip install 常见错误汇总

    本机环境RHEL8, Python3.9 pip install: 无法安装最新版本的包 在pypi上查看pkg的页面,因为有些pip包的版本对特定的python版本有要求 pip install e ...

  5. 全栈交叉编译X86完成过程经验分享

    1 CMAKE的交叉编译配置 主要是C和C++编译器的配置和SYSROOT的配置. set (CMAKE_SYSTEM_NAME "Linux") set (CMAKE_SYSTE ...

  6. Linux 环境变量配置的 6 种方法,建议收藏

    关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ Linux环境变量配置 在自定义安装软件的时候,经常需要配置环境变量,下面列举出各种对环境变量 ...

  7. PHP反序列化链分析

    前言 基本的魔术方法和反序列化漏洞原理这里就不展开了. 给出一些魔术方法的触发条件: __construct()当一个对象创建(new)时被调用,但在unserialize()时是不会自动调用的 __ ...

  8. font-family样式对照表

    .a { font-family: "微软雅黑" } .b { font-family: "黑体" } .c { font-family: "楷体&q ...

  9. Nginx分片限流

    一.全局限流 在http节点中添加 # 创建限流规则 limit_req_zone $binary_remote_addr zone=addr:10m rate=1r/s;# 客户端进行限流 # li ...

  10. linux篇-rpm包安装mysql数据库

    3.1上传以下两个rpm包到服务器上 MySQL-server-5.6.27-1.el6.x86_64.rpm MySQL-client-5.6.27-1.el6.x86_64.rpm 3.2卸载一个 ...