Unity- CommandBuffer和PostProcessing插件

CommandBuffer

CommandBuffers是预先写好一系列的渲染指令,然后在摄像机渲染时可以在适当的时机插入这一段指令,总之还是先看一下官方的案例

第一个是一个毛玻璃的案例,commandBuffers的任务就是为毛玻璃材质的shader提供一张模糊后的屏幕截图,类似GrabPass

下面放的是我为了方便理解删减过的代码,不过用的shader仍然是案例的shader,一个用于高斯模糊的shader

[ExecuteInEditMode]
public class CommandBuffer4BlurGlass : MonoBehaviour we{

	//为要实现毛玻璃效果的shader提供一张模糊后的屏幕贴图

	public Shader BlurShader;
	private CommandBuffer _commandBuffer;
	private Material _blurMat;

	private void OnEnable()
	{
		//创建CommandBuffer
		_commandBuffer=new CommandBuffer();
		_commandBuffer.name = "my grab blur";
		//检查材质
		if (_blurMat==null)
		{
			if (BlurShader == null) return;
			_blurMat=new Material(BlurShader);
			_blurMat.hideFlags = HideFlags.HideAndDontSave;
		}
		
		//申请一张临时的texture存放屏幕截图 -1-1表示摄像机的像素宽高
		//GetTemporaryRT会申请texture的同时,会把他设为一个全局的shader属性,command也可以通过这个来寻找texture
		//所以用Shader.PropertyToID得到一个唯一标识符来传入
		//你也可以用_ScreenCopyTexture在shader得到这张texture(没试验过,应该是。。。),不过这里好像用不到
		int grabScreenID = Shader.PropertyToID("_ScreenCopyTexture");
		_commandBuffer.GetTemporaryRT(grabScreenID,-1,-1,0,FilterMode.Bilinear);
		
		//从BuiltinRenderTextureType.CurrentActive得到屏幕截图
		//这里第二个参数是RenderTargetIdentifier类型,但也可以用RenderTexture之类的进行隐式转换
		_commandBuffer.Blit(BuiltinRenderTextureType.CurrentActive,grabScreenID);
		
		//申请两张小一点的贴图用来来回倒 
		int blurredID = Shader.PropertyToID("_Temp1");
		int blurredID2 = Shader.PropertyToID("_Temp2");
		//这里长宽-2我也不知道是什么意思。。大概是屏幕长宽2分之1吧
		_commandBuffer.GetTemporaryRT (blurredID, -2, -2, 0, FilterMode.Bilinear);
		_commandBuffer.GetTemporaryRT (blurredID2, -2, -2, 0, FilterMode.Bilinear);
		
		//这里进行降采样的操作,把大的texture转到小一点的texture里,减少计算模糊的性能消耗
		_commandBuffer.Blit(grabScreenID,blurredID);
		//grabScreenID没用了就释放掉
		_commandBuffer.ReleaseTemporaryRT(grabScreenID);
		
		//对图像进行高斯模糊
		
		// horizontal blur
		_commandBuffer.SetGlobalVector("offsets", new Vector4(2.0f/Screen.width,0,0,0));
		_commandBuffer.Blit (blurredID, blurredID2, _blurMat);
		// vertical blur
		_commandBuffer.SetGlobalVector("offsets", new Vector4(0,2.0f/Screen.height,0,0));
		_commandBuffer.Blit (blurredID2, blurredID, _blurMat);
		// horizontal blur
		_commandBuffer.SetGlobalVector("offsets", new Vector4(4.0f/Screen.width,0,0,0));
		_commandBuffer.Blit (blurredID, blurredID2, _blurMat);
		// vertical blur
		_commandBuffer.SetGlobalVector("offsets", new Vector4(0,4.0f/Screen.height,0,0));
		_commandBuffer.Blit (blurredID2, blurredID, _blurMat);
		
		//最后将得到的贴图设为全局贴图,这样物体的shader就可以得到这张贴图了
		//(PS:不是这里的BlurShader,BlurShader只用于对截图进行高斯模糊)
		_commandBuffer.SetGlobalTexture("_GrabBlurTexture", blurredID);
		
		_commandBuffer.ReleaseTemporaryRT(blurredID2);
		_commandBuffer.ReleaseTemporaryRT(blurredID);
		
		//最后将这个command加到摄像机上
		Camera.main.AddCommandBuffer (CameraEvent.AfterSkybox, _commandBuffer);
	}

