廣州的十七做網(wǎng)站百度首頁(yè)優(yōu)化排名
本篇將緊接上篇的2D版本對(duì)3D版的負(fù)縮放矩陣進(jìn)行解讀。
(6.1):負(fù)縮放,負(fù)定矩陣和行列式的關(guān)系(2D版本)
既然three.js對(duì)3D版的負(fù)縮放也使用行列式進(jìn)行判斷,那么,2D版的結(jié)論用到3D上其實(shí)是沒(méi)毛病的,THREE.Line2出問(wèn)題,是因?yàn)轫旤c(diǎn)生成規(guī)則跟Mesh不一致。而我們上篇提到的案例,則是用了2D平面的繞序來(lái)套到3D上,想必也是不合理的。
下面我們先針對(duì)上篇最后提到的問(wèn)題做一個(gè)測(cè)試用例
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>three_3Ddeterminant</title><style>body {margin: 0;overflow: hidden;}</style><script src="three/build/three.js"></script><script src="three/examples/js/controls/OrbitControls.js"></script>
</head><body><script>var scene = new THREE.Scene();var container = new THREE.Object3D();scene.add(container);var geometry = new THREE.PlaneGeometry(100, 50);var material = new THREE.MeshBasicMaterial({color: 0x993300, side:THREE.DoubleSide});var mesh = new THREE.Mesh(geometry, material);mesh.position.z = 100;container.add(mesh);var geometryBack = new THREE.PlaneGeometry(50, 100);var materialBack = new THREE.MeshBasicMaterial({color: 0x006600, side:THREE.DoubleSide});var meshBack = new THREE.Mesh(geometryBack, materialBack);meshBack.position.z = 90;container.add(meshBack);var width = window.innerWidth; var height = window.innerHeight; var camera = new THREE.PerspectiveCamera(60, width / height, 1, 20000);camera.position.set(0, 0, 500); var renderer = new THREE.WebGLRenderer();renderer.setSize(width, height);renderer.setClearColor(0x000000, 1); document.body.appendChild(renderer.domElement); function render() {container.rotation.y += Math.PI / 180;renderer.render(scene, camera);requestAnimationFrame(render);}render();var controls = new THREE.OrbitControls(camera,renderer.domElement);controls.addEventListener('change', render);</script>
</body>
</html>
現(xiàn)在我給兩個(gè)面片都開(kāi)啟了雙面渲染,并且讓面片所在的容器一直繞y軸旋轉(zhuǎn),讓大家大致了解這個(gè)測(cè)試用例的結(jié)構(gòu)。
真正測(cè)試的時(shí)候,我會(huì)把紅色設(shè)置為正面,綠色設(shè)置為反面,效果如下。
可見(jiàn),容器旋轉(zhuǎn)到正面時(shí),紅色面顯示綠色面隱藏,反過(guò)來(lái)則顯示綠色面。
單改容器的rotation雖然按我們的理解是一個(gè)正定矩陣,但當(dāng)它的rotation.y在90度到270度之間時(shí),點(diǎn)的繞序確實(shí)發(fā)生了變更,所以也隱藏了,這跟我們?cè)谏掀?D版本上的結(jié)論不吻合。
然后我們?cè)僭囋嚥恍D(zhuǎn),而是改scale:
var t = 0;function render() {t += 3 * Math.PI / 180;container.scale.z = Math.sin(t);renderer.render(scene, camera);requestAnimationFrame(render);
}
縮放的情況,點(diǎn)的繞序沒(méi)發(fā)生變更,但是正反面卻也交替了。
是不是有點(diǎn)神奇?嗯看來(lái)是我們對(duì)3D繞序的理解不正確。
為了方便觀察,我們把面片的xy向量繪制到容器上進(jìn)行觀察。與此同時(shí),既然我們是研究3D的版本,那么我們直接用THREE.AxisHelper把z軸也一并繪制出來(lái)(紅綠藍(lán)分別代表xyz)。
var axisHelper = new THREE.AxisHelper(300);
axisHelper.position.z = 100;
container.add(axisHelper);
接下來(lái),為了讓每條軸在不同的視角下都能看得見(jiàn),我們?cè)僮屜鄼C(jī)的位置適當(dāng)偏移一下
camera.position.set(100, 150, 500);
然后,旋轉(zhuǎn)和縮放的情況我們都看一下
咦這下我們似乎看出了一點(diǎn)端倪來(lái),不管是通過(guò)旋轉(zhuǎn)到達(dá)背面還是縮放到達(dá)背面,藍(lán)線都是朝著屏幕向里,這時(shí)候用藍(lán)線判斷正反面似乎更為合理。
沒(méi)錯(cuò)!我們拋開(kāi)繞序,返璞歸真,重新審視一下正反面,無(wú)非就是個(gè)朝向問(wèn)題。而作為面片的法線——垂直于面片的藍(lán)線正好與之相匹配。
我們?cè)偃€(gè)靜止的圖像看看
初始狀態(tài)
旋轉(zhuǎn)180度
scaleZ = -1
初始時(shí),x軸正向到y(tǒng)軸正向是逆時(shí)針旋轉(zhuǎn),z軸向前
旋轉(zhuǎn)180度時(shí),x到y(tǒng)變成順時(shí)針,z軸同時(shí)變成向后
scaleZ=-1時(shí),x到y(tǒng)的旋轉(zhuǎn)方向不變,但z軸向后了
這么一看,旋轉(zhuǎn)似乎是負(fù)負(fù)得正的結(jié)果,而z方向縮放-1的操作,則只有一個(gè)負(fù)向的變換。
事實(shí)上,圖2和圖3的兩個(gè)坐標(biāo)軸是無(wú)法只通過(guò)旋轉(zhuǎn)而完全重合。它就像我們的左右手,以及基于左右手定義的坐標(biāo)系一樣,是鏡像關(guān)系。
順帶說(shuō)句廢話:如果您是名學(xué)霸或者讀的專業(yè)跟化學(xué)有關(guān)系的話,您大概還會(huì)知道對(duì)映異構(gòu)體這個(gè)詞。它其實(shí)也是這種鏡像關(guān)系,不同繞序化學(xué)性質(zhì)也不一樣,因此有的藥名會(huì)帶著左旋右旋這樣的字眼。希望這個(gè)例子能幫助一部分小伙伴理解鏡像變換。
筆者直接從百度百科偷了個(gè)圖過(guò)來(lái),讓大家更形象的理解兩套坐標(biāo)系跟我們的左右手一樣無(wú)法完全重合,總有一個(gè)方向是反的。
講了這么多,我們對(duì)3D繞序的定義是時(shí)候跟2D區(qū)分開(kāi)來(lái)了。它應(yīng)該基于3個(gè)軸,如果定義右手坐標(biāo)系為正繞序,那左手坐標(biāo)系就是負(fù)繞序。
three.js使用的是右手坐標(biāo)系,大家比劃一下就可以發(fā)現(xiàn),旋轉(zhuǎn)180度后,坐標(biāo)系仍為右手,而縮放-1的則把坐標(biāo)系變成右手了。
因此對(duì)于3D版本的正負(fù)定矩陣,我們有這樣的一個(gè)性質(zhì)。
如果一個(gè)矩陣M可以把一個(gè)3維坐標(biāo)系從左手坐標(biāo)系變成右手坐標(biāo)系,或者反過(guò)來(lái),則矩陣M為負(fù)定矩陣,無(wú)更改則為正定矩陣,變換到共線共點(diǎn)就是零定矩陣。
下面我們就來(lái)推導(dǎo)一下,更改坐標(biāo)系繞序(左右手方向)的矩陣是不是也正好對(duì)應(yīng)上行列式的值。
初始時(shí),x軸向量為(1,0,0),y軸向量為(0,1,0),z軸向量是(0,0,1),如果矩陣是負(fù)定矩陣,那么不管你如何去旋轉(zhuǎn)它嘗試讓它跟變換前重合,就會(huì)發(fā)現(xiàn)總會(huì)有一個(gè)軸會(huì)變反的,這里我們?nèi)為變反的軸。但是我們不能直接說(shuō)它就是(0, 0, -1),因?yàn)榭赡軙?huì)被拉長(zhǎng),縮短或者扭曲。但不管怎樣,這個(gè)軸變換后的結(jié)果一定是跟初始方向不一致,也就是夾角大于90度。
這里想搞得通用點(diǎn),也可以用任意的3個(gè)不共面向量來(lái)做推導(dǎo),但是看上篇的2D版的式子都比較繁瑣了,這里就還是不要折磨大家。
先來(lái)說(shuō)步驟:
1 給定初始向量x(1,0,0),y(0,1,0),z(0,0,1),對(duì)xy做3D向量的叉乘(3D叉乘的結(jié)果為同時(shí)垂直于x和y的一個(gè)向量)計(jì)算,結(jié)果為(0,0,1),跟初始z的方向一致,也就是說(shuō),初始狀態(tài)的坐標(biāo)軸繞序?yàn)檎?#xff08;演算過(guò)程從略)
2 給定矩陣M,對(duì)初始點(diǎn)o(0,0,0),初始點(diǎn)x(1,0,0),初始點(diǎn)(0,1,0),初始點(diǎn)(0,0,1)做變換,得到新點(diǎn)o',x',y',z',同時(shí)算出向量o'x',o'y',o'z'
3 對(duì)o'x',o'y'做叉乘運(yùn)算,得到向量V,判斷它跟o'z'的夾角是否大于90度,可通過(guò)向量點(diǎn)乘運(yùn)算求得,大于0為小于90度,小于0則相反
4 點(diǎn)乘結(jié)果的正負(fù)即為矩陣縮放的正負(fù)
下面就讓我們開(kāi)始吧!
4*4矩陣對(duì)點(diǎn)的變換計(jì)算如下所示(按照前面2D版的經(jīng)驗(yàn),本次直接給最后一行填充單位矩陣的數(shù)值)
3D向量點(diǎn)乘和叉乘的公式也在此處給出
點(diǎn)乘:
叉乘:
下面我們分別計(jì)算矩陣對(duì)4個(gè)點(diǎn)變換的結(jié)果。
可以看到,這個(gè)變換跟2D的真的很像,都是最后一列完全一樣,所以在計(jì)算向量的過(guò)程中,最后一列會(huì)被完全消去。
得到的向量為
嗯,果然用給定的特殊坐標(biāo)算出來(lái)的值簡(jiǎn)單很多。有興趣的小伙伴可以自行搗鼓一般坐標(biāo)的演算,筆者就不發(fā)上來(lái)折磨大家了。
下面我們算o'x'和o'y'的叉乘值,公式前面已給出,這里我們直接套用。
然后再跟o'z'做點(diǎn)乘運(yùn)算
這兩步計(jì)算連一起可稱為向量的混合積
然后你會(huì)很驚喜地發(fā)現(xiàn),向量混合積的結(jié)果跟4*4矩陣中前3階的行列式一點(diǎn)不差!
加的部分
減的部分
同樣地,three.js也嚴(yán)格按照4階行列式定義書寫Matrix4的determinant,跟2D版本一樣,在最后一行用單位矩陣填充時(shí),4*4的結(jié)果跟3*3是沒(méi)有區(qū)別的。
然而4*4直接按定義寫,跟3*3相比,多項(xiàng)式的項(xiàng)數(shù)會(huì)從6個(gè)飆升到24個(gè)。對(duì)性能來(lái)說(shuō)并不友好。
所以我們看到three.js給的實(shí)現(xiàn)和注釋給出的鏈接的寫法是不太一樣的。
該實(shí)現(xiàn)用了行列式的余子式寫法(不懂的可以自行百度)把4*4行列式化為4個(gè)3階,并且很巧妙地取到最后一行和一列作為剔除因子。因?yàn)樵诳疾?D繞序,不研究透視w因子的情況下,最后一列一定是單位矩陣的數(shù)值,0,0,0,1,懂得編譯原理的朋友應(yīng)該就會(huì)明白,n41,n42和n43這3部分會(huì)因?yàn)榈谝粋€(gè)數(shù)為0而會(huì)對(duì)后面的運(yùn)算過(guò)程做優(yōu)化(當(dāng)然腳本語(yǔ)言未必優(yōu)化得徹底哈)。所以這一寫法可以體現(xiàn)出three.js開(kāi)發(fā)團(tuán)隊(duì)的功力確實(shí)夠深的,這個(gè)細(xì)節(jié)優(yōu)化也做到位了。
而3*3矩陣的行列式,就沒(méi)有做這樣的優(yōu)化,大概是這里還沒(méi)遇到瓶頸吧,換寫法未必合適。
當(dāng)然這里讓筆者有點(diǎn)困惑,求逆的時(shí)候它也拿了余子式寫法,并且取的不是最后一行最后一列,就有點(diǎn)謎,是怎么樣順手就怎么樣寫么?歡迎大佬們留言討論。
寫了這么多,結(jié)論是證明出來(lái)了,用左右手坐標(biāo)系的方式定義3D繞序后,繞序,正負(fù)縮放,正負(fù)定矩陣是完全等價(jià)的。
回過(guò)頭來(lái)說(shuō)說(shuō)2D,2D的叉乘是個(gè)閹割版,因?yàn)檫\(yùn)算過(guò)程中通常不想擴(kuò)展到3D,所以結(jié)果是一個(gè)數(shù)而非一個(gè)向量。但嚴(yán)格來(lái)說(shuō),2D向量叉乘的結(jié)果,它等于跟z軸平行的向量,長(zhǎng)度等于2D叉乘的數(shù)值。如果給2D坐標(biāo)全部加上z坐標(biāo),賦值為0,大家就會(huì)發(fā)現(xiàn),其結(jié)論跟3D版完全一致。感興趣的讀者可自行演算。
因此,2D版的正負(fù)定矩陣是3D版的一個(gè)特例,它們的原理完全一致。
下面來(lái)小結(jié)一下:
1 3D中的點(diǎn)繞序需要結(jié)合3軸,用跟左右手坐標(biāo)系類似的方式進(jìn)行定義
2 繞序有沒(méi)變更可通過(guò)兩向量叉乘后跟第三向量點(diǎn)乘求得,也就是混合積
3 混合積結(jié)果跟4*4矩陣前3階行列式一致
4 矩陣的正負(fù)縮放完全跟3*3矩陣的秩(或者最后一行填充了單位矩陣的4*4的秩)的符號(hào)直接對(duì)應(yīng)
5 3D正負(fù)定矩陣的性質(zhì):正定矩陣不修改坐標(biāo)系的左右手方向(繞序),負(fù)定矩陣會(huì)修改,零定矩陣會(huì)把坐標(biāo)系壓縮為共面,共線或者共點(diǎn)。零定矩陣不可逆。
好了,折騰完這波理論,下篇就暫時(shí)不折磨大家了,換回踩坑經(jīng)驗(yàn)方面的分享。感謝小伙伴們的支持和關(guān)注,我們下篇再見(jiàn)!