[UE4][UE5] 画面上の座標（スクリーン座標）から三次元座標（ワールド座標）を取得する方法について（標準のDeproject Screen to World と 自作版 ）

# はじめに
「**カメラに映っている範囲でActorを生成したいので、画面上の座標（スクリーン座標）から三次元座標（ワールド座標）を取得したい！**」というお問い合わせを頂いた際に調べたことのメモです。

ちなみにこの処理ができるようになると、**画面端にBlokingVolumeを配置してキャラを画面外に出さないようにする**といったことができます。便利

スクリーン座標…？ワールド座標…？という方はこちらとか参考になると思います。座標系たのちい
**座標系の考え方と生成・配置** : https://www.vcssl.org/ja-jp/doc/3d/coordinate

# Deproject Screen to World ノードを使う方法
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/eb45fb3a-88c8-2c84-e996-048b84237ab6.png)
エンジン標準で用意されているのが、**Deproject Screen to Worldノード**（`UGameplayStatics::DeprojectScreenToWorld`）です。これを使うことで、**現在表示されている画面における**スクリーン座標からワールド座標を取得することができます。

ただし壁や地面などの遮蔽物は考慮していないため、本当に画面に映っているのかは判定できません。なので、遮蔽物を考慮する際はLineTraceなどを使って判定をかけることになります(ちなみに、指定のActorが画面に映っているか否かは `Was Recently Rendered` ノードが有用です)。

Deproject Screen to World で簡単に実装できるのですが、`Get Player Controller` ノードの結果を渡す関係で現在表示されている画面での結果しか取れません。そのため、もし画面表示に使っていないCameraに対して同じ処理を行いたい場合は C++ で少しゴリゴリ書く必要があります。

# Deproject Screen to World ノードを自作する方法
早速ですが、C++コードの紹介です。`UGameplayStatics::DeprojectScreenToWorld` 内で走っている処理を再現した感じになります。

```cpp:MyBlueprintFunctionLibrary.h
#include "Camera/CameraComponent.h"

UFUNCTION(BlueprintCallable)
static bool DeprojectScreenToWorldWithCameraComponent(UCameraComponent* CameraComponent, float ViewRectX, float ViewRectY, const FVector2D& ScreenPosition, FVector& WorldPosition, FVector& WorldDirection);

```

```cpp:MyBlueprintFunctionLibrary.cpp
bool UMyBlueprintFunctionLibrary::DeprojectScreenToWorldWithCameraComponent(UCameraComponent* CameraComponent, float ViewRectX, float ViewRectY, const FVector2D& ScreenPosition, FVector& WorldPosition, FVector& WorldDirection)
{
	if (!CameraComponent)
	{
		return false;
	}

	FMinimalViewInfo MinimalViewInfo;
	CameraComponent->GetCameraView(0.0f, MinimalViewInfo);

	FSceneViewProjectionData ProjectionData;
	ProjectionData.ViewOrigin = MinimalViewInfo.Location;
	ProjectionData.ProjectionMatrix = MinimalViewInfo.CalculateProjectionMatrix();

	// from UGameplayStatics::CalculateViewProjectionMatricesFromMinimalView
	FMatrix ViewRotationMatrix = FInverseRotationMatrix(MinimalViewInfo.Rotation) * FMatrix(
		FPlane(0, 0, 1, 0),
		FPlane(1, 0, 0, 0),
		FPlane(0, 1, 0, 0),
		FPlane(0, 0, 0, 1));
	ProjectionData.ViewRotationMatrix = ViewRotationMatrix;

	// スクリーン座標で指定する関係で、表示してないけど画面解像度を設定する必要がある
	// 手っ取り早く手動で設定するように（手抜き
	ProjectionData.SetConstrainedViewRectangle(FIntRect(0, 0, ViewRectX, ViewRectY));

	// from UGameplayStatics::DeprojectScreenToWorld
	FMatrix const InvViewProjMatrix = ProjectionData.ComputeViewProjectionMatrix().InverseFast();
	FSceneView::DeprojectScreenToWorld(ScreenPosition, ProjectionData.GetConstrainedViewRect(), InvViewProjMatrix, /*out*/ WorldPosition, /*out*/ WorldDirection);
	return true;
}
```

これでこうして
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/eccf0717-24ec-8915-b493-eac2985a53c7.png)
こうすると…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/eb2d8a57-fcf7-b984-3836-a8720fb9697b.png)
表示していないカメラでも、スクリーン座標からワールド座標の情報を取得できるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/726d1f7c-ed4c-ef5c-8d01-c56a10c97a0e.png)
なお、Constrain Aspect Ratioを有効にして、Aspect Ratioを設定して、ノードに渡す解像度の比率を設定したAspect Ratioに合わせないと何らかの不具合が出るかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/363889/504a5d83-067f-0b83-77aa-e881422b4a14.png)

# さいごに
今回はスクリーン座標からワールド座標でしたが、ワールド座標からスクリーン座標に変換することもできます。こちらも`UGameplayStatics::ProjectWorldToScreen` ノードで簡単にできますが、表示していないカメラの場合だと今回のようにC++コードで少し書く必要があるかと思います。その際にも上記コードが参考になれば幸いです。

おわり