大家好,我是阿赵,周日的早上继续分享MaxScript学习笔记,这是第十二篇,获取和导出各种数据
1、导出数据的目的
使用3DsMax建立3D模型后,很多时候需要输出模型到别的引擎去使用,常用的格式有Obj、FBX、SLT等。有时候,这些通用的格式里面的数据不够,比如obj里面就没有带顶点颜色。有时候是因为某些格式的解析比较复杂,比如FBX,之类,我们会需要根据自己的需要来导出各种的数据。
一般来说,导出模型数据会包括以下这些:
顶点坐标
UV
UV2
顶点颜色
顶点法线
面的索引信息
蒙皮信息
材质贴图
动画关键帧
等
首先要明白一个概念,比如一个box,他是由8个顶点、6个四角面组成的。但在导出到引擎使用的时候,其实他是24个点,12个面的,因为游戏引擎绘制的一般是三角面,所以一个四角面会拆分成2个三角面。然后从UV和法线的角度,同一个顶点,有可能是被多个面同时使用的,所以顶点在属于不同的面的时候,顶点的UV坐标和法线方向也是不一样的。
所以,在导出数据的时候,可以使用自己的方式去处理数据。其中一个方法是可以参数obj格式,他把重复的顶点、UV坐标、法线方向进行合并,每个数据有一个索引index,然后在记录面数据的时候,比如一个box有12个面,他会记录12个面的信息,每个面有3个顶点,每个顶点包含了顶点索引、法线索引和uv索引的数据。这样操作下来,存储的数据量最少,也不会丢失任何数据。当然如果想把顶点颜色的数据也加进去,也可以再增加一个顶点颜色的索引,把它带在面的顶点数据里面导出。
下面还是以一个box为思考的参考,列出了获取各种信息的方法,结合着之前讲过的字符串操作方法,和文件读写方法,就可以根据自己的需要取导出自己的模型格式了
2、各种数据的获取方式举例
--获得网格顶点数
GetNumVerts boxt1.mesh
8
--获得某个通道的顶点数据数量,1通道代表UV通道,
--这里我能用1通道,是因为我已经展过UV了,如果没有展过UV,会报错
polyop.getNumMapVerts boxt1 1
24
--获得实际的三角面数
GetNumFaces boxt1.mesh
12
--获得第一个顶点的坐标
getVert boxt1.mesh 1
[-12.5,-12.5,0]
--获得第12个面使用了哪几个顶点,对应网格顶点数的索引
getface boxt1.mesh 12
[3,1,5]
--获得第12个面的法线方向
GetFaceNormal boxt1.mesh 12
[-1,0,0]
--获得第12个面引用的UV索引,对应UV坐标索引
GetTVFace boxt1.mesh 12
[9,10,11]
--获得第9个UV索引点的UV坐标
getTVert boxt1.mesh 9
[0.0005,0.0005,0]
特别说一下法线的获取方式和设置方式
法线的获取和赋值比较特殊一点,需要注意以下几点:
1.如果不通过Edit_Normals修改器来获取,法线的数据是不正确的
box02 = box()
$Box:Box003 @ [0.000000,0.000000,0.000000]
converttopoly box02
$Editable_Poly:Box003 @ [0.000000,0.000000,0.000000]
faceNum = polyop.getnumfaces box02
6
for i in 1 to faceNum do
(
local faceNormal = polyop.getfacenormal box02 i
print faceNormal
)
[0,0,-1]
[0,0,1]
[0,-1,0]
[1,0,0]
[0,1,0]
[-1,0,0]
OK
vertNum = box02.numverts
8
for i in 1 to vertNum do
(
local vertNormal = getNormal box02.mesh i
print vertNormal
)
[0,0,-1.5708]
[0,0,-1.5708]
[0,0,-1.5708]
[0,0,-1.5708]
[0,0,1.5708]
[0,0,1.5708]
[0,0,1.5708]
[0,0,1.5708]
OK
通过以上的代码,似乎可以通过polyop来获取某个面的法线方向,然后也可以用点的index通过getNormal的方法来获取到mesh里面某个点的法线方向。
但其实这个获取的方法是有问题的
(1)首先来说这个面的法线,这个面的发现方向,从一个box的4个面的角度看,是正确的,但如果我们通过添加Edit_Normals编辑器,修改其中一个面的法线,然后再次打印6个面的法线方向,会发现结果根本没有变化。所以我个人感觉,这个从polyop里面获取的面法线方向,并不是通过4个顶点的法线方向计算出来的值,只是通过4个顶点的坐标位置,算出来的一个法线方向。如果在不修改顶点法线的情况下,应该是可以用的,但我们谈论的获取法线方向,一般是指顶点的法线方向,所以这个面的法线方向其实没什么意义
(2)再来看通过getNormal方法获得的顶点法线,会发现这个值有点莫名其妙,首先,8个点只有2种法线方向,然后,这个法线也没有归一化,长度并不是1的,所以这个获取的结果,我觉得也是并没有什么用途的。实际上,一个顶点有可能不止一个法线方向的,下面会继续分析。
2.一个顶点具体包含多少个法线,要看具体的平滑组设置
box03 = box()
$Box:Box003 @ [0.000000,0.000000,0.000000]
converttopoly box03
$Editable_Poly:Box003 @ [0.000000,0.000000,0.000000]
select box03
OK
modPanel.addModToSelection (Edit_Normals()) ui:on
OK
通过一段代码,创建一个box,然后添加了一个Edit_Normals的修改器,这时候我们可以看到场景里面的box每个顶点上面伸出了3条线,这些线,其实就是法线了。由于一个box有8个顶点,所以这个时候,应该有24条法线。
为什么会有24条这么多呢?那是因为这个box每个面的光滑组都不一样,所以面与面之间,是硬转角,而虽然我们看来这个box只有8个顶点,但每个顶点同时被3个面共用,所以一个顶点需要3个法线方向来分别代表组成不同面时,该顶点的法线方向。
这时候,做一下修改,返回Editable Poly,选择所有面,把它们的光滑组都改成1
再回到Edit_Normals,这时候,可以看到每个顶点只有1根法线了。这究竟是真的一个顶点变成1根法线,还是由于同一个顶点的法线都重叠在一起了呢?我们可以通过代码去验证一下。
local myVerts = #{1,2,3,4,5,6,7,8}
local myNormals = #{}
local modN = box03.modifiers[1]
modN.ConvertVertexSelection &myVerts&myNormals
print myNormals
这里有一段代码,由于现在box03的第一个修改器就是Edit_Normals,所以modN获取了这个Edit_Normals修改器,然后通过ConvertVertexSelection ,获取指定的myVerts的8个顶点使用到的法线id到myNormals数组
当把box03的光滑组取消的时候,得到的打印结果是:
#{1…24}
当把box03的所有面光滑组设置成1后,得到的打印结果是:
#{1…8}
很明显,在把所有面的光滑组设置成1后,这个box03的法线方向的确是只剩下了8个。于是我们能得出结论,模型的一个顶点具体有多少个法线方向,是根据面的光滑组来分的。或者换个说法,面与面之间是硬边过渡,还是光滑过渡,是看组成面之间的边缘的顶点,是否共用法线。
3.通过Edit_Normals修改器来获取法线数据
通过了上面的实验,我们知道,只有通过Edit_Normals修改器,才能比较准确的获得和修改模型的法线。所以接下来就使用这个修改器来获取数据。
具体的步骤是这样的?
(1)通过ConvertVertexSelection 方法,获取到所有的NormalId
(2)通过GetFaceDegree方法逐个面获取当前的面有多少个不同角度的点组成
(3)通过GetNormalID方法,获得组成每个面每个顶点的对应NormalId
(4)根据1和4的结果,就可以知道每个顶点的发现方向了
具体代码:
local vertNum = box03.numVerts
local myVerts = #{1..vertNum}
local myNormals = #{}
local modN = box03.modifiers[1]
--通过ConvertVertexSelection把所有顶点用到的normalId存到myNormals数组
modN.ConvertVertexSelection &myVerts&myNormals
--通过GetNormal方法把每一个normalId对应的法线方向,存到normalArr里面
local normalArr = #()
for i in myNormals do
(
local norPos = modN.GetNormal i
normalArr[i] = norPos
)
print "法线集合:"
print normalArr
--获得面的数量
local faceNum = modN.GetNumFaces()
local faceNormalIdList = #()
for i in 1 to faceNum do
(
--corner的含义可以理解成这个面由多少个不同角度的点组成
local corners = modN.GetFaceDegree i
--遍历组成面的这些conner,通过GetNormalID方法,获得组成这个面的顶点对应的normalId
local tempFaceNormalList = #()
for j in 1 to corners do
(
local norInd = modN.GetNormalID i j
append tempFaceNormalList norInd
)
faceNormalIdList[i] = tempFaceNormalList
)
print "面对应法线Id"
print faceNormalIdList
运行的结果:
"法线集合:"
[0,0,-1]
[0,0,-1]
[0,0,-1]
[0,0,-1]
[0,0,1]
[0,0,1]
[0,0,1]
[0,0,1]
[0,-1,0]
[0,-1,0]
[0,-1,0]
[0,-1,0]
[1,0,0]
[1,0,0]
[1,0,0]
[1,0,0]
[0,1,0]
[0,1,0]
[0,1,0]
[0,1,0]
[-1,0,0]
[-1,0,0]
[-1,0,0]
[-1,0,0]
"面对应法线Id"
#(1, 2, 3, 4)
#(5, 6, 7, 8)
#(9, 10, 11, 12)
#(13, 14, 15, 16)
#(17, 18, 19, 20)
#(21, 22, 23, 24)
根据需要,把这些数据保存你的文件里面就可以了。
4.修改法线
顺便提一下怎样修改法线。
之前用GetNormalId可以获取到某个点的NormalId,然后用GetNormal可以获得对应NormalId的具体法线方向值。那么反过来,通过SetNormal方法,就可以改变对应NormalId的点的法线方向了,比如,刚才那个例子,需要把第16个NormalId的值改成[0.5,0.6,0.7],可以这样:
modN.SetNormal 16 [0.5,0.6,0.7]