changeset 1255:2b5ca798f6d6

did: make example town loadable and renderable, yay!
author sam <sam@basx.dev>
date Sun, 28 Jul 2024 00:17:34 +0700
parents b0f4c8ccd49a
children bfb75c934f4e
files semiconginev2/gltf.nim semiconginev2/rendering/renderer.nim tests/test_gltf.nim
diffstat 3 files changed, 80 insertions(+), 90 deletions(-) [+]
line wrap: on
line diff
--- a/semiconginev2/gltf.nim	Sat Jul 27 20:47:54 2024 +0700
+++ b/semiconginev2/gltf.nim	Sun Jul 28 00:17:34 2024 +0700
@@ -94,16 +94,25 @@
   elif t is uint32: return 5125
   elif t is float32: return 5126
 
-proc getAccessorData[T](root: JsonNode, accessor: JsonNode, mainBuffer: seq[
-    uint8]): seq[T] =
+proc componentTypeName(id: int): string =
+  if id == 5120: return int8.name
+  elif id == 5121: return uint8.name
+  elif id == 5122: return int16.name
+  elif id == 5123: return uint16.name
+  elif id == 5125: return uint32.name
+  elif id == 5126: return float32.name
+
+proc getAccessorData[T](root: JsonNode, accessor: JsonNode, mainBuffer: seq[uint8]): seq[T] =
+  if accessor.hasKey("sparse"):
+    raise newException(Exception, "Sparce accessors are currently not supported")
+
   let componentType = accessor["componentType"].getInt()
   let itemType = accessor["type"].getStr()
 
   when T is TVec or T is TMat:
-    assert componentTypeId(elementType(default(T))) == componentType, name(T) &
-        " != " & $componentType
+    assert componentTypeId(elementType(default(T))) == componentType, "Requested type '" & name(elementType(default(T))) & $componentTypeId(elementType(default(T))) & "' but actual type is '" & componentTypeName(componentType) & "'"
   else:
-    assert componentTypeId(T) == componentType, name(T) & " != " & $componentType
+    assert componentTypeId(T) == componentType, "Requested type '" & name(T) & "' but actual type is '" & componentTypeName(componentType) & "'"
 
   when T is TVec:
     when len(default(T)) == 2: assert itemType == "VEC2"
@@ -120,25 +129,18 @@
 
   let bufferView = root["bufferViews"][accessor["bufferView"].getInt()]
   assert bufferView["buffer"].getInt() == 0, "Currently no external buffers supported"
-
-  if accessor.hasKey("sparse"):
-    raise newException(Exception, "Sparce accessors are currently not supported")
-
-  let accessorOffset = if accessor.hasKey("byteOffset"): accessor[
-      "byteOffset"].getInt() else: 0
-  let length = bufferView["byteLength"].getInt()
-  let bufferOffset = bufferView["byteOffset"].getInt() + accessorOffset
+  let accessorOffset = if accessor.hasKey("byteOffset"): accessor["byteOffset"].getInt() else: 0
+  let bufferOffset = (if "byteOffset" in bufferView: bufferView["byteOffset"].getInt() else: 0) + accessorOffset
   var dstPointer = result.ToCPointer()
 
   if bufferView.hasKey("byteStride"):
     warn "Congratulations, you try to test a feature (loading buffer data with stride attributes) that we have no idea where it is used and how it can be tested (need a coresponding *.glb file)."
     # we don't support stride, have to convert stuff here... does this even work?
     for i in 0 ..< result.len:
-      copyMem(dstPointer, addr mainBuffer[bufferOffset + i * bufferView[
-          "byteStride"].getInt()], sizeof(T))
+      copyMem(dstPointer, addr(mainBuffer[bufferOffset + i * bufferView["byteStride"].getInt()]), sizeof(T))
       dstPointer = cast[typeof(dstPointer)](cast[uint](dstPointer) + sizeof(T).uint)
   else:
-    copyMem(dstPointer, addr mainBuffer[bufferOffset], length)
+    copyMem(dstPointer, addr(mainBuffer[bufferOffset]), result.len * sizeof(T))
 
 proc loadTexture(root: JsonNode, textureNode: JsonNode, mainBuffer: seq[
     uint8]): Image[BGRA] =
@@ -206,37 +208,36 @@
   if primitive.hasKey("mode"):
     result[1] = PRIMITIVE_MODE_MAP[primitive["mode"].getInt()]
 
+  if primitive.hasKey("indices"):
+    assert mapping.indices != "", "Mesh requires indices"
+
   for resultFieldName, resultValue in fieldPairs(result[0]):
     for gltfAttribute, mappedName in fieldPairs(mapping):
-      when typeof(mappedName) is string:
-        when gltfAttribute != "" and resultFieldName == mappedName:
-          assert resultValue is GPUData, "Attribute " & resultFieldName & " must be of type GPUData"
+      when typeof(mappedName) is seq:
+        when resultFieldName in mappedName:
+          var i = 0
+          for mappedIndexName in mappedName:
+            if gltfAttribute != "" and resultFieldName == mappedIndexName:
+              assert resultValue is GPUData, "Attribute " & resultFieldName & " must be of type GPUData"
+              let gltfAttributeIndexed = gltfAttribute & "_" & $i
+              if primitive["attributes"].hasKey(gltfAttributeIndexed):
+                let accessor = primitive["attributes"][gltfAttributeIndexed].getInt()
+                resultValue.data = getAccessorData[elementType(resultValue.data)](root, root["accessors"][accessor], mainBuffer)
+          inc i
+      elif typeof(mappedName) is string:
+        when resultFieldName == mappedName:
+          assert resultValue is GPUData or gltfAttribute == "material", "Attribute " & resultFieldName & " must be of type GPUData"
           when gltfAttribute == "indices":
             if primitive.hasKey(gltfAttribute):
               let accessor = primitive[gltfAttribute].getInt()
-              resultValue.data = getAccessorData[elementType(resultValue.data)](
-                  root, root["accessors"][accessor], mainBuffer)
+              resultValue.data = getAccessorData[elementType(resultValue.data)](root, root["accessors"][accessor], mainBuffer)
           elif gltfAttribute == "material":
-            if primitive.hasKey(gltfAttribute):
-              resultValue.data = typeof(resultValue.data)(primitive[
-                  gltfAttribute].getInt())
+            if primitive.hasKey(gltfAttribute): # assuming here that materials IDs are a normal field on the mesh, not GPUData
+              resultValue = typeof(resultValue)(primitive[gltfAttribute].getInt())
           else:
             if primitive["attributes"].hasKey(gltfAttribute):
               let accessor = primitive["attributes"][gltfAttribute].getInt()
-              resultValue.data = getAccessorData[elementType(resultValue.data)](
-                  root, root["accessors"][accessor], mainBuffer)
-      else:
-        var i = 0
-        for mappedIndexName in mappedName:
-          if gltfAttribute != "" and resultFieldName == mappedIndexName:
-            assert resultValue is GPUData, "Attribute " & resultFieldName & " must be of type GPUData"
-            let gltfAttributeIndexed = gltfAttribute & "_" & $i
-            if primitive["attributes"].hasKey(gltfAttributeIndexed):
-              let accessor = primitive["attributes"][
-                  gltfAttributeIndexed].getInt()
-              resultValue.data = getAccessorData[elementType(resultValue.data)](
-                  root, root["accessors"][accessor], mainBuffer)
-          inc i
+              resultValue.data = getAccessorData[elementType(resultValue.data)](root, root["accessors"][accessor], mainBuffer)
 
 proc loadNode(node: JsonNode): GltfNode =
   result = GltfNode()
@@ -304,8 +305,7 @@
   # external binary buffers are not supported
   assert data.structuredContent["buffers"].len == 1
   assert not data.structuredContent["buffers"][0].hasKey("uri")
-  let bufferLenDiff = int(chunkLength) - data.structuredContent["buffers"][0][
-      "byteLength"].getInt()
+  let bufferLenDiff = int(chunkLength) - data.structuredContent["buffers"][0]["byteLength"].getInt()
   assert 0 <= bufferLenDiff and bufferLenDiff <= 3 # binary buffer may be aligned to 4 bytes
 
   debug "Loading mesh: ", data.structuredContent.pretty
--- a/semiconginev2/rendering/renderer.nim	Sat Jul 27 20:47:54 2024 +0700
+++ b/semiconginev2/rendering/renderer.nim	Sun Jul 28 00:17:34 2024 +0700
@@ -607,10 +607,10 @@
 
 proc assertCanRenderMesh(TShader, TMesh, TInstance: typedesc) =
   for attrName, attrValue in default(TShader).fieldPairs:
-    if hasCustomPragma(attrValue, VertexAttribute):
+    when hasCustomPragma(attrValue, VertexAttribute):
       var foundAttr = false
       for meshAttrName, meshAttrValue in default(TMesh).fieldPairs:
-        if attrName == meshAttrName:
+        when attrName == meshAttrName:
           assert typeof(meshAttrValue) is GPUArray, "Mesh attribute '" & attrName & "' must be a GPUArray"
           assert typeof(attrValue) is elementType(meshAttrValue.data), "Type of shader attribute and mesh attribute '" & attrName & "' is not the same"
           foundAttr = true
--- a/tests/test_gltf.nim	Sat Jul 27 20:47:54 2024 +0700
+++ b/tests/test_gltf.nim	Sun Jul 28 00:17:34 2024 +0700
@@ -13,53 +13,58 @@
   type
     ObjectData = object
       transform: Mat4
+      materialId: int32
     Camera = object
       viewPerspective: Mat4
     Material = object
       color: Vec4f = NewVec4f(1, 1, 1, 1)
-      colorTexture: int32 = -1
+      # colorTexture: int32 = -1
       metallic: float32 = 0
       roughness: float32 = 0
-      metallicRoughnessTexture: int32 = -1
-
-      normalTexture: int32 = -1
-      occlusionTexture: int32 = -1
+      # metallicRoughnessTexture: int32 = -1
+      # normalTexture: int32 = -1
+      # occlusionTexture: int32 = -1
       emissive: Vec4f = NewVec4f(0, 0, 0, 0)
-      emissiveTexture: int32 = -1
+      # emissiveTexture: int32 = -1
     MainDescriptors = object
-      material: GPUValue[Material, UniformBuffer]
+      materials: array[32, GPUValue[Material, UniformBuffer]]
       camera: GPUValue[Camera, UniformBufferMapped]
     Shader = object
       objectData {.PushConstantAttribute.}: ObjectData
       position {.VertexAttribute.}: Vec3f
       color {.VertexAttribute.}: Vec4f
-      # uv {.VertexAttribute.}: Vec2f
+      normal {.VertexAttribute.}: Vec3f
       fragmentColor {.Pass.}: Vec4f
-      fragmentUv {.Pass.}: Vec2f
+      fragmentNormal {.Pass.}: Vec3f
       outColor {.ShaderOutput.}: Vec4f
       descriptors {.DescriptorSets.}: (MainDescriptors, )
       # code
       vertexCode: string = """
 void main() {
-  fragmentColor = color;
-  // fragmentUv = uv;
-  gl_Position = vec4(position, 1) * camera.viewPerspective;
+  fragmentColor = color * materials[objectData.materialId].color;
+  fragmentNormal = normal;
+  gl_Position = vec4(position, 1) * (objectData.transform * camera.viewPerspective);
 }"""
-      fragmentCode: string = """void main() { outColor = fragmentColor;}"""
+      fragmentCode: string = """
+const vec3 lightDir = normalize(vec3(1, -1, 1));
+void main() {
+  outColor = vec4(fragmentColor.rgb * (1 - abs(dot(fragmentNormal, lightDir))), fragmentColor.a);
+}"""
     Mesh = object
       position: GPUArray[Vec3f, VertexBuffer]
       color: GPUArray[Vec4f, VertexBuffer]
-      uv: GPUArray[Vec2f, VertexBuffer]
-    DebugMesh = object
-      position: GPUArray[Vec3f, VertexBuffer]
-      color: GPUArray[Vec4f, VertexBuffer]
+      normal: GPUArray[Vec3f, VertexBuffer]
+      indices: GPUArray[uint32, IndexBuffer]
+      material: int32
 
   var gltfData = LoadMeshes[Mesh, Material](
     "town.glb",
     MeshAttributeNames(
       POSITION: "position",
       COLOR: @["color"],
-      TEXCOORD: @["uv"],
+      NORMAL: "normal",
+      indices: "indices",
+      material: "material",
     ),
     MaterialAttributeNames(
       baseColorFactor: "color",
@@ -80,34 +85,20 @@
       ), UniformBufferMapped)
     )
   )
+  for i in 0 ..< gltfData.materials.len:
+    descriptors.data.materials[i] = asGPUValue(gltfData.materials[i], UniformBuffer)
   for mesh in mitems(gltfData.meshes):
     for primitive in mitems(mesh):
       primitive[0].color = asGPUArray(newSeqWith(primitive[0].position.data.len, NewVec4f(1, 1, 1, 1)), VertexBuffer)
       renderdata.AssignBuffers(primitive[0])
   renderdata.AssignBuffers(descriptors)
 
