01_Blender模型处理
安装BlenderMMD导入插件
方法1 Github下载
https://github.com/UuuNyaa/blender_mmd_tools/tree/v4.2.2?tab=readme-ov-file
方法2 Blender内置下载
内置下载或者到Blender插件社区下载 社区地址:https://extensions.blender.org/ 
模型处理
导入

删除无用
joints(关节点)和rigidbodies(刚体) 
多余材质合并
面,齿,舌,口合并,编辑模式下材质面板点一下直接选择就行,最后选择面,点击指定
合并后物体模式下删除材质槽
眼睛合并到一起
眉毛合并到一起
眼睛高光和透贴不要删除
头发(刘海)需要拆下来,做眼睛透过头发效果
不需要分离,选出来之后直接新建一个材质头发指定上
将头发,肌肤,黑丝都合并到饰品里,然后删掉其他的 
生成一套平滑法线用于描边
可以存在UV里面或者在定点色里面

新建python脚本
注意:mesh = bpy.data.meshes[‘星見雅’],这里的名称是网格体名称
因为是UV存值,所以需要将点位置从3维转换到2维,使用八面体映射 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import bpy
from mathutils import *
from math import *
import numpy as np
#创建空字典列表list
dict = {}
#获取模型Mesh
mesh = bpy.data.meshes['星見雅']
#calc_tangents:
#在网格中计算每个顶点的切线(tangent)、双切线(bitangent)和法线(normal),以便后续的方向相关计算。
mesh.calc_tangents(uvmap = 'UVMap')
#计算两个向量之间的夹角
# a·b = |a||b|cosθ
# θ = arccos(a·b / (|a||b|))
# 这里返回的弧度值 θ * (π/180) ≈ xxx弧度
def included_angle(v0, v1):
return np.arccos(v0.dot(v1)/(v0.length * v1.length))
# 3维降为2维
def unitVectorToOct(v):
# 步骤1:计算L1范数(曼哈顿距离)
d = abs(v.x) + abs(v.y) + abs(v.z)
# 步骤2:通过除以L1范数进行投影到八面体表面
# o 是八面体的坐标
o = Vector((v.x / d, v.y / d))
# 步骤3:如果z是负数,需要进行特殊处理以保持连续性
if v.z <= 0:
# 使用折叠技术处理八面体的下半部分
o.x = (1 - abs(o.y)) * (1 if o.x >= 0 else -1)
o.y = (1 - abs(o.x)) * (1 if o.y >= 0 else -1)
return o
#.co是coordinate的缩写,表示顶点的坐标。
# 取出模型的每一个顶点坐标,然后清空置空列表,后续存入新的坐标数据
for vertex in mesh.vertices:
# 初始化 "<Vector (1.0, 2.0, 3.0)>": []
dict[str(vertex.co)] = []
# 获取模型Mesh的每一个面
# l0,l1,l2分别表示模型Mesh的每一个面中的每一个顶点数据
# l0,l1,l2这样的写法是Blender的写法,表示loop0,loop1,loop2的循环体
# mesh.loops 存储了多边形顶点的循环信息
# poly.loop_start 表示多边形顶点的循环开始索引
for poly in mesh.polygons:
#获取模型Mesh的每一个三角面中的每一个顶点数据
l0 = mesh.loops[poly.loop_start]
l1 = mesh.loops[poly.loop_start + 1]
l2 = mesh.loops[poly.loop_start + 2]
#获取模型Mesh的每一个面中的每一个顶点数据
## 顶点的主要属性:
#vertex.co 顶点的3D坐标 (Vector类型,包含x,y,z)
#vertex.normal 顶点的法线方向
#vertex.index 顶点在mesh.vertices数组中的索引号
#vertex.groups 顶点组信息(用于骨骼绑定等)
v0 = mesh.vertices[l0.vertex_index]
v1 = mesh.vertices[l1.vertex_index]
v2 = mesh.vertices[l2.vertex_index]
#计算向量,三角形两条边向量
vec0 = v1.co - v0.co
vec1 = v2.co - v0.co
#计算向量叉积,三角形两条边向量叉乘,得到法线
n = vec0.cross(vec1)
n = n.normalized()
#v0.co 是一个 Vector 类型,包含 (x,y,z) 坐标值
#Python的字典要求键(key)必须是"可哈希的"(hashable)
#Vector 类型不能直接用作字典的键
#这里将顶点坐标转换为字符串,便于后续使用
#k0,k1,k2是字典的Key
k0 = str(v0.co) # 将顶点0的坐标转换为字符串
k1 = str(v1.co) # 将顶点1的坐标转换为字符串
k2 = str(v2.co) # 将顶点2的坐标转换为字符串
# 计算三角形三个顶点的权重
if k0 in dict:
#计算顶点0的权重,w实际上是两向量夹角角度
w = included_angle(v2.co - v0.co, v1.co - v0.co)
# 添加数据
dict[k0].append({"n" : n, "w" : w})
if k1 in dict:
w = included_angle(v0.co - v1.co, v2.co - v1.co)
dict[k1].append({"n" : n, "w" : w})
if k2 in dict:
w = included_angle(v1.co - v2.co, v0.co - v2.co)
dict[k2].append({"n" : n, "w" : w})
for poly in mesh.polygons:
# 获取每一个三角面数据
#range(poly.loop_start, poly.loop_start + 3)遍历3个点
for loop_index in range(poly.loop_start, poly.loop_start + 3):
l = mesh.loops[loop_index]
vertex_index = l.vertex_index
v = mesh.vertices[vertex_index]
#smoothNormal初始化
smoothNormal = Vector((0,0,0))
weightSum = 0
k = str(v.co)
if k in dict:
a = dict[k]
for d in a:
n = d['n']
w = d['w']
#加权平均法线的计算公式:
#例如:smoothNormal = (n1 * w1 + n2 * w2 + n3 * w3) / (w1 + w2 + w3)
smoothNormal += n * w
weightSum += w
if smoothNormal.length != 0:
smoothNormal /= weightSum
smoothNormal = smoothNormal.normalized()
else:
# 如果计算出的平滑法线无效(长度为0),
# 使用原始顶点法线作为后备方案
smoothNormal = l.normal
normal = l.normal # 获取顶点的原始法线
tangent = l.tangent # 获取顶点的原始切线
bitangent = l.bitangent # 获取顶点的原始副切线
# 计算投影
# 使用原始的切线空间基底,计算平滑法线在这个空间中的表示
x = tangent.dot(smoothNormal) # 平滑法线在原始切线方向上的投影
y = bitangent.dot(smoothNormal) # 平滑法线在原始副切线方向上的投影
z = normal.dot(smoothNormal) # 平滑法线在原始法线方向上的投影
#UV数组的大小总是等于loops的数量
uv1 = mesh.uv_layers['UVMap.001'].uv[loop_index]
uv1.vector = unitVectorToOct(Vector((x,y,z)))
如果使用顶点色传入的话不需要进行维度转换
1
2
3
4
# 存入顶点色而不是UV
vertex_color_layer = mesh.vertex_colors.active.data[loop_index]
# 直接将平滑法线的分量存入顶点色
vertex_color_layer.color = (smoothNormal.x, smoothNormal.y, smoothNormal.z)
模型拆分
将有形态键的面部单独拆一个网格,包含下面的目影一起拆
清理材质
面部一样清理一下
去除原网格体形态键
给虚幻引擎需要将模型拆分多一些,刘海拆分下来 
饰品和衣服也拆分下来
因为涉及到半透明排序还有覆层材质,眼影等全部都要拆分
拆完记得重命名和清理材质 
Blender内模型可以不需要拆
合并之前的模型,刘海记得单独一个材质插槽
Enjoy Reading This Article?
Here are some more articles you might like to read next: