如何用Unity3D创造蒸汽波滤镜(一)————滤镜架构设计

引言

什么是蒸汽波?

蒸汽波(Vaporwave)是一种20世纪10年代初兴起的音乐和视觉文化风格,以复古、梦幻和怀旧的元素为特点。它常常利用80年代和90年代的流行文化素材,比如霓虹色调、CRT电视的视觉效果,以及经典计算机用户界面的美学,来创造一种既熟悉又超现实的感官体验。

蒸汽波风格的视觉效果通常具有以下特点:

  • 颜色与光效:紫色、粉色和蓝绿色的色调主导,常伴有强烈的霓虹效果。

  • 图像噪声:加入模拟老旧电视机的雪花点和信号干扰效果。

  • 几何与失真:通过几何扭曲和鱼眼效果创造一种未来感和不稳定感。

  • 低分辨率或马赛克:模仿早期数字技术的限制。

这种风格因其怀旧的特质和独特的美学价值被广泛应用于艺术设计、游戏开发和视频制作中。

步骤 1:创建 RendererFeature 文件

  1. 创建脚本文件:

    • 在 Unity 的 Assets 文件夹下,右键选择 Create > C# Script
    • 命名脚本,例如:VaporwaveFeature.cs

在实现蒸汽波滤镜时,为了提高代码的可读性和可维护性,我们可以将需要的 Shader 和实时可调节的参数分别封装到两个独立的类中。首先,ShaderSetting 类用于集中存储所有滤镜效果所需的 Shader 引用,通过统一管理可以清晰地扩展和维护。其次,Setting 类用于存储滤镜的动态配置参数,例如颜色偏移、对比度、鱼眼强度等,这些参数可以通过 Inspector 面板实时调节,从而快速测试和调整效果。这种分离式设计不仅使代码更模块化,还显著提升了架构的扩展性和灵活性,非常适合需要多特效集成的复杂滤镜开发。

        [Serializable]
        public class ShaderSetting
        {
            public Shader ConvoluteShader = Shader.Find("Vaporwave/Convolute");
            public Shader BlitGraphShader = Shader.Find("Vaporwave/BlitGraph");
            public Shader BlitGraphInvertShader = Shader.Find("Vaporwave/BlitGraphInvert");
            public Shader SnowShader = Shader.Find("Vaporwave/Snow");
            public Shader InvertLightShader = Shader.Find("Vaporwave/InvertLight");
            public Shader InterlacedShader = Shader.Find("Vaporwave/Interlaced");
            public Shader TransposeXShader = Shader.Find("Vaporwave/TransposeX");
            public Shader YUVHandleShader = Shader.Find("Vaporwave/YUVHandle");
            public Shader RGB2YUVShader = Shader.Find("Vaporwave/RGB2YUV");
            public Shader YUV2RGBShader = Shader.Find("Vaporwave/YUV2RGB");
            public Shader GraphNoiseShader = Shader.Find("Vaporwave/GraphNoise");
            public Shader EvLEDShader = Shader.Find("Vaporwave/EvLED");
            public Shader FishEyeShader = Shader.Find("Vaporwave/FishEye");
            public Shader WaterMarkShader = Shader.Find("Vaporwave/WaterMark");
        }
        [Serializable]
        public class Setting
        {
            public bool yuvHandleXEnable = true;
            public float shiftX = 6f;
            public float shiftY = 4f;
            public float shiftU = 0f;
            public float shiftV = 0f;
            public float level = 4f;
            public float contrast = 1f;
            public float light = 1f;
            public float darkFade = 0f;
            public float brightFade = 0f;
            public float vividU = 1.18f;
            public float vividV = 0.93f;
            public bool interlacedEnable = true;
            public int interlaced = 1;
            public int interlacedLine = 4;
            public float interlacedLight = 0.2f;
            public bool transposeXEnable = false;
            public float transposeX = 0.04f;
            public float transposePow = 6.7f;
            public float transposeNoise = 0.616f;
            public bool qualityEnable = true;
            public float darkNoise = 5;
            public float lightNoise = 0;

            public bool LEDResolutionEnable = false;
            public int LEDResolutionLevel = 1;

            public bool FishEyeEnable = true;
            public float FishEyeIntensity_X = 0.1f;
            public float FishEyeIntensity_Y = 0.1f;
            public float FishEyePow = 0.5f;

            public bool WaterMarkEnable = false;
            public Vector4 MarkTextureRect = new Vector4(0.5f, 0.21f, 1f, 1f);
            public float MarkTextureAlpha = 1;

            public Texture2D MarkTexture;
            public GameObject MarkTextPrefab;
            public string MarkTextFiled;

            public bool emphasizeLines = true;

            public ConvolutionKernels.KernelType convoluteType = ConvolutionKernels.KernelType.RightTilt;
            public bool snowEffect = false;
            public bool invertLight = false;


        }

1. 定义关键变量

