欢迎光临散文网 会员登陆 & 注册

【Aegisub】制作3D效果的准备知识

2021-01-20 11:28 作者:多华宫与火火里  | 我要投稿

不知道从哪说起,先从最最最简单的旋转说起吧。比如平面上有一点P坐标为(x,y),它绕着原点旋转α角度,那么旋转以后的P的坐标怎么计算:

首先点P一开始的坐标(x,y)当然也可以表示为(r*cosd,r*sind)对吧,那么旋转后的坐标就是(r*cos(α+d),r*sin(α+d))了吧,那实际上计算一下cos(α+d)和sin(α+d)就差不多了,前者等于cosα*cosd-sinα*sind,后者等于sinα*cosd+cosα*sind。所以说一开始的坐标是(r*cosd,r*sind)而旋转后的坐标是(r*(cosα*cosd-sinα*sind),r*(sinα*cosd+cosα*sind)),

整理一下是(cosα*r*cosd-sinα*r*sind,sinα*r*cosd+cosα*r*sind),对吧,那这个r*cosd不是别人r*sind也不是别人,不就是原来的x,y吗

所以实际上现在的坐标就是(cosα*x-sinα*y,sinα*x+cosα*y),其中α就是旋转的角度。

那么在空间中呢?也是一样的道理,比如一个点绕着x轴旋转α角度,那么它绕着x轴转显然x坐标是不变的,这就相当于这个点在y轴和z轴组成的平面上旋转了α角度(x坐标保留不变)。同理,一个点绕着y轴旋转α角度,就相当于这个点在z轴和x轴组成的平面上旋转了α角度(y坐标保留不变);一个点绕着z轴旋转α角度,就相当于这个点在x轴和y轴组成的平面上旋转了α角度(z坐标保留不变),那这样就能直接从刚刚平面的计算得到三维的旋转变换公式了:

用x′、y′、z′分别表示旋转过后的x坐标、y坐标、z坐标。用θ表示旋转的角度(在右手坐标系中,旋转的正方向是右手螺旋方向,即从该轴正半轴向原点看是逆时针方向)

①绕X轴的旋转

x′=x

y′=ycosθ−zsinθ

z′=ysinθ+zcosθ

②绕Y轴的旋转

x′=zsinθ+xcosθ 

y′=y

z′=zcosθ−xsinθ

③绕Z轴的旋转

x′=cosθ*x-sinθ*y

y′=sinθ*x+cosθ*y

z′=z

这样的话,空间中任何一个点绕x、y、z轴的旋转就都可以计算了,不过,为了在某种程度上方便一些,所以可以再多引入一个矩阵的概念:

单说矩阵的话,矩阵就是一张数表,比如

就是一个2X3的矩阵,就是一张数表,有2行有3列。所以说矩阵单说定义没多大用处,重点是把矩阵作为一个计算工具来使用,是在很多地方都有用的。比如矩阵的数乘、矩阵的加法减法乘法等等等等。(其实关于矩阵,可以单独写一个函数库,伴随矩阵、矩阵转置、可逆矩阵等等等等,反正没什么用,但你可以写着玩) 

在3D效果中用一下矩阵的乘法就差不多了,首先由于矩阵是一张数表,所以当然,两个数表之前怎么相乘肯定是人为定义的(当然是有原因的定义),只要按照定义来即可,然后你就可能发现这是一个很方便的工具。

首先,矩阵相乘必须满足这个条件:第一个矩阵的列数等于第二个矩阵的行数。然后相乘的规则是:第一个矩阵的行和第二个矩阵的列乘起来(对应元素乘积作和),比如

这两个矩阵是这样相乘的:第一个矩阵的第一行乘第二个矩阵的第一列就是:第一行的第一个a11和第一列的第一个b11乘起来,再加上第一行的第二个a12乘以第一列的第二个b21,再加上第一行的第三个a13乘以第一列的第三个b31,这样就算“乘”好了第一行和第一列,得到的这个东西就是新的矩阵的第一行的第一个。然后呢,第一个矩阵的第一行又继续乘第二个矩阵的第二列,第一行的第一个a11乘以第二列的第一个b12,再加上第一行的第二个a12乘以第二列的第二个b22,再加上第一行的第三个a13乘以第二列的第三个b32,这样就“乘”好了第一行和第二列,得到的这个数就是新的矩阵的第一行的第二个元素,以此类推,接下来还要第一个矩阵的第二行乘以第二个矩阵的第一列、最后是第一个矩阵的第二行乘以第二个矩阵的第二列,然后得到的这几个数,就排成图中那样的数表。如果不会的可以自己练习一下,比如

