Implement shape keys, vertex modifier support. Fix related bugs.

This commit is contained in:
Alberto Torres 2024-12-16 21:02:13 +01:00
parent 12940dad1d
commit 0192db3f67
8 changed files with 164 additions and 27 deletions

View file

@ -234,6 +234,7 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
fragment_lines.insert(material.scene.get_lighting_code_defines.join("\n"), def_index)
if material.fragment == "":
fragment_lines.add "void main(){}"
var modifier_ubos: seq[UBO]
var vs: string
if self.material.vertex != "":
vs = self.material.vertex
@ -253,12 +254,14 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
var modifiers_uniforms, modifiers_bodies, modifiers_post_bodies: seq[string]
var required_extensions: Table[string, bool]
for m in modifiers:
let (uniform_lines, body_lines, post_transform_lines, extensions) = m.get_code(self.material.varyings)
modifiers_uniforms &= uniform_lines
modifiers_bodies &= body_lines
modifiers_post_bodies &= post_transform_lines
for e in extensions:
let code_lines = m.get_code(self.material.varyings)
modifiers_uniforms &= code_lines.uniform_lines
modifiers_bodies &= code_lines.body_lines
modifiers_post_bodies &= code_lines.post_transform_lines
for e in code_lines.extensions:
required_extensions[e] = true
if m.ubo != nil:
modifier_ubos.add m.ubo
var extension_lines: seq[string]
for e in keys(required_extensions):
extension_lines.add(&"#extension {e} : require")
@ -480,7 +483,7 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
var ubo_names = newSeqOfCap[string](material.ubos.len)
self.ubos.setLen 0
for ubo in material.ubos:
for ubo in material.ubos & modifier_ubos:
let idx = ubo.get_index prog
if idx == GL_INVALID_INDEX:
if ubo.byte_storage.len != 0:

View file

@ -261,6 +261,10 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren
self.set_flip_normals mesh.flip
let data {.cursor.} = amesh.data
for vmod in mesh.vertex_modifiers:
if vmod.update.nonNil:
vmod.update(mesh)
# unbindAllTextures()
# Main routine for each submesh

View file

@ -190,9 +190,10 @@ template is_valid*(block_index: GLuint): bool =
block_index != GL_INVALID_INDEX
proc destroy*(self: UBO) =
self.unbind()
self.byte_storage = nil
self.buffer = 0.GPUBuffer
if self != nil:
self.unbind()
self.byte_storage = nil
self.buffer = 0.GPUBuffer
# TODO: make a function/template/macro that generates GLSL code to declare the UBO from an object

View file

@ -348,6 +348,7 @@ proc cstr*(n: FNode): cstring =
template str*(n: FNode): string = $(n.cstr)
template valid*(n: FNode): bool = n.p != nil
template nonNil*(n: FNode): bool = n.p != nil
template isNil*(n: FNode): bool = n.p == nil
proc strip2*(s: string): string {.inline.} =

View file

@ -44,6 +44,7 @@ import ../attributes
import ./blend_format
import ../util
import ../objects/mesh
import ../modifiers/shape_keys
template vec2(p: ptr array[16, float32]): Vec2 = cast[ptr Vec2](p)[]
template vec3(p: ptr array[16, float32]): Vec3 = cast[ptr Vec3](p)[]
@ -64,6 +65,18 @@ type AttrData = object
count: int8
buffer: ArrRef[int8]
proc pack_normal(v: Vec3): float32 =
let x = clamp(int32(v.x * 127), -127, 127)
let y = clamp(int32((v.y + 1.0) * 127.5), 0, 255)
let z = clamp(int32((v.z + 1.0) * 127.5), 0, 255)
let i = 0x3f800000'i32 or
((x and 0x80) shl 24) or
((abs(x)) shl 16) or
((y) shl 8) or
(z)
let f = cast[float32](i)
return f - (if f >= 0.0: 1.0 else: -1.0)
proc equals[T](a: ArrRef[T], s:seq[T], offset: int): bool =
for i, v in a:
if a[i] != s[offset+i]: return false
@ -165,14 +178,14 @@ proc interleave_attributes(attributes: someArr[AttrData], indices: someArr[int32
proc calculate_normals(vertices: ArrRef[Vec3], indices, loop_sizes: ArrRef[int32]): seq[Vec3] =
proc calculate_normals(vertices: ArrRef[Vec3] | ArrRef[Vec4], indices, loop_sizes: ArrRef[int32]): seq[Vec3] =
result.setLen vertices.len
var pos = 0
var first, prev = vec3()
for ls in loop_sizes:
var n_sum = vec3()
for i in 0 ..< ls:
var v = vertices[indices[i+pos]]
var v = vertices[indices[i+pos]].xyz
case i:
of 0:
first = v
@ -196,11 +209,13 @@ proc loadMeshImpl*(self: BlendLoader, obn, mesh: FNode, ob: GameObject,
var vertices = newArrRef[Vec3](mesh.totvert.i32[0])
var kb = mesh.key.`block`.first
if kb.isNil:
# TODO: delay this until after position attribute has been read?
if mesh.mvert.valid:
for i,co in mesh.mvert[0 ..< vertices.len].co:
vertices[i] = co.f32.vec3
else:
# Use basis as source of "original" vertices
# TODO: delay this until after position attribute has been read?
let data = kb.data.get_array(vertices.len, Vec3)
for i in 0 ..< vertices.len:
vertices[i] = data[i]
@ -303,17 +318,18 @@ proc loadMeshImpl*(self: BlendLoader, obn, mesh: FNode, ob: GameObject,
of ".corner_edge":
discard
else:
echo "Unknown int32 loop attribute, name ",layer.name.str
echo "Unknown int32 loop attribute, name ", repr(layer.name.str)
of 50: # bool
case layer.name.str.split(".")[1]:
of "vs":
# UV map of all zeros or all ones
# TODO: use static value instead of ignoring
discard
else:
echo "Unknown bool loop attribute, name ",layer.name.str
discard
# case layer.name.str.split(".")[1]:
# of "vs", "es":
# # UV map of all zeros or all ones
# # TODO: use static value instead of ignoring
# discard
# else:
# echo "Unknown bool loop attribute, name ", repr(layer.name.str)
else:
echo "Unknown loop attribute type ", layer["type"].i32[0],", name ",layer.name.str
echo "Unknown loop attribute type ", layer["type"].i32[0],", name ", repr(layer.name.str)
# dump data
for layer in mesh.vdata.arr_len(layers, totlayer):
# echo layer["type"].i32[0]," v ",layer.name.str
@ -377,7 +393,7 @@ proc loadMeshImpl*(self: BlendLoader, obn, mesh: FNode, ob: GameObject,
used_materials[m] = true
materials[i] = m.int16
else:
echo "Unknown int32 loop attribute, name ",layer.name.str
echo "Unknown int32 loop attribute, name ", repr(layer.name.str)
of 50: # bool
case layer.name.str:
of ".select_poly":
@ -416,14 +432,45 @@ proc loadMeshImpl*(self: BlendLoader, obn, mesh: FNode, ob: GameObject,
# for dv in mesh.dvert[0 ..< vertices.len]:
# dump dv
# TODO: shape keys
# shape keys
var shape_keys: OrderedTable[string, float32]
var shape_key_arrays: seq[ArrRef[Vec4]]
kb = mesh.key.`block`.first
if kb.valid:
kb = kb.next
while kb.valid:
let data = kb.data.get_array(vertices.len, Vec3)
var out_array = newArrRef[Vec4](vertices.len)
for i in 0 ..< vertices.len:
out_array[i] = vec4(data[i], 0.0)
shape_key_arrays.add out_array
shape_keys[kb.name.str] = kb.curval.f32[0]
kb = kb.next
# calculate normals
let normals = calculate_normals(vertices, indices, loop_sizes)
var normals_byte = newArrRef[i8vec4](normals.len)
for i,n in normals:
normals_byte[i] = [int8(n.x*127), int8(n.y*127), int8(n.z*127), 0]
# calculate normals of shape keys
for shape in shape_key_arrays:
let normals = calculate_normals(shape, indices, loop_sizes)
for i,v in shape.mpairs:
v.w = pack_normal(normals[i])
# make relative and add shape keys attributes
for j,shape in shape_key_arrays:
for i,v in shape.mpairs:
v -= vec4(vertices[i], 0.0)
attributes.add AttrData(
name: "shape" & $j, domain: DVertex,
dtype: Float, count: 4,
buffer: shape.to(int8),
)
# add empty tangents (to be calculated later)
if required_tangents.len != 0:
var tg_attributes: seq[AttrData]
@ -542,6 +589,9 @@ proc loadMeshImpl*(self: BlendLoader, obn, mesh: FNode, ob: GameObject,
final_ia.add newArrRef(ia_per_material[i])
index_types.add UInt
ob.get_mesh.shape_keys = shape_keys
if shape_keys.len != 0:
ob.get_mesh.add_modifier(ob.engine.newShapeKeyModifier(shape_keys.len))
ob.get_mesh.generate_tangents = required_tangents.len != 0
ob.get_mesh.load_from_va_ia(final_va, final_ia, layout, index_types)
# ob.get_mesh.debug_print_vertices(0,10)

View file

@ -0,0 +1,74 @@
import ../types
import ../graphics/ubo
import ../util
import std/strformat
import std/strutils
import std/tables
proc newShapeKeyModifier*(engine: MyouEngine, count: int): VertexModifier =
let float_count = count.align4
result.get_code = proc(v: seq[Varying]): VertexModifierCodeLines =
result.uniform_lines = @[
"layout(std140) uniform ShapeKeys {",
&" vec4 shape_values[{float_count div 4}];",
"};",
# 256/255 = 1.0039216 = 2.0078433 / 2.0
# which helps us remap 0 to -1 and 255 to 1.
"""
vec3 unpack(float f){
float x = f;
float y = fract(abs(x)*128.0);
float z = fract(y*256.0);
y = y * 2.0078433 - 1.0;
z = z * 2.0078433 - 1.0;
return vec3(x,y,z);
}
""".dedent,
]
var body = @[
# Equivalent to /= 127.0, and roughly to normalize byte normals
"normal *= 0.007874;",
"float relf = 0.0;",
"vec3 co0 = co.xyz, orig_n = normal;",
]
for i in 0 ..< count:
let i4 = i div 4
body &= @[
&"co += vec4(shape{i}.xyz, 0.0) * shape_values[{i4}][{i and 3}];",
&"float shape{i}use = min(1.0, length(shape{i}.xyz) * 1000.0);",
&"relf += abs(shape_values[{i4}][{i and 3}]) * shape{i}use;",
]
# Interpolating normals instead of re-calculating them is wrong
# But it's fast, completely unnoticeable in most cases,
# and better than just not changing them (as many engines do)
# body &= "normal *= clamp(1.0 - relf, 0.0, 1.0);"
body &= "vec3 snormal = normal * 0.001;"
body &= "#define DEBUG1 0.001"
body &= "#define DEBUG2 0.001"
body &= "#define DEBUG3 0.001"
for i in 0 ..< count:
let i4 = i div 4
# I substract 0.001 to avoid using noise near 0 which makes normals
# change in places where a shape key is not affecting
body &= &"""
snormal += unpack(shape{i}.w) * max(0.0, shape_values[{i4}][{i and 3}] * shape{i}use)
;"""
body &= "normal = mix(normal, snormal, clamp(relf, 0.0, 1.0));"
result.body_lines = body
let ubo = engine.renderer.newUBO("ShapeKeys", float32, float_count)
result.ubo = ubo
result.update = proc(m: Mesh) =
var data = ubo.storage(float32)
var i = 0
for k,v in m.shape_keys:
data[i] = v
i.inc
ubo.unbind()
ubo.update()

View file

@ -578,9 +578,7 @@ proc destroy*(self: GameObject, recursive: bool = true) =
var child = child
child.destroy(true)
self.engine.objects.del(self.name)
self.object_render_ubo.unbind()
self.object_render_ubo.destroy()
self.object_ubo.unbind()
self.object_ubo.destroy()
proc convert_bone_child_to_bone_parent*(self: GameObject) =

View file

@ -207,6 +207,7 @@ type
layout*: AttributeList
vertex_modifiers*: seq[VertexModifier]
mesh_id*: int8
shape_keys*: OrderedTable[string, float32]
bone_index_maps*: seq[Table[int32,int32]]
sort_sign*: SortSign
# related_textures*: seq[unknown]
@ -502,7 +503,6 @@ type
UBO* = ref object
renderer* {.cursor.}: RenderManager
name*: string
size32*: int
byte_storage*: ArrRef[byte]
buffer*: GPUBuffer
binding_point*: int32
@ -889,9 +889,15 @@ type
rect*: (float32,float32,float32,float32)
rect_pix*: (int32,int32,int32,int32)
VertexModifierCodeLines* = object
uniform_lines*, body_lines*, post_transform_lines*: seq[string]
extensions*: seq[string]
VertexModifier* = object of RootObj
get_code*: proc(v: seq[Varying]): (seq[string], seq[string], seq[string], seq[string])
get_code*: proc(v: seq[Varying]): VertexModifierCodeLines
prepare_mesh*: proc(m: Mesh)
ubo*: UBO
update*: proc(m: Mesh)
AnimationStrip* = object