Skip to content

48. 2D Render State

Learn how to customize 2D rendering settings (render state) to achieve various screen effects.

48.1 Overview of 2D Render State

  • In 2D rendering, you can modify the following settings:

48.1.1 Color Multiplication

  • When drawing shapes or textures, RGBA components are multiplied by the original colors during rendering
  • How to set this using ScopedColorMul2D is explained in 48.3

48.1.2 Color Addition

  • When drawing shapes or textures, RGBA components are added to or subtracted from the original colors during rendering
  • How to set this using ScopedColorAdd2D is explained in 48.4

48.1.3 Blend State

  • Sets how the destination pixel colors are combined with the colors being drawn
  • How to set this using BlendState and apply it with ScopedRenderStates2D is explained in 48.7

48.1.4 Sampler State

  • Sets the interpolation method when textures are scaled up or down for rendering
  • Sets how to handle UV coordinates that exceed the 0.0-1.0 range
  • How to set this using SamplerState and apply it with ScopedRenderStates2D is explained in 48.8 and 48.9

48.1.5 Rasterizer State

  • Sets wireframe display mode
  • Sets scissor rectangle
  • Sets whether to ignore (cull) clockwise or counter-clockwise triangles
  • How to set this using RasterizerState and apply it with ScopedRenderStates2D is explained in 48.10 to 48.12

48.1.6 Viewport

  • Changes the drawing area
  • How to set this using ScopedViewport2D is explained in 48.13

48.2 How Scoped~ Works

  • In this chapter, classes with names beginning with Scoped~ will appear:
Code Description
ScopedColorMul2D Multiplies colors during drawing
ScopedColorAdd2D Adds colors during drawing
ScopedRenderStates2D Changes render state during 2D drawing
ScopedViewport2D Changes the drawing area
  • In source code, these classes may appear to have no effect because you create objects of these classes without using the variables
  • In reality, the constructor sets the passed settings to the engine, and when the object is destroyed (when the scope ends), the destructor restores the previous settings
  • Let's confirm how ScopedRenderStates2D works with the following sample code
    • The first circle is drawn in wireframe display mode, and the second circle is drawn in normal drawing mode

# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	while (System::Update())
	{
		{
			// Change render state (also save the previous state)
			const ScopedRenderStates2D rasterizer{ RasterizerState::WireframeCullNone };

			Circle{ 200, 300, 150 }.draw(ColorF{ 0.1 });

		} // Here the rasterizer destructor is called and restores the render state to the previous state

		Circle{ 600, 300, 150 }.draw(ColorF{ 0.1 });
	}
}

48.3 Color Multiplication for Drawing

  • To multiply RGBA components when drawing images or shapes, set the values you want to multiply in the constructor of a ScopedColorMul2D object
  • While that object is valid, the drawing RGBA values are multiplied
  • The default value is ColorF{ 1.0, 1.0, 1.0, 1.0 }
  • You can also set the multiplication color by passing a color to Texture's .draw() (Tutorial 31.12). ScopedColorMul2D performs this setting collectively

# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	const Texture texture1{ U"example/windmill.png" };
	const Texture texture2{ U"example/siv3d-kun.png" };
	ColorF color{ 1.0, 1.0, 1.0, 1.0 };

	while (System::Update())
	{
		{
			// Set to multiply colors during drawing
			const ScopedColorMul2D colorMul{ color };

			texture1.draw(40, 40);
			texture2.draw(400, 100);
		}

		SimpleGUI::Slider(U"R", color.r, Vec2{ 620, 40 }, 40);
		SimpleGUI::Slider(U"G", color.g, Vec2{ 620, 80 }, 40);
		SimpleGUI::Slider(U"B", color.b, Vec2{ 620, 120 }, 40);
		SimpleGUI::Slider(U"A", color.a, Vec2{ 620, 160 }, 40);
	}
}

48.4 Color Addition for Drawing

  • To add RGBA components when drawing images or shapes, set the values you want to add in the constructor of a ScopedColorAdd2D object
  • While that object is valid, RGBA values are added to the drawing
  • The default value is ColorF{ 0.0, 0.0, 0.0, 0.0 }
  • Subtraction is also possible by setting negative values
  • This is applied after the color multiplication from 48.3

# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	const Texture texture1{ U"example/windmill.png" };
	const Texture texture2{ U"example/siv3d-kun.png" };
	ColorF color{ 0.0, 0.0, 0.0, 0.0 };

	while (System::Update())
	{
		{
			// Set to add colors during drawing
			const ScopedColorAdd2D colorAdd{ color };

			texture1.draw(40, 40);
			texture2.draw(400, 100);
		}

		SimpleGUI::Slider(U"R", color.r, Vec2{ 620, 40 }, 40);
		SimpleGUI::Slider(U"G", color.g, Vec2{ 620, 80 }, 40);
		SimpleGUI::Slider(U"B", color.b, Vec2{ 620, 120 }, 40);
	}
}

