上下黑边遮幅的镜头效果其标准称谓我也不确定,搜索了一轮姑且称为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
}
}
}



