前言

本文将介绍如何使用极坐标参数方程和上一篇文章提到的距离场SDF来绘制有趣的图案。

说到曲线和几何图形的绘制,我们知道图形系统默认支持的是通过直角坐标绘制,但是有些曲线呢,不太容易使用直角坐标系来表示,却可以很方便地使用极坐标来表示,这个时候我们可以选择通过极坐标和直角坐标的相互转换,来实现图形的绘制。

下面我就用玫瑰线、花瓣线等曲线作为例子来进行演示。

在开始演示之前,我先简单介绍下极坐标和参数方程。

  • 极坐标是使用相对极点的距离,以及与X轴正向的夹角,使用这一对值来表示平面上点的坐标。

  • 参数方程表示的是点的坐标分别和相关参数的关系,所以通常会对应一个方程组,比如说二维平面上的点,会使用两个参数方程来表示。

我这里只是简单介绍一下,可能说的并不是很准确。我们也可以用圆作为例子,圆的标准方程大家都知道,假设圆心在原点,在直角坐标系下,圆的公式就是x的平方加上y的平方等于半径的平方,圆的参数方程是x等于半径乘以与X轴夹角的余弦值,y等于半径乘以与X轴夹角的正弦值。

\[\begin{cases}
x = r * cosθ \\
y = r * sinθ
\end{cases}
\]

但在极坐标系下,圆的参数方程就变成了r等于一个常量,θ等于与X轴的夹角。这基本上可以算是最简单的极坐标参数方程组了。

\[\begin{cases}
r = r \\
θ = t
\end{cases}
\]

那么基本的知识了解后,我们就可以开始使用极坐标参数方程来绘制图形和曲线了。

具体实现

现在就来演示通过曲线的极坐标参数方程来绘制曲线。

Canvas

首先先来看Canvas2D的例子,在Canvas中我们可以通过lineTo方法绘制足够多的线段将它们连在一起,来模拟曲线,所以我们可以通过参数方程获取足够多的点,将它们连接起来,这样就能最终完成曲线的绘制。

因此我们先来定义一个高阶函数parametric用于创建方程组,接收三个参数xFunc、yFunc和rFunc。xFunc和yFunc表示点的一对坐标值各自分别对应的参数方程,rFunc表示坐标映射函数。

export default function parametric(xFunc, yFunc, rFunc) {
return function(start, end, seg = 100, ...args) {
const points = [];
for (let i = 0; i <= seg; i ++) {
const p = i / seg;
// const t = start * (1 - p) + end * p;
const t = start + (end - start) * i / seg;
// console.log(t);
const x = xFunc(t, ...args);
const y = yFunc(t, ...args);
if (rFunc) points.push(rFunc(x, y));
else points.push([x, y]);
}
return {
draw: draw.bind(null, points),
points
}
}
}

这个高阶函数的返回值也是一个函数,在这个匿名函数应该不难理解,我简单说一下,它接收多个参数,必选的三个是坐标相关参数 t 的上下限start和end,以及要收集的点的数量seg,最终返回一个对象,这个对象带有一个draw方法,通过调用这个draw方法就可以完成曲线的绘制。

下面我们就通过parametric函数构造不同的曲线方程组。来看一个玫瑰线:

// 玫瑰线
const rose = parametric(
(t, a, k) => a * Math.cos(k * t), // r
t => t, // θ
fromPolar,
);

这里fromPolar是一种坐标映射函数,作用是将极坐标转换为直角坐标,这样我们才能在Canvas2d中使用坐标值。现在我们就可以通过调用rose函数,来获取曲线上的点并绘制曲线了。

rose(0, Math.PI, 100, 200, 5).draw(ctx, {strokeStyle: 'blue'}); // 玫瑰线

通过传入不同的a,我们可以改变曲线的大小,而不同的k,可以改变叶子的数目;这样就能构造出不同的图案。

所以我们也可以应用不同的曲线方程,绘制出各种曲线。

WebGL

那么除了Canvas2d,我们当然也可以在WebGL中应用曲线的参数方程实现图形的绘制。接下来我就演示几个在shader中应用参数方程的例子,并结合上篇文章中提到的距离场,完成图形的绘制。

首先还是玫瑰线:

void main() {
vec2 st = vUv - vec2(0.5);
st = polar(st);
float d = 0.5 * cos(st.y * 3.0) - st.x; gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(1.0);
gl_FragColor.a = 1.0;
}

在这段Shader代码中,这里的0.5就是刚刚在Canvas例子中的a,而3.0就是Canvas例子中的k,所以这里叶子的数目就是3。那为什么这里的d要这样定义呢?这是因为玫瑰线上的所有点都满足 0 = a * cos(k * θ) - r 这个等式,也就是说玫瑰线上的点对应的d都是0,并且玫瑰线形成的图形内部的点对应的d都大于0,而外部的点对应的d都小于0,因此这样我们就可以得到一个内部填充为白色的玫瑰线图形。

我们对玫瑰线的参数和公式做些微调,就能得到不同的图形,比如添加一个取绝对值的操作,并且给角度乘以一个常量0.5。

void main() {
float u_k = 3.0; vec2 st = vUv - vec2(0.5);
st = polar(st);
float d = 0.5 * abs(cos(st.y * u_k * 0.5)) - st.x;
gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(1.0);
gl_FragColor.a = 1.0;
}

可以看出这个图形有些类似花瓣的形状。

当我们设置不同的u_k值时,也能得到不同的图案,比如当u_k设置为1.3时,图形看上去就像一个横放的苹果。

void main() {
float u_k = 1.3; // 横放的苹果 vec2 st = vUv - vec2(0.5);
st = polar(st);
float d = 0.5 * abs(cos(st.y * u_k * 0.5)) - st.x;
gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(1.0);
gl_FragColor.a = 1.0;
}

继续微调参数方程,我们还能画出类似横放的葫芦图案:

void main() {
float u_k = 1.7; // 横放的葫芦
float u_scale = 0.5;
float u_offset = 0.2; vec2 st = vUv - vec2(0.5);
st = polar(st);
float d = u_scale * 0.5 * abs(cos(st.y * u_k * 0.5)) - st.x + u_offset;
gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(1.0);
gl_FragColor.a = 1.0;
}

相信小伙伴们通过结合三角函数、abs、smoothstep等等这些函数,还能绘制出更多有趣的图案,大家可以动手自己尝试一下。

结合随机数

接下来,我们结合一个随机数函数,来实现一个类似剪纸的图案。

在这个例子中,我们会使用一个生成随机数的函数叫做random,从代码中我们可以看出random实际上生成的是一个伪随机数,因为纹理坐标是确定的,所以根据纹理坐标生成的随机数也是确定的。

float random(vec2 st) {
return fract(
sin(
dot(st.xy, vec2(12.9898, 78.233))
) *
43758.5453123
);
}

这个函数是我学的课程上给的一个函数,应该是一个经验公式。那么接下来我们就开始来实现这个剪纸图案。

首先,我们利用实现网格的方式,将画布变为10 x 10的网格,此时得到的st变量就是片元对应的纹理坐标乘以10,接着我们分别获取到st的整数部分和小数部分。

整数部分ipos将用于生成随机数,而小数部分就用于绘制各类图案。

vec2 st = vUv * 10.0;
vec2 ipos = floor(st); // integer
vec2 fpos = fract(st); // fraction float r = random(ipos);

接着我们就可以按照随机数在不同区间绘制不同的图案,我这里就用上期学的着色器几何造型里的不同距离公式和上面的几个参数方程来实现不同图案的绘制。

void main() {
vec2 st = vUv * 10.0;
vec2 ipos = floor(st); // integer
vec2 fpos = fract(st); // fraction float r = random(ipos); float d = 0.0;
if(r < 0.14) { // 四边形
fpos = fpos - vec2(0.5);
float d = polygon_distance2(
fpos,
4,
vec2(0.0, 0.4)
);
gl_FragColor.rgb = smoothstep(0.01, 0.0, d) * vec3(1.0, 0.0, 0.0);
gl_FragColor.a = smoothstep(0.01, 0.0, d);
} else if (r < 0.28) { // 四片花瓣
float u_k = 4.0; fpos = fpos - vec2(0.5);
fpos = polar(fpos);
d = 0.5 * abs(cos(fpos.y * u_k * 0.5)) - fpos.x;
gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(1.0, 0.0, 0.0);
gl_FragColor.a = smoothstep(-0.01, 0.01, d);
} else if (r < 0.42) { // 苹果
float u_k = 1.3; fpos = fpos - vec2(0.5, 0.7);
fpos = polar(fpos);
fpos.y += 3.14 / 2.0;
// atan 的返回值是:从第一到第二象限为 0~PI,从第三到第四象限为 -PI~0
// 旋转极坐标后要保证函数定义域的一致性
if (fpos.y > 3.14) fpos.y -= 3.14 * 2.0;
float d = 0.5 * abs(cos(fpos.y * u_k * 0.5)) - fpos.x;
gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(1.0, 0.0, 0.0);
gl_FragColor.a = smoothstep(-0.01, 0.01, d);
} else if (r < 0.56) { // 六边形
fpos = fpos - vec2(0.5);
float d = polygon_distance2(
fpos,
6,
vec2(0.0, 0.4)
);
gl_FragColor.rgb = smoothstep(0.01, 0.0, d) * vec3(1.0, 0.0, 0.0);
gl_FragColor.a = smoothstep(0.01, 0.0, d);
} else if (r < 0.70) { // 五角星
fpos = fpos - vec2(0.5);
float d = star_distance(
fpos,
5,
vec2(0.15, 0.2)
);
gl_FragColor.rgb = smoothstep(0.01, 0.0, d) * vec3(1.0, 0.0, 0.0);
gl_FragColor.a = smoothstep(0.01, 0.0, d);
} else if (r < 0.84) { // 葫芦
float u_k = 1.7;
float u_scale = 0.5;
float u_offset = 0.2; fpos = fpos - vec2(0.5);
fpos = polar(fpos);
fpos.y += 3.14 / 2.0;
if (fpos.y > 3.14) fpos.y -= 3.14 * 2.0;
float d = u_scale * 0.5 * abs(cos(fpos.y * u_k * 0.5)) - fpos.x + u_offset;
gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(1.0, 0.0, 0.0);
gl_FragColor.a = smoothstep(-0.01, 0.01, d);
} else { // 花苞
float u_k = 5.0;
float u_scale = 0.13;
float u_offset = 0.2; fpos = fpos - vec2(0.5);
fpos = polar(fpos);
float d = smoothstep(-0.3, 1.0, u_scale * 0.5 * cos(fpos.y * u_k) + u_offset) - fpos.x;
gl_FragColor.rgb = smoothstep(-0.01, 0.01, d) * vec3(1.0, 0.0, 0.0);
gl_FragColor.a = smoothstep(-0.01, 0.01, d);
}
}

这样我们就实现了一个类似于剪纸的图案,实现的方式比较简单粗暴,就是用了一堆if-else,看上去不太优雅。

相信大家一定都知道怎么去实现更多图形图案了,接下去就是多动手多尝试。

代码参考:Canvas

代码参考:WebGL

