只要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 相关文章推荐
javascript 文章截取部分无损html显示实现代码
May 04 Javascript
jquery 学习之二 属性相关
Nov 23 Javascript
js显示时间 js显示最后修改时间
Jan 02 Javascript
浅谈checkbox的一些操作(实战经验)
Nov 20 Javascript
jQuery中Dom的基本操作小结
Jan 23 Javascript
Javascript实现单张图片浏览
Dec 18 Javascript
详解JavaScript编程中的数组结构
Oct 24 Javascript
写给小白的JavaScript引擎指南
Dec 04 Javascript
JavaScrpt判断一个数是否是质数的实例代码
Jun 11 Javascript
谈谈为什么你的 JavaScript 代码如此冗长
Jan 30 Javascript
理理Vue细节(推荐)
Apr 16 Javascript
搭建vscode+vue环境的详细教程
Aug 31 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
MySQL GBK→UTF-8编码转换
2007/05/24 PHP
推荐25款php中非常有用的类库
2014/09/29 PHP
PHP安全下载文件的方法
2016/04/07 PHP
详解阿里云视频直播PHP-SDK接入教程
2020/07/09 PHP
用JavaScript获取DOM元素位置和尺寸大小的方法
2013/04/12 Javascript
在页面中js获取光标/鼠标的坐标及光标的像素坐标
2013/11/11 Javascript
js实现鼠标悬停图片上时滚动文字说明的方法
2015/02/17 Javascript
jQuery插件EnPlaceholder实现输入框提示文字
2015/06/05 Javascript
自己动手制作基于jQuery的Web页面加载进度条插件
2016/06/03 Javascript
浅谈javascript中的constructor
2016/06/08 Javascript
js中scrollTop()方法和scroll()方法用法示例
2016/10/03 Javascript
解决AjaxFileupload 上传时会出现连接重置的问题
2017/07/07 Javascript
JavaScript之数组扁平化详解
2019/06/03 Javascript
使用js实现单链解决前端队列问题的方法
2020/02/03 Javascript
jquery添加div实现消息聊天框
2020/02/08 jQuery
VueQuillEditor富文本上传图片(非base64)
2020/06/03 Javascript
Python读取mp3中ID3信息的方法
2015/03/05 Python
PHP网页抓取之抓取百度贴吧邮箱数据代码分享
2016/04/13 Python
浅谈Series和DataFrame中的sort_index方法
2018/06/07 Python
Pandas DataFrame 取一行数据会得到Series的方法
2018/11/10 Python
Python脚本利用adb进行手机控制的方法
2019/07/08 Python
Django Rest framework权限的详细用法
2019/07/25 Python
在Django admin中编辑ManyToManyField的实现方法
2019/08/09 Python
Python搭建Keras CNN模型破解网站验证码的实现
2020/04/07 Python
python如何实现读取并显示图片(不需要图形界面)
2020/07/08 Python
浅析HTML5 meta viewport参数
2020/10/28 HTML / CSS
固特异美国在线轮胎店:Goodyear Tire
2019/02/23 全球购物
优秀毕业大学生推荐信
2013/11/13 职场文书
护士毕业自我鉴定
2014/02/07 职场文书
班主任寄语大全
2014/04/04 职场文书
安全责任书范本
2014/04/15 职场文书
2014年度安全工作总结
2014/12/04 职场文书
学校开除通知书
2015/04/25 职场文书
大学生逃课检讨书
2015/05/04 职场文书
食堂管理制度范本
2015/08/04 职场文书
MySQL获取所有分类的前N条记录
2021/05/07 MySQL