SRPBatcher&GPUInstancing


前言

本文章主要介绍unity的SRPBatcher和GPUInstancing,在Universal Render Pipeline(通用渲染管道,简称URP)中使用注意事项和相关知识。


一、SRP Batcher

1.简介

SRP Batcher是URP的一种新的合批技术,可以加速CPU,对于DX平台提升比较大,但对于移动平台来说,提升不是很明显,大概1.2倍左右,这部分还根据不同机型有一些差距,感兴趣的可以拿机子去验证。

SRP Batcher是为了解决什么问题诞生的?一个Drawcall被一个新的材质使用的时候,需要准备进行渲染设置工作,这部分耗时是一个drawcall的主要耗时点,所以如果场景有越多的materials,就会有越多的CPU必须使用去设置GPU 数据。传统的优化做法是减少drawcall数量去提升CPU渲染性能,而实际上真正的CPU消耗来自那些设置工作,而不是GPU drawcall本身,Drawcall只是一些Unity向GPU的 command buffer发送的字节数据。

下图显示了SRPBatcher工作流程:

SRPBatcher工作流程

更加详细的知识读者可以参考官方参考Blog,笔者主要介绍如何在urp管道中写Shader去让SRP Batcher生效,还有SRP Batcher兼容的问题等。

2.URP的Shader支持SRP Batcher

下图显示了示例目录结构:
示例目录结构
ScenePbrInput.shader的伪代码如下:

#ifndef SCENEPBR_INPUT_INCLUDED
#define SCENEPBR_INPUT_INCLUDED

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half _Cutoff;	
half _Opacity;
half4 _EmissionColor;
CBUFFER_END

#endif // SCENEPBR_INPUT_INCLUDED

ScenePbr.shader的伪代码如下:

