3DMAX脚本语言maxscript的入门教程

时间:2024-04-09 19:31:23

这几天就是有这么个需求,要批量建出1000个在尺寸/相对位置上有所差异,但组装方式比较相似的模型。用来做实验。本来是想在网上买的,哎,果然并不可能买到和需求一样的现成的模型。只好自己学3DMAX了,对着****整了半天,感觉我等新手,用3DMAX建模的速度实在是太慢了。这时候就发现了菜单栏上有一个仿佛是脚本语言的东西。

3DMAX脚本语言maxscript的入门教程

很好,简直救人于水火之中!当机立断放弃手动建模了。不过网上的maxscript教程参差不齐,很多就是小片段,还不是入门级的,要么又是丢出一大本API让人自己慢慢看。

我只是个接触maxscript总时长不超过20小时的新手,所以写的教程也是自己对着API摸索清楚的超级入门教程。仅供参考。

3DMAX脚本语言maxscript的入门教程

我的目标是批量建模1000个这样的小房子,长方体是房子主体,四棱锥是屋顶,墙面配置1门,2窗,要能批量贴材质,批量渲染。并且脚本自动计算出各个零件的空间关系,导出文本数据供后续实验,导出渲染截图,保存max文件。

反正感觉基本上都是3dmax里很常用的形状拼接,但需要大批量建模,所以必须靠脚本!

1. maxscript的基本用法:

看名字就觉得和javascript有点像,因为我自己以前是写过前端的,所以用的sublime编辑器写这种轻量级脚本。跟那些脚本语言一样,用记事本也可以写。直接在文件夹里创建一个demo.ms文件,自己改后缀为Ms就可以。并不是一定要在3dmax里头写。我是用sublime打开空的demo.ms,直接在3dmax外写代码,然后3dmax内运行代码就可以了。

切到3dmax,菜单- Maxscript- maxscript侦听器。把这个东西打开。(感觉就像一个交互式命令行)

3DMAX脚本语言maxscript的入门教程

在命令行里交互式创建长方体:

侦听器里打一句:

Box()

就会交互式的弹出下面这句话,是创建好的一个空间坐标@在0,0,0的box对象(就是长方体对象)名字是默认的Box001

$Box:Box001 @ [0.000000,0.000000,0.000000]

【我叫你一声Box001你敢答应吗?】【undefine】

试过就知道,虽然这对象名为Box001,但无法直接召唤,需要用$Box001,带上$符号,才能选中对象。

3DMAX脚本语言maxscript的入门教程

老麻烦了,试试下一种创建Box的方法。

a = Box()

跟其他编程语言一样,定义了一个a变量当它的名字。这样创建的Box,名字还是叫Box002,但我们喊一句“a”他也能选中。

3DMAX脚本语言maxscript的入门教程

我正式用来创建房体的时候是在脚本文件里写:

house01 = Box name:"house01"

maxscript里头的Box,Pyramid这种构造函数,都是可以在后面跟很多参数的,比如我跟着一个name: 还可以跟pos: width : length:  具体参数查手册。这样在创建的同时,就给box起好了名house01,同时赋值给了house01变量。

为了封装好看,我把对Box的参数设置全包裹在了子函数setHouse里。

这涉及到Maxscript里的函数体系:

    调用函数的方法:【函数名 实参】  eg:setHouse house01

    定义函数的方法:【fn 函数名 形参  = (……)】 eg: fn setHouse obj = ( …… )

为某个Box对象随机调整长宽高: 

MINN = 30

MAXX = 60

fn setHouse obj=

(

obj.width = random MINN MAXX

obj.length = random MINN MAXX

obj.height = random MINN MAXX

)

在这里设定(修改)obj的长宽高,我这里设置了随机  和其他语言差不多,反正格式就这样。因为设定房体House01,的坐标就是基准的0,0,0 所以没有改pos

 

2.创建棱锥

屋顶是个四棱锥,这里setRoof函数传了两个形参,obj是四棱锥,houseobj先前创建的Box主房体,因为我们需要屋顶和房体的长宽一致,并且它的Pos要正好搁在房体上方。所以houseobj要作为参考之一传入。多参数的函数也是一样的写法如下。都是用.访问参数,面向对象的。

--设置屋顶的长宽高,长宽等同于房体,高度随机,位置要刚好搁在房体的上方。

fn setRoof obj houseobj=

(

obj.width = houseobj.width 

obj.depth = houseobj.length

obj.height = random MINN MAXX

obj.pos = houseobj.pos+[0,0,houseobj.height]

)

--屋顶

roof01 = Pyramid name:"roof01"

setRoof roof01 house01

3.创建门

这里涉及到要把创建出的门对象平移到墙面,并且进行旋转,让它能正常的贴立在墙面上。我用的max自带的door对象来构造。其中涉及到的旋转rotate的写法,注意仔细看看。

rot = eulerangles 90 0 -90

