ShaderJoy —— 用 Shader 绘制爱心❤烟花【GLSL】

ShaderJoy 发表于 2022/01/29 03:37:00 2022/01/29
【摘要】 *ShaderJoy —— Shader 特效乐趣无穷*

ShaderJoy —— Shader 特效乐趣无穷

效果图

动态图(压缩有质量损失)

用在春节上百花齐放

tiger_1.gif

拉开一点时间

tiger.gif

再错开一点时间

tiger_2.gif

用在情人节

tiger.gif

静态图

shadertoy.png

shadertoy.png

爱心❤烟花,不仅春节可以放,情人节也可以放~

设计思路简介

首先,我们实现一个发光的粒子,代码为

vec3 burst(vec2 st, vec2 pos, float r, vec3 col)
{
    st -= pos;
    r = 0.6 * r * r;

    /// 发光效果
    return (r / dot(st, st)) * col * 0.6;
}

然后就是根据中学的物理知识,基于粒子一个上升的初速度,以及重力加速度的影响下逐渐变慢,继而达到它所能达到的最大高度。

相关代码如下

/// s = p0 + ut + 0.5at^2
/// 距离加速度公式
vec2 get_pos(vec2 u, vec2 a, vec2 p0, float t, float ang)
{
    /// 根据初始位置、水平和垂直的速度、加速度来更新当前的位置
    vec2 d = p0 + vec2(u.x * cos(ang), u.y * sin(ang)) * t + 0.5 * a * t * t;
    return d;
}

// t 时刻的粒子速度
vec2 get_velocity(vec2 u, vec2 a, float t, float ang)
{
    /// 根据加速度、当前水平和垂直的速度来更新当前的速度
    return vec2(u.x * cos(ang), u.y * sin(ang)) + a * t;
}

然后我们要考虑的是 —— 粒子达到一定高度后再发生爆(心形)爆炸,产生的新粒子的初始位置和速度。
接着,还和之前一样,垂直方向的位置和速度也要伴随着重力加速度而继续更新。

相关代码为

        /// @note 爆炸后的粒子扩散
        /// 当粒子停止上升,且粒子的当前时间已经达到了上升的时间

        if (v.y > -6.5 && v.y < 0.0 && t_i >= t_up /*&& SPAWN == 1*/)
        {
            /// 把一个圆根据角度分成若干份扇形
            float unit = (360. / snp);
            for (float j = 0.0; j < snp; j++)
            {
                float ang = rad(j * unit);

                float r = 0.035;             ///< 心形粒子的半径
                r -= (t_i - t_up) * R_RATIO; ///< 根据时间差来改变粒子的大小(变小)

                /// --------------------------------------------------
                /// @note 根据(单位圆的)角度计算笛卡尔坐标
                float x = cos(ang); //coords of unit circle
                float y = sin(ang);
                /// 心形公式
                y = y + abs(x) * sqrt( (8. - abs(x)) / 50.0 );
                /// 心形速度向量,随着时间而变小
                vec2 heart = vec2(x * x + y * y) * (0.4 / (t_i * sqrt(t_i)));

                /// 根据心形的当前速度和加速度、初始位置等更新粒子的位置
                vec2 S = get_pos(heart, acc * ACC_RATIO, h_max, t_i - (t_up), ang);
                /// --------------------------------------------------

                // vec3 pcol = colors[int(rand.x * float(NUM_COLS))];
                vec3 pcol = vec3(1.);

                particles += burst(uv, S, max(0.0, r), pcol);
            }
        }

其中心形公式的函数示意图为

根据它我们可以得到爆炸时每个粒子的初始位置。

值得注意的是,粒子的半径(尺寸)也会随着时间而逐渐变小

float r = 0.035; ///< 心形粒子的半径
r -= (t_i - t_up) * R_RATIO; ///< 根据时间差来改变粒子的大小(变小)

完整代码和详细注释


//random value
vec2 N22(vec2 p)
{
    vec3 a = fract(p.xyx * vec3(123.34, 234.34, 345.65));
    a += dot(a, a + 34.45);
    return fract(vec2(a.x * a.y, a.y * a.z));
}

vec3 burst(vec2 st, vec2 pos, float r, vec3 col)
{
    st -= pos;
    r = 0.6 * r * r;

    /// 发光效果
    return (r / dot(st, st)) * col * 0.6;
}

