はじめに
OpenSiv3Dの2D描画サンプルには、ガウシアンブラーとレンダーテクスチャを使用した多彩なテクニックが紹介されています。これらはビジュアルを豊かにするための重要な要素です。
https://siv3d.github.io/ja-jp/samples/2d/
今回は、これらのテクニックをシンプルかつ効率的に活用するため、シングルトンパターンを用いた実装を紹介します。
シングルトンパターンの適用
シングルトンパターンを用いることで、どの場所からでも簡単にレンダーターゲットを指定し、エフェクトを適用できます。例えば、BloomManager
を使用する場合は以下のようになります:
{
const auto t = BloomManager::getInstance().getRenderTarget();
// ここに発光させたいオブジェクトを描画
}
BloomManager::getInstance().draw();
このコードでは、どこからでもBloomManager
のインスタンスにアクセスし、ブルームエフェクトのレンダーターゲットを取得し、その後エフェクトを描画します。
実装
class ShadowManager
{
private:
RenderTexture shadowTexture, blur4, internal4;
ShadowManager()
: shadowTexture(Scene::Size(), ColorF{ 1.0, 0.0 }),
blur4(Scene::Size() / 4),
internal4(Scene::Size() / 4)
{}
public:
static ShadowManager& getInstance()
{
static ShadowManager instance;
return instance;
}
[[nodiscard]]
std::tuple<ScopedRenderTarget2D, ScopedRenderStates2D, Transformer2D> getTargets(const Vec2& offset = Vec2(10.0, 10.0))
{
ScopedRenderTarget2D target{ shadowTexture};
ScopedRenderStates2D blend{ BlendState::MaxAlpha };
Transformer2D transform{ Mat3x2::Translate(offset) };
return { std::move(target), std::move(blend), std::move(transform) };
}
void draw(double alpha = 0.5)
{
Shader::Downsample(shadowTexture, blur4);
Shader::GaussianBlur(blur4, internal4, blur4);
blur4.resized(Scene::Size()).draw(ColorF{ 0.0, alpha });
shadowTexture.clear(ColorF{ 1.0, 0.0 });
}
};
class BloomManager
{
private:
RenderTexture blur1, internal1, blur4, internal4, blur8, internal8;
// コンストラクター
BloomManager(const Size& sceneSize)
: blur1(sceneSize), internal1(sceneSize),
blur4(sceneSize / 4), internal4(sceneSize / 4),
blur8(sceneSize / 8), internal8(sceneSize / 8)
{
blur1.clear(ColorF{ 0.0 });
}
public:
static BloomManager& getInstance()
{
static BloomManager instance(Scene::Size());
return instance;
}
[[nodiscard]]
ScopedRenderTarget2D getRenderTarget()
{
return ScopedRenderTarget2D(blur1);
}
void draw(double a1 = 0.5, double a4 = 0.5, double a8 = 0.5)
{
// ガウシアンフィルタの適用
Shader::GaussianBlur(blur1, internal1, blur1);
Shader::Downsample(blur1, blur4);
Shader::GaussianBlur(blur4, internal4, blur4);
Shader::Downsample(blur4, blur8);
Shader::GaussianBlur(blur8, internal8, blur8);
// ブルームエフェクトの描画
const ScopedRenderStates2D blend{ BlendState::Additive };
if (a1) { blur1.resized(Scene::Size()).draw(ColorF{ a1 }); }
if (a4) { blur4.resized(Scene::Size()).draw(ColorF{ a4 }); }
if (a8) { blur8.resized(Scene::Size()).draw(ColorF{ a8 }); }
blur1.clear(ColorF{ 0.0 });
}
};
ShadowManager
クラスの紹介
ShadowManager
クラスは、影の描画を簡単に行えるように設計されています。以下の機能を提供します:
-
getTargets()
: 影を描画するためのScopedRenderTarget2D
、ScopedRenderStates2D
、Transformer2D
を返します。 -
draw()
: ぼかした影を描画します。
BloomManager
クラスの紹介
BloomManager
クラスは、ブルームエフェクトの管理を行うシングルトンです。以下の機能を提供します:
-
getRenderTarget()
: ブルームエフェクト用のScopedRenderTarget2D
を返します。 -
draw()
: ブルームエフェクトを描画します。
追記
ほかにもあとから追加した機能を紹介します。
ブラー
class BlurManager
{
public:
// フィルタのタイプを表すenum class
enum class FilterType {
Original,
GaussianBlur1x,
GaussianBlur2x,
DownsampleHalfBlur,
DownsampleQuarterBlur
};
private:
RenderTexture blur1, internal1, blur2, internal2, blur4, internal4;
// コンストラクターは非公開
BlurManager(const Size& sceneSize)
: blur1(sceneSize), internal1(sceneSize),
blur2(sceneSize / 2), internal2(sceneSize / 2),
blur4(sceneSize / 4), internal4(sceneSize / 4)
{
blur1.clear(ColorF{ 0.0 });
}
void applyGaussianBlur(RenderTexture& texture, RenderTexture& internalTexture)
{
Shader::GaussianBlur(texture, internalTexture, texture);
}
public:
// シングルトンインスタンスを取得するメソッド
static BlurManager& getInstance()
{
static BlurManager instance(Scene::Size());
return instance;
}
// ScopedRenderTarget2Dを返すメソッド
[[nodiscard]]
ScopedRenderTarget2D getRenderTarget()
{
return ScopedRenderTarget2D(blur1);
}
// フィルタを適用するメソッド
void draw(FilterType filterType)
{
switch (filterType) {
case FilterType::Original:
break;
case FilterType::GaussianBlur1x:
applyGaussianBlur(blur1, internal1);
blur1.draw();
break;
case FilterType::GaussianBlur2x:
applyGaussianBlur(blur1, internal1);
applyGaussianBlur(blur1, internal1);
blur1.draw();
break;
case FilterType::DownsampleHalfBlur:
Shader::Downsample(blur1, blur2);
applyGaussianBlur(blur2, internal2);
blur2.scaled(2.0).draw();
break;
case FilterType::DownsampleQuarterBlur:
Shader::Downsample(blur1, blur4);
applyGaussianBlur(blur4, internal4);
blur4.scaled(4.0).draw();
break;
}
// テクスチャのクリア
blur1.clear(ColorF{ 0.0 });
blur2.clear(ColorF{ 0.0 });
blur4.clear(ColorF{ 0.0 });
}
};
音量
class GlobalVolumeManager
{
private:
double globalVolume = 0.5;
std::function<void()> updateFunction = std::bind(&GlobalVolumeManager::defaultUpdateFunction, this);
GlobalVolumeManager()
{
GlobalAudio::SetVolume(globalVolume);
}
void defaultUpdateFunction()
{
if (SimpleGUI::Slider(U"Global Vol", globalVolume, Vec2{ 20, 20 }, 120, 200))
{
// グローバルオーディオの音量を変更
GlobalAudio::SetVolume(globalVolume);
}
}
public:
static GlobalVolumeManager& getInstance()
{
static GlobalVolumeManager instance;
return instance;
}
void update()
{
updateFunction();
}
void setFunction(const std::function<void()>& _updateFunction)
{
updateFunction = _updateFunction;
}
void setGlobalVolume(double globalVolume)
{
GlobalAudio::SetVolume(globalVolume);
}
};
キーコンフィグ
class KeyConfigManager
{
private:
HashTable<String, InputGroup> inputTable;
KeyConfigManager()
{
}
public:
static KeyConfigManager& getInstance()
{
static KeyConfigManager instance;
return instance;
}
void setKey(const String& keyName, const InputGroup& input)
{
inputTable[keyName] = input;
}
InputGroup& getKey(const String& keyName)
{
return inputTable[keyName];
}
bool getAnyKey() const
{
return Keyboard::GetAllInputs().size() > 0 || Mouse::GetAllInputs().size() > 0;
}
};
ウィンドウのサイズ
class WindowManager
{
private:
WindowManager()
{
}
void defaultUpdateFunction()
{
if (Window::GetState().fullscreen)
{
if (SimpleGUI::Button(U"Window mode", Vec2{ 400, 20 }))
{
// ウィンドウモードにする
Window::SetFullscreen(false);
}
}
else
{
if (SimpleGUI::Button(U"Fullscreen mode", Vec2{ 400, 20 }))
{
// フルスクリーンモードにする
Window::SetFullscreen(true);
}
}
}
std::function<void()> m_updateFunction = std::bind(&WindowManager::defaultUpdateFunction, this);
public:
static WindowManager& getInstance()
{
static WindowManager instance;
return instance;
}
void init(const String& windowTitle, const Size& windowSize = { 1280,720 })
{
Window::SetTitle(windowTitle);
Window::Resize(windowSize);
Window::SetStyle(WindowStyle::Sizable);
Scene::SetResizeMode(ResizeMode::Keep);
Scene::SetBackground(ColorF(0.0));
}
void update()
{
m_updateFunction();
}
void setUpdateFunction(const std::function<void()>& _updateFunction)
{
m_updateFunction = _updateFunction;
}
};
スクリーンキャプチャ
class ScreenCaptureManager
{
private:
ScreenCaptureManager()
{
}
void defaultUpdateFunction()
{
if (SimpleGUI::Button(U"Capture", Vec2{ Scene::Size().x - 150, 20 }))
{
capture();
}
}
std::function<void()> m_updateFunction = std::bind(&ScreenCaptureManager::defaultUpdateFunction, this);
Texture m_tex;
Stopwatch m_sw;
String GenerateUniqueName()
{
// 現在の日時を取得
const DateTime now = DateTime::Now();
// 日時から文字列を生成
String uniqueName = Format(now.year, now.month, now.day,
now.hour, now.minute, now.second, now.milliseconds);
return uniqueName;
}
public:
static ScreenCaptureManager& getInstance()
{
static ScreenCaptureManager instance;
return instance;
}
void capture()
{
ScreenCapture::RequestCurrentFrame();
}
void setUpdateFunction(const std::function<void()>& updateFunction)
{
m_updateFunction = updateFunction;
}
void update()
{
m_updateFunction();
if (ScreenCapture::HasNewFrame())
{
Image image = ScreenCapture::GetFrame();
m_tex = Texture(image);
image.savePNG(U"photo/" + GenerateUniqueName() + U".png");
m_sw.restart();
}
if (m_sw.isRunning() && m_sw.msF() < 2000)
{
const double ratio = Math::Max(0.0, (1.0 - m_sw.msF() * 0.002));
const double downRatio = Math::Max(0.0, (m_sw.msF() * 0.001 - 1.5) * 2.0);
const Vec2 centerPos = Scene::CenterF() + Vec2::Down(2000) * downRatio;
const auto rect = Scene::Rect().scaled(0.9 + 0.1 * ratio).setCenter(centerPos);
rect.drawShadow(Vec2(10, 10), 10.0);
rect(m_tex).draw();
Scene::Rect().draw(ColorF(1.0, ratio));
}
}
};
エフェクト
class EffectManager
{
private:
Effect effect;
EffectManager()
{
}
public:
static EffectManager& getInstance()
{
static EffectManager instance;
return instance;
}
void update()
{
effect.update();
}
Effect& getEffect()
{
return effect;
}
};
シリアル通信
class SerialManager
{
private:
Serial m_serial;
Array<SerialPortInfo> m_infos;
size_t m_selectedIndex = -1; // 選択されていない状態を表す
// コンストラクタはプライベート
SerialManager()
{
updateInformation();
}
public:
// シングルトンインスタンスの取得
static SerialManager& getInstance()
{
static SerialManager instance;
return instance;
}
void updateInformation()
{
m_infos = System::EnumerateSerialPorts();
if (!m_infos.isEmpty())
{
m_selectedIndex = 0; // デフォルトで最初のポートを選択
}
}
// シリアルポートを開く
bool open(size_t index)
{
if (index >= m_infos.size())
{
return false;
}
m_selectedIndex = index;
return m_serial.open(m_infos[index].port, 115200);
}
// シリアルポートを閉じる
void close()
{
if (m_serial.isOpen())
{
m_serial.close();
}
}
// データの書き込み
bool write(char data)
{
if (m_serial.isOpen())
{
return m_serial.write(&data, sizeof(data));
}
return false;
}
// データの読み取り
bool read(int16& data)
{
if (m_serial.isOpen() && m_serial.available() >= sizeof(data))
{
return m_serial.read(&data, sizeof(data)) == sizeof(data);
}
return false;
}
// シリアルポート情報の取得
const Array<SerialPortInfo>& getSerialPortInfo() const
{
return m_infos;
}
// 選択されたインデックスの取得
size_t getSelectedIndex() const
{
return m_selectedIndex;
}
// シリアルポートが開いているか確認
bool isOpen() const
{
return m_serial.isOpen();
}
// オプションの取得
Array<String> getOptions() const
{
Array<String> options;
for (const auto& info : m_infos)
{
options << U"{} ({})"_fmt(info.port, info.description);
}
options << U"none";
return options;
}
};