使用SCNTechnique实现动态模糊

3
我已经尝试了几天使用SCNTechnique实现运动模糊,但离我想要的效果还有很大差距。我在苹果论坛上提出了类似的问题,但没有得到解答。因此,我打算用代码编写一个更详细的描述来说明我想实现的目标。 设置 在屏幕上放置了一个角色和一些敌人。角色是一个正方形,敌人是圆形-为了简化这个示例。
我正在使用带有Metal的SceneKit。相机是固定的。 目标 当圆/敌人移动时,我希望它们具有运动模糊效果。当角色移动时,不应该有模糊效果。 计划 编写一个多通道SCNTechnique来处理这个问题,我认为这是实现我想要的效果的方法。 第一遍渲染:将敌人/圆形只渲染到另一个缓冲区 第二遍渲染:对此缓冲区应用运动模糊 第三遍渲染:用原始场景渲染运动模糊缓冲区。 附注:第二遍渲染会很棘手,因为我想象中每个对象都有自己的方向,而模糊效果也需要跟随移动。
因此,下面是我在SceneKit中设置对象的方式。
class Character : SCNNode
{
    override init() {
        super.init()

        let img = UIImage(named: "texture1")!

        material = SCNMaterial()
        material.diffuse.contents = img
        material.ambient.contents = img

        let geometry = SCNSphere(radius: 40)
        geometry.materials = [material]

        self.categoryBitMask = 1
    }
}

class Enemy : SCNNode
{
    override init() {
        super.init()

        let img = UIImage(named: "texture2")!

        material = SCNMaterial()
        material.diffuse.contents = img
        material.ambient.contents = img

        let geometry = SCNSphere(radius: 40)
        geometry.materials = [material]

        self.categoryBitMask = 2
    }
}

我可以将它们添加到场景中并且它们看起来不错。我可以使用SCNActions移动它们,它们的移动是正确的。

现在来讲一下我如何尝试运动模糊。

第一步

我想从这一步中仅提取敌人,因此该技术的这一部分看起来像下面这样,注意类别掩码只会为场景中的敌人绘制。

它将输出到我的自定义“enemiesColor”目标缓冲区。 注:DRAW_SCENE

<key>drawEnemies</key>
    <dict>
        <key>draw</key>
        <string>DRAW_SCENE</string>
        <key>includeCategoryMask</key>
        <integer>2</integer>
        <key>excludeCategoryMask</key>
        <integer>1</integer>
        <key>program</key>
        <string>doesntexist</string>
        <key>metalVertexShader</key>
        <string>multi_vertex</string>
        <key>metalFragmentShader</key>
        <string>multi_fragment_vert</string>
        <key>inputs</key>
        <dict>
            <key>colorSampler</key>
            <string>COLOR</string>
            <key>a_texcoord</key>
            <string>a_texcoord-symbol</string>
            <key>aPos</key>
            <string>vertexSymbol</string>
        </dict>
        <key>outputs</key>
        <dict>
            <key>color</key>
            <string>enemiesColor</string>
        </dict>
    </dict>

这个的金属渲染器如下所示:
#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>

struct custom_node_t3 {
    float4x4 modelTransform;
    float4x4 modelViewTransform;
    float4x4 normalTransform;
    float4x4 modelViewProjectionTransform;
};

struct custom_vertex_t
{
    float4 position [[attribute(SCNVertexSemanticPosition)]];
    float2 a_texcoord [[ attribute(SCNVertexSemanticTexcoord0) ]];
    //SCNGeometrySourceSemanticTexcoord
};


constexpr sampler s = sampler(coord::normalized,
                          address::repeat,
                          filter::linear);

struct out_vertex_t
{
    float4 position [[position]];
    float2 texcoord;
};

vertex out_vertex_t multi_vertex(custom_vertex_t in [[stage_in]],
                                    constant custom_node_t3& scn_node [[buffer(0)]])
{
    out_vertex_t out;
    out.texcoord = in.a_texcoord;
    out.position = scn_node.modelViewProjectionTransform *     float4(in.position.xyz, 1.0);

    return out;
};



fragment half4 multi_fragment_vert(out_vertex_t vert [[stage_in]],
                               constant SCNSceneBuffer& scn_frame     [[buffer(0)]],
                                      texture2d<float, access::sample>     colorSampler [[texture(0)]])
{

    float4 FragmentColor = colorSampler.sample( s, vert.texcoord);
    return half4(FragmentColor);
};

第二遍

我想要模糊“enemiesColor”缓冲区,目前它非常粗糙,所以我只是暂时使用高斯模糊技巧。

我将“enemiesColor”缓冲区作为输入并进行模糊处理,我将其输出为一个新的缓冲区:“enemyColor” 注意:DRAW_QUAD

这个通道的技术看起来像:

