http://blog.csdn.net/candycat1992/article/details/44040273

写在前面

注:如果你还不了解ShaderToy,请看开篇。

作为ShaderToy系列的第一篇,我们先来点简单的。下面是效果:

(CSDN目前不能传gif文件了,暂时空缺,可以看下面的原shader效果,是一样的)

原Shader地址:https://www.shadertoy.com/view/XsfGRn

代码

我们使用了之前的开篇中的基础模板。这里仅仅给出main函数的代码:

[cpp] view plaincopyprint?
  1. vec4 main(vec2 fragCoord) {
  2. vec2 p = (2.0*fragCoord.xy-iResolution.xy)/min(iResolution.y,iResolution.x);
  3. p.y -= 0.25;
  4. // background color
  5. vec3 bcol = vec3(1.0,0.8,0.7-0.07*p.y)*(1.0-0.25*length(p));
  6. // animate
  7. float tt = mod(iGlobalTime,1.5)/1.5;
  8. float ss = pow(tt,.2)*0.5 + 0.5;
  9. ss = 1.0 + ss*0.5*sin(tt*6.2831*3.0 + p.y*0.5)*exp(-tt*4.0);
  10. p *= vec2(0.5,1.5) + ss*vec2(0.5,-0.5);
  11. // shape
  12. float a = atan(p.x,p.y)/3.141593;
  13. float r = length(p);
  14. float h = abs(a);
  15. float d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.0-5.0*h);
  16. // color
  17. float s = 1.0-0.5*clamp(r/d,0.0,1.0);
  18. s = 0.75 + 0.75*p.x;
  19. s *= 1.0-0.25*r;
  20. s = 0.5 + 0.6*s;
  21. s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0, 1.0 ), 0.1 );
  22. vec3 hcol = vec3(1.0,0.5*r,0.3)*s;
  23. vec3 col = mix( bcol, hcol, smoothstep( -0.01, 0.01, d-r) );
  24. return vec4(col,1.0);
  25. }

代码很短,但包含了一些数学公式的计算,不懂的就会觉得很难理解。我们把上面代码拆分成两个部分:背景颜色(bcol),心的颜色(hcol,包含了心的跳动):

 

背景颜色

这部分比较简单。这个背景实际上是一个某点为中心,中心最亮、向边缘逐渐变暗的背景,比较常见。但通常我们都是用美术给的纹理直接用,现在我们用纯数学计算来看看如何实现它!

首先,我们在看一个重要变量的计算——p。

[cpp] view plaincopyprint?
  1. vec2 p = (2.0*fragCoord.xy-iResolution.xy)/min(iResolution.y,iResolution.x);
  2. .y -= 0.25;

第一行中,代码计算了每一个像素点和屏幕中心点之间的方向向量。我们以图为例说明。

在p.y减去0.25之前,中心点即是屏幕中心,代码先计算了每个像素点到中心的方向(如左图),然后再除以屏幕的高度(或宽度)(如右图)。图例如下:

 

这样的计算结果可以保存每个像素点距离中心的方向、远近等信息。

在这个基础上,我们还可以移动中心点的位置来控制渐变。代码中是将y减去了0.25,即将中心点向上移动了屏幕高度的0.25/2=0.125个单位。

 

我们可以通过更改代码来看到这样的效果:

[cpp] view plaincopyprint?
  1. vec3 bcol = vec3(1.0,0.8,0.7-0.07*p.y)*(1.0-length(p));
[cpp] view plaincopyprint?
  1. return vec4(bcol,1.0);
 
 

以上步骤即可得到屏幕上每一个点到中心点的方向、相对距离等信息。接下来,我们就可以根据这些信息计算背景颜色了:

[cpp] view plaincopyprint?
  1. // background color
  2. vec3 bcol = vec3(1.0,0.8,0.7-0.07*p.y)*(1.0-0.25*length(p));

