Shader从入门到放弃(四) —— 绘制闪耀星际

news2025/2/28 13:31:25

前言

经过3个章节的学习,相信大家对shader编程也逐渐的有了一些感觉,所以这次我们玩个“大”的!

今天的学习内容是绘制“闪耀星际”,正如歌中唱的那样:

星际闪耀光影,落入你的眼睛,如迷人的水晶,把浪漫放映……

好,废话不多说,直接进入正题!

绘制“闪耀星际”

原理概述

说起来简单,做起来又是怎么一回事呢。Let’s code!

编码

格子绘制

首先第一步要做的还是归一化uv坐标,然后给uv坐标乘上一个数,这个数就是我们想要将坐标划分几个格子的数量。这里我们将先将画布划分成5个格子吧。

再通过fract 函数来获得每个“格子”中的uv坐标,我们将其称为st

void mainImage(out vec4 fragColor, in vec2 fragCoord) {vec2 uv = (fragCoord.xy - .5 * iResolution.xy) / iResolution.y;uv *= 5.0;vec2 st = fract(uv) - 0.5;vec3 col = vec3(0.0);col.rg = st;fragColor = vec4(col, 1.0);
} 

结果如下:

在格子中绘制点

现在,我们开始在每个格子中绘制一个圆吧。

为了方便我们观察格子的边界,所以我们加了一个if条件,如果st坐标处于某个范围内就把当前像素显示为红色。代码如下:

 float d = length(st);
float m = smoothstep(0.1, 0.09, d);

col += m;
if(st.x > 0.45 || st.y > 0.45) {col = vec3(1.0, 0.0, 0.0);
} 

结果如下:

接下来,我们让每个点与周围8个点都进行连线。绘制直线的方法还记得吗?如果不记得了,可以回头再复习一下如何绘制一条直线

连线

此处就不再赘述如何绘制直线了,直接给出绘制直线的函数:

 float DistLine(vec2 p, vec2 a, vec2 b) {vec2 pa = p - a;vec2 ba = b - a;float t = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);return length(pa - ba * t);
}

float Line(vec2 p, vec2 a, vec2 b) {float d = DistLine(p, a, b);float m = smoothstep(0.02, 0.01, d);return m;
} 

当前格子的点要与周围的点产生连线就势必会用到 for 循环了。此处,我们还使用了一个数组来保存当前格子点的坐标与周围点的坐标,截止到目前 mainImage 中的完整代码如下:

void mainImage(out vec4 fragColor, in vec2 fragCoord) {vec2 uv = (fragCoord.xy - .5 * iResolution.xy) / iResolution.y;vec3 col = vec3(0);uv *= 5.0;vec2 st = fract(uv) - 0.5;float d = length(st);float m = smoothstep(0.1, 0.09, d);int i = 0;vec2 p[9];for(float y = -1.0; y <= 1.0; y++) {for(float x = -1.0; x <= 1.0; x++) {vec2 offs = vec2(x, y);p[i++] = offs;}}for(i = 0; i < 9; i++) {m += Line(st, p[i], p[4]);}col += m;fragColor = vec4(col, 1.0);return;col += m;if(st.x > 0.48 || st.y > 0.48) {col += 1.0;}fragColor = vec4(col, 1.);
} 

结果如下:

动起来吧

由于我们现在每个格子中的圆心坐标st都等于 vec2(0.0, 0.0),但是现在我们需要让每个点在自己的格子范围内动起来。所以,我们需要让每个格子中的圆心坐标st发生变化。而且,我们想要的是每个格子都发生不一样的变化。

此时,我们需要用到随机数。在shader程序中,并没有真正意义上的一个随机数,而是进行一些计算,让输入值改变比较小的时候,函数的输出结果发生比较大的改变。随机数是一个很大的话题,由于今天我们的重点不在随机数上,所以读者们可以跳过这一段,直接把函数拿来使用即可。

随机数函数如下:

vec2 N22(vec2 p) {p += vec2(434.67, 534.23);vec3 a = fract(p.xyx * vec3(123.34, 234.34, 345.65));a += dot(a, a + 34.45);return fract(vec2(a.x * a.y, a.y * a.z));
} 

该函数是根据一个2维的向量来生成一个另一个随机的2维向量。我们可以通过下面这个程序来看看我们的随机数是否分布的比较均匀。

vec2 n = N22(uv);
fragColor = vec4(n, 0.0, 1.0); 

通过上图我们可以看出,最终随机产生的结果还是不错的。我们就使用这个函数即可。

由于我们想要的是每个格子产生的随机数需要保持一致,所以我们不能够使用st 坐标来产生随机数,而是要获取每个格子的index或者说是 id来产生随机数。

我们可以使用floor 函数来获取每个格子的id。