<key>blurEnemies</key>
    <dict>
        <key>draw</key>
        <string>DRAW_QUAD</string>
        <key>program</key>
        <string>doesntexist</string>
        <key>metalVertexShader</key>
        <string>blur_vertex</string>
        <key>metalFragmentShader</key>
        <string>blur_fragment_vert</string>
        <key>inputs</key>
        <dict>
            <key>colorSampler</key>
            <string>COLOR</string>
            <key>enemyColor</key>
            <string>enemiesColor</string>
            <key>a_texcoord</key>
            <string>a_texcoord-symbol</string>
        </dict>
        <key>outputs</key>
        <dict>
            <key>color</key>
            <string>chrisColor</string>
        </dict>
    </dict>

并且着色器是:

// http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/
constant float offset[] = { 0.0, 1.0, 2.0, 3.0, 4.0 };
constant float weight[] = { 0.2270270270, 0.1945945946, 0.1216216216, 0.0540540541, 0.0162162162 };


vertex out_vertex_t blur_vertex(custom_vertex_t in [[stage_in]],
                             constant custom_node_t3& scn_node [[buffer(0)]])
{
    out_vertex_t out;
    out.position = in.position;
    out.texcoord = float2((in.position.x + 1.0) * 0.5 , (in.position.y + 1.0) * -0.5);

    return out;
};

fragment half4 blur_fragment_vert(out_vertex_t vert [[stage_in]],
                               texture2d<float, access::sample> colorSampler [[texture(0)]],
                                   texture2d<float, access::sample> enemyColor [[texture(1)]])
{

    float4 enemySample = enemyColor.sample(s, vert.texcoord);

    if (enemySample.a == 0)
    {
        //gl_LastFragData[0]
        return half4(0.0 ,1.0 ,0.0, 0.5);
    }


    float4 FragmentColor = colorSampler.sample( s, vert.texcoord) * weight[0];
    for (int i=1; i<5; i++) {
        FragmentColor += colorSampler.sample( s, ( vert.texcoord + float2(0.0, offset[i])/224.0 ) ) * weight[i];
        FragmentColor += colorSampler.sample( s, ( vert.texcoord - float2(0.0, offset[i])/224.0 ) ) * weight[i];
    }
    return half4(FragmentColor);


};

第三遍处理

当事情变得更加混乱时,我想将模糊的“enemyColor”缓冲器与原始场景一起应用。

我的第一个想法是,为什么不能只是叠加结果。我查看了混合模式,但没有找到合适的。

然后我想,也许我可以重新渲染场景,并将“enemyColor”和新的“color”缓冲器相加(如果这个方法即使在某种程度上起作用,可能有一些优化)。

注意:DRAW_SCENE

所以这个技巧就是:

<key>blendTogether</key>
    <dict>
        <key>draw</key>
        <string>DRAW_SCENE</string>
        <key>program</key>
        <string>doesntexist</string>
        <key>metalVertexShader</key>
        <string>plain_vertex</string>
        <key>metalFragmentShader</key>
        <string>plain_fragment_vert</string>
        <key>inputs</key>
        <dict>
            <key>colorSampler</key>
            <string>COLOR</string>
            <key>aPos</key>
            <string>vertexSymbol</string>
            <key>a_texcoord</key>
            <string>a_texcoord-symbol</string>
        </dict>
        <key>outputs</key>
        <dict>
            <key>color</key>
            <string>COLOR</string>
        </dict>
    </dict>

还有着色器(shader):

    vertex out_vertex_t plain_vertex(custom_vertex_t in [[stage_in]],
                             constant SCNSceneBuffer& scn_frame [[buffer(0)]],
                            constant custom_node_t3& scn_node [[buffer(1)]])
{
    out_vertex_t out;

    out.position = scn_node.modelViewProjectionTransform * float4(in.position.xyz, 1.0);
    out.texcoord = in.a_texcoord;

    return out;
};

fragment half4 plain_fragment_vert(out_vertex_t vert [[stage_in]],
                               texture2d<float, access::sample> colorSampler [[texture(0)]])
{

    float4 FragmentColor = colorSampler.sample( s, vert.texcoord);
    return half4(FragmentColor);

};