bcol可以认为是一个主背景颜色和像素距离中心点远近值的乘积。其中, (1.0-0.25*length(p)) 是我们对上一步得到的距离进行进一步计算,由于 length(p) 的值可能大于1(如果屏幕x轴大于y轴,那么p的x方向的绝对值有些会大于1;反之,如果屏幕y轴大于x轴,那么p的y方向的绝对值有些会大于1),因此,我们需要在 length ( p ) 的基础上乘以系数0.25。当然,0.25是经验值,我们可以把这个参数做成shader的一个属性,供调节。 vec3(1.0,0.8,0.7-0.07*p.y) 中, vec3 ( 1.0 , 0.8 , 0.7) 是背景主色调,我们同样可以把它做成一个shader属性。后面会给出。而在B通道上减去 0.07 * p . y 可以当成是一个轻微的效果修正,即在y方向上,可以有些许颜色变化,其中0.07也是一个经验值,可以更改。

心的绘制

心形的计算代码很简单。这里画心的原理是,判断该像素点是否在心的内部,如果在就是用心的颜色绘制,否则使用背景颜色绘制:

[cpp] view plaincopyprint?
  1. vec3 col = mix( bcol, hcol, smoothstep( -0.01, 0.01, d-r) );

其中我们已经讲过了bcol的计算。这里我们先忽略hcol,后面再讲,我们这里可以把它当成是一个已知的心的颜色。 smoothstep( -0.01, 0.01, d-r) 决定了我们是使用背景颜色还是心的颜色。当它值为0时,表示该像素不在心形内,则使用背景颜色,返回1时,则使用心的颜色;否则,就在背景颜色和心的颜色之间进行插值。而 smoothstep( -0.01, 0.01, d-r) 的返回值范围就是[0, 1]。 smoothstep 是CG函数,当第三个参数比-0.01小时,返回0,比0.01大时返回1,如果在-0.01和0.01之间,则返回0到1之间的值。这里, d-r 其实已经表明该像素是否在心形内(后面会讲原因):若 d-r  > 0,则在心形内,若 d-r  < 0,则在心形外。那么为什么这里要使用smoothstep呢?其实是为了使心形的边缘模糊化,更美观。下面左图是模糊后的结果,而右图是没有模糊的结果:

 

smoothstep控制模糊效果的原理在于,在心形的边界部分,d-r的值在正负0左右波动,我们可以通过添加一个[-0.01, 0.01]范围内的平缓过渡,来平缓d-r的值在正负交界处的突变。当然,我们可以更改0.01的值,来控制模糊范围。例如,如果我们改成0.05,那么效果如下:

重要:mix+smoothstep的组合,是实现这种模糊效果的很常见的搭配!

心的形状

下面,我们来解释,为什么d-r表示该像素是否在心形内。相关代码如下:

[cpp] view plaincopyprint?
  1. // shape
  2. float a = atan(p.x,p.y)/3.141593;
  3. float r = length(p);
  4. float h = abs(a);
  5. float d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.0-5.0*h);

我们先来看a的含义。它是该像素对应方向的反正切值和π相除的结果。 atan 对应CG中的 atan2 函数,它的值域为[-π, π]。我们可以理解为,它返回的是X轴旋转到该方向所需要转过的角度(不超过正负180°)。因此,a得到的其实是一个范围在[-1.0, 1.0]之间的浮点值。这里注意计算反正切时x和y方向对调了,这是因为我们的心形是竖立着的。我们用图像来理解a的含义。下图中,每条直线上的像素点得到的a都是相同的,我们用黄点表示,黄点距离远点的远近,表示了a的大小。我只画了一侧是因为另一侧的a值为负。

这样,我们就可以通过判断该像素对应方向的长度r和a的差来判断该像素是否在心形内了。如果我们以a-r为依据而非d-r,我们会得到下面的结果:

可以看出,心形只有一侧,而且看起来比最终效果要胖了许多。这就是h和d的意义。h对a取绝对值,使得a的负值区域同样可以得到半个心形。而d对h进行修正,使用的函数表达式可以根据代码看出来,至于为什么是这个函数,大家都膜拜数学的魅力好啦。。。

心的颜色

心的颜色这里有点难理解。我们把它拆成两部分:

[cpp] view plaincopyprint?
  1. vec3 hcol = vec3(1.0,0.5*r,0.3)*s;

一部分是 vec3(1.0,0.5*r,0.3) ,一部分是 s 。 vec3 ( 1.0 , 0.5 * r , 0.3 ) 完成的是一个简单的由中心向外渐变的颜色:

这个渐变色没有考虑心形的约束。而s的计算则相对复杂。它用于修正上面的渐变颜色,使得在心形内外的颜色有所区别。vec3 hcol = vec3(1.0,0.5,0.3)*s;的结果如下:

s的计算如下:

[cpp] view plaincopyprint?
  1. // color
  2. float s = 1.0-0.5*clamp(r/d,0.0,1.0);
  3. s = 0.75 + 0.75*p.x;
  4. s *= 1.0-0.25*r;
  5. s = 0.5 + 0.6*s;
  6. s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0, 1.0 ), 0.1 );

其实源码中新一行是没用的,因为第二行直接赋值了。我用图例来表示每一步的变化。我们先把所有的计算注释掉,然后依次取消注释来看看发生了什么变化。图片如下:

   

从图片我们可以直观的看出,第一行根据p的x方向来得到一个在x方向上的渐变,第二行在此基础上添加了根据p的距离来产生的渐变,虽然不明显但可以看出新的渐变有了弧形,第三行则是使用类似半兰伯特的方法,增亮了左侧暗部区域,而最后一行则关键的分出了心形内外的区域颜色。

重要:最后一步中,使用pow+clamp分割区域的方法也是我们值得借鉴的地方。

心的跳动

动画的部分肯定离不开数学函数图像的帮助。我们来看这里用到的计算:

[cpp] view plaincopyprint?
  1. // animate
  2. float tt = mod(iGlobalTime,1.5)/1.5;
  3. float ss = pow(tt,.2)*0.5 + 0.5;
  4. ss = 1.0 + ss*0.5*sin(tt*6.2831*3.0 + p.y*0.5)*exp(-tt*4.0);
  5. p *= vec2(0.5,1.5) + ss*vec2(0.5,-0.5);

第一行计算了周期时间tt,这里的周期是1.5秒,当然我们可以自己改动。

下面ss的计算就是利用函数图像来模拟动画效果。它的函数图像如图所示(这里去掉了p.y的影响部分):

如图,这个函数是一个在y轴上、数值1附近跳动的函数。

最后一行,p *= vec2(0.5,1.5) + ss*vec2(0.5,-0.5);,我们对x和y方向使用了的参数,是因为我们想要心是在水平方向上收缩、竖直方向上拉伸的动画效果,当然我们也可以修改它。

Shader增强版

我将一些参数设为shader的属性,以便在面板中调节。这些属性有:背景颜色,心的颜色,背景的离散洗漱,心的边缘模糊洗漱,以及跳动的时间周期。

