2D ライトブルームとアニメーション
https://scrapbox.io/files/67303699e0b11443e93de410.mp4
https://gyazo.com/267de140d57ca524ab58907150818155
code:bloom_anim.cpp
# include <Siv3D.hpp> // Siv3D v0.6.15
// 2D ライトブルーム
class LightBloom
{
public:
LightBloom(const Size& size)
:
size_{ size },
blur1_{ size },
internal1_{ size },
blur4_{ size / 4 },
internal4_{ size / 4 },
blur8_{ size / 8 },
internal8_{ size / 8 }
{
}
const ScopedRenderTarget2D getRenderTarget() const
{
return ScopedRenderTarget2D{ blur1_.clear(ColorF{ 0 }) };
}
void update()
{
Shader::GaussianBlur(blur1_, internal1_, blur1_);
Shader::Downsample(blur1_, blur4_);
Shader::GaussianBlur(blur4_, internal4_, blur4_);
Shader::Downsample(blur4_, blur8_);
Shader::GaussianBlur(blur8_, internal8_, blur8_);
}
void draw(const Vec2& pos = { 0, 0 }, const Color& color = Palette::White, double intensity1 = 1.0, double intensity4 = 1.0, double intensity8 = 1.0) const
{
const ScopedRenderStates2D blend{ BlendState::Additive };
blur1_.draw(pos, ColorF{ color, color.a * intensity1 / 255 });
blur4_.resized(size_).draw(pos, ColorF{ color, color.a * intensity4 / 255 });
blur8_.resized(size_).draw(pos, ColorF{ color, color.a * intensity8 / 255 });
}
private:
const Size size_;
const RenderTexture blur1_;
const RenderTexture internal1_;
const RenderTexture blur4_;
const RenderTexture internal4_;
const RenderTexture blur8_;
const RenderTexture internal8_;
};
// キラキラ
class Sparkle
{
public:
/// @param pos 生成位置
Sparkle(const Vec2& pos, const Vec2& vel)
:
pos_{ pos },
vel_{ vel },
size_{ 2.0 + Random() * 12.0 },
rot_{ Random(-1.0, 1.0) },
lifetime_{ Random(0.5, 0.7) },
delay_{ Random(-0.2, 0.2) },
time_{ StartImmediately::Yes }
{
}
void update()
{
pos_ += vel_ * Scene::DeltaTime();
}
void draw() const
{
const double t = Saturate((time_.sF() - delay_) / lifetime_);
if (t > 0)
{
const double size = size_ * (1.0 - EaseInSine(t));
const double angle = Math::Lerp(rot_, rot_ * 2.0, t);
Shape2D::Astroid(pos_, size, size, angle).draw(ColorF{ 1.0, 0.8, 1.0, 0.3 });
}
}
bool alive() const
{
return (time_.sF() - delay_) < lifetime_;
}
private:
Vec2 pos_;
Vec2 vel_;
double size_;
double rot_;
double lifetime_;
double delay_;
Stopwatch time_;
};
void DrawEmoji(const Texture& emojiTexture, double emojiSize)
{
emojiTexture.resized(emojiSize).drawAt(Scene::CenterF(), ColorF{ 1.0, 1.0 });
}
void Main()
{
Scene::SetBackground(ColorF{ 0.1 });
const Texture emoji{ U"👾"_emoji };
constexpr double EmojiSize = 128;
LightBloom bloom{ Scene::Size() };
Array<Sparkle> sparkle;
sparkle.reserve(16);
Stopwatch timeFx{ StartImmediately::Yes };
while (System::Update())
{
// Input
{
if (MouseL.down())
{
timeFx.restart();
const double n = 16 + Random(-2, 2);
const double r = (2.0 + Random(0.5)) * Sign(Random(-1.0, 1.0));
for (int i = 0; i < 16; ++i)
{
const Vec2 pos = Scene::CenterF() + Circular{ i / 16.0 * 160 + 100, i * Math::TwoPi / n * r } + RandomVec2(32.0);
const Vec2 vel = (pos - Scene::CenterF()).setLength(Random(1.0, 16.0));
sparkle.emplace_back(pos, vel);
}
}
}
// BG
{
const double t = Saturate(timeFx.sF() / 0.2);
if (t < 1)
{
Scene::Rect().draw(ColorF{ 1.0, 0.5, 1.0, 0.1 - 0.1 * EaseInSine(t) });
for (int i = 0; i < 32; ++i)
{
const double y = i * Scene::Height() / 32;
const double thickness = (Scene::Height() / 32.0 / 2) * (1.0 - EaseInQuad(t));
Line{ 0, y, Scene::Width(), y }.draw(thickness, ColorF{ 1.0, 0.5, 1.0, (0.2 - 0.2 * EaseInSine(t)) * Periodic::Square0_1(0.08s) });
}
Circle(Scene::CenterF(), Scene::Width() / 2.0 * 1.5).draw(ColorF{ 0.0, 0.0 }, ColorF{ 0.0, 0.5 });
}
}
// Sparkle
{
for (auto& s : sparkle)
{
s.update();
s.draw();
}
{
const auto _ = bloom.getRenderTarget();
for (auto& s : sparkle)
{
s.draw();
}
}
sparkle.remove_if([](const auto& s) { return not s.alive(); });
bloom.update();
bloom.draw(Vec2{ 0, 0 }, Palette::White, 0.3, 0.4, 0.5);
}
// Astroid
{
{
const double t = Saturate(timeFx.sF() / 0.3);
const auto _ = bloom.getRenderTarget();
const double alpha = 0.8 - 0.8 * EaseOutCubic(t);
const double astWidth = 100 + 200 * EaseOutQuad(t);
const double astHeight = 80 + 80 * EaseInQuad(t);
Shape2D::Astroid(Scene::CenterF(), astWidth, astHeight, 0.0).draw(ColorF{ 1.0, 0.4, 1.0, alpha });
}
bloom.update();
bloom.draw(Vec2{ 0, 0 }, Palette::White, 0.3, 0.5, 0.7);
}
// Circle (1)
{
const double tc = Saturate((timeFx.sF() - 0.07) / 0.45);
const double r = 32 + 240 * EaseOutExpo(tc);
const double thickness = 24.0 - 22 * EaseInSine(tc);
const ColorF color = ColorF{ 1.0, 0.4, 1.0, 0.5 - 0.5 * EaseOutCubic(tc)};
const auto drawCircle = [](double r, double thickness, const ColorF& color) { Circle{ Scene::CenterF(), r }.drawFrame(thickness, color); };
drawCircle(r, thickness, color);
{
const auto _ = bloom.getRenderTarget();
drawCircle(r, thickness, color);
}
bloom.update();
bloom.draw(Vec2{ 0, 0 }, Palette::White, 0.2, 0.4, 0.6);
}
// Circle (2)
{
const double tc = Saturate((timeFx.sF() - 0.02) / 0.40);
const double r = 32 + 80 * EaseOutSine(tc);
const ColorF color = ColorF{ 1.0, 0.6, 1.0, 0.3 - 0.3 * EaseOutCubic(tc) };
const auto drawCircle = [](double r, const ColorF& color) { Circle{ Scene::CenterF(), r }.draw(color); };
{
const auto _ = bloom.getRenderTarget();
drawCircle(r, color);
}
bloom.update();
bloom.draw(Vec2{ 0, 0 }, Palette::White, 0.2, 0.5, 0.8);
}
// Emoji
{
const double t = Saturate(timeFx.sF() / 0.3);
const double emojiSizeScale = 1.0 + (0.2 - 0.2 * EaseOutCubic(t)) + 0.2 * (1.0 - EaseOutCubic(Periodic::Sawtooth0_1(0.40s, timeFx.sF())));
const double emojiSize = EmojiSize * emojiSizeScale;
DrawEmoji(emoji, emojiSize);
{
const auto _ = bloom.getRenderTarget();
DrawEmoji(emoji, emojiSize);
}
bloom.update();
const double bloomInt1 = 0.4 - 0.1 * EaseInQuad(t);
const double bloomInt4 = 0.6 - 0.2 * EaseInQuad(t);
const double bloomInt8 = 0.8 - 0.3 * EaseInQuad(t);
bloom.draw(Vec2{ 0, 0 }, Palette::White, bloomInt1, bloomInt4, bloomInt8);
}
}
}