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

news2024/11/24 19:34:37

大家好,我是阿赵。
这里开始讲大面积草地渲染的第三个部分,使用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 Off

        Pass
        {
            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 texture
                half4 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 texture
			half4 col = tex2D(_MainTex, i.uv);
			clip(col.a - 0.5);
			return col;
		}
			ENDCG

		}
		*/
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/508272.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python】本地版 Whisper 自动转录器(附源码网址)

目 录 一、实时自动语音转录器简介 二、开源Whisper实时转录器 三、pyinstaller 打包发布exe应用程序四、修改版源代码 一、实时自动语音转录器简介 实时自动语音转录器是一种能够自动将语音信号转换为文字的应用程序。…

【机器学习】机器学习相关概念简述

一、什么是机器学习 机器学习指的是,在没有明确设置的情况下,使得计算机拥有自我学习能力的领域。 二、监督学习和无监督学习 2.1 监督学习 监督学习是指,我们给予算法一个数据集,其中的数据包含了若干个标签。一个例子就是给…

pdf怎么转换成jpg图片

pdf怎么转换成jpg图片?PDF格式可以在电脑和手机上使用,而且其内容不会被篡改。同时,PDF的通用兼容性较强,而且PDF文件操作简单,易于创作。PDF文件格式应用较为广泛。在我们创建PDF文件时,无论在何处查看数据…

OpenGL超级宝典第七章学习笔记:顶点处理与绘图命令

前言 本篇在讲什么 OpenGL蓝宝书第七章学习笔记 本篇适合什么 适合初学OpenGL的小白 本篇需要什么 对C语法有简单认知 对OpenGL有简单认知 最好是有OpenGL超级宝典蓝宝书 依赖Visual Studio编辑器 本篇的特色 具有全流程的图文教学 重实践,轻理论&…

Playground AI:免费绘画图像创作工具

【产品介绍】 Playground AI是一个免费的在线 AI绘画 图像创作工具。你可以用它来创作艺术作品、社交媒体帖子、演示文稿、海报、视频、logo 等等。 Playground AI 的核心技术是基于深度学习的图像生成模型,它可以根据你的输入文字或图片,自动合成出高质…

数组的应用

数组的应用 一、数组的定义二、切片替换删除数值元素 二、数组追加元素三、数组与函数相结合 一、数组的定义 相当于一串数据的集合,以空格相间隔的字符串列表,两边用括号括起来 echo ${shuzu[]}中的代表着显示所有的下标内容,当然&#…

【C++初阶】类和对象(三)

​ ​📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:C初阶 🎯长路漫漫浩浩,万事皆有期待 上一篇博客:【C初阶】…

无线蓝牙耳机佩戴舒适的有哪几款?佩戴舒适的蓝牙耳机推荐

自从真无线蓝牙耳机产品推出以来,它已经逐渐成为了当代年轻人外出必带的随身数码产品。虽然市面上不缺好产品,但大家对于蓝牙耳机的佩戴舒适度害死更加重要的,下面就来分享几款佩戴舒适的蓝牙耳机吧。 一、南卡小音舱Lite2蓝牙耳机 参考价格…

Android studio单独导入官方例程camera-calibration

1.官方例程camera-calibration 2.将官方例程camera-calibration copy到AndroidStudioProjects项目目录下 3修改AndroidManifest.xml <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android“http://schemas.android.com/apk/res/andr…

2007年计算机真题

2007年计算机真题 数学基础部分 一、用逻辑符号表达下列语句&#xff08;每小题 2 分&#xff0c;共 4 分&#xff09; 1&#xff0e;分别用两种量词形式写出&#xff1a;在北京居住的人未必都是北京人。 答: 全域: 所有人 P ( x ) \mathrm{P}(\mathrm{x}) P(x) 表示 x \…

「企业应用架构」应用架构概述

