Shader从入门到放弃(五)—— 星空II

2,777 阅读5分钟

前言

大家好,此次我们继续绘制另一片美丽的星空。咱们废话不多说,直接进入正题。

我们先看一下最后的效果:

20231102135254_rec_

让我们一步一步的揭示其中的奥秘吧~!

原理

绘制一个星星

万丈高楼从地起,我们先从绘制一个星星⭐️开始~

前面将坐标转化到 -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);
 }

image-20231102135927994

可是光有一个亮点还不够,我们还需要绘制出来星星的”星辉“~

我们可以利用 f(x,y)=1abs(xy)f(x, y) = 1- abs(x \cdot y),其图像为:

image-20231102140557690

接下来,我们为 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 ;
 }

image-20231102140846244

由于”星辉“贯穿了整个屏幕,我们希望这个星辉有一定的范围,我们可以使用 smoothstepuv坐标到中心的距离形成一个渐变的遮罩来控制”星辉“的长度。结果如下:

 return m * smoothstep(0.8, 0.2, d);

image-20231102141041457

为了让星星⭐️更加好看一些,我们可以将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);
 }

image-20231102141153067

绘制多个星星

我们已经完成了绘制一个星星的步骤,现在我们要开始绘制很多星星,之前我们学习过,我们可以将 uv乘以一个值来将整个画布"网格化"。比如:

 vec2 st = fract(uv * 3.0) - 0.5;
 col += Star(st, 1.0);

结果如下:

image-20231102141738891

我们可以将星星绘制的更加错落有致,可以利用一个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);

最终的结果如下:

image-20231102141942357

解决”断层“问题

但是我们发现这样做存在一些缺陷,因为在每个小格子中我们没有将星星绘制在中间,所以看起来星星被截断了一样。那么如何解决这个问题呢?

一个常用的解决方法是在其格子的相邻的格子中,将其补完。我们使用双重循环:

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

上面的代码可能会让你感到疑惑,如果你感到疑惑,先记住这样的做法就好了~!!!没关系,抄就完事了,以后你会领悟的~!

通过上面的代码修正后,渲染的结果如下:

image-20231102142908295

我们将上面的代码封装一下,命名为 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;
     }
 }

结果如下:

image-20231102143434614

我希望星星中有星辉仅存在于尺寸比较大的星星中,我可以使用一个 smoothstep函数来修正。最后还需要利用 sizecol的乘积来修正星星的大小。

 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;

结果如下:

image-20231102144159411

修改颜色

接下来我们为每个格子的星星赋予不同的颜色。做法与上面设置不同的尺寸类似。

 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值挂钩就行了。结果如下:

image-20231102144726371

绘制多层星星

我们现在绘制了一层的星星,接下来我们要做的事情就比较简单了,与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;
 }

image-20231102145157997

让画面动起来

最后,我们只需要引入一个全局时间,让 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)这篇文章的实现方式大同小异,唯一需要注意的点是处理 ”断层“ 的代码需要读者们仔细体会~

今天的内容也差不多了,就酱吧~ 如果你觉得有用,可以点个赞哦~

文章最后是完整的代码: