OpenGL ES特效分析 --- 跳动的心

news2024/12/28 17:39:59

很早之前就见过一个博主发的shader图片,一个跳动的心https://blog.csdn.net/Kennethdroid/article/details/104536532, 感觉太好玩了,于是想要分析一下原理,上面的博主也已经做了初步分析,但是对于我这个特效小白来说还是太难,于是就更详细的分析了一遍。
请添加图片描述

把博主对应的代码copy下来,然后一句一句的分析,全部代码如下:

#version 300 es
precision highp float;
layout(location = 0) out vec4 outColor;//输出
uniform float u_time;//时间偏移量
uniform vec2 u_screenSize;//屏幕尺寸
const float PI = 3.141592653;
void main()
{
	// move to center
	vec2 fragCoord = gl_FragCoord.xy;
	vec2 p = (2.0*fragCoord-u_screenSize.xy)/min(u_screenSize.y,u_screenSize.x);
	
	// background color
	vec3 bcol = vec3(1.0,0.8,0.8)*(1.0-0.38*length(p));
	
	// animate
	float tt = u_time;
	float ss = pow(tt,.2)*0.5 + 0.5;
	ss = 1.0 + ss*0.5*sin(tt*6.2831*3.0 + p.y*0.5)*exp(-tt*4.0);
	p *= vec2(0.5,1.5) + ss*vec2(0.5,-0.5);
	
	// shape
	p.y -= 0.25;
	float a = atan(p.x,p.y) / PI;
	float r = length(p);
	float h = abs(a);
	float d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.0-5.0*h);
	
	// color
	float s = 0.75 + 0.75*p.x;
	s *= 1.0-0.4*r;
	s = 0.3 + 0.7*s;
	s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0, 1.0 ), 0.1 );
	vec3 hcol = vec3(1.0,0.5*r,0.3)*s;
	
	vec3 col = mix( bcol, hcol, smoothstep( -0.06, 0.06, d-r) );
	
	outColor = vec4(col,1.0);
}

第一步: 分析各个部分代码的功能

第一部分 坐标偏移

// move to center
vec2 fragCoord = gl_FragCoord.xy;
vec2 p = (2.0 * fragCoord - u_screenSize.xy) / min(u_screenSize.y,u_screenSize.x);

gl_FragCoord是OpenGL内置的一个变量,表示当前片元相对于窗口所处位置的坐标,只能在Fragment Shader中使用,是vec4类型。详细的介绍请看官网,不过如果是第一次使用的话,不用管这么多的概念,我们这里仅仅是在2D里面使用,可以把gl_FragCoord.xy看成光栅化后的对应实际像素点,大小和glViewport的设置相关。对应的窗口坐标系坐标原点为左下角,左边为X轴正方向,上边为Y轴正方向。

上面这段代码是先把这个窗口坐标系经过平移u_screenSize.xy,然后缩放 2 / min(u_screenSize.y,u_screenSize.x)。
坐标p就是gl_FragCoord.xy在新坐标系统的映射。

第二部分 背景颜色

// background color
vec3 bcol = vec3(1.0,0.8,0.8)*(1.0-0.38*length(p));

设置整个Shader的背景,这里有风险 1.0-0.38*length(p)可能 < 0,导致颜色绘制不出来,所以应该先判断一下。

第三部分 跳动功能

// animate
float tt = u_time;
float ss = pow(tt,.2)*0.5 + 0.5;
ss = 1.0 + ss*0.5*sin(tt*6.2831*3.0 + p.y*0.5)*exp(-tt*4.0);
p *= vec2(0.5,1.5) + ss*vec2(0.5,-0.5);

根据时间再次变换p的坐标,让心跳起来。

第四部分 心形图案

p.y -= 0.25;
float a = atan(p.x,p.y) / PI;
float r = length(p);
float h = abs(a);
float d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.0-5.0*h);

这个部分最关键,绘制心形的图案,其中a,r属于关键信息,h是a的绝对值,d是为了将心扁平化处理进行的进一步操作。

第五部分 心形颜色

float s = 0.75 + 0.75*p.x;
s *= 1.0-0.4*r;
s = 0.3 + 0.7*s;
s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0, 1.0 ), 0.1 );
vec3 hcol = vec3(1.0,0.5*r,0.3)*s;

这里会根据上面计算出来的r 和d,将中间心形的颜色和其余的地方的颜色进行分割,关键函数是OpenGL的内置函数clamp,相当于 min(max(r/d, 0.0), 1.0)。

第六部分 合并颜色

vec3 col = mix( bcol, hcol, smoothstep( -0.06, 0.06, d-r) );

