导语: 在数字艺术和计算机图形学的奇妙世界里,Shader编程扮演着至关重要的角色,它赋予了我们创造无限可能的能力。Shader不仅是一种编程语言,更是一种艺术形式,让我们能够模拟自然界的复杂现象,如水面的波纹、光影的流转。我们将探讨Shader中时间变化原理和波纹生成原理,这两个概念是实现动态视觉效果的关键。它们如何在Shader编程中被应用,以及它们在特效开发中有何重要作用,让我们一起探索这些原理背后的科学,以及它们如何影响我们的视觉体验。
时间变化原理及其应用
周期性时间变化是shader动画中的的关键技术,尤其是在计算机图形学和动画领域。周期性运动是指一个量随时间变化而呈现出重复模式的运动。这种运动的特点是它有一个固定的周期,即完成一个完整的循环所需的时间。
为了实现周期性变化,常用三角函数(如正弦sin和余弦co函数)或其变体作为时间函数。这些函数具有自然的周期性,可以很好地模拟周期性运动。通过调整时间函数的参数,如频率、振幅、相位等,可以控制周期性运动的特性。例如,增加频率会使周期变短,而增加振幅会使运动的幅度变大。
除此以外,还可以使用其他数学模型和算法来实现更多样化的周期性运动效果。
fract(t)
时间从0-1间隔1秒的周期变化。fract(t) 函数将时间 t 的整数部分去除,只保留小数部分。这意味着 fract(t) 的值将在0到1之间循环变化,形成一个周期性的效果。其函数图像如下。
例如,使用fract(t)来实现圆圈半径随时间周期性变化的效果画一个从小到大变化的圆圈。对圆圈半径做一个fract(t)的时间周期性变化**,** 圆圈半径为r,如果r保持不变,它将是一个固定半径大小的圆圈,我们加上时间周期变化r*fract(t),可以看到圆圈在1秒中半径从小变大持续周期运动。
fract(t/n)
时间从0-1间隔n秒的周期变化。下面是fract(t/4)间隔4秒的函数图像。
1-fract(t)
时间从1-0间隔1秒的周期变化。这是一个与fract(t)逆向的函数,形成一个反向周期性的效果。其函数图像如下
例如,画一个从大到小变化的圆圈,对半径r做一个1-fract(t)的时间周期性变化 r*(1-fract(t)),可以看到圆圈在1秒中半径从大变小持续周期运动与r*fract(t)是相反的逆向运动的效果。
abs(fract(t-0.5) - 0.5)
时间从0-0.5-0在1秒内周期变化。如果要对周期时间进行压缩或延长,可以对x进行缩小或放大。而如果需要对变化运动顺序进行调整可对x进行左右平移。本质上也就是对x进行加减乘除对函数图像的调整。
例如,画一个从小到大再到小变化的圆圈
clamp(abs(sin(t)), 0, 0.5)
时间从0-0.5-0.5-0.5-0,周期变化
例如,画一个从小到大持续3秒钟再到小变化的圆圈
clamp(1-abs(t/((n+2)/2)-1),0,1/(n+2)/2))/(1/(n+2)/2))
clamp(1-abs(x/2.5-1),0,0.4)/0.4
当然这种也可以用分段函数实现,时间从0-1-n个1-1-0 一次性时间变化,可用于出场后展示几秒钟然后消失的动画,例如咱们歌词中一句歌词有一个出场动画,出场后展示几秒钟,然后有一个退场动画。
clamp(t, 0, 1)
时间从0-1一次性变化,这种变化可用于出场动画,比如需要圆圈从无到有出现在画面中,并且出现后不消失。
1-clamp(t, 0, 1)
时间1-0一次性变化,这种变化可用于退出动画,比如需要圆圈从有到无退出画面,不再出现。
时间的线性动画
有的场景需要对属性做缓动动画,本质上是对属性变化的速度进行调整,因此可以将线性变化的时间做函数变化,控制速度的变化以达到缓动效果。
将匀速线性变化的时间做函数变化,得到运动曲线,再乘以属性,从而实现属性的缓动效果。
例如:左边是匀速放大,右边是先慢速后快速easeInOutCirc曲线。
speed = easeInOutCirc(fract(t))
以上所有圆圈运动案例的shader代码实现:github.com/tanatc/Ripp…
波纹扭曲效果原理
从横截面看,波纹的形状是类似于sin函数的波形的,因此,可以用sin函数模拟波形和波形运动。
扭曲效果本质上是做像素偏移,波纹是对图像围绕一个点做sin波形的像素偏移。一圈一圈的圆形偏移。先画波纹形状,此处画一个圆形水波纹,先画圆圈,再对圆圈内像素做偏移。
Asin(ax + t),对于一个sin函数,通过调整时间函数的参数,如频率、振幅、相位等,可以控制周期性运动的特性。
a可以控制振动频率或者说周期,从波纹形状来看也就是波纹宽度从一个波峰到另一个波峰的宽度。
A可以控制振动幅度,从波纹形状来看也就是波纹的高度从波谷到波峰的垂直高度。
t可以控制函数移动的速度和方向,从波纹的运动来说就是波纹运动的快慢和左右移动的方向。
此处我们要实现的是可以控制波纹中心点、波纹高度、波纹宽度、波纹运动速度、波纹半径范围,因此将以上变量作为参数。
// 波纹偏移量计算偏移后uv
vec2 getWaveOffset(vec2 center, vec2 uv, float wave_width, float wave_height, float wave_speed, float wave_r) {
vec2 distance_vec = center - uv;
float distance = length(center - uv);
float wave_r_factor = clamp(wave_r - distance, 0.,1.) / wave_r; // 0-1, wave_r 0-1 waveRFactor 0-1
float sin_factor = sin(distance * 100. * wave_width - cc_time.x * wave_speed) * 0.1 * wave_height * 1. / (1. + distance) * wave_r_factor; // 水波纹往外波动, // 水波从里到外波长和频率不变,振幅变小,且和半径成反比(1/r)
vec2 offset = normalize(distance_vec) * sin_factor;
vec2 result_uv = offset + v_uv0;
return result_uv;
}
这里关键点在 sin(normalize * wave_width - cc_time.x * wave_speed) 利用sin函数模拟波形运动,做uv偏移。
**normalize**控制了波纹中心点到uv像素点方向上做sin波纹偏移,
**wave_width**控制了波纹宽度,
**cc_time.x * wave_speed**控制了波纹在方向上运动,使得波纹从中心点向外或向内运动。
如果要对波纹效果的半径范围进行控制,则要将半径影响因素加入到约束sin_factor。从半径范围内到超过半径范围波形影响因子需要从1到0进行变化,因此,wave_r_factor = clamp(wave_r - distance, 0.,1.)
既然有了这么多变量可以控制,那么波纹的可玩性就大了。
横向波纹
需要消除纵向的影响,只对图像在x轴方向做像素偏移,则可以确定一个中心点,固定uv的y坐标。
vec2 wave_uv = getWaveOffset(center, vec2(uv.x, center.y), wave_width, wave_height, wave_speed, wave_r);
纵向波纹
需要消除横向的影响,只对图像在y轴方向做像素偏移, 则可以确定一个中心点,固定uv的x坐标。
vec2 wave_uv = getWaveOffset(center, vec2(center.x, uv.y), wave_width, wave_height, wave_speed, wave_r);
像蝴蝶一样舞动翅膀
将中心点固定在中间,波纹的宽度调至翅膀那么宽,一半屏幕宽,波纹只做横向运动,视觉上就是左右两边对称的运动,蝴蝶舞动翅膀的效果就出来啦。
竖条玻璃纹路
竖条的玻璃纹路是只用横向的波纹,并且把波纹宽度调窄一点,就有玻璃纹路的效果。
调整参数,加上一些时间变化,运动曲线变化,就能创造设计出更多奇妙的效果。
以上所有波纹案例的shader代码实现:github.com/tanatc/Ripp…