可视化学习:使用极坐标参数方程和SDF绘制有趣的图案的更多相关文章

  1. Shadertoy 教程 Part 5 - 运用SDF绘制出更多的2D图形

    Note: This series blog was translated from Nathan Vaughn's Shaders Language Tutorial and has been au ...

  2. 可视化学习Tensorboard

    可视化学习Tensorboard TensorBoard 涉及到的运算,通常是在训练庞大的深度神经网络中出现的复杂而又难以理解的运算.为了更方便 TensorFlow 程序的理解.调试与优化,发布了一 ...

  3. R语言可视化学习笔记之添加p-value和显著性标记

    R语言可视化学习笔记之添加p-value和显著性标记 http://www.jianshu.com/p/b7274afff14f?from=timeline   上篇文章中提了一下如何通过ggpubr ...

  4. Tensorflow学习笔记3:TensorBoard可视化学习

    TensorBoard简介 Tensorflow发布包中提供了TensorBoard,用于展示Tensorflow任务在计算过程中的Graph.定量指标图以及附加数据.大致的效果如下所示, Tenso ...

  5. 【Silverlight】Bing Maps学习系列(五):绘制多边形(Polygon)图形(转)

    [Silverlight]Bing Maps学习系列(五):绘制多边形(Polygon)图形 Bing Maps Silverlight Control支持用户自定义绘制多边形(Polygon)图形, ...

  6. 87、使用TensorBoard进行可视化学习

    1.还是以手写识别为类,至于为什么一直用手写识别这个例子,原因很简单,因为书上只给出了这个类子呀,哈哈哈,好神奇 下面是可视化学习的标准函数 ''' Created on 2017年5月23日 @au ...

  7. Echart可视化学习集合

    一.基本介绍:ECharts是一款基于JavaScript的数据可视化图表库,提供直观,生动,可交互,可个性化定制的数据可视化图表.ECharts最初由百度团队开源,并于2018年初捐赠给Apache ...

  8. matplotlab可视化学习

    1 使用pip安装 使用 Python 包管理器 pip 来安装 Matplotlib 是一种最轻量级的方式.打开 CMD 命令提示符窗口,并输入以下命令: pip install matplotli ...

  9. Python可视化学习(1):Matplotlib的配置

    Matplotlib是一个优秀的可视化库,它提供了丰富的接口,让Python的可视化落地显得非常容易上手.本系列是本人学习python可视化的学习笔记,主要用于监督自己的学习进度,同时也希望和相关的博 ...

  10. python学习2:turtle的使用蟒蛇绘制的学习以及自己摸索的等边三角形绘制(跟随mooc学习)

    首先先放上蟒蛇的绘制程序 import turtle#引入外部库#def保留字用于 定义函数 def drawSnake(rad,angle,len,neckrad): for i in range( ...

随机推荐

  1. IDEA Tab键设置为4个空格

    在不同的编辑器里 Tab 的长度可能会不一致,这会导致有 Tab 的代码,用不同的编辑器打开时,格式可能会乱. 而且代码压缩时,空格会有更好的压缩率. 所以建议将 IDEA 的 Tab 键设置为 4 ...

  2. chatGPT教你学sql的事务

    事务的隔离级别 事务的隔离级别是指多个并发事务之间相互隔离的程度,主要是为了解决并发事务带来的一致性问题,它的主要作用是控制数据库中事务的可见性和可重复读. 在 SQL 标准中,定义了四种事务隔离级别 ...

  3. 基于eTS高效开发HarmonyOS课程类应用

    原文:https://mp.weixin.qq.com/s/kU76kB6T1JSqapAfGPGRHQ,点击链接查看更多技术内容. 随着HarmonyOS 3.0 Beta版的发布,API Vers ...

  4. vscode 编写node的c++ 扩展

    前言 在此介绍一下node的c++扩展在vscode 上的编译环境,在此不多说,比较完善,看了肯定明白. 正文 c++ 环境搭建 下载mingw,然后配置好环境.下载地址为,官网,可以自己百度一下. ...

  5. 重走py 之路 ——列表(一)

    前言 因为最近公司有python项目维护,所以把python的基础入门的书整理一遍,因为有些忘记了,同时在看<<python编程>>这本书的时候觉得对有基础的有很多的赘余,打算 ...

  6. IIS applicationHost.config 查找历史

    背景 iis 有时候需要修改配置,一般来说,我们会去修改applicationHost.config配置,当然,很多时候我们都需要去备份一个配置文件,但是可能忘记了,那么是否有补救的方式? 补救方式 ...

  7. C# 冻结Excel窗口以锁定行列、或解除冻结

    在处理大型Excel工作簿时,有时候我们需要在工作表中冻结窗格,这样可以在滚动查看数据的同时保持某些行或列固定不动.冻结窗格可以帮助我们更容易地导航和理解复杂的数据集.相反,当你不需要冻结窗格时,你可 ...

  8. Jenkins集成GitLab的正确姿势,实现Git代码提交触发CI/CD

    ❝ jenkins和gitlab是目前DevOps工具链中最常见的,抛开gitlab-ci不谈,gitlab代码提交触发jenkins流水线是最经典的搭配. 这里就介绍下如何配置实现jenkins和g ...

  9. 企业实施定制鞋厂ERP软件需要注意哪些问题?

    企业实施定制ERP软件是个复杂的管理系统工程,为了成功地为企业定制实施ERP软件,需要注意和解决几个关键的问题: (1) . 确立ERP系统实施和定制的决策者: (2) . 做好前期咨询与调研工作: ...

  10. 鸿蒙HarmonyOS实战-ArkUI动画(弹簧曲线动画)

    前言 弹簧曲线动画是一种模拟弹簧运动的动画效果,通过改变弹簧的拉伸或压缩来表现不同的运动状态.以下是制作弹簧曲线动画的步骤: 创建一个弹簧的模型,可以使用圆形或者曲线来代表弹簧的形状. 将弹簧固定在一 ...