	private void OnDisable()
	{
		Clean();
	}

	void Clean()
	{
		//在摄像机上移除该CommandBuffer
		Camera.main.RemoveCommandBuffer(CameraEvent.AfterSkybox,_commandBuffer);
		
		DestroyImmediate (_blurMat);
	}
	
}

把这个挂在在一个空物体上,你会发现你的Camera界面多了一条信息

打开FrameDebug你可以看到多了一个部分

我们看到多了一个叫做CommanBuffer.AfterSkyBox的部分里出现了我们添加的my grab blur,里面的六个Draw Dynamic就是我们在代码里给Commanbuffer添加的6个Blit命令。

至于为什么在AfterSkyBox里,则是这段代码决定的。

Camera.main.AddCommandBuffer (CameraEvent.AfterSkybox, _commandBuffer);

CameraEvent里有许多的枚举,都是很直接的名字,可以看下面这张图了解不同枚举代表的时机

观察这个,我们选择AfterSkyBox的原因就很明显了,此时所有的不透明物体和天空盒都渲染好了,而透明物体还没开始渲染,我们的毛玻璃就是一个透明物体,所以CommandBuffer执行结束后,因为commandBuffer.SetGlobalTexture("_GrabBlurTexture", blurredID);这个指令,毛玻璃shader就可以通过_GrabBlurTexture得到他想要的模糊屏幕截图了

这里Blit()传入的参数都是int类型的id,但他的实际类型是RenderTargetIdentifier,只不过他支持int还有RenderTexture类型的隐式转换。

Unity原文这样,应该没理解错

Render texture to use can be indicated in several ways: a RenderTexture object, a temporary render texture created with GetTemporaryRT, or one of built-in temporary textures (BuiltinRenderTextureType). All that is expressed by a RenderTargetIdentifier struct, which has implicit conversion operators to save on typing.
 

所以你是通过RenderTexture.GetTemporary之类的方式得到的RenderTexture或其他相关的类型,也是可以直接当参数传进去得,就像用Graphics.Blit()一样。

剩下的两个例子都是用在延迟渲染模式下的,而且我感觉重点好像都是在Shader里面,一个是实现自定义的光照一个是一个类似投影的效果,由于比较复杂(不懂),我就没有自己写一遍。

看了一下官方的代码,在CommandBuffer方面其实主要就是用了CommandBuffer.DrawMesh()这个指令,顾名思义,就是渲染一个物体。和DrawMesh类似的还有一个DrawRenderer方法,可以根据自己的需要选择

除此之外,在第三个案例里还用了一个SetRenderTarget()的方法,

buf.SetRenderTarget (BuiltinRenderTextureType.GBuffer0, BuiltinRenderTextureType.CameraTarget);
foreach (var decal in system.m_DecalsDiffuse)
{
    //这里的shader 返回的是diffuse
	buf.DrawMesh (m_CubeMesh, decal.transform.localToWorldMatrix, decal.m_Material);
}
// render normals-only decals into normals channel
buf.SetRenderTarget (BuiltinRenderTextureType.GBuffer2, BuiltinRenderTextureType.CameraTarget);
foreach (var decal in system.m_DecalsNormals)
{
	//这里的shader 返回的是normal
	buf.DrawMesh (m_CubeMesh, decal.transform.localToWorldMatrix, decal.m_Material);
}
// render diffuse+normals decals into two MRTs
RenderTargetIdentifier[] mrt = {BuiltinRenderTextureType.GBuffer0, BuiltinRenderTextureType.GBuffer2};
buf.SetRenderTarget (mrt, BuiltinRenderTextureType.CameraTarget);
foreach (var decal in system.m_DecalsBoth)
{
	//这里的shader 有两个out类型,一个diffuse,一个normal
	buf.DrawMesh (m_CubeMesh, decal.transform.localToWorldMatrix, decal.m_Material);
}

