Help us understand the problem. What is going on with this article?

ARFoundationとARKit3で光学迷彩的エフェクト

はじめに

こんにちわ、北千住デザインと申します。フリーランスとしてUnityでARアプリを作ってます。

先日、iOS13と共にARKit3が公開されました。そのひとつにピープルオクルージョン(他ではセグメンテーションと呼ばれることも多いです)という機能があります。ARKitではオクルージョンのために使うことを想定しているようですが、画像エフェクトにも使えます。私はこの機能を使って、フィルターアプリMEISAIを制作しています。

そのアプリ内のエフェクトの一つ(仮に光学迷彩エフェクトと呼んでます)の作り方を解説します。やり方は比較的簡単ですが、見たことのないエフェクトになってると思うので是非ごらんください:bow_tone1:

UnityでARKit3を使う

Unityで手っ取り早くARKitを利用するには、ARFoundationというARKitやARCoreのラッパーみたいなやつを使う必要があります。こちらにサンプルが公開 されてるので、これのHumanSegmentationImagesというシーンにコードを追加していきます。

今回のサンプルのリポジトリはこちらです。
https://github.com/kitasenjudesign/ARFoundationMeisaiDemo
作ったシーンやコードは主にAssets/MEISAI/にアップしてます。MITライセンスです。

人物のシルエットを赤くする

ポストエフェクトによって人物を赤くしてみます。

IMG_0886b.png

まずポストエフェクト用に、人物のシルエット(humanStencilTexture)をシェーダーに渡します。

PostEffect.cs
using UnityEngine;
using UnityEngine.XR.ARFoundation;

public class PostEffect : MonoBehaviour
{

    [SerializeField] private Shader _shader;
    [SerializeField] private AROcclusionManager occlusionManager;
    private Material _material;

    void Awake()
    {
        _material = new Material(_shader);
    }

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if(occlusionManager!=null){
            _material.SetTexture("_StencilTex",occlusionManager.humanStencilTexture);     
        }
        Graphics.Blit(source, destination, _material);
    }

}

次にhumanStencilTextureを使って、シルエットを塗り潰します。しかしhumanStencilTextureを利用する際、一つ問題があります。humanStencilTextureとカメラから得られるテクスチャのサイズや角度が違うため、UVを補正する必要があるんです。

以下の例は、スマホを縦持ちし(portrait)、カメラからの映像のテクスチャが1920x1440のとき、正しくなるように補正しています。

StencilSample.shader
Shader "StencilSample"
{
    Properties
    {
        _MainTex ("_MainTex", 2D) = "white" {}
        _StencilTex ("_StencilTex", 2D) = "white" {}

    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            sampler2D _StencilTex;

            float2 GetStencilUV( float2 uv ){

                float2 stencilUV = float2(
                    1-uv.y,
                    1-uv.x
                );

                float camTexWidth = 1920;
                float camTexHeight = 1440;
                float aspect = (camTexWidth/camTexHeight) / (_ScreenParams.y/_ScreenParams.x);

                stencilUV.y = stencilUV.y * aspect + (1-aspect)/2;

                return stencilUV;

            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 stencil = tex2D(_StencilTex, GetStencilUV(i.uv));//UVを補正

                return lerp( col, fixed4(1,0,0,1), stencil.r);

            }
            ENDCG
        }
    }
}

ノイズ関数とカメラの色を利用し光学迷彩を作る

前述の例をもとに、シェーダーでエフェクトとアニメーションを加えます。

RPReplay_Final1575685579.gif

金属や水なんかは鏡面反射や屈折なんかによってその材質感が現れます。ここでは擬似的に鏡面反射や屈折のようなエフェクトを作ることで、光学迷彩っぽいエフェクトを作っています。

これはAfterEffectでいうディスプレイスメントマップフィルタというテクニックで、テクスチャの色に従ってUVを変位させています。ここでは、カメラからのテクスチャの色とノイズ関数を使うことによって、人物の特徴をなんとなく保持しつつ、金属のような水のような質感を作り出します。ノイズ関数はこちらを使わせていただきました。

MEISAI.shader
Shader "MEISAI"
{
    Properties
    {
        _MainTex ("_MainTex", 2D) = "white" {}
        _StencilTex ("_StencilTex", 2D) = "white" {}
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "./noise/SimplexNoise3D.hlsl"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            sampler2D _StencilTex;

            float2 GetStencilUV( float2 uv ){

                float2 stencilUV = float2(
                    1-uv.y,
                    1-uv.x
                );

                float camTexWidth = 1920;
                float camTexHeight = 1440;
                float aspect = (camTexWidth/camTexHeight) / (_ScreenParams.y/_ScreenParams.x);

                stencilUV.y = stencilUV.y * aspect + (1-aspect)/2;

                return stencilUV;

            }

            fixed4 frag (v2f i) : SV_Target
            {

                fixed4 camCol = tex2D(_MainTex, i.uv);

                //ノイズ関数と色を基に変異させる。数値は適当
                float2 displacedUV = float2(
                    0.5 * snoise(float3(i.uv.x+camCol.r*2.0,i.uv.y+camCol.g*2.0, _Time.y*0.3)),
                    0.5 * snoise(float3(i.uv.x+camCol.g*2.0,i.uv.y+camCol.b*2.0, _Time.y*0.4))
                );
                displacedUV = frac( i.uv + displacedUV );

                fixed4 displacedCol = tex2D(_MainTex, displacedUV);

                fixed4 stencil = tex2D(_StencilTex, GetStencilUV(i.uv));

                return lerp( camCol, displacedCol, stencil.r);

            }
            ENDCG
        }
    }
}

おわりに

セグメンテーションとシェーダーを組み合わせることで、様々なエフェクトを作ることができます。最初、試しにシェーダーでアニメーションさせてみたとき、思った以上に面白くてびっくりしたので、ぜひやってみてください。

最後にセグメンテーションを用いた応用例をもう一つ紹介しておくと、人物からパーティクルが出るようなエフェクトも作ることができます。このやり方はまた機会があれば書きたいと思います。このエフェクトはMEISAIでも試せますので使ってみてください:bow_tone1:

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away