[cpp] view plaincopyprint?
  1. Shader "shadertoy/Heart" {
  2. Properties {
  3. _BackgroundColor ("Background Color", Color) = (1.0, 0.8, 0.7, 1.0)
  4. _HeartColor ("Heart Color", Color) = (1.0, 0.5, 0.3, 1.0)
  5. _Eccentricity ("Eccentricity", Range(0, 0.5)) = 0.25
  6. _Blur ("Edge Blur", Range(0, 0.3)) = 0.01
  7. _Duration ("Duration", Range(0.5, 10.0)) = 1.5
  8. }
  9. CGINCLUDE
  10. #include "UnityCG.cginc"
  11. #pragma target 3.0
  12. #define vec2 float2
  13. #define vec3 float3
  14. #define vec4 float4
  15. #define mat2 float2x2
  16. #define iGlobalTime _Time.y
  17. #define mod fmod
  18. #define mix lerp
  19. #define atan atan2
  20. #define fract frac
  21. #define texture2D tex2D
  22. // 屏幕的尺寸
  23. #define iResolution _ScreenParams
  24. // 屏幕中的坐标,以pixel为单位
  25. #define gl_FragCoord ((_iParam.srcPos.xy/_iParam.srcPos.w)*_ScreenParams.xy)
  26. vec4 _BackgroundColor;
  27. vec4 _HeartColor;
  28. float _Eccentricity;
  29. float _Blur;
  30. float _Duration;
  31. struct vertOut {
  32. float4 pos : SV_POSITION;
  33. float4 srcPos : TEXCOORD0;
  34. };
  35. vertOut vert(appdata_base v) {
  36. vertOut o;
  37. o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
  38. o.srcPos = ComputeScreenPos(o.pos);
  39. return o;
  40. }
  41. vec4 main(vec2 fragCoord);
  42. fixed4 frag(vertOut _iParam) : COLOR0 {
  43. vec2 fragCoord = gl_FragCoord;
  44. return main(fragCoord);
  45. }
  46. vec4 main(vec2 fragCoord) {
  47. vec2 p = (2.0*fragCoord.xy-iResolution.xy)/min(iResolution.y,iResolution.x);
  48. p.y -= 0.25;
  49. // background color
  50. vec3 bcol = vec3(1.0,0.8,0.7-0.07*p.y)*(1.0-0.25*length(p));
  51. bcol = _BackgroundColor.xyz * (1.0-_Eccentricity*length(p));
  52. // animate
  53. float tt = mod(iGlobalTime,1.5)/1.5;
  54. tt = mod(iGlobalTime,_Duration)/_Duration;
  55. float ss = pow(tt,.2)*0.5 + 0.5;
  56. ss = 1.0 + ss*0.5*sin(tt*6.2831*3.0 + p.y*0.5)*exp(-tt*4.0);
  57. p *= vec2(0.5,1.5) + ss*vec2(0.5,-0.5);
  58. // shape
  59. float a = atan(p.x,p.y)/3.141593;
  60. float r = length(p);
  61. float h = abs(a);
  62. float d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.0-5.0*h);
  63. // color
  64. float s = 1.0-0.5*clamp(r/d,0.0,1.0);
  65. s = 0.75 + 0.75*p.x;
  66. s *= 1.0-0.25*r;
  67. s = 0.5 + 0.6*s;
  68. s *= 0.5+0.5*pow( 1.0-clamp(r/d, 0.0, 1.0 ), 0.1 );
  69. vec3 hcol = vec3(1.0,0.5*r,0.3)*s;
  70. hcol = _HeartColor.xyz *s;
  71. vec3 col = mix( bcol, hcol, smoothstep( -0.01, 0.01, d - r) );
  72. col = mix( bcol, hcol, smoothstep( -_Blur, _Blur, d - r) );
  73. return vec4(col, 1.0);
  74. }
  75. ENDCG
  76. SubShader {
  77. Pass {
  78. CGPROGRAM
  79. #pragma vertex vert
  80. #pragma fragment frag
  81. #pragma fragmentoption ARB_precision_hint_fastest
  82. ENDCG
  83. }
  84. }
  85. FallBack "Diffuse"
  86. }

例如我们可以得到下面的效果:

当我们把动画速度放得很慢后,动画效果像一个装了水的心在弹动一样,也很有趣。

写在最后

ShaderToy里面大部分shader是不可以直接拿来用的,因为它的很多效果完全靠数学计算,消耗很大。当然,这也正跟它的名字一样,玩具嘛,数学+shader=牛人的玩具~

从这篇还有之前的几篇可以看出数学在shader中的重要性。几乎所有的动画都是依靠数学公式来完成的。数学提供给我们很多变化,像这里心的跳动,我们也完全可以使用其他跳动函数来模拟。我们还可以看出来,shader中很多也是经验公式,例如里面的很多参数,我们也是都可以自己修改的。

数学不好真的是硬伤,哎。还是那句话,多看看长长见识总没坏处。