-  let O = default(Vec3f)
-  let Gray = NewVec4f(0.5, 0.5, 0.5, 1)
-  var gridPos = @[O, X, O, Y, O, Z]
-  var gridColor = @[R, R, G, G, B, B]
-  for i in 0 ..< 10:
-    gridPos.add [NewVec3f(-5, -0.001, i.float32 - 5), NewVec3f(5, -0.001, i.float32 - 5)]
-    gridPos.add [NewVec3f(i.float32 - 5, -0.001, -5), NewVec3f(i.float32 - 5, -0.001, 5)]
-    gridColor.add [Gray, Gray, Gray, Gray]
-  var grid = DebugMesh(
-    position: asGPUArray(gridPos, VertexBuffer),
-    color: asGPUArray(gridColor, VertexBuffer),
-  )
-  renderdata.AssignBuffers(grid)
-
-  var pipeline = CreatePipeline[Shader](renderPass = vulkan.swapchain.renderPass, cullMode = [])
-  var debugpipeline = CreatePipeline[Shader](renderPass = vulkan.swapchain.renderPass, topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST, lineWidth=10)
+  var pipeline = CreatePipeline[Shader](renderPass = vulkan.swapchain.renderPass)
   InitDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], descriptors)
 
   renderdata.FlushAllMemory()
 
-  proc drawNode(commandbuffer: VkCommandBuffer, pipeline: Pipeline, nodeId: int,
-      transform: Mat4 = Unit4) =
+  proc drawNode(commandbuffer: VkCommandBuffer, pipeline: Pipeline, nodeId: int, transform: Mat4) =
     let nodeTransform = gltfData.nodes[nodeId].transform * transform
     if gltfData.nodes[nodeId].mesh >= 0:
       for primitive in gltfData.meshes[gltfData.nodes[nodeId].mesh]:
@@ -115,7 +106,7 @@
           commandbuffer = commandbuffer,
           pipeline = pipeline,
           mesh = primitive[0],
-          pushConstant = ObjectData(transform: nodeTransform)
+          pushConstant = ObjectData(transform: nodeTransform, materialId: primitive[0].material)
         )
     for childNode in gltfData.nodes[nodeId].children:
       drawNode(commandbuffer = commandbuffer, pipeline = pipeline, nodeId = childNode, transform = nodeTransform)
@@ -142,16 +133,13 @@
     if KeyIsDown(A): sideward -= 2
     if KeyIsDown(D): sideward += 2
 
-    let camDir = (Rotate(camPitch, X) * Rotate(camYaw, Y)) * Z
-    echo camDir
-    let camDirSide = (Rotate(camPitch, X) * Rotate(camYaw, Y)) * X
-    # echo camDir
-    # echo camDirSide
+    let camDir = (Rotate(camYaw, Y) * Rotate(camPitch, X)) * Z
+    let camDirSide = camDir.Cross(-Y).Normalized
     camPos += camDir * forward * dt
     camPos += camDirSide * sideward * dt
 
     descriptors.data.camera.data.viewPerspective = (
-      Perspective(PI/3, aspect = GetAspectRatio(), zNear = 0.001, zFar = 100) *
+      Perspective(PI/3, aspect = GetAspectRatio(), zNear = 0.1, zFar = 1) *
       Rotate(-camPitch, X) * Rotate(-camYaw, Y) * Translate(-camPos)
     )
 
@@ -164,16 +152,18 @@
         WithPipeline(commandbuffer, pipeline):
           WithBind(commandbuffer, (descriptors, ), pipeline):
             for nodeId in gltfData.scenes[0]:
-              drawNode(commandbuffer = commandbuffer, pipeline = pipeline, nodeId = nodeId)
-        WithBind(commandbuffer, (descriptors, ), pipeline):
-          WithPipeline(commandbuffer, debugpipeline):
-            Render(commandbuffer = commandbuffer, pipeline = debugpipeline, mesh = grid)
+              drawNode(
+                commandbuffer = commandbuffer,
+                pipeline = pipeline,
+                nodeId = nodeId,
+                transform = Rotate(PI / 2, Z)
+              )
 
   # cleanup
   checkVkResult vkDeviceWaitIdle(vulkan.device)
   DestroyPipeline(pipeline)
-  DestroyPipeline(debugpipeline)
   DestroyRenderData(renderdata)
+
 when isMainModule:
   var time = 1000'f32
   InitVulkan()