Shader "Custom/Scene/XXX"
{
	Properties
    {
        _BaseMap("Texture", 2D) = "white" {}
        _BaseColor("Color", Color) = (1, 1, 1, 1)
        _Cutoff("AlphaCutout", Range(0.0, 1.0)) = 0.5
		_Opacity("Opacity",Range(0.0, 1.0)) = 0
		[Toggle(_EMISS_ON)]_EmissOn("Emiss On", Float) = 0 
		[HDR]_EmissionColor("Color", Color) = (0,0,0,0)
        _EmissionMap("Emission", 2D) = "white" {}

        // BlendMode
        [HideInInspector] _Surface("__surface", Float) = 0.0
        [HideInInspector] _Blend("__blend", Float) = 0.0
        [HideInInspector] _AlphaClip("__clip", Float) = 0.0
        [HideInInspector] _SrcBlend("Src", Float) = 1.0
        [HideInInspector] _DstBlend("Dst", Float) = 0.0
        [HideInInspector] _ZWrite("ZWrite", Float) = 1.0
        [HideInInspector] _Cull("__cull", Float) = 2.0
        
        [HideInInspector] _QueueOffset("Queue offset", Float) = 0.0
		[HideInInspector] _ReceiveShadows("Receive Shadows", Float) = 1.0 
    }

    SubShader
    {
		Tags{"RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "IgnoreProjector" = "True"}
		
		Pass
        {
            Name "ForwardBase"
			Tags{"LightMode" = "UniversalForward"}

			Blend [_SrcBlend][_DstBlend]
			ZWrite [_ZWrite]
			Cull [_Cull]

            HLSLPROGRAM
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x

            #pragma vertex vert
            #pragma fragment frag
            //#pragma shader_feature_local _ALPHATEST_ON
			#pragma shader_feature_local _RECEIVE_SHADOWS_OFF
			#pragma shader_feature_local _EMISS_ON

			#pragma multi_compile _ _MAIN_LIGHT_SHADOWS
			#pragma multi_compile _ LIGHTMAP_ON

            #pragma multi_compile_fog
            #pragma multi_compile_instancing
			#include "ScenePbrInput.hlsl"
	
			TEXTURE2D(_BaseMap);		SAMPLER(sampler_BaseMap);
			TEXTURE2D(_EmissionMap);	SAMPLER(sampler_EmissionMap);

            struct Attributes
            {
                float4 positionOS	: POSITION;
				float3 normalOS		: NORMAL;
                float2 uv			: TEXCOORD0;
				float2 lightmapUV	: TEXCOORD1;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct Varyings
            {
                float4 positionCS	: SV_POSITION;
                half2 uv			: TEXCOORD0;
                half fogCoord		: TEXCOORD1;
			#ifdef _MAIN_LIGHT_SHADOWS
				half4 shadowCoord	: TEXCOORD2;
			#endif
				half3 normalWS		: TEXCOORD3;
				half3 viewDirWS		: TEXCOORD4;
				half2 lightmapUV	: TEXCOORD5;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            Varyings vert(Attributes i)
            {
                Varyings o = (Varyings)0;
                UNITY_SETUP_INSTANCE_ID(i);
                UNITY_TRANSFER_INSTANCE_ID(i, o);

                VertexPositionInputs vertexInput = GetVertexPositionInputs(i.positionOS.xyz);
                o.positionCS = vertexInput.positionCS;
                o.uv = TRANSFORM_TEX(i.uv, _BaseMap);
                o.fogCoord = ComputeFogFactor(vertexInput.positionCS.z);

			#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF)
				o.shadowCoord = GetShadowCoord(vertexInput);
			#endif
                
				OUTPUT_LIGHTMAP_UV(i.lightmapUV, unity_LightmapST, o.lightmapUV);
				half3 viewDirWS = GetCameraPositionWS() - vertexInput.positionWS;
				o.normalWS = normalize(TransformObjectToWorldNormal(i.normalOS));
                return o;
            }

            half4 frag(Varyings i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);

                half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv);
                half3 color = texColor.rgb * _BaseColor.rgb;
                half alpha = texColor.a * _BaseColor.a;
                AlphaDiscard(alpha, _Cutoff);

			#if defined(_MAIN_LIGHT_SHADOWS) && !defined(_RECEIVE_SHADOWS_OFF)
				half4 shadowCoord = i.shadowCoord;
			#else
				half4 shadowCoord = half4(0, 0, 0, 0);
			#endif

				Light mainLight = GetMainLight(shadowCoord);
			#ifdef LIGHTMAP_ON
				//half3 lightDir = mainLight.direction;
				half3 bakedGI = SampleLightmap(i.lightmapUV, i.normalWS);
				bakedGI = SubtractDirectMainLightFromLightmap(mainLight, i.normalWS, bakedGI);
				color *= bakedGI;
			#else 
				half3 atten = mainLight.distanceAttenuation * mainLight.shadowAttenuation;
				color *= atten;
			#endif

			#ifdef _EMISS_ON
				half4 emission = SAMPLE_TEXTURE2D(_EmissionMap, sampler_EmissionMap, i.uv) * _EmissionColor;
				color += emission.rgb;
			#endif
			
				color.rgb = lerp(MixFog(color.rgb, i.fogCoord), color.rgb, _SaturationScene);
				alpha -= _Opacity;
				alpha = saturate(alpha);
                return half4(color, alpha);
            }
            ENDHLSL
        }

		Pass
        {
            Name "ShadowCaster"
            Tags{"LightMode" = "ShadowCaster"}

            ZWrite On
            ZTest LEqual
            Cull[_Cull]

            HLSLPROGRAM
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
            #pragma target 2.0
         
            #pragma shader_feature_local _ALPHATEST_ON

            #pragma vertex ShadowPassVertex
            #pragma fragment ShadowPassFragment
			
			#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
			#include "ScenePbrInput.hlsl"
			//实现部分功能
		
            ENDHLSL
        }
	
		Pass
        {
            Tags{"LightMode" = "DepthOnly"}

            ZWrite On
            ColorMask 0

            HLSLPROGRAM
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
            #pragma target 2.0

            #pragma vertex DepthOnlyVertex
            #pragma fragment DepthOnlyFragment
            #pragma shader_feature_local _ALPHATEST_ON
            #pragma multi_compile_instancing

			#include "ScenePbrInput.hlsl"
			//实现部分功能
			... ...
            ENDHLSL
        }      
    }
}

从上面代码可以看出,我们需要每个pass都添加上同样的CBUFFER_START(UnityPerMaterial) CBUFFER_END(Constant Buffer),这样shader编译后才会显示:

shader编译后才会显示
需要注意的是,SRP Batcher对于Shader有不同编译的变体,最后生成的shader也是无法支持SRP Batcher,这个可以用Frame Debugger调试去查看。
比如下图的变体收集:

变体收集
A物体使用的是编译后的上面的那个变体集合,B物体使用的是下面那俩个变体集合,那即使这两个使用的同个Shader也无法支持SRP Batcher。简单来说,其合并条件为相同Shader和相同的激活的keyword。

二、GPU Instancing

1.简介

使用 GPU Instancing可使用少量绘制调用一次绘制(或渲染)同一网格的多个副本。它对于绘制诸如建筑物、树木和草地之类的在场景中重复出现的对象非常有用。合并批次的前提条件是同网格同材质,使用比较多的是植被相关的,比如草和树木。在使用上需要注意当代码调用改变属性时候,需要用MaterialPropertyBlock,从而不会破坏GPU Instancing。更为详细的知识可以阅读官方文档

总结

在URP管道中使用GPU Instancing和SRP Batcher需要注意,二者只能存在其中一种,而SRP Batcher优先级最高,对于写草和树的Shader,笔者比较建议直接支持GPU Instancing,而不支持SRP Batcher, 在移动端上效率相对SRP Batcher更好。

参考

SRP Batcher
Universal Render Pipeline

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值