# HG changeset patch # User Sam # Date 1693762277 -25200 # Node ID 59c54c4486c4d8eb17028eaf12f992819b20a691 # Parent c31e42d72253c340c18cc75c9e1a7d0745bcba01 fix: material handling, gltf loading, loader example diff -r c31e42d72253 -r 59c54c4486c4 src/semicongine/core/gpu_data.nim --- a/src/semicongine/core/gpu_data.nim Sun Sep 03 17:46:40 2023 +0700 +++ b/src/semicongine/core/gpu_data.nim Mon Sep 04 00:31:17 2023 +0700 @@ -1,15 +1,15 @@ import std/strformat import std/tables -import std/hashes import ./vulkanapi import ./vector import ./matrix import ./utils +import ./imagetypes type Sampler2DType* = object - GPUType* = float32 | float64 | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 | TVec2[int32] | TVec2[int64] | TVec3[int32] | TVec3[int64] | TVec4[int32] | TVec4[int64] | TVec2[uint32] | TVec2[uint64] | TVec3[uint32] | TVec3[uint64] | TVec4[uint32] | TVec4[uint64] | TVec2[float32] | TVec2[float64] | TVec3[float32] | TVec3[float64] | TVec4[float32] | TVec4[float64] | TMat2[float32] | TMat2[float64] | TMat23[float32] | TMat23[float64] | TMat32[float32] | TMat32[float64] | TMat3[float32] | TMat3[float64] | TMat34[float32] | TMat34[float64] | TMat43[float32] | TMat43[float64] | TMat4[float32] | TMat4[float64] | Sampler2DType + GPUType* = float32 | float64 | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 | TVec2[int32] | TVec2[int64] | TVec3[int32] | TVec3[int64] | TVec4[int32] | TVec4[int64] | TVec2[uint32] | TVec2[uint64] | TVec3[uint32] | TVec3[uint64] | TVec4[uint32] | TVec4[uint64] | TVec2[float32] | TVec2[float64] | TVec3[float32] | TVec3[float64] | TVec4[float32] | TVec4[float64] | TMat2[float32] | TMat2[float64] | TMat23[float32] | TMat23[float64] | TMat32[float32] | TMat32[float64] | TMat3[float32] | TMat3[float64] | TMat34[float32] | TMat34[float64] | TMat43[float32] | TMat43[float64] | TMat4[float32] | TMat4[float64] | Texture DataType* = enum Float32 Float64 @@ -98,7 +98,7 @@ of Mat43F64: mat43f64: TMat43[float64] of Mat4F32: mat4f32: TMat4[float32] of Mat4F64: mat4f64: TMat4[float64] - of Sampler2D: discard + of Sampler2D: texture: Texture DataList* = object len*: int case theType*: DataType @@ -142,9 +142,9 @@ of Mat34F64: mat34f64: seq[TMat34[float64]] of Mat43F32: mat43f32: seq[TMat43[float32]] of Mat43F64: mat43f64: seq[TMat43[float64]] - of Mat4F32: mat4f32*: seq[TMat4[float32]] + of Mat4F32: mat4f32: seq[TMat4[float32]] of Mat4F64: mat4f64: seq[TMat4[float64]] - of Sampler2D: discard + of Sampler2D: texture: seq[Texture] MemoryPerformanceHint* = enum PreferFastRead, PreferFastWrite ShaderAttribute* = object @@ -201,7 +201,7 @@ of Mat43F64: return a.mat43f64 == b.mat43f64 of Mat4F32: return a.mat4f32 == b.mat4f32 of Mat4F64: return a.mat4f64 == b.mat4f64 - of Sampler2D: raise newException(Exception, "'==' not defined for Sampler2D") + of Sampler2D: a.texture == b.texture func vertexInputs*(attributes: seq[ShaderAttribute]): seq[ShaderAttribute] = for attr in attributes: @@ -334,7 +334,7 @@ elif T is TMat43[float64]: Mat43F64 elif T is TMat4[float32]: Mat4F32 elif T is TMat4[float64]: Mat4F64 - elif T is Sampler2DType: Sampler2D + elif T is Texture: Sampler2D else: static: raise newException(Exception, &"Unsupported data type for GPU data: {name(T)}" ) @@ -404,6 +404,7 @@ elif T is TMat43[float64]: value.mat43f64 elif T is TMat4[float32]: value.mat4f32 elif T is TMat4[float64]: value.mat4f64 + elif T is Texture: value.texture else: {.error: "Virtual datatype has no value" .} func setValues*[T: GPUType|int|uint|float](value: var DataList, data: seq[T]) = @@ -456,6 +457,7 @@ elif T is TMat43[float64]: value.mat43f64 = data elif T is TMat4[float32]: value.mat4f32 = data elif T is TMat4[float64]: value.mat4f64 = data + elif T is Texture: value.texture = data else: {. error: "Virtual datatype has no values" .} func newDataList*(theType: DataType): DataList = @@ -571,6 +573,7 @@ elif T is TMat43[float64]: value.mat43f64 elif T is TMat4[float32]: value.mat4f32 elif T is TMat4[float64]: value.mat4f64 + elif T is Texture: value.texture else: {. error: "Virtual datatype has no values" .} func getValue*[T: GPUType|int|uint|float](value: DataList, i: int): T = @@ -622,6 +625,7 @@ elif T is TMat43[float64]: value.mat43f64[i] elif T is TMat4[float32]: value.mat4f32[i] elif T is TMat4[float64]: value.mat4f64[i] + elif T is Texture: value.texture[i] else: {. error: "Virtual datatype has no values" .} func getRawData*(value: var DataValue): (pointer, int) = @@ -816,6 +820,7 @@ elif T is TMat43[float64]: value.mat43f64 = data elif T is TMat4[float32]: value.mat4f32 = data elif T is TMat4[float64]: value.mat4f64 = data + elif T is Texture: value.texture = data else: {.error: "Virtual datatype has no value" .} func appendValues*[T: GPUType|int|uint|float](value: var DataList, data: seq[T]) = @@ -868,6 +873,7 @@ elif T is TMat43[float64]: value.mat43f64.add data elif T is TMat4[float32]: value.mat4f32.add data elif T is TMat4[float64]: value.mat4f64.add data + elif T is Texture: value.texture.add data else: {. error: "Virtual datatype has no values" .} func appendValues*(value: var DataList, data: DataList) = @@ -1015,6 +1021,7 @@ elif T is TMat43[float64]: value.mat43f64 = data elif T is TMat4[float32]: value.mat4f32 = data elif T is TMat4[float64]: value.mat4f64 = data + elif T is Texture: value.texture = data else: {. error: "Virtual datatype has no values" .} func setValue*[T: GPUType|int|uint|float](value: var DataList, i: int, data: T) = @@ -1067,6 +1074,7 @@ elif T is TMat43[float64]: value.mat43f64[i] = data elif T is TMat4[float32]: value.mat4f32[i] = data elif T is TMat4[float64]: value.mat4f64[i] = data + elif T is Texture: value.texture[i] = data else: {. error: "Virtual datatype has no values" .} const TYPEMAP = { diff -r c31e42d72253 -r 59c54c4486c4 src/semicongine/core/imagetypes.nim --- a/src/semicongine/core/imagetypes.nim Sun Sep 03 17:46:40 2023 +0700 +++ b/src/semicongine/core/imagetypes.nim Mon Sep 04 00:31:17 2023 +0700 @@ -46,10 +46,17 @@ for x in 0 ..< width: result[x, y] = fill -let EMPTYTEXTURE* = Texture(image: newImage(1, 1, @[[255'u8, 0'u8, 255'u8, 255'u8]]), sampler: Sampler( +let INVALID_TEXTURE* = 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( + magnification: VK_FILTER_NEAREST, + minification: VK_FILTER_NEAREST, + wrapModeS: VK_SAMPLER_ADDRESS_MODE_REPEAT, + wrapModeT: VK_SAMPLER_ADDRESS_MODE_REPEAT, + ) +) diff -r c31e42d72253 -r 59c54c4486c4 src/semicongine/mesh.nim --- a/src/semicongine/mesh.nim Sun Sep 03 17:46:40 2023 +0700 +++ b/src/semicongine/mesh.nim Mon Sep 04 00:31:17 2023 +0700 @@ -35,6 +35,7 @@ name*: string constants*: Table[string, DataList] textures*: Table[string, Texture] + index*: uint16 # optional, may be used to index into uniform arrays in shader let EMPTY_MATERIAL = Material( name: "empty material" @@ -300,7 +301,7 @@ proc clearDirtyAttributes*(mesh: var MeshObject) = mesh.dirtyAttributes.reset -proc transform*[T: GPUType](mesh: MeshObject, attribute: string, transform: Mat4) = +proc transform*[T: GPUType](mesh: var MeshObject, attribute: string, transform: Mat4) = if mesh.vertexData.contains(attribute): for i in 0 ..< mesh.vertexData[attribute].len: setValue(mesh.vertexData[attribute], i, transform * getValue[T](mesh.vertexData[attribute], i)) @@ -310,6 +311,12 @@ else: raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") +func getCollisionPoints*(mesh: MeshObject, positionAttribute="position"): seq[Vec3f] = + for p in getAttribute[Vec3f](mesh, positionAttribute): + result.add mesh.transform * p + +# GENERATORS ============================================================================ + proc rect*(width=1'f32, height=1'f32, color="ffffffff"): Mesh = result = Mesh( vertexCount: 4, @@ -358,6 +365,25 @@ result[].initVertexAttribute("position", pos) result[].initVertexAttribute("color", col) -func getCollisionPoints*(mesh: MeshObject, positionAttribute="position"): seq[Vec3f] = - for p in getAttribute[Vec3f](mesh, positionAttribute): - result.add mesh.transform * p +# MESH TREES ============================================================================= + +type + MeshTree* = ref object + mesh*: Mesh + transform*: Mat4 = Unit4F32 + children*: seq[MeshTree] + +proc toSeq*(tree: MeshTree): seq[Mesh] = + var queue = @[tree] + while queue.len > 0: + var current = queue.pop + if not current.mesh.isNil: + result.add current.mesh + queue.add current.children + +proc updateTransforms*(tree: MeshTree, parentTransform=Unit4F32) = + let currentTransform = parentTransform * tree.transform + if not tree.mesh.isNil: + tree.mesh.transform = currentTransform + for child in tree.children: + child.updateTransforms(currentTransform) diff -r c31e42d72253 -r 59c54c4486c4 src/semicongine/renderer.nim --- a/src/semicongine/renderer.nim Sun Sep 03 17:46:40 2023 +0700 +++ b/src/semicongine/renderer.nim Mon Sep 04 00:31:17 2023 +0700 @@ -91,13 +91,17 @@ if not foundMatch: return (true, &"shader uniform '{uniform.name}' was not found in scene globals or scene materials") for sampler in pipeline.samplers: - var foundMatch = true - for name, value in material.textures: - if name == sampler.name: - foundMatch = true - break - if not foundMatch: - return (true, &"Required texture for shader sampler '{sampler.name}' was not found in scene materials") + if scene.shaderGlobals.contains(sampler.name): + if scene.shaderGlobals[sampler.name].theType != sampler.theType: + return (true, &"shader sampler '{sampler.name}' needs type {sampler.theType} but scene global is of type {scene.shaderGlobals[sampler.name].theType}") + else: + var foundMatch = true + for name, value in material.textures: + if name == sampler.name: + foundMatch = true + break + if not foundMatch: + return (true, &"Required texture for shader sampler '{sampler.name}' was not found in scene materials") return (false, "") @@ -109,8 +113,10 @@ return (true, &"Shader input '{input.name}' is not available for mesh '{mesh}'") if input.theType != mesh[].attributeType(input.name): return (true, &"Shader input '{input.name}' expects type {input.theType}, but mesh '{mesh}' has {mesh[].attributeType(input.name)}") - if input.perInstance != mesh[].instanceAttributes.contains(input.name): - return (true, &"Shader input '{input.name}' expects to be per instance, but mesh '{mesh}' has is not as instance attribute") + if not input.perInstance and not mesh[].vertexAttributes.contains(input.name): + return (true, &"Shader input '{input.name}' expected to be vertex attribute, but mesh has no such vertex attribute (available are: {mesh[].vertexAttributes})") + 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) @@ -150,9 +156,20 @@ if not scenedata.materials.contains(mesh.material): scenedata.materials.add mesh.material for textureName, texture in mesh.material.textures.pairs: - if not scenedata.textures.hasKey(textureName): - scenedata.textures[textureName] = @[] - scenedata.textures[textureName].add renderer.device.uploadTexture(texture) + 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: + assert not scenedata.textures.contains(name) # should be handled by the above code + scenedata.textures[name] = @[] + for texture in getValues[Texture](value): + scenedata.textures[name].add renderer.device.uploadTexture(texture) + # find all meshes, populate missing attribute values for shader for mesh in scene.meshes.mitems: diff -r c31e42d72253 -r 59c54c4486c4 src/semicongine/resources/mesh.nim --- a/src/semicongine/resources/mesh.nim Sun Sep 03 17:46:40 2023 +0700 +++ b/src/semicongine/resources/mesh.nim Mon Sep 04 00:31:17 2023 +0700 @@ -19,9 +19,6 @@ glTFData = object structuredContent: JsonNode binaryBufferData: seq[uint8] - MeshTree* = ref object - mesh*: MeshObject - children*: seq[MeshTree] const JSON_CHUNK = 0x4E4F534A @@ -152,8 +149,7 @@ proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: seq[uint8], materialIndex: uint16): Material = - result = Material(name: materialNode["name"].getStr()) - + result = Material(name: materialNode["name"].getStr(), index: materialIndex) let pbr = materialNode["pbrMetallicRoughness"] # color @@ -183,7 +179,7 @@ result.constants[texture & "Index"] = newDataList(thetype=UInt8) setValue(result.constants[texture & "Index"], @[pbr[texture].getOrDefault("texCoord").getInt(0).uint8]) else: - result.textures[texture] = EMPTYTEXTURE + result.textures[texture] = EMPTY_TEXTURE result.constants[texture & "Index"] = newDataList(thetype=UInt8) setValue(result.constants[texture & "Index"], @[0'u8]) @@ -194,7 +190,7 @@ result.constants[texture & "Index"] = newDataList(thetype=UInt8) setValue(result.constants[texture & "Index"], @[materialNode[texture].getOrDefault("texCoord").getInt(0).uint8]) else: - result.textures[texture] = EMPTYTEXTURE + result.textures[texture] = EMPTY_TEXTURE result.constants[texture & "Index"] = newDataList(thetype=UInt8) setValue(result.constants[texture & "Index"], @[0'u8]) @@ -209,30 +205,31 @@ else: setValue(result.constants["emissiveFactor"], @[newVec3f(1'f32, 1'f32, 1'f32)]) - -proc addPrimitive(mesh: var MeshObject, root: JsonNode, primitiveNode: JsonNode, mainBuffer: seq[uint8]) = +proc addPrimitive(mesh: Mesh, root: JsonNode, primitiveNode: JsonNode, mainBuffer: seq[uint8]) = if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4: raise newException(Exception, "Currently only TRIANGLE mode is supported for geometry mode") var vertexCount = 0 for attribute, accessor in primitiveNode["attributes"].pairs: let data = root.getAccessorData(root["accessors"][accessor.getInt()], mainBuffer) - mesh.appendAttributeData(attribute.toLowerAscii, data) + mesh[].appendAttributeData(attribute.toLowerAscii, data) + assert data.len == vertexCount or vertexCount == 0 vertexCount = data.len var materialId = 0'u16 if primitiveNode.hasKey("material"): materialId = uint16(primitiveNode["material"].getInt()) - mesh.appendAttributeData("materialIndex", newSeqWith[uint8](vertexCount, materialId)) + mesh[].appendAttributeData("materialIndex", newSeqWith(vertexCount, materialId)) let material = loadMaterial(root, root["materials"][int(materialId)], mainBuffer, materialId) - # if mesh.material != nil and mesh.material[] != material[]: - # raise newException(Exception, &"Only one material per mesh supported at the moment") - mesh.material = material + # 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 if primitiveNode.hasKey("indices"): - assert mesh.indexType != None + assert mesh[].indexType != None let data = root.getAccessorData(root["accessors"][primitiveNode["indices"].getInt()], mainBuffer) - let baseIndex = mesh.indicesCount + let baseIndex = mesh[].indicesCount var tri: seq[int] case data.thetype of UInt16: @@ -240,20 +237,20 @@ tri.add int(entry) + baseIndex if tri.len == 3: # FYI gltf uses counter-clockwise indexing - mesh.appendIndicesData(tri[0], tri[2], tri[1]) + mesh[].appendIndicesData(tri[0], tri[1], tri[2]) tri.setLen(0) of UInt32: for entry in getValues[uint32](data): tri.add int(entry) if tri.len == 3: # FYI gltf uses counter-clockwise indexing - mesh.appendIndicesData(tri[0], tri[2], tri[1]) + mesh[].appendIndicesData(tri[0], tri[1], tri[2]) tri.setLen(0) else: raise newException(Exception, &"Unsupported index data type: {data.thetype}") # TODO: use one mesh per primitive?? right now we are merging primitives... check addPrimitive below -proc loadMesh(root: JsonNode, meshNode: JsonNode, mainBuffer: seq[uint8]): MeshObject = +proc loadMesh(root: JsonNode, meshNode: JsonNode, mainBuffer: seq[uint8]): Mesh = # check if and how we use indexes var indexCount = 0 @@ -267,7 +264,7 @@ else: indexType = Big - result = MeshObject(instanceTransforms: @[Unit4F32], indexType: indexType) + result = Mesh(instanceTransforms: @[Unit4F32], indexType: indexType) # check we have the same attributes for all primitives let attributes = meshNode["primitives"][0]["attributes"].keys.toSeq @@ -276,14 +273,15 @@ # prepare mesh attributes for attribute, accessor in meshNode["primitives"][0]["attributes"].pairs: - result.initVertexAttribute(attribute.toLowerAscii, root["accessors"][accessor.getInt()].getGPUType()) - result.initInstanceAttribute("materialIndex", 0'u16) + result[].initVertexAttribute(attribute.toLowerAscii, root["accessors"][accessor.getInt()].getGPUType()) + result[].initVertexAttribute("materialIndex", UInt16) # add all mesh data for primitive in meshNode["primitives"]: result.addPrimitive(root, primitive, mainBuffer) proc loadNode(root: JsonNode, node: JsonNode, mainBuffer: var seq[uint8]): MeshTree = + result = MeshTree() # mesh if node.hasKey("mesh"): result.mesh = loadMesh(root, root["meshes"][node["mesh"].getInt()], mainBuffer) @@ -293,7 +291,7 @@ var mat: Mat4 for i in 0 ..< node["matrix"].len: mat[i] = node["matrix"][i].getFloat() - result.mesh.transform = mat + result.transform = mat else: var (t, r, s) = (Unit4F32, Unit4F32, Unit4F32) if node.hasKey("translation"): @@ -317,7 +315,7 @@ float32(node["scale"][1].getFloat()), float32(node["scale"][2].getFloat()) ) - result.mesh.transform = t * r * s + result.transform = t * r * s # children if node.hasKey("children"): @@ -328,6 +326,8 @@ result = MeshTree() for nodeId in scenenode["nodes"]: result.children.add loadNode(root, root["nodes"][nodeId.getInt()], mainBuffer) + result.transform = scale(1, -1, 1) + result.updateTransforms() proc readglTF*(stream: Stream): seq[MeshTree] = diff -r c31e42d72253 -r 59c54c4486c4 src/semicongine/text.nim --- a/src/semicongine/text.nim Sun Sep 03 17:46:40 2023 +0700 +++ b/src/semicongine/text.nim Mon Sep 04 00:31:17 2023 +0700 @@ -28,7 +28,7 @@ ], intermediates=[attr[Vec2f]("uvFrag")], outputs=[attr[Vec4f]("color")], - samplers=[attr[Sampler2DType]("fontAtlas")], + samplers=[attr[Texture]("fontAtlas")], vertexCode="""gl_Position = vec4(position, 1.0) * transform; uvFrag = uv;""", fragmentCode="""color = texture(fontAtlas, uvFrag);""", ) diff -r c31e42d72253 -r 59c54c4486c4 tests/test_mesh.nim --- a/tests/test_mesh.nim Sun Sep 03 17:46:40 2023 +0700 +++ b/tests/test_mesh.nim Mon Sep 04 00:31:17 2023 +0700 @@ -1,67 +1,73 @@ +import std/algorithm import std/sequtils import std/tables import semicongine proc main() = - var ent1 = newEntity("hoho", {"mesh": Component(rect())}) - var ent2 = newEntity("hehe", [], ent1) - var myScene = newScene("hi", ent2) - myScene.root.transform = translate3d(0.2'f32, 0'f32, 0'f32) - myScene.root[0].transform = translate3d(0'f32, 0.2'f32, 0'f32) + # var myScene = Scene(name: "hi", meshes: @[rect()]) + # myScene.meshes[0].transform = translate3d(0.2'f32, 0'f32, 0'f32) + # myScene.root[0].transform = translate3d(0'f32, 0.2'f32, 0'f32) var scenes = [ # loadScene("default_cube.glb", "1"), # loadScene("default_cube1.glb", "3"), # loadScene("default_cube2.glb", "4"), # loadScene("flat.glb", "5"), - loadScene("tutorialk-donat.glb", "6"), + Scene(name: "Donut", meshes: loadMeshes("tutorialk-donat.glb")[0].toSeq), # myScene, # loadScene("personv3.glb", "2"), ] var engine = initEngine("Test meshes") const - vertexInput = @[ - attr[Vec3f]("position", memoryPerformanceHint=PreferFastRead), - attr[uint16]("materialIndex", memoryPerformanceHint=PreferFastRead), - attr[Vec2f]("texcoord_0", memoryPerformanceHint=PreferFastRead), - attr[Mat4]("transform", memoryPerformanceHint=PreferFastWrite, perInstance=true), - ] - intermediate = @[ - attr[Vec4f]("vertexColor"), - attr[Vec2f]("colorTexCoord"), - attr[uint16]("materialIndexOut", noInterpolation=true) - ] - fragOutput = @[attr[Vec4f]("color")] - uniforms = @[ - attr[Mat4]("projection"), - attr[Mat4]("view"), - attr[Vec4f]("baseColorFactor", arrayCount=4), - ] - samplers = @[attr[Sampler2DType]("baseColorTexture", arrayCount=4)] - (vertexCode, fragmentCode) = compileVertexFragmentShaderSet( - inputs=vertexInput, - intermediate=intermediate, - outputs=fragOutput, - uniforms=uniforms, - samplers=samplers, + shaderConfiguration = createShaderConfiguration( + inputs=[ + attr[Vec3f]("position", memoryPerformanceHint=PreferFastRead), + attr[uint16]("materialIndex", memoryPerformanceHint=PreferFastRead), + attr[Vec2f]("texcoord_0", memoryPerformanceHint=PreferFastRead), + attr[Mat4]("transform", memoryPerformanceHint=PreferFastWrite, perInstance=true), + ], + intermediates=[ + attr[Vec4f]("vertexColor"), + attr[Vec2f]("colorTexCoord"), + attr[uint16]("materialIndexOut", noInterpolation=true) + ], + outputs=[attr[Vec4f]("color")], + uniforms=[ + attr[Mat4]("projection"), + attr[Mat4]("view"), + attr[Vec4f]("baseColorFactor", arrayCount=4), + ], + samplers=[attr[Texture]("baseColorTexture", arrayCount=4)], vertexCode=""" -gl_Position = vec4(position, 1.0) * (transform * Uniforms.view * Uniforms.projection); -vertexColor = Uniforms.baseColorFactor[materialIndex]; -colorTexCoord = texcoord_0; -materialIndexOut = materialIndex; -""", - fragmentCode=""" -// vec4 col[4] = vec4[4](vec4(1, 0, 0, 1), vec4(0, 1, 0, 1), vec4(0, 0, 1, 1), vec4(1, 1, 1, 1)); -color = texture(baseColorTexture[materialIndexOut], colorTexCoord) * vertexColor; -""" + gl_Position = vec4(position, 1.0) * (transform * Uniforms.view * Uniforms.projection); + vertexColor = Uniforms.baseColorFactor[materialIndex]; + colorTexCoord = texcoord_0; + materialIndexOut = materialIndex; + """, + fragmentCode="color = texture(baseColorTexture[materialIndexOut], colorTexCoord) * vertexColor;" ) - engine.setRenderer(engine.gpuDevice.simpleForwardRenderPass(vertexCode, fragmentCode, clearColor=newVec4f(0, 0, 0, 1))) + engine.initRenderer({ + "Material": shaderConfiguration, + "Material.001": shaderConfiguration, + "Material.002": shaderConfiguration, + "Material.004": shaderConfiguration, + }.toTable) + for scene in scenes.mitems: - engine.addScene(scene, vertexInput, samplers) - scene.addShaderGlobal("projection", Unit4) - scene.addShaderGlobal("view", Unit4) - let baseColors = scene.materials.map(proc(i: Material): Vec4f = getValue[Vec4f](i[].constants["baseColorFactor"])) + scene.addShaderGlobal("projection", Unit4F32) + 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 + 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) + var size = 1'f32 elevation = 0'f32 @@ -94,7 +100,7 @@ scenes[currentScene].setShaderGlobal("projection", ortho(-ratio, ratio, -1, 1, -1, 1)) scenes[currentScene].setShaderGlobal( "view", - scale3d(size, size, size) * rotate3d(elevation, newVec3f(1, 0, 0)) * rotate3d(azimut, Yf32) + scale(size, size, size) * rotate(elevation, newVec3f(1, 0, 0)) * rotate(azimut, Yf32) ) engine.renderScene(scenes[currentScene]) engine.destroy()