@lijuhong1981/three.instancedsprite 1.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.
@@ -0,0 +1,345 @@
1
+ import Check from "@lijuhong1981/jscheck/src/Check.js";
2
+ import ImageLoader from "@lijuhong1981/jsload/src/ImageLoader.js";
3
+ import { Loader } from "@lijuhong1981/jsload/src/Loader.js";
4
+ import setValues from "@lijuhong1981/three.utils/src/setValues.js";
5
+ import { Color, InstancedBufferGeometry, MathUtils, Matrix4, Triangle, Vector2, Vector3 } from "three";
6
+
7
+ /**
8
+ * @import InstancedSpriteCollection from "./InstancedSpriteCollection.js";
9
+ * @import InstancedSpriteMesh from "./InstancedSpriteMesh.js";
10
+ * @import InstancedSpriteMaterial from "./InstancedSpriteMaterial.js";
11
+ */
12
+
13
+ const imageLoader = new ImageLoader({ cacheType: Loader.CacheType.ALONE, });
14
+
15
+ const _intersectPoint = /*@__PURE__*/ new Vector3();
16
+ const _worldScale = /*@__PURE__*/ new Vector3();
17
+ const _mvPosition = /*@__PURE__*/ new Vector3();
18
+
19
+ const _alignedPosition = /*@__PURE__*/ new Vector2();
20
+ const _rotatedPosition = /*@__PURE__*/ new Vector2();
21
+ const _viewWorldMatrix = /*@__PURE__*/ new Matrix4();
22
+
23
+ const _vA = /*@__PURE__*/ new Vector3();
24
+ const _vB = /*@__PURE__*/ new Vector3();
25
+ const _vC = /*@__PURE__*/ new Vector3();
26
+
27
+ const _uvA = /*@__PURE__*/ new Vector2();
28
+ const _uvB = /*@__PURE__*/ new Vector2();
29
+ const _uvC = /*@__PURE__*/ new Vector2();
30
+
31
+ function transformVertex(vertexPosition, mvPosition, center, scale, sin, cos) {
32
+
33
+ // compute position in camera space
34
+ _alignedPosition.subVectors(vertexPosition, center).addScalar(0.5).multiply(scale);
35
+
36
+ // to check if rotation is not zero
37
+ if (sin !== undefined) {
38
+
39
+ _rotatedPosition.x = (cos * _alignedPosition.x) - (sin * _alignedPosition.y);
40
+ _rotatedPosition.y = (sin * _alignedPosition.x) + (cos * _alignedPosition.y);
41
+
42
+ } else {
43
+
44
+ _rotatedPosition.copy(_alignedPosition);
45
+
46
+ }
47
+
48
+
49
+ vertexPosition.copy(mvPosition);
50
+ vertexPosition.x += _rotatedPosition.x;
51
+ vertexPosition.y += _rotatedPosition.y;
52
+
53
+ // transform to world space
54
+ vertexPosition.applyMatrix4(_viewWorldMatrix);
55
+
56
+ }
57
+
58
+ /**
59
+ * InstancedSprite 对象配置项选项
60
+ * @typedef {object} InstancedSpriteOptions
61
+ * @property {boolean} [show=true] 是否显示
62
+ * @property {Vector3|Array<number>} [position=(0,0,0)] 位置(世界空间)
63
+ * @property {number} [scale=1] 缩放
64
+ * @property {number} [rotation=0] 旋转(弧度)
65
+ * @property {boolean} [sizeAttenuation=true] 尺寸是否跟随相机深度变化
66
+ * @property {Vector2|Array<number>} [center=(0.5,0.5)] 锚点中心(0-1)
67
+ * @property {Color|number|string} [color=0xffffff] 颜色
68
+ * @property {number} [opacity=1] 不透明度(0-1)
69
+ * @property {string|HTMLImageElement|HTMLCanvasElement} [image] 图片资源
70
+ */
71
+
72
+ /**
73
+ * InstancedSprite 数据模型类,用于存储单个 InstancedSprite 的所有属性
74
+ *
75
+ * * InstancedSprite跟Sprite不同,它只是一个数据集合对象,并不继承自Object3D,不能直接加入到Scene中
76
+ * * 但它可以作为属性Module使用,修改InstancedSprite的属性会自动更新至GPU中
77
+ */
78
+ class InstancedSprite {
79
+ /**
80
+ * @param {InstancedSpriteOptions} options - 初始化配置项
81
+ * @param {InstancedSpriteCollection} collection - 所属InstancedSpriteCollection实例,必填
82
+ * @constructor
83
+ */
84
+ constructor(options = {}, collection) {
85
+ Check.defined('collection', collection);
86
+ /**
87
+ * 所属集合,内部设置
88
+ * @type {InstancedSpriteCollection}
89
+ * @ignore
90
+ */
91
+ this._collection = collection;
92
+ /**
93
+ * 所属Mesh,内部设置
94
+ * @type {InstancedSpriteMesh}
95
+ * @ignore
96
+ */
97
+ this._mesh = undefined;
98
+ /**
99
+ * 在 InstancedSpriteMesh 中的实例索引,由 InstancedSpriteMesh 内部设置
100
+ * @type {number}
101
+ * @ignore
102
+ */
103
+ this._instanceIndex = -1;
104
+ /**
105
+ * 对象类型标识
106
+ * @type {string}
107
+ * @readonly
108
+ */
109
+ this.type = "InstancedSprite";
110
+ /**
111
+ * 当前InstancedSprite的唯一标识id
112
+ * @type {string}
113
+ * @readonly
114
+ */
115
+ this.uuid = MathUtils.generateUUID();
116
+ /**
117
+ * 是否显示
118
+ * @type {boolean}
119
+ */
120
+ this.show = true;
121
+ this._show = true;
122
+ /**
123
+ * 世界坐标位置
124
+ * @type {Vector3}
125
+ */
126
+ this.position = new Vector3(0, 0, 0);
127
+ this._position = new Vector3(0, 0, 0);
128
+ /**
129
+ * 缩放
130
+ * @type {number}
131
+ */
132
+ this.scale = 1;
133
+ this._scale = 1;
134
+ /**
135
+ * 旋转(弧度)
136
+ * @type {number}
137
+ */
138
+ this.rotation = 0;
139
+ this._rotation = 0;
140
+ /**
141
+ * 尺寸是否跟随相机深度变化
142
+ * @type {boolean}
143
+ */
144
+ this.sizeAttenuation = true;
145
+ this._sizeAttenuation = true;
146
+ /**
147
+ * 锚点中心(0-1)
148
+ * @type {Vector2}
149
+ */
150
+ this.center = new Vector2(0.5, 0.5);
151
+ this._center = new Vector2(0.5, 0.5);
152
+ /**
153
+ * 颜色
154
+ * @type {Color}
155
+ */
156
+ this.color = new Color(0xffffff);
157
+ this._color = new Color(0xffffff);
158
+ /**
159
+ * 不透明度(0-1)
160
+ * @type {number}
161
+ */
162
+ this.opacity = 1;
163
+ this._opacity = 1;
164
+ /**
165
+ * 图片尺寸(像素),图片加载完成后可用
166
+ * @type {Vector2}
167
+ * @readonly
168
+ */
169
+ this.imageSize = new Vector2(0, 0);
170
+ this._imageSize = new Vector2(0, 0);
171
+ this._image = null;
172
+
173
+ this.setValues(options);
174
+ }
175
+ /**
176
+ * InstancedSprite对象标识
177
+ * @type {boolean}
178
+ * @readonly
179
+ */
180
+ get isInstancedSprite() { return true; }
181
+ /**
182
+ * 设置参数
183
+ * @param {InstancedSpriteOptions} options
184
+ * @returns {InstancedSprite}
185
+ */
186
+ setValues(options) {
187
+ setValues(this, options);
188
+ return this;
189
+ }
190
+ /**
191
+ * 是否可见,等同于show
192
+ * @type {boolean}
193
+ */
194
+ get visible() {
195
+ return this.show;
196
+ }
197
+ set visible(value) { this.show = value; }
198
+ /**
199
+ * 旋转(角度)
200
+ * @type {number}
201
+ */
202
+ get rotationDegrees() { return MathUtils.radToDeg(this.rotation); }
203
+ set rotationDegrees(value) { this.rotation = MathUtils.degToRad(value); }
204
+ /**
205
+ * 图片资源,可以是URL字符串、HTMLImageElement或HTMLCanvasElement
206
+ * @type {string|HTMLImageElement|HTMLCanvasElement}
207
+ */
208
+ get image() { return this._image; }
209
+ set image(value) {
210
+ if (this._image) {
211
+ if (typeof value === 'string') {
212
+ if (this._image.src === value)
213
+ return;
214
+ } else if (value instanceof HTMLImageElement || value instanceof HTMLCanvasElement) {
215
+ if (this._image === value)
216
+ return;
217
+ }
218
+ }
219
+ this._collection._setImage(value, this);
220
+ }
221
+ /**
222
+ * 图片ID,图片加载完成后可用,通常为图片URL或生成的UUID
223
+ * @type {string}
224
+ * @readonly
225
+ */
226
+ get imageId() { return this._image && this._image.src; }
227
+ /**
228
+ * 图片像素宽度,图片加载完成后可用
229
+ * @type {number}
230
+ * @readonly
231
+ */
232
+ get imageWidth() { return this.imageSize.x; }
233
+ /**
234
+ * 图片像素高度,图片加载完成后可用
235
+ * @type {number}
236
+ * @readonly
237
+ */
238
+ get imageHeight() { return this.imageSize.y; }
239
+ _remove() {
240
+ this._mesh && this._mesh.remove(this);
241
+ return this;
242
+ }
243
+ /**
244
+ * InstancedSprite对象的几何体属性
245
+ * @type {InstancedBufferGeometry}
246
+ * @readonly
247
+ */
248
+ get geometry() {
249
+ return this._mesh ? this._mesh.geometry : undefined;
250
+ }
251
+ /**
252
+ * InstancedSprite对象的材质属性
253
+ * @type {InstancedSpriteMaterial}
254
+ * @readonly
255
+ */
256
+ get material() {
257
+ return this._mesh ? this._mesh.material : undefined;
258
+ }
259
+ /**
260
+ * 从Mesh中移除该InstancedSprite对象
261
+ * @return {InstancedSprite}
262
+ */
263
+ remove() {
264
+ this._collection && this._collection.remove(this);
265
+ return this;
266
+ }
267
+ /**
268
+ * Computes intersection points between a casted ray and this sprite.
269
+ *
270
+ * @param {Raycaster} raycaster - The raycaster.
271
+ * @param {Array<Object>} intersects - The target array that holds the intersection points.
272
+ * @param {Matrix4} modelViewMatrix
273
+ */
274
+ raycast(raycaster, intersects, modelViewMatrix) {
275
+
276
+ if (this.show === false || this.imageSize.x <= 0 || this.imageSize.y <= 0) {
277
+ return;
278
+ }
279
+
280
+ _viewWorldMatrix.copy(raycaster.camera.matrixWorld);
281
+ _mvPosition.copy(this.position).applyMatrix4(modelViewMatrix);
282
+
283
+ const depth = -_mvPosition.z;
284
+ if (depth <= 0) {
285
+ return;
286
+ }
287
+
288
+ let scale = this.scale;
289
+ if (raycaster.camera.isPerspectiveCamera && this.sizeAttenuation === false) {
290
+ scale *= depth;
291
+ }
292
+ const aspectRatio = this.imageSize.x / this.imageSize.y;
293
+ _worldScale.set(1, 1, 1).multiplyScalar(scale * aspectRatio);
294
+
295
+ const rotation = this.rotation;
296
+ let sin, cos;
297
+
298
+ if (rotation !== 0) {
299
+ cos = Math.cos(rotation);
300
+ sin = Math.sin(rotation);
301
+ }
302
+
303
+ const center = this.center;
304
+
305
+ transformVertex(_vA.set(-0.5, -0.5, 0), _mvPosition, center, _worldScale, sin, cos);
306
+ transformVertex(_vB.set(0.5, -0.5, 0), _mvPosition, center, _worldScale, sin, cos);
307
+ transformVertex(_vC.set(0.5, 0.5, 0), _mvPosition, center, _worldScale, sin, cos);
308
+
309
+ _uvA.set(0, 0);
310
+ _uvB.set(1, 0);
311
+ _uvC.set(1, 1);
312
+
313
+ // check first triangle
314
+ let intersect = raycaster.ray.intersectTriangle(_vA, _vB, _vC, false, _intersectPoint);
315
+
316
+ if (intersect === null) {
317
+
318
+ // check second triangle
319
+ transformVertex(_vB.set(-0.5, 0.5, 0), _mvPosition, center, _worldScale, sin, cos);
320
+ _uvB.set(0, 1);
321
+
322
+ intersect = raycaster.ray.intersectTriangle(_vA, _vC, _vB, false, _intersectPoint);
323
+ if (intersect === null) {
324
+ return;
325
+ }
326
+
327
+ }
328
+
329
+ const distance = raycaster.ray.origin.distanceTo(_intersectPoint);
330
+
331
+ if (distance < raycaster.near || distance > raycaster.far) return;
332
+
333
+ intersects.push({
334
+ distance: distance,
335
+ point: _intersectPoint.clone(),
336
+ uv: Triangle.getInterpolation(_intersectPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2()),
337
+ face: null,
338
+ object: this,
339
+ instanceId: this._instanceIndex,
340
+ });
341
+ }
342
+ };
343
+
344
+ export default InstancedSprite;
345
+ export { InstancedSprite };
@@ -0,0 +1,226 @@
1
+ import Check from "@lijuhong1981/jscheck/src/Check.js";
2
+ import {
3
+ MathUtils,
4
+ Object3D,
5
+ Texture
6
+ } from "three";
7
+ import InstancedSprite from "./InstancedSprite.js";
8
+ import InstancedSpriteMesh from "./InstancedSpriteMesh.js";
9
+
10
+ /**
11
+ * @import InstancedSpriteOptions from "./InstancedSprite.js";
12
+ */
13
+
14
+ /**
15
+ * @type {Map<string, HTMLImageElement|HTMLCanvasElement>}
16
+ * @ignore
17
+ */
18
+ const imageCache = new Map();
19
+
20
+ /**
21
+ * InstancedSpriteCollection类,批量管理InstancedSprite实例
22
+ *
23
+ * * 继承自Object3D
24
+ * * 根据InstancedSprite的图像属性生成InstancedSpriteMaterial和InstancedSpriteMesh,
25
+ * * 管理InstancedSprite与InstancedSpriteMesh实例
26
+ * @extends Object3D
27
+ */
28
+ class InstancedSpriteCollection extends Object3D {
29
+ /**
30
+ * @param {boolean} [useNodeMaterial=false] - 是否使用TSL的NodeMaterial,默认false
31
+ * @constructor
32
+ */
33
+ constructor(useNodeMaterial = false) {
34
+ super();
35
+ /**
36
+ * 是否使用TSL的NodeMaterial,默认false
37
+ * @type {boolean}
38
+ * @readonly
39
+ * @default false
40
+ */
41
+ this.useNodeMaterial = useNodeMaterial;
42
+ /**
43
+ * 对象类型标识
44
+ * @type {string}
45
+ * @readonly
46
+ */
47
+ this.type = "InstancedSpriteCollection";
48
+ // this.frustumCulled = false;
49
+ /**
50
+ * @type {Array<InstancedSprite>}
51
+ * @ignore
52
+ */
53
+ this._instancedSprites = [];
54
+ /**
55
+ * @type {Map<HTMLImageElement|HTMLCanvasElement, InstancedSpriteMesh>}
56
+ * @ignore
57
+ */
58
+ this._meshes = new Map();
59
+ this._depthTest = true;
60
+ }
61
+ /**
62
+ * InstancedSpriteCollection对象标识
63
+ * @type {boolean}
64
+ * @readonly
65
+ */
66
+ get isInstancedSpriteCollection() { return true; }
67
+ /**
68
+ * 深度测试开关,默认为true,开启后会进行深度测试以正确处理遮挡关系,但可能会有性能影响;如果关闭则所有InstancedSprite都会被渲染在最前面,适合需要始终显示的UI元素等场景
69
+ * @type {boolean}
70
+ * @default true
71
+ */
72
+ get depthTest() { return this._depthTest; }
73
+ set depthTest(value) {
74
+ Check.typeOf.boolean(value, 'depthTest');
75
+ this._depthTest = value;
76
+ const meshes = this._meshes.values();
77
+ for (const mesh of meshes) {
78
+ mesh.depthTest = value;
79
+ }
80
+ }
81
+ /**
82
+ * @param {string|HTMLImageElement|HTMLCanvasElement} source
83
+ * @param {InstancedSprite} sprite
84
+ */
85
+ _setImage(source, sprite) {
86
+ if (source instanceof HTMLImageElement || source instanceof HTMLCanvasElement) {
87
+ if (!source.src)
88
+ source.src = MathUtils.generateUUID();
89
+ if (!imageCache.has(source.src))
90
+ imageCache.set(source.src, source);
91
+ let mesh = this._meshes.get(source);
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
+ }
120
+ } else if (typeof source === 'string') {
121
+ let image = imageCache.get(source);
122
+ if (!image) {
123
+ image = new Image();
124
+ image.src = source;
125
+ imageCache.set(source, image);
126
+ }
127
+ this._setImage(image, sprite);
128
+ } else {
129
+ throw new Error('Invalid image source type ' + source);
130
+ }
131
+ }
132
+ /**
133
+ * InstancedSprite实例数组
134
+ * @type {Array<InstancedSprite>}
135
+ * @readonly
136
+ */
137
+ get instancedSprites() {
138
+ return this._instancedSprites;
139
+ }
140
+ /**
141
+ * InstancedSprite实例数量
142
+ * @type {number}
143
+ * @readonly
144
+ */
145
+ get size() {
146
+ return this._instancedSprites.length;
147
+ }
148
+ /**
149
+ * 根据索引获取InstancedSprite实例
150
+ * @param {number} index
151
+ * @returns {InstancedSprite|undefined}
152
+ */
153
+ get(index) {
154
+ return this._instancedSprites[index];
155
+ }
156
+ /**
157
+ * 根据uuid获取InstancedSprite实例
158
+ * @param {string} uuid
159
+ * @returns {InstancedSprite|undefined}
160
+ */
161
+ getByUuid(uuid) {
162
+ for (const sprite of this._instancedSprites) {
163
+ if (sprite.uuid === uuid)
164
+ return sprite;
165
+ }
166
+ }
167
+ /**
168
+ * 添加InstancedSprite
169
+ * @type {InstancedSpriteConstructorOptions} options
170
+ * @returns {InstancedSprite}
171
+ */
172
+ add(options = {}) {
173
+ const sprite = new InstancedSprite(options, this);
174
+ this._instancedSprites.push(sprite);
175
+ return sprite;
176
+ }
177
+ /**
178
+ * 移除InstancedSprite
179
+ * @param {InstancedSprite} sprite
180
+ * @returns {InstancedSpriteCollection}
181
+ */
182
+ remove(sprite) {
183
+ const index = this._instancedSprites.indexOf(sprite);
184
+ if (index !== -1) {
185
+ this._instancedSprites.splice(index, 1);
186
+ sprite._remove();
187
+ }
188
+ return this;
189
+ }
190
+ /**
191
+ * 遍历所有的InstancedSprite
192
+ * @param {Function} callback
193
+ * @returns {InstancedSpriteCollection}
194
+ */
195
+ forEach(callback) {
196
+ this._instancedSprites.forEach(callback);
197
+ return this;
198
+ }
199
+ /**
200
+ * Computes intersection points between a casted ray and this sprite.
201
+ *
202
+ * @param {Raycaster} raycaster - The raycaster.
203
+ * @param {Array<Object>} intersects - The target array that holds the intersection points.
204
+ */
205
+ raycast(raycaster, intersects) {
206
+ const meshes = this._meshes.values();
207
+ for (const mesh of meshes) {
208
+ mesh.raycast(raycaster, intersects);
209
+ }
210
+ }
211
+ /**
212
+ * 每帧更新
213
+ * @private
214
+ */
215
+ update() {
216
+ if (!this.visible) return;
217
+
218
+ const meshes = this._meshes.values();
219
+ for (const mesh of meshes) {
220
+ mesh.update();
221
+ }
222
+ }
223
+ };
224
+
225
+ export default InstancedSpriteCollection;
226
+ export { InstancedSpriteCollection };
@@ -0,0 +1,134 @@
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
+
17
+ varying float vShow;
18
+ varying vec2 vUv;
19
+ varying vec4 vColorAndOpacity;
20
+
21
+ void main() {
22
+ // --- 1. 提前解包属性(GPU会自动优化,无性能损失)---
23
+ float show = aPositionAndShow.w;
24
+ float scale = aScaleAndRotationAndSizeAttenuation.x;
25
+ float rotation = aScaleAndRotationAndSizeAttenuation.y;
26
+ float sizeAttenuation = aScaleAndRotationAndSizeAttenuation.z;
27
+ vec2 center = aCenterAndSize.xy;
28
+ vec2 imageSize = aCenterAndSize.zw;
29
+
30
+ // --- 2. 输出varying变量 ---
31
+ vShow = show;
32
+ vUv = uv;
33
+ vColorAndOpacity = aColorAndOpacity;
34
+
35
+ // --- 3. 提前隐藏不可见物体(顶点级丢弃,性能最优)---
36
+ if (show < 0.5 || imageSize.x <= 0.0 || imageSize.y <= 0.0) {
37
+ vShow = 0.0;
38
+ gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
39
+ return;
40
+ }
41
+
42
+ // --- 4. 计算模型视图位置 ---
43
+ vec4 mvPosition = modelViewMatrix * vec4(aPositionAndShow.xyz, 1.0);
44
+
45
+ // --- 5. 深度检查:隐藏相机后面的物体,避免尺寸翻转 ---
46
+ float depth = -mvPosition.z;
47
+ if (depth <= 0.0) {
48
+ vShow = 0.0;
49
+ gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
50
+ return;
51
+ }
52
+
53
+ // --- 6. sizeAttenuation检查:大小跟随深度
54
+ if (sizeAttenuation < 0.5) {
55
+ bool isPerspective = isPerspectiveMatrix( projectionMatrix );
56
+ if ( isPerspective ) scale *= depth;
57
+ }
58
+
59
+ // --- 7. 坐标对齐
60
+ float aspectRatio = imageSize.x / imageSize.y;
61
+ vec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * aspectRatio * scale;
62
+
63
+ // --- 8. 计算旋转
64
+ vec2 rotatedPosition;
65
+ rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;
66
+ rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;
67
+ mvPosition.xy += rotatedPosition;
68
+
69
+ // --- 9. 投影变换 ---
70
+ gl_Position = projectionMatrix * mvPosition;
71
+
72
+ #include <logdepthbuf_vertex>
73
+ }
74
+ `;
75
+
76
+ const fragmentShader = `
77
+ uniform sampler2D uTexture;
78
+
79
+ #include <common>
80
+ #include <logdepthbuf_pars_fragment>
81
+
82
+ varying float vShow;
83
+ varying vec2 vUv;
84
+ varying vec4 vColorAndOpacity;
85
+
86
+ void main() {
87
+ vec4 texColor = texture2D(uTexture, vUv);
88
+ vec4 diffuseColor = texColor * vColorAndOpacity;
89
+
90
+ // 丢弃完全透明的像素
91
+ if (diffuseColor.a < 0.005 || vShow < 0.5) {
92
+ discard;
93
+ }
94
+
95
+ gl_FragColor = diffuseColor;
96
+
97
+ #include <logdepthbuf_fragment>
98
+ #include <tonemapping_fragment>
99
+ #include <colorspace_fragment>
100
+ }
101
+ `;
102
+
103
+ /**
104
+ * InstancedSprite 材质类,以attribute形式传入InstancedSprite对象实例属性
105
+ *
106
+ * @extends ShaderMaterial
107
+ */
108
+ class InstancedSpriteMaterial extends ShaderMaterial {
109
+ constructor() {
110
+ super({
111
+ vertexShader,
112
+ fragmentShader,
113
+ uniforms: {
114
+ uTexture: { value: null },
115
+ },
116
+ transparent: true,
117
+ depthTest: true,
118
+ depthWrite: true,
119
+ });
120
+ }
121
+ /**
122
+ * 图片纹理
123
+ * @type {Texture}
124
+ */
125
+ set texture(value) {
126
+ this.uniforms.uTexture.value = value;
127
+ }
128
+ get texture() {
129
+ return this.uniforms.uTexture.value;
130
+ }
131
+ };
132
+
133
+ export default InstancedSpriteMaterial;
134
+ export { InstancedSpriteMaterial };