48.5 Solid Color Texture Drawing

  • By adding color using ScopedColorAdd2D to a texture drawn black with .draw(ColorF{ 0.0 }), you can draw it in a solid color

# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
	const Texture texture{ U"example/siv3d-kun.png" };

	while (System::Update())
	{
		// Fill with black
		{
			texture.draw(0, 100, ColorF{ 0.0 });
		}

		// Fill with green
		{
			const ScopedColorAdd2D color{ 0.0, 0.6, 0.2 };
			texture.draw(250, 100, ColorF{ 0.0 });
		}

		// Fill with white
		{
			const ScopedColorAdd2D color{ 1.0, 1.0, 1.0 };
			texture.draw(500, 100, ColorF{ 0.0 });
		}
	}
}

48.6 Transparency Inversion

  • By setting transparent parts to alpha value 0.0 and non-transparent parts to alpha value -1.0 with .draw(ColorF{ 0.0, -1.0 }), then adding alpha value 1.0 using ScopedColorAdd2D, you can invert the transparent and non-transparent parts

# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
	const Texture emoji{ U"🐈"_emoji };
	const Texture texture{ U"example/siv3d-kun.png" };

	while (System::Update())
	{
		{
			const ScopedColorAdd2D color{ 0.0, 0.6, 0.2, 1.0 };
			emoji.drawAt(200, 300, ColorF{ 0.0, -1.0 });
			texture.drawAt(500, 300, ColorF{ 0.0, -1.0 });
		}
	}
}

48.7 Additive Blending

  • When you pass BlendState::Additive to the constructor of a ScopedRenderStates2D object, shapes and images are drawn with additive blending while that object is valid
  • In additive blending, RGB components are added to the background color during drawing, so overlapping areas become brighter

# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.0 });

	Array<Vec2> points;

	for (int32 i = 0; i < 400; ++i)
	{
		points << RandomVec2(Scene::Rect());
	}

	// Whether to enable additive blending
	bool additiveBlend = true;

	while (System::Update())
	{
		if (additiveBlend)
		{
			// Additive blending enabled
			const ScopedRenderStates2D blend{ BlendState::Additive };

			for (const auto& point : points)
			{
				Circle{ point, 20 }.draw(HSV{ (point.y * 100 + point.x * 100), 0.5 });
			}
		}
		else
		{
			// Normal blending
			for (const auto& point : points)
			{
				Circle{ point, 20 }.draw(HSV{ (point.y * 100 + point.x * 100), 0.5 });
			}
		}

		SimpleGUI::CheckBox(additiveBlend, U"AdditiveBlend", Vec2{ 40, 40 });
	}
}

48.8 Texture Scaling Filter

  • There are two interpolation methods for scaling textures:
Setting Name Description
Linear Bilinear interpolation (default for 2D drawing)
Nearest Nearest neighbor interpolation
  • By default, colors are interpolated using bilinear interpolation
  • When you want to scale up textures while maintaining the pixel feel, use Nearest
    • Set the sampler state SamplerState::ClampNearest in ScopedRenderStates2D

# include <Siv3D.hpp>

void Main()
{
	const Texture texture{ U"🐈"_emoji };
	bool bilinear = true;
	double scale = 1.0;

	while (System::Update())
	{
		if (bilinear)
		{
			// Bilinear interpolation (default)
			texture.scaled(scale).drawAt(400, 300);
		}
		else
		{
			// Nearest neighbor interpolation
			const ScopedRenderStates2D sampler{ SamplerState::ClampNearest };
			texture.scaled(scale).drawAt(400, 300);
		}

		SimpleGUI::Slider(scale, 0.5, 12.0, Vec2{ 40, 40 }, 200);
		SimpleGUI::CheckBox(bilinear, U"Bilinear", Vec2{ 40, 80 });
	}
}

48.9 Texture Tiling

  • You can customize how UV coordinates are handled when they exceed the 0.0-1.0 range during texture drawing
Setting Name Description
Clamp Draw the edge colors of the texture as is (default for 2D drawing)
Repeat Draw so that the edge colors of the texture continue from the opposite edge
Mirror Draw so that the edge colors of the texture continue mirrored from the opposite edge
  • The default is Clamp, combined with the default texture filtering value Linear as SamplerState::ClampLinear
  • Also refer to Tutorial 31.18 for texture repeat drawing

#include <Siv3D.hpp>

void Draw(const Texture& tree, const Texture& windmill)
{
	tree.mapped(1280, 360).draw();
	windmill.mapped(1280, 360).draw(0, 360);
}

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

	const Texture tree{ U"🌲"_emoji };
	const Texture windmill{ U"example/windmill.png" };

	size_t option = 0;

	while (System::Update())
	{
		if (option == 0)
		{
			// Set to clamp when UV coordinates exceed the 0.0-1.0 range
			const ScopedRenderStates2D sampler{ SamplerState::ClampLinear };
			Draw(tree, windmill);
		}
		else if (option == 1)
		{
			// Set to repeat mapping when UV coordinates exceed the 0.0-1.0 range
			const ScopedRenderStates2D sampler{ SamplerState::RepeatLinear };
			Draw(tree, windmill);
		}
		else if (option == 2)
		{
			// Set to mirror when UV coordinates exceed the 0.0-1.0 range
			const ScopedRenderStates2D sampler{ SamplerState::MirrorLinear };
			Draw(tree, windmill);
		}

		SimpleGUI::RadioButtons(option, { U"Clamp", U"Repeat", U"Mirror" }, Vec2{ 40, 40 });
	}
}