【ShaderToy】跳动的心相关推荐

  1. css 跳动的心_如何用纯CSS为您的情人打造一颗跳动的心

    css 跳动的心 Each year on February 14th, many people exchange cards, candies, gifts or flowers with thei ...

  2. 跳动的心 - HTML 代码

    跳动的心 - HTML 代码 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <ht ...

  3. Java CSS3:(七)CSS3 中常用的样式(跳动的心)

    CSS3 中常用的样式-跳动的心 做一个跳动的心 做一个跳动的心 <!DOCTYPE html> <html><head><meta charset=&quo ...

  4. 使用css制作跳动的心

    利用css动画制作跳动的心 代码如下: <!DOCTYPE html> <html lang="en"><head><meta chars ...

  5. c语言编程16个点阵跳动的心,Arduino点阵制作跳动的心

    这次介绍的是使用8×8点阵(没有用到驱动模块,直接用Ardunio IO口控制)制作跳动的心.差不多用完所有IO口了,才能控制64个LED灯. 点阵说明 一般数码管有出厂信息:比如型号为LG5011A ...

  6. 用matlab跳动的心,[ 教程 ] 用Arduino制作一个跳动的心

    步骤4: 连接点阵屏的第二部分 这里分2部分 Arduino引脚: 13 12 11 10 点阵屏下面一排引脚: 1 2 3 4 接下来 Arduino引脚: A2 A3 A4 A5 点阵屏下面一排引 ...

  7. 用CSS3咋写“跳动的心”?

    CSS3写跳动的心需要哪些样式(属性)? 1.动画样式: 触发式动画:①触发条件: :hover :checked :active②动画的参数: transition-duration:5s;--时间 ...

  8. 跳动的心html5,施华洛世奇跳动的心真假怎么分辨

    不少人都喜欢施华洛世奇跳动的心这款项链,那么施华洛世奇跳动的心真假如何辨别呢?不同的切割工艺出来的反光效果是不同的,施华洛世奇是有着独特的切割工艺的,因此跳动的心是有很好的反光效果的,水晶的透光度和纯 ...

  9. python绘制表情包笑脸_用micro:bit学Python ——阵列显示表情符号“笑脸跳动的心”...

    本节任务要求 使用Microbit LED阵列显示表情符号 "笑脸跳动的心" 题目分析 这是一个使用Python语言完成MicroBit板载LED阵列编程的入门进阶题目,属于入门阶 ...

  10. 跳动的心c语言源代码,电影丨跳动的心投资份额有吗?怎么投资?什么时间上映...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 国家层面对中国电影行业发展的鼓励和重视.从2015年年初到现在,传媒行业在所有分类行业里涨幅最大,表现尤为强劲;中国电影市场的持续火爆是有目共睹的文化现象 ...

最新文章

  1. 关于c数据类型的范围
  2. postgresql 编码_上万份编码测试,大数据统计反映了公司在招聘时倾向的技能是什么...
  3. Linux查看二进制文件hexeditor
  4. OWASP-ZAP扫描器的使用(攻击)
  5. android调用webservice发送header身份验证不成功
  6. Spring事务管理(注解式声明事务管理)备忘
  7. CRM管理系统、教育后台、赠品管理、优惠管理、预约管理、试听课、教师、学生、客户、学员、商品管理、科目、优惠券、完课回访、客户管理系统、收费、退费、回访、账号权限、订单流水、审批、转账、rp原型
  8. 【报告分享】2021年中国企业直播成长路径研究报告.pdf(附下载链接)
  9. 2014,这些邮件设计好厉害!
  10. Excel学习日记:L4-资料排序
  11. SELECT TOP 100 PERCENT * 的含义
  12. 固态硬盘迁移及0xc00000e错误码解决方案
  13. Lora VS NB-IoT
  14. 四步学会使用ECharts做图
  15. 做个it女人不容易呀
  16. Suricata-IDS与IPS
  17. 图片实测:智能鉴黄,哪家强
  18. vue----前端分页完整代码
  19. 怎么把一个excel拆分成多个
  20. [英语阅读]经济危机难挡日本迪斯尼大丰收

热门文章

  1. Java中使用JNA实现全局监听Linux键盘事件
  2. 把网站图片和php程序分离,我的图片服务器和WEB应用服务器相分离的简单方案
  3. java ftp connect_java操作Ftp文件的一些方式(一)
  4. r语言怎么把txt数据变成一个Rdata格式_甲基化芯片数据下载如何读入到R里面
  5. java常问的报错_java常见报错及解决
  6. 1_数据分析—数据载入、导出和探索
  7. python 可视化 画直线_用Python画江苏省地图,实现各地级市数据可视化
  8. mysql查询缓存优化配置_mysql 优化之查询高速缓冲配置 小记
  9. r语言岭回归参数选择_数据分析中常见的七种回归分析以及R语言实现(三)---岭回归...
  10. cp分解实现_对标Eureka的AP一致性,Nacos如何实现Raft算法