然后当然,很明显,根据刚刚给定的规则,你肯定知道矩阵的相乘不满足交换律,AB≠BA

还有当然,两个矩阵要能相乘必须要满足第一个矩阵的列数等于第二个矩阵的行数,也就是说,如果第一个矩阵是一个mXs的矩阵,那么第二个矩阵就需要是sXn的矩阵(即第一个矩阵的列数等于第二个矩阵的行数),乘出来得到的新的矩阵就是一个mXn的矩阵,也就是一个有m行、有n列的数表


啰嗦了一堆,主要是会有各种各样的学aeg的人,之前有一些人问过一些相似的问题,有的人学过、有的人没学过、有的人学过忘了,所以我还是简单地啰嗦了一些东西。


所以当然,刚刚讲的旋转操作就可以用到矩阵,比如说平面的旋转,刚刚讲的旋转后的新的坐标(x',y')就是(cosα*x-sinα*y,sinα*x+cosα*y),这不就是两个数吗,当然可以看成一个列向量

或者就是一个矩阵,对吧,那么旋转后的点就可以用两个矩阵相乘来计算:

对吧,根据矩阵相乘的规则,第一个矩阵的第一行乘以第二个矩阵的第一列,就是第一行的第一个乘以第一列的第一个(即cosθ*x)再加上第一行第二个乘以第一列的第二个(即sinθ*y),然后得到的cosθ*x-sinθ*y就是新的矩阵的第一行的第一个(即x'),同理y'当然等于sinθ*x+cosθ*y了

所以在空间中的旋转也是同样的道理,

①绕X轴的旋转

x′=x

y′=ycosθ−zsinθ

z′=ysinθ+zcosθ

这个可以写成:

②绕Y轴的旋转

x′=zsinθ+xcosθ 

y′=y

z′=zcosθ−xsinθ

这个可以写成:

③绕Z轴的旋转

x′=cosθ*x-sinθ*y

y′=sinθ*x+cosθ*y

z′=z

这个可以写成:

其实你乘法看熟悉了的话,肯定一眼就看出这个计算了

不管绕哪个轴旋转,乘法中第二个矩阵都是(x,y,z),而且第一个矩阵都是很统一的这个

这样的话,绕哪个轴旋转就是哪个旋转矩阵再乘(x,y,z)列矩阵即可,

比如绕x轴旋转,你就拿

这个旋转矩阵去乘即可

那么矩阵怎么用代码表示呢?用排除法也知道肯定是用table啊(用“一想法”也可知道,那什么是“一想法”呢:就是说,一想想不就一下就知道了吗。当然类似“一想法”的还有“一眼法”,意思是:看一眼不就知道了吗,比如你用看一眼就解题的方法,那叫什么,那叫“一眼法”啊!

一个矩阵当然有m行n列,所以一个mXn的矩阵肯定不能用一个一层的table就表示了,对吧,比如就上面那个矩阵,你肯定不能直接用{1,0,0,0,cosθ,-sinθ,0,sinθ,cosθ}来表示,对吧,因为这样就完完全全失去了“行”和“列”的信息了,没有了“行”和“列”的信息还叫一个数表吗?所以你只需要把这个table给设定为一个两层的table就可以表示一个矩阵了,比如上面的矩阵就表示成{{1,0,0},{0,cosθ,-sinθ},{0,sinθ,cosθ}}就行了,这样你就知道这个矩阵一共有3行对不对,每一行一共有3个元素,所以一共有3列对不对,所以这是一个3X3的矩阵对不对,比如你想知道这矩阵的第二行第三个元素,那是不是就是你这table的第二个元素这个表中表的第三个元素啊,也就是-sinθ

所以列矩阵

正常来说应该表示为{{x},{y},{z}}对吧,这样表示这个矩阵一共有3行,一共有1列,第一行第一列对应的元素是x、第二行第一列对应的元素是y等等等等(竖着和横着当然不一样哈,显然的,而且横着的话,就是{{x,y,z}}了,两个矩阵是截然不同的)

先讲矩阵相乘代码怎么写吧:

输入两个由table代表的矩阵m1和m2,建立一个新的表mtx用来储存相乘得到的新的矩阵。

怎么乘呢?一行乘一列、一行乘一列的乘,所以要for循环遍历m1的行,由于刚刚讲了两个矩阵相乘得到的新矩阵的行数是等于第一个矩阵的行数的,所以代表新的矩阵的mtx表就要有#m1个表中表,每一次循环的时候就要建立一个mtx[i]={},然后你才能在这里面放东西。然后再开始相乘,一行的每一个元素对应乘以一列的每一个元素然后加起来的结果就是mtx[i][j],也就是mtx这个新的矩阵的第i行第j列所对应的元素

这整个就是标准的矩阵相乘的代码了,但是在3D效果中,一般都是一个矩阵来乘以一个列矩阵(x,y,z),所以如果一个点储存的时候,储存成{{x},{y},{z}}就很烦(但是本身列矩阵就应该这么写的),如果你要单独的写矩阵的函数库,当然必须这么写,但是那是单独写矩阵函数库,比如

而如果你不是这样单独写矩阵计算的函数库的话,那完全可以为了3D这部分,稍微更改一下计算,因为把一个点储存成{{x},{y},{z}}感觉麻烦,不如直接储存成{x,y,z},但是这样储存就不表示一个矩阵了,但是你照样可以模仿矩阵的相乘方式,来写一个假的矩阵相乘、这个“矩阵相乘”仅仅针对的是一个矩阵来乘以(x,y,z)这种东西:

这样的话,这个代码并不是计算两个矩阵相乘的,而是针对性的为了计算(x,y,z)的坐标变换,而写的假的矩阵相乘。本身矩阵相乘应该是比如{{1,0,0},{0,cosθ,-sinθ},{0,sinθ,cosθ}}乘以{{x},{y},{z}}然后得到{x'},{y'},{z'}},而现在只针对这类计算,针对性的写一个函数,就可以直接用{{1,0,0},{0,cosθ,-sinθ},{0,sinθ,cosθ}}“乘以”{x,y,z}然后得到{x',y',z'}了

然后就能很方便的写出某点绕某轴旋转得到的新的点了:

上面的frx、fry、frz函数就分别是一个平面绕x、y、z轴旋转angle角度,然后返回一个新的平面。刚刚说了,为了方便,把一个点表示为{x,y,z}而不是矩阵那样的{{x},{y},{z}},所以要表示一个平面就是把这些一个平面的点装起来就是了,比如用{{x1,y1,z1},{x2,y2,z2},{x3,y3,z3}}就可以表示由{x1,y1,z1}和{x2,y2,z2}和{x3,y3,z3}这三个点组成的平面(注意这里的点的顺序不能乱,比如你一个平面有10个点,那么你这10个点在这table里的顺序不能乱,因为你最后一个平面要连线成一个绘图不是吗,所以你点的顺序不能乱,比如顺时针啊、逆时针啊,点的顺序肯定不能乱,不能乱序就把这些点的数据储存进去了)。然后一个平面旋转其实就是对平面上的每一个点都旋转变换,所以可以看到上面的函数,输入一个代表平面的表p,然后p这个平面有很多点,每个点都要旋转变换,所以for循环遍历p平面的每一个点,对每一个点使用刚刚的假的矩阵相乘函数,也就是这一部分代码:


对于旋转,除了刚刚的绕x、y、z轴旋转的旋转矩阵,也有比如绕任意过原点的轴旋转的旋转矩阵,假设(u,v,w)是一个单位向量(注意这里要是单位向量,即长度为1的向量),那么一个点(x,y,z)绕着(u,v,w)旋转θ度的旋转矩阵是这样的:

这再大一坨也是一个3X3的矩阵,就和刚刚的比如绕z轴旋转的旋转矩阵是一回事,直接拿这个矩阵去和(x,y,z)“乘”就是了

这个就是一个平面绕着过原点的任意轴(用一个单位向量指定)旋转得到新平面的函数,可以看到结构当然和刚刚讲的frx等等一样的,只不过这次这个旋转矩阵有一大坨而已。第三个参数axis就是填入一个单位向量的,用来指定旋转轴,比如填{√3/3,√3/3,√3/3}这个单位向量。

讲了一些简单的计算以后,介绍一下怎么建模,基于刚刚的设定,所以将一个点用一个一层的table表示,即{x,y,z},而一个平面就用两层的table表示,如{{-1,-1,-1},{1,-1,-1},{1,1,-1},{-1,1,-1}}就是4个点构成的一个面,每个点都有x,y,z坐标,而如果是一个立体图形的话就有很多个平面,所以用一个三层的table表示,也就是把一个个表示平面的table再装进一个大的table里,这个大的table就表示一个立体图形了

当你有了一个立体图形以后,你就可以进行相应的连线了,即把立体图形的每个平面的点按顺序连接起来就得到相应的绘图了,当然,由于aeg里只有x和y,并没有z,所以在连线的时候直接去掉z坐标,就能得到立体图形在屏幕上的投影了。意思就是,你建模的时候,先不管三七二十,按照空间中的点用3个坐标来算,最后经过一系列什么旋转、平移、缩放等操作过后,在最后把你的立体图形拿来连线的时候,直接丢掉z坐标就可以了,这样就有了一个立体图形的正投影。

那么连线怎么连呢?这个就很简单了,比如如果是一个平面的话,假设这个平面是{{-1,-1,-1},{1,-1,-1},{1,1,-1},{-1,1,-1}}这4个点构成的一个面,然后你一个点连接下一个点连接下一个点就是了,比如这个你就连成m -1 -1 l 1 -1 l 1 1 l -1 1即可,对吧,也就写个简单代码的事:


还有就是,因为旋转等等这些操作算出来的点可能有很多位小数,所以在连线之前,肯定要把table里的数字取舍一下,这里直接取一位小数了,所以我这个函数库生成的立体投影最多一位小数。这个函数很明显,输入的是一个平面,也就是一个装有很多点的两层table,然后遍历这个平面的每一个点,然后再遍历每个点的x、y、z坐标,对它们进行取舍。

然后再是连线的操作:

这个连线的函数,用前面提到的“一眼法”就能知道,输入的是一个平面,然后points_int函数把这个平面的每个点的坐标都取舍一下(int当然指整数,但是我这里就是指保留1位小数,请不要揍我),然后再开始连线,遍历每个点,只把x和y坐标连起来,z坐标直接不连、也就是直接丢掉z坐标,最后返回的字符串s就是这个平面在屏幕上的投影了,就是一个绘图代码了。“一眼法”万岁!

除开点的变换、模型的建立、最后的连线,还有一点需要提一下,就是“层数”问题,比方说,一个正方体一共有6个面,你不能让这6个面的层数都是0吧,因为那样的话就重在一起了,看起来就不会是一个正方体了。正方体6面,实际上不论怎么转怎么动,你最多只能看到3个面对吧,这意味着当你应该要看到3个面的时候,就要让你能看到的面在上层,比如让它们层数为1,而不能看到的面就要在下层、层数为0,所以你还需要计算立体图形在某个角度时的层数,

因为有这空间的前后遮挡关系,所以你要让在上层的平面的层数比在下层的平面的层数大,这样才能看到正确的投影。

对于凸多面体而言,只需要找到其内部的一点(只要能保证这一点在凸多面体内部即可),就可以计算层数了。遍历凸多面体的每个面,利用叉乘求出这个面的法向量,但是法向量只不过是垂直于这个平面而已,法向量的朝向是有两种可能的,此时,就要用我们刚刚找到的凸多面体内部的一点了,为了“修正”你这个平面的法向量的方向,用这个内部的点和这平面上的某一点连线(比如和这个平面的顶点1连线、比如和这平面的顶点2连线等等),然后设定一个向量向量的方向是内部点开始到平面某点上结束,这样的话,计算一下这个向量和开始得到的法向量的夹角,如果夹角大于90就将法向量的方向反向,这样就得到了“修正”后的平面的法向量了然后,每个面都用这种方式得到它们的法向量,利用这些法向量的朝向就能设定各个面的层数了。法向量的z坐标大于0的就是上层的、法向量的z坐标小于0的就说明在下层。(其实这个道理很简单,凸多面体内部的一点到某平面的方向就说明了平面在下层或上层,比如你这内部点要往下走才能最后走到这个平面(走的路径垂直于平面),那自然说明这个平面是下层的,反过来,若你这内部点要向上走(走的路径垂直于平面),最后才能走到这平面,那当然说明你这个平面是上层的。所以,求出平面的法向量以后,就要根据内部点到那个平面的向量的方向来“修正”法向量的方向,让法向量和你这内部点到平面的向量的方向一样,这样法向量的方向自然就能代表你这个平面的方向了,自然就能设定层数了)


这样的话,就算是介绍了一下3D函数库里面基本的构建。代码之类的就在后面的视频介绍

【Aegisub】制作3D效果的准备知识的评论 (共 条)

分享到微博请遵守国家法律