スケールが一様でない可能性があるのなら、ご質問者さんのおっしゃる「モデルの頂点座標をそれぞれワールド座標に直す」という作戦がいいと思いますよ。
たとえば下図はモデルがX軸だけ2倍に拡大されたものですが(ウサギの背中の上にある赤い球が取り出したい領域のつもりです)...
これをモデルのローカル空間から見ると下図のように見えるはずです。
赤い球の領域がX軸だけ半分に縮んでしまう...つまり、radius
が方向によって変化してしまうので、正しく領域内の点を求めるには結局頂点座標をワールド空間に直すのと同等の計算量が必要になってしまいそうです。
モデル頂点をそれぞれワールド座標に変換する方式を採用したとしても、Burst User GuideのUnity.Mathematicsの節 によればベクトル演算はSIMD 命令が使用されるらしいので(しかもマニュアルの注釈にあるように、3次元空間の座標変換のような4要素ベクトル演算は一番得意そうです)、パフォーマンスも結構いいんじゃないでしょうか。
それでもまだ足りないようでしたら、コンピュートシェーダー を使う作戦に切り替えたほうがいいかもしれません。これならさらに並列化され、数万点の座標変換ぐらいなら一瞬で処理できそうな気がします。
追記
Burstの効果がどの程度か試してみようと思い実験してみました。
メッシュは34834頂点のスタンフォードバニーで、下図のように球範囲内の頂点に頂点カラーを設定するというシチュエーションです。
このような効果を表現するだけなら、ウサギのマテリアルのバーテックスシェーダー内で距離判定・頂点カラー決定を行う方式にすればもっと効率的な気はしますが、それだと抜き出された頂点インデックスをC#側で利用するのが困難になるでしょうから、そういう場合はご質問者さんのようにJobを使うのはいい選択だと思います。
下記のようなスクリプトを使い...
C#
1 using System . Linq ;
2 using Unity . Burst ;
3 using Unity . Collections ;
4 using Unity . Jobs ;
5 using Unity . Mathematics ;
6 using UnityEngine ;
7 using UnityEngine . Profiling ;
8
9 namespace Scenes . FindPoints
10 {
11 public class PointFinder : MonoBehaviour
12 {
13 [ SerializeField ] private Transform model ; // スタンフォードバニーのメッシュを持つオブジェクト
14 [ SerializeField ] private Transform region ; // 抜き出し範囲を表す球オブジェクト
15
16 private SphereCollider regionCollider ;
17 private Mesh mesh ;
18 private Vector3 [ ] meshVertices ;
19 private Color32 [ ] meshColors ;
20 private NativeArray < float3 > vertices ;
21 private NativeArray < Color32 > colors ;
22 private NativeArray < Color32 > initialColors ;
23
24 private void Start ( )
25 {
26 if ( ( this . model == null ) || ( this . region == null ) )
27 {
28 Destroy ( this ) ;
29 return ;
30 }
31
32 var meshFilter = this . model . GetComponent < MeshFilter > ( ) ;
33 this . regionCollider = this . region . GetComponent < SphereCollider > ( ) ;
34 if ( ( meshFilter == null ) || ( this . regionCollider == null ) )
35 {
36 Destroy ( this ) ;
37 return ;
38 }
39
40 this . mesh = meshFilter . mesh ;
41 if ( this . mesh == null )
42 {
43 Destroy ( this ) ;
44 return ;
45 }
46
47 this . meshVertices = this . mesh . vertices ;
48 if ( this . meshVertices == null )
49 {
50 Destroy ( this ) ;
51 return ;
52 }
53
54 this . meshColors = this . mesh . colors32 ;
55 if ( ( this . meshColors == null ) || ( this . meshColors . Length != this . meshVertices . Length ) )
56 {
57 this . meshColors = Enumerable . Repeat ( new Color32 ( 255 , 255 , 255 , 255 ) , this . meshVertices . Length ) . ToArray ( ) ;
58 }
59
60 this . vertices = new NativeArray < float3 > ( this . meshVertices . Select ( v = > ( float3 ) v ) . ToArray ( ) , Allocator . Persistent ) ;
61 this . colors = new NativeArray < Color32 > ( this . meshColors , Allocator . Persistent ) ;
62 this . initialColors = new NativeArray < Color32 > ( this . meshColors , Allocator . Persistent ) ;
63 NativeArray < Color32 > . Copy ( this . meshColors , this . initialColors ) ;
64 }
65
66 private void OnDestroy ( )
67 {
68 if ( this . vertices . IsCreated )
69 {
70 this . vertices . Dispose ( ) ;
71 }
72
73 if ( this . colors . IsCreated )
74 {
75 this . colors . Dispose ( ) ;
76 }
77
78 if ( this . initialColors . IsCreated )
79 {
80 this . initialColors . Dispose ( ) ;
81 }
82 }
83
84 private void Update ( )
85 {
86 // 範囲内の頂点のインデックスを抜き出す作業
87 Profiler . BeginSample ( "Find indices" ) ;
88 var center = this . region . TransformPoint ( this . regionCollider . center ) ;
89 var scales = this . region . lossyScale ;
90 var scale = Mathf . Max ( Mathf . Max ( scales . x , scales . y ) , scales . z ) ;
91 var radius = scale * this . regionCollider . radius ;
92 var indices = new NativeList < int > ( Allocator . TempJob ) ;
93 var indicesQueue = new NativeQueue < int > ( Allocator . TempJob ) ;
94 var selectVertexQueueJob = new SelectVertexQueueJob
95 {
96 vertices = this . vertices ,
97 queue = indicesQueue . ToConcurrent ( ) ,
98 basePos = center ,
99 maxSqrDistance = radius * radius ,
100 transform = this . model . localToWorldMatrix
101 } . Schedule ( this . vertices . Length , 0 ) ;
102 new QueueToListJob
103 {
104 source = indicesQueue ,
105 result = indices
106 } . Schedule ( selectVertexQueueJob ) . Complete ( ) ;
107 indicesQueue . Dispose ( ) ;
108 Profiler . EndSample ( ) ;
109
110 // 抜き出された頂点に頂点カラーを設定、メッシュデータを更新する作業
111 Profiler . BeginSample ( "Apply vertex colors" ) ;
112 NativeArray < Color32 > . Copy ( this . initialColors , this . colors ) ;
113 new ApplyColorJob
114 {
115 indices = indices ,
116 colors = this . colors ,
117 color = new Color32 ( 255 , 0 , 0 , 255 )
118 } . Schedule ( ) . Complete ( ) ;
119 indices . Dispose ( ) ;
120 NativeArray < Color32 > . Copy ( this . colors , this . meshColors ) ;
121 this . mesh . colors32 = this . meshColors ;
122 this . mesh . UploadMeshData ( false ) ;
123 Profiler . EndSample ( ) ;
124 }
125
126 [ BurstCompile ]
127 private struct SelectVertexQueueJob : IJobParallelFor
128 {
129 [ ReadOnly ] public NativeArray < float3 > vertices ;
130 [ WriteOnly ] public NativeQueue < int > . Concurrent queue ;
131
132 public float3 basePos ;
133 public float maxSqrDistance ;
134 public float4x4 transform ;
135
136 public void Execute ( int index )
137 {
138 // ワールド座標に変換して球中心との2乗距離を調べ...
139 var v = math . mul ( this . transform , new float4 ( this . vertices [ index ] , 1.0f ) ) . xyz ;
140 var sqrDistance = math . distancesq ( this . basePos , v ) ;
141 if ( sqrDistance < this . maxSqrDistance )
142 {
143 // 範囲内ならキューにインデックスを追加
144 this . queue . Enqueue ( index ) ;
145 }
146 }
147 }
148
149 [ BurstCompile ]
150 private struct QueueToListJob : IJob
151 {
152 [ WriteOnly ] public NativeQueue < int > source ;
153 [ WriteOnly ] public NativeList < int > result ;
154
155 public void Execute ( )
156 {
157 while ( this . source . TryDequeue ( out var value ) )
158 {
159 this . result . Add ( value ) ;
160 }
161 }
162 }
163
164 [ BurstCompile ]
165 private struct ApplyColorJob : IJob
166 {
167 [ ReadOnly ] public NativeList < int > indices ;
168 [ WriteOnly ] public NativeArray < Color32 > colors ;
169
170 public Color32 color ;
171
172 public void Execute ( )
173 {
174 for ( var i = 0 ; i < this . indices . Length ; i ++ )
175 {
176 this . colors [ this . indices [ i ] ] = this . color ;
177 }
178 }
179 }
180 }
181 }
Burstオフで実行すると頂点抜き出し作業にかなりの時間がかかりますが...
Burstをオンにすると、抜き出し作業時間が大幅に短縮されました。
使ったのは数年前に入手した比較的廉価なノートパソコンでIntel Core i3-4030Uが載ったマシンですが、それでもこれだけの速度が出るならワールド座標変換方式でも悪くないんじゃないでしょうか?
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/06/03 08:02
2019/06/10 12:11