在我的场景渲染结束后,我没有得到期望的效果。第一个问题是,这样的系统是否可能实现动态模糊效果?如果不可能,我就不想再浪费时间了。第二个问题是,我做错了什么?完成技巧如下:
    <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>passes</key>
    <dict>
        <key>drawEnemies</key>
        <dict>
            <key>draw</key>
            <string>DRAW_SCENE</string>
            <key>includeCategoryMask</key>
            <integer>2</integer>
            <key>excludeCategoryMask</key>
            <integer>1</integer>
            <key>program</key>
            <string>doesntexist</string>
            <key>metalVertexShader</key>
            <string>multi_vertex</string>
            <key>metalFragmentShader</key>
            <string>multi_fragment_vert</string>
            <key>inputs</key>
            <dict>
                <key>colorSampler</key>
                <string>COLOR</string>
                <key>a_texcoord</key>
                <string>a_texcoord-symbol</string>
                <key>aPos</key>
                <string>vertexSymbol</string>
            </dict>
            <key>outputs</key>
            <dict>
                <key>color</key>
                <string>enemiesColor</string>
            </dict>
        </dict>
        <key>blurEnemies</key>
        <dict>
            <key>draw</key>
            <string>DRAW_QUAD</string>
            <key>program</key>
            <string>doesntexist</string>
            <key>metalVertexShader</key>
            <string>blur_vertex</string>
            <key>metalFragmentShader</key>
            <string>blur_fragment_vert</string>
            <key>inputs</key>
            <dict>
                <key>colorSampler</key>
                <string>COLOR</string>
                <key>enemyColor</key>
                <string>enemiesColor</string>
                <key>a_texcoord</key>
                <string>a_texcoord-symbol</string>
            </dict>
            <key>outputs</key>
            <dict>
                <key>color</key>
                <string>chrisColor</string>
            </dict>
        </dict>
        <key>blendTogether</key>
        <dict>
            <key>draw</key>
            <string>DRAW_SCENE</string>
            <key>program</key>
            <string>doesntexist</string>
            <key>metalVertexShader</key>
            <string>plain_vertex</string>
            <key>metalFragmentShader</key>
            <string>plain_fragment_vert</string>
            <key>inputs</key>
            <dict>
                <key>colorSampler</key>
                <string>COLOR</string>
                <key>aPos</key>
                <string>vertexSymbol</string>
                <key>a_texcoord</key>
                <string>a_texcoord-symbol</string>
            </dict>
            <key>outputs</key>
            <dict>
                <key>color</key>
                <string>COLOR</string>
            </dict>
        </dict>
    </dict>
    <key>sequence</key>
    <array>
        <string>blendTogether</string>
    </array>
    <key>targets</key>
    <dict>
        <key>enemiesColor</key>
        <dict>
            <key>type</key>
            <string>color</string>
        </dict>
        <key>chrisColor</key>
        <dict>
            <key>type</key>
            <string>color</string>
        </dict>
    </dict>
    <key>symbols</key>
    <dict>
        <key>a_texcoord-symbol</key>
        <dict>
            <key>semantic</key>
            <string>texcoord</string>
        </dict>
        <key>vertexSymbol</key>
        <dict>
            <key>semantic</key>
            <string>vertex</string>
        </dict>
    </dict>
</dict>
</plist>

既然你在谈论圆和正方形,我假设你的场景基本上是2D(或2.5D,即堆叠的2D图层)? - Hendrik
2个回答

3

我的相机是固定的,不会移动,你知道这是否符合我的要求吗? - Chris
1
SCNCamera的运动模糊是为相机运动而设计的,而不是可见物体的运动模糊。 - rickster
@rickster 在iOS 11中,每个对象的动态模糊效果即将到来。 - Robert

3

假设您的对象是嵌入在SceneKit 3D世界中的2D对象(如您描述的“圆形”和“正方形”),本答案将采用这种情况。

我认为使用SCNTechnique进行多通道渲染可能不是您想要实现的正确解决方案。

这是我的处理方法:

为圆形对象的纹理添加(相当宽的)透明边缘,并使SceneKit圆形对象变大,以便不透明部分具有正确的大小。透明边缘的宽度是对象可以拥有的运动模糊轨迹的最大长度。因此,如果圆形敌人可以在一帧内移动相当远,您将需要一个宽的边缘。

使用自定义SCNProgram为圆形对象指定片段着色器。这是您将实现运动模糊渲染的地方。您需要将对象速度作为自定义变量传递到着色器中(请参阅SCNProgram文档的“自定义变量”部分)。此外,您需要将速度向量转换/转换为圆形对象的2D纹理坐标系。

在片段着色器中,您可以沿着速度向量对纹理进行采样并平均采样的颜色。您可能需要根据速度的大小选择采样数量:对象移动得越快,您将想要使用更多的采样。不过,如果采样数量足够高,则固定数量的样本也可以。

Illustration of fragment shader for motion blur

在上面的示例中,小绿色正方形显示了正在评估片段着色器的示例像素。4个黄点显示了您可能评估纹理的样本位置。在这种情况下,2个样本命中纹理中的透明边缘,另外2个样本落入不透明部分。因此,在这种情况下,输出颜色的alpha值将为0.5。

您还可以尝试使用样本权重,并根据您所追求的外观使用更高的当前位置(位于像素内的样本)的权重。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接