情人节专属 纯js脚本1k大小的3D玫瑰效果


Posted in Javascript onFebruary 11, 2012

前年圣诞节上,西班牙程序员Roman Cortes带来了用纯javascript脚本编写的神奇3D圣诞树,令人印象深刻。2月14日情人节就要来临了,还是Roman Cortes,这次他又带来了用javascript脚本编写的红色玫瑰花。用代码做出的玫瑰花,这才是牛逼程序员送给女友的最好情人节礼物呢!(提示:在不同浏览器下观看效果、速度会有很大的不同)
图片是由代码生成,用户可以刷新该页面,重复观看这朵玫瑰的呈现过程。

3D玫瑰花的实现代码如下:

with(m=Math)C=cos,S=sin,P=pow,R=random;c.width=c.height=f=500;h=-250;function p(a,b,c){if(c>60)return[S(a*7)*(13+5/(.2+P(b*4,4)))-S(b)*50,b*f+50,625+C(a*7)*(13+5/(.2+P(b*4,4)))+b*400,a*1-b/2,a];A=a*2-1;B=b*2-1;if(A*A+B*B37){n=(j=c&1)?6:4;o=.5/(a+.01)+C(b*125)*3-a*300;w=b*h;return[o*C(n)+w*S(n)+j*610-390,o*S(n)-w*C(n)+550-j*350,1180+C(B+A)*99-j*300,.4-a*.1+P(1-B*B,-h*6)*.15-a*b*.4+C(a+b)/5+P(C((o*(a+1)+(B>0?w:-w))/25),30)*.1*(1-B*B),o/1e3+.7-o*w*3e-6]}if(c>32){c=c*1.16-.15;o=a*45-20;w=b*b*h;z=o*S(c)+w*C(c)+620;return[o*C(c)-w*S(c),28+C(B*.5)*99-b*b*b*60-z/2-h,z,(b*b*.3+P((1-(A*A)),7)*.15+.3)*b,b*.7]}o=A*(2-b)*(80-c*2);w=99-C(A)*120-C(b)*(-h-c*4.9)+C(P(1-b,7))*50+c*2;z=o*S(c)+w*C(c)+700;return[o*C(c)-w*S(c),B*99-C(P(b, 7))*50-c/3-z/1.35+450,z,(1-b/1.2)*.9+a*.1, P((1-b),20)/4+.05]}}setInterval('for(i=0;iz)m[q]=z,a.fillStyle="rgb("+~(s[3]*h)+","+~(s[4]*h)+","+~(s[3]*s[3]*-80)+")",a.fillRect(x,y,1,1)}',0)

当然,感兴趣的人可以了解下面的实现过程与相关理论:

这朵三维代码玫瑰的呈现效果采用了蒙特卡罗方法,创造者对蒙特卡罗方法非常推崇,他表示在功能优化和采样方面,蒙特卡罗方法是“令人难以置信的强大工具”。关于蒙特卡罗方法可以参考:Monte Carlo method 。

具体操作:

外观采样呈现效果绘制

我用了多个不同的形状图来组成这朵代码玫瑰。共使用了31个形状:24个花瓣,4个萼片,2个叶子和1根花茎,其中每一个形状图都用代码进行描绘。

首先,来定义一个采样范围:

