確かに、シーンビュー上だと正確にポイントクラウドの形に沿ったクリック判定ができていますね。Unityがどのように実現しているかは未調査ですが、コライダーとのレイキャスト的な方法ではなさそうに思います。
おそらく今回の場合にはレイキャストは効果的ではないでしょう。お試しのとおり、メッシュコライダーは普通のポリゴンメッシュのように表面を持つ物体を前提としているはずで、ポイントクラウドのように点だけで構成されたオブジェクトで使用するのは困難だろうと予想されます。
方法としては、まずはご質問者さんが試されたように素朴に頂点座標を一つひとつ調べていく手があるかと思いますが、やはり相当な高負荷になるでしょうね(しかも実際には100万点どころかもっと多数になる可能性があるのでしょうか?)。
処理コスト削減案としては、粗い判定と精密な判定の二段構えはどうでしょう。ご質問者さんの場合100万点を10万点ずつ10個のオブジェクトに分割していますが、これをもっと細かくしてそれぞれのオブジェクトにはBoxCollider
をつけ、まずRaycastAll
でヒットする可能性のあるオブジェクトだけを抜き出してから精密な頂点単位の判定を行うようにすれば、100万点すべてを処理しなくても済むようになるでしょうから効率的なんじゃないかと思います。
別の案として、以前別の方のご質問(unity マウスクリックした部分の色を抽出する )で検討した方法を利用できそうでしたので実験してみました。あちらではレンダーテクスチャ上にアルベド色を出力していますが、代わりにオブジェクト番号と頂点番号を出力すればいいんじゃないかと思います(あるいは、もし「何番目の頂点か」という情報が不要ならば、頂点座標そのものを出力してしまってもいいでしょう)。
インデックス出力シェーダーは下記のようにして...
ShaderLab
1 Shader "Unlit/VertexIndex"
2 {
3 Properties
4 {
5 _ObjectIndex ("Object Index", Int) = -1
6 }
7 SubShader
8 {
9 Tags { "RenderType"="Opaque" }
10
11 Pass
12 {
13 Cull Off
14 ZWrite On
15 ZTest LEqual
16
17 CGPROGRAM
18 #pragma vertex vert
19 #pragma fragment frag
20
21 #include "UnityCG.cginc"
22
23 struct appdata
24 {
25 float4 vertex : POSITION;
26 uint index : SV_VertexID;
27 };
28
29 struct v2f
30 {
31 float index : TEXCOORD0;
32 float4 vertex : SV_POSITION;
33 };
34
35 v2f vert (appdata v)
36 {
37 v2f o;
38 o.vertex = UnityObjectToClipPos(v.vertex);
39 o.index = v.index;
40 return o;
41 }
42
43 int _ObjectIndex;
44
45 float4 frag (v2f i) : SV_Target
46 {
47 float4 col = float4(_ObjectIndex, i.index, 0.0, 1.0);
48 return col;
49 }
50 ENDCG
51 }
52 }
53 }
ポイントクラウド生成スクリプトは下記のようにし、さしあたりクリック判定もこれに担当させることにしました。
C#
1 using System . Collections . Generic ;
2 using UnityEngine ;
3
4 public class PointCloudCreator : MonoBehaviour
5 {
6 static readonly int ObjectIndexProperty = Shader . PropertyToID ( "_ObjectIndex" ) ; // 効率化のためプロパティIDを求めておく
7
8 [ SerializeField ] private Shader vertexIndexShader ; // インスペクター上でUnlit/VertexIndexをセットしておく
9
10 int N = 10000000 ; // 頂点数
11 int N_sub = 1000000 ; // 1つのGameObject当たりの頂点数
12 List < GameObject > objs = new List < GameObject > ( ) ;
13 List < MeshRenderer > renderers = new List < MeshRenderer > ( ) ; // アクセス効率化のために、レンダラーをリスト化しておく
14 List < Mesh > meshes = new List < Mesh > ( ) ; // アクセス効率化のために、メッシュをリスト化しておく
15 List < Vector3 [ ] > vertices = new List < Vector3 [ ] > ( ) ; // アクセス効率化のために、頂点をリスト化しておく
16 Material vertexIndexMaterial ; // インデックス描画用マテリアル
17 Camera mainCamera ; // メインカメラ
18 Texture2D pixelTexture ; // インデックス描画結果を受け取るテクスチャ
19
20 void Start ( )
21 {
22 this . mainCamera = Camera . main ; // メインカメラの参照を得る
23 this . pixelTexture = new Texture2D ( 1 , 1 , TextureFormat . RGBAFloat , false ) ; // インデックス描画結果を受け取るテクスチャを作る
24 this . vertexIndexMaterial = new Material ( this . vertexIndexShader ) ; // インデックス描画用マテリアルを作る
25
26 Vector3 [ ] vertices = new Vector3 [ this . N_sub ] ;
27 int [ ] indecies = new int [ this . N_sub ] ;
28
29 for ( int i = 0 ; i < this . N ; i ++ ) {
30 if ( i % this . N_sub == 0 ) {
31 vertices = new Vector3 [ this . N_sub ] ;
32 indecies = new int [ this . N_sub ] ;
33 }
34 float offset = ( ( int ) ( i / this . N_sub ) ) * 100 ;
35 float x = Random . Range ( offset , 100 + offset ) ;
36 float y = Random . Range ( 0 , 100 ) ;
37 float z = Random . Range ( 0 , 100 ) ;
38 vertices [ i % this . N_sub ] . Set ( x , y , z ) ;
39 indecies [ i % this . N_sub ] = i % this . N_sub ;
40 if ( ( i + 1 ) % this . N_sub == 0 ) {
41 GameObject obj = new GameObject ( ) ;
42 // obj.AddComponent<PointCloudPartBehaviour>(); // PointCloudPartBehaviourは使用しない
43 MeshRenderer renderer = obj . AddComponent < MeshRenderer > ( ) ;
44 // MeshCollider collider = obj.AddComponent<MeshCollider>(); // コライダーは使用しない
45 Material material = renderer . material ;
46 Shader shader = Shader . Find ( "Mobile/Particles/VertexLit Blended" ) ;
47 material . shader = shader ;
48 Mesh mesh = obj . AddComponent < MeshFilter > ( ) . mesh ;
49 mesh . indexFormat = UnityEngine . Rendering . IndexFormat . UInt32 ;
50 mesh . vertices = vertices ;
51 mesh . SetIndices ( indecies , MeshTopology . Points , 0 ) ;
52 this . objs . Add ( obj ) ;
53 this . renderers . Add ( renderer ) ; // レンダラーをリストに追加
54 this . meshes . Add ( mesh ) ; // メッシュをリストに追加
55 this . vertices . Add ( vertices ) ; // 頂点をリストに追加
56 }
57 }
58 }
59
60 void Update ( )
61 {
62 if ( Input . GetMouseButtonDown ( 0 ) )
63 {
64 // 描画パラメーターをセットアップ、テクスチャを準備
65 Vector2 screenScale = new Vector2 ( Screen . width , Screen . height ) ;
66 Vector2 pixelPosition = ( ( Vector2 ) Input . mousePosition * 2.0f ) - screenScale ;
67 Matrix4x4 pixelMatrix = Matrix4x4 . TRS (
68 - pixelPosition ,
69 Quaternion . identity ,
70 new Vector3 ( screenScale . x , screenScale . y , 1.0f ) ) ;
71 Matrix4x4 viewMatrix = this . mainCamera . worldToCameraMatrix ;
72 RenderTexture renderTexture = RenderTexture . GetTemporary ( 1 , 1 , 24 , RenderTextureFormat . ARGBFloat ) ;
73 renderTexture . filterMode = FilterMode . Point ;
74 RenderTexture activeTexture = RenderTexture . active ;
75 RenderTexture . active = renderTexture ;
76 GL . PushMatrix ( ) ;
77
78 // プレイモードだとCamera.currentはnullだが、ビルド後の場合はnullではなくDrawMeshNowの時に
79 // 余計な影響を及ぼしてくるようだったため、Camera.currentのビュー行列を無効化して干渉を防ぐ
80 var currentCamera = Camera . current ;
81 if ( currentCamera != null )
82 {
83 currentCamera . worldToCameraMatrix = Matrix4x4 . identity ;
84 }
85
86 GL . LoadProjectionMatrix ( pixelMatrix * this . mainCamera . projectionMatrix ) ;
87
88 // まずインデックスの初期値はオブジェクト・頂点ともに-1とでもしておき...
89 GL . Clear ( true , true , new Color ( - 1.0f , - 1.0f , 0.0f , 1.0f ) ) ;
90
91 // 各メッシュを描画していく
92 // 最終的に最も手前にあるオブジェクトのインデックスが書き込まれるはず
93 for ( int i = 0 ; i < this . objs . Count ; i ++ )
94 {
95 this . vertexIndexMaterial . SetInt ( ObjectIndexProperty , i ) ;
96 this . vertexIndexMaterial . SetPass ( 0 ) ;
97 Graphics . DrawMeshNow ( this . meshes [ i ] , viewMatrix * this . renderers [ i ] . localToWorldMatrix ) ;
98 }
99
100 if ( currentCamera != null )
101 {
102 currentCamera . ResetWorldToCameraMatrix ( ) ;
103 }
104
105 GL . PopMatrix ( ) ;
106
107 // renderTextureの内容をpixelTextureに読み取り、さらにpixelTextureから色を取り出す
108 this . pixelTexture . ReadPixels ( new Rect ( 0 , 0 , 1 , 1 ) , 0 , 0 ) ;
109 RenderTexture . active = activeTexture ;
110 RenderTexture . ReleaseTemporary ( renderTexture ) ;
111 Color result = this . pixelTexture . GetPixel ( 0 , 0 ) ;
112
113 // 結果を整理
114 // もしいずれかの頂点をクリックできていれば、インデックスが0以上の値になるはず
115 int objectIndex = ( int ) result . r ;
116 int vertexIndex = ( int ) result . g ;
117 if ( objectIndex >= 0 && vertexIndex >= 0 )
118 {
119 Vector3 vertexLocalPosition = this . vertices [ objectIndex ] [ vertexIndex ] ;
120 Vector3 vertexWorldPosition = this . renderers [ objectIndex ] . localToWorldMatrix . MultiplyPoint3x4 ( vertexLocalPosition ) ;
121 Debug . Log ( $ "Vertex {vertexIndex} of object {objectIndex} : {vertexWorldPosition}" ) ;
122
123 // さしあたり頂点の位置に球体を生成してみました
124 // ポイントクラウドが巨大なので、目立つように球体も大きめにしています
125 GameObject sphere = GameObject . CreatePrimitive ( PrimitiveType . Sphere ) ;
126 sphere . transform . position = vertexWorldPosition ;
127 sphere . transform . localScale = Vector3 . one * 10.0f ;
128 }
129 }
130 }
131 }
ただし、この方法だと最前面の頂点(または最背面の頂点...プロジェクション行列の前後を反転させる)だけなら効果的に調べられるかと思いますが、RaycastAll
のようにレイ上の頂点をすべて抜き出すのは難しいと思います。
パフォーマンスについてはさしあたり1000万点でやってみたところ、これだけの数の頂点でも私の古いノートPCにしてはわりといいレスポンスではありましたが、やはりもっさり感は否めませんでした。こちらの場合でも多段階判定を組み合わせた方がいいかもしれません(そもそも画面のレンダリングがけっこうきついです...遠くの頂点を事前レンダリングしたビルボードでごまかすような対策も欲しいところです)。
※ちなみに、ご提示のコード中の下記の部分で...
C#
1 float offset = ( ( int ) ( i / N_sub ) ) * 100 ;
2 float x = Random . Range ( offset , 100 + offset ) ;
3 float y = Random . Range ( 0 , 100 ) ;
4 float z = Random . Range ( 0 , 100 ) ;
x
だけfloat
版のRange が使われているようです。そのため、生成されるランダム座標のうちX成分だけ端数が生じているようでした。もしそれで意図通り、あるいは実験目的なのでさほど重要でないということでしたら問題ないでしょうが、X座標も整数にしたい場合はoffset
をint
型にするのがいいかと思います。