Unity3D学习笔记6——GPU实例化(1)
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. 参考
Unity3D学习笔记6——GPU实例化(1)的更多相关文章
- Unity3D学习笔记7——GPU实例化(2)
目录 1. 概述 2. 详论 2.1. 实现 2.2. 解析 3. 参考 1. 概述 在上一篇文章<Unity3D学习笔记6--GPU实例化(1)>详细介绍了Unity3d中GPU实例化的 ...
- Unity3D学习笔记8——GPU实例化(3)
目录 1. 概述 2. 详论 2.1. 自动实例化 2.2. MaterialPropertyBlock 3. 参考 1. 概述 在前两篇文章<Unity3D学习笔记6--GPU实例化(1)&g ...
- 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控 ...
随机推荐
- python基础练习题(题目 画菱形)
day15 --------------------------------------------------------------- 实例022:画菱形 题目 打印出如下图案(菱形):. * * ...
- 【面试普通人VS高手系列】Dubbo的服务请求失败怎么处理?
今天分享的面试题,几乎是90%以上的互联网公司都会问到的问题. "Dubbo的服务请求失败怎么处理"? 对于这个问题,我们来看一下普通人和高手的回答. 普通人: 嗯- 我记得, D ...
- Luffy /2/ 后台数据库配置&前台创建配置
目录 二次封装Response 后台数据库配置 命令操作 pycharm连接 django操作mysql 方式一 方式二 user表设计 前台创建及配置 全局css样式配置 配置文件配置 二次封装Re ...
- petite-vue源码剖析-沙箱模型
在解析v-if和v-for等指令时我们会看到通过evaluate执行指令值中的JavaScript表达式,而且能够读取当前作用域上的属性.而evaluate的实现如下: const evalCache ...
- zookeeper篇-watch命令
点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. 每个路径节点都有一个watcher监控,当该节点数据改变时(CRUD), ...
- linux下can调试工具canutils安装与使用
0. 编译环境所需要的工具 libsocketcan-0.0.11.tar.bz2 canutils-4.0.6.tar.bz2 下载路径 https://public.pengutronix.de ...
- QtWebEngine性能问题
目录 1. 概述 2. 详论 2.1. 图形属性设置 2.2. 硬件加速设置 2.3. Qt6 3. 参考 1. 概述 Qt的Qt WebEngine模块是基于Chromium项目,但是本人在使用QW ...
- 新鲜出炉:appium2.0+ 单点触控和多点触控新的解决方案
在 appium2.0 之前,在移动端设备上的触屏操作,单手指触屏和多手指触屏分别是由 TouchAction 类,Multiaction 类实现的. 在 appium2.0 之后,这 2 个方法将会 ...
- js动态生成vue组件
代码奉上 install (Vue, options) { Vue.prototype.$message = function (message){ let Constructor = Vue.ext ...
- REST APIs
REST APIs 旨在通过HTTP 的动作语义METHOD, 以替代各种传统CRUD 操作所带来的命名问题,例如 "/userAdd"."/userDelete&quo ...