使用html5繪制三維線框圖

前言

雖然我們每個人處在三維的世界中,但是計算機屏幕卻是二維的。那么如何將三維的世界展示到二維的屏幕上呢?這時候就需要用到投影。本質(zhì)上,屏幕上展示的是二維圖形,然而二維投影圖形會給我們一種視覺上的錯覺,大腦經(jīng)過聯(lián)想和組合,就會“認為”我們看到了三維物體。

3d.png

投影是一個數(shù)學(xué)上的術(shù)語,來源于生活。想想看,現(xiàn)在有一個立方體(假設(shè)它的表面是可以透光的,并且可以自由旋轉(zhuǎn)),有一束光從立方體上面照下來,為了簡化問題,這里只考慮平行光,不考慮點光源的情況(在實際的情況中,燈光在三維世界中很重要,影響著物體被如何渲染)。這樣,就在地面上留下了一個影子。類比來說,地面就是計算機的屏幕,影子就是屏幕上繪制的圖形,立方體就是實際要表現(xiàn)的東西。當立方體自由旋轉(zhuǎn)時,把每一次的影子記錄下來,當立方體全方位旋轉(zhuǎn)后,所有影子組合起來,就完完全全地描述了這個三維的立方體。這種情況被稱之為正交投影。

轉(zhuǎn)化

理論知識扯起來很容易,但是如何寫出實際的代碼就要看手上的功夫了。首先,要描述一個三維物體,需要一些度量值,也就是坐標了。三維物體嘛,當然得有三個坐標,在JavaScript中,可以使用數(shù)組來表示坐標結(jié)構(gòu)。每一個坐標數(shù)組包含了三個元素,代表了空間中的一個點(node)。兩個點,就組成了一條線(edge),多條線段就組成了線框圖(wireframe),也就是我想要的效果。三維圖形的基礎(chǔ)在于點,準確地表達每一個點的坐標非常重要。坐標又是基于坐標系的,所以首先的問題在于明確坐標系。在這個項目,我使用右手系,和canvas繪圖標準的坐標系統(tǒng)統(tǒng)一起來 :坐標原點在屏幕左上角,X軸正方向向右,Y軸正方向向下,Z軸正方向從內(nèi)向外。

基礎(chǔ)繪制

有了坐標系統(tǒng),一切都順風(fēng)順水了。在項目中,有一個index.html文件,主要包含了canvas標簽,大小是320X480。然后引用了cube.js文件,這個文件中處理了所有的邏輯。一開始,獲取繪圖環(huán)境,并移動坐標原點到畫布中心。

var c = document.getElementById('world');
var ctx = c.getContext('2d');
ctx.translate(160, 240)

然后就是立方體的一大堆描述數(shù)據(jù),中心在坐標原點,棱長為160。

//這是八個頂點
var node0 = [-80, -80, -80];
var node1 = [-80, -80,  80];
...
var node7 = [ 80,  80,  80];
var nodes = [node0, node1, node2, node3, node4, node5, node6, node7];
//連接頂點,形成十二條棱
var edge0  = [0, 1];
var edge1  = [1, 3];
...
var edge11 = [3, 7];
var edges = [edge0, edge1, edge2, edge3, edge4, edge5, edge6, edge7, edge8, edge9, edge10, edge11];

最后把每條棱繪制出來,為了表現(xiàn)效果,著重繪制了每個頂點,并使用不同的顏色區(qū)分頂點和棱長。

var draw = function(edges, nodes, texts) {
    ctx.beginPath();
    ...
    ctx.save(); 
    ctx.translate(0.5,0.5); 
    for (var e = 0; e < edges.length; e++) {
        var n0 = edges[e][0];
        var n1 = edges[e][2];
        var node0 = nodes[n0];
        var node1 = nodes[n1];
        ctx.moveTo(node0[0], node0[1]);
        ctx.lineTo(node1[0], node1[1]);
     }
    ctx.stroke();
    ctx.restore();
    ...
}

