Unity大面积草地渲染——3、使用GPUInstancing渲染大面积的草

2023-10-07 03:20

本文主要是介绍Unity大面积草地渲染——3、使用GPUInstancing渲染大面积的草,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录
1、Shader控制一棵草的渲染
2、草地的动态交互
3、使用GPUInstancing渲染大面积的草
4、对大面积草地进行区域剔除和显示等级设置

大家好,我是阿赵。
这里开始讲大面积草地渲染的第三个部分,使用GPU Instancing来渲染大面积的草。

一、在不使用GPU Instancing时的渲染情况

为了能看性能明显一点,我写了个工具,在10乘10的范围内生成了一万棵草。
在这里插入图片描述

由于我的电脑显卡不算很差,所以可以看得到fps还能维持一百多。看看下面的参数,可以发现,渲染的三角形数有120多万,Batches有8517次,Saved by batching是0次,然后实际SetPass有8次。
这些数据代表了什么呢?
首先,为什么Batches有8千多次呢?
这是因为,我这里所有的草都是使用了同一个材质球,而同一个材质球按道理来说是可以合并渲染的。Batches指的是在运行过程中,通过消耗CPU的性能,每一帧动态的去判断哪些模型可以合并渲染,然后进行合并,最后推送到渲染管线。虽然我生成了10000棵草,但并不是所有草都进入到摄像机的裁剪视锥,只有8000多棵进入了,所以Batches是8000多。
在这里插入图片描述

如果把摄像机拉远一点,然后把地面隐藏了,在看到10000棵草的情况下,这个Batches就是10001了。10000棵草,再加一个背景色。

然后,为什么Saved by batching是0次呢?
所谓的Saved by batching,指的是计算完一次的合并数据,可以保存下来,下一帧不需要再重新计算,就可以直接的渲染了。因为这些草都是自然渲染的,没有经过任何技术的预处理,所以并没有可以保存的合并渲染数据,只能每一帧都重复的去计算。所以Saved by batching是0次,也就是完全没有。

最后,SetPass calls是8次,说明了实际上GPU执行了8次的渲染。由于GPU是并行计算的,根据显卡的性能,一次渲染可能可以渲染数百万个三角形,所以在上面已经Batches合批的情况下,显卡可以把所有草一次渲染完。从上面拉远了之后把地面隐藏的结果看,SetPass calls变成了2次。这是因为其中有6次是那个地面渲染导致的。没有了地面之后,所有草一次,背景色一次,所以是2次。

所以,最后得到的结果是,如果在显卡性能还比较好的情况下,这次的渲染并没有什么压力,因为只有2次SetPass。但对CPU的压力比较大,每一帧需要计算上万次的合批数据。这里的帧数看着还过得去,是因为我的电脑CPU也还过得去,是第10代的i7处理器。
至于面数的问题,120万面是有点多的,这个问题的解决办法我会在下一篇文章里面说,这里先不讨论。
从经验上来说,我们做游戏,特别是手机游戏,Batches的数量一般要控制在100左右,不然CPU每一帧都高负荷的计算,虽然可能不掉帧,但发热是肯定的。可能游戏运行5分钟,你的手机就已经烫到拿不住了。

于是,我们要想办法去考虑一下,怎样把合批的次数给降低?
Unity其实一直有提供2种合批方式,一种是刚才通过CPU的动态合批,另外一种是静态合批。
静态合批的使用很简单,直接选择需要合批的东西,然后勾选Batching Static选项就可以了。
在这里插入图片描述

于是也可以试试,如果用静态合批,会有怎样的结果:
在这里插入图片描述

从数据上看,Batches大幅度的降低了,好像结果不错。但仔细看场景里面的草,会发现颜色发生了变化。
然后静态合批还有一个很重要的特点,就是参与了静态合批的模型,它们的网格是已经合并成一个大网格了,所以在运行的时候,你可以显示隐藏某个模型,但不能移动、旋转或者缩放某个模型了。
这里我勾选了静态合批之后,风吹草的动画和球体交互的动画还是可以播放的,这是因为虽然合并成了大网格,但Shader里面的顶点程序还是能根据顶点坐标正常的计算。

二、使用GPU Instancing时的渲染情况

从这里开始,我们试一下使用GPU Instancing功能,看看效果如何。
在这里插入图片描述

这是使用了GPU Instancing之后的性能情况,可以看到,Batches降低到24,根据上面的经验。然后Saved by batching变成了8000多个,三角面数是120万,SetPass数还是8
在这里插入图片描述

