IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> URP下SRPBatcher,GPUInstancing,动态合批,静态合批 -> 正文阅读

[游戏开发]URP下SRPBatcher,GPUInstancing,动态合批,静态合批

SRPBatcher:

适用前提:

????????需要是同一个shader,可以是不同的材质球,Shader代码必须兼容SRP Batcher。

????????但是不支持用材质球属性块(MaterialPropertyBlock)

? ? ? ??渲染的物体必须是一个mesh或者skinned?mesh。不能是粒子。

效果:

? ? ? ? 可以有效降低SetPassCall的数目,用于CPU性能优化

优化原理:

????????1.在过去的渲染架构中,Unity采取对一个材质分配一个CBuffer(or 一个Pass,这不是重点),这个CBuffer 包括shader里的显性的参数(你自己定义的uniform参数)和隐性的参数(unity固定的uniform modelMatrix,modelviewMatrix之类。),所以每一次drawcall,要更新这个CBuffer

????????2.在SRP渲染架构中,Unity采取的策略是对一个材质分配一个半CBuffer,为什么是一个半呢?首先shader的显性参数分配到一个CBuffer里,shader的隐性参数则是N个物体共享一个CBuffer。比如一个shader 对应 10个物体,在SRP渲染架构中,一共分配了11个CBuffer,其中10个分别存这10个物体材质中定义的显性参数。然后分配一个大的共享CBuffer,把这10个物体的modelMatrix这类隐性参数都放在一起。

????????乍一看这不是负优化吗?老架构更新10个CBuffer,你现在更新11个。

????????这个策略叫做动静分离,材质的显性参数大部分都是低频更新的(你总不能一个游戏所有的材质参数每桢都改变吧),所以在理想情况下,这10个放显性参数的CBuffer就基本不修改。而modelMatrix之类的隐性参数是高频更新的,很多模型会动来动去。他们被批量放在一个CBuffer里,一次更新可以更新一片。

如下图:大型的共享CBuffer和每个材质自己的CBuffer都有各自专门的代码进行更新,大部分情况只需要更新大型共享CBuffer,从而降低了一帧内SetPassCall的数目

?标准流程和SRPBatcher流程的区别:

?以上,据Unity官方宣传 SRP Batcher 可以取得 1.2~4 倍的 CPU渲染时间提升(仅提升CPU部分,不是渲染耗时提升这么多,还得看cpu瓶颈占多大比重)

如何让Shader支持SRPBatcher:

1、必须声明所有内建引擎properties?在一个名为"UnityPerDraw"的CBUFFER里。

// 如果需要支持SRP合批,内置引擎属性必须在“UnityPerDraw”的 CBUFFER 中声明
CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld;			// 模型空间->世界空间,转换矩阵(uniform 值。它由GPU每次绘制时设置,对于该绘制期间所有顶点和片段函数的调用都将保持不变)
float4x4 unity_WorldToObject;			// 世界空间->模型空间
float4 unity_LODFade;
real4 unity_WorldTransformParams;		// 包含一些我们不再需要的转换信息,real4向量,它本身不是有效的类型,而是取决于目标平台的float4或half4的别名。(需要引入unityURP库里的"Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"才能使用real4)
CBUFFER_END

(URP内置的UnityInput.hlsl里自带更全面的代码。也就是说,如果你的代码有引用或间接引用UnityInput.hlsl,那就不用做这一步了。)

2、必须声明所有材质properties在一个名为"UnityPerMaterial"的CBUFFER里。

// 使用核心RP库中的CBUFFER_START宏定义,因为有些平台是不支持常量缓冲区的。这里不能直接用cbuffer UnityPerMaterial{ float4 _BaseColor };
// Properties大括号里声明的所有变量如果需要支持合批,都需要在UnityPerMaterial的CBUFFER中声明所有材质属性
// 在GPU给变量设置了缓冲区,则不需要每一帧从CPU传递数据到GPU,仅仅在变动时候才需要传递,能够有效降低set pass call
CBUFFER_START(UnityPerMaterial)
float4 _BaseColor;															// 将_BaseColor放入特定的常量内存缓冲区
CBUFFER_END

若需要配合GPUInstancing则需要改写为

UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
	UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)								// 把所有实例的_BaseColor以数组的形式声明并放入内存缓冲区
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)

GPUInstancing:

适用前提:

  • 兼容的平台及API

  • 相同的Mesh与Material,并且这个Material的shader只能有一个pass。, 支持不同的材质球属性块(MaterialPropertyBlock)

  • 不支持SkinnedMeshRenderer

  • Shader支持GPU Instancing

  • 缩放为负值的情况下,会不参与加速。
  • 受限于常量缓冲区在不同设备上的大小的上限,移动端支持的个数可能较低。
  • 只支持一盏实时光,要在多个光源的情况下使用实例化,我们别无选择,只能切换到延迟渲染路径。为了能够让这套机制运作起来,请将所需的编译器指令添加到我们着色器的延迟渲染通道中。

效果:

  • 批渲染Mesh相同的那些物体,以降低DrawCall数
  • 这些物体可以有不同的参数,比如颜色与缩放

原理:

