@ludicon/spark.js 0.0.7 → 0.0.9
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/README.md +5 -4
- package/package.json +3 -2
- package/src/three-gltf.js +213 -0
package/README.md
CHANGED
|
@@ -92,17 +92,17 @@ Load an image and encode it to a compressed GPU texture.
|
|
|
92
92
|
|
|
93
93
|
- A channel mask indicating the number of channels in your input: `"rgba"`, `"rgb"`, `"rg"` or `"r"`, the actual format is selected based on the device capabilities.
|
|
94
94
|
|
|
95
|
-
- An explicit WebGPU BC, ETC or ASTC format name, or an abbreviated form such as `"bc7"` or `"astc"`. Note
|
|
95
|
+
- An explicit WebGPU BC, ETC or ASTC format name, or an abbreviated form such as `"bc7"` or `"astc"`. Note: only 4x4 LDR formats are supported.
|
|
96
96
|
|
|
97
97
|
- If you specify `auto`, the input texture is analyzed to detect the necessary number of channels. This has some overhead, it's always recommended to specify the format through one of the other methods.
|
|
98
98
|
|
|
99
99
|
Default: `rgb`.
|
|
100
100
|
|
|
101
101
|
- **`alpha`**
|
|
102
|
-
Hint for the format selector. When
|
|
102
|
+
Hint for the automatic format selector. When no explicit format is provided, the format is assumed to be `"rgb"`. Supplying `alpha: true` will default to "rgba" instead.
|
|
103
103
|
|
|
104
104
|
- **`mips`** or **`generateMipmaps`** (`boolean`)
|
|
105
|
-
Whether to generate mipmaps.
|
|
105
|
+
Whether to generate mipmaps. Mipmaps are generated with a basic box filter in linear space. Default: `false`.
|
|
106
106
|
|
|
107
107
|
- **`srgb`** (`boolean`)
|
|
108
108
|
Whether to encode the image using an as sRGB format. This also affects mipmap generation. The `srgb` mode can also be inferred from the `format`. Default: `false`.
|
|
@@ -115,7 +115,8 @@ Load an image and encode it to a compressed GPU texture.
|
|
|
115
115
|
|
|
116
116
|
#### Returns
|
|
117
117
|
|
|
118
|
-
- `Promise<GPUTexture>`
|
|
118
|
+
- `Promise<GPUTexture>`
|
|
119
|
+
A promise resolving to the encoded WebGPU texture.
|
|
119
120
|
|
|
120
121
|
|
|
121
122
|
## Integration with three.js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ludicon/spark.js",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Real-Time GPU Texture Codecs for the Web",
|
|
5
5
|
"main": "dist/spark.esm.js",
|
|
6
6
|
"module": "dist/spark.esm.js",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
|
+
"src/three-gltf.js",
|
|
16
17
|
"README.md",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
],
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
"vite": "^7.0.0"
|
|
67
68
|
},
|
|
68
69
|
"peerDependencies": {
|
|
69
|
-
"three": "
|
|
70
|
+
"three": ">=0.180.0"
|
|
70
71
|
},
|
|
71
72
|
"peerDependenciesMeta": {
|
|
72
73
|
"three": {
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import * as THREE from "three/webgpu"
|
|
2
|
+
|
|
3
|
+
const Channel = {
|
|
4
|
+
R: 1, // 0001
|
|
5
|
+
G: 2, // 0010
|
|
6
|
+
B: 4, // 0100
|
|
7
|
+
A: 8, // 1000
|
|
8
|
+
RG: 3, // 0011
|
|
9
|
+
RGB: 7, // 0111
|
|
10
|
+
RGBA: 15 // 1111
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class GLTFSparkPlugin {
|
|
14
|
+
constructor(name, parser, spark) {
|
|
15
|
+
this.name = name
|
|
16
|
+
this.parser = parser
|
|
17
|
+
|
|
18
|
+
this.loaders = {
|
|
19
|
+
["rgba"]: new SparkLoader(parser.fileLoader.manager, spark, "rgba"),
|
|
20
|
+
["rgba-srgb"]: new SparkLoader(parser.fileLoader.manager, spark, "rgba", THREE.SRGBColorSpace),
|
|
21
|
+
["rgb"]: new SparkLoader(parser.fileLoader.manager, spark, "rgb"),
|
|
22
|
+
["rgb-srgb"]: new SparkLoader(parser.fileLoader.manager, spark, "rgb", THREE.SRGBColorSpace),
|
|
23
|
+
["rg"]: new SparkLoader(parser.fileLoader.manager, spark, "rg"),
|
|
24
|
+
["r"]: new SparkLoader(parser.fileLoader.manager, spark, "r"),
|
|
25
|
+
[""]: new THREE.TextureLoader()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const textureCount = this.parser.json.textures?.length || 0
|
|
29
|
+
const textureColorSpaces = new Array(textureCount).fill(THREE.NoColorSpace)
|
|
30
|
+
const textureChannels = new Array(textureCount).fill(0)
|
|
31
|
+
const textureIsNormal = new Array(textureCount).fill(false)
|
|
32
|
+
const textureIsUncompressed = new Array(textureCount).fill(false)
|
|
33
|
+
|
|
34
|
+
function assignTexture(index, channels, colorSpace, isNormal, isUncompressed) {
|
|
35
|
+
if (index === undefined) return
|
|
36
|
+
|
|
37
|
+
textureChannels[index] |= channels
|
|
38
|
+
|
|
39
|
+
if (colorSpace) {
|
|
40
|
+
textureColorSpaces[index] = colorSpace
|
|
41
|
+
}
|
|
42
|
+
if (isNormal) {
|
|
43
|
+
textureIsNormal[index] = true
|
|
44
|
+
|
|
45
|
+
// Normal map unpacking not supported in three.js prior to r181
|
|
46
|
+
if (!("NormalRGPacking" in THREE)) {
|
|
47
|
+
textureChannels[index] |= Channel.RGB
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (isUncompressed) {
|
|
51
|
+
textureIsUncompressed[index] = true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const materialDef of this.parser.json.materials) {
|
|
56
|
+
const baseColorTextureIndex = materialDef.pbrMetallicRoughness?.baseColorTexture?.index
|
|
57
|
+
if (baseColorTextureIndex !== undefined) {
|
|
58
|
+
textureColorSpaces[baseColorTextureIndex] = THREE.SRGBColorSpace
|
|
59
|
+
textureChannels[baseColorTextureIndex] |= Channel.RGB
|
|
60
|
+
|
|
61
|
+
// Base color texture expects alpha when alpha mode is MASK or BLEND.
|
|
62
|
+
if (materialDef.alphaMode == "MASK" || materialDef.alphaMode == "BLEND") {
|
|
63
|
+
textureChannels[baseColorTextureIndex] |= Channel.A
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
assignTexture(materialDef.normalTexture?.index, Channel.RG, THREE.NoColorSpace, true)
|
|
68
|
+
assignTexture(materialDef.emissiveTexture?.index, Channel.RGB, THREE.SRGBColorSpace)
|
|
69
|
+
assignTexture(materialDef.occlusionTexture?.index, Channel.R)
|
|
70
|
+
assignTexture(materialDef.pbrMetallicRoughness?.metallicRoughnessTexture?.index, Channel.G | Channel.B)
|
|
71
|
+
|
|
72
|
+
// KHR_materials_anisotropy - RG contains direction, B contains strength.
|
|
73
|
+
const anisotropyDef = materialDef.extensions?.KHR_materials_anisotropy
|
|
74
|
+
if (anisotropyDef) {
|
|
75
|
+
assignTexture(anisotropyDef.anisotropyTexture?.index, Channel.RGB)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// KHR_materials_clearcoat
|
|
79
|
+
const clearcoatDef = materialDef.extensions?.KHR_materials_clearcoat
|
|
80
|
+
if (clearcoatDef) {
|
|
81
|
+
assignTexture(clearcoatDef.clearcoatTexture?.index, Channel.RGB, THREE.SRGBColorSpace)
|
|
82
|
+
assignTexture(clearcoatDef.clearcoatRoughnessTexture?.index, Channel.R)
|
|
83
|
+
assignTexture(clearcoatDef.clearcoatNormalTexture?.index, Channel.RG, THREE.NoColorSpace, true)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// KHR_materials_diffuse_transmission
|
|
87
|
+
const diffuseTransmissionDef = materialDef.extensions?.KHR_materials_diffuse_transmission
|
|
88
|
+
if (diffuseTransmissionDef) {
|
|
89
|
+
assignTexture(diffuseTransmissionDef.diffuseTransmissionTexture?.index, Channel.A)
|
|
90
|
+
assignTexture(diffuseTransmissionDef.diffuseTransmissionColorTexture?.index, Channel.RGB, THREE.SRGBColorSpace)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// KHR_materials_iridescence
|
|
94
|
+
const iridescenceDef = materialDef.extensions?.KHR_materials_iridescence
|
|
95
|
+
if (iridescenceDef) {
|
|
96
|
+
assignTexture(iridescenceDef.iridescenceTexture?.index, Channel.R)
|
|
97
|
+
assignTexture(iridescenceDef.iridescenceThicknessTexture?.index, Channel.G)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// KHR_materials_sheen
|
|
101
|
+
const sheenDef = materialDef.extensions?.KHR_materials_sheen
|
|
102
|
+
if (sheenDef) {
|
|
103
|
+
assignTexture(sheenDef.sheenColorTexture?.index, Channel.RGB, THREE.SRGBColorSpace)
|
|
104
|
+
assignTexture(sheenDef.sheenRoughnessTextureIndex?.index, Channel.A)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// KHR_materials_specular
|
|
108
|
+
const specularDef = materialDef.extensions?.KHR_materials_specular
|
|
109
|
+
if (specularDef) {
|
|
110
|
+
assignTexture(specularDef.specularTexture?.index, Channel.RGB, THREE.SRGBColorSpace)
|
|
111
|
+
assignTexture(specularDef.specularColorTexture?.index, Channel.A)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// KHR_materials_transmission
|
|
115
|
+
const transmissionDef = materialDef.extensions?.KHR_materials_transmission
|
|
116
|
+
if (transmissionDef) {
|
|
117
|
+
assignTexture(transmissionDef.transmissionTexture?.index, Channel.R)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// KHR_materials_volume
|
|
121
|
+
const volumeDef = materialDef.extensions?.KHR_materials_volume
|
|
122
|
+
if (volumeDef) {
|
|
123
|
+
assignTexture(volumeDef.thicknessTexture?.index, Channel.G)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.textureColorSpaces = textureColorSpaces
|
|
128
|
+
this.textureChannels = textureChannels
|
|
129
|
+
this.textureIsNormal = textureIsNormal
|
|
130
|
+
this.textureIsUncompressed = textureIsUncompressed
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
loadTexture(textureIndex) {
|
|
134
|
+
const tex = this.parser.json.textures[textureIndex]
|
|
135
|
+
const imageIndex = tex.source ?? tex.extensions.EXT_texture_webp?.source ?? tex.extensions.EXT_texture_avif?.source
|
|
136
|
+
const colorSpace = this.textureColorSpaces[textureIndex]
|
|
137
|
+
const channels = this.textureChannels[textureIndex]
|
|
138
|
+
const isUncompressed = this.textureIsUncompressed[textureIndex]
|
|
139
|
+
|
|
140
|
+
let format = "rgba" // Default to 'rgba'
|
|
141
|
+
if ((channels & Channel.R) == channels) {
|
|
142
|
+
format = "r"
|
|
143
|
+
} else if ((channels & Channel.RG) == channels) {
|
|
144
|
+
format = "rg"
|
|
145
|
+
} else if ((channels & Channel.RGB) == channels) {
|
|
146
|
+
format = "rgb" + (colorSpace === THREE.SRGBColorSpace ? "-srgb" : "")
|
|
147
|
+
} else {
|
|
148
|
+
format = "rgba" + (colorSpace === THREE.SRGBColorSpace ? "-srgb" : "")
|
|
149
|
+
}
|
|
150
|
+
if (isUncompressed) {
|
|
151
|
+
format = ""
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const loader = this.loaders[format]
|
|
155
|
+
|
|
156
|
+
return this.parser.loadTextureImage(textureIndex, imageIndex, loader)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
class SparkLoader extends THREE.TextureLoader {
|
|
161
|
+
constructor(manager, spark, format, colorSpace = THREE.NoColorSpace) {
|
|
162
|
+
super(manager)
|
|
163
|
+
this.spark = spark
|
|
164
|
+
this.format = format
|
|
165
|
+
this.colorSpace = colorSpace
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
load(url, onLoad, onProgress, onError) {
|
|
169
|
+
const format = this.format
|
|
170
|
+
const srgb = this.colorSpace === THREE.SRGBColorSpace
|
|
171
|
+
const mips = true
|
|
172
|
+
|
|
173
|
+
this.spark
|
|
174
|
+
.encodeTexture(url, { format, srgb, mips })
|
|
175
|
+
.then(gpuTexture => {
|
|
176
|
+
const texture = new THREE.ExternalTexture(gpuTexture)
|
|
177
|
+
if (this.format == "rg" && "NormalRGPacking" in THREE) {
|
|
178
|
+
texture.userData.unpackNormal = THREE.NormalRGPacking
|
|
179
|
+
}
|
|
180
|
+
onLoad(texture)
|
|
181
|
+
})
|
|
182
|
+
.catch(err => {
|
|
183
|
+
// Fallback: load the original image uncompressed
|
|
184
|
+
super.load(
|
|
185
|
+
url,
|
|
186
|
+
tex => {
|
|
187
|
+
tex.colorSpace = this.colorSpace
|
|
188
|
+
onLoad?.(tex)
|
|
189
|
+
},
|
|
190
|
+
onProgress,
|
|
191
|
+
// If the fallback also fails, surface the original encoder error first
|
|
192
|
+
fallbackErr => onError?.(err ?? fallbackErr)
|
|
193
|
+
)
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function registerSparkLoader(loader, spark) {
|
|
199
|
+
// Remove existing webp and avif plugins:
|
|
200
|
+
for (let i = 0; i < loader.pluginCallbacks.length; i++) {
|
|
201
|
+
const plugin = loader.pluginCallbacks[i](loader)
|
|
202
|
+
|
|
203
|
+
if (plugin.name == "EXT_texture_webp" || plugin.name == "EXT_texture_avif") {
|
|
204
|
+
loader.unregister(loader.pluginCallbacks[i])
|
|
205
|
+
i--
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Install plugin for standard textures, and textures using webp and avif extensions.
|
|
210
|
+
loader.register(parser => new GLTFSparkPlugin("spark", parser, spark))
|
|
211
|
+
loader.register(parser => new GLTFSparkPlugin("EXT_texture_webp", parser, spark))
|
|
212
|
+
loader.register(parser => new GLTFSparkPlugin("EXT_texture_avif", parser, spark))
|
|
213
|
+
}
|