马上注册,加入CGJOY,让你轻松玩转CGJOY。
您需要 登录 才可以下载或查看,没有帐号?立即注册
×
要制作出高品质的3D图象,我们通常要避免那种趋向于完美规律化的计算机风格。一个十分规则、整齐得理想化的场景会破坏图象的真实感,为避免这种情况,我们有很多有效的技术都来制造某些将场景“搞乱”的因素。可是靠手动操作去安排一切总是不够随机化(当然费力是更实在的)。如果你使用过粒子系统,你一定知道为它们添加随机性更是产生可靠效果的关键因素。而在MAXScript命令中的 random 就是一个“制造混乱”的神奇的工具(当然它是需要小心控制的)。用法如下:
random 数值1 数值2
↑其中的两个数值应当是同一类型,它们用于指定所生成随机数值的下限和上限。
现在来用我们已有的菜鸟级MAXScript词汇,来为一个场景添加一点真实度吧。
使用"for" 和"random"为场景添加无序性
1. 打开附带的文件cafe_start.max(在这里下载) 这是一个包含一些桌椅的场景,排列得非常整齐而死板。
提示:这个场景的一个视图被设置为Listener了。要想在你自己的场景中这样玩,右键点击视图标签(也就是Top或者Left什么的)并在菜单中选择Views ->Extended -> MAXScript Listener。
2. 渲染摄象机视图,再去Render菜单下打开max内置的 RAM Player ,使用左边属于Channel A 的茶壶按钮将渲染结果载入。我们接下来操作的效果比较细微,我们可以通过这种方法更精确地对比前后的变化。
3. 首先,我们来解决桌腿排列过齐的问题。在Listener窗口中输入:
for i in $*Table* do rotate i (eulerAngles 0 0 (random -180 180))
↑把所有名字里包含关键字table的物体,随便沿Z轴旋转一个角度。
下面我们来分析一下该句中的最后一部分。MAXScript将每一行代码都视为一个表达式。这意味着每行代码的结尾都会运算出一个数值,同时也表明行内任何一个部分都可以是独立的表达式,只要它可以正确地运算得出某种数值,就可以像直接将一个简单数值放在那里一样使用。
而当MAXScript得到一个random时,它就会简单地将一个数值结果放进代码中对应的位置上。圆括号的作用在于指出数值间的运算顺序,就如同2+3*2=8而(2+3)*2=10一样。
现在让我们把每组桌椅稍微移动一下。为了使椅子们保持与对应桌子之间的相对关系,应该将它们一起移动。这可以直接通过脚本实现,只是要用上更多的词汇与好长的一堆代码,然而我们目前的练习是旨在节省时间而且简单易行的,因此可以多点几下鼠标来代替大规模输入代码的麻烦。
4.用我们熟悉的选择与连接工具,将每组椅子连接到它们对应的桌子上。
5.连接好之后,在Listener中输入:
for i in $*Table* do move i [(random 5 5),(random 5 5),0]
这样桌子们的排列就不那么惊人地整齐了。在上一行代码中,我们使用了random表达式来指定了随机的X和Y坐标(因为我们不需要桌子离开地面,所以Z坐标保持为0。)
有一个值得探讨的问题是我们为何不直接使用这样一个简单些的方法:
move $*Table* [(random 5 5),(random 5 5),0]
如果使用上面这段代码,那么random表达式实际上只为每个X和Y运算了一次,因此给予每个桌子的移动坐标完全相同,而像前面那样在for循环中使用random,则会在每次运算中生成一次随机数值,因此桌子们各自移动的幅度才会有所区别。不过当然有些时候我们也可能需要后一种方式,所以只需记住它们的不同,根据实际需要选择应用就可以了。(说实话,我觉得那个地砖效果对画面干扰太严重,实际上用第一种方法移动的时候也会让人产生所有桌子同步移动的感觉,因为它们都同时朝相似的方向移动了难以看出差别的一点点,但实际上的确是不同的,不信的话可以把5改成50什么的再回车:)
接下来轮到椅子们了。摆弄它们的位置需要我们多一点思考过程。大体来讲,它们在离桌子远近距离上的差别应当比其他方向的位移更明显,而现在的情况是每四个椅子围绕着一个桌子,这时要想实现上述效果,我们就需要在每个椅子的局部坐标上来进行move了。
6.前边我们谈到过,把in coordSys local 放到一个物体运动命令前面就可以使其在局部坐标而不是默认的世界坐标上运动,因此我们输入下面这样的代码:
for i in $*chair* do in coordSys local move i [(random -6 3), (random -1 1),0]
这样就成功地实现了我们所需的随机排列效果。椅子局部上的X轴就在它们各自朝向桌子的方向上,因此我们将它的随机程度设置得较高,而在Y轴方向上,一个较小的侧向偏移也就可以了,Z轴和前面一样,保持为0。
7.关于摆弄椅子们的最后一件事和我们最开始对桌子的操作一样,已经心中有数了吧:
for i in $*chair* do rotate i (eulerAngles 0 0 (random -5 5))
8.好了,渲染视图,并将结果装载到RAM Player的B通道里面。
对比一下,看出区别了吗?微小的混乱排列为场景带来了更多真实感,而且丝毫没有改变场景本身的布局设计“四张桌子,各配四把椅子”,这样才更像是人类的店员而不是机器人们来布置的场景。观众们可能不会看出什么不同,但可以感受到,并且完成这一细节设置仅仅需要我们输入四行代码的时间!
这个场景的最终效果可以在DVD上找到,cafe_01.max。如果你想进一步改善这一场景,可以用类似的方法移动桌子,或者旋转和缩放一下桌子上的花等等。(没找到,应该是要自己动手栽吧?)
现在我们是不是有如同方便地使用瑞士军刀似的快感呢?这才只是用了MAXScript宏大词汇库中的几个呢!
#() 和 [] 与 数列的用法
数列Array就是一个系列的数据组合。数列的表示方法是把许多数据放进一个圆括号里,数据之间用逗号分隔,括号的前面加上#号,例如:
myArray = #($Box01,$Box02,$Box03)
上面代码的作用就是定义一个名叫myArray的数列,该数列的内容即为三个box的集合。而当我们需要在其他地方使用这些box时,就可以用这一个数列来代替它们。此外当我们需要单独指定一个数列中的某个对象时,就在数列名后面加上一个方括号并在其中夹进对象的序号,比如在定义了刚才的序列之后输入:
myArray[2]
我们就得到了类似下面的返回值:
$Box:Box02 @ [6.8000152,13.702519,0.000000]
↑即数列myArray中的第二个对象的名称及其所在位置
还有,输入:
myArray[2].height
就相当于访问Box02的height 参数。
在MAXScript中,很多内置的数据结构被组织为数列的形式,并可以在一个for循环的运算中通过指定序号来访问它们。通常我们并不对数列本身进行操作,而是通过for循环在一系列的序号之中进行操作,例如:
for i in 1 to 3 do myArray[i].height = i*10
↑在这行代码中,我们分别在数列序号和目标的高度设置中使用了两个i,因为在每次运算中i的数值增加1,于是Box01的高度被设置为10,而Box02则设置为20,Box03即为30。换句话说——我们用简单的两行代码写出了一个楼梯!
在MAXScript内置的 meditMaterials数列中,我们可以使用这种技术以random功能为一组物体赋予随机的材质。假设有个场景中需要12个人举起不同的牌子。我们就可以在材质编辑器中的前12个格子中放入不同的牌子材质,选择这些牌子,输入下面的代码:
for i in selection do i.material = meditMaterials[(random 1 12)]
由于(random 1 12)的运算结果是一个数字, 那么在循环中每次运算都会产生一个随机的数字作为被指定的序号,也就是把材质编辑器中那前12个材质随机分配给所选择的每个牌子了。(然而原文中并没有提到different一词,也就是实际上通常会发生几个牌子共用同一材质,而有的材质没有被使用的情况,另外原文中所用的物体是planes,我难以通过上下文确定它究竟指的是飞机还是平面,但只要用法正确就可以了,物体本身倒是无关紧要的)
同时,一个物体的修改器堆栈也是以数列形式来访问的,modifiers[1]表示堆栈最顶部的修改器。当你不清楚一个数列到底有多长时,可以用数列里的count属性来查询到里面数据的数量,比如下列用法:
for i in 1 to $.modifiers.count do $.modifiers[i].Enabled = true
↑将所选物体的所有修改器设置为开启状态。
当我们处理多个物体的修改堆栈时,就有必要将代码分成多行来写(实际上可以一行写完,但过长的单行代码会很难于阅读和除错)。要想输入多行脚本,我们就得打开MAXScript Editor (MAXScript菜单->New Script)而不是Listener了。输入后需要执行该代码时,则使用窗口菜单中的 File -> Evaluate All命令。 多行代码一般写做这样的格式:
for i in geometry where (i.modifiers.count > 0) do
(
for j in i.modifiers where ((classOf j == meshsmooth) or (classOf j == TurboSmooth)) do
(
j.useRenderIterations = true
j.renderIterations = 3
j.iterations = 1
)
)
↑(看起来挺复杂是吧?如果把它们写成紧密的单行不是更可怕吗)用独立的圆括号和回车断行是为了将代码结构从视觉上分隔开,以便清晰易读。最上面的一个for 循环正是我们一直使用的for i in selection do 操作.而这里面的操作本身又是一个for循环,它使用了字母j而不是i作为变量名,因为i已经被前一个循环中使用了,因此我们必须换一个名称以避免产生冲突。
上面这段脚本写成通假代码是这样的意思:
for 所有 geometry 带有至少 1 个修改器 do
(
for 此物体的每个是meshsmooth 或 turbosmooth的修改器 do
(
打开该modifier的渲染插值参数
设置 render iterations为3
设置 view iterations为1
)
)
变量j在循环中为前面i所指定的每个物体的修改器代入数值j。之后它对该修改器是否MeshSmooth或者TurboSmooth进行检查判断,如果它是,就进行do后面指定的操作内容:勾选Render Iterations 的复选框,设置渲染插值为3,视图插值为1。
注意脚本后面的结束圆括号是两个,为了保证脚本的正确运行,所有圆括号都要对称使用,而上例中将括号缩进的方法就可以帮助我们更清晰地看出圆括号是否已经正确关闭了。
提示:在MAXScript Editor中,按Ctrl+B 可以选择光标附近(方或圆)括号里所有的字符。而在TextPad中按Ctrl+M则会把光标移动到最近的括号。这是一种修正未关闭括号问题的有力工具。 |