【Aegisub】制作3D效果的準(zhǔn)備知識(shí)

不知道從哪說起,先從最最最簡單的旋轉(zhuǎn)說起吧。比如平面上有一點(diǎn)P坐標(biāo)為(x,y),它繞著原點(diǎn)旋轉(zhuǎn)α角度,那么旋轉(zhuǎn)以后的P的坐標(biāo)怎么計(jì)算:

首先點(diǎn)P一開始的坐標(biāo)(x,y)當(dāng)然也可以表示為(r*cosd,r*sind)對(duì)吧,那么旋轉(zhuǎn)后的坐標(biāo)就是(r*cos(α+d),r*sin(α+d))了吧,那實(shí)際上計(jì)算一下cos(α+d)和sin(α+d)就差不多了,前者等于cosα*cosd-sinα*sind,后者等于sinα*cosd+cosα*sind。所以說一開始的坐標(biāo)是(r*cosd,r*sind)而旋轉(zhuǎn)后的坐標(biāo)是(r*(cosα*cosd-sinα*sind),r*(sinα*cosd+cosα*sind)),
整理一下是(cosα*r*cosd-sinα*r*sind,sinα*r*cosd+cosα*r*sind),對(duì)吧,那這個(gè)r*cosd不是別人r*sind也不是別人,不就是原來的x,y嗎
所以實(shí)際上現(xiàn)在的坐標(biāo)就是(cosα*x-sinα*y,sinα*x+cosα*y),其中α就是旋轉(zhuǎn)的角度。
那么在空間中呢?也是一樣的道理,比如一個(gè)點(diǎn)繞著x軸旋轉(zhuǎn)α角度,那么它繞著x軸轉(zhuǎn)顯然x坐標(biāo)是不變的,這就相當(dāng)于這個(gè)點(diǎn)在y軸和z軸組成的平面上旋轉(zhuǎn)了α角度(x坐標(biāo)保留不變)。同理,一個(gè)點(diǎn)繞著y軸旋轉(zhuǎn)α角度,就相當(dāng)于這個(gè)點(diǎn)在z軸和x軸組成的平面上旋轉(zhuǎn)了α角度(y坐標(biāo)保留不變);一個(gè)點(diǎn)繞著z軸旋轉(zhuǎn)α角度,就相當(dāng)于這個(gè)點(diǎn)在x軸和y軸組成的平面上旋轉(zhuǎn)了α角度(z坐標(biāo)保留不變),那這樣就能直接從剛剛平面的計(jì)算得到三維的旋轉(zhuǎn)變換公式了:
用x′、y′、z′分別表示旋轉(zhuǎn)過后的x坐標(biāo)、y坐標(biāo)、z坐標(biāo)。用θ表示旋轉(zhuǎn)的角度(在右手坐標(biāo)系中,旋轉(zhuǎn)的正方向是右手螺旋方向,即從該軸正半軸向原點(diǎn)看是逆時(shí)針方向)
①繞X軸的旋轉(zhuǎn)
x′=x
y′=ycosθ?zsinθ
z′=ysinθ+zcosθ
②繞Y軸的旋轉(zhuǎn)
x′=zsinθ+xcosθ?
y′=y
z′=zcosθ?xsinθ
③繞Z軸的旋轉(zhuǎn)
x′=cosθ*x-sinθ*y
y′=sinθ*x+cosθ*y
z′=z
這樣的話,空間中任何一個(gè)點(diǎn)繞x、y、z軸的旋轉(zhuǎn)就都可以計(jì)算了,不過,為了在某種程度上方便一些,所以可以再多引入一個(gè)矩陣的概念:
單說矩陣的話,矩陣就是一張數(shù)表,比如

