catlikecoding:Custom SRP(Draw Calls)

2023-10-25 10:10

本文主要是介绍catlikecoding:Custom SRP(Draw Calls),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

catlikecoding Custom SRP第二章,主要是有关着色器的内容。

1、Shaders

主要是一些基本的shader书写,直接看原文教程吧。1、Shaders

2、Batching

假设场景中有5个Cube,每个Cube都有自己独立的材质。一般情况下,场景中的Draw Call数量是5 + 1 + 1。后两项是天空盒和Clear。

2.1 SRP Batcher

批处理是组合Drawcall的过程,可减少CPU和GPU之间的通信时间。

SRP批次不会减少Draw Call的数量,而是使其更精简。它在GPU上缓存了材质属性,因此不必在每次绘制调用时都将其发送出去。这样既减少了需要传达的数据量,又减少了每个绘图调用CPU需要完成的工作。但这仅在着色器遵守用于uniform 数据的严格结构时才有效。

为了使SRP Batcher兼容着色器,就需要将材质的属性放在特定的常量GPU缓冲区,但是并非所有平台(例如OpenGL ES 2.0)都支持常量缓冲区,所以需要用核心RP库中的宏。在这种情况下,我们得到的结果与之前完全相同,只是不支持cbuffer的平台不存在cbuffer代码。

// cbuffer UnityPerMaterial {// float _BaseColor;
// };CBUFFER_START(UnityPerMaterial)float4 _BaseColor;
CBUFFER_END

还需要对unity_ObjectToWorld,unity_WorldToObject和unity_WorldTransformParams执行此操作,它们必须分组在UnityPerDraw缓冲区中。如果我们使用特定的一组值,则需要全部定义它们。对于转换组,即使我们不使用它,我们也需要包括float4 unity_LODFade。

CBUFFER_START(UnityPerDraw)float4x4 unity_ObjectToWorld;float4x4 unity_WorldToObject;float4 unity_LODFade;real4 unity_WorldTransformParams;
CBUFFER_END

此时,我们的着色器就支持Batcher了,下一步是启用SRP批处理程序,在CustomRenderPipeline脚本中写构造函数。

	public CustomRenderPipeline(){GraphicsSettings.useScriptableRenderPipelineBatching = true;}

 6,就是节省的批次。

2.2 Many Colors

即使我们使用四种材质,也可以得到一个批次。之所以可行,是因为它们的所有数据都缓存在GPU上,并且每个绘制调用仅需包含一个指向正确内存位置的偏移量。唯一的限制是每种材质的内存布局需要相同,这是因为我们对所有材质都使用相同的着色器,每个着色器仅包含一个颜色属性。

如果要为每个球体赋予自己的颜色,那么就需要创建更多的材质。接下来的代码是在不创建多个材质球的情况下,为每个球赋予自己的颜色。但是这种情况下就无法支持合批处理。原文的解释:SRP批处理程序无法处理每个对象的材质属性。因此,这24个球体每个都有一次DrawCall,由于排序,也可能将其他球体分成多个批次。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;[DisallowMultipleComponent]
public class PerObjectMaterialProperties : MonoBehaviour
{static int baseColorId = Shader.PropertyToID("_BaseColor");static MaterialPropertyBlock block;[SerializeField]Color baseColor = Color.white;void Awake(){OnValidate();}void OnValidate(){if (block == null){block = new MaterialPropertyBlock();}block.SetColor(baseColorId, baseColor);GetComponent<Renderer>().SetPropertyBlock(block);}
}

2.3 GPU Instancing

一种可以合并draw call,并对逐对象材质属性有效的方法,它被称为GPU实例化,作用是对使用相同mesh的多个对象处理成一个draw call来提交。CPU会收集每个对象的材质属性,并把它们放进实例数据的数组发送到GPU。GPU则遍历数组按照提供的顺序来渲染它们。

GPU实例化需要通过数组来提供数据,我们的shader目前不能支持。让它工作的第一步就是添加#pragma multi_compile_instancing指令。

