运动模糊与SCNTechnique

时间:2016-06-15 22:12:27

标签: shader scenekit metal scntechnique

我一直试图让运动模糊与SCNTechnique一起工作几天,我就是 无处接近我想要的。我在Apple论坛上问了一个类似的问题,但他们已经死了。 所以我想我会用代码写一个更详尽的描述。

设置

我屏幕上有一些角色有一些敌人。角色是一个正方形,敌人 是圆圈 - 本例简化。

我正在使用SceneKit和Metal。相机是固定的。

目标

当圈子/敌人移动时,我希望它们具有运动模糊效果。当角色移动时 他不应该。

提出的想法

写一个可以处理这个的多通道SCN技术,我假设这是一种做我想做的事情。

传递一个:将敌人/圈子渲染到另一个缓冲区

传递两个:将动态模糊应用于此缓冲区

传递三次:使用原始场景渲染运动模糊缓冲区。

旁注:传递两个将是棘手的,因为我想每个对象都有它自己的方向和 模糊需要效仿。

所以,这是我在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”和新的“颜色”缓冲区加在一起(如果这甚至远程工作,可能会在某处进行优化)

注意: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>

和着色器:

    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>

2 个答案:

答案 0 :(得分:3)

此答案假设您的对象是嵌入SceneKit 3D世界中的2D对象(如说明中的'圆圈'和'正方形'所暗示的那样)。

我认为使用SCNTechnique的多次传递渲染可能不是您想要实现的目标的正确解决方案。

这就是我接近它的方式:

为圆形对象的纹理添加(相当宽的)透明边距,并使SceneKit圆形对象更大,以使不透明部分具有正确的大小。透明边距的宽度是对象可以具有的运动模糊轨迹的最大长度。因此,如果圆形敌人可以在一帧中移动很远,那么你将需要很大的余量。

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

在片段着色器中,然后沿着速度矢量对纹理进行采样并对采样的颜色求平均值。您可能希望根据速度的大小选择样本数:对象移动得越快,您想要使用的样本越多。固定数量的样本也可能没问题,只要它足够高。

SCNProgram documentation

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

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

答案 1 :(得分:2)

如果您可以等到macOS Sierra / iOS 10,新的SceneKit HDI相机支持运动模糊。

视频:https://developer.apple.com/videos/play/wwdc2016/609/

源; https://developer.apple.com/library/prerelease/content/samplecode/Badger/Introduction/Intro.html