vec2 id = floor(uv);
col.rg += id * 0.3; 

通过上面的程序我们可以将id “可视化”出来以验证代码是否正确。

有了id过后,我们就可以产生随机数了。此处,我们新写一个 GetPos 函数来表示我们获取的点。

获取圆心坐标的函数如下,由于我们不仅仅是要获取当前像素所在格子中的圆心位置。而且我们还需要获取周围8个格子的圆心位置。所以我们还需要增加一个 offs 作为偏移量传入函数中。

vec2 GetPos(vec2 id, vec2 offs) {// 根据格子的 id和offs 产生一个随机数,// 乘上iTime使其根据时间发生变化vec2 n = N22(id + offs) * iTime;return sin(n) * 0.4 + offs;
} 

再修改双重 for 循环中的代码如下:

 // --- 
// float m = smoothstep(0.1, 0.09, d);
// +++
float m = 0.0;

for(float y = -1.0; y <= 1.0; y++) {for(float x = -1.0; x <= 1.0; x++) {vec2 offs = vec2(x, y);p[i++] = GetPos(id, offs);}
} 

结果如下:

现在我们已经给格子中的圆心设置了随机的值,但是我们很容易得就发现了另一个问题:

在某些连线上,他们断开了。这是因为前面格子绘制的线被后面的格子绘制的线所覆盖了。这里有一个比较简单的解决方法:

把这些断开的线重新绘制一次。

for(i = 0; i < 9; i++) {m += Line(st, p[i], p[4]);
}

m += Line(st, p[1], p[3]);
m += Line(st, p[1], p[5]);
m += Line(st, p[7], p[3]);
m += Line(st, p[7], p[5]); 

结果如下:

渐隐渐显连线

我们可以看到每个点都与它附近的8个点完成了连线。现在我们需要做的就是根据连线的长度来设置线的透明度。我们需要修改一下 Line 函数。

float Line(vec2 p, vec2 a, vec2 b) {float d = DistLine(p, a, b);float m = smoothstep(0.02, 0.01, d);float d2 = length(a - b);m *= smoothstep(1.2, 0.8, d2);return m;
} 

在上面的函数中,我们计算了线段两点的距离,如果他们之间的距离大于1.2,则隐藏,小于0.8则显示,在0.8~1.2的区间范围则在其间进行插值,得到的结果如下:

闪烁光点

连线基本完成,现在我们需要让我们的“星星”开始进行闪烁,发出blingbling的亮光!我们修改连线处的for 循环代码。

for(i = 0; i < 9; i++) {m += Line(st, p[i], p[4]);float d = length(st - p[i]);float spark = 1.0 / (d * d * 200.0);m += spark;
} 

上面的1.0 / (d * d* 200.0) 是因为我们希望我们的闪光点产生一些“辉光”的效果,所以使用了形如 1x2\frac{1}{x^2}x21​ 的函数,在靠近中心的位置很亮,然后远离中心时亮度会迅速的减弱,分母上的200是控制衰减快慢的系数。值越大则衰减的越快。

我们可以得到以下的效果:

嗯~~~ 看起来相当的不错。我们再利用一个sin函数让他们闪烁起来!

 float spark = 1.0 / (d * d * 200.0) * (sin(t * 13.0 + p[i].x * 17.0) * 0.4 + 0.6); 

此时我们的“星星”应该都闪烁起来了。我们可以把我们的格子和id 都关闭看一下效果。如下:

效果看起来很不错。到目前为止,我们的工作已经进行了一大半了。胜利就在前方了!勇士们继续加油!

星域绘制

我们把刚刚的代码封装一下,命名为Layer,正如函数名一样,它表示的是“一层”的星星绘制,我们多绘制几层,就可以得到不可思议的效果!

