简易烟花

891 阅读5分钟

一个简易的烟花。

一个烟花的生命周期,从初升到徇烂到凋零,这就是全过程。

亮点

首先需要来一个光点。 光点就是中心最亮, 距离越远,亮度衰减的越快,超出一定距离后,视觉上就无光了。 所以这里的亮度和距离的关系至少得是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.) 线性函数的效果。

image.png

max( 1. - lenSquare, 0.) 二次函数的效果

image.png

1. /len 负一次函数

image.png 负二次函数

image.png 指数的效果

image.png

升空

升空就简单了, 就是给一个斜向上的初速度,然后水平速度可以衰减,竖直来一个重力加速度。 更新光点的位置即可。

 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;
    
      }
   

chrome_ZNC74F2ov4.gif 效果就是这样。

徇烂和凋零

当烟花升空至一定高度,就会炸开。 炸开的具体表现就是,原本的光点消失了, 突然飞出许多小光点, 同样的速度衰减和重力 这些小光点是以原本的位置为中心,四面八方(噪声随机)扩散的。

基本思路有了,下面就来接着写。

我们肯定是希望烟花是多色的,所拿每个小光点的方向和颜色关联起来。

然后是时间,是从初始光点消失开始算起的,并且这里稍微调整一下。

还有速度的问题, 如果每个小光点的初始速率都是一样的,那么结果会是一个十分单调的圆框。 想让它不那么单调,这里直接让速率直接有差异即可。

       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;

        
        
    }

这里就完成了, 十分的简易,凋零这里没有再多写一个随时间衰减亮度,直接让它跟随周期消逝了,美好的事物就是这样转瞬即逝。 当然,你可以加一个。 chrome_SuLzbTQd4c.gif

这个效果,还是相当简陋的,但是不要紧, 这里以量取胜。

先简单封装一下,然后,多弄几个烟花,循环放。 代码和效果如下。


// 爆炸的位置 坐标  炸开的数目
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 ;
}

这里有一个小问题, 那就是之前烟花炸开后的颜色是和方向关联在一起的,随机函数是固定的映射,那么就会导致,多个烟花其炸开的效果,看上去可能极为相似(一模一样),所以这里需要给方向到颜色的映射加一个偏移量。

结语

本文讲述了一种十分简易(偷懒)的烟花实现方式。忽略了亿点点细节。

其中,对于物体的位置的计算是有问题的, 因为物体的速度一直在变化,而我这里直接用最后的速度乘以时间,结果当然是不准确的,准确计算应该使用定积分。 但是,效果上,还行,图形学有很多近似,只要看起来是那么回事。

同样还是位置的问题, 在烟花炸开之后, 并没有停止计算最初的位置, 后面的炸开的部分都是根据它来计算的。 从相对运动的角度来讲, 这样计算似乎是没问题的。 但是,其实我最开始的设想是,烟花到达最高点时炸开,也就是基准位置应该不变,这样就需要额外的变量来保存。

总而言之,这个烟花效果还有很多可以改进的地方,比如,最开始的升空,可以加一个小尾巴之类的。