Direct2D 1.1 开发笔记 特效篇(五) 径向模糊

(转载请注明出处)

很久没更新博客了, 有点忙, 最近有点疲惫了, 写写新的东西也不错, 这次接着说Direct2D 的特效篇. 有一年多没更新这部分了. 发现代码水平比一年前又提高了不少:)

自从用了vs2015, 腰不酸了, 腿不疼了, 一口气码几百行也不费劲儿. 所以工程代码可能需要Win10 SDK + VS2015才能编译成功. 理论Win8可以, 稍微改下Win7也行, 但是Win10能保证没问题:)

这次要要说说径向模糊:

微软提供的内建特效中拥有高斯模糊和定向模糊. 对于基本应用来说还差个径向模糊.

那么我们现在就动手做一个吧, 先做一个”游戏版”的径向模糊, 稍微简单点:
模糊算法我们大概都懂: 处理后的像素点是原来”附近”几个采样点的加权平均值. 高斯模糊就是高斯函数的加权, 普通就算术平均值即可, 简单.

所以理论上我们只要顺着一个中心点, 进行采样即可.
Direct2D 1.1 开发笔记 特效篇(五) 径向模糊_第1张图片
比如8个采样点, 强度大致上60%, (当然这是我提供的样图而已, 实际是64个采样点否则不能这么细腻),
这个输出像素点颜色就是:

这个特殊情况的算法

point = ?, ?
center = ?, ?
step = (center - point) / 8 * 0.6
color = 0
for(i: 0...8) color += get_color(point + step*i)
color /= 8

怎么样, 简单吧, 如果强度达到100%, 基本每个点都为有中心点的”基因”(忘记用啥词了).

所以这次依然是用像素着色器, 不过带有常量缓存区了(cbuffer):

// 2D纹理 第一个输入储存在t0
Texture2D InputTexture : register(t0);
// 采样器状态 第一个储存在s0
SamplerState InputSampler : register(s0);

// 常量缓存 (b0)
cbuffer RadialBlurProperties : register(b0) {
    float2  center      : packoffset(c0);
    float   magnitude   : packoffset(c0.z);
    float   samples     : packoffset(c0.w);
};

// Shader入口
float4 main(
    float4 clipSpaceOutput  : SV_POSITION,
    float4 sceneSpaceOutput : SCENE_POSITION,
    float4 texelSpaceInput0 : TEXCOORD0
    ) : SV_Target {
    // 初始化
    float4 color = 0;
    // 计算步进值
    float2 step = (center - texelSpaceInput0.xy) / samples * magnitude;
    // 遍历每个采样点
    for(float i = 0; i < samples; ++i)  {
        // 计算采样地点
        float2 pos = texelSpaceInput0.xy + step * i;
        // 添加采样数据
        color += InputTexture.Sample(InputSampler, pos);
    }
    // 平均
    return color / samples;
}

main函数没什么说的吧, 与之前的特殊情况基本一个道理.

值得说明的是:

  • 像素着色器里面的关于图像单位是1.0, 所以图像中心点就是0.5,0.5.
  • 所以这里径向模糊的中心点单位也是如此
  • 强度单位也用1.0, 方便, 一般范围[-1.0, 1.0]
  • 采样率用动态的, 也可以写死方便编译器优化.
// 常量缓存 (b0)
cbuffer RadialBlurProperties : register(b0) {
    float2  center      : packoffset(c0);
    float   magnitude   : packoffset(c0.z);
    float   samples     : packoffset(c0.w);
};

center, 模糊控制中心点。
magnitude, 模糊强度.
samples, 采样数量. 可以用整型, 但是自以为显卡的浮点运算能力比较强, 所以就用float了.
cbuffer是个关键字, 表示常量缓存, 从D3D12里面知道是以256字节对齐的, 所以尽可能用上256字节的倍数吧.
b0表示第一个常量缓存寄存器.
packoffset是因为默认128位对齐, 所以用这个.

C++对象

c++端 就简单了, 这次二合一:

// 径向模糊特效
class RadialBlurEffect final : public ID2D1EffectImpl, public ID2D1DrawTransform {
public:
    // 属性列表
    enum : UINT32 {
        // D2D1_POINT_2F
        Properties_CenterPoint = 0,
        // float
        PROPERTIES_Magnitude,
        // float
        PROPERTIES_Samples
    };
   .........
public:
    // 设置中心点
    auto SetCenter(const D2D1_POINT_2F& pt) noexcept { m_cbuffer.center = pt; }
    // 获取中心点
    auto GetCenter() const noexcept { return m_cbuffer.center; }
    // 设置程度
    auto SetMagnitude(float m) noexcept { m_cbuffer.magnitude = m; }
    // 获取程度
    auto GetMagnitude() const noexcept { return m_cbuffer.magnitude; }
    // 设置采样数量
    auto SetSamples(float s) noexcept { m_cbuffer.samples = s; }
    // 获取采样数量
    auto GetSamples() const noexcept { return m_cbuffer.samples; }
private:
    .................
    // 常量缓存
    struct {
        // 中心点
        D2D1_POINT_2F           center = D2D1_POINT_2F();
        // 程度
        float                   magnitude = 0.1f;
        // 采样点数量
        float                   samples = 16.f;
        // 常量缓存
    }                           m_cbuffer;
};

