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;
}
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from yanyan

Disturbance effect

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments