只要1K 纯JS脚本送你一朵3D红色玫瑰


Posted in Javascript onAugust 09, 2016

Roman Cortes又带来了用JavaScript脚本编写的红色玫瑰花。用代码做出的玫瑰花,这才是牛逼程序员送给女友的最好情人节礼物呢!(提示:在不同浏览器下观看效果、速度会有很大的不同)

只要1K 纯JS脚本送你一朵3D红色玫瑰

图片是由代码生成,用户可以刷新该页面,重复观看这朵玫瑰的呈现过程。

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*B<1){if(c>37){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;i<1e4;i++)if(s=p(R(),R(),i%46/.74)){z=s[2];x=~~(s[0]*f/z-h);y=~~(s[1]*f/z-h);if(!m[q=y*f+x]|m[q]>z)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 < 1; a += .1) {
for (b = 0; b < 1; b += .1) {
position = surface(a, b);
context.fillRect(position.x, position.y, 1, 1);

}

}

只要1K 纯JS脚本送你一朵3D红色玫瑰

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

只要1K 纯JS脚本送你一朵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) < radius * radius) {
// inside the circle
return {

x: x,

y: y

};
} else {
// outside the circle
return null;

}

}

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

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

}

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

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

}

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

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

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) < radius * radius) {
return {
x: x,
y: y * (1 + b) / 2 // deformation
};
} else {
return null;

}

}

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

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

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) < radius * radius) {
return {
x: x,
y: y * (1 + b) / 2,

r: 100 + Math.floor((1 - b) * 155), // this will add a gradient
g: 50,
b: 50
};
} else {
return null;
}

}

for (a = 0; a < 1; a += .01) {
for (b = 0; b < 1; b += .001) {
if (point = surface(a, b)) {
context.fillStyle = "rgb(" + point.r + "," + point.g + "," + point.b + ")";
context.fillRect(point.x, point.y, 1, 1);
}
}

}

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

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

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

只要1K 纯JS脚本送你一朵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 < 1; a += .001) {
for (b = 0; b < 1; b += .01) {
if (point = surface(a, b)) {
pX = (point.x * perspective) / (point.z - cameraZ) + halfWidth;
pY = (point.y * perspective) / (point.z - cameraZ) + halfHeight;

context.fillStyle = "rgb(" + point.r + "," + point.g + "," + point.b + ")";

context.fillRect(pX, pY, 1, 1);

}

}

}

效果为:

只要1K 纯JS脚本送你一朵3D红色玫瑰

z-buffer

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

只要1K 纯JS脚本送你一朵3D红色玫瑰

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

代码如下:

var zBuffer = [],
zBufferIndex;
for (a = 0; a < 1; a += .001) {
for (b = 0; b < 1; b += .01) {
if (point = surface(a, b)) {
pX = Math.floor((point.x * perspective) / (point.z - cameraZ) + halfWidth);
pY = Math.floor((point.y * perspective) / (point.z - cameraZ) + halfHeight);
zBufferIndex = pY * canvas.width + pX;
if ((typeof zBuffer[zBufferIndex] === "undefined") || (point.z < zBuffer[zBufferIndex])) {
zBuffer[zBufferIndex] = point.z;
context.fillStyle = "rgb(" + point.r + "," + point.g + "," + point.b + ")";

context.fillRect(pX, pY, 1, 1);

}

}

}

}

旋转

你可以使用任何矢量旋转的方法。在代码玫瑰的创建中,我使用的是欧拉旋转。现在将之前编写的管状物进行旋转,实现绕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

};

}

效果:

只要1K 纯JS脚本送你一朵3D红色玫瑰

蒙特卡罗方法

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

var i;
window.setInterval(function () {
for (i = 0; i < 10000; i++) {
if (point = surface(Math.random(), Math.random())) {
pX = Math.floor((point.x * perspective) / (point.z - cameraZ) + halfWidth);
pY = Math.floor((point.y * perspective) / (point.z - cameraZ) + halfHeight);
zBufferIndex = pY * canvas.width + pX;
if ((typeof zBuffer[zBufferIndex] === "undefined") || (point.z < zBuffer[zBufferIndex])) {
zBuffer[zBufferIndex] = point.z;
context.fillStyle = "rgb(" + point.r + "," + point.g + "," + point.b + ")";
context.fillRect(pX, pY, 1, 1);

}

}

}

}, 0);

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

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

完成

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

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
[原创]图片分页查看
Aug 28 Javascript
IE和Firefox下javascript的兼容写法小结
Dec 10 Javascript
JavaScript高级程序设计 阅读笔记(十四) js继承机制的实现
Aug 14 Javascript
Jquery 动态循环输出表格具体方法
Nov 23 Javascript
详谈JavaScript内存泄漏
Nov 14 Javascript
简介JavaScript中的setTime()方法的使用
Jun 11 Javascript
AngularJS实现Model缓存的方式
Feb 03 Javascript
js利用for in循环获取 一个对象的所有属性以及值的实例
Mar 30 Javascript
如何为vuex实现带参数的 getter和state.commit
Jan 04 Javascript
在vue中根据光标的显示与消失实现下拉列表
Sep 29 Javascript
原生JS无缝滑动轮播图
Oct 22 Javascript
JS数组及对象遍历方法代码汇总
Jun 16 Javascript
JS获取一个未知DIV高度的方法
Aug 09 #Javascript
Bootstrap实现带动画过渡的弹出框
Aug 09 #Javascript
第一次接触神奇的Bootstrap导航条
Aug 09 #Javascript
教你JS中的运算符乘方、开方及变量格式转换
Aug 09 #Javascript
基于jQuery的AJAX和JSON实现纯html数据模板
Aug 09 #Javascript
jQuery实现产品对比功能附源码下载
Aug 09 #Javascript
AngularJS利用Controller完成URL跳转
Aug 09 #Javascript
You might like
php带密码功能并下载远程文件保存本地指定目录 修改加强版
2010/05/16 PHP
PHP json_decode函数详细解析
2014/02/17 PHP
PHP使用递归按层级查找数据的方法
2019/11/10 PHP
Swoole源码中如何查询Websocket的连接问题详解
2020/08/30 PHP
jQuery 插件 将this下的div轮番显示
2009/04/09 Javascript
jQuery.get、jQuery.getJSON、jQuery.post无法返回JSON问题的解决方法
2011/07/28 Javascript
a标签跳转到指定div,jquery添加和移除class属性的实现方法
2016/10/10 Javascript
关于angularJs清除浏览器缓存的方法
2017/11/28 Javascript
vue input 输入校验字母数字组合且长度小于30的实现代码
2018/05/16 Javascript
JS开发常用工具函数(小结)
2019/07/04 Javascript
深入了解Hybrid App技术的相关知识
2019/07/17 Javascript
解决LayUI加上form.render()下拉框和单选以及复选框不出来的问题
2019/09/27 Javascript
js实现简单的点名器随机色实例代码
2020/09/20 Javascript
[36:41]完美世界DOTA2联赛循环赛FTD vs Magma第一场 10月30日
2020/10/31 DOTA
Python内置函数—vars的具体使用方法
2017/12/04 Python
Python连接Mssql基础教程之Python库pymssql
2018/09/16 Python
Python基于matplotlib画箱体图检验异常值操作示例【附xls数据文件下载】
2019/01/07 Python
python自动发微信监控报警
2019/09/06 Python
pytorch对梯度进行可视化进行梯度检查教程
2020/02/04 Python
Python无头爬虫下载文件的实现
2020/04/02 Python
Python %r和%s区别代码实例解析
2020/04/03 Python
Python-jenkins模块之folder相关操作介绍
2020/05/12 Python
PyQt5实现简单的计算器
2020/05/30 Python
Python pymysql模块安装并操作过程解析
2020/10/13 Python
Python可以用来做什么
2020/11/23 Python
详解css3 object-fit属性
2018/07/27 HTML / CSS
AmazeUI 折叠面板的实现代码
2020/08/17 HTML / CSS
AMAVII眼镜官网:时尚和设计师太阳镜
2019/05/05 全球购物
《盘古开天地》教学反思
2014/02/28 职场文书
yy婚礼主持词
2014/03/14 职场文书
餐饮商业计划书范文
2014/04/29 职场文书
关于祖国的演讲稿
2014/05/04 职场文书
教师求职自荐书
2014/06/14 职场文书
关于迟到的检讨书
2015/05/06 职场文书
高效课堂教学反思
2016/02/24 职场文书
读《儒林外史》有感:少一些功利,多一些真诚
2020/01/19 职场文书