前言
大家好,此次我们继续绘制另一片美丽的星空。咱们废话不多说,直接进入正题。
我们先看一下最后的效果:
让我们一步一步的揭示其中的奥秘吧~!
原理
绘制一个星星
万丈高楼从地起,我们先从绘制一个星星⭐️开始~
前面将坐标转化到 -0.5 ~ 0.5 之间
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (fragCoord.xy - .5 * iResolution.xy) / iResolution.y;
vec3 col = vec3(0.0);
fragColor = vec4(col, 1.0);
}
我们先绘制一个圆
float Star(vec2 uv) {
float d = length(uv);
float m = 0.04 / d;
return m;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (fragCoord.xy - .5 * iResolution.xy) / iResolution.y;
vec3 col = vec3(0.0);
col += Star(uv);
fragColor = vec4(col, 1.0);
}
可是光有一个亮点还不够,我们还需要绘制出来星星的”星辉“~
我们可以利用 ,其图像为:
接下来,我们为 Star
函数添加一个参数flare
来表示其”星辉“的强度
float Star(vec2 uv, float flare) {
float d = length(uv);
float m = 0.04 / d;
float r = max(0.0, 1.0 - abs(uv.x * uv.y * 1000.0));
m += r * flare;
return m ;
}
由于”星辉“贯穿了整个屏幕,我们希望这个星辉有一定的范围,我们可以使用 smoothstep
与 uv
坐标到中心的距离形成一个渐变的遮罩来控制”星辉“的长度。结果如下:
return m * smoothstep(0.8, 0.2, d);
为了让星星⭐️更加好看一些,我们可以将uv
坐标旋转一定角度后再添加一个”星辉“。
mat2 Rot(float a) {
float s = sin(a);
float c = cos(a);
return mat2(c, -s, s, c);
}
float Star(vec2 uv, float flare) {
float d = length(uv);
float m = 0.04 / d;
float r = max(0.0, 1.0 - abs(uv.x * uv.y * 1000.0));
m += r * flare;
uv *= Rot(3.1415 / 4.);
float r2 = max(0.0, 1.0 - abs(uv.x * uv.y * 1000.0));
m += r2 * 0.25 * flare;
return m * smoothstep(0.8, 0.2, d);
}
绘制多个星星
我们已经完成了绘制一个星星的步骤,现在我们要开始绘制很多星星,之前我们学习过,我们可以将 uv
乘以一个值来将整个画布"网格化"。比如:
vec2 st = fract(uv * 3.0) - 0.5;
col += Star(st, 1.0);
结果如下:
我们可以将星星绘制的更加错落有致,可以利用一个Hash函数来让每个格子里的星星发生一点点偏移。 Hash函数如下:
float Hash21(vec2 p) {
p = fract(p * vec2(123.45, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
mainImage
函数需要修改如下:
vec2 st = fract(uv * 3.0) - 0.5;
vec2 id = floor(uv * 3.0);
float offset = Hash21(id) * 0.5;
col += Star(st - offset, 1.0);
fragColor = vec4(col, 1.0);
最终的结果如下:
解决”断层“问题
但是我们发现这样做存在一些缺陷,因为在每个小格子中我们没有将星星绘制在中间,所以看起来星星被截断了一样。那么如何解决这个问题呢?
一个常用的解决方法是在其格子的相邻的格子中,将其补完。我们使用双重循环:
for(int y = -1; y <= 1; y++) {
for(int x = -1; x <= 1; x++) {
vec2 neighborOffset = vec2(x, y);
float n = Hash21(id + neighborOffset);
vec2 innerOffset = vec2(n - 0.5, fract(n * 52.) - 0.5);
float star = Star(st - neighborOffset - innerOffset, 1.0);
col += star;
}
}
上面的代码可能会让你感到疑惑,如果你感到疑惑,先记住这样的做法就好了~!!!没关系,抄就完事了,以后你会领悟的~!
通过上面的代码修正后,渲染的结果如下:
我们将上面的代码封装一下,命名为 Layer
函数
vec3 Layer(vec2 uv) {
vec3 col = vec3(0);
vec2 gv = fract(uv) - 0.5;
vec2 id = floor(uv);
for(int y = -1; y <= 1; y++) {
for(int x = -1; x <= 1; x++) {
vec2 neighborOffset = vec2(x, y);
float n = Hash21(id + neighborOffset);
vec2 innerOffset = vec2(n - 0.5, fract(n * 52.) - 0.5);
float star = Star(gv - neighborOffset - innerOffset, 1.0);
col += star;
}
}
return col;
}
修改星星尺寸
让我们继续,我们希望每个格子的星星的大小不同,我们已经根据格子的id
生成了“随机数”n
,我们可以直接使用这个值n
,为了引入更多的随机性,所以我们再使用fract
函数处理一下~我们修改双循环结构中的代码:
for(int y = -1; y <= 1; y++) {
for(int x = -1; x <= 1; x++) {
vec2 neighborOffset = vec2(x, y);
float n = Hash21(id + neighborOffset);
float size = fract(n * 345.34);
float star = Star(gv - neighborOffset - innerOffset, size);
col += star;
}
}
结果如下:
我希望星星中有星辉仅存在于尺寸比较大的星星中,我可以使用一个 smoothstep
函数来修正。最后还需要利用 size
与 col
的乘积来修正星星的大小。
float size = fract(n * 345.34) * 0.8 + 0.2;
//.......中间代码略
float flare = smoothstep(0.8, 0.9, size);
float star = Star(gv - neighborOffset - innerOffset, flare);
col += star * size;
结果如下:
修改颜色
接下来我们为每个格子的星星赋予不同的颜色。做法与上面设置不同的尺寸类似。
vec3 color = sin(vec3(.2, .5, .9) * fract(n * 2345.2) * 6.28) * .5 + .5;
color *= vec3(0.3 + size * 0.2, 0.5, 0.5 + size * 1.5);
这一块大家可以随意发挥,没有特定的写法,只是需要与我们这个 n
值挂钩就行了。结果如下:
绘制多层星星
我们现在绘制了一层的星星,接下来我们要做的事情就比较简单了,与Shader从入门到放弃(四) —— 绘制闪耀星际 - 掘金 (juejin.cn)中的做法类似,我们只需要绘制很多层星星就可以形成一个比较好的效果。代码与效果如下
for(float i = 0.0; i < 1.0; i += 1. / NUM_LAYERS) {
float depth = fract(i);
float fade = smoothstep(0.0, 0.3, depth) * smoothstep(1.0, 0.9, depth);
float scale = mix(20., .5, depth);
col += Layer(uv * scale + i * 435.32) * fade;
}
让画面动起来
最后,我们只需要引入一个全局时间,让 depth
跟随时间变化起来就可以实现封面图的效果了~
顺手再让uv
也跟着时间旋转起来~
float t = iTime * 0.1;
uv *= Rot(t);
for(float i = 0.0; i < 1.0; i += 1. / NUM_LAYERS) {
float depth = fract(i + t);
// .... 剩余代码略
}
总结
让我们总结一下吧,其实本篇与Shader从入门到放弃(四) —— 绘制闪耀星际 - 掘金 (juejin.cn)这篇文章的实现方式大同小异,唯一需要注意的点是处理 ”断层“ 的代码需要读者们仔细体会~
今天的内容也差不多了,就酱吧~ 如果你觉得有用,可以点个赞哦~
文章最后是完整的代码: