ビジュアル表現¶
紙から切り抜いたような描画¶
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF(1.0, 0.9, 0.7));
constexpr Vec2 pos(220, 60);
const Image image(U"example/siv3d-kun.png");
const Texture texture(image);
// 画像の輪郭から Polygon を作成
const Polygon polygon = image.alphaToPolygon(160, false);
// 凸包を計算
const Polygon convexHull = polygon.calculateConvexHull();
// Polygon を太らせる
const Polygon largeConvex = convexHull.calculateBuffer(20);
// 影用の画像
Image shadowImage(Scene::Size(), Color(255, 0));
// 影のもとになる図形を書き込む
convexHull.calculateBuffer(10).movedBy(pos + Vec2(10, 10)).overwrite(shadowImage, Color(255));
// それをぼかしたものを影用テクスチャにする
const Texture shadow(shadowImage.gaussianBlurred(40, 40));
while (System::Update())
{
shadow.draw(ColorF(0.0, 0.5));
largeConvex.draw(pos, ColorF(0.96, 0.98, 1.0));
texture.draw(pos);
}
}
付箋¶
# include <Siv3D.hpp>
void DrawStickyNote(const RectF& rect, const ColorF& noteColor)
{
// 少しだけ回転させて影を描く
{
Transformer2D t(Mat3x2::Rotate(2_deg, rect.pos));
rect.stretched(-2, 1, 1, -4).drawShadow(Vec2(0, 0), 12, 0, ColorF(0.0, 0.4));
}
rect.draw(noteColor);
}
void Main()
{
Scene::SetBackground(ColorF(1.0, 0.98, 0.96));
const Font font(36, Typeface::Bold);
while (System::Update())
{
for (auto i : step(10))
{
const RectF rect(60 + i / 5 * 280, 20 + i % 5 * 90, 230, 70);
DrawStickyNote(rect, HSV(i * 36, 0.46, 1.0));
font(U"Text").draw(rect.pos.movedBy(20, 10), ColorF(0.1, 0.95));
}
}
}
テクスチャの反射¶
# include <Siv3D.hpp>
void Main()
{
const std::array<Texture, 3> textures =
{
Texture(Emoji(U"💹")),
Texture(Emoji(U"📅")),
Texture(Emoji(U"🏡")),
};
constexpr Size imageSize = Emoji::ImageSize;
while (System::Update())
{
Rect(0, 300, 800, 300).draw(ColorF(0.2, 0.3, 0.4));
for (auto [i, texture] : Indexed(textures))
{
const Vec2 pos(140 + i * 200, 220);
texture.draw(pos);
// 反射するテクスチャ
texture(0, imageSize.y / 2, imageSize.x, imageSize.y / 2).flipped()
.draw(pos.x, pos.y + imageSize.y,
Arg::top = AlphaF(0.8), Arg::bottom = AlphaF(0.0));
}
}
}
テキストの登場¶
# include <Siv3D.hpp>
// Glyph とエフェクトの関数を組み合わせてテキストを描画
void DrawText(const DrawableText& fontText, const Vec2& pos, const ColorF& color, double t,
void f(const Vec2& penPos, const Glyph& glyph, const ColorF& color, double t), double characterdPerSec)
{
Vec2 penPos = pos;
for (const auto& glyph : fontText)
{
if (glyph.codePoint == U'\n')
{
penPos.x = pos.x;
penPos.y += fontText.font.height();
continue;
}
const double targetTime = glyph.index * characterdPerSec;
if (targetTime > t)
{
break;
}
f(penPos, glyph, color, t - targetTime);
penPos.x += glyph.xAdvance;
}
}
// 文字が上からゆっくり降ってくる表現
void TextEffect1(const Vec2& penPos, const Glyph& glyph, const ColorF& color, double t)
{
const double y = EaseInQuad(Saturate(1 - t / 0.3)) * -20.0;
const double a = Min(t / 0.3, 1.0);
glyph.texture.draw(penPos + glyph.offset + Vec2(0, y), ColorF(color, a));
}
// 文字が勢いよく現れる表現
void TextEffect2(const Vec2& penPos, const Glyph& glyph, const ColorF& color, double t)
{
const double s = Min(t / 0.1, 1.0);
const double a = Min(t / 0.2, 1.0);
glyph.texture.scaled(3.0 - s * 2).draw(penPos + glyph.offset, ColorF(color, a));
}
// 落ちてきた文字がしばらく揺れる表現
void TextEffect3(const Vec2& penPos, const Glyph& glyph, const ColorF& color, double t)
{
const double angle = Sin(t * 1440_deg) * 25_deg * Saturate(1.0 - t / 0.6);
const double y = Saturate(1 - t / 0.05) * -20.0;
glyph.texture.rotated(angle).draw(penPos + glyph.offset + Vec2(0, y), color);
}
void Main()
{
const Font font(32, Typeface::Bold);
const String text = U"Lorem ipsum dolor sit amet, consectetur\n"
U"adipiscing elit, sed do eiusmod tempor\n"
U"incididunt ut labore et dolore magna aliqua.";
Stopwatch stopwatch(true);
while (System::Update())
{
if (SimpleGUI::Button(U"Reset", Vec2(620, 520)))
{
stopwatch.restart();
}
const double t = stopwatch.sF();
DrawText(font(text), Vec2(40, 40), Palette::Skyblue, t, TextEffect1, 0.1);
DrawText(font(text), Vec2(40, 200), Palette::Orange, t, TextEffect2, 0.1);
DrawText(font(text), Vec2(40, 360), Palette::Seagreen, t, TextEffect3, 0.1);
}
}
RenderTexture を使って図形や文字の影を描く¶
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF(0.8, 0.9, 1.0));
const Font font(100, Typeface::Heavy);
const Texture emoji(Emoji(U"🐧"));
// 影の形状を書き込むレンダーテクスチャ
RenderTexture shadow(800, 600);
RenderTexture shadowInternal(shadow.size());
// 影の形状を書き込むためのブレンドステート
BlendState bs = BlendState::Default;
bs.op = BlendOp::Max;
bs.srcAlpha = Blend::SrcAlpha;
bs.dstAlpha = Blend::DestAlpha;
bs.opAlpha = BlendOp::Max;
while (System::Update())
{
const RectF rect(100 + Periodic::Sine0_1(4s) * 400, 200, 200);
const Line line(100, 100, 400, 500);
shadow.clear(ColorF(1.0, 0.0));
{
// 影の形状を書き込む
ScopedRenderTarget2D target(shadow);
ScopedRenderStates2D blend(bs);
font(U"Siv3D").draw(400, 60);
rect.draw();
line.draw(LineStyle::RoundCap, 10);
emoji.rotated(Scene::Time() * 30_deg).drawAt(600, 500);
}
// 書き込まれた影をガウスぼかしして灰色で描画
Shader::GaussianBlur(shadow, shadowInternal, shadow);
const Vec2 shadowDirection = Circular(10, Scene::Time() * 50_deg);
shadow.draw(shadowDirection, ColorF(0.5));
{
// 本来の色で描画する
font(U"Siv3D").draw(400, 60, Palette::Orange);
rect.draw(Palette::Seagreen);
line.draw(LineStyle::RoundCap, 10, Palette::White);
emoji.rotated(Scene::Time() * 30_deg).drawAt(600, 500);
}
}
}
2D ライトブルーム¶
# include <Siv3D.hpp>
void DrawScene()
{
Circle(680, 40, 20).draw();
Rect(Arg::center(680, 110), 30).draw();
Triangle(680, 180, 40).draw();
Circle(740, 40, 20).draw(HSV(0));
Rect(Arg::center(740, 110), 30).draw(HSV(120));
Triangle(740, 180, 40).draw(HSV(240));
Circle(50, 200, 300).drawFrame(4);
Circle(550, 450, 200).drawFrame(4);
for (auto i : step(12))
{
const double angle = i * 30_deg + Scene::Time() * 5_deg;
const Vec2 pos = OffsetCircular(Scene::Center(), 200, angle);
Circle(pos, 8).draw(HSV(i * 30));
}
}
void Main()
{
constexpr Size sceneSize(800, 600);
RenderTexture gaussianA1(sceneSize), gaussianB1(sceneSize);
RenderTexture gaussianA4(sceneSize / 4), gaussianB4(sceneSize / 4);
RenderTexture gaussianA8(sceneSize / 8), gaussianB8(sceneSize / 8);
double a8 = 0.0, a4 = 0.0, a1 = 0.0;
while (System::Update())
{
// 通常のシーン描画
DrawScene();
{
// ガウスぼかし用テクスチャにもう一度シーンを描く
gaussianA1.clear(ColorF(0.0));
{
ScopedRenderTarget2D target(gaussianA1);
ScopedRenderStates2D blend(BlendState::Additive);
DrawScene();
}
// オリジナルサイズのガウスぼかし (A1)
// A1 を 1/4 サイズにしてガウスぼかし (A4)
// A4 を 1/2 サイズにしてガウスぼかし (A8)
Shader::GaussianBlur(gaussianA1, gaussianB1, gaussianA1);
Shader::Downsample(gaussianA1, gaussianA4);
Shader::GaussianBlur(gaussianA4, gaussianB4, gaussianA4);
Shader::Downsample(gaussianA4, gaussianA8);
Shader::GaussianBlur(gaussianA8, gaussianB8, gaussianA8);
}
{
ScopedRenderStates2D blend(BlendState::Additive);
if (a1)
{
gaussianA1.resized(sceneSize).draw(ColorF(a1));
}
if (a4)
{
gaussianA4.resized(sceneSize).draw(ColorF(a4));
}
if (a8)
{
gaussianA8.resized(sceneSize).draw(ColorF(a8));
}
}
SimpleGUI::Slider(U"a8: {:.1f}"_fmt(a8), a8, 0.0, 4.0, Vec2(20, 20));
SimpleGUI::Slider(U"a4: {:.1f}"_fmt(a4), a4, 0.0, 4.0, Vec2(20, 60));
SimpleGUI::Slider(U"a1: {:.1f}"_fmt(a1), a1, 0.0, 4.0, Vec2(20, 100));
if (SimpleGUI::Button(U"0, 0, 0", Vec2(20, 140)))
{
a1 = a4 = a8 = 0.0;
}
if (SimpleGUI::Button(U"1, 0, 0", Vec2(20, 180)))
{
a8 = 1.0;
a4 = a1 = 0.0;
}
if (SimpleGUI::Button(U"1, 1, 0", Vec2(20, 220)))
{
a8 = a4 = 1.0;
a1 = 0.0;
}
if (SimpleGUI::Button(U"1, 1, 1", Vec2(20, 260)))
{
a8 = a4 = a1 = 1.0;
}
}
}
パターンブラシ¶
# include <Siv3D.hpp>
// パターン画像を作る
Image CreatePattern()
{
Image image(16, 16, Palette::White);
Circle(0, 4, 2).paint(image, Palette::Black);
Circle(8, 4, 2).paint(image, Palette::Black);
Circle(16, 4, 2).paint(image, Palette::Black);
Circle(4, 12, 2).paint(image, Palette::Black);
Circle(12, 12, 2).paint(image, Palette::Black);
return image;
}
// 定数バッファ (PS_1)
struct PatternBrush
{
// パターンの UV のスケール
Float2 uvScale;
Float2 _unused = {};
};
void Main()
{
constexpr Size sceneSize(600, 600);
// パターン画像のテクスチャ
const Texture patternTexture(CreatePattern(), TextureDesc::Mipped);
// パターンブラシ用のピクセルシェーダ
// シェーダファイルの拡張子は、Windows では hlsl, macOS/Linux では frag を選択
// {} 内は定数バッファの名前と、対応する定数インデックス
const PixelShader ps(U"pattern_brush" SIV3D_SELECT_SHADER(U".hlsl", U".frag"),
{ { U"PSConstants2D", 0 }, { U"PatternBrush", 1 } });
if (!ps)
{
throw Error(U"Failed to load a shader file");
}
// 定数バッファ
ConstantBuffer<PatternBrush> cb;
cb->uvScale = Float2(sceneSize) / patternTexture.size();
// ペンで書き込むレンダーテクスチャ
MSRenderTexture renderTexture(sceneSize, Palette::Black);
// ペンの太さ
double thickness = 20;
while (System::Update())
{
if (MouseL.pressed())
{
{
ScopedRenderTarget2D rt(renderTexture);
if (MouseL.down())
{
Circle(Cursor::PosF(), thickness * 0.5).draw();
}
else if (MouseL.pressed() && !Cursor::Delta().isZero())
{
Line(Cursor::PreviousPosF(), Cursor::PosF())
.draw(LineStyle::RoundCap, thickness);
}
}
Graphics2D::Flush();
renderTexture.resolve();
}
Rect(sceneSize).draw();
{
// パターン画像をインデックス [1] のテクスチャスロットにセット
Graphics2D::SetTexture(1, patternTexture);
// パターンをくり返しマッピングできるようにする
Graphics2D::SetSamplerState(1, SamplerState::RepeatLinear);
{
Graphics2D::SetConstantBuffer(ShaderStage::Pixel, 1, cb);
ScopedCustomShader2D shader(ps);
renderTexture.draw();
}
}
// パターン画像を右上に表示
patternTexture.draw(620, 20);
}
}
Texture2D g_texture0 : register(t0);
Texture2D g_texture1 : register(t1);
SamplerState g_sampler0 : register(s0);
SamplerState g_sampler1 : register(s1);
cbuffer PSConstants2D : register(b0)
{
float4 g_colorAdd;
float4 g_sdfParam;
float4 g_internal;
}
// 定数バッファ (PS_1)
cbuffer PatternBrush : register(b1)
{
float2 g_uvScale;
}
// [C++]
//struct PatternBrush
//{
// Float2 uvScale;
// Float2 _unused = {};
//};
struct PSInput
{
float4 position : SV_POSITION;
float4 color : COLOR0;
float2 uv : TEXCOORD0;
};
float4 PS(PSInput input) : SV_TARGET
{
float alpha = g_texture0.Sample(g_sampler0, input.uv).r;
float4 texColor = g_texture1.Sample(g_sampler1, input.uv * g_uvScale);
texColor.a = alpha;
return (texColor * input.color) + g_colorAdd;
}
#version 410
uniform sampler2D Texture0;
uniform sampler2D Texture1;
// PS_0
layout(std140) uniform PSConstants2D
{
vec4 g_colorAdd;
vec4 g_sdfParam;
vec4 g_internal;
};
// PS_1
layout(std140) uniform PatternBrush
{
vec2 g_uvScale;
};
// [C++]
//struct PatternBrush
//{
// Float2 uvScale;
// Float2 _unused = {};
//};
//
// PSInput
//
layout(location = 0) in vec4 Color;
layout(location = 1) in vec2 UV;
//
// PSOutput
//
layout(location = 0) out vec4 FragColor;
void main()
{
float alpha = texture(Texture0, UV).r;
vec4 texColor = texture(Texture1, UV * g_uvScale);
texColor.a = alpha;
FragColor = (texColor * Color) + g_colorAdd;
}
平行四辺形を使った背景描画¶
# include <Siv3D.hpp>
void Main()
{
Scene::SetBackground(ColorF(0.9, 0.95, 1.0));
while (System::Update())
{
Rect(450, 0, 100, 600)
.shearedX(150).draw(HSV(40, 0.5, 1.0));
Rect(550, 0, 400, 600)
.shearedX(150).draw(HSV(40, 0.8, 1.0));
for (auto i : step(5))
{
const RoundRect rr(50, 60 + i * 100, 350, 80, 40);
rr.drawShadow(Vec2(4, 4), 20, 0)
.draw();
Circle(rr.rect.pos.movedBy(rr.r, rr.r), rr.r)
.stretched(-5)
.draw(HSV(40, 0.5, 1.0));
}
}
}
文章中に画像を入れる¶
# include <Siv3D.hpp>
struct Building
{
Texture icon;
String name, desc;
};
void Main()
{
Scene::SetBackground(ColorF(0.3, 0.6, 0.4));
const Font font1(30, Typeface::Heavy), font2(15, Typeface::Medium);
const Array<Texture> emojis = {
Texture(Emoji(U"⚙️")), Texture(Emoji(U"⚡")), Texture(Emoji(U"♥")) };
const Array<Building> buildings =
{
{ Texture(Emoji(U"🏭")), U"工場", U"毎ターン 6$0 を生産する\n電力 3$1 が必要" },
{ Texture(Emoji(U"🏟")), U"スタジアム", U"毎ターン 4$2 を供給する\n電力 2$1 が必要" },
{ Texture(Emoji(U"🏖")), U"ビーチ", U"毎ターン 2$2 を供給する\n砂浜にしか建設できない" }
};
const RoundRect r0(0, 0, 360, 100, 6);
const RoundRect r1(5, 5, 90, 90, 5);
while (System::Update())
{
for (auto [i, building] : Indexed(buildings))
{
Transformer2D t(Mat3x2::Translate(40, 40 + i * 110));
r0.drawShadow(Vec2(4, 4), 8, 1)
.draw(ColorF(0.2, 0.25, 0.3))
.drawFrame(1, 1, ColorF(0.4, 0.5, 0.6));
r1.draw(ColorF(0.85, 0.9, 0.95));
building.icon.resized(80).drawAt(r1.center());
font1(building.name).draw(r1.rect.pos.movedBy(100, 0));
const Vec2 penPos0 = r1.rect.pos.movedBy(100, 42);
Vec2 penPos = penPos0;
bool onTag = false;
for (const auto& glyph : font2(building.desc))
{
if (onTag)
{
emojis[(glyph.codePoint - U'0')].resized(25).draw(penPos);
penPos.x += 25;
onTag = false;
continue;
}
if (glyph.codePoint == U'$')
{
onTag = true;
continue;
}
onTag = false;
if (glyph.codePoint == U'\n')
{
penPos.x = penPos0.x;
penPos.y += font2.height();
}
else
{
glyph.texture.draw(penPos + glyph.offset);
penPos.x += glyph.xAdvance;
}
}
}
}
}