rotate obj rot

我比较喜欢这样写,可以先在max里头按E键调整门的方向,手动边拖边试数值(一边拖动旋转,一边关注下方那三个角度信息,这样可以迅速的定出哪个轴要转90,哪个轴转-90)然后用一个rot旋转因子记录角度。rotate obj rot ,对Obj进行旋转。

Biford是门的一种,有好多好多好多门,可以看API去,我觉得Biford是最简单的一种。Frame_Width,Panel_Type这种都是根据API来的,是对门进行的一些设定,其实说是API,如果在Max里头手动选中门对象,右边工具栏调整参数,工具栏里有的参数都是可以设定数值的,调用API的过程,是把可视化的填数字进框变成了Obj.属性=XXX这样代码调用,我觉得还方便些。

door01 = BiFold name:"door01"

setDoor door01 house01

fn setDoor obj houseobj=

(

obj.width = random 10 20

obj.height = random 15 obj.width*1.5

obj.depth = 3

obj.Panel_Type = 0

rot = eulerangles 90 0 -90

rotate obj rot

noise = obj.Frame_Width

posX = houseobj.width/2

posY = random (noise*2+obj.width-houseobj.length*0.5) (houseobj.length*0.5-noise)

posZ = 0

obj.pos = houseobj.pos+[posX, posY, posZ]

)

4.加两个窗子

窗子和门一样,需要随机出现在4个墙面之一,这里考虑到门是只有一扇,房子是可以转的,所以门全固定在了正面一面,但窗子就不同了,窗子需要随机出现在4面之一,并且两个窗子之间不可重叠,窗子和门之间也不可重叠。窗子的边缘不可超过墙的边缘。总之就是限定条件又加多了很多。

fn setWindow obj houseobj =

(

obj.width = random 10 20

obj.height = random 10 20

obj.depth = 3

obj.Rail_Width =0.0

noise = obj.Horizontal_Frame_Width

if(houseobj.height-obj.height-noise*2) < (houseobj.height*0.5) then(

posZ = random 5 (houseobj.height-obj.height-noise*2)

)else(

posZ = random (houseobj.height*0.5) (houseobj.height-obj.height-noise*2)

)

x = random 1 4

if x == 1 then (

rot = eulerangles 90 0 0

rotate obj rot

rot1 = eulerangles 0 0 -90

rotate obj rot1

posX = houseobj.width/2

posY = random (-0.5*(houseobj.length-noise*2-obj.width)) (.5*(houseobj.length-noise*2-obj.width))

obj.pos = houseobj.pos+[posX, posY, posZ]

)

else if x == 2 then (

rot = eulerangles 90 0 0

rotate obj rot

posX = random (-0.5*(houseobj.width-obj.width-2*noise)) (0.5*(houseobj.width-obj.width-2*noise))

posY = houseobj.length/2

obj.pos = houseobj.pos+[posX, posY, posZ]

)

else if x == 3 then(

rot = eulerangles 90 0 -90

rotate obj rot

posX = houseobj.width/2*(-1)

posY = random (-0.5*(houseobj.length-noise*2-obj.width)) (.5*(houseobj.length-noise*2-obj.width))

obj.pos = houseobj.pos+[posX, posY, posZ]

)else(

rot = eulerangles 90 0 0

rotate obj rot

posX = random (-0.5*(houseobj.width-obj.width-2*noise)) (0.5*(houseobj.width-obj.width-2*noise))

posY = houseobj.length*(-0.5)

obj.pos = houseobj.pos+[posX, posY, posZ]

)

)

--检测房屋内部各对象的相交情况

fn checkinsect obj = (

for i in $door* do(

if intersects i obj == True then(

--format "a window[%] crush a door\n" obj.name

return True

)

)

for i in $win* do(

if i == obj then(

return False

)else(

if intersects i obj == True then(

--format "a window[%] crush a window[%]\n" obj.name i.name

return True

)

)

)

)

哎呀窗子代码好长= =,本渣代码写的不好。其实就是因为窗子可能出现在四个不同方向的墙面上,所以POS要改,旋转因子也要根绝不同方向改。

关键点:

intersects i obj == True

这个是判定两个对象有没有重叠的,因为不能把窗子放门上,也不能把窗子重合,所以需要判定

for i in $win*

遍历以win开头的对象们。。



第二部分:渲染、贴图、材质、存图

在做完第一部分之后,可以批量生成N个小房子(1房体,1屋顶,1门,2窗),但是都是白模,没得纹理没得贴图。这太丑了。