float Layer(vec2 uv) {vec2 st = fract(uv) - 0.5;vec2 id = floor(uv);float m = 0.0;int i = 0;vec2 p[9];for(float y = -1.0; y <= 1.0; y++) {for(float x = -1.0; x <= 1.0; x++) {vec2 offs = vec2(x, y);p[i++] = GetPos(id, offs);}}float t = iTime;for(i = 0; i < 9; i++) {m += Line(st, p[i], p[4]);float d = length(st - p[i]);float spark = 1.0 / (d * d * 200.0) * (sin(t * 13.0 + p[i].x * 17.0) * 0.4 + 0.6);m += spark;}m += Line(st, p[1], p[3]);m += Line(st, p[1], p[5]);m += Line(st, p[7], p[3]);m += Line(st, p[7], p[5]);return m;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {vec2 uv = (fragCoord.xy - .5 * iResolution.xy) / iResolution.y;vec3 col = vec3(0);uv *= 5.0;col += Layer(uv);fragColor = vec4(col, 1.0);
} 

我们再次利用for循环来多绘制几层

float t = iTime * 0.1;
for(float i = 0.0; i < 1.0; i += 1. / 4.) {float depth = fract(i + t);float size = mix(10.0, 0.5, depth);col += Layer(uv * size + i);
} 

此处我们引入了另一个变量depth来表示每层的深度,深度值越大(离屏幕越近)则size越小,反之则越大。效果如下:

唔,现在看起来是相当的不错!不过我们还可以添加一些细节。现在每层星星的出现和消失看起来都有一些的突兀,所以我们可以根据当前层的深度值来增加一些淡入和淡出。

现在的效果看起来是真的真的很不错!!!

最后我们稍加润色,可以给星星加上一些变化的颜色。再加上一个渐变的背景色。

此处给出最终的代码:

总结

今天的代码总算是完结了,这算是我们经过之前的学习可以完成的“大作业”了吧。只需要短短的不到100行代码就绘制出了如此迷人的场景,这正是shader的迷人之处。我们回顾一下其中涉及到的技巧:

1.uv坐标归一化
2.格子分割(id)
3.随机数
4.线的绘制
5.利用淡入淡出来润色

以上就是今天的全部内容了,希望各位能够多加练习,尽早达到熟练的程度。如果你觉得本文很不错,别忘了点赞哦~

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/161396.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Dubbo泛化调用

Dubbo泛化调用 1. 场景 场景一&#xff1a;我们要搭建一个统一的测试平台&#xff0c;可以让各个业务方在测试平台中通过输入接口、分组名、方法名以及参数值&#xff0c;在线测试自己发布的 RPC 服务。这时我们就有一个问题要解决&#xff0c;我们搭建统一的测试平台实际上是…

【论文速递】CVPR2022 - 泛化的小样本语义分割

【论文速递】CVPR2022 - 泛化的小样本语义分割 【论文原文】&#xff1a;Generalized Few-shot Semantic Segmentation 获取地址&#xff1a;https://openaccess.thecvf.com/content/CVPR2022/papers/Tian_Generalized_Few-Shot_Semantic_Segmentation_CVPR_2022_paper.pdf博…

【ROS2 入门】Jeston TX1 JetPack_4.6.3环境 ubuntu 18.04 ROS2 安装

大家好&#xff0c;我是虎哥&#xff0c;从今天开始&#xff0c;我将花一段时间&#xff0c;开始将自己从ROS1切换到ROS2&#xff0c;在上一篇中&#xff0c;我们再虚拟机环境中安装了 ROS2 eloquent版本&#xff0c;并完成了初步的验证&#xff0c;但是做为一个偏硬件的博主&a…

nginx禁止外网访问

1、安装 libmaxminddb 库 apt updateapt install libmaxminddb0 libmaxminddb-dev mmdb-bin上面安装的软件包是&#xff1a; libmaxminddb0 libmaxminddb-dev 是MaxMind地理定位数据库mmdb-bin – 二进制。 从命令行调用的程序。 使用此命令手动定位 IP 安装参考 2、下载geoi…

node学习笔记

阶段一 1 初始Node.js javascript 运行环境 1.2 Node.js中的javacript 运行环境 1.3 Node.js环境安装 百度 1.4 node.js 执行javaScript 代码 2 fs文件系统模块 2.1 fs文件系统模块概念 导入文件系统模块&#xff1a; const fs require(fs)fs.readFile() // 1 导入fs文件…

【进阶C语言】通讯录(后期会升级)

文章目录一.基本框架与功能二.头文件的详细内容三.函数的实现1.打印菜单2.初始化通讯录3.添加联系人信息4.打印联系人信息5.查找名字6.删除联系人信息7.查找联系人8.修改联系人信息9.排序联系人&#xff08;按照名字&#xff09;四.总结1.test.c2.contact.c3.contact.h一.基本框…

智能电风扇(stm32f103c8t6)(直流电机,热敏传感器)(TIM,ADC)

前言 我的毕业论文的课题 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、热敏传感器计算温度&#xff08;ADC采样单通道&#xff09; #include "stm32f10x.h" // Device header#define T25 298.15 #define B 3…

看完这篇 教你玩转渗透测试靶机vulnhub——MONEYBOX: 1

Vulnhub靶机MONEYBOX: 1渗透测试详解Vulnhub靶机介绍&#xff1a;Vulnhub靶机下载&#xff1a;Vulnhub靶机安装&#xff1a;Vulnhub靶机漏洞详解&#xff1a;①&#xff1a;信息收集&#xff1a;②&#xff1a;FTP匿名登入&#xff1a;③&#xff1a;SSH暴力破解④&#xff1a;…

Java操作Word模板产生全新内容Word

1. spire.doc的jar引用 首先我们需要用到国产word处理工具jar包spire.doc&#xff0c;可以通过maven仓库寻找&#xff0c;然后在pom文件中直接引用。 此处需要注意&#xff0c;我们需要使用的是spire.doc.free&#xff08;免费版的&#xff09;&#xff0c;切勿使用spire.doc&a…

c++ - 第23节 - C++的类型转换

1.C语言中的类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&#xff0c;或者返回值类型与接收返回值类型不一致时&#xff0c;就需要发生类型转化&#xff0c;C语言中总共有两种形式的类型转换&#xff1a;隐式类型转换和…

业务安全情报 | 数十万元的数据报告,竟被50元批量转售

近期监测到某咨询公司针数据报告大量泄漏事件&#xff0c;该机构历年的数据报告以及近期更新的针对VIP会员的付费报告均在电商等渠道可以低价获取。 BSL-2022-a3c28号情报文件显示黑灰产通过作弊方式获取查看、下载权限&#xff0c;绕过限制将报告数据大量下载&#xff0c;并通…

javaEE初阶 — 认识文件

文章目录认识文件1. 树型结构组织和目录2. 文件路径&#xff08;Path&#xff09;2.1 绝对路径2.2 相对路径3. 文件的类型认识文件 文件分为 狭义 和 广义 两种 狭义的文件&#xff1a;指的是硬盘上的 文件 和 目录 广义的文件&#xff1a; 泛指计算机中很多的软硬件资源。操…

2022(一等奖)D926刘家峡库区潜在滑坡InSAR识别与分析

作品介绍 1 应用背景 滑坡是普遍存在于世界各地山区的主要灾害之一&#xff0c;严重威胁着人类的生命财产安全和自然环境。滑坡不但会直接破坏人类生命财产安全和建筑物&#xff0c;而且还会造成堰塞湖等次生灾害&#xff0c;进而对人类的生命财产安全和基础设施等造成二次破坏…

暴力破解 SSH

Kali 的 MSF 终端&#xff0c;对渗透目标主机 的 SSH 服务进行暴力破解。 破解的是否成功取决于字典和目标是否使用弱密码。 一&#xff0c;实验环境 分别是攻击机和靶机&#xff08;也可是其他目标服务器&#xff09; 二、利用 SSH 弱密码进行暴力破解 暴力破解最重要的要…

100 道 Linux 笔试题,能拿 80 分就算大神!

本套笔试题共100题&#xff0c;每题1分&#xff0c;共100分。&#xff08;参考答案在文章末尾&#xff09; 1.cron 后台常驻程序 (daemon) 用于&#xff1a; A. 负责文件在网络中的共享 B. 管理打印子系统 C. 跟踪管理系统信息和错误 D. 管理系统日常任务的调度 2.在大多数Li…

如何使用无标签数据进行预训练?

一、直观解释 简单来说就是“造目标”。也即人为地去构造一些子任务&#xff08;论文里的protext task&#xff09;&#xff0c;或者利用一些最基本的公理性常识&#xff0c;去设计“类似监督”的任务。所不同的是&#xff0c;我们引入的“类似监督”的任务通常是用来完成表征…

day13|559.n叉树的最大深度、222.完全二叉树的节点个数

559.n叉树的最大深度 给定一个 N 叉树&#xff0c;找到其最大深度。最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。N 叉树输入按层序遍历序列化表示&#xff0c;每组子节点由空值分隔&#xff08;请参见示例&#xff09;。 示例 1&#xff1a; 输入&#xff1a;r…

基于opencv的图像:边缘检测 (完整代码+详细教程)

给出“离散拉普拉斯算子”一般形式的数学推导 离散值的导数使用差分代替: 所以: 以(x, y)为中心点,在水平和垂直方向上应用拉普拉斯算子,滤波器(对应a=1的情况)为:

广告归因-让你彻底弄归因架构实现

这里会引用神策数据很多的介绍&#xff0c;然后进行总结 归因方法 自归因 渠道商帮我们做归因&#xff0c;有的是每个用户打开 app 都回传给渠道商&#xff0c;渠道商自己归因有的如华为是从应用商店安装时&#xff0c;应用商店把归因信息写入到 app, 然后首次安装启动时能从本…

【信息学CSP-J近16年历年真题64题】真题练习与解析 第12题之加工零件

加工零件 描述 凯凯的工厂正在有条不紊地生产一种神奇的零件,神奇的零件的生产过程自然也很神奇。工厂里有 𝑛 位工人,工人们从 1~𝑛编号。某些工人之间存在双向的零件传送带。保证每两名工人之间最多只存在一条传送带。 如果 𝑥 号工人想生产一个被加工到第 𝐿(…