在上一篇文章中,我使用了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圖拙劣)

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

因此我們可以看到球的幾個(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)系所帶來的陰暗顏色變化等,以及球體背面是否需要映射等。留作下一步解決吧~