canvas實(shí)現(xiàn)簡(jiǎn)單3D旋轉(zhuǎn)效果

在上一篇文章中,我使用了canvas制作了一個(gè)動(dòng)態(tài)的時(shí)鐘(點(diǎn)此處查看),難點(diǎn)在于找到旋轉(zhuǎn)的坐標(biāo)。
近日看到多處實(shí)現(xiàn)3D旋轉(zhuǎn)效果的js庫(kù),心中癢癢,也想自己在canvas上制作3D效果
請(qǐng)先看DEMO

想在2D的canvas上繪制3D效果,最直觀的方法是三維坐標(biāo)轉(zhuǎn)換為二維坐標(biāo)。轉(zhuǎn)換的方法是考慮映射,即假想在人眼和canvas之間存在一個(gè)三維球體,所看到的球體每一個(gè)點(diǎn)都可以映射到球后的canvas上。參見圖1(請(qǐng)?jiān)徖L圖拙劣)

圖1

給球加上坐標(biāo),即


圖2

因此我們可以看到球的幾個(gè)因素會(huì)影響canvas坐標(biāo)的變化,因此設(shè)置球?qū)ο螅喊税霃?、到眼睛和canvas的距離以及后面會(huì)加上的星星數(shù)量。

var ball = { 
    rad: 100,
    starNum: 50,
    disToCanvas: 200,
    disToEye: 400
};

下面考慮將球坐標(biāo)投影到canvas坐標(biāo),為了便于計(jì)算,將球心作為原點(diǎn)(0,0,0),假設(shè)球心投影到canvas的點(diǎn)是(0,0)(需要對(duì)canvas進(jìn)行原點(diǎn)變換)。先根據(jù)距離、球的大小計(jì)算出投影需要的整個(gè)面積。都是數(shù)學(xué),直接貼上代碼:

var cvs = {
    size: 0,
    circlePos: {
        x: 0,
        y: 0
    },
    setCvsSize: function() {
        var cs = ball.rad / ball.disToEye; //arcsin
        var theta = Math.asin(cs);
        cvs.size = 2 * Math.tan(theta) * (ball.disToCanvas + ball.disToEye);
        drawing.width = cvs.size;
        drawing.height = cvs.size;
        ctx.translate(drawing.width / 2, drawing.width / 2);
    }
};

這段代碼的是設(shè)置最終的投影對(duì)象,包含size、circlePos(中心坐標(biāo)),以及設(shè)置投影映射(尺寸、原點(diǎn))的函數(shù),為了便于計(jì)算,將畫布的原點(diǎn)設(shè)置到投影對(duì)象的中心了。
下面就是投影的代碼塊了,先看代碼:

tans3DTo2DProj: function(pos3D) {
        var pos2D = {};
        var scale = (ball.disToEye - pos3D.y) / (ball.disToEye + ball.disToCanvas);
        pos2D.x = pos3D.x / scale;
        pos2D.y = pos3D.z / scale;
        return pos2D;
    },

這段代碼是我定義變換對(duì)象中的一個(gè)方法,原理顯而易見,根據(jù)比例設(shè)置canvas上的坐標(biāo)。有了映射方法,還需要有3d旋轉(zhuǎn)方法。實(shí)際上旋轉(zhuǎn)三維坐標(biāo)與旋轉(zhuǎn)二維求其坐標(biāo)的原理是一致的,把旋轉(zhuǎn)分為水平和垂直兩種(其他的旋轉(zhuǎn)方向都可以通過這兩種的旋轉(zhuǎn)組合達(dá)到),根據(jù)旋轉(zhuǎn)方向,沿著旋轉(zhuǎn)軸線的垂直面將球體剖開轉(zhuǎn)為二維即可,二維的旋轉(zhuǎn)求解坐標(biāo)可參見我的上一篇文章:使用canvas制作簡(jiǎn)易實(shí)時(shí)動(dòng)態(tài)時(shí)鐘,此不贅述。
因此完整的轉(zhuǎn)換對(duì)象定義如下,包含映射、水平旋轉(zhuǎn)、垂直旋轉(zhuǎn)、映射后坐標(biāo)更正為canvas的坐標(biāo)習(xí)慣以及將映射、更正canvas坐標(biāo)集成起來的方法。

var transUtil = {
    tans3DTo2DProj: function(pos3D) {
        var pos2D = {};
        var scale = (ball.disToEye - pos3D.y) / (ball.disToEye + ball.disToCanvas);
        pos2D.x = pos3D.x / scale;
        pos2D.y = pos3D.z / scale;
        return pos2D;
    },
    horRotate3d: function(pos3D, theta) {
        var nexPos = {};
        nexPos.z = pos3D.z;
        nexPos.x = pos3D.x * Math.cos(theta / 180 * Math.PI) - pos3D.y * Math.sin(theta / 180 * Math.PI);
        nexPos.y = pos3D.x * Math.sin(theta / 180 * Math.PI) + pos3D.y * Math.cos(theta / 180 * Math.PI);
        return nexPos;
    },
    verRotate3d: function(pos3D, theta) {
        var nexPos = {};
        nexPos.x = pos3D.x;
        nexPos.y = pos3D.y * Math.cos(theta / 180 * Math.PI) - pos3D.z * Math.sin(theta / 180 * Math.PI);
        nexPos.z = pos3D.y * Math.sin(theta / 180 * Math.PI) + pos3D.z * Math.cos(theta / 180 * Math.PI);
        return nexPos;
    },
    transToCanvasPos: function(pos2D) {
        var nexPos = {};
        nexPos.x = pos2D.x;
        nexPos.y = -pos2D.y;
        return nexPos;
    },
    trans3Dto2DAll: function(pos3D) {
        var res = transUtil.transToCanvasPos(transUtil.tans3DTo2DProj(pos3D));
        return res;
    }
};

現(xiàn)在映射、旋轉(zhuǎn)的工具有了,就是假想球體、以及球體上的小星星,并繪制其映射圖案的問題了。
源碼不貼了,有需要可以參考完整代碼guthub。
不足的是還需要考慮球體的遠(yuǎn)近關(guān)系所帶來的陰暗顏色變化等,以及球體背面是否需要映射等。留作下一步解決吧~

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

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

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