回答編集履歴

2

実験結果を追記

2019/06/03 22:08

投稿

Bongo
Bongo

スコア10807

test CHANGED
@@ -25,3 +25,411 @@
25
25
  モデル頂点をそれぞれワールド座標に変換する方式を採用したとしても、[Burst User GuideのUnity.Mathematicsの節](https://docs.unity3d.com/Packages/com.unity.burst@0.2/manual/index.html#unitymathematics)によればベクトル演算は[SIMD](https://ja.wikipedia.org/wiki/SIMD)命令が使用されるらしいので(しかもマニュアルの注釈にあるように、3次元空間の座標変換のような4要素ベクトル演算は一番得意そうです)、パフォーマンスも結構いいんじゃないでしょうか。
26
26
 
27
27
  それでもまだ足りないようでしたら、[コンピュートシェーダー](https://docs.unity3d.com/ja/current/Manual/class-ComputeShader.html)を使う作戦に切り替えたほうがいいかもしれません。これならさらに並列化され、数万点の座標変換ぐらいなら一瞬で処理できそうな気がします。
28
+
29
+
30
+
31
+ **追記**
32
+
33
+ Burstの効果がどの程度か試してみようと思い実験してみました。
34
+
35
+ メッシュは34834頂点のスタンフォードバニーで、下図のように球範囲内の頂点に頂点カラーを設定するというシチュエーションです。
36
+
37
+
38
+
39
+ ![図1](688d32a959f46cebe61969351275ba95.gif)
40
+
41
+
42
+
43
+ このような効果を表現するだけなら、ウサギのマテリアルのバーテックスシェーダー内で距離判定・頂点カラー決定を行う方式にすればもっと効率的な気はしますが、それだと抜き出された頂点インデックスをC#側で利用するのが困難になるでしょうから、そういう場合はご質問者さんのようにJobを使うのはいい選択だと思います。
44
+
45
+
46
+
47
+ 下記のようなスクリプトを使い...
48
+
49
+
50
+
51
+ ```C#
52
+
53
+ using System.Linq;
54
+
55
+ using Unity.Burst;
56
+
57
+ using Unity.Collections;
58
+
59
+ using Unity.Jobs;
60
+
61
+ using Unity.Mathematics;
62
+
63
+ using UnityEngine;
64
+
65
+ using UnityEngine.Profiling;
66
+
67
+
68
+
69
+ namespace Scenes.FindPoints
70
+
71
+ {
72
+
73
+ public class PointFinder : MonoBehaviour
74
+
75
+ {
76
+
77
+ [SerializeField] private Transform model; // スタンフォードバニーのメッシュを持つオブジェクト
78
+
79
+ [SerializeField] private Transform region; // 抜き出し範囲を表す球オブジェクト
80
+
81
+
82
+
83
+ private SphereCollider regionCollider;
84
+
85
+ private Mesh mesh;
86
+
87
+ private Vector3[] meshVertices;
88
+
89
+ private Color32[] meshColors;
90
+
91
+ private NativeArray<float3> vertices;
92
+
93
+ private NativeArray<Color32> colors;
94
+
95
+ private NativeArray<Color32> initialColors;
96
+
97
+
98
+
99
+ private void Start()
100
+
101
+ {
102
+
103
+ if ((this.model == null) || (this.region == null))
104
+
105
+ {
106
+
107
+ Destroy(this);
108
+
109
+ return;
110
+
111
+ }
112
+
113
+
114
+
115
+ var meshFilter = this.model.GetComponent<MeshFilter>();
116
+
117
+ this.regionCollider = this.region.GetComponent<SphereCollider>();
118
+
119
+ if ((meshFilter == null) || (this.regionCollider == null))
120
+
121
+ {
122
+
123
+ Destroy(this);
124
+
125
+ return;
126
+
127
+ }
128
+
129
+
130
+
131
+ this.mesh = meshFilter.mesh;
132
+
133
+ if (this.mesh == null)
134
+
135
+ {
136
+
137
+ Destroy(this);
138
+
139
+ return;
140
+
141
+ }
142
+
143
+
144
+
145
+ this.meshVertices = this.mesh.vertices;
146
+
147
+ if (this.meshVertices == null)
148
+
149
+ {
150
+
151
+ Destroy(this);
152
+
153
+ return;
154
+
155
+ }
156
+
157
+
158
+
159
+ this.meshColors = this.mesh.colors32;
160
+
161
+ if ((this.meshColors == null) || (this.meshColors.Length != this.meshVertices.Length))
162
+
163
+ {
164
+
165
+ this.meshColors = Enumerable.Repeat(new Color32(255, 255, 255, 255), this.meshVertices.Length).ToArray();
166
+
167
+ }
168
+
169
+
170
+
171
+ this.vertices = new NativeArray<float3>(this.meshVertices.Select(v => (float3)v).ToArray(), Allocator.Persistent);
172
+
173
+ this.colors = new NativeArray<Color32>(this.meshColors, Allocator.Persistent);
174
+
175
+ this.initialColors = new NativeArray<Color32>(this.meshColors, Allocator.Persistent);
176
+
177
+ NativeArray<Color32>.Copy(this.meshColors, this.initialColors);
178
+
179
+ }
180
+
181
+
182
+
183
+ private void OnDestroy()
184
+
185
+ {
186
+
187
+ if (this.vertices.IsCreated)
188
+
189
+ {
190
+
191
+ this.vertices.Dispose();
192
+
193
+ }
194
+
195
+
196
+
197
+ if (this.colors.IsCreated)
198
+
199
+ {
200
+
201
+ this.colors.Dispose();
202
+
203
+ }
204
+
205
+
206
+
207
+ if (this.initialColors.IsCreated)
208
+
209
+ {
210
+
211
+ this.initialColors.Dispose();
212
+
213
+ }
214
+
215
+ }
216
+
217
+
218
+
219
+ private void Update()
220
+
221
+ {
222
+
223
+ // 範囲内の頂点のインデックスを抜き出す作業
224
+
225
+ Profiler.BeginSample("Find indices");
226
+
227
+ var center = this.region.TransformPoint(this.regionCollider.center);
228
+
229
+ var scales = this.region.lossyScale;
230
+
231
+ var scale = Mathf.Max(Mathf.Max(scales.x, scales.y), scales.z);
232
+
233
+ var radius = scale * this.regionCollider.radius;
234
+
235
+ var indices = new NativeList<int>(Allocator.TempJob);
236
+
237
+ var indicesQueue = new NativeQueue<int>(Allocator.TempJob);
238
+
239
+ var selectVertexQueueJob = new SelectVertexQueueJob
240
+
241
+ {
242
+
243
+ vertices = this.vertices,
244
+
245
+ queue = indicesQueue.ToConcurrent(),
246
+
247
+ basePos = center,
248
+
249
+ maxSqrDistance = radius * radius,
250
+
251
+ transform = this.model.localToWorldMatrix
252
+
253
+ }.Schedule(this.vertices.Length, 0);
254
+
255
+ new QueueToListJob
256
+
257
+ {
258
+
259
+ source = indicesQueue,
260
+
261
+ result = indices
262
+
263
+ }.Schedule(selectVertexQueueJob).Complete();
264
+
265
+ indicesQueue.Dispose();
266
+
267
+ Profiler.EndSample();
268
+
269
+
270
+
271
+ // 抜き出された頂点に頂点カラーを設定、メッシュデータを更新する作業
272
+
273
+ Profiler.BeginSample("Apply vertex colors");
274
+
275
+ NativeArray<Color32>.Copy(this.initialColors, this.colors);
276
+
277
+ new ApplyColorJob
278
+
279
+ {
280
+
281
+ indices = indices,
282
+
283
+ colors = this.colors,
284
+
285
+ color = new Color32(255, 0, 0, 255)
286
+
287
+ }.Schedule().Complete();
288
+
289
+ indices.Dispose();
290
+
291
+ NativeArray<Color32>.Copy(this.colors, this.meshColors);
292
+
293
+ this.mesh.colors32 = this.meshColors;
294
+
295
+ this.mesh.UploadMeshData(false);
296
+
297
+ Profiler.EndSample();
298
+
299
+ }
300
+
301
+
302
+
303
+ [BurstCompile]
304
+
305
+ private struct SelectVertexQueueJob : IJobParallelFor
306
+
307
+ {
308
+
309
+ [ReadOnly] public NativeArray<float3> vertices;
310
+
311
+ [WriteOnly] public NativeQueue<int>.Concurrent queue;
312
+
313
+
314
+
315
+ public float3 basePos;
316
+
317
+ public float maxSqrDistance;
318
+
319
+ public float4x4 transform;
320
+
321
+
322
+
323
+ public void Execute(int index)
324
+
325
+ {
326
+
327
+ // ワールド座標に変換して球中心との2乗距離を調べ...
328
+
329
+ var v = math.mul(this.transform, new float4(this.vertices[index], 1.0f)).xyz;
330
+
331
+ var sqrDistance = math.distancesq(this.basePos, v);
332
+
333
+ if (sqrDistance < this.maxSqrDistance)
334
+
335
+ {
336
+
337
+ // 範囲内ならキューにインデックスを追加
338
+
339
+ this.queue.Enqueue(index);
340
+
341
+ }
342
+
343
+ }
344
+
345
+ }
346
+
347
+
348
+
349
+ [BurstCompile]
350
+
351
+ private struct QueueToListJob : IJob
352
+
353
+ {
354
+
355
+ [WriteOnly] public NativeQueue<int> source;
356
+
357
+ [WriteOnly] public NativeList<int> result;
358
+
359
+
360
+
361
+ public void Execute()
362
+
363
+ {
364
+
365
+ while (this.source.TryDequeue(out var value))
366
+
367
+ {
368
+
369
+ this.result.Add(value);
370
+
371
+ }
372
+
373
+ }
374
+
375
+ }
376
+
377
+
378
+
379
+ [BurstCompile]
380
+
381
+ private struct ApplyColorJob : IJob
382
+
383
+ {
384
+
385
+ [ReadOnly] public NativeList<int> indices;
386
+
387
+ [WriteOnly] public NativeArray<Color32> colors;
388
+
389
+
390
+
391
+ public Color32 color;
392
+
393
+
394
+
395
+ public void Execute()
396
+
397
+ {
398
+
399
+ for (var i = 0; i < this.indices.Length; i++)
400
+
401
+ {
402
+
403
+ this.colors[this.indices[i]] = this.color;
404
+
405
+ }
406
+
407
+ }
408
+
409
+ }
410
+
411
+ }
412
+
413
+ }
414
+
415
+ ```
416
+
417
+
418
+
419
+ Burstオフで実行すると頂点抜き出し作業にかなりの時間がかかりますが...
420
+
421
+
422
+
423
+ ![図2](1b7e8b228e41881251da28c92568e069.png)
424
+
425
+
426
+
427
+ Burstをオンにすると、抜き出し作業時間が大幅に短縮されました。
428
+
429
+
430
+
431
+ ![図3](711914d0915750113ad937a40c1da99e.png)
432
+
433
+
434
+
435
+ 使ったのは数年前に入手した比較的廉価なノートパソコンでIntel Core i3-4030Uが載ったマシンですが、それでもこれだけの速度が出るならワールド座標変換方式でも悪くないんじゃないでしょうか?

1

文章表現の修正

2019/06/03 22:08

投稿

Bongo
Bongo

スコア10807

test CHANGED
@@ -18,10 +18,10 @@
18
18
 
19
19
 
20
20
 
21
- 赤い球の領域がX軸だけ半分に縮んでしまう...つまり、`radius`が方向によって変化してしまうので、正しく領域内の点を求めるには結局頂点座標をワールド空間に直すのと同等の計算量が必要になってしまうんじゃないしょうか
21
+ 赤い球の領域がX軸だけ半分に縮んでしまう...つまり、`radius`が方向によって変化してしまうので、正しく領域内の点を求めるには結局頂点座標をワールド空間に直すのと同等の計算量が必要になってしまいそうで
22
22
 
23
23
 
24
24
 
25
- [Burst User GuideのUnity.Mathematicsの節](https://docs.unity3d.com/Packages/com.unity.burst@0.2/manual/index.html#unitymathematics)によればベクトル演算は[SIMD](https://ja.wikipedia.org/wiki/SIMD)命令が使用されるらしいので(しかもマニュアルの注釈にあるように、3次元空間の座標変換のような4要素ベクトル演算は一番得意そうです)、パフォーマンスも結構いいんじゃないでしょうか。
25
+ モデル頂点をそれぞれワールド座標に変換する方式を採用したとしても、[Burst User GuideのUnity.Mathematicsの節](https://docs.unity3d.com/Packages/com.unity.burst@0.2/manual/index.html#unitymathematics)によればベクトル演算は[SIMD](https://ja.wikipedia.org/wiki/SIMD)命令が使用されるらしいので(しかもマニュアルの注釈にあるように、3次元空間の座標変換のような4要素ベクトル演算は一番得意そうです)、パフォーマンスも結構いいんじゃないでしょうか。
26
26
 
27
27
  それでもまだ足りないようでしたら、[コンピュートシェーダー](https://docs.unity3d.com/ja/current/Manual/class-ComputeShader.html)を使う作戦に切り替えたほうがいいかもしれません。これならさらに並列化され、数万点の座標変換ぐらいなら一瞬で処理できそうな気がします。