コンテンツにスキップ

3. 奥行き型 UI の作成

3.1 通常の Quad の問題

  • 長方形でない Quad にテクスチャを貼り付けるとゆがんでしまいます

# include <Siv3D.hpp>

// チェッカーパターンの Image を作る
Image MakeCheckerPattern()
{
	Image image{ 1280, 720 , Palette::White };
	for (auto p : step(image.size() / Size{ 40, 40 }))
	{
		if (IsEven(p.x + p.y))
		{
			Rect{ p * 40, 40 }.overwrite(image, Color{ 40 });
		}
	}
	return image;
}

void Main()
{
	Window::Resize(1280, 720);

	Scene::SetBackground(ColorF{ 0.8, 0.9, 1.0 });

	const Texture checker{ MakeCheckerPattern(), TextureDesc::Mipped };

	constexpr double circleR = 12.0;

	Quad quad{ Vec2{100, 300}, Vec2{500, 300}, Vec2{500, 600}, Vec2{100, 600} };

	Optional<size_t> grabbedIndex;

	while (System::Update())
	{
		quad(checker).draw();

		if (grabbedIndex)
		{
			if (not MouseL.pressed())
			{
				grabbedIndex.reset();
			}
			else
			{
				quad.p(*grabbedIndex).moveBy(Cursor::DeltaF());
			}
		}
		else
		{
			for (auto i : step(4))
			{
				const Circle circle = quad.p(i).asCircle(circleR);

				if (circle.mouseOver())
				{
					Cursor::RequestStyle(CursorStyle::Hand);
				}

				if (circle.leftClicked())
				{
					grabbedIndex = i;
					break;
				}
			}
		}

		for (auto i : step(4))
		{
			quad.p(i).asCircle(circleR).draw(ColorF{ 1.0, 0.3, 0.3, 0.5 });
		}
	}
}
  • ホモグラフィ変換シェーダを使うことで解決します

# include <Siv3D.hpp>

struct Homography
{
	Float4 m1;
	Float4 m2;
	Float4 m3;
};

// チェッカーパターンの Image を作る
Image MakeCheckerPattern()
{
	Image image{ 1280, 720 , Palette::White };
	for (auto p : step(image.size() / Size{ 40, 40 }))
	{
		if (IsEven(p.x + p.y))
		{
			Rect{ p * 40, 40 }.overwrite(image, Color{ 40 });
		}
	}
	return image;
}

void Main()
{
	Window::Resize(1280, 720);
	Scene::SetBackground(ColorF{ 0.8, 0.9, 1.0 });

	const Texture texture{ U"example/bay.jpg", TextureDesc::Mipped };
	const Texture checker{ MakeCheckerPattern(), TextureDesc::Mipped };

	constexpr double circleR = 12.0;
	const VertexShader vs = HLSL{ U"example/shader/hlsl/homography.hlsl", U"VS" }
		| GLSL{ U"example/shader/glsl/homography.vert", {{ U"VSConstants2D", 0 }, { U"VSHomography", 1} } };
	const PixelShader ps = HLSL{ U"example/shader/hlsl/homography.hlsl", U"PS" }
		| GLSL{ U"example/shader/glsl/homography.frag", {{ U"PSConstants2D", 0 }, { U"PSHomography", 1} } };

	if ((not vs) || (not ps))
	{
		throw Error{ U"Failed to load shader files" };
	}

	ConstantBuffer<Homography> vsHomography;
	ConstantBuffer<Homography> psHomography;

	Quad quad{ Vec2{100, 300}, Vec2{500, 300}, Vec2{500, 600}, Vec2{100, 600} };
	Optional<size_t> grabbedIndex;

	bool homography = true;

	while (System::Update())
	{
		SimpleGUI::CheckBox(homography, U"Homography", Vec2{ 40, 40 });

		if (homography)
		{
			const ScopedCustomShader2D shader{ vs, ps };
			const ScopedRenderStates2D sampler{ SamplerState::ClampAniso };

			{
				const Mat3x3 mat = Mat3x3::Homography(quad.movedBy(580, 0));
				vsHomography = { Float4{ mat._11_12_13, 0 }, Float4{ mat._21_22_23, 0 }, Float4{ mat._31_32_33, 0 } };
				Graphics2D::SetVSConstantBuffer(1, vsHomography);

				const Mat3x3 inv = mat.inverse();
				psHomography = { Float4{ inv._11_12_13, 0 }, Float4{ inv._21_22_23, 0 }, Float4{ inv._31_32_33, 0 } };
				Graphics2D::SetPSConstantBuffer(1, psHomography);

				// 1x1 の Rect に貼り付けて描くと、適切にホモグラフィ変換される
				Rect{ 1 }(checker).draw();
			}

			{
				const Mat3x3 mat = Mat3x3::Homography(quad);
				vsHomography = { Float4{ mat._11_12_13, 0 }, Float4{ mat._21_22_23, 0 }, Float4{ mat._31_32_33, 0 } };
				Graphics2D::SetVSConstantBuffer(1, vsHomography);

				const Mat3x3 inv = mat.inverse();
				psHomography = { Float4{ inv._11_12_13, 0 }, Float4{ inv._21_22_23, 0 }, Float4{ inv._31_32_33, 0 } };
				Graphics2D::SetPSConstantBuffer(1, psHomography);

				// 1x1 の Rect に貼り付けて描くと、適切にホモグラフィ変換される
				Rect{ 1 }(texture).draw();
			}
		}
		else
		{
			quad.movedBy(580, 0)(checker).draw();
			quad(texture).draw();
		}

		if (grabbedIndex)
		{
			if (not MouseL.pressed())
			{
				grabbedIndex.reset();
			}
			else
			{
				quad.p(*grabbedIndex).moveBy(Cursor::DeltaF());
			}
		}
		else
		{
			for (auto i : step(4))
			{
				const Circle circle = quad.p(i).asCircle(circleR);

				if (circle.mouseOver())
				{
					Cursor::RequestStyle(CursorStyle::Hand);
				}

				if (circle.leftClicked())
				{
					grabbedIndex = i;
					break;
				}
			}
		}

		for (auto i : step(4))
		{
			quad.p(i).asCircle(circleR).draw(ColorF{ 1.0, 0.3, 0.3, 0.5 });
		}
	}
}

3.2 レンダーテクスチャの使用

  • レンダーテクスチャに描画した UI をホモグラフィ変換によって射影することで、3D の奥行き感のある UI を実現できます

# include <Siv3D.hpp>

// ホモグラフィ変換シェーダのパラメータ
struct Homography
{
	Float4 m1;
	Float4 m2;
	Float4 m3;
};

