Forked from alexmalyutindev/bake_morph_textures.py
Created
November 21, 2024 14:38
-
-
Save belzecue/d32049a31f5ee9ad200b3f7e0c2f9e1e to your computer and use it in GitHub Desktop.
Vertex animation baker for Blender.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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