MIT 许可证 NuGet

首页 / 文档 / Compute 特效

Compute 特效

后处理链中的一切 — TAA、泛光、GTAO、DDGI、天空 LUT — 都是一个 ComputeEffect,通过你会为自己使用的同一个公开 API 注册。没有特权内置路径,这意味着你写的特效拥有 与内置特效完全一样的能力。

一个特效是什么

四个成员。Name 用于诊断, Phase 决定它何时运行, Initialize 创建资源, Record 派发。可选的 OnResize 重建任何按窗口尺寸分配的东西。

using Season.Rendering;

sealed class EdgeEffect : ComputeEffect
{
    public const string TextureName = "compute://edges";

    public override string Name => "edges";
    public override ComputePhase Phase => ComputePhase.AfterScene;

    ComputeKernel? _kernel;
    int _width, _height;

    public override bool Initialize(IGraphics graphics)
    {
        _width = graphics.Width;
        _height = graphics.Height;

        _kernel = graphics.CreateComputeKernel(new ComputeKernelDesc
        {
            Name = "edges",
            Source = new ShaderSourceSet { Hlsl = Hlsl, EntryPoint = "CSMain" },
            Bindings = /* ... */,
            WorkgroupX = 8, WorkgroupY = 8, WorkgroupZ = 1
        });

        if (_kernel == null) return false;
        return graphics.CreateComputeTexture(TextureName, _width, _height);
    }

    public override void Record(IGraphics graphics)
    {
        graphics.DispatchCompute(new ComputeDispatchArgs
        {
            Kernel = _kernel!,
            Resources = /* ... */,
            GroupsX = (_width + 7) / 8,
            GroupsY = (_height + 7) / 8,
            GroupsZ = 1
        });
    }
}
返回 false 是受支持的失败方式

Initialize 返回 false 不是错误条件,它是 优雅降级路径:特效被丢弃,它创建的任何东西都不留下, 帧在没有它的情况下继续运行。用它处理任何你在启动时发现的事情 — 这台设备 不支持 compute、这个后端没有着色器源码、用户关掉的某个模式。 每个内置特效都这样做,这就是为什么同一个应用能跑在一台完全没有 compute 的机器上。

内核为四个后端携带源码

ShaderSourceSet 有一个 Hlsl、Glsl、 Msl 和 Wgsl 槽,加上一个 EntryPoint,默认为 CSMain。空槽意味着该后端无法运行这个内核,并且 在那里注册会干净地失败,而不是拖到绘制时。

类型用途
ComputeKernelDesc名称、源码集、绑定列表、工作组大小。ValidateWorkgroupSize() 抓出没有后端接受的大小。
ComputeBindingDesc一个槽:它的 ComputeBindingType,对存储纹理来说还有它的 ComputeStorageFormat。
ComputeResourceRef你在派发时绑定的东西 — 一个纹理名、一个 StorageBuffer 或一个渲染目标。隐式转换意味着你通常只需传字符串。
ComputeDispatchArgs一个 ref struct:内核、推式常量、资源引用和组数。每帧无需分配任何东西。

工作组大小默认为 8×8×1,这是几乎每个全屏内核 想要的形状。让描述符里的值与 Record 中的除数保持一致 — 没人替你检查这一点, 症状是右边缘和下边缘少了一条像素。

注册,以及为什么顺序重要

在 base.Create() 之后注册,那时图形设备已存在。 同一阶段的特效按注册顺序记录,所以注册顺序就是 执行顺序。

void RegisterEffects()
{
    var g = Season.Basic.Graphics.Instance;

    // Returns false when the effect declined to initialize. Skip the
    // debug control that would display its output and carry on.
    if (FrameSchedule.RegisterCompute(g, new EdgeEffect()))
        AddControl(new Sprite2D { Name = EdgeEffect.TextureName, /* ... */ });
}
TAA 必须注册在泛光之前

两者都住在 AfterScene,而泛光必须消费 已解析的图像。顺序反过来注册,泛光就会放大 TAA 本来要 消除的闪烁,结果是高光上的可见闪烁,而不是细微的 差别。参考应用按 plasma、场景拷贝、TAA、泛光的顺序注册, 原因写在注释里。

UnregisterCompute 释放特效创建的一切; ResizeCompute 在窗口变化时替你调用并转发到 OnResize。你永远不调用 Execute — 帧会调用。

两个阶段

阶段何时典型用途
FrameStart场景绘制之前。场景将要读取的一切:天空与透射率 LUT、云噪声、程序化纹理、SDF 切片视图。
AfterScene场景颜色、深度和速度可用时。后处理:解析、色调映射输入、AO、泛光、调试可视化。
Record 只可以派发

不开启或关闭通道,不绘制调用,不屏障。同步在 后端的 DispatchCompute 内部处理,这既防止一个 特效破坏另一个的资源,也让四个后端能实现同一个 特效列表。如果一个内核需要前一个内核的输出,就在同一个 Record 里按顺序派发它们 — GTAO 对它的 两次模糊通道正是这么做的。

输出是有名字的纹理

一个特效在 compute:// 名下创建它的存储纹理,并把有趣的那张发布到 FrameSchedule 上,让帧的其余部分无需知道类型 就能找到它。GtaoEffect 创建 compute://gtao/ao 并把它赋给 FrameSchedule.AoTexture;光照通道读取那个字段。

特效阶段发布
SkyAtmosphereEffectFrameStartSkyViewTexture、CloudNoiseTexture、AerialLutTexture
PlasmaEffectFrameStartcompute://plasma — compute 基线冒烟测试
Sdf3DViewEffectFrameStartcompute://sdf3dview
TaaEffectAfterScenecompute://taa0 / taa1 乒乓,TaaActive
BloomEffectAfterSceneBloomTexture,加上它的 compute://bloom/ mip 链
GtaoEffectAfterSceneAoTexture
DdgiEffectAfterScene探针和 SDF 体积
DepthView、VelocityView、SceneColorCopyAfterScenecompute://depthview、compute://velocityview、compute://scenecopy

FrameSchedule 也暴露通道输出本身 — SceneColor、SceneDepth、 SceneVelocity、ShadowMap、 PostColor — 以及说明阴影和 后处理通道是否运行过的标志。在 Initialize 中读取它们,以决定 你的特效能否工作。

调试你构建的东西

因为输出是有名字的纹理,而 Sprite2D 按名字绘制纹理,把任何中间阶段 放上屏幕只需三行。这就是参考应用调试模式的 整个实现。

AddControl(new Sprite2D
{
    Name = Season.Rendering.Effects.GtaoEffect.TextureName,
    Color = Colors.White,
    PosX = 20, PosY = 580, Width = 240
});
调试模式并排显示深度、速度、环境光遮蔽与探针视图 调试模式

深度、运动矢量、GTAO、泛光 mip 链、TAA 历史和场景 SDF 都能 这样看到。唯一的例外是 3D 输出:它们用 compute3d:// 前缀,而精灵无法显示体积,所以 可视化一个体积需要一个切片内核 — Sdf3DViewEffect 就是那个内核,如果你 自己写体积特效,它也是一个合理的模板。

从这里出发

后处理与 Compute 覆盖这些特效所接入的帧图 和逐后端支持矩阵。 全局光照与 AO 走读 两个最大的例子。

指南到此结束。再往后的参考是源码,而 Apps/Engine 里的参考应用是这里每一页 的完整示例。