就是一個(gè)2X3的矩陣,就是一張數(shù)表,有2行有3列。所以說矩陣單說定義沒多大用處,重點(diǎn)是把矩陣作為一個(gè)計(jì)算工具來使用,是在很多地方都有用的。比如矩陣的數(shù)乘、矩陣的加法減法乘法等等等等。(其實(shí)關(guān)于矩陣,可以單獨(dú)寫一個(gè)函數(shù)庫,伴隨矩陣、矩陣轉(zhuǎn)置、可逆矩陣等等等等,反正沒什么用,但你可以寫著玩)?
在3D效果中用一下矩陣的乘法就差不多了,首先由于矩陣是一張數(shù)表,所以當(dāng)然,兩個(gè)數(shù)表之前怎么相乘肯定是人為定義的(當(dāng)然是有原因的定義),只要按照定義來即可,然后你就可能發(fā)現(xiàn)這是一個(gè)很方便的工具。
首先,矩陣相乘必須滿足這個(gè)條件:第一個(gè)矩陣的列數(shù)等于第二個(gè)矩陣的行數(shù)。然后相乘的規(guī)則是:第一個(gè)矩陣的行和第二個(gè)矩陣的列乘起來(對(duì)應(yīng)元素乘積作和),比如

這兩個(gè)矩陣是這樣相乘的:第一個(gè)矩陣的第一行乘第二個(gè)矩陣的第一列就是:第一行的第一個(gè)a11和第一列的第一個(gè)b11乘起來,再加上第一行的第二個(gè)a12乘以第一列的第二個(gè)b21,再加上第一行的第三個(gè)a13乘以第一列的第三個(gè)b31,這樣就算“乘”好了第一行和第一列,得到的這個(gè)東西就是新的矩陣的第一行的第一個(gè)。然后呢,第一個(gè)矩陣的第一行又繼續(xù)乘第二個(gè)矩陣的第二列,第一行的第一個(gè)a11乘以第二列的第一個(gè)b12,再加上第一行的第二個(gè)a12乘以第二列的第二個(gè)b22,再加上第一行的第三個(gè)a13乘以第二列的第三個(gè)b32,這樣就“乘”好了第一行和第二列,得到的這個(gè)數(shù)就是新的矩陣的第一行的第二個(gè)元素,以此類推,接下來還要第一個(gè)矩陣的第二行乘以第二個(gè)矩陣的第一列、最后是第一個(gè)矩陣的第二行乘以第二個(gè)矩陣的第二列,然后得到的這幾個(gè)數(shù),就排成圖中那樣的數(shù)表。如果不會(huì)的可以自己練習(xí)一下,比如

然后當(dāng)然,很明顯,根據(jù)剛剛給定的規(guī)則,你肯定知道矩陣的相乘不滿足交換律,AB≠BA
還有當(dāng)然,兩個(gè)矩陣要能相乘必須要滿足第一個(gè)矩陣的列數(shù)等于第二個(gè)矩陣的行數(shù),也就是說,如果第一個(gè)矩陣是一個(gè)mXs的矩陣,那么第二個(gè)矩陣就需要是sXn的矩陣(即第一個(gè)矩陣的列數(shù)等于第二個(gè)矩陣的行數(shù)),乘出來得到的新的矩陣就是一個(gè)mXn的矩陣,也就是一個(gè)有m行、有n列的數(shù)表
啰嗦了一堆,主要是會(huì)有各種各樣的學(xué)aeg的人,之前有一些人問過一些相似的問題,有的人學(xué)過、有的人沒學(xué)過、有的人學(xué)過忘了,所以我還是簡單地啰嗦了一些東西。
所以當(dāng)然,剛剛講的旋轉(zhuǎn)操作就可以用到矩陣,比如說平面的旋轉(zhuǎn),剛剛講的旋轉(zhuǎn)后的新的坐標(biāo)(x',y')就是(cosα*x-sinα*y,sinα*x+cosα*y),這不就是兩個(gè)數(shù)嗎,當(dāng)然可以看成一個(gè)列向量

或者就是一個(gè)矩陣,對(duì)吧,那么旋轉(zhuǎn)后的點(diǎn)就可以用兩個(gè)矩陣相乘來計(jì)算:

