前两天看到一篇关于雨滴效果制作的文章,学习实践了一下。这里贴一下涉及到的知识点。
1.CommandBuffer
用来存储一系列渲染指令,可以通过Camera(Camera.AddCommandBuffer)
,Light( Light.AddCommandBuffer
)及Graphics(Graphics.ExecuteCommandBuffer
)来调用执行。
2.ComputeShader
ComputeShader运行在GPU上,可以执行大量并行算法,或者用来加速游戏渲染。为了更高效的使用Compute Shader,需要额外了解GPU的架构及常见并行算法。
使用ComputerShader之前需要先使用SystemInfo.supportsComputeShaders测试可用性。下面是基本的ComputeShader格式
// test.compute
// 一个Compute Shader必须至少有一个 kernel, #pragma kernel后可添加额外的宏,如OTHER_DEFINE
#pragma kernel FillWithRed OTHER_DEFINE
RWTexture2D res;
[numthreads(100,1,1)]
void FillWithRed (uint3 dtid : SV_DispatchThreadID)
{
res[dtid.xy] = float4(1,0,0,1);
}
之后可以通过 ComputeShader.Dispatch来执行shader。方式如下:
ComputeShader shader;
int kernal = shader.FindKernel("CSMain");
shader.Dispatch(kernal, 100, 1, 1);
3. ComputeBuffer
GPU数据缓存,通常是在ComputeShader中使用,可以填充数据,也可以获取数据。
在HLSL格式里,使用StructuredBuffer
4. GPU Instance
GPU Instance是一种将相同Mesh的多个拷贝在少量的draw call中完成绘制的技术。经常在绘制建筑,树木,草木或者其他一些在场景中重复出现的物件中使用。可以明显的提升渲染性能。
GPU Instance在每个draw call中绘制的必须是完全相同的mesh,但是每个mesh可以拥有不同的参数(形如颜色,缩放值)。
为了使用GPU Instance,需要开启材质的 Enable Instancing选项。
除了以上方式,也可以在脚本中调用Graphics.DrawMeshInstanced 或者 Graphics.DrawMeshInstancedIndirect执行GPU Instance。
通常,Unity只会对仅有Transform变化的GameObject做批处理,为了为每个实例添加更多的变体,需要在Shader中添加per-instance 属性,示例如下:
Shader "SimplestInstancedShader"
{
Properties
{
_Color ("Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID // necessary only if you want to access instanced properties in __fragment Shader__.
};
//声明实例属性_Color
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)
v2f vert(appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o); // necessary only if you want to access instanced properties in the fragment Shader.
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i); // necessary only if any instanced properties are going to be accessed in the fragment Shader.
return UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
}
ENDCG
}
}
}
上面声明了_Color属性,Unity会从MaterialPropertyBlock中采集_Color属性,设置到GameObject的Shader中,并将GameObject合并在一个DrawCall中
MaterialPropertyBlock props = new MaterialPropertyBlock();
MeshRenderer renderer;
foreach (GameObject obj in objects)
{
float r = Random.Range(0.0f, 1.0f);
float g = Random.Range(0.0f, 1.0f);
float b = Random.Range(0.0f, 1.0f);
props.SetColor("_Color", new Color(r, g, b));
renderer = obj.GetComponent();
renderer.SetPropertyBlock(props);
}
一些高级Tips
1.当Unity进行batch的时候,Static batching
优先级要比instancing高,也就是说,如果将某个GameObject设置为static batching,Unity 会禁掉instancing,即使使用了instancing的shader,但是会完成static batching。这时,Inspector窗口会显示警告提示关闭static batch。
GPU Instancing的优先级会比dynamic batching高,当执行instancing的时候,dynamic batching会被禁用。
2.某些情况如材质改变或者深度排序会导致阻止Unity进行自动的instancing,这是可以调用Graphics.DrawMeshInstanced
强制执行GPU instancing。
3.从Unity2018.1开始,Unity支持GI 渲染时,在GPU Instancing使用光照探针及occlusion探针。可以在MaterialPropertyBlock中提供 light probe 及 occlusion probe数据(LightProbeUsage
参数中设置).
4.使用UnityObjectToClipPos(v.vertex) 而不是 mul(UNITY_MATRIX_MVP,v.vertex),前者更高效。
其他需要注意的地方:
1.SurfaceShader默认会生成示例变量,但是可以用#pragma noinstancing禁止这一行为。
2.Graphics.DrawMeshInstanced需要在材质设置中开启GPU Instancing设定,但是Graphics.DrawMeshInstancedIndirect不需要。
3.instanced draw call在Frame Debugger中以 Draw Mesh (instanced)形式出现。
4.在前向渲染的时候,只有base pass会执行instancing,add pass不会
5.如果有两个以上的pass,只有第一个pass会被instanced,因为后面的pass需要强制在一起渲染,这个操作会是材质发生变化.
5.Draw call batching
Dynamic batching:通常用于特别小的mesh,在cpu层面做顶点变换,之后将相似的顶点合批在一个draw call中绘制。会耗费大量cpu时间。
Static batching: 将静态的GameObject合并成一个大的mesh,从而提高渲染的速度。缺点是会耗费大量内存和硬盘空间。
只有使用相同的材质的GameObjects才会被合批处理。
1.如果除了纹理,两个材质完全相同,可以将两个纹理合在一张大的纹理中。
2.Renderer.material会创建一份材质的拷贝,而不是共享。需要调用Renderer.sharedMaterial共享材质
3.Shadow casters在材质不同的情况下也会被合批处理,只要供Shadow casters使用的材质参数一样即可。
4.一次动态合批不能超过900个顶点属性,不能超过300个顶点。
5.镜面GameObject(scale = -1)不能被合批
6.除了Shadow casters外,使用不同material的实例的对象不能被合批
7.动态合批需要将顶点全部转化为世界坐标,所以只有当这项消耗低于合批的时候才值得做动态合批。