这次同时继承于ID2D1EffectImpl, ID2D1DrawTransform, 方便编写.

更新cbuffer:

修改了控制变量, 就同步修改cbuffer, 这是正解. 但是修改一个就同步修改, 性能肯定低下, 毕竟受限于显卡带宽. 所以我们只需要在渲染前检查: 如果修改了, 就同步显卡数据. 微软为我们提供了ID2D1EffectImpl::PrepareForRender接口, 方便检查:

// 准备渲染
IFACEMETHODIMP RadialBlurEffect::PrepareForRender(D2D1_CHANGE_TYPE changeType) noexcept {
    if (changeType == D2D1_CHANGE_TYPE_NONE) return S_OK;
    // 设置像素着色器
    m_pDrawInfo->SetPixelShader(GUID_DustPG_RadialBlurShader);
    // 修改了属性
    if (changeType == D2D1_CHANGE_TYPE_PROPERTIES) {
        // 更新常量缓存
        return m_pDrawInfo->SetPixelShaderConstantBuffer(
            reinterpret_cast(&m_cbuffer), sizeof(m_cbuffer)
            );
    }
    return S_OK;
}

变量绑定

需要两部分:

  • Xml信息
  • D2D1_PROPERTY_BINDING数据

    Xml比较简单添加Property节点就行:

xml(
<Effect>
    <Property name = "DisplayName" type = "string" value = "RadiaBlurEffect" />
    <Property name = "Author" type = "string" value = "dustpg" />
    <Property name = "Category" type = "string" value = "Transform" />
    <Property name = "Description" type = "string" value = "径向模糊" />
    <Inputs>
        <Input name = "Source" />
    Inputs>
    <Property name='Center' type='vector2'>
        <Property name='DisplayName' type='string' value='radial blur center'/>
    Property>
    <Property name='Magnitude' type='float'>
        <Property name='DisplayName' type='string' value='radial blur cagnitude'/>
    Property>
    <Property name='Samples' type='float'>
        <Property name='DisplayName' type='string' value='radial blur samples count'/>
    Property>
Effect>

参考前面几篇有张列表, type属性里面需要填写.

D2D1_PROPERTY_BINDING用微软提供的宏D2D1_VALUE_TYPE_BINDING就行
, 不过我的失败了, 不知道是不是多继承的锅, 所以就自己写了个, 匿名表达式就是不错:

#define MY_D2D1_VALUE_TYPE_BINDING(CLASS, TYPE, NAME)\
    {\
        L#NAME, [](IUnknown* obj, const BYTE* data, UINT32 len) noexcept {\
            assert(obj && data && len == sizeof(TYPE));\
            auto impl = static_cast(obj);\
            auto ths = static_cast(impl);\
            ths->Set##NAME(*reinterpret_cast(data));\
            return S_OK;\
        },  [](const IUnknown* obj, BYTE* OPTIONAL data, UINT32 len, UINT32* OPTIONAL outeln) noexcept {\
            assert(obj);\
            if (data) {\
                auto impl = static_cast<const ID2D1EffectImpl*>(obj);\
                auto ths = static_cast<const RadialBlurEffect*>(impl);\
                *reinterpret_cast(data) = ths->Get##NAME();\
            }\
            if (outeln) {\
                *outeln = sizeof(TYPE);\
            }\
            return S_OK;\
        }\
    }

有点麻烦, 不过也就那样了. 不过我发现, 这样的话, 岂不是要限定CPU与GPU的大小段一致? ╮( ̄▽ ̄)╭ 不过这不是我们关心的事情.

注册, 初始化, 就不说了.

更新控制变量大致就这样:

// 设置径向模糊参数
void ImageRenderer::config_blur_properties() noexcept {
    // 设置特性
    const int NAMELESS = 400;
    const int NAMELESS_2 = NAMELESS / 2;
    {
        // 计算中心点
        POINT pt; ::GetCursorPos(&pt);
        ::ScreenToClient(m_hwnd, &pt);
        auto size = m_pTestBitmap->GetSize();
        size.width = float(pt.x) / size.width;
        size.height = float(pt.y) / size.height;
        // 计算强度
        auto magnitude = static_cast<float>(int(m_cFrameCount) % NAMELESS - NAMELESS_2) 
            / static_cast<float>(NAMELESS_2);
        magnitude = std::abs(magnitude) * 2.f - 1.f;
        // 计算采样量
        float sam = 64.f;
        // 设置数据
        m_pRadialBlurEffect->SetValue(RadialBlurEffect::Properties_CenterPoint, size);
        m_pRadialBlurEffect->SetValue(RadialBlurEffect::PROPERTIES_Magnitude, magnitude);
        m_pRadialBlurEffect->SetValue(RadialBlurEffect::PROPERTIES_Samples, sam);
    }
}

Direct2D 1.1 开发笔记 特效篇(五) 径向模糊_第2张图片
工程代码为了方便大家, 我放在了github上面,
因为可以在线浏览, 这一节的地址在这里. 对了, 不忘记shader的编译方式, 为了节约运行时时间, 这里依然采用预先编译的方式, 忘了的看看前面.

你可能感兴趣的:(C++,Direct2D)