html5的canvas为我们提供了浏览器原生支持的绘图API。
(或者怎么说呢,大多数浏览器已经为我们提供了原生的绘图API:HTML5的canvas)
目前,这个API只提供2D context,并不支持3D绘图,但是web上从来就不缺牛人,各种canvas下绘制的3D效果层出不穷,令人吱吱称赞。(补充,后来有了WebGL)
有3D圣诞树:http://www.romancortes.com/blog/how-i-did-the-1kb-christmas-tree
有3D的FPS:使用 HTML 5 canvas 和光线投影算法创建伪 3D 游戏
还有3D俄罗斯:http://www.benjoffe.com/code/games/torus/
不胜枚举…
其实,无论canvas是否提供API,在我们目前这种二维显示设备下显示,势必都要将3维形状投影到2维平面坐标上。无论多炫的3D效果也只是二维平面上的投影。
对于此,读过《三体》特别是第三部《死神永生》的同学或许会大有感触吧。
人们总是喜欢用这样一个类比:想象生活在三维空间中的一张二维平面画中的扁片人,不管这幅画多么丰富多彩,其中的二维人只能看到周围世界的侧面,在他们眼中,周围的人和事物都是一些长短不一的线段而已。只有当一个二维扁片人从画中飘出来,进人三维空间,再回头看那幅画,才能看到画的全貌。
这个类比,其实也只是进一步描述了四维感觉的不可描述。
关于《三体——死神永生》里四维空间(不算时间维)的讨论,这里还有篇有意思的博文:四维世界的堵车问题
好了,言归正传。
下面提供一个三维到二维的投影算法(from www.benjoffe.com):
var theta = 4.2; //转角
var eleva = 0.6; //仰角
function iso(x,y,z){
var dist = Math.sqrt(x*x+y*y);
var angle = (x==0 && y==0) ? 0 : Math.atan(y/x) + theta + ((x<0)? Math.PI : 0);x=Math.cos(angle)*dist;
y=-Math.sin(angle)*dist;
var fact = (y*Math.cos(eleva) + z*Math.sin(eleva)+8)/8;
y=y*Math.sin(eleva) - z*Math.cos(eleva);
x*=fact;
y*=fact;return {
x: x,
y: y
};
}
输入是x,y,z三个三维坐标下的值,输出是x,y两个二维坐标值。
我们应用一下:
下面是一个3D球
以上代码是对球面的方程 x^2+y^2+z^2=r^2进行求解,将解(x,y,z)代入iso方法,最后根据输出二维坐标进行绘图。
对于这个球面方程的解法,也是各有各的写法。
(这里有个Functions 3D的应用,用来将方程式输出成3D图形:http://www.benjoffe.com/code/tools/functions3d/)
如果你看到过我这篇文章的话:笛卡尔情书的秘密——心形函数的绘制
我相信你也很可能知道网上还有一个3D版是心形函数:(x^2 + (9/4)y^2 + z^2 – 1)^3 – x^2*z^3 – (9/80)y^2*z^3 = 0
下面我将使用上面的iso方法在canvas中将其绘制出来,你可以拖到鼠标来看3D效果。
我们知道,三维空间下的坐标系不止直角坐标一种,还有 圆柱坐标系,球坐标系等等。
下面我们将iso方法转换一下,是输入使用球坐标系值(θ,Φ,r)——转角,仰角,球半径。
首先我们先要知道,三维直角坐标系于球坐标系的换算式:
x=rsinθcosφ
y=rsinθsinφ
z=rcosθ
呃哦,代入iso函数后我们发现iso变的更简单了:
var theta = 4.2; //转角
var eleva = 0.6; //仰角
function iso(a,b,r){
var x,y,z
x=r*Math.cos(a+this.theta)*Math.sin(b);
y=r*Math.sin(a+this.theta)*Math.sin(b);
z=r*Math.cos(b);var fact = (y*Math.cos(this.eleva) + z*Math.sin(this.eleva)+8)/8;
y=y*Math.sin(this.eleva) + z*Math.cos(this.eleva);
x*=fact;
y*=fact;
return {
x: x,
y: y
};
}
下面是我们使用球坐标系绘制的三维图形(三维投射到二维的图形)
我怎么就感觉, 你应该就是我同一公司的人 ?
上午分享完的一些东西, 马上, 就可以在你的里面见到了. 太巧合了吧.
连举的几个例子, 都 ……
哈哈. 如果不是, 那世界, 真可爱了. :-)
回复
ONEBOYS 回复:
二月 24th, 2011 at 21:54
神奇,那可真是太巧了。你们公司不错啊,可以有这种”无实用价值”的分享。希望什么时候看看这份分享,连例子也…例子是我今天写的,竟能想到一块去,神奇。
回复
hehe123 回复:
二月 24th, 2011 at 22:04
@ONEBOYS,
嗯, 是说你举的那几个例子.
我的分享会在 本周末的 w3c 杭州站分享上出现.
嗯, 这个确实是 “无实用价值” 呵呵. 所以公司领导在计较.
回复
ONEBOYS 回复:
二月 25th, 2011 at 08:26
呵呵,明白了。
可惜去不了现场啊。
回复
lbs1991 回复:
四月 25th, 2014 at 19:49
@ONEBOYS, 大神
var angle = (x==0 && y==0) ? 0 : Math.atan(y/x) + theta + ((x<0)? Math.PI : 0);
x=Math.cos(angle)*dist;
y=-Math.sin(angle)*dist;
var fact = (y*Math.cos(eleva) + z*Math.sin(eleva)+8)/8;
y=y*Math.sin(eleva) – z*Math.cos(eleva);
x*=fact;
y*=fact;
这个iso看不懂,能不能解释下奥,跪求啊
回复
超级复杂的代码。不懂技术的我只能被虐了
回复
这篇NB
回复
呵呵, 距离上次留言1年多了. 因为有个需求, 我使用并修改了你的一个demo, 没用自己的, 理由见blog.
http://www.cnblogs.com/hehe123/archive/2012/08/10/2625156.html
:-)
回复
ONEBOYS 回复:
八月 21st, 2012 at 11:17
@hehe123, ,恩,有时间用webgl也做做看,应该更方便,更赞。
回复
问个问题,那个投影算法,跟webgl中的渲染,是不是差不多呀?我感觉差不多
回复
Neoxone 回复:
一月 18th, 2017 at 19:57
@心智无力的内敛, webGL里没怎么简单。我们这个如果要转换的点非常多的话,那是根本不可用的,为了效率,webGL里是用矩阵来存储的,而且用来存储的矩阵还需要分模型矩阵,视图矩阵,投影矩阵。我们这个只是一个简单的模拟罢了。
回复
心智无力的内敛 回复:
一月 19th, 2017 at 15:37
@Neoxone,
恩, 我感觉的没错 作用是一样的,就是稍微简单了点儿。
回复