void Main()
{
	Window::Resize(1000, 600);
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// ホモグラフィ変換用のシェーダ
	const VertexShader vs = HLSL{ U"example/shader/hlsl/homography.hlsl", U"VS" }
		| GLSL{ U"example/shader/glsl/homography.vert", {{ U"VSConstants2D", 0 }, { U"VSHomography", 1} } };
	const PixelShader ps = HLSL{ U"example/shader/hlsl/homography.hlsl", U"PS" }
		| GLSL{ U"example/shader/glsl/homography.frag", {{ U"PSConstants2D", 0 }, { U"PSHomography", 1} } };

	if ((not vs) || (not ps))
	{
		throw Error{ U"Failed to load shader files" };
	}

	// ホモグラフィ変換シェーダの定数バッファ(パラメータ)
	ConstantBuffer<Homography> vsHomography;
	ConstantBuffer<Homography> psHomography;

	const Font font{ FontMethod::MSDF, 48, Typeface::Bold };
	const ColorF PrimaryColor{ 0.98, 0.96, 0.94 };

	const Rect BaseRect{ 0, 0, 600, 600 };

	const Rect Button1{ 40, 40, 560, 200 };
	const Rect Button2{ 100, 260, 240, 100 };
	const Rect Button3{ 360, 260, 240, 100 };
	const Rect Button4{ 160, 380, 440, 140 };

	// UI の描画先のレンダーテクスチャ
	MSRenderTexture renderTexture{ BaseRect.size };

	Quad quad{ Vec2{0, 0}, Vec2{600, 0}, Vec2{600, 600}, Vec2{0, 600} };
	Optional<size_t> grabbedIndex;

	while (System::Update())
	{
		// レンダーテクスチャに UI を描く
		{
			// renderTexture を ColorF{ 1.0, 0.0 } でクリアし,
			// renderTexture をレンダーターゲットにする
			const ScopedRenderTarget2D renderTarget{ renderTexture.clear(ColorF{ 1.0, 0.0 }) };

			// renderTexture のアルファ値がすべて 0 なので、最大のアルファ値を書き込むようなブレンドステートを適用する
			BlendState blend = BlendState::Default2D;
			blend.opAlpha = BlendOp::Max;
			blend.dstAlpha = Blend::DestAlpha;
			blend.srcAlpha = Blend::SrcAlpha;
			const ScopedRenderStates2D renderState{ blend };

			// UI を描画する
			{
				Button1.movedBy(12, 10).draw(ColorF{ 0.5, 0.4, 0.3 });
				Button1.draw(PrimaryColor);
				font(U"探索").draw(88, Arg::leftCenter(80, 140), ColorF{ 0.4, 0.3, 0.2 });

				Button2.movedBy(12, 10).draw(ColorF{ 0.5, 0.4, 0.3 });
				Button2.draw(PrimaryColor);
				font(U"任務").draw(44, Arg::leftCenter(120, 310), ColorF{ 0.4, 0.3, 0.2 });

				Button3.movedBy(12, 10).draw(ColorF{ 0.5, 0.4, 0.3 });
				Button3.draw(PrimaryColor);
				font(U"編成").draw(44, Arg::leftCenter(380, 310), ColorF{ 0.4, 0.3, 0.2 });

				Button4.draw(ColorF{ 0.2, 0.4, 0.6 });
				font(U"イベント").draw(33, Arg::leftCenter(180, 415));

				Rect{ 60, 540, 540, 60 }.draw(ColorF{ 0.0, 0.6 });
			}

			// MSRenderTexture の完成には
			// 2D 描画命令の発行 (Flush) + MSAA の解決 (Resolve) が必要
			Graphics2D::Flush();
			renderTexture.resolve();
		}

		// 奥行き型の UI を描く
		{
			// レンダーテクスチャをホモグラフィ変換で射影する
			{
				const ScopedCustomShader2D shader{ vs, ps };
				const ScopedRenderStates2D sampler{ SamplerState::ClampAniso };

				const Mat3x3 mat = Mat3x3::Homography(quad);
				vsHomography = { Float4{ mat._11_12_13, 0 }, Float4{ mat._21_22_23, 0 }, Float4{ mat._31_32_33, 0 } };
				Graphics2D::SetVSConstantBuffer(1, vsHomography);

				const Mat3x3 inv = mat.inverse();
				psHomography = { Float4{ inv._11_12_13, 0 }, Float4{ inv._21_22_23, 0 }, Float4{ inv._31_32_33, 0 } };
				Graphics2D::SetPSConstantBuffer(1, psHomography);

				// 1x1 の Rect に貼り付けて描くと、適切にホモグラフィ変換される
				Rect{ 1 }(renderTexture).draw();
			}

			// (参考)ホモグラフィ変換をしない場合(上をコメントアウトして、こちらを使う)
			//{
			//	quad(renderTexture).draw();
			//}
		}

		// 4 つの頂点の操作と表示
		{
			if (grabbedIndex)
			{
				if (not MouseL.pressed())
				{
					grabbedIndex.reset();
				}
				else
				{
					quad.p(*grabbedIndex).moveBy(Cursor::DeltaF());
				}
			}
			else
			{
				for (auto i : step(4))
				{
					const Circle circle = quad.p(i).asCircle(10);

					if (circle.mouseOver())
					{
						Cursor::RequestStyle(CursorStyle::Hand);
					}

					if (circle.leftClicked())
					{
						grabbedIndex = i;
						break;
					}
				}
			}

			for (auto i : step(4))
			{
				quad.p(i).asCircle(10).draw(ColorF{ 1.0, 0.3, 0.3, 0.5 });
			}
		}
	}
}