这会让Unity为我们的shader生成两个变体,一个支持,一个不支持CPU实例化。材质面板中也出现了一个开关,让我们可以依据不同的材质选择不同的版本。

使用GPU实例化需要在shader中提供当下渲染对象的索引,索引是顶点数据提供的。hlsl定义了宏来简化这个过程,由于我们还要传递POSITION参数,所以我们需要为vertex函数定义了结构体参数,作为函数的输入。

原文解释:当使用GPU实例化的时候对象索引也可以作为顶点属性,我们可以在恰当的时候添加它,只需要把UNITY_VERTEX_INPUT_INSTANCE_ID放入Attributes结构体中。

接下来,在UnlitPassVertex的开头添加。这将从输入中提取索引,并将其储存在其他Instancing宏所依赖的全局变量中。

struct Attributes {float3 positionOS : POSITION;UNITY_VERTEX_INPUT_INSTANCE_ID
};

现在还不支持不同的实例材质参数,要添加这个功能需要用一个引用数组来替换原来的_BaseColor。

UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)//float4 _BaseColor;UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)

批次的大小是有限制的,这取决于目标平台和每个实例的数据,如果你超过这个限制,那么最终得到的不止一个批次,此外,排序可以打断批次,如果有多种材质在使用的话。

VertexShader的输出输出也可以再简化一下代码,最后的UnlitPass.hlsl代码如下:

// NOT GPU Instance
/* #ifndef CUSTOM_UNLIT_PASS_INCLUDED
#define CUSTOM_UNLIT_PASS_INCLUDED#include "../ShaderLibrary/Common.hlsl"CBUFFER_START(UnityPerMaterial)float4 _BaseColor;
CBUFFER_ENDfloat4 UnlitPassVertex (float3 positionOS : POSITION) : SV_POSITION {float3 positionWS = TransformObjectToWorld(positionOS.xyz);return TransformWorldToHClip(positionWS);
}float4 UnlitPassFragment () : SV_TARGET {return _BaseColor;
}#endif
*/// GPU Instance
#ifndef CUSTOM_UNLIT_PASS_INCLUDED
#define CUSTOM_UNLIT_PASS_INCLUDED#include "../ShaderLibrary/Common.hlsl"UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)//float4 _BaseColor;UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)struct Attributes {float3 positionOS : POSITION;UNITY_VERTEX_INPUT_INSTANCE_ID
};struct Varyings {float4 positionCS : SV_POSITION;UNITY_VERTEX_INPUT_INSTANCE_ID
};Varyings UnlitPassVertex (Attributes input) { //: SV_POSITION {Varyings output;UNITY_SETUP_INSTANCE_ID(input);UNITY_TRANSFER_INSTANCE_ID(input, output);float3 positionWS = TransformObjectToWorld(input.positionOS);output.positionCS = TransformWorldToHClip(positionWS);return output;
}float4 UnlitPassFragment (Varyings input) : SV_TARGET {UNITY_SETUP_INSTANCE_ID(input);return UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _BaseColor);
}#endif

2.4 Drawing Many Instanced Meshes

通过代码,使用GPU实例化绘制多个相同mesh的实例化网格。

我们也可以手动生成许多游戏对象,但是这里我们不手动这么做。我们通过填充一个变换矩阵和颜色的数组来渲染一个网格,这就是GPU实例化最有用的地方。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class MeshBall : MonoBehaviour
{static int baseColorId = Shader.PropertyToID("_BaseColor");[SerializeField]Mesh mesh = default;[SerializeField]Material material = default;Matrix4x4[] matrices = new Matrix4x4[1023];Vector4[] baseColors = new Vector4[1023];MaterialPropertyBlock block;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);}}void Update(){if (block == null){block = new MaterialPropertyBlock();block.SetVectorArray(baseColorId, baseColors);}Graphics.DrawMeshInstanced(mesh, 0, material, matrices, 1023, block);}
}

2.5 Dynamic Batching

