66
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【unityプロ技】Advent Calendar 2019

Day 5

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

Last updated at Posted at 2019-12-08

はじめに

こんにちわ、北千住デザインと申します。フリーランスとして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:

66
36
1

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
  3. You can use dark theme
What you can do with signing up
66
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?