一个简易的烟花。
一个烟花的生命周期,从初升到徇烂到凋零,这就是全过程。
亮点
首先需要来一个光点。 光点就是中心最亮, 距离越远,亮度衰减的越快,超出一定距离后,视觉上就无光了。 所以这里的亮度和距离的关系至少得是2次以上, 也可以用指数。
来试一试。希望是距离越远,亮度越小,所以用 1-len ,来取反。
p
是点的位置, st
是当前片元坐标,r
是光点的半径 。
这个结果会用作基础色的系数,所以结果要超过1,这样才能使得中心的颜色超出基础色的亮度。
float lightSpot(vec2 p , vec2 st, float r){
vec2 d = p- st ;
float lenSquare =dot(d,d) ,len = sqrt(lenSquare);
return 1.-len ;
}
max( 1. - len, 0.)
线性函数的效果。
max( 1. - lenSquare, 0.)
二次函数的效果
1. /len
负一次函数
负二次函数
指数的效果
升空
升空就简单了, 就是给一个斜向上的初速度,然后水平速度可以衰减,竖直来一个重力加速度。 更新光点的位置即可。
float dt = mod( time, 5.) ;
vec3 bc = vec3( dir * pos1, 1.)
float k = 1./ (dt*dt +1.); // 衰减系数
dir*= k;
dir.y -= 1. ; // 重力
if( dt <3.){ //升空的时间
pos1 += dir * dt ;
bg += lightSpot(pos1, st, .03)* bc * k;
}
效果就是这样。
徇烂和凋零
当烟花升空至一定高度,就会炸开。 炸开的具体表现就是,原本的光点消失了, 突然飞出许多小光点, 同样的速度衰减和重力 这些小光点是以原本的位置为中心,四面八方(噪声随机)扩散的。
基本思路有了,下面就来接着写。
我们肯定是希望烟花是多色的,所拿每个小光点的方向和颜色关联起来。
然后是时间,是从初始光点消失开始算起的,并且这里稍微调整一下。
还有速度的问题, 如果每个小光点的初始速率都是一样的,那么结果会是一个十分单调的圆框。 想让它不那么单调,这里直接让速率直接有差异即可。
if( dt <1.5){ //炸开的时间
bg += lightSpot(pos1, st, .03)* bc * k;
}else {
dt -=1.5 ;
for(int i = 0; i < 15; i++) {
float fi = float(i);
vec2 dir = noise2d(vec2(20.313,1.57 ) * fi*2. +2.732145); // 也不必归一化了
bc = vec3( dir * pos1, rand(fi*3. )) ;
float k = 1./ (dt*dt +1.); // 衰减系数
dir*=(fi+ 1.)*.5 * k;//速率差异化
pos1 += dir * dt ;
bg += lightSpot(pos1, st, .03)* bc * k;
}
这里就完成了, 十分的简易,凋零这里没有再多写一个随时间衰减亮度,直接让它跟随周期消逝了,美好的事物就是这样转瞬即逝。 当然,你可以加一个。
这个效果,还是相当简陋的,但是不要紧, 这里以量取胜。
先简单封装一下,然后,多弄几个烟花,循环放。 代码和效果如下。
// 爆炸的位置 坐标 炸开的数目
vec3 explosion( vec2 p , vec2 st ,int n , float duration , float time,float offset){
vec3 color ;
vec2 dir ,pos;
vec3 bc ;
float dt = time - 1.5 ; //这里直接控制时间方便 飞3秒就炸开
for(int i = 0; i < n; i++) {
float fi = float(i);
// 因为p的位置一直在变所以没法关联方向
dir = noise2d(vec2(20.313,1.57 ) * fi*2. +2.732145* offset); // 也不必归一化了
// 因为基础色都是和方向关联, 那所有烟花都一样了,所以再喝初始位置进行关联,让颜色有所不同
bc = vec3( dir * p, rand(fi*3. )) ;// 基础色 如果希望颜色和方向的关联不大的话,
// 按那个的例子, 速率要不一样才行, 确实这样有层次感些 ,但是这个速度分层也得偏移一下
float k = 1./ (dt*dt +1.); // 衰减系数
dir*=(fi+ 1.)*.5 * k;// 初速度大一点,后面衰减, 衰减越来越慢
if( dt <duration){ //炸开的时间
pos = dir * dt + p ;
color += lightSpot(pos, st, .03)* bc * k;
// color += spot(pos, st, fi) * k; // 它这个算法的效果真的很赞啊
}
}
return color ;
}
vec3 fireworks (vec2 st, int worksNum, int n){
vec3 color, bc ;
vec2 startPos , dir;
for(int fw = 0; fw < worksNum; fw++) {
float fw_f = float ( fw) ;
float randVal =rand(fw_f + .5) ;
startPos = vec2(4. *randVal*- 2. , -3.) ;
dir = vec2( -startPos.x ,12. + randVal) ; // 让两边的烟花都往中间跑
bc = vec3(randVal, 1.-randVal,.9 ) ;
float t = mod( time + fw_f, 5.);
dir*= .9/(t+ 1.);// 衰减
dir.y -= t*1. ; //重力
startPos+= t * dir ;
if( t>1.5){
color+= explosion(startPos, st, 20, 3. , t,float(fw));
// bg+= explosion(pos1+ vec2(-1.5,-.5), st, 20, 7.);
// bg+= explosion(pos1+ vec2(-.5,.5), st, 20, 7.);
// bg+= explosion(pos1, st, 20, 7.);
}else{
float spot1 = lightSpot(startPos, st, .08) ;
color += bc*spot1 *(sin(time*2.)*.4 +.6) ; //要做亮点 还是加法比较合适
}
}
return color ;
}
这里有一个小问题, 那就是之前烟花炸开后的颜色是和方向关联在一起的,随机函数是固定的映射,那么就会导致,多个烟花其炸开的效果,看上去可能极为相似(一模一样),所以这里需要给方向到颜色的映射加一个偏移量。
结语
本文讲述了一种十分简易(偷懒)的烟花实现方式。忽略了亿点点细节。
其中,对于物体的位置的计算是有问题的, 因为物体的速度一直在变化,而我这里直接用最后的速度乘以时间,结果当然是不准确的,准确计算应该使用定积分。 但是,效果上,还行,图形学有很多近似,只要看起来是那么回事。
同样还是位置的问题, 在烟花炸开之后, 并没有停止计算最初的位置, 后面的炸开的部分都是根据它来计算的。 从相对运动的角度来讲, 这样计算似乎是没问题的。 但是,其实我最开始的设想是,烟花到达最高点时炸开,也就是基准位置应该不变,这样就需要额外的变量来保存。
总而言之,这个烟花效果还有很多可以改进的地方,比如,最开始的升空,可以加一个小尾巴之类的。