我们需要在 ScriptableRendererFeature 中定义以下几个核心变量:

  • ShaderSettingSetting 实例:用于管理滤镜所需的 Shader 和可调节参数。
  • 自定义渲染 Pass (VaporwaverPass):负责执行具体的滤镜逻辑。
  • RenderPassEvent:决定 Pass 在渲染管线中的执行时机。
public ShaderSetting shaderSetting;   // 存储 Shader 引用
public Setting m_Setting;            // 存储动态配置参数
private VaporwaverPass m_ScriptablePass;  // 自定义 Pass
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing; // Pass 执行时机

2. Create 方法

Create 方法在渲染器初始化时被调用,用于创建并配置 Pass 的实例。

  • 初始化 Pass:通过 m_SettingshaderSetting 实例化 VaporwaverPass
  • 设置 Pass 属性:根据 renderPassEvent 调整 Pass 的行为(如是否启用反转渲染等)。
public override void Create()
{
    m_ScriptablePass = new VaporwaverPass(m_Setting, shaderSetting); // 初始化 Pass

    // 设置 Pass 的执行属性
    m_ScriptablePass.invert = renderPassEvent == RenderPassEvent.AfterRendering;
    m_ScriptablePass.renderPassEvent = renderPassEvent;
}

3. AddRenderPasses 方法

AddRenderPasses 方法在每帧渲染时被调用,用于将自定义 Pass 添加到渲染流水线中。

  • 检查 Pass 是否有效:避免因未初始化或设置错误导致运行时异常。
  • 添加到渲染管线:调用 renderer.EnqueuePass 方法将 Pass 注册到当前帧的渲染任务中。
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
    if (m_ScriptablePass != null)
    {
        renderer.EnqueuePass(m_ScriptablePass);  // 将 Pass 添加到渲染队列
    }
}

在蒸汽波滤镜的实现中,为了动态计算滤镜效果,我们需要生成临时材质和 RenderTextures,并在渲染过程中通过 Shader 实现效果处理。以下是核心步骤和逻辑的详细说明:

1. 生成临时材质

VaporwaverPass 构造函数中,我们使用 ShaderSetting 中定义的 Shader 引用,通过 CoreUtils.CreateEngineMaterial 动态生成所需的临时材质。

目的:
  • 管理材质资源: 为每种滤镜效果生成独立的材质。
  • 与 Shader 绑定: 每个材质对应一个特定的 Shader,实现具体的滤镜逻辑。
public Material convoluteMat;
public Material blitMat;
public Material blitMatInvert;
// ...其他材质定义

public VaporwaverPass(Setting setting, ShaderSetting shaderSetting)
{
    this.setting = setting;

    // 动态生成材质
    convoluteMat = CoreUtils.CreateEngineMaterial(shaderSetting.ConvoluteShader);
    blitMat = CoreUtils.CreateEngineMaterial(shaderSetting.BlitGraphShader);
    blitMatInvert = CoreUtils.CreateEngineMaterial(shaderSetting.BlitGraphInvertShader);
    snowMat = CoreUtils.CreateEngineMaterial(shaderSetting.SnowShader);
    invertLight = CoreUtils.CreateEngineMaterial(shaderSetting.InvertLightShader);
    interlaced = CoreUtils.CreateEngineMaterial(shaderSetting.InterlacedShader);
    transposeX = CoreUtils.CreateEngineMaterial(shaderSetting.TransposeXShader);
    yuvHandle = CoreUtils.CreateEngineMaterial(shaderSetting.YUVHandleShader);
    rgb2yuv = CoreUtils.CreateEngineMaterial(shaderSetting.RGB2YUVShader);
    yuv2rgb = CoreUtils.CreateEngineMaterial(shaderSetting.YUV2RGBShader);
    graphNoise = CoreUtils.CreateEngineMaterial(shaderSetting.GraphNoiseShader);
    EvLED = CoreUtils.CreateEngineMaterial(shaderSetting.EvLEDShader);
    FishEye = CoreUtils.CreateEngineMaterial(shaderSetting.FishEyeShader);
    WaterMark = CoreUtils.CreateEngineMaterial(shaderSetting.WaterMarkShader);
}

2. 定义临时 RTHandle

在滤镜计算过程中,我们需要两个临时的 RenderTextures (temRT0temRT1),用于在效果处理时进行 Ping-Pong 传递。

目的:
  • 数据传递: 通过 Ping-Pong 机制在两个 RTHandle 之间交替处理数据。
  • 资源优化: 动态创建和复用 RT,避免重复分配和释放内存。
public RTHandle temRT0;
public RTHandle temRT1;
public float2 texSize;