和上面的做法一样,拉远了摄像机,并且把地面隐藏了,可以看到实际Batches是21,SetPass calls是2。
从结果可以看出,使用了GPU Instancing之后,渲染的三角面数并没有降低,但Batches降低了,比之前的静态合批还低,这是因为静态合批的大模型网格还是要遵循Unity本身一个网格的顶点数不能超过65536个的规则,而GPU Instancing是没有这个限制,但同一个渲染批次渲染的物体最大只能1023个。
在Batches大幅度降低之后,草地上的草从外观上看并没有什么变化,而且每棵草都可以移动、旋转和缩放,没有任何限制。

三、怎样使用GPU Instancing

1、Shader修改

在这里插入图片描述

使用GPU Instancing的方式很简单,在材质球上面勾选Enable GPU Instancing,就可以了。
不过如果你自己写Shader,会发现材质球上面根本没有这个选项。如果想出现这个选项,需要在Shader里面加点东西:
(1)#pragma multi_compile_instancing
(2)在appdata和v2f结构体里面添加UNITY_VERTEX_INPUT_INSTANCE_ID
(3)在顶点程序和片段程序里面添加UNITY_SETUP_INSTANCE_ID

2、程序调用渲染方法

刚刚介绍的方法,是直接把勾选了Enable GPU Instancing的模型放在场景里面渲染,还可以有另外一种方式,不需要在场景里面摆放模型,而是通过使用渲染的API,传入需要渲染的网格模型、材质球,还有需要摆放模型的Matrix4x4矩阵,让程序自己去渲染。
可以使用的API有2种,各位可以查阅Unity自带的帮助文档:

1.Graphics.DrawMeshInstanced

在这里插入图片描述

使用方法举例:

Graphics.DrawMeshInstanced(mesh, 0, mat, matrixs, matrixs.Length);

之前漏了说,这个API一次渲染的长度是不能超过1023的,所以这里的matrixs矩阵数组,我们要自己计算一下长度,如果超出了1023个,就要新建数组来继续存放。

2.CommandBuffer.DrawMeshInstanced

在这里插入图片描述

使用方法举例:

CommandBuffer mBuff = new CommandBuffer();
mBuff.DrawMeshInstanced(mesh, 0, mat,0, matrixs, matrixs.Length);
Camera.main.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, mBuff);

四、完整Shader

