郑重声明: 本文首发于人工博客

1、导读

你能想象到1K的代码能写出什么样的功能强大、效果炫酷的作品吗?来吧,今天小编带领大家认识下下面这位大神的作品。

西班牙程序员Roman Cortes用纯JavaScript脚本编写的玫瑰花。

这才是牛逼程序员送给女友的最好情人节礼物呢!(提示:在不同浏览器下观看效果、速度会有很大的不同)

2、先来张效果图

在线预览

预览效果1:

预览效果2

3、原理解读

3.1 蒙特卡罗方法

蒙特卡罗方法是令人难以置信的强大的工具。我用他们所有的时间,对于很多类型的函数优化和抽样问题,他们几乎像魔法一样当你有更多的CPU时间比设计和编码算法。在上升的情况下,它是非常有用的代码大小的优化。

如果你不知道很多关于蒙特卡罗方法,你可以读到他们在这个优秀的维基百科文章。

3.2 明确的表面和采样/绘图

定义的形状玫瑰我使用多个explicit-defined表面。我使用一个共有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);
}
}

结果:

现在,让我们尝试更多密集采样间隔(比间隔=更稠密采样):

正如你所看到的,当你样品越来越密集,点越来越近,到密度时的距离从一个点到他们的邻居比像素更小,表面是完全填充在屏幕上(见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);
}
}
}

结果:

3.3 3D曲面和透视投影

定义3d曲面很简单:只需向surface函数添加一个z属性


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

现在,添加透视投影,首先我们必须定义一个相机:

结果:

我将我的相机放在(0,0,cameraZ),我将调用“透视图”的距离,从相机到画布。我将考虑我的画布在x/y平面上,以(0,0,cameraZ + perspective)为中心。现在,每个采样点将被投影到画布:


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

结果如下:

3.4 Z-buffer

z-buffer是计算机图形学中很常见的一种技术,它可以在距离摄像机较近的点上绘制距离摄像机较远的点。它的工作原理是保持一个数组与每像素画近z的图像。

这是可视化的z缓冲的玫瑰,与黑色的相机远,白色接近它。

实现:


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

3.5旋转这个圆柱体

你可以使用任何向量旋转方法。对于玫瑰,我使用了欧拉旋转。让我们实现一个绕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
};
}

3.6 蒙特卡洛取样

我在文章中使用了基于时间间隔的抽样。它需要为每个表面设置一个适当的间隔。如果间隔很大,渲染速度会很快,但最终会在表面留下一些没有填充的洞。另一方面,如果间隔太短,则呈现增量的时间会达到无法接受的数量。

那么,让我们切换到蒙特卡罗抽样:


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个点然后让屏幕根据间隔更新。

另外,只有在伪随机数发生器质量良好的情况下,才能保证曲面的完全填充。在一些浏览器中,数学。随机是用一个线性同余发生器实现的,这可能会导致一些曲面的问题。如果你需要一个好的PRNG采样,你可以使用高质量的像Mersenne Twister(它有JS实现),或者在一些浏览器中可用的加密随机生成器。使用低差异序列也是非常明智的。

4、总结

完成玫瑰,玫瑰的每一部分,每一个表面,都是同时呈现的。我为函数添加了第三个参数,该函数选择玫瑰的部分来返回一个点。数学上它是一个分段函数,每一块都代表玫瑰的一部分。在花瓣的例子中,我使用旋转和拉伸/变形来创建所有的花瓣。所有的工作都是通过混合本文中暴露的概念来完成的。

虽然通过采样显式表面是一种非常著名的方法,也是最古老的3d图形方法之一,但我的分段/蒙特卡罗/z-buffer方法可能很少像我这样用于艺术目的。虽然不是非常具有创新性,在实际场景中也不是很有用,但是它非常适合js1k的环境,在这种环境中,简单性和最小的大小都是需要的。

通过这篇文章,我真的希望能够激励那些对计算机图形感兴趣的读者去尝试和享受不同的渲染方法。在图形领域有一个完整的世界,研究和使用它是令人惊奇的。


版权声明:本文为人工博客的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

本文链接:https://www.94rg.com/article/1724