function surface(a, b) { // I'm using a and b as parameters ranging from 0 to 1. return { x: a*50, y: b*50 }; // this surface will be a square of 50x50 units of size }

然后,编写形状描绘代码:

var canvas = document.body.appendChild(document.createElement("canvas")), context = canvas.getContext("2d"), a, b, position; // Now I'm going to sample the surface at .1 intervals for a and b parameters: for (a = 0; a

这时,看到的效果是这样的:

情人节专属 纯js脚本1k大小的3D玫瑰效果

现在,尝试一下更密集的采样间隔:

情人节专属 纯js脚本1k大小的3D玫瑰效果

正如现在所看到的,因为采样间隔越来越密集,点越来越接近,到最高密度时,相邻点之间的距离小于一个像素,肉眼就看不到间隔(见0.01)。为了不造成太大的视觉差,再进一步缩小采样间隔,此时,绘制区已经填满(比较结果为0.01和0.001)。

接下来,我用这个公式来绘制一个圆形:(X-X0)^ 2 +(Y-Y0)^ 2 <半径^ 2,其中(X0,Y0)为圆心:

function surface(a, b) { var x = a * 100, y = b * 100, radius = 50, x0 = 50, y0 = 50; if ((x - x0) * (x - x0) + (y - y0) * (y - y0)

为了防止溢出,还要加上一个采样条件:

if (position = surface(a, b)) { context.fillRect(position.x, position.y, 1, 1); }

结果如下:

情人节专属 纯js脚本1k大小的3D玫瑰效果

有不同的方法来定义一个圆,其中一些并不需要拒绝采样。我并无一定要使用哪一种来定义圆圈的意思,所以下面用另一种方法来定义一个圆:

function surface(a, b) { // Circle using polar coordinates var angle = a * Math.PI * 2, radius = 50, x0 = 50, y0 = 50; return { x: Math.cos(angle) * radius * b + x0, y: Math.sin(angle) * radius * b + y0 }; }

如图:

情人节专属 纯js脚本1k大小的3D玫瑰效果

(此方法相比前一个方法需要密集采样以进行填充。)

好了,现在让圆变形,以使它看起来更像是一个花瓣:

function surface(a, b) { var x = a * 100, y = b * 100, radius = 50, x0 = 50, y0 = 50; if ((x - x0) * (x - x0) + (y - y0) * (y - y0)

结果:

情人节专属 纯js脚本1k大小的3D玫瑰效果

这看起来已经很像一个玫瑰花瓣的形状了。在这里也可以试试通过修改一些函数数值,将会出现很多有趣的形状。

接下来应该给它添加色彩了:

function surface(a, b) { var x = a * 100, y = b * 100, radius = 50, x0 = 50, y0 = 50; if ((x - x0) * (x - x0) + (y - y0) * (y - y0)

结果:

情人节专属 纯js脚本1k大小的3D玫瑰效果

一片带色的花瓣就出现了。

3D曲面和透视投影

定义三维表面很简单,比如,来定义一个管状物体:

function surface(a, b) { var angle = a * Math.PI * 2, radius = 100, length = 400; return { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius, z: b * length - length / 2, // by subtracting length/2 I have centered the tube at (0, 0, 0) r: 0, g: Math.floor(b * 255), b: 0 }; }

接着添加投影透视图,首先需要我们定义一个摄像头:

情人节专属 纯js脚本1k大小的3D玫瑰效果

如上图,将摄像头放置在(0,0,Z)位置,画布在X / Y平面。投影到画布上的采样点为:

var pX, pY, // projected on canvas x and y coordinates perspective = 350, halfHeight = canvas.height / 2, halfWidth = canvas.width / 2, cameraZ = -700; for (a = 0; a

效果为:

情人节专属 纯js脚本1k大小的3D玫瑰效果

z-buffer

z-buffer在计算机图形学中是一个相当普遍的技术,在为物件进行着色时,执行“隐藏面消除”工作,使隐藏物件背后的部分就不会被显示出来。

情人节专属 纯js脚本1k大小的3D玫瑰效果

上图是用z-buffer技术处理后的玫瑰。(可以看到已经具有立体感了)

代码如下:

var zBuffer = [], zBufferIndex; for (a = 0; a

旋转

你可以使用任何矢量旋转的方法。在代码玫瑰的创建中,我使用的是欧拉旋转。现在将之前编写的管状物进行旋转,实现绕Y轴旋转:

function surface(a, b) { var angle = a * Math.PI * 2, radius = 100, length = 400, x = Math.cos(angle) * radius, y = Math.sin(angle) * radius, z = b * length - length / 2, yAxisRotationAngle = -.4, // in radians! rotatedX = x * Math.cos(yAxisRotationAngle) + z * Math.sin(yAxisRotationAngle), rotatedZ = x * -Math.sin(yAxisRotationAngle) + z * Math.cos(yAxisRotationAngle); return { x: rotatedX, y: y, z: rotatedZ, r: 0, g: Math.floor(b * 255), b: 0 }; }

效果:

情人节专属 纯js脚本1k大小的3D玫瑰效果

蒙特卡罗方法

关于采样时间,间隔过大过小都会引起极差的视觉感受,所以,需要设置合理的采样间隔,这里使用蒙特卡罗方法。

var i; window.setInterval(function () { for (i = 0; i

设置a和b为随机参数,用足够的采样完成表面填充。我每次绘制10000点,然后静待屏幕完成更新。

另外需要注意的是,如果随机数发生错误时,表面填充效果会出错。有些浏览器中,Math.random的执行是线性的,这就有可能导致表面填充效果出错。这时,就得使用类似Mersenne Twister(一种随机数算法)这样的东西去进行高质量的PRNG采样,从而避免错误的发生。

完成

为了使玫瑰的每个部分在同一时间完成并呈现,还需要添加一个功能,为每部分设置一个参数以返回值来进行同步。并用一个分段函数代表玫瑰的各个部分。比如在花瓣部分,我用旋转和变形来创建它们。

虽然表面采样方法是创建三维图形非常著名的、最古老的方法之一,但这种把蒙特卡罗、z-buffer加入到表面采样中的方法并不常见。对于现实生活场景的制作,这也许算不上很有创意,但它简易的代码实现和很小的体积仍令人满意。

希望这篇文章能激发计算机图形学爱好者来尝试不同的呈现方法,并从中获得乐趣。(Roman Cortes)

英文原址:romancortes.com 

Javascript 相关文章推荐
javascript oop开发滑动(slide)菜单控件
Aug 25 Javascript
JS判断元素为数字的奇异写法分享
Aug 01 Javascript
Javascript实现颜色rgb与16进制转换的方法
Apr 18 Javascript
jQuery取消ajax请求的方法
Jun 09 Javascript
漫谈JS引擎的运行机制 你应该知道什么
Jun 15 Javascript
JS选取DOM元素的简单方法
Jul 08 Javascript
Vue自定义指令介绍(2)
Dec 08 Javascript
JS实现含有中文字符串的友好截取功能分析
Mar 13 Javascript
Vue-router 类似Vuex实现组件化开发的示例
Sep 15 Javascript
jQuery实现使用sort方法对json数据排序的方法
Apr 17 jQuery
原生Vue 实现右键菜单组件功能
Dec 16 Javascript
基于JavaScript实现轮播图效果
Jan 02 Javascript
基于jquery实现状态限定编辑的代码
Feb 11 #Javascript
通过jquery还原含有rowspan、colspan的table的实现方法
Feb 10 #Javascript
用JQuery实现表格隔行变色和突出显示当前行的代码
Feb 10 #Javascript
了解jQuery技巧来提高你的代码(个人觉得那个jquery的手册很不错)
Feb 10 #Javascript
基于jquery点击自以外任意处,关闭自身的代码
Feb 10 #Javascript
jqPlot 图表中文API使用文档及源码和在线示例
Feb 07 #Javascript
jquery中的mouseleave和mouseout的区别 模仿下拉框效果
Feb 07 #Javascript
You might like
香妃
2021/03/03 冲泡冲煮
PHP中常用的转义函数
2014/02/28 PHP
PHP微信开发之二维码生成类
2015/06/26 PHP
实例简介PHP的一些高级面向对象编程的特性
2015/11/27 PHP
PHP排序算法之堆排序(Heap Sort)实例详解
2018/04/21 PHP
PHP模糊查询技术实例分析【附源码下载】
2019/03/07 PHP
yii2的restful api路由实例详解
2019/05/14 PHP
laravel框架邮箱认证实现方法详解
2019/11/22 PHP
laravel admin实现分类树/模型树的示例代码
2020/06/10 PHP
Mootools 1.2教程 Fx.Tween的使用
2009/09/15 Javascript
如何用js控制frame的隐藏或显示的解决办法
2013/03/20 Javascript
js实现进度条的方法
2015/02/13 Javascript
深入解析AngularJS框架中$scope的作用与生命周期
2016/03/05 Javascript
利用D3.js实现最简单的柱状图示例代码
2016/12/09 Javascript
jQuery EasyUi 验证功能实例解析
2017/01/06 Javascript
关于vuex的学习实践笔记
2017/04/05 Javascript
微信小程序 wx.request方法的异步封装实例详解
2017/05/18 Javascript
Javascript实现从小到大的数组转换成二叉搜索树
2017/06/13 Javascript
详解用vue编写弹出框组件
2017/07/04 Javascript
浅谈layui里的上传控件问题
2019/09/26 Javascript
Vue组件通信$attrs、$listeners实现原理解析
2020/09/03 Javascript
vue项目中企业微信使用js-sdk时config和agentConfig配置方式详解
2020/12/15 Vue.js
[10:07]2014DOTA2国际邀请赛 实拍选手现场观战DK对阵Titan
2014/07/12 DOTA
[40:48]DOTA2上海特级锦标赛D组败者赛 Liquid VS COL第二局
2016/02/28 DOTA
Python2.7基于笛卡尔积算法实现N个数组的排列组合运算示例
2017/11/23 Python
Python matplotlib 画图窗口显示到gui或者控制台的实例
2018/05/24 Python
Jupyter notebook远程访问服务器的方法
2018/05/24 Python
浅谈DataFrame和SparkSql取值误区
2018/06/09 Python
python通用读取vcf文件的类(复制粘贴即可用)
2020/02/29 Python
基于HTML5 Canvas的3D动态Chart图表的示例
2017/11/02 HTML / CSS
JBL澳大利亚官方商店:扬声器、耳机和音响系统
2018/05/24 全球购物
档案室主任岗位职责
2014/02/12 职场文书
美术教学感言
2014/02/22 职场文书
说明书格式及范文
2014/05/07 职场文书
党的群众路线教育实践活动对照检查材料(四风)
2014/09/27 职场文书
Python自动化实战之接口请求的实现
2022/05/30 Python