VertexAnimation
使用MeshInstance2D播放顶点动画,模型必须按照教程操作,UV2用顶点色替代
这是3D的Shader
shader_type spatial;
render_mode cull_disabled;//双面显示
uniform sampler2D position;
uniform float scale = 1.0;
uniform float texture_width = 128;
uniform float num_frames = 10;
uniform float frame:hint_range(0.0, 100.0, 0.01) = 0.0;
void vertex(){
float pixel = 1.0 / texture_width;
float half_pixel = pixel * 0.5;
float frame_pixel_size = 1.0 / num_frames;
vec3 pos = texture(position, UV2 + vec2(half_pixel, -((frame + 0.5) * frame_pixel_size))).xyz;
float new_x = (pos.x * 2.0) - 1.0;
float new_y = (pos.z * 2.0) - 1.0;
float new_z = ((pos.y * 2.0) - 1.0) * -1.0;
VERTEX = vec3(new_x,new_y,new_z) * scale;
}
下面是2D的Shader
主要还是blender部分的模型操作,下面是blender的VAT导出插件
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bmesh
import mathutils
import json
bl_info = {
"name" : "godot",
"author" : "godot",
"description" : "",
"blender" : (2, 80, 0),
"version" : (0, 0, 1),
"location" : "",
"warning" : "",
"category" : "Generic"
}
def property():
bpy.types.Scene.AnimFrame = bpy.props.IntProperty(
name="AnimFrame",
min=1,
)
bpy.types.Scene.RangeScaling = bpy.props.FloatProperty(
name="RangeScaling",
min=1,
)
class CreateGodotVAT(bpy.types.Operator):
bl_idname = "triangle.vat"
bl_label = "vat"
bl_options = {"REGISTER","UNDO"}
def bake_morph_textures(self,obj, frame_range, scale, name, output_dir):
print("A:" + output_dir)
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 = self.new_object_from_frame(obj,f)
new_pixels = self.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]
self.write_output_image(pixels_pos, name + "_position",[width,height],output_dir)
self.write_output_image(pixels_nrm, name + "_normal",[width,height],output_dir)
self.write_output_image(pixels_tng, name + "_tangent",[width,height],output_dir)
#frame_zero = self.new_object_from_frame(obj,0)
self.create_morph_uv_set(bpy.context.active_object)
#self.export_mesh(frame_zero,output_dir,name)
def write_output_image(self,pixel_list,name,size,output_dir):
image = bpy.data.images.new(name,width=size[0],height=size[1])
image.pixels = pixel_list
bpy.context.scene.sequencer_colorspace_settings.name = "Raw"
bpy.context.scene.view_settings.gamma = 0.0
bpy.context.scene.view_settings.view_transform = "Raw"
#bpy.context.scene.render.image_settings.color_depth = '16'
#bpy.context.scene.render.image_settings.file_format = 'OPEN_EXR'
#bpy.context.scene.render.use_render_cache = True
image.save_render(output_dir+name+".png",scene=bpy.context.scene)
def new_object_from_frame(self,obj,f):
context = bpy.context
scene = context.scene
scene.frame_set(f)
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(self,obj,position_scale):
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 = self.unsign_vector(vert.tangent.copy())
normal = self.unsign_vector(vert.normal.copy())
position = self.unsign_vector(obj.data.vertices[index].co.copy() / position_scale)
tangent.append(1.0)
normal.append(1.0)
position.append(1.0)
vertex_data[index] = [position,normal,tangent]
return vertex_data
def unsign_vector(self,vec,as_list=True):
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(self,obj):
try:
# 删除UV2
uv_layers = bpy.context.active_object.data.uv_layers
uv_layers.remove(uv_layers['UVMap2'])
except:
pass
# 重新生成UV2贴图
bm = bmesh.new()
bm.from_mesh(obj.data)
uv_layer = bm.loops.layers.uv.new("UVMap2")
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
bm.to_mesh(obj.data)
def export_mesh(self,obj,output_dir,name):
context = bpy.context
context.collection.objects.link(obj)
context.view_layer.objects.active = obj
output_dir = output_dir + name + "_" + "mesh.gltf"
bpy.ops.export_scene.gltf(use_selection=True, filepath=output_dir)
def execute(self, context: bpy.types.Context):
mesh = bpy.context.active_object
# 先保存0到100帧
frame_range = [0,bpy.data.scenes["Scene"]["AnimFrame"]]
self.bake_morph_textures(mesh,frame_range,bpy.data.scenes["Scene"]["RangeScaling"],"vat","C://Users//yanyan//Desktop//godot//")
return {"FINISHED"}
class CreateGodotMesh(bpy.types.Operator):
bl_idname = "triangle.hello"
bl_label = "hello"
bl_options = {"REGISTER","UNDO"}
def execute(self, context: bpy.types.Context):
print("CreateGodotMesh:hello blender")
mesh = bpy.context.active_object
points = mesh.data.vertices
#uvs = mesh.data.uv_layers.active.uv.values()
polygons = mesh.data.polygons.values()
py_points = []
for i in points:
_list = [round(i.co[0], 4),round(i.co[1], 4),round(i.co[2], 4)]
py_points.append(_list)
py_polygons = []
for i in polygons:
_list = [i.vertices[0],i.vertices[1],i.vertices[2]]
py_polygons.append(_list)
data = {"vertices":py_points,
"polygons":py_polygons}
n = 0
for uvi in mesh.data.uv_layers:
uvs = uvi.uv.values()
py_uvs = []
for i in uvs:
_list = [round(i.vector[0], 4),round(i.vector[1], 4)]
py_uvs.append(_list)
data["uv_" + str(n)] = py_uvs
n=n+1
with open('C://Users//yanyan//Desktop//godot//mesh.json','w+') as f:
json.dump(data,f)
print("保存完毕")
return {"FINISHED"}
class WGodot(bpy.types.Panel):
bl_idname = "WGodot"
bl_label = "godot工具"
# 标签分类
bl_category = "Godot"
# ui_type
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_context = "objectmode"
def draw(self,context):
layout = self.layout
layout.label(text="模型必须先转成三角面",icon="BLENDER")
row = layout.row()
# 生成按钮
row.operator("triangle.hello",text="生成Godot模型",icon="CUBE")
col = layout.column()
col.prop(context.scene,"AnimFrame",text="顶点动画帧数")
col.prop(context.scene,"RangeScaling",text="范围缩放")
# 生成按钮
col.operator("triangle.vat",text="生成顶点动画贴图",icon="CUBE")
def register():
print("hello blender")
property()
bpy.utils.register_class(CreateGodotMesh)
bpy.utils.register_class(CreateGodotVAT)
bpy.utils.register_class(WGodot)
...
def unregister():
print("goodbye blender")
bpy.utils.unregister_class(CreateGodotMesh)
bpy.utils.unregister_class(CreateGodotVAT)
bpy.utils.unregister_class(WGodot)
...
下面是godot的模型导入插件
tool
extends EditorPlugin
var scene_menu
static func find_child_by_class(node:Node, cls:String):
var child_list = []
for child in node.get_children():
if child.get_class() == cls:
child_list.append(child)
return child_list
func _enter_tree():
var base = get_editor_interface().get_base_control()
var scene_tree_dock = base.find_node("Scene", true, false)
scene_menu = find_child_by_class(scene_tree_dock,"PopupMenu")
scene_menu[1].connect("about_to_show",self,"menu_changed")
scene_menu[1].connect("id_pressed",self,"id_pressed")
func menu_changed():
if scene_menu[1].get_item_count() == 0:
return
var name = scene_menu[1].get_item_text(clamp(scene_menu[1].get_item_count() - 1,0,9999))
var m:PopupMenu = scene_menu[1]
if name == "删除节点":
var nodes = get_editor_interface().get_selection().get_selected_nodes()
if nodes[0] as MeshInstance2D:
m.add_item("Load Blender Mesh",99)#不能超过100
func loadJson():
var file = File.new()
file.open("C://Users//yanyan//Desktop//godot//mesh.json", File.READ)
var data = parse_json(file.get_as_text())
return data
func id_pressed(id):
if id == 99:
print("加载json模型文件")
var nodes = get_editor_interface().get_selection().get_selected_nodes()
var data = self.loadJson()
var st = SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
var n=0
for i in data["polygons"]:
var vertices = PoolVector3Array()
var uv = PoolVector2Array()
var color = PoolColorArray()
var uv2 = PoolVector2Array()
var polygons = PoolIntArray()
vertices.push_back(Vector3(data["vertices"][i[0]][0],data["vertices"][i[0]][1],data["vertices"][i[0]][2]) )
vertices.push_back(Vector3(data["vertices"][i[1]][0],data["vertices"][i[1]][1],data["vertices"][i[1]][2]) )
vertices.push_back(Vector3(data["vertices"][i[2]][0],data["vertices"][i[2]][1],data["vertices"][i[2]][2]) )
uv.push_back(Vector2(data["uv_0"][n][0],data["uv_0"][n][1]))
uv.push_back(Vector2(data["uv_0"][n+1][0],data["uv_0"][n+1][1]))
uv.push_back(Vector2(data["uv_0"][n+2][0],data["uv_0"][n+2][1]))
if data.has("uv_1"):
uv2.push_back(Vector2(data["uv_1"][n][0],data["uv_1"][n][1]))
uv2.push_back(Vector2(data["uv_1"][n+1][0],data["uv_1"][n+1][1]))
uv2.push_back(Vector2(data["uv_1"][n+2][0],data["uv_1"][n+2][1]))
color.push_back(Color(data["uv_1"][n][0],data["uv_1"][n][1],0.0,1.0))
color.push_back(Color(data["uv_1"][n+1][0],data["uv_1"][n+1][1],0.0,1.0))
color.push_back(Color(data["uv_1"][n+2][0],data["uv_1"][n+2][1],0.0,1.0))
st.add_triangle_fan(vertices,uv,color,uv2)
n=n+3
nodes[0].mesh = st.commit()
print(nodes)
func _exit_tree():
pass
Shader code
shader_type canvas_item;
//https://www.bilibili.com/video/BV1hm4y1P7Yd/
uniform sampler2D position;
uniform float scale = 1.0;
uniform float texture_width = 128;
uniform float num_frames = 10;
uniform float frame:hint_range(0.0, 100.0, 0.01) = 0.0;
void vertex(){
float pixel = 1.0 / texture_width;
float half_pixel = pixel * 0.5;
float frame_pixel_size = 1.0 / num_frames;
vec2 uv2 = vec2(COLOR.r,COLOR.g);
uv2 = uv2 + vec2(half_pixel, ((frame + 0.5) * frame_pixel_size));
vec3 pos = texture(position, uv2).xyz;
float new_x = (pos.x * 2.0) - 1.0;
float new_y = (pos.z * 2.0) - 1.0;
float new_z = ((pos.y * 2.0) - 1.0) * -1.0;
COLOR = vec4(UV,0.0,1.0);
VERTEX = vec2(new_x,new_z) * scale;
}