【Unity】ARで表示するCGを現実空間になるべく馴染ませる

#欲望
ARで表示するCGを、現実の空間に溶け込ませたい！

#手段
1. 影だけシェーダを作って検出した平面に割り当てる
2. 平面のオクルージョンを有効にする
3. カメラの映像から動的に環境マップを作る

##自分の環境
Unity 2019.4.1f1
Universal RP 7.3.1
ARFoundation 3.0.1
ARKit XR Plugin 3.0.1

Macbook Pro 13 2017 Mid メモリ8GB
MacOS 10.14.6

iPhone XS
iOS 13.6

この記事では通常のパイプラインではなく、URPを使います。

ARFoundationとURPについての記事
[【Unity】Universal RPでARアプリ開発](https://qiita.com/komakinex/items/a3011ee5828b16923405)

作ったアプリについての記事
[【Unity】Shader GraphでVertex Animation Textureを使う](https://qiita.com/komakinex/items/07c0a75271ca886f0de5)


#1.影だけシェーダ

Shader Graphで影だけを受けるシェーダ
この記事をベースに
[シェーダーグラフのカスタムライティング：Unity 2019](https://blogs.unity3d.com/jp/2019/07/31/custom-lighting-in-shader-graph-expanding-your-graphs-in-2019/)
こちらの回答を参考にしました。
[C# - Unity Universal RP における影のみの描画とレイヤーを用いた透過｜teratail](https://teratail.com/questions/238746)

![スクリーンショット 2020-09-01 13.13.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/9e2c4592-6116-feab-b348-e44ba15250eb.png)

![スクリーンショット 2020-09-01 13.15.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/7bb2617f-2e9a-49c2-20b0-8590823e360b.png)


![スクリーンショット 2020-09-01 13.14.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/25354fa8-84cc-428e-6b5d-66e558a790f7.png)

![スクリーンショット 2020-09-01 13.14.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/d81c3060-14df-8480-f589-20e454952432.png)

公式のファイルから、MainLight関数を変更しています。

```hlsl:CustomLighting.hlsl

#ifndef CUSTOM_LIGHTING_INCLUDED
#define CUSTOM_LIGHTING_INCLUDED

void MainLight_float(float3 WorldPos, out float3 Direction, out float3 Color, out float DistanceAtten, out float ShadowAtten)
{
#if SHADERGRAPH_PREVIEW
   Direction = float3(0.5, 0.5, 0);
   Color = 1;
   DistanceAtten = 1;
   ShadowAtten = 1;
#else
#if SHADOWS_SCREEN
   float4 clipPos = TransformWorldToHClip(WorldPos);
   float4 shadowCoord = ComputeScreenPos(clipPos);
#else
   float4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
#endif
   Light mainLight = GetMainLight();
   Direction = mainLight.direction;
   Color = mainLight.color;
   DistanceAtten = mainLight.distanceAttenuation;
   ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
   float4 shadowParams = GetMainLightShadowParams();
   ShadowAtten = SampleShadowmap(TEXTURE2D_ARGS(_MainLightShadowmapTexture, sampler_MainLightShadowmapTexture), TransformWorldToShadowCoord(WorldPos), shadowSamplingData, shadowParams, false);
#endif
}

void MainLight_half(float3 WorldPos, out half3 Direction, out half3 Color, out half DistanceAtten, out half ShadowAtten)
{
#if SHADERGRAPH_PREVIEW
   Direction = half3(0.5, 0.5, 0);
   Color = 1;
   DistanceAtten = 1;
   ShadowAtten = 1;
#else
#if SHADOWS_SCREEN
   half4 clipPos = TransformWorldToHClip(WorldPos);
   half4 shadowCoord = ComputeScreenPos(clipPos);
#else
   half4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
#endif
   Light mainLight = GetMainLight();
   Direction = mainLight.direction;
   Color = mainLight.color;
   DistanceAtten = mainLight.distanceAttenuation;
   ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
   half4 shadowParams = GetMainLightShadowParams();
   ShadowAtten = SampleShadowmap(TEXTURE2D_ARGS(_MainLightShadowmapTexture, sampler_MainLightShadowmapTexture), TransformWorldToShadowCoord(WorldPos), shadowSamplingData, shadowParams, false);
#endif
}

void DirectSpecular_float(float3 Specular, float Smoothness, float3 Direction, float3 Color, float3 WorldNormal, float3 WorldView, out float3 Out)
{
#if SHADERGRAPH_PREVIEW
    Out = 0;
#else
    Smoothness = exp2(10 * Smoothness + 1);
    WorldNormal = normalize(WorldNormal);
    WorldView = SafeNormalize(WorldView);
    Out = LightingSpecular(Color, Direction, WorldNormal, WorldView, float4(Specular, 0), Smoothness);
#endif
}

void DirectSpecular_half(half3 Specular, half Smoothness, half3 Direction, half3 Color, half3 WorldNormal, half3 WorldView, out half3 Out)
{
#if SHADERGRAPH_PREVIEW
    Out = 0;
#else
    Smoothness = exp2(10 * Smoothness + 1);
    WorldNormal = normalize(WorldNormal);
    WorldView = SafeNormalize(WorldView);
    Out = LightingSpecular(Color, Direction, WorldNormal, WorldView,half4(Specular, 0), Smoothness);
#endif
}

void AdditionalLights_float(float3 SpecColor, float Smoothness, float3 WorldPosition, float3 WorldNormal, float3 WorldView, out float3 Diffuse, out float3 Specular)
{
    float3 diffuseColor = 0;
    float3 specularColor = 0;

#ifndef SHADERGRAPH_PREVIEW
    Smoothness = exp2(10 * Smoothness + 1);
    WorldNormal = normalize(WorldNormal);
    WorldView = SafeNormalize(WorldView);
    int pixelLightCount = GetAdditionalLightsCount();
    for (int i = 0; i < pixelLightCount; ++i)
    {
        Light light = GetAdditionalLight(i, WorldPosition);
        half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
        diffuseColor += LightingLambert(attenuatedLightColor, light.direction, WorldNormal);
        specularColor += LightingSpecular(attenuatedLightColor, light.direction, WorldNormal, WorldView, float4(SpecColor, 0), Smoothness);
    }
#endif

    Diffuse = diffuseColor;
    Specular = specularColor;
}

void AdditionalLights_half(half3 SpecColor, half Smoothness, half3 WorldPosition, half3 WorldNormal, half3 WorldView, out half3 Diffuse, out half3 Specular)
{
    half3 diffuseColor = 0;
    half3 specularColor = 0;

#ifndef SHADERGRAPH_PREVIEW
    Smoothness = exp2(10 * Smoothness + 1);
    WorldNormal = normalize(WorldNormal);
    WorldView = SafeNormalize(WorldView);
    int pixelLightCount = GetAdditionalLightsCount();
    for (int i = 0; i < pixelLightCount; ++i)
    {
        Light light = GetAdditionalLight(i, WorldPosition);
        half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
        diffuseColor += LightingLambert(attenuatedLightColor, light.direction, WorldNormal);
        specularColor += LightingSpecular(attenuatedLightColor, light.direction, WorldNormal, WorldView, half4(SpecColor, 0), Smoothness);
    }
#endif

    Diffuse = diffuseColor;
    Specular = specularColor;
}

#endif
```



このシェーダからマテリアルを作成し、Hierarchy/XR/AR Default Planeにアタッチします。
AR Default PlaneのLine RendererコンポーネントをRemoveし、Prefab化します。
AR Session OriginにAR Plane Managerコンポーネントを追加し、PlanePrefabに先ほど作ったプレハブをあてると、影が表示されます。


影の解像度はProject/SettingフォルダにあるUniversalRP 〇〇Quality（今使ってるやつ）のShadowのDistanceで調整します。
どのRender Piplineを使っているかはProject Setting/GraphicsのScriptable Render Pipline Settingsで確認できます。



#2. 平面のオクルージョン
検出された平面より下にあるオブジェクトが隠れるようにします。

このオクルージョンと影を共存するためにコードで書くことに変更しました。
影
https://github.com/dilmerv/ARFoundationOcclusion/tree/feature/URPOcclusion
オクルージョン
https://github.com/Unity-Technologies/arfoundation-samples/issues/47

影と合わせるために元のコードから`Queue`、`ZTest`、`ZWrite`あたりを変更しました。

```shader:ShadowOcclusion.shader

Shader "ShadowOcclusion"
{
    Properties
    {
        _ShadowColor ("Shadow Color", Color) = (0.35,0.4,0.45,1.0)
    }
 
    SubShader
    {
        Tags
        {
            "RenderPipeline"="UniversalPipeline"
            "RenderType"="Transparent"
            "Queue"="Geometry-1"
        }
 
        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode" = "UniversalForward" }
 
            Blend DstColor Zero, Zero One
            Cull Back
            ZTest LEqual
            ZWrite On

   
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
 
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
            #pragma target 2.0
 
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
            #pragma multi_compile _ _SHADOWS_SOFT
            #pragma multi_compile_fog
 
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
 
            CBUFFER_START(UnityPerMaterial)
            float4 _ShadowColor;
            CBUFFER_END
 
            struct Attributes
            {
                float4 positionOS : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
 
            struct Varyings
            {
                float4 positionCS               : SV_POSITION;
                float3 positionWS               : TEXCOORD0;
                float fogCoord                  : TEXCOORD1;
                UNITY_VERTEX_INPUT_INSTANCE_ID
                UNITY_VERTEX_OUTPUT_STEREO
            };
 
            Varyings vert (Attributes input)
            {
                Varyings output = (Varyings)0;
 
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_TRANSFER_INSTANCE_ID(input, output);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
 
                VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
                output.positionCS = vertexInput.positionCS;
                output.positionWS = vertexInput.positionWS;
                output.fogCoord = ComputeFogFactor(vertexInput.positionCS.z);
 
                return output;
            }
 
            half4 frag (Varyings input) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
 
                half4 color = half4(1,1,1,1);
 
            #ifdef _MAIN_LIGHT_SHADOWS
                VertexPositionInputs vertexInput = (VertexPositionInputs)0;
                vertexInput.positionWS = input.positionWS;
 
                float4 shadowCoord = GetShadowCoord(vertexInput);
                half shadowAttenutation = MainLightRealtimeShadow(shadowCoord);
                color = lerp(half4(1,1,1,1), _ShadowColor, (1.0 - shadowAttenutation) * _ShadowColor.a);
                color.rgb = MixFogColor(color.rgb, half3(1,1,1), input.fogCoord);
            #endif
                return color;
            }
 
            ENDHLSL
        }
    }
}
```

#3. カメラの映像から動的に環境マップを作る

この動画の2分12秒からを参考にしました。
[Creating Immersive AR with Unity 2019!](https://youtu.be/FuKzngwzDYI?t=132)

AR Session OriginにAR Plane Managerコンポーネントを追加します。

次に空のGameObjectを作成し、AR Environment Probeコンポーネントを追加すると、Reflection Probeも同時に追加されます。これをプレハブ化し、AR Plane ManagerのPrefabにあてると動的に環境マップが作られるようになりました。

Metallicを1にしたマテリアルと組み合わせると、カメラに写っている空間を反射するようになります。

以上を設定したAR Session Originです。
![スクリーンショット 2020-09-01 13.40.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144702/c571e44c-450b-b264-fe8d-a017bfd1acad.png)