嗨,朋友们!国庆节到了,为了庆祝伟大的祖国母亲 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_COL
和FLAG_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
函数,用于在一个二维纹理坐标系中绘制一个星形图案。我们逐行分析一下这段代码的功能:
参数说明:
vec2 uv
: 当前纹理坐标。vec2 pos
: 星星的中心位置。float angle
: 星星的旋转角度(以度为单位)。float scale
: 星星的缩放因子。
代码解析:
-
uv -= pos;
:- 将
uv
的坐标相对于星星的中心位置pos
进行平移。这是为了使星星的中心位于原点。
- 将
-
uv /= scale;
:- 将
uv
坐标缩放,使得星星大小可控。scale
值越大,星星越小。
- 将
-
uv *= ROT(radians(angle));
:- 将
uv
坐标旋转一个角度。这里的ROT
函数可能是一个自定义的旋转矩阵,用于将坐标旋转到指定的角度。
- 将
-
uv.x = abs(uv.x);
:- 将
uv
的 x 坐标取绝对值。这样做可以使星星的两侧对称。
- 将
-
float unit = 2.0 * PI / 5.0;
:- 计算出每个星星的尖角的角度。一个五角星有 5 个尖角,因此这里计算出每个尖角之间的角度。
-
float d1, d2, d3, d4;
:- 通过
dot
函数计算uv
与不同角度的单位向量的点积,这样可以得到等于各个尖角的距离值。其中unit * n
的值代表了每个尖角的方向角。
- 通过
-
float d = min(max(d1, d2), max(d3, d4));
:- 通过选择最小的最大值来确定
d
。这个值代表了距离星星边缘的距离。
- 通过选择最小的最大值来确定
-
float w = fwidth(d);
:- 计算
d
的宽度。这通常用来控制平滑边缘的过渡。
- 计算
-
return smoothstep(w, -w, d - 0.2);
:- 使用
smoothstep
函数进行平滑处理。这个函数会根据d
的值在区间-w
到w
内进行平滑插值,从而生成一个光滑的星星形状。
- 使用
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
指定)生成星星:star1
到star5
是通过调用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.旗帜飘扬
如下图,飘扬其实就是让旗帜扭动扭动
float t = sin(uv.x * 3. + iTime * 2.0 + uv.y * 2.0);
uv.y += t * 0.05;
-
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 之间的值。
- 这行代码计算一个浮点数
-
uv.y += t * 0.05;
- 这行代码更改了
uv.y
的值。 t * 0.05
:乘以一个小值(0.05),使得uv.y
的变化不太剧烈。这样可以实现微小偏移。uv.y
的值被这个微小的变化加上,从而导致uv
的 y 坐标发生动态变化。
- 这行代码更改了
总体来说,这段代码用于创建一个基于 uv.x
和 uv.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);
-
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
,用于调整整个正弦函数的输入,以产生特定的波动效果。
- 这行代码计算变量
-
col += w * .15 * sign(inFlag);
- 这行代码将计算得到的
w
乘以 0.15 并与col
相加。 col
是一个颜色变量,通常用于控制某个像素的最终颜色。sign(inFlag)
会返回inFlag
的符号:如果inFlag
大于 0,返回 1;如果小于 0,返回 -1;如果等于 0,返回 0。这个函数用来控制颜色的叠加方向,可能通过inFlag
来决定是增加还是减少颜色强度。
- 这行代码将计算得到的
综上,这段代码生成了一种基于时间和坐标的颜色波动效果
总结
这段代码通过定义颜色、计算坐标和旋转,以及使用平滑过渡,来创建一个动态的旗帜和星星的图案。虽然它看起来复杂,但分解开来,每一步都是为了达到一个简单的目标:在屏幕上绘制美丽的图形。
希望这个简单的解释能帮助你理解这段代码是如何工作的。如果你对图形学感兴趣,这是一个非常好的开始!