NB的程序员,亮瞎了你的眼吗?的更多相关文章

  1. 一名合格的JAVA程序员需要点亮那些技能树?

    这是从450家企业的招聘信息中统计而来,相对来说还是比较真实的,虽然有些公司的招聘要求万年不变,但还是可以大致反应企业的招聘要求的. 尽管Struts2漏洞频出,但是由于政府.银行以及传统企业遗留项目 ...

  2. .Net程序员玩转Android系列之二~Android Framework概要(1)

    从windows操作系统说起 人们总是喜欢从将陌生的事物和自己所了解的东西关联起来,以加深对未知事物的了解,这一讲我们从windows操作系统说起,逐步引领带大家走入android的世界.写任何程序都 ...

  3. [Mac A]为什么国外程序员爱用 Mac?

    from http://www.vpsee.com/2009/06/why-programmers-love-mac/ Mac 在国外很受欢迎,尤其是在 设计/web开发/IT 人员圈子里.普通用户喜 ...

  4. 传播正能量——做一个快乐的程序员

    引子 今天在博客园看到施瓦小辛格的文章我们搞开发的为什么会感觉到累,顿时有感而发.自己本来不擅长写文章,更不擅长写这种非技术性的文章,但是在思绪喷薄之际,还是止不住有很多话要说.针对从客观上说&quo ...

  5. [转]ThoughtWorks(中国)程序员读书雷达

    http://agiledon.github.io/blog/2013/04/17/thoughtworks-developer-reading-radar/#rd?sukey=f64bfa68330 ...

  6. SQL Server 隐式转换引发的躺枪死锁-程序员需知

    在SQL Server的应用开发过程(尤其是二次开发)中可能由于开发人员对表的结构不够了解,造成开发过程中使用了不合理的方式造成数据库引擎未按预定执行,以致影响业务.这是非常值得注意的.这次为大家介绍 ...

  7. Java程序员从笨鸟到菜鸟之(一百零一)sql注入攻击详解(二)sql注入过程详解

    在上篇博客中我们分析了sql注入的原理,今天我们就来看一下sql注入的整体过程,也就是说如何进行sql注入,由于本人数据库和网络方面知识有限,此文章是对网上大量同类文章的分析与总结,其中有不少直接引用 ...

  8. 转载:java程序员如何拿到2万月薪

    作者:匿名用户链接:https://www.zhihu.com/question/39890405/answer/83676977来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  9. [PHP]程序员技能栈

    [PHP]程序员技能栈.md-/Users/zjh/Documents/我的文章/[PHP]程序员技能栈 html{font-family: sans-serif;-ms-text-size-adju ...

随机推荐

  1. javascript 容易混淆遗忘的基础知识

    1.  标识符     所谓标识符,就是指变量.函数.属性的名字,或者函数的参数.标识符可以是按照下列格式规则组合起来的一或多个字符:     1.1   第一个字符必须是一个字母.下划线( _ )或 ...

  2. supersockets接收过滤器(ReceiveFilter)

    接收过滤器(ReceiveFilter)用于将接收到的二进制数据转化成请求实例(RequestInfo). 实现一个接收过滤器(ReceiveFilter), 你需要实现接口 IReceiveFilt ...

  3. Python--day21--复习

    序列化模块总结: jison格式化输出: Serialize obj to a JSON formatted str.(字符串表示的json对象) Skipkeys:默认值是False,如果dict的 ...

  4. 【u233】单词化简

    Time Limit: 1 second Memory Limit: 64 MB [问题描述] 最近情报人员得到了一些经过加密的文章,每个单词都很长.破译人员想到先把单词化简一下,方法是把每个单词尽量 ...

  5. JOISC2014 Day2 E "交朋友" (思维+假的SCC)

    传送门 题目描述 你是活跃在历史幕后的一名特工,为了世界和平而夜以继日地努力着. 这个世界有N个国家,编号为1..N; 你的目的是在这N个国家之间建立尽可能多的友好关系. 你为了制定一个特工工作的计划 ...

  6. 2018-9-3-C#-const-和-readonly-有什么区别

    title author date CreateTime categories C# const 和 readonly 有什么区别 lindexi 2018-9-3 16:52:7 +0800 201 ...

  7. H3C 主机名与IP地址映射需求

  8. H3C 端口隔离简介

  9. 【39.68%】【CF 714 C】Filya and Homework

    time limit per test 1 second memory limit per test 256 megabytes input standard input output standar ...

  10. 多校 HDU - 6614 AND Minimum Spanning Tree (二进制)

    传送门 AND Minimum Spanning Tree Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 ...