/// s = p0 + ut + 0.5at^2
/// 距离加速度公式
vec2 get_pos(vec2 u, vec2 a, vec2 p0, float t, float ang)
{
    /// 根据初始位置、水平和垂直的速度、加速度来更新当前的位置
    vec2 d = p0 + vec2(u.x * cos(ang), u.y * sin(ang)) * t + 0.5 * a * t * t;
    return d;
}

// t 时刻的粒子速度
vec2 get_velocity(vec2 u, vec2 a, float t, float ang)
{
    /// 根据加速度、当前水平和垂直的速度来更新当前的速度
    return vec2(u.x * cos(ang), u.y * sin(ang)) + a * t;
}

#define rad(x) radians(x)
float np = 100.;
float snp = 20.;
float R = 0.032;
float R_RATIO = 0.04;
float ACC_RATIO = 0.03;
float ANG = 90.;
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (2.*fragCoord - iResolution.xy) / iResolution.y;
    float aspect = iResolution.x / iResolution.y;
    vec3 col = vec3(0.0);
    float t = mod(iTime, 10.);

    vec2 u = vec2(0.);                ///< 初速度
    const vec2 acc = vec2(0.0, -9.8); ///< 重力加速度 acc
    float ang = rad(ANG);             ///< 上升粒子的发射角度

    vec3 particles = vec3(0.0); //particle

    for (float i = 0.; i < np; i++)
    {
        float r = R;
        vec2 rand = N22(vec2(i));

        /// @note 爆炸前的粒子上升

        /// 初始位置
        vec2 ip = vec2(sin(30.*rand.x) * aspect, -1. + r);

        /// 真正初始化速度
        u = vec2(sin(5.*rand.x), 5. + sin(4.*rand.y));

        float t_i = t - i / 5.; ///< 时间差异化
        vec2 s = get_pos(u, acc, ip, t_i, ang);
        vec2 v = get_velocity(u, acc, t_i, ang);

        /// 计算竖直向上的运动时间
        float t_up = u.y * sin(ang) / abs(acc.y);
        /// 根据时间计算出向上运动的最大高度
        vec2 h_max = get_pos(u, acc, ip, t_up, ang);

        vec3 pcol = vec3(1., 1., 1.);


        if (v.y < -0.5) ///< 下落速度超过一定大小则消失
        {
            r = 0.0;    ///< 隐藏
        }

        particles += burst(uv, s, r, pcol); ///< 发射上升的粒子


        /// @note 爆炸后的粒子扩散
        /// 当粒子停止上升,且粒子的当前时间已经达到了上升的时间

        if (v.y > -6.5 && v.y < 0.0 && t_i >= t_up /*&& SPAWN == 1*/)
        {
            /// 把一个圆根据角度分成若干份扇形
            float unit = (360. / snp);
            for (float j = 0.0; j < snp; j++)
            {
                float ang = rad(j * unit);

                float r = 0.035;             ///< 心形粒子的半径
                r -= (t_i - t_up) * R_RATIO; ///< 根据时间差来改变粒子的大小(变小)

                /// --------------------------------------------------
                /// @note 根据(单位圆的)角度计算笛卡尔坐标
                float x = cos(ang); //coords of unit circle
                float y = sin(ang);
                /// 心形公式
                y = y + abs(x) * sqrt( (8. - abs(x)) / 50.0 );
                /// 心形速度向量,随着时间而变小
                vec2 heart = vec2(x * x + y * y) * (0.4 / (t_i * sqrt(t_i)));

                /// 根据心形的当前速度和加速度、初始位置等更新粒子的位置
                vec2 S = get_pos(heart, acc * ACC_RATIO, h_max, t_i - (t_up), ang);
                /// --------------------------------------------------

                vec3 pcol = vec3(1.);
                particles += burst(uv, S, max(0.0, r), pcol);
            }
        }

    }

    col = particles;

    fragColor = vec4(col,  1.0);
}

通过以上代码则可以实现以下“核心”效果

tiger_3.gif

经过少许的修改,则达到文章开头的效果,更多有趣效果,敬请关注 ShaderJoy 专栏的其他文章。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请发送邮件至:cloudbbs@huaweicloud.com;如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。