只要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 相关文章推荐
jQuery 动画弹出窗体支持多种展现方式
Apr 29 Javascript
封装的jquery翻页滚动(示例代码)
Nov 18 Javascript
jquery ajax 局部无刷新更新数据的实现案例
Feb 08 Javascript
可编辑下拉框的2种实现方式
Jun 13 Javascript
使用js Math.random()函数生成n到m间的随机数字
Oct 09 Javascript
ES6字符串模板,剩余参数,默认参数功能与用法示例
Apr 06 Javascript
JS实现仿饿了么在浏览器标签页失去焦点时网页Title改变
Jun 01 Javascript
详解Angular2 之 结构型指令
Jun 21 Javascript
解决vue 打包发布去#和页面空白的问题
Sep 04 Javascript
vue实现点击选中,其他的不选中方法
Sep 05 Javascript
关于微信小程序map组件z-index的层级问题分析
Jul 09 Javascript
JS删除对象中某一属性案例详解
Sep 08 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中json_encode()和json_decode()
2014/05/25 PHP
THinkPHP获取客户端IP与IP地址查询的方法
2016/11/14 PHP
php显示页码分页类的封装
2017/06/08 PHP
PHP通过GD库实现验证码功能示例
2019/02/23 PHP
Laravel框架实现的使用smtp发送邮件功能示例
2019/03/12 PHP
JavaScript开发时的五个注意事项
2007/12/08 Javascript
jquery ajax abort()的使用方法
2010/10/28 Javascript
常用一些Javascript判断函数
2012/08/14 Javascript
jquery中html、val与text三者属性取值的联系与区别介绍
2013/12/29 Javascript
js数组方法扩展实现数组统计函数
2014/04/09 Javascript
javascript格式化json显示实例分析
2015/04/21 Javascript
jQuery实现弹出窗口中切换登录与注册表单
2015/06/05 Javascript
使用AngularJS创建单页应用的编程指引
2015/06/19 Javascript
JavaScript获取表格(table)当前行的值、删除行、增加行
2015/07/03 Javascript
JS实现点击登录弹出窗口同时背景色渐变动画效果
2016/03/25 Javascript
EasyUI Pagination 分页的两种做法小结
2016/07/09 Javascript
Bootstrap中data-target 到底是什么
2017/02/14 Javascript
JavaScrpt的面向对象全面解析
2017/05/09 Javascript
JS异步函数队列功能实例分析
2017/11/28 Javascript
JavaScript常见事件处理程序实例总结
2019/01/05 Javascript
nodejs一个简单的文件服务器的创建方法
2019/09/13 NodeJs
vue实现购物车选择功能
2020/01/10 Javascript
封装 axios+promise通用请求函数操作
2020/08/11 Javascript
[05:06]TI4西雅图DOTA2前线报道 海涛密探LGD训练
2014/07/09 DOTA
python一行sql太长折成多行并且有多个参数的方法
2018/07/19 Python
python-opencv 将连续图片写成视频格式的方法
2019/01/08 Python
python tkinter库实现气泡屏保和锁屏
2019/07/29 Python
利用Tensorboard绘制网络识别准确率和loss曲线实例
2020/02/15 Python
python正则表达式的懒惰匹配和贪婪匹配说明
2020/07/13 Python
CSS3用@font-face实现自定义英文字体
2013/09/23 HTML / CSS
改变生活的男士内衣:SAXX Underwear
2019/08/28 全球购物
什么是Linux虚拟文件系统VFS
2012/01/31 面试题
学生检讨书怎么写
2014/10/09 职场文书
公司趣味运动会开幕词
2016/03/04 职场文书
年中了,该如何写好个人述职报告?
2019/07/02 职场文书
管理者日常工作必备:22条企业管理流程模板!
2019/07/12 职场文书