将背景色和心的颜色合并,关键函数smoothstep,d-r在[-0.06, 0.06]之间平滑过度,具体效果可以参考https://zhuanlan.zhihu.com/p/157758600。

第二步:拆分核心功能和次要功能,分析核心功能

可以看出,这个shader通过拆分成六部分,生成了最终的颜色,其中有很多优化的地方,但是最核心的只有两个部分,心形图案 和 心形颜色,所以我们主要分析这两个部分,此外为了正常显示,我们也需要把坐标从左下角移动到屏幕中心,所以也保留第一部分,整体变更为

void main()
{
// move to center
// 第一部分,移动坐标
vec2 fragCoord = gl_FragCoord.xy;
vec2 p = (2.0*fragCoord-u_screenSize.xy)/min(u_screenSize.y,u_screenSize.x);

// shape
// 第四部分,确定心形
p.y -= 0.25;
float a = atan(p.x,p.y) / PI;
float r = length(p);
float h = abs(a);
//float d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.0-5.0*h);
float d = h;//先去掉扁平化处理,先理解整体实现在搞细节

// color
// 第五部分,心形颜色
float s = 0.75 + 0.75; //颜色部分也去掉花里胡哨
//float s = 0.75 + 0.75*p.x;
//s *= 1.0-0.4*r; //颜色部分也去掉花里胡哨
//s = 0.3 + 0.7*s; //颜色部分也去掉花里胡哨
s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0, 1.0 ), 0.1 );
vec3 hcol = vec3(1.0,0.5*r,0.3)*s;

outColor = vec4(hcol,1.0);
}

效果如下:
请添加图片描述
下面详细分析一下怎么产生的这种效果

vec2 fragCoord = gl_FragCoord.xy;
vec2 p = (2.0*fragCoord-u_screenSize.xy)/min(u_screenSize.y,u_screenSize.x);

// shape
float a = atan(p.x,p.y) / PI;
float r = length(p);
float h = abs(a);
float d = h;

// color
float s = 0.75 + 0.75; //颜色部分也去掉花里胡哨
s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0, 1.0 ), 0.1 );
vec3 hcol = vec3(1.0,0.5*r,0.3)*s;

先看颜色部分,代码如下,看似花里胡哨,其实总体而言可以理解为
基础颜色 * clamp(r/d, 0.0, 1.0)

float s = 0.75 + 0.75; //颜色部分也去掉花里胡哨
s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0, 1.0), 0.1 );
vec3 hcol = vec3(1.0,0.5*r,0.3)*s;

所以其中最关键的clamp(r/d, 0.0, 1.0)
r和d的具体数学公式

float a = atan(p.x,p.y) / PI;
float r = length(p);
float h = abs(a);
float d = h;

整体结合一下,将clamp(r/d, 0.0, 1.0)转换为数学坐标系,整个公式可以表示为:
在这里插入图片描述
这里需要注意的一点是 这里的arctan和数学上的arctan不是很一致,一般来说数学上的arctan会返回[−π/2,π/2],但是这里的是返回 [−π,π],详情请参考https://registry.khronos.org/OpenGL-Refpages/es3.0/html/atan.xhtml,它会根据(x, y) (其实这里是(y, x))点所在的坐标系,得到具体的角度。于是乎,用desmos模拟一下,得到如下图:
在这里插入图片描述
哎呀妈呀,和实际输出的一样,真的是个心形。
至于为什么有两个,是因为我们用的arctan并不会输出到[−π,π],而是[−π/2,π/2],所以为了模拟,有主动加了一个π,然后它里面应该还有一个限制条件,比如实际返回在[−π/2,π/2]之间的时候,y应该大于0,这些我没有在desmos上设置(因为不会~),所以导致desmos上是两个,但是绘制的时候肯定是一个。
这样就解释了如何渲染出一个心形…完美。

然后再在看看颜色:

float s = 0.75 + 0.75; //颜色部分也去掉花里胡哨
s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0, 1.0), 0.1 );
vec3 hcol = vec3(1.0,0.5*r,0.3)*s;

可以发现,它实际是使用pow( 1.0-clamp(r/d, 0.0, 1.0), 0.1 )来利用clamp的结果的,我们用desmos看一下x的0.1次方的图案, 它在靠近0的时候,会有一个突变,所以转换到pow( 1.0-clamp(r/d, 0.0, 1.0), 0.1 )中,在clamp快等于1的时候,会发生一个突变,这样就很好的分隔了心形颜色和心形之外的颜色。
在这里插入图片描述
核心的功能就全部分析完了。

