573 字
3 分钟
极简 RenderFeature 案例

极简 RenderFeature 案例#

Shader 部分#

TIP

可以查看博主之前的帖子,使用 Rider 实时模板来提升开发效率。RiderSkill

Shader "Hidden/GrayScale"
{
SubShader
{
Tags
{
"RenderPipeline" = "UniversalPipeline"
"RenderType" = "Opaque"
}
Pass
{
Name "GrayScale"
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes
{
// 直接告诉 Shader:你现在正在处理第几个顶点
uint vertexID : SV_VertexID;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D_X(_BlitTexture);
SAMPLER(sampler_BlitTexture);
Varyings vert(Attributes input)
{
Varyings output;
output.positionHCS = GetFullScreenTriangleVertexPosition(input.vertexID);
output.uv = GetFullScreenTriangleTexCoord(input.vertexID);
return output;
}
half4 frag(Varyings input) : SV_Target
{
half4 color = SAMPLE_TEXTURE2D_X(_BlitTexture,sampler_BlitTexture,input.uv);
// 把 RGB 三个颜色值,按照不同的重要程度加权,计算出一个灰度值。
half gray = dot(color.rgb,half3(0.299,0.587,0.114));
return half4(gray,gray,gray,color.a);
}
ENDHLSL
}
}
}

half gray = dot(color.rgb,half3(0.299,0.587,0.114));这行代码把 RGB 三个颜色值,按照不同的重要程度加权,计算出一个灰度值。

NOTE

普通 Shader 和 Blit 全屏 Shader 的区别。

POSITION 是”从模型顶点里拿位置”。

SV_VertexID 是”直接告诉 Shader:你现在正在处理第几个顶点”。

我们的 Blit Shader 根本没有真正的模型顶点数据拿,所以用的是 SV_VertexID

RT 部分#

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class GrayScaleRF : ScriptableRendererFeature
{
class GrayScalePass : ScriptableRenderPass
{
private Material material;
private RTHandle cameraColorTarget;
private RTHandle temporaryRT;
public GrayScalePass(Material material)
{
this.material = material;
}
// 使用专门的函数进行初始化,让代码更加规整
public void SetTarget(RTHandle target)
{
cameraColorTarget = target;
}
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor;
descriptor.depthBufferBits = 0;
RenderingUtils.ReAllocateIfNeeded(ref temporaryRT, descriptor, FilterMode.Point, TextureWrapMode.Clamp,
name: "_GrayScaleTemp");
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if(material == null) return;
CommandBuffer cmd = CommandBufferPool.Get("GrayScale");
// Camera -> 临时 RT
Blitter.BlitCameraTexture(cmd,cameraColorTarget,temporaryRT);
// 临时RT -> Camera
Blitter.BlitCameraTexture(cmd,temporaryRT,cameraColorTarget,material,0);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
// 实际开发中实际不太会用 OnCameraCleanup,而是使用 Dispose,与 RF 的 Dispose 一起调用。
public void Dispose()
{
temporaryRT?.Release();
temporaryRT = null;
}
}
[SerializeField] private Material material;
private GrayScalePass pass;
public override void Create()
{
pass = new GrayScalePass(material);
pass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
}
// 在 AddRenderPasses 之前执行,SetTarget 应该在此方法中进行配置
public override void SetupRenderPasses(ScriptableRenderer renderer, in RenderingData renderingData)
{
if(material == null)
return;
if(renderingData.cameraData.cameraType != CameraType.Game) return;
pass.SetTarget(renderer.cameraColorTargetHandle);
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if(material == null)
return;
if(renderingData.cameraData.cameraType != CameraType.Game) return;
renderer.EnqueuePass(pass);
}
protected override void Dispose(bool disposing)
{
pass?.Dispose();
}
}

应用效果#

最终效果

找到当前使用的 RenderAsset 点击 Add Renderer Feature 将我们自定义的 GrayScaleRF 插入。

新建 Material 使用 GrayScale Shader,将材质拖入对应的材质槽位。可以在 Game 窗口看到最后的效果。