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

目录
相关文章
|
前端开发 JavaScript 定位技术
threejs绘制风羽
threejs绘制风羽
253 0
|
图形学
浅谈Unity之ShaderGraph-等高线和高程渐变设色
ShaderGraph实现等高线和高程渐变设色
|
1月前
Threejs绘制圆锥体
这篇文章讲解了如何在Three.js中创建并正确定向圆锥体,确保其在不同场景下的稳定显示,涵盖了生成圆锥体几何体、设置材质和纹理以及解决可能的倾斜显示问题等内容。
36 1
Threejs绘制圆锥体
|
1月前
ThreeJs绘制贝塞尔曲线
这篇文章介绍了如何利用Three.js绘制贝塞尔曲线,并提供了实现的代码示例与说明。
35 2
ThreeJs绘制贝塞尔曲线
|
1月前
|
JSON 数据格式
Cesium绘制一个正方体
这篇文章详细说明了如何在Cesium中创建并精确控制一个厘米级精度的立方体模型。
27 2
Cesium绘制一个正方体
|
1月前
Threejs绘制传送带
这篇文章详细介绍了如何使用Three.js绘制一个动态的传送带模型,包括传送带的几何体创建、纹理应用以及实现带体循环移动的动画效果。
26 0
Threejs绘制传送带
|
3月前
|
存储 JavaScript vr&ar
three.js中的矩阵变换(模型视图投影变换)
three.js中的矩阵变换(模型视图投影变换)
79 1
|
3月前
|
缓存 图形学 C++
Unreal学习笔记2-绘制简单三角形
Unreal学习笔记2-绘制简单三角形
33 0
|
5月前
|
图形学
【实现100个unity特效之2】使用shader和shader Graph实现2d图片描边效果(附源码)
【实现100个unity特效之2】使用shader和shader Graph实现2d图片描边效果(附源码)
235 0
|
缓存
学习OpenGL ES之绘制一个正方体
学习OpenGL ES之绘制一个正方体
学习OpenGL ES之绘制一个正方体