#欲望
ARで表示するCGを、現実の空間に溶け込ませたい!
#手段
- 影だけシェーダを作って検出した平面に割り当てる
- 平面のオクルージョンを有効にする
- カメラの映像から動的に環境マップを作る
##自分の環境
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アプリ開発
作ったアプリについての記事
【Unity】Shader GraphでVertex Animation Textureを使う
#1.影だけシェーダ
Shader Graphで影だけを受けるシェーダ
この記事をベースに
シェーダーグラフのカスタムライティング:Unity 2019
こちらの回答を参考にしました。
C# - Unity Universal RP における影のみの描画とレイヤーを用いた透過|teratail
公式のファイルから、MainLight関数を変更しています。
#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"
{
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!
AR Session OriginにAR Plane Managerコンポーネントを追加します。
次に空のGameObjectを作成し、AR Environment Probeコンポーネントを追加すると、Reflection Probeも同時に追加されます。これをプレハブ化し、AR Plane ManagerのPrefabにあてると動的に環境マップが作られるようになりました。
Metallicを1にしたマテリアルと組み合わせると、カメラに写っている空間を反射するようになります。