🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

3485閲覧

unity 画面上での重なり判定がしたい

Shi-553

総合スコア1

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2021/02/17 09:58

前提・実現したいこと

平行投影のカメラで3Dオブジェクトどうし(A、B)が画面上で重なっているかどうかを判定したいです。
カメラ、オブジェクトは回転させません。

考えたこと

Aを埋めるようにカメラからAに向けてBoxCast、SphereCastを沢山やって判定する。
→丸と四角でどう形を作るかがとても面倒です。

補足情報(FW/ツールのバージョンなど)

Unity 2019.4.19を使っています。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

一案としてはMeshからPolygonCollider2Dを作り(参考:Generate 2D polygon collider from 3D mesh - Unity Answers)、それを利用して重なり判定を行う手がありそうです。
あるいは私からも別案として、カメラに下記のようなスクリプトをアタッチし...

C#

1using System.Collections.Generic; 2using UnityEngine; 3using UnityEngine.Rendering; 4 5// カメラにアタッチして使うことを想定しました 6[RequireComponent(typeof(Camera))] 7public class OverlapDetector : MonoBehaviour 8{ 9 private static readonly int IntersectionTexture = Shader.PropertyToID("_IntersectionTexture"); 10 private static readonly int ObjectTexture = Shader.PropertyToID("_ObjectTexture"); 11 private static readonly int ReductionTexture0 = Shader.PropertyToID("_ReductionTexture0"); 12 private static readonly int ReductionTexture1 = Shader.PropertyToID("_ReductionTexture1"); 13 14 // ここに後述のHidden/OverlapDetectorをセットしておきます 15 [SerializeField] private Shader detectorShader; 16 17 private readonly List<Material> materials = new List<Material>(); 18 private readonly List<Renderer> renderers = new List<Renderer>(); 19 private new Camera camera; 20 private CommandBuffer detectionCommands; 21 private Material detectorMaterial; 22 private RenderTexture resultRenderTexture; 23 private Texture2D resultTexture; 24 25 private void Awake() 26 { 27 if (this.detectorShader == null) 28 { 29 Debug.LogError($"{nameof(this.detectorShader)} not set."); 30 Destroy(this); 31 return; 32 } 33 34 this.detectorMaterial = new Material(this.detectorShader); 35 this.resultRenderTexture = new RenderTexture(1, 1, 0, RenderTextureFormat.ARGB32); 36 this.resultTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false); 37 this.camera = this.GetComponent<Camera>(); 38 this.detectionCommands = new CommandBuffer 39 { 40 name = "Overlap Detection" 41 }; 42 } 43 44 private void OnDestroy() 45 { 46 Destroy(this.detectorMaterial); 47 Destroy(this.resultRenderTexture); 48 Destroy(this.resultTexture); 49 } 50 51 // オブジェクト同士の映像上の重なりを判定したいときにこのメソッドを実行することにしました 52 // 引数として与えたオブジェクトがすべて重なっている領域があればtrue、さもなくばfalseを返します 53 // レンダーテクスチャ上に別途オブジェクトをレンダリングしてみて判定する作りのため、 54 // 多数のオブジェクトを判定、あるいは高頻度で判定しようとするとそれなりに描画負荷を要します 55 public bool DetectOverlap(params GameObject[] gameObjects) 56 { 57 if (gameObjects == null) 58 { 59 return false; 60 } 61 62 // 判定用のテクスチャサイズは、さしあたり画面サイズよりも大きな2の冪乗の正方形としました 63 // 高解像度だとこれも負荷増大の原因となるので、もっと粗い判定で十分ならば 64 // このサイズをもっと小さくするのも一案かと思います 65 var size = Mathf.NextPowerOfTwo(Mathf.Max(Screen.width, Screen.height)); 66 67 // CommandBufferを構築 68 var c = this.detectionCommands; 69 c.Clear(); 70 71 // このカメラのビュー・プロジェクション行列を用い... 72 c.SetViewProjectionMatrices( 73 this.camera.worldToCameraMatrix, 74 GL.GetGPUProjectionMatrix(this.camera.projectionMatrix, true)); 75 76 // 重なり部分を表すテクスチャ、および個々のオブジェクトの描画に使うテクスチャを用意し... 77 c.GetTemporaryRT(IntersectionTexture, size, size, 0, FilterMode.Bilinear, RenderTextureFormat.R8); 78 c.GetTemporaryRT(ObjectTexture, size, size, 0, FilterMode.Point, RenderTextureFormat.R8); 79 80 // まず重なりテクスチャを白で塗りつぶし... 81 c.SetRenderTarget(IntersectionTexture); 82 c.ClearRenderTarget(true, true, Color.white); 83 84 // 処理対象のオブジェクトそれぞれについて... 85 foreach (var o in gameObjects) 86 { 87 if (o == null) 88 { 89 continue; 90 } 91 92 // 描画用テクスチャを透明で塗りつぶし... 93 c.SetRenderTarget(ObjectTexture); 94 c.ClearRenderTarget(true, true, Color.clear); 95 96 // オブジェクトを構成するすべてのレンダラーを集めて... 97 o.GetComponentsInChildren(this.renderers); 98 foreach (var r in this.renderers) 99 { 100 if (r == null) 101 { 102 continue; 103 } 104 105 // レンダラーを白色で描画し... 106 r.GetSharedMaterials(this.materials); 107 var materialCount = this.materials.Count; 108 for (var i = 0; i < materialCount; i++) 109 { 110 c.DrawRenderer(r, this.detectorMaterial, i, 0); 111 } 112 } 113 114 // オブジェクトの描画結果を重なりテクスチャへ乗算合成することで、 115 // オブジェクト同士が重なっている部分だけが白色として残るようにして... 116 c.Blit(ObjectTexture, IntersectionTexture, this.detectorMaterial, 1); 117 } 118 119 // それを半々に縮小していき(このとき、近傍4ピクセルのうち1つ以上白色の 120 // ピクセルがあれば、縮小結果も白色になるようにしています)... 121 var fromTexture = ReductionTexture0; 122 var toTexture = ReductionTexture1; 123 c.GetTemporaryRT(fromTexture, size, size, 0, FilterMode.Bilinear, RenderTextureFormat.R8); 124 c.Blit(IntersectionTexture, fromTexture); 125 while (size > 1) 126 { 127 size >>= 1; 128 c.GetTemporaryRT(toTexture, size, size, 0, FilterMode.Bilinear, RenderTextureFormat.R8); 129 c.Blit(fromTexture, toTexture, this.detectorMaterial, 2); 130 c.ReleaseTemporaryRT(fromTexture); 131 var previousFromTexture = fromTexture; 132 fromTexture = toTexture; 133 toTexture = previousFromTexture; 134 } 135 136 // 最終的に1ピクセルまで縮め、その色を取得して 137 // 白色かどうかで重なりがあるかを判定しました 138 // わざわざこのように縮小する処理を加えた理由としては、広い面積の色を 139 // ReadPixelsで取得するのは高コストであるためで、そうするよりはGPU上で 140 // 縮小してから1ピクセル取得する方がマシだろうと思ってのことです 141 c.Blit(fromTexture, this.resultRenderTexture); 142 c.ReleaseTemporaryRT(fromTexture); 143 c.ReleaseTemporaryRT(IntersectionTexture); 144 c.ReleaseTemporaryRT(ObjectTexture); 145 var activeTexture = RenderTexture.active; 146 Graphics.ExecuteCommandBuffer(c); 147 RenderTexture.active = this.resultRenderTexture; 148 this.resultTexture.ReadPixels(new Rect(0, 0, 1, 1), 0, 0, false); 149 RenderTexture.active = activeTexture; 150 var result = this.resultTexture.GetPixel(0, 0); 151 return result.r > 0.0f; 152 } 153}