fn renderr maxname = (

renderpic = #()  --为贴图们准备空集合

ca=TargetCamera pos:[180,150,75]  --设置摄像机位置

tobj=targetobject pos:[0,0,45] --设置摄像机目标点的位置(ca 和 tobj的连线其实就是照相的方向,决定着之后以何种角度给房子 拍照渲染)

ca.target=tobj

--贴屋顶(roof_num 对应着我准备的4种不同的房屋素材)

roof_num = random 1 4

roof_file = "D:\\max2012\\demo\\sucai\\roof" + (roof_num as string) +".jpg"

meditMaterials[1].diffuseMap = bitmaptexture filename:roof_file   --给材质库的插槽1 ,附上漫反射位图贴图

($roof*).material = meditMaterials[1]     --给屋顶对象,绑定上材质库插槽1的材质

append renderpic roof_file    --把素材图添加到空集合里

--贴墙(同理)

wall_num = random 1 4

wall_file = "D:\\max2012\\demo\\sucai\\wall" + (wall_num as string) +".jpg"

meditMaterials[2].diffuseMap = bitmaptexture filename:wall_file

($house*).material = meditMaterials[2]

append renderpic wall_file

--贴窗户 (这里需要使用UVW贴图,不然窗户素材会平铺到窗子上,显得很丑)

window_num = random 1 4

window_file = "D:\\max2012\\demo\\sucai\\window" + (window_num as string) +".jpg"

meditMaterials[3].diffuseMap = bitmaptexture filename:window_file  --给材质库插槽3赋上窗户贴图

for i in $win* do(   --对每个窗子,添加修改器UVWmap() ,然后在依次进行材质绑定

addModifier i (UVWmap())

i.material = meditMaterials[3]

)

append renderpic window_file

--贴门(同理窗子UVW)

door_num = random 1 4

door_file = "D:\\max2012\\demo\\sucai\\door" + (door_num as string) +".jpg"

meditMaterials[4].diffuseMap = bitmaptexture filename:door_file

addModifier ($door*) (UVWmap())

($door*).material = meditMaterials[4]

append renderpic door_file

--渲染+存图

ambientcolor = (color 255 255 255)  --设置环境光(搞不太懂,白的不出错就行)

render camera:ca outputFile:("D:\\max2012\\demo\\"+FILE_LOCATE+"\\pics\\"+maxname+".bmp") vfb:off  --以照相机ca的角度渲染,输出文件位于指定地点,vfb:off  不清楚是什么。

return renderpic   --把贴图位置存在集合里收好

)

就这么一段,完成了:

1.将贴图以漫反射形式赋给材质库的插槽(其实就是一个个材质球)

2.将门窗等对象绑定上材质球

3.考虑UVW展开并贴图,可以防重复(肯定有其他方法只是我不懂)

4. render 的瞬间,设置以摄像机角度渲染,设置输出位置



第三部分:打印对象数据

我们随机建好了一栋栋小房子,需要对它进行标注,即获取到每个对象的位置,尺寸,相对关系。

keypoint:

1. 使用for i in Geometry,遍历当前的所有形状对象。包括Box pyramid Biford等等

2. 使用classOf i 获取i对象的类别

3. 使用format做格式化输出,注意有的字符要转义(和其他语言共通的那种转义)

fn printall houseobj = (

for i in Geometry do

(

if classOf i == Box then

(

format "[\"root\",\"%\",%,[%,%,%,%,%,%]]\n" (i.name) (i.pos) (-i.width/2) (i.width/2) (-i.length/2) (i.length/2) (0) (i.height)

)

else if classof i == Fixed then

(

win_width = i.width + i.Horizontal_Frame_Width*2

win_height = i.height +i.Vertical_Frame_Width*2

if (i.pos.x == houseobj.width/2) or (i.pos.x == houseobj.width/2*(-1)) then

(

format "[\"%\",\"%\",%,[%,%,%,%,%,%]]\n" houseobj.name i.name i.pos (-i.depth/2) (i.depth/2) (-win_width/2) (win_width/2) (0) (win_height)

)

…………

)

)



第四部分:往文件里写数据

也是使用format。

format "[\"%\",\"%\",%,[%,%,%,%,%,%],\"%\"]\n" houseobj.name i.name i.pos (-win_width/2) (win_width/2) (-i.depth/2) (i.depth/2) (0) (win_height) (renderinfo[3])to:filename

就是format "123" to:filename

这里的filename是文件句柄,用下面的代码创建文件并get句柄,注意自己灵活点改循环啥的,比如1.txt 套循环里建1-1000.txt

out_name = "D:/max2012/demo/data/1.txt"

out_file = createfile out_name

format "123" to:out_file

close out_file

这个close out_file,对于我的max2012来说是无效的,就是我创建的N个txt,除非大退3dmax,不然都显示占用中,不能删除不能改名。不知道为什么,我加上flush out_file 也没用,就是必须大退3dmax才能解除对文件的占用。

如果有解决了问题的请留言告诉我,O(∩_∩)O谢谢

 

对,还有两个小tips:

F11快捷键打开maxscript侦听器

ctrl+D 对侦听器清屏,看着干净些

ctrl+R 快捷键打开编辑好的“demo.ms”并运行