三人行必有我师焉,择其善者而从之,其不善者而改之
三人行必有我师焉,择其善者而从之,其不善者而改之
三人行必有我师焉,择其善者而从之,其不善者而改之
三人行必有我师焉,择其善者而从之,其不善者而改之
三人行必有我师焉,择其善者而从之,其不善者而改之

在canvas 2D API下实现3D效果(3D版心形函数的绘制)
作者:Neoxone    发表时间: 2011年02月24号,星期四     阅读:24,210 次

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
};
}

下面是我们使用球坐标系绘制的三维图形(三维投射到二维的图形)

标签: , , , , ,

12 条评论 发表在“在canvas 2D API下实现3D效果(3D版心形函数的绘制)”上

  1. hehe123 说:

    我怎么就感觉, 你应该就是我同一公司的人 ?
    上午分享完的一些东西, 马上, 就可以在你的里面见到了. 太巧合了吧.
    连举的几个例子, 都 ……

    哈哈. 如果不是, 那世界, 真可爱了. :-)

    回复

    ONEBOYS 回复:

    神奇,那可真是太巧了。你们公司不错啊,可以有这种”无实用价值”的分享。希望什么时候看看这份分享,连例子也…例子是我今天写的,竟能想到一块去,神奇。

    回复

    hehe123 回复:

    @ONEBOYS,
    嗯, 是说你举的那几个例子.
    我的分享会在 本周末的 w3c 杭州站分享上出现.
    嗯, 这个确实是 “无实用价值” 呵呵. 所以公司领导在计较.

    回复

    ONEBOYS 回复:

    呵呵,明白了。
    可惜去不了现场啊。

    回复

    lbs1991 回复:

    @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看不懂,能不能解释下奥,跪求啊

    回复

  2. mtoou 说:

    超级复杂的代码。不懂技术的我只能被虐了

    回复

  3. hehe123 说:

    呵呵, 距离上次留言1年多了. 因为有个需求, 我使用并修改了你的一个demo, 没用自己的, 理由见blog.
    http://www.cnblogs.com/hehe123/archive/2012/08/10/2625156.html
    :-)

    回复

    ONEBOYS 回复:

    @hehe123, ):7:( ,恩,有时间用webgl也做做看,应该更方便,更赞。

    回复

  4. 心智无力的内敛 说:

    问个问题,那个投影算法,跟webgl中的渲染,是不是差不多呀?我感觉差不多

    回复

    Neoxone 回复:

    @心智无力的内敛, webGL里没怎么简单。我们这个如果要转换的点非常多的话,那是根本不可用的,为了效率,webGL里是用矩阵来存储的,而且用来存储的矩阵还需要分模型矩阵,视图矩阵,投影矩阵。我们这个只是一个简单的模拟罢了。

    回复

    心智无力的内敛 回复:

    @Neoxone,
    恩, 我感觉的没错 作用是一样的,就是稍微简单了点儿。

    回复

留下回复

):9:( ):8:( ):7:( ):6:( ):5:( ):4:( ):3:( ):2:( ):20:( ):1:( ):19:( ):18:( ):17:( ):16:( ):15:( ):14:( ):13:( ):12:( ):11:( ):10:(