對(duì)吧,根據(jù)矩陣相乘的規(guī)則,第一個(gè)矩陣的第一行乘以第二個(gè)矩陣的第一列,就是第一行的第一個(gè)乘以第一列的第一個(gè)(即cosθ*x)再加上第一行第二個(gè)乘以第一列的第二個(gè)(即sinθ*y),然后得到的cosθ*x-sinθ*y就是新的矩陣的第一行的第一個(gè)(即x'),同理y'當(dāng)然等于sinθ*x+cosθ*y了
所以在空間中的旋轉(zhuǎn)也是同樣的道理,
①繞X軸的旋轉(zhuǎn)
x′=x
y′=ycosθ?zsinθ
z′=ysinθ+zcosθ
這個(gè)可以寫成:

②繞Y軸的旋轉(zhuǎn)
x′=zsinθ+xcosθ?
y′=y
z′=zcosθ?xsinθ
這個(gè)可以寫成:

③繞Z軸的旋轉(zhuǎn)
x′=cosθ*x-sinθ*y
y′=sinθ*x+cosθ*y
z′=z
這個(gè)可以寫成:

其實(shí)你乘法看熟悉了的話,肯定一眼就看出這個(gè)計(jì)算了
不管繞哪個(gè)軸旋轉(zhuǎn),乘法中第二個(gè)矩陣都是(x,y,z),而且第一個(gè)矩陣都是很統(tǒng)一的這個(gè)

這樣的話,繞哪個(gè)軸旋轉(zhuǎn)就是哪個(gè)旋轉(zhuǎn)矩陣再乘(x,y,z)列矩陣即可,
比如繞x軸旋轉(zhuǎn),你就拿

這個(gè)旋轉(zhuǎn)矩陣去乘即可
那么矩陣怎么用代碼表示呢?用排除法也知道肯定是用table?。?span id="s0sssss00s" class="color-blue-02">用“一想法”也可知道,那什么是“一想法”呢:就是說,一想想不就一下就知道了嗎。當(dāng)然類似“一想法”的還有“一眼法”,意思是:看一眼不就知道了嗎,比如你用看一眼就解題的方法,那叫什么,那叫“一眼法”?。?/span>)
一個(gè)矩陣當(dāng)然有m行n列,所以一個(gè)mXn的矩陣肯定不能用一個(gè)一層的table就表示了,對(duì)吧,比如就上面那個(gè)矩陣,你肯定不能直接用{1,0,0,0,cosθ,-sinθ,0,sinθ,cosθ}來表示,對(duì)吧,因?yàn)檫@樣就完完全全失去了“行”和“列”的信息了,沒有了“行”和“列”的信息還叫一個(gè)數(shù)表嗎?所以你只需要把這個(gè)table給設(shè)定為一個(gè)兩層的table就可以表示一個(gè)矩陣了,比如上面的矩陣就表示成{{1,0,0},{0,cosθ,-sinθ},{0,sinθ,cosθ}}就行了,這樣你就知道這個(gè)矩陣一共有3行對(duì)不對(duì),每一行一共有3個(gè)元素,所以一共有3列對(duì)不對(duì),所以這是一個(gè)3X3的矩陣對(duì)不對(duì),比如你想知道這矩陣的第二行第三個(gè)元素,那是不是就是你這table的第二個(gè)元素這個(gè)表中表的第三個(gè)元素啊,也就是-sinθ
所以列矩陣

正常來說應(yīng)該表示為{{x},{y},{z}}對(duì)吧,這樣表示這個(gè)矩陣一共有3行,一共有1列,第一行第一列對(duì)應(yīng)的元素是x、第二行第一列對(duì)應(yīng)的元素是y等等等等(豎著和橫著當(dāng)然不一樣哈,顯然的,而且橫著的話,就是{{x,y,z}}了,兩個(gè)矩陣是截然不同的)
先講矩陣相乘代碼怎么寫吧:

輸入兩個(gè)由table代表的矩陣m1和m2,建立一個(gè)新的表mtx用來儲(chǔ)存相乘得到的新的矩陣。
怎么乘呢?一行乘一列、一行乘一列的乘,所以要for循環(huán)遍歷m1的行,由于剛剛講了兩個(gè)矩陣相乘得到的新矩陣的行數(shù)是等于第一個(gè)矩陣的行數(shù)的,所以代表新的矩陣的mtx表就要有#m1個(gè)表中表,每一次循環(huán)的時(shí)候就要建立一個(gè)mtx[i]={},然后你才能在這里面放東西。然后再開始相乘,一行的每一個(gè)元素對(duì)應(yīng)乘以一列的每一個(gè)元素然后加起來的結(jié)果就是mtx[i][j],也就是mtx這個(gè)新的矩陣的第i行第j列所對(duì)應(yīng)的元素
這整個(gè)就是標(biāo)準(zhǔn)的矩陣相乘的代碼了,但是在3D效果中,一般都是一個(gè)矩陣來乘以一個(gè)列矩陣(x,y,z),所以如果一個(gè)點(diǎn)儲(chǔ)存的時(shí)候,儲(chǔ)存成{{x},{y},{z}}就很煩(但是本身列矩陣就應(yīng)該這么寫的),如果你要單獨(dú)的寫矩陣的函數(shù)庫,當(dāng)然必須這么寫,但是那是單獨(dú)寫矩陣函數(shù)庫,比如

而如果你不是這樣單獨(dú)寫矩陣計(jì)算的函數(shù)庫的話,那完全可以為了3D這部分,稍微更改一下計(jì)算,因?yàn)榘岩粋€(gè)點(diǎn)儲(chǔ)存成{{x},{y},{z}}感覺麻煩,不如直接儲(chǔ)存成{x,y,z},但是這樣儲(chǔ)存就不表示一個(gè)矩陣了,但是你照樣可以模仿矩陣的相乘方式,來寫一個(gè)假的矩陣相乘、這個(gè)“矩陣相乘”僅僅針對(duì)的是一個(gè)矩陣來乘以(x,y,z)這種東西:

這樣的話,這個(gè)代碼并不是計(jì)算兩個(gè)矩陣相乘的,而是針對(duì)性的為了計(jì)算(x,y,z)的坐標(biāo)變換,而寫的假的矩陣相乘。本身矩陣相乘應(yīng)該是比如{{1,0,0},{0,cosθ,-sinθ},{0,sinθ,cosθ}}乘以{{x},{y},{z}}然后得到{x'},{y'},{z'}},而現(xiàn)在只針對(duì)這類計(jì)算,針對(duì)性的寫一個(gè)函數(shù),就可以直接用{{1,0,0},{0,cosθ,-sinθ},{0,sinθ,cosθ}}“乘以”{x,y,z}然后得到{x',y',z'}了
然后就能很方便的寫出某點(diǎn)繞某軸旋轉(zhuǎn)得到的新的點(diǎn)了:


上面的frx、fry、frz函數(shù)就分別是一個(gè)平面繞x、y、z軸旋轉(zhuǎn)angle角度,然后返回一個(gè)新的平面。剛剛說了,為了方便,把一個(gè)點(diǎn)表示為{x,y,z}而不是矩陣那樣的{{x},{y},{z}},所以要表示一個(gè)平面就是把這些一個(gè)平面的點(diǎn)裝起來就是了,比如用{{x1,y1,z1},{x2,y2,z2},{x3,y3,z3}}就可以表示由{x1,y1,z1}和{x2,y2,z2}和{x3,y3,z3}這三個(gè)點(diǎn)組成的平面(注意這里的點(diǎn)的順序不能亂,比如你一個(gè)平面有10個(gè)點(diǎn),那么你這10個(gè)點(diǎn)在這table里的順序不能亂,因?yàn)槟阕詈笠粋€(gè)平面要連線成一個(gè)繪圖不是嗎,所以你點(diǎn)的順序不能亂,比如順時(shí)針啊、逆時(shí)針啊,點(diǎn)的順序肯定不能亂,不能亂序就把這些點(diǎn)的數(shù)據(jù)儲(chǔ)存進(jìn)去了)。然后一個(gè)平面旋轉(zhuǎn)其實(shí)就是對(duì)平面上的每一個(gè)點(diǎn)都旋轉(zhuǎn)變換,所以可以看到上面的函數(shù),輸入一個(gè)代表平面的表p,然后p這個(gè)平面有很多點(diǎn),每個(gè)點(diǎn)都要旋轉(zhuǎn)變換,所以for循環(huán)遍歷p平面的每一個(gè)點(diǎn),對(duì)每一個(gè)點(diǎn)使用剛剛的假的矩陣相乘函數(shù),也就是這一部分代碼:

對(duì)于旋轉(zhuǎn),除了剛剛的繞x、y、z軸旋轉(zhuǎn)的旋轉(zhuǎn)矩陣,也有比如繞任意過原點(diǎn)的軸旋轉(zhuǎn)的旋轉(zhuǎn)矩陣,假設(shè)(u,v,w)是一個(gè)單位向量(注意這里要是單位向量,即長度為1的向量),那么一個(gè)點(diǎn)(x,y,z)繞著(u,v,w)旋轉(zhuǎn)θ度的旋轉(zhuǎn)矩陣是這樣的:

這再大一坨也是一個(gè)3X3的矩陣,就和剛剛的比如繞z軸旋轉(zhuǎn)的旋轉(zhuǎn)矩陣是一回事,直接拿這個(gè)矩陣去和(x,y,z)“乘”就是了

這個(gè)就是一個(gè)平面繞著過原點(diǎn)的任意軸(用一個(gè)單位向量指定)旋轉(zhuǎn)得到新平面的函數(shù),可以看到結(jié)構(gòu)當(dāng)然和剛剛講的frx等等一樣的,只不過這次這個(gè)旋轉(zhuǎn)矩陣有一大坨而已。第三個(gè)參數(shù)axis就是填入一個(gè)單位向量的,用來指定旋轉(zhuǎn)軸,比如填{√3/3,√3/3,√3/3}這個(gè)單位向量。
講了一些簡單的計(jì)算以后,介紹一下怎么建模,基于剛剛的設(shè)定,所以將一個(gè)點(diǎn)用一個(gè)一層的table表示,即{x,y,z},而一個(gè)平面就用兩層的table表示,如{{-1,-1,-1},{1,-1,-1},{1,1,-1},{-1,1,-1}}就是4個(gè)點(diǎn)構(gòu)成的一個(gè)面,每個(gè)點(diǎn)都有x,y,z坐標(biāo),而如果是一個(gè)立體圖形的話就有很多個(gè)平面,所以用一個(gè)三層的table表示,也就是把一個(gè)個(gè)表示平面的table再裝進(jìn)一個(gè)大的table里,這個(gè)大的table就表示一個(gè)立體圖形了
當(dāng)你有了一個(gè)立體圖形以后,你就可以進(jìn)行相應(yīng)的連線了,即把立體圖形的每個(gè)平面的點(diǎn)按順序連接起來就得到相應(yīng)的繪圖了,當(dāng)然,由于aeg里只有x和y,并沒有z,所以在連線的時(shí)候直接去掉z坐標(biāo),就能得到立體圖形在屏幕上的投影了。意思就是,你建模的時(shí)候,先不管三七二十,按照空間中的點(diǎn)用3個(gè)坐標(biāo)來算,最后經(jīng)過一系列什么旋轉(zhuǎn)、平移、縮放等操作過后,在最后把你的立體圖形拿來連線的時(shí)候,直接丟掉z坐標(biāo)就可以了,這樣就有了一個(gè)立體圖形的正投影。
那么連線怎么連呢?這個(gè)就很簡單了,比如如果是一個(gè)平面的話,假設(shè)這個(gè)平面是{{-1,-1,-1},{1,-1,-1},{1,1,-1},{-1,1,-1}}這4個(gè)點(diǎn)構(gòu)成的一個(gè)面,然后你一個(gè)點(diǎn)連接下一個(gè)點(diǎn)連接下一個(gè)點(diǎn)就是了,比如這個(gè)你就連成m -1 -1 l 1 -1 l 1 1 l -1 1即可,對(duì)吧,也就寫個(gè)簡單代碼的事:

還有就是,因?yàn)樾D(zhuǎn)等等這些操作算出來的點(diǎn)可能有很多位小數(shù),所以在連線之前,肯定要把table里的數(shù)字取舍一下,這里直接取一位小數(shù)了,所以我這個(gè)函數(shù)庫生成的立體投影最多一位小數(shù)。這個(gè)函數(shù)很明顯,輸入的是一個(gè)平面,也就是一個(gè)裝有很多點(diǎn)的兩層table,然后遍歷這個(gè)平面的每一個(gè)點(diǎn),然后再遍歷每個(gè)點(diǎn)的x、y、z坐標(biāo),對(duì)它們進(jìn)行取舍。
然后再是連線的操作:

