1.绘制背景
在计算背景时,代码中使用了取余fmod函数与step函数:
- mod 取余,在CG内使用的是fmod函数,比如fmod(1.5, 1.0) 返回0.5
- step用于大小的比较,step(a,x) : 0 if x<a; 1 if x>=a; 比如: step(1, 1.2), 返回1; step(1, 0.8) 返回0;
我们先把背景绘制改造成Unity可以使用的代码放进去看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
vec4 main(vec2 fragCoord) { fixed3 COLOR1 = fixed3(0.0, 0.0, 0.3); fixed3 COLOR2 = fixed3(0.5, 0.0, 0.0); //屏幕坐标映射到0-1范围 vec2 uv = fragCoord.xy / iResolution.xy; //绘制背景 vec3 final_color = vec3(1,1,1); vec3 bg_color = vec3(0,0,0); vec3 wave_color = vec3(0,0,0); float c1 = fmod(uv.x, 2.0 * _BLOCK_WIDTH); c1 = step(_BLOCK_WIDTH, c1); float c2 = fmod(uv.y, 2.0 * _BLOCK_WIDTH); c2 = step(_BLOCK_WIDTH, c2); bg_color = mix(uv.x * COLOR1, uv.y * COLOR2, c1 * c2); return float4(bg_color.rgb, 1); } |
取余操作其实就是把背景按照对应方向切割成若干小块。以x轴为例,uv的范围本来为[0-1],当_BLOCK_WIDTH为0.1时,就是将x方向切割为1/(2 0.1)=5份。每份的范围也从[0-1]变成了[0-0.2]。
step操作把原来连续的[0-0.2]范围转换为0和1的信号,也就是当c1取值在[0-0.1]时返回0,[0.1-0.2]时返回1。
mix(lerp)插值为背景额外添加了一个颜色渐变的效果。
当c1和c2中存在一个0时,表示我们绘制的是背景,取值则为uv.x color1,其中color1代表蓝色,而uv的取值是[0-1],而且Unity绘制的原点在左下角,所以就会有背景的左侧的颜色是黑色(更偏向0),右侧颜色是蓝色(更偏向color1的0.3)。
c1和c2都为1时则为格子,对应取值为uv.y * color2,color2代表红色,所以就会有格子的下侧的颜色是黑色(更偏向0),上侧颜色是红色(更偏向color2的0.5)。
2.绘制线
其实直接把剩下的源码直接放到shader内就可以还原效果了,但是为了搞清楚代码里到底搞了什么操作,我们还是一步步来还原效果,首先先从绘制一条线开始:
1 2 3 4 5 6 |
float wave_width = 0.01; uv = -1.0 + 2.0 * uv; wave_width = 1.0 / (150.0 * max(abs(uv.y), 0.0001)); wave_color = vec3(wave_width * 1.9, wave_width, wave_width * 1.5); return float4(wave_color, 1); |
这里我们可以看到,作者对uv进行了一次映射,原uv的范围是[0-1],经过映射后新的uv范围变成了[-1,1],这也是为了后面计算方便吧(大概)。
接下来的width计算一开始让我摸不到头脑,原代码使用的是wave_width = abs(1.0 / (50.0 * uv.y));
,但是我一琢磨这y的范围不是[-1-1]吗?那中间不就有了一次除0的操作了吗?所以我把代码稍微改了一下,防止了除0操作。这个width计算结果取值范围为[0.0067-67],y值越接近0(屏幕中间),则值越大。这里的150其实也是颜色改变的效率(其实这个150才是真正影响线条宽度的值),在y值变化率保持不变时,这个值越大结果就会越小。
然后就是使用这个width(所以这个根本就跟宽度无关,这虚假的命名!)计算最终的颜色值。因为y越靠近0这个width值越大,对颜色的影响也就越大。所以屏幕的中间就出现了一条偏红和蓝的亮线,而越远离屏幕中心,因为width值太小以至于无法被我们感知颜色变化。
3.加上时间和周期函数
接下来就让我们直接把效果补全添加上对应的周期函数和时间变量吧!
1 2 3 4 5 6 7 8 9 10 |
float wave_width = 0.01; uv = -1.0 + 2.0 * uv; uv.y += 0.1; for(float i = 0.0; i < 10.0; i++) { uv.y += (0.07 * sin(uv.x + i/7.0 + _Time.y )); wave_width = abs(1.0 / (150.0 * max(abs(uv.y), 0.0001))); wave_color += vec3(wave_width * 1.9, wave_width, wave_width * 1.5); } return float4(wave_color, 1); |
既然画一条线是根据y的位置计算颜色进行叠加,那么画多条线也是类似的效果,所以这里遍历了10次进行了10条线颜色的叠加。
众所周知周期函数为y=asin(θ+φ),而为了防止线重叠,这里给每条线都加了一个偏移量φ,也是与i相关。其中的0.07和/7都是一些为了让效果看上去更好的理论值。
4.最终效果与最终代码
最终代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
Shader "Shadertoy/Wave" { Properties{ _Parameters("Circle Parameters", Vector) = (0.5, 0.5, 10, 0) // Center: (x, y), Radius: z _Color("Circle Color", Color) = (1, 1, 1, 1) _BackgroundColor("Background Color", Color) = (1, 1, 1, 1) _BLOCK_WIDTH("Block Width", float) = 0.01 } CGINCLUDE #include "UnityCG.cginc" #pragma target 3.0 #define vec2 float2 #define vec3 float3 #define vec4 float4 #define mat2 float2x2 #define mat3 float3x3 #define mat4 float4x4 #define iGlobalTime _Time.y #define mod fmod #define mix lerp #define fract frac #define texture2D tex2D #define iResolution _ScreenParams #define gl_FragCoord ((_iParam.scrPos.xy/_iParam.scrPos.w) * _ScreenParams.xy) #define PI2 6.28318530718 #define pi 3.14159265358979 #define halfpi (pi * 0.5) #define oneoverpi (1.0 / pi) float _BLOCK_WIDTH = 0.01; struct v2f { float4 pos : SV_POSITION; float4 scrPos : TEXCOORD0; }; v2f vert(appdata_base v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.scrPos = ComputeScreenPos(o.pos); return o; } vec4 main(vec2 fragCoord); fixed4 frag(v2f _iParam) : COLOR0{ vec2 fragCoord = gl_FragCoord; return main(gl_FragCoord); } vec4 main(vec2 fragCoord) { fixed3 COLOR1 = fixed3(0.0, 0.0, 0.3); fixed3 COLOR2 = fixed3(0.5, 0.0, 0.0); //屏幕坐标映射到0-1范围 vec2 uv = fragCoord.xy / iResolution.xy; //绘制背景 vec3 final_color = vec3(1,1,1); vec3 bg_color = vec3(0,0,0); vec3 wave_color = vec3(0,0,0); float c1 = fmod(uv.x, 2.0 * _BLOCK_WIDTH); c1 = step(_BLOCK_WIDTH, c1); float c2 = fmod(uv.y, 2.0 * _BLOCK_WIDTH); c2 = step(_BLOCK_WIDTH, c2); bg_color = mix(uv.x * COLOR1, uv.y * COLOR2, c1 * c2); // 画线 float wave_width = 0.01; uv = -1.0 + 2.0 * uv; uv.y += 0.1; for(float i = 0.0; i < 10.0; i++) { uv.y += (0.07 * sin(uv.x + i/7.0 + _Time.y )); wave_width = abs(1.0 / (150.0 * max(abs(uv.y), 0.0001))); wave_color += vec3(wave_width * 1.9, wave_width, wave_width * 1.5); } final_color = bg_color + wave_color; return float4(final_color, 1); } ENDCG SubShader{ Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest ENDCG } } FallBack Off } |
最终效果