@lijuhong1981/three.instancedsprite 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +213 -10
- package/README.md +146 -55
- package/index.js +6 -5
- package/package.json +11 -4
- package/src/InstancedSprite.js +14 -1
- package/src/InstancedSpriteCollection.js +205 -57
- package/src/InstancedSpriteMaterial.js +154 -147
- package/src/InstancedSpriteMesh.js +510 -425
- package/src/InstancedSpriteNodeMaterial.js +12 -5
- package/src/TextureAtlas.js +125 -0
- package/.claude/settings.local.json +0 -7
|
@@ -2,10 +2,10 @@ import Check from "@lijuhong1981/jscheck/src/Check.js";
|
|
|
2
2
|
import {
|
|
3
3
|
MathUtils,
|
|
4
4
|
Object3D,
|
|
5
|
-
Texture
|
|
6
5
|
} from "three";
|
|
7
6
|
import InstancedSprite from "./InstancedSprite.js";
|
|
8
7
|
import InstancedSpriteMesh from "./InstancedSpriteMesh.js";
|
|
8
|
+
import TextureAtlas from "./TextureAtlas.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @import InstancedSpriteOptions from "./InstancedSprite.js";
|
|
@@ -19,18 +19,22 @@ const imageCache = new Map();
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* InstancedSpriteCollection类,批量管理InstancedSprite实例
|
|
22
|
-
*
|
|
22
|
+
*
|
|
23
23
|
* * 继承自Object3D
|
|
24
|
-
* *
|
|
25
|
-
* *
|
|
24
|
+
* * 使用纹理图集将不同图片打包,一张图集对应一个 InstancedSpriteMesh(一次 draw call)
|
|
25
|
+
* * 图集扩容到上限后会新建图集与 Mesh
|
|
26
26
|
* @extends Object3D
|
|
27
27
|
*/
|
|
28
28
|
class InstancedSpriteCollection extends Object3D {
|
|
29
29
|
/**
|
|
30
|
-
* @param {
|
|
30
|
+
* @param {object} [options] - 配置项
|
|
31
|
+
* @param {boolean} [options.useNodeMaterial=false] - 是否使用TSL的NodeMaterial(WebGPU),默认false
|
|
32
|
+
* @param {number} [options.initialSize=1024] - 图集初始边长
|
|
33
|
+
* @param {number} [options.maxSize=8192] - 图集最大边长
|
|
34
|
+
* @param {number} [options.padding=2] - 子图间距
|
|
31
35
|
* @constructor
|
|
32
36
|
*/
|
|
33
|
-
constructor(
|
|
37
|
+
constructor(options = {}) {
|
|
34
38
|
super();
|
|
35
39
|
/**
|
|
36
40
|
* 是否使用TSL的NodeMaterial,默认false
|
|
@@ -38,25 +42,49 @@ class InstancedSpriteCollection extends Object3D {
|
|
|
38
42
|
* @readonly
|
|
39
43
|
* @default false
|
|
40
44
|
*/
|
|
41
|
-
this.useNodeMaterial = useNodeMaterial;
|
|
45
|
+
this.useNodeMaterial = options.useNodeMaterial === true;
|
|
42
46
|
/**
|
|
43
47
|
* 对象类型标识
|
|
44
48
|
* @type {string}
|
|
45
49
|
* @readonly
|
|
46
50
|
*/
|
|
47
51
|
this.type = "InstancedSpriteCollection";
|
|
48
|
-
// this.frustumCulled = false;
|
|
49
52
|
/**
|
|
50
53
|
* @type {Array<InstancedSprite>}
|
|
51
54
|
* @ignore
|
|
52
55
|
*/
|
|
53
56
|
this._instancedSprites = [];
|
|
54
57
|
/**
|
|
55
|
-
* @type {Map<
|
|
58
|
+
* @type {Map<string, InstancedSprite>}
|
|
56
59
|
* @ignore
|
|
57
60
|
*/
|
|
58
|
-
this.
|
|
61
|
+
this._spriteByUuid = new Map();
|
|
62
|
+
/**
|
|
63
|
+
* 图集与对应 Mesh 的列表,按创建顺序排列
|
|
64
|
+
* @type {Array<{atlas:TextureAtlas, mesh:InstancedSpriteMesh}>}
|
|
65
|
+
* @ignore
|
|
66
|
+
*/
|
|
67
|
+
this._atlasList = [];
|
|
68
|
+
/**
|
|
69
|
+
* 图片源到所在图集条目的索引,避免同一张图片被重复打包进不同图集
|
|
70
|
+
* @type {Map<HTMLImageElement|HTMLCanvasElement, {atlas:TextureAtlas, mesh:InstancedSpriteMesh}>}
|
|
71
|
+
* @ignore
|
|
72
|
+
*/
|
|
73
|
+
this._imageEntry = new Map();
|
|
74
|
+
/**
|
|
75
|
+
* 图集配置项,新建图集时使用
|
|
76
|
+
* @type {object}
|
|
77
|
+
* @ignore
|
|
78
|
+
*/
|
|
79
|
+
this._atlasOptions = options;
|
|
59
80
|
this._depthTest = true;
|
|
81
|
+
this._depthWrite = true;
|
|
82
|
+
/**
|
|
83
|
+
* 预留容量,在创建新Mesh时应用,undefined表示不预留
|
|
84
|
+
* @type {number|undefined}
|
|
85
|
+
* @ignore
|
|
86
|
+
*/
|
|
87
|
+
this._reservedCapacity = undefined;
|
|
60
88
|
}
|
|
61
89
|
/**
|
|
62
90
|
* InstancedSpriteCollection对象标识
|
|
@@ -71,11 +99,23 @@ class InstancedSpriteCollection extends Object3D {
|
|
|
71
99
|
*/
|
|
72
100
|
get depthTest() { return this._depthTest; }
|
|
73
101
|
set depthTest(value) {
|
|
74
|
-
Check.typeOf.boolean(
|
|
102
|
+
Check.typeOf.boolean('depthTest', value);
|
|
75
103
|
this._depthTest = value;
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
104
|
+
for (const entry of this._atlasList) {
|
|
105
|
+
entry.mesh.depthTest = value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 深度写入开关,默认为true;多个半透明 Sprite 重叠时,开启深度写入可能导致排序瑕疵(后方 Sprite 被错误遮挡),关闭可缓解,适合半透明标签/粒子等场景
|
|
110
|
+
* @type {boolean}
|
|
111
|
+
* @default true
|
|
112
|
+
*/
|
|
113
|
+
get depthWrite() { return this._depthWrite; }
|
|
114
|
+
set depthWrite(value) {
|
|
115
|
+
Check.typeOf.boolean('depthWrite', value);
|
|
116
|
+
this._depthWrite = value;
|
|
117
|
+
for (const entry of this._atlasList) {
|
|
118
|
+
entry.mesh.depthWrite = value;
|
|
79
119
|
}
|
|
80
120
|
}
|
|
81
121
|
/**
|
|
@@ -88,35 +128,7 @@ class InstancedSpriteCollection extends Object3D {
|
|
|
88
128
|
source.src = MathUtils.generateUUID();
|
|
89
129
|
if (!imageCache.has(source.src))
|
|
90
130
|
imageCache.set(source.src, source);
|
|
91
|
-
|
|
92
|
-
if (!mesh) {
|
|
93
|
-
const texture = new Texture();
|
|
94
|
-
if (source instanceof HTMLCanvasElement || (source instanceof HTMLImageElement && source.complete)) {
|
|
95
|
-
texture.image = source;
|
|
96
|
-
texture.needsUpdate = true;
|
|
97
|
-
} else {
|
|
98
|
-
const onload = () => {
|
|
99
|
-
source.removeEventListener('load', onload);
|
|
100
|
-
texture.image = source;
|
|
101
|
-
texture.needsUpdate = true;
|
|
102
|
-
sprite.imageSize.set(source.width, source.height);
|
|
103
|
-
};
|
|
104
|
-
source.addEventListener('load', onload);
|
|
105
|
-
}
|
|
106
|
-
mesh = new InstancedSpriteMesh(this, texture);
|
|
107
|
-
mesh.depthTest = this._depthTest;
|
|
108
|
-
this._meshes.set(source, mesh);
|
|
109
|
-
super.add(mesh);
|
|
110
|
-
}
|
|
111
|
-
if (sprite._mesh === mesh)
|
|
112
|
-
return;
|
|
113
|
-
// instancedSprite如果已经有所属的mesh了,则从所属的Mesh中移除
|
|
114
|
-
sprite.remove();
|
|
115
|
-
mesh.add(sprite);
|
|
116
|
-
sprite._image = source;
|
|
117
|
-
if (source.width !== 0 && source.height !== 0) {
|
|
118
|
-
sprite.imageSize.set(source.width, source.height);
|
|
119
|
-
}
|
|
131
|
+
this._setImageAtlas(source, sprite);
|
|
120
132
|
} else if (typeof source === 'string') {
|
|
121
133
|
let image = imageCache.get(source);
|
|
122
134
|
if (!image) {
|
|
@@ -129,6 +141,95 @@ class InstancedSpriteCollection extends Object3D {
|
|
|
129
141
|
throw new Error('Invalid image source type ' + source);
|
|
130
142
|
}
|
|
131
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* 将图片打包进图集并加入对应 Mesh
|
|
146
|
+
* @param {HTMLImageElement|HTMLCanvasElement} source
|
|
147
|
+
* @param {InstancedSprite} sprite
|
|
148
|
+
* @private
|
|
149
|
+
*/
|
|
150
|
+
_setImageAtlas(source, sprite) {
|
|
151
|
+
sprite._image = source;
|
|
152
|
+
const loaded = source instanceof HTMLCanvasElement || (source.width > 0 && source.height > 0);
|
|
153
|
+
if (loaded) {
|
|
154
|
+
this._assignAtlas(source, sprite);
|
|
155
|
+
} else {
|
|
156
|
+
const onload = () => {
|
|
157
|
+
source.removeEventListener('load', onload);
|
|
158
|
+
// 图片加载完成前 sprite 可能已被移除,此时不再加入图集
|
|
159
|
+
if (this._spriteByUuid.get(sprite.uuid) === sprite)
|
|
160
|
+
this._assignAtlas(source, sprite);
|
|
161
|
+
};
|
|
162
|
+
source.addEventListener('load', onload);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 将已加载的图片打包进图集,设置 sprite 的 uvRect 与 imageSize,并加入图集 Mesh
|
|
167
|
+
* @param {HTMLImageElement|HTMLCanvasElement} source
|
|
168
|
+
* @param {InstancedSprite} sprite
|
|
169
|
+
* @private
|
|
170
|
+
*/
|
|
171
|
+
_assignAtlas(source, sprite) {
|
|
172
|
+
// 已打包过的图片复用其所在图集,避免把同一张图重复打进不同图集
|
|
173
|
+
let entry = this._imageEntry.get(source);
|
|
174
|
+
if (!entry) {
|
|
175
|
+
entry = this._atlasList[this._atlasList.length - 1];
|
|
176
|
+
let rect = entry ? entry.atlas.add(source) : null;
|
|
177
|
+
if (rect === null && entry) {
|
|
178
|
+
// 图集已满,尝试扩容
|
|
179
|
+
if (entry.atlas.grow()) {
|
|
180
|
+
// 扩容会重建纹理,需同步更新 Mesh 材质引用的纹理
|
|
181
|
+
entry.mesh.material.texture = entry.atlas.texture;
|
|
182
|
+
this._recomputeUvRects(entry);
|
|
183
|
+
rect = entry.atlas.add(source);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (rect === null) {
|
|
187
|
+
// 图集已达最大尺寸,新建图集与 Mesh
|
|
188
|
+
entry = this._createAtlasEntry();
|
|
189
|
+
this._atlasList.push(entry);
|
|
190
|
+
rect = entry.atlas.add(source);
|
|
191
|
+
}
|
|
192
|
+
this._imageEntry.set(source, entry);
|
|
193
|
+
}
|
|
194
|
+
const rect = entry.atlas.getRect(source);
|
|
195
|
+
const uv = entry.atlas.getUvRect(rect);
|
|
196
|
+
sprite.uvRect.set(uv[0], uv[1], uv[2], uv[3]);
|
|
197
|
+
sprite.imageSize.set(source.width, source.height);
|
|
198
|
+
sprite._remove();
|
|
199
|
+
entry.mesh.add(sprite);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 图集扩容后重新归一化该图集内所有 sprite 的 uvRect
|
|
203
|
+
* @param {{atlas:TextureAtlas, mesh:InstancedSpriteMesh}} entry
|
|
204
|
+
* @private
|
|
205
|
+
*/
|
|
206
|
+
_recomputeUvRects(entry) {
|
|
207
|
+
for (const sprite of this._instancedSprites) {
|
|
208
|
+
if (sprite._mesh === entry.mesh) {
|
|
209
|
+
const rect = entry.atlas.getRect(sprite._image);
|
|
210
|
+
if (rect) {
|
|
211
|
+
const uv = entry.atlas.getUvRect(rect);
|
|
212
|
+
sprite.uvRect.set(uv[0], uv[1], uv[2], uv[3]);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* 创建新的图集与对应 Mesh
|
|
219
|
+
* @returns {{atlas:TextureAtlas, mesh:InstancedSpriteMesh}}
|
|
220
|
+
* @private
|
|
221
|
+
*/
|
|
222
|
+
_createAtlasEntry() {
|
|
223
|
+
const atlas = new TextureAtlas(this._atlasOptions);
|
|
224
|
+
const mesh = new InstancedSpriteMesh(this, atlas.texture);
|
|
225
|
+
mesh.depthTest = this._depthTest;
|
|
226
|
+
mesh.depthWrite = this._depthWrite;
|
|
227
|
+
if (this._reservedCapacity !== undefined) {
|
|
228
|
+
mesh.reserve(this._reservedCapacity);
|
|
229
|
+
}
|
|
230
|
+
super.add(mesh);
|
|
231
|
+
return { atlas, mesh };
|
|
232
|
+
}
|
|
132
233
|
/**
|
|
133
234
|
* InstancedSprite实例数组
|
|
134
235
|
* @type {Array<InstancedSprite>}
|
|
@@ -147,7 +248,7 @@ class InstancedSpriteCollection extends Object3D {
|
|
|
147
248
|
}
|
|
148
249
|
/**
|
|
149
250
|
* 根据索引获取InstancedSprite实例
|
|
150
|
-
* @param {number} index
|
|
251
|
+
* @param {number} index
|
|
151
252
|
* @returns {InstancedSprite|undefined}
|
|
152
253
|
*/
|
|
153
254
|
get(index) {
|
|
@@ -159,19 +260,17 @@ class InstancedSpriteCollection extends Object3D {
|
|
|
159
260
|
* @returns {InstancedSprite|undefined}
|
|
160
261
|
*/
|
|
161
262
|
getByUuid(uuid) {
|
|
162
|
-
|
|
163
|
-
if (sprite.uuid === uuid)
|
|
164
|
-
return sprite;
|
|
165
|
-
}
|
|
263
|
+
return this._spriteByUuid.get(uuid);
|
|
166
264
|
}
|
|
167
265
|
/**
|
|
168
266
|
* 添加InstancedSprite
|
|
169
|
-
* @
|
|
267
|
+
* @param {InstancedSpriteOptions} options - 初始化配置项
|
|
170
268
|
* @returns {InstancedSprite}
|
|
171
269
|
*/
|
|
172
270
|
add(options = {}) {
|
|
173
271
|
const sprite = new InstancedSprite(options, this);
|
|
174
272
|
this._instancedSprites.push(sprite);
|
|
273
|
+
this._spriteByUuid.set(sprite.uuid, sprite);
|
|
175
274
|
return sprite;
|
|
176
275
|
}
|
|
177
276
|
/**
|
|
@@ -183,10 +282,40 @@ class InstancedSpriteCollection extends Object3D {
|
|
|
183
282
|
const index = this._instancedSprites.indexOf(sprite);
|
|
184
283
|
if (index !== -1) {
|
|
185
284
|
this._instancedSprites.splice(index, 1);
|
|
285
|
+
this._spriteByUuid.delete(sprite.uuid);
|
|
186
286
|
sprite._remove();
|
|
187
287
|
}
|
|
188
288
|
return this;
|
|
189
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* 移除所有InstancedSprite,并清空所有Mesh的实例数据(Mesh与图集保留以便复用)
|
|
292
|
+
* @returns {InstancedSpriteCollection}
|
|
293
|
+
*/
|
|
294
|
+
clear() {
|
|
295
|
+
for (const sprite of this._instancedSprites) {
|
|
296
|
+
sprite._mesh = undefined;
|
|
297
|
+
sprite._instanceIndex = -1;
|
|
298
|
+
}
|
|
299
|
+
this._instancedSprites.length = 0;
|
|
300
|
+
this._spriteByUuid.clear();
|
|
301
|
+
for (const entry of this._atlasList) {
|
|
302
|
+
entry.mesh.clear();
|
|
303
|
+
}
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* 预分配所有Mesh的容量,避免后续动态扩容
|
|
308
|
+
* @param {number} capacity - 需要预留的实例数量
|
|
309
|
+
* @returns {InstancedSpriteCollection}
|
|
310
|
+
*/
|
|
311
|
+
reserve(capacity) {
|
|
312
|
+
Check.typeOf.number('capacity', capacity);
|
|
313
|
+
this._reservedCapacity = capacity;
|
|
314
|
+
for (const entry of this._atlasList) {
|
|
315
|
+
entry.mesh.reserve(capacity);
|
|
316
|
+
}
|
|
317
|
+
return this;
|
|
318
|
+
}
|
|
190
319
|
/**
|
|
191
320
|
* 遍历所有的InstancedSprite
|
|
192
321
|
* @param {Function} callback
|
|
@@ -203,24 +332,43 @@ class InstancedSpriteCollection extends Object3D {
|
|
|
203
332
|
* @param {Array<Object>} intersects - The target array that holds the intersection points.
|
|
204
333
|
*/
|
|
205
334
|
raycast(raycaster, intersects) {
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
mesh.raycast(raycaster, intersects);
|
|
335
|
+
for (const entry of this._atlasList) {
|
|
336
|
+
entry.mesh.raycast(raycaster, intersects);
|
|
209
337
|
}
|
|
210
338
|
}
|
|
211
339
|
/**
|
|
212
340
|
* 每帧更新
|
|
213
341
|
* @private
|
|
214
|
-
|
|
342
|
+
*/
|
|
215
343
|
update() {
|
|
216
344
|
if (!this.visible) return;
|
|
217
345
|
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
mesh.update();
|
|
346
|
+
for (const entry of this._atlasList) {
|
|
347
|
+
entry.mesh.update();
|
|
221
348
|
}
|
|
222
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* 释放所有 GPU 资源(几何体、材质、图集纹理),并移除所有子 Mesh
|
|
352
|
+
* @returns {InstancedSpriteCollection}
|
|
353
|
+
*/
|
|
354
|
+
dispose() {
|
|
355
|
+
this.clear();
|
|
356
|
+
for (const entry of this._atlasList) {
|
|
357
|
+
entry.mesh.dispose();
|
|
358
|
+
entry.atlas.texture.dispose();
|
|
359
|
+
}
|
|
360
|
+
this._atlasList.length = 0;
|
|
361
|
+
this._imageEntry.clear();
|
|
362
|
+
super.clear();
|
|
363
|
+
return this;
|
|
364
|
+
}
|
|
223
365
|
};
|
|
224
366
|
|
|
225
367
|
export default InstancedSpriteCollection;
|
|
226
|
-
export { InstancedSpriteCollection };
|
|
368
|
+
export { InstancedSpriteCollection };
|
|
369
|
+
/**
|
|
370
|
+
* InstancedSpriteCollection 的别名。
|
|
371
|
+
* @class
|
|
372
|
+
* @name BillboardCollection
|
|
373
|
+
*/
|
|
374
|
+
export const BillboardCollection = InstancedSpriteCollection;
|
|
@@ -1,147 +1,154 @@
|
|
|
1
|
-
import { ShaderMaterial, Texture } from "three";
|
|
2
|
-
|
|
3
|
-
const vertexShader = `
|
|
4
|
-
// Three.js ShaderMaterial专用,自动注入uv、position、modelViewMatrix、projectionMatrix
|
|
5
|
-
precision highp float;
|
|
6
|
-
precision highp int;
|
|
7
|
-
|
|
8
|
-
#include <common>
|
|
9
|
-
#include <logdepthbuf_pars_vertex>
|
|
10
|
-
|
|
11
|
-
// 基础属性
|
|
12
|
-
attribute vec4 aPositionAndShow; // xyz: InstancedSprite位置(世界空间),w:显示标志(0/1)
|
|
13
|
-
attribute vec4 aCenterAndSize; // xy: 锚点中心(0-1), zw: 图片宽高(像素/米)
|
|
14
|
-
attribute vec3 aScaleAndRotationAndSizeAttenuation; // x: 缩放, y: 旋转(弧度), z: 大小跟随相机深度(0/1)
|
|
15
|
-
attribute vec4 aColorAndOpacity; // RGBA颜色
|
|
16
|
-
attribute vec4 aPickColorAndEnabled; // xyz:拾取颜色,w:是否启用拾取颜色(0/1)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
varying
|
|
20
|
-
varying
|
|
21
|
-
varying vec4
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
float
|
|
27
|
-
float
|
|
28
|
-
float
|
|
29
|
-
|
|
30
|
-
vec2
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
rotatedPosition
|
|
69
|
-
rotatedPosition.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
#include <
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
varying
|
|
87
|
-
varying
|
|
88
|
-
varying vec4
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
vec4
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
#include <
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
*
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
export
|
|
1
|
+
import { ShaderMaterial, Texture } from "three";
|
|
2
|
+
|
|
3
|
+
const vertexShader = `
|
|
4
|
+
// Three.js ShaderMaterial专用,自动注入uv、position、modelViewMatrix、projectionMatrix
|
|
5
|
+
precision highp float;
|
|
6
|
+
precision highp int;
|
|
7
|
+
|
|
8
|
+
#include <common>
|
|
9
|
+
#include <logdepthbuf_pars_vertex>
|
|
10
|
+
|
|
11
|
+
// 基础属性
|
|
12
|
+
attribute vec4 aPositionAndShow; // xyz: InstancedSprite位置(世界空间),w:显示标志(0/1)
|
|
13
|
+
attribute vec4 aCenterAndSize; // xy: 锚点中心(0-1), zw: 图片宽高(像素/米)
|
|
14
|
+
attribute vec3 aScaleAndRotationAndSizeAttenuation; // x: 缩放, y: 旋转(弧度), z: 大小跟随相机深度(0/1)
|
|
15
|
+
attribute vec4 aColorAndOpacity; // RGBA颜色
|
|
16
|
+
attribute vec4 aPickColorAndEnabled; // xyz:拾取颜色,w:是否启用拾取颜色(0/1)
|
|
17
|
+
attribute vec4 aUvRect; // xy: UV偏移, zw: UV缩放(非图集模式为(0,0,1,1))
|
|
18
|
+
|
|
19
|
+
varying float vShow;
|
|
20
|
+
varying vec2 vUv;
|
|
21
|
+
varying vec4 vColorAndOpacity;
|
|
22
|
+
varying vec4 vPickColorAndEnabled;
|
|
23
|
+
|
|
24
|
+
void main() {
|
|
25
|
+
// --- 1. 提前解包属性(GPU会自动优化,无性能损失)---
|
|
26
|
+
float show = aPositionAndShow.w;
|
|
27
|
+
float scale = aScaleAndRotationAndSizeAttenuation.x;
|
|
28
|
+
float rotation = aScaleAndRotationAndSizeAttenuation.y;
|
|
29
|
+
float sizeAttenuation = aScaleAndRotationAndSizeAttenuation.z;
|
|
30
|
+
vec2 center = aCenterAndSize.xy;
|
|
31
|
+
vec2 imageSize = aCenterAndSize.zw;
|
|
32
|
+
|
|
33
|
+
// --- 2. 输出varying变量 ---
|
|
34
|
+
vShow = show;
|
|
35
|
+
vUv = aUvRect.xy + uv * aUvRect.zw;
|
|
36
|
+
vColorAndOpacity = aColorAndOpacity;
|
|
37
|
+
vPickColorAndEnabled = aPickColorAndEnabled;
|
|
38
|
+
|
|
39
|
+
// --- 3. 提前隐藏不可见物体(顶点级丢弃,性能最优)---
|
|
40
|
+
if (show < 0.5 || imageSize.x <= 0.0 || imageSize.y <= 0.0) {
|
|
41
|
+
vShow = 0.0;
|
|
42
|
+
gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --- 4. 计算模型视图位置 ---
|
|
47
|
+
vec4 mvPosition = modelViewMatrix * vec4(aPositionAndShow.xyz, 1.0);
|
|
48
|
+
|
|
49
|
+
// --- 5. 深度检查:隐藏相机后面的物体,避免尺寸翻转 ---
|
|
50
|
+
float depth = -mvPosition.z;
|
|
51
|
+
if (depth <= 0.0) {
|
|
52
|
+
vShow = 0.0;
|
|
53
|
+
gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// --- 6. sizeAttenuation检查:大小跟随深度
|
|
58
|
+
if (sizeAttenuation < 0.5) {
|
|
59
|
+
bool isPerspective = isPerspectiveMatrix( projectionMatrix );
|
|
60
|
+
if ( isPerspective ) scale *= depth;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// --- 7. 坐标对齐
|
|
64
|
+
float aspectRatio = imageSize.x / imageSize.y;
|
|
65
|
+
vec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * aspectRatio * scale;
|
|
66
|
+
|
|
67
|
+
// --- 8. 计算旋转
|
|
68
|
+
vec2 rotatedPosition;
|
|
69
|
+
rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;
|
|
70
|
+
rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;
|
|
71
|
+
mvPosition.xy += rotatedPosition;
|
|
72
|
+
|
|
73
|
+
// --- 9. 投影变换 ---
|
|
74
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
75
|
+
|
|
76
|
+
#include <logdepthbuf_vertex>
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
const fragmentShader = `
|
|
81
|
+
uniform sampler2D uTexture;
|
|
82
|
+
|
|
83
|
+
#include <common>
|
|
84
|
+
#include <logdepthbuf_pars_fragment>
|
|
85
|
+
|
|
86
|
+
varying float vShow;
|
|
87
|
+
varying vec2 vUv;
|
|
88
|
+
varying vec4 vColorAndOpacity;
|
|
89
|
+
varying vec4 vPickColorAndEnabled;
|
|
90
|
+
|
|
91
|
+
void main() {
|
|
92
|
+
if (vShow < 0.5) {
|
|
93
|
+
discard;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
float enablePickColor = vPickColorAndEnabled.w;
|
|
97
|
+
|
|
98
|
+
if (enablePickColor > 0.5){
|
|
99
|
+
gl_FragColor = vec4(vPickColorAndEnabled.xyz, 1.0);
|
|
100
|
+
} else {
|
|
101
|
+
vec4 texColor = texture2D(uTexture, vUv);
|
|
102
|
+
vec4 diffuseColor = texColor * vColorAndOpacity;
|
|
103
|
+
|
|
104
|
+
// 丢弃完全透明的像素
|
|
105
|
+
if (diffuseColor.a < 0.005) {
|
|
106
|
+
discard;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
gl_FragColor = diffuseColor;
|
|
110
|
+
#include <logdepthbuf_fragment>
|
|
111
|
+
#include <tonemapping_fragment>
|
|
112
|
+
#include <colorspace_fragment>
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* InstancedSprite 材质类,以attribute形式传入InstancedSprite对象实例属性
|
|
119
|
+
*
|
|
120
|
+
* @extends ShaderMaterial
|
|
121
|
+
*/
|
|
122
|
+
class InstancedSpriteMaterial extends ShaderMaterial {
|
|
123
|
+
constructor() {
|
|
124
|
+
super({
|
|
125
|
+
vertexShader,
|
|
126
|
+
fragmentShader,
|
|
127
|
+
uniforms: {
|
|
128
|
+
uTexture: { value: null },
|
|
129
|
+
},
|
|
130
|
+
transparent: true,
|
|
131
|
+
depthTest: true,
|
|
132
|
+
depthWrite: true,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 图片纹理
|
|
137
|
+
* @type {Texture}
|
|
138
|
+
*/
|
|
139
|
+
set texture(value) {
|
|
140
|
+
this.uniforms.uTexture.value = value;
|
|
141
|
+
}
|
|
142
|
+
get texture() {
|
|
143
|
+
return this.uniforms.uTexture.value;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export default InstancedSpriteMaterial;
|
|
148
|
+
export { InstancedSpriteMaterial };
|
|
149
|
+
/**
|
|
150
|
+
* InstancedSpriteMaterial 的别名。
|
|
151
|
+
* @class
|
|
152
|
+
* @name BillboardMaterial
|
|
153
|
+
*/
|
|
154
|
+
export const BillboardMaterial = InstancedSpriteMaterial;
|