# HG changeset patch # User Sam # Date 1695224104 -25200 # Node ID 6a09fe5dc99bab176e45bb5b92c065a3a6d0a1e5 # Parent fb22fd8142b9542f733228c6ade9e690a7c6e5c0 add: multi-material for meshes diff -r fb22fd8142b9 -r 6a09fe5dc99b src/semicongine/mesh.nim --- a/src/semicongine/mesh.nim Wed Sep 20 22:02:20 2023 +0700 +++ b/src/semicongine/mesh.nim Wed Sep 20 22:35:04 2023 +0700 @@ -23,7 +23,7 @@ of Tiny: tinyIndices: seq[array[3, uint8]] of Small: smallIndices: seq[array[3, uint16]] of Big: bigIndices: seq[array[3, uint32]] - material*: Material + materials*: seq[Material] transform*: Mat4 = Unit4 instanceTransforms*: seq[Mat4] applyMeshTransformToInstances*: bool = true # if true, the transform attribute for the shader will apply the instance transform AND the mesh transform, to each instance @@ -146,7 +146,7 @@ vertexCount: positions.len, instanceTransforms: @instanceTransforms, transform: transform, - material: material, + materials: @[material], ) result[].initVertexAttribute("position", positions.toSeq) @@ -370,7 +370,7 @@ result = MeshObject( vertexCount: mesh.indicesCount, indexType: None, - material: mesh.material, + materials: mesh.materials, transform: mesh.transform, instanceTransforms: mesh.instanceTransforms, visible: mesh.visible, @@ -415,7 +415,7 @@ instanceTransforms: @[Unit4F32], indexType: Small, smallIndices: @[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]], - material: DEFAULT_MATERIAL + materials: @[DEFAULT_MATERIAL] ) let @@ -429,7 +429,7 @@ result[].initVertexAttribute("uv", @[newVec2f(0, 0), newVec2f(1, 0), newVec2f(1, 1), newVec2f(0, 1)]) proc tri*(width=1'f32, height=1'f32, color="ffffffff"): Mesh = - result = Mesh(vertexCount: 3, instanceTransforms: @[Unit4F32], material: DEFAULT_MATERIAL) + result = Mesh(vertexCount: 3, instanceTransforms: @[Unit4F32], materials: @[DEFAULT_MATERIAL]) let half_w = width / 2 half_h = height / 2 @@ -440,7 +440,7 @@ proc circle*(width=1'f32, height=1'f32, nSegments=12, color="ffffffff"): Mesh = assert nSegments >= 3 - result = Mesh(vertexCount: 3 + nSegments, instanceTransforms: @[Unit4F32], indexType: Small, material: DEFAULT_MATERIAL) + result = Mesh(vertexCount: 3 + nSegments, instanceTransforms: @[Unit4F32], indexType: Small, materials: @[DEFAULT_MATERIAL]) let half_w = width / 2 diff -r fb22fd8142b9 -r 6a09fe5dc99b src/semicongine/renderer.nim --- a/src/semicongine/renderer.nim Wed Sep 20 22:02:20 2023 +0700 +++ b/src/semicongine/renderer.nim Wed Sep 20 22:35:04 2023 +0700 @@ -2,6 +2,7 @@ import std/tables import std/strformat import std/sequtils +import std/strutils import std/logging import ./core @@ -43,7 +44,7 @@ emptyTexture: VulkanTexture func usesMaterial(scene: Scene, materialName: string): bool = - return scene.meshes.anyIt(it.material.name == materialName) + return scene.meshes.anyIt(it.materials.anyIt(it.name == materialName)) proc initRenderer*(device: Device, shaders: Table[string, ShaderConfiguration], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])): Renderer = assert device.vk.valid @@ -121,7 +122,10 @@ if input.perInstance and not mesh[].instanceAttributes.contains(input.name): return (true, &"Shader input '{input.name}' expected to be per instance attribute, but mesh has no such instance attribute (available are: {mesh[].instanceAttributes})") - return materialCompatibleWithPipeline(scene, mesh.material, pipeline) + var pipelineCompatabilities = mesh.materials.mapIt(materialCompatibleWithPipeline(scene, it, pipeline)) + if pipelineCompatabilities.filterIt(not it[0]).len == 0: + return (true, pipelineCompatabilities.mapIt(it[1]).join(" / ")) + return (false, "") func checkSceneIntegrity(renderer: Renderer, scene: Scene) = if scene.meshes.len == 0: @@ -133,7 +137,7 @@ for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs: shaderTypes.add materialName for mesh in scene.meshes: - if mesh.material.name == materialName: + if mesh.materials.anyIt(it.name == materialName): foundRenderableObject = true let (error, message) = scene.meshCompatibleWithPipeline(mesh, pipeline) if error: @@ -142,8 +146,9 @@ if not foundRenderableObject: var materialTypes: seq[string] for mesh in scene.meshes: - if not materialTypes.contains(mesh.material.name): - materialTypes.add mesh.material.name + for material in mesh.materials: + if not materialTypes.contains(material.name): + materialTypes.add material.name raise newException(Exception, &"Scene '{scene.name}' has been added but materials are not compatible with any registered shader: Materials in scene: {materialTypes}, registered shader-materialtypes: {shaderTypes}") proc setupDrawableBuffers*(renderer: var Renderer, scene: var Scene) = @@ -156,15 +161,16 @@ var scenedata = SceneData() for mesh in scene.meshes: - if not scenedata.materials.contains(mesh.material): - scenedata.materials.add mesh.material - for textureName, texture in mesh.material.textures.pairs: - if scene.shaderGlobals.contains(textureName) and scene.shaderGlobals[textureName].theType == Sampler2D: - warn &"Ignoring material texture '{textureName}' as scene-global textures with the same name have been defined" - else: - if not scenedata.textures.hasKey(textureName): - scenedata.textures[textureName] = @[] - scenedata.textures[textureName].add renderer.device.uploadTexture(texture) + for material in mesh.materials: + if not scenedata.materials.contains(material): + scenedata.materials.add material + for textureName, texture in material.textures.pairs: + if scene.shaderGlobals.contains(textureName) and scene.shaderGlobals[textureName].theType == Sampler2D: + warn &"Ignoring material texture '{textureName}' as scene-global textures with the same name have been defined" + else: + if not scenedata.textures.hasKey(textureName): + scenedata.textures[textureName] = @[] + scenedata.textures[textureName].add renderer.device.uploadTexture(texture) for name, value in scene.shaderGlobals.pairs: if value.theType == Sampler2D: @@ -428,7 +434,7 @@ debug &"Start pipeline for '{materialName}'" commandBuffer.vkCmdBindPipeline(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.vk) commandBuffer.vkCmdBindDescriptorSets(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.layout, 0, 1, addr(renderer.scenedata[scene].descriptorSets[pipeline.vk][renderer.swapchain.currentInFlight].vk), 0, nil) - for (drawable, mesh) in renderer.scenedata[scene].drawables.filterIt(it[1].visible and it[1].material.name == materialName): + for (drawable, mesh) in renderer.scenedata[scene].drawables.filterIt(it[1].visible and it[1].materials.anyIt(it.name == materialName)): drawable.draw(commandBuffer, vertexBuffers=renderer.scenedata[scene].vertexBuffers, indexBuffer=renderer.scenedata[scene].indexBuffer, pipeline.vk) if i < renderer.renderPass.subpasses.len - 1: diff -r fb22fd8142b9 -r 6a09fe5dc99b src/semicongine/resources/mesh.nim --- a/src/semicongine/resources/mesh.nim Wed Sep 20 22:02:20 2023 +0700 +++ b/src/semicongine/resources/mesh.nim Wed Sep 20 22:35:04 2023 +0700 @@ -226,9 +226,10 @@ # FIX: materialIndex is designed to support multiple different materials per mesh (as it is per vertex), # but or current mesh/rendering implementation is only designed for a single material # currently this is usually handled by adding the values as shader globals - mesh[].material = material + # TODO: this is bad + mesh[].materials = @[material] else: - mesh[].material = DEFAULT_MATERIAL + mesh[].materials = @[DEFAULT_MATERIAL] if primitiveNode.hasKey("indices"): assert mesh[].indexType != None diff -r fb22fd8142b9 -r 6a09fe5dc99b src/semicongine/text.nim --- a/src/semicongine/text.nim Wed Sep 20 22:02:20 2023 +0700 +++ b/src/semicongine/text.nim Wed Sep 20 22:35:04 2023 +0700 @@ -106,10 +106,10 @@ result.mesh = newMesh(positions = positions, indices = indices, uvs = uvs) result.mesh[].renameAttribute("position", POSITION_ATTRIB) result.mesh[].renameAttribute("uv", UV_ATTRIB) - result.mesh.material = Material( + result.mesh.materials = @[Material( name: TEXT_MATERIAL, textures: {"fontAtlas": font.fontAtlas}.toTable, - ) + )] result.updateMesh() diff -r fb22fd8142b9 -r 6a09fe5dc99b tests/test_materials.nim --- a/tests/test_materials.nim Wed Sep 20 22:02:20 2023 +0700 +++ b/tests/test_materials.nim Wed Sep 20 22:35:04 2023 +0700 @@ -21,7 +21,7 @@ proc main() = var flag = rect() - flag.material = material + flag.materials = @[material] var scene = Scene(name: "main", meshes: @[flag]) scene.addShaderGlobalArray("test2", @[0'f32, 0'f32]) diff -r fb22fd8142b9 -r 6a09fe5dc99b tests/test_mesh.nim --- a/tests/test_mesh.nim Wed Sep 20 22:02:20 2023 +0700 +++ b/tests/test_mesh.nim Wed Sep 20 22:35:04 2023 +0700 @@ -58,12 +58,11 @@ scene.addShaderGlobal("view", Unit4F32) var materials: Table[uint16, Material] for mesh in scene.meshes: - if not materials.contains(mesh.material.index): - materials[mesh.material.index] = mesh.material + for material in mesh.materials: + if not materials.contains(material.index): + materials[material.index] = material let baseColors = sortedByIt(values(materials).toSeq, it.index).mapIt(getValue[Vec4f](it.constants["baseColorFactor"], 0)) let baseTextures = sortedByIt(values(materials).toSeq, it.index).mapIt(it.textures["baseColorTexture"]) - for t in baseTextures: - echo "- ", t scene.addShaderGlobalArray("baseColorFactor", baseColors) scene.addShaderGlobalArray("baseColorTexture", baseTextures) engine.addScene(scene) diff -r fb22fd8142b9 -r 6a09fe5dc99b tests/test_vulkan_wrapper.nim --- a/tests/test_vulkan_wrapper.nim Wed Sep 20 22:02:20 2023 +0700 +++ b/tests/test_vulkan_wrapper.nim Wed Sep 20 22:35:04 2023 +0700 @@ -124,9 +124,9 @@ var r = rect(color="ff0000") var t = tri(color="0000ff") var c = circle(color="00ff00") - r.material = mat - t.material = mat - c.material = mat + r.materials = @[mat] + t.materials = @[mat] + c.materials = @[mat] r.transform = translate(newVec3f(0.5, -0.3)) t.transform = translate(newVec3f(0.3, 0.3)) c.transform = translate(newVec3f(-0.3, 0.1)) @@ -147,8 +147,8 @@ var r1 = rect(color="ffffff") r2 = rect(color="000000") - r1.material = mat - r2.material = mat3 + r1.materials = @[mat] + r2.materials = @[mat3] r1.transform = translate(newVec3f(-0.5)) r2.transform = translate(newVec3f(+0.5)) result = @[r1, r2]