3.3 無関係なコードを削除

  • 3.2 のコードから、4 頂点の操作に関連するコードを除去しました

# include <Siv3D.hpp>

// ホモグラフィ変換シェーダのパラメータ
struct Homography
{
	Float4 m1;
	Float4 m2;
	Float4 m3;
};

void Main()
{
	Window::Resize(1000, 600);
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// ホモグラフィ変換用のシェーダ
	const VertexShader vs = HLSL{ U"example/shader/hlsl/homography.hlsl", U"VS" }
		| GLSL{ U"example/shader/glsl/homography.vert", {{ U"VSConstants2D", 0 }, { U"VSHomography", 1} } };
	const PixelShader ps = HLSL{ U"example/shader/hlsl/homography.hlsl", U"PS" }
		| GLSL{ U"example/shader/glsl/homography.frag", {{ U"PSConstants2D", 0 }, { U"PSHomography", 1} } };

	if ((not vs) || (not ps))
	{
		throw Error{ U"Failed to load shader files" };
	}

	// ホモグラフィ変換シェーダの定数バッファ(パラメータ)
	ConstantBuffer<Homography> vsHomography;
	ConstantBuffer<Homography> psHomography;

	const Font font{ FontMethod::MSDF, 48, Typeface::Bold };
	const ColorF PrimaryColor{ 0.98, 0.96, 0.94 };

	// 変換前の四角形
	const Rect BaseRect{ 0, 0, 600, 600 };
	// 変換後の四角形
	const Quad TargetQuad{ 500, 60, 1000, 0, 1000, 600, 480, 520 };

	const Rect Button1{ 40, 40, 560, 200 };
	const Rect Button2{ 100, 260, 240, 100 };
	const Rect Button3{ 360, 260, 240, 100 };
	const Rect Button4{ 160, 380, 440, 140 };

	// UI の描画先のレンダーテクスチャ
	MSRenderTexture renderTexture{ BaseRect.size };

	while (System::Update())
	{
		// レンダーテクスチャに UI を描く
		{
			// renderTexture を ColorF{ 1.0, 0.0 } でクリアし,
			// renderTexture をレンダーターゲットにする
			const ScopedRenderTarget2D renderTarget{ renderTexture.clear(ColorF{ 1.0, 0.0 }) };

			// renderTexture のアルファ値がすべて 0 なので、最大のアルファ値を書き込むようなブレンドステートを適用する
			BlendState blend = BlendState::Default2D;
			blend.opAlpha = BlendOp::Max;
			blend.dstAlpha = Blend::DestAlpha;
			blend.srcAlpha = Blend::SrcAlpha;
			const ScopedRenderStates2D renderState{ blend };

			// UI を描画する
			{
				Button1.movedBy(12, 10).draw(ColorF{ 0.5, 0.4, 0.3 });
				Button1.draw(PrimaryColor);
				font(U"探索").draw(88, Arg::leftCenter(80, 140), ColorF{ 0.4, 0.3, 0.2 });

				Button2.movedBy(12, 10).draw(ColorF{ 0.5, 0.4, 0.3 });
				Button2.draw(PrimaryColor);
				font(U"任務").draw(44, Arg::leftCenter(120, 310), ColorF{ 0.4, 0.3, 0.2 });

				Button3.movedBy(12, 10).draw(ColorF{ 0.5, 0.4, 0.3 });
				Button3.draw(PrimaryColor);
				font(U"編成").draw(44, Arg::leftCenter(380, 310), ColorF{ 0.4, 0.3, 0.2 });

				Button4.draw(ColorF{ 0.2, 0.4, 0.6 });
				font(U"イベント").draw(33, Arg::leftCenter(180, 415));

				Rect{ 60, 540, 540, 60 }.draw(ColorF{ 0.0, 0.6 });
			}

			// MSRenderTexture の完成には
			// 2D 描画命令の発行 (Flush) + MSAA の解決 (Resolve) が必要
			Graphics2D::Flush();
			renderTexture.resolve();
		}

		// 奥行き型の UI を描く
		{
			// レンダーテクスチャをホモグラフィ変換で射影する
			{
				const ScopedCustomShader2D shader{ vs, ps };
				const ScopedRenderStates2D sampler{ SamplerState::ClampAniso };

				const Mat3x3 mat = Mat3x3::Homography(TargetQuad);
				vsHomography = { Float4{ mat._11_12_13, 0 }, Float4{ mat._21_22_23, 0 }, Float4{ mat._31_32_33, 0 } };
				Graphics2D::SetVSConstantBuffer(1, vsHomography);

				const Mat3x3 inv = mat.inverse();
				psHomography = { Float4{ inv._11_12_13, 0 }, Float4{ inv._21_22_23, 0 }, Float4{ inv._31_32_33, 0 } };
				Graphics2D::SetPSConstantBuffer(1, psHomography);

				// 1x1 の Rect に貼り付けて描くと、適切にホモグラフィ変換される
				Rect{ 1 }(renderTexture).draw();
			}
		}
	}
}

