离赛程结束还有3天~
今天我们要来延续昨天的问题探讨~
我在上一篇似乎没有把问题描述的很好,所以可能大家蛮confused的 :(
我这边再仔细的讲讲这个问题的细节:
图片中描述着一个横置的小箱子,这个小箱子里面悬吊着两颗球,两颗球的水平距离为z。
而现在我们要把这个小箱子的内部景象画在一张纸上,
这边虽然是说画在纸上,不如说是以一点透视的方式『投影』在纸上。
而如果现在纸的位置距离箱子最外边的一颗球W单位,而这颗球在纸上被投影的长度是原本球的直径的0.8倍,
那麽请问比较靠里面的那颗球(与另一颗球间距z),他被投影在纸上时,他的投影长度会是原本球直径的几倍?
实际上如果要用上面这张图来思考几何比例,可能会不太好理解,所以我们在这边把这个案例画成像下面这样:
在这边我们可以看到投影的射线最後会聚在一个点上,我们会把这个点叫做『视点
』。
而因为两颗球最後都是被投影到同一张纸上,所以我们就可以像上图
这样透过比例运算去算出来另外一颗球投影在纸上的长度应该会是4W/(5W+z)
倍的球直径长度。
而在这边,因为4W
也就是视点
到投影面
的距离,而5W+z
则可以分成4W
和W+z
两部分,也分别就是(视点
到投影面
)和(投影面
到物体
)的距离。
所以这边我们其实可以推导出一个关系
就是:
透视缩放率(Scale Ratio) = P/(P+Z)
其中P
会是视点
到投影面
的距离(其实就是焦距
),Z
则是投影面
到物体
的距离,也就是canvas
三维座标系下的Z轴
座标(假设座标原点就在投影面上)。
有了像这样的一个公式,我们就有办法在canvas
上面构筑景深
关系
我们接下来会用一个Codepen
上面的简单的案例来演示如何渲染景深~
Codepen: https://codepen.io/team/basedesign/pen/mvJQWX
备用连结: https://codepen.io/mizok_contest/pen/GRvJGNG
其实这是一个我在逛
codepen
时偶然发现的一个小型实作,而这个案例还蛮适合用来讲解这次提及的主题。
我们可以看到这个作者在画面中创造了大量的方形粒子,然後让他们在3D
空间中移动。
然而这是怎麽做到的呢?
首先我们可以先看看核心的透视计算部分
,也就是在54
行的project
方法(隶属於Dot
类)
// Do some math to project the 3D position into the 2D canvas
project() {
this.scaleProjected = PERSPECTIVE / (PERSPECTIVE + this.z);
this.xProjected = (this.x * this.scaleProjected) + PROJECTION_CENTER_X;
this.yProjected = (this.y * this.scaleProjected) + PROJECTION_CENTER_Y;
}
在这个方法中,我们可以看到他运用了我们刚刚提到的透视缩放率
公式,而且还同时运用在x
和y
轴上,
x
和y
分别是每颗粒子的x
和y
座标,而这个坐标系的中心点是位於canvas
正中央。
接着, 有了可以投影x
,y
座标的方法之後,再来就是只要能够让粒子的z
轴座标能够随时间变化,就能够产生如同画面中一般的动画了~
而这个案例是透过GSAP的TweenMax.to()方法来做循环补间动画
,我们可以在Dot
类的constructor
(第44行) 看到这个方法的运用方式,
这个方法简单来说就是可以让目标物件的property根据时间产生变化,详细可以看 https://greensock.com/docs/v2/TweenMax/static.to()
在这个案例中我们收获最大的点就是x
,y
投影座标的计算,理解了他的计算方式,我们其实就可以试着用canvas
画一个基本的3D
物件出来了~
Codepen: https://codepen.io/mizok_contest/pen/vYJOaZq
其实讲完了上面的案例之後,这个案例应该就显得很简单了~
但是我还是讲讲程序的细节~
function draw(ctx,dots,size){
const projective = 500; //假定透视焦距为500
dots.forEach((o,i)=>{
// 计算透视投影後的座标阵列
const projectArr = project(o[0],o[1],o[2], projective,ctx.canvas);
const px= projectArr[0];
const py= projectArr[1];
// 把座标点位画出来
drawCircle(ctx, px, py, 5, 'white', 1);
// 如果座标跟座标之间的距离刚好是边长,那就连线
for(let j =0;j<dots.length;j++){
if(dist(dots[i],dots[j])==size){
let projectArrAnother = project(dots[j][0],dots[j][1],dots[j][2], projective,ctx.canvas);
ctx.beginPath();
ctx.moveTo(px,py);
ctx.lineTo(projectArrAnother[0],projectArrAnother[1]);
ctx.lineWidth=5;
ctx.strokeStyle="white";
ctx.stroke();
ctx.closePath();
}
}
})
}
到这边为止我们就成功画出了第一个透视3D物件了,而且我们还可以透过操作x/y/z值让他在空间中移动! 在接下来的系列文中,我们会提到稍微进阶一点的操作,敬请期待~ :D
<<: 第 28 集:Bootstrap 客制化 component 元件样式
如果你有时间的,不妨也拿笔本子测试一下,看看能做多少,在文章点赞留言我会第一时间发你答案!或者加我...
今天想要来记录这篇文件 Why the “volatile” type class should n...
前面说了那麽久,但看起来好像这些都不是拿来给一般民众使用的AR装置,之後AR又有什麽变化呢!?让我们...
今天这篇是我们实作库米狗屋●KummyShop的情境电商模拟的最终章了!今天我们要把先前建的一堆订单...
影片继续看下去 选择 Rotate 底下的 Direction 就是顺时钟还是逆时钟旋转。 我们选择...