博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
情人节送ta一朵独一无二的玫瑰花
阅读量:5999 次
发布时间:2019-06-20

本文共 4474 字,大约阅读时间需要 14 分钟。

hot3.png

【情人节福利】JS实现玫瑰花

  • 原创来自于博客

  • 中文版翻译于 有增改

前言

先放压缩过的html代码,把下段代码复制到你的新建的html文件用浏览器打开就能看到一个玫瑰。我们管它叫生产版好了,三步就能完成。

在文档的最后附上一个简单的开发版本,供大家定制 :-)

情人节这么浪漫的告白,快去试试吧,送她或者他一朵与众不同的玫瑰花

代码(生产版)

    Rose  
    
  
  
  
    

115834_B0tR_2291061.png

 

详细步骤

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

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

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

这时,看到的效果是这样的:enter image description here

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

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

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

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

结果如下:

enter image description here

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

enter image description here

(此方法相比前一个方法需要密集采样以进行填充。) 好了,现在让圆变形,以使它看起来更像是一个花瓣:

enter image description here

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

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

enter image description here

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

3D曲面和透视投影

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

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

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

enter image description here

z-buffer

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

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

talk is cheap, show the code!

旋转

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

enter image description here

蒙特卡罗方法

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

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

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

完成源码(开发版)

    Rose  
    
  
  
 
 var canvas = document.getElementsByTagName('canvas')[0]; var context = canvas.getContext('2d'); var a = context;  var b = document.body; var c = canvas; document.body.clientWidth;  var zBuffer = [];  var SIZE = 777; canvas.width = canvas.height = SIZE; var h = -350; function surface(a, b, c) {    if (c > 60) {     return {       x : Math.sin(a * 7) * (13 + 5 / (.2 + Math.pow(b * 4, 4)))  - Math.sin(b) * 50,      y : b * SIZE + 50,       z : 625 + Math.cos(a * 7)   * (13 + 5 / (.2 + Math.pow(b * 4, 4))) + b * 400,      r : a * 1 - b / 2,  g : a  };     }  var A = a * 2 - 1;  var B = b * 2 - 1;  if (A * A + B * B < 1) {  if (c > 37) {    var j = c & 1;     var n = j ? 6 : 4;      var o = .5 / (a + .01) + Math.cos(b * 125) * 3 - a * 300;     var w = b * h;     return {       x : o * Math.cos(n) + w * Math.sin(n) + j * 610 - 390,      y : o * Math.sin(n) - w * Math.cos(n) + 550 - j * 350,      z : 1180 + Math.cos(B + A) * 99 - j * 300,      r : .4  - a  * .1   + Math.pow(1 - B * B, -h * 6)  * .15  - a  * b  * .4   + Math.cos(a + b)  / 5   + Math.pow(Math.cos((o * (a + 1) + (B > 0 ? w  : -w)) / 25), 30) * .1 * (1 - B * B),  g : o / 1e3 + .7 - o * w * 3e-6   };    }  if (c > 32) {     c = c * 1.16 - .15;     var o = a * 45 - 20;     var w = b * b * h;     var z = o * Math.sin(c) + w * Math.cos(c) + 620;     return {      x : o * Math.cos(c) - w * Math.sin(c),      y : 28 + Math.cos(B * .5) * 99 - b * b * b * 60 - z / 2  - h,      z : z,      r : (b * b * .3 + Math.pow((1 - (A * A)), 7) * .15 + .3)  * b,      g : b * .7     };    }  var o = A * (2 - b) * (80 - c * 2);    var w = 99 - Math.cos(A) * 120 - Math.cos(b) * (-h - c * 4.9)  + Math.cos(Math.pow(1 - b, 7)) * 50 + c * 2;    var z = o * Math.sin(c) + w * Math.cos(c) + 700;    return {     x : o * Math.cos(c) - w * Math.sin(c),     y : B * 99 - Math.cos(Math.pow(b, 7)) * 50 - c / 3 - z  / 1.35 + 450,  z : z,     r : (1 - b / 1.2) * .9 + a * .1,     g : Math.pow((1 - b), 20) / 4 + .05    };    }   }   setInterval(function() {    for ( var i = 0; i < 10000; i++) {      var part = i % 46;      var c = part / .74;      var point = surface(Math.random(), Math.random(), c);     if (point) {      var z = point.z;      var x = parseInt(point.x * SIZE / z - h);      var y = parseInt(point.y * SIZE / z - h);      var zBufferIndex = y * SIZE + x;      if ((typeof zBuffer[zBufferIndex] === "undefined")  || (zBuffer[zBufferIndex] > z)) {       zBuffer[zBufferIndex] = z;        var r = -parseInt(point.r * h);  var g = -parseInt(point.g * h);       var b = -parseInt(point.r * point.r * -80);        context.fillStyle = "rgb(" + r + "," + g + "," + b  + ")";       context.fillRect(x, y, 1, 1);      }     }    }   }, 0);   

 

115836_ZPvO_2291061.png

 

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

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

Happy Valentine's Day

注:本文详细出自实验楼,转载请注明出处。

转载于:https://my.oschina.net/shiyanlou/blog/377997

你可能感兴趣的文章
Redis在java开发中使用
查看>>
input file样式美化
查看>>
博客园页面设置
查看>>
docker环境搭建
查看>>
开发过程中,ps要做的事情
查看>>
[IOS] Storyboard全解析-第一部分
查看>>
CSS:opacity 的取值范围是 0~1
查看>>
Silverlight 自定义的附加属性
查看>>
常见问题
查看>>
Sqlite插入或更新
查看>>
Jenkins添加Windows自动化构建方案
查看>>
PHP实现文件下载
查看>>
调用天气预报接口
查看>>
node.js中使用http模块创建服务器和客户端
查看>>
LeetCode 453. Minimum Moves to Equal Array Elements C#
查看>>
Away3D基础教程(六):支持双面交互的PlaneGeometry
查看>>
(十五)Centos之安装jdk
查看>>
51nod 最长公共子序列+输出路径
查看>>
RISC-V: custom instruction and its simulation(转)
查看>>
博客园个性时钟,Play with me !!!
查看>>