3.4 射影後の四角形のあたり判定

  • ある Rect の射影後の Quad は次のように得ることができます

# include <Siv3D.hpp>

// ホモグラフィ変換シェーダのパラメータ
struct Homography
{
	Float4 m1;
	Float4 m2;
	Float4 m3;
};

void Main()
{
	Window::Resize(1000, 600);
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// ホモグラフィ変換用のシェーダ
	const VertexShader vs = HLSL{ U"example/shader/hlsl/homography.hlsl", U"VS" }
		| GLSL{ U"example/shader/glsl/homography.vert", {{ U"VSConstants2D", 0 }, { U"VSHomography", 1} } };
	const PixelShader ps = HLSL{ U"example/shader/hlsl/homography.hlsl", U"PS" }
		| GLSL{ U"example/shader/glsl/homography.frag", {{ U"PSConstants2D", 0 }, { U"PSHomography", 1} } };

	if ((not vs) || (not ps))
	{
		throw Error{ U"Failed to load shader files" };
	}

	// ホモグラフィ変換シェーダの定数バッファ(パラメータ)
	ConstantBuffer<Homography> vsHomography;
	ConstantBuffer<Homography> psHomography;

	const Font font{ FontMethod::MSDF, 48, Typeface::Bold };
	const ColorF PrimaryColor{ 0.98, 0.96, 0.94 };
	const ColorF HoverColor{ 1.0, 0.96, 0.8 };

	// 変換前の四角形
	const Rect BaseRect{ 0, 0, 600, 600 };
	// 変換後の四角形
	const Quad TargetQuad{ 500, 60, 1000, 0, 1000, 600, 480, 520 };
	// ホモグラフィ変換の射影行列を得る
	const Mat3x3 projection = Mat3x3::Homography(Rect{ 600 }.asQuad(), TargetQuad);

	const Rect Button1{ 40, 40, 560, 200 };
	const Rect Button2{ 100, 260, 240, 100 };
	const Rect Button3{ 360, 260, 240, 100 };
	const Rect Button4{ 160, 380, 440, 140 };

	// 各ボタンの射影後の四角形
	const Quad Button1Quad = projection.transformRect(Button1);
	const Quad Button2Quad = projection.transformRect(Button2);
	const Quad Button3Quad = projection.transformRect(Button3);
	const Quad Button4Quad = projection.transformRect(Button4);

	// UI の描画先のレンダーテクスチャ
	MSRenderTexture renderTexture{ BaseRect.size };

	while (System::Update())
	{
		// レンダーテクスチャに UI を描く
		{
			// renderTexture を ColorF{ 1.0, 0.0 } でクリアし,
			// renderTexture をレンダーターゲットにする
			const ScopedRenderTarget2D renderTarget{ renderTexture.clear(ColorF{ 1.0, 0.0 }) };

			// renderTexture のアルファ値がすべて 0 なので、最大のアルファ値を書き込むようなブレンドステートを適用する
			BlendState blend = BlendState::Default2D;
			blend.opAlpha = BlendOp::Max;
			blend.dstAlpha = Blend::DestAlpha;
			blend.srcAlpha = Blend::SrcAlpha;
			const ScopedRenderStates2D renderState{ blend };

			// UI を描画する
			{
				Button1.movedBy(12, 10).draw(ColorF{ 0.5, 0.4, 0.3 });
				Button1.draw(Button1Quad.mouseOver() ? HoverColor : PrimaryColor);
				font(U"探索").draw(88, Arg::leftCenter(80, 140), ColorF{ 0.4, 0.3, 0.2 });
				if (Button1Quad.mouseOver())
				{
					Cursor::RequestStyle(CursorStyle::Hand);
				}

				Button2.movedBy(12, 10).draw(ColorF{ 0.5, 0.4, 0.3 });
				Button2.draw(Button2Quad.mouseOver() ? HoverColor : PrimaryColor);
				font(U"任務").draw(44, Arg::leftCenter(120, 310), ColorF{ 0.4, 0.3, 0.2 });
				if (Button2Quad.mouseOver())
				{
					Cursor::RequestStyle(CursorStyle::Hand);
				}

				Button3.movedBy(12, 10).draw(ColorF{ 0.5, 0.4, 0.3 });
				Button3.draw(Button3Quad.mouseOver() ? HoverColor : PrimaryColor);
				font(U"編成").draw(44, Arg::leftCenter(380, 310), ColorF{ 0.4, 0.3, 0.2 });
				if (Button3Quad.mouseOver())
				{
					Cursor::RequestStyle(CursorStyle::Hand);
				}

				Button4.draw(ColorF{ 0.2, 0.4, 0.6 });
				font(U"イベント").draw(33, Arg::leftCenter(180, 415));
				if (Button4Quad.mouseOver())
				{
					Cursor::RequestStyle(CursorStyle::Hand);
				}

				Rect{ 60, 540, 540, 60 }.draw(ColorF{ 0.0, 0.6 });
			}

			// MSRenderTexture の完成には
			// 2D 描画命令の発行 (Flush) + MSAA の解決 (Resolve) が必要
			Graphics2D::Flush();
			renderTexture.resolve();
		}

		// 奥行き型の UI を描く
		{
			// レンダーテクスチャをホモグラフィ変換で射影する
			{
				const ScopedCustomShader2D shader{ vs, ps };
				const ScopedRenderStates2D sampler{ SamplerState::ClampAniso };

				const Mat3x3 mat = Mat3x3::Homography(TargetQuad);
				vsHomography = { Float4{ mat._11_12_13, 0 }, Float4{ mat._21_22_23, 0 }, Float4{ mat._31_32_33, 0 } };
				Graphics2D::SetVSConstantBuffer(1, vsHomography);

				const Mat3x3 inv = mat.inverse();
				psHomography = { Float4{ inv._11_12_13, 0 }, Float4{ inv._21_22_23, 0 }, Float4{ inv._31_32_33, 0 } };
				Graphics2D::SetPSConstantBuffer(1, psHomography);

				// 1x1 の Rect に貼り付けて描くと、適切にホモグラフィ変換される
				Rect{ 1 }(renderTexture).draw();
			}
		}
	}
}

