PostProcessLayer.cs
它必须在Camera上添加。
[RequireComponent(typeof(Camera))]
public sealed class PostProcessLayer : MonoBehaviour
{
……
}
#if UNITY_2019_1_OR_NEWER
// We always use a CommandBuffer to blit to the final render target
// OnRenderImage is used only to avoid the automatic blit from the RenderTexture of Camera.forceIntoRenderTexture to the actual target
[ImageEffectUsesCommandBuffer]
void OnRenderImage(RenderTexture src, RenderTexture dst)
{
if (finalBlitToCameraTarget)
RenderTexture.active = dst; // silence warning
else
Graphics.Blit(src, dst);
}
#endif
属性:ImageEffectUsesCommandBuffer
https://docs.unity3d.com/2019.1/Documentation/ScriptReference/ImageEffectUsesCommandBuffer.html
Camera强制输出到:RenderTexture
Camera.forceIntoRenderTexture
https://docs.unity3d.com/ScriptReference/Camera-forceIntoRenderTexture.html
UpdateVolumeSystem——设置参数
public void Render(PostProcessRenderContext context)——绘制
绘制分为三个步骤:
1、内置的
2、嵌入的
3、最后一个pass
Color grading 中文翻译:颜色校正
判断是否有激活的后处理效果:
public bool HasActiveEffects(PostProcessEvent evt, PostProcessRenderContext context)
{
var list = sortedBundles[evt];
foreach (var item in list)
{
bool enabledAndSupported = item.bundle.settings.IsEnabledAndSupported(context);
if (context.isSceneView) //如果sceneview下需要后处理
{
if (item.bundle.attribute.allowInSceneView && enabledAndSupported)
return true;
}
else if (enabledAndSupported)
{
return true;
}
}
return false;
}
后处理基础类:
PostProcessEffectSettings
绘制后处理的插入命令:
int RenderInjectionPoint(PostProcessEvent evt, PostProcessRenderContext context, string marker, int releaseTargetAfterUse = -1)
{
int tempTarget = m_TargetPool.Get(); //从池子里拿一个序号,这个序号是一个int
var finalDestination = context.destination;
var cmd = context.command;
context.GetScreenSpaceTemporaryRT(cmd, tempTarget, 0, context.sourceFormat); //GetScreenSpaceTemporaryRT这个函数见下:
context.destination = tempTarget;
RenderList(sortedBundles[evt], context, marker); //这个函数见下:
context.source = tempTarget;
context.destination = finalDestination;
if (releaseTargetAfterUse > -1)
cmd.ReleaseTemporaryRT(releaseTargetAfterUse);
return tempTarget;
}
m_TargetPool是一个很简单的池子:
我其实很想解释的每行,但是太简单了,就不做详细一行一行注释了。
class TargetPool
{
readonly List<int> m_Pool;
int m_Current;
internal TargetPool()
{
m_Pool = new List<int>();
Get(); // Pre-warm with a default target to avoid black frame on first frame
}
internal int Get()
{
int ret = Get(m_Current);
m_Current++;
return ret;
}
int Get(int i)
{
int ret;
if (m_Pool.Count > i)
{
ret = m_Pool[i];
}
else
{
// Avoid discontinuities
while (m_Pool.Count <= i)
m_Pool.Add(Shader.PropertyToID("_TargetPool" + i));
ret = m_Pool[i];
}
return ret;
}
internal void Reset()
{
m_Current = 0;
}
}
开始绘制:
void RenderList(List<SerializedBundleRef> list, PostProcessRenderContext context, string marker)
{
var cmd = context.command;
cmd.BeginSample(marker);
// First gather active effects - we need this to manage render targets more efficiently
m_ActiveEffects.Clear();
for (int i = 0; i < list.Count; i++)
{
var effect = list[i].bundle;
if (effect.settings.IsEnabledAndSupported(context))
{
if (!context.isSceneView || (context.isSceneView && effect.attribute.allowInSceneView))
m_ActiveEffects.Add(effect.renderer);
}
}
int count = m_ActiveEffects.Count;
// If there's only one active effect, we can simply execute it and skip the rest
if (count == 1) //如果只有一个后处理效果:
{
m_ActiveEffects[0].Render(context);
}
else
{
// Else create the target chain
m_Targets.Clear();
m_Targets.Add(context.source); // First target is always source,添加source第一个
int tempTarget1 = m_TargetPool.Get(); //申请第一个id
int tempTarget2 = m_TargetPool.Get(); //申请第二个id
for (int i = 0; i < count - 1; i++) //for循环遍历count-1个
m_Targets.Add(i % 2 == 0 ? tempTarget1 : tempTarget2); //现在有count个了
m_Targets.Add(context.destination); // Last target is always destination,添加destination最后一个,目前有count+1个了
// Render
context.GetScreenSpaceTemporaryRT(cmd, tempTarget1, 0, context.sourceFormat);
if (count > 2)
context.GetScreenSpaceTemporaryRT(cmd, tempTarget2, 0, context.sourceFormat);
for (int i = 0; i < count; i++)
{
context.source = m_Targets[i];
context.destination = m_Targets[i + 1];
m_ActiveEffects[i].Render(context);
}
cmd.ReleaseTemporaryRT(tempTarget1);
if (count > 2)
cmd.ReleaseTemporaryRT(tempTarget2);
}
cmd.EndSample(marker);
}
blit过程示意图:
执行之前保存dest保存的id:
finalID = dest;
dest = tempID;
blit(xxx);
source = dest;
dest = finalID;
需要的东西:
1、commandbuffer
2、rt的id和rt
3、源、目的
4、blit方法
5、shader,材质球,网格点
6、释放rt
1、commandbuffer的创建并添加到camera的指定点:
CommandBuffer m_LegacyCmdBuffer = new CommandBuffer { name = “Post-processing” };
并且为摄像机添加这个buffer到指定的插入点:
m_Camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, m_LegacyCmdBuffer);
2、rt的id并且申请rt
int tempTarget = m_TargetPool.Get();
context.GetScreenSpaceTemporaryRT(cmd, tempTarget, 0, context.sourceFormat);
申请的代码:
cmd.GetTemporaryRT(nameID, actualWidth, actualHeight, depthBufferBits, filter, colorFormat, readWrite);
3、源、目的
源头肯定是看到的东西,目的可以是一个rt
var cameraTarget = new RenderTargetIdentifier(BuiltinRenderTextureType.CameraTarget);
这是摄像机看到的那个图
4、blit操作
cmd.SetGlobalTexture(ShaderIDs.MainTex, source);
cmd.SetRenderTargetWithLoadStoreAction(destination, viewport == null ? loadAction : LoadAction.Load, StoreAction.Store);
if (viewport != null)
cmd.SetViewport(viewport.Value);
bool clear = (loadAction == LoadAction.Clear);
if(clear) loadAction = LoadAction.DontCare;
if (clear) cmd.ClearRenderTarget(true, true, Color.clear);
cmd.DrawMesh(fullscreenTriangle, Matrix4x4.identity, propertySheet.material, 0, pass, propertySheet.properties);
5、fullscreenTriangle和材质球
三角形网格:
s_FullscreenTriangle = new Mesh { name = "Fullscreen Triangle" };
s_FullscreenTriangle.SetVertices(new List<Vector3>
{
new Vector3(-1f, -1f, 0f),
new Vector3(-1f, 3f, 0f),
new Vector3( 3f, -1f, 0f)
});
s_FullscreenTriangle.SetIndices(new [] { 0, 1, 2 }, MeshTopology.Triangles, 0, false);
s_FullscreenTriangle.UploadMeshData(false);
一个简单拷贝摄像机画面的shader:
struct AttributesDefault
{
float3 vertex : POSITION;
};
struct VaryingsDefault
{
float4 vertex : SV_POSITION;
float2 texcoord : TEXCOORD0;
float2 texcoordStereo : TEXCOORD1;
#if STEREO_INSTANCING_ENABLED
uint stereoTargetEyeIndex : SV_RenderTargetArrayIndex;
#endif
};
float2 TransformTriangleVertexToUV(float2 vertex)
{
float2 uv = (vertex + 1.0) * 0.5;
return uv;
}
VaryingsDefault VertDefault(AttributesDefault v)
{
VaryingsDefault o;
o.vertex = float4(v.vertex.xy, 0.0, 1.0);
o.texcoord = TransformTriangleVertexToUV(v.vertex.xy);
#if UNITY_UV_STARTS_AT_TOP
o.texcoord = o.texcoord * float2(1.0, -1.0) + float2(0.0, 1.0);
#endif
o.texcoordStereo = TransformStereoScreenSpaceTex(o.texcoord, 1.0);
return o;
}
float4 Frag(VaryingsDefault i) : SV_Target
{
float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoordStereo);
return color;
}
6、释放rt
cmd.ReleaseTemporaryRT(tempTarget1);
ok,至此,我们已经将讲解一个完整的blit所需要的元素。