前两个参数一般是颜色和深度,

深度不要管,颜色我粗浅估计就是shader中的片元函数的返回值,所以SetRenderTarget就是决定shader里算出来的值应该被存储在哪里,因为这里是延迟渲染模式,所以会先算几何信息再算关照,而所有的几何信息信息的存贮在一个叫GBuffer的东西里,其中GBuffer0存储的是漫射信息Diffuse,GBuffer2存储的是则是法线信息normal(更多可以看这个),所以通过SetRenderTarget就可以指令将得到的信息存储到对应的信息区域,第三个例子共提供了三个shader,

normalOnly这个shader返回的实际是法线信息,所以要把它指向GBuffer2

return fixed4(nor*0.5+0.5,1);

而DiffuseNormal这个shader则是通过两个out参数来传递diffuse和normal信息

void frag(v2f i, out half4 outDiffuse : COLOR0, out half4 outNormal : COLOR1)

所以SetRenderTarget第一个参数指定的是包含GBuffer0 GBuufe2的数组。

CommandBuffer的其他方法

这部分涉及到ComputeShader  不了解,先跳过

ComputeShader就是这样的可称为GPGPU编程的东西。顾名思义,它是一段shader程序。它有shader的特性,比如是在GPU上运行的,但它又是脱离于正常的渲染管线之外的,即不对渲染结果造成影响。这跟我们普通的shader不同。

         这种shader扩展名为.compute,是DX11的HLSL风格的shader,当然,也可以转为GLSL,使其能兼容OpenGL(目前还没测试,只在DX11上测试过)

https://blog.csdn.net/csharpupdown/article/details/79385544


这部分就是设置各种全局可访问的数据


这部分可以设置摄像头的各种参数,像修改摄像机的各种变换矩阵啦,视锥体啦


去掉这三大块,剩下就没多少了,有些方法涉及的概念太高级了,放弃理解

PostProcessing

PostProcessing是Unity提供的一个后处理的插件,之前只会用OnRenderImage()实现实现一些简单屏幕后处理效果,之所以把它和CommandBuffer放在一块,是因为我发现它实现后处理效果用的也是CommandBuffer。

用法

首先在目标摄像头挂载一个PostProcessLayer脚本

anti-aliasing就是反锯齿效果,如果摄像机是延迟渲染模式,还会有个DeferredFog

Layer 表示后处理所在的层,也就是说PostProcessVolume所在的layer没有被包括,那么PostProcessVolume上的后处理效果就不会生效

右键生成一个Post-Process Volume

他需要一个Profile文件,Profile就是一个序列化的文件,存储了你想要实现的屏幕特效,一开始是空的,要点new生成一个,然后就可以在Aseet面板里看到这样的一个资源文件了。

你可以在这里增减特效,当然也可以直接在Post-Process Volumn里调整,Post-Process Volumn把这个资源读取并显示在Inspector上了

它有一个box碰撞器,如名字里的Volumn,他可以设置一个区域,只要当摄像机进入该区域,这个PostProcess才会生效,如果你想让这个postProcess任何地方都有,就把Is Global点上

点Add effect就可以添加特效了,总共有有11个内置的后处理效果,自己试一下就知道是干嘛的了,也可以看这个

环境光遮蔽 

屏幕空间反射

动态模糊

自动曝光(模拟人眼对光线的适应,)Auto Exposure

Bloom

径向的色彩偏移 Chromatic Aberration

调色滤镜 Color Grading

景深 Depth of Field