48.10 Wireframe Display

  • There is a mode that draws only the wireframes of triangles that make up shapes and images
Setting Name Description
Wireframe Wireframe display
Solid Normal drawing mode (default for 2D drawing)
  • When you pass RasterizerState::WireframeCullNone to the constructor of a ScopedRenderStates2D object, wireframe display mode is enabled while that object is valid
  • Wireframe display is not available in the Web version

# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	const Texture texture{ U"example/windmill.png" };

	while (System::Update())
	{
		{
			// Set to wireframe display mode
			const ScopedRenderStates2D rasterizer{ RasterizerState::WireframeCullNone };

			texture.draw(40, 40);

			Circle{ 600, 400, 150 }.draw(ColorF{ 0.1 });

			Shape2D::Star(100, Vec2{ 160, 400 }).draw(Palette::Yellow);
		}
	}
}

48.11 Scissor Rectangle

  • Setting a scissor rectangle allows you to prevent drawing outside a rectangular area
  • Register the scissor rectangle area with Graphics2D::SetScissorRect(), and apply a RasterizerState with .scissorEnable set to true using ScopedRenderStates2D to enable the scissor rectangle

#include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	const Texture texture1{ U"example/windmill.png" };
	const Texture texture2{ U"example/siv3d-kun.png" };

	// Register the scissor rectangle range
	Graphics2D::SetScissorRect(Rect{ 100, 100, 300, 200 });

	while (System::Update())
	{
		{
			RasterizerState rs = RasterizerState::Default2D;
			rs.scissorEnable = true;

			// Enable scissor rectangle
			const ScopedRenderStates2D rasterizer{ rs };

			texture1.draw(40, 40);
			texture2.draw(160, 100);
		}
	}
}

48.12 Culling

  • You can set whether to ignore (cull) clockwise (front-facing) triangles and counter-clockwise (back-facing) triangles
Setting Name Description
CullNone No culling (default for 2D drawing)
CullFront Cull clockwise (front-facing) triangles
CullBack Cull counter-clockwise (back-facing) triangles
  • When you pass RasterizerState::SolidCullBack to the constructor of a ScopedRenderStates2D object, counter-clockwise triangles are culled while that object is valid
  • Normally, back-facing triangles do not occur unless you explicitly specify counter-clockwise vertices

# include <Siv3D.hpp>

void Draw()
{
	// Circle (all triangles are front-facing)
	Circle{ 200, 350, 150 }.draw(Palette::Seagreen);

	// Clockwise (front-facing) triangle
	Triangle{ 500, 50, 700, 250, 500, 250 }.draw(Palette::Seagreen);

	// Counter-clockwise (back-facing) triangle
	Triangle{ 500, 300, 500, 500, 700, 500, }.draw(Palette::Seagreen);
}

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	size_t option = 0;

	while (System::Update())
	{
		if (option == 0)
		{
			// No culling
			const ScopedRenderStates2D rasterizer{ RasterizerState::SolidCullNone };
			Draw();
		}
		else if (option == 1)
		{
			// Cull clockwise triangles
			const ScopedRenderStates2D rasterizer{ RasterizerState::SolidCullFront };
			Draw();

		}
		else if (option == 2)
		{
			// Cull counter-clockwise triangles
			const ScopedRenderStates2D rasterizer{ RasterizerState::SolidCullBack };
			Draw();
		}

		SimpleGUI::RadioButtons(option, { U"CullNone", U"CullFront", U"CullBack" }, Vec2{ 40, 40 });
	}
}

48.13 Viewport

  • Creating a ScopedViewport2D object allows you to create a virtual scene within the scene and define a new drawing area
  • During drawing, the top-left of the viewport rectangle becomes the (0, 0) drawing coordinate, and anything that extends outside the rectangle is not drawn
  • Viewport only affects drawing coordinates. If you want to move the mouse cursor coordinates to match the viewport, combine it with Transformer2D which is learned in Tutorial 49

# include <Siv3D.hpp>

void Main()
{
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	const Texture cat{ U"🐈"_emoji };

	const Rect viewportRect{ 400, 300, 300, 200 };

	while (System::Update())
	{
		{
			// Apply viewport
			const ScopedViewport2D viewport{ viewportRect };

			Circle{ 200, 150, 200 }.draw();

			cat.drawAt(40, 40);
		}

		viewportRect.drawFrame(0, 2, Palette::Seagreen);
	}
}