如你所見。這篇就是要講下使用transformjs製作星球的過程。你也可以無視文章,直接去看原始碼和線上演示:
程式碼100行多一點,直接看也沒有什麼壓力。下面分幾步講解下。
生成球上點座標
設球心為 (a,b,c),半徑為r,
則球的標準方程為 (x-a)²+(y-b)²+(z-c)²=r²
這裡假設球心的(0,0,0),則:
標準方程為 x²+y²+z²=r²
因為可以渲染的時候再把球的本地座標轉為世界座標進行位移,所以球心(0,0,0)便可以。
function randomPoints() {
var x, y, z, j = -1, i = 0;
for (; i < size; i++) {
x = getRandomNumber(-250, 250);
y = getRandomNumber(-250, 250);
j *= -1;
if (x * x + y * y <= r * r) {
z = j * Math.sqrt(Math.abs(r * r - x * x - y * y));
positions.push({x: x, y: y, z: z});
rd_positions.push({x: x, y: y, z: z});
}
}
}
上面的生成過程很取巧:
- 1.隨機生成2D內的圓內的座標x和y。(x * x + y * y <= r * r就是表示圓內)
- 2.根據2D維度的座標推算其屬於球面上的z
其中positions用來存放所有點的local座標,rd_positions用來以後存放投影后的座標。
座標轉Dom
function createImgs() {
var i = 0,
len = positions.length;
for (; i < len; i++) {
var img = document.createElement("img");
img.style.position = "absolute";
img.style.left = "0px";
img.style.top = "0px";
img.src = "../asset/star.png";
document.body.appendChild(img);
Transform(img,true);
transformImg(img,i);
img_list.push(img);
}
}
所有的點都對應建立一個絕對定位的圖片,並且通過Transform(img,true)給img注入transformation能力。注意第二個引數true代表關閉透視投影,因為投影下面會自己去實現。
投影變換
function positionsProjection() {
var index = 0,
len=positions.length;
for (; index < len; index++) {
var p = positions[index];
var rp = rd_positions[index];
//perspective projection
//rp.x = p.x * distance / Math.abs(camera_position.z - p.z);
//rp.y = p.y * distance / Math.abs(camera_position.z - p.z);
//orthogonal projection
rp.x = p.x ;
rp.y = p.y ;
}
}
為了簡單起見,把球心和攝像機(也可以叫眼睛、亦或是視點)的座標分別設定為:
center = {x: 300, y: 300, z: 0},
camera_position = {x: 300, y: 300, z: 500},
distance = 600,
distance代表攝像機到投影平面的距離,攝像機就固定在球心的正前方不動,這樣進行透視投影或正交投影計算起來無比方便,免去用齊次座標、4*4矩陣的過程。如下簡單推導便可:
這裡需要注意的是,上面是透視投影的圖解,會產生近大遠小的感覺。透視投影是視錐體(上圖沒有把視錐體畫出來),正交投影是立方體。
正交投影如下圖解,x和y座標投影后不變就可以了:
可以這麼理解:
- 透視投影從一個點看無數個點
- 正交投影從無數個點看無數個點
旋轉
function rotate() {
var cx,
z,
i = 0,
len=positions.length;
for (; i < len; i++) {
cx = positions[i].x;
z = positions[i].z;
positions[i].x = positions[i].x * Math.cos(step_angle) - positions[i].z * Math.sin(step_angle);
positions[i].z = positions[i].z * Math.cos(step_angle) + cx * Math.sin(step_angle);
}
}
可以看到,上面是繞y軸旋轉,所以y的座標不變,x和z需要經過下面的matrix變換:
Transformation
function transformImg(img, i) {
var z = positions[i].z;
img.translateX = center.x + rd_positions[i].x;
img.translateY = center.x + rd_positions[i].y;
//projection
img.scaleX = img.scaleY = 0.5 * distance / Math.abs(camera_position.z - z);
img.style.opacity =0.1+ 1 - (r - z) / (2 * r);
}
function render(){
var i = 0,
len=positions.length;
for (; i < len; i++) {
transformImg(img_list[i],i);
}
}
初始化和迴圈
function tick() {
rotate();
positionsProjection();
render();
requestAnimationFrame(tick);
}
(function () {
randomPoints();
createImgs();
positionsProjection();
tick();
})();
通過通過上面幾行程式碼串整個流程。通過requestAnimationFrame迴圈執行tick。
最後
為了加深理解,你可以把原始碼 clone下來,然後改程式碼實現:
- 試試繞著z軸旋轉
- 試試繞著x軸旋轉
- 試試切換下透視投影和正交投影
- 透視投影的時候試著修改攝像機的z座標
- 正交投影的時候試著修改攝像機的z座標
- 透視投影的時候試著修改到投影面的距離
- 正交投影的時候試著修改到投影面的距離
- 不使用星星素材換過其他素材會達到意想不到的酷炫效果
第二種實現方式:試試Transform(img,false)
因為Transform第二個引數不傳,或者設定為false的時候是開啟透視投影的。
所以可以設定img.translateZ來使用瀏覽器自身的透視投影,省去positionsProjection過程。
建立圖片的時候,使用下面的方式注入Transformation能力,
Transform(img, false);
- 即開啟透視投影,
- 即近大遠小,
- 即不用自己去positionsProjection
- 即不用自己去設定圖片的scaleX和scaleY
渲染的時候直接使用原始座標便可:
function transformImg(img, i) {
var p = positions[i];
img.translateX = p.x;
img.translateY = p.y;
img.translateZ = p.z;
img.style.opacity =0.1+ 1 - (r - p.z) / (2 * r);
}
function render(){
var i = 0,
len=positions.length;
for (; i < len; i++) {
transformImg(img_list[i],i);
}
}
迴圈和初始化,不再需要投影過程:
function tick() {
rotate();
render();
requestAnimationFrame(tick);
}
(function () {
randomPoints();
createImgs();
tick();
})();
transformjs
transformjs提供了基礎的transformation能力,不與任何時間和運動庫繫結。雖然官網demo簡單,但是稍微費點腦細胞就可以做出很酷炫的效果。所以酷炫靠大家,用transformjs就對了。
傳送門:transformjs 主頁 | transformjs Github
所有例子可以在上面找到。