将共享相同材质的多个小网格合并为一个较大的网格,然后绘制该网格。较大的网格一般按需生成,所以动态合批仅适用于较小的网格。球体还是太大了,但立方体可以使用。要跟踪查看它的过程,需要禁用GPU实例化。代码设置如下:

		var drawingSettings = new DrawingSettings(unlitShaderTagId, sortingSettings) {enableDynamicBatching = true,enableInstancing = false};
		GraphicsSettings.useScriptableRenderPipelineBatching = false;

一般来说,GPU实例化优于动态批处理。动态批处理也有一些注意事项,例如,当涉及不同的比例时,不能保证较大网格的法线向量为单位长度。此外,绘制顺序也将更改,因为它现在是单个网格而不是多个。

2.6 Configuring Batching

设置我们的渲染管线到底最终采用哪种合批操作。这部分代码较多,直接看官网流程吧 —— 配置批处理

2.7 总结

总结一下:这里对应下图的Custom RP配置,对应下图。

Use Dynamic Bathching:将共享相同材质的多个小网格合并为一个较大的网格,然后绘制该网格。但是这个需要禁用SRP批处理,因为SRP批处理的优先级高。

Use GPU Instance:对于使用相同mesh的多个对象(如两个Cube,一个Cube一个Sphere不行)处理成一个draw call来提交。需要使用同一个材质球,每个实例可以具有不同的参数(可以给不同的物体通过上文的PerObjectMaterialProperties设置)并且这些相同mesh的对象不需要scale一样。这个也需要禁用SRP批处理,因为SRP批处理的优先级高。其实这里说SRP批处理和GPU Instance不能同时用也不准确,只是两者同时用的时候,不一定能够发挥最优的性能。

Use SRP Batcher:可以使用不同的材质球,但是必须需要使用相同的着色器,合批的原理:SRP合批并不会减少drawcall的调用而是让它们更加精简,它将材质属性缓存到GPU上。

3、Transparency

可以将渲染队列设置为透明,但是这只会在绘制对象时改变绘制顺序,而不是如何绘制。

3.1 Blend Modes

两个shader属性:_SrcBlend and _DstBlend。他们是混合模式的枚举。

为了简化编辑,我们可以将Enum属性添加到属性中,并使用完全限定的UnityEngine.Rendering.BlendMode Enum类型作为参数,这样在unity中就可以选择采用哪种混合方式了。

 此时的设置:这个默认值代表我们已经使用的不透明配置,源被设置为1,意味着它被完全添加,目标被设置为0,以为着它完全被忽略了。

标准透明度的源混合模式是SrcAlpha,这意味着渲染颜色的RGB分量乘以其alpha分量。因此,alpha值越低越弱。然后将目标混合模式设置为相反:OneMinusSrcAlpha,以达到总权重1。

3.2 ZWrite

这部分是shader相关的内容,说白了就是当采用半透明的材质时,需要关闭ZWrite。

[Enum(Off, 0, On, 1)] _ZWrite ("Z Write", Float) = 1Blend [_SrcBlend] [_DstBlend]
ZWrite [_ZWrite]

3.3  Use Texture

需要提供纹理直接上代码:

// Unlit.shader
_BaseColor ("Color", Color) = (1.0, 1.0, 1.0, 1.0)
_BaseMap("Texture", 2D) = "white" {}// UnlitPass.hlsl
TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);UNITY_INSTANCING_BUFFER_START(UnityPerMaterial)//float4 _BaseColor;UNITY_DEFINE_INSTANCED_PROP(float4, _BaseMap_ST)UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_INSTANCING_BUFFER_END(UnityPerMaterial)

写到这里就会发现,Attributes和Varyings结构就是我们默认渲染管线的Unlit Shader的appdata,v2f结构体,一个是CPU传递的数据,一个是Vertex Shader传递给Fragment Shader的数据,代码如下:

// appdata
struct Attributes {float3 positionOS : POSITION;float2 baseUV : TEXCOORD0;UNITY_VERTEX_INPUT_INSTANCE_ID
};// v2f
struct Varyings {float4 positionCS : SV_POSITION;float2 baseUV : VAR_BASE_UV;UNITY_VERTEX_INPUT_INSTANCE_ID
};