ctx.stroke是核心的描邊方法,在調(diào)用此方法前用moveTolineTo勾畫好路徑。在這一步有兩個點要注意:第一是繪制之前使用beginPath方法來重新開始新的繪圖路徑,防止循環(huán)調(diào)用draw方法時出現(xiàn)殘影現(xiàn)象。第二是繪制前先使用save方法保存繪圖環(huán)境,然后translate(0.5,0.5)偏移0.5像素,繪制完成后復(fù)原,來緩解canvas的像素模糊效應(yīng)(出現(xiàn)的原因是canvas繪圖時最小的繪制單位是一像素,當使用整數(shù)點坐標繪制一像素寬的線段時,線條會被擴展到兩個像素寬,使得線段看起來比較模糊)。
繪制完成后,用瀏覽器觀察,結(jié)果不是很理想:因為屏幕上只有一個正方形而已。其中的原因也很好理解,盡管三維線框圖已經(jīng)被完全繪制在canvas上,但坐標系中的Z軸沒有被表現(xiàn)出來,只看到Z軸方向的投影。小學(xué)生都知道正方體的俯視圖是正方形,項目已經(jīng)成功了一半。
前言中說過,立方體的假設(shè)條件是可以自由旋轉(zhuǎn)?,F(xiàn)在就要用到這個條件,開始旋轉(zhuǎn)立方體,時時刻刻繪制正方體的每一次投影,在旋轉(zhuǎn)的時候,角度改變,頂點的位置由原始頂點的投影得到,三角函數(shù)派上用場。

旋轉(zhuǎn)起來

簡化問題,先考慮繞著Z軸旋轉(zhuǎn)的情況。從本質(zhì)上說,坐標系中的每一個點的坐標就是這個點在坐標軸上的投影位置。

sin.png

初始頂點(x,y)的位置可以用下方的公式一表示:

formula1.png

當繞Z軸旋轉(zhuǎn)β角度時,新的頂點(x',y')的位置用公式二表示:

formula2.png

根據(jù)腦子里還沒忘光的三角公式,可以把公式二展開成公式三:

formula3.png

將公式一代入公式三,最后得到核心的公式四:

formula4.png

有了核心的公式四,把數(shù)學(xué)語言翻譯成編程語言,寫到cube.js文件中。

var rotateZ3D = function(theta, nodes) {
    var sin_t = Math.sin(theta);
    var cos_t = Math.cos(theta);
    for (var n = 0; n < nodes.length; n++) {
        var node = nodes[n];
        var x = node[0];
        var y = node[1];
        node[0] = x * cos_t - y * sin_t;
        node[1] = y * cos_t + x * sin_t;
    }
}

X軸和Y軸上的旋轉(zhuǎn)與此類似。接下來的處理很簡單,監(jiān)聽鼠標拖動的事件,清空畫布ctx.clearRect(-160, -240, 320, 480);,根據(jù)拖動的距離和方向設(shè)定旋轉(zhuǎn)的角度以及所繞的坐標軸,以此為參數(shù)調(diào)用旋轉(zhuǎn)方法。更新頂點坐標后,以新的坐標點繪制線框圖。為了便于理解,在畫布上,我還繪制了坐標軸,在每個點上方添加了數(shù)字標識,這些都不是重點,就不一一細說了。

鏈接

后記

以前覺得所學(xué)到的各種高深的數(shù)學(xué)知識并沒有什么用處,完全是為了應(yīng)付考試才聽了一點兒?,F(xiàn)在看來,是我太年輕。教育與實踐的脫鉤讓很多學(xué)生不能夠?qū)W以致用。知識其實并不是力量,能夠運用知識才是力量,我正在努力挖掘自身的力量。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 三維幾何的矩陣變換: 平移變換: 比例變換: 繞X軸旋轉(zhuǎn): 繞Y軸旋轉(zhuǎn): 繞Z軸旋轉(zhuǎn): X軸反射變換: Y軸反射變...
    cain_huang閱讀 6,825評論 2 6
  • 1 序: 很多新接觸GIS的人員對地圖投影以及坐標系統(tǒng)很難理解,甚至做GIS開發(fā)做了好幾年的人也有這方面的疑惑,地...
    三維GIS那點事_王躍軍閱讀 17,842評論 3 43
  • 最近生活上發(fā)生了一點小轉(zhuǎn)折,忽然想起來翻看農(nóng)歷。原來又是一年七月了,算一算還有半年的光陰,又會感嘆:“這一年,過得...
    望月塵閱讀 486評論 0 1
  • 有緣結(jié)識簡書是因為親愛的蓉蓉,其實跟蓉蓉也好久沒有聯(lián)系了,廣州分開后,我們都有了各自的目標和方向,雖然過程不...
    煙花易冷_d940閱讀 219評論 2 0
  • 越來越渴望一份新的工作,我不想再繼續(xù)做這份工作。真的很無聊。每天都不知道自己在做什么,也不知道自己做的有什么意義。...
    好怪獸小姐閱讀 203評論 0 0

友情鏈接更多精彩內(nèi)容