初中的5 个数学公式画出飘扬的五星红旗

205 阅读7分钟

2410054656

嗨,朋友们!国庆节到了,为了庆祝伟大的祖国母亲 75 周岁生日快乐,我们用数学函数创建一个飘扬的五星红旗的动画。

这些数学函数是用一种叫做GLSL(OpenGL Shading Language)的语言写的,它是专门为图形硬件设计的。现在,让我们一步步来理解这段代码是如何工作的。完整代码在 www.shadertoy.com/view/X3sBzN , 点击就可以查看哦~

1. 定义颜色和常量

首先,我们看到一些定义颜色和数学常量的代码:

#define STAR_COL vec3(252, 238, 033)/255.0
#define FLAG_COL vec3(244, 000, 002)/255.0
#define PI 3.141592653
#define TAU PI*2.
#define ROT(a) mat2(cos(a),-sin(a),sin(a),cos(a))
  • STAR_COLFLAG_COL 是两种颜色,分别用来表示星星和旗帜的颜色。
  • PI 是圆周率,大约是3.14159,用来计算圆和角度。
  • TAU 是一个完整的圆,等于 PI * 2
  • ROT(a) 是一个旋转矩阵,用来旋转图形。

2. 绘制星星

接下来,我们看到一个函数 Star,它用来绘制星星:

float Star(vec2 uv, vec2 pos, float angle, float scale) {
    uv -= pos;
    uv /=scale;
    uv *= ROT(radians(angle));
    uv.x = abs(uv.x);
    float unit = 2.0 * PI / 5.0;
    float d1 = dot(uv, vec2(sin(unit * 1.0), cos(unit * 1.0)));
    float d2 = dot(uv, vec2(sin(unit * 3.0), cos(unit * 3.0)));
    float d3 = dot(uv, vec2(sin(unit * 0.0), cos(unit * 0.0)));
    float d4 = dot(uv, vec2(sin(unit * 2.0), cos(unit * 2.0)));
    float d  = min(max(d1, d2), max(d3, d4));
    // d = length(uv);
    float w  = fwidth(d);
    return smoothstep(w, -w, d-0.2);
}

这段代码定义了一个 Star 函数,用于在一个二维纹理坐标系中绘制一个星形图案。我们逐行分析一下这段代码的功能:

参数说明:

  1. vec2 uv: 当前纹理坐标。
  2. vec2 pos: 星星的中心位置。
  3. float angle: 星星的旋转角度(以度为单位)。
  4. float scale: 星星的缩放因子。

代码解析:

  1. uv -= pos;:

    • uv 的坐标相对于星星的中心位置 pos 进行平移。这是为了使星星的中心位于原点。
  2. uv /= scale;:

    • uv 坐标缩放,使得星星大小可控。scale 值越大,星星越小。
  3. uv *= ROT(radians(angle));:

    • uv 坐标旋转一个角度。这里的 ROT 函数可能是一个自定义的旋转矩阵,用于将坐标旋转到指定的角度。
  4. uv.x = abs(uv.x);:

    • uv 的 x 坐标取绝对值。这样做可以使星星的两侧对称。
  5. float unit = 2.0 * PI / 5.0;:

    • 计算出每个星星的尖角的角度。一个五角星有 5 个尖角,因此这里计算出每个尖角之间的角度。
  6. float d1, d2, d3, d4;:

    • 通过 dot 函数计算 uv 与不同角度的单位向量的点积,这样可以得到等于各个尖角的距离值。其中 unit * n 的值代表了每个尖角的方向角。
  7. float d = min(max(d1, d2), max(d3, d4));:

    • 通过选择最小的最大值来确定 d。这个值代表了距离星星边缘的距离。
  8. float w = fwidth(d);:

    • 计算 d 的宽度。这通常用来控制平滑边缘的过渡。
  9. return smoothstep(w, -w, d - 0.2);:

    • 使用 smoothstep 函数进行平滑处理。这个函数会根据 d 的值在区间 -ww 内进行平滑插值,从而生成一个光滑的星星形状。

3. 绘制旗帜

然后,我们看到一个函数 Flag,它用来绘制整个旗帜:

vec3 Flag(vec2 uv) {
    float star1 = Star(uv, vec2(-0.7, 0.3), 0., 0.3);
    float star2 = Star(uv, vec2(-0.4, 0.5), 30., 0.1);
    float star3 = Star(uv, vec2(-0.25, 0.4), 15., 0.1);
    float star4 = Star(uv, vec2(-0.25, 0.2), 0., 0.1);
    float star5 = Star(uv, vec2(-0.4, 0.1), 30., 0.1);
    
    float d = star1+star2+star3+star4+star5;
    vec3 col = mix(FLAG_COL, STAR_COL, clamp(0., 1., d));
        
    col *= smoothstep(.01, .0, abs(uv.y) - 2.0 / 3.0);
    col *= smoothstep(.01, .0, abs(uv.x) - 1.);
    
    return col;
}

这段代码是用于生成一个带有星星的旗帜效果的着色器函数,通常在计算机图形学和游戏开发中使用。下面是代码各个部分的详细解释:

函数说明

  • vec3 Flag(vec2 uv):定义一个名为 Flag 的函数,它接受一个二维向量 uv(范围从 -1 到 1),并返回一个三维向量 col,表示最终的颜色。

星星绘制

  • 该函数调用了多个 Star 函数,分别在不同的位置(由 vec2 指定)生成星星:
    • star1star5 是通过调用 Star 函数生成的五颗星星。
    • 每颗星星的位置和旋转角度(以度为单位)都不同,并且有不同的大小(最后一个参数表示星星大小)。

颜色插值

  • float d = star1 + star2 + star3 + star4 + star5;:将所有星星的值相加,得到一个合成值 d,此值决定了星星的亮度(或数量)。
  • vec3 col = mix(FLAG_COL, STAR_COL, clamp(0., 1., d));:使用线性插值,将旗帜色 FLAG_COL 和星星色 STAR_COL 进行混合,具体混合量由 d 计算得出。clamp(0., 1., d) 确保值在 0 到 1 之间。

平滑边缘

  • col *= smoothstep(.01, .0, abs(uv.y) - 2.0 / 3.0);:对颜色进行平滑处理,确保当 uv.y 超过某个阈值时(这里是 2/3 ),颜色变得渐变,这样会使得旗帜的底部呈现出更为柔和的过渡。
  • col *= smoothstep(.01, .0, abs(uv.x) - 1.);:同样地对 uv.x 进行平滑处理,确保在 x 轴的边缘上颜色也会齿轮平滑的效果。

4.旗帜飘扬

如下图,飘扬其实就是让旗帜扭动扭动

2410055404

float t = sin(uv.x * 3. + iTime * 2.0 + uv.y * 2.0);
uv.y +=  t * 0.05;
  1. float t = sin(uv.x * 3. + iTime * 2.0 + uv.y * 2.0);

    • 这行代码计算一个浮点数 t,它的值由 sin 函数决定。
    • uv.x * 3.:将 uv.x 乘以 3,可能用于增加频率。
    • iTime * 2.0:这是一个时间变量(通常在渲染过程中更新),将时间乘以 2,用于动态变化。
    • uv.y * 2.0:将 uv.y 乘以 2,也可能用于增加频率。
    • 最终,sin 函数会对上面的结果进行计算,生成一个在 -1 到 1 之间的值。
  2. uv.y += t * 0.05;

    • 这行代码更改了 uv.y 的值。
    • t * 0.05:乘以一个小值(0.05),使得 uv.y 的变化不太剧烈。这样可以实现微小偏移。
    • uv.y 的值被这个微小的变化加上,从而导致 uv 的 y 坐标发生动态变化。

总体来说,这段代码用于创建一个基于 uv.xuv.y(通常用于纹理坐标)的波动效果,结合时间 iTime,可以让纹理或图形在渲染时产生动态波动的视觉效果。

5. 旗帜阴影

对于阴影的计算往往需要用到光照公式,但是由于我们是一个 2D图像暂时无法计算,于是我们在寻找一个波函数,不同的是这个波函数不在作用于 x,y坐标,而是控制图像的颜色,这样深浅变化就有类似阴影的效果

 float w = sin((uv.x + uv.y - iTime * .75 + sin(1.5 * uv.x + 4.5 * uv.y) * PI * .3)
                  * PI * .6); 
    col += w * .15 * sign(inFlag);

2410055610

  1. float w = sin((uv.x + uv.y - iTime * .75 + sin(1.5 * uv.x + 4.5 * uv.y) * PI * .3) * PI * .6);

    • 这行代码计算变量 w,它是一个正弦(sine)函数的结果。
    • uv 是一个二维向量,通常用来表示纹理坐标或屏幕坐标。
    • iTime 是一个时间变量,通常表示从程序开始以来经过的时间,它的乘法(iTime * .75)会使得这个值随着时间的推移而变化,从而导致渲染效果随时间更新。
    • sin(1.5 * uv.x + 4.5 * uv.y) * PI * .3 计算出一段额外的波动效果,并乘以一个常数(PI * .3),渲染出一种变化的纹理或波动效果。
    • 最后的结果乘以 PI * .6,用于调整整个正弦函数的输入,以产生特定的波动效果。
  2. col += w * .15 * sign(inFlag);

    • 这行代码将计算得到的 w 乘以 0.15 并与 col 相加。
    • col 是一个颜色变量,通常用于控制某个像素的最终颜色。
    • sign(inFlag) 会返回 inFlag 的符号:如果 inFlag 大于 0,返回 1;如果小于 0,返回 -1;如果等于 0,返回 0。这个函数用来控制颜色的叠加方向,可能通过 inFlag 来决定是增加还是减少颜色强度。

综上,这段代码生成了一种基于时间和坐标的颜色波动效果

总结

这段代码通过定义颜色、计算坐标和旋转,以及使用平滑过渡,来创建一个动态的旗帜和星星的图案。虽然它看起来复杂,但分解开来,每一步都是为了达到一个简单的目标:在屏幕上绘制美丽的图形。

希望这个简单的解释能帮助你理解这段代码是如何工作的。如果你对图形学感兴趣,这是一个非常好的开始!