胶片纤维 Grain

扭曲 Lens Distortion

遮罩 Vignette

想用如果你想在游戏进行的每个特定的时机生成一个特效,用QuickVolume()方法

public class VignettePulse : MonoBehaviour
{
    PostProcessVolume m_Volume;
    Vignette m_Vignette;

    void Start()
    {
        m_Vignette = ScriptableObject.CreateInstance<Vignette>();
        m_Vignette.enabled.Override(true);
        m_Vignette.intensity.Override(1f);

        m_Volume = PostProcessManager.instance.QuickVolume(gameObject.layer, 100f, m_Vignette);
    }

    void Update()
    {
        m_Vignette.intensity.value = Mathf.Sin(Time.realtimeSinceStartup);
    }

    void OnDestroy()
    {
        RuntimeUtilities.DestroyVolume(m_Volume, true, true);
    }
}

另外也可以写自定义屏幕特效 不过shader要用HLSL(考虑到兼容性)

c#部分需要两个类 分别继承PostProcessEffectSettings和PostProcessEffectRenderer<Grayscale>

这里实现一个灰阶的屏幕特效

using System;
using UnityEngine;
using UnityEngine.Rendering.PostProcessing;
[Serializable]
[PostProcess(typeof(GrayscaleRenderer), PostProcessEvent.AfterStack, "Custom/Grayscale")]
public sealed class Grayscale : PostProcessEffectSettings
{
    [Range(0f, 1f), Tooltip("Grayscale effect intensity.")]
    public FloatParameter blend = new FloatParameter { value = 0.5f };
}
 
public sealed class GrayscaleRenderer : PostProcessEffectRenderer<Grayscale>
{
    public override void Render(PostProcessRenderContext context)
    {
        var sheet = context.propertySheets.Get(Shader.Find("Hidden/Custom/Grayscale"));
        sheet.properties.SetFloat("_Blend", settings.blend);
        context.command.BlitFullscreenTriangle(context.source, context.destination, sheet, 0);
    }
}

继承PostProcessEffectSettings的类是收集数据的,也就是显示在Inspector面板上的各种属性,所以脚本的名字也应该和这个类名一致。

因为Post-process Volume Profile是可序列化的文件,所以附加在上面的PE自然是要可以序列化的,要加[Serializable]特性

然后是这个特性
[PostProcess(typeof(GrayscaleRenderer), PostProcessEvent.AfterStack, "Custom/Grayscale")]

第一个参数指定真正实现渲染特效的继承PostProcessEffectRenderer<T>的类。

第二个参数表示在一系列特效中,他的渲染顺序,有三个选择

  • BeforeTransparent: the effect will only be applied to opaque objects before the transparent pass is done.
  • BeforeStack: the effect will be applied before the built-in stack kicks-in. That includes anti-aliasing, depth-of-field, tonemapping etc.
  • AfterStack: the effect will be applied after the builtin stack and before FXAA (if it's enabled) & final-pass dithering.

最后一个参数就是他的选择路径

在GrayscaleRenderer 重写Render()方法实现渲染的逻辑。

除了Render(),还有Init(),Release()等可以重写

附上shader,这些代码都是官方的,用的是HLSL的代码块,而不是CG的

在/PostProcessing/Shaders/API/下有各个平台的unity提供的shader的API

Shader "Hidden/Custom/Grayscale"
{
    HLSLINCLUDE

        #include "PostProcessing/Shaders/StdLib.hlsl"

        TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
        float _Blend;

        float4 Frag(VaryingsDefault i) : SV_Target
        {
            float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
            float luminance = dot(color.rgb, float3(0.2126729, 0.7151522, 0.0721750));
            color.rgb = lerp(color.rgb, luminance.xxx, _Blend.xxx);
            return color;
        }

    ENDHLSL

    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            HLSLPROGRAM

                #pragma vertex VertDefault
                #pragma fragment Frag

            ENDHLSL
        }
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值