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

简介: *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 专栏的其他文章。

目录
相关文章
|
机器学习/深度学习 自然语言处理 数据处理
论文《Attention is All You Need》
论文《Attention is All You Need》
1891 1
|
机器学习/深度学习 监控 算法
图像去雾综述
图像去雾综述
|
运维 Linux Go
grafana 8.x配置日报定时发送配置及踩坑经过
grafana 8.x配置日报定时发送配置及踩坑经过
1930 0
grafana 8.x配置日报定时发送配置及踩坑经过
|
人工智能 自然语言处理 供应链
从第十批算法备案通过名单中分析算法的属地占比、行业及应用情况
2025年3月12日,国家网信办公布第十批深度合成算法通过名单,共395款。主要分布在广东、北京、上海、浙江等地,占比超80%,涵盖智能对话、图像生成、文本生成等多行业。典型应用包括医疗、教育、金融等领域,如觅健医疗内容生成算法、匠邦AI智能生成合成算法等。服务角色以面向用户为主,技术趋势为多模态融合与垂直领域专业化。
|
8月前
|
XML JSON 编解码
从JSON到Protobuf,深入序列化方案的选型与原理
序列化是数据跨边界传输的“翻译官”,将结构化数据转为二进制流。JSON可读性强但冗余大,Protobuf高效紧凑、性能优越,成主流选择。不同场景需权衡标准化与定制优化,选最合适方案。
534 3
|
存储 API 图形学
Unity 给Animator动画添加事件(动态的)
在 Unity 中,通过动画事件系统可在动画播放的特定时间点触发自定义函数。动态添加事件的步骤包括获取 `AnimationClip` 对象,创建并添加 `AnimationEvent`,最后调用 `Rebind()` 更新动画控制器。示例代码展示了如何在动画开始、中间和结束时触发事件,实现与游戏逻辑的交互。
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
542 15
|
人工智能 自然语言处理 计算机视觉
StyleStudio:支持图像风格迁移的文生图模型,能将融合参考图像的风格和文本提示内容生成风格一致的图像
StyleStudio 是一种文本驱动的风格迁移模型,能够将参考图像的风格与文本提示内容融合。通过跨模态 AdaIN 机制、基于风格的分类器自由引导等技术,解决了风格过拟合、控制限制和文本错位等问题,提升了风格迁移的质量和文本对齐的准确性。
822 8
StyleStudio:支持图像风格迁移的文生图模型,能将融合参考图像的风格和文本提示内容生成风格一致的图像
|
编解码 图形学 开发者
【unity小技巧】使用三种方式实现瞄准瞄具放大变焦效果
【unity小技巧】使用三种方式实现瞄准瞄具放大变焦效果
1232 0