
基于Compute Shader的高斯模糊
2025/4/1大约 5 分钟
基于Compute Shader的高斯模糊

com.qstx.rendererfeature
仓库地址
注意:本文使用的是URP14,不同版本略有差异
一、 自定义RendererFeature的实现
简单自定义RandererFeature的实现可参考URP官方教程
比较重要的是需要单独实现自己的ScriptableRendererFeature类和ScriptableRenderPass类
1. ScriptableRendererFeature类
比较重要的方法:
- Create:初始化RendererFeature所需的资源,每次序列化时调用
- Dispose:释放申请的资源
- OnCameraPreCull:在渲染管线Cull之前的准备
- AddRenderPasses:向渲染管线中加入自定义Pass,会在每次渲染之前执行一次
- SetupRenderPasses:Render Target初始化完成后的回调,保证此时可以访问Render Target
2. ScriptableRenderPass类
比较重要的属性:
- renderPassEvent:决定Pass在渲染管线中的执行时机
比较重要的方法:
- Configure:在Pass执行前调用,用于配置Render Target的属性
- ConfigureClear:配置当前Pass Render Target的Clear标志,应在Configure中调用
- ConfigureTarget:配置当前Pass Render Target的Render Target,应在Configure中调用
- Execute:Pass真正的执行逻辑
- OnCameraSetup:相机开始渲染前调用,用于配置Render Target及其Clear标志
- OnCameraCleanup:相机结束渲染后调用,用于释放当前Pass创建的资源
- OnFinishCameraStackRendering:一个相机栈中所有相机渲染完成后调用,用于释放当前Pass申请但供相机栈中其他相机使用的资源
二、 基于Compute Shader实现高斯模糊效果
1. 指定输入输出参数:
//输出要求可写
RWTexture2D<float4> Result;
Texture2D<float4> InputTexture;
//需要模糊的像素半径
int BlurRadius;2. 定义核函数
[numthreads(8, 8, 1)]
void GaussianBlurFull (uint3 id : SV_DispatchThreadID)
{
float4 color = float4(0, 0, 0, 0);
float weightSum = 0.0;
for (int y = -BlurRadius; y <= BlurRadius; y++)
{
for (int x = -BlurRadius; x <= BlurRadius; x++)
{
float2 offset = float2(x, y);
//根据距离求像素对目标像素的贡献权重
float weight = exp(-(x * x + y * y) / (2.0 * (BlurRadius+1) * BlurRadius+1));
color += InputTexture[id.xy + offset];
weightSum += weight;
}
}
//计算卷积后的最终颜色值,这个值包含了BlurRadius范围内所有像素的加权贡献
Result[id.xy] = color / weightSum;
}但是二维高斯函数又可以分为两个不同维度上一维的高斯函数的积,因此可以将执行复杂度从 降低到
下面分别实现了水平方向高斯模糊和竖直方向高斯模糊的核函数
[numthreads(8, 8, 1)]
void GaussianBlurHorizontal (uint3 id : SV_DispatchThreadID)
{
float4 color = float4(0, 0, 0, 0);
float weightSum = 0.0;
for (int x = -BlurRadius; x <= BlurRadius; x++)
{
float2 offset = float2(x, 0);
float weight = exp(-(x * x) / (2.0 * (BlurRadius+1) * BlurRadius+1));
color += InputTexture[id.xy + offset];
weightSum += weight;
}
Result[id.xy] = color / weightSum;
}
[numthreads(8, 8, 1)]
void GaussianBlurVertical (uint3 id : SV_DispatchThreadID)
{
float4 color = float4(0, 0, 0, 0);
float weightSum = 0.0;
for (int y = -BlurRadius; y <= BlurRadius; y++)
{
float2 offset = float2(0, y);
float weight = exp(-(y * y) / (2.0 * (BlurRadius+1) * BlurRadius+1));
color += InputTexture[id.xy + offset];
weightSum += weight;
}
Result[id.xy] = color / weightSum;
}三、 GaussianBlurPass的实现
1. 定义必要属性
public ComputeShader computeShader;//用于执行高斯模糊的Compute Shader
public int blurRadius = 5;
public BlurMode blurMode = BlurMode.Full;//高斯模糊的方式,见下文GaussianBlurRendererFeature.BlurMode中的定义
//用于中间渲染的临时Render Target
private RenderTextureDescriptor tempDesc;
private RTHandle tempTarget1;
private RTHandle tempTarget2;
private int horizontalKernalIdx = -1;
private int verticalKernalIdx = -1;
private int fullKernalIdx = -1;2. 构造时获取成员属性的设置
public GaussianBlurPass(ComputeShader shader)
{
computeShader = shader;
renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;//在后处理之前执行
//确定不同方式的高斯模糊核函数
horizontalKernalIdx = computeShader.FindKernel("GaussianBlurHorizontal");
verticalKernalIdx = computeShader.FindKernel("GaussianBlurVertical");
fullKernalIdx = computeShader.FindKernel("GaussianBlurFull");
}3. 每次执行渲染之前配置RT
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
//保持与相机RT属性一致,但不需要深度且可写
tempDesc = cameraTextureDescriptor;
tempDesc.depthStencilFormat = GraphicsFormat.None;
tempDesc.enableRandomWrite = true;
//RT属性发生变化或RT还不存在时重新分配
RenderingUtils.ReAllocateIfNeeded(ref tempTarget1, in tempDesc, name: "TempTarget1");
if (blurMode == BlurMode.HorizontalAndVertical)
RenderingUtils.ReAllocateIfNeeded(ref tempTarget2, in tempDesc, name: "TempTarget2");
}4. 执行渲染
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
//确定执行的核函数
int curKernelIdx = -1;
switch (blurMode)
{
case BlurMode.Full:
curKernelIdx = fullKernalIdx;
break;
case BlurMode.Horizontal:
case BlurMode.HorizontalAndVertical:
curKernelIdx = horizontalKernalIdx;
break;
case BlurMode.Vertical:
curKernelIdx = verticalKernalIdx;
break;
}
CommandBuffer cmd = CommandBufferPool.Get("Gaussian Blur");
var src = renderingData.cameraData.renderer.cameraColorTargetHandle;
//向Command Buffer中提交绘制
// Dispatch the compute shader
cmd.SetComputeTextureParam(computeShader, curKernelIdx, "InputTexture", src);
cmd.SetComputeTextureParam(computeShader, curKernelIdx, "Result", tempTarget1);
cmd.SetComputeFloatParam(computeShader, "BlurRadius", blurRadius);
cmd.DispatchCompute(computeShader, curKernelIdx,
Mathf.CeilToInt(renderingData.cameraData.cameraTargetDescriptor.width / 8.0f),
Mathf.CeilToInt(renderingData.cameraData.cameraTargetDescriptor.height / 8.0f), 1);
//如果是分两个阶段执行二维高斯模糊,需要继续运行第二个核函数
if (blurMode == BlurMode.HorizontalAndVertical)
{
// Dispatch the compute shader
cmd.SetComputeTextureParam(computeShader, verticalKernalIdx, "InputTexture", tempTarget1);
cmd.SetComputeTextureParam(computeShader, verticalKernalIdx, "Result", tempTarget2);
cmd.SetComputeFloatParam(computeShader, "BlurRadius", blurRadius);
cmd.DispatchCompute(computeShader, verticalKernalIdx,
Mathf.CeilToInt(renderingData.cameraData.cameraTargetDescriptor.width / 8.0f),
Mathf.CeilToInt(renderingData.cameraData.cameraTargetDescriptor.height / 8.0f), 1);
cmd.Blit(tempTarget2, src);//将结果写回相机
}
else
{
cmd.Blit(tempTarget1, src);//将结果写回相机
}
//提交Command Buffer
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}5. 释放资源
public void Dispose()
{
tempTarget1?.Release();
tempTarget2?.Release();
}四、 GaussianBlurRendererFeature的实现
1. 定义必要属性
开放所需的Compute Shader等属性,以便于在面板上设置
[System.Serializable]
public enum BlurMode
{
Horizontal,
Vertical,
HorizontalAndVertical,
Full,
}
[System.Serializable]
public class Settings
{
[Range(0,100)]public int blurRadius = 5;
public BlurMode blurMode = BlurMode.Full;
}
public Settings settings = new Settings();
public ComputeShader computeShader;
private GaussianBlurPass blurPass;2. 创建Pass
public override void Create()
{
blurPass = new GaussianBlurPass(settings.computeShader);
}3. 提交Pass
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.computeShader == null)
{
Debug.LogError("Compute Shader is missing!");
return;
}
blurPass.computeShader = settings.computeShader;
blurPass.blurRadius = settings.blurRadius;
blurPass.blurMode = settings.blurMode;
renderer.EnqueuePass(blurPass);
}4. 释放资源
protected override void Dispose(bool disposing)
{
blurPass.Dispose();
}五、 执行效果


水平模糊

竖直模糊

先水平再竖直模糊

二维卷积模糊
重要
两个一维高斯模糊比一个二维高斯模糊更优,但效果并无差异。
将模糊半径设置为60:
- 先水平再竖直模糊,平均帧率为275FPS
- 二维卷积模糊,平均帧率为24FPS

先水平再竖直模糊

二维卷积模糊