public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
    var desc = renderingData.cameraData.cameraTargetDescriptor;
    RenderTextureDescriptor temRTDescriptor;
    
    // 设置 RT 的分辨率
    texSize = new float2(desc.width, desc.height);
    temRTDescriptor = new RenderTextureDescriptor(Mathf.CeilToInt(desc.width), Mathf.CeilToInt(desc.height), RenderTextureFormat.ARGBFloat, 0, 0);
    temRTDescriptor.msaaSamples = 1;
    temRTDescriptor.useMipMap = false;
    temRTDescriptor.colorFormat = renderingData.cameraData.cameraTargetDescriptor.colorFormat;
    temRTDescriptor.sRGB = false;

    // 动态分配临时 RT
    RenderingUtils.ReAllocateIfNeeded(ref temRT0, temRTDescriptor);
    RenderingUtils.ReAllocateIfNeeded(ref temRT1, temRTDescriptor);

    // 配置 RT 的目标和清除方式
    ConfigureTarget(temRT0);
    temRT0.rt.wrapMode = TextureWrapMode.Repeat;
    ConfigureClear(ClearFlag.None, Color.black);

    ConfigureTarget(temRT1);
    temRT1.rt.wrapMode = TextureWrapMode.Repeat;
    ConfigureClear(ClearFlag.None, Color.black);
}

3. 使用 Ping-Pong 机制传递数据

通过在 temRT0temRT1 之间交替传递数据,实现滤镜效果的分步处理。

关键逻辑:
  • GetSourceRTGetTargetRT 方法: 动态确定当前的输入和输出 RT。
  • SwapRT 方法: 每次处理后交换 RT,确保下一个步骤使用正确的输入。
public RTHandle GetSourceRT()
{
    return pingpong ? temRT1 : temRT0;
}

public RTHandle GetTargetRT()
{
    return pingpong ? temRT0 : temRT1;
}

public void SwapRT()
{
    pingpong = !pingpong;
}

4. 滤镜效果的执行

最终在滤镜计算完成后,将处理结果从临时 RT 写回到渲染通道。

逻辑步骤:
  1. 初始化临时 RT。
  2. 使用材质和 Shader 处理数据。
  3. 将结果从目标 RT 写回到渲染管线。
            public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
            {
                RTHandle cameraTarget = renderingData.cameraData.renderer.cameraColorTargetHandle;
                if (cameraTarget.rt != null)
                {

                    CommandBuffer cmd = CommandBufferPool.Get("Varporwave Pass");
                    pingpong = false;


                    cmd.Blit(cameraTarget, temRT0, blitMat);
                    //---------------------------------------
                    if (setting.emphasizeLines)
                    {
                        Convolute(cmd);
                    }
                    if (setting.WaterMarkEnable && setting.MarkTexture != null)
                    {
                        WaterMarkEffect(cmd, cameraTarget);
                    }
                    if (setting.snowEffect)
                    {
                        SnowEffect(cmd);
                    }
                    else
                    {
                        if (setting.yuvHandleXEnable)
                        {
                            YUVHandleEffect(cmd);
                        }
                        if (setting.invertLight)
                        {
                            InvertLightEffect(cmd);
                        }
                        if (setting.FishEyeEnable)
                        {
                            FishEyeEffect(cmd);
                        }
                        if (setting.interlacedEnable)
                        {
                            InterlacedEffect(cmd);
                        }
                        if (setting.transposeXEnable)
                        {
                            TransposeXEffect(cmd);
                        }
                    }
                    if (setting.qualityEnable)
                    {
                        QualitySetting(cmd);
                    }

                    if (setting.LEDResolutionEnable)
                    {
                        LEDEffect(cmd);
                    }

                    //---------------------------------------
                    if (pingpong)
                    {
                        cmd.Blit(temRT1, cameraTarget, blitMat);
                    }
                    else
                    {
                        cmd.Blit(temRT0, cameraTarget, blitMat);
                    }
                    context.ExecuteCommandBuffer(cmd);


                    CommandBufferPool.Release(cmd);
                }
            }

通过这种设计,我们构建了一个灵活且模块化的多滤镜架构。利用这种方式,我们不仅可以轻松地在代码中添加或移除滤镜,还可以通过清晰的结构直观地管理滤镜的执行顺序和启用状态。

结语与参考资源

通过本篇文章,我们构建了一个灵活且模块化的多滤镜架构,为实现炫酷的蒸汽波风格效果奠定了坚实的基础。在后续文章中,我们将进一步探索如何实现具体的滤镜效果,如颜色偏移、鱼眼失真和图像噪声等。

如果你对本文提到的内容感兴趣,欢迎参考以下资源:

  • 本教程相关代码和项目托管在 GitHub:Higgins-PT/Vaporwave_Unity。这是对蒸汽波滤镜的优化与适配,专为 Unity 环境设计。
  • 本项目的灵感来源于 itorr 的开源项目 Vaporwave。这是一个独立的蒸汽波特效项目,为滤镜设计提供了重要的技术参考。

后记

本项目的部分代码由一位正在学习的初级 TA 编写(是为了让他练习,才不是因为作者太懒了不想亲自写),因此可能存在某些不够规范或需要进一步优化的地方,还请大家多多包涵。如果你在使用过程中发现问题或有任何改进建议,非常欢迎通过 GitHub 提交 Issue 或 Pull Request,让我们一同完善这个项目。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值