上下黑边遮幅的镜头效果其标准称谓我也不确定,搜索了一轮姑且称为LetterBoxing(一种画面缩放策略),在游戏中常见用于播放过场动画时的镜头处理,带出一种信息:“当前是在播放动画/电影片段,玩家暂时无法操作”。
在Unity Post Processing Stack 3(Post Processing Volume)中,有自带的vignette效果,但它是把屏幕四周都上色,于是手动实现一个上下黑边的LetterBoxing效果并整合到Volume中,好处是可以像其他效果一样,通过Volume对象的Weight来调节参数:
自定义Volume Component
自定义Renderer Feature
RendererFeature只传入一个材质当做参数,而其余的参数放在Volume中调节,甚至开关也是由Volume控制。
Scriptable Renderer Feature
public class LetterBoxingRenderFeature : ScriptableRendererFeature { private LetterBoxingPass _letterBoxingPass; //把材质当做参数配置,避免打包时漏掉Shader //若用shader生成材质,则必须在ProjectSetting窗口中配置好常驻Shader,否则打包之后材质会创建不出来 public Material letterBoxingMaterial; public override void Create() { _letterBoxingPass = new LetterBoxingPass(letterBoxingMaterial); } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { _letterBoxingPass.Setup(renderer.cameraColorTarget); renderer.EnqueuePass(_letterBoxingPass); } }
Scriptable Render Pass
public class LetterBoxingPass : ScriptableRenderPass { private Material _letterBoxingMat; private LetterBoxing _letterBoxing; private RenderTargetIdentifier _currentTarget; static readonly string RenderTag = "Letter Boxing Effect"; static readonly int MainTexId = Shader.PropertyToID("_MainTex"); static readonly int TempTargetId = Shader.PropertyToID("LetterBoxingTemp"); static readonly int SizeId = Shader.PropertyToID("_Size"); public LetterBoxingPass(Material mat) { //与PostProcessingVolume做了绑定,必须赶在PostProcessPass执行之前 renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; //把材质当做参数配置,避免打包时漏掉Shader //若用shader生成材质,则必须在ProjectSetting窗口中配置好常驻Shader,否则打包之后材质会创建不出来 _letterBoxingMat = mat; } public void Setup(in RenderTargetIdentifier currentTarget) { _currentTarget = currentTarget; } public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { if (_letterBoxingMat == null) { Debug.LogError("Material not created."); return; } if (!renderingData.cameraData.postProcessEnabled) return; var stack = VolumeManager.instance.stack; if(_letterBoxing==null) _letterBoxing = stack.GetComponent<LetterBoxing>(); if (_letterBoxing == null || !_letterBoxing.IsActive()) { return; } if (_letterBoxing.verticalSize.value <= float.Epsilon) {return;} var cmd = CommandBufferPool.Get(RenderTag); ref var cameraData = ref renderingData.cameraData; var source = _currentTarget; int destination = TempTargetId; var w = cameraData.camera.scaledPixelWidth; var h = cameraData.camera.scaledPixelHeight; _letterBoxingMat.SetFloat(SizeId, _letterBoxing.verticalSize.value); int shaderPass = 0; cmd.SetGlobalTexture(MainTexId, source); cmd.GetTemporaryRT(destination, w, h, 0, FilterMode.Bilinear, RenderTextureFormat.Default); cmd.Blit(source, destination); cmd.Blit(destination, source, _letterBoxingMat, shaderPass); context.ExecuteCommandBuffer(cmd); CommandBufferPool.Release(cmd); } }
Volume Component
public class LetterBoxing : VolumeComponent, IPostProcessComponent { public BoolParameter on = new BoolParameter(false); [Tooltip("上下黑边对于屏幕高度的占比")]//这里的默认值只能取min,否则会导致修改volume权重的时候插值不正确,因为是取“默认值”作为插值左边参数的 public ClampedFloatParameter verticalSize = new ClampedFloatParameter(0f, 0f, .5f); public bool IsActive() => on.value; public bool IsTileCompatible() => false; }
Shader
Shader "MyAct/LetterBoxing" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex Vert #pragma fragment Frag #include "UnityCG.cginc" sampler2D _MainTex; float _Size; ... fixed4 Frag (v2f i) : SV_Target { float2 uv = i.uv; if(uv.y < _Size || uv.y > 1-_Size) return fixed4(0, 0, 0, 1); fixed4 col = tex2D(_MainTex, uv); return col; } ENDCG } } }