search
LoginSignup
3

More than 3 years have passed since last update.

posted at

updated at

Organization

ARKit3とMetalで人体のみにエフェクトをかける

AR Advent Calendar 2019 17日目の記事です。

はじめに

ARKit3からPeople Occlusionが使用可能になりました。
それを使用して人体だけにエフェクトを掛けようとして躓いたので対応方法を残しておきます。

12chip以降搭載のiPhoneでのみ動作します。

内容としてはほぼこちらのものです。
How to add a CIFilter to MTLTexture Using ARMatteGenerator?

まずはサンプルを動かす

Apple提供サンプルコード Effecting People Occlusion in Custom Renderers をダウンロードし実行。

起動後、画面をタップするとPlaneが表示されます。
手を動かすことで奥行きと人体部分のマスク効果がわかります。

picture001.png

人体のみ赤くする

さて人体のみに効果をかけていきます。

Shader.metalcompositeImageFragmentShaderを変更します。

// Composite the image fragment function.
fragment half4 compositeImageFragmentShader(CompositeColorInOut in [[ stage_in ]],
                                    texture2d<float, access::sample> capturedImageTextureY [[ texture(0) ]],
                                    texture2d<float, access::sample> capturedImageTextureCbCr [[ texture(1) ]],
                                    texture2d<float, access::sample> sceneColorTexture [[ texture(2) ]],
                                    depth2d<float, access::sample> sceneDepthTexture [[ texture(3) ]],
                                    texture2d<float, access::sample> alphaTexture [[ texture(4) ]],
                                    texture2d<float, access::sample> dilatedDepthTexture [[ texture(5) ]],
                                    constant SharedUniforms &uniforms [[ buffer(kBufferIndexSharedUniforms) ]])
{
    constexpr sampler s(address::clamp_to_edge, filter::linear);

    float2 cameraTexCoord = in.texCoordCamera;
    float2 sceneTexCoord = in.texCoordScene;

    // Sample Y and CbCr textures to get the YCbCr color at the given texture coordinate.
    float4 rgb = ycbcrToRGBTransform(capturedImageTextureY.sample(s, cameraTexCoord), capturedImageTextureCbCr.sample(s, cameraTexCoord));

    // Perform composition with the matting.
    half4 sceneColor = half4(sceneColorTexture.sample(s, sceneTexCoord));
    float sceneDepth = sceneDepthTexture.sample(s, sceneTexCoord);

    half4 cameraColor = half4(rgb);
    half alpha = half(alphaTexture.sample(s, cameraTexCoord).r);

    half showOccluder = 1.0;

    if (uniforms.useDepth) {
        float dilatedLinearDepth = half(dilatedDepthTexture.sample(s, cameraTexCoord).r);

        // Project linear depth with the projection matrix.
        float dilatedDepth = clamp((uniforms.projectionMatrix[2][2] * -dilatedLinearDepth + uniforms.projectionMatrix[3][2]) / (uniforms.projectionMatrix[2][3] * -dilatedLinearDepth + uniforms.projectionMatrix[3][3]), 0.0, 1.0);

        showOccluder = (half)step(dilatedDepth, sceneDepth); // forwardZ case
    }

    // 赤で置き換え
    // half4 occluderResult = mix(sceneColor, cameraColor, alpha); // 元の記述
    half4 occluderResult = mix(sceneColor, half4(float4(1.0, 0.0, 0.0, 1.0)), alpha);

    half4 mattingResult = mix(sceneColor, occluderResult, showOccluder);
    return mattingResult;
}

変更箇所は以下のみです。

    // 赤で置き換え
    half4 occluderResult = mix(sceneColor, half4(float4(1.0, 0.0, 0.0, 1.0)), alpha);

Picture002.png

別のエフェクトをかける

float random(float offset, float2 tex_coord, float time) {
    float2 non_repeating = float2(12.9898 * time, 78.233 * time);
    float sum = dot(tex_coord, non_repeating);
    float sine = sin(sum);
    float huge_number = sine * 43758.5453 * offset;
    float fraction = fract(huge_number);
    return fraction;
}

taken from https://github.com/twostraws/ShaderKit

    float randFloat = random(1.0, cameraTexCoord, rgb[0]);
    half4 occluderResult = mix(sceneColor, half4(float4(randFloat, randFloat, randFloat, 1.0)), alpha);

Picture003.png

さいごに

この方法の利点はMetalで高速に動かしていることと、Shaderでの表現力ですね。
他のShaderも試していきたいと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3