3.5 完成

  • UI をにぎやかにします
  • 右端に向かって暗くなる効果を追加します

# include <Siv3D.hpp>

// ホモグラフィ変換シェーダのパラメータ
struct Homography
{
	Float4 m1;
	Float4 m2;
	Float4 m3;
};

void Main()
{
	Window::Resize(1000, 600);
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// ホモグラフィ変換用のシェーダ
	const VertexShader vs = HLSL{ U"example/shader/hlsl/homography.hlsl", U"VS" }
		| GLSL{ U"example/shader/glsl/homography.vert", {{ U"VSConstants2D", 0 }, { U"VSHomography", 1} } };
	const PixelShader ps = HLSL{ U"example/shader/hlsl/homography.hlsl", U"PS" }
		| GLSL{ U"example/shader/glsl/homography.frag", {{ U"PSConstants2D", 0 }, { U"PSHomography", 1} } };

	if ((not vs) || (not ps))
	{
		throw Error{ U"Failed to load shader files" };
	}

	// ホモグラフィ変換シェーダの定数バッファ(パラメータ)
	ConstantBuffer<Homography> vsHomography;
	ConstantBuffer<Homography> psHomography;

	const Font font{ FontMethod::MSDF, 48, Typeface::Bold };
	const Texture compassIcon{ 0xF018B_icon, 90 };
	const Texture swordIcon{ 0xF18BE_icon, 90 };
	const Texture plusIcon{ 0xF0417_icon, 42 };
	const Texture moneyEmoji{ U"💰"_emoji };
	const Texture gemEmoji{ U"💎"_emoji };
	const ColorF PrimaryColor{ 0.98, 0.96, 0.94 };
	const ColorF HoverColor{ 1.0, 0.96, 0.8 };

	// 変換前の四角形
	const Rect BaseRect{ 0, 0, 600, 600 };
	// 変換後の四角形
	const Quad TargetQuad{ 500, 60, 1000, 0, 1000, 600, 480, 520 };
	// ホモグラフィ変換の射影行列を得る
	const Mat3x3 projection = Mat3x3::Homography(Rect{ 600 }.asQuad(), TargetQuad);

	const Rect Button1{ 40, 40, 560, 200 };
	const Rect Button2{ 100, 260, 240, 100 };
	const Rect Button3{ 360, 260, 240, 100 };
	const Rect Button4{ 160, 380, 440, 140 };
	const Rect Button5{ Arg::center(230, 570), 40 };

	// 各ボタンの射影後の四角形
	const Quad Button1Quad = projection.transformRect(Button1);
	const Quad Button2Quad = projection.transformRect(Button2);
	const Quad Button3Quad = projection.transformRect(Button3);
	const Quad Button4Quad = projection.transformRect(Button4);
	const Quad Button5Quad = projection.transformRect(Button5);

	// UI の描画先のレンダーテクスチャ
	MSRenderTexture renderTexture{ BaseRect.size };

	while (System::Update())
	{
		// レンダーテクスチャに UI を描く
		{
			// renderTexture を ColorF{ 1.0, 0.0 } でクリアし,
			// renderTexture をレンダーターゲットにする
			const ScopedRenderTarget2D renderTarget{ renderTexture.clear(ColorF{ 1.0, 0.0 }) };

			// renderTexture のアルファ値がすべて 0 なので、最大のアルファ値を書き込むようなブレンドステートを適用する
			BlendState blend = BlendState::Default2D;
			blend.opAlpha = BlendOp::Max;
			blend.dstAlpha = Blend::DestAlpha;
			blend.srcAlpha = Blend::SrcAlpha;
			const ScopedRenderStates2D renderState{ blend };

			// UI を描画する
			{
				// 探索
				{
					Button1.movedBy(12, 10).draw(ColorF{ 0.5, 0.4, 0.3 });
					Button1.draw(Button1Quad.mouseOver() ? HoverColor : PrimaryColor);
					font(U"探索").draw(88, Arg::leftCenter(80, 140), ColorF{ 0.4, 0.3, 0.2 });
					if (Button1Quad.mouseOver())
					{
						Cursor::RequestStyle(CursorStyle::Hand);
					}
				}

				// 任務
				{
					Button2.movedBy(12, 10).draw(ColorF{ 0.5, 0.4, 0.3 });
					Button2.draw(Button2Quad.mouseOver() ? HoverColor : PrimaryColor);
					font(U"任務").draw(44, Arg::leftCenter(120, 310), ColorF{ 0.4, 0.3, 0.2 });
					compassIcon.drawAt(280, 310, ColorF{ 0.8 });
					if (Button2Quad.mouseOver())
					{
						Cursor::RequestStyle(CursorStyle::Hand);
					}
				}

				// 編成
				{
					Button3.movedBy(12, 10).draw(ColorF{ 0.5, 0.4, 0.3 });
					Button3.draw(Button3Quad.mouseOver() ? HoverColor : PrimaryColor);
					font(U"編成").draw(44, Arg::leftCenter(380, 310), ColorF{ 0.4, 0.3, 0.2 });
					swordIcon.drawAt(540, 310, ColorF{ 0.8 });
					if (Button3Quad.mouseOver())
					{
						Cursor::RequestStyle(CursorStyle::Hand);
					}
				}

				// イベント
				{
					Button4.draw(ColorF{ 0.2, 0.4, 0.6 });
					font(U"イベント").draw(33, Arg::leftCenter(180, 415));
					if (Button4Quad.mouseOver())
					{
						Cursor::RequestStyle(CursorStyle::Hand);
					}
				}

				// ジェムとお金
				{
					Rect{ 60, 540, 540, 60 }.draw(ColorF{ 0.0, 0.6 });
					gemEmoji.scaled(0.36).drawAt(120, 570);
					font(U"67").draw(TextStyle::Outline(0.0, 0.2, ColorF{ 0.1 }), 36, Arg::leftCenter(150, 570));

					Circle{ Button5.center(), 20 }.draw(ColorF{ 0.2, 0.8 });
					plusIcon.drawAt(Button5.center(), Button5Quad.mouseOver() ? HoverColor : PrimaryColor);
					if (Button5Quad.mouseOver())
					{
						Cursor::RequestStyle(CursorStyle::Hand);
					}

					moneyEmoji.scaled(0.36).drawAt(300, 570);
					font(ThousandsSeparate(12345)).draw(TextStyle::Outline(0.0, 0.2, ColorF{ 0.1 }), 36, Arg::leftCenter(330, 570));
				}
			}

			// MSRenderTexture の完成には
			// 2D 描画命令の発行 (Flush) + MSAA の解決 (Resolve) が必要
			Graphics2D::Flush();
			renderTexture.resolve();
		}

		// 奥行き型の UI を描く
		{
			// 右端に向かって影の効果
			Rect{ 460, 0, 540, 600 }.draw(Arg::left = ColorF{ 0.0, 0.0 }, Arg::right = ColorF{ 0.0, 0.2 });

			// レンダーテクスチャをホモグラフィ変換で射影する
			{
				const ScopedCustomShader2D shader{ vs, ps };
				const ScopedRenderStates2D sampler{ SamplerState::ClampAniso };

				const Mat3x3 mat = Mat3x3::Homography(TargetQuad);
				vsHomography = { Float4{ mat._11_12_13, 0 }, Float4{ mat._21_22_23, 0 }, Float4{ mat._31_32_33, 0 } };
				Graphics2D::SetVSConstantBuffer(1, vsHomography);

				const Mat3x3 inv = mat.inverse();
				psHomography = { Float4{ inv._11_12_13, 0 }, Float4{ inv._21_22_23, 0 }, Float4{ inv._31_32_33, 0 } };
				Graphics2D::SetPSConstantBuffer(1, psHomography);

				// 1x1 の Rect に貼り付けて描くと、適切にホモグラフィ変換される
				Rect{ 1 }(renderTexture).draw();
			}
		}
	}
}