然后就是在FragmentShader中采用纹理了,使用SAMPLE_TEXTURE2D宏,将纹理、采样器状态和坐标作为参数,在此处对纹理进行采样。

// Vertex Shader
Varyings UnlitPassVertex (Attributes input) { //: SV_POSITION {Varyings output;UNITY_SETUP_INSTANCE_ID(input);UNITY_TRANSFER_INSTANCE_ID(input, output);float3 positionWS = TransformObjectToWorld(input.positionOS);output.positionCS = TransformWorldToHClip(positionWS);float4 baseST = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _BaseMap_ST);output.baseUV = input.baseUV * baseST.xy + baseST.zw;return output;
}// Fragment Shader
float4 UnlitPassFragment (Varyings input) : SV_TARGET {UNITY_SETUP_INSTANCE_ID(input);float4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.baseUV);float4 baseColor = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _BaseColor);return baseMap * baseColor;
}

现在我们的材质就能够支持纹理了。

 3.4 Alpha Clipping

就是透明度剔除,clip函数,clip(x),当x小于0时,就不渲染此片元。

_BaseColor("Color", Color) = (1.0, 1.0, 1.0, 1.0)
_Cutoff ("Alpha Cutoff", Range(0.0, 1.0)) = 0.5UNITY_DEFINE_INSTANCED_PROP(float4, _BaseColor)
UNITY_DEFINE_INSTANCED_PROP(float, _Cutoff)// Fragment Shader
float4 UnlitPassFragment (Varyings input) : SV_TARGET {UNITY_SETUP_INSTANCE_ID(input);float4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.baseUV);float4 baseColor = UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _BaseColor);float4 base =  baseMap * baseColor;clip(base.a - UNITY_ACCESS_INSTANCED_PROP(UnityPerMaterial, _Cutoff));return base;
}

3.5 Render Queue:Alpha Test

一个材质通常使用半透明混合或者裁剪,而不是同时都使用。一个典型的裁剪类型的材质除了丢弃的那些片段以外是完全不透明的,并会写入深度缓冲。它使用的是AlphaTest渲染队列,这意味着它会在完全不透明的对象后面进行渲染。这样做是因为丢弃片段使得一些GPU优化变得不可能,因为三角形不再被认为完全覆盖了它们背后的东西。通过首先绘制完全不透明的对象,它们可能最终覆盖了alpha剪裁对象的一部分(没discard),这样就不需要处理它们隐藏的片段。

因此,将我们的材质的Render Queue设置为AlphaTest。

 3.6 Cutoff Per Object

在PerObjectMaterialProperties.cs中将Cutoff像Color一样添加。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;[DisallowMultipleComponent]
public class PerObjectMaterialProperties : MonoBehaviour
{static int baseColorId = Shader.PropertyToID("_BaseColor");static int cutoffId = Shader.PropertyToID("_Cutoff");static MaterialPropertyBlock block;[SerializeField]Color baseColor = Color.white;[SerializeField, Range(0f, 1f)]float cutoff = 0.5f;void Awake(){OnValidate();}void OnValidate(){if (block == null){block = new MaterialPropertyBlock();}block.SetColor(baseColorId, baseColor);block.SetFloat(cutoffId, cutoff);GetComponent<Renderer>().SetPropertyBlock(block);}
}

3.7 Ball of Alpha-Clipped Spheres

还记得如果用相同的mesh就可以用GPU Instance来优化我们的合批吧,这里让我们随机产生相关的mesh,给予随机的颜色和Culloff,就是前文的MeshBall.cs,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class MeshBall : MonoBehaviour
{static int baseColorId = Shader.PropertyToID("_BaseColor");[SerializeField]Mesh mesh = default;[SerializeField]Material material = default;Matrix4x4[] matrices = new Matrix4x4[1023];Vector4[] baseColors = new Vector4[1023];MaterialPropertyBlock block;void Awake(){for (int i = 0; i < matrices.Length; i++){matrices[i] = Matrix4x4.TRS(Random.insideUnitSphere * 10f, Quaternion.Euler(Random.value * 360f, Random.value * 360f, Random.value * 360f), Vector3.one * Random.Range(0.5f, 1.5f));baseColors[i] = new Vector4(Random.value, Random.value, Random.value, Random.Range(0.5f, 1f));}}void Update(){if (block == null){block = new MaterialPropertyBlock();block.SetVectorArray(baseColorId, baseColors);}Graphics.DrawMeshInstanced(mesh, 0, material, matrices, 1023, block);}
}