在信息系统中&#xff0c;应用架构或应用架构是构成企业架构&#xff08;EA&#xff09;支柱的几个架构域之一 应用架构描述了业务中使用的应用程序的行为&#xff0c;重点是它们如何相互之间以及如何与用户交互。它关注的是应用程序消费和生成的数据&#xff0c;而不是它们的内…

问题杂谈(三十)项目中引入Geotools

步骤 在pom.xml中确定好需要引入的模块&#xff0c;根据自己的需要修改 <!--geotool工具包--><dependency><groupId>org.geotools</groupId><artifactId>gt-epsg-hsql</artifactId><version>27.1</version></dependency&g…

这个学期,110多所高校把AI和大模型带进校园

2023 年春季学期&#xff0c;飞桨校园 AI Day 已登陆全国 114 所高校&#xff0c;为同学们提供了丰富的 AI 学习交流机会。 截至目前&#xff0c;超过 400 所高校的同学线上参与 AI 知识竞赛 PK &#xff0c;累计 2 万余名同学参与春季 AI Day 活动&#xff0c;更有 65 所高校举…

Express框架的路由配置

Express 是一个流行的基于 Node.js 的 Web 开发框架&#xff0c;它可以帮助我们快速搭建一个 Web 应用程序。在 Express 中&#xff0c;路由是一个非常重要的概念。路由指的是根据客户端请求的不同路径和 HTTP 方法来执行不同的处理逻辑。在本篇博客中&#xff0c;我们将介绍如…

美团太细了,HashMap可以存null,ConcurrentHashMap不可以,为什么?

△Hollis, 一个对Coding有着独特追求的人△ 这是Hollis的第 420 篇原创分享 作者 l Hollis 来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09; 我们知道&#xff0c;ConcurrentHashMap在使用时&#xff0c;和HashMap有一个比较大的区别&#xff0c;那就是HashM…

基于Spring Boot的在线考试系统

系统分析 可行性分析 一个完整的系统&#xff0c;可行性分析是必须要有的&#xff0c;因为关系到系统生存问题&#xff0c;对开发的意义进行分析&#xff0c;能否通过本系统来补充线下在线考试管理模式中的缺限&#xff0c;去解决其中的不足等&#xff0c;通过对本系统&#…

FastStone Capture安装、注册及使用教程(截屏、滚动截图、录屏、图片编辑工具)

FastStone Capture是一款集截屏、滚动截图、录屏、图片编辑为一体轻量级截图软件。免费试用30天。 如果你需要找一个具有上述红色字描述的功能的软件的话&#xff0c;可以继续往下阅读。若是你想找一个截图贴图的软件&#xff0c;可以参考&#xff1a; Snipaste介绍、安装、使用…

学系统集成项目管理工程师(中项)系列19b_成本管理(下)

1. 成本估算 1.1. 编制完成项目活动所需资源的大致成本 1.2. 在设计阶段多做些额外的工作可能减少执行阶段和产品运行时的成本 1.3. 项目估算的准确性随着项目的进展而提高 1.3.1. 【19下选48】 1.4. 针对完成活动所需资源的可能成本进行的量化评估 1.5. 容易被忽视的主要…

如何从0开始系统的学习kotlin?

Kotlin强大的静态语言特性相信不用我多讲&#xff0c;大家都或多或少明白一些。无论是对于安卓程序员还是JavaEE程序员来说&#xff0c;掌握Kotlin都是十分有必要的。 Kotlin作为谷歌官方支持的编程语言&#xff0c;目前&#xff0c;不少公司的Android开发工程师岗位表示要有K…

基于springboot广场舞团

系统分析 系统可行性分析 1、经济可行性 由于本系统本身存在一些技术层面的缺陷&#xff0c;并不能直接用于商业用途&#xff0c;只想要通过该系统的开发提高自身学术水平&#xff0c;不需要特定服务器等额外花费。所有创造及工作过程仅需在个人电脑上就能实现&#xff0c;使…