這個(gè)連線的函數(shù),用前面提到的“一眼法”就能知道,輸入的是一個(gè)平面,然后points_int函數(shù)把這個(gè)平面的每個(gè)點(diǎn)的坐標(biāo)都取舍一下(int當(dāng)然指整數(shù),但是我這里就是指保留1位小數(shù),請(qǐng)不要揍我),然后再開始連線,遍歷每個(gè)點(diǎn),只把x和y坐標(biāo)連起來,z坐標(biāo)直接不連、也就是直接丟掉z坐標(biāo),最后返回的字符串s就是這個(gè)平面在屏幕上的投影了,就是一個(gè)繪圖代碼了。“一眼法”萬歲!
除開點(diǎn)的變換、模型的建立、最后的連線,還有一點(diǎn)需要提一下,就是“層數(shù)”問題,比方說,一個(gè)正方體一共有6個(gè)面,你不能讓這6個(gè)面的層數(shù)都是0吧,因?yàn)槟菢拥脑捑椭卦谝黄鹆耍雌饋砭筒粫?huì)是一個(gè)正方體了。正方體6面,實(shí)際上不論怎么轉(zhuǎn)怎么動(dòng),你最多只能看到3個(gè)面對(duì)吧,這意味著當(dāng)你應(yīng)該要看到3個(gè)面的時(shí)候,就要讓你能看到的面在上層,比如讓它們層數(shù)為1,而不能看到的面就要在下層、層數(shù)為0,所以你還需要計(jì)算立體圖形在某個(gè)角度時(shí)的層數(shù),
因?yàn)橛羞@空間的前后遮擋關(guān)系,所以你要讓在上層的平面的層數(shù)比在下層的平面的層數(shù)大,這樣才能看到正確的投影。
對(duì)于凸多面體而言,只需要找到其內(nèi)部的一點(diǎn)(只要能保證這一點(diǎn)在凸多面體內(nèi)部即可),就可以計(jì)算層數(shù)了。遍歷凸多面體的每個(gè)面,利用叉乘求出這個(gè)面的法向量,但是法向量只不過是垂直于這個(gè)平面而已,法向量的朝向是有兩種可能的,此時(shí),就要用我們剛剛找到的凸多面體內(nèi)部的一點(diǎn)了,為了“修正”你這個(gè)平面的法向量的方向,用這個(gè)內(nèi)部的點(diǎn)和這平面上的某一點(diǎn)連線(比如和這個(gè)平面的頂點(diǎn)1連線、比如和這平面的頂點(diǎn)2連線等等),然后設(shè)定一個(gè)向量向量的方向是內(nèi)部點(diǎn)開始到平面某點(diǎn)上結(jié)束,這樣的話,計(jì)算一下這個(gè)向量和開始得到的法向量的夾角,如果夾角大于90就將法向量的方向反向,這樣就得到了“修正”后的平面的法向量了然后,每個(gè)面都用這種方式得到它們的法向量,利用這些法向量的朝向就能設(shè)定各個(gè)面的層數(shù)了。法向量的z坐標(biāo)大于0的就是上層的、法向量的z坐標(biāo)小于0的就說明在下層。(其實(shí)這個(gè)道理很簡單,凸多面體內(nèi)部的一點(diǎn)到某平面的方向就說明了平面在下層或上層,比如你這內(nèi)部點(diǎn)要往下走才能最后走到這個(gè)平面(走的路徑垂直于平面),那自然說明這個(gè)平面是下層的,反過來,若你這內(nèi)部點(diǎn)要向上走(走的路徑垂直于平面),最后才能走到這平面,那當(dāng)然說明你這個(gè)平面是上層的。所以,求出平面的法向量以后,就要根據(jù)內(nèi)部點(diǎn)到那個(gè)平面的向量的方向來“修正”法向量的方向,讓法向量和你這內(nèi)部點(diǎn)到平面的向量的方向一樣,這樣法向量的方向自然就能代表你這個(gè)平面的方向了,自然就能設(shè)定層數(shù)了)
這樣的話,就算是介紹了一下3D函數(shù)庫里面基本的構(gòu)建。代碼之類的就在后面的視頻介紹