跟2.4中的颜色数组一样,这里Unity最终还是会向GPU发送一个裁剪值的数组,每个实例一个,即使它们都是相同的。该值是材质上的copy,因此可以通过修改它来一次性让所有球体的孔发生变化

接下来,要想看到场景中的效果,记得将我们的材质Enable GPU Instancing和我们的渲染管线的Use GPU Instance开启。

 最后,上一张第二章的最后效果图,此章完结撒花 ~~~

这篇关于catlikecoding:Custom SRP(Draw Calls)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Matlab draw a sector 画一个扇形

Matlab draw a sector 画一个扇形 Matlab的函数代码: function [ sector ] = Draw_a_sector( map, center,StartR, EndR, StartAngle, EndAngle )%% Get indexs(row,column)size_map=size(map);for i = 1:size_map(2)index

数据标注:批量转换json文件,出现AttributeError: module ‘labelme.utils‘ has no attribute ‘draw_label‘错误

labelme版本更换为3.11.2 "D:\Anaconda3\Lib\site-packages\labelme\utils\draw.py"缺失?: import ioimport os.path as ospimport numpy as npimport PIL.Imageimport PIL.ImageDrawimport PIL.ImageFontdef label_co

单一职责原则 SRP

单一职责原则,就一个类而言,引起其变化的原因只应该有一个。本质上是实现程序松耦合的目的,当功能改变的时候对其他功能尽可能少的影响。

OpenAI Gym custom environment: Discrete observation space with real values

题意:OpenAI Gym 自定义环境:具有实数值的离散观测空间 问题背景: I would like to create custom openai gym environment that has discrete state space, but with float values. To be more precise, it should be a range of valu

安装jmeter的梯度压测线程组(Custom Thread Groups)的插件

1、打开:Install :: JMeter-Plugins.org 2、进入主页后点击下面图片的链接进行安装 3、将安装包放入apache-jmeter-5.4.1    >   lib       >    ext  里面 4、打开Jmeter 点击下面的【Plugins Manager】 5、进入 【Plugins Manager】后选择【Avaliable Plug

【教你一键解决】draw.io中输入英文显示成中文且输入位置移到首位

问题描述:当英文输入一个“a”时,会自动出现中文“一个”,再输入“a”才会出现“a”,删除时无法把中文删除,如下图所示。 解决方法:关闭浏览器的自动翻译功能即可,如下图所示。

SVN安装过程出现“Custom action InstallWMISchemaExecute failed:服务不存在,或已被标记为删除”

在安装SVN服务端的时候,总是出现下面的错误,进而停止安装。具体的错误如下:                            安装了好多的版本,都出现了这样的问题。我的系统是window7 64位的。      哪位路过的大神给评论一下,给点建议。帮我解决一下!在此谢谢啦!

安卓自定义标题时候you cannot combine custom titles with other

title_layout文件 public class MainActivity extends Activity {     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInsta

x11 draw_pixels

对图像每个像素 统一 加一个值 创建一个图片 char *data = (char*)malloc(256*256*4);XImage *img = XCreateImage(display,visual,DefaultDepth(display,screen_num),ZPixmap,0,data,256,256,32,0); 调用 XAddPixel 添加值 int count =

Creating custom and compound Views in Android - Tutorial(翻译)

Creating custom and compound Views in Android - Tutorial(翻译) 译前的: 之前做了三篇学习笔记,从知乎上面看到了这篇英文的推荐,总的来说可以是一篇导读,没有相关的学习,看这篇,可以作为一个学习脉络导向;有相关的学习底子,可以作为一个基础夯实、思维理清。没想到一翻译就是四个多小时…英语渣,很多词句都不太准确,幸好有之前的学习基础打底…