detectorShaderとして下記シェーダーを用い...

ShaderLab

1Shader "Hidden/OverlapDetector" 2{ 3 Properties 4 { 5 _MainTex ("Texture", 2D) = "white" {} 6 } 7 SubShader 8 { 9 Cull Off 10 ZWrite Off 11 ZTest Always 12 13 CGINCLUDE 14 #include "UnityCG.cginc" 15 16 struct appdata 17 { 18 float4 vertex : POSITION; 19 float2 uv : TEXCOORD0; 20 }; 21 22 struct v2f 23 { 24 float2 uv : TEXCOORD0; 25 float4 vertex : SV_POSITION; 26 }; 27 28 v2f vert(appdata v) 29 { 30 v2f o; 31 o.vertex = UnityObjectToClipPos(v.vertex); 32 o.uv = v.uv; 33 return o; 34 } 35 36 sampler2D _MainTex; 37 ENDCG 38 39 // 単純な白色塗り...オブジェクト描画用 40 Pass 41 { 42 CGPROGRAM 43 #pragma vertex vert 44 #pragma fragment frag 45 46 fixed4 frag(v2f i) : SV_Target 47 { 48 return 1.0; 49 } 50 ENDCG 51 } 52 53 // 乗算合成...重なり領域抽出用 54 Pass 55 { 56 Blend DstColor Zero 57 58 CGPROGRAM 59 #pragma vertex vert 60 #pragma fragment frag 61 62 fixed4 frag(v2f i) : SV_Target 63 { 64 return tex2D(_MainTex, i.uv); 65 } 66 ENDCG 67 } 68 69 // 取得した色を切り上げて出力...テクスチャサイズ縮小用 70 Pass 71 { 72 CGPROGRAM 73 #pragma vertex vert 74 #pragma fragment frag 75 76 fixed4 frag(v2f i) : SV_Target 77 { 78 return ceil(tex2D(_MainTex, i.uv)); 79 } 80 ENDCG 81 } 82 } 83}

実験用に別途用意した下記スクリプトで判定を行ったところ...

C#

1using UnityEngine; 2 3public class OverlapDetectorTest : MonoBehaviour 4{ 5 [SerializeField] private OverlapDetector detector; 6 [SerializeField] private GameObject[] targets; 7 8 private bool previousResult; 9 10 private void Update() 11 { 12 var result = this.detector.DetectOverlap(this.targets); 13 if (result == this.previousResult) 14 { 15 return; 16 } 17 18 Debug.Log(result); 19 this.previousResult = result; 20 } 21}

下図のように重なりが判定されました。

図

前者のPolygonCollider2D案なら、一度外形を生成すればそれを使い回すことで動作時の負荷を抑えられそうに思います。また必要に応じて物理的応答をさせるのにも使えるでしょう。
後者の画像による判定方式は、描画負荷と引き換えにオブジェクトやカメラの回転、あるいはオブジェクトのアニメーションによってカメラから見たときの外形がころころ変化するケースでも対応しやすい利点があるかと思います。
ですが今回はオブジェクトもカメラも回転しないとのことですので、前者の案の方が向いているかもしれませんね。回転する場合であっても、カメラのZ軸に沿った回転ならば外形が変化しないはずですので、前者の案で対応可能かと思います。

投稿2021/02/18 22:17

Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Shi-553

2021/02/19 12:20

2案もありがとうございます! DirectXのステンシルバッファをみて描画してみれば判定出来そうとは思ってたんですが、unityでの座標変換からシェーダーまで未知のエリアだったので勉強になります。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問