第三步:优化效果 添加功能

对比一下预期,我们想要心的上面更巧,下部分更尖,让心更好看,下面蓝线就是我们想要的大概效果。
请添加图片描述
然后看他shape部分的具体实现,如下。对比上面的核心实现,修改了d的实现,变得更加扁平化。

float a = atan(p.x,p.y) / PI;
float r = length(p);
float h = abs(a);
float d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.0-5.0*h);

为了详细了解这个变化,我们继续用desmos看一下变化:
在这里插入图片描述
可以看到,对比单纯的x线,扁平化的线最开始会更大,之后会更小,这样就可以让a(角度)最开始弧度变的更大,到结尾处变得更尖,这块不知道作者是怎么想出来的公式,也不清楚如何用数学公式表达,有知道同学欢迎评论区交流。
请添加图片描述
然后添加背景颜色,就是上面的第二部分和第六部分

//背景颜色
vec3 bcol = vec3(1.0,0.8,0.8)*(1.0-0.38*length(p));

······
//合并颜色
vec3 col = mix( bcol, hcol, smoothstep( -0.06, 0.06, d-r) );

背景颜色距离中心越远,越像黑色。
合并的时候,用smoothstep对画面效果实现从0 到1 的平滑过度,这样在心与背景交界处,就会有一层蒙版似得东西,让边界不在分明。

动画效果就不分析了,相当于改坐标。

整体分析下来,只能说 牛逼 ,编程的尽头果然是数学,完全不知道这是怎么想出来的。

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

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

相关文章

VMware Aria Operations 8.12 - 自动驾驶式 IT 运维管理

VMware Aria Operations 8.12 - 自动驾驶式 IT 运维管理 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-aria-operations/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 自动驾驶式 IT 运维管理 VMware Aria Op…

YOLOv5白皮书-第Y3周:yolov5s.yaml文件解读

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客 &#x1f356; 原作者&#xff1a;K同学啊|接辅导、项目定制 ● 难度&#xff1a;夯实基础⭐⭐ ● 语言&#xff1a;Python3、Pytorch3 ● 时间&#xff1a;5月8日-5月12日 &#x1f37a;要求&#xff…

电脑永久删除文件怎么找回来?分享一种数据恢复方法

电脑对于日常生活和工作都起着重要作用&#xff0c;但是在我们日常办公中&#xff0c;有时会误删除一些文档&#xff0c;甚至永久删除&#xff0c;当我们想找回的时候却手足无措&#xff0c;不知道该怎么办。同时在很多用户的观念里&#xff0c;当我们无法从回收站恢复时&#…

Openai+Coursera: ChatGPT Prompt Engineering(一)

想和大家分享一下最近学习的Coursera和openai联合打造ChatGPT Prompt Engineering在线课程&#xff0c;下面是通过API来访问ChatGPT的主要代码&#xff1a; import openaiopenai.api_key XXXXXXXXXdef get_completion(prompt, model"gpt-3.5-turbo"):messages [{&…

【OpenCV】学习课-图像裁剪与拼接(2)!

1. 图像的裁剪 cv2.selectROI() ###可以通过鼠标选择感兴趣的矩形区域&#xff08;ROI&#xff09; import cv2img cv2.imread("xxx.png", flags1) # flags1 读取彩色图像(BGR)roi cv2.selectROI(img, showCrosshairTrue, fromCenterFalse) xmin, ymin, w, h…

2022年平均工资出炉,IT行业又是第一

根据5月9日国家统计局最新资料显示&#xff0c;2022年&#xff0c;全国城镇非私营单位就业人员年平均工资为114029元&#xff0c;比上年增长6.7%&#xff0c;扣除通胀后实际增长4.6%。其中&#xff0c;行业间的差距相当明显。根据资料显示&#xff0c;2022年无论是在私营单位还…

ESP8266(1):搭建Linux环境ESP8266_RTOS_SDK,ESP8266使用GPIO控制继电器

0&#xff09;准备工作&#xff0c;之前一直对esp8266不了解&#xff0c;现在想着给鱼缸加个定时打氧的程序&#xff0c;控制泵的工作。所以购买了一个esp8266 Relay&#xff0c;自己摸索写个简单程序。让泵工作一段时间&#xff0c;再休眠一段时间。 1)宿主机Ubuntu 20.04.5 …

Vue 项目利用 HBuilderX 打包 APP 流程

