changeset 814:6a09fe5dc99b

add: multi-material for meshes
author Sam <sam@basx.dev>
date Wed, 20 Sep 2023 22:35:04 +0700
parents fb22fd8142b9
children 5341af5b9b2b
files src/semicongine/mesh.nim src/semicongine/renderer.nim src/semicongine/resources/mesh.nim src/semicongine/text.nim tests/test_materials.nim tests/test_mesh.nim tests/test_vulkan_wrapper.nim
diffstat 7 files changed, 41 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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:
--- 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
--- 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()
 
--- 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])
 
--- 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)
--- 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]