comparison semiconginev2/gltf.nim @ 1247:c15770761865

add: gltf loading test, gltf loading for materials
author sam <sam@basx.dev>
date Wed, 24 Jul 2024 23:26:34 +0700
parents d594b1d07d49
children 317bb5a73606
comparison
equal deleted inserted replaced
1246:356089365076 1247:c15770761865
1 type 1 type
2 GLTFMesh[TMesh, TMaterial] = object 2 GLTFMesh*[TMesh, TMaterial] = object
3 scenes: seq[int] 3 scenes*: seq[seq[int]] # each scene has a seq of node indices
4 nodes: seq[int] 4 nodes*: seq[seq[int]] # each node has a seq of mesh indices
5 meshes: seq[TMesh] 5 meshes*: seq[TMesh]
6 materials: seq[TMaterial] 6 materials*: seq[TMaterial]
7 textures*: seq[Image[BGRA]]
7 glTFHeader = object 8 glTFHeader = object
8 magic: uint32 9 magic: uint32
9 version: uint32 10 version: uint32
10 length: uint32 11 length: uint32
11 glTFData = object 12 glTFData = object
12 structuredContent: JsonNode 13 structuredContent: JsonNode
13 binaryBufferData: seq[uint8] 14 binaryBufferData: seq[uint8]
14 15
15 MaterialAttributeNames = object 16 MaterialAttributeNames = object
17 # pbr
18 baseColorTexture: string
19 baseColorTextureUv: string
16 baseColorFactor: string 20 baseColorFactor: string
17 emissiveFactor: string 21 metallicRoughnessTexture: string
22 metallicRoughnessTextureUv: string
18 metallicFactor: string 23 metallicFactor: string
19 roughnessFactor: string 24 roughnessFactor: string
20 baseColorTexture: string 25
21 metallicRoughnessTexture: string 26 # other
22 normalTexture: string 27 normalTexture: string
28 normalTextureUv: string
23 occlusionTexture: string 29 occlusionTexture: string
30 occlusionTextureUv: string
24 emissiveTexture: string 31 emissiveTexture: string
32 emissiveTextureUv: string
33 emissiveFactor: string
34
35 #[
36 static:
37 let TypeIds = {
38 int8: 5120,
39 uint8: 5121,
40 int16: 5122,
41 uint16: 5123,
42 uint32: 5125,
43 float32: 5126,
44 }.toTable
45 ]#
25 46
26 const 47 const
27 HEADER_MAGIC = 0x46546C67 48 HEADER_MAGIC = 0x46546C67
28 JSON_CHUNK = 0x4E4F534A 49 JSON_CHUNK = 0x4E4F534A
29 BINARY_CHUNK = 0x004E4942 50 BINARY_CHUNK = 0x004E4942
51 #[
30 ACCESSOR_TYPE_MAP = { 52 ACCESSOR_TYPE_MAP = {
31 5120: Int8, 53 5120: Int8,
32 5121: UInt8, 54 5121: UInt8,
33 5122: Int16, 55 5122: Int16,
34 5123: UInt16, 56 5123: UInt16,
35 5125: UInt32, 57 5125: UInt32,
36 5126: Float32, 58 5126: Float32,
37 }.toTable 59 }.toTable
60 ]#
38 SAMPLER_FILTER_MODE_MAP = { 61 SAMPLER_FILTER_MODE_MAP = {
39 9728: VK_FILTER_NEAREST, 62 9728: VK_FILTER_NEAREST,
40 9729: VK_FILTER_LINEAR, 63 9729: VK_FILTER_LINEAR,
41 9984: VK_FILTER_NEAREST, 64 9984: VK_FILTER_NEAREST,
42 9985: VK_FILTER_LINEAR, 65 9985: VK_FILTER_LINEAR,
46 SAMPLER_WRAP_MODE_MAP = { 69 SAMPLER_WRAP_MODE_MAP = {
47 33071: VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, 70 33071: VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
48 33648: VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, 71 33648: VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT,
49 10497: VK_SAMPLER_ADDRESS_MODE_REPEAT 72 10497: VK_SAMPLER_ADDRESS_MODE_REPEAT
50 }.toTable 73 }.toTable
51 GLTF_MATERIAL_MAPPING = { 74
52 "color": "baseColorFactor", 75 #[
53 "emissiveColor": "emissiveFactor",
54 "metallic": "metallicFactor",
55 "roughness", "roughnessFactor",
56 "baseTexture": "baseColorTexture",
57 "metallicRoughnessTexture": "metallicRoughnessTexture",
58 "normalTexture": "normalTexture",
59 "occlusionTexture": "occlusionTexture",
60 "emissiveTexture": "emissiveTexture",
61 }.toTable
62
63 proc getGPUType(accessor: JsonNode, attribute: string): DataType = 76 proc getGPUType(accessor: JsonNode, attribute: string): DataType =
64 # TODO: no full support for all datatypes that glTF may provide 77 # TODO: no full support for all datatypes that glTF may provide
65 # semicongine/core/gpu_data should maybe generated with macros to allow for all combinations 78 # semicongine/core/gpu_data should maybe generated with macros to allow for all combinations
66 let componentType = ACCESSOR_TYPE_MAP[accessor["componentType"].getInt()] 79 let componentType = ACCESSOR_TYPE_MAP[accessor["componentType"].getInt()]
67 let theType = accessor["type"].getStr() 80 let theType = accessor["type"].getStr()
93 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}") 106 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}")
94 of "MAT4": 107 of "MAT4":
95 case componentType 108 case componentType
96 of Float32: return Vec4F32 109 of Float32: return Vec4F32
97 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}") 110 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}")
111 ]#
98 112
99 proc getBufferViewData(bufferView: JsonNode, mainBuffer: seq[uint8], baseBufferOffset = 0): seq[uint8] = 113 proc getBufferViewData(bufferView: JsonNode, mainBuffer: seq[uint8], baseBufferOffset = 0): seq[uint8] =
100 assert bufferView["buffer"].getInt() == 0, "Currently no external buffers supported" 114 assert bufferView["buffer"].getInt() == 0, "Currently no external buffers supported"
101 115
102 result = newSeq[uint8](bufferView["byteLength"].getInt()) 116 result = newSeq[uint8](bufferView["byteLength"].getInt())
105 119
106 if bufferView.hasKey("byteStride"): 120 if bufferView.hasKey("byteStride"):
107 raise newException(Exception, "Unsupported feature: byteStride in buffer view") 121 raise newException(Exception, "Unsupported feature: byteStride in buffer view")
108 copyMem(dstPointer, addr mainBuffer[bufferOffset], result.len) 122 copyMem(dstPointer, addr mainBuffer[bufferOffset], result.len)
109 123
124 #[
110 proc getAccessorData(root: JsonNode, accessor: JsonNode, mainBuffer: seq[uint8]): DataList = 125 proc getAccessorData(root: JsonNode, accessor: JsonNode, mainBuffer: seq[uint8]): DataList =
111 result = InitDataList(thetype = accessor.getGPUType("??")) 126 result = InitDataList(thetype = accessor.getGPUType("??"))
112 result.SetLen(accessor["count"].getInt()) 127 result.SetLen(accessor["count"].getInt())
113 128
114 let bufferView = root["bufferViews"][accessor["bufferView"].getInt()] 129 let bufferView = root["bufferViews"][accessor["bufferView"].getInt()]
128 for i in 0 ..< int(result.len): 143 for i in 0 ..< int(result.len):
129 copyMem(dstPointer, addr mainBuffer[bufferOffset + i * bufferView["byteStride"].getInt()], int(result.thetype.Size)) 144 copyMem(dstPointer, addr mainBuffer[bufferOffset + i * bufferView["byteStride"].getInt()], int(result.thetype.Size))
130 dstPointer = cast[pointer](cast[uint](dstPointer) + result.thetype.Size) 145 dstPointer = cast[pointer](cast[uint](dstPointer) + result.thetype.Size)
131 else: 146 else:
132 copyMem(dstPointer, addr mainBuffer[bufferOffset], length) 147 copyMem(dstPointer, addr mainBuffer[bufferOffset], length)
133 148 ]#
134 proc loadImage(root: JsonNode, imageIndex: int, mainBuffer: seq[uint8]): Image[RGBAPixel] = 149
150 proc loadTexture(root: JsonNode, textureNode: JsonNode, mainBuffer: seq[uint8]): Image[BGRA] =
151
152 let imageIndex = textureNode["source"].getInt()
153
135 if root["images"][imageIndex].hasKey("uri"): 154 if root["images"][imageIndex].hasKey("uri"):
136 raise newException(Exception, "Unsupported feature: Load images from external files") 155 raise newException(Exception, "Unsupported feature: Cannot load images from external files")
156 let imageType = root["images"][imageIndex]["mimeType"].getStr()
157 assert imageType == "image/png", "glTF loader currently only supports PNG"
137 158
138 let bufferView = root["bufferViews"][root["images"][imageIndex]["bufferView"].getInt()] 159 let bufferView = root["bufferViews"][root["images"][imageIndex]["bufferView"].getInt()]
139 let imgData = newStringStream(cast[string](getBufferViewData(bufferView, mainBuffer))) 160 result = LoadImage[BGRA](getBufferViewData(bufferView, mainBuffer))
140
141 let imageType = root["images"][imageIndex]["mimeType"].getStr()
142 case imageType
143 of "image/bmp":
144 result = ReadBMP(imgData)
145 of "image/png":
146 result = ReadPNG(imgData)
147 else:
148 raise newException(Exception, "Unsupported feature: Load image of type " & imageType)
149
150 proc loadTexture(root: JsonNode, textureIndex: int, mainBuffer: seq[uint8]): Texture =
151 let textureNode = root["textures"][textureIndex]
152 result = Texture(isGrayscale: false)
153 result.colorImage = loadImage(root, textureNode["source"].getInt(), mainBuffer)
154 result.name = root["images"][textureNode["source"].getInt()]["name"].getStr()
155 if result.name == "":
156 result.name = &"Texture{textureIndex}"
157 161
158 if textureNode.hasKey("sampler"): 162 if textureNode.hasKey("sampler"):
159 let sampler = root["samplers"][textureNode["sampler"].getInt()] 163 let sampler = root["samplers"][textureNode["sampler"].getInt()]
160 if sampler.hasKey("magFilter"): 164 if sampler.hasKey("magFilter"):
161 result.sampler.magnification = SAMPLER_FILTER_MODE_MAP[sampler["magFilter"].getInt()] 165 result.magInterpolation = SAMPLER_FILTER_MODE_MAP[sampler["magFilter"].getInt()]
162 if sampler.hasKey("minFilter"): 166 if sampler.hasKey("minFilter"):
163 result.sampler.minification = SAMPLER_FILTER_MODE_MAP[sampler["minFilter"].getInt()] 167 result.minInterpolation = SAMPLER_FILTER_MODE_MAP[sampler["minFilter"].getInt()]
164 if sampler.hasKey("wrapS"): 168 if sampler.hasKey("wrapS"):
165 result.sampler.wrapModeS = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()] 169 result.wrapU = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()]
166 if sampler.hasKey("wrapT"): 170 if sampler.hasKey("wrapT"):
167 result.sampler.wrapModeT = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()] 171 result.wrapV = SAMPLER_WRAP_MODE_MAP[sampler["wrapT"].getInt()]
168 172
173 proc getVec4f(node: JsonNode): Vec4f =
174 NewVec4f(node[0].getFloat(), node[1].getFloat(), node[2].getFloat(), node[3].getFloat())
169 175
170 proc loadMaterial[TMaterial]( 176 proc loadMaterial[TMaterial](
171 root: JsonNode, 177 root: JsonNode,
172 materialNode: JsonNode, 178 materialNode: JsonNode,
173 mainBuffer: seq[uint8], 179 mainBuffer: seq[uint8],
174 mapping: MaterialAttributeNames 180 mapping: static MaterialAttributeNames
175 ): TMaterial = 181 ): TMaterial =
182 result = TMaterial()
183
176 let pbr = materialNode["pbrMetallicRoughness"] 184 let pbr = materialNode["pbrMetallicRoughness"]
177 for glName, glValue in fieldPairs(mapping): 185 for name, value in fieldPairs(result):
178 if glValue != "": 186 for gltfAttribute, mappedName in fieldPairs(mapping):
179 for name, value in fieldPairs(result): 187 when gltfAttribute != "" and name == mappedName:
180 when name == glName: 188 if pbr.hasKey(gltfAttribute):
181 value = 189 when gltfAttribute.endsWith("Texture"):
182 190 value = typeof(value)(pbr[gltfAttribute]["index"].getInt())
183 #[ 191 elif gltfAttribute.endsWith("TextureUv"):
184 192 value = typeof(pbr[gltfAttribute[0 ..< ^2]]["index"].getInt())
185 # color 193 elif gltfAttribute in ["baseColorFactor", "emissiveFactor"]:
186 if defaultMaterial.attributes.contains("color"): 194 value = pbr[gltfAttribute].getVec4f()
187 attributes["color"] = InitDataList(thetype = Vec4F32) 195 elif gltfAttribute in ["metallicFactor", "roughnessFactor"]:
188 if pbr.hasKey(GLTF_MATERIAL_MAPPING["color"]): 196 value = pbr[gltfAttribute].getFloat()
189 attributes["color"] = @[NewVec4f( 197 else:
190 pbr[GLTF_MATERIAL_MAPPING["color"]][0].getFloat(), 198 {.error: "Unsupported gltf material attribute".}
191 pbr[GLTF_MATERIAL_MAPPING["color"]][1].getFloat(), 199
192 pbr[GLTF_MATERIAL_MAPPING["color"]][2].getFloat(), 200
193 pbr[GLTF_MATERIAL_MAPPING["color"]][3].getFloat(), 201 #[
194 )]
195 else:
196 attributes["color"] = @[NewVec4f(1, 1, 1, 1)]
197
198 # pbr material values
199 for factor in ["metallic", "roughness"]:
200 if defaultMaterial.attributes.contains(factor):
201 attributes[factor] = InitDataList(thetype = Float32)
202 if pbr.hasKey(GLTF_MATERIAL_MAPPING[factor]):
203 attributes[factor] = @[float32(pbr[GLTF_MATERIAL_MAPPING[factor]].getFloat())]
204 else:
205 attributes[factor] = @[0.5'f32]
206
207 # pbr material textures
208 for texture in ["baseTexture", "metallicRoughnessTexture"]:
209 if defaultMaterial.attributes.contains(texture):
210 attributes[texture] = InitDataList(thetype = TextureType)
211 # attributes[texture & "Index"] = InitDataList(thetype=UInt8)
212 if pbr.hasKey(GLTF_MATERIAL_MAPPING[texture]):
213 attributes[texture] = @[loadTexture(root, pbr[GLTF_MATERIAL_MAPPING[texture]]["index"].getInt(), mainBuffer)]
214 else:
215 attributes[texture] = @[EMPTY_TEXTURE]
216
217 # generic material textures
218 for texture in ["normalTexture", "occlusionTexture", "emissiveTexture"]:
219 if defaultMaterial.attributes.contains(texture):
220 attributes[texture] = InitDataList(thetype = TextureType)
221 # attributes[texture & "Index"] = InitDataList(thetype=UInt8)
222 if materialNode.hasKey(GLTF_MATERIAL_MAPPING[texture]):
223 attributes[texture] = @[loadTexture(root, materialNode[texture]["index"].getInt(), mainBuffer)]
224 else:
225 attributes[texture] = @[EMPTY_TEXTURE]
226
227 # emissiv color
228 if defaultMaterial.attributes.contains("emissiveColor"):
229 attributes["emissiveColor"] = InitDataList(thetype = Vec3F32)
230 if materialNode.hasKey(GLTF_MATERIAL_MAPPING["emissiveColor"]):
231 attributes["emissiveColor"] = @[NewVec3f(
232 materialNode[GLTF_MATERIAL_MAPPING["emissiveColor"]][0].getFloat(),
233 materialNode[GLTF_MATERIAL_MAPPING["emissiveColor"]][1].getFloat(),
234 materialNode[GLTF_MATERIAL_MAPPING["emissiveColor"]][2].getFloat(),
235 )]
236 else:
237 attributes["emissiveColor"] = @[NewVec3f(1'f32, 1'f32, 1'f32)]
238 ]#
239
240 202
241 proc loadMesh(meshname: string, root: JsonNode, primitiveNode: JsonNode, materials: seq[MaterialData], mainBuffer: seq[uint8]): Mesh = 203 proc loadMesh(meshname: string, root: JsonNode, primitiveNode: JsonNode, materials: seq[MaterialData], mainBuffer: seq[uint8]): Mesh =
242 if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4: 204 if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4:
243 raise newException(Exception, "Currently only TRIANGLE mode is supported for geometry mode") 205 raise newException(Exception, "Currently only TRIANGLE mode is supported for geometry mode")
244 206
293 tri.setLen(0) 255 tri.setLen(0)
294 else: 256 else:
295 raise newException(Exception, &"Unsupported index data type: {data.thetype}") 257 raise newException(Exception, &"Unsupported index data type: {data.thetype}")
296 # TODO: getting from gltf to vulkan system is still messed up somehow, see other TODO 258 # TODO: getting from gltf to vulkan system is still messed up somehow, see other TODO
297 Transform[Vec3f](result[], "position", Scale(1, -1, 1)) 259 Transform[Vec3f](result[], "position", Scale(1, -1, 1))
260
298 261
299 proc loadNode(root: JsonNode, node: JsonNode, materials: seq[MaterialData], mainBuffer: var seq[uint8]): MeshTree = 262 proc loadNode(root: JsonNode, node: JsonNode, materials: seq[MaterialData], mainBuffer: var seq[uint8]): MeshTree =
300 result = MeshTree() 263 result = MeshTree()
301 # mesh 264 # mesh
302 if node.hasKey("mesh"): 265 if node.hasKey("mesh"):
347 result.children.add loadNode(root, root["nodes"][nodeId.getInt()], materials, mainBuffer) 310 result.children.add loadNode(root, root["nodes"][nodeId.getInt()], materials, mainBuffer)
348 # TODO: getting from gltf to vulkan system is still messed up somehow (i.e. not consistent for different files), see other TODO 311 # TODO: getting from gltf to vulkan system is still messed up somehow (i.e. not consistent for different files), see other TODO
349 # result.transform = Scale(1, -1, 1) 312 # result.transform = Scale(1, -1, 1)
350 result.updateTransforms() 313 result.updateTransforms()
351 314
352 315 ]#
353 proc ReadglTF*[TMaterial, TMesh]( 316
317 proc ReadglTF*[TMesh, TMaterial](
354 stream: Stream, 318 stream: Stream,
355 attributeNames: MaterialAttributeNames, 319 baseColorFactor: static string = "",
356 baseColorFactor = "", 320 emissiveFactor: static string = "",
357 emissiveFactor = "", 321 metallicFactor: static string = "",
358 metallicFactor = "", 322 roughnessFactor: static string = "",
359 roughnessFactor = "", 323 baseColorTexture: static string = "",
360 baseColorTexture = "", 324 metallicRoughnessTexture: static string = "",
361 metallicRoughnessTexture = "", 325 normalTexture: static string = "",
362 normalTexture = "", 326 occlusionTexture: static string = "",
363 occlusionTexture = "", 327 emissiveTexture: static string = "",
364 emissiveTexture = "",
365 ): GLTFMesh[TMesh, TMaterial] = 328 ): GLTFMesh[TMesh, TMaterial] =
366 let mapping = MaterialAttributeNames( 329 const mapping = MaterialAttributeNames(
367 baseColorFactor: baseColorFactor 330 baseColorFactor: baseColorFactor,
368 emissiveFactor: emissiveFactor 331 emissiveFactor: emissiveFactor,
369 metallicFactor: metallicFactor 332 metallicFactor: metallicFactor,
370 roughnessFactor: roughnessFactor 333 roughnessFactor: roughnessFactor,
371 baseColorTexture: baseColorTexture 334 baseColorTexture: baseColorTexture,
372 metallicRoughnessTexture: metallicRoughnessTexture 335 metallicRoughnessTexture: metallicRoughnessTexture,
373 normalTexture: normalTexture 336 normalTexture: normalTexture,
374 occlusionTexture: occlusionTexture 337 occlusionTexture: occlusionTexture,
375 emissiveTexture: emissiveTexture 338 emissiveTexture: emissiveTexture,
376 ) 339 )
377 var 340 var
378 header: glTFHeader 341 header: glTFHeader
379 data: glTFData 342 data: glTFData
380 343
400 let bufferLenDiff = int(chunkLength) - data.structuredContent["buffers"][0]["byteLength"].getInt() 363 let bufferLenDiff = int(chunkLength) - data.structuredContent["buffers"][0]["byteLength"].getInt()
401 assert 0 <= bufferLenDiff and bufferLenDiff <= 3 # binary buffer may be aligned to 4 bytes 364 assert 0 <= bufferLenDiff and bufferLenDiff <= 3 # binary buffer may be aligned to 4 bytes
402 365
403 debug "Loading mesh: ", data.structuredContent.pretty 366 debug "Loading mesh: ", data.structuredContent.pretty
404 367
405 var materials: seq[MaterialData] 368 if "materials" in data.structuredContent:
406 for materialnode in data.structuredContent["materials"]: 369 for materialnode in items(data.structuredContent["materials"]):
407 result.materials.add loadMaterial[TMaterial](data.structuredContent, materialnode, data.binaryBufferData, mapping) 370 result.materials.add loadMaterial[TMaterial](data.structuredContent, materialnode, data.binaryBufferData, mapping)
408 371
409 for scenedata in data.structuredContent["scenes"]: 372 if "textures" in data.structuredContent:
410 result.add data.structuredContent.loadScene(scenedata, materials, data.binaryBufferData) 373 for texturenode in items(data.structuredContent["textures"]):
374 result.textures.add loadTexture(data.structuredContent, texturenode, data.binaryBufferData)
375
376 echo result
377 # for scenedata in data.structuredContent["scenes"]:
378 # result.add data.structuredContent.loadScene(scenedata, materials, data.binaryBufferData)
379 #
380 proc LoadMeshes*[TMesh, TMaterial](
381 path: string,
382 baseColorFactor: static string = "",
383 emissiveFactor: static string = "",
384 metallicFactor: static string = "",
385 roughnessFactor: static string = "",
386 baseColorTexture: static string = "",
387 metallicRoughnessTexture: static string = "",
388 normalTexture: static string = "",
389 occlusionTexture: static string = "",
390 emissiveTexture: static string = "",
391 package = DEFAULT_PACKAGE
392 ): GLTFMesh[TMesh, TMaterial] =
393 ReadglTF[TMesh, TMaterial](
394 stream = loadResource_intern(path, package = package),
395 baseColorFactor = baseColorFactor,
396 emissiveFactor = emissiveFactor,
397 metallicFactor = metallicFactor,
398 roughnessFactor = roughnessFactor,
399 baseColorTexture = baseColorTexture,
400 metallicRoughnessTexture = metallicRoughnessTexture,
401 normalTexture = normalTexture,
402 occlusionTexture = occlusionTexture,
403 emissiveTexture = emissiveTexture,
404 )