Skip to content

Instantly share code, notes, and snippets.

@alexmalyutindev
Last active February 21, 2024 04:36
Show Gist options
  • Save alexmalyutindev/8438b1e10d3817a1d5e6c20e35720d39 to your computer and use it in GitHub Desktop.
Save alexmalyutindev/8438b1e10d3817a1d5e6c20e35720d39 to your computer and use it in GitHub Desktop.
Vertex animation baker for Blender.
# Source
# Vertex animation textures, beanbags and boneless animations
# by Martin Donald
# https://www.youtube.com/watch?v=NQ5Dllbxbz4
import bpy
import bmesh
import mathutils
def bake_morph_textures(obj, frame_range, scale, name, output_dir):
""" Bake and export morph textures for the specified object and frame range
obj -- the object to export (frame 0 is default pose)
frame_range -- List [start frame, end frame]
scale -- fL. maximum vertex position along any axis for rescaling positions to 0..1
name -- str. prefix for output files
outup_dir -- str. output directory for mesh + textures
"""
scene_path = bpy.path.abspath("//")
if "//" in output_dir:
output_dir = output_dir.replace("//", scene_path)
pixels_pos = list()
pixels_nrm = list()
pixels_tng = list()
width = 0
for i in range(frame_range[1] - frame_range[0]):
f = i + frame_range[0]
temp_obj = new_object_from_frame(obj, f)
new_pixels = get_vertex_data_from_frame(temp_obj, scale)
width = len(new_pixels)
for pixel in new_pixels:
pixels_pos += pixel[0]
pixels_nrm += pixel[1]
pixels_tng += pixel[2]
height = frame_range[1] - frame_range[0]
write_output_image(pixels_pos, name + '_position', [width, height], output_dir)
write_output_image(pixels_nrm, name + '_normal', [width, height], output_dir)
write_output_image(pixels_tng, name + '_tangent', [width, height], output_dir)
frame_zero = new_object_from_frame(obj, 0)
create_morph_uv_set(frame_zero)
export_mesh(frame_zero, output_dir, name)
return frame_zero
def write_output_image(pixel_list, name, size, output_dir):
image = bpy. data. images.new(name, width=size[0], height=size[1])
image.pixels = pixel_list
image. save_render(output_dir + name + ".png", scene=bpy. context. scene)
def new_object_from_frame(obj, f):
""" Create a new mesh from the evaluated version of obj at frame f.
"""
context = bpy.context
scene = context.scene
scene.frame_set(f) # the correct way to set a frame and update depsgraph
# need depsgraph to access evaluated mesh attributes
dg = context.view_layer.depsgraph
eval_obj = obj.evaluated_get(dg)
duplicate = bpy.data.objects.new('frame_0', bpy.data.meshes.new_from_object(eval_obj))
return duplicate
def get_vertex_data_from_frame(obj, position_scale):
""" Given an object, return the Position, Normal and Tangent for each vertex.
"""
obj.data.calc_tangents()
vertex_data = [None] * len (obj.data.vertices)
for face in obj.data.polygons:
for vert in [obj.data.loops[i] for i in face.loop_indices]:
index = vert.vertex_index
tangent = unsign_vector(vert.tangent.copy())
normal = unsign_vector(vert.normal.copy())
position = unsign_vector(obj.data.vertices[index].co.copy() / position_scale)
# Image object expects RGBA, append A
tangent.append(1.0)
normal.append(1.0)
position.append(1.0)
vertex_data[index] = [position, normal, tangent]
return vertex_data
def unsign_vector(vec, as_list=True):
"""Rescale input vector from -1..1 to 0..1.
"""
vec += mathutils. Vector((1.0, 1.0, 1.0))
vec /= 2.0
if as_list:
return list(vec.to_tuple())
else:
return vec
def create_morph_uv_set(obj):
bm = bmesh.new()
bm.from_mesh(obj.data)
#not sure why i make two but thats also what houdini does
uv_layer = bm.loops.layers.uv.new("uv")
uv_layer2 = bm.loops.layers.uv.new("uv2")
pixel_size = 1.0 / len(bm.verts)
i = 0
for v in bm.verts:
for l in v.link_loops:
uv_data = l[uv_layer]
uv_data.uv = mathutils.Vector((i * pixel_size, 0.0))
i += 1
exportobj = bm
bm.to_mesh(obj.data)
def export_mesh(obj, output_dir, name):
context = bpy.context
context.collection.objects.link(obj)
context.view_layer.objects.active = obj
#this is super ugly but works
bpy.data.objects['export'].select_set(True)
bpy.data.objects['Plane'].select_set(False)
output_dir = output_dir + name + "_" + "mesh.fbx"
bpy.ops.export_scene.fbx(use_selection=True, filepath=output_dir)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment