@ludicon/spark.js 0.0.7 → 0.0.8
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/package.json +2 -1
- package/src/three-gltf.js +189 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ludicon/spark.js",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
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
|
],
|
|
@@ -0,0 +1,189 @@
|
|
|
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
|
+
assignTexture(materialDef.anisotropyTexture?.index, Channel.RGB)
|
|
74
|
+
|
|
75
|
+
// KHR_materials_clearcoat
|
|
76
|
+
assignTexture(materialDef.clearcoatTexture?.index, Channel.RGB, THREE.SRGBColorSpace)
|
|
77
|
+
assignTexture(materialDef.clearcoatRoughnessTexture?.index, Channel.R)
|
|
78
|
+
assignTexture(materialDef.clearcoatNormalTexture?.index, Channel.RG, THREE.NoColorSpace, true)
|
|
79
|
+
|
|
80
|
+
// KHR_materials_diffuse_transmission
|
|
81
|
+
assignTexture(materialDef.diffuseTransmissionTexture?.index, Channel.A)
|
|
82
|
+
assignTexture(materialDef.diffuseTransmissionColorTexture?.index, Channel.RGB, THREE.SRGBColorSpace)
|
|
83
|
+
|
|
84
|
+
// KHR_materials_iridescence
|
|
85
|
+
assignTexture(materialDef.iridescenceTexture?.index, Channel.R)
|
|
86
|
+
assignTexture(materialDef.iridescenceThicknessTexture?.index, Channel.G)
|
|
87
|
+
|
|
88
|
+
// KHR_materials_sheen
|
|
89
|
+
assignTexture(materialDef.sheenColorTexture?.index, Channel.RGB, THREE.SRGBColorSpace)
|
|
90
|
+
assignTexture(materialDef.sheenRoughnessTextureIndex?.index, Channel.A)
|
|
91
|
+
|
|
92
|
+
// KHR_materials_specular
|
|
93
|
+
assignTexture(materialDef.specularTexture?.index, Channel.RGB, THREE.SRGBColorSpace)
|
|
94
|
+
assignTexture(materialDef.specularColorTexture?.index, Channel.A)
|
|
95
|
+
|
|
96
|
+
// KHR_materials_transmission
|
|
97
|
+
assignTexture(materialDef.transmissionTexture?.index, Channel.R)
|
|
98
|
+
|
|
99
|
+
// KHR_materials_volume
|
|
100
|
+
assignTexture(materialDef.thicknessTexture?.index, Channel.G)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.textureColorSpaces = textureColorSpaces
|
|
104
|
+
this.textureChannels = textureChannels
|
|
105
|
+
this.textureIsNormal = textureIsNormal
|
|
106
|
+
this.textureIsUncompressed = textureIsUncompressed
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
loadTexture(textureIndex) {
|
|
110
|
+
const tex = this.parser.json.textures[textureIndex]
|
|
111
|
+
const imageIndex = tex.source ?? tex.extensions.EXT_texture_webp?.source ?? tex.extensions.EXT_texture_avif?.source
|
|
112
|
+
const colorSpace = this.textureColorSpaces[textureIndex]
|
|
113
|
+
const channels = this.textureChannels[textureIndex]
|
|
114
|
+
const isUncompressed = this.textureIsUncompressed[textureIndex]
|
|
115
|
+
|
|
116
|
+
let format = "rgba" // Default to 'rgba'
|
|
117
|
+
if ((channels & Channel.R) == channels) {
|
|
118
|
+
format = "r"
|
|
119
|
+
} else if ((channels & Channel.RG) == channels) {
|
|
120
|
+
format = "rg"
|
|
121
|
+
} else if ((channels & Channel.RGB) == channels) {
|
|
122
|
+
format = "rgb" + (colorSpace === THREE.SRGBColorSpace ? "-srgb" : "")
|
|
123
|
+
} else {
|
|
124
|
+
format = "rgba" + (colorSpace === THREE.SRGBColorSpace ? "-srgb" : "")
|
|
125
|
+
}
|
|
126
|
+
if (isUncompressed) {
|
|
127
|
+
format = ""
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const loader = this.loaders[format]
|
|
131
|
+
|
|
132
|
+
return this.parser.loadTextureImage(textureIndex, imageIndex, loader)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
class SparkLoader extends THREE.TextureLoader {
|
|
137
|
+
constructor(manager, spark, format, colorSpace = THREE.NoColorSpace) {
|
|
138
|
+
super(manager)
|
|
139
|
+
this.spark = spark
|
|
140
|
+
this.format = format
|
|
141
|
+
this.colorSpace = colorSpace
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
load(url, onLoad, onProgress, onError) {
|
|
145
|
+
const format = this.format
|
|
146
|
+
const srgb = this.colorSpace === THREE.SRGBColorSpace
|
|
147
|
+
const mips = true
|
|
148
|
+
|
|
149
|
+
this.spark
|
|
150
|
+
.encodeTexture(url, { format, srgb, mips })
|
|
151
|
+
.then(gpuTexture => {
|
|
152
|
+
const texture = new THREE.ExternalTexture(gpuTexture)
|
|
153
|
+
if (this.format == "rg" && "NormalRGPacking" in THREE) {
|
|
154
|
+
texture.userData.unpackNormal = THREE.NormalRGPacking
|
|
155
|
+
}
|
|
156
|
+
onLoad(texture)
|
|
157
|
+
})
|
|
158
|
+
.catch(err => {
|
|
159
|
+
// Fallback: load the original image uncompressed
|
|
160
|
+
super.load(
|
|
161
|
+
url,
|
|
162
|
+
tex => {
|
|
163
|
+
tex.colorSpace = this.colorSpace
|
|
164
|
+
onLoad?.(tex)
|
|
165
|
+
},
|
|
166
|
+
onProgress,
|
|
167
|
+
// If the fallback also fails, surface the original encoder error first
|
|
168
|
+
fallbackErr => onError?.(err ?? fallbackErr)
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function registerSparkLoader(loader, spark) {
|
|
175
|
+
// Remove existing webp and avif plugins:
|
|
176
|
+
for (let i = 0; i < loader.pluginCallbacks.length; i++) {
|
|
177
|
+
const plugin = loader.pluginCallbacks[i](loader)
|
|
178
|
+
|
|
179
|
+
if (plugin.name == "EXT_texture_webp" || plugin.name == "EXT_texture_avif") {
|
|
180
|
+
loader.unregister(loader.pluginCallbacks[i])
|
|
181
|
+
i--
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Install plugin for standard textures, and textures using webp and avif extensions.
|
|
186
|
+
loader.register(parser => new GLTFSparkPlugin("spark", parser, spark))
|
|
187
|
+
loader.register(parser => new GLTFSparkPlugin("EXT_texture_webp", parser, spark))
|
|
188
|
+
loader.register(parser => new GLTFSparkPlugin("EXT_texture_avif", parser, spark))
|
|
189
|
+
}
|