Unity会在运行时对于正在视野中的符合要求的所有对象,将其位置、缩放、uv偏移、lightmapindex等相关信息放到CBuffer(Constant Buffer)中,然后统一保存在显存中的“统一/常量缓冲器”中,当一个对象作为实例送入渲染流程时,在执行DrawCall操作后,根据传入的InstanceID从显存中取出当前实例对应的部分共享信息与从GPU常量缓冲器中取出对应对象的相关信息一并传递到下一渲染阶段,与此同时,不同的着色器阶段都可以从缓存区中直接获取到需要的常量,不用设置两次常量。总的来说就是一次性存入所有对象的公共信息到CBuffer,后续根据id来取,不用每次都发数据到GPU。

流程图:

如何使用GUPInstancing:

Shader "Custom RP/Unlit"
{
	Properties{
		_BaseColor("Color", Color) = (1.0, 1.0, 1.0, 1.0)
	}
	SubShader {
		Pass {
			HLSLPROGRAM

			// 让shader支持GUIInstancing 
			// 一次对具有相同网格物体的多个对象发出一次绘图调用。
			// CPU收集所有每个对象的变换和材质属性,并将它们放入数组中,然后发送给GPU(SetPassCall)。
			// 最后,GPU遍历所有条目,并按提供顺序对其进行渲染。
			#pragma multi_compile_instancing

			#pragma vertex UnlitPassVertex
			#pragma fragment UnlitPassFragment
			#include "UnlitPass.hlsl"					// 里面定义了顶点着色器以及片元着色器
			ENDHLSL
		}
	}
}


// --------------以下是UnlitPass.hlsl里的代码-----------------

// 为了支持GUIInstancing,这里CBUFFER_START改成用UNITY_INSTANCING_BUFFER_START宏
UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)
	UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)								// 把所有实例的_BaseColor放入内存缓冲区
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)

// 顶点着色器输入
struct Attributes{
	float3 positionOS : POSITION;
	UNITY_VERTEX_INPUT_INSTANCE_ID												// 启用GUIInstancing的时候,用此宏,可以让顶点传入实例化id
};

// 顶点着色器输出
struct Varyings {
	float4 positionCS : SV_POSITION;
	UNITY_VERTEX_INPUT_INSTANCE_ID												// 启用GUIInstancing的时候,用此宏,让顶点着色器输出实例化id
};

Varyings UnlitPassVertex(Attributes input){
	Varyings output;
	UNITY_SETUP_INSTANCE_ID(input);												// 从input中提取对象索引,并将其存储在其他GUIInstancing相关宏所依赖的全局静态变量中
	UNITY_TRANSFER_INSTANCE_ID(input, output);									// 把input中的实例化id转换到片元着色器中用的实例化id
	float3 positionWS = TransformObjectToWorld(input.positionOS);
	output.positionCS = TransformWorldToHClip(positionWS);
	return output;
}

float4 UnlitPassFragment(Varyings input) : SV_TARGET{
	UNITY_SETUP_INSTANCE_ID(input);												// 从input中提取对象索引,并将其存储在其他GUIInstancing相关宏所依赖的全局静态变量中
	return UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _BaseColor);			// 根据实例id从_BaseColor数组中取出对应的_BaseColor
}

当需要创建海量mesh的时候,一般不要用实例化游戏物体的方式,这样会比较消耗性能,推荐使用Graphics.DrawMeshInstanced来创建。

例子:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 动态生成大量球体mesh,用来测试GPUInstancing
/// </summary>
public class MeshBall : MonoBehaviour
{
    static int baseColorId = Shader.PropertyToID("_BaseColor");

    [SerializeField]
    Mesh mesh = default;         // 手动拖入mesh

    [SerializeField]
    Material material = default;  // 手动拖入支持GPUInstancing的材质球

    Matrix4x4[] matrices = new Matrix4x4[1023];
    Vector4[] baseColors = new Vector4[1023];

    MaterialPropertyBlock block;

    private void Awake()
    {
        for (int i = 0; i < matrices.Length; i++)
        {
            matrices[i] = Matrix4x4.TRS(Random.insideUnitSphere * 10f, Quaternion.identity, Vector3.one);
            baseColors[i] = new Vector4(Random.value, Random.value, Random.value, 1f);
        }
    }

    private void Update()
    {
        if(block == null)
        {
            block = new MaterialPropertyBlock();
            block.SetVectorArray(baseColorId, baseColors);
        }
        Graphics.DrawMeshInstanced(mesh, 0, material, matrices, 1023, block);
    }
}

Graphics.DrawMeshInstanced()这方法还有两问题

1.一次最多画1023个元素,如果超出就会报错,所以需要将草进行分类管理。

2.它不提供裁切的功能,也就是说摄像机看不到的地方,这些草是不会被剔除掉的,依然会被渲染。

解决这个问题,为了避免运行时暴力的for循环来判断是否在视野内,我采取的方法是预先将场景分成20X20若干个格子(可根据游戏的可视范围而定)根据玩家的位置,始终只渲染周围9个格子内的草元素,这样将大幅度减少运行时for循环的次数。

如果每个草的顶点色是不一样的怎么办呢?可以用MaterialPropertyBlock来让同一个材质求有不同的属性

未完待续。。。

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-01-28 12:15:50  更:2022-01-28 12:17:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 18:32:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码