Shader "azhao/Grass"
{Properties{_MainTex("MainTex", 2D) = "white" {}_hmin("hmin", Range(0 , 1)) = 0_hmax("hmax", Range(0 , 1)) = 1_hOffset("hOffset", Range(-1 , 1)) = 0_vmin("vmin", Range(0 , 1)) = 0_vmax("vmax", Range(0 , 1)) = 1_vOffset("vOffset", Range(-5 , 5)) = 0_topCol("topCol", Color) = (0,1,0,0)_windOffset("windOffset", Vector) = (0,0,0,0)_bottomCol("bottomCol", Color) = (0,0,0,0)_roleMul("roleMul", Range(0 , 10)) = 0_roleHOffset("roleHOffset", Range(0 , 10)) = 0}SubShader{Tags{"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout" }Cull OffPass{CGPROGRAM#pragma vertex vert#pragma fragment frag//要使用GPU Instancing必须加上这句#pragma multi_compile_instancing#include "UnityShaderVariables.cginc"#pragma target 3.0#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;//要使用GPU Instancing必须加上这句UNITY_VERTEX_INPUT_INSTANCE_ID};struct v2f{                float4 pos : SV_POSITION;float2 uv : TEXCOORD0;float3 centerPos : TEXCOORD1;float3 worldPos : TEXCOORD2;float3 hvVal : TEXCOORD3;//要使用GPU Instancing必须加上这句UNITY_VERTEX_INPUT_INSTANCE_ID};uniform float _hmin;uniform float _hmax;uniform float _vmin;uniform float _vmax;uniform float _vOffset;uniform float2 _windOffset;uniform float3 rolePos;uniform float _roleMul;uniform float _hOffset;uniform float _roleHOffset;uniform sampler2D _MainTex;uniform float4 _MainTex_ST;uniform float4 _topCol;uniform float4 _bottomCol;SamplerState sampler_MainTex;v2f vert (appdata v){v2f o;//要使用GPU Instancing必须加上这句UNITY_SETUP_INSTANCE_ID(v);o.uv = TRANSFORM_TEX(v.uv, _MainTex);o.centerPos = mul(unity_ObjectToWorld, float4(float3(0, 0, 0), 1)).xyz;o.worldPos = mul(unity_ObjectToWorld, v.vertex);float hVal = smoothstep(_hmin, _hmax, o.worldPos.y - o.centerPos.y);float vVal = smoothstep(_vmin, _vmax, distance(o.worldPos.xz, o.centerPos.xz));float hvVal = hVal * vVal;o.hvVal = float3(hVal, vVal, hvVal);float hVertexOffset = hvVal * _hOffset;float2 vVertexOffset = (o.worldPos.xz - o.centerPos.xz)*hvVal*_vOffset;float2 wind = _windOffset * hVal*_SinTime.w;float roleDis = (1 - distance(o.worldPos.xz, rolePos.xz));float2 roleNor = (o.worldPos.xz - rolePos.xz)*step(0, roleDis)*(roleDis*_roleMul);float2 rolePosXZOffset = vVertexOffset + wind * (1 - roleNor) + roleNor * hVal;float rolePosYOffset = hVertexOffset - saturate(roleDis*_roleHOffset);o.pos = UnityObjectToClipPos(v.vertex+float3(rolePosXZOffset.x, rolePosYOffset, rolePosXZOffset.y));return o;}half4 frag (v2f i) : SV_Target{//要使用GPU Instancing必须加上这句UNITY_SETUP_INSTANCE_ID(i);// sample the texturehalf4 col = tex2D(_MainTex, i.uv);half3 finalCol = col.rgb * _topCol.rgb*i.hvVal.z + col.rgb;finalCol = clamp(finalCol*i.hvVal.x + _bottomCol * (1 - i.hvVal.x)*finalCol,  half3(0, 0, 0), half3(1, 1, 1));half alpha = col.a;clip(alpha - 0.5);return half4(finalCol,alpha);}ENDCG}/*//为了产生影子,加多一个pass,不过在大量渲染的情况下,不建议加阴影,性能实在差Pass {Name "ShadowCaster"Tags { "LightMode" = "ShadowCaster" }CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;//要使用GPU Instancing必须加上这句UNITY_VERTEX_INPUT_INSTANCE_ID};struct v2f{float4 pos : SV_POSITION;float2 uv : TEXCOORD0;float3 centerPos : TEXCOORD1;float3 worldPos : TEXCOORD2;float3 hvVal : TEXCOORD3;//要使用GPU Instancing必须加上这句UNITY_VERTEX_INPUT_INSTANCE_ID};uniform float _hmin;uniform float _hmax;uniform float _vmin;uniform float _vmax;uniform float _vOffset;uniform float2 _windOffset;uniform float3 rolePos;uniform float _roleMul;uniform float _hOffset;uniform float _roleHOffset;uniform sampler2D _MainTex;uniform float4 _MainTex_ST;uniform float4 _topCol;uniform float4 _bottomCol;SamplerState sampler_MainTex;v2f vert(appdata v){v2f o;//要使用GPU Instancing必须加上这句UNITY_SETUP_INSTANCE_ID(v);o.uv = TRANSFORM_TEX(v.uv, _MainTex);o.centerPos = mul(unity_ObjectToWorld, float4(float3(0, 0, 0), 1)).xyz;o.worldPos = mul(unity_ObjectToWorld, v.vertex);float hVal = smoothstep(_hmin, _hmax, o.worldPos.y - o.centerPos.y);float vVal = smoothstep(_vmin, _vmax, distance(o.worldPos.xz, o.centerPos.xz));float hvVal = hVal * vVal;o.hvVal = float3(hVal, vVal, hvVal);float hVertexOffset = hvVal * _hOffset;float2 vVertexOffset = (o.worldPos.xz - o.centerPos.xz)*hvVal*_vOffset;float2 wind = _windOffset * hVal*_SinTime.w;float roleDis = (1 - distance(o.worldPos.xz, rolePos.xz));float2 roleNor = (o.worldPos.xz - rolePos.xz)*step(0, roleDis)*(roleDis*_roleMul);float2 rolePosXZOffset = vVertexOffset + wind * (1 - roleNor) + roleNor * hVal;float rolePosYOffset = hVertexOffset - saturate(roleDis*_roleHOffset);o.pos = UnityObjectToClipPos(v.vertex + float3(rolePosXZOffset.x, rolePosYOffset, rolePosXZOffset.y));return o;}half4 frag(v2f i) : SV_Target{//要使用GPU Instancing必须加上这句UNITY_SETUP_INSTANCE_ID(i);// sample the texturehalf4 col = tex2D(_MainTex, i.uv);clip(col.a - 0.5);return col;}ENDCG}*/}
}

这篇关于Unity大面积草地渲染——3、使用GPUInstancing渲染大面积的草的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/155708

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念