想要将 Vue 打包成 App&#xff0c;要借助一些插件工具&#xff0c;例如&#xff1a;Electron、Cordova 等&#xff0c;或者直接利用开发工具&#xff0c;例如&#xff1a;Android Studio、HBuilderX 等。本文的目的是带大家通过 HBuilder 开发工具对 Vue 项目进行打包。 一、…

MySQL-函数

1.什么是函数 函数在计算机语言的使用中贯穿始终&#xff0c;函数的作用是什么呢&#xff1f;它可以把我们经常使用的代码封装起来&#xff0c; 需要的时候直接调用即可。这样既 提高了代码效率 &#xff0c;又 提高了可维护性 。在 SQL 中我们也可以使用函数 对检索出来的数据…

7.设计模式之责任链模式

前言 责任链&#xff0c;即将能够处理同一类请求的对象连成一条链&#xff0c;所提交的请求沿着链传递&#xff0c; 链上的对象逐个判断是否有能力处理该请求&#xff0c;如果能则处理&#xff0c;如果不能则传递给链上的下一个对象。为了避免请求发送者与多个请求处理者耦合在…

git如何将本地分支推送到远程(远程上没有该分支)

Author: wencoo Blog&#xff1a;https://wencoo.blog.csdn.net/ Date: 12/05/2023 Details:文章目录 正文 或 背景7.如果远程新建了一个分支&#xff0c;本地没有该分支。8.如果本地新建了一个分支 branch_name&#xff0c;但是在远程没有。报错解决 参考打赏 正文 或 背景 本…

Java8之Stream操作

Java8之Stream操作 stream干啥用的&#xff1f;创建流中间操作终结操作好文推荐----接口优化思想 stream干啥用的&#xff1f; Stream 就是操作数据用的。使用起来很方便 创建流 → 中间操作 → 终结操作 Stream的操作可以分为两大类&#xff1a;中间操作、终结操作 中间操作可…

【项目源码】采用UWB技术开发的定位系统源码,室内定位系统源码

UWB技术定位系统源码&#xff0c;高精度人员定位系统源码&#xff0c;智慧工厂人员定位系统源码&#xff0c;室内定位系统源码 技术架构&#xff1a;单体服务 硬件&#xff08;UWB定位基站、卡牌&#xff09; 开发语言&#xff1a;java 开发工具&#xff1a;idea 、VS Code…

字节小组长薪资被应届生员工倒挂7K,不把老员工当人?

一位字节跳动的小管理爆出&#xff0c;无意中看到了整个部门薪资&#xff0c;本以为自己算比较高的&#xff0c;但看完之后整个人都傻眼了。小组长的职位月薪28K&#xff0c;而手下组员却是35K&#xff0c;当天晚上抽了一包烟也没想明白是为什么。 楼主表示&#xff0c;自己是字…

基于java的闲置物品交易系统的设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据你想解决的问题&#xff0c;今天给…

YOLOv5改进系列(4)——添加ECA注意力机制

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制 YOLOv5改进系列(2)——添加CBAM注意力机制

T265录制的rosbag拆包:拆IMU序列和图像序列方法以及如何制作双目euroc、双目tum数据集

目录 1.录制bag包 2.左右目图像的拆解 3.拆IMU数据 4.如何制作eruoc与tum数据集 4.1 eruoc数据集格式 4.2 对齐时间戳 4.3 编写imu.csv文件 4.4 生成索引文件 4.一个脚本完成拆包 1.录制bag包 这里推荐我的同学的博客&#xff0c;大家可以参考这篇博客录制T265的ros包并…

移动互联网市场是不需要Android 开发了吗?

就近有不少朋友在吐槽&#xff1a;如今Android工作真的难找&#xff01;从年初找到了现在&#xff0c;是市场不需要Android 开发人才了&#xff0c;人都迷茫了&#xff0c;是不是应该考虑转行了&#xff1f; 其实这种情况往年也是有的&#xff0c;但是今年显得的更严重一些。我…

【el-select】多选的简单用法

即使element用了很久&#xff0c;还是会有新的东西没用过 el-select的多选和日期组件有范围的el-date-picker很相似&#xff0c;都是绑定数组&#xff0c;然后给需要的字段再赋值 加上multiple属性即可&#xff0c;collapse-tags看自己需不需要 这个时候v-model绑定的是数组 …

【C++初阶】C++模版(初阶)

文章目录 前言泛型编程函数模版函数模板概念函数模板格式函数模板的原理函数模板的实例化1.隐式实例化2.显式实例化 函数模版的匹配规则 类模版类模板的定义格式类模板的实例化 总结 前言 C的模版也是相较于C语言更有优势的地方&#xff0c;正是有了模版&#xff0c;才让C真正…