# HG changeset patch # User Sam # Date 1700670167 -25200 # Node ID e314407ea9db78472074977edc660a1232e489c2 # Parent 0a0402d1d729758447361514bf91c0b58fb22843 fix: setup of materials, still need to check with multiple materials in scene (maybe write new test?) diff -r 0a0402d1d729 -r e314407ea9db src/semicongine/core/imagetypes.nim --- a/src/semicongine/core/imagetypes.nim Sat Oct 21 01:06:51 2023 +0700 +++ b/src/semicongine/core/imagetypes.nim Wed Nov 22 23:22:47 2023 +0700 @@ -1,3 +1,5 @@ +import std/strformat + import ./vulkanapi import ./vector @@ -22,6 +24,12 @@ converter toRGBA*(p: Pixel): Vec4f = newVec4f(float32(p[0]) / 255'f32, float32(p[1]) / 255'f32, float32(p[2]) / 255'f32, float32(p[3]) / 255'f32) +proc `$`*(image: Image): string = + &"{image.width}x{image.height}" + +proc `$`*(texture: Texture): string = + &"{texture.name} {texture.image}" + proc `[]`*(image: Image, x, y: int): Pixel = assert x < image.width assert y < image.height @@ -50,14 +58,14 @@ for x in 0 ..< width: result[x, y] = fill -let INVALID_TEXTURE* = Texture(image: newImage(1, 1, @[[255'u8, 0'u8, 255'u8, 255'u8]]), sampler: Sampler( +let INVALID_TEXTURE* = Texture(name: "Invalid texture", image: newImage(1, 1, @[[255'u8, 0'u8, 255'u8, 255'u8]]), sampler: Sampler( magnification: VK_FILTER_NEAREST, minification: VK_FILTER_NEAREST, wrapModeS: VK_SAMPLER_ADDRESS_MODE_REPEAT, wrapModeT: VK_SAMPLER_ADDRESS_MODE_REPEAT, ) ) -let EMPTY_TEXTURE* = Texture(image: newImage(1, 1, @[[255'u8, 255'u8, 255'u8, 255'u8]]), sampler: Sampler( +let EMPTY_TEXTURE* = Texture(name: "Empty texture", image: newImage(1, 1, @[[255'u8, 255'u8, 255'u8, 255'u8]]), sampler: Sampler( magnification: VK_FILTER_NEAREST, minification: VK_FILTER_NEAREST, wrapModeS: VK_SAMPLER_ADDRESS_MODE_REPEAT, diff -r 0a0402d1d729 -r e314407ea9db src/semicongine/renderer.nim --- a/src/semicongine/renderer.nim Sat Oct 21 01:06:51 2023 +0700 +++ b/src/semicongine/renderer.nim Wed Nov 22 23:22:47 2023 +0700 @@ -21,7 +21,8 @@ import ./mesh import ./material -const TRANSFORMATTRIBUTE = "transform" +const TRANSFORM_ATTRIBUTE = "transform" +const MATERIALINDEX_ATTRIBUTE = "materialIndex" const VERTEX_ATTRIB_ALIGNMENT = 4 # used for buffer alignment type @@ -109,7 +110,7 @@ func meshCompatibleWithPipeline(scene: Scene, mesh: Mesh, pipeline: Pipeline): (bool, string) = for input in pipeline.inputs: - if input.name == TRANSFORMATTRIBUTE: # will be populated automatically + if input.name == TRANSFORM_ATTRIBUTE: # will be populated automatically continue if not (input.name in mesh[].attributes): return (true, &"Shader input '{input.name}' is not available for mesh") @@ -143,14 +144,31 @@ raise newException(Exception, &"Mesh '{mesh}' not compatible with assigned pipeline ({materialType}) because: {message}") if not foundRenderableObject: - var matTypes: seq[string] + var matTypes: Table[string, MaterialType] for mesh in scene.meshes: if not matTypes.contains(mesh.material.name): - matTypes.add mesh.material.name - raise newException(Exception, &"Scene '{scene.name}' has been added but materials are not compatible with any registered shader: Materials in scene: {matTypes}, registered shader-materialtypes: {materialTypes}") + matTypes[mesh.material.name] = mesh.material.theType + assert false, &"Scene '{scene.name}' has been added but materials are not compatible with any registered shader: Materials in scene: {matTypes}, registered shader-materialtypes: {materialTypes}" proc setupDrawableBuffers*(renderer: var Renderer, scene: var Scene) = assert not (scene in renderer.scenedata) + + # find all material data and group it by material type + var materials: Table[MaterialType, seq[MaterialData]] + + for mesh in scene.meshes: + if not materials.contains(mesh.material.theType): + materials[mesh.material.theType] = @[] + if not materials[mesh.material.theType].contains(mesh.material): + materials[mesh.material.theType].add mesh.material + + # automatically populate material and tranform attributes + for mesh in scene.meshes: + if not (TRANSFORM_ATTRIBUTE in mesh[].attributes): + mesh[].initInstanceAttribute(TRANSFORM_ATTRIBUTE, Unit4) + if not (MATERIALINDEX_ATTRIBUTE in mesh[].attributes): + mesh[].initInstanceAttribute(MATERIALINDEX_ATTRIBUTE, uint16(materials[mesh.material.theType].find(mesh.material))) + renderer.checkSceneIntegrity(scene) let @@ -158,6 +176,7 @@ samplers = renderer.samplers(scene) var scenedata = SceneData() + # collect textures from mesh materials, add them to scenedata and upload to GPU for mesh in scene.meshes: if not scenedata.materials.contains(mesh.material): scenedata.materials.add mesh.material @@ -170,6 +189,7 @@ scenedata.textures[name] = @[] scenedata.textures[name].add renderer.device.uploadTexture(getValue[Texture](value, 0)) + # collect textures from shader globals, add them to scenedata and upload to GPU for name, value in scene.shaderGlobals.pairs: if value.theType == TextureType: assert not scenedata.textures.contains(name) # should be handled by the above code @@ -177,10 +197,11 @@ for texture in getValues[Texture](value)[]: scenedata.textures[name].add renderer.device.uploadTexture(texture) + #[ this might be a bad idea, I think originaly just a work around # find all meshes, populate missing attribute values for shader for mesh in scene.meshes.mitems: for inputAttr in inputs: - if inputAttr.name == TRANSFORMATTRIBUTE: + if inputAttr.name == TRANSFORM_ATTRIBUTE: mesh[].initInstanceAttribute(inputAttr.name, inputAttr.thetype) elif not mesh[].attributes.contains(inputAttr.name): warn(&"Mesh is missing data for shader attribute {inputAttr.name}, auto-filling with empty values") @@ -189,6 +210,7 @@ else: mesh[].initVertexAttribute(inputAttr.name, inputAttr.thetype) assert mesh[].attributeType(inputAttr.name) == inputAttr.thetype, &"mesh attribute {inputAttr.name} has type {mesh[].attributeType(inputAttr.name)} but shader expects {inputAttr.thetype}" + ]# # create index buffer if necessary var indicesBufferSize = 0 @@ -336,8 +358,8 @@ assert scene in renderer.scenedata for (drawable, mesh) in renderer.scenedata[scene].drawables.mitems: - if mesh[].attributes.contains(TRANSFORMATTRIBUTE): - mesh[].updateInstanceTransforms(TRANSFORMATTRIBUTE) + if mesh[].attributes.contains(TRANSFORM_ATTRIBUTE): + mesh[].updateInstanceTransforms(TRANSFORM_ATTRIBUTE) let attrs = (if forceAll: mesh[].attributes else: mesh[].dirtyAttributes) for attribute in attrs: renderer.refreshMeshAttributeData(scene, drawable, mesh, attribute) diff -r 0a0402d1d729 -r e314407ea9db src/semicongine/resources.nim --- a/src/semicongine/resources.nim Sat Oct 21 01:06:51 2023 +0700 +++ b/src/semicongine/resources.nim Wed Nov 22 23:22:47 2023 +0700 @@ -10,6 +10,7 @@ import ./resources/mesh import ./resources/font import ./mesh +import ./material export image export audio @@ -133,11 +134,11 @@ let defaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=+[{]};:,<.>/? $!@#%^&*()\"'".toRunes() loadResource_intern(path).readTrueType(name, defaultCharset, color, resolution) -proc loadMeshes*(path: string): seq[MeshTree] = - loadResource_intern(path).readglTF() +proc loadMeshes*(path: string, defaultMaterial: MaterialType): seq[MeshTree] = + loadResource_intern(path).readglTF(defaultMaterial) -proc loadFirstMesh*(path: string): Mesh = - loadResource_intern(path).readglTF()[0].toSeq[0] +proc loadFirstMesh*(path: string, defaultMaterial: MaterialType): Mesh = + loadResource_intern(path).readglTF(defaultMaterial)[0].toSeq[0] proc modList*(): seq[string] = modList_intern() diff -r 0a0402d1d729 -r e314407ea9db src/semicongine/resources/mesh.nim --- a/src/semicongine/resources/mesh.nim Sat Oct 21 01:06:51 2023 +0700 +++ b/src/semicongine/resources/mesh.nim Wed Nov 22 23:22:47 2023 +0700 @@ -44,6 +44,17 @@ 33648: VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, 10497: VK_SAMPLER_ADDRESS_MODE_REPEAT }.toTable + GLTF_MATERIAL_MAPPING = { + "color": "baseColorFactor", + "emissiveColor": "emissiveFactor", + "metallic": "metallicFactor", + "roughness", "roughnessFactor", + "baseTexture": "baseColorTexture", + "metallicRoughnessTexture": "metallicRoughnessTexture", + "normalTexture": "normalTexture", + "occlusionTexture": "occlusionTexture", + "emissiveTexture": "emissiveTexture", + }.toTable proc getGPUType(accessor: JsonNode, attribute: string): DataType = # TODO: no full support for all datatypes that glTF may provide @@ -136,6 +147,8 @@ let textureNode = root["textures"][textureIndex] result.image = loadImage(root, textureNode["source"].getInt(), mainBuffer) result.name = root["images"][textureNode["source"].getInt()]["name"].getStr() + if result.name == "": + result.name = &"Texture{textureIndex}" if textureNode.hasKey("sampler"): let sampler = root["samplers"][textureNode["sampler"].getInt()] @@ -149,64 +162,71 @@ result.sampler.wrapModeT = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()] -proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: seq[uint8]): MaterialData = - result = MaterialData(name: materialNode["name"].getStr()) +proc loadMaterial(root: JsonNode, materialNode: JsonNode, defaultMaterial: MaterialType, mainBuffer: seq[uint8]): MaterialData = let pbr = materialNode["pbrMetallicRoughness"] + var attributes: Table[string, DataList] # color - result.attributes["baseColorFactor"] = initDataList(thetype=Vec4F32) - if pbr.hasKey("baseColorFactor"): - setValue(result.attributes["baseColorFactor"], @[newVec4f( - pbr["baseColorFactor"][0].getFloat(), - pbr["baseColorFactor"][1].getFloat(), - pbr["baseColorFactor"][2].getFloat(), - pbr["baseColorFactor"][3].getFloat(), - )]) - else: - setValue(result.attributes["baseColorFactor"], @[newVec4f(1, 1, 1, 1)]) + if defaultMaterial.attributes.contains("color"): + attributes["color"] = initDataList(thetype=Vec4F32) + if pbr.hasKey(GLTF_MATERIAL_MAPPING["color"]): + setValue(attributes["color"], @[newVec4f( + pbr[GLTF_MATERIAL_MAPPING["color"]][0].getFloat(), + pbr[GLTF_MATERIAL_MAPPING["color"]][1].getFloat(), + pbr[GLTF_MATERIAL_MAPPING["color"]][2].getFloat(), + pbr[GLTF_MATERIAL_MAPPING["color"]][3].getFloat(), + )]) + else: + setValue(attributes["color"], @[newVec4f(1, 1, 1, 1)]) - # pbr material values - for factor in ["metallicFactor", "roughnessFactor"]: - result.attributes[factor] = initDataList(thetype=Float32) - if pbr.hasKey(factor): - setValue(result.attributes[factor], @[float32(pbr[factor].getFloat())]) - else: - setValue(result.attributes[factor], @[0.5'f32]) + # pbr material values + for factor in ["metallic", "roughness"]: + if defaultMaterial.attributes.contains(factor): + attributes[factor] = initDataList(thetype=Float32) + if pbr.hasKey(GLTF_MATERIAL_MAPPING[factor]): + setValue(attributes[factor], @[float32(pbr[GLTF_MATERIAL_MAPPING[factor]].getFloat())]) + else: + setValue(attributes[factor], @[0.5'f32]) # pbr material textures - for texture in ["baseColorTexture", "metallicRoughnessTexture"]: - result.attributes[texture] = initDataList(thetype=TextureType) - result.attributes[texture & "Index"] = initDataList(thetype=UInt8) - if pbr.hasKey(texture): - setValue(result.attributes[texture], @[loadTexture(root, pbr[texture]["index"].getInt(), mainBuffer)]) - setValue(result.attributes[texture & "Index"], @[pbr[texture].getOrDefault("texCoord").getInt(0).uint8]) - else: - setValue(result.attributes[texture & "Index"], @[EMPTY_TEXTURE]) - setValue(result.attributes[texture & "Index"], @[0'u8]) + for texture in ["baseTexture", "metallicRoughnessTexture"]: + if defaultMaterial.attributes.contains(texture): + attributes[texture] = initDataList(thetype=TextureType) + # attributes[texture & "Index"] = initDataList(thetype=UInt8) + if pbr.hasKey(GLTF_MATERIAL_MAPPING[texture]): + setValue(attributes[texture], @[loadTexture(root, pbr[GLTF_MATERIAL_MAPPING[texture]]["index"].getInt(), mainBuffer)]) + # setValue(attributes[texture & "Index"], @[pbr[GLTF_MATERIAL_MAPPING[texture]].getOrDefault("texCoord").getInt(0).uint8]) + else: + setValue(attributes[texture], @[EMPTY_TEXTURE]) + # setValue(attributes[texture & "Index"], @[0'u8]) # generic material textures for texture in ["normalTexture", "occlusionTexture", "emissiveTexture"]: - result.attributes[texture] = initDataList(thetype=TextureType) - result.attributes[texture & "Index"] = initDataList(thetype=UInt8) - if materialNode.hasKey(texture): - setValue(result.attributes[texture], @[loadTexture(root, materialNode[texture]["index"].getInt(), mainBuffer)]) - setValue(result.attributes[texture & "Index"], @[materialNode[texture].getOrDefault("texCoord").getInt(0).uint8]) - else: - setValue(result.attributes[texture], @[EMPTY_TEXTURE]) - setValue(result.attributes[texture & "Index"], @[0'u8]) + if defaultMaterial.attributes.contains(texture): + attributes[texture] = initDataList(thetype=TextureType) + # attributes[texture & "Index"] = initDataList(thetype=UInt8) + if materialNode.hasKey(GLTF_MATERIAL_MAPPING[texture]): + setValue(attributes[texture], @[loadTexture(root, materialNode[texture]["index"].getInt(), mainBuffer)]) + # setValue(attributes[texture & "Index"], @[materialNode[texture].getOrDefault("texCoord").getInt(0).uint8]) + else: + setValue(attributes[texture], @[EMPTY_TEXTURE]) + # setValue(attributes[texture & "Index"], @[0'u8]) # emissiv color - result.attributes["emissiveFactor"] = initDataList(thetype=Vec3F32) - if materialNode.hasKey("emissiveFactor"): - setValue(result.attributes["emissiveFactor"], @[newVec3f( - materialNode["emissiveFactor"][0].getFloat(), - materialNode["emissiveFactor"][1].getFloat(), - materialNode["emissiveFactor"][2].getFloat(), - )]) - else: - setValue(result.attributes["emissiveFactor"], @[newVec3f(1'f32, 1'f32, 1'f32)]) + if defaultMaterial.attributes.contains("emissiveColor"): + attributes["emissiveColor"] = initDataList(thetype=Vec3F32) + if materialNode.hasKey(GLTF_MATERIAL_MAPPING["emissiveColor"]): + setValue(attributes["emissiveColor"], @[newVec3f( + materialNode[GLTF_MATERIAL_MAPPING["emissiveColor"]][0].getFloat(), + materialNode[GLTF_MATERIAL_MAPPING["emissiveColor"]][1].getFloat(), + materialNode[GLTF_MATERIAL_MAPPING["emissiveColor"]][2].getFloat(), + )]) + else: + setValue(attributes["emissiveColor"], @[newVec3f(1'f32, 1'f32, 1'f32)]) -proc loadMesh(meshname: string, root: JsonNode, primitiveNode: JsonNode, mainBuffer: seq[uint8]): Mesh = + result = initMaterialData(materialType=defaultMaterial, name=materialNode["name"].getStr(), attributes=attributes) + +proc loadMesh(meshname: string, root: JsonNode, primitiveNode: JsonNode, defaultMaterial: MaterialType, mainBuffer: seq[uint8]): Mesh = if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4: raise newException(Exception, "Currently only TRIANGLE mode is supported for geometry mode") @@ -236,7 +256,7 @@ if primitiveNode.hasKey("material"): let materialId = primitiveNode["material"].getInt() - result[].material = loadMaterial(root, root["materials"][materialId], mainBuffer) + result[].material = loadMaterial(root, root["materials"][materialId], defaultMaterial, mainBuffer) else: result[].material = DEFAULT_MATERIAL @@ -263,13 +283,13 @@ raise newException(Exception, &"Unsupported index data type: {data.thetype}") transform[Vec3f](result[], "position", scale(1, -1, 1)) -proc loadNode(root: JsonNode, node: JsonNode, mainBuffer: var seq[uint8]): MeshTree = +proc loadNode(root: JsonNode, node: JsonNode, defaultMaterial: MaterialType, mainBuffer: var seq[uint8]): MeshTree = result = MeshTree() # mesh if node.hasKey("mesh"): let mesh = root["meshes"][node["mesh"].getInt()] for primitive in mesh["primitives"]: - result.children.add MeshTree(mesh: loadMesh(mesh["name"].getStr(), root, primitive, mainBuffer)) + result.children.add MeshTree(mesh: loadMesh(mesh["name"].getStr(), root, primitive, defaultMaterial, mainBuffer)) # transformation if node.hasKey("matrix"): @@ -305,16 +325,16 @@ # children if node.hasKey("children"): for childNode in node["children"]: - result.children.add loadNode(root, root["nodes"][childNode.getInt()], mainBuffer) + result.children.add loadNode(root, root["nodes"][childNode.getInt()], defaultMaterial, mainBuffer) -proc loadMeshTree(root: JsonNode, scenenode: JsonNode, mainBuffer: var seq[uint8]): MeshTree = +proc loadMeshTree(root: JsonNode, scenenode: JsonNode, defaultMaterial: MaterialType, mainBuffer: var seq[uint8]): MeshTree = result = MeshTree() for nodeId in scenenode["nodes"]: - result.children.add loadNode(root, root["nodes"][nodeId.getInt()], mainBuffer) + result.children.add loadNode(root, root["nodes"][nodeId.getInt()], defaultMaterial, mainBuffer) result.updateTransforms() -proc readglTF*(stream: Stream): seq[MeshTree] = +proc readglTF*(stream: Stream, defaultMaterial: MaterialType): seq[MeshTree] = var header: glTFHeader data: glTFData @@ -344,4 +364,4 @@ debug "Loading mesh: ", data.structuredContent.pretty for scenedata in data.structuredContent["scenes"]: - result.add data.structuredContent.loadMeshTree(scenedata, data.binaryBufferData) + result.add data.structuredContent.loadMeshTree(scenedata, defaultMaterial, data.binaryBufferData) diff -r 0a0402d1d729 -r e314407ea9db src/semicongine/text.nim --- a/src/semicongine/text.nim Sat Oct 21 01:06:51 2023 +0700 +++ b/src/semicongine/text.nim Wed Nov 22 23:22:47 2023 +0700 @@ -115,6 +115,7 @@ result.mesh[].renameAttribute("position", POSITION_ATTRIB) result.mesh[].renameAttribute("uv", UV_ATTRIB) result.mesh.material = MaterialData( + theType: TEXT_MATERIAL_TYPE, name: font.name & " text", attributes: {"fontAtlas": initDataList(@[font.fontAtlas])}.toTable, )